PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.20
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.20
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
wp-optimize / includes / class-wp-optimize-database-information.php

class-wp-optimize-database-information.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.20, at includes/class-wp-optimize-database-information.php

665 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) die('No direct access allowed');
4
5 class WP_Optimize_Database_Information {
6
7 const UNKNOWN_DB = 'unknown';
8 const MARIA_DB = 'MariaDB';
9 const PERCONA_DB = 'Percona';
10 // for some reason coding standard parser give error here WordPress.DB.RestrictedFunctions.mysql_mysql_db
11 const MYSQL_DB = 'MysqlDB';
12
13 const MYISAM_ENGINE = 'MyISAM';
14 const MEMORY_ENGINE = 'Memory';
15 const INNODB_ENGINE = 'InnoDB';
16 const ARCHIVE_ENGINE = 'ARCHIVE';
17 const CSV_ENGINE = 'CSV';
18 const NDB_ENGINE = 'NDB';
19 const ARIA_ENGINE = 'Aria'; // MariaDB
20 const VIEW = 'VIEW';
21
22 /**
23 * Returns singleton instance object
24 *
25 * @return WP_Optimize_Database_Information Returns `WP_Optimize_Database_Information` object
26 */
27 public static function instance() {
28 static $_instance = null;
29 if (null === $_instance) {
30 $_instance = new self();
31 }
32 return $_instance;
33 }
34
35 /**
36 * Returns server type MySQL or MariaDB if mysql database or Unknown if not mysql.
37 *
38 * @return string
39 */
40 public function get_server_type() {
41 global $wpdb;
42 static $server_type = null;
43
44 if (!$wpdb->is_mysql) return self::UNKNOWN_DB;
45
46 if (null !== $server_type) return $server_type;
47
48 $server_type = self::MYSQL_DB;
49
50 $variables = $wpdb->get_results('SHOW SESSION VARIABLES LIKE "version%"');
51
52 if (!empty($variables)) {
53 foreach ($variables as $variable) {
54 if (preg_match('/mariadb/i', $variable->Value)) {
55 $server_type = self::MARIA_DB;
56 }
57 if (preg_match('/percona/i', $variable->Value)) {
58 $server_type = self::PERCONA_DB;
59 }
60 }
61 }
62
63 return $server_type;
64 }
65
66 /**
67 * Returns database server version
68 *
69 * @return string|bool
70 */
71 public function get_version() {
72 $version = $this->get_option_value('version');
73
74 if (!empty($version)) {
75 if (preg_match('/^(\d+)(\.\d+)+/', $version, $match)) {
76 return $match[0];
77 }
78 }
79
80 return false;
81 }
82
83 /**
84 * Return table type by $table_name.
85 *
86 * @param String $table_name Database table name.
87 * @return String|Boolean - returns false upon failure
88 */
89 public function get_table_type($table_name) {
90 $table_info = $this->get_table_status($table_name);
91
92 if ($table_info) {
93 if (!$table_info->Engine && $this->is_view($table_name)) return self::VIEW;
94
95 return $table_info->Engine;
96 }
97
98 return false;
99 }
100
101 /**
102 * Returns information about database table.
103 *
104 * @param string $table_name
105 * @param bool $update if true, then force request to database and don't use cached values.
106 * @return bool|mixed
107 */
108 public function get_table_status($table_name, $update = false) {
109 $tables_info = $this->get_show_table_status($update, $table_name);
110
111 foreach ($tables_info as $table_info) {
112 if ($table_name == $table_info->Name) return $table_info;
113 }
114
115 return false;
116 }
117
118 /**
119 * Returns result for query SHOW TABLE STATUS.
120 *
121 * @param bool $update refresh or no cached data
122 * @return array
123 */
124 public function get_show_table_status($update = false, $table_name = '') {
125 global $wpdb;
126 static $tables_info = array();
127 static $fetched_all_tables = false;
128
129 if ($fetched_all_tables && !$update) {
130 return $tables_info;
131 }
132
133 // If a table name is provided, and the whole record hasn't been fetched yet, only fetch the information for the current table.
134 // This allows for a big performance gain when using WP-CLI or doing single optimizations.
135 if ($table_name && !$fetched_all_tables) {
136 $sql = $wpdb->prepare("SHOW TABLE STATUS LIKE '%s'", $table_name);
137 $tables_info = $wpdb->get_results($sql);
138 } else {
139 if ($update || empty($tables_info) || !is_array($tables_info) || !$fetched_all_tables) {
140 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
141 $fetched_all_tables = true;
142 foreach ($tables_info as $i => $table) {
143 $rows_count = get_transient('wpo_' . $table->Name . '_count');
144 if (false === $rows_count) break;
145 $tables_info[$i]->Rows = $rows_count;
146 }
147 }
148 }
149
150 // If option innodb_file_per_table is disabled then Data_free column will have summary overhead value for all table.
151 if (!empty($tables_info)) {
152 foreach ($tables_info as $i => $table) {
153 if (self::INNODB_ENGINE == $table->Engine && false == $this->is_option_enabled('innodb_file_per_table')) {
154 $tables_info[$i]->Data_free = 0;
155 }
156 }
157 }
158
159 return $tables_info;
160 }
161
162 /**
163 * Whether a table exists
164 *
165 * @return boolean
166 */
167 public function table_exists($table_name, $use_default_prefix = true) {
168 global $wpdb;
169 return null !== $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($use_default_prefix ? $wpdb->prefix.$table_name : $table_name)));
170 }
171
172 /**
173 * Returns result for query SHOW FULL TABLES as associative array [table_name] => table_type.
174 *
175 * @return array
176 */
177 public function get_show_full_tables() {
178 global $wpdb;
179
180 static $tables_info = array();
181
182 if (empty($tables_info) || !is_array($tables_info)) {
183 $_tables_info = $wpdb->get_results('SHOW FULL TABLES', ARRAY_N);
184
185 if (!empty($_tables_info)) {
186 foreach ($_tables_info as $row) {
187 $tables_info[$row[0]] = $row[1];
188 }
189 }
190 }
191
192 return $tables_info;
193 }
194
195 /**
196 * Checks if table is a VIEW.
197 *
198 * @param string $table_name
199 * @return bool
200 */
201 public function is_view($table_name) {
202 $tables_info = $this->get_show_full_tables();
203
204 if (!array_key_exists($table_name, $tables_info)) return false;
205
206 return ('VIEW' == $tables_info[$table_name]);
207 }
208
209 /**
210 * Returns true if DDL supported.
211 *
212 * @return bool
213 */
214 public function has_online_ddl() {
215 if (self::MYSQL_DB == $this->get_server_type()) {
216 if (version_compare($this->get_version(), '5.7', '>=')) {
217 return true;
218 } else {
219 return false;
220 }
221 } elseif (self::MARIA_DB == $this->get_server_type()) {
222 if (version_compare($this->get_version(), '10.0.0', '>=')) {
223 return true;
224 } else {
225 return false;
226 }
227 }
228
229 return false;
230 }
231
232 /**
233 * Returns database option variable
234 *
235 * @param string $option_name Name of database option.
236 * @return mixed|null
237 */
238 public function get_option_value($option_name) {
239 global $wpdb;
240 static $options = array();
241
242 if (array_key_exists($option_name, $options)) return $options[$option_name];
243
244 $option = $wpdb->get_row(
245 $wpdb->prepare('SHOW SESSION VARIABLES LIKE %s', $option_name)
246 );
247
248 if (!empty($option)) {
249 $options[$option_name] = $option->Value;
250 return $option->Value;
251 }
252
253 return null;
254 }
255
256 /**
257 * Returns true if database option $option_name
258 *
259 * @param string $option_name Name of database option name.
260 * @return bool
261 */
262 public function is_option_enabled($option_name) {
263 $option_value = $this->get_option_value($option_name);
264
265 if ('ON' == strtoupper($option_value)) return true;
266 return false;
267 }
268
269 /**
270 * Returns true if table $table_name is optimizable
271 *
272 * @param string $table_name Name of database table
273 * @return bool
274 */
275 public function is_table_optimizable($table_name) {
276 $server_type = $this->get_server_type();
277 $server_version = $this->get_version();
278 $table_type = $this->get_table_type($table_name);
279
280 // return true if table is MyISAM.
281 if (self::MYISAM_ENGINE == $table_type) return true;
282
283 // return true if table is Archive or Aria.
284 if (self::ARCHIVE_ENGINE == $table_type || self::ARIA_ENGINE == $table_type) return true;
285
286 // if InnoDB then check if we can optimize.
287 if (self::INNODB_ENGINE == $table_type) {
288 // check for MysqlDB.
289 if (self::MYSQL_DB == $server_type && $this->has_online_ddl()) {
290 return true;
291 }
292
293 // check for MariaDB.
294 if (self::MARIA_DB == $server_type) {
295 // if innodb_file_per_table enabled or version not older than 10.1.1 and innodb_defragment enabled.
296 if ($this->is_option_enabled('innodb_file_per_table') || (version_compare($server_version, '10.1.1', '>=') && $this->is_option_enabled('innodb_defragment'))) {
297 return true;
298 }
299 }
300 }
301
302 // otherwise return false.
303 return false;
304 }
305
306 /**
307 * Returns true if table type is supported for optimization.
308 *
309 * @param string $table_name Name of database table
310 * @return bool
311 */
312 public function is_table_type_optimize_supported($table_name) {
313 $table_type = $this->get_table_type($table_name);
314
315 $supported_table_types = array(
316 self::MYISAM_ENGINE,
317 self::INNODB_ENGINE,
318 self::ARCHIVE_ENGINE,
319 self::ARIA_ENGINE,
320 );
321
322 return in_array($table_type, $supported_table_types);
323 }
324
325 /**
326 * Returns true if table type is supported for repair.
327 *
328 * @param string $table_name
329 * @return bool
330 */
331 public function is_table_type_repair_supported($table_name) {
332 $table_type = $this->get_table_type($table_name);
333
334 $supported_table_types = array(
335 self::MYISAM_ENGINE,
336 self::ARCHIVE_ENGINE,
337 self::CSV_ENGINE,
338 );
339
340 return in_array($table_type, $supported_table_types);
341 }
342
343 /**
344 * Run CHECK TABLE query and returns statuses for single or list of tables.
345 *
346 * @param array|string $table
347 */
348 public function check_table($table) {
349 global $wpdb;
350
351 if (is_array($table)) {
352 $table = join('`,`', $table);
353 }
354
355 $result = array();
356
357 if (empty($table)) return $result;
358
359 $query_result = $wpdb->get_results('CHECK TABLE `'.$table.'`;');
360
361 if (empty($query_result)) return $result;
362
363 foreach ($query_result as $row) {
364 $table_name_parts = explode('.', rtrim($row->Table, ' .'));
365 $table_name = array_pop($table_name_parts);
366
367 if (!array_key_exists($table_name, $result)) {
368 $result[$table_name] = array(
369 'status' => '',
370 'corrupted' => false,
371 );
372 }
373
374 if ('error' == $row->Msg_type) {
375 $result[$table_name]['status'] = $row->Msg_type;
376
377 if (preg_match('/corrupt/i', $row->Msg_text)) {
378 $result[$table_name]['corrupted'] = true;
379 } else {
380 $result[$table_name]['message'] = $row->Msg_text;
381 }
382 }
383
384 if ('status' == $row->Msg_type) {
385 $result[$table_name]['status'] = $row->Msg_text;
386 }
387 }
388
389 return $result;
390 }
391
392 /**
393 * Check all supported for repair tables and return statuses for them.
394 *
395 * @return array
396 */
397 public function check_all_tables() {
398 static $result = null;
399
400 if (null !== $result) return $result;
401
402 $tables = $this->get_show_table_status();
403 $supported_tables = array();
404
405 foreach ($tables as $table) {
406 if ('' == $table->Engine || $this->is_table_type_repair_supported($table->Name)) {
407 $supported_tables[] = $table->Name;
408 }
409 }
410
411 $result = $this->check_table($supported_tables);
412
413 return $result;
414 }
415
416 /**
417 * Returns true if table needing repair.
418 *
419 * @param string $table_name Database table name.
420 */
421 public function is_table_needing_repair($table_name) {
422 $table_statuses = $this->check_all_tables();
423
424 if (!$this->is_table_type_repair_supported($table_name)) return false;
425
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 }
431 }
432
433 /**
434 * Check if $table using by any of installed plugins.
435 *
436 * @param string $table
437 * @return bool
438 */
439 public function is_table_using_by_plugin($table) {
440 $plugin_names = $this->get_table_plugin($table);
441
442 // if we can't determine which plugin use $table then return true.
443 if (false == $plugin_names) {
444 return true;
445 }
446
447 // is WordPress core table or using by any of installed plugins then return true.
448 foreach ($plugin_names as $plugin_name) {
449 if (__('WordPress core', 'wp-optimize') == $plugin_name || in_array($plugin_name, $this->get_all_installed_plugins())) {
450 return true;
451 }
452 }
453
454 return false;
455 }
456
457 /**
458 * Get blog_id
459 *
460 * @param string $table_name
461 * @return int
462 */
463 public function get_table_blog_id($table_name) {
464 global $wpdb;
465
466 if (is_multisite()) {
467 $blogs_ids = wp_list_pluck(WP_Optimize()->get_sites(), 'blog_id');
468
469 // if match with base_prefix_(number)_
470 if (preg_match('/^'.$wpdb->base_prefix.'(\d+)_/', $table_name, $match)) {
471 // check if matched number in available sites.
472 if (false !== array_search($match[1], $blogs_ids)) return $match[1];
473 }
474 }
475
476 return 1;
477 }
478
479 /**
480 * Get information about relations between tables and plugins. [ 'table' => ['plugin1', 'plugin2', ...], ... ].
481 *
482 * @return array
483 */
484 private function get_all_plugin_tables_relationship() {
485 static $plugin_tables = array();
486
487 if (!empty($plugin_tables)) return $plugin_tables;
488
489 $wp_core_tables = array(
490 'blogs',
491 'blog_versions',
492 'commentmeta',
493 'comments',
494 'links',
495 'options',
496 'postmeta',
497 'posts',
498 'registration_log',
499 'signups',
500 'term_relationships',
501 'term_taxonomy',
502 'termmeta',
503 'terms',
504 'usermeta',
505 'users',
506 'site',
507 'sitemeta',
508 );
509
510 $plugin_tables_json_file = $this->get_plugin_json_file_path();
511 $fallback_plugin_tables_json_file = WPO_PLUGIN_MAIN_PATH.'plugin.json';
512
513 if (is_file($plugin_tables_json_file) && is_readable($plugin_tables_json_file)) {
514 // get data from plugin.json file.
515 $plugin_tables = json_decode(file_get_contents($plugin_tables_json_file), true);
516 }
517
518 // Fallback to the bundled version if the list is empty
519 if (empty($plugin_tables)) {
520 if (is_file($fallback_plugin_tables_json_file) && is_readable($fallback_plugin_tables_json_file)) {
521 // get data from the bundled plugin.json file.
522 $plugin_tables = json_decode(file_get_contents($fallback_plugin_tables_json_file), true);
523 }
524 }
525
526 foreach ($wp_core_tables as $table) {
527 $plugin_tables[$table][] = __('WordPress core', 'wp-optimize');
528 }
529
530 // add WP-Optimize tables.
531 $wpo = 'wp-optimize';
532 if (false === array_search($wpo, $plugin_tables['tm_taskmeta']) && false === array_search($wpo, $plugin_tables['tm_tasks'])) {
533 $plugin_tables['tm_taskmeta'][] = $wpo;
534 $plugin_tables['tm_tasks'][] = $wpo;
535 }
536
537 return $plugin_tables;
538 }
539
540 /**
541 * Try to get plugin name by table name and return it or return false if plugin is not defined.
542 *
543 * @param string $table
544 * @return array|bool - array with plugin slugs or false.
545 */
546 public function get_table_plugin($table) {
547 global $wpdb;
548
549 // delete table prefix.
550 $table = preg_replace('/^'.$wpdb->prefix.'([0-9]+_)?/', '', $table);
551 $plugins_tables = $this->get_all_plugin_tables_relationship();
552
553 if (array_key_exists($table, $plugins_tables)) {
554 return $plugins_tables[$table];
555 }
556
557 return false;
558 }
559
560 /**
561 * Get the path where the updated plugin.json is stored
562 *
563 * @return string
564 */
565 private function get_plugin_json_file_path() {
566 $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');
568 }
569
570 /**
571 * Get all installed plugin slugs.
572 *
573 * @return array
574 */
575 public function get_all_installed_plugins() {
576 static $installed_plugins;
577
578 if (is_array($installed_plugins)) return $installed_plugins;
579
580 $installed_plugins = array();
581
582 if (!function_exists('get_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
583
584 $plugins = get_plugins();
585
586 foreach ($plugins as $plugin_file => $plugin_data) {
587 if ('' != $plugin_data['TextDomain']) {
588 $installed_plugins[] = $plugin_data['TextDomain'];
589 } else {
590 $plugin_file_parts = explode('/', $plugin_file);
591 $installed_plugins[]= $plugin_file_parts[0];
592 }
593 }
594
595 return $installed_plugins;
596 }
597
598 /**
599 * Check current plugin status installed/not installed and active/inactive.
600 *
601 * @param string $plugin
602 * @return array - ['installed' => true|false, 'active' => true|false]
603 */
604 public function get_plugin_status($plugin) {
605
606 if (!function_exists('get_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
607 $plugins = get_plugins();
608
609 // return true for wp-optimize without checking.
610 if ('wp-optimize' == $plugin) {
611 return array(
612 'installed' => true,
613 'active' => true,
614 );
615 }
616
617 $installed = false;
618 $active = false;
619
620 foreach ($plugins as $plugin_file => $plugin_data) {
621 $plugin_file_parts = explode('/', $plugin_file);
622 $plugin_slug = $plugin_file_parts[0];
623
624 if ($plugin == $plugin_slug) {
625 $installed = true;
626 $active = is_plugin_active($plugin_file);
627 }
628 }
629
630 return array(
631 'installed' => $installed,
632 'active' => $active,
633 );
634 }
635
636 /**
637 * Update list in plugin.json, if necessary
638 *
639 * @return void
640 */
641 public function update_plugin_json() {
642 // Add the possibility to turn this off.
643 if (!apply_filters('wpo_update_plugin_json', true)) return;
644
645 $update_request = wp_remote_get('https://plugins.svn.wordpress.org/wp-optimize/trunk/plugin.json', array('timeout' => 3000));
646 if (200 !== wp_remote_retrieve_response_code($update_request)) return;
647 $json_content = wp_remote_retrieve_body($update_request);
648 if (json_decode($json_content)) {
649 file_put_contents($this->get_plugin_json_file_path(), $json_content);
650 }
651 }
652
653 /**
654 * Cache all table rows count
655 */
656 public function wpo_update_record_count() {
657 global $wpdb;
658 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
659 foreach ($tables_info as $table) {
660 $rows_count = $wpdb->get_var("SELECT COUNT(*) FROM `$table->Name`");
661 set_transient('wpo_' . $table->Name . '_count', $rows_count, 24*60*60);
662 }
663 }
664 }
665