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

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

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