PreparedSQLPlaceholdersSniff
extends Sniff
in package
uses
MinimumWPVersionTrait, WPDBTrait
Checks for incorrect use of the $wpdb->prepare method.
Checks the following issues:
- The only placeholders supported are: %d, %f (%F), %s, %i, and their variations.
- Literal % signs need to be properly escaped as
%%. - Simple placeholders (%d, %f, %F, %s, %i) should be left unquoted in the query string.
- Complex placeholders - numbered and formatted variants - will not be quoted automagically by $wpdb->prepare(), so if used for values, should be quoted in the query string. The only exception to this is complex placeholders for %i. In that case, the replacement will still be backtick-quoted.
- Either an array of replacements should be passed matching the number of placeholders found or individual parameters for each placeholder should be passed.
- Wildcards for LIKE compare values should be passed in via a replacement parameter.
The sniff allows for a specific pattern with a variable number of placeholders
created using code along the lines of:
sprintf( 'query .... IN (%s) ...', implode( ',', array_fill( 0, count( $something ), '%s' ) ) ).
Tags
Table of Contents
Constants
- PREPARE_PLACEHOLDER_REGEX = '(?: (?<![^%]%) # Don\'t match a literal % (%%), including when it could overlap with a placeholder. (?: % # Start of placeholder. (?:[0-9]+\\\\?\$)? # Optional ordering of the placeholders. [+-]? # Optional sign specifier. (?: (?:0|\'.)? # Optional padding specifier - excluding the space. -? # Optional alignment specifier. [0-9]* # Optional width specifier. (?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character. | # Only recognize the space as padding in combination with a width specifier. (?:[ ])? # Optional space padding specifier. -? # Optional alignment specifier. [0-9]+ # Width specifier. (?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character. ) [dfFsi] # Type specifier. ) )'
- These regexes were originally copied from https://www.php.net/function.sprintf#93552 and adjusted for limitations in `$wpdb->prepare()`.
- UNSUPPORTED_PLACEHOLDER_REGEX = '`(?: (?<!%) # Don\'t match a literal % (%%). ( % # Start of placeholder. (?! # Negative look ahead. %[^%] # Not a correct literal % (%%). | %%[dfFsi] # Nor a correct literal % (%%), followed by a simple placeholder. ) (?:[0-9]+\\\\??\$)?+ # Optional ordering of the placeholders. [+-]?+ # Optional sign specifier. (?: (?:0|\'.)?+ # Optional padding specifier - excluding the space. -?+ # Optional alignment specifier. [0-9]*+ # Optional width specifier. (?:\.(?:[ 0]|\'.)?[0-9]+)?+ # Optional precision specifier with optional padding character. | # Only recognize the space as padding in combination with a width specifier. (?:[ ])?+ # Optional space padding specifier. -?+ # Optional alignment specifier. [0-9]++ # Width specifier. (?:\.(?:[ 0]|\'.)?[0-9]+)?+ # Optional precision specifier with optional padding character. ) (?![dfFsi]) # Negative look ahead: not one of the supported placeholders. (?:[^ \'"]*|$) # but something else instead. ) )`x'
- Similar to above, but for the placeholder types *not* supported.
Properties
- $minimum_wp_version : string
- Minimum supported WordPress version.
- $methodPtr : int
- Storage for the stack pointer to the method call token.
- $phpcsFile : File
- The current file being sniffed.
- $target_methods : array<string|int, mixed>
- List of $wpdb methods we are interested in.
- $tokens : array<string|int, mixed>
- The list of tokens in the current file being sniffed.
- $default_minimum_wp_version : string
- Default minimum supported WordPress version.
- $regex_quote : string
- Simple regex snippet to recognize and remember quotes.
Methods
- process() : int|void
- Set sniff properties and hand off to child class for processing of the token.
- process_token() : void
- Processes this test, when one of its tokens is encountered.
- register() : array<string|int, mixed>
- Returns an array of tokens this test wants to listen for.
- analyse_implode() : bool
- Analyse an implode() function call to see if it contains a specific code pattern to dynamically create placeholders.
- analyse_sprintf() : int
- Analyse a sprintf() query wrapper to see if it contains a specific code pattern to deal correctly with `IN` queries.
- get_regex_quote_snippet() : string
- Retrieve a regex snippet to recognize and remember quotes based on the quote style used in the original string (if any).
- is_wpdb_method_call() : bool
- Checks whether this is a call to a $wpdb method that we want to sniff.
- set_minimum_wp_version() : void
- Overrule the minimum supported WordPress version with a command-line/config value.
- wp_version_compare() : bool
- Compares two version numbers.
- normalize_version_number() : string
- Normalize a version number.
Constants
PREPARE_PLACEHOLDER_REGEX
These regexes were originally copied from https://www.php.net/function.sprintf#93552 and adjusted for limitations in `$wpdb->prepare()`.
public
string
PREPARE_PLACEHOLDER_REGEX
= '(?:
(?<![^%]%) # Don\'t match a literal % (%%), including when it could overlap with a placeholder.
(?:
% # Start of placeholder.
(?:[0-9]+\\\\?\$)? # Optional ordering of the placeholders.
[+-]? # Optional sign specifier.
(?:
(?:0|\'.)? # Optional padding specifier - excluding the space.
-? # Optional alignment specifier.
[0-9]* # Optional width specifier.
(?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character.
| # Only recognize the space as padding in combination with a width specifier.
(?:[ ])? # Optional space padding specifier.
-? # Optional alignment specifier.
[0-9]+ # Width specifier.
(?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character.
)
[dfFsi] # Type specifier.
)
)'
Near duplicate of the one used in the WP.I18n sniff, but with fewer types allowed.
Note: The regex delimiters and modifiers are not included to allow this regex to be concatenated together with other regex partials.
Tags
UNSUPPORTED_PLACEHOLDER_REGEX
Similar to above, but for the placeholder types *not* supported.
public
string
UNSUPPORTED_PLACEHOLDER_REGEX
= '`(?:
(?<!%) # Don\'t match a literal % (%%).
(
% # Start of placeholder.
(?! # Negative look ahead.
%[^%] # Not a correct literal % (%%).
|
%%[dfFsi] # Nor a correct literal % (%%), followed by a simple placeholder.
)
(?:[0-9]+\\\\??\$)?+ # Optional ordering of the placeholders.
[+-]?+ # Optional sign specifier.
(?:
(?:0|\'.)?+ # Optional padding specifier - excluding the space.
-?+ # Optional alignment specifier.
[0-9]*+ # Optional width specifier.
(?:\.(?:[ 0]|\'.)?[0-9]+)?+ # Optional precision specifier with optional padding character.
| # Only recognize the space as padding in combination with a width specifier.
(?:[ ])?+ # Optional space padding specifier.
-?+ # Optional alignment specifier.
[0-9]++ # Width specifier.
(?:\.(?:[ 0]|\'.)?[0-9]+)?+ # Optional precision specifier with optional padding character.
)
(?![dfFsi]) # Negative look ahead: not one of the supported placeholders.
(?:[^ \'"]*|$) # but something else instead.
)
)`x'
Note: all optional parts are forced to be greedy to allow for the negative look ahead at the end to work.
Tags
Properties
$minimum_wp_version
Minimum supported WordPress version.
public
string
$minimum_wp_version
Currently used by the WordPress.Security.PreparedSQLPlaceholders,
WordPress.WP.AlternativeFunctions, WordPress.WP.Capabilities,
WordPress.WP.DeprecatedClasses, WordPress.WP.DeprecatedFunctions,
WordPress.WP.DeprecatedParameter and the WordPress.WP.DeprecatedParameterValues sniff.
These sniffs will adapt their behaviour based on the minimum supported WP version indicated. By default, it is set to presume that a project will support the current WP version and up to three releases before.
This property allows changing the minimum supported WP version used by these sniffs by setting a property in a custom phpcs.xml ruleset. This property will need to be set for each sniff which uses it.
Example usage:
Alternatively, the value can be passed in one go for all sniffs using it via
the command line or by setting a <config> value in a custom phpcs.xml ruleset.
CL: phpcs --runtime-set minimum_wp_version 5.7
Ruleset: <config name="minimum_wp_version" value="6.0"/>
WordPress version.
Tags
$methodPtr
Storage for the stack pointer to the method call token.
protected
int
$methodPtr
Tags
$phpcsFile
The current file being sniffed.
protected
File
$phpcsFile
Tags
$target_methods
List of $wpdb methods we are interested in.
protected
array<string|int, mixed>
$target_methods
= array('prepare' => true)
Tags
$tokens
The list of tokens in the current file being sniffed.
protected
array<string|int, mixed>
$tokens
Tags
$default_minimum_wp_version
Default minimum supported WordPress version.
private
string
$default_minimum_wp_version
= '6.2'
By default, the minimum_wp_version presumes that a project will support the current WP version and up to three releases before.
}
WordPress version.
Tags
$regex_quote
Simple regex snippet to recognize and remember quotes.
private
string
$regex_quote
= '["\']'
Tags
Methods
process()
Set sniff properties and hand off to child class for processing of the token.
public
process(File $phpcsFile, int $stackPtr) : int|void
Parameters
- $phpcsFile : File
-
The file being scanned.
- $stackPtr : int
-
The position of the current token in the stack passed in $tokens.
Tags
Return values
int|void —Integer stack pointer to skip forward or void to continue normal file processing.
process_token()
Processes this test, when one of its tokens is encountered.
public
process_token(int $stackPtr) : void
Parameters
- $stackPtr : int
-
The position of the current token in the stack.
Tags
register()
Returns an array of tokens this test wants to listen for.
public
register() : array<string|int, mixed>
Tags
Return values
array<string|int, mixed>analyse_implode()
Analyse an implode() function call to see if it contains a specific code pattern to dynamically create placeholders.
protected
analyse_implode(int $implode_token) : bool
The pattern we are searching for is:
implode( ',', array_fill( 0, count( $something ), '%s' ) )
This pattern presumes unquoted placeholders!
Identifiers (%i) are not supported, as this function is designed to work
with IN(), which contains a list of values. In the future, it should
be possible to simplify code using the implode/array_fill pattern to
use a variable number of identifiers, e.g. CONCAT(%...i),
https://core.trac.wordpress.org/ticket/54042
Parameters
- $implode_token : int
-
The stackPtr to the implode function call.
Tags
Return values
bool —True if the pattern is found, false otherwise.
analyse_sprintf()
Analyse a sprintf() query wrapper to see if it contains a specific code pattern to deal correctly with `IN` queries.
protected
analyse_sprintf(array<string|int, mixed> $sprintf_params) : int
The pattern we are searching for is:
sprintf( 'query ....', implode( ',', array_fill( 0, count( $something ), '%s' ) ) )
Parameters
- $sprintf_params : array<string|int, mixed>
-
Parameters details for the sprintf call.
Tags
Return values
int —The number of times the pattern was found in the replacements.
get_regex_quote_snippet()
Retrieve a regex snippet to recognize and remember quotes based on the quote style used in the original string (if any).
protected
get_regex_quote_snippet(string $stripped_content, string $original_content) : string
This allows for recognizing " and \' in single quoted strings,
recognizing ' and \" in double quotes strings and ' and "when the quote
style is unknown or it is a non-quoted string (heredoc/nowdoc and such).
Parameters
- $stripped_content : string
-
Text string content without surrounding quotes.
- $original_content : string
-
Original content for the same text string.
Tags
Return values
stringis_wpdb_method_call()
Checks whether this is a call to a $wpdb method that we want to sniff.
protected
final is_wpdb_method_call(File $phpcsFile, int $stackPtr, array<string|int, mixed> $target_methods) : bool
If available in the class using this trait, the $methodPtr, $i and $end properties are automatically set to correspond to the start and end of the method call. The $i property is also set if this is not a method call but rather the use of a $wpdb property.
Parameters
- $phpcsFile : File
-
The file being scanned.
- $stackPtr : int
-
The index of the $wpdb variable.
- $target_methods : array<string|int, mixed>
-
Array of methods. Key(s) should be method name in lowercase.
Tags
Return values
bool —Whether this is a $wpdb method call.
set_minimum_wp_version()
Overrule the minimum supported WordPress version with a command-line/config value.
protected
final set_minimum_wp_version() : void
Handle setting the minimum supported WP version in one go for all sniffs which
expect it via the command line or via a <config> variable in a ruleset.
The config variable overrules the default $minimum_wp_version and/or a
$minimum_wp_version set for individual sniffs through the ruleset.
Tags
wp_version_compare()
Compares two version numbers.
protected
final wp_version_compare(string $version1, string $version2, string $operator) : bool
Parameters
- $version1 : string
-
First version number.
- $version2 : string
-
Second version number.
- $operator : string
-
Comparison operator.
Tags
Return values
boolnormalize_version_number()
Normalize a version number.
private
normalize_version_number(string $version) : string
Ensures that a version number is comparable via the PHP version_compare() function by making sure it complies with the minimum "PHP-standardized" version number requirements.
Presumes the input is a numeric version number string. The behaviour with other input is undefined.
Parameters
- $version : string
-
Version number.