@@ -1462,16 +1462,9 @@ protected function is_in_isset_or_empty( $stackPtr ) {
14621462 return true ;
14631463 }
14641464
1465- if ( \T_STRING === $ previous_code && 'array_key_exists ' === $ this ->tokens [ $ previous_non_empty ]['content ' ] ) {
1466- if ( $ this ->is_class_object_call ( $ previous_non_empty ) === true ) {
1467- return false ;
1468- }
1469-
1470- if ( $ this ->is_token_namespaced ( $ previous_non_empty ) === true ) {
1471- return false ;
1472- }
1473-
1474- $ second_param = $ this ->get_function_call_parameter ( $ previous_non_empty , 2 );
1465+ $ functionPtr = $ this ->is_in_function_call ( $ stackPtr , array ( 'array_key_exists ' => true ) );
1466+ if ( false !== $ functionPtr ) {
1467+ $ second_param = $ this ->get_function_call_parameter ( $ functionPtr , 2 );
14751468 if ( $ stackPtr >= $ second_param ['start ' ] && $ stackPtr <= $ second_param ['end ' ] ) {
14761469 return true ;
14771470 }
@@ -1545,6 +1538,85 @@ protected function is_token_namespaced( $stackPtr ) {
15451538 return true ;
15461539 }
15471540
1541+ /**
1542+ * Check if a token is (part of) a parameter for a function call to a select list of functions.
1543+ *
1544+ * This is useful, for instance, when trying to determine the context a variable is used in.
1545+ *
1546+ * For example: this function could be used to determine if the variable `$foo` is used
1547+ * in a global function call to the function `is_foo()`.
1548+ * In that case, a call to this function would return the stackPtr to the T_STRING `is_foo`
1549+ * for code like: `is_foo( $foo, 'some_other_param' )`, while it would return `false` for
1550+ * the following code `is_bar( $foo, 'some_other_param' )`.
1551+ *
1552+ * @since 2.1.0
1553+ *
1554+ * @param int $stackPtr The index of the token in the stack.
1555+ * @param array $valid_functions List of valid function names.
1556+ * Note: The keys to this array should be the function names
1557+ * in lowercase. Values are irrelevant.
1558+ * @param bool $global Optional. Whether to make sure that the function call is
1559+ * to a global function. If `false`, calls to methods, be it static
1560+ * `Class::method()` or via an object `$obj->method()`, and
1561+ * namespaced function calls, like `MyNS\function_name()` will
1562+ * also be accepted.
1563+ * Defaults to `true`.
1564+ * @param bool $allow_nested Optional. Whether to allow for nested function calls within the
1565+ * call to this function.
1566+ * I.e. when checking whether a token is within a function call
1567+ * to `strtolower()`, whether to accept `strtolower( trim( $var ) )`
1568+ * or only `strtolower( $var )`.
1569+ * Defaults to `false`.
1570+ *
1571+ * @return int|bool Stack pointer to the function call T_STRING token or false otherwise.
1572+ */
1573+ protected function is_in_function_call ( $ stackPtr , $ valid_functions , $ global = true , $ allow_nested = false ) {
1574+ if ( ! isset ( $ this ->tokens [ $ stackPtr ]['nested_parenthesis ' ] ) ) {
1575+ return false ;
1576+ }
1577+
1578+ $ nested_parenthesis = $ this ->tokens [ $ stackPtr ]['nested_parenthesis ' ];
1579+ if ( false === $ allow_nested ) {
1580+ $ nested_parenthesis = array_reverse ( $ nested_parenthesis , true );
1581+ }
1582+
1583+ foreach ( $ nested_parenthesis as $ open => $ close ) {
1584+
1585+ $ prev_non_empty = $ this ->phpcsFile ->findPrevious ( Tokens::$ emptyTokens , ( $ open - 1 ), null , true , null , true );
1586+ if ( false === $ prev_non_empty || \T_STRING !== $ this ->tokens [ $ prev_non_empty ]['code ' ] ) {
1587+ continue ;
1588+ }
1589+
1590+ if ( isset ( $ valid_functions [ strtolower ( $ this ->tokens [ $ prev_non_empty ]['content ' ] ) ] ) === false ) {
1591+ if ( false === $ allow_nested ) {
1592+ // Function call encountered, but not to one of the allowed functions.
1593+ return false ;
1594+ }
1595+
1596+ continue ;
1597+ }
1598+
1599+ if ( false === $ global ) {
1600+ return $ prev_non_empty ;
1601+ }
1602+
1603+ /*
1604+ * Now, make sure it is a global function.
1605+ */
1606+ if ( $ this ->is_class_object_call ( $ prev_non_empty ) === true ) {
1607+ continue ;
1608+ }
1609+
1610+ if ( $ this ->is_token_namespaced ( $ prev_non_empty ) === true ) {
1611+ continue ;
1612+ }
1613+
1614+ return $ prev_non_empty ;
1615+ }
1616+
1617+ return false ;
1618+ }
1619+
15481620 /**
15491621 * Check if something is only being sanitized.
15501622 *
@@ -1643,9 +1715,16 @@ protected function is_sanitized( $stackPtr, $require_unslash = false ) {
16431715 return true ;
16441716 }
16451717
1646- // If this isn't a call to a function, it sure isn't a sanitizing function.
1647- if ( \T_STRING !== $ this ->tokens [ $ functionPtr ]['code ' ] ) {
1648- if ( $ require_unslash ) {
1718+ $ valid_functions = $ this ->sanitizingFunctions ;
1719+ $ valid_functions += $ this ->unslashingSanitizingFunctions ;
1720+ $ valid_functions ['wp_unslash ' ] = true ;
1721+ $ valid_functions ['array_map ' ] = true ;
1722+
1723+ $ functionPtr = $ this ->is_in_function_call ( $ stackPtr , $ valid_functions );
1724+
1725+ // If this isn't a call to one of the valid functions, it sure isn't a sanitizing function.
1726+ if ( false === $ functionPtr ) {
1727+ if ( true === $ require_unslash ) {
16491728 $ this ->add_unslash_error ( $ stackPtr );
16501729 }
16511730
@@ -1657,15 +1736,17 @@ protected function is_sanitized( $stackPtr, $require_unslash = false ) {
16571736 // Check if wp_unslash() is being used.
16581737 if ( 'wp_unslash ' === $ functionName ) {
16591738
1660- $ is_unslashed = true ;
1661- $ function_opener = array_pop ( $ nested_openers );
1739+ $ is_unslashed = true ;
1740+
1741+ unset( $ valid_functions ['wp_unslash ' ] );
1742+ $ higherFunctionPtr = $ this ->is_in_function_call ( $ functionPtr , $ valid_functions );
16621743
1663- // If there is no other function being used, this value is unsanitized.
1664- if ( ! isset ( $ function_opener ) ) {
1744+ // If there is no other valid function being used, this value is unsanitized.
1745+ if ( false === $ higherFunctionPtr ) {
16651746 return false ;
16661747 }
16671748
1668- $ functionPtr = $ this -> phpcsFile -> findPrevious ( Tokens:: $ emptyTokens , ( $ function_opener - 1 ), null , true , null , true ) ;
1749+ $ functionPtr = $ higherFunctionPtr ;
16691750 $ functionName = $ this ->tokens [ $ functionPtr ]['content ' ];
16701751
16711752 } else {
0 commit comments