1818 */
1919class ProperEscapingFunctionSniff extends Sniff {
2020
21+ /**
22+ * Regular expression to match the end of HTML attributes.
23+ *
24+ * @var string
25+ */
26+ const ATTR_END_REGEX = '`(?<attrname>href|src|url|(^|\s+)action)?=(?: \\\\)?[" \']*$`i ' ;
27+
2128 /**
2229 * List of escaping functions which are being tested.
2330 *
@@ -46,13 +53,16 @@ class ProperEscapingFunctionSniff extends Sniff {
4653 T_OPEN_TAG => T_OPEN_TAG ,
4754 T_OPEN_TAG_WITH_ECHO => T_OPEN_TAG_WITH_ECHO ,
4855 T_STRING_CONCAT => T_STRING_CONCAT ,
49- T_COMMA => T_COMMA ,
5056 T_NS_SEPARATOR => T_NS_SEPARATOR ,
5157 ];
5258
5359 /**
5460 * List of attributes associated with url outputs.
5561 *
62+ * @deprecated 2.3.1 Currently unused by the sniff, but needed for
63+ * for public methods which extending sniffs may be
64+ * relying on.
65+ *
5666 * @var array
5767 */
5868 private $ url_attrs = [
@@ -65,6 +75,10 @@ class ProperEscapingFunctionSniff extends Sniff {
6575 /**
6676 * List of syntaxes for inside attribute detection.
6777 *
78+ * @deprecated 2.3.1 Currently unused by the sniff, but needed for
79+ * for public methods which extending sniffs may be
80+ * relying on.
81+ *
6882 * @var array
6983 */
7084 private $ attr_endings = [
@@ -75,6 +89,14 @@ class ProperEscapingFunctionSniff extends Sniff {
7589 '= \\" ' ,
7690 ];
7791
92+ /**
93+ * Keep track of whether or not we're currently in the first statement of a short open echo tag.
94+ *
95+ * @var int|false Integer stack pointer to the end of the first statement in the current
96+ * short open echo tag or false when not in a short open echo tag.
97+ */
98+ private $ in_short_echo = false ;
99+
78100 /**
79101 * Returns an array of tokens this test wants to listen for.
80102 *
@@ -83,7 +105,10 @@ class ProperEscapingFunctionSniff extends Sniff {
83105 public function register () {
84106 $ this ->echo_or_concat_tokens += Tokens::$ emptyTokens ;
85107
86- return [ T_STRING ];
108+ return [
109+ T_STRING ,
110+ T_OPEN_TAG_WITH_ECHO ,
111+ ];
87112 }
88113
89114 /**
@@ -94,6 +119,35 @@ public function register() {
94119 * @return void
95120 */
96121 public function process_token ( $ stackPtr ) {
122+ /*
123+ * Short open echo tags will act as an echo for the first expression and
124+ * allow for passing multiple comma-separated parameters.
125+ * However, short open echo tags also allow for additional statements after, but
126+ * those have to be full PHP statements, not expressions.
127+ *
128+ * This snippet of code will keep track of whether or not we're in the first
129+ * expression in a short open echo tag.
130+ * $phpcsFile->findStartOfStatement() unfortunately is useless, as it will return
131+ * the first token in the statement, which can be anything - variable, text string -
132+ * without any indication of whether this is the start of a normal statement or
133+ * a short open echo expression.
134+ * So, if we used that, we'd need to walk back from every start of statement to
135+ * the previous non-empty to see if it is the short open echo tag.
136+ */
137+ if ( $ this ->tokens [ $ stackPtr ]['code ' ] === T_OPEN_TAG_WITH_ECHO ) {
138+ $ end_of_echo = $ this ->phpcsFile ->findNext ( [ T_SEMICOLON , T_CLOSE_TAG ], ( $ stackPtr + 1 ) );
139+ if ( $ end_of_echo === false ) {
140+ $ this ->in_short_echo = $ this ->phpcsFile ->numTokens ;
141+ } else {
142+ $ this ->in_short_echo = $ end_of_echo ;
143+ }
144+
145+ return ;
146+ }
147+
148+ if ( $ this ->in_short_echo !== false && $ this ->in_short_echo < $ stackPtr ) {
149+ $ this ->in_short_echo = false ;
150+ }
97151
98152 $ function_name = strtolower ( $ this ->tokens [ $ stackPtr ]['content ' ] );
99153
@@ -107,7 +161,17 @@ public function process_token( $stackPtr ) {
107161 return ;
108162 }
109163
110- $ html = $ this ->phpcsFile ->findPrevious ( $ this ->echo_or_concat_tokens , $ stackPtr - 1 , null , true );
164+ $ ignore = $ this ->echo_or_concat_tokens ;
165+ if ( $ this ->in_short_echo !== false ) {
166+ $ ignore [ T_COMMA ] = T_COMMA ;
167+ } else {
168+ $ start_of_statement = $ this ->phpcsFile ->findStartOfStatement ( $ stackPtr , T_COMMA );
169+ if ( $ this ->tokens [ $ start_of_statement ]['code ' ] === T_ECHO ) {
170+ $ ignore [ T_COMMA ] = T_COMMA ;
171+ }
172+ }
173+
174+ $ html = $ this ->phpcsFile ->findPrevious ( $ ignore , $ stackPtr - 1 , null , true );
111175
112176 // Use $textStringTokens b/c heredoc and nowdoc tokens will never be encountered in this context anyways..
113177 if ( $ html === false || isset ( Tokens::$ textStringTokens [ $ this ->tokens [ $ html ]['code ' ] ] ) === false ) {
@@ -129,13 +193,17 @@ public function process_token( $stackPtr ) {
129193 return ;
130194 }
131195
132- if ( $ escaping_type !== 'url ' && $ this ->attr_expects_url ( $ content ) ) {
196+ if ( preg_match ( self ::ATTR_END_REGEX , $ content , $ matches ) !== 1 ) {
197+ return ;
198+ }
199+
200+ if ( $ escaping_type !== 'url ' && empty ( $ matches ['attrname ' ] ) === false ) {
133201 $ message = 'Wrong escaping function. href, src, and action attributes should be escaped by `esc_url()`, not by `%s()`. ' ;
134202 $ this ->phpcsFile ->addError ( $ message , $ stackPtr , 'hrefSrcEscUrl ' , $ data );
135203 return ;
136204 }
137205
138- if ( $ escaping_type === 'html ' && $ this -> is_html_attr ( $ content ) ) {
206+ if ( $ escaping_type === 'html ' ) {
139207 $ message = 'Wrong escaping function. HTML attributes should be escaped by `esc_attr()`, not by `%s()`. ' ;
140208 $ this ->phpcsFile ->addError ( $ message , $ stackPtr , 'htmlAttrNotByEscHTML ' , $ data );
141209 return ;
@@ -145,6 +213,8 @@ public function process_token( $stackPtr ) {
145213 /**
146214 * Tests whether provided string ends with open attribute which expects a URL value.
147215 *
216+ * @deprecated 2.3.1
217+ *
148218 * @param string $content Haystack in which we look for an open attribute which exects a URL value.
149219 *
150220 * @return bool True if string ends with open attribute which expects a URL value.
@@ -165,6 +235,8 @@ public function attr_expects_url( $content ) {
165235 /**
166236 * Tests whether provided string ends with open HMTL attribute.
167237 *
238+ * @deprecated 2.3.1
239+ *
168240 * @param string $content Haystack in which we look for open HTML attribute.
169241 *
170242 * @return bool True if string ends with open HTML attribute.
0 commit comments