<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[54734] branches/6.1/tests/phpunit/tests/db.php: Database: Revert [53575].</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/54734">54734</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/54734","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>SergeyBiryukov</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2022-10-31 20:43:56 +0000 (Mon, 31 Oct 2022)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Database: Revert <a href="https://core.trac.wordpress.org/changeset/53575">[53575]</a>.
When using `'%%%s%%'` pattern with `$wpdb->prepare()`, it works on 6.0.3 but does not on 6.1-RC. Why? The inserted value is wrapped in quotes on 6.1-RC5 whereas it is not on <= 6.0.3.
With 6.1 final release tomorrow, more time is needed to further investigate and test. Reverting this changeset to restore the previous behavior.
This commit also adds a dataset for testing the `'%%%s%%'` pattern.
Props SergeyBiryukov, hellofromTonya, bernhard-reiter, desrosj, davidbaumwald, jorbin.
Reviewed by hellofromTonya, SergeyBiryukov.
Merges <a href="https://core.trac.wordpress.org/changeset/54733">[54733]</a> to the 6.1 branch.
Fixes <a href="https://core.trac.wordpress.org/ticket/56933">#56933</a>.
See <a href="https://core.trac.wordpress.org/ticket/52506">#52506</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#branches61srcwpincludesclasswpdbphp">branches/6.1/src/wp-includes/class-wpdb.php</a></li>
<li><a href="#branches61testsphpunittestsdbphp">branches/6.1/tests/phpunit/tests/db.php</a></li>
</ul>
<h3>Property Changed</h3>
<ul>
<li><a href="#branches61">branches/6.1/</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<span class="cx" style="display: block; padding: 0 10px">Index: branches/6.1
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- branches/6.1 2022-10-31 20:38:20 UTC (rev 54733)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ branches/6.1 2022-10-31 20:43:56 UTC (rev 54734)
</ins><a id="branches61"></a>
<div class="propset"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Property changes: branches/6.1</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnmergeinfo"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: svn:mergeinfo</h4></div>
<span class="cx" style="display: block; padding: 0 10px"> /branches/5.0:43681-43682,43684-43688,43719-43720,43723,43726-43727,43729-43731,43734-43744,43747,43751-43754,43758,43760-43765,43767-43770,43772,43774-43781,43783,43785,43790-43806,43808-43821,43825,43828,43830-43834,43836-43843,43846-43863,43867-43889,43891-43894,43897-43905,43908-43909,43911-43929,43931-43942,43946-43947,43949-43956,43959-43964,43967-43969,43988,43994,44014,44017,44047,44183,44185,44187-44206,44208-44213,44231-44232,44235,44248,44284,44287-44288
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/5.5:49373-49379,49381
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/5.8:51889
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/trunk:54645-54646,54649,54652,54660,54662,54669,54674,54678,54686,54690,54693,54698,54706,54724,54729
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/trunk:54645-54646,54649,54652,54660,54662,54669,54674,54678,54686,54690,54693,54698,54706,54724,54729,54733
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="branches61srcwpincludesclasswpdbphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/6.1/src/wp-includes/class-wpdb.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/6.1/src/wp-includes/class-wpdb.php 2022-10-31 20:38:20 UTC (rev 54733)
+++ branches/6.1/src/wp-includes/class-wpdb.php 2022-10-31 20:43:56 UTC (rev 54734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -655,41 +655,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Backward compatibility, where wpdb::prepare() has not quoted formatted/argnum placeholders.
- *
- * Historically this could be used for table/field names, or for some string formatting, e.g.
- *
- * $wpdb->prepare( 'WHERE `%1s` = "%1s something %1s" OR %1$s = "%-10s"', 'field_1', 'a', 'b', 'c' );
- *
- * But it's risky, e.g. forgetting to add quotes, resulting in SQL Injection vulnerabilities:
- *
- * $wpdb->prepare( 'WHERE (id = %1s) OR (id = %2$s)', $_GET['id'], $_GET['id'] ); // ?id=id
- *
- * This feature is preserved while plugin authors update their code to use safer approaches:
- *
- * $wpdb->prepare( 'WHERE %1s = %s', $_GET['key'], $_GET['value'] );
- * $wpdb->prepare( 'WHERE %i = %s', $_GET['key'], $_GET['value'] );
- *
- * While changing to false will be fine for queries not using formatted/argnum placeholders,
- * any remaining cases are most likely going to result in SQL errors (good, in a way):
- *
- * $wpdb->prepare( 'WHERE %1s = "%-10s"', 'my_field', 'my_value' );
- * true = WHERE my_field = "my_value "
- * false = WHERE 'my_field' = "'my_value '"
- *
- * But there may be some queries that result in an SQL Injection vulnerability:
- *
- * $wpdb->prepare( 'WHERE id = %1s', $_GET['id'] ); // ?id=id
- *
- * So there may need to be a `_doing_it_wrong()` phase, after we know everyone can use
- * identifier placeholders (%i), but before this feature is disabled or removed.
- *
- * @since 6.1.0
- * @var bool
- */
- private $allow_unsafe_unquoted_parameters = true;
-
- /**
</del><span class="cx" style="display: block; padding: 0 10px"> * Whether to use mysqli over mysql. Default false.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1398,37 +1363,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Escapes an identifier for a MySQL database, e.g. table/field names.
- *
- * @since 6.1.0
- *
- * @param string $identifier Identifier to escape.
- * @return string Escaped identifier.
- */
- public function escape_identifier( $identifier ) {
- return '`' . $this->_escape_identifier_value( $identifier ) . '`';
- }
-
- /**
- * Escapes an identifier value without adding the surrounding quotes.
- *
- * - Permitted characters in quoted identifiers include the full Unicode
- * Basic Multilingual Plane (BMP), except U+0000.
- * - To quote the identifier itself, you need to double the character, e.g. `a``b`.
- *
- * @since 6.1.0
- * @access private
- *
- * @link https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
- *
- * @param string $identifier Identifier to escape.
- * @return string Escaped identifier.
- */
- private function _escape_identifier_value( $identifier ) {
- return str_replace( '`', '``', $identifier );
- }
-
- /**
</del><span class="cx" style="display: block; padding: 0 10px"> * Prepares a SQL query for safe execution.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * Uses sprintf()-like syntax. The following placeholders can be used in the query string:
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1436,7 +1370,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * - %d (integer)
</span><span class="cx" style="display: block; padding: 0 10px"> * - %f (float)
</span><span class="cx" style="display: block; padding: 0 10px"> * - %s (string)
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * - %i (identifier, e.g. table/field names)
</del><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * All placeholders MUST be left unquoted in the query string. A corresponding argument
</span><span class="cx" style="display: block; padding: 0 10px"> * MUST be passed for each placeholder.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1469,10 +1402,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
</span><span class="cx" style="display: block; padding: 0 10px"> * by updating the function signature. The second parameter was changed
</span><span class="cx" style="display: block; padding: 0 10px"> * from `$args` to `...$args`.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 6.1.0 Added `%i` for identifiers, e.g. table or field names.
- * Check support via `wpdb::has_cap( 'identifier_placeholders' )`.
- * This preserves compatibility with sprintf(), as the C version uses
- * `%d` and `$i` as a signed integer, whereas PHP only supports `%d`.
</del><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @link https://www.php.net/sprintf Description of syntax.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1504,6 +1433,28 @@
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // If args were passed as an array (as in vsprintf), move them up.
+ $passed_as_array = false;
+ if ( isset( $args[0] ) && is_array( $args[0] ) && 1 === count( $args ) ) {
+ $passed_as_array = true;
+ $args = $args[0];
+ }
+
+ foreach ( $args as $arg ) {
+ if ( ! is_scalar( $arg ) && ! is_null( $arg ) ) {
+ wp_load_translations_early();
+ _doing_it_wrong(
+ 'wpdb::prepare',
+ sprintf(
+ /* translators: %s: Value type. */
+ __( 'Unsupported value type (%s).' ),
+ gettype( $arg )
+ ),
+ '4.8.2'
+ );
+ }
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /*
</span><span class="cx" style="display: block; padding: 0 10px"> * Specify the formatting allowed in a placeholder. The following are allowed:
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1524,106 +1475,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> $query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes.
</span><span class="cx" style="display: block; padding: 0 10px"> $query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $query = preg_replace( '/(?<!%)%s/', "'%s'", $query ); // Quote the strings, avoiding escaped strings like %%s.
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // Escape any unescaped percents (i.e. anything unrecognised).
- $query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdfFi]))/", '%%\\1', $query );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $query = preg_replace( "/(?<!%)(%($allowed_format)?f)/", '%\\2F', $query ); // Force floats to be locale-unaware.
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // Extract placeholders from the query.
- $split_query = preg_split( "/(^|[^%]|(?:%%)+)(%(?:$allowed_format)?[sdfFi])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdF]))/", '%%\\1', $query ); // Escape any unescaped percents.
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $split_query_count = count( $split_query );
- /*
- * Split always returns with 1 value before the first placeholder (even with $query = "%s"),
- * then 3 additional values per placeholder.
- */
- $placeholder_count = ( ( $split_query_count - 1 ) / 3 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Count the number of valid placeholders in the query.
+ $placeholders = preg_match_all( "/(^|[^%]|(%%)+)%($allowed_format)?[sdF]/", $query, $matches );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // If args were passed as an array, as in vsprintf(), move them up.
- $passed_as_array = ( isset( $args[0] ) && is_array( $args[0] ) && 1 === count( $args ) );
- if ( $passed_as_array ) {
- $args = $args[0];
- }
-
- $new_query = '';
- $key = 2; // Keys 0 and 1 in $split_query contain values before the first placeholder.
- $arg_id = 0;
- $arg_identifiers = array();
- $arg_strings = array();
-
- while ( $key < $split_query_count ) {
- $placeholder = $split_query[ $key ];
-
- $format = substr( $placeholder, 1, -1 );
- $type = substr( $placeholder, -1 );
-
- if ( 'f' === $type ) { // Force floats to be locale-unaware.
- $type = 'F';
- $placeholder = '%' . $format . $type;
- }
-
- if ( 'i' === $type ) {
- $placeholder = '`%' . $format . 's`';
- // Using a simple strpos() due to previous checking (e.g. $allowed_format).
- $argnum_pos = strpos( $format, '$' );
-
- if ( false !== $argnum_pos ) {
- // sprintf() argnum starts at 1, $arg_id from 0.
- $arg_identifiers[] = ( intval( substr( $format, 0, $argnum_pos ) ) - 1 );
- } else {
- $arg_identifiers[] = $arg_id;
- }
- } elseif ( 'd' !== $type && 'F' !== $type ) {
- /*
- * i.e. ( 's' === $type ), where 'd' and 'F' keeps $placeholder unchanged,
- * and we ensure string escaping is used as a safe default (e.g. even if 'x').
- */
- $argnum_pos = strpos( $format, '$' );
-
- if ( false !== $argnum_pos ) {
- $arg_strings[] = ( intval( substr( $format, 0, $argnum_pos ) ) - 1 );
- }
-
- // Unquoted strings for backward compatibility (dangerous).
- if ( true !== $this->allow_unsafe_unquoted_parameters || '' === $format ) {
- $placeholder = "'%" . $format . "s'";
- }
- }
-
- // Glue (-2), any leading characters (-1), then the new $placeholder.
- $new_query .= $split_query[ $key - 2 ] . $split_query[ $key - 1 ] . $placeholder;
-
- $key += 3;
- $arg_id++;
- }
-
- // Replace $query; and add remaining $query characters, or index 0 if there were no placeholders.
- $query = $new_query . $split_query[ $key - 2 ];
-
- $dual_use = array_intersect( $arg_identifiers, $arg_strings );
-
- if ( count( $dual_use ) ) {
- wp_load_translations_early();
- _doing_it_wrong(
- 'wpdb::prepare',
- sprintf(
- /* translators: %s: A comma-separated list of arguments found to be a problem. */
- __( 'Arguments (%s) cannot be used for both String and Identifier escaping.' ),
- implode( ', ', $dual_use )
- ),
- '6.1.0'
- );
-
- return;
- }
-
</del><span class="cx" style="display: block; padding: 0 10px"> $args_count = count( $args );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( $args_count !== $placeholder_count ) {
- if ( 1 === $placeholder_count && $passed_as_array ) {
- /*
- * If the passed query only expected one argument,
- * but the wrong number of arguments was sent as an array, bail.
- */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $args_count !== $placeholders ) {
+ if ( 1 === $placeholders && $passed_as_array ) {
+ // If the passed query only expected one argument, but the wrong number of arguments were sent as an array, bail.
</ins><span class="cx" style="display: block; padding: 0 10px"> wp_load_translations_early();
</span><span class="cx" style="display: block; padding: 0 10px"> _doing_it_wrong(
</span><span class="cx" style="display: block; padding: 0 10px"> 'wpdb::prepare',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1644,7 +1509,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> sprintf(
</span><span class="cx" style="display: block; padding: 0 10px"> /* translators: 1: Number of placeholders, 2: Number of arguments passed. */
</span><span class="cx" style="display: block; padding: 0 10px"> __( 'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $placeholder_count,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $placeholders,
</ins><span class="cx" style="display: block; padding: 0 10px"> $args_count
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> '4.8.3'
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1654,18 +1519,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * If we don't have enough arguments to match the placeholders,
</span><span class="cx" style="display: block; padding: 0 10px"> * return an empty string to avoid a fatal error on PHP 8.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( $args_count < $placeholder_count ) {
- $max_numbered_placeholder = 0;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $args_count < $placeholders ) {
+ $max_numbered_placeholder = ! empty( $matches[3] ) ? max( array_map( 'intval', $matches[3] ) ) : 0;
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- for ( $i = 2, $l = $split_query_count; $i < $l; $i += 3 ) {
- // Assume a leading number is for a numbered placeholder, e.g. '%3$s'.
- $argnum = intval( substr( $split_query[ $i ], 1 ) );
-
- if ( $max_numbered_placeholder < $argnum ) {
- $max_numbered_placeholder = $argnum;
- }
- }
-
</del><span class="cx" style="display: block; padding: 0 10px"> if ( ! $max_numbered_placeholder || $args_count < $max_numbered_placeholder ) {
</span><span class="cx" style="display: block; padding: 0 10px"> return '';
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1673,36 +1529,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $args_escaped = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ array_walk( $args, array( $this, 'escape_by_ref' ) );
+ $query = vsprintf( $query, $args );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- foreach ( $args as $i => $value ) {
- if ( in_array( $i, $arg_identifiers, true ) ) {
- $args_escaped[] = $this->_escape_identifier_value( $value );
- } elseif ( is_int( $value ) || is_float( $value ) ) {
- $args_escaped[] = $value;
- } else {
- if ( ! is_scalar( $value ) && ! is_null( $value ) ) {
- wp_load_translations_early();
- _doing_it_wrong(
- 'wpdb::prepare',
- sprintf(
- /* translators: %s: Value type. */
- __( 'Unsupported value type (%s).' ),
- gettype( $value )
- ),
- '4.8.2'
- );
-
- // Preserving old behavior, where values are escaped as strings.
- $value = '';
- }
-
- $args_escaped[] = $this->_real_escape( $value );
- }
- }
-
- $query = vsprintf( $query, $args_escaped );
-
</del><span class="cx" style="display: block; padding: 0 10px"> return $this->add_placeholder_escape( $query );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3950,13 +3779,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 2.7.0
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.1.0 Added support for the 'utf8mb4' feature.
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.6.0 Added support for the 'utf8mb4_520' feature.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 6.1.0 Added support for the 'identifier_placeholders' feature.
</del><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @see wpdb::db_version()
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string $db_cap The feature to check for. Accepts 'collation', 'group_concat',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * 'subqueries', 'set_charset', 'utf8mb4', 'utf8mb4_520',
- * or 'identifier_placeholders'.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 'subqueries', 'set_charset', 'utf8mb4', or 'utf8mb4_520'.
</ins><span class="cx" style="display: block; padding: 0 10px"> * @return bool True when the database feature is supported, false otherwise.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function has_cap( $db_cap ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4001,12 +3828,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> case 'utf8mb4_520': // @since 4.6.0
</span><span class="cx" style="display: block; padding: 0 10px"> return version_compare( $db_version, '5.6', '>=' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- case 'identifier_placeholders': // @since 6.1.0
- /*
- * As of WordPress 6.1, wpdb::prepare() supports identifiers via '%i',
- * e.g. table/field names.
- */
- return true;
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> return false;
</span></span></pre></div>
<a id="branches61testsphpunittestsdbphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/6.1/tests/phpunit/tests/db.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/6.1/tests/phpunit/tests/db.php 2022-10-31 20:38:20 UTC (rev 54733)
+++ branches/6.1/tests/phpunit/tests/db.php 2022-10-31 20:43:56 UTC (rev 54734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -494,11 +494,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertTrue( $wpdb->has_cap( 'collation' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertTrue( $wpdb->has_cap( 'group_concat' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertTrue( $wpdb->has_cap( 'subqueries' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $this->assertTrue( $wpdb->has_cap( 'identifier_placeholders' ) );
</del><span class="cx" style="display: block; padding: 0 10px"> $this->assertTrue( $wpdb->has_cap( 'COLLATION' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertTrue( $wpdb->has_cap( 'GROUP_CONCAT' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertTrue( $wpdb->has_cap( 'SUBQUERIES' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $this->assertTrue( $wpdb->has_cap( 'IDENTIFIER_PLACEHOLDERS' ) );
</del><span class="cx" style="display: block; padding: 0 10px"> $this->assertSame(
</span><span class="cx" style="display: block; padding: 0 10px"> version_compare( $wpdb->db_version(), '5.0.7', '>=' ),
</span><span class="cx" style="display: block; padding: 0 10px"> $wpdb->has_cap( 'set_charset' )
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1717,135 +1715,26 @@
</span><span class="cx" style="display: block; padding: 0 10px"> false,
</span><span class="cx" style="display: block; padding: 0 10px"> "'{$placeholder_escape}'{$placeholder_escape}s 'hello'",
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+ * @ticket 56933.
+ * When preparing a '%%%s%%', test that the inserted value
+ * is not wrapped in single quotes between the 2 hex values.
+ */
</ins><span class="cx" style="display: block; padding: 0 10px"> array(
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ '%%%s%%',
+ 'hello',
+ false,
+ "{$placeholder_escape}hello{$placeholder_escape}",
+ ),
+ array(
</ins><span class="cx" style="display: block; padding: 0 10px"> "'%-'#5s' '%'#-+-5s'",
</span><span class="cx" style="display: block; padding: 0 10px"> array( 'hello', 'foo' ),
</span><span class="cx" style="display: block; padding: 0 10px"> false,
</span><span class="cx" style="display: block; padding: 0 10px"> "'hello' 'foo##'",
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- array(
- 'SELECT * FROM %i WHERE %i = %d;',
- array( 'my_table', 'my_field', 321 ),
- false,
- 'SELECT * FROM `my_table` WHERE `my_field` = 321;',
- ),
- array(
- 'WHERE %i = %d;',
- array( 'evil_`_field', 321 ),
- false,
- 'WHERE `evil_``_field` = 321;', // To quote the identifier itself, then you need to double the character, e.g. `a``b`.
- ),
- array(
- 'WHERE %i = %d;',
- array( 'evil_````````_field', 321 ),
- false,
- 'WHERE `evil_````````````````_field` = 321;',
- ),
- array(
- 'WHERE %i = %d;',
- array( '``evil_field``', 321 ),
- false,
- 'WHERE `````evil_field````` = 321;',
- ),
- array(
- 'WHERE %i = %d;',
- array( 'evil\'field', 321 ),
- false,
- 'WHERE `evil\'field` = 321;',
- ),
- array(
- 'WHERE %i = %d;',
- array( 'evil_\``_field', 321 ),
- false,
- 'WHERE `evil_\````_field` = 321;',
- ),
- array(
- 'WHERE %i = %d;',
- array( 'evil_%s_field', 321 ),
- false,
- "WHERE `evil_{$placeholder_escape}s_field` = 321;",
- ),
- array(
- 'WHERE %i = %d;',
- array( 'value`', 321 ),
- false,
- 'WHERE `value``` = 321;',
- ),
- array(
- 'WHERE `%i = %d;',
- array( ' AND evil_value', 321 ),
- false,
- 'WHERE `` AND evil_value` = 321;', // Won't run (SQL parse error: "Unclosed quote").
- ),
- array(
- 'WHERE %i` = %d;',
- array( 'evil_value -- ', 321 ),
- false,
- 'WHERE `evil_value -- `` = 321;', // Won't run (SQL parse error: "Unclosed quote").
- ),
- array(
- 'WHERE `%i`` = %d;',
- array( ' AND true -- ', 321 ),
- false,
- 'WHERE `` AND true -- ``` = 321;', // Won't run (Unknown column '').
- ),
- array(
- 'WHERE ``%i` = %d;',
- array( ' AND true -- ', 321 ),
- false,
- 'WHERE ``` AND true -- `` = 321;', // Won't run (SQL parse error: "Unclosed quote").
- ),
- array(
- 'WHERE %2$i = %1$d;',
- array( '1', 'two' ),
- false,
- 'WHERE `two` = 1;',
- ),
- array(
- 'WHERE \'%i\' = 1 AND "%i" = 2 AND `%i` = 3 AND ``%i`` = 4 AND %15i = 5',
- array( 'my_field1', 'my_field2', 'my_field3', 'my_field4', 'my_field5' ),
- false,
- 'WHERE \'`my_field1`\' = 1 AND "`my_field2`" = 2 AND ``my_field3`` = 3 AND ```my_field4``` = 4 AND ` my_field5` = 5', // Does not remove any existing quotes, always adds it's own (safer).
- ),
- array(
- 'WHERE id = %d AND %i LIKE %2$s LIMIT 1',
- array( 123, 'field -- ', false ),
- true, // Incorrect usage.
- null, // Should be rejected, otherwise the `%1$s` could use Identifier escaping, e.g. 'WHERE `field -- ` LIKE field -- LIMIT 1' (thanks @vortfu).
- ),
- array(
- 'WHERE %i LIKE %s LIMIT 1',
- array( "field' -- ", "field' -- " ),
- false,
- "WHERE `field' -- ` LIKE 'field\' -- ' LIMIT 1", // In contrast to the above, Identifier vs String escaping is used.
- ),
</del><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- public function test_allow_unsafe_unquoted_parameters() {
- global $wpdb;
-
- $sql = 'WHERE (%i = %s) OR (%10i = %10s) OR (%5$i = %6$s)';
- $values = array( 'field_a', 'string_a', 'field_b', 'string_b', 'field_c', 'string_c' );
-
- $default = $wpdb->allow_unsafe_unquoted_parameters;
-
- $wpdb->allow_unsafe_unquoted_parameters = true;
-
- // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
- $part = $wpdb->prepare( $sql, $values );
- $this->assertSame( 'WHERE (`field_a` = \'string_a\') OR (` field_b` = string_b) OR (`field_c` = string_c)', $part ); // Unsafe, unquoted parameters.
-
- $wpdb->allow_unsafe_unquoted_parameters = false;
-
- // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
- $part = $wpdb->prepare( $sql, $values );
- $this->assertSame( 'WHERE (`field_a` = \'string_a\') OR (` field_b` = \' string_b\') OR (`field_c` = \'string_c\')', $part );
-
- $wpdb->allow_unsafe_unquoted_parameters = $default;
-
- }
-
</del><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * @dataProvider data_escape_and_prepare
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span></span></pre>
</div>
</div>
</body>
</html>