PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.3.7
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.3.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 0.3.8 All 32 releases
code-engine / classes / rest.php

rest.php in Code Engine – PHP Snippets, AI Functions & Automation for WordPress 0.3.7, at classes/rest.php

879 lines 30.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Meow_MWCODE_Rest
4 {
5 private $admin = null;
6 private $core = null;
7 private $snippet = null;
8 private $namespace = 'code-engine/v1';
9
10 private $bearer_token = null;
11
12 public function __construct( $core, $admin, $snippet ) {
13 if ( !current_user_can( 'administrator' ) ) {
14 return;
15 }
16 $this->core = $core;
17 $this->admin = $admin;
18 $this->snippet = $snippet;
19 add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
20 }
21
22 function rest_api_init() {
23 try {
24 #region REST Settings
25 register_rest_route( $this->namespace, '/settings/update', array(
26 'methods' => 'POST',
27 'permission_callback' => array( $this->core, 'can_access_settings' ),
28 'callback' => array( $this, 'rest_settings_update' )
29 ) );
30 register_rest_route( $this->namespace, '/settings/list', array(
31 'methods' => 'GET',
32 'permission_callback' => array( $this->core, 'can_access_settings' ),
33 'callback' => array( $this, 'rest_settings_list' ),
34 ) );
35 register_rest_route( $this->namespace, '/settings/reset', array(
36 'methods' => 'POST',
37 'permission_callback' => array( $this->core, 'can_access_settings' ),
38 'callback' => array( $this, 'rest_settings_reset' ),
39 ) );
40 register_rest_route( $this->namespace, '/settings/functions', array(
41 'methods' => 'GET',
42 'permission_callback' => array( $this->core, 'can_access_settings' ),
43 'callback' => array( $this, 'rest_export_functions' ),
44 ) );
45 register_rest_route( $this->namespace, '/settings/functions/replace', array(
46 'methods' => 'POST',
47 'permission_callback' => array( $this->core, 'can_access_settings' ),
48 'callback' => array( $this, 'rest_replace_functions' ),
49 ) );
50 register_rest_route( $this->namespace, '/settings/functions/raw', array(
51 'methods' => 'GET',
52 'permission_callback' => array( $this->core, 'can_access_settings' ),
53 'callback' => array( $this, 'rest_functions_raw' ),
54 ) );
55 register_rest_route( $this->namespace, '/settings/functions/raw', array(
56 'methods' => 'POST',
57 'permission_callback' => array( $this->core, 'can_access_settings' ),
58 'callback' => array( $this, 'rest_functions_raw_update' ),
59 ) );
60 #endregion
61
62 #region MISC
63 register_rest_route( $this->namespace, '/quick_start', array(
64 'methods' => 'POST',
65 'permission_callback' => array( $this->core, 'can_access_settings' ),
66 'callback' => array( $this, 'rest_quick_start' ),
67 ) );
68
69 register_rest_route( $this->namespace, '/server_clock', array(
70 'methods' => 'GET',
71 'permission_callback' => array( $this->core, 'can_access_settings' ),
72 'callback' => array( $this, 'rest_server_clock' ),
73 ) );
74
75 register_rest_route( $this->namespace, '/fetch_posts', array(
76 'methods' => 'POST',
77 'permission_callback' => array( $this->core, 'can_access_features' ),
78 'callback' => array( $this, 'rest_fetch_posts' ),
79 'args' => array(
80 'search' => array( 'required' => false ),
81 'offset' => array( 'required' => false, 'default' => 0 ),
82 'limit' => array( 'required' => false, 'default' => 10 ),
83 )
84 ) );
85
86 #endregion
87
88 #region REST AI Engine
89 register_rest_route( $this->namespace, '/ai_read_comments', array(
90 'methods' => 'POST',
91 'permission_callback' => array( $this->core, 'can_access_settings' ),
92 'callback' => array( $this, 'rest_ai_read_comments' ),
93 ) );
94 register_rest_route( $this->namespace, '/ai_query', array(
95 'methods' => 'POST',
96 'permission_callback' => array( $this->core, 'can_access_settings' ),
97 'callback' => array( $this, 'rest_ai_query' ),
98 ) );
99 #endregion
100
101 #region REST Run
102 register_rest_route( $this->namespace, '/run/test', [
103 'methods' => 'POST',
104 'callback' => [ $this, 'rest_test' ],
105 'permission_callback' => [ $this->core, 'can_access_settings' ],
106 ] );
107
108 register_rest_route( $this->namespace, '/run/lint', [
109 'methods' => 'POST',
110 'callback' => [ $this, 'rest_lint' ],
111 'permission_callback' => [ $this->core, 'can_access_settings' ],
112 ] );
113 #endregion
114
115 #region REST Snippets
116
117 register_rest_route($this->namespace, '/snippets/list', [
118 'methods' => 'POST',
119 'callback' => [$this, 'rest_list'],
120 'permission_callback' => [$this->core, 'can_access_settings'],
121 ]);
122
123 register_rest_route( $this->namespace, '/snippets/stats', [
124 'methods' => 'GET',
125 'callback' => [$this, 'rest_stats'],
126 'permission_callback' => [$this->core, 'can_access_settings'],
127 ]);
128
129 register_rest_route($this->namespace, '/snippets/tags', [
130 'methods' => 'POST',
131 'callback' => [$this, 'rest_tags'],
132 'permission_callback' => [$this->core, 'can_access_settings'],
133 ]);
134
135 register_rest_route($this->namespace, '/snippets/import', [
136 'methods' => 'GET',
137 'callback' => [$this, 'rest_import'],
138 'permission_callback' => [$this->core, 'can_access_settings'],
139 ]);
140
141 register_rest_route($this->namespace, '/snippets/add', array(
142 'methods' => 'POST',
143 'callback' => array($this, 'rest_add'),
144 'permission_callback' => array($this->core, 'can_access_settings')
145 ));
146
147 register_rest_route($this->namespace, '/snippets/update', array(
148 'methods' => 'POST',
149 'callback' => array($this, 'rest_update'),
150 'permission_callback' => array($this->core, 'can_access_settings')
151 ));
152
153 register_rest_route($this->namespace, '/snippets/delete', array(
154 'methods' => 'POST',
155 'callback' => array($this, 'rest_delete'),
156 'permission_callback' => array($this->core, 'can_access_settings')
157 ));
158
159
160 register_rest_route($this->namespace, '/snippets/replace', array(
161 'methods' => 'POST',
162 'callback' => array($this, 'rest_replace'),
163 'permission_callback' => array($this->core, 'can_access_settings')
164 ));
165
166
167 // SNIPPETS ENDPOINTS
168 $snippets = $this->snippet->select(
169 null, // offset
170 -1, // limit
171 [
172 [ 'accessor' => 'active', 'value' => 1 ],
173 [ 'accessor' => 'endpoint', 'value' => 1 ],
174 ], // filter
175 null, // sort
176 );
177
178 $public_api_enabled = $this->core->get_option( 'api_endpoint', false );
179 if ( (int) $snippets['total'] > 0 && $public_api_enabled ) {
180
181 $this->bearer_token = $this->core->get_option( 'api_token', null );
182 if ( !empty( $this->bearer_token ) ) {
183 add_filter( 'mwcode_allow_public_api', [ $this, 'auth_via_bearer_token' ], 10, 3 );
184 }
185
186 $data = $snippets['data'];
187
188 foreach ( $data as $snippet ) {
189 register_rest_route( $this->namespace, '/snippets-endpoint/' . $snippet['endpoint'], array(
190 'methods' => $snippet['method'] ?: 'POST',
191 'permission_callback' => function( $request ) {
192 return $this->can_access_public_api( 'snippets-endpoint', $request );
193 },
194 'callback' => array( $this, 'rest_call_snippet' ),
195 'args' => array(
196 'id' => array( 'required' => true, 'default' => $snippet['id'] ),
197 ),
198 ) );
199
200 //$this->core->log( "Registered REST API endpoint: /{$this->namespace}/snippets-endpoint/{$snippet['endpoint']} ({$snippet['method']})" );
201 }
202 }
203
204 #endregion
205
206 #region REST Logs
207 register_rest_route( $this->namespace, '/get_logs', array(
208 'methods' => 'GET',
209 'permission_callback' => array( $this->core, 'can_access_features' ),
210 'callback' => array( $this, 'rest_get_logs' )
211 ) );
212 register_rest_route( $this->namespace, '/clear_logs', array(
213 'methods' => 'GET',
214 'permission_callback' => array( $this->core, 'can_access_features' ),
215 'callback' => array( $this, 'rest_clear_logs' )
216 ) );
217
218 #endregion
219 }
220 catch (Exception $e) {
221 var_dump($e);
222 }
223 }
224
225 #region Auth
226
227 public function auth_via_bearer_token( $allow, $feature, $extra ) {
228 if ( !empty( $extra ) && !empty( $extra->get_header( 'Authorization' ) ) ) {
229 $token = $extra->get_header( 'Authorization' );
230 $token = str_replace( 'Bearer ', '', $token );
231 if ( $token === $this->bearer_token ) {
232 $admin = $this->get_admin_user();
233 wp_set_current_user( $admin->ID, $admin->user_login );
234 return true;
235 }
236 }
237 return $allow;
238 }
239
240 function can_access_public_api( $feature, $extra ) {
241 $logged_in = is_user_logged_in();
242 return apply_filters( 'mwcode_allow_public_api', $logged_in, $feature, $extra );
243 }
244
245 function get_admin_user() {
246 $admin = get_users( [ 'role' => 'administrator' ] );
247 if ( !empty( $admin ) ) {
248 return $admin[0];
249 }
250 return null;
251 }
252
253 #endregion
254
255 #region Logs
256
257 function rest_get_logs() {
258 $logs = $this->core->get_logs();
259 return new WP_REST_Response( [ 'success' => true, 'data' => $logs ], 200 );
260 }
261
262 function rest_clear_logs() {
263 $this->core->clear_logs();
264 return new WP_REST_Response( [ 'success' => true ], 200 );
265 }
266
267
268 #endregion
269
270 #region Settings
271
272 function rest_settings_list() {
273 return new WP_REST_Response( [
274 'success' => true,
275 'options' => $this->core->get_all_options()
276 ], 200 );
277 }
278
279
280 function rest_settings_update( $request ) {
281 try {
282 $params = $request->get_json_params();
283 $value = $params['options'];
284 $options = $this->core->update_options( $value );
285 $success = !!$options;
286 $message = __( $success ? 'OK' : "Could not update options.", 'code-engine' );
287 return new WP_REST_Response([ 'success' => $success, 'message' => $message, 'options' => $options ], 200 );
288 }
289 catch ( Exception $e ) {
290 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
291 }
292 }
293
294 function rest_settings_reset() {
295 try {
296 $options = $this->core->reset_options();
297 $success = !!$options;
298 $message = __( $success ? 'OK' : "Could not reset options.", 'code-engine' );
299 return new WP_REST_Response([ 'success' => $success, 'message' => $message, 'options' => $options ], 200 );
300 }
301 catch ( Exception $e ) {
302 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
303 }
304 }
305
306 function rest_export_functions() {
307 $functions = $this->snippet->get_functions();
308 return new WP_REST_Response([ 'success' => true, 'functions' => $functions ], 200);
309 }
310
311 function rest_replace_functions( $request ) {
312 try {
313 $params = $request->get_json_params();
314 $functions = $params['functions'];
315
316 $this->snippet->set_functions( $functions );
317
318 return new WP_REST_Response([ 'success' => true, 'data' => $functions ], 200);
319 } catch (Exception $e) {
320 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
321 }
322
323 }
324
325 function rest_functions_raw() {
326 $functions = $this->snippet->get_functions_raw();
327 return new WP_REST_Response([ 'success' => true, 'functions' => $functions ], 200);
328 }
329
330 function rest_functions_raw_update( $request ) {
331 try {
332 $params = $request->get_json_params();
333 $functions = $params['functions'];
334
335 if ( empty( $functions ) ) {
336 $functions = [];
337 } else {
338 // Decode the JSON string into a PHP array
339 $functions = json_decode($functions, true);
340
341 // Check if decoding was successful
342 if (json_last_error() !== JSON_ERROR_NONE) {
343 return new WP_REST_Response([ 'success' => false, 'message' => 'Invalid JSON format' ], 400);
344 }
345 }
346
347 $this->snippet->set_functions_raw( $functions );
348
349 return new WP_REST_Response([ 'success' => true, 'data' => $functions ], 200);
350 } catch (Exception $e) {
351 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
352 }
353 }
354
355 #endregion
356
357
358 #region MISC
359
360 function rest_quick_start( $request ) {
361 global $mwai;
362
363 if ( is_null( $mwai ) || !isset( $mwai ) ) {
364 return new WP_REST_Response([ 'success' => false, 'message' => 'AI Engine is not available.' ], 500 );
365 }
366
367 $params = $request->get_json_params();
368 if( empty( $params['prompt'] ) ) {
369 return new WP_REST_Response([ 'success' => false, 'message' => 'Prompt is required.' ], 500 );
370 }
371
372 $user_prompt = "<user_prompt>" . $params['prompt'] . "</user_prompt>";
373
374 // Main instructions: explain the categories and how to generate the snippet
375 $prompt_instructions = "<instruction>"
376 . "Create a code snippet based on the user's prompt for a WordPress environment. "
377 . "The snippet can be in PHP or JS. If the prompt does not specify the language, assume PHP. "
378 . "Below are the categories you can use for the code snippet:\n\n"
379 . "1. 'function': This can be either a JS or PHP function. The snippet should only contain the function itself, "
380 . " with no code outside its declaration. The function can take parameters; for each parameter, specify a "
381 . " default value, type ('string', 'number', 'boolean', 'mixed', etc.), required boolean, and a description.\n"
382 . "2. 'scheduled': This can only be PHP. It’s code that will be run by a cron job for scheduled tasks. "
383 . " If the user wants a task to run over time or on a schedule, use this category. The scheduling itself "
384 . " is handled automatically, so you only need to generate the main code. You can specify a target hour "
385 . " and minute.\n"
386 . "3. 'persistent', 'back', 'front': This is a PHP-only snippet for use across the website, typically for WordPress filters or "
387 . " action hooks. You can specify if the snippet should be 'persistent', 'front', or 'back' to indicate "
388 . " whether it loads on the front end, back end, or both. These are know as global snippets.\n"
389 . "4. 'content': This can be either 'content_js' or 'content_php'. A shortcode will be generated for this "
390 . " snippet so the user can place it inside a post.\n"
391 . "</instruction>";
392
393 // Goal: specify what data to generate
394 $prompt_goal = "<goal>"
395 . "Generate the snippet’s code and the metadata needed to describe it, including:\n"
396 . "- A snippet name\n"
397 . "- A description\n"
398 . "- The type (one of 'backend', 'frontend', 'function', 'persistent', 'scheduled', 'content_php', 'content_js')\n"
399 . "- Tags (comma-separated)\n"
400 . "For certain types, include additional details:\n"
401 . "- For a 'function' type, provide the scope ('js' or 'php') and its parameters, including default values, types, required and descriptions.\n"
402 . "- For a 'scheduled' snippet, specify the target hour and minute.\n"
403 . "</goal>";
404
405 // Warning: any additional information the user should know
406 $prompt_warning = "<warning>"
407 . "Dont use any <?php tags, <script> tags or markdown ``` format, just provide the raw code. "
408 . " When using a 'scheduled' all the cron scheduling is handled automatically, so you only need to generate the main code and dont need to worry about the scheduling logic. "
409 . "If the type is 'function', the code should only contain the function itself, with no code outside its declaration, as well as no comments outside of the function. THIS IS THE CASE ONLY FOR FUNCTIONS. For any other type, the code is invoked using eval() and should be a complete snippet of code. If the snippet is a function type the default values should NOT be deplared in the parameters, this will be done automatically. If the snippet is not a function type but your wirte a function, make sure to call the function at the end of the code. "
410 . " If the snippet is a content type, the code should be the content of the shortcode, not the shortcode itself. and NOT a function, it should be raw code with an echo statement. There are no attributes for content snippets. The shortcode is generated automatically like [code-engine id=''] and the id is the snippet id. "
411 . "</warning>";
412
413 // Format: how to structure the final output
414 $prompt_format = "<format>"
415 . "Return all information in JSON format with the following keys:\n"
416 . "- 'code': the code for the snippet. It should not be on one line only, make sure it is well formated.\n"
417 . "- 'name': the snippet name\n"
418 . "- 'description': a description of the snippet\n"
419 . "- 'type': the snippet type\n"
420 . "- 'tags': the snippet tags\n"
421 . "- 'scope': the scope (only applicable if type is 'function')\n"
422 . "- 'parameters': an array of parameter objects (only for 'function'), each with 'name', 'default', 'type', and 'description'\n"
423 . "- 'target_hour': the target hour (only for 'scheduled')\n"
424 . "- 'target_minute': the target minute (only for 'scheduled')\n"
425 . "- 'misc': any additional information. If you had to assume some stuff, like a filter name or a thing the user didn't think about, let it know by adding messages in this key.\n"
426 . "</format>";
427
428 // Combine all prompt parts
429 $prompt = $prompt_instructions . $prompt_goal . $prompt_format . $prompt_warning . $user_prompt;
430
431 $response = $mwai->simpleJsonQuery( $prompt );
432
433 return new WP_REST_Response( [ 'success' => true, 'data' => $response ], 200 );
434 }
435
436 function rest_server_clock() {
437 $now = current_datetime();
438 $now = $now->format( 'H:i:s ( Y/m/d )' );
439
440 return new WP_REST_Response( [ 'success' => true, 'time' => $now ], 200 );
441 }
442
443 function rest_fetch_posts( $request ) {
444 try {
445 $params = $request->get_json_params();
446 $search = isset($params['search']) ? $params['search'] : '';
447 $offset = isset($params['offset']) ? intval($params['offset']) : 0;
448 $limit = isset($params['limit']) ? intval($params['limit']) : 10;
449
450 global $wpdb;
451 $searchPlaceholder = $search ? '%' . $search . '%' : '';
452 $where_search_clause = $search ? $wpdb->prepare(
453 "AND ( p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_name LIKE %s ) ",
454 $searchPlaceholder,
455 $searchPlaceholder,
456 $searchPlaceholder
457 ) : '';
458
459 $posts = $wpdb->get_results(
460 $wpdb->prepare(
461 "SELECT p.ID, p.post_title, p.post_date, p.post_status, u.display_name as author
462 FROM $wpdb->posts p
463 LEFT JOIN $wpdb->users u ON p.post_author = u.ID
464 WHERE p.post_type = 'post'
465 AND p.post_status IN ('publish', 'draft', 'private')
466 $where_search_clause
467 ORDER BY p.post_date DESC
468 LIMIT %d, %d",
469 $offset,
470 $limit
471 ),
472 OBJECT
473 );
474
475 $posts_count = (int)$wpdb->get_var(
476 "SELECT COUNT(*)
477 FROM $wpdb->posts p
478 WHERE p.post_type = 'post'
479 AND p.post_status IN ('publish', 'draft', 'private')
480 $where_search_clause"
481 );
482
483 $data = array_map(function($post) {
484 return [
485 'id' => $post->ID,
486 'title' => $post->post_title,
487 'date' => $post->post_date,
488 'author' => $post->author,
489 'status' => $post->post_status
490 ];
491 }, $posts);
492
493 return new WP_REST_Response([
494 'success' => true,
495 'data' => $data,
496 'total' => $posts_count
497 ], 200);
498 } catch (Exception $e) {
499 return new WP_REST_Response(['success' => false, 'message' => $e->getMessage()], 500);
500 }
501 }
502
503 #endregion
504
505 #region AI Engine
506
507 function rest_ai_query( $request ) {
508
509 $query = $request->get_param('query');
510 $code = $request->get_param('code');
511
512 global $mwai;
513
514 if ( is_null( $query ) || !isset( $query ) ) {
515 return new WP_REST_Response([ 'success' => false, 'message' => 'No query provided.' ], 500 );
516 }
517
518 if ( is_null( $mwai ) || !isset( $mwai ) ) {
519 return new WP_REST_Response([ 'success' => false, 'message' => 'AI Engine is not available.' ], 500 );
520 }
521
522 $prompt = "In your response, do not include any explanation outside the code if this is a functon, everything should be inside. Any information should be added as comments only, not example function calls. Do not use <?php tags or markdown ``` format, just provide the raw code. Based on the code provided, modify it as needed: $query\n\n$code";
523 $response = $mwai->simpleTextQuery( $prompt );
524
525 return new WP_REST_Response([ 'success' => true, 'code' => $response ], 200);
526 }
527
528 function rest_ai_read_comments( $request ) {
529 $code = $request->get_param('code');
530 global $mwai;
531
532 if ( is_null( $code ) || !isset( $code ) ) {
533 return new WP_REST_Response([ 'success' => false, 'message' => 'No code provided.' ], 500 );
534 }
535
536 if ( is_null( $mwai ) || !isset( $mwai ) ) {
537 return new WP_REST_Response([ 'success' => false, 'message' => 'AI Engine is not available.' ], 500 );
538 }
539
540 $prompt = "Based on the comments in the code, modify the snippet so it accords with what the comments say. In your response, only include the modfied code with the comments removed and nothing else, no <?php tag, no markdown ``` format, just the raw code.\n\n$code";
541 $prompt = apply_filters( 'mwcode_ai_suggestion_prompt', $prompt, $code );
542
543 $response = $mwai->simpleTextQuery( $prompt );
544
545 return new WP_REST_Response([ 'success' => true, 'code' => $response ], 200);
546
547 }
548
549 #endregion
550
551 #region Snippets
552
553 function rest_call_snippet( $request ) {
554 try {
555 $route = $request->get_route();
556 $endpoint = basename( $route );
557
558 $id = $request->get_param( 'id' );
559
560 $snippet = $this->snippet->select_one( $id );
561 if ( empty( $snippet ) || $snippet['endpoint'] !== $endpoint ) {
562 return new WP_REST_Response( ['success' => false, 'message' => 'Snippet not found.' ], 404 );
563 } elseif ( !$snippet['active'] ) {
564 return new WP_REST_Response( ['success' => false, 'message' => 'Snippet is not active.' ], 403 );
565 }
566
567 // Get the arguments from the request
568 $args = $request->get_param('args');
569 if ( !empty( $args ) ) {
570 $args = json_decode( $args, true );
571 if ( json_last_error() !== JSON_ERROR_NONE ) {
572 throw new Exception('Failed to decode JSON from args: ' . json_last_error_msg() );
573 }
574 }
575
576 // Get the body from the request
577 $body = $request->get_body();
578 if ( !empty( $body ) ) {
579 $bodyDecoded = json_decode( $body, true );
580 if ( json_last_error() !== JSON_ERROR_NONE ) {
581 throw new Exception( 'Failed to decode JSON from body: ' . json_last_error_msg() );
582 }
583 if ( !empty( $args ) && is_array( $args ) ) {
584 $args = array_merge( $args, $bodyDecoded );
585 } else {
586 $args = $bodyDecoded;
587 }
588 }
589
590 $output = $this->core->run_snippet( $id, $args );
591
592 return new WP_REST_Response( $output, 200);
593 } catch (ParseError $e) {
594 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
595 } catch ( Exception $e ) {
596 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
597 }
598 }
599
600 public function rest_list( $request )
601 {
602 try {
603 $params = $request->get_json_params();
604 $page = $params['page'] ? intval($params['page']) : 1;
605 $limit = $params['limit'] ?? 10;
606 $offset = ($page - 1) * $limit;
607
608 $filters = $params['filters'] ?? [];
609
610 $filters = array_filter( $filters, function( $filter ) {
611 return $filter['value'] !== 'all';
612 });
613
614 foreach ($filters as $key => $filter) {
615 if ( $filter['accessor'] === 'scope' && $filter['value'] === 'disabled' ) {
616 $filters[] = ['accessor' => 'active', 'value' => 0];
617 unset( $filters[$key] );
618 }
619 }
620
621 $sort = ['accessor' => 'id', 'by' => 'asc'];
622 $sortByFunctionName = $params['sort']['accessor'] == 'functionName';
623
624 if ( !$sortByFunctionName && !empty( $params['sort'] ) ) {
625 $sort = $params['sort'];
626 }
627
628
629 if ( !empty( $filters['scope'] ) ) {
630 //$filters['scope'] = [ 'accessor' => 'scope', 'value' => 'frontend' ];
631 }
632
633 $list = $this->snippet->select( $offset, $limit, $filters, $sort );
634
635 //when listing let's reset any error message on the dashboard
636 $this->core->update_option( 'fatal_error', '' );
637
638
639 //TODO: Delete in future versions, we are converting the scopes since they have changed
640 $to_update = [];
641 foreach ( $list['data'] as $key => $snippet ) {
642 if ( $snippet['scope'] === 'admin' ) {
643 $list['data'][$key]['scope'] = 'backend';
644 $to_update[] = $key;
645 }
646
647 if ( $snippet['scope'] === 'front' ) {
648 $list['data'][$key]['scope'] = 'frontend';
649 $to_update[] = $key;
650 }
651
652 if ( $snippet['scope'] === 'front,admin' ) {
653 $list['data'][$key]['scope'] = 'persistent';
654 $to_update[] = $key;
655 }
656
657 if ( $snippet['scope'] === 'library' ) {
658 $list['data'][$key]['scope'] = 'persistent';
659 $to_update[] = $key;
660 }
661
662 if ( $snippet['scope'] === 'interval' ) {
663 $list['data'][$key]['scope'] = 'scheduled';
664 $to_update[] = $key;
665 }
666 }
667
668 foreach ( $to_update as $key ) {
669 $snippet = $list['data'][$key];
670 $this->core->log( "Updating snippet for new scopes: " . $snippet['id'] );
671 $this->snippet->update( $snippet );
672 }
673
674 if ( $sortByFunctionName ) {
675 // Manually sort by functionName
676 usort( $list['data'], function( $a, $b ) use ( $params ) {
677 $result = strcmp( $a['functionName'], $b['functionName'] );
678 // If sorting by 'desc', invert the comparison result
679 if ( $params['sort']['by'] === 'desc' ) {
680 return -$result;
681 }
682 // Otherwise, return the comparison result for 'asc'
683 return $result;
684 } );
685 }
686
687 //$this->core->log( "snippets" . print_r($list['data'], 1) );
688
689 return new WP_REST_Response([ 'success' => true, 'total' => $list['total'], 'data' => $list['data'] ], 200);
690 } catch (Exception $e) {
691 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
692 }
693 }
694
695 public function rest_stats( )
696 {
697 try {
698 $stats = $this->snippet->stats();
699 return new WP_REST_Response([ 'success' => true, 'data' => $stats ], 200);
700 } catch (Exception $e) {
701 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
702 }
703 }
704
705 public function rest_tags()
706 {
707 try {
708 $list = $this->snippet->select_tags();
709 return new WP_REST_Response([ 'success' => true, 'data' => $list ], 200);
710 } catch (Exception $e) {
711 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
712 }
713 }
714
715 public function rest_import( )
716 {
717 try {
718 $snippet = $this->snippet->import( );
719 return new WP_REST_Response([ 'success' => true, 'total' => $snippet ], 200);
720 } catch (Exception $e) {
721 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
722 }
723 }
724
725 public function rest_add( $request )
726 {
727 try {
728 $params = $request->get_json_params();
729 $name = $params['name'];
730 $code = $params['code'];
731 if ( empty( $name ) || empty( $code ) ) {
732 throw new Exception( __( 'Name and code are required.', 'code-engine' ) );
733 }
734
735 $res = $this->core->add_snippet( $params );
736 $snippet = $res['snippet'];
737 $result = $res['result'];
738
739 return new WP_REST_Response([ 'success' => true, 'data' => $result, 'snippet' => $snippet ], 200);
740 } catch (Exception $e) {
741 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
742 }
743 }
744
745 public function rest_update( $request )
746 {
747 try {
748 $params = $request->get_json_params();
749 $id = $params['id'];
750 if ( empty( $id ) ) {
751 throw new Exception( __( 'ID is required.', 'code-engine' ) );
752 }
753
754 $params['update'] = true;
755 $this->snippet->validate( $params );
756 $this->snippet->create_or_update_function_snippet( $params );
757 $this->snippet->create_or_update_interval_snippet( $params );
758
759 $params = $this->snippet->formatParamsForDatabase( $params );
760 $result = $this->snippet->update( $params );
761 return new WP_REST_Response([ 'success' => true, 'data' => $result ], 200);
762 } catch (Exception $e) {
763 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
764 }
765 }
766
767
768 public function rest_delete( $request )
769 {
770 try {
771 $params = $request->get_json_params();
772 $id = $params['id'];
773
774 if ( empty( $id ) ) {
775 throw new Exception( __( 'ID is required.', 'code-engine' ) );
776 }
777
778 $this->snippet->delete_function_snippet( $params );
779 $this->snippet->delete_interval_snippet( $params );
780
781 $result = $this->snippet->delete( $params );
782
783 return new WP_REST_Response([ 'success' => true, 'data' => $result ], 200);
784 } catch (Exception $e) {
785 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
786 }
787 }
788
789 public function rest_replace( $request ){
790 try {
791 $params = $request->get_json_params();
792 $snippets = $params['snippets'];
793
794 // Delete all current snippets
795 $this->snippet->delete_all();
796
797 // Insert new snippets
798 $result = [];
799 foreach ( $snippets as $snippet ) {
800 $this->snippet->validate( $snippet );
801 $snippet = $this->snippet->formatParamsForDatabase( $snippet );
802 $result[] = $this->snippet->insert( $snippet );
803 }
804
805 return new WP_REST_Response([ 'success' => true, 'data' => $result ], 200);
806 } catch (Exception $e) {
807 return new WP_REST_Response( ['success' => false, 'message' => $e->getMessage() ], 500 );
808 }
809 }
810
811
812 #endregion
813
814 #region Run
815
816 /**
817 * Execute a function snippet and return the result.
818 *
819 * It executes the provided function code with the given arguments and returns the result or any error encountered.
820 *
821 * @param WP_REST_Request $request The REST API request object.
822 * @return WP_REST_Response The REST API response containing the result or error.
823 */
824 public function rest_test( $request ) {
825 if ( is_array( $request ) ) {
826 $params = $request;
827 } else {
828 $params = $request->get_json_params();
829 }
830
831 try {
832 $output = "(Code Engine) No output.";
833 $error = null;
834
835 $scope = $params['scope'];
836 if ( $scope === 'function' ) {
837 $output = $this->core->run_snippet( null, [], $params );
838 } else {
839 $output = $this->core->run_non_fn_snippet( null, $params['code'], true );
840 }
841
842 $error = $output['error'] ?? null;
843 unset( $output['error'] );
844
845 return new WP_REST_Response( [ 'success' => true, 'return' => $output, 'error' => $error ], 200 );
846 } catch ( Exception $e ) {
847 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
848 }
849 }
850
851
852 /**
853 * Lint the code.
854 *
855 * @param $request
856 * @return WP_REST_Response
857 */
858 public function rest_lint( $request ) {
859 try {
860 $params = $request->get_json_params();
861 $code = $params['code'];
862
863 if ( empty( $code ) ) {
864 throw new Exception( __( 'Code is required.', 'code-engine' ) );
865 }
866
867 $linting_new_code = empty( $params['id'] );
868 $result = $this->core->parse_snippet( $code, $linting_new_code );
869
870 return new WP_REST_Response( [ 'success' => true, 'lint' => $result ], 200 );
871 } catch ( ParseError $e ) {
872 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
873 } catch ( Exception $e ) {
874 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
875 }
876 }
877 #endregion
878 }
879