<?php

        
if ( is_null$query ) ) {
            return;
        }

        
/*
         * This is not meant to be foolproof -- but it will catch obviously incorrect usage.
         *
         * Note: str_contains() is not used here, as this file can be included
         * directly outside of WordPress core, e.g. by HyperDB, in which case
         * the polyfills from wp-includes/compat.php are not loaded.
         */
        
if ( false === strpos$query'%' ) ) {
            
wp_load_translations_early();
            
_doing_it_wrong(
                
'wpdb::prepare',
                
sprintf(
                    
/* translators: %s: wpdb::prepare() */
                    
__'The query argument of %s must have a placeholder.' ),
                    
'wpdb::prepare()'
                
),
                
'3.9.0'
            
);
        }

        
/*
         * Specify the formatting allowed in a placeholder. The following are allowed:
         *
         * - Sign specifier, e.g. $+d
         * - Numbered placeholders, e.g. %1$s
         * - Padding specifier, including custom padding characters, e.g. %05s, %'#5s
         * - Alignment specifier, e.g. %05-s
         * - Precision specifier, e.g. %.2f
         */
        
$allowed_format '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?';

        
/*
         * If a %s placeholder already has quotes around it, removing the existing quotes
         * and re-inserting them ensures the quotes are consistent.
         *
         * For backward compatibility, this is only applied to %s, and not to placeholders like %1$s,
         * which are frequently used in the middle of longer strings, or as table name placeholders.
         */
        
$query str_replace"'%s'"'%s'$query ); // Strip any existing single quotes.
        
$query str_replace'"%s"''%s'$query ); // Strip any existing double quotes.

        // Escape any unescaped percents (i.e. anything unrecognised).
        
$query preg_replace"/%(?:%|$|(?!(\.\.\.)?($allowed_format)?[sdfFi]))/"'%%\\1'$query );

        
// Extract placeholders from the query.
        
$split_query preg_split"/(^|[^%]|(?:%%)+)(%(?:\.\.\.)?(?:$allowed_format)?[sdfFi])/"$query, -1PREG_SPLIT_DELIM_CAPTURE );

        
$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 ) / );

        
// If args were passed as an array, as in vsprintf(), move them up.
        
$passed_as_array = ( isset( $args[0] ) && is_array$args[0] ) && === count$args ) );
        if ( 
$passed_as_array && isset( $split_query[2] ) && substr$split_query[2], 1) === '...' && isset( $args[0][0] ) && false === is_array$args[0][0] ) ) {
            
$passed_as_array false// The first (and only) placeholder is using variadics (e.g. '%...d'), and that array has *not* been $passed_as_array, e.g. `$wpdb->prepare('id IN (%...d)', [ 1, 2, 3 ] );`.
        
}
        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_current     0;
        
$arg_offset      0;
        
$arg_identifiers = array();
        
$arg_strings     = array();
        
$arg_variadics   = array();

        while ( 
$key $split_query_count ) {

            
// Glue (-2), any leading characters (-1); then the placeholder.
            
$prefix      $split_query$key ] . $split_query$key ];
            
$placeholder $split_query$key ];

            
$variadic = ( '...' === substr$placeholder1) );
            if ( 
$variadic ) {
                
$placeholder '%' substr$placeholder);
            }

            
$format substr$placeholder1, -);
            
$type   substr$placeholder, -);

            
$escaped null;
            for ( 
$l = ( strlen$prefix ) - ); $l >= 0$l-- ) {
                if ( 
'%' === $prefix$l ] ) {
                    
$escaped = ( null === $escaped true : ! $escaped );
                } else {
                    break;
                }
            }

            if ( 
'f' === $type && true === $this->allow_unsafe_unquoted_parameters && null !== $escaped ) {
                
/*
                 * Before WP 6.2 the "force floats to be locale-unaware" RegEx didn't
                 * convert "%%%f" to "%%%F" (note the uppercase F).
                 * This was because it didn't check to see if the leading "%" was escaped.
                 * And because the "Escape any unescaped percents" RegEx used "[sdF]" in its
                 * negative lookahead assertion, when there was an odd number of "%", it added
                 * an extra "%", to give the fully escaped "%%%%f" (so it's never a placeholder).
                 */

                
$new_placeholder '%' . ( true === $escaped '' '%' ) . $format $type;

                --
$placeholder_count;

            } elseif ( 
true === $escaped ) { // Don't change the $placeholder to contain an argnum.

                
if ( $variadic ) {
                    
$new_placeholder '%...' substr$placeholder);
                } else {
                    
$new_placeholder $placeholder;
                }

                --
$placeholder_count;

            } else {

                
// Force floats to be locale-unaware.
                
if ( 'f' === $type ) {
                    
$type 'F';
                }

                
$set_format = ( '' !== $format );

                
$argnum_pos strpos$format'$' );
                if ( 
false !== $argnum_pos ) {
                    
$argnum_value = (int) substr$format0$argnum_pos );
                    
$format       substr$format, ( $argnum_pos ) );
                } else {
                    
$argnum_value = ++$arg_current// Argnum starts at 1.
                
}

                
$new_argnum = ( $argnum_value $arg_offset );

                if ( 
$variadic ) {
                    if ( 
'i' === $type ) {
                        
$new_prefix        '`%';
                        
$new_suffix        $format 's`';
                        
$arg_identifiers[] = ( $argnum_value );
                    } elseif ( 
'd' === $type || 'F' === $type ) {
                        
$new_prefix '%'// No need to quote integers or floats.
                        
$new_suffix $format $type;
                    } else {
                        
$new_prefix "'%";
                        
$new_suffix $format $type "'";
                    }
                    
$new_placeholder '';
                    
$arg_count       count$args[ ( $argnum_value ) ] ); // The argnum in $format starts from 1, but $args index start from 0.
                    
for ( $k 0$k $arg_count$k++ ) {
                        
$new_placeholder .= $new_prefix . ( $new_argnum $k ) . '$' $new_suffix ',';
                    }
                    
$new_placeholder substr$new_placeholder0, -);
                    
$arg_offset     += ( $arg_count ); // The arg already counts as 1, the offset is how many extra to move.
                    
$arg_variadics[] = ( $argnum_value );
                } elseif ( 
'i' === $type ) {
                    
$new_placeholder   '`%' $new_argnum '$' $format 's`';
                    
$arg_identifiers[] = ( $argnum_value );
                } elseif ( 
'd' === $type || 'F' === $type ) {
                    
$new_placeholder '%' $new_argnum '$' $format $type// No need to quote integers or floats.
                
} else { // i.e. ( 's' === $type ).
                    
if ( true === $this->allow_unsafe_unquoted_parameters && ( $set_format || null !== $escaped ) ) {
                            
// Unquoted strings for backward compatibility (dangerous).
                            // First, "numbered or formatted string placeholders (eg, %1$s, %5s)"
                            // Second, if "%s" has a "%" before it, even if it's unrelated (e.g. "LIKE '%%%s%%'").
                        
$new_placeholder '%' $new_argnum '$' $format 's';
                    } else {
                        
$new_placeholder "'%" $new_argnum '$' $format "s'";
                    }
                    
$arg_strings[] = ( $argnum_value );
                }
            }

            
// Glue (-2), any leading characters (-1), then $new_placeholder.
            
$new_query .= $prefix $new_placeholder;

            
$key += 3;

        }

        
// Replace $query; and add remaining $query characters, or index 0 if there were no placeholders.
        
$query $new_query $split_query$key ];

        
$dual_use array_intersect$arg_identifiers$arg_strings );

        if ( 
count$dual_use ) > ) {
            
wp_load_translations_early();

            
$used_placeholders = array();

            
$key    2;
            
$arg_id 0;
            
// Parse again (only used when there is an error).
            
while ( $key $split_query_count ) {
                
$placeholder $split_query$key ];

                
$format substr$placeholder1, -);

                
$argnum_pos strpos$format'$' );

                if ( 
false !== $argnum_pos ) {
                    
$arg_pos = ( ( (int) substr$format0$argnum_pos ) ) - );
                } else {
                    
$arg_pos $arg_id;
                }

                
$used_placeholders$arg_pos ][] = $placeholder;

                
$key += 3;
                
$arg_id++;
            }

            
$conflicts = array();
            foreach ( 
$dual_use as $arg_pos ) {
                
$conflicts[] = implode' and '$used_placeholders$arg_pos ] );
            }

            
_doing_it_wrong(
                
'wpdb::prepare',
                
sprintf(
                    
/* translators: %s: A list of placeholders found to be a problem. */
                    
__'Arguments cannot be prepared as both an Identifier and Value. Found the following conflicts: %s' ),
                    
implode', '$conflicts )
                ),
                
'6.2.0'
            
);

            return;
        }

        
$args_count count$args );

        if ( 
$args_count !== $placeholder_count ) {
            if ( 
=== $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.
                 */
                
wp_load_translations_early();
                
_doing_it_wrong(
                    
'wpdb::prepare',
                    
__'The query only expected one placeholder, but an array of multiple placeholders was sent.' ),
                    
'4.9.0'
                
);

                return;
            } else {
                
/*
                 * If we don't have the right number of placeholders,
                 * but they were passed as individual arguments,
                 * or we were expecting multiple arguments in an array, throw a warning.
                 */
                
wp_load_translations_early();
                
_doing_it_wrong(
                    
'wpdb::prepare',
                    
sprintf(
                        
/* translators: 1: Number of placeholders, 2: Number of arguments passed. */
                        
__'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ),
                        
$placeholder_count,
                        
$args_count
                    
),
                    
'4.8.3'
                
);

                
/*
                 * If we don't have enough arguments to match the placeholders,
                 * return an empty string to avoid a fatal error on PHP 8.
                 */
                
if ( $args_count $placeholder_count ) {
                    
$max_numbered_placeholder 0;

                    for ( 
$i 2$l $split_query_count$i $l$i += ) {
                        
// Assume a leading number is for a numbered placeholder, e.g. '%3$s'.
                        
$argnum = (int) substr$split_query$i ], );

                        if ( 
$max_numbered_placeholder $argnum ) {
                            
$max_numbered_placeholder $argnum;
                        }
                    }

                    if ( ! 
$max_numbered_placeholder || $args_count $max_numbered_placeholder ) {
                        return 
'';
                    }
                }
            }
        }

        
$args_escaped = array();

        foreach ( 
$args as $i => $value ) {
            if ( 
in_array$i$arg_variadicstrue ) ) {
                if ( 
in_array$i$arg_identifierstrue ) ) {
                    
$args_escaped array_merge$args_escapedarray_map( array( $this'_escape_identifier_value' ), $value ) );
                } else {
                    
$args_escaped array_merge$args_escapedarray_map( array( $this'_real_escape' ), $value ) );
                }
            } elseif ( 
in_array$i$arg_identifierstrue ) ) {
                
$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 );

        return 
$this->add_placeholder_escape$query );