PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.4.8
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.4.8
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 +186 -49 0.3.60.4.8 View file →
@@ -12,9 +12,14 @@
12 12 public $is_rest = false;
13 13 public $is_cli = false;
14 14 public $site_url = null;
15 15 public $mwcode = null;
16 + public $licenser = null;
16 17
18 + // IDs of global snippets already executed this request (by the plugins_loaded pass
19 + // or by load_global_snippets), so a global never runs twice and never re-declares.
20 + public $loaded_global_ids = [];
21 +
17 22 private $option_name = 'mwcode_options';
18 23
19 24 public function __construct() {
20 25 global $mwcode;
@@ -19,9 +24,9 @@
19 24 public function __construct() {
20 25 global $mwcode;
21 26
22 27 $this->site_url = get_site_url();
23 - $this->is_rest = MeowCommon_Helpers::is_rest();
28 + $this->is_rest = MeowKit_MWCODE_Helpers::is_rest();
24 29 $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
25 30
26 31 // Snippets
27 32 $snippet = new Meow_MWCODE_Modules_Snippet( $this );
@@ -37,8 +42,13 @@
37 42 add_action( 'plugins_loaded', array( $this, 'init' ) );
38 43 }
39 44
40 45 function init() {
46 + // Initialize the licenser for Pro version
47 + if ( class_exists( 'MeowKitPro_MWCODE_Licenser' ) ) {
48 + $this->licenser = new MeowKitPro_MWCODE_Licenser( MWCODE_PREFIX, MWCODE_ENTRY, MWCODE_DOMAIN, MWCODE_ITEM_ID, MWCODE_VERSION );
49 + }
50 +
41 51 // Part of the core, settings and stuff
42 52 $this->admin = new Meow_MWCODE_Admin( $this );
43 53
44 54 // Only for REST
@@ -104,8 +114,11 @@
104 114 "api_token" => md5( time() . rand() ),
105 115
106 116 //MCP
107 117 "mcp_support" => false,
118 +
119 + //MAINTENANCE
120 + "clean_uninstall" => false,
108 121 ];
109 122 }
110 123
111 124 function get_all_options( ) {
@@ -119,19 +132,15 @@
119 132 return $options;
120 133 }
121 134
122 135 function update_options( $options ) {
123 - $current_options = get_option($this->option_name);
124 136
125 - if ($current_options === $options) {
126 - // $this->log('💾 The options are already the expected value.');
127 - } else {
128 - if ( !update_option( $this->option_name, $options, false ) ) {
129 - $this->log( '💾 There was an issue updating the options.' );
130 - }
137 + $options = $this->sanitize_options( $options );
138 +
139 + if ( !update_option( $this->option_name, $options, false ) ) {
140 + //$this->log( '💾 There was an issue updating the options.' );
131 141 }
132 -
133 - $options = $this->sanitize_options( $options );
142 +
134 143 return $options;
135 144 }
136 145
137 146 function update_option( $option, $value ) {
@@ -162,9 +171,9 @@
162 171 $options_modified = true;
163 172 }
164 173
165 174 // Update AI Engine status
166 - $options_modified = $this->updateAIEngineStatus( $options ) || $options_modified;
175 + $options = $this->updateAIEngineStatus( $options );
167 176
168 177 // Disable AI related features if AI Engine is not available
169 178 if ( ! $options['ai_engine_status'] ) {
170 179 if ( $options['ai_suggestions'] !== false ) {
@@ -174,12 +183,8 @@
174 183 // Note: We don't disable MCP support here anymore
175 184 // It will be checked at runtime in the MCP class
176 185 }
177 186
178 - if ( $options_modified ) {
179 - update_option( $this->option_name, $options, false );
180 - }
181 -
182 187 return $options;
183 188 }
184 189
185 190 private function updateAIEngineStatus( &$options ) {
@@ -184,31 +189,13 @@
184 189
185 190 private function updateAIEngineStatus( &$options ) {
186 191 global $mwai;
187 192
188 - if ( is_null( $mwai ) || ! isset( $mwai ) ) {
189 - $options['ai_engine_status'] = false;
190 - $options['ai_engine_message'] = 'AI Engine is not available.';
191 - return true;
192 - }
193 + $options['mwai_has_ai'] = !empty( $mwai ) && method_exists( $mwai, 'hasAI' ) && $mwai->hasAI();
194 + // Legacy
195 + $options['ai_engine_status'] = $options['mwai_has_ai'];
193 196
194 - try {
195 - $status = $mwai->checkStatus();
196 -
197 - if ( $options['ai_engine_status'] != true || $options['ai_engine_message'] != $status ) {
198 - $options['ai_engine_status'] = true;
199 - $options['ai_engine_message'] = $status;
200 - return true;
201 - }
202 - } catch ( Exception $e ) {
203 - if ( $options['ai_engine_status'] != false || $options['ai_engine_message'] != $e->getMessage() ) {
204 - $options['ai_engine_status'] = false;
205 - $options['ai_engine_message'] = $e->getMessage();
206 - return true;
207 - }
208 - }
209 -
210 - return false;
197 + return $options;
211 198 }
212 199
213 200 #endregion
214 201
@@ -279,16 +266,16 @@
279 266 $value = array_map( 'trim', $value );
280 267 }
281 268
282 269 if ( $type === 'array' ) {
283 - $value = json_encode( $value );
284 - $value = str_replace( '\\', '', $value );
270 + // Convert to PHP array format instead of JSON
271 + $value = var_export( $value, true );
285 272 }
286 273
287 274 return [ $name, $value ];
288 275 }
289 276
290 - function run_non_fn_snippet( $id, $code = null, $test = false ) {
277 + function run_non_fn_snippet( $id, $code = null, $test = false, $prefix = '' ) {
291 278 // Retrieve the snippet code from the provided code or via the snippet ID.
292 279 if ( $code ) {
293 280 $snippet = [ 'code' => $code ];
294 281 } else {
@@ -295,13 +282,17 @@
295 282 $snippet = $this->get_snippet( $id );
296 283 }
297 284
298 285 // Remove any PHP opening tag.
299 - $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
286 + $snippet['code'] = $this->snippet->sanitize_code( $snippet['code'] );
300 287
301 288 if ( $test ) {
302 289 $snippet['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $snippet['code'] );
303 290 }
291 +
292 + if( $prefix ) {
293 + $snippet['code'] = $prefix . "\n" . $snippet['code'];
294 + }
304 295
305 296 $error = null;
306 297 $output = null;
307 298
@@ -395,8 +386,18 @@
395 386
396 387 $this->log( '⚡ Arguments provided: ' . json_encode( $args ) );
397 388 }
398 389
390 + // Global snippets are meant to be always accessible. On non-whitelisted REST routes
391 + // (Workflow Engine, MCP, AI function-calling) the plugins_loaded pass blocks them, so
392 + // make sure their helper library is loaded before we run a function that may call it.
393 + $this->load_global_snippets();
394 +
395 + // Make every *other* active PHP function snippet available so this function can
396 + // call its siblings. We pass the current name as the exception so the target is
397 + // still defined below (with the edited/test code when testing), not pre-defined here.
398 + $this->define_all_functions( $params['name'] );
399 +
399 400 // Check if the function has already been defined
400 401 if ( !in_array( $params['name'], $defined_functions ) ) {
401 402
402 403 // If not, proceed with modification and definition
@@ -435,13 +436,14 @@
435 436 if ( $index < count( $params['args'] ) - 1 ) {
436 437 $params['code'] .= ', ';
437 438 }
438 439 }
440 +
439 441 $params['code'] .= ");\necho print_r(\$mwcode_result, true);";
440 442
441 443 $error = null;
442 444 $output = null;
443 -
445 +
444 446 try {
445 447 ob_start();
446 448 eval( $params['code'] );
447 449 $output = ob_get_clean();
@@ -509,8 +511,132 @@
509 511
510 512 return null;
511 513 }
512 514
515 + /**
516 + * Load the active global snippets (persistent + backend/frontend for this context)
517 + * that haven't already run this request, so on-demand function execution has the same
518 + * always-available helper library a normal page load would. Callable functions are
519 + * typically small wrappers around these globals.
520 + *
521 + * On non-whitelisted REST routes (Workflow Engine, MCP, AI function-calling) the
522 + * plugins_loaded pass blocks global snippets for safety; this restores them for the
523 + * deliberate, authorized act of executing a snippet. The loaded-id registry guarantees
524 + * each global runs at most once per request, so nothing is ever re-declared.
525 + */
526 + function load_global_snippets() {
527 + global $current_mwcode_snippet;
528 + static $done = false;
529 + if ( $done ) {
530 + return;
531 + }
532 + $done = true;
533 +
534 + if ( empty( $this->snippet ) ) {
535 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
536 + }
537 +
538 + $scope = is_admin() ? [ 'backend', 'persistent' ] : [ 'frontend', 'persistent' ];
539 +
540 + $snippets = $this->snippet->select(
541 + null, // offset
542 + -1, // limit (all)
543 + [
544 + [ 'accessor' => 'active', 'value' => 1 ],
545 + [ 'accessor' => 'scope', 'value' => $scope ],
546 + ],
547 + [ 'accessor' => 'priority', 'by' => 'DESC' ]
548 + )['data'] ?? [];
549 +
550 + foreach ( $snippets as $snippet ) {
551 + // Skip globals already executed this request (e.g. by the plugins_loaded pass).
552 + if ( in_array( $snippet['id'], $this->loaded_global_ids ) ) {
553 + continue;
554 + }
555 + $this->loaded_global_ids[] = $snippet['id'];
556 +
557 + $code = $this->snippet->sanitize_code( $snippet['code'] );
558 + $current_mwcode_snippet = $snippet;
559 + try {
560 + ob_start();
561 + eval( $code );
562 + ob_end_clean();
563 + } catch ( Throwable $e ) {
564 + ob_end_clean();
565 + $this->log( "⚠️ Code Engine: Failed to load global snippet \"{$snippet['name']}\": " . $e->getMessage() );
566 + }
567 + }
568 + $current_mwcode_snippet = null;
569 + }
570 +
571 + /**
572 + * Declare every active PHP function snippet in the current request, without
573 + * invoking any of them, so function snippets can call one another.
574 + *
575 + * Function snippets are not auto-loaded on every request (unlike global/backend/
576 + * frontend scopes) — they are meant to run on demand. This is the PHP counterpart
577 + * to get_js_functions_to_push(): it makes the whole library of functions callable
578 + * before a function is executed (via REST, MCP, AI function-calling, Workflow Engine).
579 + *
580 + * Idempotent: a static guard runs the full pass only once per request, and each
581 + * definition is wrapped in function_exists() so nothing is ever redefined.
582 + *
583 + * @param string|null $except Function name to skip (the one run_snippet is about to
584 + * define itself, so edited/test code keeps priority).
585 + */
586 + function define_all_functions( $except = null ) {
587 + static $loaded = false;
588 + if ( $loaded ) {
589 + return;
590 + }
591 + $loaded = true;
592 +
593 + if ( empty( $this->snippet ) ) {
594 + $this->snippet = new Meow_MWCODE_Modules_Snippet( $this );
595 + }
596 +
597 + // One query for every active function snippet (code included), then enrich with
598 + // the function metadata (name + target) the same way run_snippet does.
599 + $snippets = $this->snippet->select(
600 + null, // offset
601 + -1, // limit (all)
602 + [
603 + [ 'accessor' => 'active', 'value' => 1 ],
604 + [ 'accessor' => 'scope', 'value' => 'function' ],
605 + ],
606 + [] // sort
607 + )['data'] ?? [];
608 +
609 + if ( empty( $snippets ) ) {
610 + return;
611 + }
612 +
613 + $this->snippet->get_function_snippets_data( $snippets );
614 +
615 + foreach ( $snippets as $snippet ) {
616 + $name = $snippet['functionName'] ?? '';
617 + $target = strtolower( $snippet['functionTarget'] ?? 'php' );
618 +
619 + // Skip JS functions (pushed to the front-end separately), the function the
620 + // caller will define itself, and anything already declared in this request.
621 + if ( $name === '' || $target === 'js' || $name === $except || function_exists( $name ) ) {
622 + continue;
623 + }
624 +
625 + // Mirror run_snippet()'s non-test handling: drop echo statements, then declare
626 + // (never call) the function, guarded so a later run_snippet() call is a no-op.
627 + $code = $this->snippet->sanitize_code( $snippet['code'] );
628 + $code = preg_replace( '/echo\s+(.+?);/s', '', $code );
629 + $code = "if (!function_exists('{$name}')) {\n{$code}\n}\n";
630 +
631 + try {
632 + eval( $code );
633 + } catch ( Throwable $e ) {
634 + $this->log( "⚠️ Code Engine: Failed to pre-define function \"{$name}\": " . $e->getMessage() );
635 + }
636 + }
637 + }
638 +
513 639 public function get_js_functions_to_push() {
514 640 $functions = $this->snippet->get_functions();
515 641 $js_functions = [];
516 642 foreach ( $functions as &$function ) {
@@ -591,9 +717,9 @@
591 717 $blocked = false;
592 718 //$blocked = true;
593 719 }
594 720 // Block REST requests that aren't whitelisted
595 - elseif ( MeowCommon_Helpers::is_rest() && !Meow_MWCODE_Core::is_white_listed_rest() ) {
721 + elseif ( MeowKit_MWCODE_Helpers::is_rest() && !Meow_MWCODE_Core::is_white_listed_rest() ) {
596 722 $blocked = true;
597 723 }
598 724
599 725 if ( empty( $this->snippet ) ) {
@@ -624,9 +750,9 @@
624 750 return;
625 751 }
626 752
627 753 $snippets = array_map( function ( $snippet ) use ( $blocked ) {
628 - $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
754 + $snippet['code'] = $this->snippet->sanitize_code( $snippet['code'] );
629 755 $snippet['blocked'] = $blocked;
630 756
631 757 // If the snippet must be executed only in the frontend, we bypass the block
632 758 if ( !is_admin() && $snippet['scope'] === 'frontend' ) {
@@ -642,16 +768,26 @@
642 768
643 769 #endregion
644 770
645 771 #region Shortcodes
772 + function separate_mwcode_atts( $atts ) {
646 773
774 + if( array_key_exists( 'id', $atts ) ) unset( $atts['id'] );
775 + if( array_key_exists( 'target', $atts ) ) unset( $atts['target'] );
776 + if( array_key_exists( 'code', $atts ) ) unset( $atts['code'] );
777 +
778 + return $atts;
779 + }
780 +
647 781 function content_shortcode( $atts ) {
648 782
783 + $user_atts = $this->separate_mwcode_atts( $atts );
784 +
649 785 $atts = shortcode_atts( array(
650 - 'id' => null,
651 - 'target' => null,
652 - 'code' => null,
653 - ), $atts );
786 + 'id' => null,
787 + 'target' => null, // js or php
788 + 'code' => null, // For Guttenberg block usage
789 + ), $atts, 'code-engine' );
654 790
655 791 $id = $atts['id'];
656 792 $target = $atts['target'];
657 793 $code = $atts['code'];
@@ -734,9 +870,10 @@
734 870 $output = '<script>' . $snippet['code'] . '</script>';
735 871 }
736 872
737 873 if ( $is_content_php ) {
738 - $output = $this->run_non_fn_snippet( $id );
874 + $prefix = "\$mwcode_atts = unserialize( '" . serialize( $user_atts ) . "' );";
875 + $output = $this->run_non_fn_snippet( $id, null, false, $prefix );
739 876 }
740 877
741 878 return $output;
742 879 }