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

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