Skip to content

Commit ad860aa

Browse files
committed
ACP2E-1915: Implement sniff that reports usage of the QPrint, Base64, Uuencode, and HTML-ENTITIES 'text encodings' with MBString functions as deprecated
1 parent ecdcbed commit ad860aa

File tree

4 files changed

+493
-0
lines changed

4 files changed

+493
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010

1111
.phpunit.result.cache
1212
.DS_Store
13+
phpunit.xml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento2\Sniffs\PHPCompatibility;
8+
9+
use PHP_CodeSniffer\Files\File;
10+
use PHPCompatibility\Sniff;
11+
use PHP_CodeSniffer_Tokens as Tokens;
12+
13+
/**
14+
* Detect: Usage of the QPrint, Base64, Uuencode, and HTML-ENTITIES encodings is deprecated for all MBString functions.
15+
*
16+
* PHP version 8.2
17+
*
18+
* @link https://www.php.net/manual/en/migration82.deprecated.php
19+
*/
20+
class DeprecatedEncodingsForMBStringFunctionsSniff extends Sniff
21+
{
22+
/**
23+
* List of tokens which when they precede the $stackPtr indicate that this
24+
* is not a function call.
25+
*
26+
* @var array
27+
*/
28+
private $ignoreTokens = [
29+
\T_DOUBLE_COLON => true,
30+
\T_OBJECT_OPERATOR => true,
31+
\T_FUNCTION => true,
32+
\T_NEW => true,
33+
\T_CONST => true,
34+
\T_USE => true,
35+
];
36+
37+
/**
38+
* Text string tokens to examine.
39+
*
40+
* @var array
41+
*/
42+
private $textStringTokens = [
43+
\T_CONSTANT_ENCAPSED_STRING => true,
44+
\T_DOUBLE_QUOTED_STRING => true,
45+
\T_INLINE_HTML => true,
46+
\T_HEREDOC => true,
47+
\T_NOWDOC => true,
48+
];
49+
50+
/**
51+
* A list of MBString functions that emits deprecation notice
52+
*
53+
* @var \int[][]
54+
*/
55+
protected $targetFunctions = [
56+
'mb_check_encoding' => [2],
57+
'mb_chr' => [2],
58+
'mb_convert_case' => [3],
59+
'mb_convert_encoding' => [2],
60+
'mb_convert_kana' => [3],
61+
'mb_convert_variables' => [1],
62+
'mb_decode_numericentity' => [3],
63+
'mb_encode_numericentity' => [3],
64+
'mb_encoding_aliases' => [1],
65+
'mb_ord' => [2],
66+
'mb_scrub' => [2],
67+
'mb_str_split' => [3],
68+
'mb_strcut' => [4],
69+
'mb_strimwidth' => [5],
70+
'mb_stripos' => [4],
71+
'mb_stristr' => [4],
72+
'mb_strlen' => [2],
73+
'mb_strpos' => [4],
74+
'mb_strrchr' => [4],
75+
'mb_strrichr' => [4],
76+
'mb_strripos' => [4],
77+
'mb_strrpos' => [4],
78+
'mb_strstr' => [4],
79+
'mb_strtolower' => [2],
80+
'mb_strtoupper' => [2],
81+
'mb_strwidth' => [2],
82+
'mb_substr_count' => [3],
83+
'mb_substr' => [4],
84+
];
85+
86+
/**
87+
* Message per encoding
88+
*
89+
* @var string[]
90+
*/
91+
protected $messages = [
92+
'qprint' => 'Handling QPrint via mbstring is deprecated since PHP 8.2;' .
93+
' use quoted_printable_encode/quoted_printable_decode instead',
94+
'quoted-printable' => 'Handling QPrint via mbstring is deprecated since PHP 8.2;' .
95+
' use quoted_printable_encode/quoted_printable_decode instead',
96+
'base64' => 'Handling Base64 via mbstring is deprecated since PHP 8.2;' .
97+
' use base64_encode/base64_decode instead',
98+
'uuencode' => 'Handling Uuencode via mbstring is deprecated since PHP 8.2;' .
99+
' use convert_uuencode/convert_uudecode instead',
100+
'html-entities' => 'Handling HTML entities via mbstring is deprecated since PHP 8.2;' .
101+
' use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead',
102+
'html' => 'Handling HTML entities via mbstring is deprecated since PHP 8.2;' .
103+
' use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead'
104+
];
105+
106+
/**
107+
* @inheritdoc
108+
*/
109+
public function register()
110+
{
111+
return [\T_STRING];
112+
}
113+
114+
/**
115+
* @inheritdoc
116+
*/
117+
public function process(File $phpcsFile, $stackPtr)
118+
{
119+
if (!$this->supportsAbove('8.2')) {
120+
return;
121+
}
122+
123+
$tokens = $phpcsFile->getTokens();
124+
$function = $tokens[$stackPtr]['content'];
125+
$functionLc = strtolower($function);
126+
127+
if (strpos($functionLc, 'mb_') !== 0) {
128+
return;
129+
}
130+
131+
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
132+
133+
if (isset($this->ignoreTokens[$tokens[$prevNonEmpty]['code']]) === true) {
134+
// Not a call to a PHP function.
135+
return;
136+
}
137+
138+
if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR
139+
&& $tokens[$prevNonEmpty - 1]['code'] === \T_STRING
140+
) {
141+
// Namespaced function.
142+
return;
143+
}
144+
145+
$parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
146+
147+
foreach ($parameters as $index => $parameter) {
148+
$this->processParameter($phpcsFile, $function, $index, $parameter);
149+
}
150+
}
151+
152+
/**
153+
* Process function parameter
154+
*
155+
* @param File $phpcsFile
156+
* @param string $function
157+
* @param int $index
158+
* @param array $parameter
159+
* @return void
160+
*/
161+
public function processParameter(File $phpcsFile, string $function, int $index, array $parameter)
162+
{
163+
$functionLc = strtolower($function);
164+
if (!isset($this->targetFunctions[$functionLc]) || !in_array($index, $this->targetFunctions[$functionLc])) {
165+
return;
166+
}
167+
168+
$tokens = $phpcsFile->getTokens();
169+
$targetParam = $parameter;
170+
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $targetParam['start'], $targetParam['end'], true);
171+
172+
if ($nextNonEmpty !== false && in_array($tokens[$nextNonEmpty]['code'], [\T_ARRAY, \T_OPEN_SHORT_ARRAY])) {
173+
$items = $this->getFunctionCallParameters($phpcsFile, $nextNonEmpty);
174+
} else {
175+
$items = [$parameter];
176+
}
177+
178+
foreach ($items as $item) {
179+
for ($i = $item['start']; $i <= $item['end']; $i++) {
180+
if ($tokens[$i]['code'] === \T_STRING
181+
|| $tokens[$i]['code'] === \T_VARIABLE
182+
) {
183+
// Variable, constant, function call. Ignore complete item as undetermined.
184+
break;
185+
}
186+
187+
if (isset($this->textStringTokens[$tokens[$i]['code']]) === true) {
188+
$encoding = $this->stripQuotes(strtolower(trim($tokens[$i]['content'])));
189+
$encodings = array_flip(explode(',', $encoding));
190+
if (count(array_intersect_key($encodings, $this->messages)) > 0) {
191+
$phpcsFile->addWarning(
192+
$this->messages[$encoding],
193+
$i,
194+
'Deprecated',
195+
[$item['raw']]
196+
);
197+
198+
}
199+
200+
// Only throw one error per array item.
201+
break;
202+
}
203+
}
204+
}
205+
}
206+
}

0 commit comments

Comments
 (0)