PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.7
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.7
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / vendor / wpfluent / framework / src / WPFluent / Database / Schema.php

Schema.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.7, at vendor/wpfluent/framework/src/WPFluent/Database/Schema.php

962 lines 26.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\Framework\Database;
4
5 use FluentForm\Framework\Database\Concerns\MaintainsDatabase;
6
7 class Schema
8 {
9 use MaintainsDatabase;
10
11 /**
12 * Keep track of custom tables when unit testing
13 *
14 * @var array
15 */
16 public static $customTempTables = [];
17
18 /**
19 * Get the global $wpdb instance
20 *
21 * @return \wpdb The global $wpdb
22 */
23 public static function db()
24 {
25 return $GLOBALS['wpdb'];
26 }
27
28 /**
29 * Get schema/db information
30 *
31 * @return string|array
32 */
33 public static function getInfo($key = null)
34 {
35 $db = static::db();
36
37 if (static::isSqlite()) {
38 $info = [
39 'dbname' => 'sqlite',
40 'prefix' => $db->prefix,
41 'dbhost' => '',
42 'username' => '',
43 'password' => '',
44 'charset' => 'utf8',
45 'collation' => '',
46 'tables' => static::getTableList(),
47 ];
48
49 return $key ? ($info[$key] ?? null) : $info;
50 }
51
52 $info = [
53 // @phpstan-ignore-next-line
54 'dbname' => $db->dbname,
55 'prefix' => $db->prefix,
56 // @phpstan-ignore-next-line
57 'dbhost' => $db->dbhost,
58 // @phpstan-ignore-next-line
59 'username' => $db->dbuser,
60 // @phpstan-ignore-next-line
61 'password' => $db->dbpassword,
62 'charset' => $db->charset,
63 'collation' => $db->collate,
64 'tables' => static::getTableList(),
65 ];
66
67 return $key ? $info[$key] : $info;
68 }
69
70 /**
71 * Migrates database table(s)
72 *
73 * @param string|array $table The table name without prefix
74 * or an array where each key is table name and value is sql.
75 *
76 * @param string $sql Optional
77 * @return mixed
78 */
79 public static function migrate($table, $sql = null)
80 {
81 if (!$sql && is_array($table)) {
82 $result = [];
83 foreach ($table as $t => $s) {
84 $result = array_merge(
85 $result, (array) static::createTable($t, $s)
86 );
87 }
88 return $result;
89 } else {
90 return static::createTable($table, $sql);
91 }
92 }
93
94 /**
95 * Creates a new table if doesn't exist using dbDelta function
96 *
97 * @param string $table The table name without the prefix
98 * @param string $sql The sql to create table or an absolute path of a
99 * .sql file containing the column definations for creating the new table.
100 *
101 * @return string Message
102 */
103 public static function createTableIfNotExist($table, $sql)
104 {
105 if (!static::hasTable($table)) {
106 return static::createTable($table, $sql);
107 }
108 }
109
110 /**
111 * Checks if a column exists in a table
112 *
113 * @param string $column The column name of the table
114 * @param string $table The table name without prefix
115 * @return boolean
116 */
117
118 public static function hasColumn($column, $table)
119 {
120 $wpdb = static::db();
121
122 $table = static::table($table);
123
124 $columns = $wpdb->get_col("DESCRIBE $table");
125
126 return in_array($column, $columns);
127 }
128
129 /**
130 * Checks if a table exists
131 *
132 * @param string $table The table name without prefix
133 * @return boolean
134 */
135 public static function hasTable($table)
136 {
137 $wpdb = static::db();
138
139 $table = static::table($table);
140
141 $result = $wpdb->get_var("SHOW TABLES LIKE '" . $table . "'") == $table;
142
143 if ($result) {
144 return $result;
145 }
146
147 // Check if any temporary table exists by this table name.
148
149 // At first, store the original state of the error suppress
150 // error and then turn off the error from being shown, so
151 // error will be not shown if there is no temporary
152 // table, then restore the error state.
153 $isErrorSuppressed = $wpdb->suppress_errors;
154
155 $wpdb->suppress_errors = true;
156
157 static::query("SELECT 1 FROM %{$table}% WHERE 0");
158
159 $hasError = !empty($wpdb->last_error);
160
161 $wpdb->suppress_errors = $isErrorSuppressed;
162
163 return !$hasError;
164 }
165
166 /**
167 * Resolves the table prefix and makes the table name with prefix
168 *
169 * @param string $table The table name without the prefix
170 * @return string The resolved table name with the prefix
171 */
172 public static function table($table)
173 {
174 $wpdb = static::db();
175
176 $prefix = $wpdb->prefix;
177
178 if (strpos($table, $prefix) === 0) {
179 return $table;
180 }
181
182 return isset($wpdb->{$table}) ? $wpdb->{$table} : ($wpdb->prefix.$table);
183 }
184
185
186 /**
187 * Resolves the sql prefix
188 *
189 * @param string $sql The file name or the raw sql
190 * @return string The resolved sql
191 */
192 public static function sql($sql = '')
193 {
194 $allowedSqlFileFormats = [
195 'sql'
196 ];
197
198 foreach ($allowedSqlFileFormats as $format) {
199 if (str_ends_with($sql, '.' . $format)) {
200 $sql = @file_exists($sql) ? file_get_contents($sql) : $sql;
201 break;
202 }
203 }
204
205 return $sql;
206 }
207
208 /**
209 * Clean the sql string by removing the comments.
210 *
211 * @param string $sql
212 * @return string
213 */
214 public static function cleanUp($sql)
215 {
216 $sql = static::sql($sql);
217
218 // Remove inline -- comments
219 $sql = preg_replace('/--.*$/m', '', $sql);
220
221 // Remove inline # comments
222 $sql = preg_replace('/#.*$/m', '', $sql);
223
224 // Remove block /* ... */
225 $sql = preg_replace('/\/\*.*?\*\//s', '', $sql);
226
227 // Collapse multiple spaces & newlines
228 // $sql = preg_replace('/\s+/', ' ', $sql);
229
230 // Remove dangling commas before closing parenthesis
231 $sql = preg_replace('/,\s*\)/', ')', $sql);
232
233 return trim($sql);
234 }
235
236 /**
237 * Creates a new table using dbDelta function or alters the table if exists.
238 *
239 * @param string $table The table name without the prefix
240 * @param string $sql The sql to create table or an absolute path of a
241 * .sql file containing the column definations for creating the new table.
242 *
243 * @return string message
244 */
245 public static function createTable($table, $sql)
246 {
247 $table = static::table($table);
248
249 $sql = static::cleanUp($sql);
250
251 if (static::isSqlite()) {
252 $sql = preg_replace('/\bjson\b/i', 'longtext', $sql);
253 }
254
255 $collate = static::db()->get_charset_collate();
256
257 return static::callDBDelta(
258 "CREATE TABLE $table (
259 ".PHP_EOL.trim(trim($sql), ',').PHP_EOL."
260 ) $collate;"
261 );
262 }
263
264 /**
265 * Alters an existing table if exists
266 *
267 * @param string $table The table name without the prefix
268 * @param string $sql The sql to create table or an absolute path of a
269 * .sql file containing the column definations for creating the new table.
270 *
271 * @return string message
272 */
273 public static function alterTableIfExists($table, $sql)
274 {
275 if (static::hasTable($table)) {
276 return static::alterTable($table, $sql);
277 }
278 }
279
280 /**
281 * Alters an existing table
282 *
283 * @param string $table The table name without the prefix
284 * @param string $sql The sql to create table or an absolute path of a
285 * .sql file containing the column definations for creating the new table.
286 *
287 * @return string message
288 */
289 public static function alterTable($table, $sql)
290 {
291 $table = static::table($table);
292
293 $sql = static::cleanUp($sql);
294
295 if (static::isSqlite()) {
296 $sql = preg_replace('/\bjson\b/i', 'longtext', $sql);
297 }
298
299 $parts = array_filter(array_map('trim', static::splitAlterClauses($sql)));
300
301 if (static::isSqlite()) {
302 // SQLite only supports one ALTER TABLE operation per statement.
303 $result = null;
304 foreach ($parts as $part) {
305 $result = static::query("ALTER TABLE $table {$part};");
306 }
307 return $result;
308 }
309
310 $sql = "ALTER TABLE $table ".PHP_EOL.rtrim(
311 trim(implode(','.PHP_EOL, $parts)), ';'
312 ).";";
313
314 return static::query($sql);
315 }
316
317 /**
318 * Split a comma-separated ALTER TABLE clause list, respecting parentheses
319 * so that DECIMAL(10,2) and similar types are not split mid-definition.
320 *
321 * @param string $sql
322 * @return string[]
323 */
324 protected static function splitAlterClauses($sql)
325 {
326 $parts = [];
327 $depth = 0;
328 $current = '';
329
330 for ($i = 0, $len = strlen($sql); $i < $len; $i++) {
331 $char = $sql[$i];
332 if ($char === '(') {
333 $depth++;
334 } elseif ($char === ')') {
335 $depth--;
336 } elseif ($char === ',' && $depth === 0) {
337 $parts[] = $current;
338 $current = '';
339 continue;
340 }
341 $current .= $char;
342 }
343
344 if ($current !== '') {
345 $parts[] = $current;
346 }
347
348 return $parts;
349 }
350
351 /**
352 * Alters an existing table using dbDelta function if exists, otherwise creates
353 * it. Alters an existing table but takes the table creation column defination.
354 * This is because, the dbDelta functioin can create or update a table using
355 * the table creation defination. In this, case, if a table exists and the
356 * columns are matched then nothing happens but if there's any difference
357 * in the new sql then the dbDelta alters the table using the new sql
358 * defination but doesn't delete any columns. So, after the dbDelta
359 * finishes it's job, any non-existing columns in the new sql
360 * defination will be deleted from the existing table. if
361 * table is not there then the table gets created.
362 *
363 * @param string $table The table name without the prefix
364 * @param string $sql The sql to create table or an absolute path of a
365 * .sql file containing the column definations for creating the new table.
366 *
367 * @return string message
368 */
369 public static function updateTable($table, $sql)
370 {
371 $sql = static::cleanUp($sql);
372
373 if (static::isSqlite()) {
374 $sql = preg_replace('/\bjson\b/i', 'longtext', $sql);
375 }
376
377 $columnsDefinitions = array_map('trim', static::splitAlterClauses($sql));
378 $schemaSql = implode(",\n", $columnsDefinitions);
379
380 // Extract desired column names (skip constraint/index clauses)
381 $columns = [];
382 foreach ($columnsDefinitions as $definition) {
383 if (preg_match('/^(?:PRIMARY\s+KEY|UNIQUE(?:(?:\s+KEY|\s+INDEX))?|KEY|INDEX|CONSTRAINT|FOREIGN\s+KEY|FULLTEXT|SPATIAL)\b/i', ltrim($definition))) {
384 continue;
385 }
386
387 if (preg_match('/^`?(\w+)`?\s+/i', $definition, $matches)) {
388 $columns[$matches[1]] = $definition;
389 }
390 }
391
392 $tbl = static::table($table);
393
394 // SQLite cannot drop PRIMARY KEY columns or columns referenced by indexes
395 // via native ALTER TABLE DROP COLUMN.
396 //
397 // Workarounds:
398 // - Indexed columns: drop the index first with DROP INDEX,
399 // then drop the column.
400 // - PRIMARY KEY columns: use CHANGE COLUMN to rebuild the table without
401 // the PK attribute (renaming the column to a temp name), then drop
402 // the renamed column.
403 if (static::isSqlite()) {
404 // Collect PRIMARY KEY columns and per-column index key names.
405 $indexRows = (array) static::db()->get_results("SHOW INDEX FROM {$tbl}");
406 $pkColumns = [];
407 $columnIndexes = []; // column_name => [key_name, ...]
408 foreach ($indexRows as $row) {
409 if ($row->Key_name === 'PRIMARY') {
410 $pkColumns[] = $row->Column_name;
411 } else {
412 $columnIndexes[$row->Column_name][] = $row->Key_name;
413 }
414 }
415
416 $existingColumns = static::getColumns($table) ?: [];
417
418 // 1. Add any desired columns not yet in the table.
419 foreach ($columns as $colName => $definition) {
420 if (!in_array($colName, $existingColumns)) {
421 static::db()->query("ALTER TABLE {$tbl} ADD COLUMN {$definition}");
422 }
423 }
424
425 // 2. Drop every column that is not in the desired schema.
426 foreach ($existingColumns as $column) {
427 if (isset($columns[$column])) {
428 continue; // desired — keep it
429 }
430
431 // Drop non-primary indexes referencing this column first, otherwise
432 // native SQLite ALTER TABLE DROP COLUMN fails.
433 foreach ($columnIndexes[$column] ?? [] as $keyName) {
434 static::db()->query("ALTER TABLE {$tbl} DROP INDEX {$keyName}");
435 }
436
437 if (in_array($column, $pkColumns)) {
438 // Native SQLite ALTER TABLE DROP COLUMN rejects PRIMARY KEY columns.
439 // Use CHANGE COLUMN to trigger an internal table rebuild that strips
440 // the PK attribute, then drop the (now ordinary) renamed column.
441 $tmpCol = $column . '_wpf_drop';
442 static::db()->query("ALTER TABLE {$tbl} CHANGE COLUMN {$column} {$tmpCol} INT NULL");
443 static::db()->query("ALTER TABLE {$tbl} DROP COLUMN {$tmpCol}");
444 } else {
445 static::db()->query("ALTER TABLE {$tbl} DROP COLUMN {$column}");
446 }
447 }
448
449 return [$tbl => "Updated table structure"];
450 }
451
452 // MySQL path: use dbDelta to create/update, then drop extra columns.
453 $result = static::createTable($table, $schemaSql);
454 $existingColumns = static::getColumns($table) ?: [];
455
456 // Add missing columns
457 foreach ($columns as $colName => $definition) {
458 if (!in_array($colName, $existingColumns)) {
459 static::query("ALTER TABLE $tbl ADD COLUMN $definition");
460 $result[$tbl.'.'.$colName] = "Added column {$tbl}.{$colName}";
461 }
462 }
463
464 // Drop extra columns
465 foreach ($existingColumns as $column) {
466 if (!isset($columns[$column])) {
467 static::query("ALTER TABLE $tbl DROP COLUMN $column");
468 $result[$tbl.'.'.$column] = "Dropped column {$tbl}.{$column}";
469 }
470 }
471
472 return $result;
473 }
474
475 /**
476 * Drops/deletes an existing table.
477 *
478 * @param string $table The table name without the prefix
479 * @param bool $disableForeignKeyCheck Optional.
480 * @return bool
481 */
482 public static function dropTable($table, $disableForeignKeyCheck = true)
483 {
484 return static::db()->query('DROP TABLE ' . static::table($table));
485 }
486
487 /**
488 * Drops/deletes an existing table if exists
489 *
490 * @param string $table The table name without the prefix
491 * @param bool $disableForeignKeyCheck Optional.
492 * @return bool
493 */
494 public static function dropTableIfExists($table, $disableForeignKeyCheck = true)
495 {
496 if (static::hasTable($table)) {
497 return static::dropTable($table, $disableForeignKeyCheck);
498 }
499 }
500
501 /**
502 * Truncate a table.
503 *
504 * @param string $table
505 * @return bool
506 */
507 public static function truncate($table)
508 {
509 $table = static::table($table);
510
511 $result = static::db()->query("TRUNCATE TABLE $table");
512
513 if (static::isSqlite()) {
514 // SQLite translates TRUNCATE TABLE to DELETE FROM, which does NOT reset
515 // the auto-increment counter. Manually clear the sqlite_sequence row so
516 // that the next INSERT starts at id=1 (matching MySQL TRUNCATE behaviour).
517 static::db()->query("DELETE FROM sqlite_sequence WHERE name='{$table}'");
518 }
519
520 return $result;
521 }
522
523 /**
524 * Truncate a table if exists.
525 *
526 * @param string $table
527 * @return bool
528 */
529 public static function truncateTableIfExists($table)
530 {
531 if (static::hasTable($table)) {
532 return static::truncate($table);
533 }
534 }
535
536 /**
537 * Adds a new index to a column of given table.
538 *
539 * @param string $table Table name
540 * @param string $index Columns name
541 * @return bool
542 * @see https://developer.wordpress.org/reference/functions/add_clean_index
543 */
544 public static function addIndex($table, $index)
545 {
546 return add_clean_index(static::table($table), $index);
547 }
548
549 /**
550 * Drops an index from a column of given table.
551 *
552 * @param string $table Table name
553 * @param string $index Columns name
554 * @return bool
555 * @see https://developer.wordpress.org/reference/functions/drop_index
556 */
557 public static function dropIndex($table, $index)
558 {
559 if (static::isSqlite()) {
560 $tbl = static::table($table);
561 return static::db()->query("ALTER TABLE {$tbl} DROP INDEX {$index}");
562 }
563
564 return drop_index(static::table($table), $index);
565 }
566
567 /**
568 * Makes raw query and can resolve the table name from the query
569 * and can form a full table name including the table prefix if
570 * the table name is wrapped like: %table_name% in the query.
571 *
572 * @param string $query
573 * @return mixed
574 */
575 public static function query($query)
576 {
577 $query = preg_replace_callback('/(?<![\'"])%([a-zA-Z0-9_-]+)%(?![\'"])/', function ($matches) {
578 return static::table($matches[1]);
579 }, $query);
580
581 if (preg_match('/^(SELECT|SHOW|DESCRIBE|EXPLAIN)\s+/i', trim($query))) {
582 return static::db()->get_results($query, OBJECT);
583 }
584
585 return static::db()->query($query);
586 }
587
588 /**
589 * Get a list of all columns from the given table name.
590 *
591 * @param string $table The table name without the prefix
592 * @return array|null
593 */
594 public static function getColumns($table)
595 {
596 if (!static::hasTable($table)) {
597 return null;
598 }
599
600 return static::db()->get_col(
601 'DESCRIBE ' . static::table($table), 0
602 );
603 }
604
605 /**
606 * Gets a list of all columns including column information
607 *
608 * @param string $table The table name without the prefix
609 * @return array
610 */
611 protected static function protectedGetColumnsWithTypes($table)
612 {
613 if (!static::hasTable($table)) return;
614
615 $table = static::table($table);
616
617 if (static::isSqlite()) {
618 // INFORMATION_SCHEMA is not available in SQLite.
619 // Use DESCRIBE which the WP SQLite plugin translates with
620 // proper MySQL type mapping from its data type cache.
621 $columns = (array) static::db()->get_results("DESCRIBE {$table}");
622 return array_map(function ($col, $pos) {
623 $extra = $col->Extra ?? '';
624 // SQLite INTEGER PRIMARY KEY is auto_increment but DESCRIBE
625 // doesn't report it in Extra — detect from Key + Type.
626 if (empty($extra) && ($col->Key ?? '') === 'PRI') {
627 $type = strtoupper($col->Type ?? '');
628 if (in_array($type, ['INTEGER', 'BIGINT', 'BIGINT(20)', 'BIGINT(20) UNSIGNED', 'INT'])) {
629 $extra = 'auto_increment';
630 }
631 }
632
633 return [
634 'column_name' => $col->Field,
635 'ordinal_position' => $pos + 1,
636 'column_default' => $col->Default,
637 'is_nullable' => ($col->Null === 'YES' ? 'YES' : 'NO'),
638 'data_type' => strtolower(
639 preg_replace('/\(.*/', '', $col->Type)
640 ),
641 'character_maximum_length' => preg_match(
642 '/\((\d+)\)/', $col->Type, $matches
643 ) ? (int)$matches[1] : null,
644 'numeric_precision' => null,
645 'numeric_scale' => null,
646 'column_key' => $col->Key ?? '',
647 'extra' => $extra,
648 ];
649 }, $columns, array_keys($columns));
650 }
651
652 // @phpstan-ignore-next-line
653 $db = static::db()->dbname;
654
655 $fields = [
656 'COLUMN_NAME',
657 'ORDINAL_POSITION',
658 'COLUMN_DEFAULT',
659 'IS_NULLABLE',
660 'DATA_TYPE',
661 'CHARACTER_MAXIMUM_LENGTH',
662 'NUMERIC_PRECISION',
663 'NUMERIC_SCALE',
664 'COLUMN_KEY',
665 'EXTRA',
666 ];
667
668 $sql = "SELECT " . implode(',', $fields) . " FROM INFORMATION_SCHEMA.COLUMNS";
669 $sql .= " WHERE TABLE_NAME = '".$table."' AND TABLE_SCHEMA = '".$db."'";
670
671 return array_map(function($i) {
672 $item = [];
673 foreach ((array) $i as $key => $value) {
674 $item[strtolower($key)] = $value;
675 }
676 return $item;
677 }, (array) static::db()->get_results($sql));
678 }
679
680 public static function getColumnsWithTypes($table)
681 {
682 $columns = static::protectedGetColumnsWithTypes($table);
683
684 if (!empty($columns)) {
685 return $columns;
686 }
687
688 if (static::isSqlite()) {
689 return [];
690 }
691
692 $columns = static::db()->get_results(
693 'SHOW COLUMNS FROM `'.static::table($table).'`'
694 );
695
696 return array_map(function ($col) {
697 return [
698 'column_name' => $col->Field,
699 'ordinal_position' => null,
700 'column_default' => $col->Default,
701 'is_nullable' => ($col->Null === 'YES' ? 'YES' : 'NO'),
702 'data_type' => strtolower(
703 preg_replace('/\(.*/', '', $col->Type)
704 ),
705 'character_maximum_length' => preg_match(
706 '/\((\d+)\)/', $col->Type, $matches
707 ) ? (int)$matches[1] : null,
708 'numeric_precision' => null,
709 'numeric_scale' => null,
710 'column_key' => $col->Key,
711 'extra' => $col->Extra,
712 ];
713 }, (array) $columns);
714 }
715
716 /**
717 * Gets a list of all columns including column information
718 *
719 * @param string $table The table name without the prefix
720 * @return array
721 */
722 public static function describeTable($table)
723 {
724 return static::getColumnsWithTypes($table);
725 }
726
727 /**
728 * Gets a list of all foreign keys from the given table name.
729 *
730 * @param string $table
731 * @return array
732 */
733 public static function getTableForeignKeys($table)
734 {
735 $table = static::table($table);
736
737 if (static::isSqlite()) {
738 // Parse foreign keys from the CREATE TABLE statement in sqlite_master
739 // since PRAGMA queries cannot go through $wpdb.
740 $row = static::db()->get_row(
741 "SELECT sql FROM sqlite_master WHERE type='table' AND name='{$table}'"
742 );
743 if (!$row || empty($row->sql)) {
744 return [];
745 }
746 $fks = [];
747 if (preg_match_all(
748 '/FOREIGN\s+KEY\s*\(\s*[`"]?(\w+)[`"]?\s*\)\s*REFERENCES\s+[`"]?(\w+)[`"]?\s*\(\s*[`"]?(\w+)[`"]?\s*\)/i',
749 $row->sql, $matches, PREG_SET_ORDER
750 )) {
751 foreach ($matches as $m) {
752 $fks[] = [
753 'column_name' => $m[1],
754 'referenced_table' => $m[2],
755 'referenced_column' => $m[3],
756 ];
757 }
758 }
759 return $fks;
760 }
761
762 // @phpstan-ignore-next-line
763 $db = static::db()->dbname;
764
765 $sql = "SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
766 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
767 WHERE TABLE_NAME = '{$table}'
768 AND TABLE_SCHEMA = '{$db}'
769 AND REFERENCED_TABLE_NAME IS NOT NULL";
770
771 return array_map(function ($i) {
772 return [
773 'column_name' => $i->COLUMN_NAME,
774 'referenced_table' => $i->REFERENCED_TABLE_NAME,
775 'referenced_column' => $i->REFERENCED_COLUMN_NAME,
776 ];
777 }, (array) static::db()->get_results($sql));
778 }
779
780 /**
781 * Retrieves the list of all available built-in tables from
782 * the database using WordPress' $wpdb->tables native method.
783 *
784 * @param string $scope
785 * @param boolean $prefix
786 * @param integer $blogId
787 * @return string[] WP Table names. When a prefix is requested,
788 * the key is the unprefixed table name.
789 * @see https://developer.wordpress.org/reference/classes/wpdb/tables/
790 */
791 public static function tables($scope = 'all', $prefix = true, $blogId = 0)
792 {
793 return static::db()->tables($scope, $prefix, $blogId);
794 }
795
796 /**
797 * Retrieves the list of all available tables from the database.
798 *
799 * @param string $dbname optional
800 * @return array
801 */
802 public static function getTables($dbname = null)
803 {
804 return static::getTableList($dbname);
805 }
806
807 /**
808 * Retrieves the list of all available tables in the database.
809 *
810 * @param string $dbname optional
811 * @return array
812 */
813 public static function getTableList($dbname = null)
814 {
815 if (static::isSqlite()) {
816 return array_map(function ($i) {
817 return $i->name;
818 }, (array) static::db()->get_results(
819 "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
820 ));
821 }
822
823 // @phpstan-ignore-next-line
824 $dbname = $dbname ?: static::db()->dbname;
825 $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES";
826 $sql .= " WHERE TABLE_SCHEMA = '".$dbname."'";
827
828 return array_map(function($i) {
829 return $i->TABLE_NAME;
830 }, (array) static::db()->get_results($sql));
831 }
832
833 /**
834 * Retrieves the list of all available views in the database.
835 *
836 * @param string $dbname optional
837 * @return array
838 */
839 public static function getViews($dbname = null)
840 {
841 if (static::isSqlite()) {
842 return array_map(function ($i) {
843 return $i->name;
844 }, (array) static::db()->get_results(
845 "SELECT name FROM sqlite_master WHERE type='view'"
846 ));
847 }
848
849 // @phpstan-ignore-next-line
850 $dbname = $dbname ?: static::db()->dbname;
851 $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS";
852 $sql .= " WHERE TABLE_SCHEMA = '".$dbname."'";
853
854 return array_map(function ($i) {
855 return $i->TABLE_NAME;
856 }, (array) static::db()->get_results($sql));
857 }
858
859 /**
860 * Retrieves the SQL definition of a specific view.
861 *
862 * @param string $name The view name
863 * @param string $dbname optional
864 * @return string|null
865 */
866 public static function getView($name, $dbname = null)
867 {
868 if (static::isSqlite()) {
869 $result = static::db()->get_row(
870 "SELECT sql FROM sqlite_master WHERE type='view' AND name='".$name."'"
871 );
872
873 return $result ? $result->sql : null;
874 }
875
876 // @phpstan-ignore-next-line
877 $dbname = $dbname ?: static::db()->dbname;
878 $result = static::db()->get_row(
879 "SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS"
880 . " WHERE TABLE_SCHEMA = '".$dbname."'"
881 . " AND TABLE_NAME = '".$name."'"
882 );
883
884 return $result ? $result->VIEW_DEFINITION : null;
885 }
886
887 /**
888 * Determine if the connected database is a sqlite database.
889 *
890 * @return bool
891 */
892 public static function isSqlite()
893 {
894 return defined('DB_ENGINE') && DB_ENGINE === 'sqlite';
895 }
896
897 /**
898 * Determine if the connected database is a mariadb database.
899 *
900 * @return bool
901 */
902 public static function isMaria()
903 {
904 if (static::isSqlite()) {
905 return false;
906 }
907
908 return str_contains(
909 static::db()->get_var('SELECT VERSION()'), 'MariaDB'
910 );
911 }
912
913 /**
914 * Retrieve the current database engine name.
915 *
916 * @param string $table
917 * @return string
918 */
919 public static function getEngine($table)
920 {
921 if (static::isSqlite()) {
922 return 'sqlite';
923 }
924
925 return static::db()->get_var(
926 'SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = "' . static::table($table) . '"'
927 );
928 }
929
930 /**
931 * The wrapper for calling dbDelta function
932 *
933 * @param string $sql
934 * @return mixed
935 */
936 public static function callDBDelta($sql)
937 {
938 if (!function_exists('dbDelta')) {
939 require (ABSPATH . 'wp-admin/includes/upgrade.php');
940 }
941
942 $result = dbDelta($sql);
943
944 if (php_sapi_name() === 'cli') {
945 $key = array_key_first($result);
946 if ($key && !str_contains($key, '.')) {
947 static::$customTempTables[] = $key;
948 }
949 }
950
951 return $result;
952 }
953
954 /**
955 * Helper to get the driver-specific JSON type.
956 */
957 public static function jsonType()
958 {
959 return static::isSqlite() ? 'longtext' : 'json';
960 }
961 }
962