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

984 lines 34.2 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( $snippet['functionBehavior'] ) || empty( $snippet['functionBehavior'] ) ) {
147 $snippet['functionBehavior'] = 'dynamic';
148 }
149 else {
150 $snippet['functionBehavior'] = $function_snippet['behavior'];
151 }
152 if ( !isset( $snippet['functionTarget'] ) || empty( $snippet['functionTarget'] ) ) {
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['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'];
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 private function sanitize_code( $code )
616 {
617 $code = preg_replace( '/<\?php/', '', $code, 1 );
618 $code = ltrim( $code );
619 return $code;
620 }
621
622 #endregion
623
624 #region Snippet CRUD
625
626 public function select( $offset, $limit, $filters, $sort )
627 {
628 if ( !$this->check_db() ) {
629 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
630 }
631
632 $list = [];
633 $offset = !empty( $offset ) ? intval( $offset ) : 0;
634 $limit = !empty( $limit ) ? intval( $limit ) : 10;
635 $filters = !empty( $filters ) ? $filters : [];
636 $sort = !empty( $sort ) ? $sort : ['accessor' => 'updated', 'by' => 'desc'];
637 $query = "SELECT * FROM $this->table_name";
638
639 // Filters
640 if ( is_array( $filters ) && count( $filters ) > 0 ) {
641 $where = [];
642
643 $freshFilters = [];
644 // Little trick that allows searching by tags using the snippet accessor
645 // And to have a global scope that will search for backend, frontend, and persistent
646 foreach ( $filters as $filter ) {
647 if ( $filter['accessor'] === 'snippet' ) {
648 //$freshFilters['accessor'] = 'tags';
649 $freshFilters[] = [ 'accessor' => 'tags', 'value' => $filter['value'] ];
650 }
651 else if ( $filter['accessor'] === 'scope' && $filter['value'] === 'global' ) {
652 $freshFilters[] = [ 'accessor' => 'scope', 'value' => ['backend', 'frontend', 'persistent'] ];
653 }
654 else {
655 $freshFilters[] = $filter;
656 }
657 }
658 $filters = $freshFilters;
659
660 foreach ( $filters as $filter ) {
661 if ( $filter['accessor'] === 'tags' ) {
662 $value = ( array )$filter['value'];
663
664 if ( count( $value ) === 0 ) {
665 continue;
666 }
667 $where_unit = [];
668 foreach ( $value as $tag ) {
669 if ( strpos( $tag, ',' ) !== false ) {
670 $tags = explode( ',', $tag );
671 $where_combination_unit = [];
672 foreach ( $tags as $t ) {
673 $where_combination_unit[] = "FIND_IN_SET( '{$t}', tags )";
674 }
675 $where_unit[] = '( ' . implode( ' AND ', $where_combination_unit ) . ' )';
676 continue;
677 }
678 $where_unit[] = "FIND_IN_SET( '{$tag}', tags )";
679 }
680 $where[] = '( ' . implode( ' OR ', $where_unit ) . ' )';
681 } elseif ( $filter['accessor'] === 'active' ) {
682 $value = esc_sql( $filter['value'] );
683 $where[] = $this->wpdb->prepare( "active = %d", $value );
684 } elseif ( $filter['accessor'] === 'endpoint' ) {
685 $where[] = boolval( $filter['value'] ) ? "endpoint <> ''" : "endpoint = ''";
686 } elseif ( $filter['accessor'] === 'scope' ) {
687 if ( is_array( $filter['value'] ) ) {
688 $scopes = array_map( function( $scope ) {
689 return esc_sql( $scope );
690 }, $filter['value'] );
691 $where[] = "scope IN ('" . implode( "', '", $scopes ) . "')";
692 } else {
693 $value = esc_sql( $filter['value'] );
694 $where[] = $this->wpdb->prepare( "scope = %s", $value );
695 }
696 }
697 }
698 if ( count( $where ) > 0 ) {
699 $query .= " WHERE " . implode( " AND ", $where );
700 }
701 }
702
703 // Count based on this query
704 $list['total'] = $this->wpdb->get_var( "SELECT COUNT( * ) FROM ( $query ) AS t" );
705
706 // Order by
707 $query .= " ORDER BY " . esc_sql( $sort['accessor'] ) . " " . esc_sql( $sort['by'] );
708
709 // Limits
710 if ( $limit > 0 ) {
711 $query .= " LIMIT $offset, $limit";
712 }
713
714 $list['data'] = array_map( function ( $snippet ) {
715 return $this->formatParamsForFront( $snippet );
716 }, $this->wpdb->get_results( $query, ARRAY_A ) );
717
718 $this->get_function_snippets_data( $list['data'] );
719 $this->get_interval_snippets_data( $list['data'] );
720
721 return $list;
722 }
723
724 public function select_tags( )
725 {
726 if ( !$this->check_db( ) ) {
727 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
728 }
729
730 $tags = [];
731 $query = "SELECT tags FROM $this->table_name";
732 $result = $this->wpdb->get_results( $query, ARRAY_A );
733 foreach ( $result as $row ) {
734 $tags = array_merge( $tags, explode( ',', $row['tags'] ) );
735 }
736 // Remove the scope tags: admin, front, once.
737 $tags = array_diff( $tags, ['backend', 'frontend', 'function', 'persistent', 'scheduled'] );
738 return array_values( array_unique( $tags ) );
739 }
740
741 public function select_one( $id, $options = [] )
742 {
743 if ( !$this->check_db( ) ) {
744 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
745 }
746
747 $query = "SELECT * FROM $this->table_name WHERE id = %s";
748 if ( isset( $options['active'] ) ) {
749 $query .= " AND active = " . ( $options['active'] ? '1' : '0' );
750 }
751
752 return $this->formatParamsForFront(
753 $this->wpdb->get_row(
754 $this->wpdb->prepare( $query, ( string ) $id ),
755 ARRAY_A
756 )
757 );
758 }
759
760 public function insert( $insert_data )
761 {
762 if ( !$this->check_db( ) ) {
763 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
764 }
765
766 $data = [];
767 $update_columns = array_keys( MWCODE_SNIPPET_COLUMNS );
768
769 foreach ( $update_columns as $column ) {
770 if ( isset( $insert_data[$column] ) ) {
771 $data[$column] = $insert_data[$column];
772 } else {
773 unset( $data[$column] ); // Remove it if it's empty, so it uses the default db value.
774 }
775 }
776
777 $data['created'] = date( 'Y-m-d H:i:s' );
778 $data['updated'] = date( 'Y-m-d H:i:s' );
779
780 $this->wpdb->insert( $this->table_name, $data );
781 $id = $this->wpdb->insert_id;
782 if ( !$id ) {
783 throw new Exception( __( 'Could not insert the snippet.', 'code-engine' ) );
784 }
785 return $id;
786 }
787
788 public function update( $update_data )
789 {
790 if ( !$this->check_db( ) ) {
791 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
792 }
793
794 $data = [];
795 $update_columns = array_keys( MWCODE_SNIPPET_COLUMNS );
796 foreach ( $update_columns as $column ) {
797 if ( isset( $update_data[$column] ) ) {
798 $data[$column] = $update_data[$column];
799 }
800 }
801 if ( count( $data ) === 0 ) {
802 throw new Exception( __( 'No data to update.', 'code-engine' ) );
803 }
804 $data['updated'] = date( 'Y-m-d H:i:s' );
805 $result = $this->wpdb->update( $this->table_name, $data, ['id' => $update_data['id']] );
806 if ( $result === false ) {
807 throw new Exception( __( 'Could not insert the snippet.', 'code-engine' ) );
808 }
809 return $result;
810 }
811
812 public function force_disable( $id )
813 {
814 if ( !$this->check_db( ) ) {
815 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
816 }
817
818 $result = $this->wpdb->update( $this->table_name, ['active' => 0], ['id' => $id] );
819 if ( $result === false ) {
820 throw new Exception( __( 'Could not disable the snippet.', 'code-engine' ) );
821 }
822 return $result;
823 }
824
825 public function delete( $delete_data )
826 {
827 if ( !$this->check_db( ) ) {
828 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
829 }
830
831 $result = $this->wpdb->delete( $this->table_name, ['id' => $delete_data['id']] );
832 if ( $result === false ) {
833 throw new Exception( __( 'Could not delete the snippet.', 'code-engine' ) );
834 }
835 return $result;
836 }
837
838 public function delete_all( )
839 {
840 if ( !$this->check_db( ) ) {
841 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
842 }
843
844 $result = $this->wpdb->query( "TRUNCATE TABLE $this->table_name" );
845 if ( $result === false ) {
846 throw new Exception( __( 'Could not delete all snippets.', 'code-engine' ) );
847 }
848 return $result;
849 }
850
851 #endregion
852
853 #region Database
854
855 function create_db( )
856 {
857 $this->core->log( '💾 ( Code Engine ) Creating Table: ' . $this->table_name );
858 try {
859 $charset_collate = $this->wpdb->get_charset_collate( );
860
861 $column_definitions = array_map( function ( $column_name, $column_definition ) {
862 return "$column_name $column_definition";
863 }, array_keys( MWCODE_SNIPPET_COLUMNS ), MWCODE_SNIPPET_COLUMNS );
864 $column_definitions = implode( ",\n", $column_definitions ) . ', PRIMARY KEY ( id )';
865
866 $sql = "CREATE TABLE $this->table_name ( $column_definitions ) $charset_collate;";
867 $this->core->log( '💾 ( Code Engine ) Create table request: ' . $sql );
868 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
869 dbDelta( $sql );
870 } catch ( Exception $e ) {
871 $this->core->log( '💾 ( Code Engine ) Error creating Table: ' . $e->getMessage( ) );
872 }
873
874 add_option( 'mwcode_db_snippet_version', $this->mwcode_db_snippet_version );
875 }
876
877 function check_db( )
878 {
879 if ( $this->does_table_exist( $this->table_name ) ) {
880 $this->check_columns( );
881 $this->db_check = true;
882 } else {
883 $this->create_db( );
884 $this->core->log( '💾 ( Code Engine ) Table created, checking if it was successful.' );
885 $this->db_check = $this->does_table_exist( $this->table_name );
886 }
887
888 return $this->db_check;
889 }
890
891 private function check_columns( )
892 {
893 $db_version = get_option( 'mwcode_db_snippet_version' );
894 if ( $db_version == $this->mwcode_db_snippet_version ) {
895 return;
896 }
897
898 $this->core->log( '💾 ( Code Engine ) Database version is ' . $db_version . ', upgrading to ' . $this->mwcode_db_snippet_version . '.' );
899
900 global $wpdb;
901 $table_name = $this->table_name;
902 $charset = $wpdb->get_charset_collate( );
903 $desired_columns = MWCODE_SNIPPET_COLUMNS;
904 $existing_columns = $wpdb->get_results( "DESCRIBE $table_name", ARRAY_A );
905
906 // Handle column removals
907 $columns_to_remove = array_diff( array_column( $existing_columns, 'Field' ), array_keys( $desired_columns ) );
908 if ( !empty( $columns_to_remove ) ) {
909 $remove_queries = array_map( function ( $column_name ) use ( $table_name ) {
910 return "DROP COLUMN $column_name";
911 }, $columns_to_remove );
912 $remove_query = "ALTER TABLE $table_name " . implode( ', ', $remove_queries );
913 $wpdb->query( $remove_query );
914 }
915
916 // Handle column additions and updates
917 $alter_queries = array( );
918 foreach ( $desired_columns as $column_name => $column_definition ) {
919 $existing_column = array_filter( $existing_columns, function ( $column ) use ( $column_name ) {
920 return $column['Field'] === $column_name;
921 } );
922
923 if ( empty( $existing_column ) ) {
924 $alter_queries[] = "ADD COLUMN $column_name $column_definition";
925 } else {
926 $existing_column = array_shift( $existing_column );
927 $existing_column_definition = $existing_column['Type'];
928 if ( $existing_column_definition !== $column_definition ) {
929 $alter_queries[] = "MODIFY COLUMN $column_name $column_definition";
930 }
931 }
932 }
933
934 if ( !empty( $alter_queries ) ) {
935 $alter_query = "ALTER TABLE $table_name " . implode( ', ', $alter_queries );
936 $wpdb->query( $alter_query );
937 }
938
939 update_option( 'mwcode_db_snippet_version', $this->mwcode_db_snippet_version );
940 }
941
942 private function does_table_exist( $table_name )
943 {
944
945 $found = false;
946 $table_name = strtolower( $table_name );
947
948 // Try the fast way first
949 try {
950 $query = "SHOW TABLES LIKE '{$table_name}'";
951 $result = strtolower( $this->wpdb->get_var( $query ) );
952
953 $found = $result === $table_name;
954 } catch ( Exception $e ) {
955 $this->core->log( '💾 ( Code Engine ) Database Check 1 Error: ' . $e->getMessage( ) );
956 }
957
958 // If not found, try the slow way
959 if ( !$found ) {
960 try {
961 $query = "SHOW TABLES";
962 $tables = $this->wpdb->get_results( $query, ARRAY_N );
963 foreach ( $tables as $table ) {
964 $result = strtolower( $table[0] );
965 if ( $result === $table_name ) {
966 $found = true;
967 break;
968 }
969 }
970 } catch ( Exception $e ) {
971 $this->core->log( '💾 ( Code Engine ) Database Check 2 Error: ' . $e->getMessage( ) );
972 }
973 }
974
975 if ( !$found ) {
976 $this->core->log( '💾 ( Code Engine ) Database table doesn\'t seem to exist.' );
977 }
978
979 return $found;
980 }
981
982 #endregion
983 }
984