PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
← All changes | includes/class-wp-optimize-database-information.php +202 -61 3.4.0 → 4.7.0 View file →
@@ -80,15 +80,16 @@
80 80 return false;
81 81 }
82 82
83 83 /**
84 - * Return table type by $table_name.
84 + * Return the table type by $table_name.
85 85 *
86 - * @param String $table_name Database table name.
87 - * @return String|Boolean - returns false upon failure
86 + * @param string $table_name Database table name.
87 + * @param object|null $table_info Optional pre-fetched table status object (e.g. from a bulk fetch), to avoid a redundant lookup.
88 + * @return string|boolean - returns false upon failure
88 89 */
89 - public function get_table_type($table_name) {
90 - $table_info = $this->get_table_status($table_name);
90 + public function get_table_type($table_name, $table_info = null) {
91 + if (null === $table_info) $table_info = $this->get_table_status($table_name);
91 92
92 93 if ($table_info) {
93 94 if (!$table_info->Engine && $this->is_view($table_name)) return self::VIEW;
94 95
@@ -102,15 +103,15 @@
102 103 * Returns information about database table.
103 104 *
104 105 * @param string $table_name
105 106 * @param bool $update if true, then force request to database and don't use cached values.
106 - * @return bool|mixed
107 + * @return mixed
107 108 */
108 109 public function get_table_status($table_name, $update = false) {
109 110 $tables_info = $this->get_show_table_status($update, $table_name);
110 111
111 112 foreach ($tables_info as $table_info) {
112 - if ($table_name == $table_info->Name) return $table_info;
113 + if ($table_name === $table_info->Name) return $table_info;
113 114 }
114 115
115 116 return false;
116 117 }
@@ -115,12 +116,73 @@
115 116 return false;
116 117 }
117 118
118 119 /**
120 + * Count the tables in the database
121 + *
122 + * @param string $prefix The generic prefix of the tables in the database
123 + * @param bool $update if true, then force request to database and don't use cached values.
124 + * @return int
125 + */
126 + public function get_table_count($prefix = '', $update = false) {
127 + global $wpdb;
128 + static $table_counts = array();
129 +
130 + if (!$update && isset($table_counts[$prefix])) return $table_counts[$prefix];
131 +
132 + $table_counts[$prefix] = (int) $wpdb->get_var(
133 + $wpdb->prepare(
134 + "SELECT COUNT(1)
135 + FROM INFORMATION_SCHEMA.TABLES
136 + WHERE TABLE_SCHEMA = %s
137 + AND TABLE_NAME LIKE %s",
138 + DB_NAME,
139 + $prefix . '%'
140 + )
141 + );
142 +
143 + return $table_counts[$prefix];
144 + }
145 +
146 + /**
147 + * Get all tables names from information_schema
148 + *
149 + * @param string $prefix The global table prefix
150 + * @param int $offset Paginate the result to avoid request timeout for large table counts
151 + * @param int $limit Pagination size
152 + * @return array
153 + */
154 + public function get_tables_names($prefix = '', $offset = 0, $limit = null) {
155 + global $wpdb;
156 +
157 + $sql = "SELECT *
158 + FROM INFORMATION_SCHEMA.TABLES
159 + WHERE TABLE_SCHEMA = %s
160 + AND TABLE_NAME LIKE %s";
161 +
162 + $params = array(DB_NAME, $prefix . '_%');
163 +
164 + if (null !== $limit) {
165 + $sql .= " LIMIT %d, %d";
166 + $params[] = (int) $offset;
167 + $params[] = (int) $limit;
168 + }
169 +
170 + $result = $wpdb->get_results($wpdb->prepare($sql, $params)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql only contains static text and %s/%d placeholders, passed through $wpdb->prepare()
171 +
172 + $names = array();
173 + foreach ($result as $row) {
174 + $names[] = $row->TABLE_NAME;
175 + }
176 +
177 + return $names;
178 + }
179 +
180 + /**
119 181 * Returns result for query SHOW TABLE STATUS.
120 182 *
121 183 * @param bool $update refresh or no cached data
122 - * @return array
184 + * @return array|object|stdClass[]|null
123 185 */
124 186 public function get_show_table_status($update = false, $table_name = '') {
125 187 global $wpdb;
126 188 static $tables_info = array();
@@ -132,10 +194,9 @@
132 194
133 195 // If a table name is provided, and the whole record hasn't been fetched yet, only fetch the information for the current table.
134 196 // This allows for a big performance gain when using WP-CLI or doing single optimizations.
135 197 if ($table_name && !$fetched_all_tables) {
136 - $sql = $wpdb->prepare("SHOW TABLE STATUS LIKE '%s'", $table_name);
137 - $tables_info = $wpdb->get_results($sql);
198 + $tables_info = $wpdb->get_results($wpdb->prepare("SHOW TABLE STATUS LIKE %s", $table_name));
138 199 } else {
139 200 if ($update || empty($tables_info) || !is_array($tables_info) || !$fetched_all_tables) {
140 201 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
141 202 $fetched_all_tables = true;
@@ -149,9 +210,9 @@
149 210
150 211 // If option innodb_file_per_table is disabled then Data_free column will have summary overhead value for all table.
151 212 if (!empty($tables_info)) {
152 213 foreach ($tables_info as $i => $table) {
153 - if (self::INNODB_ENGINE == $table->Engine && false == $this->is_option_enabled('innodb_file_per_table')) {
214 + if (self::INNODB_ENGINE == $table->Engine && !$this->is_option_enabled('innodb_file_per_table')) {
154 215 $tables_info[$i]->Data_free = 0;
155 216 }
156 217 }
157 218 }
@@ -161,8 +222,10 @@
161 222
162 223 /**
163 224 * Whether a table exists
164 225 *
226 + * @param string $table_name Name of the table
227 + * @param bool $use_default_prefix Whether to use default prefix
165 228 * @return boolean
166 229 */
167 230 public function table_exists($table_name, $use_default_prefix = true) {
168 231 global $wpdb;
@@ -202,9 +265,9 @@
202 265 $tables_info = $this->get_show_full_tables();
203 266
204 267 if (!array_key_exists($table_name, $tables_info)) return false;
205 268
206 - return ('VIEW' == $tables_info[$table_name]);
269 + return ('VIEW' === $tables_info[$table_name]);
207 270 }
208 271
209 272 /**
210 273 * Returns true if DDL supported.
@@ -211,15 +274,15 @@
211 274 *
212 275 * @return bool
213 276 */
214 277 public function has_online_ddl() {
215 - if (self::MYSQL_DB == $this->get_server_type()) {
278 + if (self::MYSQL_DB === $this->get_server_type()) {
216 279 if (version_compare($this->get_version(), '5.7', '>=')) {
217 280 return true;
218 281 } else {
219 282 return false;
220 283 }
221 - } elseif (self::MARIA_DB == $this->get_server_type()) {
284 + } elseif (self::MARIA_DB === $this->get_server_type()) {
222 285 if (version_compare($this->get_version(), '10.0.0', '>=')) {
223 286 return true;
224 287 } else {
225 288 return false;
@@ -261,38 +324,38 @@
261 324 */
262 325 public function is_option_enabled($option_name) {
263 326 $option_value = $this->get_option_value($option_name);
264 327
265 - if ('ON' == strtoupper($option_value)) return true;
266 - return false;
328 + return 'ON' === strtoupper($option_value);
267 329 }
268 330
269 331 /**
270 332 * Returns true if table $table_name is optimizable
271 333 *
272 - * @param string $table_name Name of database table
334 + * @param string $table_name Name of database table
335 + * @param string|bool $table_type Optional pre-resolved table engine/type, to avoid a redundant lookup.
273 336 * @return bool
274 337 */
275 - public function is_table_optimizable($table_name) {
338 + public function is_table_optimizable($table_name, $table_type = null) {
276 339 $server_type = $this->get_server_type();
277 340 $server_version = $this->get_version();
278 - $table_type = $this->get_table_type($table_name);
341 + $table_type = null !== $table_type ? $table_type : $this->get_table_type($table_name);
279 342
280 343 // return true if table is MyISAM.
281 - if (self::MYISAM_ENGINE == $table_type) return true;
344 + if (self::MYISAM_ENGINE === $table_type) return true;
282 345
283 346 // return true if table is Archive or Aria.
284 - if (self::ARCHIVE_ENGINE == $table_type || self::ARIA_ENGINE == $table_type) return true;
347 + if (self::ARCHIVE_ENGINE === $table_type || self::ARIA_ENGINE === $table_type) return true;
285 348
286 349 // if InnoDB then check if we can optimize.
287 - if (self::INNODB_ENGINE == $table_type) {
350 + if (self::INNODB_ENGINE === $table_type) {
288 351 // check for MysqlDB.
289 - if (self::MYSQL_DB == $server_type && $this->has_online_ddl()) {
352 + if (self::MYSQL_DB === $server_type && $this->has_online_ddl()) {
290 353 return true;
291 354 }
292 355
293 356 // check for MariaDB.
294 - if (self::MARIA_DB == $server_type) {
357 + if (self::MARIA_DB === $server_type) {
295 358 // if innodb_file_per_table enabled or version not older than 10.1.1 and innodb_defragment enabled.
296 359 if ($this->is_option_enabled('innodb_file_per_table') || (version_compare($server_version, '10.1.1', '>=') && $this->is_option_enabled('innodb_defragment'))) {
297 360 return true;
298 361 }
@@ -303,15 +366,16 @@
303 366 return false;
304 367 }
305 368
306 369 /**
307 - * Returns true if table type is supported for optimization.
370 + * Returns true if the table type is supported for optimization.
308 371 *
309 - * @param string $table_name Name of database table
372 + * @param string $table_name Name of database table
373 + * @param string|bool $table_type Optional pre-resolved table engine/type, to avoid a redundant lookup.
310 374 * @return bool
311 375 */
312 - public function is_table_type_optimize_supported($table_name) {
313 - $table_type = $this->get_table_type($table_name);
376 + public function is_table_type_optimize_supported($table_name, $table_type = null) {
377 + $table_type = null !== $table_type ? $table_type : $this->get_table_type($table_name);
314 378
315 379 $supported_table_types = array(
316 380 self::MYISAM_ENGINE,
317 381 self::INNODB_ENGINE,
@@ -324,13 +388,14 @@
324 388
325 389 /**
326 390 * Returns true if table type is supported for repair.
327 391 *
328 - * @param string $table_name
392 + * @param string $table_name
393 + * @param string|bool $table_type Optional pre-resolved table engine/type, to avoid a redundant lookup.
329 394 * @return bool
330 395 */
331 - public function is_table_type_repair_supported($table_name) {
332 - $table_type = $this->get_table_type($table_name);
396 + public function is_table_type_repair_supported($table_name, $table_type = null) {
397 + $table_type = null !== $table_type ? $table_type : $this->get_table_type($table_name);
333 398
334 399 $supported_table_types = array(
335 400 self::MYISAM_ENGINE,
336 401 self::ARCHIVE_ENGINE,
@@ -343,8 +408,9 @@
343 408 /**
344 409 * Run CHECK TABLE query and returns statuses for single or list of tables.
345 410 *
346 411 * @param array|string $table
412 + * @return array
347 413 */
348 414 public function check_table($table) {
349 415 global $wpdb;
350 416
@@ -355,9 +421,9 @@
355 421 $result = array();
356 422
357 423 if (empty($table)) return $result;
358 424
359 - $query_result = $wpdb->get_results('CHECK TABLE `'.$table.'`;');
425 + $query_result = $wpdb->get_results('CHECK TABLE `'. esc_sql($table).'`;');
360 426
361 427 if (empty($query_result)) return $result;
362 428
363 429 foreach ($query_result as $row) {
@@ -370,9 +436,9 @@
370 436 'corrupted' => false,
371 437 );
372 438 }
373 439
374 - if ('error' == $row->Msg_type) {
440 + if ('error' === $row->Msg_type) {
375 441 $result[$table_name]['status'] = $row->Msg_type;
376 442
377 443 if (preg_match('/corrupt/i', $row->Msg_text)) {
378 444 $result[$table_name]['corrupted'] = true;
@@ -380,9 +446,9 @@
380 446 $result[$table_name]['message'] = $row->Msg_text;
381 447 }
382 448 }
383 449
384 - if ('status' == $row->Msg_type) {
450 + if ('status' === $row->Msg_type) {
385 451 $result[$table_name]['status'] = $row->Msg_text;
386 452 }
387 453 }
388 454
@@ -402,9 +468,9 @@
402 468 $tables = $this->get_show_table_status();
403 469 $supported_tables = array();
404 470
405 471 foreach ($tables as $table) {
406 - if ('' == $table->Engine || $this->is_table_type_repair_supported($table->Name)) {
472 + if ('' === $table->Engine || $this->is_table_type_repair_supported($table->Name, $table->Engine)) {
407 473 $supported_tables[] = $table->Name;
408 474 }
409 475 }
410 476
@@ -415,39 +481,35 @@
415 481
416 482 /**
417 483 * Returns true if table needing repair.
418 484 *
419 - * @param string $table_name Database table name.
485 + * @param string $table_name Database table name.
486 + * @param string|bool $table_type Optional pre-resolved table engine/type, to avoid a redundant lookup.
420 487 */
421 - public function is_table_needing_repair($table_name) {
488 + public function is_table_needing_repair($table_name, $table_type = null) {
422 489 $table_statuses = $this->check_all_tables();
423 490
424 - if (!$this->is_table_type_repair_supported($table_name)) return false;
491 + if (!$this->is_table_type_repair_supported($table_name, $table_type)) return false;
425 492
426 - if (is_array($table_statuses) && array_key_exists($table_name, $table_statuses) && $table_statuses[$table_name]['corrupted']) {
427 - return true;
428 - } else {
429 - return false;
430 - }
493 + return (array_key_exists($table_name, $table_statuses) && $table_statuses[$table_name]['corrupted']);
431 494 }
432 495
433 496 /**
434 - * Check if $table using by any of installed plugins.
497 + * Check if any of plugins from $plugin_names is installed.
435 498 *
436 - * @param string $table
499 + * @param array $plugin_names
437 500 * @return bool
438 501 */
439 - public function is_table_using_by_plugin($table) {
440 - $plugin_names = $this->get_table_plugin($table);
502 + public function is_any_plugin_installed($plugin_names) {
441 503
442 - // if we can't determine which plugin use $table then return true.
443 - if (false == $plugin_names) {
444 - return true;
504 + if (empty($plugin_names)) {
505 + return false;
445 506 }
446 507
447 508 // is WordPress core table or using by any of installed plugins then return true.
448 509 foreach ($plugin_names as $plugin_name) {
449 - if (__('WordPress core', 'wp-optimize') == $plugin_name || in_array($plugin_name, $this->get_all_installed_plugins())) {
510 + // $plugin_name is a translatable string, so we compare it with a translated string
511 + if (__('WordPress core', 'wp-optimize') === $plugin_name || in_array($plugin_name, $this->get_all_installed_plugins())) {
450 512 return true;
451 513 }
452 514 }
453 515
@@ -468,9 +530,9 @@
468 530
469 531 // if match with base_prefix_(number)_
470 532 if (preg_match('/^'.$wpdb->base_prefix.'(\d+)_/', $table_name, $match)) {
471 533 // check if matched number in available sites.
472 - if (false !== array_search($match[1], $blogs_ids)) return $match[1];
534 + if (in_array($match[1], $blogs_ids)) return absint($match[1]);
473 535 }
474 536 }
475 537
476 538 return 1;
@@ -528,10 +590,16 @@
528 590 }
529 591
530 592 // add WP-Optimize tables.
531 593 $wpo = 'wp-optimize';
532 - if (false === array_search($wpo, $plugin_tables['tm_taskmeta']) && false === array_search($wpo, $plugin_tables['tm_tasks'])) {
594 + $plugin_tables['tm_taskmeta'] = $plugin_tables['tm_taskmeta'] ?? array();
595 + $plugin_tables['tm_tasks'] = $plugin_tables['tm_tasks'] ?? array();
596 +
597 + if (!in_array($wpo, $plugin_tables['tm_taskmeta'], true)) {
533 598 $plugin_tables['tm_taskmeta'][] = $wpo;
599 + }
600 +
601 + if (!in_array($wpo, $plugin_tables['tm_tasks'], true)) {
534 602 $plugin_tables['tm_tasks'][] = $wpo;
535 603 }
536 604
537 605 return $plugin_tables;
@@ -545,16 +613,32 @@
545 613 */
546 614 public function get_table_plugin($table) {
547 615 global $wpdb;
548 616
617 + $plugins = array();
618 + $original_table = $table;
549 619 // delete table prefix.
550 620 $table = preg_replace('/^'.$wpdb->prefix.'([0-9]+_)?/', '', $table);
551 621 $plugins_tables = $this->get_all_plugin_tables_relationship();
552 622
623 + // If a direct match exists.
553 624 if (array_key_exists($table, $plugins_tables)) {
554 - return $plugins_tables[$table];
625 + $plugins = $plugins_tables[$table];
555 626 }
556 627
628 + // if has pro_quiz in name then try to match with learndash tables.
629 + if (false !== stripos($original_table, 'pro_quiz')) {
630 + $match_learndash_tables_plugin = $this->match_learndash_tables_plugin($original_table);
631 +
632 + if (!empty($match_learndash_tables_plugin)) {
633 + $plugins = array_merge($plugins, $match_learndash_tables_plugin);
634 + }
635 + }
636 +
637 + if (!empty($plugins)) {
638 + return array_unique($plugins);
639 + }
640 +
557 641 return false;
558 642 }
559 643
560 644 /**
@@ -563,9 +647,10 @@
563 647 * @return string
564 648 */
565 649 private function get_plugin_json_file_path() {
566 650 $uploads_dir = wp_upload_dir(null, false);
567 - return apply_filters('wpo_get_plugin_json_file_path', trailingslashit($uploads_dir['basedir']).'wpo-plugins-tables-list.json');
651 + $filtered_path = apply_filters('wpo_get_plugin_json_file_path', trailingslashit($uploads_dir['basedir']).'wpo/wpo-plugins-tables-list.json');
652 + return is_string($filtered_path) ? $filtered_path : '';
568 653 }
569 654
570 655 /**
571 656 * Get all installed plugin slugs.
@@ -583,9 +668,9 @@
583 668
584 669 $plugins = get_plugins();
585 670
586 671 foreach ($plugins as $plugin_file => $plugin_data) {
587 - if ('' != $plugin_data['TextDomain']) {
672 + if ('' !== $plugin_data['TextDomain']) {
588 673 $installed_plugins[] = $plugin_data['TextDomain'];
589 674 } else {
590 675 $plugin_file_parts = explode('/', $plugin_file);
591 676 $installed_plugins[]= $plugin_file_parts[0];
@@ -606,9 +691,9 @@
606 691 if (!function_exists('get_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
607 692 $plugins = get_plugins();
608 693
609 694 // return true for wp-optimize without checking.
610 - if ('wp-optimize' == $plugin) {
695 + if ('wp-optimize' === $plugin) {
611 696 return array(
612 697 'installed' => true,
613 698 'active' => true,
614 699 );
@@ -618,11 +703,11 @@
618 703 $active = false;
619 704
620 705 foreach ($plugins as $plugin_file => $plugin_data) {
621 706 $plugin_file_parts = explode('/', $plugin_file);
622 - $plugin_slug = $plugin_file_parts[0];
707 + $plugin_slug = $plugin_file_parts[0] ?? '';
623 708
624 - if ($plugin == $plugin_slug) {
709 + if ($plugin === $plugin_slug) {
625 710 $installed = true;
626 711 $active = is_plugin_active($plugin_file);
627 712 }
628 713 }
@@ -645,13 +730,36 @@
645 730 $update_request = wp_remote_get('https://plugins.svn.wordpress.org/wp-optimize/trunk/plugin.json', array('timeout' => 3000));
646 731 if (200 !== wp_remote_retrieve_response_code($update_request)) return;
647 732 $json_content = wp_remote_retrieve_body($update_request);
648 733 if (json_decode($json_content)) {
649 - file_put_contents($this->get_plugin_json_file_path(), $json_content);
734 + // phpcs:disable
735 + // WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- WP_Filesystem not available this early
736 + // Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
737 + if (false === @file_put_contents($this->get_plugin_json_file_path(), $json_content)) {
738 + error_log("WP-Optimize: wpo-plugins-tables-list.json couldn't be updated");
739 + return;
740 + }
741 + // phpcs:enable
742 + $this->change_plugin_json_permissions();
650 743 }
651 744 }
652 745
653 746 /**
747 + * Set permissions to 640 for plugin.json
748 + *
749 + * @return void
750 + */
751 + public function change_plugin_json_permissions() {
752 + $plugin_json_file_path = $this->get_plugin_json_file_path();
753 + // phpcs:disable
754 + // Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
755 + if (is_file($plugin_json_file_path)) {
756 + @chmod($plugin_json_file_path, 0640);
757 + }
758 + // phpcs:enable
759 + }
760 +
761 + /**
654 762 * Cache all table rows count
655 763 */
656 764 public function wpo_update_record_count() {
657 765 global $wpdb;
@@ -656,9 +764,42 @@
656 764 public function wpo_update_record_count() {
657 765 global $wpdb;
658 766 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
659 767 foreach ($tables_info as $table) {
660 - $rows_count = $wpdb->get_var("SELECT COUNT(*) FROM `$table->Name`");
768 + $rows_count = $wpdb->get_var("SELECT COUNT(*) FROM `" . esc_sql($table->Name) . "`");
661 769 set_transient('wpo_' . $table->Name . '_count', $rows_count, 24*60*60);
662 770 }
771 + }
772 +
773 + /**
774 + * Check if the give table name belongs to a LearnDash plugin table and return the plugin slug.
775 + *
776 + * @param string $table_name The original table name with prefix to check if it belongs to LearnDash plugin.
777 + * @return array
778 + */
779 + private function match_learndash_tables_plugin($table_name) {
780 +
781 + $table_names = array(
782 + 'pro_quiz_category',
783 + 'pro_quiz_form',
784 + 'pro_quiz_lock',
785 + 'pro_quiz_prerequisite',
786 + 'pro_quiz_question',
787 + 'pro_quiz_statistic',
788 + 'pro_quiz_statistic_ref',
789 + 'pro_quiz_template',
790 + 'pro_quiz_toplist',
791 + );
792 +
793 + $learndash_slug = 'sfwd-lms';
794 +
795 + foreach ($table_names as $learndash_table) {
796 + $table_suffix = substr($table_name, -strlen($learndash_table));
797 +
798 + if ($table_suffix === $learndash_table) {
799 + return array($learndash_slug);
800 + }
801 + }
802 +
803 + return array();
663 804 }
664 805 }