PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 110
Lasso Lite – Affiliate Link Manager & Product Displays v110
157 155 156 154 153 152 151 150 149 148 trunk 0.9.9 104 105 106 107 108 109 110 111 112 113 114 115 116 All 56 releases
simple-urls / models / class-model.php

class-model.php in Lasso Lite – Affiliate Link Manager & Product Displays 110, at models/class-model.php

1,149 lines 28.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Models
4 *
5 * @package Models
6 */
7
8 namespace LassoLite\Models;
9
10 use LassoLite\Classes\Cache_Per_Process;
11 use LassoLite\Classes\Helper as Lasso_Helper;
12 use LassoLite\Classes\Update_DB;
13
14 /**
15 * Model
16 *
17 * HOW TO USE?
18 * 1. Create a new class object (child class): $model = new Model_Child_Class();
19 * 2. Insert:
20 * a. Set data for columns: $model->set_[column_name]($value);
21 * b. Insert data: $model->insert();
22 * 3. Update:
23 * a. Update a model object after using get_one($id):
24 * i. Just update what columns you want: $model->set_[column_name]($value);
25 * ii. Update data: $model->update();
26 * b. Update a non-model object:
27 * i. Just update what columns you want: $model->set_[column_name]($value);
28 * ii. Update data: $model->update($id);
29 * 4. Delete:
30 * a. Delete a model object after using get_one($id): $model->delete();
31 * b. Delete a non-model object: $model->delete($id);
32 *
33 * COMMON FUNCTIONS
34 * 1. Get table name: $model->get_table_name();
35 * 2. Get a record: $model->get_one($id);
36 * 3. Get all records: $model->get_all($limit, $page);
37 * 4. Get prefix of the table in WP: Model::get_prefix();
38 * 5. Get table name of WP (with prefix): Model::get_wp_table_name('posts');
39 * 6. Create the table: $model->create_table();
40 * 7. Add default data: $model->add_default_data();
41 *
42 * WPDB CLASS: https://developer.wordpress.org/reference/classes/wpdb/
43 * 1. Get a record: Model::get_row($sql, $output, $enable_cache);
44 * 2. Get multiple records: Model::get_results($sql, $output, $enable_cache);
45 * 3. Get a var: Model::get_var($sql, $enable_cache);
46 * 4. Get a column: Model::get_col($sql, $enable_cache);
47 * 5. Run a general query: Model::query($sql);
48 * 5. Replace a row (update or create if it doesn't exist): Model::replace($table, $data, $format);
49 */
50 abstract class Model {
51 const LASSO_RECREATE_TABLES_LIMIT_TIMES = 3;
52
53 /**
54 * Table name
55 *
56 * @var string
57 */
58 protected $table;
59
60 /**
61 * Columns of the table
62 *
63 * @var array
64 */
65 protected $columns;
66
67 /**
68 * Primary key of the table
69 *
70 * @var string
71 */
72 protected $primary_key;
73
74 /**
75 * Data from DB
76 *
77 * @var object $data
78 */
79 protected $data;
80
81 /**
82 * Default
83 *
84 * @var mixed $default_data
85 */
86 protected $default_data;
87
88 /**
89 * Data from DB
90 *
91 * @var bool $is_db_loaded
92 */
93 protected $is_db_loaded = false;
94
95 /**
96 * Table charset
97 *
98 * @var array $table_charset
99 */
100 protected $table_charset = array();
101
102 /**
103 * Table collation
104 *
105 * @var array $table_collation
106 */
107 protected $table_collation = array();
108
109 /**
110 * Table collation default
111 *
112 * @var array $table_collation_default
113 */
114 protected $table_collation_default = array();
115
116 /**
117 * Column meta
118 *
119 * @var array $col_meta
120 */
121 protected $col_meta = array();
122
123 /**
124 * Create table
125 */
126 abstract public function create_table();
127
128 /**
129 * Use to check object is mapped properties
130 *
131 * @var bool $is_map_properties
132 */
133 private $is_map_properties = false;
134
135 /**
136 * Model constructor.
137 *
138 * @param object $object An object.
139 */
140 public function __construct( $object = null ) {
141 if ( ! is_null( $object ) && ! $this->is_map_properties ) {
142 $this->map_properties( $object );
143 }
144 }
145
146 /**
147 * Get wpdb
148 *
149 * @return wpdb WP wpdb class.
150 */
151 public static function get_wpdb() {
152 global $wpdb;
153
154 return $wpdb;
155 }
156
157 /**
158 * Get WP prefix
159 *
160 * @return string WP prefix of table.
161 */
162 public static function get_db_name() {
163 return self::get_wpdb()->dbname;
164 }
165
166 /**
167 * Get WP prefix
168 *
169 * @return string WP prefix of table.
170 */
171 public static function get_prefix() {
172 return self::get_wpdb()->prefix;
173 }
174
175 /**
176 * Get Lasso table name
177 *
178 * @return string Get current table name in WP.
179 */
180 public function get_table_name() {
181 return self::get_prefix() . $this->table;
182 }
183
184 /**
185 * Get WP table name
186 *
187 * @param string $table Table name in DB.
188 *
189 * @return string Table name in WP.
190 */
191 public static function get_wp_table_name( $table ) {
192 return self::get_prefix() . $table;
193 }
194
195 /**
196 * Get all columns of the table
197 */
198 public function get_all_columns() {
199 return $this->columns;
200 }
201
202 /**
203 * Get default data
204 */
205 public function get_default_data() {
206 return $this->default_data;
207 }
208
209 /**
210 * Get a row in DB
211 *
212 * @param int $id Id in DB. Default to null.
213 * @param array $select_columns Custom selecting the columns. Default to empty array.
214 *
215 * @return array|object
216 */
217 public function get_one( $id = null, $select_columns = array() ) {
218 $method = 'get_' . $this->primary_key;
219 $id = $id ? $id : $this->$method();
220
221 return $this->get_one_by_col( $this->primary_key, $id, $select_columns );
222 }
223
224 /**
225 * Get all rows in DB
226 *
227 * @param int $limit Number of rows are returned, 0 is no limit. Default to 10.
228 * @param int $page Page number. Default to 1.
229 * @param array $select_columns Custom selecting the columns. Default to empty array.
230 *
231 * @return array|object
232 */
233 public function get_all( $limit = 10, $page = 1, $select_columns = array() ) {
234 $select = $this->build_select_columns( $select_columns );
235
236 $sql = '
237 SELECT ' . $select . '
238 FROM ' . $this->get_table_name() . '
239 ';
240
241 if ( $limit > 0 ) {
242 $index = ( $page - 1 ) * $limit;
243
244 $sql .= ' LIMIT %d OFFSET %d';
245 $sql = self::prepare( $sql, $limit, $index );
246 }
247
248 return $this->get_results( $sql );
249 }
250
251 /**
252 * Insert a row in DB
253 */
254 public function insert() {
255 $result = self::get_wpdb()->insert(
256 $this->get_table_name(),
257 $this->data
258 );
259 if ( $result ) {
260 if ( in_array( 'id', $this->columns, true ) ) {
261 $this->map_property( 'id', self::get_wpdb()->insert_id );
262 }
263 }
264 return $result;
265 }
266
267 /**
268 * Update a row in DB
269 *
270 * @param int|string $id Id in the table. Default to null.
271 */
272 public function update( $id = null ) {
273 $method = 'get_' . $this->primary_key;
274 $id = $id ? $id : $this->$method();
275
276 if ( ! $id ) {
277 return false;
278 }
279
280 return $this->update_by_col( $this->primary_key, $id );
281 }
282
283 /**
284 * Delete a row in DB
285 *
286 * @param int|string $id Id in the table. Default to null.
287 */
288 public function delete( $id = null ) {
289 $method = 'get_' . $this->primary_key;
290 $id = $id ? $id : $this->$method();
291
292 if ( ! $id ) {
293 return false;
294 }
295
296 return $this->delete_by_col( $this->primary_key, $id );
297 }
298
299 /**
300 * Bulk upsert (insert/update) records into a table using WPDB. All rows must contain the same keys.
301 * Returns number of affected (inserted) rows.
302 *
303 * @param array $rows Table data.
304 */
305 public function bulk_upsert( $rows ) {
306 $table = $this->get_table_name();
307
308 $ids = array();
309 if ( 0 === count( $rows ) ) {
310 return array( false, $ids );
311 }
312
313 // ? Extract column list from first row of data
314 $columns = array_keys( Lasso_Helper::convert_stdclass_to_array( $rows[0] ) );
315 asort( $columns );
316 $column_list = '`' . implode( '`, `', $columns ) . '`';
317
318 // ? Start building SQL, initialise data and placeholder arrays
319 // ? $sql = "INSERT INTO `$table` ($column_list) VALUES\n";
320 $sql = "REPLACE INTO `$table` ($column_list) VALUES\n"; // ? upsert in mysql
321 $placeholders = array();
322 $data = array();
323
324 // ? Build placeholders for each row, and add values to data array
325 foreach ( $rows as $row ) {
326 $row = Lasso_Helper::convert_stdclass_to_array( $row );
327 ksort( $row );
328 $row_placeholders = array();
329 $ids[] = intval( $row['id'] );
330
331 foreach ( $row as $value ) {
332 $data[] = $value;
333 $row_placeholders[] = is_numeric( $value ) ? '%d' : '%s';
334 }
335
336 $placeholders[] = '(' . implode( ', ', $row_placeholders ) . ')';
337 }
338
339 // ? Stitch all rows together
340 $sql .= implode( ",\n", $placeholders );
341 $prepare = self::prepare( $sql, $data ); // phpcs:ignore
342
343 // ? Run the query. Returns number of affected rows.
344 return array( self::query( $prepare ), $ids ); // phpcs:ignore
345 }
346
347 /**
348 * Check whether a column exists or not
349 *
350 * @param string $table Table name.
351 * @param string $column Column name.
352 */
353 public static function column_exists( $table, $column ) {
354 $sql = "
355 SELECT *
356 FROM information_schema.columns
357 WHERE
358 table_schema = '" . self::get_db_name() . "'
359 AND table_name = '" . $table . "'
360 AND column_name = '" . $column . "'
361 LIMIT 1
362 ";
363
364 $result = self::get_results( $sql );
365 $result = isset( $result[0] ) ? true : false;
366
367 return $result;
368 }
369
370 /**
371 * Check whether columns exist or not
372 *
373 * @param array $columns Column name.
374 */
375 public function are_columns_created( $columns ) {
376 if ( ! is_array( $columns ) || empty( $columns ) ) {
377 return false;
378 }
379
380 $expected_count = count( $columns );
381 $columns = "'" . implode( "', '", $columns ) . "'";
382
383 // @codingStandardsIgnoreStart
384 $sql = "
385 SELECT count(COLUMN_NAME) as total
386 FROM information_schema.COLUMNS
387 WHERE TABLE_SCHEMA = %s
388 AND TABLE_NAME = %s
389 AND COLUMN_NAME IN ($columns)
390 ";
391 $prepare = self::prepare( $sql, self::get_db_name(), $this->get_table_name() );
392 // @codingStandardsIgnoreEnd
393 $result = self::get_var( $prepare );
394 $result = intval( $result );
395
396 return $result === $expected_count;
397 }
398
399 /**
400 * Check whether a tahble exists or not
401 *
402 * @param array $table Table name.
403 */
404 public static function table_exists( $table ) {
405 // @codingStandardsIgnoreStart
406 $prepare = self::prepare(
407 "
408 SELECT count(*)
409 FROM information_schema.TABLES
410 WHERE TABLE_SCHEMA = DATABASE()
411 AND TABLE_NAME = %s
412 ",
413 $table
414 );
415 // @codingStandardsIgnoreEnd
416 $result = self::get_var( $prepare );
417
418 return intval( $result ) === 1;
419 }
420
421 /**
422 * Check whether a tahble exists or not
423 */
424 public function is_table_created() {
425 // @codingStandardsIgnoreStart
426 $prepare = self::prepare(
427 "
428 SELECT count(*)
429 FROM information_schema.TABLES
430 WHERE TABLE_SCHEMA = DATABASE()
431 AND TABLE_NAME = %s
432 ",
433 $this->get_table_name()
434 );
435 // @codingStandardsIgnoreEnd
436 $result = self::get_var( $prepare );
437
438 return intval( $result ) === 1;
439 }
440
441 /**
442 * Drop index in the table
443 *
444 * @param string $index_name Index name.
445 *
446 * @return bool
447 */
448 public function is_index_created( $index_name ) {
449 $sql = '
450 SHOW INDEX
451 FROM ' . $this->get_table_name() . '
452 WHERE KEY_NAME = %s
453 ';
454 $prepare = self::prepare( $sql, $index_name );
455 $index = self::query( $prepare );
456
457 return $index > 0;
458 }
459
460 /**
461 * Drop columns in the table
462 *
463 * @param array $columns Columns list.
464 */
465 public function drop_columns( $columns ) {
466 if ( ! is_array( $columns ) || empty( $columns ) ) {
467 return false;
468 }
469
470 foreach ( $columns as $column ) {
471 if ( $this->are_columns_created( array( $column ) ) ) {
472 $query = '
473 ALTER TABLE ' . $this->get_table_name() . '
474 DROP COLUMN `' . $column . '`
475 ';
476 self::query( $query );
477 }
478 }
479 }
480
481 /**
482 * Drop Index
483 *
484 * @param string $index_name Index name.
485 */
486 public function drop_index( $index_name ) {
487 $index_exists = $this->is_index_created( $index_name );
488
489 if ( ! $index_exists ) {
490 return false;
491 }
492
493 $sql = '
494 ALTER TABLE ' . $this->get_table_name() . '
495 DROP INDEX `' . $index_name . '`
496 ';
497
498 return self::query( $sql );
499 }
500
501 /**
502 * Drop table
503 */
504 public function drop_table() {
505 $sql = 'DROP TABLE IF EXISTS ' . $this->get_table_name();
506
507 return self::query( $sql );
508 }
509
510 /**
511 * Get property name by method
512 *
513 * @param string $method Method.
514 */
515 private function get_property_name( $method ) {
516 return substr_replace( $method, '', 0, 4 );
517 }
518
519 /**
520 * Call a method in this class
521 *
522 * @param string $method Method name.
523 * @param array $args Arguments.
524 */
525 public function __call( $method, $args ) {
526 $prefix = substr_replace( $method, '', 4 );
527
528 switch ( $prefix ) {
529 case 'get_':
530 return $this->$method;
531
532 case 'set_':
533 $this->$method = $args[0] ?? null;
534 break;
535 }
536
537 return null;
538 }
539
540 /**
541 * Set value for a property
542 *
543 * @param string $name Function name (set_property_name).
544 * @param mix $value Property value.
545 */
546 public function __set( $name, $value ) {
547 $method = strtolower( $name );
548 $property = $this->get_property_name( $method );
549
550 if ( ! $property ) {
551 $property = $method;
552 }
553
554 if ( ! in_array( $property, $this->columns, true ) ) {
555 return;
556 }
557
558 // ? see if there exists a extra setter method: setName()
559 if ( ! method_exists( $this, $method ) ) {
560 // ? if there is no setter, receive all public/protected vars and set the correct one if found
561 $this->data[ $property ] = $value;
562 } else {
563 $this->$method( $value ); // ? call the setter with the value
564 }
565 }
566
567 /**
568 * Get value for a property
569 *
570 * @param string $name Function name (get_property_name).
571 *
572 * @return mixed Property value.
573 */
574 public function __get( $name ) {
575 $method = strtolower( $name );
576 $property = $this->get_property_name( $method );
577
578 if ( ! $property ) {
579 $property = $method;
580 }
581
582 if ( ! in_array( $property, $this->columns, true ) ) {
583 return;
584 }
585
586 // ? see if there is an extra getter method: get_name()
587 if ( ! method_exists( $this, $method ) ) {
588 // ? if there is no getter, receive all public/protected vars and return the correct one if found
589 return $this->data[ $property ] ?? null;
590 } else {
591 return $this->$method(); // ? call the getter
592 }
593
594 return null;
595 }
596
597 /**
598 * Get a row in DB by a column
599 *
600 * @param string $column Column name in DB.
601 * @param string $value Value in DB.
602 * @param array $select_columns Custom selected the columns. Default to empty array.
603 *
604 * @return array|object
605 */
606 public function get_one_by_col( $column, $value, $select_columns = array() ) {
607 $result = null;
608
609 if ( ! $column || ! $value ) {
610 return $result;
611 }
612
613 $select = $this->build_select_columns( $select_columns );
614 $sql = '
615 SELECT ' . $select . '
616 FROM ' . $this->get_table_name() . '
617 WHERE `' . $column . '` = %s
618 ';
619 $prepare = self::prepare( $sql, $value ); // phpcs:ignore
620
621 $result = self::get_row( $prepare );
622 $this->map_properties( $result );
623 $this->is_db_loaded = true;
624
625 return $this;
626 }
627
628 /**
629 * Update a row by a column
630 *
631 * @param string $column Column name in DB.
632 * @param string $value Value in DB.
633 */
634 public function update_by_col( $column, $value ) {
635 $result = self::get_wpdb()->update(
636 $this->get_table_name(),
637 $this->data,
638 array( $column => $value )
639 );
640
641 return $result;
642 }
643
644 /**
645 * Delete rows by a column
646 *
647 * @param string $column Column name in DB.
648 * @param string $value Value in DB.
649 */
650 public function delete_by_col( $column, $value ) {
651 $result = self::get_wpdb()->delete(
652 $this->get_table_name(),
653 array( $column => $value )
654 );
655
656 return $result;
657 }
658
659 /**
660 * Prepare data
661 *
662 * @param string $sql SQL query.
663 * @param mixed ...$args Further variables to substitute into the query's placeholders if being called with individual arguments.
664 */
665 public static function prepare( $sql, ...$args ) {
666 return self::get_wpdb()->prepare( $sql, ...$args );
667 }
668
669 /**
670 * Get row
671 * Get row from cache if existed
672 *
673 * @param string $sql Sql query.
674 * @param string $output Type of results.
675 * @param boolean $enable_cache Enable cache.
676 *
677 * @return array|object|null|void Database query result in format specified by $output or null on failure.
678 */
679 public static function get_row( $sql, $output = 'OBJECT', $enable_cache = false ) {
680 $result = null;
681 $cache_string = md5( trim( (string) $sql ) . $output . __FUNCTION__ );
682
683 if ( $enable_cache ) {
684 $result = Cache_Per_Process::get_instance()->get_cache( $cache_string );
685 }
686
687 if ( ! $result ) {
688 $wpdb = self::get_wpdb();
689 $result = $wpdb->get_row( $sql, $output ); // phpcs:ignore
690 self::log_error( $wpdb->last_error );
691
692 if ( $enable_cache ) {
693 Cache_Per_Process::get_instance()->set_cache( $cache_string, $result );
694 }
695 }
696
697 return $result;
698 }
699
700 /**
701 * Map data into object
702 *
703 * @param object $row A record from DB.
704 */
705 protected function map_properties( $row ) {
706 if ( ! $row || $this->is_map_properties ) {
707 return;
708 }
709
710 $columns = $this->columns;
711 foreach ( $columns as $column ) {
712 $method = 'set_' . $column;
713 $this->$method( $row->$column ?? null );
714 }
715 $this->is_map_properties = true;
716 return $this;
717 }
718
719 /**
720 * Set value for column
721 *
722 * @param string $column Column name.
723 * @param string $value Value.
724 */
725 protected function map_property( $column, $value ) {
726 $method = 'set_' . $column;
727 $this->$method( $value ?? null );
728 return $this;
729 }
730
731 /**
732 * Get results
733 * Get results from cache if existed
734 *
735 * @param string $sql Sql query.
736 * @param string $output Type of results.
737 * @param boolean $enable_cache Enable cache.
738 *
739 * @return array|object|null Database query results.
740 */
741 public static function get_results( $sql, $output = 'OBJECT', $enable_cache = false ) {
742 $results = null;
743 $cache_string = md5( trim( (string) $sql ) . $output . __FUNCTION__ );
744
745 if ( $enable_cache ) {
746 $results = Cache_Per_Process::get_instance()->get_cache( $cache_string );
747 }
748
749 if ( ! $results ) {
750 $wpdb = self::get_wpdb();
751 $results = $wpdb->get_results( $sql, $output ); // phpcs:ignore
752 self::log_error( $wpdb->last_error );
753
754 if ( $enable_cache ) {
755 Cache_Per_Process::get_instance()->set_cache( $cache_string, $results );
756 }
757 }
758
759 return $results;
760 }
761
762 /**
763 * The replace method replaces a row in a table if it exists
764 * or inserts a new row in a table if the row did not already exist.
765 *
766 * @param string $table The name of the table to replace data in.
767 * @param array $data Data to replace (in column => value pairs).
768 * Both $data columns and $data values should be “raw” (neither should be SQL escaped).
769 */
770 public static function replace( $table, $data ) {
771 $wpdb = self::get_wpdb();
772 $results = $wpdb->replace( $table, $data ); // phpcs:ignore
773 self::log_error( $wpdb->last_error );
774
775 return $results;
776 }
777
778 /**
779 * Run query
780 *
781 * @param string $sql Sql query.
782 *
783 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries.
784 * Number of rows affected/selected for all other queries. Boolean false on error.
785 */
786 public static function query( $sql ) {
787 $wpdb = self::get_wpdb();
788 $results = $wpdb->query( $sql ); // phpcs:ignore
789 self::log_error( $wpdb->last_error );
790
791 return $results;
792 }
793
794 /**
795 * Get var
796 * Get var from cache if existed
797 *
798 * @param string $sql Sql query.
799 * @param boolean $enable_cache Enable cache.
800 *
801 * @return mixed Database query result (as string), or null on failure.
802 */
803 public static function get_var( $sql, $enable_cache = false ) {
804 $results = null;
805 $cache_string = md5( trim( (string) $sql ) . __FUNCTION__ );
806
807 if ( $enable_cache ) {
808 $results = Cache_Per_Process::get_instance()->get_cache( $cache_string );
809 }
810
811 if ( ! $results ) {
812 $wpdb = self::get_wpdb();
813 $results = $wpdb->get_var( $sql ); // phpcs:ignore
814 self::log_error( $wpdb->last_error );
815
816 if ( $enable_cache ) {
817 Cache_Per_Process::get_instance()->set_cache( $cache_string, $results );
818 }
819 }
820
821 return $results;
822 }
823
824 /**
825 * Get col of the rows
826 * Get col from cache if existed
827 *
828 * @param string $sql Sql query.
829 * @param boolean $enable_cache Is use cache.
830 */
831 public static function get_col( $sql, $enable_cache = false ) {
832 $results = null;
833 $cache_string = md5( trim( (string) $sql ) . __FUNCTION__ );
834
835 if ( $enable_cache ) {
836 $results = Cache_Per_Process::get_instance()->get_cache( $cache_string );
837 }
838
839 if ( ! $results ) {
840 global $wpdb;
841
842 $results = $wpdb->get_col( $sql ); // phpcs:ignore
843 self::log_error( $wpdb->last_error );
844
845 if ( $enable_cache ) {
846 Cache_Per_Process::get_instance()->set_cache( $cache_string, $results );
847 }
848 }
849
850 return $results;
851 }
852
853 /**
854 * Count items by a sql query
855 *
856 * @param string $sql Sql query.
857 */
858 public static function get_count( $sql ) {
859 $count_sql = '
860 SELECT COUNT(*) AS `count`
861 FROM (' . $sql . ') AS `tbl_count`
862 ';
863
864 $result = self::get_var( $count_sql );
865 $result = intval( $result );
866
867 return $result;
868 }
869
870 /**
871 * Print error log message to log file
872 *
873 * @param string $error Error message.
874 */
875 private static function log_error( $error ) {
876 $log_name = 'sql_errors';
877 if ( ! empty( $error ) ) {
878 if ( Lasso_Helper::is_lasso_tables_does_not_exist_error( $error ) // ? Only recreate Lasso's tables when error relative to Lasso's table.
879 || strpos( $error, 'Illegal mix of collations' ) !== false
880 || strpos( $error, 'Unknown column' ) !== false
881 ) {
882 if ( ! self::should_recreate_tables() ) {
883 return;
884 }
885
886 Update_DB::create_tables();
887 }
888
889 // ? Add force write log for lasso_debug, to see what happen when Lasso call query.
890 trigger_error( $error, E_USER_NOTICE ); // phpcs:ignore
891 }
892 }
893
894 /**
895 * Check if number of time to call Lasso_Activator::create_lasso_table() > limit time. So we decide that should call this method or not
896 *
897 * @return bool
898 */
899 private static function should_recreate_tables() {
900 // ? Define global $$lasso_recreate_table_time if not existed.
901 if ( ! array_key_exists( 'lasso_recreate_table_time', $GLOBALS ) ) {
902 global $lasso_recreate_table_time;
903 $lasso_recreate_table_time = 0;
904 } else {
905 global $lasso_recreate_table_time;
906 }
907
908 // ? Increase number of times we call Lasso_Activator::create_lasso_table().
909 ++$lasso_recreate_table_time;
910
911 if ( $lasso_recreate_table_time > self::LASSO_RECREATE_TABLES_LIMIT_TIMES ) {
912 return false;
913 }
914
915 return true;
916 }
917
918 /**
919 * Get charset collate
920 */
921 protected function get_charset_collate() {
922 return self::get_wpdb()->get_charset_collate();
923 }
924
925 /**
926 * Create table
927 *
928 * @param string $sql SQL query.
929 * @param string $table Table name.
930 * @param string $reference_charset_table Reference table to get charset.
931 */
932 protected function modify_table( $sql, $table, $reference_charset_table = null ) {
933 if ( ! function_exists( 'dbDelta' ) ) {
934 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
935 }
936
937 $result = dbDelta( $sql );
938 self::log_error( self::get_wpdb()->last_error );
939
940 $reference_charset_table = $reference_charset_table ? $reference_charset_table : self::get_wp_table_name( 'posts' );
941 $reference_charset_status = $this->get_table_charset( $reference_charset_table, true );
942 $current_table_status = $this->get_table_charset( $table, false );
943
944 if ( ! $reference_charset_status[1] !== $current_table_status[1] ) {
945 $result = $this->update_table_collation( $table, $reference_charset_status[0], $reference_charset_status[1] );
946 } else {
947 $result = true;
948 }
949
950 $result = $result[ $table ] ?? 'Table already exists.';
951 if ( 'Table already exists.' === $result ) {
952 $check = self::get_row( "CHECK TABLE $table" );
953 $check_msg_txt = $check->Msg_text ?? ''; // phpcs:ignore
954 if ( 'OK' === $check_msg_txt ) {
955 $result = 'The table is okay, it does not need to be repaired.';
956 } else {
957 // @codeCoverageIgnoreStart
958 $repair = self::get_row( "REPAIR TABLE $table" );
959 $repair_msg_txt = $repair->Msg_text ?? ''; // phpcs:ignore
960 $result = 'OK' === $repair_msg_txt
961 ? "Successfully repaired the $table table."
962 : "Failed to repair the $table table. Error: $repair->Msg_text";
963 // @codeCoverageIgnoreEnd
964 }
965 }
966
967 return array( $table, $result );
968 }
969
970 /**
971 * Get table charset
972 *
973 * @param string $table Table name.
974 * @param bool $is_posts Is posts table or not. Default to false.
975 */
976 private function get_table_charset( $table, $is_posts = false ) {
977 // @codeCoverageIgnoreStart
978 $wpdb = self::get_wpdb();
979
980 $tablekey = strtolower( $table );
981 $charset = apply_filters( 'pre_get_table_charset', null, $table );
982 if ( null !== $charset ) {
983 return $charset;
984 }
985
986 if ( isset( $this->table_charset[ $tablekey ] ) ) {
987 return array(
988 $this->table_charset[ $tablekey ],
989 $this->table_collation[ $tablekey ],
990 $this->table_collation_default,
991 $this->get_charset_collate(),
992 );
993 }
994
995 $charsets_collections = array();
996 $columns = array();
997
998 $table_parts = explode( '.', $table );
999 $table = '`' . implode( '`.`', $table_parts ) . '`';
1000 $results = self::get_results( "SHOW FULL COLUMNS FROM $table" );
1001 if ( ! $results ) {
1002 return 'wpdb_get_table_charset_failure';
1003 }
1004
1005 foreach ( $results as $column ) {
1006 $columns[ strtolower( $column->Field ) ] = $column; // phpcs:ignore
1007 }
1008
1009 $this->col_meta[ $tablekey ] = $columns;
1010
1011 foreach ( $columns as $column ) {
1012 if ( ! empty( $column->Collation ) ) { // phpcs:ignore
1013 $this->table_collation[ $tablekey ] = $column->Collation; // phpcs:ignore
1014
1015 if ( $is_posts ) {
1016 $this->table_collation_default = $column->Collation; // phpcs:ignore
1017 }
1018
1019 list( $charset ) = explode( '_', $column->Collation ); // phpcs:ignore
1020
1021 // If the current connection can't support utf8mb4 characters, let's only send 3-byte utf8 characters.
1022 if ( 'utf8mb4' === $charset && ! $wpdb->has_cap( 'utf8mb4' ) ) {
1023 $charset = 'utf8';
1024 }
1025
1026 $charsets_collections[ strtolower( $charset ) ] = $column->Collation; // phpcs:ignore
1027 } else {
1028 $this->table_collation[ $tablekey ] = $this->table_collation_default;
1029 }
1030
1031 list( $type ) = explode( '(', $column->Type ); // phpcs:ignore
1032
1033 // A binary/blob means the whole query gets treated like this.
1034 if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ), true ) ) {
1035 $this->table_charset[ $tablekey ] = 'binary';
1036 return 'binary';
1037 }
1038 }
1039
1040 // utf8mb3 is an alias for utf8.
1041 if ( isset( $charsets_collections['utf8mb3'] ) ) {
1042 $charsets_collections['utf8'] = str_replace( 'utf8mb3', 'utf8', $charsets_collections['utf8mb3'] );
1043 $this->table_collation[ $tablekey ] = $charsets_collections['utf8'];
1044 unset( $charsets_collections['utf8mb3'] );
1045 }
1046
1047 // Check if we have more than one charset in play.
1048 $count = count( $charsets_collections );
1049 if ( 1 === $count ) {
1050 $charset = key( $charsets_collections );
1051 } elseif ( 0 === $count ) {
1052 // No charsets, assume this table can store whatever.
1053 $charset = false;
1054 } else {
1055 // More than one charset. Remove latin1 if present and recalculate.
1056 unset( $charsets_collections['latin1'] );
1057 $count = count( $charsets_collections );
1058 if ( 1 === $count ) {
1059 // Only one charset (besides latin1).
1060 $charset = key( $charsets_collections );
1061
1062 // ? Update suitable collation for this charset
1063 $this->table_collation[ $tablekey ] = $charsets_collections[ $charset ];
1064 } elseif ( 2 === $count && isset( $charsets_collections['utf8'], $charsets_collections['utf8mb4'] ) ) {
1065 // Two charsets, but they're utf8 and utf8mb4, use utf8.
1066 $charset = 'utf8';
1067
1068 // ? Update suitable collation for this charset
1069 $this->table_collation[ $tablekey ] = $charsets_collections['utf8'];
1070 } else {
1071 // Two mixed character sets. ascii.
1072 $charset = 'ascii';
1073
1074 // ? Update suitable collation for this charset
1075 $this->table_collation[ $tablekey ] = 'ascii_general_ci';
1076 }
1077 }
1078
1079 $this->table_charset[ $tablekey ] = $charset;
1080
1081 return array(
1082 $this->table_charset[ $tablekey ],
1083 $this->table_collation[ $tablekey ],
1084 $this->table_collation_default,
1085 $this->get_charset_collate(),
1086 );
1087 // @codeCoverageIgnoreEnd
1088 }
1089
1090 /**
1091 * Update table collation
1092 *
1093 * @param string $table Table name.
1094 * @param string $character Table character. Default to utf8mb4.
1095 * @param string $collate Table collation. Default to utf8mb4_unicode_520_ci.
1096 */
1097 private function update_table_collation( $table, $character = 'utf8mb4', $collate = 'utf8mb4_unicode_520_ci' ) {
1098 if ( '' === $character || '' === $collate ) {
1099 return false;
1100 }
1101
1102 $sql = '
1103 ALTER TABLE ' . $table . '
1104 CONVERT TO CHARACTER SET ' . $character . '
1105 COLLATE ' . $collate . ';
1106 ';
1107
1108 return self::query( $sql );
1109 }
1110
1111 /**
1112 * Build select columns query.
1113 *
1114 * @param array $select_columns Custom selecting columns.
1115 * @return string
1116 */
1117 private function build_select_columns( $select_columns ) {
1118 if ( empty( $select_columns ) || ! is_array( $select_columns ) ) {
1119 return '*';
1120 }
1121
1122 $valid_columns = array_intersect( $select_columns, $this->columns );
1123 if ( empty( $valid_columns ) ) {
1124 $select = '*';
1125 } else {
1126 $select = implode( ',', $valid_columns );
1127 }
1128
1129 return $select;
1130 }
1131
1132 /**
1133 * Get total records of table.
1134 *
1135 * @param bool $enable_cache Is Enable query cache per process.
1136 * @return int
1137 */
1138 public function total_count( $enable_cache = false ) {
1139 $sql = '
1140 SELECT COUNT(*) as total
1141 FROM ' . $this->get_table_name() . '
1142 ';
1143
1144 $result = self::get_var( $sql, $enable_cache );
1145
1146 return empty( $result ) ? 0 : intval( $result );
1147 }
1148 }
1149