PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.2.9
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.2.9
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 +675 -179 0.0.10.2.9 View file →
@@ -1,265 +1,761 @@
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 + private function sanitize_arg( $name, $value ) {
209 + if ( $name[0] !== '$' ) { $name = '$' . $name; }
210 +
211 + if ( !empty( $value ) && !is_numeric( $value ) && $value[0] !== '"' && $value[strlen( $value ) - 1] !== '"' ) {
212 + $value = '"' . esc_sql( $value ) . '"';
213 + }
214 +
215 + return [ $name, $value ];
111 216 }
112 217
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 );
218 + function run_non_fn_snippet( $id, $code = null, $test = false ) {
219 + // Retrieve the snippet code from the provided code or via the snippet ID.
220 + if ( $code ) {
221 + $snippet = [ 'code' => $code ];
222 + } else {
223 + $snippet = $this->get_snippet( $id );
126 224 }
127 - throw new Exception( __( 'Unsupported source selected.', 'code-engine' ) );
225 +
226 + // Remove any PHP opening tag.
227 + $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
228 +
229 +
230 + if ( $test ) {
231 + $snippet['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $snippet['code'] );
232 + }
233 +
234 + $error = null;
235 + $output = null;
236 +
237 + try {
238 + ob_start();
239 + eval( $snippet['code'] );
240 + $output = ob_get_clean();
241 + } catch ( Throwable $e ) {
242 + $error = new Exception( 'Error executing the snippet, ' . $e->getMessage() );
243 + ob_clean();
244 + } finally {
245 + restore_error_handler();
246 + }
247 +
248 + // If in test mode, return output as an array of lines with an 'error' key if needed.
249 + if ( $test ) {
250 + $output = explode( "\n", trim( $output ) );
251 + if ( $error !== null ) {
252 + $output['error'] = $error->getMessage();
253 + }
254 + } else {
255 + if ( $error !== null ) {
256 + throw $error;
257 + }
258 + }
259 +
260 + return $output;
128 261 }
129 262
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') );
263 + function run_snippet( $id, $args = [], $params = [] )
264 + {
265 + // Static array to track defined functions
266 + static $defined_functions = array();
267 +
268 + if ( $id ) { // If there is an ID, we get the snippet, if not we get the data from the params
269 + $snippet = $this->get_snippet( $id );
270 + $this->snippet->get_function_snippets_data( $snippet ); // adds the function data to the snippet
271 +
272 + $params = [ // We set the params according to the snippet we fetched
273 + 'test' => false, // If we pass an ID to the function, we are not testing the snippet
274 + // 'test' => $params['test'] ?? false if needed we can still use ID and test at the same time (should not happen)
275 + 'code' => $snippet['code'],
276 + 'name' => $snippet['functionName'],
277 + 'args' => $snippet['functionArgs'],
278 + 'values' => $snippet['functionArgsDict'] // Contains the default values of the arguments
279 + ];
140 280 }
141 281
142 - $function_name = $matches[1][0];
143 - $arg_string = $matches[2][0];
282 + // Sanitize all the arguments if the option is enabled
283 + if ( $this->get_option( 'sanitize_arguments', true ) ) {
144 284
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 )] = [];
285 + if ( $args ) {
286 + foreach ( $args as $name => $value ) {
287 + list( $sanitizedName, $sanitizedValue ) = $this->sanitize_arg( $name, $value );
288 + unset( $args[$name] );
289 +
290 + $args[$sanitizedName] = $sanitizedValue;
157 291 }
158 292 }
293 +
294 + foreach ( $params['values'] as $name => $value ) {
295 +
296 + if( array_key_exists( 'input', $value) ) {
297 + list( $sanitizedInputName, $sanitizedInputValue ) = $this->sanitize_arg( $name, $value['input'] );
298 + $params['values'][$sanitizedInputName]['input'] = $sanitizedInputValue;
299 + }
300 +
301 + if( array_key_exists( 'default', $value) ) {
302 + list( $sanitizedDefaultValueName, $sanitizedDefaultValue ) = $this->sanitize_arg( $name, $value['default'] );
303 + $params['values'][$sanitizedDefaultValueName]['default'] = $sanitizedDefaultValue;
304 + }
305 + }
306 +
159 307 }
160 308
161 - return [
162 - 'function_name' => $function_name,
163 - 'args' => $args
164 - ];
309 +
310 +
311 + // Make sure the function is existing and is the one in the snippet
312 + if ( empty( $params['code'] ) ) {
313 + throw new Exception( 'Code Engine: The snippet code appears to be empty.' );
314 + }
315 +
316 + if ( empty( $params['name'] ) || ! str_contains( $params['code'], $params['name'] ) ) {
317 + throw new Exception( "Code Engine: Function name does not match. The name should be {$params['name']}." );
318 + }
319 +
320 + // Overwrite the default values with the provided ones
321 + if ( $args ) {
322 + foreach ( $args as $name => $value ) {
323 + $params['values'][$name]['input'] = $value;
324 + }
325 +
326 + $this->log( '⚡ Arguments provided: ' . json_encode( $args ) );
327 + }
328 +
329 + // Check if the function has already been defined
330 + if ( !in_array( $params['name'], $defined_functions ) ) {
331 +
332 + // If not, proceed with modification and definition
333 + if ( $params['test'] ) { // Make sure the echo statement uses a line break
334 + $params['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $params['code'] );
335 + } else { // Remove all echo statements
336 + $params['code'] = preg_replace( '/echo\s+(.+?);/s', '', $params['code'] );
337 + }
338 +
339 + $params['code'] = "if (!function_exists('{$params['name']}')) {\n" . $params['code'] . "\n}\n";
340 +
341 + // Add the function name to the array to avoid redefinition
342 + $defined_functions[] = $params['name'];
343 + } else {
344 + // If already defined, just prepare to call the function without redefining it
345 + $params['code'] = '';
346 + }
347 +
348 + // Prepare the code to be executed
349 + $params['code'] .= "\n\$mwcode_result = {$params['name']}(";
350 + foreach ( $params['args'] as $index => $arg ) {
351 + $value = 'null'; // In case the argument is not provided it will be null
352 +
353 + if ( array_key_exists( $arg, $params['values'] ) ) { // Avoid warnings if the argument is not provided
354 +
355 + // If the argument is provided, use it, if not use the default value
356 + if ( !empty( $params['values'][$arg]['input'] ) ) {
357 + $value = $params['values'][$arg]['input'];
358 +
359 + } else if ( !empty( $params['values'][$arg]['default'] ) ) {
360 + $value = $params['values'][$arg]['default'];
361 + }
362 + }
363 +
364 + $params['code'] .= "{$value}";
365 + if ( $index < count( $params['args'] ) - 1 ) {
366 + $params['code'] .= ', ';
367 + }
368 + }
369 + $params['code'] .= ");\necho print_r(\$mwcode_result, true);";
370 +
371 + $error = null;
372 + $output = null;
373 +
374 + try {
375 + ob_start();
376 + eval( $params['code'] );
377 + $output = ob_get_clean();
378 +
379 + if ( $params['test'] ){
380 + $output = explode( "\n", $output );
381 + }
382 +
383 + } catch ( Throwable $e ) {
384 + //$this->log('Code Engine: Error executing the function: ' . $e->getMessage());
385 + $error = new Exception(' Error executing the function, ' . $e->getMessage());
386 +
387 + ob_clean();
388 + } finally {
389 + restore_error_handler();
390 + }
391 +
392 + if ( $error !== null ) {
393 + if( $params['test'] ){
394 + $output['error'] = $error->getMessage();
395 + } else {
396 + throw $error;
397 + }
398 + }
399 +
400 + return $output;
165 401 }
166 402
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 403
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' ) );
404 + function parse_snippet( $code, $new_snippet = false ){
405 + $parser = ( new ParserFactory( ) )->createForNewestSupportedVersion( );
406 +
407 + if( !$this->snippet ){
408 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
182 409 }
183 410
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 );
411 + // First we check the function names are unique
412 + $fn = $this->snippet->sanitize_and_check_functions( $code, $new_snippet );
413 + if ( ! $fn['is_valid'] ) {
414 +
415 + $lint = [
416 + 'line' => 1,
417 + 'attributes' => $fn['attributes'][0],
418 + 'raw_message' => implode(', ', $fn['errors'][0]),
419 + 'message' => implode(', ', $fn['errors'][0]),
420 + ];
421 +
422 + return $lint;
423 + }
424 +
425 + try {
426 + $stmts = $parser->parse( $code );
427 + $result = $stmts;
428 + } catch ( PhpParser\Error $e ) {
429 +
430 + $lint = [
431 + 'line' => $e->getStartLine(),
432 + 'attributes' => $e->getAttributes(),
433 + 'raw_message' => $e->getRawMessage(),
434 + 'message' => $e->getMessage(),
435 + ];
436 +
437 + return $lint;
438 + }
439 +
440 + return null;
187 441 }
188 442
443 + public function get_js_functions_to_push() {
444 + $functions = $this->snippet->get_functions();
445 + $js_functions = [];
446 + foreach ( $functions as &$function ) {
447 + if ( !isset( $function['target'] ) ) {
448 + $function['target'] = 'php';
449 + }
450 + if ( $function['target'] == 'js' ) {
451 + $js_functions[] = $function;
452 + }
453 + }
454 + $snippets = [];
455 + foreach ( $js_functions as $function ) {
456 + $snippet = $this->snippet->select_one( $function['snippetId'] );
457 + $snippet['function_info'] = $function; // Add function info to snippet
458 + $snippets[] = $snippet;
459 + }
460 +
461 + return $this->generate_js_functions_code( $snippets );
462 + }
463 +
464 + function generate_js_functions_code ($snippets ) {
465 + $code = "";
466 + foreach ( $snippets as $snippet ) {
467 + $function_code = $snippet['code'];
468 + $function_info = $snippet['function_info'];
469 +
470 + // Extract function name and arguments
471 + preg_match( '/(?:const|let|var)?\s*(\w+)\s*=\s*\((.*?)\)\s*=>/', $function_code, $matches );
472 + $function_name = $matches[1] ?? $function_info['name'];
473 + $function_args = $matches[2] ?? '';
474 +
475 + // Prepare default values
476 + $default_args = [];
477 + foreach ( $function_info['args'] as $arg ) {
478 + if ( isset( $arg['default'] ) && $arg['default'] !== '' ) {
479 + $default_args[$arg['name']] = $arg['default'];
480 + }
481 + }
482 +
483 + // Modify function to use default values
484 + if ( !empty( $default_args ) ) {
485 + $new_args = explode( ',', $function_args );
486 + foreach ( $new_args as &$arg ) {
487 + $arg = trim( $arg );
488 + if ( isset( $default_args[$arg] ) ) {
489 + $arg .= " = " . json_encode( $default_args[$arg] );
490 + }
491 + }
492 + $new_args_string = implode( ', ', $new_args );
493 + $function_code = preg_replace(
494 + '/(\w+)\s*=\s*\((.*?)\)\s*=>/',
495 + "$1 = ($new_args_string) =>",
496 + $function_code
497 + );
498 + }
499 +
500 + $code .= $function_code . "\n\n";
501 + }
502 +
503 + return $code;
504 + }
505 +
506 +
189 507 /**
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' ) );
508 + * [STATIC] Execute active snippets.
509 + *
510 + * @return array
511 + */
512 + public function execute_active_snippets() {
513 +
514 + $blocked = false;
515 + $page = isset( $_GET["page"] ) ? sanitize_text_field( $_GET["page"] ) : null;
516 + if ( $page === 'mwcode_settings' || !Meow_MWCODE_Core::is_white_listed_rest() ) {
517 + $blocked = true;
204 518 }
205 - }
206 519
520 + if ( empty( $this->snippet ) ) {
521 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
522 + }
523 +
524 + $ts = $this->get_option( 'thrown_snippet', null );
525 + if ( !empty( $ts ) ) {
526 + $this->log( "⚠️ Your snippet \"{$ts['name']}\" has thrown a fatal error last time, so we disabled it. Please check the logs for more information." );
527 + $this->snippet->force_disable( $ts['id'] );
528 + $this->update_option( 'thrown_snippet', null );
529 + }
530 +
531 + $scope = is_admin() ? [ 'backend', 'persistent' ] : [ 'frontend', 'persistent' ];
532 + // Get all active snippets
533 +
534 +
535 +
536 + $snippets = $this->snippet->select(
537 + null, // offset
538 + -1, // limit
539 + [
540 + [ 'accessor' => 'active', 'value' => 1 ],
541 + [ 'accessor' => 'scope', 'value' => $scope ],
542 + ], // filter
543 + [ 'accessor' => 'priority', 'by' => 'DESC' ] // sort
544 + )['data'];
545 +
546 +
547 + if ( empty( $snippets ) ) {
548 + return;
549 + }
550 +
551 + $snippets = array_map( function ( $snippet ) use ( $blocked ) {
552 + $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
553 + $snippet['blocked'] = $blocked;
554 +
555 + // If the snippet must be executed only in the frontend, we bypass the block
556 + if ( !is_admin() && $snippet['scope'] === 'frontend' ) {
557 + $snippet['blocked'] = false;
558 + }
559 +
560 + return $snippet;
561 + }, $snippets );
562 +
563 +
564 +
565 + return $snippets;
566 + }
567 +
568 +
207 569 #endregion
208 570
209 - #region Capabilities
571 + #reion Shortcodes
210 572
211 - public function can_access_settings() {
212 - return apply_filters( 'cdegn_allow_setup', current_user_can( 'manage_options' ) );
213 - }
573 + function content_shortcode( $atts ) {
214 574
215 - public function can_access_features() {
216 - return apply_filters( 'cdegn_allow_usage', current_user_can( 'administrator' ) );
575 + $atts = shortcode_atts( array(
576 + 'id' => null,
577 + ), $atts );
578 +
579 + $id = $atts['id'];
580 + if ( empty( $id ) ) {
581 + return '<b>Code Engine:</b> Please provide a snippet ID.';
582 + }
583 +
584 + $snippet = $this->get_snippet( $id );
585 +
586 + if ( empty( $snippet ) ) {
587 + return '<b>Code Engine:</b> The snippet does not exist.';
588 + }
589 +
590 + //Check if the snippet scope is either content_php or content_js
591 + $is_content_php = $snippet['scope'] === 'content_php';
592 + $is_content_js = $snippet['scope'] === 'content_js';
593 +
594 + if ( !$is_content_php && !$is_content_js ) {
595 + return '<b>Code Engine:</b> The snippet is not a content snippet.';
596 + }
597 +
598 + //Check if the snippet is active
599 + if ( !$snippet['active'] ) {
600 + return '<b>Code Engine:</b> The snippet is not active.';
601 + }
602 +
603 + $output = '<b>Code Engine:</b> No output.';
604 +
605 + if ( $is_content_js ) {
606 + $output = '<script>' . $snippet['code'] . '</script>';
607 + }
608 +
609 + if ( $is_content_php ) {
610 + $output = $this->run_non_fn_snippet( $id );
611 + }
612 +
613 + return $output;
217 614 }
218 615
219 616 #endregion
220 617
221 - #region Options
618 + #region Logs
222 619
223 - public function get_option( $option, $default = null ) {
620 + function get_logs() {
621 + $log_file_path = $this->get_logs_path();
622 +
623 + if ( !file_exists( $log_file_path ) ) {
624 + return "Empty log file.";
625 + }
626 +
627 + $content = file_get_contents( $log_file_path );
628 + $lines = explode( "\n", $content );
629 + $lines = array_filter( $lines );
630 + $lines = array_reverse( $lines );
631 + $content = implode( "\n", $lines );
632 + return $content;
633 + }
634 +
635 + function clear_logs() {
636 + $logPath = $this->get_logs_path();
637 + if ( file_exists( $logPath ) ) {
638 + unlink( $logPath );
639 + }
640 +
224 641 $options = $this->get_all_options();
225 - return $options[$option] ?? $default;
642 + $options['logs_path'] = null;
643 + $this->update_options( $options );
226 644 }
227 645
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;
646 + function get_logs_path() {
647 + $uploads_dir = wp_upload_dir();
648 + $uploads_dir_path = trailingslashit( $uploads_dir['basedir'] );
649 +
650 + $path = $this->get_option( 'logs_path' );
651 +
652 + if ( $path && file_exists( $path ) ) {
653 + // make sure the path is legal (within the uploads directory with the MWCODE_PREFIX and log extension)
654 + if ( strpos( $path, $uploads_dir_path ) !== 0 || strpos( $path, MWCODE_PREFIX ) === false || substr( $path, -4 ) !== '.log' ) {
655 + $path = null;
656 + } else {
657 + return $path;
238 658 }
239 659 }
240 - return $options;
660 +
661 + if ( !$path ) {
662 + $path = $uploads_dir_path . MWCODE_PREFIX . "_" . $this->random_ascii_chars() . ".log";
663 + if ( !file_exists( $path ) ) {
664 + touch( $path );
665 + }
666 + $options = $this->get_all_options();
667 + $options['logs_path'] = $path;
668 + $this->update_options( $options );
669 + }
670 +
671 + return $path;
241 672 }
242 673
243 - public function update_options( $options ) {
244 - if ( !update_option( $this->option_name, $options, false ) ) {
245 - return false;
674 + function log( $data = null ) {
675 + if ( !$this->get_option( 'server_debug_mode', false ) ) { return false; }
676 + $log_file_path = $this->get_logs_path();
677 + $fh = @fopen( $log_file_path, 'a' );
678 + if ( !$fh ) { return false; }
679 + $date = date( "Y-m-d H:i:s" );
680 + if ( is_null( $data ) ) {
681 + fwrite( $fh, "\n" );
246 682 }
247 - $options = $this->get_all_options( true );
248 - return $options;
683 + else {
684 + fwrite( $fh, "$date: {$data}\n" );
685 + //$this->log( "[MWCODE] $data" );
686 + }
687 + fclose( $fh );
688 + return true;
249 689 }
250 690
251 - public function update_option( $option, $value ) {
252 - $options = $this->get_all_options( true );
253 - $options[$option] = $value;
254 - return $this->update_options( $options );
691 + private function random_ascii_chars( $length = 8 ) {
692 + $characters = array_merge( range( 'A', 'Z' ), range( 'a', 'z' ), range( '0', '9' ) );
693 + $characters_length = count( $characters );
694 + $random_string = '';
695 +
696 + for ( $i = 0; $i < $length; $i++ ) {
697 + $random_string .= $characters[rand(0, $characters_length - 1)];
698 + }
699 +
700 + return $random_string;
255 701 }
256 702
257 - public function reset_options() {
258 - delete_option( $this->option_name );
259 - return $this->get_all_options();
260 - }
703 + #endregion
261 704
705 + #region Helpers
706 +
707 + /**
708 + * Check if the request is from a white-listed REST route.
709 + *
710 + * @return bool
711 + */
712 + public static function is_white_listed_rest() {
713 + $authorized = false;
714 + $white_listed = array(
715 + 'mwai/v1',
716 + 'mwai-ui/v1',
717 + 'media-file-renamer/v1',
718 + 'media-cleaner/v1',
719 + 'wplr/v1',
720 + 'code-engine/v1',
721 + 'wp/v2',
722 + 'meow-gallery/v1',
723 + );
724 +
725 + $white_listed = apply_filters( 'meow_mwcode_white_listed_rest', $white_listed );
726 +
727 + $route = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : null;
728 + $requested_route = null;
729 +
730 + if ( $route ) {
731 + $route_parts = explode( '/wp-json/', $route );
732 +
733 + if ( isset( $route_parts[1] ) ) {
734 + $requested_route = trim( $route_parts[1], '/' );
735 + foreach ( $white_listed as $white_listed_route ) {
736 + if ( strpos( $requested_route, $white_listed_route ) === 0 ) {
737 + $authorized = true;
738 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
739 + return $authorized;
740 + }
741 + }
742 + }
743 +
744 + if ( is_admin() ) {
745 + $authorized = true;
746 +
747 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
748 + return $authorized;
749 + }
750 +
751 +
752 + }
753 +
754 + $authorized = apply_filters( 'meow_mwcode_white_listed_rest_authorized', $authorized, $requested_route );
755 + return $authorized;
756 + }
757 +
262 758 #endregion
263 759 }
264 760
265 -?>
761 +?>