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

661 lines 16.6 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 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.
131 if ($table_name && !$fetched_all_tables) {
132 $sql = $wpdb->prepare("SHOW TABLE STATUS LIKE '%s'", $table_name);
133 $tables_info = $wpdb->get_results($sql);
134 } else {
135 if ($update || empty($tables_info) || !is_array($tables_info) || !$fetched_all_tables) {
136 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
137 $fetched_all_tables = true;
138 foreach ($tables_info as $i => $table) {
139 $rows_count = get_transient($table->Name . '_count');
140 if (false === $rows_count) break;
141 $tables_info[$i]->Rows = $rows_count;
142 }
143 }
144 }
145
146 // If option innodb_file_per_table is disabled then Data_free column will have summary overhead value for all table.
147 if (!empty($tables_info)) {
148 foreach ($tables_info as $i => $table) {
149 if (self::INNODB_ENGINE == $table->Engine && false == $this->is_option_enabled('innodb_file_per_table')) {
150 $tables_info[$i]->Data_free = 0;
151 }
152 }
153 }
154
155 return $tables_info;
156 }
157
158 /**
159 * Whether a table exists
160 *
161 * @return boolean
162 */
163 public function table_exists($table_name, $use_default_prefix = true) {
164 global $wpdb;
165 return null !== $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($use_default_prefix ? $wpdb->prefix.$table_name : $table_name)));
166 }
167
168 /**
169 * Returns result for query SHOW FULL TABLES as associative array [table_name] => table_type.
170 *
171 * @return array
172 */
173 public function get_show_full_tables() {
174 global $wpdb;
175
176 static $tables_info = array();
177
178 if (empty($tables_info) || !is_array($tables_info)) {
179 $_tables_info = $wpdb->get_results('SHOW FULL TABLES', ARRAY_N);
180
181 if (!empty($_tables_info)) {
182 foreach ($_tables_info as $row) {
183 $tables_info[$row[0]] = $row[1];
184 }
185 }
186 }
187
188 return $tables_info;
189 }
190
191 /**
192 * Checks if table is a VIEW.
193 *
194 * @param string $table_name
195 * @return bool
196 */
197 public function is_view($table_name) {
198 $tables_info = $this->get_show_full_tables();
199
200 if (!array_key_exists($table_name, $tables_info)) return false;
201
202 return ('VIEW' == $tables_info[$table_name]);
203 }
204
205 /**
206 * Returns true if DDL supported.
207 *
208 * @return bool
209 */
210 public function has_online_ddl() {
211 if (self::MYSQL_DB == $this->get_server_type()) {
212 if (version_compare($this->get_version(), '5.7', '>=')) {
213 return true;
214 } else {
215 return false;
216 }
217 } elseif (self::MARIA_DB == $this->get_server_type()) {
218 if (version_compare($this->get_version(), '10.0.0', '>=')) {
219 return true;
220 } else {
221 return false;
222 }
223 }
224
225 return false;
226 }
227
228 /**
229 * Returns database option variable
230 *
231 * @param string $option_name Name of database option.
232 * @return mixed|null
233 */
234 public function get_option_value($option_name) {
235 global $wpdb;
236 static $options = array();
237
238 if (array_key_exists($option_name, $options)) return $options[$option_name];
239
240 $option = $wpdb->get_row(
241 $wpdb->prepare('SHOW SESSION VARIABLES LIKE %s', $option_name)
242 );
243
244 if (!empty($option)) {
245 $options[$option_name] = $option->Value;
246 return $option->Value;
247 }
248
249 return null;
250 }
251
252 /**
253 * Returns true if database option $option_name
254 *
255 * @param string $option_name Name of database option name.
256 * @return bool
257 */
258 public function is_option_enabled($option_name) {
259 $option_value = $this->get_option_value($option_name);
260
261 if ('ON' == strtoupper($option_value)) return true;
262 return false;
263 }
264
265 /**
266 * Returns true if table $table_name is optimizable
267 *
268 * @param string $table_name Name of database table
269 * @return bool
270 */
271 public function is_table_optimizable($table_name) {
272 $server_type = $this->get_server_type();
273 $server_version = $this->get_version();
274 $table_type = $this->get_table_type($table_name);
275
276 // return true if table is MyISAM.
277 if (self::MYISAM_ENGINE == $table_type) return true;
278
279 // return true if table is Archive or Aria.
280 if (self::ARCHIVE_ENGINE == $table_type || self::ARIA_ENGINE == $table_type) return true;
281
282 // if InnoDB then check if we can optimize.
283 if (self::INNODB_ENGINE == $table_type) {
284 // check for MysqlDB.
285 if (self::MYSQL_DB == $server_type && $this->has_online_ddl()) {
286 return true;
287 }
288
289 // check for MariaDB.
290 if (self::MARIA_DB == $server_type) {
291 // if innodb_file_per_table enabled or version not older than 10.1.1 and innodb_defragment enabled.
292 if ($this->is_option_enabled('innodb_file_per_table') || (version_compare($server_version, '10.1.1', '>=') && $this->is_option_enabled('innodb_defragment'))) {
293 return true;
294 }
295 }
296 }
297
298 // otherwise return false.
299 return false;
300 }
301
302 /**
303 * Returns true if table type is supported for optimization.
304 *
305 * @param string $table_name Name of database table
306 * @return bool
307 */
308 public function is_table_type_optimize_supported($table_name) {
309 $table_type = $this->get_table_type($table_name);
310
311 $supported_table_types = array(
312 self::MYISAM_ENGINE,
313 self::INNODB_ENGINE,
314 self::ARCHIVE_ENGINE,
315 self::ARIA_ENGINE,
316 );
317
318 return in_array($table_type, $supported_table_types);
319 }
320
321 /**
322 * Returns true if table type is supported for repair.
323 *
324 * @param string $table_name
325 * @return bool
326 */
327 public function is_table_type_repair_supported($table_name) {
328 $table_type = $this->get_table_type($table_name);
329
330 $supported_table_types = array(
331 self::MYISAM_ENGINE,
332 self::ARCHIVE_ENGINE,
333 self::CSV_ENGINE,
334 );
335
336 return in_array($table_type, $supported_table_types);
337 }
338
339 /**
340 * Run CHECK TABLE query and returns statuses for single or list of tables.
341 *
342 * @param array|string $table
343 */
344 public function check_table($table) {
345 global $wpdb;
346
347 if (is_array($table)) {
348 $table = join('`,`', $table);
349 }
350
351 $result = array();
352
353 if (empty($table)) return $result;
354
355 $query_result = $wpdb->get_results('CHECK TABLE `'.$table.'`;');
356
357 if (empty($query_result)) return $result;
358
359 foreach ($query_result as $row) {
360 $table_name_parts = explode('.', rtrim($row->Table, ' .'));
361 $table_name = array_pop($table_name_parts);
362
363 if (!array_key_exists($table_name, $result)) {
364 $result[$table_name] = array(
365 'status' => '',
366 'corrupted' => false,
367 );
368 }
369
370 if ('error' == $row->Msg_type) {
371 $result[$table_name]['status'] = $row->Msg_type;
372
373 if (preg_match('/corrupt/i', $row->Msg_text)) {
374 $result[$table_name]['corrupted'] = true;
375 } else {
376 $result[$table_name]['message'] = $row->Msg_text;
377 }
378 }
379
380 if ('status' == $row->Msg_type) {
381 $result[$table_name]['status'] = $row->Msg_text;
382 }
383 }
384
385 return $result;
386 }
387
388 /**
389 * Check all supported for repair tables and return statuses for them.
390 *
391 * @return array
392 */
393 public function check_all_tables() {
394 static $result = null;
395
396 if (null !== $result) return $result;
397
398 $tables = $this->get_show_table_status();
399 $supported_tables = array();
400
401 foreach ($tables as $table) {
402 if ('' == $table->Engine || $this->is_table_type_repair_supported($table->Name)) {
403 $supported_tables[] = $table->Name;
404 }
405 }
406
407 $result = $this->check_table($supported_tables);
408
409 return $result;
410 }
411
412 /**
413 * Returns true if table needing repair.
414 *
415 * @param string $table_name Database table name.
416 */
417 public function is_table_needing_repair($table_name) {
418 $table_statuses = $this->check_all_tables();
419
420 if (!$this->is_table_type_repair_supported($table_name)) return false;
421
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 }
427 }
428
429 /**
430 * Check if $table using by any of installed plugins.
431 *
432 * @param string $table
433 * @return bool
434 */
435 public function is_table_using_by_plugin($table) {
436 $plugin_names = $this->get_table_plugin($table);
437
438 // if we can't determine which plugin use $table then return true.
439 if (false == $plugin_names) {
440 return true;
441 }
442
443 // is WordPress core table or using by any of installed plugins then return true.
444 foreach ($plugin_names as $plugin_name) {
445 if (__('WordPress core', 'wp-optimize') == $plugin_name || in_array($plugin_name, $this->get_all_installed_plugins())) {
446 return true;
447 }
448 }
449
450 return false;
451 }
452
453 /**
454 * Get blog_id
455 *
456 * @param string $table_name
457 * @return int
458 */
459 public function get_table_blog_id($table_name) {
460 global $wpdb;
461
462 if (is_multisite()) {
463 $blogs_ids = wp_list_pluck(WP_Optimize()->get_sites(), 'blog_id');
464
465 // if match with base_prefix_(number)_
466 if (preg_match('/^'.$wpdb->base_prefix.'(\d+)_/', $table_name, $match)) {
467 // check if matched number in available sites.
468 if (false !== array_search($match[1], $blogs_ids)) return $match[1];
469 }
470 }
471
472 return 1;
473 }
474
475 /**
476 * Get information about relations between tables and plugins. [ 'table' => ['plugin1', 'plugin2', ...], ... ].
477 *
478 * @return array
479 */
480 private function get_all_plugin_tables_relationship() {
481 static $plugin_tables = array();
482
483 if (!empty($plugin_tables)) return $plugin_tables;
484
485 $wp_core_tables = array(
486 'blogs',
487 'blog_versions',
488 'commentmeta',
489 'comments',
490 'links',
491 'options',
492 'postmeta',
493 'posts',
494 'registration_log',
495 'signups',
496 'term_relationships',
497 'term_taxonomy',
498 'termmeta',
499 'terms',
500 'usermeta',
501 'users',
502 'site',
503 'sitemeta',
504 );
505
506 $plugin_tables_json_file = $this->get_plugin_json_file_path();
507 $fallback_plugin_tables_json_file = WPO_PLUGIN_MAIN_PATH.'plugin.json';
508
509 if (is_file($plugin_tables_json_file) && is_readable($plugin_tables_json_file)) {
510 // get data from plugin.json file.
511 $plugin_tables = json_decode(file_get_contents($plugin_tables_json_file), true);
512 }
513
514 // Fallback to the bundled version if the list is empty
515 if (empty($plugin_tables)) {
516 if (is_file($fallback_plugin_tables_json_file) && is_readable($fallback_plugin_tables_json_file)) {
517 // get data from the bundled plugin.json file.
518 $plugin_tables = json_decode(file_get_contents($fallback_plugin_tables_json_file), true);
519 }
520 }
521
522 foreach ($wp_core_tables as $table) {
523 $plugin_tables[$table][] = __('WordPress core', 'wp-optimize');
524 }
525
526 // add WP-Optimize tables.
527 $wpo = 'wp-optimize';
528 if (false === array_search($wpo, $plugin_tables['tm_taskmeta']) && false === array_search($wpo, $plugin_tables['tm_tasks'])) {
529 $plugin_tables['tm_taskmeta'][] = $wpo;
530 $plugin_tables['tm_tasks'][] = $wpo;
531 }
532
533 return $plugin_tables;
534 }
535
536 /**
537 * Try to get plugin name by table name and return it or return false if plugin is not defined.
538 *
539 * @param string $table
540 * @return array|bool - array with plugin slugs or false.
541 */
542 public function get_table_plugin($table) {
543 global $wpdb;
544
545 // delete table prefix.
546 $table = preg_replace('/^'.$wpdb->prefix.'([0-9]+_)?/', '', $table);
547 $plugins_tables = $this->get_all_plugin_tables_relationship();
548
549 if (array_key_exists($table, $plugins_tables)) {
550 return $plugins_tables[$table];
551 }
552
553 return false;
554 }
555
556 /**
557 * Get the path where the updated plugin.json is stored
558 *
559 * @return string
560 */
561 private function get_plugin_json_file_path() {
562 $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');
564 }
565
566 /**
567 * Get all installed plugin slugs.
568 *
569 * @return array
570 */
571 public function get_all_installed_plugins() {
572 static $installed_plugins;
573
574 if (is_array($installed_plugins)) return $installed_plugins;
575
576 $installed_plugins = array();
577
578 if (!function_exists('get_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
579
580 $plugins = get_plugins();
581
582 foreach ($plugins as $plugin_file => $plugin_data) {
583 if ('' != $plugin_data['TextDomain']) {
584 $installed_plugins[] = $plugin_data['TextDomain'];
585 } else {
586 $plugin_file_parts = explode('/', $plugin_file);
587 $installed_plugins[]= $plugin_file_parts[0];
588 }
589 }
590
591 return $installed_plugins;
592 }
593
594 /**
595 * Check current plugin status installed/not installed and active/inactive.
596 *
597 * @param string $plugin
598 * @return array - ['installed' => true|false, 'active' => true|false]
599 */
600 public function get_plugin_status($plugin) {
601
602 if (!function_exists('get_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
603 $plugins = get_plugins();
604
605 // return true for wp-optimize without checking.
606 if ('wp-optimize' == $plugin) {
607 return array(
608 'installed' => true,
609 'active' => true,
610 );
611 }
612
613 $installed = false;
614 $active = false;
615
616 foreach ($plugins as $plugin_file => $plugin_data) {
617 $plugin_file_parts = explode('/', $plugin_file);
618 $plugin_slug = $plugin_file_parts[0];
619
620 if ($plugin == $plugin_slug) {
621 $installed = true;
622 $active = is_plugin_active($plugin_file);
623 }
624 }
625
626 return array(
627 'installed' => $installed,
628 'active' => $active,
629 );
630 }
631
632 /**
633 * Update list in plugin.json, if necessary
634 *
635 * @return void
636 */
637 public function update_plugin_json() {
638 // Add the possibility to turn this off.
639 if (!apply_filters('wpo_update_plugin_json', true)) return;
640
641 $update_request = wp_remote_get('https://plugins.svn.wordpress.org/wp-optimize/trunk/plugin.json', array('timeout' => 3000));
642 if (200 !== wp_remote_retrieve_response_code($update_request)) return;
643 $json_content = wp_remote_retrieve_body($update_request);
644 if (json_decode($json_content)) {
645 file_put_contents($this->get_plugin_json_file_path(), $json_content);
646 }
647 }
648
649 /**
650 * Cache all table rows count
651 */
652 public function wpo_update_record_count() {
653 global $wpdb;
654 $tables_info = $wpdb->get_results('SHOW TABLE STATUS');
655 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);
658 }
659 }
660 }
661