PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.4.9
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.4.9
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.4.9, at classes/rest.php

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