PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.4.7
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.4.7
0.5.6 0.5.5 0.5.4 0.5.3 0.5.2 0.5.1 0.5.0 0.4.9 0.4.8 0.4.7 0.4.6 trunk 0.0.1 0.0.2 0.2.8 0.2.9 0.3.0 0.3.1 0.3.2 0.3.3 0.3.4 0.3.5 0.3.6 0.3.7 0.3.8 All 32 releases
code-engine / classes / modules / snippet.php

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

1,008 lines 35.0 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 "content" => "content_php",
396 ];
397
398 if ( array_key_exists( $params['scope'], $scopes ) ) {
399 $params['scope'] = $scopes[$params['scope']];
400 }
401
402
403 if ( !empty( $params['tags'] ) ) {
404 $tags = is_array( $params['tags'] ) ? $params['tags'] : explode( ',', $params['tags'] );
405 // Add the current scope to tags and remove duplicates
406 $tags[] = $params['scope'];
407 $tags = array_unique( $tags );
408
409 // Filter tags to include only valid scopes or the current scope
410 $tags = array_filter( $tags, function ( $tag ) use ( $valide_scopes, $params ) {
411 return !in_array( $tag, $valide_scopes ) || $tag === $params['scope'];
412 } );
413
414 // Reset array keys and assign back to params
415 $params['tags'] = array_values( $tags );
416 } else {
417 $params['tags'] = $params['scope'];
418 }
419
420 if ( !in_array( $params['scope'], $valide_scopes ) ) {
421 $errors[] = sprintf( __( 'Invalid scope: %s', 'code-engine' ), $params['scope'] );
422 }
423 }
424
425 // Make sure that the function name declared in the are unique
426 if ( isset( $params['code'] ) && !empty( $params['code'] ) ) {
427 $is_updating = array_key_exists( 'update', $params ) && $params['update'] === true;
428 $function_data = $this->sanitize_and_check_functions( $params['code'], $is_updating );
429 if ( !$function_data['is_valid'] ) {
430 $errors = array_merge( $errors, $function_data['errors'] );
431 }
432 }
433
434 if ( isset( $params['scope'] ) && $params['scope'] === 'function' ) {
435 if ( !isset( $params['functionName'] ) || empty( $params['functionName'] ) ) {
436 $errors[] = __( 'Function name is required.', 'code-engine' );
437 }
438 }
439
440 if ( isset( $params['functionError'] ) && !empty( $params['functionError'] ) ) {
441 $errors[] = __( 'An error was not resolved. (' . $params['functionError'] . ')', 'code-engine' );
442 }
443
444 if ( !empty( $errors ) ) {
445 throw new Exception( "❌ Your code could not be saved because of the following issue( s ): \n\n" . implode( ', ', $errors ) );
446 }
447
448 return $params;
449 }
450
451 public function sanitize_and_check_functions( $code, $is_updating = false )
452 {
453 $errors = [];
454 $function_names = [];
455 $attributes = [];
456
457 // Regular expression to match function declarations
458 $pattern = '/function\s+(\w+)\s*\(/';
459
460 // Find all function declarations
461 preg_match_all( $pattern, $code, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
462
463 if ( !empty( $matches ) ) {
464 foreach ( $matches as $match ) {
465 $function_name = $match[1][0];
466 $start_pos = $match[0][1];
467
468 // Calculate line numbers and positions
469 $lines_before = substr_count( substr( $code, 0, $start_pos ), "\n" );
470 $line_start = strrpos( substr( $code, 0, $start_pos ), "\n" ) + 1;
471 $line_end = strpos( $code, "\n", $start_pos );
472 if ( $line_end === false ) $line_end = strlen( $code );
473
474 $attr = [
475 'startLine' => $lines_before + 1,
476 'startTokenPos' => $start_pos - $line_start,
477 'startFilePos' => $start_pos,
478 'endLine' => $lines_before + 1,
479 'endTokenPos' => $line_end - $line_start,
480 'endFilePos' => $line_end
481 ];
482
483 // Check if function already exists in PHP
484 if ( function_exists( $function_name ) && !$is_updating ) {
485 $errors[] = [
486 'message' => "Function '$function_name' is already declared in PHP.",
487 'attributes' => $attr
488 ];
489 }
490 // Check if function is already declared in this code block
491 elseif ( in_array( $function_name, $function_names ) ) {
492 $errors[] = [
493 'message' => "Function '$function_name' is declared multiple times in the provided code.",
494 'attributes' => $attr
495 ];
496 } else {
497 $function_names[] = $function_name;
498 }
499
500 $attributes[] = $attr;
501 }
502 }
503
504 return [
505 'is_valid' => empty( $errors ),
506 'errors' => $errors,
507 'function_names' => $function_names,
508 'attributes' => $attributes
509 ];
510 }
511
512 /**
513 * Format parameters for saving in the database
514 *
515 * @param array $params
516 * @return array
517 */
518 public function formatParamsForDatabase( $params )
519 {
520 // Gather the scope tags into the tags
521 $tags = null;
522 if ( isset( $params['tags'] ) ) {
523 if ( is_array( $params['tags'] ) ) {
524 $tags = array_map( function ( $tag ) {
525 return trim( $tag );
526 }, $params['tags'] );
527 } else {
528 $tags = array_map( function ( $tag ) {
529 return trim( $tag );
530 }, explode( ',', $params['tags'] ) );
531 }
532 }
533 if ( isset( $params['scope'] ) ) {
534 $tags = array_merge( $tags, is_array( $params['scope'] ) ? $params['scope'] : explode( ',', $params['scope'] ) );
535 }
536 if ( count( $tags ) > 0 ) {
537 $tags = array_filter( $tags, function ( $tag ) {
538 return $tag !== '';
539 } );
540 }
541 $params['tags'] = $tags ? implode( ',', $tags ) : '';
542 //$params['code'] = $this->sanitize_code( $params['code'] );
543 return $params;
544 }
545
546 /**
547 * Format parameters for the front-end
548 *
549 * @param array $params
550 * @return array
551 */
552 private function formatParamsForFront( $params )
553 {
554 // Separate the scope tags from the tags
555 $scopes = ['backend', 'frontend', 'function', 'persistent', 'scheduled', 'content_php', 'content_js'];
556
557 if ( isset( $params['tags'] ) && !empty( $params['tags'] ) ) {
558
559 $tags = array_map( function ( $tag ) use ( $scopes ) {
560 if ( in_array( $tag, $scopes ) ) {
561 return null;
562 }
563 return trim( $tag );
564 }, explode( ',', $params['tags'] ) );
565 $params['tags'] = array_filter( $tags, function ( $tag ) {
566 return $tag !== null;
567 } );
568 }
569 return $params;
570 }
571
572 public function stats()
573 {
574 $scopes = ['function', 'scheduled', 'global', 'content_php', 'content_js'];
575 $globalScopes = ['backend', 'frontend', 'persistent'];
576
577 $stats = [
578 'all' => 0,
579 'disabled' => $this->wpdb->get_var( "SELECT COUNT( * ) FROM $this->table_name WHERE active = 0" ),
580 ];
581
582 foreach ( $scopes as $scope ) {
583 if ( $scope === 'global' ) {
584 $globalScopeQuery = implode( "', '", array_map( 'esc_sql', $globalScopes ) );
585 $stats[$scope] = $this->wpdb->get_var( "SELECT COUNT( * ) FROM $this->table_name WHERE scope IN ('$globalScopeQuery')" );
586 } else {
587 $stats[$scope] = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT( * ) FROM $this->table_name WHERE scope = %s", $scope ) );
588 }
589 $stats['all'] += $stats[$scope];
590 }
591
592 return $stats;
593 }
594
595 public function import( )
596 {
597 $table = $this->wpdb->prefix . 'snippets';
598 $snippets = $this->wpdb->get_results( "SELECT * FROM $table", ARRAY_A );
599
600 if ( !$snippets ) {
601 return 0;
602 }
603
604 // Disable ( set active = 0 ) all the snippets in the old table
605 $this->wpdb->update( $table, ['active' => 0], ['active' => 1] );
606
607 foreach ( $snippets as $snippet ) {
608 $snippet['id'] = null; // Reset the ID so it will be inserted as a new snippet.
609 $snippet = $this->validate( $snippet );
610 $this->insert( $this->formatParamsForDatabase( $snippet ) );
611 }
612
613 return count( $snippets );
614 }
615
616 public function sanitize_code( $code )
617 {
618 $code = ltrim( $code );
619
620 $first_chats = substr( $code, 0, 5 );
621 if( $first_chats === '<?php' ) {
622 $code = substr( $code, 5 );
623 $code = ltrim( $code );
624 }
625
626 return $code;
627 }
628
629 public function delete_duplicates() {
630 // Delete snippets that have the same code and scope, keeping only the most recent one ( based on the updated column )
631 $query = "DELETE t1 FROM $this->table_name t1
632 INNER JOIN $this->table_name t2
633 WHERE t1.id < t2.id
634 AND t1.code = t2.code
635 AND t1.scope = t2.scope";
636
637 $this->wpdb->query( $query );
638
639 return $this->wpdb->rows_affected;
640 }
641
642 #endregion
643
644 #region Snippet CRUD
645
646 public function select( $offset, $limit, $filters, $sort )
647 {
648 if ( !$this->check_db() ) {
649 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
650 }
651
652 $list = [];
653 $offset = !empty( $offset ) ? intval( $offset ) : 0;
654 $limit = !empty( $limit ) ? intval( $limit ) : 10;
655 $filters = !empty( $filters ) ? $filters : [];
656 $sort = !empty( $sort ) ? $sort : ['accessor' => 'updated', 'by' => 'desc'];
657 $query = "SELECT * FROM $this->table_name";
658
659 // Filters
660 if ( is_array( $filters ) && count( $filters ) > 0 ) {
661 $where = [];
662
663 $freshFilters = [];
664 // Little trick that allows searching by tags using the snippet accessor
665 // And to have a global scope that will search for backend, frontend, and persistent
666 foreach ( $filters as $filter ) {
667 if ( $filter['accessor'] === 'snippet' ) {
668 //$freshFilters['accessor'] = 'tags';
669 $freshFilters[] = [ 'accessor' => 'tags', 'value' => $filter['value'] ];
670 }
671 else if ( $filter['accessor'] === 'scope' && $filter['value'] === 'global' ) {
672 $freshFilters[] = [ 'accessor' => 'scope', 'value' => ['backend', 'frontend', 'persistent'] ];
673 }
674 else {
675 $freshFilters[] = $filter;
676 }
677 }
678 $filters = $freshFilters;
679
680 foreach ( $filters as $filter ) {
681 if ( $filter['accessor'] === 'tags' ) {
682 $value = ( array )$filter['value'];
683
684 if ( count( $value ) === 0 ) {
685 continue;
686 }
687 $where_unit = [];
688 foreach ( $value as $tag ) {
689 if ( strpos( $tag, ',' ) !== false ) {
690 $tags = explode( ',', $tag );
691 $where_combination_unit = [];
692 foreach ( $tags as $t ) {
693 $where_combination_unit[] = "FIND_IN_SET( '{$t}', tags )";
694 }
695 $where_unit[] = '( ' . implode( ' AND ', $where_combination_unit ) . ' )';
696 continue;
697 }
698 $where_unit[] = "FIND_IN_SET( '{$tag}', tags )";
699 }
700 $where[] = '( ' . implode( ' OR ', $where_unit ) . ' )';
701 } elseif ( $filter['accessor'] === 'active' ) {
702 $value = esc_sql( $filter['value'] );
703 $where[] = $this->wpdb->prepare( "active = %d", $value );
704 } elseif ( $filter['accessor'] === 'endpoint' ) {
705 $where[] = boolval( $filter['value'] ) ? "endpoint <> ''" : "endpoint = ''";
706 } elseif ( $filter['accessor'] === 'scope' ) {
707 if ( is_array( $filter['value'] ) ) {
708 $scopes = array_map( function( $scope ) {
709 return esc_sql( $scope );
710 }, $filter['value'] );
711 $where[] = "scope IN ('" . implode( "', '", $scopes ) . "')";
712 } else if ( !empty( $filter['value'] ) ) {
713 $value = esc_sql( $filter['value'] );
714 $where[] = $this->wpdb->prepare( "scope = %s", $value );
715 }
716 }
717 }
718 if ( count( $where ) > 0 ) {
719 $query .= " WHERE " . implode( " AND ", $where );
720 }
721 }
722
723 // Count based on this query
724 $list['total'] = $this->wpdb->get_var( "SELECT COUNT( * ) FROM ( $query ) AS t" );
725
726 // Order by
727 $query .= " ORDER BY " . esc_sql( $sort['accessor'] ) . " " . esc_sql( $sort['by'] );
728
729 // Limits
730 if ( $limit > 0 ) {
731 $query .= " LIMIT $offset, $limit";
732 }
733
734 $list['data'] = array_map( function ( $snippet ) {
735 return $this->formatParamsForFront( $snippet );
736 }, $this->wpdb->get_results( $query, ARRAY_A ) );
737
738 $this->get_function_snippets_data( $list['data'] );
739 $this->get_interval_snippets_data( $list['data'] );
740
741 return $list;
742 }
743
744 public function select_tags( )
745 {
746 if ( !$this->check_db( ) ) {
747 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
748 }
749
750 $tags = [];
751 $query = "SELECT tags FROM $this->table_name";
752 $result = $this->wpdb->get_results( $query, ARRAY_A );
753 foreach ( $result as $row ) {
754 $tags = array_merge( $tags, explode( ',', $row['tags'] ) );
755 }
756 // Remove the scope tags: admin, front, once.
757 $tags = array_diff( $tags, ['backend', 'frontend', 'function', 'persistent', 'scheduled'] );
758 return array_values( array_unique( $tags ) );
759 }
760
761 public function select_one( $id, $options = [] )
762 {
763 if ( !$this->check_db( ) ) {
764 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
765 }
766
767 $query = "SELECT * FROM $this->table_name WHERE id = %s";
768 if ( isset( $options['active'] ) ) {
769 $query .= " AND active = " . ( $options['active'] ? '1' : '0' );
770 }
771
772 return $this->formatParamsForFront(
773 $this->wpdb->get_row(
774 $this->wpdb->prepare( $query, ( string ) $id ),
775 ARRAY_A
776 )
777 );
778 }
779
780 public function insert( $insert_data )
781 {
782 if ( !$this->check_db( ) ) {
783 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
784 }
785
786 $data = [];
787 $update_columns = array_keys( MWCODE_SNIPPET_COLUMNS );
788
789 foreach ( $update_columns as $column ) {
790 if ( isset( $insert_data[$column] ) ) {
791 $data[$column] = $insert_data[$column];
792 } else {
793 unset( $data[$column] ); // Remove it if it's empty, so it uses the default db value.
794 }
795 }
796
797 $data['created'] = date( 'Y-m-d H:i:s' );
798 $data['updated'] = date( 'Y-m-d H:i:s' );
799
800 $this->wpdb->insert( $this->table_name, $data );
801 $id = $this->wpdb->insert_id;
802 if ( !$id ) {
803 throw new Exception( __( 'Could not insert the snippet.', 'code-engine' ) );
804 }
805 return $id;
806 }
807
808 public function update( $update_data )
809 {
810 if ( !$this->check_db( ) ) {
811 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
812 }
813
814 $data = [];
815 $update_columns = array_keys( MWCODE_SNIPPET_COLUMNS );
816 foreach ( $update_columns as $column ) {
817 if ( isset( $update_data[$column] ) ) {
818 $data[$column] = $update_data[$column];
819 }
820 }
821 if ( count( $data ) === 0 ) {
822 throw new Exception( __( 'No data to update.', 'code-engine' ) );
823 }
824 $data['updated'] = date( 'Y-m-d H:i:s' );
825 $result = $this->wpdb->update( $this->table_name, $data, ['id' => $update_data['id']] );
826 if ( $result === false ) {
827 throw new Exception( __( 'Could not insert the snippet.', 'code-engine' ) );
828 }
829 return $result;
830 }
831
832 public function force_disable( $id )
833 {
834 if ( !$this->check_db( ) ) {
835 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
836 }
837
838 $result = $this->wpdb->update( $this->table_name, ['active' => 0], ['id' => $id] );
839 if ( $result === false ) {
840 throw new Exception( __( 'Could not disable the snippet.', 'code-engine' ) );
841 }
842 return $result;
843 }
844
845 public function delete( $delete_data )
846 {
847 if ( !$this->check_db( ) ) {
848 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
849 }
850
851 $result = $this->wpdb->delete( $this->table_name, ['id' => $delete_data['id']] );
852 if ( $result === false ) {
853 throw new Exception( __( 'Could not delete the snippet.', 'code-engine' ) );
854 }
855 return $result;
856 }
857
858 public function delete_all( )
859 {
860 if ( !$this->check_db( ) ) {
861 throw new Exception( __( 'Could not access the database.', 'code-engine' ) );
862 }
863
864 $result = $this->wpdb->query( "TRUNCATE TABLE $this->table_name" );
865 if ( $result === false ) {
866 throw new Exception( __( 'Could not delete all snippets.', 'code-engine' ) );
867 }
868 return $result;
869 }
870
871 #endregion
872
873 #region Database
874
875 function create_db( )
876 {
877 $this->core->log( '💾 ( Code Engine ) Creating Table: ' . $this->table_name );
878 try {
879 $charset_collate = $this->wpdb->get_charset_collate( );
880
881 $column_definitions = array_map( function ( $column_name, $column_definition ) {
882 return "$column_name $column_definition";
883 }, array_keys( MWCODE_SNIPPET_COLUMNS ), MWCODE_SNIPPET_COLUMNS );
884 $column_definitions = implode( ",\n", $column_definitions ) . ', PRIMARY KEY ( id )';
885
886 $sql = "CREATE TABLE $this->table_name ( $column_definitions ) $charset_collate;";
887 $this->core->log( '💾 ( Code Engine ) Create table request: ' . $sql );
888 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
889 dbDelta( $sql );
890 } catch ( Exception $e ) {
891 $this->core->log( '💾 ( Code Engine ) Error creating Table: ' . $e->getMessage( ) );
892 }
893
894 add_option( 'mwcode_db_snippet_version', $this->mwcode_db_snippet_version );
895 }
896
897 function check_db( )
898 {
899 if ( $this->db_check ) {
900 return true;
901 }
902
903 if ( $this->does_table_exist( $this->table_name ) ) {
904 $this->check_columns( );
905 $this->db_check = true;
906 } else {
907 $this->create_db( );
908 $this->core->log( '💾 ( Code Engine ) Table created, checking if it was successful.' );
909 $this->db_check = $this->does_table_exist( $this->table_name );
910 }
911
912 return $this->db_check;
913 }
914
915 private function check_columns( )
916 {
917 $db_version = get_option( 'mwcode_db_snippet_version' );
918 if ( $db_version == $this->mwcode_db_snippet_version ) {
919 return;
920 }
921
922 $this->core->log( '💾 ( Code Engine ) Database version is ' . $db_version . ', upgrading to ' . $this->mwcode_db_snippet_version . '.' );
923
924 global $wpdb;
925 $table_name = $this->table_name;
926 $charset = $wpdb->get_charset_collate( );
927 $desired_columns = MWCODE_SNIPPET_COLUMNS;
928 $existing_columns = $wpdb->get_results( "DESCRIBE $table_name", ARRAY_A );
929
930 // Handle column removals
931 $columns_to_remove = array_diff( array_column( $existing_columns, 'Field' ), array_keys( $desired_columns ) );
932 if ( !empty( $columns_to_remove ) ) {
933 $remove_queries = array_map( function ( $column_name ) use ( $table_name ) {
934 return "DROP COLUMN $column_name";
935 }, $columns_to_remove );
936 $remove_query = "ALTER TABLE $table_name " . implode( ', ', $remove_queries );
937 $wpdb->query( $remove_query );
938 }
939
940 // Handle column additions and updates
941 $alter_queries = array( );
942 foreach ( $desired_columns as $column_name => $column_definition ) {
943 $existing_column = array_filter( $existing_columns, function ( $column ) use ( $column_name ) {
944 return $column['Field'] === $column_name;
945 } );
946
947 if ( empty( $existing_column ) ) {
948 $alter_queries[] = "ADD COLUMN $column_name $column_definition";
949 } else {
950 $existing_column = array_shift( $existing_column );
951 $existing_column_definition = $existing_column['Type'];
952 if ( $existing_column_definition !== $column_definition ) {
953 $alter_queries[] = "MODIFY COLUMN $column_name $column_definition";
954 }
955 }
956 }
957
958 if ( !empty( $alter_queries ) ) {
959 $alter_query = "ALTER TABLE $table_name " . implode( ', ', $alter_queries );
960 $wpdb->query( $alter_query );
961 }
962
963 update_option( 'mwcode_db_snippet_version', $this->mwcode_db_snippet_version );
964 }
965
966 private function does_table_exist( $table_name )
967 {
968
969 $found = false;
970 $table_name = strtolower( $table_name );
971
972 // Try the fast way first
973 try {
974 $query = "SHOW TABLES LIKE '{$table_name}'";
975 $result = strtolower( $this->wpdb->get_var( $query ) );
976
977 $found = $result === $table_name;
978 } catch ( Exception $e ) {
979 $this->core->log( '💾 ( Code Engine ) Database Check 1 Error: ' . $e->getMessage( ) );
980 }
981
982 // If not found, try the slow way
983 if ( !$found ) {
984 try {
985 $query = "SHOW TABLES";
986 $tables = $this->wpdb->get_results( $query, ARRAY_N );
987 foreach ( $tables as $table ) {
988 $result = strtolower( $table[0] );
989 if ( $result === $table_name ) {
990 $found = true;
991 break;
992 }
993 }
994 } catch ( Exception $e ) {
995 $this->core->log( '💾 ( Code Engine ) Database Check 2 Error: ' . $e->getMessage( ) );
996 }
997 }
998
999 if ( !$found ) {
1000 $this->core->log( '💾 ( Code Engine ) Database table doesn\'t seem to exist.' );
1001 }
1002
1003 return $found;
1004 }
1005
1006 #endregion
1007 }
1008