@@ -1916,17 +1916,20 @@ protected function get_array_access_key( $stackPtr ) {
19161916 *
19171917 * @since 0.5.0
19181918 * @since 2.1.0 Now recognizes array_key_exists() and key_exists() as validation functions.
1919+ * @since 2.1.0 Stricter check on whether the correct variable and the correct
1920+ * array keys are being validated.
19191921 *
1920- * @param int $stackPtr The index of this token in the stack.
1921- * @param string $array_key An array key to check for ("bar" in $foo['bar']).
1922- * @param bool $in_condition_only Whether to require that this use of the
1923- * variable occur within the scope of the
1924- * validating condition, or just in the same
1925- * scope as it (default).
1922+ * @param int $stackPtr The index of this token in the stack.
1923+ * @param array|string $array_keys An array key to check for ("bar" in $foo['bar'])
1924+ * or an array of keys for multi-level array access.
1925+ * @param bool $in_condition_only Whether to require that this use of the
1926+ * variable occur within the scope of the
1927+ * validating condition, or just in the same
1928+ * scope as it (default).
19261929 *
19271930 * @return bool Whether the var is validated.
19281931 */
1929- protected function is_validated ( $ stackPtr , $ array_key = null , $ in_condition_only = false ) {
1932+ protected function is_validated ( $ stackPtr , $ array_keys = array () , $ in_condition_only = false ) {
19301933
19311934 if ( $ in_condition_only ) {
19321935 /*
@@ -1977,11 +1980,14 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl
19771980 }
19781981
19791982 $ scope_end = $ stackPtr ;
1983+ }
19801984
1985+ if ( ! empty ( $ array_keys ) && ! is_array ( $ array_keys ) ) {
1986+ $ array_keys = (array ) $ array_keys ;
19811987 }
19821988
1983- $ bare_array_key = $ this -> strip_quotes ( $ array_key );
1984- $ targets = array (
1989+ $ bare_array_keys = array_map ( array ( $ this , ' strip_quotes ' ), $ array_keys );
1990+ $ targets = array (
19851991 \T_ISSET => 'construct ' ,
19861992 \T_EMPTY => 'construct ' ,
19871993 \T_UNSET => 'construct ' ,
@@ -2016,11 +2022,15 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl
20162022 continue ;
20172023 }
20182024
2019- // If we're checking for a specific array key (ex: 'hello' in
2025+ // If we're checking for specific array keys (ex: 'hello' in
20202026 // $_POST['hello']), that must match too. Quote-style, however, doesn't matter.
2021- if ( isset ( $ array_key )
2022- && $ this ->strip_quotes ( $ this ->get_array_access_key ( $ i ) ) !== $ bare_array_key ) {
2023- continue ;
2027+ if ( ! empty ( $ bare_array_keys ) ) {
2028+ $ found_keys = $ this ->get_array_access_keys ( $ i );
2029+ $ found_keys = array_map ( array ( $ this , 'strip_quotes ' ), $ found_keys );
2030+ $ diff = array_diff_assoc ( $ bare_array_keys , $ found_keys );
2031+ if ( ! empty ( $ diff ) ) {
2032+ continue ;
2033+ }
20242034 }
20252035
20262036 return true ;
@@ -2053,12 +2063,45 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl
20532063 }
20542064
20552065 $ params = $ this ->get_function_call_parameters ( $ i );
2056- if ( $ params[ 2 ][ ' raw ' ] !== $ this -> tokens [ $ stackPtr ][ ' content ' ] ) {
2066+ if ( count ( $ params ) < 2 ) {
20572067 continue 2 ;
20582068 }
20592069
2060- if ( isset ( $ array_key )
2061- && $ this ->strip_quotes ( $ params [1 ]['raw ' ] ) !== $ bare_array_key ) {
2070+ $ param2_first_token = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , $ params [2 ]['start ' ], ( $ params [2 ]['end ' ] + 1 ), true );
2071+ if ( false === $ param2_first_token
2072+ || \T_VARIABLE !== $ this ->tokens [ $ param2_first_token ]['code ' ]
2073+ || $ this ->tokens [ $ param2_first_token ]['content ' ] !== $ this ->tokens [ $ stackPtr ]['content ' ]
2074+ ) {
2075+ continue 2 ;
2076+ }
2077+
2078+ if ( ! empty ( $ bare_array_keys ) ) {
2079+ // Prevent the original array from being altered.
2080+ $ bare_keys = $ bare_array_keys ;
2081+ $ last_key = array_pop ( $ bare_keys );
2082+
2083+ /*
2084+ * For multi-level array access, the complete set of keys could be split between
2085+ * the first and the second parameter, but could also be completely in the second
2086+ * parameter, so we need to check both options.
2087+ */
2088+
2089+ $ found_keys = $ this ->get_array_access_keys ( $ param2_first_token );
2090+ $ found_keys = array_map ( array ( $ this , 'strip_quotes ' ), $ found_keys );
2091+
2092+ // First try matching the complete set against the second parameter.
2093+ $ diff = array_diff_assoc ( $ bare_array_keys , $ found_keys );
2094+ if ( empty ( $ diff ) ) {
2095+ return true ;
2096+ }
2097+
2098+ // If that failed, try getting an exact match for the subset against the
2099+ // second parameter and the last key against the first.
2100+ if ( $ bare_keys === $ found_keys && $ this ->strip_quotes ( $ params [1 ]['raw ' ] ) === $ last_key ) {
2101+ return true ;
2102+ }
2103+
2104+ // Didn't find the correct array keys.
20622105 continue 2 ;
20632106 }
20642107
0 commit comments