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 +209 -64 3.2.9 → 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();
@@ -125,19 +187,22 @@
125 187 global $wpdb;
126 188 static $tables_info = array();
127 189 static $fetched_all_tables = false;
128 190
191 + if ($fetched_all_tables && !$update) {
192 + return $tables_info;
193 + }
194 +
129 195 // If a table name is provided, and the whole record hasn't been fetched yet, only fetch the information for the current table.
130 - // This allows for a big preformance gain when using WP-CLI or doing single optimizations.
196 + // This allows for a big performance gain when using WP-CLI or doing single optimizations.
131 197 if ($table_name && !$fetched_all_tables) {
132 - $sql = $wpdb->prepare("SHOW TABLE STATUS LIKE '%s'", $table_name);
133 - $tables_info = $wpdb->get_results($sql);
198 + $tables_info = $wpdb->get_results($wpdb->prepare("SHOW TABLE STATUS LIKE %s", $table_name));
134 199 } else {
135 200 if ($update || empty($tables_info) || !is_array($tables_info) || !$fetched_all_tables) {
136 201 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
137 202 $fetched_all_tables = true;
138 203 foreach ($tables_info as $i => $table) {
139 - $rows_count = get_transient($table->Name . '_count');
204 + $rows_count = get_transient('wpo_' . $table->Name . '_count');
140 205 if (false === $rows_count) break;
141 206 $tables_info[$i]->Rows = $rows_count;
142 207 }
143 208 }
@@ -145,9 +210,9 @@
145 210
146 211 // If option innodb_file_per_table is disabled then Data_free column will have summary overhead value for all table.
147 212 if (!empty($tables_info)) {
148 213 foreach ($tables_info as $i => $table) {
149 - 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')) {
150 215 $tables_info[$i]->Data_free = 0;
151 216 }
152 217 }
153 218 }
@@ -157,8 +222,10 @@
157 222
158 223 /**
159 224 * Whether a table exists
160 225 *
226 + * @param string $table_name Name of the table
227 + * @param bool $use_default_prefix Whether to use default prefix
161 228 * @return boolean
162 229 */
163 230 public function table_exists($table_name, $use_default_prefix = true) {
164 231 global $wpdb;
@@ -198,9 +265,9 @@
198 265 $tables_info = $this->get_show_full_tables();
199 266
200 267 if (!array_key_exists($table_name, $tables_info)) return false;
201 268
202 - return ('VIEW' == $tables_info[$table_name]);
269 + return ('VIEW' === $tables_info[$table_name]);
203 270 }
204 271
205 272 /**
206 273 * Returns true if DDL supported.
@@ -207,15 +274,15 @@
207 274 *
208 275 * @return bool
209 276 */
210 277 public function has_online_ddl() {
211 - if (self::MYSQL_DB == $this->get_server_type()) {
278 + if (self::MYSQL_DB === $this->get_server_type()) {
212 279 if (version_compare($this->get_version(), '5.7', '>=')) {
213 280 return true;
214 281 } else {
215 282 return false;
216 283 }
217 - } elseif (self::MARIA_DB == $this->get_server_type()) {
284 + } elseif (self::MARIA_DB === $this->get_server_type()) {
218 285 if (version_compare($this->get_version(), '10.0.0', '>=')) {
219 286 return true;
220 287 } else {
221 288 return false;
@@ -257,38 +324,38 @@
257 324 */
258 325 public function is_option_enabled($option_name) {
259 326 $option_value = $this->get_option_value($option_name);
260 327
261 - if ('ON' == strtoupper($option_value)) return true;
262 - return false;
328 + return 'ON' === strtoupper($option_value);
263 329 }
264 330
265 331 /**
266 332 * Returns true if table $table_name is optimizable
267 333 *
268 - * @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.
269 336 * @return bool
270 337 */
271 - public function is_table_optimizable($table_name) {
338 + public function is_table_optimizable($table_name, $table_type = null) {
272 339 $server_type = $this->get_server_type();
273 340 $server_version = $this->get_version();
274 - $table_type = $this->get_table_type($table_name);
341 + $table_type = null !== $table_type ? $table_type : $this->get_table_type($table_name);
275 342
276 343 // return true if table is MyISAM.
277 - if (self::MYISAM_ENGINE == $table_type) return true;
344 + if (self::MYISAM_ENGINE === $table_type) return true;
278 345
279 346 // return true if table is Archive or Aria.
280 - 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;
281 348
282 349 // if InnoDB then check if we can optimize.
283 - if (self::INNODB_ENGINE == $table_type) {
350 + if (self::INNODB_ENGINE === $table_type) {
284 351 // check for MysqlDB.
285 - if (self::MYSQL_DB == $server_type && $this->has_online_ddl()) {
352 + if (self::MYSQL_DB === $server_type && $this->has_online_ddl()) {
286 353 return true;
287 354 }
288 355
289 356 // check for MariaDB.
290 - if (self::MARIA_DB == $server_type) {
357 + if (self::MARIA_DB === $server_type) {
291 358 // if innodb_file_per_table enabled or version not older than 10.1.1 and innodb_defragment enabled.
292 359 if ($this->is_option_enabled('innodb_file_per_table') || (version_compare($server_version, '10.1.1', '>=') && $this->is_option_enabled('innodb_defragment'))) {
293 360 return true;
294 361 }
@@ -299,15 +366,16 @@
299 366 return false;
300 367 }
301 368
302 369 /**
303 - * Returns true if table type is supported for optimization.
370 + * Returns true if the table type is supported for optimization.
304 371 *
305 - * @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.
306 374 * @return bool
307 375 */
308 - public function is_table_type_optimize_supported($table_name) {
309 - $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);
310 378
311 379 $supported_table_types = array(
312 380 self::MYISAM_ENGINE,
313 381 self::INNODB_ENGINE,
@@ -320,13 +388,14 @@
320 388
321 389 /**
322 390 * Returns true if table type is supported for repair.
323 391 *
324 - * @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.
325 394 * @return bool
326 395 */
327 - public function is_table_type_repair_supported($table_name) {
328 - $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);
329 398
330 399 $supported_table_types = array(
331 400 self::MYISAM_ENGINE,
332 401 self::ARCHIVE_ENGINE,
@@ -339,8 +408,9 @@
339 408 /**
340 409 * Run CHECK TABLE query and returns statuses for single or list of tables.
341 410 *
342 411 * @param array|string $table
412 + * @return array
343 413 */
344 414 public function check_table($table) {
345 415 global $wpdb;
346 416
@@ -351,9 +421,9 @@
351 421 $result = array();
352 422
353 423 if (empty($table)) return $result;
354 424
355 - $query_result = $wpdb->get_results('CHECK TABLE `'.$table.'`;');
425 + $query_result = $wpdb->get_results('CHECK TABLE `'. esc_sql($table).'`;');
356 426
357 427 if (empty($query_result)) return $result;
358 428
359 429 foreach ($query_result as $row) {
@@ -366,9 +436,9 @@
366 436 'corrupted' => false,
367 437 );
368 438 }
369 439
370 - if ('error' == $row->Msg_type) {
440 + if ('error' === $row->Msg_type) {
371 441 $result[$table_name]['status'] = $row->Msg_type;
372 442
373 443 if (preg_match('/corrupt/i', $row->Msg_text)) {
374 444 $result[$table_name]['corrupted'] = true;
@@ -376,9 +446,9 @@
376 446 $result[$table_name]['message'] = $row->Msg_text;
377 447 }
378 448 }
379 449
380 - if ('status' == $row->Msg_type) {
450 + if ('status' === $row->Msg_type) {
381 451 $result[$table_name]['status'] = $row->Msg_text;
382 452 }
383 453 }
384 454
@@ -398,9 +468,9 @@
398 468 $tables = $this->get_show_table_status();
399 469 $supported_tables = array();
400 470
401 471 foreach ($tables as $table) {
402 - 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)) {
403 473 $supported_tables[] = $table->Name;
404 474 }
405 475 }
406 476
@@ -411,39 +481,35 @@
411 481
412 482 /**
413 483 * Returns true if table needing repair.
414 484 *
415 - * @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.
416 487 */
417 - public function is_table_needing_repair($table_name) {
488 + public function is_table_needing_repair($table_name, $table_type = null) {
418 489 $table_statuses = $this->check_all_tables();
419 490
420 - 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;
421 492
422 - if (is_array($table_statuses) && array_key_exists($table_name, $table_statuses) && $table_statuses[$table_name]['corrupted']) {
423 - return true;
424 - } else {
425 - return false;
426 - }
493 + return (array_key_exists($table_name, $table_statuses) && $table_statuses[$table_name]['corrupted']);
427 494 }
428 495
429 496 /**
430 - * Check if $table using by any of installed plugins.
497 + * Check if any of plugins from $plugin_names is installed.
431 498 *
432 - * @param string $table
499 + * @param array $plugin_names
433 500 * @return bool
434 501 */
435 - public function is_table_using_by_plugin($table) {
436 - $plugin_names = $this->get_table_plugin($table);
502 + public function is_any_plugin_installed($plugin_names) {
437 503
438 - // if we can't determine which plugin use $table then return true.
439 - if (false == $plugin_names) {
440 - return true;
504 + if (empty($plugin_names)) {
505 + return false;
441 506 }
442 507
443 508 // is WordPress core table or using by any of installed plugins then return true.
444 509 foreach ($plugin_names as $plugin_name) {
445 - 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())) {
446 512 return true;
447 513 }
448 514 }
449 515
@@ -464,9 +530,9 @@
464 530
465 531 // if match with base_prefix_(number)_
466 532 if (preg_match('/^'.$wpdb->base_prefix.'(\d+)_/', $table_name, $match)) {
467 533 // check if matched number in available sites.
468 - if (false !== array_search($match[1], $blogs_ids)) return $match[1];
534 + if (in_array($match[1], $blogs_ids)) return absint($match[1]);
469 535 }
470 536 }
471 537
472 538 return 1;
@@ -524,10 +590,16 @@
524 590 }
525 591
526 592 // add WP-Optimize tables.
527 593 $wpo = 'wp-optimize';
528 - 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)) {
529 598 $plugin_tables['tm_taskmeta'][] = $wpo;
599 + }
600 +
601 + if (!in_array($wpo, $plugin_tables['tm_tasks'], true)) {
530 602 $plugin_tables['tm_tasks'][] = $wpo;
531 603 }
532 604
533 605 return $plugin_tables;
@@ -541,16 +613,32 @@
541 613 */
542 614 public function get_table_plugin($table) {
543 615 global $wpdb;
544 616
617 + $plugins = array();
618 + $original_table = $table;
545 619 // delete table prefix.
546 620 $table = preg_replace('/^'.$wpdb->prefix.'([0-9]+_)?/', '', $table);
547 621 $plugins_tables = $this->get_all_plugin_tables_relationship();
548 622
623 + // If a direct match exists.
549 624 if (array_key_exists($table, $plugins_tables)) {
550 - return $plugins_tables[$table];
625 + $plugins = $plugins_tables[$table];
551 626 }
552 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 +
553 641 return false;
554 642 }
555 643
556 644 /**
@@ -559,9 +647,10 @@
559 647 * @return string
560 648 */
561 649 private function get_plugin_json_file_path() {
562 650 $uploads_dir = wp_upload_dir(null, false);
563 - 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 : '';
564 653 }
565 654
566 655 /**
567 656 * Get all installed plugin slugs.
@@ -579,9 +668,9 @@
579 668
580 669 $plugins = get_plugins();
581 670
582 671 foreach ($plugins as $plugin_file => $plugin_data) {
583 - if ('' != $plugin_data['TextDomain']) {
672 + if ('' !== $plugin_data['TextDomain']) {
584 673 $installed_plugins[] = $plugin_data['TextDomain'];
585 674 } else {
586 675 $plugin_file_parts = explode('/', $plugin_file);
587 676 $installed_plugins[]= $plugin_file_parts[0];
@@ -602,9 +691,9 @@
602 691 if (!function_exists('get_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
603 692 $plugins = get_plugins();
604 693
605 694 // return true for wp-optimize without checking.
606 - if ('wp-optimize' == $plugin) {
695 + if ('wp-optimize' === $plugin) {
607 696 return array(
608 697 'installed' => true,
609 698 'active' => true,
610 699 );
@@ -614,11 +703,11 @@
614 703 $active = false;
615 704
616 705 foreach ($plugins as $plugin_file => $plugin_data) {
617 706 $plugin_file_parts = explode('/', $plugin_file);
618 - $plugin_slug = $plugin_file_parts[0];
707 + $plugin_slug = $plugin_file_parts[0] ?? '';
619 708
620 - if ($plugin == $plugin_slug) {
709 + if ($plugin === $plugin_slug) {
621 710 $installed = true;
622 711 $active = is_plugin_active($plugin_file);
623 712 }
624 713 }
@@ -641,13 +730,36 @@
641 730 $update_request = wp_remote_get('https://plugins.svn.wordpress.org/wp-optimize/trunk/plugin.json', array('timeout' => 3000));
642 731 if (200 !== wp_remote_retrieve_response_code($update_request)) return;
643 732 $json_content = wp_remote_retrieve_body($update_request);
644 733 if (json_decode($json_content)) {
645 - 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();
646 743 }
647 744 }
648 745
649 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 + /**
650 762 * Cache all table rows count
651 763 */
652 764 public function wpo_update_record_count() {
653 765 global $wpdb;
@@ -652,9 +764,42 @@
652 764 public function wpo_update_record_count() {
653 765 global $wpdb;
654 766 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
655 767 foreach ($tables_info as $table) {
656 - $rows_count = $wpdb->get_var("SELECT COUNT(*) FROM `$table->Name`");
657 - set_transient($table->Name . '_count', $rows_count, 24*60*60);
768 + $rows_count = $wpdb->get_var("SELECT COUNT(*) FROM `" . esc_sql($table->Name) . "`");
769 + set_transient('wpo_' . $table->Name . '_count', $rows_count, 24*60*60);
658 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();
659 804 }
660 805 }