PluginProbe
Code Snippets / 2.14.6
Code Snippets v2.14.6
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 2.14.6, at php/class-validator.php

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