PluginProbe
Code Snippets / 3.1.1
Code Snippets v3.1.1
3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 3.0.1 All 64 releases
code-snippets / php / class-validator.php

class-validator.php in Code Snippets 3.1.1, at php/class-validator.php

262 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets;
4
5 /**
6 * Validates code prior to execution.
7 *
8 * @package Code_Snippets
9 */
10 class Validator {
11
12 /**
13 * Code to validate.
14 *
15 * @var string
16 */
17 private $code;
18
19 /**
20 * List of tokens.
21 *
22 * @var array
23 */
24 private $tokens;
25
26 /**
27 * The index of the token currently being examined.
28 *
29 * @var int
30 */
31 private $current;
32
33 /**
34 * The total number of tokens.
35 *
36 * @var int
37 */
38 private $length;
39
40 /**
41 * Array to keep track of the various function, class and interface identifiers which have been defined.
42 *
43 * @var array
44 */
45 private $defined_identifiers = array();
46
47 /**
48 * Exclude certain tokens from being checked.
49 *
50 * @var array
51 */
52 private $exceptions = array();
53
54 /**
55 * Class constructor.
56 *
57 * @param string $code Snippet code for parsing.
58 */
59 public function __construct( $code ) {
60 $this->code = $code;
61 $this->tokens = token_get_all( "<?php\n" . $this->code );
62 $this->length = count( $this->tokens );
63 $this->current = 0;
64 }
65
66 /**
67 * Determine whether the parser has reached the end of the list of tokens.
68 *
69 * @return bool
70 */
71 private function end() {
72 return $this->current === $this->length;
73 }
74
75 /**
76 * Retrieve the next token without moving the pointer
77 *
78 * @return string|array|null The current token if the list has not been expended, null otherwise.
79 */
80 private function peek() {
81 return $this->end() ? null : $this->tokens[ $this->current ];
82 }
83
84 /**
85 * Move the pointer to the next token, if there is one.
86 *
87 * If the first argument is provided, only move the pointer if the tokens match.
88 */
89 private function next() {
90 if ( ! $this->end() ) {
91 $this->current++;
92 }
93 }
94
95 /**
96 * Check whether a particular identifier has been used previously.
97 *
98 * @param string $type Which type of identifier this is. Supports T_FUNCTION, T_CLASS and T_INTERFACE.
99 * @param string $identifier The name of the identifier itself.
100 *
101 * @return bool true if the identifier is not unique.
102 */
103 private function check_duplicate_identifier( $type, $identifier ) {
104
105 if ( ! isset( $this->defined_identifiers[ $type ] ) ) {
106 switch ( $type ) {
107 case T_FUNCTION:
108 $defined_functions = get_defined_functions();
109 $this->defined_identifiers[ T_FUNCTION ] = array_merge( $defined_functions['internal'], $defined_functions['user'] );
110 break;
111
112 case T_CLASS:
113 $this->defined_identifiers[ T_CLASS ] = get_declared_classes();
114 break;
115
116 case T_INTERFACE:
117 $this->defined_identifiers[ T_INTERFACE ] = get_declared_interfaces();
118 break;
119
120 default:
121 return false;
122 }
123 }
124
125 $duplicate = in_array( $identifier, $this->defined_identifiers[ $type ], true );
126 array_unshift( $this->defined_identifiers[ $type ], $identifier );
127
128 return $duplicate && ! ( isset( $this->exceptions[ $type ] ) && in_array( $identifier, $this->exceptions[ $type ], true ) );
129 }
130
131 /**
132 * Validate the given PHP code and return the result.
133 *
134 * @return array|bool Array containing message if an error was encountered, false if validation was successful.
135 */
136 public function validate() {
137
138 while ( ! $this->end() ) {
139 $token = $this->peek();
140 $this->next();
141
142 if ( ! is_array( $token ) ) {
143 continue;
144 }
145
146 // If this is a function or class exists check, then allow this function or class to be defined.
147 if ( T_STRING === $token[0] && 'function_exists' === $token[1] || 'class_exists' === $token[1] ) {
148 $type = 'function_exists' === $token[1] ? T_FUNCTION : T_CLASS;
149
150 // Eat tokens until we find the function or class name.
151 while ( ! $this->end() && T_CONSTANT_ENCAPSED_STRING !== $token[0] ) {
152 $token = $this->peek();
153 $this->next();
154 }
155
156 // Add the identifier to the list of exceptions.
157 $this->exceptions[ $type ] = isset( $this->exceptions[ $type ] ) ? $this->exceptions[ $type ] : array();
158 $this->exceptions[ $type ][] = trim( $token[1], '\'"' );
159 continue;
160 }
161
162 // If we have a double colon, followed by a class, then consume it before the next section.
163 if ( T_DOUBLE_COLON === $token[0] ) {
164 $token = $this->peek();
165 $this->next();
166
167 if ( T_CLASS === $token[0] ) {
168 $this->next();
169 $token = $this->peek();
170 }
171 }
172
173 // Only look for class and function declaration tokens.
174 if ( T_CLASS !== $token[0] && T_FUNCTION !== $token[0] ) {
175 continue;
176 }
177
178 $structure_type = $token[0];
179
180 // Continue eating tokens until we find the name of the class or function.
181 while ( ! $this->end() && T_STRING !== $token[0] &&
182 ( T_FUNCTION !== $structure_type || '(' !== $token ) && ( T_CLASS !== $structure_type || '{' !== $token ) ) {
183 $token = $this->peek();
184 $this->next();
185 }
186
187 // If we've eaten all the tokens without discovering a name, then there must be a syntax error, so return appropriately.
188 if ( $this->end() ) {
189 return array(
190 'message' => __( 'Parse error: syntax error, unexpected end of snippet.', 'code-snippets' ),
191 'line' => $token[2],
192 );
193 }
194
195 // If the function or class is anonymous, with no name, then no need to check.
196 if ( ! ( T_FUNCTION === $structure_type && '(' === $token ) && ! ( T_CLASS === $structure_type && '{' === $token ) ) {
197
198 // Check whether the name has already been defined.
199 if ( $this->check_duplicate_identifier( $structure_type, $token[1] ) ) {
200 switch ( $structure_type ) {
201 case T_FUNCTION:
202 /* translators: %s: PHP function name */
203 $message = __( 'Cannot redeclare function %s.', 'code-snippets' );
204 break;
205 case T_CLASS:
206 /* translators: %s: PHP class name */
207 $message = __( 'Cannot redeclare class %s.', 'code-snippets' );
208 break;
209 case T_INTERFACE:
210 /* translators: %s: PHP interface name */
211 $message = __( 'Cannot redeclare interface %s.', 'code-snippets' );
212 break;
213 default:
214 /* translators: %s: PHP identifier name*/
215 $message = __( 'Cannot redeclare %s.', 'code-snippets' );
216 }
217
218 return array(
219 'message' => sprintf( $message, $token[1] ),
220 'line' => $token[2],
221 );
222 }
223 }
224
225 // If we have entered into a class, eat tokens until we find the closing brace.
226 if ( T_CLASS !== $structure_type ) {
227 continue;
228 }
229
230 // Find the opening brace for the class.
231 while ( ! $this->end() && '{' !== $token ) {
232 $token = $this->peek();
233 $this->next();
234 }
235
236 // Continue traversing the class tokens until we have found the class closing brace.
237 $depth = 1;
238 while ( ! $this->end() && $depth > 0 ) {
239 $token = $this->peek();
240
241 if ( '{' === $token ) {
242 $depth++;
243 } elseif ( '}' === $token ) {
244 $depth--;
245 }
246
247 $this->next();
248 }
249
250 // If we did not make it out of the class, then there's a problem.
251 if ( $depth > 0 ) {
252 return array(
253 'message' => __( 'Parse error: syntax error, unexpected end of snippet', 'code-snippets' ),
254 'line' => $token[2],
255 );
256 }
257 }
258
259 return false;
260 }
261 }
262