PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.3.0
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.3.0
0.5.7 0.5.6 0.5.5 0.5.4 0.5.3 0.5.2 0.5.1 0.5.0 0.4.9 0.4.8 0.4.7 0.4.6 trunk 0.0.1 0.0.2 0.2.8 0.2.9 0.3.0 0.3.1 0.3.2 0.3.3 0.3.4 0.3.5 0.3.6 0.3.7 All 33 releases
← All changes | classes/core.php +668 -764 0.3.40.3.0 View file →
@@ -6,924 +6,828 @@
6 6 use PhpParser\Error;
7 7
8 8 class Meow_MWCODE_Core
9 9 {
10 - public $admin = null;
11 - public $snippet = null;
12 - public $is_rest = false;
13 - public $is_cli = false;
14 - public $site_url = null;
15 - public $mwcode = null;
10 + public $admin = null;
11 + public $snippet = null;
12 + public $is_rest = false;
13 + public $is_cli = false;
14 + public $site_url = null;
15 + public $mwcode = null;
16 16
17 - private $option_name = 'mwcode_options';
17 + private $option_name = 'mwcode_options';
18 18
19 - public function __construct() {
20 - global $mwcode;
19 + public function __construct() {
20 + global $mwcode;
21 +
22 + $this->site_url = get_site_url();
23 + $this->is_rest = MeowCommon_Helpers::is_rest();
24 + $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
25 +
26 + // Snippets
27 + $snippet = new Meow_MWCODE_Modules_Snippet( $this );
28 + $this->snippet = $snippet;
21 29
22 - $this->site_url = get_site_url();
23 - $this->is_rest = MeowCommon_Helpers::is_rest();
24 - $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
30 + // Create API before plugins_loaded
31 + $this->mwcode = new Meow_MWCODE_API( $this, $snippet );
32 + $mwcode = $this->mwcode;
25 33
26 - // Snippets
27 - $snippet = new Meow_MWCODE_Modules_Snippet( $this );
28 - $this->snippet = $snippet;
34 + // Add the shortcode for the "content" snippets
35 + add_shortcode( 'code-engine', [ $this, 'content_shortcode' ] );
36 +
37 + add_action( 'plugins_loaded', array( $this, 'init' ) );
38 + }
29 39
30 - // Create API before plugins_loaded
31 - $this->mwcode = new Meow_MWCODE_API( $this, $snippet );
32 - $mwcode = $this->mwcode;
40 + function init() {
41 + // Part of the core, settings and stuff
42 + $this->admin = new Meow_MWCODE_Admin( $this );
33 43
34 - // Add the shortcode for the "content" snippets
35 - add_shortcode( 'code-engine', [ $this, 'content_shortcode' ] );
44 + // Only for REST
45 + if ( $this->is_rest ) {
46 + new Meow_MWCODE_Rest( $this, $this->admin, $this->snippet );
47 + }
48 + }
36 49
37 - add_action( 'plugins_loaded', array( $this, 'init' ) );
38 - }
39 50
40 - function init() {
41 - // Part of the core, settings and stuff
42 - $this->admin = new Meow_MWCODE_Admin( $this );
51 + /**
52 + *
53 + * Roles & Access Rights
54 + *
55 + */
56 + #region Roles & Access Rights
57 + public function can_access_settings() {
58 + return apply_filters( 'mwcode_allow_setup', current_user_can( 'manage_options' ) );
59 + }
43 60
44 - // Only for REST
45 - if ( $this->is_rest ) {
46 - new Meow_MWCODE_Rest( $this, $this->admin, $this->snippet );
47 - }
48 -
49 - // MCP integration - check both class and global variable
50 - if ( class_exists( 'Meow_MWAI_Core' ) || isset( $GLOBALS['mwai'] ) ) {
51 - new Meow_MWCODE_MCP( $this );
52 - }
53 - }
61 + public function can_access_features() {
62 + return apply_filters( 'mwcode_allow_usage', current_user_can( 'administrator' ) );
63 + }
54 64
55 - /**
56 - *
57 - * Roles & Access Rights
58 - *
59 - */
60 - #region Roles & Access Rights
61 - public function can_access_settings() {
62 - return apply_filters( 'mwcode_allow_setup', current_user_can( 'manage_options' ) );
63 - }
65 + public function check_rest_nonce( $request ) {
66 + $nonce = $request->get_header( 'X-WP-Nonce' );
67 + return wp_verify_nonce( $nonce, 'wp_rest' );
68 + }
69 + #endregion
64 70
65 - public function can_access_features() {
66 - return apply_filters( 'mwcode_allow_usage', current_user_can( 'administrator' ) );
67 - }
71 + #region Options
68 72
69 - public function check_rest_nonce( $request ) {
70 - $nonce = $request->get_header( 'X-WP-Nonce' );
71 - return wp_verify_nonce( $nonce, 'wp_rest' );
72 - }
73 - #endregion
73 + function get_option( $option, $default = null ) {
74 + $options = $this->get_all_options();
75 + return $options[$option] ?? $default;
76 + }
74 77
75 - #region Options
78 + function list_options() {
79 + return [
80 + //Safemode
81 + "safe_mode_status" => "on", // on, off, whitelist
82 + "safe_mode_whitelist" => [],
83 +
84 + //LOGS
85 + "server_debug_mode" => false,
76 86
77 - function get_option( $option, $default = null ) {
78 - $options = $this->get_all_options();
79 - return $options[$option] ?? $default;
80 - }
87 + //UI
88 + "ui_show_preview" => true,
81 89
82 - function list_options() {
83 - return [
84 - //Safemode
85 - "safe_mode_status" => "on", // on, off, whitelist
86 - "safe_mode_whitelist" => [],
87 - "disallow_block_php" => true, // Do not allow PHP code to be execute through Blocks "code" parameter
88 -
89 - //LOGS
90 - "server_debug_mode" => false,
90 + //AI
91 + "ai_suggestions" => false,
92 + "ai_engine_status"=> false,
93 + "ai_engine_message" => "",
91 94
92 - //UI
93 - "ui_show_preview" => false,
95 + //API
96 + "api_endpoint" => false,
97 + "api_token" => md5( time() . rand() ),
98 + ];
99 + }
94 100
95 - //AI
96 - "ai_suggestions" => false,
97 - "ai_engine_status"=> false,
98 - "ai_engine_message" => "",
101 + function get_all_options( ) {
102 + $options = get_option( $this->option_name, $this->list_options( ) );
103 + $options = $this->sanitize_options( $options );
104 +
105 + return $options;
106 + }
99 107
100 - //API
101 - "api_endpoint" => false,
102 - "api_token" => md5( time() . rand() ),
103 -
104 - //MCP
105 - "mcp_support" => false,
106 - ];
107 - }
108 + function update_options( $options ) {
109 + $current_options = get_option($this->option_name);
110 +
111 + if ($current_options === $options) {
112 + // $this->log('💾 The options are already the expected value.');
113 + } else {
114 + if ( !update_option( $this->option_name, $options, false ) ) {
115 + $this->log( '💾 There was an issue updating the options.' );
116 + }
117 + }
118 +
119 + $options = $this->sanitize_options( $options );
120 + return $options;
121 + }
108 122
109 - function get_all_options( ) {
110 - $options = get_option( $this->option_name, [] );
111 - $defaults = $this->list_options();
112 -
113 - // Merge with defaults to ensure all options exist
114 - $options = array_merge( $defaults, $options );
115 -
116 - $options = $this->sanitize_options( $options );
117 - return $options;
118 - }
123 + function update_option( $option, $value ) {
124 + $options = $this->get_all_options();
125 + $options[$option] = $value;
126 + return $this->update_options( $options );
127 + }
119 128
120 - function update_options( $options ) {
121 - $current_options = get_option($this->option_name);
129 + function reset_options() {
130 + if ( $this->get_all_options() === $this->list_options() ) {
131 + return true;
132 + }
133 + return $this->update_options( $this->list_options() );
134 + }
122 135
123 - if ($current_options === $options) {
124 - // $this->log('💾 The options are already the expected value.');
125 - } else {
126 - if ( !update_option( $this->option_name, $options, false ) ) {
127 - $this->log( '💾 There was an issue updating the options.' );
128 - }
129 - }
136 + // Validate and keep the options clean and logical.
137 + function sanitize_options( $options ) {
138 + $options_modified = false;
130 139
131 - $options = $this->sanitize_options( $options );
132 - return $options;
133 - }
140 + // Make sure safe mode whitelist is an array
141 + if ( ! is_array( $options['safe_mode_whitelist'] ) ) {
142 + $options['safe_mode_whitelist'] = explode( ",", $options['safe_mode_whitelist'] );
143 + $options_modified = true;
144 + }
134 145
135 - function update_option( $option, $value ) {
136 - $options = $this->get_all_options();
137 - $options[$option] = $value;
138 - return $this->update_options( $options );
139 - }
146 + // Update AI Engine status
147 + $options_modified = $this->updateAIEngineStatus( $options ) || $options_modified;
140 148
141 - function reset_options() {
142 - if ( $this->get_all_options() === $this->list_options() ) {
143 - return true;
144 - }
145 - return $this->update_options( $this->list_options() );
146 - }
149 + // Disable AI related features if AI Engine is not available
150 + if ( ! $options['ai_engine_status'] && $options['ai_suggestions'] !== false ) {
151 + $options['ai_suggestions'] = false;
152 + $options_modified = true;
153 + }
147 154
148 - // Validate and keep the options clean and logical.
149 - function sanitize_options( $options ) {
150 - $options_modified = false;
151 -
152 - // Ensure mcp_support exists in options
153 - if ( !isset( $options['mcp_support'] ) ) {
154 - $options['mcp_support'] = false;
155 - }
155 + if ( $options_modified ) {
156 + update_option( $this->option_name, $options, false );
157 + }
156 158
157 - // Make sure safe mode whitelist is an array
158 - if ( ! is_array( $options['safe_mode_whitelist'] ) ) {
159 - $options['safe_mode_whitelist'] = explode( ",", $options['safe_mode_whitelist'] );
160 - $options_modified = true;
161 - }
159 + return $options;
160 + }
162 161
163 - // Update AI Engine status
164 - $options_modified = $this->updateAIEngineStatus( $options ) || $options_modified;
162 + private function updateAIEngineStatus( &$options ) {
163 + global $mwai;
165 164
166 - // Disable AI related features if AI Engine is not available
167 - if ( ! $options['ai_engine_status'] ) {
168 - if ( $options['ai_suggestions'] !== false ) {
169 - $options['ai_suggestions'] = false;
170 - $options_modified = true;
171 - }
172 - // Note: We don't disable MCP support here anymore
173 - // It will be checked at runtime in the MCP class
174 - }
165 + if ( is_null( $mwai ) || ! isset( $mwai ) ) {
166 + $options['ai_engine_status'] = false;
167 + $options['ai_engine_message'] = 'AI Engine is not available.';
168 + return true;
169 + }
175 170
176 - if ( $options_modified ) {
177 - update_option( $this->option_name, $options, false );
178 - }
171 + try {
172 + $status = $mwai->checkStatus();
179 173
180 - return $options;
181 - }
174 + if ( $options['ai_engine_status'] != true || $options['ai_engine_message'] != $status ) {
175 + $options['ai_engine_status'] = true;
176 + $options['ai_engine_message'] = $status;
177 + return true;
178 + }
179 + } catch ( Exception $e ) {
180 + if ( $options['ai_engine_status'] != false || $options['ai_engine_message'] != $e->getMessage() ) {
181 + $options['ai_engine_status'] = false;
182 + $options['ai_engine_message'] = $e->getMessage();
183 + return true;
184 + }
185 + }
182 186
183 - private function updateAIEngineStatus( &$options ) {
184 - global $mwai;
187 + return false;
188 + }
185 189
186 - if ( is_null( $mwai ) || ! isset( $mwai ) ) {
187 - $options['ai_engine_status'] = false;
188 - $options['ai_engine_message'] = 'AI Engine is not available.';
189 - return true;
190 - }
190 + // #endregion
191 191
192 - try {
193 - $status = $mwai->checkStatus();
192 + #region Snippets
194 193
195 - if ( $options['ai_engine_status'] != true || $options['ai_engine_message'] != $status ) {
196 - $options['ai_engine_status'] = true;
197 - $options['ai_engine_message'] = $status;
198 - return true;
199 - }
200 - } catch ( Exception $e ) {
201 - if ( $options['ai_engine_status'] != false || $options['ai_engine_message'] != $e->getMessage() ) {
202 - $options['ai_engine_status'] = false;
203 - $options['ai_engine_message'] = $e->getMessage();
204 - return true;
205 - }
194 + /**
195 + * Get snippet.
196 + *
197 + * @param $id
198 + * @return mixed
199 + */
200 + protected function get_snippet( $id ) {
201 + if ( $this->snippet === null ) {
202 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
203 + }
204 +
205 + return $this->snippet->select_one( $id );
206 206 }
207 207
208 - return false;
209 - }
208 + function add_snippet( $params ) {
210 209
211 - #endregion
210 + $response = [
211 + "snippet" => null,
212 + "result" => false,
213 + ];
212 214
213 - #region Snippets
215 + $this->snippet->validate( $params );
214 216
215 - /**
216 - * Get snippet.
217 - *
218 - * @param $id
219 - * @return mixed
220 - */
221 - protected function get_snippet( $id ) {
222 - if ( $this->snippet === null ) {
223 - $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
224 - }
217 + $params = $this->snippet->formatParamsForDatabase( $params );
218 + $result = $this->snippet->insert( $params );
219 + $snippet = $this->snippet->select_one( $result );
225 220
226 - return $this->snippet->select_one( $id );
227 - }
221 + if( $result ) {
222 + $params['id'] = (string)$result;
228 223
229 - function add_snippet( $params ) {
224 + $this->snippet->create_or_update_function_snippet( $params );
225 + $this->snippet->create_or_update_interval_snippet( $params );
230 226
231 - $response = [
232 - "snippet" => null,
233 - "result" => false,
234 - ];
227 + $this->snippet->get_function_snippets_data( $snippet );
228 + }
235 229
236 - $this->snippet->validate( $params );
230 + $response['snippet'] = $snippet;
231 + $response['result'] = $result;
237 232
238 - $params = $this->snippet->formatParamsForDatabase( $params );
239 - $result = $this->snippet->insert( $params );
240 - $snippet = $this->snippet->select_one( $result );
233 + return $response;
234 + }
241 235
242 - if( $result ) {
243 - $params['id'] = (string)$result;
236 + private function sanitize_arg( $name, $value, $type = null) {
237 + $real_type = gettype( $value );
244 238
245 - $this->snippet->create_or_update_function_snippet( $params );
246 - $this->snippet->create_or_update_interval_snippet( $params );
239 + if ( $name[0] !== '$' ) { $name = '$' . $name; }
247 240
248 - $this->snippet->get_function_snippets_data( $snippet );
249 - }
241 + if ( $type == null ) {
242 + $type = $real_type;
243 + }
244 +
245 + if ( $type != 'array' && !empty( $value ) && !is_numeric( $value ) && $value[0] !== '"' && $value[strlen( $value ) - 1] !== '"' ) {
246 + $value = '"' . esc_sql( $value ) . '"';
247 + }
250 248
251 - $response['snippet'] = $snippet;
252 - $response['result'] = $result;
249 + if ( $type === 'array' && $real_type === 'string' ) {
250 + // We got a string like this: "["a", "b", "c"]" or "[ 1, 2, 3 ]"
251 + // We need to convert it to an array
252 + $value = str_replace( '"', '', $value );
253 + $value = str_replace( '[', '', $value );
254 + $value = str_replace( ']', '', $value );
255 + $value = explode( ',', $value );
256 + $value = array_map( 'trim', $value );
257 + }
253 258
254 - return $response;
255 - }
259 + if ( $type === 'array' ) {
260 + $value = json_encode( $value );
261 + $value = str_replace( '\\', '', $value );
262 + }
256 263
257 - private function sanitize_arg( $name, $value, $type = null) {
258 - $real_type = gettype( $value );
264 + return [ $name, $value ];
265 + }
259 266
260 - if ( $name[0] !== '$' ) { $name = '$' . $name; }
267 + function run_non_fn_snippet( $id, $code = null, $test = false ) {
268 + // Retrieve the snippet code from the provided code or via the snippet ID.
269 + if ( $code ) {
270 + $snippet = [ 'code' => $code ];
271 + } else {
272 + $snippet = $this->get_snippet( $id );
273 + }
274 +
275 + // Remove any PHP opening tag.
276 + $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
277 +
261 278
262 - if ( $type == null ) {
263 - $type = $real_type;
264 - }
279 + if ( $test ) {
280 + $snippet['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $snippet['code'] );
281 + }
282 +
283 + $error = null;
284 + $output = null;
285 +
286 + try {
287 + ob_start();
288 + eval( $snippet['code'] );
289 + $output = ob_get_clean();
290 + } catch ( Throwable $e ) {
291 + $snippet_id = $id ? " ( ID: $id )" : '(Content Gutenberg Block)';
292 + $this->log( '🔴 Error executing the snippet ' . $snippet_id . ' : ' . $e->getMessage() );
293 + ob_clean();
294 + } finally {
295 + restore_error_handler();
296 + }
297 +
298 + // If in test mode, return output as an array of lines with an 'error' key if needed.
299 + if ( $test ) {
300 + $output = explode( "\n", trim( $output ) );
301 + if ( $error !== null ) {
302 + $output['error'] = $error->getMessage();
303 + }
304 + } else {
305 + if ( $error !== null ) {
306 + throw $error;
307 + }
308 + }
309 +
310 + return $output;
311 + }
265 312
266 - if ( $type != 'array' && !empty( $value ) && !is_numeric( $value ) && $value[0] !== '"' && $value[strlen( $value ) - 1] !== '"' ) {
267 - $value = '"' . esc_sql( $value ) . '"';
268 - }
313 + function run_snippet( $id, $args = [], $params = [] )
314 + {
315 + // Static array to track defined functions
316 + static $defined_functions = array();
269 317
270 - if ( $type === 'array' && $real_type === 'string' ) {
271 - // We got a string like this: "["a", "b", "c"]" or "[ 1, 2, 3 ]"
272 - // We need to convert it to an array
273 - $value = str_replace( '"', '', $value );
274 - $value = str_replace( '[', '', $value );
275 - $value = str_replace( ']', '', $value );
276 - $value = explode( ',', $value );
277 - $value = array_map( 'trim', $value );
278 - }
318 + if ( $id ) { // If there is an ID, we get the snippet, if not we get the data from the params
319 + $snippet = $this->get_snippet( $id );
320 + $this->snippet->get_function_snippets_data( $snippet ); // adds the function data to the snippet
279 321
280 - if ( $type === 'array' ) {
281 - $value = json_encode( $value );
282 - $value = str_replace( '\\', '', $value );
283 - }
322 + $params = [ // We set the params according to the snippet we fetched
323 + 'test' => false, // If we pass an ID to the function, we are not testing the snippet
324 + // 'test' => $params['test'] ?? false if needed we can still use ID and test at the same time (should not happen)
325 + 'code' => $snippet['code'],
326 + 'name' => $snippet['functionName'],
327 + 'args' => $snippet['functionArgs'],
328 + 'values' => $snippet['functionArgsDict'] // Contains the default values of the arguments
329 + ];
330 + }
284 331
285 - return [ $name, $value ];
286 - }
332 + // Sanitize all the arguments if the option is enabled
333 + if ( $this->get_option( 'sanitize_arguments', true ) ) {
287 334
288 - function run_non_fn_snippet( $id, $code = null, $test = false ) {
289 - // Retrieve the snippet code from the provided code or via the snippet ID.
290 - if ( $code ) {
291 - $snippet = [ 'code' => $code ];
292 - } else {
293 - $snippet = $this->get_snippet( $id );
294 - }
335 + if ( $args ) {
336 + foreach ( $args as $name => $value ) {
337 + list( $sanitizedName, $sanitizedValue ) = $this->sanitize_arg( $name, $value, $value['type'] );
338 + unset( $args[$name] );
295 339
296 - // Remove any PHP opening tag.
297 - $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
340 + $args[$sanitizedName] = $sanitizedValue;
341 + }
342 + }
298 343
299 - if ( $test ) {
300 - $snippet['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $snippet['code'] );
301 - }
302 -
303 - $error = null;
304 - $output = null;
305 -
306 - try {
307 - ob_start();
308 - eval( $snippet['code'] );
309 - $output = ob_get_clean();
310 - } catch ( Throwable $e ) {
311 - $snippet_id = $id ? " ( ID: $id )" : '(Content Gutenberg Block)';
312 - $this->log( '🔴 Error executing the snippet ' . $snippet_id . ' : ' . $e->getMessage() );
313 - ob_clean();
314 - } finally {
315 - restore_error_handler();
316 - }
317 -
318 - // If in test mode, return output as an array of lines with an 'error' key if needed.
319 - if ( $test ) {
320 - $output = explode( "\n", trim( $output ) );
321 - if ( $error !== null ) {
322 - $output['error'] = $error->getMessage();
323 - }
324 - } else {
325 - if ( $error !== null ) {
326 - throw $error;
327 - }
328 - }
329 -
330 - return $output;
331 - }
344 + foreach ( $params['values'] as $name => $value ) {
332 345
333 - function run_snippet( $id, $args = [], $params = [] )
334 - {
335 - // Static array to track defined functions
336 - static $defined_functions = array();
346 + if( array_key_exists( 'input', $value) ) {
347 + list( $sanitizedInputName, $sanitizedInputValue ) = $this->sanitize_arg( $name, $value['input'], $value['type'] );
348 + $params['values'][$sanitizedInputName]['input'] = $sanitizedInputValue;
349 + }
350 +
351 + if( array_key_exists( 'default', $value) ) {
352 + list( $sanitizedDefaultValueName, $sanitizedDefaultValue ) = $this->sanitize_arg( $name, $value['default'], $value['type'] );
353 + $params['values'][$sanitizedDefaultValueName]['default'] = $sanitizedDefaultValue;
354 + }
355 + }
337 356
338 - if ( $id ) { // If there is an ID, we get the snippet, if not we get the data from the params
339 - $snippet = $this->get_snippet( $id );
340 - $this->snippet->get_function_snippets_data( $snippet ); // adds the function data to the snippet
357 + }
341 358
342 - $params = [ // We set the params according to the snippet we fetched
343 - 'test' => false, // If we pass an ID to the function, we are not testing the snippet
344 - // 'test' => $params['test'] ?? false if needed we can still use ID and test at the same time (should not happen)
345 - 'code' => $snippet['code'],
346 - 'name' => $snippet['functionName'],
347 - 'args' => $snippet['functionArgs'],
348 - 'values' => $snippet['functionArgsDict'] // Contains the default values of the arguments
349 - ];
350 - }
359 + // Make sure the function is existing and is the one in the snippet
360 + if ( empty( $params['code'] ) ) {
361 + throw new Exception( 'Code Engine: The snippet code appears to be empty.' );
362 + }
363 +
364 + if ( empty( $params['name'] ) || ! str_contains( $params['code'], $params['name'] ) ) {
365 + throw new Exception( "Code Engine: Function name does not match. The name should be {$params['name']}." );
366 + }
351 367
352 - // Sanitize all the arguments if the option is enabled
353 - if ( $this->get_option( 'sanitize_arguments', true ) ) {
368 + // Overwrite the default values with the provided ones
369 + if ( $args ) {
370 + foreach ( $args as $name => $value ) {
371 + $params['values'][$name]['input'] = $value;
372 + }
354 373
355 - if ( $args ) {
356 - foreach ( $args as $name => $value ) {
357 - list( $sanitizedName, $sanitizedValue ) = $this->sanitize_arg( $name, $value );
358 - unset( $args[$name] );
374 + $this->log( '⚡ Arguments provided: ' . json_encode( $args ) );
375 + }
359 376
360 - $args[$sanitizedName] = $sanitizedValue;
361 - }
362 - }
377 + // Check if the function has already been defined
378 + if ( !in_array( $params['name'], $defined_functions ) ) {
363 379
364 - foreach ( $params['values'] as $name => $value ) {
380 + // If not, proceed with modification and definition
381 + if ( $params['test'] ) { // Make sure the echo statement uses a line break
382 + $params['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $params['code'] );
383 + } else { // Remove all echo statements
384 + $params['code'] = preg_replace( '/echo\s+(.+?);/s', '', $params['code'] );
385 + }
365 386
366 - if( array_key_exists( 'input', $value) ) {
367 - list( $sanitizedInputName, $sanitizedInputValue ) = $this->sanitize_arg( $name, $value['input'], $value['type'] );
368 - $params['values'][$sanitizedInputName]['input'] = $sanitizedInputValue;
369 - }
387 + $params['code'] = "if (!function_exists('{$params['name']}')) {\n" . $params['code'] . "\n}\n";
370 388
371 - if( array_key_exists( 'default', $value) ) {
372 - list( $sanitizedDefaultValueName, $sanitizedDefaultValue ) = $this->sanitize_arg( $name, $value['default'], $value['type'] );
373 - $params['values'][$sanitizedDefaultValueName]['default'] = $sanitizedDefaultValue;
374 - }
375 - }
389 + // Add the function name to the array to avoid redefinition
390 + $defined_functions[] = $params['name'];
391 + } else {
392 + // If already defined, just prepare to call the function without redefining it
393 + $params['code'] = '';
394 + }
376 395
377 - }
396 + // Prepare the code to be executed
397 + $params['code'] .= "\n\$mwcode_result = {$params['name']}(";
398 + foreach ( $params['args'] as $index => $arg ) {
399 + $value = 'null'; // In case the argument is not provided it will be null
378 400
379 - // Make sure the function is existing and is the one in the snippet
380 - if ( empty( $params['code'] ) ) {
381 - throw new Exception( 'Code Engine: The snippet code appears to be empty.' );
382 - }
401 + if ( array_key_exists( $arg, $params['values'] ) ) { // Avoid warnings if the argument is not provided
383 402
384 - if ( empty( $params['name'] ) || ! str_contains( $params['code'], $params['name'] ) ) {
385 - throw new Exception( "Code Engine: Function name does not match. The name should be {$params['name']}." );
386 - }
403 + // If the argument is provided, use it, if not use the default value
404 + if ( !empty( $params['values'][$arg]['input'] ) ) {
405 + $value = $params['values'][$arg]['input'];
387 406
388 - // Overwrite the default values with the provided ones
389 - if ( $args ) {
390 - foreach ( $args as $name => $value ) {
391 - $params['values'][$name]['input'] = $value;
392 - }
407 + } else if ( !empty( $params['values'][$arg]['default'] ) ) {
408 + $value = $params['values'][$arg]['default'];
409 + }
410 + }
393 411
394 - $this->log( '⚡ Arguments provided: ' . json_encode( $args ) );
395 - }
412 + $params['code'] .= "{$value}";
413 + if ( $index < count( $params['args'] ) - 1 ) {
414 + $params['code'] .= ', ';
415 + }
416 + }
417 + $params['code'] .= ");\necho print_r(\$mwcode_result, true);";
396 418
397 - // Check if the function has already been defined
398 - if ( !in_array( $params['name'], $defined_functions ) ) {
419 + $error = null;
420 + $output = null;
399 421
400 - // If not, proceed with modification and definition
401 - if ( $params['test'] ) { // Make sure the echo statement uses a line break
402 - $params['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $params['code'] );
403 - } else { // Remove all echo statements
404 - $params['code'] = preg_replace( '/echo\s+(.+?);/s', '', $params['code'] );
405 - }
422 + try {
423 + ob_start();
424 + eval( $params['code'] );
425 + $output = ob_get_clean();
426 +
427 + if ( $params['test'] ){
428 + $output = explode( "\n", $output );
429 + }
430 +
431 + } catch ( Throwable $e ) {
432 + //$this->log('Code Engine: Error executing the function: ' . $e->getMessage());
433 + $error = new Exception(' Error executing the function, ' . $e->getMessage());
406 434
407 - $params['code'] = "if (!function_exists('{$params['name']}')) {\n" . $params['code'] . "\n}\n";
435 + ob_clean();
436 + } finally {
437 + restore_error_handler();
438 + }
408 439
409 - // Add the function name to the array to avoid redefinition
410 - $defined_functions[] = $params['name'];
411 - } else {
412 - // If already defined, just prepare to call the function without redefining it
413 - $params['code'] = '';
414 - }
440 + if ( $error !== null ) {
441 + if( $params['test'] ){
442 + $output['error'] = $error->getMessage();
443 + } else {
444 + throw $error;
445 + }
446 + }
415 447
416 - // Prepare the code to be executed
417 - $params['code'] .= "\n\$mwcode_result = {$params['name']}(";
418 - foreach ( $params['args'] as $index => $arg ) {
419 - $value = 'null'; // In case the argument is not provided it will be null
448 + return $output;
449 + }
420 450
421 - if ( array_key_exists( $arg, $params['values'] ) ) { // Avoid warnings if the argument is not provided
422 451
423 - // If the argument is provided, use it, if not use the default value
424 - if ( !empty( $params['values'][$arg]['input'] ) ) {
425 - $value = $params['values'][$arg]['input'];
452 + function parse_snippet( $code, $new_snippet = false ){
453 + $parser = ( new ParserFactory( ) )->createForNewestSupportedVersion( );
426 454
427 - } else if ( !empty( $params['values'][$arg]['default'] ) ) {
428 - $value = $params['values'][$arg]['default'];
429 - }
430 - }
455 + if( !$this->snippet ){
456 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
457 + }
431 458
432 - $params['code'] .= "{$value}";
433 - if ( $index < count( $params['args'] ) - 1 ) {
434 - $params['code'] .= ', ';
435 - }
436 - }
437 - $params['code'] .= ");\necho print_r(\$mwcode_result, true);";
459 + // First we check the function names are unique
460 + $fn = $this->snippet->sanitize_and_check_functions( $code, $new_snippet );
461 + if ( ! $fn['is_valid'] ) {
438 462
439 - $error = null;
440 - $output = null;
463 + $lint = [
464 + 'line' => 1,
465 + 'attributes' => $fn['attributes'][0],
466 + 'raw_message' => implode(', ', $fn['errors'][0]),
467 + 'message' => implode(', ', $fn['errors'][0]),
468 + ];
441 469
442 - try {
443 - ob_start();
444 - eval( $params['code'] );
445 - $output = ob_get_clean();
446 -
447 - if ( $params['test'] ){
448 - $output = explode( "\n", $output );
449 - }
450 -
451 - } catch ( Throwable $e ) {
452 - //$this->log('Code Engine: Error executing the function: ' . $e->getMessage());
453 - $error = new Exception(' Error executing the function, ' . $e->getMessage());
470 + return $lint;
471 + }
454 472
455 - ob_clean();
456 - } finally {
457 - restore_error_handler();
458 - }
473 + try {
474 + $stmts = $parser->parse( $code );
475 + $result = $stmts;
476 + } catch ( PhpParser\Error $e ) {
459 477
460 - if ( $error !== null ) {
461 - if( $params['test'] ){
462 - $output['error'] = $error->getMessage();
463 - } else {
464 - throw $error;
465 - }
466 - }
478 + $lint = [
479 + 'line' => $e->getStartLine(),
480 + 'attributes' => $e->getAttributes(),
481 + 'raw_message' => $e->getRawMessage(),
482 + 'message' => $e->getMessage(),
483 + ];
467 484
468 - return $output;
469 - }
485 + return $lint;
486 + }
470 487
488 + return null;
489 + }
471 490
472 - function parse_snippet( $code, $new_snippet = false ){
473 - $parser = ( new ParserFactory( ) )->createForNewestSupportedVersion( );
491 + public function get_js_functions_to_push() {
492 + $functions = $this->snippet->get_functions();
493 + $js_functions = [];
494 + foreach ( $functions as &$function ) {
495 + if ( !isset( $function['target'] ) ) {
496 + $function['target'] = 'php';
497 + }
498 + if ( $function['target'] == 'js' ) {
499 + $js_functions[] = $function;
500 + }
501 + }
502 + $snippets = [];
503 + foreach ( $js_functions as $function ) {
504 + $snippet = $this->snippet->select_one( $function['snippetId'] );
505 + $snippet['function_info'] = $function; // Add function info to snippet
506 + $snippets[] = $snippet;
507 + }
508 +
509 + return $this->generate_js_functions_code( $snippets );
510 + }
511 +
512 + function generate_js_functions_code ($snippets ) {
513 + $code = "";
514 + foreach ( $snippets as $snippet ) {
515 + $function_code = $snippet['code'];
516 + $function_info = $snippet['function_info'];
517 +
518 + // Extract function name and arguments
519 + preg_match( '/(?:const|let|var)?\s*(\w+)\s*=\s*\((.*?)\)\s*=>/', $function_code, $matches );
520 + $function_name = $matches[1] ?? $function_info['name'];
521 + $function_args = $matches[2] ?? '';
522 +
523 + // Prepare default values
524 + $default_args = [];
525 + foreach ( $function_info['args'] as $arg ) {
526 + if ( isset( $arg['default'] ) && $arg['default'] !== '' ) {
527 + $default_args[$arg['name']] = $arg['default'];
528 + }
529 + }
530 +
531 + // Modify function to use default values
532 + if ( !empty( $default_args ) ) {
533 + $new_args = explode( ',', $function_args );
534 + foreach ( $new_args as &$arg ) {
535 + $arg = trim( $arg );
536 + if ( isset( $default_args[$arg] ) ) {
537 + $arg .= " = " . json_encode( $default_args[$arg] );
538 + }
539 + }
540 + $new_args_string = implode( ', ', $new_args );
541 + $function_code = preg_replace(
542 + '/(\w+)\s*=\s*\((.*?)\)\s*=>/',
543 + "$1 = ($new_args_string) =>",
544 + $function_code
545 + );
546 + }
547 +
548 + $code .= $function_code . "\n\n";
549 + }
474 550
475 - if( !$this->snippet ){
476 - $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
477 - }
551 + return $code;
552 + }
478 553
479 - // First we check the function names are unique
480 - $fn = $this->snippet->sanitize_and_check_functions( $code, $new_snippet );
481 - if ( ! $fn['is_valid'] ) {
482 554
483 - $lint = [
484 - 'line' => 1,
485 - 'attributes' => $fn['attributes'][0],
486 - 'raw_message' => implode(', ', $fn['errors'][0]),
487 - 'message' => implode(', ', $fn['errors'][0]),
488 - ];
555 + /**
556 + * [STATIC] Execute active snippets.
557 + *
558 + * @return array
559 + */
560 + public function execute_active_snippets() {
489 561
490 - return $lint;
491 - }
562 + $blocked = false;
563 + $page = isset( $_GET["page"] ) ? sanitize_text_field( $_GET["page"] ) : null;
564 + if ( $page === 'mwcode_settings' || !Meow_MWCODE_Core::is_white_listed_rest() ) {
565 + $blocked = true;
566 + }
492 567
493 - try {
494 - $stmts = $parser->parse( $code );
495 - $result = $stmts;
496 - } catch ( PhpParser\Error $e ) {
568 + if ( empty( $this->snippet ) ) {
569 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
570 + }
497 571
498 - $lint = [
499 - 'line' => $e->getStartLine(),
500 - 'attributes' => $e->getAttributes(),
501 - 'raw_message' => $e->getRawMessage(),
502 - 'message' => $e->getMessage(),
503 - ];
572 + $ts = $this->get_option( 'thrown_snippet', null );
573 + if ( !empty( $ts ) ) {
574 + $this->log( "⚠️ Your snippet \"{$ts['name']}\" has thrown a fatal error last time, so we disabled it. Please check the logs for more information." );
575 + $this->snippet->force_disable( $ts['id'] );
576 + $this->update_option( 'thrown_snippet', null );
577 + }
504 578
505 - return $lint;
506 - }
579 + $scope = is_admin() ? [ 'backend', 'persistent' ] : [ 'frontend', 'persistent' ];
580 + // Get all active snippets
507 581
508 - return null;
509 - }
582 +
510 583
511 - public function get_js_functions_to_push() {
512 - $functions = $this->snippet->get_functions();
513 - $js_functions = [];
514 - foreach ( $functions as &$function ) {
515 - if ( !isset( $function['target'] ) ) {
516 - $function['target'] = 'php';
517 - }
518 - if ( $function['target'] == 'js' ) {
519 - $js_functions[] = $function;
520 - }
521 - }
522 - $snippets = [];
523 - foreach ( $js_functions as $function ) {
524 - $snippet = $this->snippet->select_one( $function['snippetId'] );
525 - $snippet['function_info'] = $function; // Add function info to snippet
526 - $snippets[] = $snippet;
527 - }
584 + $snippets = $this->snippet->select(
585 + null, // offset
586 + -1, // limit
587 + [
588 + [ 'accessor' => 'active', 'value' => 1 ],
589 + [ 'accessor' => 'scope', 'value' => $scope ],
590 + ], // filter
591 + [ 'accessor' => 'priority', 'by' => 'DESC' ] // sort
592 + )['data'];
528 593
529 - return $this->generate_js_functions_code( $snippets );
530 - }
531 -
532 - function generate_js_functions_code ($snippets ) {
533 - $code = "";
534 - foreach ( $snippets as $snippet ) {
535 - $function_code = $snippet['code'];
536 - $function_info = $snippet['function_info'];
537 594
538 - // Extract function name and arguments
539 - preg_match( '/(?:const|let|var)?\s*(\w+)\s*=\s*\((.*?)\)\s*=>/', $function_code, $matches );
540 - $function_name = $matches[1] ?? $function_info['name'];
541 - $function_args = $matches[2] ?? '';
542 -
543 - // Prepare default values
544 - $default_args = [];
545 - foreach ( $function_info['args'] as $arg ) {
546 - if ( isset( $arg['default'] ) && $arg['default'] !== '' ) {
547 - $default_args[$arg['name']] = $arg['default'];
595 + if ( empty( $snippets ) ) {
596 + return;
548 597 }
549 - }
550 598
551 - // Modify function to use default values
552 - if ( !empty( $default_args ) ) {
553 - $new_args = explode( ',', $function_args );
554 - foreach ( $new_args as &$arg ) {
555 - $arg = trim( $arg );
556 - if ( isset( $default_args[$arg] ) ) {
557 - $arg .= " = " . json_encode( $default_args[$arg] );
558 - }
559 - }
560 - $new_args_string = implode( ', ', $new_args );
561 - $function_code = preg_replace(
562 - '/(\w+)\s*=\s*\((.*?)\)\s*=>/',
563 - "$1 = ($new_args_string) =>",
564 - $function_code
565 - );
566 - }
599 + $snippets = array_map( function ( $snippet ) use ( $blocked ) {
600 + $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
601 + $snippet['blocked'] = $blocked;
567 602
568 - $code .= $function_code . "\n\n";
569 - }
603 + // If the snippet must be executed only in the frontend, we bypass the block
604 + if ( !is_admin() && $snippet['scope'] === 'frontend' ) {
605 + $snippet['blocked'] = false;
606 + }
570 607
571 - return $code;
572 - }
608 + return $snippet;
609 + }, $snippets );
573 610
611 +
574 612
575 - /**
576 - * [STATIC] Execute active snippets.
577 - *
578 - * @return array
579 - */
580 - public function execute_active_snippets() {
581 -
582 - $blocked = false;
583 - $page = isset( $_GET["page"] ) ? sanitize_text_field( $_GET["page"] ) : null;
584 -
585 -
586 - if ( $page === 'mwcode_settings' ) {
587 - // If we blocks global snippets like nonce_life filter, we would block the settings page so let's remove the block for this page
588 -
589 - $blocked = false;
590 - //$blocked = true;
613 + return $snippets;
591 614 }
592 - // Block REST requests that aren't whitelisted
593 - elseif ( MeowCommon_Helpers::is_rest() && !Meow_MWCODE_Core::is_white_listed_rest() ) {
594 - $blocked = true;
595 - }
596 615
597 - if ( empty( $this->snippet ) ) {
598 - $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
599 - }
600 616
601 - $ts = $this->get_option( 'thrown_snippet', null );
602 - if ( !empty( $ts ) ) {
603 - $this->log( "⚠️ Your snippet \"{$ts['name']}\" has thrown a fatal error last time, so we disabled it. Please check the logs for more information." );
604 - $this->snippet->force_disable( $ts['id'] );
605 - $this->update_option( 'thrown_snippet', null );
606 - }
617 + #endregion
607 618
608 - $scope = is_admin() ? [ 'backend', 'persistent' ] : [ 'frontend', 'persistent' ];
609 - // Get all active snippets
619 + #reion Shortcodes
610 620
611 - $snippets = $this->snippet->select(
612 - null, // offset
613 - -1, // limit
614 - [
615 - [ 'accessor' => 'active', 'value' => 1 ],
616 - [ 'accessor' => 'scope', 'value' => $scope ],
617 - ], // filter
618 - [ 'accessor' => 'priority', 'by' => 'DESC' ] // sort
619 - )['data'];
621 + function content_shortcode( $atts ) {
620 622
621 - if ( empty( $snippets ) ) {
622 - return;
623 - }
623 + $atts = shortcode_atts( array(
624 + 'id' => null,
625 + 'target' => null,
626 + 'code' => null,
627 + ), $atts );
624 628
625 - $snippets = array_map( function ( $snippet ) use ( $blocked ) {
626 - $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
627 - $snippet['blocked'] = $blocked;
629 + $id = $atts['id'];
630 + $target = $atts['target'];
631 + $code = $atts['code'];
628 632
629 - // If the snippet must be executed only in the frontend, we bypass the block
630 - if ( !is_admin() && $snippet['scope'] === 'frontend' ) {
631 - $snippet['blocked'] = false;
632 - }
633 + // If the ID is null, it means it comes from a Guttenberg block
634 + $is_block = empty( $id ) && !empty( $code );
635 + if( $is_block ){
633 636
634 - return $snippet;
635 - }, $snippets );
637 + // Because the code from Blocks are sanitized, we need to replace the &quot; with "
638 + $code = str_replace( '&quot;', '"', $code );
636 639
637 - return $snippets;
638 - }
640 + if ( $target === 'js' ) {
641 + $output = '<script>' . $code . '</script>';
642 + }
643 +
644 + if ( $target === 'php' ) {
645 + $output = $this->run_non_fn_snippet( null, $code );
646 + }
647 +
648 + return $output;
649 + }
639 650
651 + // If the ID is not null, it means it comes from a shortcode
652 + if ( empty( $id ) && empty( $code ) ) {
653 + return '<b>Code Engine:</b> Please provide a snippet ID.';
654 + }
640 655
641 - #endregion
656 + $snippet = $this->get_snippet( $id );
642 657
643 - #region Shortcodes
658 + if ( empty( $snippet ) ) {
659 + return '<b>Code Engine:</b> The snippet does not exist.';
660 + }
644 661
645 - function content_shortcode( $atts ) {
662 + //Check if the snippet scope is either content_php or content_js
663 + $is_content_php = $snippet['scope'] === 'content_php';
664 + $is_content_js = $snippet['scope'] === 'content_js';
646 665
647 - $atts = shortcode_atts( array(
648 - 'id' => null,
649 - 'target' => null,
650 - 'code' => null,
651 - ), $atts );
666 + if ( !$is_content_php && !$is_content_js ) {
667 + return '<b>Code Engine:</b> The snippet is not a content snippet.';
668 + }
652 669
653 - $id = $atts['id'];
654 - $target = $atts['target'];
655 - $code = $atts['code'];
656 -
657 - $no_js = defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML;
658 - $no_php = $this->get_option( 'disallow_block_php', true );
659 -
660 - // If the ID is null, it means it comes from a Guttenberg block
661 - $is_block = empty( $id ) && !empty( $code );
670 + //Check if the snippet is active
671 + if ( !$snippet['active'] ) {
672 + return '<b>Code Engine:</b> The snippet is not active.';
673 + }
662 674
663 - if( $is_block ) {
675 + $output = '<b>Code Engine:</b> No output.';
664 676
665 - if( $target !== 'js' && $target !== 'php' ) {
666 - return '<b>Code Engine:</b> Please provide a valid target (js or php).';
667 - }
677 + if ( $is_content_js ) {
678 + $output = '<script>' . $snippet['code'] . '</script>';
679 + }
668 680
669 - if ( $no_js && $target === 'js' ) {
670 - return '<b>Code Engine:</b> Code Block JS are disabled because unfiltered HTML is not allowed on your server.';
671 - }
681 + if ( $is_content_php ) {
682 + $output = $this->run_non_fn_snippet( $id );
683 + }
672 684
673 - if ( $no_php && $target === 'php' ) {
674 - return '<b>Code Engine:</b> Code Block PHP are disabled. If you are an administrator, you can enable it in the settings, this is not recommended. Please use a Content Snippet ( PHP ) instead.';
675 - }
685 + return $output;
686 + }
676 687
677 - // Because the code from Blocks are sanitized, we need to replace the &quot; with "
678 - $code = str_replace( '&quot;', '"', $code );
688 + #endregion
679 689
680 - if ( $target === 'js' ) {
681 - $output = '<script>' . $code . '</script>';
682 - }
690 + #region Logs
683 691
684 - if ( $target === 'php' ) {
685 - $output = $this->run_non_fn_snippet( null, $code );
686 - }
692 + function get_logs() {
693 + $log_file_path = $this->get_logs_path();
687 694
688 - return $output;
689 - }
695 + if ( !file_exists( $log_file_path ) ) {
696 + return "Empty log file.";
697 + }
690 698
691 - // If not a block, we get the snippet by ID
692 - // If the ID is not null, it means it comes from a shortcode
693 - if ( empty( $id ) && empty( $code ) ) {
694 - return '<b>Code Engine:</b> Please provide a snippet ID.';
695 - }
699 + $content = file_get_contents( $log_file_path );
700 + $lines = explode( "\n", $content );
701 + $lines = array_filter( $lines );
702 + $lines = array_reverse( $lines );
703 + $content = implode( "\n", $lines );
704 + return $content;
705 + }
696 706
697 - $snippet = $this->get_snippet( $id );
707 + function clear_logs() {
708 + $logPath = $this->get_logs_path();
709 + if ( file_exists( $logPath ) ) {
710 + unlink( $logPath );
711 + }
698 712
699 - if ( empty( $snippet ) ) {
700 - return '<b>Code Engine:</b> The snippet does not exist.';
701 - }
713 + $options = $this->get_all_options();
714 + $options['logs_path'] = null;
715 + $this->update_options( $options );
716 + }
702 717
703 - //Check if the snippet scope is either content_php or content_js
704 - $is_content_php = $snippet['scope'] === 'content_php';
705 - $is_content_js = $snippet['scope'] === 'content_js';
718 + function get_logs_path() {
719 + $uploads_dir = wp_upload_dir();
720 + $uploads_dir_path = trailingslashit( $uploads_dir['basedir'] );
706 721
707 - if ( !$is_content_php && !$is_content_js ) {
708 - return '<b>Code Engine:</b> The snippet is not a content snippet.';
709 - }
722 + $path = $this->get_option( 'logs_path' );
710 723
711 - if( $no_js && $is_content_js ) {
712 - return '<b>Code Engine:</b> Code Engine JS snippets are disabled because unfiltered HTML is not allowed on your server.';
713 - }
724 + if ( $path && file_exists( $path ) ) {
725 + // make sure the path is legal (within the uploads directory with the MWCODE_PREFIX and log extension)
726 + if ( strpos( $path, $uploads_dir_path ) !== 0 || strpos( $path, MWCODE_PREFIX ) === false || substr( $path, -4 ) !== '.log' ) {
727 + $path = null;
728 + } else {
729 + return $path;
730 + }
731 + }
714 732
715 - //Check if the snippet is active
716 - if ( !$snippet['active'] ) {
717 - return '<b>Code Engine:</b> The snippet is not active.';
718 - }
733 + if ( !$path ) {
734 + $path = $uploads_dir_path . MWCODE_PREFIX . "_" . $this->random_ascii_chars() . ".log";
735 + if ( !file_exists( $path ) ) {
736 + touch( $path );
737 + }
738 + $options = $this->get_all_options();
739 + $options['logs_path'] = $path;
740 + $this->update_options( $options );
741 + }
719 742
720 - $output = '<b>Code Engine:</b> No output.';
743 + return $path;
744 + }
721 745
722 - if ( $is_content_js ) {
723 - $output = '<script>' . $snippet['code'] . '</script>';
724 - }
746 + function log( $data = null ) {
747 + if ( !$this->get_option( 'server_debug_mode', false ) ) { return false; }
748 + $log_file_path = $this->get_logs_path();
749 + $fh = @fopen( $log_file_path, 'a' );
750 + if ( !$fh ) { return false; }
751 + $date = date( "Y-m-d H:i:s" );
752 + if ( is_null( $data ) ) {
753 + fwrite( $fh, "\n" );
754 + }
755 + else {
756 + fwrite( $fh, "$date: {$data}\n" );
757 + //$this->log( "[MWCODE] $data" );
758 + }
759 + fclose( $fh );
760 + return true;
761 + }
725 762
726 - if ( $is_content_php ) {
727 - $output = $this->run_non_fn_snippet( $id );
728 - }
763 + private function random_ascii_chars( $length = 8 ) {
764 + $characters = array_merge( range( 'A', 'Z' ), range( 'a', 'z' ), range( '0', '9' ) );
765 + $characters_length = count( $characters );
766 + $random_string = '';
729 767
730 - return $output;
731 - }
768 + for ( $i = 0; $i < $length; $i++ ) {
769 + $random_string .= $characters[rand(0, $characters_length - 1)];
770 + }
732 771
733 - #endregion
772 + return $random_string;
773 + }
734 774
735 - #region Logs
775 + #endregion
736 776
737 - function get_logs() {
738 - $log_file_path = $this->get_logs_path();
777 + #region Helpers
739 778
740 - if ( !file_exists( $log_file_path ) ) {
741 - return "Empty log file.";
742 - }
779 + /**
780 + * Check if the request is from a white-listed REST route.
781 + *
782 + * @return bool
783 + */
784 + public static function is_white_listed_rest() {
785 + $authorized = false;
786 + $white_listed = array(
787 + 'mwai/v1',
788 + 'mwai-ui/v1',
789 + 'media-file-renamer/v1',
790 + 'media-cleaner/v1',
791 + 'wplr/v1',
792 + 'code-engine/v1',
793 + 'wp/v2',
794 + 'meow-gallery/v1',
795 + );
743 796
744 - $content = file_get_contents( $log_file_path );
745 - $lines = explode( "\n", $content );
746 - $lines = array_filter( $lines );
747 - $lines = array_reverse( $lines );
748 - $content = implode( "\n", $lines );
749 - return $content;
750 - }
797 + $white_listed = apply_filters( 'meow_mwcode_white_listed_rest', $white_listed );
751 798
752 - function clear_logs() {
753 - $logPath = $this->get_logs_path();
754 - if ( file_exists( $logPath ) ) {
755 - unlink( $logPath );
756 - }
799 + $route = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : null;
800 + $requested_route = null;
801 +
802 + if ( $route ) {
803 + $route_parts = explode( '/wp-json/', $route );
804 +
805 + if ( isset( $route_parts[1] ) ) {
806 + $requested_route = trim( $route_parts[1], '/' );
807 + foreach ( $white_listed as $white_listed_route ) {
808 + if ( strpos( $requested_route, $white_listed_route ) === 0 ) {
809 + $authorized = true;
810 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
811 + return $authorized;
812 + }
813 + }
814 + }
815 +
816 + if ( is_admin() ) {
817 + $authorized = true;
757 818
758 - $options = $this->get_all_options();
759 - $options['logs_path'] = null;
760 - $this->update_options( $options );
761 - }
819 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
820 + return $authorized;
821 + }
762 822
763 - function get_logs_path() {
764 - $uploads_dir = wp_upload_dir();
765 - $uploads_dir_path = trailingslashit( $uploads_dir['basedir'] );
766 823
767 - $path = $this->get_option( 'logs_path' );
824 + }
768 825
769 - if ( $path && file_exists( $path ) ) {
770 - // make sure the path is legal (within the uploads directory with the MWCODE_PREFIX and log extension)
771 - if ( strpos( $path, $uploads_dir_path ) !== 0 || strpos( $path, MWCODE_PREFIX ) === false || substr( $path, -4 ) !== '.log' ) {
772 - $path = null;
773 - } else {
774 - return $path;
775 - }
826 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
827 + return $authorized;
776 828 }
777 829
778 - if ( !$path ) {
779 - $path = $uploads_dir_path . MWCODE_PREFIX . "_" . $this->random_ascii_chars() . ".log";
780 - if ( !file_exists( $path ) ) {
781 - touch( $path );
782 - }
783 - $options = $this->get_all_options();
784 - $options['logs_path'] = $path;
785 - $this->update_options( $options );
786 - }
787 -
788 - return $path;
789 - }
790 -
791 - function log( $data = null ) {
792 - if ( !$this->get_option( 'server_debug_mode', false ) ) { return false; }
793 - $log_file_path = $this->get_logs_path();
794 - $fh = @fopen( $log_file_path, 'a' );
795 - if ( !$fh ) { return false; }
796 - $date = date( "Y-m-d H:i:s" );
797 - if ( is_null( $data ) ) {
798 - fwrite( $fh, "\n" );
799 - }
800 - else {
801 - fwrite( $fh, "$date: {$data}\n" );
802 - //$this->log( "[MWCODE] $data" );
803 - }
804 - fclose( $fh );
805 - return true;
806 - }
807 -
808 - private function random_ascii_chars( $length = 8 ) {
809 - $characters = array_merge( range( 'A', 'Z' ), range( 'a', 'z' ), range( '0', '9' ) );
810 - $characters_length = count( $characters );
811 - $random_string = '';
812 -
813 - for ( $i = 0; $i < $length; $i++ ) {
814 - $random_string .= $characters[rand(0, $characters_length - 1)];
815 - }
816 -
817 - return $random_string;
818 - }
819 -
820 - #endregion
821 -
822 - #region Helpers
823 -
824 - /**
825 - * Check if the request is from a white-listed REST route.
826 - *
827 - * @return bool
828 - */
829 - public static function is_white_listed_rest() {
830 - $options = get_option( 'mwcode_snippet_vault_options', array() );
831 -
832 - // Early return if bypass is enabled
833 - if ( !empty( $options['bypass_rest_security'] ) ) {
834 - return true;
835 - }
836 -
837 - // Early return for admin requests
838 - if ( is_admin() ) {
839 - return apply_filters( 'mwcode_rest_authorized', true, null );
840 - }
841 -
842 - // Get the requested route
843 - $requested_route = self::get_requested_rest_route();
844 - if ( !$requested_route ) {
845 - return apply_filters( 'mwcode_rest_authorized', false, null );
846 - }
847 -
848 - // Check against whitelist
849 - $white_listed = apply_filters( 'mwcode_rest_whitelist', array(
850 - 'mwai/v1',
851 - 'mwai-ui/v1',
852 - 'media-file-renamer/v1',
853 - 'media-cleaner/v1',
854 - 'wplr/v1',
855 - 'code-engine/v1',
856 - 'wp/v2',
857 - 'meow-gallery/v1',
858 - 'mcp/v1',
859 - ));
860 -
861 - $authorized = self::is_route_whitelisted( $requested_route, $white_listed );
862 -
863 - // Log if debug mode is enabled
864 - if ( !empty( $options['server_debug_mode'] ) ) {
865 - self::log_route_status( $requested_route, $authorized );
866 - }
867 -
868 - return apply_filters( 'mwcode_rest_authorized', $authorized, $requested_route );
869 - }
870 -
871 - /**
872 - * Extract the REST route from the request URI.
873 - *
874 - * @return string|null
875 - */
876 - public static function get_requested_rest_route() {
877 - if ( !isset( $_SERVER['REQUEST_URI'] ) ) {
878 - return null;
879 - }
880 -
881 - $route_parts = explode( '/wp-json/', $_SERVER['REQUEST_URI'] );
882 -
883 - if ( isset( $route_parts[1] ) ) {
884 - return trim( $route_parts[1], '/' );
885 - }
886 -
887 - return null;
888 - }
889 -
890 - /**
891 - * Check if a route is in the whitelist.
892 - *
893 - * @param string $route The route to check
894 - * @param array $white_listed The whitelist array
895 - * @return bool
896 - */
897 - private static function is_route_whitelisted( $route, $white_listed ) {
898 - foreach ( $white_listed as $white_listed_route ) {
899 - if ( strpos( $route, $white_listed_route ) === 0 ) {
900 - return true;
901 - }
902 - }
903 - return false;
904 - }
905 -
906 - /**
907 - * Log the route authorization status.
908 - *
909 - * @param string $route The route being checked
910 - * @param bool $authorized Whether the route is authorized
911 - */
912 - private static function log_route_status( $route, $authorized ) {
913 - global $mwcode_core;
914 -
915 - $message = $authorized
916 - ? "✅ REST route authorized: " . $route
917 - : "❌ REST route rejected (not whitelisted): " . $route;
918 -
919 - if ( isset( $mwcode_core ) ) {
920 - $mwcode_core->log( $message );
921 - } else {
922 - error_log( "[Code Engine] " . $message );
923 - }
924 - }
925 -
926 - #endregion
830 + #endregion
927 831 }
928 832
929 833 ?>