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 +182 -51 0.3.70.4.8 View file →
@@ -14,8 +14,12 @@
14 14 public $site_url = null;
15 15 public $mwcode = null;
16 16 public $licenser = null;
17 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 +
18 22 private $option_name = 'mwcode_options';
19 23
20 24 public function __construct() {
21 25 global $mwcode;
@@ -20,9 +24,9 @@
20 24 public function __construct() {
21 25 global $mwcode;
22 26
23 27 $this->site_url = get_site_url();
24 - $this->is_rest = MeowCommon_Helpers::is_rest();
28 + $this->is_rest = MeowKit_MWCODE_Helpers::is_rest();
25 29 $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
26 30
27 31 // Snippets
28 32 $snippet = new Meow_MWCODE_Modules_Snippet( $this );
@@ -39,10 +43,10 @@
39 43 }
40 44
41 45 function init() {
42 46 // Initialize the licenser for Pro version
43 - if ( class_exists( 'MeowCommonPro_Licenser' ) ) {
44 - $this->licenser = new MeowCommonPro_Licenser( MWCODE_PREFIX, MWCODE_ENTRY, MWCODE_DOMAIN, MWCODE_ITEM_ID, MWCODE_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 );
45 49 }
46 50
47 51 // Part of the core, settings and stuff
48 52 $this->admin = new Meow_MWCODE_Admin( $this );
@@ -110,8 +114,11 @@
110 114 "api_token" => md5( time() . rand() ),
111 115
112 116 //MCP
113 117 "mcp_support" => false,
118 +
119 + //MAINTENANCE
120 + "clean_uninstall" => false,
114 121 ];
115 122 }
116 123
117 124 function get_all_options( ) {
@@ -125,19 +132,15 @@
125 132 return $options;
126 133 }
127 134
128 135 function update_options( $options ) {
129 - $current_options = get_option($this->option_name);
130 136
131 - if ($current_options === $options) {
132 - // $this->log('💾 The options are already the expected value.');
133 - } else {
134 - if ( !update_option( $this->option_name, $options, false ) ) {
135 - $this->log( '💾 There was an issue updating the options.' );
136 - }
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.' );
137 141 }
138 -
139 - $options = $this->sanitize_options( $options );
142 +
140 143 return $options;
141 144 }
142 145
143 146 function update_option( $option, $value ) {
@@ -168,9 +171,9 @@
168 171 $options_modified = true;
169 172 }
170 173
171 174 // Update AI Engine status
172 - $options_modified = $this->updateAIEngineStatus( $options ) || $options_modified;
175 + $options = $this->updateAIEngineStatus( $options );
173 176
174 177 // Disable AI related features if AI Engine is not available
175 178 if ( ! $options['ai_engine_status'] ) {
176 179 if ( $options['ai_suggestions'] !== false ) {
@@ -180,12 +183,8 @@
180 183 // Note: We don't disable MCP support here anymore
181 184 // It will be checked at runtime in the MCP class
182 185 }
183 186
184 - if ( $options_modified ) {
185 - update_option( $this->option_name, $options, false );
186 - }
187 -
188 187 return $options;
189 188 }
190 189
191 190 private function updateAIEngineStatus( &$options ) {
@@ -190,31 +189,13 @@
190 189
191 190 private function updateAIEngineStatus( &$options ) {
192 191 global $mwai;
193 192
194 - if ( is_null( $mwai ) || ! isset( $mwai ) ) {
195 - $options['ai_engine_status'] = false;
196 - $options['ai_engine_message'] = 'AI Engine is not available.';
197 - return true;
198 - }
193 + $options['mwai_has_ai'] = !empty( $mwai ) && method_exists( $mwai, 'hasAI' ) && $mwai->hasAI();
194 + // Legacy
195 + $options['ai_engine_status'] = $options['mwai_has_ai'];
199 196
200 - try {
201 - $status = $mwai->checkStatus();
202 -
203 - if ( $options['ai_engine_status'] != true || $options['ai_engine_message'] != $status ) {
204 - $options['ai_engine_status'] = true;
205 - $options['ai_engine_message'] = $status;
206 - return true;
207 - }
208 - } catch ( Exception $e ) {
209 - if ( $options['ai_engine_status'] != false || $options['ai_engine_message'] != $e->getMessage() ) {
210 - $options['ai_engine_status'] = false;
211 - $options['ai_engine_message'] = $e->getMessage();
212 - return true;
213 - }
214 - }
215 -
216 - return false;
197 + return $options;
217 198 }
218 199
219 200 #endregion
220 201
@@ -285,16 +266,16 @@
285 266 $value = array_map( 'trim', $value );
286 267 }
287 268
288 269 if ( $type === 'array' ) {
289 - $value = json_encode( $value );
290 - $value = str_replace( '\\', '', $value );
270 + // Convert to PHP array format instead of JSON
271 + $value = var_export( $value, true );
291 272 }
292 273
293 274 return [ $name, $value ];
294 275 }
295 276
296 - function run_non_fn_snippet( $id, $code = null, $test = false ) {
277 + function run_non_fn_snippet( $id, $code = null, $test = false, $prefix = '' ) {
297 278 // Retrieve the snippet code from the provided code or via the snippet ID.
298 279 if ( $code ) {
299 280 $snippet = [ 'code' => $code ];
300 281 } else {
@@ -301,13 +282,17 @@
301 282 $snippet = $this->get_snippet( $id );
302 283 }
303 284
304 285 // Remove any PHP opening tag.
305 - $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
286 + $snippet['code'] = $this->snippet->sanitize_code( $snippet['code'] );
306 287
307 288 if ( $test ) {
308 289 $snippet['code'] = preg_replace( '/echo\s+(.+?);/s', 'echo $1 . "\n";', $snippet['code'] );
309 290 }
291 +
292 + if( $prefix ) {
293 + $snippet['code'] = $prefix . "\n" . $snippet['code'];
294 + }
310 295
311 296 $error = null;
312 297 $output = null;
313 298
@@ -401,8 +386,18 @@
401 386
402 387 $this->log( '⚡ Arguments provided: ' . json_encode( $args ) );
403 388 }
404 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 +
405 400 // Check if the function has already been defined
406 401 if ( !in_array( $params['name'], $defined_functions ) ) {
407 402
408 403 // If not, proceed with modification and definition
@@ -441,13 +436,14 @@
441 436 if ( $index < count( $params['args'] ) - 1 ) {
442 437 $params['code'] .= ', ';
443 438 }
444 439 }
440 +
445 441 $params['code'] .= ");\necho print_r(\$mwcode_result, true);";
446 442
447 443 $error = null;
448 444 $output = null;
449 -
445 +
450 446 try {
451 447 ob_start();
452 448 eval( $params['code'] );
453 449 $output = ob_get_clean();
@@ -515,8 +511,132 @@
515 511
516 512 return null;
517 513 }
518 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 +
519 639 public function get_js_functions_to_push() {
520 640 $functions = $this->snippet->get_functions();
521 641 $js_functions = [];
522 642 foreach ( $functions as &$function ) {
@@ -597,9 +717,9 @@
597 717 $blocked = false;
598 718 //$blocked = true;
599 719 }
600 720 // Block REST requests that aren't whitelisted
601 - 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() ) {
602 722 $blocked = true;
603 723 }
604 724
605 725 if ( empty( $this->snippet ) ) {
@@ -630,9 +750,9 @@
630 750 return;
631 751 }
632 752
633 753 $snippets = array_map( function ( $snippet ) use ( $blocked ) {
634 - $snippet['code'] = preg_replace( '/<\?php/', '', $snippet['code'], 1 );
754 + $snippet['code'] = $this->snippet->sanitize_code( $snippet['code'] );
635 755 $snippet['blocked'] = $blocked;
636 756
637 757 // If the snippet must be executed only in the frontend, we bypass the block
638 758 if ( !is_admin() && $snippet['scope'] === 'frontend' ) {
@@ -648,16 +768,26 @@
648 768
649 769 #endregion
650 770
651 771 #region Shortcodes
772 + function separate_mwcode_atts( $atts ) {
652 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 +
653 781 function content_shortcode( $atts ) {
654 782
783 + $user_atts = $this->separate_mwcode_atts( $atts );
784 +
655 785 $atts = shortcode_atts( array(
656 - 'id' => null,
657 - 'target' => null,
658 - 'code' => null,
659 - ), $atts );
786 + 'id' => null,
787 + 'target' => null, // js or php
788 + 'code' => null, // For Guttenberg block usage
789 + ), $atts, 'code-engine' );
660 790
661 791 $id = $atts['id'];
662 792 $target = $atts['target'];
663 793 $code = $atts['code'];
@@ -740,9 +870,10 @@
740 870 $output = '<script>' . $snippet['code'] . '</script>';
741 871 }
742 872
743 873 if ( $is_content_php ) {
744 - $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 );
745 876 }
746 877
747 878 return $output;
748 879 }