PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.4.5
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.4.5
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 / modules / snippet.php

snippet.php in Code Engine – PHP Snippets, AI Functions & Automation for WordPress 0.4.5, at classes/modules/snippet.php

994 lines 34.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 class Meow_MWCODE_Modules_Snippet
3 {
4 private $table = 'mwcode_snippets';
5 private $option_functions = 'mwcode_functions';
6 private $option_intervals = 'mwcode_intervals';
7
8 private $wpdb = null;
9 private $db_check = false;
10 private $table_name = null;
11 private $core = null;
12
13 private $mwcode_db_snippet_version = '1.0';
14
15
16
17 public function __construct( $core = null )
18 {
19 global $wpdb;
20 $this->wpdb = $wpdb;
21 $this->table_name = $this->wpdb->prefix . $this->table;
22
23 if ( $core == null ) {
24 $this->core = new Meow_MWCODE_Core();
25 }
26
27 $this->core = $core;
28 }
29
30 #region Interval Snippets
31
32 public function get_scheduled( )
33 {
34
35 $interval_snippets = get_option( $this->option_intervals, [] );
36
37 return $interval_snippets;
38 }
39
40 public function create_or_update_interval_snippet( $params )
41 {
42 if ( $params['scope'] != 'scheduled' ) return;
43
44 $interval_snippets = get_option( $this->option_intervals, [] );
45
46 $snippetId = $params['id'];
47 $snippet = [
48 'snippetId' => $snippetId,
49 'hours' => $params['intervalHours'],
50 'minutes' => $params['intervalMinutes'],
51 ];
52
53 $snippetExists = false;
54 foreach ( $interval_snippets as $key => $existingSnippet ) {
55 if ( $existingSnippet['snippetId'] == $snippetId ) {
56 $interval_snippets[$key] = $snippet;
57 $snippetExists = true;
58 break;
59 }
60 }
61
62 if ( !$snippetExists ) {
63 $interval_snippets[] = $snippet;
64 }
65
66 update_option( $this->option_intervals, $interval_snippets );
67 }
68
69 public function delete_interval_snippet( $params )
70 {
71 $interval_snippets = get_option( $this->option_intervals, [] );
72
73 $snippetId = $params['id'];
74 $updatedSnippets = [];
75
76 foreach ( $interval_snippets as $existingSnippet ) {
77 if ( $existingSnippet['snippetId'] != $snippetId ) {
78 $updatedSnippets[] = $existingSnippet;
79 } else {
80 $hook = 'mwcode_execute_snippet_' . $existingSnippet['snippetId'];
81 $timestamp = wp_next_scheduled( $hook );
82 if ( $timestamp ) {
83 wp_unschedule_event( $timestamp, $hook );
84 }
85 }
86 }
87
88 update_option( $this->option_intervals, $updatedSnippets );
89 }
90
91 private function get_interval_snippets_data( &$snippets )
92 {
93 if ( isset( $snippets['id'] ) ) {
94 $this->get_interval_snippet_data( $snippets );
95 } else {
96 foreach ( $snippets as &$snippet ) {
97 $this->get_interval_snippet_data( $snippet );
98 }
99 }
100 }
101
102 private function get_interval_snippet_data( &$snippet )
103 {
104 if ( $snippet['scope'] != 'scheduled' ) {
105 return;
106 }
107
108 $interval_snippets = get_option( $this->option_intervals, [] );
109
110 $snippetId = $snippet['id'];
111 $snippet['intervalHours'] = '';
112 $snippet['intervalMinutes'] = '';
113
114 foreach ( $interval_snippets as $interval_snippet ) {
115 if ( $interval_snippet['snippetId'] == $snippetId ) {
116 $snippet['intervalHours'] = $interval_snippet['hours'];
117 $snippet['intervalMinutes'] = $interval_snippet['minutes'];
118 }
119 }
120 }
121
122
123 #endregion
124
125 #region Functions Snippets
126
127 private function get_function_snippet_data( &$snippet )
128 {
129 if ( $snippet['scope'] != 'function' ) {
130 return;
131 }
132
133 $functions_snippet = get_option( $this->option_functions, [] );
134
135 $snippetId = $snippet['id'];
136 $snippet['functionName'] = '';
137 $snippet['functionArgs'] = [];
138 $snippet['functionArgsDict'] = [];
139 $snippet['functionBehavior'] = '';
140 $snippet['functionTarget'] = 'PHP';
141
142 foreach ( $functions_snippet as $function_snippet ) {
143 if ( $function_snippet['snippetId'] == $snippetId ) {
144
145 $snippet['functionName'] = $function_snippet['name'];
146 if ( !isset( $function_snippet['behavior'] ) || empty( $function_snippet['behavior'] ) ) {
147 $snippet['functionBehavior'] = 'dynamic';
148 }
149 else {
150 $snippet['functionBehavior'] = $function_snippet['behavior'];
151 }
152 if ( !isset( $function_snippet['target'] ) || empty( $function_snippet['target'] ) ) {
153 $snippet['functionTarget'] = 'PHP';
154 }
155 else {
156 $snippet['functionTarget'] = $function_snippet['target'];
157 }
158 foreach ( $function_snippet['args'] as $arg ) {
159 $snippet['functionArgs'][] = $arg['name'];
160 $snippet['functionArgsDict'][$arg['name']] = $arg;
161 }
162 }
163 }
164 }
165
166 public function get_function_snippets_data( &$snippets )
167 {
168 if ( isset( $snippets['id'] ) ) {
169 $this->get_function_snippet_data( $snippets );
170 } else {
171 foreach ( $snippets as &$snippet ) {
172 $this->get_function_snippet_data( $snippet );
173 }
174 }
175 }
176
177 public function delete_function_snippet( $params )
178 {
179 $functions_snippet = get_option( $this->option_functions, [] );
180
181 $snippetId = $params['id'];
182 $updatedSnippets = [];
183
184 foreach ( $functions_snippet as $existingSnippet ) {
185 if ( $existingSnippet['snippetId'] != $snippetId ) {
186 $updatedSnippets[] = $existingSnippet;
187 }
188 }
189
190 update_option( $this->option_functions, $updatedSnippets );
191 }
192
193 private function sanitize_function_snippet( $snippet )
194 {
195 // Add logic to make sure the function snippet is valid
196 // 1 - Make sure the function always has a behavior ( is none set it to "dynamic" )
197 if ( !isset( $snippet['behavior'] ) || empty( $snippet['behavior'] ) ) {
198 $snippet['behavior'] = 'dynamic';
199 }
200
201
202 return $snippet;
203 }
204
205 public function create_or_update_function_snippet( $params )
206 {
207 if ( $params['scope'] != 'function' ) return;
208
209 $functions_snippet = get_option( $this->option_functions, [] );
210
211 $snippetId = $params['id'];
212 $snippet = [
213 'snippetId' => $snippetId,
214 'active' => $params['active'],
215 'name' => $params['functionName'],
216 'behavior' => $params['functionBehavior'],
217 'desc' => $params['description'] ?? '',
218 'target' => $params['functionTarget'] ?? 'PHP',
219 'args' => [],
220 ];
221
222 foreach ( $params['functionArgs'] as $argName ) {
223 if ( !empty( $argName ) ) {
224 $argData = $params['functionArgsDict'][$argName];
225 $snippet['args'][] = [
226 'name' => $argName,
227 'desc' => $argData['description'] ?? $argData['desc'] ?? '', // Support both 'description' and 'desc'
228 'default' => $argData['default'],
229 'required' => empty( $argData['default'] ),
230 'type' => $argData['type'],
231 ];
232 } else {
233 // TODO: The client-side sends an empty string when there is no argument.
234 // This has to be fixed on the client-side.
235 $this->core->log( '❌ ( Code Engine ) Empty argument name.' );
236 }
237 }
238
239 $snippet = $this->sanitize_function_snippet( $snippet );
240
241 $snippetExists = false;
242 foreach ( $functions_snippet as $key => $existingSnippet ) {
243 if ( $existingSnippet['snippetId'] == $snippetId ) {
244 $functions_snippet[$key] = $snippet;
245 $snippetExists = true;
246 break;
247 }
248 }
249
250 if ( !$snippetExists ) {
251 $functions_snippet[] = $snippet;
252 }
253
254 update_option( $this->option_functions, $functions_snippet );
255 }
256
257 public function get_functions_raw( )
258 {
259 return get_option( $this->option_functions, [] );
260 }
261
262 public function set_functions_raw( $functions )
263 {
264 return update_option( $this->option_functions, $functions );
265 }
266
267 public function get_functions( )
268 {
269 $functions = get_option( $this->option_functions, array( ) );
270
271 //TODO: Delete this later, it's just to make sure all functions have the "active" key, as it's new.
272 $needs_update = false;
273 $filtered_functions = [];
274
275
276
277 foreach ( $functions as $function ) {
278
279 if ( !array_key_exists( 'active', $function ) ) {
280 $snippet = $this->select_one( $function['snippetId'] );
281
282 if ( !empty( $snippet ) ) {
283 $function['active'] = $snippet['active'];
284 $this->core->log( '�
285 Function\'s related snippet found: ' . $function['snippetId'] . '. Active: ' . $function['active'] );
286 } else {
287 $this->core->log( '❌ Function\'s related snippet not found: ' . $function['snippetId'] . '. Disabling function.' );
288 $function['active'] = false;
289 }
290
291 $needs_update = true;
292 }
293
294 $filtered_functions[] = $function;
295 }
296
297 if ( $needs_update ) {
298 update_option( $this->option_functions, $filtered_functions );
299 }
300
301 $filtered_functions = array_filter( $filtered_functions, function ( $function ) {
302 return $function['snippetId'] !== null && $function['active'];
303 } );
304
305 // Reindex the keys
306 $filtered_functions = array_values( $filtered_functions );
307
308 return $filtered_functions;
309 }
310
311 public function set_functions( $functions )
312 {
313 update_option( $this->option_functions, $functions );
314 }
315
316
317 public function get_function( $id, $options = [] )
318 {
319 $functions = $this->get_functions( );
320
321 foreach ( $functions as $function ) {
322 if ( $function['snippetId'] == $id ) {
323
324 if ( array_key_exists( 'php_ready_args', $options ) && $options['php_ready_args'] === false ) {
325 $function['args'] = array_map( function ( $arg ) {
326 $arg['name'] = ltrim( $arg['name'], '$' );
327 return $arg;
328 }, $function['args'] );
329 }
330
331 return $function;
332 }
333 }
334 return null;
335 }
336
337 public function get_function_by_name( $name, $options = [] )
338 {
339 $functions = $this->get_functions( );
340
341 foreach ( $functions as $function ) {
342 if ( $function['name'] == $name ) {
343
344 if ( array_key_exists( 'php_ready_args', $options ) && $options['php_ready_args'] === false ) {
345 $function['args'] = array_map( function ( $arg ) {
346 $arg['name'] = ltrim( $arg['name'], '$' );
347 return $arg;
348 }, $function['args'] );
349 }
350
351 return $function;
352 }
353 }
354
355 return null;
356 }
357
358 #endregion
359
360 #region Utilities
361
362 /**
363 * Validate the parameters with consistency with other snippets.
364 * For example, the endpoint should be unique.
365 *
366 * @param array $params
367 * @return void
368 * @throws Exception
369 */
370 public function validate( $params )
371 {
372 $valide_scopes = ['backend', 'frontend', 'function', 'persistent', 'scheduled', 'content_php', 'content_js'];
373 $errors = [];
374 if ( isset( $params['endpoint'] ) && !empty( $params['endpoint'] ) ) {
375 $query = $this->wpdb->prepare(
376 "SELECT * FROM $this->table_name where endpoint = %s",
377 $params['endpoint']
378 );
379 if ( isset( $params['id'] ) && !empty( $params['id'] ) ) {
380 $query .= $this->wpdb->prepare( " AND id <> %d", ( int ) $params['id'] );
381 }
382 $exists = ( int ) $this->wpdb->get_var( "SELECT COUNT( * ) FROM ( $query ) AS t" ) > 0;
383 if ( $exists ) {
384 $errors[] = __( 'The endpoint is already used.', 'code-engine' );
385 }
386 }
387
388
389 if ( isset( $params['scope'] ) && !empty( $params['scope'] ) ) {
390
391 $scopes = [
392 "global" => "persistent",
393 "front-end" => "frontend",
394 "admin" => "backend",
395 ];
396
397 if ( array_key_exists( $params['scope'], $scopes ) ) {
398 $params['scope'] = $scopes[$params['scope']];
399 }
400
401
402 if ( !empty( $params['tags'] ) ) {
403 $tags = is_array( $params['tags'] ) ? $params['tags'] : explode( ',', $params['tags'] );
404 // Add the current scope to tags and remove duplicates
405 $tags[] = $params['scope'];
406 $tags = array_unique( $tags );
407
408 // Filter tags to include only valid scopes or the current scope
409 $tags = array_filter( $tags, function ( $tag ) use ( $valide_scopes, $params ) {
410 return !in_array( $tag, $valide_scopes ) || $tag === $params['scope'];
411 } );
412
413 // Reset array keys and assign back to params
414 $params['tags'] = array_values( $tags );
415 } else {
416 $params['tags'] = $params['scope'];
417 }
418
419 if ( !in_array( $params['scope'], $valide_scopes ) ) {
420 $errors[] = sprintf( __( 'Invalid scope: %s', 'code-engine' ), $params['scope'] );
421 }
422 }
423
424 // Make sure that the function name declared in the are unique
425 if ( isset( $params['code'] ) && !empty( $params['code'] ) ) {
426 $is_updating = array_key_exists( 'update', $params ) && $params['update'] === true;
427 $function_data = $this->sanitize_and_check_functions( $params['code'], $is_updating );
428 if ( !$function_data['is_valid'] ) {
429 $errors = array_merge( $errors, $function_data['errors'] );
430 }
431 }
432
433 if ( isset( $params['scope'] ) && $params['scope'] === 'function' ) {
434 if ( !isset( $params['functionName'] ) || empty( $params['functionName'] ) ) {
435 $errors[] = __( 'Function name is required.', 'code-engine' );
436 }
437 }
438
439 if ( isset( $params['functionError'] ) && !empty( $params['functionError'] ) ) {
440 $errors[] = __( 'An error was not resolved. (' . $params['functionError'] . ')', 'code-engine' );
441 }
442
443 if ( !empty( $errors ) ) {
444 throw new Exception( "❌ Your code could not be saved because of the following issue( s ): \n\n" . implode( ', ', $errors ) );
445 }
446
447 return $params;
448 }
449
450 public function sanitize_and_check_functions( $code, $is_updating = false )
451 {
452 $errors = [];
453 $function_names = [];
454 $attributes = [];
455
456 // Regular expression to match function declarations
457 $pattern = '/function\s+(\w+)\s*\(/';
458
459 // Find all function declarations
460 preg_match_all( $pattern, $code, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
461
462 if ( !empty( $matches ) ) {
463 foreach ( $matches as $match ) {
464 $function_name = $match[1][0];
465 $start_pos = $match[0][1];
466
467 // Calculate line numbers and positions
468 $lines_before = substr_count( substr( $code, 0, $start_pos ), "\n" );
469 $line_start = strrpos( substr( $code, 0, $start_pos ), "\n" ) + 1;
470 $line_end = strpos( $code, "\n", $start_pos );
471 if ( $line_end === false ) $line_end = strlen( $code );
472
473 $attr = [
474 'startLine' => $lines_before + 1,
475 'startTokenPos' => $start_pos - $line_start,
476 'startFilePos' => $start_pos,
477 'endLine' => $lines_before + 1,
478 'endTokenPos' => $line_end - $line_start,
479 'endFilePos' => $line_end
480 ];
481
482 // Check if function already exists in PHP
483 if ( function_exists( $function_name ) && !$is_updating ) {
484 $errors[] = [
485 'message' => "Function '$function_name' is already declared in PHP.",
486 'attributes' => $attr
487 ];
488 }
489 // Check if function is already declared in this code block
490 elseif ( in_array( $function_name, $function_names ) ) {
491 $errors[] = [
492 'message' => "Function '$function_name' is declared multiple times in the provided code.",
493 'attributes' => $attr
494 ];
495 } else {
496 $function_names[] = $function_name;
497 }
498
499 $attributes[] = $attr;
500 }
501 }
502
503 return [
504 'is_valid' => empty( $errors ),
505 'errors' => $errors,
506 'function_names' => $function_names,
507 'attributes' => $attributes
508 ];
509 }
510
511 /**
512 * Format parameters for saving in the database
513 *
514 * @param array $params
515 * @return array
516 */
517 public function formatParamsForDatabase( $params )
518 {
519 // Gather the scope tags into the tags
520 $tags = null;
521 if ( isset( $params['tags'] ) ) {
522 if ( is_array( $params['tags'] ) ) {
523 $tags = array_map( function ( $tag ) {
524 return trim( $tag );
525 }, $params['tags'] );
526 } else {
527 $tags = array_map( function ( $tag ) {
528 return trim( $tag );
529 }, explode( ',', $params['tags'] ) );
530 }
531 }
532 if ( isset( $params['scope'] ) ) {
533 $tags = array_merge( $tags, is_array( $params['scope'] ) ? $params['scope'] : explode( ',', $params['scope'] ) );
534 }
535 if ( count( $tags ) > 0 ) {
536 $tags = array_filter( $tags, function ( $tag ) {
537 return $tag !== '';
538 } );
539 }
540 $params['tags'] = $tags ? implode( ',', $tags ) : '';
541 //$params['code'] = $this->sanitize_code( $params['code'] );
542 return $params;
543 }
544
545 /**
546 * Format parameters for the front-end
547 *
548 * @param array $params
549 * @return array
550 */
551 private function formatParamsForFront( $params )
552 {
553 // Separate the scope tags from the tags
554 $scopes = ['backend', 'frontend', 'function', 'persistent', 'scheduled', 'content_php', 'content_js'];
555
556 if ( isset( $params['tags'] ) && !empty( $params['tags'] ) ) {
557
558 $tags = array_map( function ( $tag ) use ( $scopes ) {
559 if ( in_array( $tag, $scopes ) ) {
560 return null;
561 }
562 return trim( $tag );
563 }, explode( ',', $params['tags'] ) );
564 $params['tags'] = array_filter( $tags, function ( $tag ) {
565 return $tag !== null;
566 } );
567 }
568 return $params;
569 }
570
571 public function stats()
572 {
573 $scopes = ['function', 'scheduled', 'global', 'content_php', 'content_js'];
574 $globalScopes = ['backend', 'frontend', 'persistent'];
575
576 $stats = [
577 'all' => 0,
578 'disabled' => $this->wpdb->get_var( "SELECT COUNT( * ) FROM $this->table_name WHERE active = 0" ),
579 ];
580
581 foreach ( $scopes as $scope ) {
582 if ( $scope === 'global' ) {
583 $globalScopeQuery = implode( "', '", array_map( 'esc_sql', $globalScopes ) );
584 $stats[$scope] = $this->wpdb->get_var( "SELECT COUNT( * ) FROM $this->table_name WHERE scope IN ('$globalScopeQuery')" );
585 } else {
586 $stats[$scope] = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT( * ) FROM $this->table_name WHERE scope = %s", $scope ) );
587 }
588 $stats['all'] += $stats[$scope];
589 }
590
591 return $stats;
592 }
593
594 public function import( )
595 {
596 $table = $this->wpdb->prefix . 'snippets';
597 $snippets = $this->wpdb->get_results( "SELECT * FROM $table", ARRAY_A );
598
599 if ( !$snippets ) {
600 return 0;
601 }
602
603 // Disable ( set active = 0 ) all the snippets in the old table
604 $this->wpdb->update( $table, ['active' => 0], ['active' => 1] );
605
606 foreach ( $snippets as $snippet ) {
607 $snippet['id'] = null; // Reset the ID so it will be inserted as a new snippet.
608 $snippet = $this->validate( $snippet );
609 $this->insert( $this->formatParamsForDatabase( $snippet ) );
610 }
611
612 return count( $snippets );
613 }
614
615 public function sanitize_code( $code )
616 {
617 $code = ltrim( $code );
618
619 $first_chats = substr( $code, 0, 5 );
620 if( $first_chats === '<?php' ) {
621 $code = substr( $code, 5 );
622 $code = ltrim( $code );
623 }
624
625 return $code;
626 }
627
628 #endregion
629
630 #region Snippet CRUD
631
632 public function select( $offset, $limit, $filters, $sort )
633 {
634 if ( !$this->check_db() ) {
635 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
636 }
637
638 $list = [];
639 $offset = !empty( $offset ) ? intval( $offset ) : 0;
640 $limit = !empty( $limit ) ? intval( $limit ) : 10;
641 $filters = !empty( $filters ) ? $filters : [];
642 $sort = !empty( $sort ) ? $sort : ['accessor' => 'updated', 'by' => 'desc'];
643 $query = "SELECT * FROM $this->table_name";
644
645 // Filters
646 if ( is_array( $filters ) && count( $filters ) > 0 ) {
647 $where = [];
648
649 $freshFilters = [];
650 // Little trick that allows searching by tags using the snippet accessor
651 // And to have a global scope that will search for backend, frontend, and persistent
652 foreach ( $filters as $filter ) {
653 if ( $filter['accessor'] === 'snippet' ) {
654 //$freshFilters['accessor'] = 'tags';
655 $freshFilters[] = [ 'accessor' => 'tags', 'value' => $filter['value'] ];
656 }
657 else if ( $filter['accessor'] === 'scope' && $filter['value'] === 'global' ) {
658 $freshFilters[] = [ 'accessor' => 'scope', 'value' => ['backend', 'frontend', 'persistent'] ];
659 }
660 else {
661 $freshFilters[] = $filter;
662 }
663 }
664 $filters = $freshFilters;
665
666 foreach ( $filters as $filter ) {
667 if ( $filter['accessor'] === 'tags' ) {
668 $value = ( array )$filter['value'];
669
670 if ( count( $value ) === 0 ) {
671 continue;
672 }
673 $where_unit = [];
674 foreach ( $value as $tag ) {
675 if ( strpos( $tag, ',' ) !== false ) {
676 $tags = explode( ',', $tag );
677 $where_combination_unit = [];
678 foreach ( $tags as $t ) {
679 $where_combination_unit[] = "FIND_IN_SET( '{$t}', tags )";
680 }
681 $where_unit[] = '( ' . implode( ' AND ', $where_combination_unit ) . ' )';
682 continue;
683 }
684 $where_unit[] = "FIND_IN_SET( '{$tag}', tags )";
685 }
686 $where[] = '( ' . implode( ' OR ', $where_unit ) . ' )';
687 } elseif ( $filter['accessor'] === 'active' ) {
688 $value = esc_sql( $filter['value'] );
689 $where[] = $this->wpdb->prepare( "active = %d", $value );
690 } elseif ( $filter['accessor'] === 'endpoint' ) {
691 $where[] = boolval( $filter['value'] ) ? "endpoint <> ''" : "endpoint = ''";
692 } elseif ( $filter['accessor'] === 'scope' ) {
693 if ( is_array( $filter['value'] ) ) {
694 $scopes = array_map( function( $scope ) {
695 return esc_sql( $scope );
696 }, $filter['value'] );
697 $where[] = "scope IN ('" . implode( "', '", $scopes ) . "')";
698 } else if ( !empty( $filter['value'] ) ) {
699 $value = esc_sql( $filter['value'] );
700 $where[] = $this->wpdb->prepare( "scope = %s", $value );
701 }
702 }
703 }
704 if ( count( $where ) > 0 ) {
705 $query .= " WHERE " . implode( " AND ", $where );
706 }
707 }
708
709 // Count based on this query
710 $list['total'] = $this->wpdb->get_var( "SELECT COUNT( * ) FROM ( $query ) AS t" );
711
712 // Order by
713 $query .= " ORDER BY " . esc_sql( $sort['accessor'] ) . " " . esc_sql( $sort['by'] );
714
715 // Limits
716 if ( $limit > 0 ) {
717 $query .= " LIMIT $offset, $limit";
718 }
719
720 $list['data'] = array_map( function ( $snippet ) {
721 return $this->formatParamsForFront( $snippet );
722 }, $this->wpdb->get_results( $query, ARRAY_A ) );
723
724 $this->get_function_snippets_data( $list['data'] );
725 $this->get_interval_snippets_data( $list['data'] );
726
727 return $list;
728 }
729
730 public function select_tags( )
731 {
732 if ( !$this->check_db( ) ) {
733 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
734 }
735
736 $tags = [];
737 $query = "SELECT tags FROM $this->table_name";
738 $result = $this->wpdb->get_results( $query, ARRAY_A );
739 foreach ( $result as $row ) {
740 $tags = array_merge( $tags, explode( ',', $row['tags'] ) );
741 }
742 // Remove the scope tags: admin, front, once.
743 $tags = array_diff( $tags, ['backend', 'frontend', 'function', 'persistent', 'scheduled'] );
744 return array_values( array_unique( $tags ) );
745 }
746
747 public function select_one( $id, $options = [] )
748 {
749 if ( !$this->check_db( ) ) {
750 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
751 }
752
753 $query = "SELECT * FROM $this->table_name WHERE id = %s";
754 if ( isset( $options['active'] ) ) {
755 $query .= " AND active = " . ( $options['active'] ? '1' : '0' );
756 }
757
758 return $this->formatParamsForFront(
759 $this->wpdb->get_row(
760 $this->wpdb->prepare( $query, ( string ) $id ),
761 ARRAY_A
762 )
763 );
764 }
765
766 public function insert( $insert_data )
767 {
768 if ( !$this->check_db( ) ) {
769 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
770 }
771
772 $data = [];
773 $update_columns = array_keys( MWCODE_SNIPPET_COLUMNS );
774
775 foreach ( $update_columns as $column ) {
776 if ( isset( $insert_data[$column] ) ) {
777 $data[$column] = $insert_data[$column];
778 } else {
779 unset( $data[$column] ); // Remove it if it's empty, so it uses the default db value.
780 }
781 }
782
783 $data['created'] = date( 'Y-m-d H:i:s' );
784 $data['updated'] = date( 'Y-m-d H:i:s' );
785
786 $this->wpdb->insert( $this->table_name, $data );
787 $id = $this->wpdb->insert_id;
788 if ( !$id ) {
789 throw new Exception( __( 'Could not insert the snippet.', 'code-engine' ) );
790 }
791 return $id;
792 }
793
794 public function update( $update_data )
795 {
796 if ( !$this->check_db( ) ) {
797 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
798 }
799
800 $data = [];
801 $update_columns = array_keys( MWCODE_SNIPPET_COLUMNS );
802 foreach ( $update_columns as $column ) {
803 if ( isset( $update_data[$column] ) ) {
804 $data[$column] = $update_data[$column];
805 }
806 }
807 if ( count( $data ) === 0 ) {
808 throw new Exception( __( 'No data to update.', 'code-engine' ) );
809 }
810 $data['updated'] = date( 'Y-m-d H:i:s' );
811 $result = $this->wpdb->update( $this->table_name, $data, ['id' => $update_data['id']] );
812 if ( $result === false ) {
813 throw new Exception( __( 'Could not insert the snippet.', 'code-engine' ) );
814 }
815 return $result;
816 }
817
818 public function force_disable( $id )
819 {
820 if ( !$this->check_db( ) ) {
821 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
822 }
823
824 $result = $this->wpdb->update( $this->table_name, ['active' => 0], ['id' => $id] );
825 if ( $result === false ) {
826 throw new Exception( __( 'Could not disable the snippet.', 'code-engine' ) );
827 }
828 return $result;
829 }
830
831 public function delete( $delete_data )
832 {
833 if ( !$this->check_db( ) ) {
834 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
835 }
836
837 $result = $this->wpdb->delete( $this->table_name, ['id' => $delete_data['id']] );
838 if ( $result === false ) {
839 throw new Exception( __( 'Could not delete the snippet.', 'code-engine' ) );
840 }
841 return $result;
842 }
843
844 public function delete_all( )
845 {
846 if ( !$this->check_db( ) ) {
847 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
848 }
849
850 $result = $this->wpdb->query( "TRUNCATE TABLE $this->table_name" );
851 if ( $result === false ) {
852 throw new Exception( __( 'Could not delete all snippets.', 'code-engine' ) );
853 }
854 return $result;
855 }
856
857 #endregion
858
859 #region Database
860
861 function create_db( )
862 {
863 $this->core->log( '💾 ( Code Engine ) Creating Table: ' . $this->table_name );
864 try {
865 $charset_collate = $this->wpdb->get_charset_collate( );
866
867 $column_definitions = array_map( function ( $column_name, $column_definition ) {
868 return "$column_name $column_definition";
869 }, array_keys( MWCODE_SNIPPET_COLUMNS ), MWCODE_SNIPPET_COLUMNS );
870 $column_definitions = implode( ",\n", $column_definitions ) . ', PRIMARY KEY ( id )';
871
872 $sql = "CREATE TABLE $this->table_name ( $column_definitions ) $charset_collate;";
873 $this->core->log( '💾 ( Code Engine ) Create table request: ' . $sql );
874 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
875 dbDelta( $sql );
876 } catch ( Exception $e ) {
877 $this->core->log( '💾 ( Code Engine ) Error creating Table: ' . $e->getMessage( ) );
878 }
879
880 add_option( 'mwcode_db_snippet_version', $this->mwcode_db_snippet_version );
881 }
882
883 function check_db( )
884 {
885 if ( $this->db_check ) {
886 return true;
887 }
888
889 if ( $this->does_table_exist( $this->table_name ) ) {
890 $this->check_columns( );
891 $this->db_check = true;
892 } else {
893 $this->create_db( );
894 $this->core->log( '💾 ( Code Engine ) Table created, checking if it was successful.' );
895 $this->db_check = $this->does_table_exist( $this->table_name );
896 }
897
898 return $this->db_check;
899 }
900
901 private function check_columns( )
902 {
903 $db_version = get_option( 'mwcode_db_snippet_version' );
904 if ( $db_version == $this->mwcode_db_snippet_version ) {
905 return;
906 }
907
908 $this->core->log( '💾 ( Code Engine ) Database version is ' . $db_version . ', upgrading to ' . $this->mwcode_db_snippet_version . '.' );
909
910 global $wpdb;
911 $table_name = $this->table_name;
912 $charset = $wpdb->get_charset_collate( );
913 $desired_columns = MWCODE_SNIPPET_COLUMNS;
914 $existing_columns = $wpdb->get_results( "DESCRIBE $table_name", ARRAY_A );
915
916 // Handle column removals
917 $columns_to_remove = array_diff( array_column( $existing_columns, 'Field' ), array_keys( $desired_columns ) );
918 if ( !empty( $columns_to_remove ) ) {
919 $remove_queries = array_map( function ( $column_name ) use ( $table_name ) {
920 return "DROP COLUMN $column_name";
921 }, $columns_to_remove );
922 $remove_query = "ALTER TABLE $table_name " . implode( ', ', $remove_queries );
923 $wpdb->query( $remove_query );
924 }
925
926 // Handle column additions and updates
927 $alter_queries = array( );
928 foreach ( $desired_columns as $column_name => $column_definition ) {
929 $existing_column = array_filter( $existing_columns, function ( $column ) use ( $column_name ) {
930 return $column['Field'] === $column_name;
931 } );
932
933 if ( empty( $existing_column ) ) {
934 $alter_queries[] = "ADD COLUMN $column_name $column_definition";
935 } else {
936 $existing_column = array_shift( $existing_column );
937 $existing_column_definition = $existing_column['Type'];
938 if ( $existing_column_definition !== $column_definition ) {
939 $alter_queries[] = "MODIFY COLUMN $column_name $column_definition";
940 }
941 }
942 }
943
944 if ( !empty( $alter_queries ) ) {
945 $alter_query = "ALTER TABLE $table_name " . implode( ', ', $alter_queries );
946 $wpdb->query( $alter_query );
947 }
948
949 update_option( 'mwcode_db_snippet_version', $this->mwcode_db_snippet_version );
950 }
951
952 private function does_table_exist( $table_name )
953 {
954
955 $found = false;
956 $table_name = strtolower( $table_name );
957
958 // Try the fast way first
959 try {
960 $query = "SHOW TABLES LIKE '{$table_name}'";
961 $result = strtolower( $this->wpdb->get_var( $query ) );
962
963 $found = $result === $table_name;
964 } catch ( Exception $e ) {
965 $this->core->log( '💾 ( Code Engine ) Database Check 1 Error: ' . $e->getMessage( ) );
966 }
967
968 // If not found, try the slow way
969 if ( !$found ) {
970 try {
971 $query = "SHOW TABLES";
972 $tables = $this->wpdb->get_results( $query, ARRAY_N );
973 foreach ( $tables as $table ) {
974 $result = strtolower( $table[0] );
975 if ( $result === $table_name ) {
976 $found = true;
977 break;
978 }
979 }
980 } catch ( Exception $e ) {
981 $this->core->log( '💾 ( Code Engine ) Database Check 2 Error: ' . $e->getMessage( ) );
982 }
983 }
984
985 if ( !$found ) {
986 $this->core->log( '💾 ( Code Engine ) Database table doesn\'t seem to exist.' );
987 }
988
989 return $found;
990 }
991
992 #endregion
993 }
994