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 +747 -179 0.0.20.3.0 View file →
@@ -1,265 +1,833 @@
1 1 <?php
2 2
3 -if ( ! defined( 'ABSPATH' ) ) exit;
3 +require_once ( MWCODE_PATH . '/vendor/autoload.php' );
4 +use PhpParser\ParserFactory;
5 +use PhpParser\NodeDumper;
6 +use PhpParser\Error;
4 7
5 -require_once( CDEGN_PATH . '/constants/init.php' );
6 -class Meow_CDEGN_Core
8 +class Meow_MWCODE_Core
7 9 {
8 10 public $admin = null;
11 + public $snippet = null;
12 + public $is_rest = false;
9 13 public $is_cli = false;
10 14 public $site_url = null;
11 - private $option_name = 'cdegn_options';
15 + public $mwcode = null;
12 16
13 - /** @var Meow_CDEGN_Snippets_Code_Snippets */
14 - protected $snippets_code_snippets = null;
17 + private $option_name = 'mwcode_options';
15 18
16 - /** @var Meow_CDEGN_Snippets_Wpcode */
17 - protected $snippets_wpcode = null;
18 -
19 19 public function __construct() {
20 + global $mwcode;
21 +
20 22 $this->site_url = get_site_url();
23 + $this->is_rest = MeowCommon_Helpers::is_rest();
21 24 $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
25 +
26 + // Snippets
27 + $snippet = new Meow_MWCODE_Modules_Snippet( $this );
28 + $this->snippet = $snippet;
29 +
30 + // Create API before plugins_loaded
31 + $this->mwcode = new Meow_MWCODE_API( $this, $snippet );
32 + $mwcode = $this->mwcode;
33 +
34 + // Add the shortcode for the "content" snippets
35 + add_shortcode( 'code-engine', [ $this, 'content_shortcode' ] );
36 +
22 37 add_action( 'plugins_loaded', array( $this, 'init' ) );
23 -
24 - $this->snippets_code_snippets = new Meow_CDEGN_Snippets_Code_Snippets();
25 - $this->snippets_wpcode = new Meow_CDEGN_Snippets_Wpcode();
26 38 }
27 39
28 - public function init() {
40 + function init() {
29 41 // Part of the core, settings and stuff
30 - $this->admin = new Meow_CDEGN_Admin( $this );
31 - new Meow_CDEGN_Rest( $this );
42 + $this->admin = new Meow_MWCODE_Admin( $this );
43 +
44 + // Only for REST
45 + if ( $this->is_rest ) {
46 + new Meow_MWCODE_Rest( $this, $this->admin, $this->snippet );
47 + }
32 48 }
33 49
34 - #region Code Snippets
35 50
36 51 /**
37 - * Add the new option to the cdegn_functions option.
38 52 *
39 - * @param array $new_option
40 - * @return void
53 + * Roles & Access Rights
54 + *
41 55 */
42 - public function add_cdegn_functions( array $new_option ): void {
43 - $cdegn_functions = get_option( 'cdegn_functions', [] );
56 + #region Roles & Access Rights
57 + public function can_access_settings() {
58 + return apply_filters( 'mwcode_allow_setup', current_user_can( 'manage_options' ) );
59 + }
44 60
45 - // Replace the option if it already exists, otherwise add it
46 - $index = $this->cdegn_functions_search( $new_option, $cdegn_functions );
47 - if ( $index === false ) {
48 - $cdegn_functions[] = $new_option;
61 + public function can_access_features() {
62 + return apply_filters( 'mwcode_allow_usage', current_user_can( 'administrator' ) );
63 + }
64 +
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
70 +
71 + #region Options
72 +
73 + function get_option( $option, $default = null ) {
74 + $options = $this->get_all_options();
75 + return $options[$option] ?? $default;
76 + }
77 +
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,
86 +
87 + //UI
88 + "ui_show_preview" => true,
89 +
90 + //AI
91 + "ai_suggestions" => false,
92 + "ai_engine_status"=> false,
93 + "ai_engine_message" => "",
94 +
95 + //API
96 + "api_endpoint" => false,
97 + "api_token" => md5( time() . rand() ),
98 + ];
99 + }
100 +
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 + }
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.');
49 113 } else {
50 - $cdegn_functions[$index] = $new_option;
114 + if ( !update_option( $this->option_name, $options, false ) ) {
115 + $this->log( '💾 There was an issue updating the options.' );
116 + }
51 117 }
118 +
119 + $options = $this->sanitize_options( $options );
120 + return $options;
121 + }
52 122
53 - update_option( 'cdegn_functions', $cdegn_functions );
123 + function update_option( $option, $value ) {
124 + $options = $this->get_all_options();
125 + $options[$option] = $value;
126 + return $this->update_options( $options );
54 127 }
55 128
56 - /**
57 - * Remove the option in the cdegn_functions option.
58 - *
59 - * @param int $snippet_id
60 - * @param string $snippet_src
61 - * @return void
62 - */
63 - public function remove_cdegn_functions( int $snippet_id, string $snippet_src ): void {
64 - $cdegn_functions = get_option( 'cdegn_functions', [] );
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 + }
65 135
66 - // Remove the option if it exists, otherwise do nothing
67 - $index = $this->cdegn_functions_search( [ 'snippet_id' => $snippet_id, 'snippet_src' => $snippet_src ], $cdegn_functions );
68 - if ( $index === false ) {
69 - return;
136 + // Validate and keep the options clean and logical.
137 + function sanitize_options( $options ) {
138 + $options_modified = false;
139 +
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;
70 144 }
71 145
72 - unset( $cdegn_functions[$index] );
73 - $cdegn_functions = array_values( $cdegn_functions );
74 - update_option( 'cdegn_functions', $cdegn_functions );
146 + // Update AI Engine status
147 + $options_modified = $this->updateAIEngineStatus( $options ) || $options_modified;
148 +
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 + }
154 +
155 + if ( $options_modified ) {
156 + update_option( $this->option_name, $options, false );
157 + }
158 +
159 + return $options;
75 160 }
76 161
77 - /**
78 - * Searches the cdegn_functions array for a given cdegn_function.
79 - * and returns the first corresponding key if successful.
80 - *
81 - * @param array $needle_cdegn_function
82 - * @param array $haystack_cdegn_functions
83 - * @return int|false
84 - */
85 - protected function cdegn_functions_search( array $needle_cdegn_function, array $haystack_cdegn_functions ) {
86 - $index = false;
87 - foreach ( $haystack_cdegn_functions as $key => $cdegn_function ) {
88 - if (
89 - $cdegn_function['snippet_id'] === $needle_cdegn_function['snippet_id']
90 - && $cdegn_function['snippet_src'] === $needle_cdegn_function['snippet_src']
91 - ) {
92 - $index = $key;
93 - break;
162 + private function updateAIEngineStatus( &$options ) {
163 + global $mwai;
164 +
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 + }
170 +
171 + try {
172 + $status = $mwai->checkStatus();
173 +
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;
94 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 + }
95 185 }
96 - return $index;
186 +
187 + return false;
97 188 }
98 189
190 + // #endregion
191 +
192 + #region Snippets
193 +
99 194 /**
100 - * Analyze a code snippet.
101 - *
102 - * @param int $snippet_id
103 - * @param string $snippet_src
104 - * @return array
105 - */
106 - public function analyze_code( int $snippet_id, string $snippet_src ): array {
107 - $this->check_snippet_src_availability( $snippet_src );
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 + }
108 204
109 - $snippet = $this->get_snippet( $snippet_id, $snippet_src );
110 - return $this->parse_snippet_code( $snippet->code() );
205 + return $this->snippet->select_one( $id );
206 + }
207 +
208 + function add_snippet( $params ) {
209 +
210 + $response = [
211 + "snippet" => null,
212 + "result" => false,
213 + ];
214 +
215 + $this->snippet->validate( $params );
216 +
217 + $params = $this->snippet->formatParamsForDatabase( $params );
218 + $result = $this->snippet->insert( $params );
219 + $snippet = $this->snippet->select_one( $result );
220 +
221 + if( $result ) {
222 + $params['id'] = (string)$result;
223 +
224 + $this->snippet->create_or_update_function_snippet( $params );
225 + $this->snippet->create_or_update_interval_snippet( $params );
226 +
227 + $this->snippet->get_function_snippets_data( $snippet );
228 + }
229 +
230 + $response['snippet'] = $snippet;
231 + $response['result'] = $result;
232 +
233 + return $response;
111 234 }
112 235
113 - /**
114 - * Get a specific snippet.
115 - *
116 - * @param int $snippet_id
117 - * @param string $snippet_src
118 - * @return Meow_CDEGN_Models_Snippet
119 - */
120 - public function get_snippet( int $snippet_id, string $snippet_src ): Meow_CDEGN_Models_Snippet {
121 - switch ( $snippet_src ) {
122 - case Meow_CDEGN_Snippets_Code_Snippets::$src:
123 - return $this->snippets_code_snippets->getById( $snippet_id );
124 - case Meow_CDEGN_Snippets_Wpcode::$src:
125 - return $this->snippets_wpcode->getById( $snippet_id );
236 + private function sanitize_arg( $name, $value, $type = null) {
237 + $real_type = gettype( $value );
238 +
239 + if ( $name[0] !== '$' ) { $name = '$' . $name; }
240 +
241 + if ( $type == null ) {
242 + $type = $real_type;
126 243 }
127 - throw new Exception( __( 'Unsupported source selected.', 'code-engine' ) );
244 +
245 + if ( $type != 'array' && !empty( $value ) && !is_numeric( $value ) && $value[0] !== '"' && $value[strlen( $value ) - 1] !== '"' ) {
246 + $value = '"' . esc_sql( $value ) . '"';
247 + }
248 +
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 + }
258 +
259 + if ( $type === 'array' ) {
260 + $value = json_encode( $value );
261 + $value = str_replace( '\\', '', $value );
262 + }
263 +
264 + return [ $name, $value ];
128 265 }
129 266
130 - /**
131 - * Parse a snippet code.
132 - *
133 - * @param string $code
134 - * @return array
135 - */
136 - protected function parse_snippet_code( string $code ): array {
137 - $pattern = '/function\s+(\w+)\s*\((.*?)\)/';
138 - if ( preg_match_all( $pattern, $code, $matches ) !== 1 ) {
139 - throw new Exception( __( 'Input must contain exactly one function definition.', 'code-engine') );
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 );
140 273 }
274 +
275 + // Remove any PHP opening tag.
276 + $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
277 +
141 278
142 - $function_name = $matches[1][0];
143 - $arg_string = $matches[2][0];
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 + }
144 312
145 - $args = [];
146 - if ( $arg_string ) {
147 - $arg_parts = explode( ',', $arg_string );
148 - foreach ( $arg_parts as $arg_part ) {
149 - $arg_part = trim( $arg_part );
150 - if ( strpos( $arg_part, '=' ) !== false ) {
151 - list( $argName, $argDefault ) = explode( '=', $arg_part, 2 );
152 - $args[ trim( $argName )] = [
153 - 'default' => trim( $argDefault )
154 - ];
155 - } else {
156 - $args[ trim( $arg_part )] = [];
313 + function run_snippet( $id, $args = [], $params = [] )
314 + {
315 + // Static array to track defined functions
316 + static $defined_functions = array();
317 +
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
321 +
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 + }
331 +
332 + // Sanitize all the arguments if the option is enabled
333 + if ( $this->get_option( 'sanitize_arguments', true ) ) {
334 +
335 + if ( $args ) {
336 + foreach ( $args as $name => $value ) {
337 + list( $sanitizedName, $sanitizedValue ) = $this->sanitize_arg( $name, $value, $value['type'] );
338 + unset( $args[$name] );
339 +
340 + $args[$sanitizedName] = $sanitizedValue;
157 341 }
158 342 }
343 +
344 + foreach ( $params['values'] as $name => $value ) {
345 +
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 + }
356 +
159 357 }
160 358
161 - return [
162 - 'function_name' => $function_name,
163 - 'args' => $args
164 - ];
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 + }
367 +
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 + }
373 +
374 + $this->log( '⚡ Arguments provided: ' . json_encode( $args ) );
375 + }
376 +
377 + // Check if the function has already been defined
378 + if ( !in_array( $params['name'], $defined_functions ) ) {
379 +
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 + }
386 +
387 + $params['code'] = "if (!function_exists('{$params['name']}')) {\n" . $params['code'] . "\n}\n";
388 +
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 + }
395 +
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
400 +
401 + if ( array_key_exists( $arg, $params['values'] ) ) { // Avoid warnings if the argument is not provided
402 +
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'];
406 +
407 + } else if ( !empty( $params['values'][$arg]['default'] ) ) {
408 + $value = $params['values'][$arg]['default'];
409 + }
410 + }
411 +
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);";
418 +
419 + $error = null;
420 + $output = null;
421 +
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());
434 +
435 + ob_clean();
436 + } finally {
437 + restore_error_handler();
438 + }
439 +
440 + if ( $error !== null ) {
441 + if( $params['test'] ){
442 + $output['error'] = $error->getMessage();
443 + } else {
444 + throw $error;
445 + }
446 + }
447 +
448 + return $output;
165 449 }
166 450
167 - /**
168 - * Get the code quality of a code snippet via the AI Engine.
169 - * Throws an exception if the AI Engine is not installed.
170 - *
171 - * @param int $snippet_id
172 - * @param string $snippet_src
173 - * @return string
174 - * @throws Exception
175 - */
176 - public function get_code_quality_via_ai_engine( int $snippet_id, string $snippet_src ): string {
177 - $this->check_snippet_src_availability( $snippet_src );
178 451
179 - global $mwai;
180 - if ( !isset( $mwai ) ) {
181 - throw new Exception( __( 'AI Engine is not installed. Please download it <a href="https://wordpress.org/plugins/ai-engine/" target="_blank">here</a>.', 'code-engine' ) );
452 + function parse_snippet( $code, $new_snippet = false ){
453 + $parser = ( new ParserFactory( ) )->createForNewestSupportedVersion( );
454 +
455 + if( !$this->snippet ){
456 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
182 457 }
183 458
184 - $snippet = $this->get_snippet( $snippet_id, $snippet_src );
185 - $result = $mwai->simpleTextQuery( "Please check this code, in terms of quality, and security. List the potential issues. Each issue should be on one line, use a line return between each issue. Keep the issues and explanations short. Maximum of 10 issues. Here is the code:\n\n" . $snippet->code() );
186 - return nl2br( $result );
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'] ) {
462 +
463 + $lint = [
464 + 'line' => 1,
465 + 'attributes' => $fn['attributes'][0],
466 + 'raw_message' => implode(', ', $fn['errors'][0]),
467 + 'message' => implode(', ', $fn['errors'][0]),
468 + ];
469 +
470 + return $lint;
471 + }
472 +
473 + try {
474 + $stmts = $parser->parse( $code );
475 + $result = $stmts;
476 + } catch ( PhpParser\Error $e ) {
477 +
478 + $lint = [
479 + 'line' => $e->getStartLine(),
480 + 'attributes' => $e->getAttributes(),
481 + 'raw_message' => $e->getRawMessage(),
482 + 'message' => $e->getMessage(),
483 + ];
484 +
485 + return $lint;
486 + }
487 +
488 + return null;
187 489 }
188 490
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 + }
550 +
551 + return $code;
552 + }
553 +
554 +
189 555 /**
190 - * Check the snippet source availability.
191 - * Throws an exception if the snippet source is not supported.
192 - *
193 - * @param string $snippet_src
194 - * @return void
195 - * @throws Exception
196 - */
197 - protected function check_snippet_src_availability( string $snippet_src ): void {
198 - $support_srcs = [
199 - Meow_CDEGN_Snippets_Code_Snippets::$src,
200 - Meow_CDEGN_Snippets_Wpcode::$src,
201 - ];
202 - if ( !in_array( $snippet_src, $support_srcs, true ) ) {
203 - throw new Exception( __( 'Only Code Snippets and WPCode are supported for now.', 'code-engine' ) );
556 + * [STATIC] Execute active snippets.
557 + *
558 + * @return array
559 + */
560 + public function execute_active_snippets() {
561 +
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;
204 566 }
205 - }
206 567
568 + if ( empty( $this->snippet ) ) {
569 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
570 + }
571 +
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 + }
578 +
579 + $scope = is_admin() ? [ 'backend', 'persistent' ] : [ 'frontend', 'persistent' ];
580 + // Get all active snippets
581 +
582 +
583 +
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'];
593 +
594 +
595 + if ( empty( $snippets ) ) {
596 + return;
597 + }
598 +
599 + $snippets = array_map( function ( $snippet ) use ( $blocked ) {
600 + $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
601 + $snippet['blocked'] = $blocked;
602 +
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 + }
607 +
608 + return $snippet;
609 + }, $snippets );
610 +
611 +
612 +
613 + return $snippets;
614 + }
615 +
616 +
207 617 #endregion
208 618
209 - #region Capabilities
619 + #reion Shortcodes
210 620
211 - public function can_access_settings() {
212 - return apply_filters( 'cdegn_allow_setup', current_user_can( 'manage_options' ) );
213 - }
621 + function content_shortcode( $atts ) {
214 622
215 - public function can_access_features() {
216 - return apply_filters( 'cdegn_allow_usage', current_user_can( 'administrator' ) );
623 + $atts = shortcode_atts( array(
624 + 'id' => null,
625 + 'target' => null,
626 + 'code' => null,
627 + ), $atts );
628 +
629 + $id = $atts['id'];
630 + $target = $atts['target'];
631 + $code = $atts['code'];
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 ){
636 +
637 + // Because the code from Blocks are sanitized, we need to replace the &quot; with "
638 + $code = str_replace( '&quot;', '"', $code );
639 +
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 + }
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 + }
655 +
656 + $snippet = $this->get_snippet( $id );
657 +
658 + if ( empty( $snippet ) ) {
659 + return '<b>Code Engine:</b> The snippet does not exist.';
660 + }
661 +
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';
665 +
666 + if ( !$is_content_php && !$is_content_js ) {
667 + return '<b>Code Engine:</b> The snippet is not a content snippet.';
668 + }
669 +
670 + //Check if the snippet is active
671 + if ( !$snippet['active'] ) {
672 + return '<b>Code Engine:</b> The snippet is not active.';
673 + }
674 +
675 + $output = '<b>Code Engine:</b> No output.';
676 +
677 + if ( $is_content_js ) {
678 + $output = '<script>' . $snippet['code'] . '</script>';
679 + }
680 +
681 + if ( $is_content_php ) {
682 + $output = $this->run_non_fn_snippet( $id );
683 + }
684 +
685 + return $output;
217 686 }
218 687
219 688 #endregion
220 689
221 - #region Options
690 + #region Logs
222 691
223 - public function get_option( $option, $default = null ) {
692 + function get_logs() {
693 + $log_file_path = $this->get_logs_path();
694 +
695 + if ( !file_exists( $log_file_path ) ) {
696 + return "Empty log file.";
697 + }
698 +
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 + }
706 +
707 + function clear_logs() {
708 + $logPath = $this->get_logs_path();
709 + if ( file_exists( $logPath ) ) {
710 + unlink( $logPath );
711 + }
712 +
224 713 $options = $this->get_all_options();
225 - return $options[$option] ?? $default;
714 + $options['logs_path'] = null;
715 + $this->update_options( $options );
226 716 }
227 717
228 - public function get_all_options( $force = false ) {
229 - // We could cache options this way, but if we do, the apply_filters seems to be called too early.
230 - // That causes issues with filters used to modify the options dynamically (in AI Engine, for example).
231 - // if ( !$force && !is_null( $this->options ) ) {
232 - // return $this->options;
233 - // }
234 - $options = get_option( $this->option_name, [] );
235 - foreach ( CDEGN_OPTIONS as $key => $value ) {
236 - if ( !isset( $options[$key] ) ) {
237 - $options[$key] = $value;
718 + function get_logs_path() {
719 + $uploads_dir = wp_upload_dir();
720 + $uploads_dir_path = trailingslashit( $uploads_dir['basedir'] );
721 +
722 + $path = $this->get_option( 'logs_path' );
723 +
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;
238 730 }
239 731 }
240 - return $options;
732 +
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 + }
742 +
743 + return $path;
241 744 }
242 745
243 - public function update_options( $options ) {
244 - if ( !update_option( $this->option_name, $options, false ) ) {
245 - return false;
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" );
246 754 }
247 - $options = $this->get_all_options( true );
248 - return $options;
755 + else {
756 + fwrite( $fh, "$date: {$data}\n" );
757 + //$this->log( "[MWCODE] $data" );
758 + }
759 + fclose( $fh );
760 + return true;
249 761 }
250 762
251 - public function update_option( $option, $value ) {
252 - $options = $this->get_all_options( true );
253 - $options[$option] = $value;
254 - return $this->update_options( $options );
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 = '';
767 +
768 + for ( $i = 0; $i < $length; $i++ ) {
769 + $random_string .= $characters[rand(0, $characters_length - 1)];
770 + }
771 +
772 + return $random_string;
255 773 }
256 774
257 - public function reset_options() {
258 - delete_option( $this->option_name );
259 - return $this->get_all_options();
260 - }
775 + #endregion
261 776
777 + #region Helpers
778 +
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 + );
796 +
797 + $white_listed = apply_filters( 'meow_mwcode_white_listed_rest', $white_listed );
798 +
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;
818 +
819 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
820 + return $authorized;
821 + }
822 +
823 +
824 + }
825 +
826 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
827 + return $authorized;
828 + }
829 +
262 830 #endregion
263 831 }
264 832
265 -?>
833 +?>