PluginProbe
WPMasterToolKit (WPMTK) – All in one plugin / 1.2.0
WPMasterToolKit (WPMTK) – All in one plugin v1.2.0
2.22.0 2.21.0 2.20.1 trunk 1.0.0 1.1.0 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.12.4 1.12.5 1.13.0 1.13.1 1.14.0 1.15.0 1.2.0 1.2.1 1.3.0 1.4.0 1.5.0 All 60 releases
wpmastertoolkit / admin / class-php-code-validator.php

class-php-code-validator.php in WPMasterToolKit (WPMTK) – All in one plugin 1.2.0, at admin/class-php-code-validator.php

323 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4 /**
5 * WPMastertoolkit_PHP_Code_Validator
6 *
7 * @copyright Forked from https://github.com/WPManageNinja/easy-code-manager/blob/master/app/Services/PhpValidator.php
8 */
9 class WPMastertoolkit_PHP_Code_Validator {
10
11 /**
12 * Code to validate.
13 *
14 * @var string
15 */
16 private $code;
17
18 /**
19 * List of tokens.
20 *
21 * @var array<string>
22 */
23 private $tokens;
24
25 /**
26 * The index of the token currently being examined.
27 *
28 * @var integer
29 */
30 private $current;
31
32 /**
33 * The total number of tokens.
34 *
35 * @var integer
36 */
37 private $length;
38
39 /**
40 * Array to keep track of the various function, class and interface identifiers which have been defined.
41 *
42 * @var array<string, string[]>
43 */
44 private $defined_identifiers = [];
45
46 /**
47 * Exclude certain tokens from being checked.
48 *
49 * @var array<string, string[]>
50 */
51 private $exceptions = [];
52
53 /**
54 * Class constructor.
55 *
56 * @param string $code Snippet code for parsing.
57 */
58 public function __construct(string $code)
59 {
60 $this->code = $code;
61 $this->tokens = token_get_all($this->code);
62
63 $this->length = count($this->tokens);
64 $this->current = 0;
65 }
66
67 /**
68 * Determine whether the parser has reached the end of the list of tokens.
69 *
70 * @return bool
71 */
72 private function end(): bool
73 {
74 return $this->current === $this->length;
75 }
76
77 /**
78 * Retrieve the next token without moving the pointer
79 *
80 * @return string|array<string|int>|null The current token if the list has not been expended, null otherwise.
81 */
82 private function peek()
83 {
84 return $this->end() ? null : $this->tokens[$this->current];
85 }
86
87 /**
88 * Move the pointer to the next token, if there is one.
89 *
90 * If the first argument is provided, only move the pointer if the tokens match.
91 */
92 private function next()
93 {
94 if (!$this->end()) {
95 $this->current++;
96 }
97 }
98
99 /**
100 * Check whether a particular identifier has been used previously.
101 *
102 * @param string $type Which type of identifier this is. Supports T_FUNCTION, T_CLASS and T_INTERFACE.
103 * @param string $identifier The name of the identifier itself.
104 *
105 * @return bool true if the identifier is not unique.
106 */
107 private function check_duplicate_identifier(string $type, string $identifier): bool
108 {
109
110 if (!isset($this->defined_identifiers[$type])) {
111 switch ($type) {
112 case T_FUNCTION:
113 $defined_functions = get_defined_functions();
114 $this->defined_identifiers[T_FUNCTION] = array_merge($defined_functions['internal'], $defined_functions['user']);
115 break;
116
117 case T_CLASS:
118 $this->defined_identifiers[T_CLASS] = get_declared_classes();
119 break;
120
121 case T_INTERFACE:
122 $this->defined_identifiers[T_INTERFACE] = get_declared_interfaces();
123 break;
124
125 default:
126 return false;
127 }
128 }
129
130 $duplicate = in_array($identifier, $this->defined_identifiers[$type], true);
131 array_unshift($this->defined_identifiers[$type], $identifier);
132
133 return $duplicate && !(isset($this->exceptions[$type]) && in_array($identifier, $this->exceptions[$type], true));
134 }
135
136 /**
137 * Validate the given PHP code and return the result.
138 *
139 * @return \WP_Error|true WP_Error containing message if an error was encountered, true if validation was successful.
140 */
141 public function validate()
142 {
143
144 while (!$this->end()) {
145 $token = $this->peek();
146 $this->next();
147
148 if (!is_array($token)) {
149 continue;
150 }
151
152 // If this is a function or class exists check, then allow this function or class to be defined.
153 if (T_STRING === $token[0] && 'function_exists' === $token[1] || 'class_exists' === $token[1]) {
154 $type = 'function_exists' === $token[1] ? T_FUNCTION : T_CLASS;
155
156 // Eat tokens until we find the function or class name.
157 while (!$this->end() && T_CONSTANT_ENCAPSED_STRING !== $token[0]) {
158 $token = $this->peek();
159 $this->next();
160 }
161
162 // Add the identifier to the list of exceptions.
163 $this->exceptions[$type] = $this->exceptions[$type] ?? [];
164 $this->exceptions[$type][] = trim($token[1], '\'"');
165 continue;
166 }
167
168 // If we have a double colon, followed by a class, then consume it before the next section.
169 if (T_DOUBLE_COLON === $token[0]) {
170 $token = $this->peek();
171 $this->next();
172
173 if (T_CLASS === $token[0]) {
174 $this->next();
175 $token = $this->peek();
176 }
177 }
178
179 // Only look for class and function declaration tokens.
180 if (T_CLASS !== $token[0] && T_FUNCTION !== $token[0]) {
181 continue;
182 }
183
184 /**
185 * Ensure the type of $token is inferred correctly.
186 *
187 * @var string|array<string|int> $token
188 */
189 $structure_type = $token[0];
190
191 // Continue eating tokens until we find the name of the class or function.
192 while (!$this->end() && T_STRING !== $token[0] &&
193 (T_FUNCTION !== $structure_type || '(' !== $token) && (T_CLASS !== $structure_type || '{' !== $token)) {
194 $token = $this->peek();
195 $this->next();
196 }
197
198 // If we've eaten all the tokens without discovering a name, then there must be a syntax error, so return appropriately.
199 if ($this->end()) {
200 return new \WP_Error(
201 'parse_error',
202 __('Parse error: syntax error, unexpected end of snippet.', 'wpmastertoolkit'),
203 array(
204 'line' => $token[2],
205 ));
206 }
207
208 // If the function or class is anonymous, with no name, then no need to check.
209 if (!(T_FUNCTION === $structure_type && '(' === $token) && !(T_CLASS === $structure_type && '{' === $token)) {
210
211 // Check whether the name has already been defined.
212 if ($this->check_duplicate_identifier($structure_type, $token[1])) {
213 switch ($structure_type) {
214 case T_FUNCTION:
215 /* translators: %s: PHP function name */
216 $message = __('Cannot redeclare function %s.', 'wpmastertoolkit');
217 break;
218 case T_CLASS:
219 /* translators: %s: PHP class name */
220 $message = __('Cannot redeclare class %s.', 'wpmastertoolkit');
221 break;
222 case T_INTERFACE:
223 /* translators: %s: PHP interface name */
224 $message = __('Cannot redeclare interface %s.', 'wpmastertoolkit');
225 break;
226 default:
227 /* translators: %s: PHP identifier name*/
228 $message = __('Cannot redeclare %s.', 'wpmastertoolkit');
229 }
230
231 return new \WP_Error(
232 'duplicate_error',
233 sprintf($message, $token[1]),
234 array(
235 'line' => $token[2],
236 ));
237 }
238 }
239
240 // If we have entered into a class, eat tokens until we find the closing brace.
241 if (T_CLASS !== $structure_type) {
242 continue;
243 }
244
245 // Find the opening brace for the class.
246 while (!$this->end() && '{' !== $token) {
247 $token = $this->peek();
248 $this->next();
249 }
250
251 // Continue traversing the class tokens until we have found the class closing brace.
252 $depth = 1;
253 while (!$this->end() && $depth > 0) {
254 $token = $this->peek();
255
256 if ('{' === $token) {
257 $depth++;
258 } elseif ('}' === $token) {
259 $depth--;
260 }
261
262 $this->next();
263 }
264
265 // If we did not make it out of the class, then there's a problem.
266 if ($depth > 0) {
267 return new \WP_Error(
268 'syntax_error',
269 __('Parse error: syntax error, unexpected end of snippet', 'wpmastertoolkit'),
270 array(
271 'line' => $token[2],
272 ));
273 }
274 }
275
276 return true;
277 }
278
279 function checkRunTimeError()
280 {
281 $code = $this->code;
282
283 // Remove the opening PHP tag.
284 $code = preg_replace('/^<\?php/', '', $code);
285
286 ob_start();
287 try {
288 $result = eval($code); // phpcs:ignore Squiz.PHP.Eval.Discouraged
289 } catch (\ParseError $parse_error) {
290 $result = new \WP_Error('runtime_error', $parse_error->getMessage(), array(
291 'line' => $parse_error->getLine(),
292 ));
293 }
294 $output = ob_get_clean();
295
296 if (is_wp_error($result)) {
297 return $result;
298 }
299
300 // if($output) {
301 // return new \WP_Error(
302 // 'has_buffer',
303 // 'PHP code should not have print / echo statement',
304 // array(
305 // 'line' => 0,
306 // 'output' => $output
307 // ));
308 // }
309
310 if ($result) {
311 return new \WP_Error(
312 'has_buffer',
313 'PHP code should not have print / echo statement',
314 array(
315 'line' => 0,
316 'output' => $result
317 ));
318 }
319
320 return true;
321 }
322 }
323