Index: wp-includes/wp-db.php
===================================================================
--- wp-includes/wp-db.php	(revision 25671)
+++ wp-includes/wp-db.php	(working copy)
@@ -953,27 +953,80 @@
 	}
 
 	/**
-	 * Prepares a SQL query for safe execution. Uses sprintf()-like syntax.
+	 * Flattens an array, removing any nested arrays and bringing all their
+	 * non-array items up to the top level.
 	 *
+	 * This method processes the passed array depth-first.
+	 * It will flatten arrays inside arrays, no matter how deeply nested, limited
+	 * only by PHP's recursion limit.
+	 *
+	 * @access private
+	 * @param array $array The array to flatten.
+	 * @return array|mixed The flattened array if an array was passed, otherwise whatever was passed is returned unmodified.
+	 */
+	function _flatten_array( $array ) {
+		if ( !is_array( $array ) )
+			return $array;
+		$new_array = array();
+		foreach ( $array as $item ) {
+			if ( is_array( $item ) )
+				$new_array = array_merge( $new_array, $this->_flatten_array( $item ) );
+			else
+				$new_array[] = $item;
+		}
+		return $new_array;
+	}
+
+	/**
+	 * A callback function to replace repeated tokens via a regular expression.
+	 *
+	 * For example:
+	 * %#3s would be replaced with %s,%s,%s
+	 * %#1d would be replaced with %d
+	 * %#0f would be replaced with an empty string, or in other words, it would be removed.
+	 *
+	 * @access private
+	 * @param array $matches The matches array from preg_replace_callback
+	 * @return string The replacement string
+	 */
+	function _expand_repeated_params( $matches ) {
+		$replacement = '%' . $matches[2];
+		$replacements = array_fill( 0, $matches[1], $replacement );
+		return implode( ',', $replacements );
+	}
+
+	/**
+	 * Prepares an SQL query for safe execution. Uses sprintf()-like syntax.
+	 *
 	 * The following directives can be used in the query format string:
 	 *   %d (integer)
 	 *   %f (float)
 	 *   %s (string)
+	 * %#<num>d (num integers)
+	 * %#<num>f (num floats)
+	 * %#<num>s (num strings)
 	 *   %% (literal percentage sign - no argument needed)
+	 * Where num is an integer >= 0.
 	 *
-	 * All of %d, %f, and %s are to be left unquoted in the query string and they need an argument passed for them.
+	 * All of %d, %f, and %s are to be left unquoted in the query string, and they need an argument passed for them.
+	 * Repeated integers, floats, or strings must have as many arguments as their number specifies, and must also be left unquoted.
 	 * Literals (%) as parts of the query must be properly written as %%.
 	 *
 	 * This function only supports a small subset of the sprintf syntax; it only supports %d (integer), %f (float), and %s (string).
+	 * It also supports repeated forms of the above, which are not supported by sprintf.
 	 * Does not support sign, padding, alignment, width or precision specifiers.
 	 * Does not support argument numbering/swapping.
 	 *
 	 * May be called like {@link http://php.net/sprintf sprintf()} or like {@link http://php.net/vsprintf vsprintf()}.
+	 * Any arrays inside the arguments passed will be flattened, so you can pass
+	 * multiple arrays or even arrays containing arrays, and it will be the same
+	 * as just passing the integer, float or string values from those arrays.
 	 *
-	 * Both %d and %s should be left unquoted in the query string.
+	 * All integer, float and string directives should be left unquoted.
 	 *
 	 * <code>
-	 * wpdb::prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d", 'foo', 1337 )
+	 * wpdb::prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d", 'foo', 1337 );
+	 * wpdb::prepare( "SELECT * FROM `table` WHERE `column` in (%#3d) AND `field` = %s", array( 1, 2, 3 ), 'foo' );
 	 * wpdb::prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' );
 	 * </code>
 	 *
@@ -995,12 +1048,11 @@
 
 		$args = func_get_args();
 		array_shift( $args );
-		// If args were passed as an array (as in vsprintf), move them up
-		if ( isset( $args[0] ) && is_array($args[0]) )
-			$args = $args[0];
-		$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it
-		$query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting
-		$query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware
+		// Flatten the args passed to bring all items out of arrays.
+		$args = $this->_flatten_array( $args );
+		$query = preg_replace( '|([\'"])(%(?:#\d+)?s)\1|', '$2', $query ); // in case someone mistakenly already single or double quoted it
+		$query = preg_replace( '|(?<!%)%f|', '%F', $query ); // Force floats to be locale unaware
+		$query = preg_replace_callback( '|(?<!%)%#(\d+)([dfs])|', array($this, '_expand_repeated_params'), $query ); // Expand the repeated parameters.
 		$query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s
 		array_walk( $args, array( $this, 'escape_by_ref' ) );
 		return @vsprintf( $query, $args );
