PluginProbe
WP Reset / 1.45
WP Reset v1.45
trunk 1.0 1.1 1.11 1.20 1.25 1.30 1.35 1.40 1.45 1.50 1.55 1.60 1.65 1.70 1.75 1.77 1.80 1.81 1.82 1.83 1.84 1.85 1.86 1.90 All 42 releases
wp-reset / wp-reset.php

wp-reset.php in WP Reset 1.45, at wp-reset.php

1,867 lines 76.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: WP Reset
4 Plugin URI: https://wpreset.com/
5 Description: Reset the site to default installation values without modifying any files. Deletes all customizations and content.
6 Version: 1.45
7 Author: WebFactory Ltd
8 Author URI: https://www.webfactoryltd.com/
9 Text Domain: wp-reset
10
11 Copyright 2015 - 2018 Web factory Ltd (email: wpreset@webfactoryltd.com)
12
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License, version 2, as
15 published by the Free Software Foundation.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 */
26
27 // include only file
28 if (!defined('ABSPATH')) {
29 wp_die(__('Do not open this file directly.', 'wp-error'));
30 }
31
32
33 // load WP-CLI commands, if needed
34 if (defined('WP_CLI') && WP_CLI) {
35 require_once dirname( __FILE__ ) . '/wp-reset-cli.php';
36 }
37
38
39 class WP_Reset {
40 protected static $instance = null;
41 public $version = 0;
42 public $plugin_url = '';
43 public $plugin_dir = '';
44 public $snapshots_folder = 'wp-reset-snapshots-export';
45 protected $options = array();
46 private $delete_count = 0;
47 private $core_tables = array('commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'term_relationships', 'term_taxonomy', 'termmeta', 'terms', 'usermeta', 'users');
48
49
50 /**
51 * Creates a new WP_Reset object and implements singleton
52 *
53 * @return WP_Reset
54 */
55 static function getInstance() {
56 if (!is_a(self::$instance, 'WP_Reset')) {
57 self::$instance = new WP_Reset();
58 }
59
60 return self::$instance;
61 } // getInstance
62
63
64 /**
65 * Initialize properties, hook to filters and actions
66 *
67 * @return null
68 */
69 private function __construct() {
70 $this->version = $this->get_plugin_version();
71 $this->plugin_dir = plugin_dir_path(__FILE__);
72 $this->plugin_url = plugin_dir_url(__FILE__);
73 $this->load_options();
74
75 add_action('admin_menu', array($this, 'admin_menu'));
76 add_action('admin_init', array($this, 'do_all_actions'));
77 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
78 add_action('wp_ajax_wp_reset_dismiss_notice', array($this, 'ajax_dismiss_notice'));
79 add_action('wp_ajax_wp_reset_run_tool', array($this, 'ajax_run_tool'));
80
81 add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'plugin_action_links'));
82 add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
83 add_filter('admin_footer_text', array($this, 'admin_footer_text'));
84 add_filter('install_plugins_table_api_args_featured', array($this, 'featured_plugins_tab'));
85
86 $this->core_tables = array_map(function($tbl) { global $wpdb; return $wpdb->prefix . $tbl; }, $this->core_tables);
87 } // __construct
88
89
90 /**
91 * Get plugin version from file header
92 *
93 * @return string
94 */
95 function get_plugin_version() {
96 $plugin_data = get_file_data(__FILE__, array('version' => 'Version'), 'plugin');
97
98 return $plugin_data['version'];
99 } // get_plugin_version
100
101
102 /**
103 * Load and prepare the options array
104 * If needed create a new DB entry
105 *
106 * @return array
107 */
108 private function load_options() {
109 $options = get_option('wp-reset', array());
110 $change = false;
111
112 if (!isset($options['meta'])) {
113 $options['meta'] = array('first_version' => $this->version, 'first_install' => current_time('timestamp', true), 'reset_count' => 0);
114 $change = true;
115 }
116 if (!isset($options['dismissed_notices'])) {
117 $options['dismissed_notices'] = array();
118 $change = true;
119 }
120 if (!isset($options['last_run'])) {
121 $options['last_run'] = array();
122 $change = true;
123 }
124 if (!isset($options['options'])) {
125 $options['options'] = array();
126 $change = true;
127 }
128 if ($change) {
129 update_option('wp-reset', $options, true);
130 }
131
132 $this->options = $options;
133 return $options;
134 } // load_options
135
136
137 /**
138 * Get meta part of plugin options
139 *
140 * @return array
141 */
142 function get_meta() {
143 return $this->options['meta'];
144 } // get_meta
145
146
147 /**
148 * Get all dismissed notices, or check for one specific notice
149 *
150 * @param string $notice_name Optional. Check if specified notice is dismissed.
151 *
152 * @return bool|array
153 */
154 function get_dismissed_notices($notice_name = '') {
155 $notices = $this->options['dismissed_notices'];
156
157 if (empty($notice_name)) {
158 return $notices;
159 } else {
160 if (empty($notices[$notice_name])) {
161 return false;
162 } else {
163 return true;
164 }
165 }
166 } // get_dismissed_notices
167
168
169 /**
170 * Get options part of plugin options
171 *
172 * todo: not completed
173 *
174 * @param string $key Optional.
175 *
176 * @return array
177 */
178 function get_options($key = '') {
179 return $this->options['options'];
180 } // get_options
181
182
183 /**
184 * Update plugin options, currently entire array
185 *
186 * todo: this handles the entire options array although it should only do the options part - it's confusing
187 *
188 * @param string $key Data to save.
189 * @param string $data Option key.
190 *
191 * @return bool
192 */
193 function update_options($key, $data) {
194 $this->options[$key] = $data;
195 $tmp = update_option('wp-reset', $this->options);
196
197 return $tmp;
198 } // set_options
199
200
201 /**
202 * Add plugin menu entry under Tools menu
203 *
204 * @return null
205 */
206 function admin_menu() {
207 add_management_page(__('WP Reset', 'wp-reset'), __('WP Reset', 'wp-reset'), 'administrator', 'wp-reset', array($this, 'plugin_page'));
208 } // admin_menu
209
210
211 /**
212 * Dismiss notice via AJAX call
213 *
214 * @return null
215 */
216 function ajax_dismiss_notice() {
217 check_ajax_referer('wp-reset_dismiss_notice');
218
219 $notice_name = trim(@$_GET['notice_name']);
220 if (!$this->dismiss_notice($notice_name)) {
221 wp_send_json_error('Notice is already dismissed.');
222 } else {
223 wp_send_json_success();
224 }
225 } // ajax_dismiss_notice
226
227
228 /**
229 * Dismiss notice by adding it to dismissed_notices options array
230 *
231 * @param string $notice_name Notice to dismiss.
232 *
233 * @return bool
234 */
235 function dismiss_notice($notice_name) {
236 if ($this->get_dismissed_notices($notice_name)) {
237 return false;
238 } else {
239 $notices = $this->get_dismissed_notices();
240 $notices[$notice_name] = true;
241 $this->update_options('dismissed_notices', $notices);
242 return true;
243 }
244 } // dismiss_notice
245
246
247 /**
248 * Returns all WP pointers
249 *
250 * @return array
251 */
252 function get_pointers() {
253 $pointers = array();
254
255 $pointers['welcome'] = array('target' => '#menu-tools', 'edge' => 'left', 'align' => 'right', 'content' => 'Thank you for installing the <b style="font-weight: 800;">WP Reset</b> plugin!<br>Open <a href="' . admin_url('tools.php?page=wp-reset'). '">Tools - WP Reset</a> to access resetting tools and start developing &amp; debugging faster.');
256
257 return $pointers;
258 } // get_pointers
259
260
261 /**
262 * Enqueue CSS and JS files
263 *
264 * @return null
265 */
266 function admin_enqueue_scripts($hook) {
267 // welcome pointer is shown on all pages except WPR, untill dismissed
268 $pointers = $this->get_pointers();
269 $dismissed_notices = $this->get_dismissed_notices();
270
271 foreach ($dismissed_notices as $notice_name => $tmp) {
272 if ($tmp) {
273 unset($pointers[$notice_name]);
274 }
275 } // foreach
276
277 if (!empty($pointers) && 'tools_page_wp-reset' != $hook) {
278 $pointers['_nonce_dismiss_pointer'] = wp_create_nonce('wp-reset_dismiss_notice');
279
280 wp_enqueue_style('wp-pointer');
281
282 wp_enqueue_script('wp-reset-pointers', $this->plugin_url . 'js/wp-reset-pointers.js', array('jquery'), $this->version, true);
283 wp_enqueue_script('wp-pointer');
284 wp_localize_script('wp-pointer', 'wp_reset_pointers', $pointers);
285 }
286
287 // exit early if not on WP Reset page
288 if ('tools_page_wp-reset' != $hook) {
289 return;
290 }
291
292 $options = $this->get_options();
293
294 $js_localize = array('undocumented_error' => __('An undocumented error has occured. Please refresh the page and try again.', 'wp-reset'),
295 'documented_error' => __('An error has occured.', 'wp-reset'),
296 'plugin_name' => __('WP Reset', 'wp-reset'),
297 'settings_url' => admin_url('tools.php?page=wp-reset'),
298 'icon_url' => $this->plugin_url . 'img/wp-reset-icon.png',
299 'invalid_confirmation' => __('Please type "reset" in the confirmation field.', 'wp-reset'),
300 'invalid_confirmation_title' => __('Invalid confirmation', 'wp-reset'),
301 'cancel_button' => __('Cancel', 'wp-reset'),
302 'ok_button' => __('OK', 'wp-reset'),
303 'confirm_button' => __('Reset WordPress', 'wp-reset'),
304 'confirm_title' => __('Are you sure you want to proceed?', 'wp-reset'),
305 'confirm1' => __('Clicking "Reset WordPress" will reset your site to default values. All content will be lost. There is NO UNDO.', 'wp-reset'),
306 'confirm2' => __('Click "Cancel" to abort.', 'wp-reset'),
307 'doing_reset' => __('Resetting in progress. Please wait.', 'wp-reset'),
308 'nonce_dismiss_notice' => wp_create_nonce('wp-reset_dismiss_notice'),
309 'nonce_run_tool' => wp_create_nonce('wp-reset_run_tool'),
310 'nonce_do_reset' => wp_create_nonce('wp-reset_do_reset'));
311
312 wp_enqueue_style('wp-reset', $this->plugin_url . 'css/wp-reset.css', array(), $this->version);
313 wp_enqueue_style('wp-reset-sweetalert2', $this->plugin_url . 'css/sweetalert2.min.css', array(), $this->version);
314
315 wp_enqueue_script('jquery-ui-tabs');
316 wp_enqueue_script('wp-reset-sweetalert2', $this->plugin_url . 'js/sweetalert2.min.js', array('jquery'), $this->version, true);
317 wp_enqueue_script('wp-reset', $this->plugin_url . 'js/wp-reset.js', array('jquery'), $this->version, true);
318 wp_localize_script('wp-reset', 'wp_reset', $js_localize);
319
320 // fix for aggressive plugins that include their CSS on all pages
321 wp_dequeue_style('uiStyleSheet');
322 wp_dequeue_style('wpcufpnAdmin' );
323 wp_dequeue_style('unifStyleSheet' );
324 wp_dequeue_style('wpcufpn_codemirror');
325 wp_dequeue_style('wpcufpn_codemirrorTheme');
326 wp_dequeue_style('collapse-admin-css');
327 wp_dequeue_style('jquery-ui-css');
328 wp_dequeue_style('tribe-common-admin');
329 wp_dequeue_style('file-manager__jquery-ui-css');
330 wp_dequeue_style('file-manager__jquery-ui-css-theme');
331 wp_dequeue_style('wpmegmaps-jqueryui');
332 wp_dequeue_style('wp-botwatch-css');
333 } // admin_enqueue_scripts
334
335
336 /**
337 * Check if WP-CLI is available and running
338 *
339 * @return bool
340 */
341 function is_cli_running() {
342 if (defined('WP_CLI') && WP_CLI) {
343 return true;
344 } else {
345 return false;
346 }
347 } // is_cli_running
348
349
350 /**
351 * Deletes all transients.
352 *
353 * @return int Number of deleted transient DB entries
354 */
355 function do_delete_transients() {
356 global $wpdb;
357
358 $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'");
359
360 return $count;
361 } // do_delete_transients
362
363
364 /**
365 * Deletes all files in uploads folder.
366 *
367 * @return int Number of deleted files and folders.
368 */
369 function do_delete_uploads() {
370 $upload_dir = wp_get_upload_dir();
371
372 $this->delete_folder($upload_dir['basedir'], $upload_dir['basedir']);
373
374 return $this->delete_count;
375 } // do_delete_uploads
376
377
378 /**
379 * Recursively deletes a folder
380 *
381 * @param string $folder Recursive param.
382 * @param string $base_folder Base folder.
383 *
384 * @return bool
385 */
386 private function delete_folder($folder, $base_folder) {
387 $files = array_diff(scandir($folder), array('.', '..'));
388
389 foreach ($files as $file) {
390 if (is_dir($folder . DIRECTORY_SEPARATOR . $file)) {
391 $this->delete_folder($folder . DIRECTORY_SEPARATOR . $file, $base_folder);
392 } else {
393 $tmp = @unlink($folder . DIRECTORY_SEPARATOR . $file);
394 $this->delete_count = $this->delete_count + (int) $tmp;
395 }
396 } // foreach
397
398 if ($folder != $base_folder) {
399 $tmp = @rmdir($folder);
400 $this->delete_count = $this->delete_count + (int) $tmp;
401 return $tmp;
402 } else {
403 return true;
404 }
405 } // delete_folder
406
407
408 /**
409 * Deactivate and delete all plugins
410 *
411 * @param bool $keep_wp_reset Keep WP Reset active and installed
412 * @param bool $silent_deactivate Skip individual plugin deactivation functions when deactivating
413 *
414 * @return int Number of deleted plugins.
415 */
416 function do_delete_plugins($keep_wp_reset = true, $silent_deactivate = false) {
417 if (!function_exists('get_plugins')) {
418 require_once ABSPATH . 'wp-admin/includes/plugin.php';
419 }
420
421 $wp_reset_basename = plugin_basename(__FILE__);
422
423 $all_plugins = get_plugins();
424 $active_plugins = (array) get_option('active_plugins', array());
425 if (true == $keep_wp_reset) {
426 if (($key = array_search($wp_reset_basename, $active_plugins)) !== false) {
427 unset($active_plugins[$key]);
428 }
429 unset($all_plugins[$wp_reset_basename]);
430 }
431
432 if (!empty($active_plugins)) {
433 deactivate_plugins($active_plugins, $silent_deactivate, false);
434 }
435
436 if (!empty($all_plugins)) {
437 delete_plugins(array_keys($all_plugins));
438 }
439
440 return sizeof($all_plugins);
441 } // do_delete_plugins
442
443
444 /**
445 * Delete all themes
446 *
447 * @param bool $keep_default_theme Keep default theme
448 *
449 * @return int Number of deleted themes.
450 */
451 function do_delete_themes($keep_default_theme = true) {
452 $default_theme = 'twentyseventeen';
453 $all_themes = wp_get_themes(array('errors' => null));
454
455 if (true == $keep_default_theme) {
456 unset($all_themes[$default_theme]);
457 }
458
459 foreach ($all_themes as $theme_slug => $theme_details) {
460 $res = delete_theme($theme_slug);
461 }
462
463 if (false == $keep_default_theme) {
464 update_option('template', '');
465 update_option('stylesheet', '');
466 }
467
468 return sizeof($all_themes);
469 } // do_delete_themes
470
471
472 /**
473 * Truncate custom tables
474 *
475 * @return int Number of truncated tables.
476 */
477 function do_truncate_custom_tables() {
478 global $wpdb;
479 $custom_tables = $this->get_custom_tables();
480
481 foreach ($custom_tables as $tbl) {
482 $wpdb->query('TRUNCATE TABLE ' . $tbl['name']);
483 } // foreach
484
485 return sizeof($custom_tables);
486 } // do_truncate_custom_tables
487
488
489 /**
490 * Drop custom tables
491 *
492 * @return int Number of dropped tables.
493 */
494 function do_drop_custom_tables() {
495 global $wpdb;
496 $custom_tables = $this->get_custom_tables();
497
498 foreach ($custom_tables as $tbl) {
499 $wpdb->query('DROP TABLE IF EXISTS ' . $tbl['name']);
500 } // foreach
501
502 return sizeof($custom_tables);
503 } // do_drop_custom_tables
504
505
506 /**
507 * Run one tool via AJAX call
508 *
509 * @return null
510 */
511 function ajax_run_tool() {
512 check_ajax_referer('wp-reset_run_tool');
513
514 $tool = trim(@$_GET['tool']);
515 $extra_data = trim(@$_GET['extra_data']);
516
517 if ($tool == 'delete_transients') {
518 $cnt = $this->do_delete_transients();
519 wp_send_json_success($cnt);
520 } elseif ($tool == 'delete_themes') {
521 $cnt = $this->do_delete_themes(false);
522 wp_send_json_success($cnt);
523 } elseif ($tool == 'delete_plugins') {
524 $cnt = $this->do_delete_plugins(true);
525 wp_send_json_success($cnt);
526 } elseif ($tool == 'delete_uploads') {
527 $cnt = $this->do_delete_uploads();
528 wp_send_json_success($cnt);
529 } elseif ($tool == 'drop_custom_tables') {
530 $cnt = $this->do_drop_custom_tables();
531 wp_send_json_success($cnt);
532 } elseif ($tool == 'truncate_custom_tables') {
533 $cnt = $this->do_truncate_custom_tables();
534 wp_send_json_success($cnt);
535 } elseif ($tool == 'delete_snapshot') {
536 $res = $this->do_delete_snapshot($extra_data);
537 if (is_wp_error($res)) {
538 wp_send_json_error($res->get_error_message());
539 } else {
540 wp_send_json_success();
541 }
542 } elseif ($tool == 'download_snapshot') {
543 $res = $this->do_export_snapshot($extra_data);
544 if (is_wp_error($res)) {
545 wp_send_json_error($res->get_error_message());
546 } else {
547 $url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
548 wp_send_json_success($url);
549 }
550 } elseif ($tool == 'restore_snapshot') {
551 $res = $this->do_restore_snapshot($extra_data);
552 if (is_wp_error($res)) {
553 wp_send_json_error($res->get_error_message());
554 } else {
555 wp_send_json_success();
556 }
557 } elseif ($tool == 'compare_snapshots') {
558 $res = $this->do_compare_snapshots($extra_data);
559 if (is_wp_error($res)) {
560 wp_send_json_error($res->get_error_message());
561 } else {
562 wp_send_json_success($res);
563 }
564 } elseif ($tool == 'create_snapshot') {
565 $res = $this->do_create_snapshot($extra_data);
566 if (is_wp_error($res)) {
567 wp_send_json_error($res->get_error_message());
568 } else {
569 wp_send_json_success();
570 }
571 } else {
572 wp_send_json_error(__('Unknown tool.', 'wp-reset'));
573 }
574 } // ajax_run_tool
575
576
577 /**
578 * Reinstall / reset the WP site
579 * There are no failsafes in the function - it reinstalls when called
580 * Redirects when done
581 *
582 * @param array $params Optional.
583 *
584 * @return null
585 */
586 function do_reinstall($params = array()) {
587 global $current_user, $wpdb;
588
589 // only admins can reset; double-check
590 if (!$this->is_cli_running() && !current_user_can('administrator')) {
591 return false;
592 }
593
594 // make sure the function is available to us
595 if (!function_exists('wp_install')) {
596 require ABSPATH . '/wp-admin/includes/upgrade.php';
597 }
598
599 // save values that need to be restored after reset
600 // todo: use params to determine what gets restored after reset
601 $blogname = get_option('blogname');
602 $blog_public = get_option('blog_public');
603 $wplang = get_option('wplang');
604 $siteurl = get_option('siteurl');
605 $home = get_option('home');
606 $snapshots = $this->get_snapshots();
607
608 $active_plugins = get_option('active_plugins');
609 $active_theme = wp_get_theme();
610
611 // for WP-CLI
612 if (!$current_user->ID) {
613 $tmp = get_users(array('role' => 'administrator', 'order' => 'ASC', 'order_by' => 'ID'));
614 if (empty($tmp[0]->user_login)) {
615 return new WP_Error('no_user', 'Reset failed. Unable to find any admin users in database.');
616 }
617 $current_user = $tmp[0];
618 }
619
620 // delete custom tables with WP's prefix
621 $prefix = str_replace('_', '\_', $wpdb->prefix);
622 $tables = $wpdb->get_col("SHOW TABLES LIKE '{$prefix}%'");
623 foreach ($tables as $table) {
624 $wpdb->query("DROP TABLE $table");
625 }
626
627 // supress errors for WP_CLI
628 // todo: do something better
629 $result = @wp_install($blogname, $current_user->user_login, $current_user->user_email, $blog_public, '', md5(rand()), $wplang);
630 $user_id = $result['user_id'];
631
632 // restore user pass
633 $query = $wpdb->prepare("UPDATE {$wpdb->users} SET user_pass = %s, user_activation_key = '' WHERE ID = %d LIMIT 1", array($current_user->user_pass, $user_id));
634 $wpdb->query($query);
635
636 // restore rest of the settings including WP Reset's
637 update_option('siteurl', $siteurl);
638 update_option('home', $home);
639 update_option('wp-reset', $this->options);
640 update_option('wp-reset-snapshots', $snapshots);
641
642 // remove password nag
643 if (get_user_meta($user_id, 'default_password_nag')) {
644 update_user_meta($user_id, 'default_password_nag', false);
645 }
646 if (get_user_meta($user_id, $wpdb->prefix . 'default_password_nag')) {
647 update_user_meta($user_id, $wpdb->prefix . 'default_password_nag', false );
648 }
649
650 $meta = $this->get_meta();
651 $meta['reset_count']++;
652 $this->update_options('meta', $meta);
653
654 // reactivate theme
655 if (!empty($params['reactivate_theme'])) {
656 switch_theme($active_theme->get_stylesheet());
657 }
658
659 // reactivate WP Reset
660 if (!empty($params['reactivate_wpreset'])) {
661 activate_plugin(plugin_basename( __FILE__ ));
662 }
663
664 // reactivate all plugins
665 if (!empty($params['reactivate_plugins'])) {
666 foreach ($active_plugins as $plugin_file) {
667 activate_plugin($plugin_file);
668 }
669 }
670
671 if (!$this->is_cli_running()) {
672 // log out and log in the old/new user
673 // since the password doesn't change this is potentially unnecessary
674 wp_clear_auth_cookie();
675 wp_set_auth_cookie($user_id);
676
677 wp_redirect(admin_url() . '?wp-reset=success');
678 exit;
679 }
680 } // do_reinstall
681
682
683 /**
684 * Checks wp_reset post value and performs all actions
685 * todo: handle messages for various actions
686 *
687 * @return null|bool
688 */
689 function do_all_actions() {
690 // only admins can perform actions
691 if (!current_user_can('administrator')) {
692 return;
693 }
694
695 if (!empty($_GET['wp-reset']) && stristr($_SERVER['HTTP_REFERER'], 'wp-reset')) {
696 add_action('admin_notices', array($this, 'notice_successfull_reset'));
697 }
698
699 // check nonce
700 if (true === isset($_POST['wp_reset_confirm']) && false === wp_verify_nonce(@$_POST['_wpnonce'], 'wp-reset')) {
701 add_settings_error('wp-reset', 'bad-nonce', __('Something went wrong. Please refresh the page and try again.', 'wp-reset'), 'error');
702 return false;
703 }
704
705 // check confirmation code
706 if (true === isset($_POST['wp_reset_confirm']) && 'reset' !== $_POST['wp_reset_confirm']) {
707 add_settings_error('wp-reset', 'bad-confirm', __('<b>Invalid confirmation code.</b> Please type "reset" in the confirmation field.', 'wp-reset'), 'error');
708 return false;
709 }
710
711 // only one action at the moment
712 if (true === isset($_POST['wp_reset_confirm']) && 'reset' === $_POST['wp_reset_confirm']) {
713 $defaults = array('reactivate_theme' => '0',
714 'reactivate_plugins' => '0',
715 'reactivate_wpreset' => '0');
716 $params = shortcode_atts($defaults, (array) @$_POST['wpr-post-reset']);
717
718 $this->do_reinstall($params);
719 }
720 } // do_all_actions
721
722
723 /**
724 * Add "Open WP Reset Tools" action link to plugins table, left part
725 *
726 * @param array $links Initial list of links.
727 *
728 * @return array
729 */
730 function plugin_action_links($links) {
731 $settings_link = '<a href="' . admin_url('tools.php?page=wp-reset') . '" title="' . __('Open WP Reset Tools', 'wp-reset') . '">' . __('Open WP Reset Tools', 'wp-reset') . '</a>';
732
733 array_unshift($links, $settings_link);
734
735 return $links;
736 } // plugin_action_links
737
738
739 /**
740 * Add links to plugin's description in plugins table
741 *
742 * @param array $links Initial list of links.
743 * @param string $file Basename of current plugin.
744 *
745 * @return array
746 */
747 function plugin_meta_links($links, $file) {
748 if ($file !== plugin_basename(__FILE__)) {
749 return $links;
750 }
751
752 $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset" title="' . __('Get help', 'wp-reset') . '">' . __('Support', 'wp-reset') . '</a>';
753 $home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-reset') . '">' . __('Plugin Homepage', 'wp-reset') . '</a>';
754 $rate_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" title="' . __('Rate the plugin', 'wp-reset') . '">' . __('Rate the plugin �
755 �
756 �
757 �
758 �
759 ', 'wp-reset') . '</a>';
760
761 $links[] = $support_link;
762 $links[] = $home_link;
763 $links[] = $rate_link;
764
765 return $links;
766 } // plugin_meta_links
767
768
769 /**
770 * Test if we're on WPR's admin page
771 *
772 * @return bool
773 */
774 function is_plugin_page() {
775 $current_screen = get_current_screen();
776
777 if ($current_screen->id == 'tools_page_wp-reset') {
778 return true;
779 } else {
780 return false;
781 }
782 } // is_plugin_page
783
784
785 /**
786 * Add powered by text in admin footer
787 *
788 * @param string $text Default footer text.
789 *
790 * @return string
791 */
792 function admin_footer_text($text) {
793 if (!$this->is_plugin_page()) {
794 return $text;
795 }
796
797 $text = '<i><a href="' . $this->generate_web_link('admin_footer') . '" title="' . __('Visit WP Reset page for more info', 'wp-reset') . '" target="_blank">WP Reset</a> v' . $this->version . ' by <a href="https://www.webfactoryltd.com/" title="' . __('Visit our site to get more great plugins', 'wp-reset'). '" target="_blank">WebFactory Ltd</a>. Proudly sponsored by <a target="_blank" href="https://ipgeolocation.io/">IP Geolocation</a> - premium GeoIP service for developers.</i>';
798
799 return $text;
800 } // admin_footer_text
801
802
803 /**
804 * Loads plugin's translated strings
805 *
806 * @return null
807 */
808 function load_textdomain() {
809 load_plugin_textdomain('wp-reset');
810 } // load_textdomain
811
812
813 /**
814 * Inform the user that WordPress has been successfully reset
815 *
816 * @return null
817 */
818 function notice_successfull_reset() {
819 global $current_user;
820
821 echo '<div id="message" class="updated fade"><p>' . sprintf(__( '<b>Site has been reset</b> to default settings. User "%s" was restored with the password unchanged. Open <a href="%s">WP Reset</a> to do another reset.', 'wp-reset'), $current_user->user_login, admin_url('tools.php?page=wp-reset')) . '</p></div>';
822 } // notice_successfull_reset
823
824
825 /**
826 * Outputs complete plugin's admin page
827 *
828 * @return null
829 */
830 function plugin_page() {
831 $notice_shown = false;
832 $meta = $this->get_meta();
833 $notices = $this->get_dismissed_notices();
834 $snapshots = $this->get_snapshots();
835
836 // double check for admin priv
837 if (!current_user_can('administrator')) {
838 wp_die(__('Sorry, you are not allowed to access this page.', 'wp-reset'));
839 }
840
841 settings_errors();
842 echo '<div class="wrap">';
843 echo '<h1><img id="logo-icon" src="' . $this->plugin_url . 'img/wp-reset-logo.png" title="' . __('WP Reset', 'wp-reset') . '" alt="' . __('WP Reset', 'wp-reset') . '"> <span>proudly sponsored by <a href="https://ipgeolocation.io/" target="_blank">IP Geolocation</a></span></h1>';
844 echo '<form id="wp_reset_form" action="' . admin_url('tools.php?page=wp-reset') . '" method="post" autocomplete="off">';
845
846 if (false === $notice_shown && is_multisite()) {
847 echo '<div class="card notice-wrapper notice-error">';
848 echo '<h2>' . __('WP Reset is not compatible with multisite!', 'wp-reset') . '</h2>';
849 echo '<p>' . __('Please be careful when using WP Reset with multisite enabled. It\'s not recommended to reset the main site. Sub-sites should be OK. We\'re working on making it fully compatible with WP-MU. <b>Till then please be careful.</b> Thank you for understanding.', 'wp-reset') . '</p>';
850 echo '</div>';
851 $notice_shown = true;
852 }
853
854 if ((!empty($meta['reset_count']) || !empty($snapshots)) && false === $notice_shown && false == $this->get_dismissed_notices('rate')) {
855 echo '<div class="card notice-wrapper">';
856 echo '<h2>' . __('Please help us keep the plugin free &amp; up-to-date', 'wp-reset') . '</h2>';
857 echo '<p>' . __('If you use &amp; enjoy WP Reset, <b>please rate it on WordPress.org</b>. It only takes a second and helps us keep the plugin free and maintained. Thank you!', 'wp-reset') . '</p>';
858 echo '<p><a class="button-primary button" title="' . __('Rate WP Reset', 'wp-reset') . '" target="_blank" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post">' . __('Help keep the plugin free - rate it!', 'wp-reset') . '</a> <a href="#" class="wpr-dismiss-notice dismiss-notice-rate" data-notice="rate">' . __('I\'ve already rated it', 'wp-reset') . '</a></p>';
859 echo '</div>';
860 $notice_shown = true;
861 }
862
863 // Tidy Repo ad
864 // disabled for now
865 if (false && false === $notice_shown && $meta['reset_count'] >= 2 && false == $this->get_dismissed_notices('tidy')) {
866 echo '<div class="card notice-wrapper">';
867 echo '<h2>' . __('Are you a plugin author? Get your plugin reviewed on Tidy Repo', 'wp-reset') . '</h2>';
868 echo '<p>' . __('Since 2013 Tidy Repo has been reviewing the best and most reliable WordPress plugins. <b>Submitting a plugin is free</b>, so you have nothing to lose and a lot of exposure to gain when it gets reviewed.', 'wp-reset') . '</p>';
869 echo '<p><a class="button-primary button" title="' . __('Rate WP Reset', 'wp-reset') . '" target="_blank" href="https://tidyrepo.com/?utm_source=wp-reset-free&utm_medium=plugin&utm_content=notification&utm_campaign=wp-reset-free-v' . $this->version . '">' . __('Let Tidy Repo know you have a great plugin', 'wp-reset') . '</a> <a href="#" class="wpr-dismiss-notice dismiss-notice-rate" data-notice="tidy">' . __('Thanks, I\'m not interested', 'wp-reset') . '</a></p>';
870 echo '</div>';
871 $notice_shown = true;
872 }
873
874 // tabs
875 echo '<div id="wp-reset-tabs" class' . __('="', 'wp-reset') . 'ui-tabs">';
876
877 echo '<ul class="wpr-main-tab">';
878 echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
879 echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
880 echo '<li><a href="#tab-snapshots">' . __('DB Snapshots', 'wp-reset') . '</a></li>';
881 echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
882 if (empty($notices['geoip_tab'])) {
883 echo '<li><a href="#tab-geoip">' . __('IP Geolocation', 'wp-reset') . '</a></li>';
884 }
885 echo '</ul>';
886
887 echo '<div style="display: none;" id="tab-reset">';
888 $this->tab_reset();
889 echo '</div>';
890
891 echo '<div style="display: none;" id="tab-tools">';
892 $this->tab_tools();
893 echo '</div>';
894
895 echo '<div style="display: none;" id="tab-snapshots">';
896 $this->tab_snapshots();
897 echo '</div>';
898
899 echo '<div style="display: none;" id="tab-support">';
900 $this->tab_support();
901 echo '</div>';
902
903 if (empty($notices['geoip_tab'])) {
904 echo '<div style="display: none;" id="tab-geoip">';
905 $this->tab_geoip();
906 echo '</div>';
907 }
908
909 echo '</div>'; // tabs
910
911 echo '</form>';
912 echo '</div>'; // wrap
913 } // plugin_page
914
915
916 /**
917 * Echoes content for reset tab
918 *
919 * @return null
920 */
921 private function tab_reset() {
922 global $current_user, $wpdb;
923
924 echo '<div class="card" id="card-description">';
925 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
926 echo '<h2>' . __('Please read carefully before proceeding. There is NO UNDO!', 'wp-reset') . '</h2>';
927 echo '<b class="red">' . __('Resetting will delete:', 'wp-reset') . '</b>';
928 echo '<ul class="plain-list">';
929 echo '<li>' . __('all posts, pages, custom post types, comments, media entries, users', 'wp-reset') . '</li>';
930 echo '<li>' . __('all default WP database tables', 'wp-reset') . '</li>';
931 echo '<li>' . sprintf(__('all custom database tables that have the same prefix "%s" as default tables in this installation', 'wp-reset'), $wpdb->prefix) . '</li>';
932 echo '</ul>';
933
934 echo '<b class="green">' . __('Resetting will not delete:', 'wp-reset') . '</b>';
935 echo '<ul class="plain-list">';
936 echo '<li>' . __('media files - they\'ll remain in the <i>wp-uploads</i> folder but will no longer be listed under Media', 'wp-reset') . '</li>';
937 echo '<li>' . __('no files are touched; plugins, themes, uploads - everything stays', 'wp-reset') . '</li>';
938 echo '<li>' . __('site title, WordPress address, site address, site language and search engine visibility settings', 'wp-reset') . '</li>';
939 echo '<li>' . sprintf(__('logged in user "%s" will be restored with the current password', 'wp-reset'), $current_user->user_login) . '</li>';
940 echo '</ul>';
941
942 echo '<b>' . __('What happens when I click the Reset button?', 'wp-reset') . '</b>';
943 echo '<ul class="plain-list">';
944 echo '<li>' . __('you will have to confirm the action one more time because there is NO UNDO', 'wp-reset') . '</li>';
945 echo '<li>' . __('everything will be reset; see bullets above for details', 'wp-reset') . '</li>';
946 echo '<li>' . __('site title, WordPress address, site address, site language, search engine visibility and current user will be restored', 'wp-reset') . '</li>';
947 echo '<li>' . __('you will be logged out, automatically logged in and taken to the admin dashboard', 'wp-reset') . '</li>';
948 echo '<li>' . __('WP Reset plugin will be reactivated if that option is chosen in the <a href="#card-post-reset">post-reset options</a>', 'wp-reset') . '</li>';
949 echo '</ul>';
950
951 echo '<b>' . __('WP-CLI Support', 'wp-reset') . '</b>';
952 echo '<p>' . sprintf(__('All features available via GUI are available in WP-CLI as well. To get the list of commands run %s. Instead of the active user, the first user with admin privileges found in the database will be restored. ', 'wp-reset'), '<code>wp help reset</code>');
953 echo sprintf(__('All actions have to be confirmed. If you want to skip confirmation use the standard %s option. Please be carefull - there is NO UNDO.', 'wp-reset'), '<code>--yes</code>') . '</p>';
954 echo '</div>';
955
956 $theme = wp_get_theme();
957
958 echo '<div class="card" id="card-post-reset">';
959 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
960 echo '<h2>' . __('Post-reset actions', 'wp-reset') . '</h2>';
961 echo '<p><label for="reactivate-theme"><input name="wpr-post-reset[reactivate_theme]" type="checkbox" id="reactivate-theme" value="1"> ' . __('Reactivate current theme', 'wp-reset') . ' - ' . $theme->get('Name') . '</label></p>';
962 echo '<p><label for="reactivate-wpreset"><input name="wpr-post-reset[reactivate_wpreset]" type="checkbox" id="reactivate-wpreset" value="1" checked> ' . __('Reactivate WP Reset plugin', 'wp-reset') . '</label></p>';
963 echo '<p><label for="reactivate-plugins"><input name="wpr-post-reset[reactivate_plugins]" type="checkbox" id="reactivate-plugins" value="1"> ' . __('Reactivate all currently active plugins', 'wp-reset') . '</label></p>';
964 echo '</div>';
965
966 echo '<div class="card">';
967 echo '<h2>' . __('Reset', 'wp-reset') . '</h2>';
968 echo '<p>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress" button. <b>There is NO UNDO. No backups are made by WP Reset.</b>', 'wp-reset') . '</p>';
969
970 wp_nonce_field('wp-reset');
971 echo '<p><input id="wp_reset_confirm" type="text" name="wp_reset_confirm" placeholder="' . esc_attr__('Type in "reset"', 'wp-reset'). '" value="" autocomplete="off"> &nbsp;';
972 echo '<input id="wp_reset_submit" type="button" class="button-primary" value="' . __('Reset WordPress', 'wp-reset') . '"></p>';
973 echo '</div>';
974 } // tab_reset
975
976
977 /**
978 * Echoes content for tools tab
979 *
980 * @return null
981 */
982 private function tab_tools() {
983 global $wpdb;
984
985 echo '<div class="card">';
986 echo '<h2>' . __('Delete Transients', 'wp-reset') . '</h2>';
987 echo '<p>' . __('All transient related database entries will be deleted. Including expired and non-expired transients, and orphaned transient timeout entries.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
988 echo '<p><a data-btn-confirm="Delete all transients" data-text-wait="Deleting transients. Please wait." data-text-confirm="All database entries related to transients will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n transient database entries have been deleted." class="button button-delete" href="#" id="delete-transients">Delete all transients</a></p>';
989 echo '</div>';
990
991 $upload_dir = wp_upload_dir(date('Y/m'), true);
992 $upload_dir['basedir'] = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $upload_dir['basedir']);
993
994 echo '<div class="card">';
995 echo '<h2>' . __('Clean Uploads Folder', 'wp-reset') . '</h2>';
996 echo '<p>' . __('All files in <code>' . $upload_dir['basedir'] . '</code> folder will be deleted. Including folders and subfolders, and files in subfolders. Files associated with <a href="' . admin_url('upload.php') . '">media</a> entries will be deleted too.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
997 if (false != $upload_dir['error']) {
998 echo '<p><span style="color:#dd3036;"><b>Tool is not available.</b></span> Folder is not writeable by WordPress. Please check file and folder access rights.</p>';
999 } else {
1000 echo '<p><a data-btn-confirm="Delete everything in uploads folder" data-text-wait="Deleting uploads. Please wait." data-text-confirm="All files and folders in uploads will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n files &amp; folders have been deleted." class="button button-delete" href="#" id="delete-uploads">Delete all files &amp; folders in uploads folder</a></p>';
1001 }
1002 echo '</div>';
1003
1004 $theme = wp_get_theme();
1005
1006 echo '<div class="card">';
1007 echo '<h2>' . __('Delete Themes', 'wp-reset') . '</h2>';
1008 echo '<p>' . __('All themes will be deleted. Including the currently active theme - ' . $theme->get('Name') . '.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
1009 echo '<p><a data-btn-confirm="Delete all themes" data-text-wait="Deleting all themes. Please wait." data-text-confirm="All themes will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n themes have been deleted." class="button button-delete" href="#" id="delete-themes">Delete all themes</a></p>';
1010 echo '</div>';
1011
1012 echo '<div class="card">';
1013 echo '<h2>' . __('Delete Plugins', 'wp-reset') . '</h2>';
1014 echo '<p>' . __('All plugins will be deleted except for WP Reset which will remain active.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
1015 echo '<p><a data-btn-confirm="Delete plugins" data-text-wait="Deleting plugins. Please wait." data-text-confirm="All plugins except WP Reset will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n plugins have been deleted." class="button button-delete" href="#" id="delete-plugins">Delete plugins</a></p>';
1016 echo '</div>';
1017
1018 $custom_tables = $this->get_custom_tables();
1019
1020 echo '<div class="card">';
1021 echo '<h2>' . __('Empty or Delete Custom Tables', 'wp-reset') . '</h2>';
1022 echo '<p>' . __('This action affects only custom tables with <code>' . $wpdb->prefix . '</code> prefix. Core WP tables and other tables in the database that do not have that prefix will not be deleted/emptied. Deleting (dropping) tables completely removes them from the database. Emptying (truncating) removes all content from them, but keeps the structure intact.<br><b>There is NO UNDO. WP Reset does not make any backups.</b></p>', 'wp-reset');
1023 if ($custom_tables) {
1024 echo '<p>' . __('The following ' . sizeof($custom_tables) . ' custom tables are affected by this tool: ');
1025 foreach ($custom_tables as $tbl) {
1026 echo '<code>' . $tbl['name'] . '</code>';
1027 if (next($custom_tables)) {
1028 echo ', ';
1029 }
1030 } // foreach
1031 echo '.</p>';
1032 $custom_tables_btns = '';
1033 } else {
1034 echo '<p>' . __('There are no custom tables. There\'s nothing for this tool to empty or delete.', 'wp-reset') . '</p>';
1035 $custom_tables_btns = ' disabled';
1036 }
1037 echo '<p><a data-btn-confirm="Empty custom tables" data-text-wait="Emptying custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be emptied. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n custom tables have been emptied." class="button button-delete' . $custom_tables_btns . '" href="#" id="truncate-custom-tables">Empty (truncate) custom tables</a>&nbsp; &nbsp;';
1038 echo '<a data-btn-confirm="Delete custom tables" data-text-wait="Deleting custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n custom tables have been deleted." class="button button-delete' . $custom_tables_btns . '" href="#" id="drop-custom-tables">Delete (drop) custom tables</a></p>';
1039
1040 echo '</div>';
1041 } // tab_tools
1042
1043
1044 /**
1045 * Echoes content for support tab
1046 *
1047 * @return null
1048 */
1049 private function tab_support() {
1050 echo '<div class="card">';
1051 echo '<h2>' . __('Public support forum', 'wp-reset') . '</h2>';
1052 echo '<p>' . __('We are very active on the <a href="https://wordpress.org/support/plugin/wp-reset" target="_blank">official WP Reset support forum</a>. If you found a bug, have a feature idea or just want to say hi - please drop by. We love to hear back from our users.', 'wp-reset') . '</p>';
1053 echo '</div>';
1054
1055 echo '<div class="card">';
1056 echo '<h2>' . __('Private contact', 'wp-reset') . '</h2>';
1057 echo '<p>' . __('If there\'s a need to contact us privately send emails to <a href="mailto:wpreset@webfactoryltd.com">wpreset@webfactoryltd.com</a>. Please know that although we\'ll gladly have a look at issues you are having with any site, we can\'t promise we\'ll fix them. Thank you for understanding.', 'wp-reset') . '</p>';
1058 echo '</div>';
1059
1060 echo '<div class="card">';
1061 echo '<h2>' . __('Care to help out?', 'wp-reset') . '</h2>';
1062 echo '<p>' . __('No need for donations or anything like that :) If you can give us a <a href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" target="_blank">five star rating</a> you\'ll help out more than you can imagine. Thank you!', 'wp-reset') . '</p>';
1063 echo '</div>';
1064 } // tab_support
1065
1066
1067 /**
1068 * Echoes content for snapshots tab
1069 *
1070 * @return null
1071 */
1072 private function tab_snapshots() {
1073 global $wpdb;
1074 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1075
1076 echo '<div class="card" id="card-snapshots">';
1077 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1078 echo '<h2>' . __('Database Snapshots', 'wp-reset') . '</h2>';
1079 echo '<p>A snapshot is a copy of all WP database tables, standard and custom ones, saved in your database. Files are not saved or included in snapshots in any way.<br>
1080 Snapshots are primarily a development tool. Although they can be used for backups (and downloaded), we suggest finding a more suitable tool for live sites, such as <a href="https://wordpress.org/plugins/updraftplus/" target="_blank">UpdraftPlus</a>. Use snapshots to find out what changes a plugin made to your database or to quickly restore the dev environment after testing database related changes.<br>Restoring a snapshot does not affect other snapshots, or WP Reset settings.</p>';
1081 echo '<p>Snapshots are still in development. If you see a bug or just have an idea how to make the tool better, please let us know <a href="https://twitter.com/WebFactoryLtd" target="_blank">@webfactoryltd</a> or <a href="mailto:wpreset@webfactoryltd.com?subject=WPR%20DB%20Snapshots%20Feedback">email us</a>. Thank you!</p>';
1082
1083 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1084 if (is_array($table_status)) {
1085 foreach ($table_status as $index => $table) {
1086 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1087 continue;
1088 }
1089 if (empty($table->Engine)) {
1090 continue;
1091 }
1092
1093 $tbl_rows += $table->Rows;
1094 $tbl_size += $table->Data_length + $table->Index_length;
1095 if (in_array($table->Name, $this->core_tables)) {
1096 $tbl_core++;
1097 } else {
1098 $tbl_custom++;
1099 }
1100 } // foreach
1101
1102 echo '<p><b>Currently used WordPress tables</b>, prefixed with <i>' . $wpdb->prefix . '</i>, consist of ' . $tbl_core . ' standard and ';
1103 if ($tbl_custom) {
1104 echo $tbl_custom . ' custom table' . ($tbl_custom == 1? '': 's');
1105 } else {
1106 echo 'no custom tables';
1107 }
1108 echo ' totaling ' . $this->format_size($tbl_size) .' in ' . number_format($tbl_rows) . ' rows.</p>';
1109 }
1110
1111 echo '';
1112 echo '</div>';
1113
1114 echo '<div class="card no-padding-bottom">';
1115 echo '<a id="create-new-snapshot-primary" data-msg-success="Snapshot created!" data-msg-wait="Creating snapshot. Please wait." data-btn-confirm="Create snapshot" data-placeholder="Snapshot name or brief description, ie: before plugin install" data-text="Enter snapshot name or brief description, up to 64 characters." data-title="Create a new snapshot" title="Create a new database snapshot" href="#" class="button button-primary create-new-snapshot create-new-snapshot-corner">' . __('Create new', 'wp-reset') . '</a>';
1116 echo '<h2>' . __('Saved Snapshots', 'wp-reset') . '</h2>';
1117
1118 if ($snapshots = $this->get_snapshots()) {
1119 echo '<table id="wpr-snapshots">';
1120 echo '<tr><th>Name</th><th>Info &amp; Size</th><th class="ss-actions">Actions</th></tr>';
1121 foreach ($snapshots as $ss) {
1122 echo '<tr id="wpr-ss-' . $ss['uid'] . '">';
1123 if (!empty($ss['name'])) {
1124 echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . $ss['name'] . '</td>';
1125 $name = $ss['name'];
1126 } else {
1127 echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . '' . date(get_option('date_format'), strtotime($ss['timestamp'])) . '<br>@ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '</td>';
1128 $name = 'created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
1129 }
1130 echo '<td>' . $ss['tbl_core'] . ' standard &amp; ';
1131 if ($ss['tbl_custom']) {
1132 echo $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1? '': 's');
1133 } else {
1134 echo 'no custom tables';
1135 }
1136 echo ' totaling ' . $this->format_size($ss['tbl_size']) . ' in ' . number_format($ss['tbl_rows']) . ' rows</td>';
1137 echo '<td>';
1138 echo '<a data-title="Current DB tables compared to snapshot %s" data-wait-msg="Comparing. Please wait." data-name="' . $name . '" title="Compare snapshot to current database tables" href="#" class="ss-action compare-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-visibility"></span></a>';
1139 echo '<a data-btn-confirm="Restore snapshot" data-text-wait="Restoring snapshot. Please wait." data-text-confirm="Are you sure you want to restore the selected snapshot? There is NO UNDO.<br>Restoring the snapshot will delete all current standard and custom tables and replace them with tables from the snapshot." data-text-done="Snapshot has been restored. Click OK to reload the page with new data." title="Restore snapshot by overwriting current database tables" href="#" class="ss-action restore-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-backup"></span></a>';
1140 echo '<a data-success-msg="Snapshot export created!<br><a href=\'%s\'>Download it</a>" data-wait-msg="Exporting snapshot. Please wait." title="Download snapshot as gzipped SQL dump" href="#" class="ss-action download-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-download"></span></a>';
1141 echo '<a data-btn-confirm="Delete snapshot" data-text-wait="Deleting snapshot. Please wait." data-text-confirm="Are you sure you want to delete the selected snapshot and all its data? There is NO UNDO.<br>Deleting the snapshot will not affect the active database tables in any way." data-text-done="Snapshot has been deleted." title="Permanently delete snapshot" href="#" class="ss-action delete-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-trash"></span></a></td>';
1142 echo '</tr>';
1143 } // foreach
1144 echo '</table>';
1145 echo '<p id="ss-no-snapshots" class="hidden">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
1146 } else {
1147 echo '<p id="ss-no-snapshots">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
1148 }
1149
1150 echo '</div>';
1151 } // tab_snapshots
1152
1153
1154 /**
1155 * Echoes content for sponsor tab
1156 *
1157 * @return null
1158 */
1159 private function tab_geoip() {
1160 echo '<div class="card">';
1161 echo '<h2>' . __('WP Reset is proudly sponsored by IP Geolocation', 'wp-reset') . '</h2>';
1162 echo '<p>' . __('Keeping a plugin maintained, supported and free is neither easy nor cheap that\'s why we\'re thrilled that a <a href="https://ipgeolocation.io/" target="_blank">premium GeoIP service</a> decided to sponsor WP Reset. No notifications, no popups, no shady links. They keep the plugin free and clean.', 'wp-reset') . '</p>';
1163 echo '</div>';
1164
1165 echo '<div class="card">';
1166 echo '<h2>' . __('Why would I need a GeoIP service?', 'wp-reset') . '</h2>';
1167 echo '<p>' . __('IP addresses are boring and don\'t mean much to people. However, they can easily be transformed into a huge source of data by using a GeoIP service. From filtering and segmenting users to providing a better UX - geographical data can enhance any web app! See an <a href="https://wpreset.com/geoip-transform-boring-data-better-user-experience/" target="_blank">example</a> we recently wrote about.', 'wp-reset') . '</p>';
1168 echo '<p><a href="https://ipgeolocation.io/ip-location" target="_blank" class="button">See what data is available for your IP address</a></p>';
1169 echo '</div>';
1170
1171 echo '<div class="card">';
1172 echo '<h2>' . __('Get a free account', 'wp-reset') . '</h2>';
1173 echo '<p>' . __('IP Geolocation knows how difficult it is to start any new project. That\'s why they offer <a href="https://ipgeolocation.io/signup" target="_blank">50,000 API requests per month for free</a>. No credit card required, no tricks - just register for an account and you can use the service. It\'s a great way to add value to any web project.' , 'wp-reset') . '</p>';
1174 echo '<p><a href="https://ipgeolocation.io/signup" target="_blank" class="button">Get a FREE account with 50,000 API requests a month</a></p>';
1175 echo '</div>';
1176
1177 echo '<p>Permanently <a href="#" class="wpr-dismiss-notice" data-notice="geoip_tab">remove this tab</a>.</p>';
1178 } // tab_geoip
1179
1180
1181 /**
1182 * Helper function for generating UTM tagged links
1183 *
1184 * @param string $placement Optional. UTM content param.
1185 * @param string $page Optional. Page to link to.
1186 * @param array $params Optional. Extra URL params.
1187 * @param string $anchor Optional. URL anchor part.
1188 *
1189 * @return string
1190 */
1191 function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '') {
1192 $base_url = 'https://wpreset.com';
1193
1194 if ('/' != $page) {
1195 $page = '/' . trim($page, '/') . '/';
1196 }
1197 if ($page == '//') {
1198 $page = '/';
1199 }
1200
1201 $parts = array_merge(array('utm_source' => 'wp-reset-free', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-reset-free-v' . $this->version), $params);
1202
1203 if (!empty($anchor)) {
1204 $anchor = '#' . trim($anchor, '#');
1205 }
1206
1207 $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;
1208
1209 return $out;
1210 } // generate_web_link
1211
1212
1213 /**
1214 * Returns all saved snapshots from DB
1215 *
1216 * @return array
1217 */
1218 function get_snapshots() {
1219 $snapshots = get_option('wp-reset-snapshots', array());
1220
1221 return $snapshots;
1222 } // get_snapshots
1223
1224
1225 /**
1226 * Returns all custom table names, with prefix
1227 *
1228 * @return array
1229 */
1230 function get_custom_tables() {
1231 global $wpdb;
1232 $custom_tables = array();
1233
1234 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1235 if (is_array($table_status)) {
1236 foreach ($table_status as $index => $table) {
1237 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1238 continue;
1239 }
1240 if (empty($table->Engine)) {
1241 continue;
1242 }
1243
1244 if (false === in_array($table->Name, $this->core_tables)) {
1245 $custom_tables[] = array('name' => $table->Name, 'rows' => $table->Rows, 'data_length' => $table->Data_length, 'index_length' => $table->Index_length);
1246 }
1247 } // foreach
1248 }
1249
1250 return $custom_tables;
1251 } // get_custom tables
1252
1253
1254 /**
1255 * Format file size to human readable string
1256 *
1257 * @param int $bytes Size in bytes to format.
1258 *
1259 * @return string
1260 */
1261 function format_size($bytes) {
1262 if ($bytes > 1073741824) {
1263 return number_format_i18n($bytes / 1073741824, 2) . ' GB';
1264 } elseif ($bytes > 1048576) {
1265 return number_format_i18n($bytes / 1048576, 1) . ' MB';
1266 } elseif ($bytes > 1024) {
1267 return number_format_i18n($bytes / 1024, 1) . ' KB';
1268 } else {
1269 return number_format_i18n($bytes, 0) . ' bytes';
1270 }
1271 } // format_size
1272
1273
1274 /**
1275 * Creates snapshot of current tables by copying them in the DB and saving metadata.
1276 *
1277 * @param int $name Optional. Name for the new snapshot.
1278 *
1279 * @return array|WP_Error Snapshot details in array on success, or error object on fail.
1280 */
1281 function do_create_snapshot($name = '') {
1282 global $wpdb;
1283 $snapshots = $this->get_snapshots();
1284 $snapshot = array();
1285 $uid = $this->generate_snapshot_uid();
1286 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1287
1288 if (!$uid) {
1289 return new WP_Error(1, 'Unable to generate a valid snapshot UID.');
1290 }
1291
1292 if ($name) {
1293 $snapshot['name'] = substr(trim($name), 0, 64);
1294 } else {
1295 $snapshot['name'] = '';
1296 }
1297 $snapshot['uid'] = $uid;
1298 $snapshot['timestamp'] = current_time('mysql');
1299
1300 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1301 if (is_array($table_status)) {
1302 foreach ($table_status as $index => $table) {
1303 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1304 continue;
1305 }
1306 if (empty($table->Engine)) {
1307 continue;
1308 }
1309
1310 $tbl_rows += $table->Rows;
1311 $tbl_size += $table->Data_length + $table->Index_length;
1312 if (in_array($table->Name, $this->core_tables)) {
1313 $tbl_core++;
1314 } else {
1315 $tbl_custom++;
1316 }
1317
1318 $wpdb->query('OPTIMIZE TABLE ' . $table->Name);
1319 $wpdb->query('CREATE TABLE ' . $uid . '_' . $table->Name .' LIKE ' . $table->Name);
1320 $wpdb->query('INSERT ' . $uid . '_' . $table->Name . ' SELECT * FROM ' . $table->Name);
1321 } // foreach
1322 } else {
1323 return new WP_Error(1, 'Can\'t get table status data.');
1324 }
1325
1326 $snapshot['tbl_core'] = $tbl_core;
1327 $snapshot['tbl_custom'] = $tbl_custom;
1328 $snapshot['tbl_rows'] = $tbl_rows;
1329 $snapshot['tbl_size'] = $tbl_size;
1330
1331
1332 $snapshots[$uid] = $snapshot;
1333 update_option('wp-reset-snapshots', $snapshots);
1334
1335 return $snapshot;
1336 } // create_snapshot
1337
1338
1339 /**
1340 * Delete snapshot metadata and tables from DB
1341 *
1342 * @param string $uid Snapshot unique 6-char ID.
1343 *
1344 * @return bool|WP_Error True on success, or error object on fail.
1345 */
1346 function do_delete_snapshot($uid = '') {
1347 global $wpdb;
1348 $snapshots = $this->get_snapshots();
1349
1350 if (strlen($uid) != 6) {
1351 return new WP_Error(1, 'Invalid UID format.');
1352 }
1353
1354 if (!isset($snapshots[$uid])) {
1355 return new WP_Error(1, 'Unknown snapshot ID.');
1356 }
1357
1358 $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%')));
1359 foreach ($tables as $table) {
1360 $wpdb->query('DROP TABLE IF EXISTS ' . $table);
1361 }
1362
1363 unset($snapshots[$uid]);
1364 update_option('wp-reset-snapshots', $snapshots);
1365
1366 return true;
1367 } // delete_snapshot
1368
1369
1370 /**
1371 * Exports snapshot as SQL dump; saved in gzipped file in WP_CONTENT folder.
1372 *
1373 * @param string $uid Snapshot unique 6-char ID.
1374 *
1375 * @return string|WP_Error Export base filename, or error object on fail.
1376 */
1377 function do_export_snapshot($uid = '') {
1378 global $wpdb;
1379 $snapshots = $this->get_snapshots();
1380
1381 if (strlen($uid) != 6) {
1382 return new WP_Error(1, 'Invalid snapshot ID format.');
1383 }
1384
1385 if (!isset($snapshots[$uid])) {
1386 return new WP_Error(1, 'Unknown snapshot ID.');
1387 }
1388
1389 require_once $this->plugin_dir . 'libs/dumper.php';
1390
1391 try {
1392 $world_dumper = Shuttle_Dumper::create(array(
1393 'host' => DB_HOST,
1394 'username' => DB_USER,
1395 'password' => DB_PASSWORD,
1396 'db_name' => DB_NAME,
1397 ));
1398
1399 $folder = wp_mkdir_p(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder);
1400 if (!$folder) {
1401 return new WP_Error(1, 'Unable to create wp-content/' . $this->snapshots_folder . '/ folder.');
1402 }
1403
1404 $world_dumper->dump(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder . '/wp-reset-snapshot-' . $uid . '.sql.gz', $uid . '_');
1405 } catch(Shuttle_Exception $e) {
1406 return new WP_Error(1, "Couldn't dump snapshot: " . $e->getMessage());
1407 }
1408
1409 return 'wp-reset-snapshot-' . $uid . '.sql.gz';
1410 } // export_snapshot
1411
1412
1413 /**
1414 * Replace current tables with ones in snapshot.
1415 *
1416 * @param string $uid Snapshot unique 6-char ID.
1417 *
1418 * @return bool|WP_Error True on success, or error object on fail.
1419 */
1420 function do_restore_snapshot($uid = '') {
1421 global $wpdb;
1422 $new_tables = array();
1423 $snapshots = $this->get_snapshots();
1424
1425 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
1426 return $res;
1427 }
1428
1429 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1430 if (is_array($table_status)) {
1431 foreach ($table_status as $index => $table) {
1432 if (0 !== stripos($table->Name, $uid . '_')) {
1433 continue;
1434 }
1435 if (empty($table->Engine)) {
1436 continue;
1437 }
1438
1439 $new_tables[] = $table->Name;
1440 } // foreach
1441 } else {
1442 return new WP_Error(1, 'Can\'t get table status data.');
1443 }
1444
1445 foreach ($table_status as $index => $table) {
1446 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1447 continue;
1448 }
1449 if (empty($table->Engine)) {
1450 continue;
1451 }
1452
1453 $wpdb->query('DROP TABLE ' . $table->Name);
1454 } // foreach
1455
1456 // copy snapshot tables to original name
1457 foreach ($new_tables as $table) {
1458 $new_name = str_replace($uid . '_', '', $table);
1459
1460 $wpdb->query('CREATE TABLE ' . $new_name . ' LIKE ' . $table);
1461 $wpdb->query('INSERT ' . $new_name . ' SELECT * FROM ' . $table);
1462 }
1463
1464 wp_cache_flush();
1465 update_option('wp-reset', $this->options);
1466 update_option('wp-reset-snapshots', $snapshots);
1467
1468 return true;
1469 } // restore_snapshot
1470
1471
1472 /**
1473 * Verifies snapshot integrity by comparing metadata and data in DB
1474 *
1475 * @param string $uid Snapshot unique 6-char ID.
1476 *
1477 * @return bool|WP_Error True on success, or error object on fail.
1478 */
1479 function verify_snapshot_integrity($uid) {
1480 global $wpdb;
1481 $tbl_core = $tbl_custom = 0;
1482 $snapshots = $this->get_snapshots();
1483
1484 if (strlen($uid) != 6) {
1485 return new WP_Error(1, 'Invalid snapshot ID format.');
1486 }
1487
1488 if (!isset($snapshots[$uid])) {
1489 return new WP_Error(1, 'Unknown snapshot ID.');
1490 }
1491
1492 $snapshot = $snapshots[$uid];
1493
1494 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1495 if (is_array($table_status)) {
1496 foreach ($table_status as $index => $table) {
1497 if (0 !== stripos($table->Name, $uid . '_')) {
1498 continue;
1499 }
1500 if (empty($table->Engine)) {
1501 continue;
1502 }
1503
1504 if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
1505 $tbl_core++;
1506 } else {
1507 $tbl_custom++;
1508 }
1509 } // foreach
1510
1511 if ($tbl_core != $snapshot['tbl_core'] || $tbl_custom != $snapshot['tbl_custom']) {
1512 return new WP_Error(1, 'Snapshot data has been compromised. Saved metadata does not match data in the DB. Contact WP Reset support if data is critical, or restore it via a MySQL GUI.');
1513 }
1514 } else {
1515 return new WP_Error(1, 'Can\'t get table status data.');
1516 }
1517
1518 return true;
1519 } // verify_snapshot_integrity
1520
1521
1522 /**
1523 * Compares a selected snapshot with the current table set in DB
1524 *
1525 * @param string $uid Snapshot unique 6-char ID.
1526 *
1527 * @return string|WP_Error Formatted table with details on success, or error object on fail.
1528 */
1529 function do_compare_snapshots($uid) {
1530 global $wpdb;
1531 $tbl_core = $tbl_custom = 0;
1532 $current = $snapshot = array();
1533 $out = $out2 = $out3 = '';
1534
1535 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
1536 return $res;
1537 }
1538
1539 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1540 foreach ($table_status as $index => $table) {
1541 if (empty($table->Engine)) {
1542 continue;
1543 }
1544
1545 if (0 !== stripos($table->Name, $uid . '_') && 0 !== stripos($table->Name, $wpdb->prefix)) {
1546 continue;
1547 }
1548
1549 $info = array();
1550 $info['rows'] = $table->Rows;
1551 $info['size_data'] = $table->Data_length;
1552 $info['size_index'] = $table->Index_length;
1553 $schema = $wpdb->get_row('SHOW CREATE TABLE ' . $table->Name, ARRAY_N);
1554 $info['schema'] = $schema[1];
1555 $info['engine'] = $table->Engine;
1556 $info['fullname'] = $table->Name;
1557 $basename = str_replace(array($uid . '_'), array(''), $table->Name);
1558 $info['basename'] = $basename;
1559 $info['corename'] = str_replace(array($wpdb->prefix), array(''), $basename);
1560 $info['uid'] = $uid;
1561
1562 if (0 === stripos($table->Name, $uid . '_')) {
1563 $snapshot[$basename] = $info;
1564 }
1565
1566 if (0 === stripos($table->Name, $wpdb->prefix)) {
1567 $info['uid'] = '';
1568 $current[$basename] = $info;
1569 }
1570 } // foreach
1571
1572 $in_both = array_keys(array_intersect_key($current, $snapshot));
1573 $in_current_only = array_diff_key($current, $snapshot);
1574 $in_snapshot_only = array_diff_key($snapshot, $current);
1575
1576 $out .= '<br><br>';
1577 foreach ($in_current_only as $table) {
1578 $out .= '<div class="wpr-table-container in-current-only" data-table="' . $table['basename'] . '">';
1579 $out .= '<table>';
1580 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
1581 $out .= '<td><b>' . $table['fullname'] . '</b></td>';
1582 $out .= '<td>table is not present in snapshot<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1583 $out .= '</tr>';
1584 $out .= '<tr class="hidden">';
1585 $out .= '<td>';
1586 $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1? '': 's') . ' totaling ' . $this->format_size($table['size_data']) . ' in data and ' . $this->format_size($table['size_index']) . ' in index.</p>';
1587 $out .= '<pre>' . $table['schema'] . '</pre>';
1588 $out .= '</td>';
1589 $out .= '<td>&nbsp;</td>';
1590 $out .= '</tr>';
1591 $out .= '</table>';
1592 $out .= '</div>';
1593 } // foreach in current only
1594
1595 foreach ($in_snapshot_only as $table) {
1596 $out .= '<div class="wpr-table-container in-snapshot-only" data-table="' . $table['basename'] . '">';
1597 $out .= '<table>';
1598 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
1599 $out .= '<td>table is not present in current tables</td>';
1600 $out .= '<td><b>' . $table['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1601 $out .= '</tr>';
1602 $out .= '<tr class="hidden">';
1603 $out .= '<td>&nbsp;</td>';
1604 $out .= '<td>';
1605 $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1? '': 's') . ' totaling ' . $this->format_size($table['size_data']) . ' in data and ' . $this->format_size($table['size_index']) . ' in index.</p>';
1606 $out .= '<pre>' . $table['schema'] . '</pre>';
1607 $out .= '</td>';
1608 $out .= '</tr>';
1609 $out .= '</table>';
1610 $out .= '</div>';
1611 } // foreach in snapshot only
1612
1613 foreach ($in_both as $tablename) {
1614 $tbl_current = $current[$tablename];
1615 $tbl_snapshot = $snapshot[$tablename];
1616
1617 $schema1 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_current['schema'], 1);
1618 $schema2 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_snapshot['schema'], 1);
1619 $tbl_snapshot['tmp_schema'] = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $tbl_snapshot['schema']);
1620 $schema2 = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $schema2);
1621
1622 if ($tbl_current['rows'] == $tbl_snapshot['rows'] && $tbl_current['schema'] == $tbl_snapshot['tmp_schema']) {
1623 $out3 .= '<div class="wpr-table-container identical" data-table="' . $tablename . '">';
1624 $out3 .= '<table>';
1625 $out3 .= '<tr title="Click to show/hide more info" class="wpr-table-match header-row">';
1626 $out3 .= '<td><b>' . $tbl_current['fullname'] . '</b></td>';
1627 $out3 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1628 $out3 .= '</tr>';
1629 $out3 .= '<tr class="hidden">';
1630 $out3 .= '<td>';
1631 $out3 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
1632 $out3 .= '<pre>' . $tbl_current['schema'] . '</pre>';
1633 $out3 .= '</td>';
1634 $out3 .= '<td>';
1635 $out3 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
1636 $out3 .= '<pre>' . $tbl_snapshot['schema'] . '</pre>';
1637 $out3 .= '</td>';
1638 $out3 .= '</tr>';
1639 $out3 .= '</table>';
1640 $out3 .= '</div>';
1641 } elseif ($schema1 != $schema2) {
1642 require_once $this->plugin_dir . 'libs/diff.php';
1643 require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
1644 $diff = new Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
1645 $renderer = new Diff_Renderer_Html_SideBySide;
1646
1647 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
1648 $out2 .= '<table>';
1649 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
1650 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> table schemas do not match</td>';
1651 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> table schemas do not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1652 $out2 .= '</tr>';
1653 $out2 .= '<tr class="hidden">';
1654 $out2 .= '<td>';
1655 $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
1656 $out2 .= '</td>';
1657 $out2 .= '<td>';
1658 $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
1659 $out2 .= '</td>';
1660 $out2 .= '</tr>';
1661 $out2 .= '<tr class="hidden">';
1662 $out2 .= '<td colspan="2" class="no-padding">';
1663 $out2 .= $diff->Render($renderer);
1664 $out2 .= '</td>';
1665 $out2 .= '</tr>';
1666 $out2 .= '</table>';
1667 $out2 .= '</div>';
1668 } else {
1669 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
1670 $out2 .= '<table>';
1671 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
1672 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> data in tables does not match</td>';
1673 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> data in tables does not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1674 $out2 .= '</tr>';
1675 $out2 .= '<tr class="hidden">';
1676 $out2 .= '<td>';
1677 $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
1678 $out2 .= '</td>';
1679 $out2 .= '<td>';
1680 $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
1681 $out2 .= '</td>';
1682 $out2 .= '</tr>';
1683
1684 $out2 .= '<tr class="hidden">';
1685 $out2 .= '<td colspan="2">';
1686 if ($tbl_current['corename'] == 'options') {
1687 $ss_prefix = $tbl_snapshot['uid'] . '_' . $wpdb->prefix;
1688 $diff_rows = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$wpdb->prefix}options.option_value != {$ss_prefix}options.option_value LIMIT 100;");
1689 $only_current = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$ss_prefix}options.option_value IS NULL LIMIT 100;");
1690 $only_snapshot = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$wpdb->prefix}options.option_value IS NULL LIMIT 100;");
1691 $out2 .= '<table class="table_diff">';
1692 $out2 .= '<tr><td style="width: 100px;"><b>Option Name</b></td><td><b>Current Value</b></td><td><b>Snapshot Value</b></td></tr>';
1693 foreach ($diff_rows as $row) {
1694 $out2 .= '<tr>';
1695 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
1696 $out2 .= '<td>' . (empty($row->current_value)? '<i>empty</i>': $row->current_value) . '</td>';
1697 $out2 .= '<td>' . (empty($row->snapshot_value)? '<i>empty</i>': $row->snapshot_value) . '</td>';
1698 $out2 .= '</tr>';
1699 } // foreach
1700 foreach ($only_current as $row) {
1701 $out2 .= '<tr>';
1702 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
1703 $out2 .= '<td>' . (empty($row->current_value)? '<i>empty</i>': $row->current_value) . '</td>';
1704 $out2 .= '<td><i>not found in snapshot</i></td>';
1705 $out2 .= '</tr>';
1706 } // foreach
1707 foreach ($only_current as $row) {
1708 $out2 .= '<tr>';
1709 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
1710 $out2 .= '<td><i>not found in current tables</i></td>';
1711 $out2 .= '<td>' . (empty($row->snapshot_value)? '<i>empty</i>': $row->snapshot_value) . '</td>';
1712 $out2 .= '</tr>';
1713 } // foreach
1714 $out2 .= '</table>';
1715 } else {
1716 $out2 .= '<p class="textcenter">Detailed data diff is not available for this table.</p>';
1717 }
1718 $out2 .= '</td>';
1719 $out2 .= '</tr>';
1720
1721 $out2 .= '</table>';
1722 $out2 .= '</div>';
1723 }
1724 } // foreach in both
1725
1726 return $out . $out2 . $out3;
1727 } // do_compare_snapshots
1728
1729
1730 /**
1731 * Generates a unique 6-char snapshot ID; verified non-existing
1732 *
1733 * @return string
1734 */
1735 function generate_snapshot_uid() {
1736 global $wpdb;
1737 $snapshots = $this->get_snapshots();
1738 $cnt = 0;
1739 $uid = false;
1740
1741 do {
1742 $cnt++;
1743 $uid = sprintf('%06x', mt_rand(0, 0xFFFFFF));
1744
1745 $verify_db = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array('%' . $uid . '%')));
1746 } while (!empty($verify_db) && isset($snapshots[$uid]) && $cnt < 30);
1747
1748 if ($cnt == 30) {
1749 $uid = false;
1750 }
1751
1752 return $uid;
1753 } // generate_snapshot_uid
1754
1755
1756 /**
1757 * Helper function for adding plugins to featured list
1758 *
1759 * @return array
1760 */
1761 function featured_plugins_tab($args) {
1762 add_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);
1763
1764 return $args;
1765 } // featured_plugins_tab
1766
1767
1768 /**
1769 * Add single plugin to featured list
1770 *
1771 * @return object
1772 */
1773 function add_plugin_featured($plugin_slug, $res) {
1774 // check if plugin is alredy on the list
1775 if (is_array($res->plugins)) {
1776 foreach ($res->plugins as $plugin) {
1777 if ($plugin->slug == $plugin_slug) {
1778 return $res;
1779 }
1780 } // foreach
1781 }
1782
1783 if ($plugin_info = get_transient('wf-plugin-info-' . $plugin_slug)) {
1784 $tmp1 = array_slice($res->plugins, 0, 2, false);
1785 $tmp2 = array_slice($res->plugins, 2, sizeof($res->plugins) - 2, false);
1786 $res->plugins = array_merge($tmp1, array($plugin_info), $tmp2);
1787 } else {
1788 $plugin_info = plugins_api('plugin_information', array(
1789 'slug' => $plugin_slug,
1790 'is_ssl' => is_ssl(),
1791 'fields' => array(
1792 'banners' => true,
1793 'reviews' => true,
1794 'downloaded' => true,
1795 'active_installs' => true,
1796 'icons' => true,
1797 'short_description' => true,
1798 )
1799 ));
1800 if (!is_wp_error($plugin_info)) {
1801 $tmp1 = array_slice($res->plugins, 0, 2, false);
1802 $tmp2 = array_slice($res->plugins, 2, sizeof($res->plugins) - 2, false);
1803 $res->plugins = array_merge($tmp1, array($plugin_info), $tmp2);
1804 set_transient('wf-plugin-info-' . $plugin_slug, $plugin_info, DAY_IN_SECONDS * 7);
1805 }
1806 }
1807
1808 return $res;
1809 } // add_plugin_featured
1810
1811
1812 /**
1813 * Add plugins to featured plugins list
1814 *
1815 * @return object
1816 */
1817 function plugins_api_result($res, $action, $args) {
1818 remove_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);
1819
1820 $res = $this->add_plugin_featured('security-ninja', $res);
1821
1822 return $res;
1823 } // plugins_api_result
1824
1825
1826 /**
1827 * Clean up on uninstall; no action on deactive at the moment
1828 *
1829 * @return null
1830 */
1831 static function uninstall() {
1832 delete_option('wp-reset');
1833 delete_option('wp-reset-snapshots');
1834 } // uninstall
1835
1836
1837 /**
1838 * Disabled; we use singleton pattern so magic functions need to be disabled
1839 *
1840 * @return null
1841 */
1842 private function __clone() {}
1843
1844
1845 /**
1846 * Disabled; we use singleton pattern so magic functions need to be disabled
1847 *
1848 * @return null
1849 */
1850 private function __sleep() {}
1851
1852
1853 /**
1854 * Disabled; we use singleton pattern so magic functions need to be disabled
1855 *
1856 * @return null
1857 */
1858 private function __wakeup() {}
1859 } // WP_Reset class
1860
1861
1862 // Create plugin instance and hook things up
1863 global $wp_reset;
1864 $wp_reset = WP_Reset::getInstance();
1865 add_action('plugins_loaded', array($wp_reset, 'load_textdomain'));
1866 register_uninstall_hook(__FILE__, array('WP_Reset', 'uninstall'));
1867