PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / trunk
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts vtrunk
2.7.7 2.7.6 2.7.5 2.7.4 trunk 1.3 2.0.4 2.0.6 2.1.91 2.2.4 2.2.7 2.2.9 2.3.1 2.3.10 2.4.10 2.4.2 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.6.0 2.6.1 2.7.0 All 28 releases
insert-php / includes / class.code-validator.php

class.code-validator.php in Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts trunk, at includes/class.code-validator.php

167 lines 4.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Preflight checks for executable snippets.
4 *
5 * @package WP_Plugin_Insert_PHP
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * WINP_Code_Validator class.
15 */
16 class WINP_Code_Validator {
17
18 /**
19 * Find a top-level function declaration that would be redeclared by eval().
20 *
21 * PHP terminates the request when eval() declares an already-loaded function,
22 * so this conflict has to be detected before evaluating the snippet.
23 *
24 * @param string $snippet_code Snippet code.
25 * @param string $snippet_type Snippet type.
26 * @return array{name:string,line:int}|null Conflict details, or null when none is found.
27 */
28 public static function find_function_redeclaration( $snippet_code, $snippet_type ) {
29 $source = WINP_SNIPPET_TYPE_UNIVERSAL === $snippet_type
30 ? $snippet_code
31 : '<?php ' . $snippet_code;
32 $tokens = token_get_all( $source );
33
34 $brace_depth = 0;
35 $namespace = '';
36 $namespace_depth = 0;
37 $reading_namespace = false;
38 $namespace_name = '';
39 $declared = [];
40
41 foreach ( $tokens as $index => $token ) {
42 if ( is_array( $token ) ) {
43 if ( in_array( $token[0], [ T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES ], true ) ) {
44 ++$brace_depth;
45 continue;
46 }
47
48 if ( T_NAMESPACE === $token[0] ) {
49 $reading_namespace = true;
50 $namespace_name = '';
51 continue;
52 }
53
54 if ( $reading_namespace ) {
55 if ( self::is_namespace_name_token( $token[0] ) ) {
56 $namespace_name .= $token[1];
57 } elseif ( ! in_array( $token[0], [ T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ], true ) ) {
58 $reading_namespace = false;
59 $namespace_name = '';
60 }
61 continue;
62 }
63
64 if ( T_FUNCTION !== $token[0] || $brace_depth !== $namespace_depth ) {
65 continue;
66 }
67
68 $function_name = self::get_function_name( $tokens, $index );
69 if ( null === $function_name ) {
70 continue;
71 }
72
73 $qualified_name = $namespace ? $namespace . '\\' . $function_name : $function_name;
74 $normalized_name = strtolower( $qualified_name );
75
76 if ( isset( $declared[ $normalized_name ] ) || function_exists( $qualified_name ) ) {
77 return [
78 'name' => $qualified_name,
79 'line' => (int) $token[2],
80 ];
81 }
82
83 $declared[ $normalized_name ] = true;
84 continue;
85 }
86
87 if ( $reading_namespace ) {
88 if ( ';' === $token ) {
89 $namespace = trim( $namespace_name, '\\' );
90 $namespace_depth = 0;
91 $reading_namespace = false;
92 } elseif ( '{' === $token ) {
93 ++$brace_depth;
94 $namespace = trim( $namespace_name, '\\' );
95 $namespace_depth = $brace_depth;
96 $reading_namespace = false;
97 } else {
98 $reading_namespace = false;
99 $namespace_name = '';
100 }
101 continue;
102 }
103
104 if ( '{' === $token ) {
105 ++$brace_depth;
106 } elseif ( '}' === $token ) {
107 $brace_depth = max( 0, $brace_depth - 1 );
108 if ( $namespace_depth > $brace_depth ) {
109 $namespace = '';
110 $namespace_depth = 0;
111 }
112 }
113 }
114
115 return null;
116 }
117
118 /**
119 * Check whether a token can be part of a namespace name.
120 *
121 * @param int $token_id Token identifier.
122 * @return bool
123 */
124 private static function is_namespace_name_token( $token_id ) {
125 $name_tokens = [ T_STRING, T_NS_SEPARATOR ];
126
127 foreach ( [ 'T_NAME_QUALIFIED', 'T_NAME_FULLY_QUALIFIED' ] as $constant_name ) {
128 if ( defined( $constant_name ) ) {
129 $name_tokens[] = constant( $constant_name );
130 }
131 }
132
133 return in_array( $token_id, $name_tokens, true );
134 }
135
136 /**
137 * Get the name following a function token, ignoring whitespace and references.
138 *
139 * @param array<int, array<int, int|string>|string> $tokens Token stream.
140 * @param int $function_index Function token index.
141 * @return string|null
142 */
143 private static function get_function_name( $tokens, $function_index ) {
144 $token_count = count( $tokens );
145
146 for ( $index = $function_index + 1; $index < $token_count; ++$index ) {
147 $token = $tokens[ $index ];
148
149 if ( is_array( $token ) ) {
150 if ( in_array( $token[0], [ T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ], true ) || '&' === $token[1] ) {
151 continue;
152 }
153
154 return T_STRING === $token[0] ? (string) $token[1] : null;
155 }
156
157 if ( '&' === $token ) {
158 continue;
159 }
160
161 return null;
162 }
163
164 return null;
165 }
166 }
167