PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / trunk
Code Engine – PHP Snippets, AI Functions & Automation for WordPress vtrunk
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 / api.php

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

879 lines 29.3 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_API {
4 public $core;
5 private $snippet_module;
6 private $debug = false;
7
8 public function __construct( $core, $snippet_module ) {
9 $this->core = $core;
10 $this->snippet_module = $snippet_module;
11 $this->debug = $this->core->get_option( 'server_debug_mode' );
12 }
13
14 #region Simple API
15
16 /**
17 * Get a snippet by its ID.
18 *
19 * The options are used to filter the snippets:
20 * - 'php_ready_args' (bool): If false, the arguments will not be formatted for PHP. (no $ before the names).
21 *
22 * @param int $id The snippet ID.
23 * @param array $options Options for filtering.
24 *
25 * @return array|null The snippet data or null if not found.
26 * @throws Exception If the snippet cannot be retrieved.
27 */
28 public function getSnippet( $id, $options = [] ) {
29 try {
30 if ( empty( $id ) ) {
31 throw new Exception( 'The snippet ID is required.' );
32 }
33
34 if ( $this->debug ) {
35 $this->core->log( sprintf( 'API [GetSnippet]: ID=%d, Options=%s', $id, json_encode( $options ) ) );
36 }
37
38 // Try to get as function snippet first (for backward compatibility)
39 $snippet = $this->snippet_module->get_function( $id, $options );
40
41 // If not found as function, get from general snippets
42 if ( empty( $snippet ) ) {
43 $snippet = $this->snippet_module->select_one( $id, $options );
44 if ( empty( $snippet ) ) {
45 throw new Exception( sprintf( 'Snippet with ID %d not found.', $id ) );
46 }
47 }
48
49 return $snippet;
50 }
51 catch ( Exception $e ) {
52 if ( $this->debug ) {
53 $this->core->log( '⚠️ API Error [GetSnippet]: ' . $e->getMessage() );
54 }
55 throw $e;
56 }
57 }
58
59 /**
60 * Get a snippet by its name.
61 *
62 * The options are used to filter the snippets:
63 * - 'php_ready_args' (bool): If false, the arguments will not be formatted for PHP. (no $ before the names).
64 *
65 * @param string $name The name of the snippet to be retrieved.
66 * @param array $options Options for filtering.
67 *
68 * @return array|null The snippet data or null if not found.
69 * @throws Exception If the snippet cannot be retrieved.
70 */
71 public function getSnippetByName( $name, $options = [] ) {
72 try {
73 if ( empty( $name ) ) {
74 throw new Exception( 'The snippet name is required.' );
75 }
76
77 if ( $this->debug ) {
78 $this->core->log( sprintf( 'API [GetSnippetByName]: Name=%s, Options=%s', $name, json_encode( $options ) ) );
79 }
80
81 $snippet = $this->snippet_module->get_function_by_name( $name, $options );
82 if ( empty( $snippet ) ) {
83 throw new Exception( sprintf( 'Snippet with name "%s" not found.', $name ) );
84 }
85
86 return $snippet;
87 }
88 catch ( Exception $e ) {
89 if ( $this->debug ) {
90 $this->core->log( '⚠️ API Error [GetSnippetByName]: ' . $e->getMessage() );
91 }
92 throw $e;
93 }
94 }
95
96 /**
97 * Get all snippets.
98 *
99 * @param bool $safe Whether to filter out snippets with invalid names.
100 * @param string|null $scope Optional scope filter: 'function', 'backend', 'frontend', 'scheduled', 'persistent'.
101 * @return array The list of snippets.
102 */
103 public function getSnippets( $safe = true, $scope = null ) {
104 try {
105
106 $allSnippets = $this->snippet_module->select( 0, 9999,
107 [ ['accessor' => 'scope', 'value' => $scope] ]
108 , [] );
109
110 $snippets = $allSnippets['data'] ?? [];
111
112
113 if ( $safe ) {
114
115 $snippets = array_filter( $snippets, function( $snippet ) {
116
117 if( $snippet['scope'] === 'function' ) {
118
119 if( array_key_exists( 'functionName', $snippet ) )
120 {
121 $name = $snippet['functionName'];
122 if ( !preg_match( '/^[a-zA-Z0-9_-]{1,64}$/', $name ) ) {
123 if ( $this->debug ) {
124 $this->core->log( sprintf( 'API [GetSnippets]: Filtered out snippet with invalid name: %s', $name ) );
125 }
126 return false;
127 }
128 } else {
129 if ( $this->debug ) {
130 $this->core->log( 'API [GetSnippets]: Filtered out snippet with missing functionName.' );
131 }
132 return false;
133 }
134
135 }
136
137 return true;
138 } );
139 }
140
141 return array_values( $snippets ); // Reset array keys
142 }
143 catch ( Exception $e ) {
144 if ( $this->debug ) {
145 $this->core->log( '⚠️ API Error [GetSnippets]: ' . $e->getMessage() );
146 }
147 throw $e;
148 }
149 }
150
151 /**
152 * Backward compatibility wrapper for getSnippets().
153 * @deprecated Use getSnippets() instead.
154 *
155 * @param bool $safe Whether to filter out snippets with invalid names.
156 * @return array The list of function snippets only.
157 */
158 public function get_functions( $safe = true ) {
159 $this->core->log( '⚠️ API Warning: get_functions() is deprecated. Please use getSnippets() instead.' );
160 // Return only function snippets for backward compatibility
161 return $this->getSnippets( $safe, 'function' );
162 }
163
164 /**
165 * Execute a snippet by its ID.
166 * The arguments should be an associative array with the argument names as keys.
167 * Example: [ "$city" => "'Tokyo'", "$date" => "1999" ]
168 *
169 * @param int $id The snippet ID.
170 * @param array $args The arguments to pass to the snippet.
171 *
172 * @return mixed The result of the snippet execution.
173 * @throws Exception If the snippet cannot be executed.
174 */
175 public function executeSnippet( $id, $args = [], $reply = null ) {
176 try {
177 if ( empty( $id ) ) {
178 throw new Exception( 'The snippet ID is required.' );
179 }
180
181 if ( $this->debug ) {
182 $this->core->log( sprintf( 'API [ExecuteSnippet]: ID=%d, Args=%s', $id, json_encode( $args ) ) );
183 }
184
185 // Verify the snippet exists
186 $snippet = $this->snippet_module->get_function( $id );
187 if ( empty( $snippet ) ) {
188 throw new Exception( sprintf( 'Snippet with ID %d not found.', $id ) );
189 }
190
191 $query = $reply ? $reply->query : null;
192 $array_query = [];
193 if( $query ) {
194 $array_query['envId'] = $query->envId ?? 'No EnvId in Query';
195 $array_query['botId'] = $query->botId ?? 'No BotId in Query';
196 $array_query['customId'] = $query->customId ?? 'No CustomId in Query';
197 $array_query['embeddingsEnvId'] = $query->embeddingsEnvId ?? 'No EmbeddingsEnvId in Query';
198
199
200 $array_query['chatId'] = $query->chatId ?? 'No ChatId in Query';
201 $array_query['session'] = $query->session ?? 'No Session in Query';
202 $array_query['scope'] = $query->scope ?? 'No Scope in Query';
203
204 $array_query['instructions'] = $query->instructions ?? 'No Instructions in Query';
205 $array_query['context'] = $query->context ?? 'No Context in Query';
206
207 $array_query['messages'] = $query->messages ? json_encode( $query->messages ) : 'No Messages in Query';
208 $array_query['message'] = $query->message ?? 'No Message in Query';
209
210 $array_query['model'] = $query->model ?? 'No Model in Query';
211 $array_query['feature'] = $query->feature ?? 'No Feature in Query';
212 }
213
214 $args['__mwai_query'] = $array_query;
215
216 $output = $this->core->run_snippet( $id, $args );
217
218 if ( $this->debug ) {
219 $shortOutput = is_string( $output ) ? substr( $output, 0, 100 ) : json_encode( $output );
220 $this->core->log( sprintf( 'API [ExecuteSnippet]: Success, Output=%s%s',
221 $shortOutput,
222 strlen( $shortOutput ) > 100 ? '...' : ''
223 ) );
224 }
225
226 return $output;
227 }
228 catch ( Exception $e ) {
229 if ( $this->debug ) {
230 $this->core->log( '⚠️ API Error [ExecuteSnippet]: ' . $e->getMessage() );
231 }
232 throw $e;
233 }
234 }
235
236 /**
237 * Execute a snippet by its name.
238 *
239 * @param string $name The snippet name.
240 * @param array $args The arguments to pass to the snippet.
241 *
242 * @return mixed The result of the snippet execution.
243 * @throws Exception If the snippet cannot be executed.
244 */
245 /**
246 * Declare every active PHP function snippet in the current request so they can call
247 * one another, without executing any of them. Idempotent and cheap (runs once per
248 * request). executeSnippet()/executeSnippetByName() already call this internally, so
249 * you only need it to make function snippets callable from your own custom PHP.
250 *
251 * @return void
252 */
253 public function loadFunctions() {
254 $this->core->define_all_functions();
255 }
256
257 public function executeSnippetByName( $name, $args = [] ) {
258 try {
259 if ( empty( $name ) ) {
260 throw new Exception( 'The snippet name is required.' );
261 }
262
263 if ( $this->debug ) {
264 $this->core->log( sprintf( 'API [ExecuteSnippetByName]: Name=%s, Args=%s', $name, json_encode( $args ) ) );
265 }
266
267 // Get the snippet by name to find its ID
268 $snippet = $this->snippet_module->get_function_by_name( $name );
269 if ( empty( $snippet ) || empty( $snippet['snippetId'] ) ) {
270 throw new Exception( sprintf( 'Snippet with name "%s" not found.', $name ) );
271 }
272
273 return $this->executeSnippet( $snippet['snippetId'], $args );
274 }
275 catch ( Exception $e ) {
276 if ( $this->debug ) {
277 $this->core->log( '⚠️ API Error [ExecuteSnippetByName]: ' . $e->getMessage() );
278 }
279 throw $e;
280 }
281 }
282 #endregion
283
284 #region Standard API
285
286 /**
287 * Create a new snippet.
288 *
289 * @param string $name Name of the snippet.
290 * @param string $code Code of the snippet.
291 * @param string $scope Scope of the snippet: 'function', 'backend', 'frontend', 'scheduled', 'persistent', 'content_php', 'content_js'.
292 * @param array $options Additional options:
293 * - target: 'php' or 'js' (for function snippets)
294 * - description: Description of the snippet
295 * - args: Arguments for function snippets
296 * - argsData: Argument data for function snippets (use 'description' for each argument's description)
297 * - behavior: 'dynamic' or 'static' (for function snippets)
298 * - mcp: Expose this function as its own MCP tool in AI Engine (for function snippets)
299 * - tags: Array of tags
300 * - priority: Execution priority
301 * - active: Active status (true/false)
302 * - endpoint: REST endpoint
303 * - method: HTTP method (GET/POST)
304 * - intervalHours: Hours for scheduled snippets
305 * - intervalMinutes: Minutes for scheduled snippets
306 *
307 * @return array The created snippet data.
308 * @throws Exception If the snippet cannot be created.
309 */
310 public function createSnippet( $name, $code, $scope = 'function', $options = [] ) {
311
312 try {
313 if ( empty( $name ) ) {
314 throw new Exception( 'The snippet name is required.' );
315 }
316
317 if ( empty( $code ) ) {
318 throw new Exception( 'The snippet code is required.' );
319 }
320
321 $validScopes = ['function', 'backend', 'frontend', 'scheduled', 'persistent', 'content_php', 'content_js'];
322 if ( !in_array( $scope, $validScopes ) ) {
323 throw new Exception( sprintf( 'Invalid scope. Must be one of: %s', implode( ', ', $validScopes ) ) );
324 }
325
326 // Extract options with defaults
327 $target = $options['target'] ?? 'php';
328 $description = $options['description'] ?? '';
329 $args = $options['args'] ?? [];
330 $argsData = $options['argsData'] ?? [];
331 $behavior = $options['behavior'] ?? 'dynamic';
332 $mcp = $options['mcp'] ?? false;
333 $tags = $options['tags'] ?? ['api'];
334 $priority = $options['priority'] ?? 10;
335 $active = $options['active'] ?? true;
336 $endpoint = $options['endpoint'] ?? '';
337 $method = $options['method'] ?? 'POST';
338 $intervalHours = $options['intervalHours'] ?? 0;
339 $intervalMinutes = $options['intervalMinutes'] ?? 0;
340
341 // Validate function-specific options
342 if ( $scope === 'function' ) {
343 if ( !in_array( $target, ['php', 'js'] ) ) {
344 throw new Exception( 'The target must be either "php" or "js" for function snippets.' );
345 }
346 if ( !in_array( $behavior, ['dynamic', 'static'] ) ) {
347 throw new Exception( 'The behavior must be either "dynamic" or "static" for function snippets.' );
348 }
349 }
350
351 if ( $this->debug ) {
352 $shortCode = substr( $code, 0, 100 );
353 $this->core->log( sprintf( 'API [CreateSnippet]: Name=%s, Scope=%s, Options=%s',
354 $name, $scope, json_encode( $options ) ) );
355 }
356
357 $params = [
358 // Core values
359 'id' => null,
360 'name' => $name,
361 'code' => $code,
362 'scope' => $scope,
363 'active' => $active ? 1 : 0,
364 'priority' => $priority,
365 'tags' => $tags,
366 'description' => $description,
367 'endpoint' => $endpoint,
368 'method' => $method,
369 ];
370
371 // Add function-specific params
372 if ( $scope === 'function' ) {
373 $params['functionName'] = $name;
374 $params['functionTarget'] = $target;
375 $params['functionArgs'] = $args;
376 $params['functionArgsDict'] = $argsData;
377 $params['functionBehavior'] = $behavior;
378 $params['functionMcp'] = $mcp;
379 }
380
381 // Add scheduled-specific params
382 if ( $scope === 'scheduled' ) {
383 $params['intervalHours'] = $intervalHours;
384 $params['intervalMinutes'] = $intervalMinutes;
385 }
386
387 $result = $this->core->add_snippet( $params );
388
389 if ( $this->debug ) {
390 $this->core->log( sprintf( 'API [CreateSnippet]: Success, ID=%d', $result['id'] ?? 0 ) );
391 }
392
393 return $result;
394 }
395 catch ( Exception $e ) {
396 if ( $this->debug ) {
397 $this->core->log( '⚠️ API Error [CreateSnippet]: ' . $e->getMessage() );
398 }
399 throw $e;
400 }
401 }
402
403 /**
404 * Update a snippet by its ID.
405 * All parameters except ID are optional. If not specified, the current values will be used.
406 *
407 * @param int $id ID of the snippet to update.
408 * @param array $params Parameters to update. Can include:
409 * - name: Snippet name
410 * - code: Snippet code
411 * - scope: Snippet scope
412 * - description: Description
413 * - active: Active status
414 * - priority: Execution priority
415 * - tags: Array of tags
416 * - endpoint: REST endpoint
417 * - method: HTTP method
418 * - target: 'php' or 'js' (for function snippets)
419 * - args: Arguments (for function snippets)
420 * - argsData: Argument data (for function snippets)
421 * - behavior: 'dynamic' or 'static' (for function snippets)
422 * - mcp: Expose this function as its own MCP tool in AI Engine (for function snippets)
423 * - intervalHours: Hours (for scheduled snippets)
424 * - intervalMinutes: Minutes (for scheduled snippets)
425 *
426 * @return array The updated snippet data.
427 * @throws Exception If the snippet cannot be updated.
428 */
429 public function updateSnippet( $id, $params = [] ) {
430 try {
431 if ( empty( $id ) ) {
432 throw new Exception( 'The snippet ID is required.' );
433 }
434
435 if ( $this->debug ) {
436 $this->core->log( sprintf( 'API [UpdateSnippet]: ID=%d, Params=%s', $id, json_encode( $params ) ) );
437 }
438
439 // Get existing snippet
440 $snippet = $this->snippet_module->select_one( $id );
441 if ( empty( $snippet ) ) {
442 throw new Exception( sprintf( 'Snippet with ID %d not found.', $id ) );
443 }
444
445 // Validate scope if provided
446 if ( isset( $params['scope'] ) ) {
447 $validScopes = ['function', 'backend', 'frontend', 'scheduled', 'persistent', 'content_php', 'content_js'];
448 if ( !in_array( $params['scope'], $validScopes ) ) {
449 throw new Exception( sprintf( 'Invalid scope. Must be one of: %s', implode( ', ', $validScopes ) ) );
450 }
451 }
452
453 // Validate function-specific parameters if provided
454 $scope = $params['scope'] ?? $snippet['scope'];
455 if ( $scope === 'function' ) {
456 if ( isset( $params['target'] ) && !in_array( $params['target'], ['php', 'js'] ) ) {
457 throw new Exception( 'The target must be either "php" or "js" for function snippets.' );
458 }
459 if ( isset( $params['behavior'] ) && !in_array( $params['behavior'], ['dynamic', 'static'] ) ) {
460 throw new Exception( 'The behavior must be either "dynamic" or "static" for function snippets.' );
461 }
462 }
463
464 // Build update params - merge with existing values
465 $updateParams = [
466 'id' => $id,
467 'name' => $params['name'] ?? $snippet['name'],
468 'code' => $params['code'] ?? $snippet['code'],
469 'scope' => $scope,
470 'active' => isset( $params['active'] ) ? ( $params['active'] ? 1 : 0 ) : $snippet['active'],
471 'priority' => $params['priority'] ?? $snippet['priority'],
472 'tags' => $params['tags'] ?? $snippet['tags'],
473 'description' => $params['description'] ?? $snippet['description'] ?? '',
474 'endpoint' => $params['endpoint'] ?? $snippet['endpoint'] ?? '',
475 'method' => $params['method'] ?? $snippet['method'] ?? 'POST',
476 ];
477
478 // Add function-specific params if it's a function snippet
479 if ( $scope === 'function' ) {
480 $updateParams['functionName'] = $params['name'] ?? $snippet['name'];
481 $updateParams['functionTarget'] = $params['target'] ?? $snippet['functionTarget'] ?? 'php';
482 $updateParams['functionArgs'] = $params['args'] ?? $snippet['functionArgs'] ?? [];
483 $updateParams['functionArgsDict'] = $params['argsData'] ?? $snippet['functionArgsDict'] ?? [];
484 $updateParams['functionBehavior'] = $params['behavior'] ?? $snippet['functionBehavior'] ?? 'dynamic';
485 // select_one() doesn't carry function metadata, so read the current MCP flag
486 // from the stored function to preserve it when the caller doesn't set it.
487 $existingFn = $this->snippet_module->get_function( $id );
488 $updateParams['functionMcp'] = $params['mcp'] ?? ( $existingFn['mcp'] ?? false );
489 }
490
491 // Add scheduled-specific params if it's a scheduled snippet
492 if ( $scope === 'scheduled' ) {
493 $updateParams['intervalHours'] = $params['intervalHours'] ?? $snippet['intervalHours'] ?? 0;
494 $updateParams['intervalMinutes'] = $params['intervalMinutes'] ?? $snippet['intervalMinutes'] ?? 0;
495 }
496
497 $result = $this->core->add_snippet( $updateParams );
498
499 if ( $this->debug ) {
500 $this->core->log( sprintf( 'API [UpdateSnippet]: Success, ID=%d', $result['id'] ?? $id ) );
501 }
502
503 return $result;
504 }
505 catch ( Exception $e ) {
506 if ( $this->debug ) {
507 $this->core->log( '⚠️ API Error [UpdateSnippet]: ' . $e->getMessage() );
508 }
509 throw $e;
510 }
511 }
512
513 /**
514 * Delete a snippet by its ID.
515 *
516 * @param int $id Snippet ID.
517 * @return bool True if the snippet was deleted successfully.
518 * @throws Exception If the snippet cannot be deleted.
519 */
520 public function deleteSnippet( $id ) {
521 try {
522 if ( empty( $id ) ) {
523 throw new Exception( 'The snippet ID is required.' );
524 }
525
526 if ( $this->debug ) {
527 $this->core->log( sprintf( 'API [DeleteSnippet]: ID=%d', $id ) );
528 }
529
530 // Verify the snippet exists
531 $snippet = $this->snippet_module->select_one( $id );
532 if ( empty( $snippet ) ) {
533 throw new Exception( sprintf( 'Snippet with ID %d not found.', $id ) );
534 }
535
536 $params = [ 'id' => $id ];
537
538 // Delete function snippet data if it's a function
539 if ( $snippet['scope'] === 'function' ) {
540 $this->snippet_module->delete_function_snippet( $params );
541 }
542
543 // Delete scheduled snippet data if it's scheduled
544 if ( $snippet['scope'] === 'scheduled' ) {
545 $this->snippet_module->delete_interval_snippet( $params );
546 }
547
548 $result = $this->snippet_module->delete( $params );
549
550 if ( $this->debug ) {
551 $this->core->log( sprintf( 'API [DeleteSnippet]: Success, ID=%d', $id ) );
552 }
553
554 return !empty( $result );
555 }
556 catch ( Exception $e ) {
557 if ( $this->debug ) {
558 $this->core->log( '⚠️ API Error [DeleteSnippet]: ' . $e->getMessage() );
559 }
560 throw $e;
561 }
562 }
563
564 /**
565 * Delete a snippet by its name.
566 *
567 * @param string $name Snippet name.
568 * @return bool True if the snippet was deleted successfully.
569 * @throws Exception If the snippet cannot be deleted.
570 */
571 public function deleteSnippetByName( $name ) {
572 try {
573 if ( empty( $name ) ) {
574 throw new Exception( 'The snippet name is required.' );
575 }
576
577 if ( $this->debug ) {
578 $this->core->log( sprintf( 'API [DeleteSnippetByName]: Name=%s', $name ) );
579 }
580
581 // Try to find as function first
582 $snippet = $this->snippet_module->get_function_by_name( $name );
583 if ( !empty( $snippet ) && !empty( $snippet['snippetId'] ) ) {
584 return $this->deleteSnippet( $snippet['snippetId'] );
585 }
586
587 // If not found as function, search in all snippets
588 $allSnippets = $this->snippet_module->select( 0, 9999, [], [] );
589 foreach ( $allSnippets['data'] as $s ) {
590 if ( $s['name'] === $name ) {
591 return $this->deleteSnippet( $s['id'] );
592 }
593 }
594
595 throw new Exception( sprintf( 'Snippet with name "%s" not found.', $name ) );
596 }
597 catch ( Exception $e ) {
598 if ( $this->debug ) {
599 $this->core->log( '⚠️ API Error [DeleteSnippetByName]: ' . $e->getMessage() );
600 }
601 throw $e;
602 }
603 }
604 #endregion
605
606 #region Standard API (No REST API)
607
608 /**
609 * Check if a snippet exists by ID.
610 *
611 * @param int $id The snippet ID.
612 * @return bool True if the snippet exists.
613 */
614 public function snippetExists( $id ) {
615 try {
616 if ( empty( $id ) ) {
617 return false;
618 }
619
620 $snippet = $this->snippet_module->select_one( $id );
621 return !empty( $snippet );
622 }
623 catch ( Exception $e ) {
624 if ( $this->debug ) {
625 $this->core->log( '⚠️ API Error [SnippetExists]: ' . $e->getMessage() );
626 }
627 return false;
628 }
629 }
630
631 /**
632 * Check if a snippet exists by name.
633 *
634 * @param string $name The snippet name.
635 * @return bool True if the snippet exists.
636 */
637 public function snippetExistsByName( $name ) {
638 try {
639 if ( empty( $name ) ) {
640 return false;
641 }
642
643 // Check in functions first
644 $snippet = $this->snippet_module->get_function_by_name( $name );
645 if ( !empty( $snippet ) ) {
646 return true;
647 }
648
649 // Check in all snippets
650 $allSnippets = $this->snippet_module->select( 0, 9999, [], [] );
651 foreach ( $allSnippets['data'] as $s ) {
652 if ( $s['name'] === $name ) {
653 return true;
654 }
655 }
656
657 return false;
658 }
659 catch ( Exception $e ) {
660 if ( $this->debug ) {
661 $this->core->log( '⚠️ API Error [SnippetExistsByName]: ' . $e->getMessage() );
662 }
663 return false;
664 }
665 }
666
667 /**
668 * Validates snippet code syntax.
669 *
670 * @param string $code The snippet code to validate.
671 * @param string $target The target language ('php' or 'js').
672 * @return array ['valid' => bool, 'error' => string|null]
673 */
674 public function validateSnippetCode( $code, $target = 'php' ) {
675 try {
676 if ( empty( $code ) ) {
677 return [ 'valid' => false, 'error' => 'Code is empty.' ];
678 }
679
680 if ( !in_array( $target, ['php', 'js'] ) ) {
681 return [ 'valid' => false, 'error' => 'Invalid target language.' ];
682 }
683
684 if ( $target === 'php' ) {
685 // Use the core's validate_php_code method if available
686 if ( method_exists( $this->core, 'validate_php_code' ) ) {
687 $validation = $this->core->validate_php_code( $code );
688 return [
689 'valid' => $validation['valid'] ?? false,
690 'error' => $validation['error'] ?? null
691 ];
692 }
693 }
694
695 // Basic validation if no specific validator available
696 return [ 'valid' => true, 'error' => null ];
697 }
698 catch ( Exception $e ) {
699 return [ 'valid' => false, 'error' => $e->getMessage() ];
700 }
701 }
702 #endregion
703
704 #region Snippet Management
705
706 /**
707 * Activate a snippet by its ID.
708 *
709 * @param int $id The snippet ID.
710 * @return bool True if activated successfully.
711 * @throws Exception If the snippet cannot be activated.
712 */
713 public function activateSnippet( $id ) {
714 try {
715 if ( empty( $id ) ) {
716 throw new Exception( 'The snippet ID is required.' );
717 }
718
719 if ( $this->debug ) {
720 $this->core->log( sprintf( 'API [ActivateSnippet]: ID=%d', $id ) );
721 }
722
723 // Get the snippet
724 $snippet = $this->snippet_module->select_one( $id );
725 if ( empty( $snippet ) ) {
726 throw new Exception( sprintf( 'Snippet with ID %d not found.', $id ) );
727 }
728
729 // Update only the active status
730 $result = $this->updateSnippet( $id, [ 'active' => true ] );
731 return !empty( $result );
732 }
733 catch ( Exception $e ) {
734 if ( $this->debug ) {
735 $this->core->log( '⚠️ API Error [ActivateSnippet]: ' . $e->getMessage() );
736 }
737 throw $e;
738 }
739 }
740
741 /**
742 * Deactivate a snippet by its ID.
743 *
744 * @param int $id The snippet ID.
745 * @return bool True if deactivated successfully.
746 * @throws Exception If the snippet cannot be deactivated.
747 */
748 public function deactivateSnippet( $id ) {
749 try {
750 if ( empty( $id ) ) {
751 throw new Exception( 'The snippet ID is required.' );
752 }
753
754 if ( $this->debug ) {
755 $this->core->log( sprintf( 'API [DeactivateSnippet]: ID=%d', $id ) );
756 }
757
758 // Get the snippet
759 $snippet = $this->snippet_module->select_one( $id );
760 if ( empty( $snippet ) ) {
761 throw new Exception( sprintf( 'Snippet with ID %d not found.', $id ) );
762 }
763
764 // Update only the active status
765 $result = $this->updateSnippet( $id, [ 'active' => false ] );
766 return !empty( $result );
767 }
768 catch ( Exception $e ) {
769 if ( $this->debug ) {
770 $this->core->log( '⚠️ API Error [DeactivateSnippet]: ' . $e->getMessage() );
771 }
772 throw $e;
773 }
774 }
775
776 /**
777 * Get snippets by scope.
778 *
779 * @param string $scope The scope to filter by: 'backend', 'frontend', 'function', 'scheduled', 'persistent', 'content_php', 'content_js'.
780 * @param array $filters Additional filters: 'active' (bool), 'tags' (array).
781 * @return array The list of snippets matching the criteria.
782 * @throws Exception If the scope is invalid.
783 */
784 public function getSnippetsByScope( $scope, $filters = [] ) {
785 try {
786 $validScopes = ['function', 'backend', 'frontend', 'scheduled', 'persistent', 'content_js', 'content_php'];
787 if ( !in_array( $scope, $validScopes ) ) {
788 throw new Exception( sprintf( 'Invalid scope. Must be one of: %s', implode( ', ', $validScopes ) ) );
789 }
790
791 if ( $this->debug ) {
792 $this->core->log( sprintf( 'API [GetSnippetsByScope]: Scope=%s, Filters=%s', $scope, json_encode( $filters ) ) );
793 }
794
795 // For function scope, we can use the existing get_functions method
796 if ( $scope === 'function' && empty( $filters ) ) {
797 return $this->snippet_module->get_functions();
798 }
799
800 // For other scopes or with filters, we need to query all snippets
801 $allSnippets = $this->snippet_module->select( 0, 9999, [], [] );
802 $filtered = [];
803
804 foreach ( $allSnippets['data'] as $snippet ) {
805 // Filter by scope
806 if ( $snippet['scope'] !== $scope ) {
807 continue;
808 }
809
810 // Apply additional filters
811 if ( isset( $filters['active'] ) && $snippet['active'] != $filters['active'] ) {
812 continue;
813 }
814
815 if ( isset( $filters['tags'] ) && is_array( $filters['tags'] ) ) {
816 $snippetTags = is_array( $snippet['tags'] ) ? $snippet['tags'] : [];
817 $hasAllTags = true;
818 foreach ( $filters['tags'] as $tag ) {
819 if ( !in_array( $tag, $snippetTags ) ) {
820 $hasAllTags = false;
821 break;
822 }
823 }
824 if ( !$hasAllTags ) {
825 continue;
826 }
827 }
828
829 $filtered[] = $snippet;
830 }
831
832 return $filtered;
833 }
834 catch ( Exception $e ) {
835 if ( $this->debug ) {
836 $this->core->log( '⚠️ API Error [GetSnippetsByScope]: ' . $e->getMessage() );
837 }
838 throw $e;
839 }
840 }
841
842 /**
843 * Get all active snippets.
844 *
845 * @param string|null $scope Optional scope filter.
846 * @return array The list of active snippets.
847 */
848 public function getActiveSnippets( $scope = null ) {
849 try {
850 if ( $this->debug ) {
851 $this->core->log( sprintf( 'API [GetActiveSnippets]: Scope=%s', $scope ?? 'all' ) );
852 }
853
854 if ( $scope ) {
855 return $this->getSnippetsByScope( $scope, [ 'active' => true ] );
856 }
857
858 // Get all active snippets
859 $allSnippets = $this->snippet_module->select( 0, 9999, [], [] );
860 $active = [];
861
862 foreach ( $allSnippets['data'] as $snippet ) {
863 if ( $snippet['active'] ) {
864 $active[] = $snippet;
865 }
866 }
867
868 return $active;
869 }
870 catch ( Exception $e ) {
871 if ( $this->debug ) {
872 $this->core->log( '⚠️ API Error [GetActiveSnippets]: ' . $e->getMessage() );
873 }
874 throw $e;
875 }
876 }
877 #endregion
878 }
879