PluginProbe
WP Reset / 1.40
WP Reset v1.40
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.40, at wp-reset.php

1,701 lines 70.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.40
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
85 $this->core_tables = array_map(function($tbl) { global $wpdb; return $wpdb->prefix . $tbl; }, $this->core_tables);
86 } // __construct
87
88
89 /**
90 * Get plugin version from file header
91 *
92 * @return string
93 */
94 function get_plugin_version() {
95 $plugin_data = get_file_data(__FILE__, array('version' => 'Version'), 'plugin');
96
97 return $plugin_data['version'];
98 } // get_plugin_version
99
100
101 /**
102 * Load and prepare the options array
103 * If needed create a new DB entry
104 *
105 * @return array
106 */
107 private function load_options() {
108 $options = get_option('wp-reset', array());
109 $change = false;
110
111 if (!isset($options['meta'])) {
112 $options['meta'] = array('first_version' => $this->version, 'first_install' => current_time('timestamp', true), 'reset_count' => 0);
113 $change = true;
114 }
115 if (!isset($options['dismissed_notices'])) {
116 $options['dismissed_notices'] = array();
117 $change = true;
118 }
119 if (!isset($options['last_run'])) {
120 $options['last_run'] = array();
121 $change = true;
122 }
123 if (!isset($options['options'])) {
124 $options['options'] = array();
125 $change = true;
126 }
127 if ($change) {
128 update_option('wp-reset', $options, true);
129 }
130
131 $this->options = $options;
132 return $options;
133 } // load_options
134
135
136 /**
137 * Get meta part of plugin options
138 *
139 * @return array
140 */
141 function get_meta() {
142 return $this->options['meta'];
143 } // get_meta
144
145
146 /**
147 * Get all dismissed notices, or check for one specific notice
148 *
149 * @param string $notice_name Optional. Check if specified notice is dismissed.
150 *
151 * @return bool|array
152 */
153 function get_dismissed_notices($notice_name = '') {
154 $notices = $this->options['dismissed_notices'];
155
156 if (empty($notice_name)) {
157 return $notices;
158 } else {
159 if (empty($notices[$notice_name])) {
160 return false;
161 } else {
162 return true;
163 }
164 }
165 } // get_dismissed_notices
166
167
168 /**
169 * Get options part of plugin options
170 *
171 * todo: not completed
172 *
173 * @param string $key Optional.
174 *
175 * @return array
176 */
177 function get_options($key = '') {
178 return $this->options['options'];
179 } // get_options
180
181
182 /**
183 * Update plugin options, currently entire array
184 *
185 * todo: this handles the entire options array although it should only do the options part - it's confusing
186 *
187 * @param string $key Data to save.
188 * @param string $data Option key.
189 *
190 * @return bool
191 */
192 function update_options($key, $data) {
193 $this->options[$key] = $data;
194 $tmp = update_option('wp-reset', $this->options);
195
196 return $tmp;
197 } // set_options
198
199
200 /**
201 * Add plugin menu entry under Tools menu
202 *
203 * @return null
204 */
205 function admin_menu() {
206 add_management_page(__('WP Reset', 'wp-reset'), __('WP Reset', 'wp-reset'), 'administrator', 'wp-reset', array($this, 'plugin_page'));
207 } // admin_menu
208
209
210 /**
211 * Dismiss notice via AJAX call
212 *
213 * @return null
214 */
215 function ajax_dismiss_notice() {
216 check_ajax_referer('wp-reset_dismiss_notice');
217
218 $notice_name = trim(@$_GET['notice_name']);
219 if (!$this->dismiss_notice($notice_name)) {
220 wp_send_json_error('Notice is already dismissed.');
221 } else {
222 wp_send_json_success();
223 }
224 } // ajax_dismiss_notice
225
226
227 /**
228 * Dismiss notice by adding it to dismissed_notices options array
229 *
230 * @param string $notice_name Notice to dismiss.
231 *
232 * @return bool
233 */
234 function dismiss_notice($notice_name) {
235 if ($this->get_dismissed_notices($notice_name)) {
236 return false;
237 } else {
238 $notices = $this->get_dismissed_notices();
239 $notices[$notice_name] = true;
240 $this->update_options('dismissed_notices', $notices);
241 return true;
242 }
243 } // dismiss_notice
244
245
246 /**
247 * Returns all WP pointers
248 *
249 * @return array
250 */
251 function get_pointers() {
252 $pointers = array();
253
254 $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.');
255
256 return $pointers;
257 } // get_pointers
258
259
260 /**
261 * Enqueue CSS and JS files
262 *
263 * @return null
264 */
265 function admin_enqueue_scripts($hook) {
266 // welcome pointer is shown on all pages except WPR, untill dismissed
267 $pointers = $this->get_pointers();
268 $dismissed_notices = $this->get_dismissed_notices();
269
270 foreach ($dismissed_notices as $notice_name => $tmp) {
271 if ($tmp) {
272 unset($pointers[$notice_name]);
273 }
274 } // foreach
275
276 if (!empty($pointers) && 'tools_page_wp-reset' != $hook) {
277 $pointers['_nonce_dismiss_pointer'] = wp_create_nonce('wp-reset_dismiss_notice');
278
279 wp_enqueue_style('wp-pointer');
280
281 wp_enqueue_script('wp-reset-pointers', $this->plugin_url . 'js/wp-reset-pointers.js', array('jquery'), $this->version, true);
282 wp_enqueue_script('wp-pointer');
283 wp_localize_script('wp-pointer', 'wp_reset_pointers', $pointers);
284 }
285
286 // exit early if not on WP Reset page
287 if ('tools_page_wp-reset' != $hook) {
288 return;
289 }
290
291 $options = $this->get_options();
292
293 $js_localize = array('undocumented_error' => __('An undocumented error has occured. Please refresh the page and try again.', 'wp-reset'),
294 'documented_error' => __('An error has occured.', 'wp-reset'),
295 'plugin_name' => __('WP Reset', 'wp-reset'),
296 'settings_url' => admin_url('tools.php?page=wp-reset'),
297 'icon_url' => $this->plugin_url . 'img/wp-reset-icon.png',
298 'invalid_confirmation' => __('Please type "reset" in the confirmation field.', 'wp-reset'),
299 'invalid_confirmation_title' => __('Invalid confirmation', 'wp-reset'),
300 'cancel_button' => __('Cancel', 'wp-reset'),
301 'ok_button' => __('OK', 'wp-reset'),
302 'confirm_button' => __('Reset WordPress', 'wp-reset'),
303 'confirm_title' => __('Are you sure you want to proceed?', 'wp-reset'),
304 'confirm1' => __('Clicking "Reset WordPress" will reset your site to default values. All content will be lost. There is NO UNDO.', 'wp-reset'),
305 'confirm2' => __('Click "Cancel" to abort.', 'wp-reset'),
306 'doing_reset' => __('Resetting in progress. Please wait.', 'wp-reset'),
307 'nonce_dismiss_notice' => wp_create_nonce('wp-reset_dismiss_notice'),
308 'nonce_run_tool' => wp_create_nonce('wp-reset_run_tool'),
309 'nonce_do_reset' => wp_create_nonce('wp-reset_do_reset'));
310
311 wp_enqueue_style('wp-reset', $this->plugin_url . 'css/wp-reset.css', array(), $this->version);
312 wp_enqueue_style('wp-reset-sweetalert2', $this->plugin_url . 'css/sweetalert2.min.css', array(), $this->version);
313
314 wp_enqueue_script('jquery-ui-tabs');
315 wp_enqueue_script('wp-reset-sweetalert2', $this->plugin_url . 'js/sweetalert2.min.js', array('jquery'), $this->version, true);
316 wp_enqueue_script('wp-reset', $this->plugin_url . 'js/wp-reset.js', array('jquery'), $this->version, true);
317 wp_localize_script('wp-reset', 'wp_reset', $js_localize);
318
319 // fix for aggressive plugins that include their CSS on all pages
320 wp_dequeue_style('uiStyleSheet');
321 wp_dequeue_style('wpcufpnAdmin' );
322 wp_dequeue_style('unifStyleSheet' );
323 wp_dequeue_style('wpcufpn_codemirror');
324 wp_dequeue_style('wpcufpn_codemirrorTheme');
325 wp_dequeue_style('collapse-admin-css');
326 wp_dequeue_style('jquery-ui-css');
327 wp_dequeue_style('tribe-common-admin');
328 wp_dequeue_style('file-manager__jquery-ui-css');
329 wp_dequeue_style('file-manager__jquery-ui-css-theme');
330 wp_dequeue_style('wpmegmaps-jqueryui');
331 wp_dequeue_style('wp-botwatch-css');
332 } // admin_enqueue_scripts
333
334
335 /**
336 * Check if WP-CLI is available and running
337 *
338 * @return bool
339 */
340 function is_cli_running() {
341 if (defined('WP_CLI') && WP_CLI) {
342 return true;
343 } else {
344 return false;
345 }
346 } // is_cli_running
347
348
349 /**
350 * Deletes all transients.
351 *
352 * @return int Number of deleted transient DB entries
353 */
354 function do_delete_transients() {
355 global $wpdb;
356
357 $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'");
358
359 return $count;
360 } // do_delete_transients
361
362
363 /**
364 * Deletes all files in uploads folder.
365 *
366 * @return int Number of deleted files and folders.
367 */
368 function do_delete_uploads() {
369 $upload_dir = wp_get_upload_dir();
370
371 $this->delete_folder($upload_dir['basedir'], $upload_dir['basedir']);
372
373 return $this->delete_count;
374 } // do_delete_uploads
375
376
377 /**
378 * Recursively deletes a folder
379 *
380 * @param string $folder Recursive param.
381 * @param string $base_folder Base folder.
382 *
383 * @return bool
384 */
385 private function delete_folder($folder, $base_folder) {
386 $files = array_diff(scandir($folder), array('.', '..'));
387
388 foreach ($files as $file) {
389 if (is_dir($folder . DIRECTORY_SEPARATOR . $file)) {
390 $this->delete_folder($folder . DIRECTORY_SEPARATOR . $file, $base_folder);
391 } else {
392 $tmp = @unlink($folder . DIRECTORY_SEPARATOR . $file);
393 $this->delete_count = $this->delete_count + (int) $tmp;
394 }
395 } // foreach
396
397 if ($folder != $base_folder) {
398 $tmp = @rmdir($folder);
399 $this->delete_count = $this->delete_count + (int) $tmp;
400 return $tmp;
401 } else {
402 return true;
403 }
404 } // delete_folder
405
406
407 /**
408 * Deactivate and delete all plugins
409 *
410 * @param bool $keep_wp_reset Keep WP Reset active and installed
411 * @param bool $silent_deactivate Skip individual plugin deactivation functions when deactivating
412 *
413 * @return int Number of deleted plugins.
414 */
415 function do_delete_plugins($keep_wp_reset = true, $silent_deactivate = false) {
416 if (!function_exists('get_plugins')) {
417 require_once ABSPATH . 'wp-admin/includes/plugin.php';
418 }
419
420 $wp_reset_basename = plugin_basename(__FILE__);
421
422 $all_plugins = get_plugins();
423 $active_plugins = (array) get_option('active_plugins', array());
424 if (true == $keep_wp_reset) {
425 if (($key = array_search($wp_reset_basename, $active_plugins)) !== false) {
426 unset($active_plugins[$key]);
427 }
428 unset($all_plugins[$wp_reset_basename]);
429 }
430
431 if (!empty($active_plugins)) {
432 deactivate_plugins($active_plugins, $silent_deactivate, false);
433 }
434
435 if (!empty($all_plugins)) {
436 delete_plugins(array_keys($all_plugins));
437 }
438
439 return sizeof($all_plugins);
440 } // do_delete_plugins
441
442
443 /**
444 * Delete all themes
445 *
446 * @param bool $keep_default_theme Keep default theme
447 *
448 * @return int Number of deleted themes.
449 */
450 function do_delete_themes($keep_default_theme = true) {
451 $default_theme = 'twentyseventeen';
452 $all_themes = wp_get_themes(array('errors' => null));
453
454 if (true == $keep_default_theme) {
455 unset($all_themes[$default_theme]);
456 }
457
458 foreach ($all_themes as $theme_slug => $theme_details) {
459 $res = delete_theme($theme_slug);
460 }
461
462 if (false == $keep_default_theme) {
463 update_option('template', '');
464 update_option('stylesheet', '');
465 }
466
467 return sizeof($all_themes);
468 } // do_delete_themes
469
470
471 /**
472 * Run one tool via AJAX call
473 *
474 * @return null
475 */
476 function ajax_run_tool() {
477 check_ajax_referer('wp-reset_run_tool');
478
479 $tool = trim(@$_GET['tool']);
480 $extra_data = trim(@$_GET['extra_data']);
481
482 if ($tool == 'delete_transients') {
483 $cnt = $this->do_delete_transients();
484 wp_send_json_success($cnt);
485 } elseif ($tool == 'delete_themes') {
486 $cnt = $this->do_delete_themes(false);
487 wp_send_json_success($cnt);
488 } elseif ($tool == 'delete_plugins') {
489 $cnt = $this->do_delete_plugins(true);
490 wp_send_json_success($cnt);
491 } elseif ($tool == 'delete_uploads') {
492 $cnt = $this->do_delete_uploads();
493 wp_send_json_success($cnt);
494 } elseif ($tool == 'delete_snapshot') {
495 $res = $this->do_delete_snapshot($extra_data);
496 if (is_wp_error($res)) {
497 wp_send_json_error($res->get_error_message());
498 } else {
499 wp_send_json_success();
500 }
501 } elseif ($tool == 'download_snapshot') {
502 $res = $this->do_export_snapshot($extra_data);
503 if (is_wp_error($res)) {
504 wp_send_json_error($res->get_error_message());
505 } else {
506 $url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
507 wp_send_json_success($url);
508 }
509 } elseif ($tool == 'restore_snapshot') {
510 $res = $this->do_restore_snapshot($extra_data);
511 if (is_wp_error($res)) {
512 wp_send_json_error($res->get_error_message());
513 } else {
514 wp_send_json_success();
515 }
516 } elseif ($tool == 'compare_snapshots') {
517 $res = $this->do_compare_snapshots($extra_data);
518 if (is_wp_error($res)) {
519 wp_send_json_error($res->get_error_message());
520 } else {
521 wp_send_json_success($res);
522 }
523 } elseif ($tool == 'create_snapshot') {
524 $res = $this->do_create_snapshot($extra_data);
525 if (is_wp_error($res)) {
526 wp_send_json_error($res->get_error_message());
527 } else {
528 wp_send_json_success();
529 }
530 } else {
531 wp_send_json_error(__('Unknown tool.', 'wp-reset'));
532 }
533 } // ajax_run_tool
534
535
536 /**
537 * Reinstall / reset the WP site
538 * There are no failsafes in the function - it reinstalls when called
539 * Redirects when done
540 *
541 * @param array $params Optional.
542 *
543 * @return null
544 */
545 function do_reinstall($params = array()) {
546 global $current_user, $wpdb;
547
548 // only admins can reset; double-check
549 if (!$this->is_cli_running() && !current_user_can('administrator')) {
550 return false;
551 }
552
553 // make sure the function is available to us
554 if (!function_exists('wp_install')) {
555 require ABSPATH . '/wp-admin/includes/upgrade.php';
556 }
557
558 // save values that need to be restored after reset
559 // todo: use params to determine what gets restored after reset
560 $blogname = get_option('blogname');
561 $blog_public = get_option('blog_public');
562 $wplang = get_option('wplang');
563 $siteurl = get_option('siteurl');
564 $home = get_option('home');
565 $snapshots = $this->get_snapshots();
566
567 $active_plugins = get_option('active_plugins');
568 $active_theme = wp_get_theme();
569
570 // for WP-CLI
571 if (!$current_user->ID) {
572 $tmp = get_users(array('role' => 'administrator', 'order' => 'ASC', 'order_by' => 'ID'));
573 if (empty($tmp[0]->user_login)) {
574 return new WP_Error('no_user', 'Reset failed. Unable to find any admin users in database.');
575 }
576 $current_user = $tmp[0];
577 }
578
579 // delete custom tables with WP's prefix
580 $prefix = str_replace('_', '\_', $wpdb->prefix);
581 $tables = $wpdb->get_col("SHOW TABLES LIKE '{$prefix}%'");
582 foreach ($tables as $table) {
583 $wpdb->query("DROP TABLE $table");
584 }
585
586 // supress errors for WP_CLI
587 // todo: do something better
588 $result = @wp_install($blogname, $current_user->user_login, $current_user->user_email, $blog_public, '', md5(rand()), $wplang);
589 $user_id = $result['user_id'];
590
591 // restore user pass
592 $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));
593 $wpdb->query($query);
594
595 // restore rest of the settings including WP Reset's
596 update_option('siteurl', $siteurl);
597 update_option('home', $home);
598 update_option('wp-reset', $this->options);
599 update_option('wp-reset-snapshots', $snapshots);
600
601 // remove password nag
602 if (get_user_meta($user_id, 'default_password_nag')) {
603 update_user_meta($user_id, 'default_password_nag', false);
604 }
605 if (get_user_meta($user_id, $wpdb->prefix . 'default_password_nag')) {
606 update_user_meta($user_id, $wpdb->prefix . 'default_password_nag', false );
607 }
608
609 $meta = $this->get_meta();
610 $meta['reset_count']++;
611 $this->update_options('meta', $meta);
612
613 // reactivate theme
614 if (!empty($params['reactivate_theme'])) {
615 switch_theme($active_theme->get_stylesheet());
616 }
617
618 // reactivate WP Reset
619 if (!empty($params['reactivate_wpreset'])) {
620 activate_plugin(plugin_basename( __FILE__ ));
621 }
622
623 // reactivate all plugins
624 if (!empty($params['reactivate_plugins'])) {
625 foreach ($active_plugins as $plugin_file) {
626 activate_plugin($plugin_file);
627 }
628 }
629
630 if (!$this->is_cli_running()) {
631 // log out and log in the old/new user
632 // since the password doesn't change this is potentially unnecessary
633 wp_clear_auth_cookie();
634 wp_set_auth_cookie($user_id);
635
636 wp_redirect(admin_url() . '?wp-reset=success');
637 exit;
638 }
639 } // do_reinstall
640
641
642 /**
643 * Checks wp_reset post value and performs all actions
644 * todo: handle messages for various actions
645 *
646 * @return null|bool
647 */
648 function do_all_actions() {
649 // only admins can perform actions
650 if (!current_user_can('administrator')) {
651 return;
652 }
653
654 if (!empty($_GET['wp-reset']) && stristr($_SERVER['HTTP_REFERER'], 'wp-reset')) {
655 add_action('admin_notices', array($this, 'notice_successfull_reset'));
656 }
657
658 // check nonce
659 if (true === isset($_POST['wp_reset_confirm']) && false === wp_verify_nonce(@$_POST['_wpnonce'], 'wp-reset')) {
660 add_settings_error('wp-reset', 'bad-nonce', __('Something went wrong. Please refresh the page and try again.', 'wp-reset'), 'error');
661 return false;
662 }
663
664 // check confirmation code
665 if (true === isset($_POST['wp_reset_confirm']) && 'reset' !== $_POST['wp_reset_confirm']) {
666 add_settings_error('wp-reset', 'bad-confirm', __('<b>Invalid confirmation code.</b> Please type "reset" in the confirmation field.', 'wp-reset'), 'error');
667 return false;
668 }
669
670 // only one action at the moment
671 if (true === isset($_POST['wp_reset_confirm']) && 'reset' === $_POST['wp_reset_confirm']) {
672 $defaults = array('reactivate_theme' => '0',
673 'reactivate_plugins' => '0',
674 'reactivate_wpreset' => '0');
675 $params = shortcode_atts($defaults, (array) @$_POST['wpr-post-reset']);
676
677 $this->do_reinstall($params);
678 }
679 } // do_all_actions
680
681
682 /**
683 * Add "Reset WordPress" action link to plugins table, left part
684 *
685 * @param array $links Initial list of links.
686 *
687 * @return array
688 */
689 function plugin_action_links($links) {
690 $settings_link = '<a href="' . admin_url('tools.php?page=wp-reset') . '" title="' . __('Reset WordPress', 'wp-reset') . '">' . __('Reset WordPress', 'wp-reset') . '</a>';
691
692 array_unshift($links, $settings_link);
693
694 return $links;
695 } // plugin_action_links
696
697
698 /**
699 * Add links to plugin's description in plugins table
700 *
701 * @param array $links Initial list of links.
702 * @param string $file Basename of current plugin.
703 *
704 * @return array
705 */
706 function plugin_meta_links($links, $file) {
707 if ($file !== plugin_basename(__FILE__)) {
708 return $links;
709 }
710
711 $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset" title="' . __('Get help', 'wp-reset') . '">' . __('Support', 'wp-reset') . '</a>';
712 $home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-reset') . '">' . __('Plugin Homepage', 'wp-reset') . '</a>';
713 $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 �
714 �
715 �
716 �
717 �
718 ', 'wp-reset') . '</a>';
719
720 $links[] = $support_link;
721 $links[] = $home_link;
722 $links[] = $rate_link;
723
724 return $links;
725 } // plugin_meta_links
726
727
728 /**
729 * Test if we're on WPR's admin page
730 *
731 * @return bool
732 */
733 function is_plugin_page() {
734 $current_screen = get_current_screen();
735
736 if ($current_screen->id == 'tools_page_wp-reset') {
737 return true;
738 } else {
739 return false;
740 }
741 } // is_plugin_page
742
743
744 /**
745 * Add powered by text in admin footer
746 *
747 * @param string $text Default footer text.
748 *
749 * @return string
750 */
751 function admin_footer_text($text) {
752 if (!$this->is_plugin_page()) {
753 return $text;
754 }
755
756 $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>';
757
758 return $text;
759 } // admin_footer_text
760
761
762 /**
763 * Loads plugin's translated strings
764 *
765 * @return null
766 */
767 function load_textdomain() {
768 load_plugin_textdomain('wp-reset');
769 } // load_textdomain
770
771
772 /**
773 * Inform the user that WordPress has been successfully reset
774 *
775 * @return null
776 */
777 function notice_successfull_reset() {
778 global $current_user;
779
780 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>';
781 } // notice_successfull_reset
782
783
784 /**
785 * Outputs complete plugin's admin page
786 *
787 * @return null
788 */
789 function plugin_page() {
790 $notice_shown = false;
791 $meta = $this->get_meta();
792 $notices = $this->get_dismissed_notices();
793 $snapshots = $this->get_snapshots();
794
795 // double check for admin priv
796 if (!current_user_can('administrator')) {
797 wp_die(__('Sorry, you are not allowed to access this page.', 'wp-reset'));
798 }
799
800 settings_errors();
801 echo '<div class="wrap">';
802 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>';
803 echo '<form id="wp_reset_form" action="' . admin_url('tools.php?page=wp-reset') . '" method="post" autocomplete="off">';
804
805 if (false === $notice_shown && is_multisite()) {
806 echo '<div class="card notice-wrapper notice-error">';
807 echo '<h2>' . __('WP Reset is not compatible with multisite!', 'wp-reset') . '</h2>';
808 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>';
809 echo '</div>';
810 $notice_shown = true;
811 }
812
813 if ((!empty($meta['reset_count']) || !empty($snapshots)) && false === $notice_shown && false == $this->get_dismissed_notices('rate')) {
814 echo '<div class="card notice-wrapper">';
815 echo '<h2>' . __('Please help us keep the plugin free &amp; up-to-date', 'wp-reset') . '</h2>';
816 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>';
817 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>';
818 echo '</div>';
819 $notice_shown = true;
820 }
821
822 // Tidy Repo ad
823 // disabled for now
824 if (false && false === $notice_shown && $meta['reset_count'] >= 2 && false == $this->get_dismissed_notices('tidy')) {
825 echo '<div class="card notice-wrapper">';
826 echo '<h2>' . __('Are you a plugin author? Get your plugin reviewed on Tidy Repo', 'wp-reset') . '</h2>';
827 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>';
828 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>';
829 echo '</div>';
830 $notice_shown = true;
831 }
832
833 // tabs
834 echo '<div id="wp-reset-tabs" class' . __('="', 'wp-reset') . 'ui-tabs">';
835
836 echo '<ul class="wpr-main-tab">';
837 echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
838 echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
839 echo '<li><a href="#tab-snapshots">' . __('DB Snapshots', 'wp-reset') . '</a></li>';
840 echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
841 if (empty($notices['geoip_tab'])) {
842 echo '<li><a href="#tab-geoip">' . __('IP Geolocation', 'wp-reset') . '</a></li>';
843 }
844 echo '</ul>';
845
846 echo '<div style="display: none;" id="tab-reset">';
847 $this->tab_reset();
848 echo '</div>';
849
850 echo '<div style="display: none;" id="tab-tools">';
851 $this->tab_tools();
852 echo '</div>';
853
854 echo '<div style="display: none;" id="tab-snapshots">';
855 $this->tab_snapshots();
856 echo '</div>';
857
858 echo '<div style="display: none;" id="tab-support">';
859 $this->tab_support();
860 echo '</div>';
861
862 if (empty($notices['geoip_tab'])) {
863 echo '<div style="display: none;" id="tab-geoip">';
864 $this->tab_geoip();
865 echo '</div>';
866 }
867
868 echo '</div>'; // tabs
869
870 echo '</form>';
871 echo '</div>'; // wrap
872 } // plugin_page
873
874
875 /**
876 * Echoes content for reset tab
877 *
878 * @return null
879 */
880 private function tab_reset() {
881 global $current_user, $wpdb;
882
883 echo '<div class="card" id="card-description">';
884 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
885 echo '<h2>' . __('Please read carefully before proceeding. There is NO UNDO!', 'wp-reset') . '</h2>';
886 echo '<b class="red">' . __('Resetting will delete:', 'wp-reset') . '</b>';
887 echo '<ul class="plain-list">';
888 echo '<li>' . __('all posts, pages, custom post types, comments, media entries, users', 'wp-reset') . '</li>';
889 echo '<li>' . __('all default WP database tables', 'wp-reset') . '</li>';
890 echo '<li>' . sprintf(__('all custom database tables that have the same prefix "%s" as default tables in this installation', 'wp-reset'), $wpdb->prefix) . '</li>';
891 echo '</ul>';
892
893 echo '<b class="green">' . __('Resetting will not delete:', 'wp-reset') . '</b>';
894 echo '<ul class="plain-list">';
895 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>';
896 echo '<li>' . __('no files are touched; plugins, themes, uploads - everything stays', 'wp-reset') . '</li>';
897 echo '<li>' . __('site title, WordPress address, site address, site language and search engine visibility settings', 'wp-reset') . '</li>';
898 echo '<li>' . sprintf(__('logged in user "%s" will be restored with the current password', 'wp-reset'), $current_user->user_login) . '</li>';
899 echo '</ul>';
900
901 echo '<b>' . __('What happens when I click the Reset button?', 'wp-reset') . '</b>';
902 echo '<ul class="plain-list">';
903 echo '<li>' . __('you will have to confirm the action one more time because there is NO UNDO', 'wp-reset') . '</li>';
904 echo '<li>' . __('everything will be reset; see bullets above for details', 'wp-reset') . '</li>';
905 echo '<li>' . __('site title, WordPress address, site address, site language, search engine visibility and current user will be restored', 'wp-reset') . '</li>';
906 echo '<li>' . __('you will be logged out, automatically logged in and taken to the admin dashboard', 'wp-reset') . '</li>';
907 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>';
908 echo '</ul>';
909
910 echo '<b>' . __('WP-CLI Support', 'wp-reset') . '</b>';
911 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>');
912 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>';
913 echo '</div>';
914
915 $theme = wp_get_theme();
916
917 echo '<div class="card" id="card-post-reset">';
918 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
919 echo '<h2>' . __('Post-reset actions', 'wp-reset') . '</h2>';
920 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>';
921 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>';
922 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>';
923 echo '</div>';
924
925 echo '<div class="card">';
926 echo '<h2>' . __('Reset', 'wp-reset') . '</h2>';
927 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>';
928
929 wp_nonce_field('wp-reset');
930 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;';
931 echo '<input id="wp_reset_submit" type="button" class="button-primary" value="' . __('Reset WordPress', 'wp-reset') . '"></p>';
932 echo '</div>';
933 } // tab_reset
934
935
936 /**
937 * Echoes content for tools tab
938 *
939 * @return null
940 */
941 private function tab_tools() {
942 $theme = wp_get_theme();
943
944 echo '<div class="card">';
945 echo '<h2>' . __('Transients', 'wp-reset') . '</h2>';
946 echo '<p>' . __('All transient related database entries will be deleted. Including expired and non-expired transients, and orphaned timeout entries. <b>There is NO UNDO. WP Reset will not make any backups.</b>', 'wp-reset') . '</p>';
947 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" href="#" id="delete-transients">Delete all transients</a></p>';
948 echo '</div>';
949
950 $upload_dir = wp_upload_dir(date('Y/m'), true);
951
952 echo '<div class="card">';
953 echo '<h2>' . __('Uploads Folder', 'wp-reset') . '</h2>';
954 echo '<p>' . __('All files in <code>' . $upload_dir['basedir'] . '</code> folder will be deleted. Including folders and subfolder, and files in subfolders. Files associated with <a href="' . admin_url('upload.php') . '">media</a> entries will be deleted too. <b>There is NO UNDO. WP Reset will not make any backups.</b>', 'wp-reset') . '</p>';
955 if (false != $upload_dir['error']) {
956 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>';
957 } else {
958 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" href="#" id="delete-uploads">Delete all files &amp; folders in uploads folder</a></p>';
959 }
960 echo '</div>';
961
962 echo '<div class="card">';
963 echo '<h2>' . __('Themes', 'wp-reset') . '</h2>';
964 echo '<p>' . __('All themes will be deleted. Including the currently active theme - ' . $theme->get('Name') . '. <b>There is NO UNDO. WP Reset will not make any backups.</b>', 'wp-reset') . '</p>';
965 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" href="#" id="delete-themes">Delete all themes</a></p>';
966 echo '</div>';
967
968 echo '<div class="card">';
969 echo '<h2>' . __('Plugins', 'wp-reset') . '</h2>';
970 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. WP Reset will not make any backups.</b>', 'wp-reset') . '</p>';
971 echo '<p>WP Reset plugin will no be deleted or disabled.</p>';
972 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" href="#" id="delete-plugins">Delete plugins</a></p>';
973 echo '</div>';
974 } // tab_tools
975
976
977 /**
978 * Echoes content for support tab
979 *
980 * @return null
981 */
982 private function tab_support() {
983 echo '<div class="card">';
984 echo '<h2>' . __('Public support forum', 'wp-reset') . '</h2>';
985 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>';
986 echo '</div>';
987
988 echo '<div class="card">';
989 echo '<h2>' . __('Private contact', 'wp-reset') . '</h2>';
990 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>';
991 echo '</div>';
992
993 echo '<div class="card">';
994 echo '<h2>' . __('Care to help out?', 'wp-reset') . '</h2>';
995 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>';
996 echo '</div>';
997 } // tab_support
998
999
1000 /**
1001 * Echoes content for snapshots tab
1002 *
1003 * @return null
1004 */
1005 private function tab_snapshots() {
1006 global $wpdb;
1007 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1008
1009 echo '<div class="card" id="card-snapshots">';
1010 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1011 echo '<h2>' . __('Database Snapshots', 'wp-reset') . '</h2>';
1012 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>
1013 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>';
1014 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>';
1015
1016 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1017 if (is_array($table_status)) {
1018 foreach ($table_status as $index => $table) {
1019 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1020 continue;
1021 }
1022 if (empty($table->Engine)) {
1023 continue;
1024 }
1025
1026 $tbl_rows += $table->Rows;
1027 $tbl_size += $table->Data_length + $table->Index_length;
1028 if (in_array($table->Name, $this->core_tables)) {
1029 $tbl_core++;
1030 } else {
1031 $tbl_custom++;
1032 }
1033 } // foreach
1034
1035 echo '<p><b>Currently used WordPress tables</b>, prefixed with <i>' . $wpdb->prefix . '</i>, consist of ' . $tbl_core . ' standard and ';
1036 if ($tbl_custom) {
1037 echo $tbl_custom . ' custom table' . ($tbl_custom == 1? '': 's');
1038 } else {
1039 echo 'no custom tables';
1040 }
1041 echo ' totaling ' . $this->format_size($tbl_size) .' in ' . number_format($tbl_rows) . ' rows.</p>';
1042 }
1043
1044 echo '';
1045 echo '</div>';
1046
1047 echo '<div class="card no-padding-bottom">';
1048 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>';
1049 echo '<h2>' . __('Saved Snapshots', 'wp-reset') . '</h2>';
1050
1051 if ($snapshots = $this->get_snapshots()) {
1052 echo '<table id="wpr-snapshots">';
1053 echo '<tr><th>Name</th><th>Info &amp; Size</th><th class="ss-actions">Actions</th></tr>';
1054 foreach ($snapshots as $ss) {
1055 echo '<tr id="wpr-ss-' . $ss['uid'] . '">';
1056 if (!empty($ss['name'])) {
1057 echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . $ss['name'] . '</td>';
1058 $name = $ss['name'];
1059 } else {
1060 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>';
1061 $name = 'created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
1062 }
1063 echo '<td>' . $ss['tbl_core'] . ' standard &amp; ';
1064 if ($ss['tbl_custom']) {
1065 echo $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1? '': 's');
1066 } else {
1067 echo 'no custom tables';
1068 }
1069 echo ' totaling ' . $this->format_size($ss['tbl_size']) . ' in ' . number_format($ss['tbl_rows']) . ' rows</td>';
1070 echo '<td>';
1071 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>';
1072 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>';
1073 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>';
1074 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>';
1075 echo '</tr>';
1076 } // foreach
1077 echo '</table>';
1078 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>';
1079 } else {
1080 echo '<p id="ss-no-snapshots">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
1081 }
1082
1083 echo '</div>';
1084 } // tab_snapshots
1085
1086
1087 /**
1088 * Echoes content for sponsor tab
1089 *
1090 * @return null
1091 */
1092 private function tab_geoip() {
1093 echo '<div class="card">';
1094 echo '<h2>' . __('WP Reset is proudly sponsored by IP Geolocation', 'wp-reset') . '</h2>';
1095 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>';
1096 echo '</div>';
1097
1098 echo '<div class="card">';
1099 echo '<h2>' . __('Why would I need a GeoIP service?', 'wp-reset') . '</h2>';
1100 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>';
1101 echo '<p><a href="https://ipgeolocation.io/ip-location" target="_blank" class="button">See what data is available for your IP address</a></p>';
1102 echo '</div>';
1103
1104 echo '<div class="card">';
1105 echo '<h2>' . __('Get a free account', 'wp-reset') . '</h2>';
1106 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>';
1107 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>';
1108 echo '</div>';
1109
1110 echo '<p>Permanently <a href="#" class="wpr-dismiss-notice" data-notice="geoip_tab">remove this tab</a>.</p>';
1111 } // tab_geoip
1112
1113
1114 /**
1115 * Helper function for generating UTM tagged links
1116 *
1117 * @param string $placement Optional. UTM content param.
1118 * @param string $page Optional. Page to link to.
1119 * @param array $params Optional. Extra URL params.
1120 * @param string $anchor Optional. URL anchor part.
1121 *
1122 * @return string
1123 */
1124 function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '') {
1125 $base_url = 'https://wpreset.com';
1126
1127 if ('/' != $page) {
1128 $page = '/' . trim($page, '/') . '/';
1129 }
1130 if ($page == '//') {
1131 $page = '/';
1132 }
1133
1134 $parts = array_merge(array('utm_source' => 'wp-reset-free', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-reset-free-v' . $this->version), $params);
1135
1136 if (!empty($anchor)) {
1137 $anchor = '#' . trim($anchor, '#');
1138 }
1139
1140 $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;
1141
1142 return $out;
1143 } // generate_web_link
1144
1145
1146 /**
1147 * Returns all saved snapshots from DB
1148 *
1149 * @return array
1150 */
1151 function get_snapshots() {
1152 $snapshots = get_option('wp-reset-snapshots', array());
1153
1154 return $snapshots;
1155 } // get_snapshots
1156
1157
1158 /**
1159 * Format file size to human readable string
1160 *
1161 * @param int $bytes Size in bytes to format.
1162 *
1163 * @return string
1164 */
1165 function format_size($bytes) {
1166 if ($bytes > 1073741824) {
1167 return number_format_i18n($bytes / 1073741824, 2) . ' GB';
1168 } elseif ($bytes > 1048576) {
1169 return number_format_i18n($bytes / 1048576, 1) . ' MB';
1170 } elseif ($bytes > 1024) {
1171 return number_format_i18n($bytes / 1024, 1) . ' KB';
1172 } else {
1173 return number_format_i18n($bytes, 0) . ' bytes';
1174 }
1175 } // format_size
1176
1177
1178 /**
1179 * Creates snapshot of current tables by copying them in the DB and saving metadata.
1180 *
1181 * @param int $name Optional. Name for the new snapshot.
1182 *
1183 * @return array|WP_Error Snapshot details in array on success, or error object on fail.
1184 */
1185 function do_create_snapshot($name = '') {
1186 global $wpdb;
1187 $snapshots = $this->get_snapshots();
1188 $snapshot = array();
1189 $uid = $this->generate_snapshot_uid();
1190 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1191
1192 if (!$uid) {
1193 return new WP_Error(1, 'Unable to generate a valid snapshot UID.');
1194 }
1195
1196 if ($name) {
1197 $snapshot['name'] = substr(trim($name), 0, 64);
1198 } else {
1199 $snapshot['name'] = '';
1200 }
1201 $snapshot['uid'] = $uid;
1202 $snapshot['timestamp'] = current_time('mysql');
1203
1204 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1205 if (is_array($table_status)) {
1206 foreach ($table_status as $index => $table) {
1207 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1208 continue;
1209 }
1210 if (empty($table->Engine)) {
1211 continue;
1212 }
1213
1214 $tbl_rows += $table->Rows;
1215 $tbl_size += $table->Data_length + $table->Index_length;
1216 if (in_array($table->Name, $this->core_tables)) {
1217 $tbl_core++;
1218 } else {
1219 $tbl_custom++;
1220 }
1221
1222 $wpdb->query('OPTIMIZE TABLE ' . $table->Name);
1223 $wpdb->query('CREATE TABLE ' . $uid . '_' . $table->Name .' LIKE ' . $table->Name);
1224 $wpdb->query('INSERT ' . $uid . '_' . $table->Name . ' SELECT * FROM ' . $table->Name);
1225 } // foreach
1226 } else {
1227 return new WP_Error(1, 'Can\'t get table status data.');
1228 }
1229
1230 $snapshot['tbl_core'] = $tbl_core;
1231 $snapshot['tbl_custom'] = $tbl_custom;
1232 $snapshot['tbl_rows'] = $tbl_rows;
1233 $snapshot['tbl_size'] = $tbl_size;
1234
1235
1236 $snapshots[$uid] = $snapshot;
1237 update_option('wp-reset-snapshots', $snapshots);
1238
1239 return $snapshot;
1240 } // create_snapshot
1241
1242
1243 /**
1244 * Delete snapshot metadata and tables from DB
1245 *
1246 * @param string $uid Snapshot unique 6-char ID.
1247 *
1248 * @return bool|WP_Error True on success, or error object on fail.
1249 */
1250 function do_delete_snapshot($uid = '') {
1251 global $wpdb;
1252 $snapshots = $this->get_snapshots();
1253
1254 if (strlen($uid) != 6) {
1255 return new WP_Error(1, 'Invalid UID format.');
1256 }
1257
1258 if (!isset($snapshots[$uid])) {
1259 return new WP_Error(1, 'Unknown snapshot ID.');
1260 }
1261
1262 $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%')));
1263 foreach ($tables as $table) {
1264 $wpdb->query('DROP TABLE IF EXISTS ' . $table);
1265 }
1266
1267 unset($snapshots[$uid]);
1268 update_option('wp-reset-snapshots', $snapshots);
1269
1270 return true;
1271 } // delete_snapshot
1272
1273
1274 /**
1275 * Exports snapshot as SQL dump; saved in gzipped file in WP_CONTENT folder.
1276 *
1277 * @param string $uid Snapshot unique 6-char ID.
1278 *
1279 * @return string|WP_Error Export base filename, or error object on fail.
1280 */
1281 function do_export_snapshot($uid = '') {
1282 global $wpdb;
1283 $snapshots = $this->get_snapshots();
1284
1285 if (strlen($uid) != 6) {
1286 return new WP_Error(1, 'Invalid snapshot ID format.');
1287 }
1288
1289 if (!isset($snapshots[$uid])) {
1290 return new WP_Error(1, 'Unknown snapshot ID.');
1291 }
1292
1293 require_once $this->plugin_dir . 'libs/dumper.php';
1294
1295 try {
1296 $world_dumper = Shuttle_Dumper::create(array(
1297 'host' => DB_HOST,
1298 'username' => DB_USER,
1299 'password' => DB_PASSWORD,
1300 'db_name' => DB_NAME,
1301 ));
1302
1303 $folder = wp_mkdir_p(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder);
1304 if (!$folder) {
1305 return new WP_Error(1, 'Unable to create wp-content/' . $this->snapshots_folder . '/ folder.');
1306 }
1307
1308 $world_dumper->dump(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder . '/wp-reset-snapshot-' . $uid . '.sql.gz', $uid . '_');
1309 } catch(Shuttle_Exception $e) {
1310 return new WP_Error(1, "Couldn't dump snapshot: " . $e->getMessage());
1311 }
1312
1313 return 'wp-reset-snapshot-' . $uid . '.sql.gz';
1314 } // export_snapshot
1315
1316
1317 /**
1318 * Replace current tables with ones in snapshot.
1319 *
1320 * @param string $uid Snapshot unique 6-char ID.
1321 *
1322 * @return bool|WP_Error True on success, or error object on fail.
1323 */
1324 function do_restore_snapshot($uid = '') {
1325 global $wpdb;
1326 $new_tables = array();
1327 $snapshots = $this->get_snapshots();
1328
1329 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
1330 return $res;
1331 }
1332
1333 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1334 if (is_array($table_status)) {
1335 foreach ($table_status as $index => $table) {
1336 if (0 !== stripos($table->Name, $uid . '_')) {
1337 continue;
1338 }
1339 if (empty($table->Engine)) {
1340 continue;
1341 }
1342
1343 $new_tables[] = $table->Name;
1344 } // foreach
1345 } else {
1346 return new WP_Error(1, 'Can\'t get table status data.');
1347 }
1348
1349 foreach ($table_status as $index => $table) {
1350 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1351 continue;
1352 }
1353 if (empty($table->Engine)) {
1354 continue;
1355 }
1356
1357 $wpdb->query('DROP TABLE ' . $table->Name);
1358 } // foreach
1359
1360 // copy snapshot tables to original name
1361 foreach ($new_tables as $table) {
1362 $new_name = str_replace($uid . '_', '', $table);
1363
1364 $wpdb->query('CREATE TABLE ' . $new_name . ' LIKE ' . $table);
1365 $wpdb->query('INSERT ' . $new_name . ' SELECT * FROM ' . $table);
1366 }
1367
1368 wp_cache_flush();
1369 update_option('wp-reset', $this->options);
1370 update_option('wp-reset-snapshots', $snapshots);
1371
1372 return true;
1373 } // restore_snapshot
1374
1375
1376 /**
1377 * Verifies snapshot integrity by comparing metadata and data in DB
1378 *
1379 * @param string $uid Snapshot unique 6-char ID.
1380 *
1381 * @return bool|WP_Error True on success, or error object on fail.
1382 */
1383 function verify_snapshot_integrity($uid) {
1384 global $wpdb;
1385 $tbl_core = $tbl_custom = 0;
1386 $snapshots = $this->get_snapshots();
1387
1388 if (strlen($uid) != 6) {
1389 return new WP_Error(1, 'Invalid snapshot ID format.');
1390 }
1391
1392 if (!isset($snapshots[$uid])) {
1393 return new WP_Error(1, 'Unknown snapshot ID.');
1394 }
1395
1396 $snapshot = $snapshots[$uid];
1397
1398 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1399 if (is_array($table_status)) {
1400 foreach ($table_status as $index => $table) {
1401 if (0 !== stripos($table->Name, $uid . '_')) {
1402 continue;
1403 }
1404 if (empty($table->Engine)) {
1405 continue;
1406 }
1407
1408 if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
1409 $tbl_core++;
1410 } else {
1411 $tbl_custom++;
1412 }
1413 } // foreach
1414
1415 if ($tbl_core != $snapshot['tbl_core'] || $tbl_custom != $snapshot['tbl_custom']) {
1416 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.');
1417 }
1418 } else {
1419 return new WP_Error(1, 'Can\'t get table status data.');
1420 }
1421
1422 return true;
1423 } // verify_snapshot_integrity
1424
1425
1426 /**
1427 * Compares a selected snapshot with the current table set in DB
1428 *
1429 * @param string $uid Snapshot unique 6-char ID.
1430 *
1431 * @return string|WP_Error Formatted table with details on success, or error object on fail.
1432 */
1433 function do_compare_snapshots($uid) {
1434 global $wpdb;
1435 $tbl_core = $tbl_custom = 0;
1436 $current = $snapshot = array();
1437 $out = $out2 = $out3 = '';
1438
1439 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
1440 return $res;
1441 }
1442
1443 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1444 foreach ($table_status as $index => $table) {
1445 if (empty($table->Engine)) {
1446 continue;
1447 }
1448
1449 if (0 !== stripos($table->Name, $uid . '_') && 0 !== stripos($table->Name, $wpdb->prefix)) {
1450 continue;
1451 }
1452
1453 $info = array();
1454 $info['rows'] = $table->Rows;
1455 $info['size_data'] = $table->Data_length;
1456 $info['size_index'] = $table->Index_length;
1457 $schema = $wpdb->get_row('SHOW CREATE TABLE ' . $table->Name, ARRAY_N);
1458 $info['schema'] = $schema[1];
1459 $info['engine'] = $table->Engine;
1460 $info['fullname'] = $table->Name;
1461 $basename = str_replace(array($uid . '_'), array(''), $table->Name);
1462 $info['basename'] = $basename;
1463 $info['corename'] = str_replace(array($wpdb->prefix), array(''), $basename);
1464 $info['uid'] = $uid;
1465
1466 if (0 === stripos($table->Name, $uid . '_')) {
1467 $snapshot[$basename] = $info;
1468 }
1469
1470 if (0 === stripos($table->Name, $wpdb->prefix)) {
1471 $info['uid'] = '';
1472 $current[$basename] = $info;
1473 }
1474 } // foreach
1475
1476 $in_both = array_keys(array_intersect_key($current, $snapshot));
1477 $in_current_only = array_diff_key($current, $snapshot);
1478 $in_snapshot_only = array_diff_key($snapshot, $current);
1479
1480 $out .= '<br><br>';
1481 foreach ($in_current_only as $table) {
1482 $out .= '<div class="wpr-table-container in-current-only" data-table="' . $table['basename'] . '">';
1483 $out .= '<table>';
1484 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
1485 $out .= '<td><b>' . $table['fullname'] . '</b></td>';
1486 $out .= '<td>table is not present in snapshot<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1487 $out .= '</tr>';
1488 $out .= '<tr class="hidden">';
1489 $out .= '<td>';
1490 $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>';
1491 $out .= '<pre>' . $table['schema'] . '</pre>';
1492 $out .= '</td>';
1493 $out .= '<td>&nbsp;</td>';
1494 $out .= '</tr>';
1495 $out .= '</table>';
1496 $out .= '</div>';
1497 } // foreach in current only
1498
1499 foreach ($in_snapshot_only as $table) {
1500 $out .= '<div class="wpr-table-container in-snapshot-only" data-table="' . $table['basename'] . '">';
1501 $out .= '<table>';
1502 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
1503 $out .= '<td>table is not present in current tables</td>';
1504 $out .= '<td><b>' . $table['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1505 $out .= '</tr>';
1506 $out .= '<tr class="hidden">';
1507 $out .= '<td>&nbsp;</td>';
1508 $out .= '<td>';
1509 $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>';
1510 $out .= '<pre>' . $table['schema'] . '</pre>';
1511 $out .= '</td>';
1512 $out .= '</tr>';
1513 $out .= '</table>';
1514 $out .= '</div>';
1515 } // foreach in snapshot only
1516
1517 foreach ($in_both as $tablename) {
1518 $tbl_current = $current[$tablename];
1519 $tbl_snapshot = $snapshot[$tablename];
1520
1521 $schema1 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_current['schema'], 1);
1522 $schema2 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_snapshot['schema'], 1);
1523 $tbl_snapshot['tmp_schema'] = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $tbl_snapshot['schema']);
1524 $schema2 = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $schema2);
1525
1526 if ($tbl_current['rows'] == $tbl_snapshot['rows'] && $tbl_current['schema'] == $tbl_snapshot['tmp_schema']) {
1527 $out3 .= '<div class="wpr-table-container identical" data-table="' . $tablename . '">';
1528 $out3 .= '<table>';
1529 $out3 .= '<tr title="Click to show/hide more info" class="wpr-table-match header-row">';
1530 $out3 .= '<td><b>' . $tbl_current['fullname'] . '</b></td>';
1531 $out3 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1532 $out3 .= '</tr>';
1533 $out3 .= '<tr class="hidden">';
1534 $out3 .= '<td>';
1535 $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>';
1536 $out3 .= '<pre>' . $tbl_current['schema'] . '</pre>';
1537 $out3 .= '</td>';
1538 $out3 .= '<td>';
1539 $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>';
1540 $out3 .= '<pre>' . $tbl_snapshot['schema'] . '</pre>';
1541 $out3 .= '</td>';
1542 $out3 .= '</tr>';
1543 $out3 .= '</table>';
1544 $out3 .= '</div>';
1545 } elseif ($schema1 != $schema2) {
1546 require_once $this->plugin_dir . 'libs/diff.php';
1547 require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
1548 $diff = new Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
1549 $renderer = new Diff_Renderer_Html_SideBySide;
1550
1551 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
1552 $out2 .= '<table>';
1553 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
1554 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> table schemas do not match</td>';
1555 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> table schemas do not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1556 $out2 .= '</tr>';
1557 $out2 .= '<tr class="hidden">';
1558 $out2 .= '<td>';
1559 $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>';
1560 $out2 .= '</td>';
1561 $out2 .= '<td>';
1562 $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>';
1563 $out2 .= '</td>';
1564 $out2 .= '</tr>';
1565 $out2 .= '<tr class="hidden">';
1566 $out2 .= '<td colspan="2" class="no-padding">';
1567 $out2 .= $diff->Render($renderer);
1568 $out2 .= '</td>';
1569 $out2 .= '</tr>';
1570 $out2 .= '</table>';
1571 $out2 .= '</div>';
1572 } else {
1573 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
1574 $out2 .= '<table>';
1575 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
1576 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> data in tables does not match</td>';
1577 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> data in tables does not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
1578 $out2 .= '</tr>';
1579 $out2 .= '<tr class="hidden">';
1580 $out2 .= '<td>';
1581 $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>';
1582 $out2 .= '</td>';
1583 $out2 .= '<td>';
1584 $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>';
1585 $out2 .= '</td>';
1586 $out2 .= '</tr>';
1587
1588 $out2 .= '<tr class="hidden">';
1589 $out2 .= '<td colspan="2">';
1590 if ($tbl_current['corename'] == 'options') {
1591 $ss_prefix = $tbl_snapshot['uid'] . '_' . $wpdb->prefix;
1592 $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;");
1593 $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;");
1594 $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;");
1595 $out2 .= '<table class="table_diff">';
1596 $out2 .= '<tr><td style="width: 100px;"><b>Option Name</b></td><td><b>Current Value</b></td><td><b>Snapshot Value</b></td></tr>';
1597 foreach ($diff_rows as $row) {
1598 $out2 .= '<tr>';
1599 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
1600 $out2 .= '<td>' . (empty($row->current_value)? '<i>empty</i>': $row->current_value) . '</td>';
1601 $out2 .= '<td>' . (empty($row->snapshot_value)? '<i>empty</i>': $row->snapshot_value) . '</td>';
1602 $out2 .= '</tr>';
1603 } // foreach
1604 foreach ($only_current as $row) {
1605 $out2 .= '<tr>';
1606 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
1607 $out2 .= '<td>' . (empty($row->current_value)? '<i>empty</i>': $row->current_value) . '</td>';
1608 $out2 .= '<td><i>not found in snapshot</i></td>';
1609 $out2 .= '</tr>';
1610 } // foreach
1611 foreach ($only_current as $row) {
1612 $out2 .= '<tr>';
1613 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
1614 $out2 .= '<td><i>not found in current tables</i></td>';
1615 $out2 .= '<td>' . (empty($row->snapshot_value)? '<i>empty</i>': $row->snapshot_value) . '</td>';
1616 $out2 .= '</tr>';
1617 } // foreach
1618 $out2 .= '</table>';
1619 } else {
1620 $out2 .= '<p class="textcenter">Detailed data diff is not available for this table.</p>';
1621 }
1622 $out2 .= '</td>';
1623 $out2 .= '</tr>';
1624
1625 $out2 .= '</table>';
1626 $out2 .= '</div>';
1627 }
1628 } // foreach in both
1629
1630 return $out . $out2 . $out3;
1631 } // do_compare_snapshots
1632
1633
1634 /**
1635 * Generates a unique 6-char snapshot ID; verified non-existing
1636 *
1637 * @return string
1638 */
1639 function generate_snapshot_uid() {
1640 global $wpdb;
1641 $snapshots = $this->get_snapshots();
1642 $cnt = 0;
1643 $uid = false;
1644
1645 do {
1646 $cnt++;
1647 $uid = sprintf('%06x', mt_rand(0, 0xFFFFFF));
1648
1649 $verify_db = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array('%' . $uid . '%')));
1650 } while (!empty($verify_db) && isset($snapshots[$uid]) && $cnt < 30);
1651
1652 if ($cnt == 30) {
1653 $uid = false;
1654 }
1655
1656 return $uid;
1657 } // generate_snapshot_uid
1658
1659
1660 /**
1661 * Clean up on uninstall; no action on deactive at the moment
1662 *
1663 * @return null
1664 */
1665 static function uninstall() {
1666 delete_option('wp-reset');
1667 delete_option('wp-reset-snapshots');
1668 } // uninstall
1669
1670
1671 /**
1672 * Disabled; we use singleton pattern so magic functions need to be disabled
1673 *
1674 * @return null
1675 */
1676 private function __clone() {}
1677
1678
1679 /**
1680 * Disabled; we use singleton pattern so magic functions need to be disabled
1681 *
1682 * @return null
1683 */
1684 private function __sleep() {}
1685
1686
1687 /**
1688 * Disabled; we use singleton pattern so magic functions need to be disabled
1689 *
1690 * @return null
1691 */
1692 private function __wakeup() {}
1693 } // WP_Reset class
1694
1695
1696 // Create plugin instance and hook things up
1697 global $wp_reset;
1698 $wp_reset = WP_Reset::getInstance();
1699 add_action('plugins_loaded', array($wp_reset, 'load_textdomain'));
1700 register_uninstall_hook(__FILE__, array('WP_Reset', 'uninstall'));
1701