PluginProbe
WP Reset / 2.06
WP Reset v2.06
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 2.06, at wp-reset.php

3,193 lines 163.9 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 entire site or just selected parts while reserving the option to undo by using snapshots.
6 Version: 2.06
7 Requires at least: 4.0
8 Requires PHP: 5.2
9 Tested up to: 6.9
10 License: GPLv2 or later
11 Author: WebFactory Ltd
12 Author URI: https://www.webfactoryltd.com/
13 Text Domain: wp-reset
14
15 Copyright 2015 - 2025 WebFactory Ltd (email: wpreset@webfactoryltd.com)
16
17 This program is free software; you can redistribute it and/or modify
18 it under the terms of the GNU General Public License, version 2, as
19 published by the Free Software Foundation.
20
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 */
30
31 // include only file
32 if (!defined('ABSPATH')) {
33 die('Do not open this file directly.');
34 }
35
36
37 define('WP_RESET_FILE', __FILE__);
38
39 require_once dirname(__FILE__) . '/wp-reset-utility.php';
40 require_once dirname(__FILE__) . '/wp-reset-licensing.php';
41
42 require_once dirname(__FILE__) . '/wf-flyout/wf-flyout.php';
43 new wf_flyout(__FILE__);
44
45 // load WP-CLI commands, if needed
46 if (defined('WP_CLI') && WP_CLI) {
47 require_once dirname(__FILE__) . '/wp-reset-cli.php';
48 }
49
50
51 class WP_Reset
52 {
53 protected static $instance = null;
54 public $version = 0;
55 public $plugin_url = '';
56 public $plugin_dir = '';
57 public $snapshots_folder = 'wp-reset-snapshots-export';
58 protected $options = array();
59 private $delete_count = 0;
60 private $licensing_servers = array('https://dashboard.wpreset.com/api/');
61 public $core_tables = array('commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'term_relationships', 'term_taxonomy', 'termmeta', 'terms', 'usermeta', 'users');
62 private $license = null;
63 public $filesystem_initialized = false;
64
65 /**
66 * Creates a new WP_Reset object and implements singleton
67 *
68 * @return WP_Reset
69 */
70 static function getInstance()
71 {
72 if (!is_a(self::$instance, 'WP_Reset')) {
73 self::$instance = new WP_Reset();
74 }
75
76 return self::$instance;
77 } // getInstance
78
79
80 /**
81 * Initialize properties, hook to filters and actions
82 *
83 * @return null
84 */
85 private function __construct()
86 {
87 $this->version = $this->get_plugin_version();
88 $this->plugin_dir = plugin_dir_path(__FILE__);
89 $this->plugin_url = plugin_dir_url(__FILE__);
90 $this->load_options();
91
92 $this->license = new WF_Licensing(array(
93 'prefix' => 'wpr',
94 'licensing_servers' => $this->licensing_servers,
95 'version' => $this->version,
96 'plugin_file' => __FILE__,
97 'plugin_page' => 'tools_page_wp-reset',
98 'skip_hooks' => false,
99 'debug' => false,
100 'js_folder' => plugin_dir_url(__FILE__) . '/js/'
101 ));
102
103 add_action('admin_menu', array($this, 'admin_menu'));
104 add_action('admin_init', array($this, 'do_all_actions'));
105 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
106 add_action('admin_action_wpr_dismiss_notice', array($this, 'action_dismiss_notice'));
107 add_action('wp_ajax_wp_reset_dismiss_notice', array($this, 'ajax_dismiss_notice'));
108 add_action('wp_ajax_wp_reset_run_tool', array($this, 'ajax_run_tool'));
109 add_action('admin_print_scripts', array($this, 'remove_admin_notices'));
110 add_action('admin_action_wpr_install_wpfssl', array($this, 'install_wpfssl'));
111
112 add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'plugin_action_links'));
113 add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
114 add_filter('admin_footer_text', array($this, 'admin_footer_text'));
115
116 $this->core_tables = array_map(function ($tbl) {
117 global $wpdb;
118 return $wpdb->prefix . $tbl;
119 }, $this->core_tables);
120 } // __construct
121
122
123 /**
124 * Get plugin version from file header
125 *
126 * @return string
127 */
128 function get_plugin_version()
129 {
130 $plugin_data = get_file_data(__FILE__, array('version' => 'Version'), 'plugin');
131
132 return $plugin_data['version'];
133 } // get_plugin_version
134
135
136 /**
137 * Load and prepare the options array
138 * If needed create a new DB entry
139 *
140 * @return array
141 */
142 private function load_options()
143 {
144 $options = get_option('wp-reset', array());
145 $change = false;
146
147 if (!isset($options['meta'])) {
148 $options['meta'] = array('first_version' => $this->version, 'first_install' => current_time('timestamp', true), 'reset_count' => 0);
149 $change = true;
150 }
151 if (!isset($options['dismissed_notices'])) {
152 $options['dismissed_notices'] = array();
153 $change = true;
154 }
155 if (!isset($options['last_run'])) {
156 $options['last_run'] = array();
157 $change = true;
158 }
159 if (!isset($options['options'])) {
160 $options['options'] = array();
161 $change = true;
162 }
163 if ($change) {
164 update_option('wp-reset', $options, true);
165 }
166
167 $this->options = $options;
168 return $options;
169 } // load_options
170
171
172 /**
173 * Get meta part of plugin options
174 *
175 * @return array
176 */
177 function get_meta()
178 {
179 return $this->options['meta'];
180 } // get_meta
181
182
183 /**
184 * Get all dismissed notices, or check for one specific notice
185 *
186 * @param string $notice_name Optional. Check if specified notice is dismissed.
187 *
188 * @return bool|array
189 */
190 function get_dismissed_notices($notice_name = '')
191 {
192 $notices = $this->options['dismissed_notices'];
193
194 if (empty($notice_name)) {
195 return $notices;
196 } else {
197 if (empty($notices[$notice_name])) {
198 return false;
199 } else {
200 return true;
201 }
202 }
203 } // get_dismissed_notices
204
205
206 /**
207 * Get options part of plugin options
208 *
209 * @param string $key Optional.
210 *
211 * @return array
212 */
213 function get_options()
214 {
215 return $this->options['options'];
216 } // get_options
217
218
219 /**
220 * Update specified plugin options key
221 *
222 * @param string $key Data to save.
223 * @param string $data Option key.
224 *
225 * @return bool
226 */
227 function update_options($key, $data)
228 {
229 if (false === in_array($key, array('meta', 'license', 'dismissed_notices', 'options'))) {
230 user_error('Unknown options key.', E_USER_ERROR);
231 return false;
232 }
233
234 $this->options[$key] = $data;
235 $tmp = update_option('wp-reset', $this->options);
236
237 return $tmp;
238 } // update_options
239
240
241 /**
242 * Add plugin menu entry under Tools menu
243 *
244 * @return null
245 */
246 function admin_menu()
247 {
248 add_management_page(__('WP Reset', 'wp-reset'), __('WP Reset', 'wp-reset'), 'administrator', 'wp-reset', array($this, 'plugin_page'));
249 } // admin_menu
250
251
252 /**
253 * Dismiss notice via AJAX call
254 *
255 * @return null
256 */
257 function ajax_dismiss_notice()
258 {
259 check_ajax_referer('wp-reset_dismiss_notice');
260
261 if (!current_user_can('manage_options')) {
262 wp_send_json_error(__('You are not allowed to run this action.', 'wp-reset'));
263 }
264
265 if (!isset($_GET['notice_name'])) {
266 wp_send_json_error(__('Unknown Notice.', 'wp-reset'));
267 }
268
269 $notice_name = trim(sanitize_text_field(wp_unslash($_GET['notice_name'])));
270 if (!$this->dismiss_notice($notice_name)) {
271 wp_send_json_error(__('Notice is already dismissed.', 'wp-reset'));
272 } else {
273 wp_send_json_success();
274 }
275 } // ajax_dismiss_notice
276
277
278 /**
279 * Dismiss notice via admin action
280 *
281 * @return null
282 */
283 function action_dismiss_notice()
284 {
285 if (!isset($_GET['notice']) || !isset($_GET['_wpnonce']) || false == wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'wpr_dismiss_notice')) {
286 wp_die('Please reload the page and try again.');
287 }
288
289 if (empty($_GET['notice'])) {
290 wp_safe_redirect(admin_url());
291 exit;
292 }
293
294 $notice_name = trim(sanitize_text_field(wp_unslash($_GET['notice'])));
295 $this->dismiss_notice($notice_name);
296
297 if (!empty($_GET['redirect'])) {
298 wp_safe_redirect(sanitize_url(wp_unslash($_GET['redirect'])));
299 } else {
300 wp_safe_redirect(admin_url());
301 }
302
303 exit;
304 } // action_dismiss_notice
305
306
307 /**
308 * Dismiss notice by adding it to dismissed_notices options array
309 *
310 * @param string $notice_name Notice to dismiss.
311 *
312 * @return bool
313 */
314 function dismiss_notice($notice_name)
315 {
316 if ($this->get_dismissed_notices($notice_name)) {
317 return false;
318 } else {
319 $notices = $this->get_dismissed_notices();
320 $notices[$notice_name] = true;
321 $this->update_options('dismissed_notices', $notices);
322 return true;
323 }
324 } // dismiss_notice
325
326
327 /**
328 * Returns all WP pointers
329 *
330 * @return array
331 */
332 function get_pointers()
333 {
334 $pointers = array();
335
336 $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="' . esc_url(admin_url('tools.php?page=wp-reset')) . '">Tools - WP Reset</a> to access resetting tools and start developing &amp; debugging faster.');
337
338 return $pointers;
339 } // get_pointers
340
341
342 /**
343 * Enqueue CSS and JS files
344 *
345 * @return null
346 */
347 function admin_enqueue_scripts($hook)
348 {
349 // welcome pointer is shown on all pages except WPR to admins, until dismissed
350 $pointers = $this->get_pointers();
351 $dismissed_notices = $this->get_dismissed_notices();
352
353 foreach ($dismissed_notices as $notice_name => $tmp) {
354 if ($tmp) {
355 unset($pointers[$notice_name]);
356 }
357 } // foreach
358
359 if (!empty($pointers) && !$this->is_plugin_page() && current_user_can('manage_options')) {
360 $pointers['_nonce_dismiss_pointer'] = wp_create_nonce('wp-reset_dismiss_notice');
361
362 wp_enqueue_style('wp-pointer');
363
364 wp_enqueue_script('wp-reset-pointers', $this->plugin_url . 'js/wp-reset-pointers.js', array('jquery'), $this->version, true);
365 wp_enqueue_script('wp-pointer');
366 wp_localize_script('wp-pointer', 'wp_reset_pointers', $pointers);
367 }
368
369 // exit early if not on WP Reset page
370 if (!$this->is_plugin_page()) {
371 return;
372 }
373
374 $js_localize = array(
375 'undocumented_error' => __('An undocumented error has occurred. Please refresh the page and try again.', 'wp-reset'),
376 'documented_error' => __('An error has occurred.', 'wp-reset'),
377 'plugin_name' => __('WP Reset', 'wp-reset'),
378 'settings_url' => admin_url('tools.php?page=wp-reset'),
379 'wpfssl_install_url' => add_query_arg(array('action' => 'wpr_install_wpfssl', '_wpnonce' => wp_create_nonce('install_wpfssl'), 'rnd' => wp_rand()), admin_url('admin.php')),
380 'icon_url' => $this->plugin_url . 'img/wp-reset-icon.png',
381 'invalid_confirmation' => __('Please type "reset" in the confirmation field.', 'wp-reset'),
382 'invalid_confirmation_title' => __('Invalid confirmation', 'wp-reset'),
383 'cancel_button' => __('Cancel', 'wp-reset'),
384 'ok_button' => __('OK', 'wp-reset'),
385 'confirm_button' => __('Reset WordPress', 'wp-reset'),
386 'confirm_title' => __('Are you sure you want to proceed?', 'wp-reset'),
387 'confirm_title_reset' => __('Are you sure you want to reset the site?', 'wp-reset'),
388 'confirm1' => __('Clicking "Reset WordPress" will reset your site to default values. All content will be lost. Always <a href="#" class="create-new-snapshot" data-description="Before resetting the site">create a snapshot</a> if you want to be able to undo.</b>', 'wp-reset'),
389 'confirm2' => __('Click "Cancel" to abort.', 'wp-reset'),
390 'doing_reset' => __('Resetting in progress. Please wait.', 'wp-reset'),
391 'snapshot_success' => __('Snapshot created', 'wp-reset'),
392 'snapshot_wait' => __('Creating snapshot. Please wait.', 'wp-reset'),
393 'snapshot_confirm' => __('Create snapshot', 'wp-reset'),
394 'snapshot_placeholder' => __('Snapshot name or brief description, ie: before plugin install', 'wp-reset'),
395 'snapshot_text' => __('Enter snapshot name or brief description', 'wp-reset'),
396 'snapshot_title' => __('Create a new snapshot', 'wp-reset'),
397 'nonce_dismiss_notice' => wp_create_nonce('wp-reset_dismiss_notice'),
398 'activating' => __('Activating', 'wp-reset'),
399 'deactivating' => __('Deactivating', 'wp-reset'),
400 'deleting' => __('Deleting', 'wp-reset'),
401 'installing' => __('Installing', 'wp-reset'),
402 'activate_failed' => __('Could not activate', 'wp-reset'),
403 'deactivate_failed' => __('Could not deactivate', 'wp-reset'),
404 'delete_failed' => __('Could not delete', 'wp-reset'),
405 'install_failed' => __('Could not install', 'wp-reset'),
406 'install_failed_existing' => __('is already installed', 'wp-reset'),
407 'nonce_run_tool' => wp_create_nonce('wp-reset_run_tool'),
408 'nonce_do_reset' => wp_create_nonce('wp-reset_do_reset'),
409 );
410
411 wp_enqueue_style('plugin-install');
412 wp_enqueue_style('wp-jquery-ui-dialog');
413 wp_enqueue_style('wp-reset', $this->plugin_url . 'css/wp-reset.css', array(), $this->version);
414 wp_enqueue_style('wp-reset-sweetalert2', $this->plugin_url . 'css/sweetalert2.min.css', array(), $this->version);
415 wp_enqueue_style('wp-reset-tooltipster', $this->plugin_url . 'css/tooltipster.bundle.min.css', array(), $this->version);
416
417 wp_enqueue_script('plugin-install');
418 wp_enqueue_script('jquery-ui-tabs');
419 wp_enqueue_script('jquery-ui-dialog');
420 wp_enqueue_script('wp-reset-sweetalert2', $this->plugin_url . 'js/wp-reset-libs.min.js', array('jquery'), $this->version, true);
421 wp_enqueue_script('wp-reset', $this->plugin_url . 'js/wp-reset.js', array('jquery'), $this->version, true);
422 wp_localize_script('wp-reset', 'wp_reset', $js_localize);
423
424 add_thickbox();
425
426 // fix for aggressive plugins that include their CSS on all pages
427 wp_dequeue_style('uiStyleSheet');
428 wp_dequeue_style('wpcufpnAdmin');
429 wp_dequeue_style('unifStyleSheet');
430 wp_dequeue_style('wpcufpn_codemirror');
431 wp_dequeue_style('wpcufpn_codemirrorTheme');
432 wp_dequeue_style('collapse-admin-css');
433 wp_dequeue_style('jquery-ui-css');
434 wp_dequeue_style('tribe-common-admin');
435 wp_dequeue_style('file-manager__jquery-ui-css');
436 wp_dequeue_style('file-manager__jquery-ui-css-theme');
437 wp_dequeue_style('wpmegmaps-jqueryui');
438 wp_dequeue_style('wp-botwatch-css');
439 wp_dequeue_style('uap_main_admin_style');
440 wp_dequeue_style('uap_font_awesome');
441 wp_dequeue_style('uap_jquery-ui.min.css');
442 } // admin_enqueue_scripts
443
444
445 /**
446 * Remove all WP notices on WPR page
447 *
448 * @return null
449 */
450 function remove_admin_notices()
451 {
452 if (!$this->is_plugin_page()) {
453 return false;
454 }
455
456 global $wp_filter;
457 unset($wp_filter['user_admin_notices'], $wp_filter['admin_notices']);
458 } // remove_admin_notices
459
460
461 /**
462 * Check if WP-CLI is available and running
463 *
464 * @return bool
465 */
466 static function is_cli_running()
467 {
468 if (!is_null($value = apply_filters('wp-reset-override-is-cli-running', null))) {
469 return (bool) $value;
470 }
471
472 if (defined('WP_CLI') && WP_CLI) {
473 return true;
474 } else {
475 return false;
476 }
477 } // is_cli_running
478
479
480 /**
481 * Check if given plugin is installed
482 *
483 * @param [string] $slug Plugin slug
484 * @return boolean
485 */
486 function is_plugin_installed($slug)
487 {
488 if (!function_exists('get_plugins')) {
489 require_once ABSPATH . 'wp-admin/includes/plugin.php';
490 }
491 $all_plugins = get_plugins();
492
493 if (!empty($all_plugins[$slug])) {
494 return true;
495 } else {
496 return false;
497 }
498 } // is_plugin_installed
499
500
501 /**
502 * Deletes all transients.
503 *
504 * @return int Number of deleted transient DB entries
505 */
506 function do_delete_transients()
507 {
508 global $wpdb;
509
510 // phpcs:ignore db call warning as we are using uncommon queries
511 $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'"); // phpcs:ignore
512
513 wp_cache_flush();
514
515 do_action('wp_reset_delete_transients', $count);
516
517 return $count;
518 } // do_delete_transients
519
520
521 /**
522 * Purge all cache for popular caching plugins
523 *
524 * @return bool true
525 */
526 function do_purge_cache()
527 {
528 global $wp_reset;
529
530 wp_cache_flush();
531 $wp_reset->do_delete_transients();
532
533 if (function_exists('w3tc_flush_all')) {
534 w3tc_flush_all();
535 }
536 if (function_exists('wp_cache_clear_cache')) {
537 wp_cache_clear_cache();
538 }
539 if (method_exists('LiteSpeed_Cache_API', 'purge_all')) {
540 LiteSpeed_Cache_API::purge_all();
541 }
542 if (class_exists('Endurance_Page_Cache')) {
543 $epc = new Endurance_Page_Cache;
544 $epc->purge_all();
545 }
546 if (class_exists('SG_CachePress_Supercacher') && method_exists('SG_CachePress_Supercacher', 'purge_cache')) {
547 SG_CachePress_Supercacher::purge_cache(true);
548 }
549 if (class_exists('SiteGround_Optimizer\Supercacher\Supercacher')) {
550 SiteGround_Optimizer\Supercacher\Supercacher::purge_cache();
551 }
552 if (isset($GLOBALS['wp_fastest_cache']) && method_exists($GLOBALS['wp_fastest_cache'], 'deleteCache')) {
553 $GLOBALS['wp_fastest_cache']->deleteCache(true);
554 }
555 if (is_callable(array('Swift_Performance_Cache', 'clear_all_cache'))) {
556 Swift_Performance_Cache::clear_all_cache();
557 }
558 if (is_callable(array('Hummingbird\WP_Hummingbird', 'flush_cache'))) {
559 Hummingbird\WP_Hummingbird::flush_cache(true, false);
560 }
561
562 do_action('wp_reset_purge_cache');
563
564 return true;
565 } // do_purge_cache
566
567
568 /**
569 * Resets all theme options (mods).
570 *
571 * @param bool $all_themes Delete mods for all themes or just the current one
572 *
573 * @return int Number of deleted mod DB entries
574 */
575 function do_reset_theme_options($all_themes = true)
576 {
577 global $wpdb;
578
579 // phpcs:ignore db call warning as we are using uncommon queries
580 $count = $wpdb->query($wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE %s OR option_name LIKE %s", array('mods\_%', 'theme_mods\_%'))); // phpcs:ignore
581
582 do_action('wp_reset_reset_theme_options', $count);
583
584 return $count;
585 } // do_reset_theme_options
586
587
588 /**
589 * Deletes all files in uploads folder.
590 *
591 * @return int Number of deleted files and folders.
592 */
593 function do_delete_uploads()
594 {
595 global $wp_filesystem;
596 $this->wp_init_filesystem();
597
598 $upload_dir = wp_get_upload_dir();
599 $this->delete_count = 0;
600
601 if ( $wp_filesystem->is_dir( $upload_dir['basedir'] ) ) {
602
603 // Get all files and directories in the folder.
604 $files = $wp_filesystem->dirlist( $upload_dir['basedir'] );
605
606 // Loop through the files and delete them.
607 foreach ( $files as $file => $details ) {
608 $file_path = trailingslashit( $upload_dir['basedir'] ) . $file;
609 $wp_filesystem->delete( $file_path, true );
610 $this->delete_count++;
611 }
612 }
613
614 do_action('wp_reset_delete_uploads', $this->delete_count);
615
616 return $this->delete_count;
617 } // do_delete_uploads
618
619 /**
620 * Deactivate all plugins
621 *
622 * @param array keep_wp_reset - Keep WP Reset active and installed, silent_deactivate - Skip individual plugin deactivation functions when deactivating
623 *
624 * @return int Number of deactivated plugins.
625 */
626 function do_deactivate_plugins($params = array())
627 {
628 if (!function_exists('get_plugins')) {
629 require_once ABSPATH . 'wp-admin/includes/plugin.php';
630 }
631 if (!function_exists('request_filesystem_credentials')) {
632 require_once ABSPATH . 'wp-admin/includes/file.php';
633 }
634
635 $wp_reset_basename = plugin_basename(WP_RESET_FILE);
636 $params = shortcode_atts(array('keep_wp_reset' => true, 'silent_deactivate' => false), (array) $params);
637
638 $active_plugins = (array) get_option('active_plugins', array());
639 if ($params['keep_wp_reset']) {
640 if (($key = array_search($wp_reset_basename, $active_plugins)) !== false) {
641 unset($active_plugins[$key]);
642 }
643 }
644
645 if (!empty($active_plugins)) {
646 deactivate_plugins($active_plugins, $params['silent_deactivate'], false);
647 }
648
649 do_action('wp_reset_deactivate_plugins', $active_plugins, $params);
650
651 return sizeof($active_plugins);
652 } // do_deactivate_plugins
653
654
655 /**
656 * Delete all plugins
657 *
658 * @param array keep_wp_reset - Keep WP Reset active and installed
659 *
660 * @return int Number of deleted plugins.
661 */
662 function do_delete_plugins($params = array())
663 {
664 if (!function_exists('get_plugins')) {
665 require_once ABSPATH . 'wp-admin/includes/plugin.php';
666 }
667 if (!function_exists('request_filesystem_credentials')) {
668 require_once ABSPATH . 'wp-admin/includes/file.php';
669 }
670
671 $wp_reset_basename = plugin_basename(WP_RESET_FILE);
672 $params = shortcode_atts(array('keep_wp_reset' => true), (array) $params);
673
674 $all_plugins = get_plugins();
675 if ($params['keep_wp_reset']) {
676 unset($all_plugins[$wp_reset_basename]);
677 }
678
679 if (!empty($all_plugins)) {
680 delete_plugins(array_keys($all_plugins));
681 }
682
683 do_action('wp_reset_delete_plugins', $all_plugins, $params);
684
685 return sizeof($all_plugins);
686 } // do_delete_plugins
687
688
689 /**
690 * Delete all themes
691 *
692 * @param bool $keep_default_theme Keep default theme
693 *
694 * @return int Number of deleted themes.
695 */
696 function do_delete_themes($keep_default_theme = true)
697 {
698 global $wp_version;
699
700 if (!function_exists('delete_theme')) {
701 require_once ABSPATH . 'wp-admin/includes/theme.php';
702 }
703
704 if (!function_exists('request_filesystem_credentials')) {
705 require_once ABSPATH . 'wp-admin/includes/file.php';
706 }
707
708 if (version_compare($wp_version, '5.0', '<') === true) {
709 $default_theme = 'twentyseventeen';
710 } else {
711 $default_theme = 'twentytwentyone';
712 }
713
714 $all_themes = wp_get_themes(array('errors' => null));
715
716 if (true == $keep_default_theme) {
717 unset($all_themes[$default_theme]);
718 }
719
720 foreach ($all_themes as $theme_slug => $theme_details) {
721 $res = delete_theme($theme_slug);
722 }
723
724 if (false == $keep_default_theme) {
725 update_option('template', '');
726 update_option('stylesheet', '');
727 update_option('current_theme', '');
728 }
729
730 do_action('wp_reset_delete_themes', $all_themes);
731
732 return sizeof($all_themes);
733 } // do_delete_themes
734
735
736 /**
737 * Truncate custom tables
738 *
739 * @return int Number of truncated tables.
740 */
741 function do_truncate_custom_tables()
742 {
743 global $wpdb;
744 $custom_tables = $this->get_custom_tables();
745
746 foreach ($custom_tables as $tbl) {
747 $wpdb->wpreset_custom_table = $tbl['name'];
748 // phpcs:ignore db call warning as we are using uncommon queries
749 $wpdb->query('SET foreign_key_checks = 0'); // phpcs:ignore
750 $wpdb->query("TRUNCATE TABLE " . $wpdb->wpreset_custom_table); // phpcs:ignore
751 } // foreach
752
753 do_action('wp_reset_truncate_custom_tables', $custom_tables);
754
755 return sizeof($custom_tables);
756 } // do_truncate_custom_tables
757
758
759 /**
760 * Drop custom tables
761 *
762 * @return int Number of dropped tables.
763 */
764 function do_drop_custom_tables()
765 {
766 global $wpdb;
767 $custom_tables = $this->get_custom_tables();
768
769 foreach ($custom_tables as $tbl) {
770 $wpdb->wpreset_custom_table = $tbl['name'];
771 // phpcs:ignore db call warning as we are using uncommon queries
772 $wpdb->query('SET foreign_key_checks = 0'); // phpcs:ignore
773 $wpdb->query("DROP TABLE IF EXISTS " . $wpdb->wpreset_custom_table); // phpcs:ignore
774 } // foreach
775
776 do_action('wp_reset_drop_custom_tables', $custom_tables);
777
778 return sizeof($custom_tables);
779 } // do_drop_custom_tables
780
781
782 /**
783 * Delete .htaccess file
784 *
785 * @return bool|WP_Error Action status.
786 */
787 function do_delete_htaccess()
788 {
789 global $wp_filesystem;
790
791 if (empty($wp_filesystem)) {
792 require_once ABSPATH . '/wp-admin/includes/file.php';
793 WP_Filesystem();
794 }
795
796 $htaccess_path = $this->get_htaccess_path();
797 clearstatcache();
798
799 do_action('wp_reset_delete_htaccess', $htaccess_path);
800
801 if (!$wp_filesystem->is_readable($htaccess_path)) {
802 return new WP_Error(1, 'Htaccess file does not exist; there\'s nothing to delete.');
803 }
804
805 if (!$wp_filesystem->is_writable($htaccess_path)) {
806 return new WP_Error(1, 'Htaccess file is not writable.');
807 }
808
809 if ($wp_filesystem->delete($htaccess_path, false, 'f')) {
810 return true;
811 } else {
812 return new WP_Error(1, 'Unknown error. Unable to delete htaccess file.');
813 }
814 } // do_delete_htaccess
815
816
817 /**
818 * Get .htaccess file path.
819 *
820 * @return string
821 */
822 function get_htaccess_path()
823 {
824 if (!function_exists('get_home_path')) {
825 require_once ABSPATH . 'wp-admin/includes/file.php';
826 }
827
828 if ($this->is_cli_running()) {
829 $_SERVER['SCRIPT_FILENAME'] = ABSPATH;
830 }
831
832 $filepath = get_home_path() . '.htaccess';
833
834 return $filepath;
835 } // get_htaccess_path
836
837
838 /**
839 * Run one tool via AJAX call
840 *
841 * @return null
842 */
843 function ajax_run_tool()
844 {
845 check_ajax_referer('wp-reset_run_tool');
846
847 if (!current_user_can('manage_options')) {
848 wp_send_json_error(__('You are not allowed to run this action.', 'wp-reset'));
849 }
850
851 if(!isset($_GET['tool'])){
852 wp_send_json_error(__('Unknown tool.', 'wp-reset'));
853 }
854
855 $tool = trim(sanitize_text_field(wp_unslash($_GET['tool'])));
856
857 if(isset($_GET['extra_data'])){
858 $extra_data = trim(sanitize_text_field(wp_unslash($_GET['extra_data'])));
859 } else {
860 $extra_data = '';
861 }
862
863 if ($tool == 'delete_transients') {
864 $cnt = $this->do_delete_transients();
865 wp_send_json_success($cnt);
866 } elseif ($tool == 'reset_theme_options') {
867 $cnt = $this->do_reset_theme_options(true);
868 wp_send_json_success($cnt);
869 } elseif ($tool == 'purge_cache') {
870 $this->do_purge_cache();
871 wp_send_json_success();
872 } elseif ($tool == 'delete_wp_cookies') {
873 wp_clear_auth_cookie();
874 wp_send_json_success();
875 } elseif ($tool == 'delete_themes') {
876 $cnt = $this->do_delete_themes(false);
877 wp_send_json_success($cnt);
878 } elseif ($tool == 'deactivate_plugins') {
879 $cnt = $this->do_deactivate_plugins($extra_data);
880 wp_send_json_success($cnt);
881 } elseif ($tool == 'delete_plugins') {
882 $cnt = $this->do_delete_plugins($extra_data);
883 wp_send_json_success($cnt);
884 } elseif ($tool == 'delete_uploads') {
885 $cnt = $this->do_delete_uploads();
886 wp_send_json_success($cnt);
887 } elseif ($tool == 'delete_htaccess') {
888 $tmp = $this->do_delete_htaccess();
889 if (is_wp_error($tmp)) {
890 wp_send_json_error($tmp->get_error_message());
891 } else {
892 wp_send_json_success($tmp);
893 }
894 } elseif ($tool == 'drop_custom_tables') {
895 $cnt = $this->do_drop_custom_tables();
896 wp_send_json_success($cnt);
897 } elseif ($tool == 'truncate_custom_tables') {
898 $cnt = $this->do_truncate_custom_tables();
899 wp_send_json_success($cnt);
900 } elseif ($tool == 'delete_snapshot') {
901 $res = $this->do_delete_snapshot($extra_data);
902 if (is_wp_error($res)) {
903 wp_send_json_error($res->get_error_message());
904 } else {
905 wp_send_json_success();
906 }
907 } elseif ($tool == 'download_snapshot') {
908 $res = $this->do_export_snapshot($extra_data);
909 if (is_wp_error($res)) {
910 wp_send_json_error($res->get_error_message());
911 } else {
912 $url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
913 wp_send_json_success($url);
914 }
915 } elseif ($tool == 'restore_snapshot') {
916 $res = $this->do_restore_snapshot($extra_data);
917 if (is_wp_error($res)) {
918 wp_send_json_error($res->get_error_message());
919 } else {
920 wp_send_json_success();
921 }
922 } elseif ($tool == 'compare_snapshots') {
923 $res = $this->do_compare_snapshots($extra_data);
924 if (is_wp_error($res)) {
925 wp_send_json_error($res->get_error_message());
926 } else {
927 wp_send_json_success($res);
928 }
929 } elseif ($tool == 'create_snapshot') {
930 $res = $this->do_create_snapshot($extra_data);
931 if (is_wp_error($res)) {
932 wp_send_json_error($res->get_error_message());
933 } else {
934 wp_send_json_success();
935 }
936 } elseif ($tool == 'get_table_details') {
937 $res = WP_Reset_Utility::get_table_details();
938 wp_send_json_success($res);
939 } elseif (
940 $tool == 'check_deactivate_plugin' ||
941 $tool == 'check_delete_plugin' ||
942 $tool == 'check_install_plugin' ||
943 $tool == 'check_activate_plugin'
944 ) {
945 if(!isset($_GET['slug'])){
946 wp_send_json_success('unknown plugin');
947 }
948
949 $slug = sanitize_text_field(wp_unslash($_GET['slug']));
950 $path = $this->get_plugin_path($slug);
951
952 if (false !== ($error = get_transient('wf_install_error_' . $slug))) {
953 delete_transient('wf_install_error_' . $slug);
954 wp_send_json_success($error);
955 }
956
957 if (false !== $path) {
958 $active_plugins = (array) get_option('active_plugins', array());
959 if (false !== array_search($path, $active_plugins)) {
960 wp_send_json_success('active');
961 } else {
962 wp_send_json_success('inactive');
963 }
964 } else {
965 wp_send_json_success('deleted');
966 }
967 } elseif ($tool == 'install_plugin') {
968 if(!isset($_GET['slug'])){
969 wp_send_json_success('unknown plugin');
970 }
971
972 $slug = sanitize_text_field(wp_unslash($_GET['slug']));
973
974 @include_once ABSPATH . 'wp-admin/includes/plugin.php';
975 @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
976 @include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
977 @include_once ABSPATH . 'wp-admin/includes/file.php';
978 @include_once ABSPATH . 'wp-admin/includes/misc.php';
979
980 wp_cache_flush();
981
982 $path = $this->get_plugin_path($slug);
983
984 if (false !== $path) {
985 // Plugin is already installed
986 wp_send_json_success();
987 } else {
988 // Install Plugin
989 $skin = new WP_Ajax_Upgrader_Skin();
990 $upgrader = new Plugin_Upgrader($skin);
991 $upgrader->install('https://downloads.wordpress.org/plugin/' . $slug . '.latest-stable.zip');
992 wp_send_json_success();
993 }
994 } elseif ($tool == 'activate_plugin') {
995 if(!isset($_GET['slug'])){
996 wp_send_json_success('unknown plugin');
997 }
998
999 $slug = sanitize_text_field(wp_unslash($_GET['slug']));
1000
1001 $path = $this->get_plugin_path($slug);
1002 activate_plugin($path);
1003 wp_send_json_success();
1004 } elseif ($tool == 'before_reset') {
1005 $active_plugins = get_option('active_plugins');
1006 set_transient('wpr_active_plugins', $active_plugins, 100);
1007 remove_all_actions('update_option_active_plugins');
1008 update_option('active_plugins', array(plugin_basename(__FILE__)));
1009 wp_send_json_success();
1010 } else {
1011 wp_send_json_error(__('Unknown tool.', 'wp-reset'));
1012 }
1013 } // ajax_run_tool
1014
1015
1016 /**
1017 * Get plugin path from slug
1018 *
1019 * @return string path
1020 */
1021 function get_plugin_path($slug)
1022 {
1023 $all_plugins = get_plugins();
1024 foreach ($all_plugins as $plugin_path => $plugin) {
1025 if (strpos($plugin_path, $slug . '/') === 0) {
1026 return $plugin_path;
1027 }
1028 }
1029 return false;
1030 } // get_plugin_path
1031
1032
1033 /**
1034 * Reinstall / reset the WP site
1035 * There are no failsafes in the function - it reinstalls when called
1036 * Redirects when done
1037 *
1038 * @param array $params Optional.
1039 *
1040 * @return null
1041 */
1042 function do_reinstall($params = array())
1043 {
1044 global $current_user, $wpdb;
1045
1046 // only admins can reset; double-check
1047 if (!$this->is_cli_running() && !current_user_can('manage_options')) {
1048 return false;
1049 }
1050
1051 // make sure the function is available to us
1052 if (!function_exists('wp_install')) {
1053 require ABSPATH . '/wp-admin/includes/upgrade.php';
1054 }
1055
1056 // save values that need to be restored after reset
1057 $blogname = get_option('blogname');
1058 $blog_public = get_option('blog_public');
1059 $wplang = get_option('wplang');
1060 $siteurl = get_option('siteurl');
1061 $home = get_option('home');
1062 $snapshots = $this->get_snapshots();
1063
1064 $active_plugins = get_transient('wpr_active_plugins');
1065 $active_theme = wp_get_theme();
1066
1067 // for WP-CLI
1068 if (!$current_user->ID) {
1069 $tmp = get_users(array('role' => 'administrator', 'order' => 'ASC', 'order_by' => 'ID'));
1070 if (empty($tmp[0]->user_login)) {
1071 return new WP_Error(1, 'Reset failed. Unable to find any admin users in database.');
1072 }
1073 $current_user = $tmp[0];
1074 }
1075
1076 // delete custom tables with WP's prefix
1077 $prefix = str_replace('_', '\_', $wpdb->prefix);
1078
1079 // phpcs:ignore db call warning as we are using uncommon queries
1080 $tables = $wpdb->get_col($wpdb->prepare("SHOW TABLES LIKE %s", array($prefix . '%'))); // phpcs:ignore
1081
1082 foreach ($tables as $table) {
1083 $wpdb->wpreset_table = $table;
1084 $wpdb->query("DROP TABLE " . $wpdb->wpreset_table); // phpcs:ignore
1085 }
1086
1087 $old_user_pass = $current_user->user_pass;
1088
1089 // suppress errors for WP_CLI
1090 $result = @wp_install($blogname, $current_user->user_login, $current_user->user_email, $blog_public, '', md5(wp_rand()), $wplang);
1091 $user_id = $result['user_id'];
1092
1093 // restore user pass
1094 // phpcs:ignore db call warning as we are using uncommon queries
1095 $wpdb->query($wpdb->prepare("UPDATE {$wpdb->users} SET user_pass = %s, user_activation_key = %s WHERE ID = %d LIMIT 1", array($old_user_pass, '', $user_id))); // phpcs:ignore
1096 $current_user->user_pass = $old_user_pass;
1097
1098 // restore rest of the settings including WP Reset's
1099 update_option('siteurl', $siteurl);
1100 update_option('home', $home);
1101 update_option('wp-reset', $this->options);
1102 update_option('wp-reset-snapshots', $snapshots);
1103
1104 // remove password nag
1105 if (get_user_meta($user_id, 'default_password_nag')) {
1106 update_user_meta($user_id, 'default_password_nag', false);
1107 }
1108 if (get_user_meta($user_id, $wpdb->prefix . 'default_password_nag')) {
1109 update_user_meta($user_id, $wpdb->prefix . 'default_password_nag', false);
1110 }
1111
1112 $meta = $this->get_meta();
1113 $meta['reset_count']++;
1114 $this->update_options('meta', $meta);
1115
1116 // reactivate theme
1117 if (!empty($params['reactivate_theme'])) {
1118 switch_theme($active_theme->get_stylesheet());
1119 }
1120
1121 // reactivate WP Reset
1122 if (!empty($params['reactivate_wpreset'])) {
1123 activate_plugin(plugin_basename(__FILE__));
1124 }
1125
1126 // reactivate all plugins
1127 if (!empty($params['reactivate_plugins'])) {
1128 foreach ($active_plugins as $plugin_file) {
1129 activate_plugin($plugin_file);
1130 }
1131 }
1132
1133 if (!$this->is_cli_running()) {
1134 // log out and log in the old/new user
1135 // since the password doesn't change this is potentially unnecessary
1136 wp_clear_auth_cookie();
1137 wp_set_auth_cookie($user_id);
1138
1139 wp_safe_redirect(admin_url() . '?wp-reset=success');
1140 exit;
1141 }
1142 } // do_reinstall
1143
1144
1145 /**
1146 * Checks wp_reset post value and performs all actions
1147 *
1148 * @return null|bool
1149 */
1150 function do_all_actions()
1151 {
1152 // only admins can perform actions
1153 if (!current_user_can('manage_options')) {
1154 return;
1155 }
1156
1157 if (!empty($_GET['wp-reset']) && sanitize_text_field(wp_unslash($_GET['wp-reset'])) == 'success') {
1158 add_action('admin_notices', array($this, 'notice_successful_reset'));
1159 }
1160
1161 // check nonce
1162 if (true === isset($_POST['wp_reset_confirm']) && (!isset($_POST['_wpnonce']) || false === wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'wp-reset'))) {
1163 add_settings_error('wp-reset', 'bad-nonce', __('Something went wrong. Please refresh the page and try again.', 'wp-reset'), 'error');
1164 return false;
1165 }
1166
1167 // check confirmation code
1168 if (true === isset($_POST['wp_reset_confirm']) && 'reset' !== sanitize_text_field(wp_unslash($_POST['wp_reset_confirm']))) {
1169 add_settings_error('wp-reset', 'bad-confirm', __('<b>Invalid confirmation code.</b> Please type "reset" in the confirmation field.', 'wp-reset'), 'error');
1170 return false;
1171 }
1172
1173 // only one action at the moment
1174 if (true === isset($_POST['wp_reset_confirm']) && 'reset' === sanitize_text_field(wp_unslash($_POST['wp_reset_confirm']))) {
1175 $params = array(
1176 'reactivate_theme' => '0',
1177 'reactivate_plugins' => '0',
1178 'reactivate_wpreset' => '0',
1179 );
1180 if (isset($_POST['wpr-post-reset']['reactivate_theme'])) {
1181 $params['reactivate_theme'] = true;
1182 }
1183 if (isset($_POST['wpr-post-reset']['reactivate_plugins'])) {
1184 $params['reactivate_plugins'] = true;
1185 }
1186 if (isset($_POST['wpr-post-reset']['reactivate_wpreset'])) {
1187 $params['reactivate_wpreset'] = true;
1188 }
1189
1190 $this->do_reinstall($params);
1191 }
1192 } // do_all_actions
1193
1194
1195 /**
1196 * Add "Open WP Reset Tools" action link to plugins table, left part
1197 *
1198 * @param array $links Initial list of links.
1199 *
1200 * @return array
1201 */
1202 function plugin_action_links($links)
1203 {
1204 $settings_link = '<a href="' . esc_url(admin_url('tools.php?page=wp-reset')) . '" title="' . esc_attr(__('Open WP Reset Tools', 'wp-reset')) . '">' . esc_html(__('Open WP Reset Tools', 'wp-reset')) . '</a>';
1205
1206 array_unshift($links, $settings_link);
1207
1208 return $links;
1209 } // plugin_action_links
1210
1211
1212 /**
1213 * Add links to plugin's description in plugins table
1214 *
1215 * @param array $links Initial list of links.
1216 * @param string $file Basename of current plugin.
1217 *
1218 * @return array
1219 */
1220 function plugin_meta_links($links, $file)
1221 {
1222 if ($file !== plugin_basename(__FILE__)) {
1223 return $links;
1224 }
1225
1226 $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset" title="' . __('Get help', 'wp-reset') . '">' . __('Support', 'wp-reset') . '</a>';
1227 $home_link = '<a target="_blank" href="' . esc_url($this->generate_web_link('plugins-table-right')) . '" title="' . __('Plugin Homepage', 'wp-reset') . '">' . __('Plugin Homepage', 'wp-reset') . '</a>';
1228 $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 �
1229 �
1230 �
1231 �
1232 �
1233 ', 'wp-reset') . '</a>';
1234
1235 $links[] = $support_link;
1236 $links[] = $home_link;
1237 $links[] = $rate_link;
1238
1239 return $links;
1240 } // plugin_meta_links
1241
1242
1243 /**
1244 * Test if we're on WPR's admin page
1245 *
1246 * @return bool
1247 */
1248 function is_plugin_page()
1249 {
1250 $current_screen = get_current_screen();
1251
1252 if (!empty($current_screen->id) && $current_screen->id == 'tools_page_wp-reset') {
1253 return true;
1254 } else {
1255 return false;
1256 }
1257 } // is_plugin_page
1258
1259
1260 /**
1261 * Add powered by text in admin footer
1262 *
1263 * @param string $text Default footer text.
1264 *
1265 * @return string
1266 */
1267 function admin_footer_text($text)
1268 {
1269 if (!$this->is_plugin_page()) {
1270 return $text;
1271 }
1272
1273 $text = '<i><a href="' . esc_url($this->generate_web_link('admin_footer')) . '" title="' . esc_attr(__('Visit WP Reset page for more info', 'wp-reset')) . '" target="_blank">WP Reset</a> v' . $this->version . '. Please <a target="_blank" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" title="Rate the plugin">rate the plugin <span>�
1274 �
1275 �
1276 �
1277 �
1278 </span></a> to help us spread the word. Thank you from the WP Reset team!</i>';
1279
1280 return $text;
1281 } // admin_footer_text
1282
1283
1284 /**
1285 * Loads plugin's translated strings
1286 *
1287 * @return null
1288 */
1289 function load_textdomain()
1290 {
1291 load_plugin_textdomain('wp-reset');
1292 } // load_textdomain
1293
1294
1295 /**
1296 * Inform the user that WordPress has been successfully reset
1297 *
1298 * @return null
1299 */
1300 function notice_successful_reset()
1301 {
1302 global $current_user;
1303 /* translators: %1$s is replaced with the current user's username, %2$s is replaced with the URL to WP Reset settings page */
1304 WP_Reset_Utility::wp_kses_wf('<div style="padding: 15px; display: inline-block; font-size: 14px;" id="message" class="updated"><p style="font-size: 14px;">' . sprintf(__('<b>Site has been successfully reset to default settings.</b><br>User "%1$s" was restored with the password unchanged. Open <a href="%2$s">WP Reset</a> to do another reset.', 'wp-reset'), esc_html($current_user->user_login), esc_url(admin_url('tools.php?page=wp-reset'))) . '</p>');
1305
1306
1307 if (false == $this->get_dismissed_notices('rate')) {
1308 $request_url = isset($_SERVER['REQUEST_URI'])?sanitize_url(wp_unslash($_SERVER['REQUEST_URI'])):'';
1309 $dismiss_url = add_query_arg(array('action' => 'wpr_dismiss_notice', 'notice' => 'rate', 'redirect' => urlencode($request_url)), admin_url('admin.php'));
1310 $dismiss_url = wp_nonce_url($dismiss_url, 'wpr_dismiss_notice');
1311
1312 echo '<p style="font-size: 14px;">';
1313 echo 'If WP Reset helped you please rate it so we can continue supporting it and helping others. Thank you!<br>';
1314 echo '<a style="margin-top: 5px;" class="button button-secondary" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" target="_blank">You deserve it, I\'ll rate it!</a> &nbsp; &nbsp; <a href="' . esc_url($dismiss_url) . '">I already rated it</a>';
1315 echo '</p>';
1316 }
1317
1318 echo '</div>';
1319 } // notice_successful_reset
1320
1321
1322 /**
1323 * Generate a button that initiates snapshot creation
1324 *
1325 * @param string $tool_id Tool ID.
1326 * @param string $description Snapshot description.
1327 *
1328 * @return string
1329 */
1330 function get_snapshot_button($tool_id = '', $description = '')
1331 {
1332 $out = '';
1333 $out .= '<a data-tool-id="' . esc_attr($tool_id) . '" data-description="' . esc_attr($description) . '" class="button create-new-snapshot" href="#">Create snapshot</a>';
1334
1335 return $out;
1336 } // get_snapshot_button
1337
1338
1339 /**
1340 * Generate card header including title and action buttons
1341 *
1342 * @param string $title Card title.
1343 * @param string $card_id Card element #ID.
1344 * @param array $params Individual icons arguments
1345 *
1346 * @return string
1347 */
1348 function get_card_header($title, $card_id, $params = array())
1349 {
1350 $params = shortcode_atts(array(
1351 'documentation_link' => false,
1352 'iot_button' => false,
1353 'collapse_button' => false,
1354 'create_snapshot' => false,
1355 'pro' => false
1356 ), (array) $params);
1357
1358 if ($params['documentation_link'] === true) {
1359 $params['documentation_link'] = $card_id;
1360 }
1361
1362 $out = '';
1363 $out .= '<h4 id="' . esc_attr($card_id) . '"><span class="card-name">' . esc_html($title);
1364 if ($params['pro']) {
1365 $out .= ' - <a data-feature="' . esc_attr($card_id) . '" class="pro-feature tooltip" title="WP Reset PRO tool" href="#"><span class="pro">PRO</span> tool</a>';
1366 }
1367 $out .= '</span>';
1368 $out .= '<div class="card-header-right">';
1369 if ($params['documentation_link']) {
1370 $out .= '<a class="documentation-link tooltip" href="' . esc_url($this->generate_web_link('documentation_link', '/documentation/')) . '" title="' . __('Open documentation for this tool', 'wp-reset') . '" target="blank"><span class="dashicons dashicons-editor-help"></span></a>';
1371 }
1372 if ($params['iot_button']) {
1373 $out .= '<a class="scrollto tooltip" href="#iot" title="Jump to Index of Tools"><span class="dashicons dashicons-screenoptions"></span></a>';
1374 }
1375 if ($params['create_snapshot']) {
1376 $out .= '<a id="create-new-snapshot-primary" title="Create a new snapshot" href="#" class="button button-primary create-new-snapshot tooltip">' . __('Create Snapshot', 'wp-reset') . '</a>';
1377 }
1378 if ($params['collapse_button']) {
1379 $out .= '<a class="toggle-card tooltip" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1380 }
1381 $out .= '</div></h4>';
1382
1383 return $out;
1384 } // get_card_header
1385
1386
1387 /**
1388 * Generate tool icons and description detailing what it modifies
1389 *
1390 * @param bool $modify_files Does the tool modify files?
1391 * @param bool $modify_db Does the tool modify the database?
1392 * @param bool $plural Is there more than one tool in the set?
1393 *
1394 * @return string
1395 */
1396 function get_tool_icons($modify_files = false, $modify_db = false, $plural = false)
1397 {
1398 $out = '';
1399 $modify_files = (bool) $modify_files;
1400 $modify_db = (bool) $modify_db;
1401 $plural = (bool) $plural;
1402
1403 $out .= '<p class="tool-icons">';
1404 $out .= '<i class="icon-doc-text-inv' . ($modify_files ? ' red' : '') . '"></i> ';
1405 $out .= '<i class="icon-database' . ($modify_db ? ' red' : '') . '"></i> ';
1406
1407 if ($plural) {
1408 if ($modify_files && $modify_db) {
1409 $out .= 'these tools <b>modify files &amp; the database</b>';
1410 } elseif (!$modify_files && $modify_db) {
1411 $out .= 'these tools <b>modify the database</b> but they don\'t modify any files</b>';
1412 } elseif ($modify_files && !$modify_db) {
1413 $out .= 'these tools <b>modify files</b> but they don\'t modify the database</b>';
1414 }
1415 } else {
1416 if ($modify_files && $modify_db) {
1417 $out .= 'this tool <b>modifies files &amp; the database</b>';
1418 } elseif (!$modify_files && $modify_db) {
1419 $out .= 'this tool <b>modifies the database</b> but it doesn\'t modify any files</b>';
1420 } elseif ($modify_files && !$modify_db) {
1421 $out .= 'this tool <b>modifies files</b> but it doesn\'t modify the database</b>';
1422 } else {
1423 $out .= 'this tool doesn\'t modify files or the database';
1424 }
1425 }
1426 $out .= '</p>';
1427
1428 return $out;
1429 } // get_tool_icons
1430
1431
1432 /**
1433 * Outputs complete plugin's admin page
1434 *
1435 * @return null
1436 */
1437 function plugin_page()
1438 {
1439 // double check for admin privileges
1440 if (!current_user_can('manage_options')) {
1441 wp_die(esc_html__('Sorry, you are not allowed to access this page.', 'wp-reset'));
1442 }
1443
1444 echo '<div class="wrap">';
1445 echo '<form id="wp_reset_form" action="' . esc_url(admin_url('tools.php?page=wp-reset')) . '" method="post" autocomplete="off">';
1446
1447 echo '<header>';
1448 echo '<div class="wpr-container">';
1449 echo '<img id="logo-icon" src="' . esc_url($this->plugin_url) . 'img/wp-reset-logo.png" title="' . esc_html__('WP Reset', 'wp-reset') . '" alt="' . esc_html__('WP Reset', 'wp-reset') . '">';
1450 echo '</div>';
1451 echo '</header>';
1452
1453 echo '<div id="loading-tabs"><img class="rotating" src="' . esc_url($this->plugin_url) . 'img/wp-reset-icon.png' . '" alt="Loading. Please wait." title="Loading. Please wait."></div>';
1454
1455 echo '<div id="wp-reset-tabs" class="ui-tabs" style="display: none;">';
1456
1457 echo '<nav>';
1458 echo '<div class="wpr-container">';
1459 echo '<ul class="wpr-main-tab">';
1460 echo '<li><a href="#tab-reset">' . esc_html(__('Reset', 'wp-reset')) . '</a></li>';
1461 echo '<li><a href="#tab-tools">' . esc_html(__('Tools', 'wp-reset')) . '</a></li>';
1462 echo '<li><a href="#tab-snapshots">' . esc_html(__('Snapshots', 'wp-reset')) . '</a></li>';
1463 echo '<li><a href="#tab-collections">' . esc_html(__('Collections', 'wp-reset')) . '</a></li>';
1464 echo '<li><a href="#tab-support">' . esc_html(__('Support', 'wp-reset')) . '</a></li>';
1465 echo '<li class="tab-pro"><a href="#tab-pro">' . esc_html(__('PRO', 'wp-reset')) . '</a></li>';
1466 echo '</ul>';
1467 echo '</div>'; // container
1468 echo '</nav>';
1469
1470 echo '<div id="wpr-notifications">';
1471 echo '<div class="wpr-container">';
1472 $this->custom_notifications();
1473 echo '</div>';
1474 echo '</div>'; // wpr-notifications
1475
1476 // tabs
1477 echo '<div class="wpr-container">';
1478 echo '<div id="wpr-content">';
1479
1480 echo '<div style="display: none;" id="tab-reset">';
1481 $this->tab_reset();
1482 echo '</div>';
1483
1484 echo '<div style="display: none;" id="tab-tools">';
1485 $this->tab_tools();
1486 echo '</div>';
1487
1488 echo '<div style="display: none;" id="tab-snapshots">';
1489 $this->tab_snapshots();
1490 echo '</div>';
1491
1492 echo '<div style="display: none;" id="tab-collections">';
1493 $this->tab_collections();
1494 echo '</div>';
1495
1496 echo '<div style="display: none;" id="tab-support">';
1497 $this->tab_support();
1498 echo '</div>';
1499
1500 echo '<div style="display: none;" id="tab-pro">';
1501 $this->tab_pro();
1502 echo '</div>';
1503
1504 echo '</div>'; // content
1505 echo '</div>'; // container
1506 echo '</div>'; // wp-reset-tabs
1507
1508 echo '</form>';
1509
1510 echo '<div id="wpr-sidebar-ads">';
1511 echo '<div id="wpr-ad">';
1512 echo '<h3 class="textcenter"><b>Save time &amp; money with WP Reset PRO! First WP dev tool for non-devs.</b></h3>';
1513 echo '<p class="textcenter"><a href="#" data-feature="sidebar-logo" class="button-pro-feature textcenter"><img style="max-width: 90%;" src="' . esc_url($this->plugin_url) . '/img/wp-reset-logo.png" alt="WP Reset PRO" title="WP Reset PRO"></a></p>';
1514 echo '<ul class="plain-list">
1515 <li>25+ Reset Tools</li>
1516 <li>Plugins &amp; Themes Collections</li>
1517 <li>Automatic Snapshots</li>
1518 <li>WP Reset Cloud, Dropbox &amp; Google Drive support</li>
1519 <li>Emergency Recovery Script</li>
1520 <li>White-label Mode + Complete Plugin Rebranding</li>
1521 <li>Licenses &amp; Sites Manager (remote SaaS dashboard)</li>
1522 <li>Friendly email support from plugin developers</li>
1523 </ul>';
1524 echo '<p class="textcenter"><a href="#" data-feature="sidebar-button" class="button-pro-feature button button-primary">Get PRO now</a></p>';
1525 echo '</div>';
1526
1527 if (!defined('WPFSSL_OPTIONS_KEY')) {
1528 echo '<div id="wpfssl-ad">';
1529 echo '<h3 class="textcenter"><b>Problems with SSL certificate?<br>Moving a site from HTTP to HTTPS?<br>Mixed content giving you troubles?<br><br><u>Fix all SSL problems with one plugin!</u></b></h3>';
1530 echo '<p class="textcenter"><a href="#" class="textcenter install-wpfssl"><img style="max-width: 90%;" src="' . esc_url($this->plugin_url) . '/img/wp-force-ssl-logo.png" alt="WP Force SSL" title="WP Force SSL"></a></p>';
1531 echo '<p class="textcenter"><br><a href="#" class="install-wpfssl button button-primary">Install &amp; activate the free WP Force SSL plugin</a></p><p><a href="https://wordpress.org/plugins/wp-force-ssl/" target="_blank">WP Force SSL</a> is a free WP plugin maintained by the same team as this Maintenance plugin. It has <b>+150,000 users, 5-star rating</b>, and is hosted on the official WP repository.</p>';
1532 echo '</div>';
1533 }
1534 echo '</div>';
1535
1536 echo '</div>'; // wrap
1537 } // plugin_page
1538
1539
1540 /**
1541 * Echoes all custom plugin notitications
1542 *
1543 * @return null
1544 */
1545 private function custom_notifications()
1546 {
1547 $notice_shown = false;
1548 $meta = $this->get_meta();
1549 $snapshots = $this->get_snapshots();
1550
1551 // update to PRO after activating the license
1552 if ($this->license->is_active()) {
1553 echo '<div class="card notice-wrapper notice-info">';
1554 echo '<h2>' . esc_html(__('Thank you for purchasing WP Reset PRO!', 'wp-reset')) . '</h2>';
1555 echo '<p>Your license has been verified &amp; activated.</b><br>To start using the PRO version, please follow these steps:';
1556 echo '<ol>';
1557 echo '<li><a href="https://dashboard.wpreset.com/pro-download/" target="_blank">Download</a> the latest version of the PRO plugin.</li>';
1558 echo '<li>Go to <a href="' . esc_url(admin_url('plugin-install.php')) . '">Plugins - Add New - Upload Plugin</a> and upload the ZIP you just downloaded.</li>';
1559 echo '<li>If asked to replace (overwrite) the free version - confirm it.</li>';
1560 echo '<li>Activate the plugin.</li>';
1561 echo '<li>That\'s it, no more steps.</li>';
1562 echo '</ol>';
1563 echo '</div>';
1564 $notice_shown = true;
1565 }
1566
1567 // warn that WPR is not WPMU compatible
1568 if (false === $notice_shown && is_multisite()) {
1569 echo '<div class="card notice-wrapper notice-error">';
1570 echo '<h2>' . esc_html__('WP Reset is not compatible with multisite!', 'wp-reset') . '</h2>';
1571 WP_Reset_Utility::wp_kses_wf('<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>');
1572 echo '</div>';
1573 $notice_shown = true;
1574 }
1575
1576 // ask for review
1577 if ((!empty($meta['reset_count']) || !empty($snapshots) || current_time('timestamp', true) - $meta['first_install'] > DAY_IN_SECONDS)
1578 && false === $notice_shown
1579 && false == $this->get_dismissed_notices('rate')
1580 ) {
1581 echo '<div class="card notice-wrapper notice-info">';
1582 echo '<h2>' . esc_html__('Please help us spread the word &amp; keep the plugin up-to-date', 'wp-reset') . '</h2>';
1583 WP_Reset_Utility::wp_kses_wf('<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 maintained. Thank you!', 'wp-reset') . '</p>');
1584 echo '<p><a class="button-primary button" title="' . esc_html__('Rate WP Reset', 'wp-reset') . '" target="_blank" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post">' . esc_html__('Rate the plugin �
1585 �
1586 �
1587 �
1588 �
1589 ', 'wp-reset') . '</a> <a href="#" class="wpr-dismiss-notice dismiss-notice-rate" data-notice="rate">' . esc_html__('I\'ve already rated it', 'wp-reset') . '</a></p>';
1590 echo '</div>';
1591 $notice_shown = true;
1592 }
1593 } // custom_notifications
1594
1595
1596 /**
1597 * Echoes content for reset tab
1598 *
1599 * @return null
1600 */
1601 private function tab_reset()
1602 {
1603 global $current_user, $wpdb;
1604
1605 echo '<div class="card">';
1606 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Please read carefully before proceeding', 'wp-reset'), 'reset-description', array('collapse_button' => true)));
1607 echo '<div class="card-body">';
1608 echo '<p>The following table details what data will be deleted (reset or destroyed) when a selected reset tool is run. Please read it! ';
1609 echo 'If something is not clear <a href="#" class="change-tab" data-tab="4">contact support</a> before running any tools. It\'s better to ask than to be sorry!';
1610 echo '</p>';
1611 echo '<p><i class="dashicons dashicons-trash red tooltip" title="Tool WILL delete, reset or destroy the noted data" style="vertical-align: bottom;"></i> - tool WILL delete, reset or destroy the noted data<br>';
1612 echo '<i class="dashicons dashicons-yes tooltip" title="Tool will NOT touch the noted data in any way" style="vertical-align: bottom;"></i> - tool will NOT touch the noted data in any way</p>';
1613
1614 echo '<table id="reset-details" class="">';
1615 echo '<tr>';
1616 echo '<th>&nbsp;</th>';
1617 echo '<th>Options Reset<br><a data-feature="tool-options-reset" class="pro-feature" href="#"><span class="pro">PRO</span> tool</a></th>';
1618 echo '<th nowrap>Site Reset</th>';
1619 echo '<th>Nuclear Reset<br><a data-feature="tool-nuclear-reset" class="pro-feature" href="#"><span class="pro">PRO</span> tool</a></th>';
1620 echo '</tr>';
1621
1622 $rows = array();
1623 $rows['Posts, pages & custom post types'] = array(0, 1, 1);
1624 $rows['Comments'] = array(0, 1, 1);
1625 $rows['Media'] = array(0, 1, 1);
1626 $rows['Media files'] = array(0, 0, 1);
1627 $rows['Users'] = array(0, 1, 1);
1628 $rows['User roles'] = array(1, 1, 1);
1629 $rows['Current user - ' . $current_user->user_login] = array(0, 0, 0);
1630 $rows['Widgets'] = array(1, 1, 1);
1631 $rows['Transients'] = array(1, 1, 1);
1632 $rows['Settings &amp; options (from WP, plugins & themes)'] = array(1, 1, 1);
1633 $rows['Site title, WP address, site address,' . PHP_EOL . 'search engine visibility, timezone'] = array(0, 0, 0);
1634 $rows['Site language'] = array(0, 0, 1);
1635 $rows['Data in all default WP tables'] = array(0, 1, 1);
1636 $rows['Custom database tables with prefix ' . $wpdb->prefix] = array(0, 1, 1);
1637 $rows['Other database tables'] = array(0, 0, 0);
1638 $rows['Plugin files'] = array(0, 0, 1);
1639 $rows['MU plugin files'] = array(0, 0, 1);
1640 $rows['Drop-in files'] = array(0, 0, 1);
1641 $rows['Theme files'] = array(0, 0, 1);
1642 $rows['All files in uploads'] = array(0, 0, 1);
1643 $rows['Custom folders in wp-content'] = array(0, 0, 1);
1644
1645 foreach ($rows as $tool => $opt) {
1646 echo '<tr>';
1647 echo '<td>';
1648 WP_Reset_Utility::wp_kses_wf(nl2br(esc_html($tool)));
1649 echo '</td>';
1650 if (empty($opt[0])) {
1651 echo '<td><i class="dashicons dashicons-yes tooltip" title="Data will NOT be deleted, reset or modified"></i></td>';
1652 } else {
1653 echo '<td><i class="dashicons dashicons-trash red tooltip" title="Data WILL BE deleted, reset or modified"></i></td>';
1654 }
1655 if (empty($opt[1])) {
1656 echo '<td><i class="dashicons dashicons-yes tooltip" title="Data will NOT be deleted, reset or modified"></i></td>';
1657 } else {
1658 echo '<td><i class="dashicons dashicons-trash red tooltip" title="Data WILL BE deleted, reset or modified"></i></td>';
1659 }
1660 if (empty($opt[2])) {
1661 echo '<td><i class="dashicons dashicons-yes tooltip" title="Data will NOT be deleted, reset or modified"></i></td>';
1662 } else {
1663 echo '<td><i class="dashicons dashicons-trash red tooltip" title="Data WILL BE deleted, reset or modified"></i></td>';
1664 }
1665 echo '</tr>';
1666 } // foreach $rows
1667 echo '<tfoot>';
1668 echo '<tr>';
1669 echo '<th>&nbsp;</th>';
1670 echo '<th>Options Reset<br><a data-feature="tool-options-reset" class="pro-feature" href="#"><span class="pro">PRO</span> tool</a></th>';
1671 echo '<th nowrap>Site Reset</th>';
1672 echo '<th>Nuclear Reset<br><a data-feature="tool-nuclear-reset" class="pro-feature" href="#"><span class="pro">PRO</span> tool</a></th>';
1673 echo '</tr>';
1674 echo '</tfoot>';
1675 echo '</table>';
1676
1677 echo '<p><b>' . esc_html__('What happens when I run any Reset tool?', 'wp-reset') . '</b></p>';
1678 echo '<ul class="plain-list">';
1679 WP_Reset_Utility::wp_kses_wf('<li>' . __('remember, always <b>make a backup first</b> or use <a href="#" class="change-tab" data-tab="2">snapshots</a>', 'wp-reset') . '</li>');
1680 echo '<li>' . esc_html__('you will have to confirm the action one more time', 'wp-reset') . '</li>';
1681 echo '<li>' . esc_html__('see the table above to find out what exactly will be reset or deleted', 'wp-reset') . '</li>';
1682 echo '<li>' . esc_html__('site title, WordPress URL, site URL, site language, search engine visibility and current user will always be restored', 'wp-reset') . '</li>';
1683 echo '<li>' . esc_html__('you will be logged out, automatically logged back in and taken to the admin dashboard', 'wp-reset') . '</li>';
1684 echo '<li>' . esc_html__('WP Reset plugin will be reactivated if that option is chosen', 'wp-reset') . '</li>';
1685 echo '</ul>';
1686
1687 echo '<p><b>' . esc_html__('WP-CLI Support', 'wp-reset') . '</b><br>';
1688 /* translators: %s is replaced with the literal html "<code>wp help reset</code>" */
1689 echo '' . sprintf(esc_html__('All tools 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>');
1690 /* translators: %s is replaced with the literal html "<code>--yes</code>" */
1691 echo sprintf(esc_html__('All actions have to be confirmed. If you want to skip confirmation use the standard %s option. Please be careful and backup first.', 'wp-reset'), '<code>--yes</code>') . '</p>';
1692
1693 echo '</div></div>'; // card description
1694
1695 $theme = wp_get_theme();
1696 $theme_name = $theme->get('Name');
1697 if (empty($theme_name)) {
1698 $theme_name = '<i>no active theme</i>';
1699 }
1700 $active_plugins = get_option('active_plugins');
1701
1702 // options reset
1703 echo '<div class="card">';
1704 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Options Reset', 'wp-reset'), 'tool-options-reset', array('collapse_button' => true, 'pro' => true)));
1705 echo '<div class="card-body">';
1706 echo '<p>Options table will be reset to default values meaning all WP core settings, widgets, theme settings and customizations, and plugin settings will be gone. Other content and files will not be touched including posts, pages, custom post types, comments and other data stored in separate tables. Site URL and name will be kept as well. Please see the <a href="#reset-details" class="scrollto">table above</a> for details.</p>';
1707
1708 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, true));
1709
1710 echo '<p><br><label for="reset-options-reactivate-theme"><input type="checkbox" id="reset-options-reactivate-theme" value="1"> ' . esc_html__('Reactivate current theme', 'wp-reset') . ' - ' . esc_html($theme_name) . '</label></p>';
1711 echo '<p><label for="reset-options-reactivate-plugins"><input type="checkbox" id="reset-options-reactivate-plugins" value="1"> Reactivate ' . esc_attr(sizeof($active_plugins)) . ' currently active plugin' . (sizeof($active_plugins) != 1 ? 's' : '') . ' (WP Reset will reactivate by default)</label></p>';
1712
1713 echo '<p class="mb0"><a class="button button-delete button-pro-feature" href="#">Reset all options - <span data-feature="tool-options-reset" class="pro-feature"><span class="pro">PRO</span> tool</span></a></p>';
1714 echo '</div>';
1715 echo '</div>'; // options reset
1716
1717 echo '<div class="card">';
1718 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Site Reset', 'wp-reset'), 'tool-site-reset', array('collapse_button' => true)));
1719 echo '<div class="card-body">';
1720 echo '<p><label for="reactivate-theme"><input name="wpr-post-reset[reactivate_theme]" type="checkbox" id="reactivate-theme" value="1"> ' . esc_html__('Reactivate current theme', 'wp-reset') . ' - ' . esc_html($theme->get('Name')) . '</label></p>';
1721 echo '<p><label for="reactivate-wpreset"><input name="wpr-post-reset[reactivate_wpreset]" type="checkbox" id="reactivate-wpreset" value="1" checked> ' . esc_html__('Reactivate WP Reset plugin', 'wp-reset') . '</label></p>';
1722
1723 echo '<p><label for="reactivate-plugins"><input name="wpr-post-reset[reactivate_plugins]" type="checkbox" id="reactivate-plugins" value="1"> ' . esc_html__('Reactivate all currently active plugins', 'wp-reset') . '</label></p>';
1724 WP_Reset_Utility::wp_kses_wf('<p>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress" button.<br>Always <a href="#" class="create-new-snapshot" data-description="Before resetting the site">create a snapshot</a> before resetting if you want to be able to undo.', 'wp-reset') . '</p>');
1725
1726 wp_nonce_field('wp-reset');
1727 /* translators: %s is replaced with "reset"*/
1728 echo '<p class="mb0"><input id="wp_reset_confirm" type="text" name="wp_reset_confirm" placeholder="' . esc_attr(sprintf(__('Type in: %s', 'wp-reset'), '"reset"')) . '" value="" autocomplete="off"> &nbsp;';
1729 echo '<a id="wp_reset_submit" class="button button-delete">' . esc_html__('Reset Site', 'wp-reset') . '</a>';
1730 WP_Reset_Utility::wp_kses_wf($this->get_snapshot_button('reset-wordpress', 'Before resetting the site'));
1731 echo '</p>';
1732 echo '</div>';
1733 echo '</div>'; // card reset
1734
1735 // nuclear reset
1736 echo '<div class="card">';
1737 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Nuclear Site Reset', 'wp-reset'), 'tool-nuclear-reset', array('collapse_button' => true, 'pro' => true)));
1738 echo '<div class="card-body">';
1739 echo '<p>All data will be deleted or reset (see the <a href="#reset-details" class="scrollto">explanation table</a> for details). All data stored in the database including custom tables with <code>' . esc_html($wpdb->prefix) . '</code> prefix, as well as all files in wp-content, themes and plugins folders. The only thing restored after reset will be your user account so you can log in again, and the basic WP settings like site URL. Please see the <a href="#reset-details" class="scrollto">table above</a> for details.</p>';
1740
1741 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, true));
1742
1743 if (is_multisite()) {
1744 echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would delete files shared by multiple sites in the WP network.</p>';
1745 } else {
1746 echo '<p><br><label for="nuclear-reset-reactivate-wpreset"><input type="checkbox" id="nuclear-reset-reactivate-wpreset" value="1" checked> ' . esc_html__('Reactivate WP Reset plugin', 'wp-reset') . '</label></p>';
1747
1748 WP_Reset_Utility::wp_kses_wf('<p>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress &amp; Delete All Custom Files &amp; Data" button. <b>There is NO UNDO.', 'wp-reset') . '</b></p>');
1749
1750 echo '<p class="mb0"><input id="nuclear_reset_confirm" type="text" placeholder="' . esc_html__('Type in "reset"', 'wp-reset') . '" value="" autocomplete="off"> &nbsp;';
1751 echo '<a class="button button-delete button-pro-feature" href="#">' . esc_html__('Reset WordPress &amp; Delete All Custom Files &amp; Data', 'wp-reset') . ' - <span data-feature="tool-nuclear-reset" class="pro-feature"><span class="pro">PRO</span> tool</span></a></p>';
1752 }
1753 echo '</div>';
1754 echo '</div>'; // nuclear reset
1755 } // tab_reset
1756
1757
1758 /**
1759 * Echoes content for tools tab
1760 *
1761 * @return null
1762 */
1763 private function tab_tools()
1764 {
1765 global $wpdb, $wp_version, $wp_filesystem;
1766 $this->wp_init_filesystem();
1767
1768 $tools = array(
1769 'tool-reset-theme-options' => 'Reset Theme Options',
1770 '_tool-reset-user-roles' => 'Reset User Roles',
1771 'tool-delete-transients' => 'Delete Transients',
1772 'tool-purge-cache' => 'Purge Cache',
1773 'tool-delete-local-data' => 'Delete Local Data',
1774 '_tool-delete-content' => 'Delete Content',
1775 '_tool-delete-widgets' => 'Delete Widgets',
1776 'tool-delete-themes' => 'Delete Themes',
1777 'tool-delete-plugins' => 'Delete Plugins',
1778 '_tool-delete-mu-plugins-dropins' => 'Delete MU Plugins & Drop-ins',
1779 'tool-delete-uploads' => 'Clean uploads Folder',
1780 '_tool-delete-wp-content' => 'Clean wp-content Folder',
1781 'tool-empty-delete-custom-tables' => 'Empty or Delete Custom Tables',
1782 '_tool-switch-wp-version' => 'Switch WP Version',
1783 'tool-delete-htaccess' => 'Delete .htaccess File'
1784 );
1785
1786 echo '<div class="card">';
1787 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Index of Tools', 'wp-reset'), 'iot', array('collapse_button' => true)));
1788 echo '<div class="card-body">';
1789 $i = 0;
1790 $tools_nb = sizeof($tools);
1791 foreach ($tools as $tool_id => $tool_name) {
1792 if ($i == 0) {
1793 echo '<div class="third">';
1794 echo '<ul class="mb0 plain-list">';
1795 }
1796 if ($i == 5 || $i == 10) {
1797 echo '</div>';
1798 echo '<div class="third">';
1799 echo '<ul class="mb0 plain-list">';
1800 }
1801
1802 if ($tool_id[0] == '_') {
1803 $tool_id = ltrim($tool_id, '_');
1804 echo '<li><a title="Jump to ' . esc_attr($tool_name) . ' tool" class="scrollto" href="#' . esc_attr($tool_id) . '">' . esc_html($tool_name) . '</a> <a class="pro-feature" href="#" data-feature="' . esc_attr($tool_id) . '"><span class="pro">PRO</span> tool</a></li>';
1805 } else {
1806 echo '<li><a title="Jump to ' . esc_attr($tool_name) . ' tool" class="scrollto" href="#' . esc_attr($tool_id) . '">' . esc_html($tool_name) . '</a></li>';
1807 }
1808
1809 if ($i == $tools_nb - 1) {
1810 echo '</ul>';
1811 echo '</div>'; // third
1812 }
1813 $i++;
1814 } // foreach tools
1815 echo '</div>';
1816 echo '</div>';
1817
1818 echo '<div class="card">';
1819 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Reset Theme Options', 'wp-reset'), 'tool-reset-theme-options', array('iot_button' => true, 'collapse_button' => true)));
1820 echo '<div class="card-body">';
1821 WP_Reset_Utility::wp_kses_wf('<p>' . __('All options (mods) for all themes will be reset; not just for the active theme. The tool works only for themes that use the <a href="https://codex.wordpress.org/Theme_Modification_API" target="_blank">WordPress theme modification API</a>. If options are saved in some other, custom way they won\'t be reset.<br> Always <a href="#" class="create-new-snapshot" data-description="Before resetting theme options">create a snapshot</a> before using this tool if you want to be able to undo its actions.', 'wp-reset') . '</p>');
1822 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, true));
1823 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to reset all theme options?" data-btn-confirm="Reset theme options" data-text-wait="Resetting theme options. Please wait." data-text-confirm="All options (mods) for all themes will be reset. Always ' . esc_attr('<a data-description="Before resetting theme options" href="#" class="create-new-snapshot">create a snapshot</a> if you want to be able to undo') . '." data-text-done="Options for %n themes have been reset." data-text-done-singular="Options for one theme have been reset." class="button button-delete" href="#" id="reset-theme-options">Reset theme options</a>';
1824 WP_Reset_Utility::wp_kses_wf($this->get_snapshot_button('reset-theme-options', 'Before resetting theme options') . '</p>');
1825 echo '</div>';
1826 echo '</div>'; // reset theme options
1827
1828 echo '<div class="card default-collapsed">';
1829 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Reset User Roles', 'wp-reset'), 'tool-reset-user-roles', array('collapse_button' => true, 'iot_button' => true, 'pro' => true)));
1830 echo '<div class="card-body">';
1831 echo '<p>Default user roles\' capatibilities will be reset to their default values. All custom roles will be deleted.<br>Users that had custom roles will not be assigned any default ones and might not be able to log in. Roles have to be (re)assigned to them manually.</p>';
1832 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, true));
1833 echo '<p class="mb0"><a class="button button-delete button-pro-feature" href="#">Reset user roles - <span data-feature="tool-reset-user-roles" class="pro-feature"><span class="pro">PRO</span> tool</span></a>';
1834 WP_Reset_Utility::wp_kses_wf($this->get_snapshot_button('reset-user-roles', 'Before resetting user roles') . '</p>');
1835 echo '</div>';
1836 echo '</div>'; // reset user roles
1837
1838 echo '<div class="card">';
1839 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete Transients', 'wp-reset'), 'tool-delete-transients', array('iot_button' => true, 'collapse_button' => true)));
1840 echo '<div class="card-body">';
1841 echo '<p>All transient related database entries will be deleted. Including expired and non-expired transients, and orphaned transient timeout entries.<br>Always <a href="#" data-description="Before deleting transients" class="create-new-snapshot">create a snapshot</a> before using this tool if you want to be able to undo its actions.</p>';
1842 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, true));
1843 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all transients?" 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. Always ' . esc_attr('<a data-description="Before deleting transients" href="#" class="create-new-snapshot">create a snapshot</a> if you want to be able to undo') . '." data-text-done="%n transient database entries have been deleted." data-text-done-singular="One transient database entry has been deleted." class="button button-delete" href="#" id="delete-transients">Delete all transients</a>';
1844 WP_Reset_Utility::wp_kses_wf($this->get_snapshot_button('delete-transients', 'Before deleting transients') . '</p>');
1845 echo '</div>';
1846 echo '</div>'; // delete transients
1847
1848 echo '<div class="card">';
1849 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Purge Cache', 'wp-reset'), 'tool-purge-cache', array('collapse_button' => true, 'iot_button' => true)));
1850 echo '<div class="card-body">';
1851 echo '<p>All cache objects stored in both files and the database will be deleted. Along with WP object cache and transients, cache from the following plugins will be purged: W3 Total Cache, WP Cache, LiteSpeed Cache, Endurance Page Cache, SiteGround Optimizer, WP Fastest Cache and Swift Performance.</p>';
1852 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, true));
1853 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to purge all cache?" data-btn-confirm="Purge cache" data-text-wait="Purging cache. Please wait." data-text-confirm="All cache objects will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="Cache has been purged." data-text-done-singular="Cache has been purged." class="button button-delete" href="#" id="purge-cache">Purge cache</a></p>';
1854 echo '</div>';
1855 echo '</div>'; // purge cache
1856
1857 echo '<div class="card">';
1858 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete Local Data', 'wp-reset'), 'tool-delete-local-data', array('collapse_button' => true, 'iot_button' => true)));
1859 echo '<div class="card-body">';
1860 echo '<p>All local storage and session storage data will be deleted. Cookies without a custom set path will be deleted as well. WP cookies are not touched, with Delete Local Data button.<br>Deleting all WordPress cookies (including authentication cookies) will delete all WP related cookies and user (you) will be logged out on the next page reload.
1861 </p>';
1862 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, false));
1863 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all local data?" data-btn-confirm="Delete local data" data-text-wait="Deleting local data. Please wait." data-text-confirm="All local data; cookies, local storage and local session will be deleted. There is NO UNDO. WP Reset does not make backups of local data." data-text-done="%n local data objects have been deleted." data-text-done-singular="One local data object has been deleted." class="button button-delete" href="#" id="delete-local-data">Delete local data</a><a data-confirm-title="Are you sure you want to delete all WP related cookies?" data-btn-confirm="Delete all WordPress cookies" data-text-wait="Deleting WP cookies. Please wait." data-text-confirm="All WP cookies including authentication ones will be deleted. You will have to log in again. There is NO UNDO. WP Reset does not make backups of cookies." data-text-done="All WP cookies have been deleted. Reload the page to login again." data-text-done-singular="All WP cookies have been deleted. Reload the page to login again." class="button button-delete" href="#" id="delete-wp-cookies">Delete all WordPress cookies</a></p>';
1864 echo '</div>';
1865 echo '</div>'; // delete local data
1866
1867 echo '<div class="card default-collapsed">';
1868 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete Content', 'wp-reset'), 'tool-delete-content', array('collapse_button' => true, 'iot_button' => true, 'pro' => true)));
1869 echo '<div class="card-body">';
1870 echo '<p>Besides content, all linked or child records (for selected content) will be deleted to prevent creating orphaned rows in the database. For instance, for posts that\'s posts, post meta, and comments related to posts. Delete process does not call any WP hooks such as <i>before_delete_post</i>. Choosing a post type or taxonomy does not delete that parent object it deletes the child objects. Parent objects are defined in code. If you want to remove them, remove their code definition. When media is deleted, files are left in the uploads folder. To delete files use the <a class="scrollto" href="#tool-delete-uploads">Clean uploads Folder</a> tool. Deleting users does not affect the current, logged in user account. All orphaned objects will be reassigned to him.</p>';
1871
1872 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, true));
1873
1874 $post_types = get_post_types('', false, 'and');
1875 $taxonomies = get_taxonomies('', false, 'and');
1876
1877 echo '<p><select size="6" multiple id="delete-content-types">';
1878 // phpcs:ignore db call warning as we are using uncommon queries
1879 echo '<option value="_comments">Comments (' . ((int) $wpdb->get_var("SELECT COUNT(comment_id) FROM $wpdb->comments")) . ')</option>'; // phpcs:ignore
1880 echo '<option value="_users">Users (' . ((int) $wpdb->get_var("SELECT COUNT(id) FROM $wpdb->users")) . ')</option>'; // phpcs:ignore
1881 foreach ($post_types as $type) {
1882 $count = wp_count_posts($type->name, 'readable');
1883 $tmp = 0;
1884 foreach ($count as $cnt) {
1885 $tmp += (int) $cnt;
1886 }
1887 echo '<option value="' . esc_attr($type->name) . '">Post type - ' . esc_html($type->label . ' (' . $tmp) . ')</option>';
1888 } // foreach post types
1889 foreach ($taxonomies as $tax) {
1890 echo '<option value="_tax_' . esc_attr($tax->name) . '">Taxonomy - ' . esc_html($tax->label . ' (' . wp_count_terms($tax->name)) . ')</option>';
1891 } // foreach post types
1892
1893 echo '</select><br>';
1894 echo 'Select content object(s) you want to delete. Use ctrl + click to select multiple objects.</p>';
1895
1896 echo '<p class="mb0"><a class="button button-delete button-pro-feature" href="#">Delete content - <span data-feature="tool-delete-content" class="pro-feature"><span class="pro">PRO</span> tool</span></a></p>';
1897 echo '</div>';
1898 echo '</div>'; // delete content
1899
1900 echo '<div class="card default-collapsed">';
1901 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete Widgets', 'wp-reset'), 'tool-delete-widgets', array('collapse_button' => true, 'iot_button' => true, 'pro' => true)));
1902 echo '<div class="card-body">';
1903 echo '<p>All widgets, orphaned, active and inactive ones, as well as widgets in active and inactive sidebars will be deleted including their settings. After deleting, WordPress will automatically recreate default, empty database entries related to widgets. So, no matter how many times users run the tool it will never return "no data deleted". That\'s expected and normal.</p>';
1904
1905 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, true));
1906
1907 echo '<p class="mb0"><a class="button button-delete button-pro-feature" href="#">Delete widgets - <span data-feature="tool-delete-widgets" class="pro-feature"><span class="pro">PRO</span> tool</span></a></p>';
1908 echo '</div>';
1909 echo '</div>'; // delete widgets
1910
1911 $theme = wp_get_theme();
1912
1913 echo '<div class="card">';
1914 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete Themes', 'wp-reset'), 'tool-delete-themes', array('iot_button' => true, 'collapse_button' => true)));
1915 echo '<div class="card-body">';
1916 /* translators: %s is replaced with the currently active theme name */
1917 WP_Reset_Utility::wp_kses_wf('<p>' . sprintf(__('All themes will be deleted. Including the currently active theme - %s.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset'), esc_html($theme->get('Name'))) . '</p>');
1918 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, true));
1919 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all themes?" 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 does not make any file backups." data-text-done="%n themes have been deleted." data-text-done-singular="One theme has been deleted." class="button button-delete" href="#" id="delete-themes">Delete all themes</a></p>';
1920 echo '</div>';
1921 echo '</div>'; // delete themes
1922
1923 echo '<div class="card">';
1924 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete Plugins', 'wp-reset'), 'tool-delete-plugins', array('iot_button' => true, 'collapse_button' => true)));
1925 echo '<div class="card-body">';
1926 WP_Reset_Utility::wp_kses_wf('<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 file backups.</b>', 'wp-reset') . '</p>');
1927 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, true));
1928 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all plugins?" 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 does not make any file backups." data-text-done="%n plugins have been deleted." data-text-done-singular="One plugin has been deleted." class="button button-delete" href="#" id="delete-plugins">Delete plugins</a></p>';
1929 echo '</div>';
1930 echo '</div>'; // delete plugins
1931
1932 echo '<div class="card default-collapsed">';
1933 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete MU Plugins & Drop-ins', 'wp-reset'), 'tool-delete-mu-plugins-dropins', array('collapse_button' => true, 'iot_button' => true, 'pro' => true)));
1934 echo '<div class="card-body">';
1935 echo '<p>MU Plugins are located in <code>/wp-content/mu-plugins/</code> and are, as the name suggests, must-use plugins that are automatically activated by WP and can\'t be deactiavated via the <a href="' . esc_url(admin_url('plugins.php?plugin_status=mustuse')) . '" target="_blank">plugins interface</a>, although if any are used, they are listed in the "Must Use" tab.<br>';
1936 echo 'Drop-ins are pieces of code found in <code>/wp-content/</code> that replace default, built-in WordPress functionality. Most often used are <code>db.php</code> and <code>advanced-cache.php</code> that implement custom DB and cache functionality. They can\'t be deactivated via the <a href="' . esc_url(admin_url('plugins.php?plugin_status=dropins')) . '" target="_blank">plugins interface</a> but if any are present are listed in the "Drop-in" tab.</p>';
1937
1938 if (is_multisite()) {
1939 echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would delete plugins for all sites in the network since they all share the same plugin files.</p>';
1940 } else {
1941 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, false, true));
1942 echo '<p class="mb0"><a class="button button-delete button-pro-feature" href="#">Delete must use plugins - <span data-feature="tool-delete-mu-plugins" class="pro-feature"><span class="pro">PRO</span> tool</span></a><a class="button button-delete button-pro-feature" href="#">Delete drop-ins - <span data-feature="tool-delete-dropins" class="pro-feature"><span class="pro">PRO</span> tool</span></a></p>';
1943 }
1944 echo '</div>';
1945 echo '</div>'; // delete MU plugins and dropins
1946
1947 $upload_dir = wp_upload_dir(gmdate('Y/m'), true);
1948 $upload_dir['basedir'] = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $upload_dir['basedir']);
1949
1950 echo '<div class="card">';
1951 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Clean uploads Folder', 'wp-reset'), 'tool-delete-uploads', array('iot_button' => true, 'collapse_button' => true)));
1952 echo '<div class="card-body">';
1953 /* translators: %1$s is replaced with the uploads folder path, %2$s is replaced with the URL to WordPress Media upload page */
1954 WP_Reset_Utility::wp_kses_wf('<p>' . sprintf(__('All files in <code>%1$s</code> folder will be deleted. Including folders and subfolders, and files in subfolders. Files associated with <a href="%2$s">media</a> entries will be deleted too.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset'), esc_html($upload_dir['basedir']), esc_url(admin_url('upload.php'))) . '</p>');
1955 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, false));
1956 if (false != $upload_dir['error']) {
1957 echo '<p class="mb0"><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>';
1958 } else {
1959 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all files &amp; folders in uploads folder?" 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 does not make any file backups." data-text-done="%n files &amp; folders have been deleted." data-text-done-singular="One file or folder has been deleted." class="button button-delete" href="#" id="delete-uploads">Delete all files &amp; folders in uploads folder</a></p>';
1960 }
1961 echo '</div>';
1962 echo '</div>'; // clean uploads folder
1963
1964 echo '<div class="card default-collapsed">';
1965 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Clean wp-content Folder', 'wp-reset'), 'tool-delete-wp-content', array('collapse_button' => true, 'iot_button' => true, 'pro' => true)));
1966 echo '<div class="card-body">';
1967 echo '<p>All folders and their content in <code>wp-content</code> folder except the following ones will be deleted: <code>mu-plugins</code>, <code>plugins</code>, <code>themes</code>, <code>uploads</code>, <code>wp-reset-autosnapshots</code>, <code>wp-reset-snapshots-export</code>.</p>';
1968 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, false));
1969 if (false === $wp_filesystem->is_writable(trailingslashit(WP_CONTENT_DIR))) {
1970 echo '<p class="mb0"><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>';
1971 } else {
1972 echo '<p class="mb0"><a class="button button-delete button-pro-feature" href="#">Clean wp-content folder - <span data-feature="tool-delete-wp-content" class="pro-feature"><span class="pro">PRO</span> tool</span></a></p>';
1973 }
1974 echo '</div>';
1975 echo '</div>'; // clean wp-content
1976
1977 $custom_tables = $this->get_custom_tables();
1978
1979 echo '<div class="card">';
1980 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Empty or Delete Custom Tables', 'wp-reset'), 'tool-empty-delete-custom-tables', array('iot_button' => true, 'collapse_button' => true)));
1981 echo '<div class="card-body">';
1982 /* translators: %s is replaced with the table prefix */
1983 WP_Reset_Utility::wp_kses_wf('<p>' . sprintf(__('This action affects only custom tables with <code>%s</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>Always <a href="#" class="create-new-snapshot" data-description="Before deleting custom tables">create a snapshot</a> before using this tool if you want to be able to undo its actions.</p>', 'wp-reset'), esc_html($wpdb->prefix)));
1984 if ($custom_tables) {
1985 /* translators: %d is replaced with the number of custom tables */
1986 WP_Reset_Utility::wp_kses_wf('<p>' . sprintf(__('The following %d custom tables are affected by this tool: ', 'wp-reset'), sizeof($custom_tables)));
1987 foreach ($custom_tables as $tbl) {
1988 echo '<code>' . esc_html($tbl['name']) . '</code>';
1989 if (next($custom_tables)) {
1990 echo ', ';
1991 }
1992 } // foreach
1993 echo '.</p>';
1994 $custom_tables_btns = '';
1995 } else {
1996 echo '<p>' . esc_html__('There are no custom tables. There\'s nothing for this tool to empty or delete.', 'wp-reset') . '</p>';
1997 $custom_tables_btns = ' disabled';
1998 }
1999 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(false, true, true));
2000 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to empty all custom tables?" data-btn-confirm="Empty custom tables" data-text-wait="Emptying custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . esc_attr($wpdb->prefix) . '</code> will be emptied. Always ' . esc_attr('<a href="#" class="create-new-snapshot" data-description="Before emptying custom tables">create a snapshot</a> if you want to be able to undo') . '." data-text-done="%n custom tables have been emptied." data-text-done-singular="One custom table has been emptied." class="button button-delete' . esc_attr($custom_tables_btns) . '" href="#" id="truncate-custom-tables">Empty (truncate) custom tables</a>';
2001 echo '<a data-confirm-title="Are you sure you want to delete all custom tables?" data-btn-confirm="Delete custom tables" data-text-wait="Deleting custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . esc_attr($wpdb->prefix) . '</code> will be deleted. Always ' . esc_attr('<a href="#" class="create-new-snapshot" data-description="Before deleting custom tables">create a snapshot</a> if you want to be able to undo') . '." data-text-done="%n custom tables have been deleted." data-text-done-singular="One custom table has been deleted." class="button button-delete' . esc_attr($custom_tables_btns) . '" href="#" id="drop-custom-tables">Delete (drop) custom tables</a>';
2002 WP_Reset_Utility::wp_kses_wf($this->get_snapshot_button('drop-custom-tables', 'Before deleting custom tables'));
2003 echo '</p>';
2004 echo '</div>';
2005 echo '</div>'; // empty custom tables
2006
2007 echo '<div class="card default-collapsed">';
2008 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Switch WP Version', 'wp-reset'), 'tool-switch-wp-version', array('collapse_button' => true, 'iot_button' => true, 'pro' => true)));
2009 echo '<div class="card-body">';
2010 if (is_multisite()) {
2011 echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would change the WP version for all sites in the network since they all share the same core files.</p>';
2012 } else {
2013 echo '<p>Replace current WordPress version with the selected new version. Switching from a previous version, to a newer version is mostly supported and properly handled by the WP installer. Reverting WordPress, rolling back WordPress to a previous version is not supported. Results may vary!</p>';
2014 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, true));
2015
2016 $wp_versions = WP_Reset_Utility::get_wordpress_versions();
2017 echo '<p><label for="select-wp-version">Select the WordPress version to switch to:</label> ';
2018 echo '<select id="select-wp-version">';
2019 echo '<option value="">select WordPress version</option>';
2020 foreach ($wp_versions as $version => $release_date) {
2021 if ($release_date == 'bleeding') {
2022 echo '<option value="bleeding">WordPress v' . esc_html($version) . ' (Bleeding edge nightly)' . ($wp_version == $version ? ' - installed' : '') . '</option>';
2023 } elseif ($release_date == 'point') {
2024 echo '<option value="point-' . esc_attr(substr($version, 0, 3)) . '">WordPress v' . esc_html($version) . ' (Point release nightly)' . ($wp_version == $version ? ' - installed' : '') . '</option>';
2025 } else {
2026 echo '<option value="' . esc_attr($version) . '">WordPress v' . esc_html($version) . ' (' . esc_attr(gmdate('Y-m-d', $release_date)) . ')' . ($wp_version == $version ? ' - installed' : '') . '</option>';
2027 }
2028 }
2029 echo '</select></p>';
2030
2031 echo '<p class="mb0">';
2032 echo '<a class="button button-delete button-pro-feature" href="#">Switch WordPress version - <span data-feature="tool-switch-wp-version" class="pro-feature"><span class="pro">PRO</span> tool</span></a>';
2033 echo '</p>';
2034 }
2035 echo '</div>';
2036 echo '</div>'; // switch WP version
2037
2038 echo '<div class="card">';
2039 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Delete .htaccess File', 'wp-reset'), 'tool-delete-htaccess', array('iot_button' => true, 'collapse_button' => true)));
2040 echo '<div class="card-body">';
2041 /* translators: %s is replaced with the .htaccess file path */
2042 WP_Reset_Utility::wp_kses_wf('<p>' . sprintf(__('This action deletes the .htaccess file located in <code>%s</code><br><b>There is NO UNDO. WP Reset does not make any file backups.</b></p>', 'wp-reset'), esc_html($this->get_htaccess_path())));
2043
2044 echo '<p>If you need to edit .htaccess, install our free <a href="' . esc_url(admin_url('plugin-install.php?tab=plugin-information&plugin=wp-htaccess-editor&TB_iframe=true&width=600&height=550')) . '" class="thickbox open-plugin-details-modal">WP Htaccess Editor</a> plugin. It automatically creates backups when you edit .htaccess as well as checks for syntax errors. To create the default .htaccess file open <a href="' . esc_url(admin_url('options-permalink.php')) . '">Settings - Permalinks</a> and re-save settings. WordPress will recreate the file.</p>';
2045 WP_Reset_Utility::wp_kses_wf($this->get_tool_icons(true, false));
2046 echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete the .htaccess file?" 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 does not make any file backups." data-text-done="Htaccess file has been deleted." data-text-done-singular="Htaccess file has been deleted." class="button button-delete" href="#" id="delete-htaccess">Delete .htaccess file</a></p>';
2047
2048 echo '</div>';
2049 echo '</div>'; // delete htaccess
2050 } // tab_tools
2051
2052
2053 /**
2054 * Echoes content for collections tab
2055 *
2056 * @return null
2057 */
2058 private function tab_collections()
2059 {
2060 echo '<div class="card">';
2061 WP_Reset_Utility::wp_kses_wf($this->get_card_header('What are Plugin & Theme Collections?', 'collections-info', array('collapse_button' => false)));
2062 echo '<div class="card-body">';
2063 WP_Reset_Utility::wp_kses_wf('<p>' . __('Have a set of plugins (and themes) that you install and activate after every reset? Or on every fresh WordPress installation? Well, no more clicking install &amp; active for ten minutes! Build the collection once and install it with one click as many times as needed.</p><p>WP Reset stores collections in the cloud so they\'re accessible on every site you build. You can use free plugins and themes from the official repo, and PRO ones by uploading a ZIP file. We\'ll safely store your license keys too, so you have everything in one place.', 'wp-reset') . '</p>');
2064 echo '<p><a class="button button-secondary button-pro-feature" href="#">Add a new collection - <span data-feature="collections" class="pro-feature"><span class="pro">PRO</span> feature</span></a> &nbsp; <a class="button button-secondary button-pro-feature" href="#">Reload my saved collections from the cloud - <span data-feature="collections" class="pro-feature"><span class="pro">PRO</span> feature</span></a></p>';
2065 echo '</div>';
2066 echo '</div>'; // collections-info
2067
2068 $plugins = array();
2069 $plugins['eps-301-redirects'] = array('name' => '301 Redirects', 'desc' => 'Easiest way to manage redirects');
2070 $plugins['classic-editor'] = array('name' => 'Classic Editor', 'desc' => 'Any easy fix for all your Gutenberg caused troubles');
2071 $plugins['simple-author-box'] = array('name' => 'Simple Author Box', 'desc' => 'Simplest way to add responsive, great looking author boxes');
2072 $plugins['sticky-menu-or-anything-on-scroll'] = array('name' => 'Sticky Menu (or Anything!) on Scroll', 'desc' => 'Make any element on the page sticky.');
2073 $plugins['under-construction-page'] = array('name' => 'UnderConstructionPage', 'desc' => 'Working on your site? Put it in the under construction mode.');
2074 $plugins['wp-external-links'] = array('name' => 'WP External Links', 'desc' => 'Manage all external & internal links. Control icons, nofollow, noopener, UGC, sponsored and if links open in new window or new tab.');
2075
2076 echo '<div class="card" data-collection-id="1">';
2077 WP_Reset_Utility::wp_kses_wf($this->get_card_header('Must Have WordPress Plugins', 'collection-id-1', array('collapse_button' => false)));
2078 echo '<div class="card-body"><div class="thirdx2"><p class="_mb0"></p><div class="dropdown dropdown-right">
2079 <a class="button dropdown-toggle" href="#">Install collection</a>
2080 <div class="dropdown-menu">
2081 <a class="dropdown-item install-collection" data-activate="true" href="#">Install &amp; activate collection</a>
2082 <a class="dropdown-item install-collection" href="#">Install collection</a>
2083 <a data-feature="collections" class="dropdown-item button-pro-feature" href="#">Delete installed plugins &amp; themes then install &amp; activate collection - <span class="pro-feature" data-feature="cloud-wpr"><span class="pro">PRO</span> Feature</span></a>
2084 <a data-feature="collections" class="dropdown-item button-pro-feature" href="#">Delete installed plugins &amp; themes then install collection - <span class="pro-feature" data-feature="cloud-wpr"><span class="pro">PRO</span> Feature</span></a>
2085 </div>
2086 </div><a class="button add-collection-item button-pro-feature" href="#">Add new plugin or theme - <span data-feature="collections" class="pro-feature"><span class="pro">PRO</span> feature</span></a></div><div class="third textright"><p class="_mb0"></p><div class="dropdown">
2087 <a class="button dropdown-toggle" href="#">Actions</a>
2088 <div class="dropdown-menu">
2089 <a class="dropdown-item button-pro-feature" href="#">Add new collection - <span class="pro-feature" data-feature="collections"><span class="pro">PRO</span> Feature</span></a>
2090 <a class="dropdown-item button-pro-feature" href="#">Rename collection - <span class="pro-feature" data-feature="collections"><span class="pro">PRO</span> Feature</span></a>
2091 <a class="dropdown-item button-delete button-pro-feature" href="#">Delete collection - <span class="pro-feature" data-feature="collections"><span class="pro">PRO</span> Feature</span></a>
2092 </div>
2093 </div><p></p></div><table class="collection-table"><tbody><tr><th>Type</th><th>Name &amp; Note</th><th class="actions">Actions</th></tr>';
2094 foreach ($plugins as $slug => $plugin) {
2095 echo '<tr data-slug="' . esc_attr($slug) . '"><td><span class="dashicons dashicons-admin-plugins tooltip" title="Plugin"></span><span class="dashicons dashicons-wordpress tooltip" title="Comes from the WordPress repository"></span></td><td class="collection-item-details"><span>' . esc_html($plugin['name']) . '</span><i>' . esc_html($plugin['desc']) . '</i></td><td class="textcenter"><div class="dropdown">
2096 <a class="button dropdown-toggle" href="#">Actions</a>
2097 <div class="dropdown-menu">
2098 <a href="#" class="dropdown-item install-collection-item button-pro-feature">Install - <span class="pro-feature" data-feature="collections"><span class="pro">PRO</span> Feature</span></a>
2099 <a href="#" class="dropdown-item install-collection-item button-pro-feature">Install &amp; Activate - <span class="pro-feature" data-feature="collections"><span class="pro">PRO</span> Feature</span></a>
2100 <a href="#" class="dropdown-item edit-collection-item button-pro-feature">Edit - <span class="pro-feature" data-feature="collections"><span class="pro">PRO</span> Feature</span></a>
2101 <a href="#" class="dropdown-item button-delete button-link-delete delete-collection-item button-pro-feature">Delete - <span class="pro-feature" data-feature="collections"><span class="pro">PRO</span> Feature</span></a>
2102 </div>
2103 </div></td></tr>';
2104 } // foreach plugin
2105 echo '</tbody></table></div></div>';
2106 } // tab_collections
2107
2108
2109 /**
2110 * Echoes content for support tab
2111 *
2112 * @return null
2113 */
2114 private function tab_support()
2115 {
2116 echo '<div class="card">';
2117 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Documentation', 'wp-reset'), 'support-documentation', array('collapse_button' => false)));
2118 echo '<div class="card-body">';
2119 /* translators: %s is replaced with the link to the documentation page */
2120 WP_Reset_Utility::wp_kses_wf('<p class="mb0">' . sprintf(__('All tools and features are explained in detail in <a href="%s" target="_blank">the documentation</a>. We did our best to describe how things work on both the code level and an "average user" level.', 'wp-reset'), esc_url($this->generate_web_link('support-tab', '/documentation/'))) . '</p>');
2121 echo '</div>';
2122 echo '</div>'; // documentation
2123
2124 echo '<div class="card">';
2125 WP_Reset_Utility::wp_kses_wf($this->get_card_header('Emergency Recovery Script', 'support-ers', array('collapse_button' => false, 'pro' => true)));
2126 echo '<div class="card-body">';
2127 echo '<p>Emergency Recovery Script is a standalone, single-file, WordPress independent PHP script created to <b>recover WordPress sites from the most difficult situations</b>. When access to the admin is not possible when core files are compromised (accidental delete or malware related situations), when you get the white screen of death, can\'t log in for whatever reason or a plugin has killed your site - emergency recovery script can fix the problem! Some of the things ERS can do;</p>';
2128 echo '<ul class="plain-list">';
2129 echo '<li>Test the integrity of all WP core files and reinstall them if needed</li>';
2130 echo '<li>Detect and remove all files in core folders that are not a part of WP</li>';
2131 echo '<li>Deactivate and activate plugins without logging in to WP admin</li>';
2132 echo '<li>Deactivate and activate themes without logging in to WP admin</li>';
2133 echo '<li>Reset user privileges and roles</li>';
2134 echo '<li>Create new WP admin accounts without logging in to WP admin or knowing the admin username/password</li>';
2135 echo '<li>Modify WordPress address and site address</li>';
2136 echo '</ul>';
2137 echo '<p class="mb0">You can install the script as a preventive measure, so it\'s always available in case of an emergency (don\'t worry, it\'s password protected), or upload it only when needed. On production sites, when big and potentially dangerous changes rarely happen, we suggest uploading it only when needed. On test sites, have it ready in advance because there\'s a higher probability that you\'ll need it. Emergency Recovery Script is a <span class="pro-feature pro-feature-text" data-feature="support-ers">WP Reset <span>PRO</span></span> tool.</p>';
2138 echo '</div>';
2139 echo '</div>'; // emergency recovery script
2140
2141 echo '<div class="card">';
2142 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Public Support Forum', 'wp-reset'), 'support-forum', array('collapse_button' => false)));
2143 echo '<div class="card-body">';
2144 WP_Reset_Utility::wp_kses_wf('<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>');
2145 echo '</div>';
2146 echo '</div>'; // forum
2147
2148 echo '<div class="card">';
2149 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Premium Email Support', 'wp-reset'), 'support-email', array('collapse_button' => false, 'pro' => true)));
2150 echo '<div class="card-body">';
2151 echo '<p class="mb0">Need urgent support? Have one of our devs personally help you with your issue. All PRO license holders have access to premium email support. Get <span class="pro-feature pro-feature-text" data-feature="support-email">WP Reset <span>PRO</span></span> now.</p>';
2152 echo '</div>';
2153 echo '</div>'; // email support
2154
2155 echo '<div class="card">';
2156 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Care to Help Out?', 'wp-reset'), 'support-help-out', array('collapse_button' => false)));
2157 echo '<div class="card-body">';
2158 WP_Reset_Utility::wp_kses_wf('<p class="mb0">' . __('No need for donations :) 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. A public mention <a href="https://twitter.com/webfactoryltd" target="_blank">@webfactoryltd</a> also does wonders. Thank you!', 'wp-reset') . '</p>');
2159 echo '</div>';
2160 echo '</div>'; // help out
2161 } // tab_support
2162
2163
2164 /**
2165 * Echoes content for pro tab
2166 *
2167 * @return null
2168 */
2169 private function tab_pro()
2170 {
2171 $agency_lifetime = $this->generate_web_link('pricing-table', '/buy/', array('p' => 'wp-reset-pro-agency-ltd-launch'));
2172 $team_lifetime = $this->generate_web_link('pricing-table', '/', array(), 'pricing');
2173 $personal_lifetime = $this->generate_web_link('pricing-table', '/buy/', array('p' => 'wp-reset-pro-personal-ltd-launch'));
2174
2175 echo '<div class="card">';
2176 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('WP Reset PRO', 'wp-reset'), 'pro-features', array('collapse_button' => false)));
2177 echo '<div class="card-body">';
2178 echo '<p>More ways to reset your site, more tools, automatic snapshots, collections, email support and the emergency recover script - that\'s WP Reset PRO in a nutshell. The same <b>quality and easy-of-use</b> you experienced in the free version is very much a part of the PRO one, but extended and upgraded with more tools that will save you even more time.</p>';
2179 echo '<p>WP Reset PRO is aimed towards <b>webmasters, agencies, and everyone who buildsa a lot of WordPress sites</b>. It\'s much, much more than a "reset" tool. It\'s an easy way to start a new site, to test changes and to get out of the thickest jams. And thanks to its cloud features and the Dashboard it\'ll give you access to collections and snapshots on all the sites you\'re working on - instantly, without dragging any files along.</p>';
2180 echo '<p>Give WP Reset PRO a go. <b>It\'ll pay itself out in hours saved within the first few days!</b></p>';
2181 echo '<p>If you already have a PRO license, activate it below.</p>';
2182 echo '<p class="textcenter"><a href="#" data-feature="purchase-pro" class="button-pro-feature button button-delete">Get PRO now</a></p>';
2183 echo '</div>';
2184 echo '</div>';
2185
2186 echo '<div class="card">';
2187 WP_Reset_Utility::wp_kses_wf($this->get_card_header(__('Activate PRO License', 'wp-reset'), 'pro-activate', array('collapse_button' => false)));
2188 echo '<div class="card-body">';
2189
2190 echo '<p>License key is visible on the confirmation screen, right after purchasing. You can also find it in the confirmation email sent to the email address provided on purchase. Or use keys created with the <a href="https://dashboard.wpreset.com/licenses/" target="_blank">license manager</a>.</p>
2191 <p>If you don\'t have a license - <a class="button-pro-feature" href="#" data-feature="purchase-pro2">purchase one now</a>. In case of problems with the license please <a href="' . esc_url($this->generate_web_link('pro-tab-license', '/contact/')) . '" target="_blank">contact support</a>.</p>';
2192
2193 echo '<hr>';
2194 echo '<p><label for="wpr-license-key">License Key: </label><input class="regular-text" type="text" id="wpr-license-key" value="' . ($this->license->get_license('license_key') != 'keyless' ? esc_attr($this->license->get_license('license_key')) : '') . '" placeholder="12345678-12345678-12345678-12345678">';
2195
2196 echo '<br><label>Status: </label>';
2197 if ($this->license->is_active()) {
2198 $license_formatted = $this->license->get_license_formatted();
2199 echo '<b style="color: #66b317;">Active</b><br>
2200 <label>Type: </label>' . esc_html($license_formatted['name_long']);
2201 echo '<br><label>Valid: </label>' . esc_html($license_formatted['valid_until']);
2202
2203 echo '<p>Thank you for purchasing WP Reset PRO! <b>Your license has been verified and activated.</b>';
2204 echo '<br>To start using the PRO version, please follow these steps:</p>';
2205 echo '<ol>';
2206 echo '<li><a href="https://dashboard.wpreset.com/pro-download/" target="_blank">Download</a> the latest version of the PRO plugin.</li>';
2207 echo '<li>Go to <a href="' . esc_url(admin_url('plugin-install.php')) . '">Plugins - Add New - Upload Plugin</a> and upload the ZIP you just downloaded.</li>';
2208 echo '<li>If asked to replace (overwrite) the free version - confirm it.</li>';
2209 echo '<li>Activate the plugin.</li>';
2210 echo '<li>That\'s it, no more steps.</li>';
2211 echo '</ol>';
2212 } else { // not active
2213 echo '<strong style="color: #ea1919;">Inactive</strong>';
2214 if (!empty($this->license->get_license('error'))) {
2215 echo '<br><label>Error: </label>' . esc_html($this->license->get_license('error'));
2216 }
2217 }
2218 echo '</p>';
2219
2220 echo '<p>';
2221 if ($this->license->is_active()) {
2222 echo '<a href="#" id="wpr-save-license" data-text-wait="Validating. Please wait." class="button button-secondary">Save &amp; Revalidate License</a>';
2223 echo '&nbsp; &nbsp;<a href="#" id="wpr-deactivate-license" data-text-wait="Deactivating. Please wait." class="button button-delete">Deactivate License</a>';
2224 } else {
2225 echo '<a href="#" id="wpr-save-license" data-text-wait="Activating. Please wait." class="button button-primary">Save &amp; Activate License</a>';
2226 echo '&nbsp; &nbsp;<a href="#" data-text-wait="Activating. Please wait." class="button button-secondary" id="wpr-keyless-activation">Keyless Activation</a>';
2227 }
2228 echo '</p>';
2229 echo '<p class="mb0"><i>By attempting to activate a license you agree to share the following data with <a target="_blank" href="https://www.webfactoryltd.com/">WebFactory Ltd</a>: license key, site URL, site title, site WP version, site IP, and WP Reset plugin (free) version.</i>';
2230 echo '</p>';
2231
2232 echo '</div>';
2233 echo '</div>'; // activate PRO
2234
2235 WP_Reset_Utility::wp_kses_wf($this->pro_dialog());
2236 } // tab_pro
2237
2238
2239 function pro_dialog()
2240 {
2241 $out = '';
2242
2243 $out .= '<div id="wpreset-pro-dialog" style="display: none;" title="WP Reset PRO"><span class="ui-helper-hidden-accessible"><input type="text"/></span>';
2244
2245 $out .= '<div class="center logo"><a href="https://wpreset.com/?ref=wpreset-free-pricing-table" target="_blank"><img src="' . $this->plugin_url . 'img/wp-reset-logo.png' . '" alt="WP Reset PRO" title="WP Reset PRO"></a><br>';
2246
2247 $out .= '<span>Limited PRO Offer - <b>all prices are LIFETIME</b>! Pay once &amp; use forever!</span>';
2248 $out .= '</div>';
2249
2250 $out .= '<table id="wpreset-pro-table">';
2251 $out .= '<tr>';
2252 $out .= '<td class="center">Lifetime Personal License</td>';
2253 $out .= '<td class="center">Lifetime Team License</td>';
2254 $out .= '<td class="center">Lifetime Agency License</td>';
2255 $out .= '</tr>';
2256
2257 $out .= '<tr class="prices">';
2258 $out .= '<td class="center"><del>$39 /year</del><br><span>$59</span> <b>/lifetime</b></td>';
2259 $out .= '<td class="center"><del>$79 /year</del><br><span>$69</span> <b>/lifetime</b></td>';
2260 $out .= '<td class="center"><del>$119 /year</del><br><span>$149</span> <b>/lifetime</b></td>';
2261 $out .= '</tr>';
2262
2263 $out .= '<tr>';
2264 $out .= '<td><span class="dashicons dashicons-yes"></span><b>1 Site License</b></td>';
2265 $out .= '<td><span class="dashicons dashicons-yes"></span><b>5 Sites License</b></td>';
2266 $out .= '<td><span class="dashicons dashicons-yes"></span><b>100 Sites License</b></td>';
2267 $out .= '</tr>';
2268
2269 $out .= '<tr>';
2270 $out .= '<td><span class="dashicons dashicons-yes"></span>All Plugin Features &amp; Tools</td>';
2271 $out .= '<td><span class="dashicons dashicons-yes"></span>All Plugin Features &amp; Tools</td>';
2272 $out .= '<td><span class="dashicons dashicons-yes"></span>All Plugin Features &amp; Tools</td>';
2273 $out .= '</tr>';
2274
2275 $out .= '<tr>';
2276 $out .= '<td><span class="dashicons dashicons-yes"></span>Lifetime Updates &amp; Support</td>';
2277 $out .= '<td><span class="dashicons dashicons-yes"></span>Lifetime Updates &amp; Support</td>';
2278 $out .= '<td><span class="dashicons dashicons-yes"></span>Lifetime Updates &amp; Support</td>';
2279 $out .= '</tr>';
2280
2281 $out .= '<tr>';
2282 $out .= '<td><span class="dashicons dashicons-yes"></span>2 GB WP Reset Cloud Storage</td>';
2283 $out .= '<td><span class="dashicons dashicons-yes"></span>10 GB WP Reset Cloud Storage</td>';
2284 $out .= '<td><span class="dashicons dashicons-yes"></span>100 GB WP Reset Cloud Storage</td>';
2285 $out .= '</tr>';
2286
2287 $out .= '<tr>';
2288 $out .= '<td><span class="dashicons dashicons-yes"></span>Dropbox &amp; Google Drive support</td>';
2289 $out .= '<td><span class="dashicons dashicons-yes"></span>Dropbox &amp; Google Drive support</td>';
2290 $out .= '<td><span class="dashicons dashicons-yes"></span>Dropbox &amp; Google Drive support</td>';
2291 $out .= '</tr>';
2292
2293 $out .= '<tr>';
2294 $out .= '<td><span class="dashicons dashicons-yes"></span>Plugins & Themes Collections</td>';
2295 $out .= '<td><span class="dashicons dashicons-yes"></span>Plugins & Themes Collections</td>';
2296 $out .= '<td><span class="dashicons dashicons-yes"></span>Plugins & Themes Collections</td>';
2297 $out .= '</tr>';
2298
2299 $out .= '<tr>';
2300 $out .= '<td><span class="dashicons dashicons-yes"></span>Automatic Snapshots</td>';
2301 $out .= '<td><span class="dashicons dashicons-yes"></span>Automatic Snapshots</td>';
2302 $out .= '<td><span class="dashicons dashicons-yes"></span>Automatic Snapshots</td>';
2303 $out .= '</tr>';
2304
2305 $out .= '<tr>';
2306 $out .= '<td><span class="dashicons dashicons-yes"></span>Emergency Recovery Script</td>';
2307 $out .= '<td><span class="dashicons dashicons-yes"></span>Emergency Recovery Script</td>';
2308 $out .= '<td><span class="dashicons dashicons-yes"></span>Emergency Recovery Script</td>';
2309 $out .= '</tr>';
2310
2311 $out .= '<tr>';
2312 $out .= '<td><span class="dashicons dashicons-no"></span>Licenses &amp; Sites Manager</td>';
2313 $out .= '<td><span class="dashicons dashicons-yes"></span>Licenses &amp; Sites Manager</td>';
2314 $out .= '<td><span class="dashicons dashicons-yes"></span>Licenses &amp; Sites Manager</td>';
2315 $out .= '</tr>';
2316
2317 $out .= '<tr>';
2318 $out .= '<td><span class="dashicons dashicons-no"></span>White-label Mode</td>';
2319 $out .= '<td><span class="dashicons dashicons-yes"></span>White-label Mode</td>';
2320 $out .= '<td><span class="dashicons dashicons-yes"></span>White-label Mode</td>';
2321 $out .= '</tr>';
2322
2323 $out .= '<tr>';
2324 $out .= '<td><span class="dashicons dashicons-no"></span>Full Plugin Rebranding</td>';
2325 $out .= '<td><span class="dashicons dashicons-no"></span>Full Plugin Rebranding</td>';
2326 $out .= '<td><span class="dashicons dashicons-yes"></span>Full Plugin Rebranding</td>';
2327 $out .= '</tr>';
2328
2329 $out .= '<tr>';
2330 $out .= '<td><a class="button button-buy" data-href-org="https://wpreset.com/buy2/?product=personal-free&ref=pricing-table" href="https://wpreset.com/buy2/?product=personal-free&ref=pricing-table" target="_blank">Lifetime License<br>$59 -&gt; BUY NOW</a></td>';
2331 $out .= '<td><a class="button button-buy" data-href-org="https://wpreset.com/buy2/?product=team-free&ref=pricing-table" href="https://wpreset.com/buy2/?product=team-free&ref=pricing-table" target="_blank">Lifetime License<br>$69 -&gt; BUY NOW</a></td>';
2332 $out .= '<td><a class="button button-buy" data-href-org="https://wpreset.com/buy2/?product=agency-free&ref=pricing-table" href="https://wpreset.com/buy2/?product=agency-free&ref=pricing-table" target="_blank">Lifetime License<br>$149 -&gt; BUY NOW</a></td>';
2333 $out .= '</tr>';
2334
2335 $out .= '</table>';
2336
2337 $out .= '<div class="center footer"><b>100% No-Risk Money Back Guarantee!</b> If you don\'t like the plugin over the next 7 days, we will happily refund 100% of your money. No questions asked! Payments are processed by our merchant of records - <a href="https://paddle.com/" target="_blank">Paddle</a>.</div></div>';
2338
2339 return $out;
2340 } // pro_dialog
2341
2342
2343 /**
2344 * Echoes content for snapshots tab
2345 *
2346 * @return null
2347 */
2348 private function tab_snapshots()
2349 {
2350 global $wpdb;
2351 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
2352
2353 echo '<div class="card" id="card-snapshots">';
2354 echo '<h4>';
2355 echo esc_html__('Snapshots', 'wp-reset');
2356 echo '<div class="card-header-right"><a class="toggle-card tooltip" href="#" title="' . esc_html__('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a></div>';
2357 echo '</h4>';
2358 echo '<div class="card-body">';
2359 echo '<p>A snapshot is a copy of all WP database tables, standard and custom ones, saved in the site\'s database. <a href="https://www.youtube.com/watch?v=xBfMmS12vMY" target="_blank">Watch a short video</a> overview and tutorial about Snapshots.</p>';
2360
2361 echo '<p>Snapshots are primarily a development tool. When using various reset tools we advise using our 1-click snapshot tool available in every tool\'s confirmation dialog. If a full backup that includes files is needed, use one of the <a href="' . esc_url(admin_url('plugin-install.php?s=backup&tab=search&type=term')) . '" target="_blank">backup plugins</a> from the repo.</p>';
2362
2363 echo '<p>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. Restoring a snapshot does not affect other snapshots, or WP Reset settings.</p>';
2364
2365 echo '<p>To automatically generate snapshots on plugin, theme, and core update, activate, deactivate and similar events enable automatic snapshots available in <span class="pro-feature pro-feature-text" data-feature="snapshots-auto">WP Reset <span>PRO</span></span>.</p>';
2366
2367 // phpcs:ignore db call warning as we are using uncommon queries
2368 $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); // phpcs:ignore
2369 if (is_array($tables)) {
2370 foreach ($tables as $table) {
2371 if (0 !== stripos($table[0], $wpdb->prefix)) {
2372 continue;
2373 }
2374
2375 if (in_array($table[0], $this->core_tables)) {
2376 $tbl_core++;
2377 } else {
2378 $tbl_custom++;
2379 }
2380 } // foreach
2381
2382 echo '<p class="mb0"><b>Currently used WordPress tables</b>, prefixed with <i>' . esc_html($wpdb->prefix) . '</i>, consist of ' . esc_html($tbl_core) . ' standard and ';
2383 if ($tbl_custom) {
2384 echo esc_attr($tbl_custom) . ' custom table' . ($tbl_custom == 1 ? '' : 's');
2385 } else {
2386 echo 'no custom tables';
2387 }
2388 echo ' <span id="wpr-table-details"><a href="#" id="show-table-details">(show details)</a></span>';
2389 } else {
2390 echo '<b>Tables information is not available.</b> Something is not working properly on your site. Snapshots won\'t work.';
2391 }
2392
2393 echo '</div>';
2394 echo '</div>';
2395
2396 echo '<div class="card">';
2397 WP_Reset_Utility::wp_kses_wf($this->get_card_header('User Created Snapshots', 'snapshots-user', array('collapse_button' => 1, 'create_snapshot' => true, 'snapshot_actions' => true)));
2398 echo '<div class="card-body">';
2399 if ($snapshots = $this->get_snapshots()) {
2400 $snapshots = array_reverse($snapshots);
2401 echo '<table id="wpr-snapshots">';
2402 echo '<tr><th>Date</th><th>Description</th><th class="ss-size">Size</th><th class="ss-actions">
2403 <div class="dropdown" style="font-weight:normal; margin-left: 4px;"><a class="button dropdown-toggle" href="#">Actions</a>
2404 <div class="dropdown-menu">
2405 <a class="dropdown-item button-pro-feature" href="#">Import a snapshot - <span class="pro-feature" data-feature="snapshots"><span class="pro">PRO</span> Feature</span></a>
2406 <a class="dropdown-item button-pro-feature" href="#">Cloud snapshots - <span class="pro-feature" data-feature="snapshots"><span class="pro">PRO</span> Feature</span></a>
2407 </div>
2408 </div></th></tr>';
2409 foreach ($snapshots as $ss) {
2410 echo '<tr id="wpr-ss-' . esc_attr($ss['uid']) . '">';
2411 if (!empty($ss['name'])) {
2412 $name = $ss['name'];
2413 } else {
2414 $name = 'created on ' . gmdate(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . gmdate(get_option('time_format'), strtotime($ss['timestamp']));
2415 }
2416
2417 echo '<td>';
2418 if (current_time('timestamp') - strtotime($ss['timestamp']) > 12 * HOUR_IN_SECONDS) {
2419 echo esc_attr(gmdate(get_option('date_format'), strtotime($ss['timestamp']))) . '<br>@ ' . esc_attr(gmdate(get_option('time_format'), strtotime($ss['timestamp'])));
2420 } else {
2421 echo esc_attr(human_time_diff(strtotime($ss['timestamp']), current_time('timestamp'))) . ' ago';
2422 }
2423 echo '</td>';
2424
2425 echo '<td>';
2426 if (!empty($ss['name'])) {
2427 echo '<b>' . esc_html($ss['name']) . '</b><br>';
2428 }
2429 echo esc_attr($ss['tbl_core']) . ' standard &amp; ';
2430 if ($ss['tbl_custom']) {
2431 echo esc_attr($ss['tbl_custom']) . ' custom table' . ($ss['tbl_custom'] == 1 ? '' : 's');
2432 } else {
2433 echo 'no custom tables';
2434 }
2435 echo ' totaling ' . esc_attr(number_format($ss['tbl_rows'])) . ' rows</td>';
2436 echo '<td class="ss-size">' . esc_attr(WP_Reset_Utility::format_size($ss['tbl_size'])) . '</td>';
2437 echo '<td>';
2438 echo '<div class="dropdown">
2439 <a class="button dropdown-toggle" href="#">Actions</a>
2440 <div class="dropdown-menu">';
2441 echo '<a data-title="Current DB tables compared to snapshot %s" data-wait-msg="Comparing. Please wait." data-name="' . esc_attr($name) . '" title="Compare snapshot to current database tables" href="#" class="ss-action compare-snapshot dropdown-item tooltip" data-ss-uid="' . esc_attr($ss['uid']) . '">Compare snapshot to current data</a>';
2442 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 dropdown-item tooltip" data-ss-uid="' . esc_attr($ss['uid']) . '">Restore snapshot</a>';
2443 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 dropdown-item tooltip" data-ss-uid="' . esc_attr($ss['uid']) . '">Download snapshot</a>';
2444 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 dropdown-item tooltip" data-ss-uid="' . esc_attr($ss['uid']) . '">Delete snapshot</a>';
2445 echo '<a href="#" title="WP Reset PRO feature" data-feature="cloud-wpr" class="ss-action dropdown-item button-pro-feature tooltip">Upload to WP Reset Cloud - <span class="pro-feature" data-feature="cloud-wpr"><span class="pro">PRO</span> Feature</span></a>';
2446 echo '<a href="#" title="WP Reset PRO feature" class="ss-action dropdown-item button-pro-feature tooltip" data-feature="cloud-general">Upload to Dropbox, Google Drive, or pCloud - <span class="pro-feature" data-feature="cloud-general"><span class="pro">PRO</span> Feature</span></a>';
2447 echo '</div></div></td>';
2448 echo '</tr>';
2449 } // foreach
2450 echo '</table>';
2451 echo '<p id="ss-no-snapshots" class="mb0 textcenter hidden">There are no user created snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
2452 } else {
2453 echo '<p id="ss-no-snapshots" class="mb0 textcenter">There are no user created snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
2454 }
2455 echo '</div>';
2456 echo '</div>';
2457
2458 echo '<div class="card">';
2459 WP_Reset_Utility::wp_kses_wf($this->get_card_header('Automatic Snapshots', 'snapshots-auto', array('collapse_button' => false, 'pro' => true)));
2460 echo '<div class="card-body">';
2461 echo '<p><span class="pro-feature pro-feature-text" data-feature="snapshots-auto">WP Reset <span>PRO</span></span> creates automatic snapshots before significant events occur on your site that can cause it to stop working correctly. Plugin, theme and core updates, plugin and theme activations and deactivations all of those can happen in the background without your knowledge. With automatic snapshots, you can roll back any update with a single click. Snapshots can be uploaded to the WP Reset Cloud, Dropbox, Google Drive or pCloud, giving you an extra layer of security.<br>
2462 Upgrade to <span class="pro-feature pro-feature-text" data-feature="snapshots-auto">WP Reset <span>PRO</span></span> to enable automatic snapshots and give your site an extra layer of safety.</p>';
2463 echo '</div>';
2464 echo '</div>';
2465 } // tab_snapshots
2466
2467
2468 /**
2469 * Helper function for generating links
2470 *
2471 * @param string $placement Optional. UTM content param.
2472 * @param string $page Optional. Page to link to.
2473 * @param array $params Optional. Extra URL params.
2474 * @param string $anchor Optional. URL anchor part.
2475 *
2476 * @return string
2477 */
2478 function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '')
2479 {
2480 $base_url = 'https://wpreset.com';
2481
2482 if ('/' != $page) {
2483 $page = '/' . trim($page, '/') . '/';
2484 }
2485 if ($page == '//') {
2486 $page = '/';
2487 }
2488
2489 if ($placement) {
2490 $placement = trim($placement);
2491 $placement = '-' . $placement;
2492 }
2493
2494 $parts = array_merge(array('ref' => 'wp-reset-free' . $placement), $params);
2495
2496 if (!empty($anchor)) {
2497 $anchor = '#' . trim($anchor, '#');
2498 }
2499
2500 $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;
2501
2502 return $out;
2503 } // generate_web_link
2504
2505
2506 /**
2507 * Returns all saved snapshots from DB
2508 *
2509 * @return array
2510 */
2511 function get_snapshots()
2512 {
2513 $snapshots = get_option('wp-reset-snapshots', array());
2514
2515 return $snapshots;
2516 } // get_snapshots
2517
2518
2519 /**
2520 * Returns all custom table names, with prefix
2521 *
2522 * @return array
2523 */
2524 function get_custom_tables()
2525 {
2526 global $wpdb;
2527 $custom_tables = array();
2528
2529 // phpcs:ignore db call warning as we are using uncommon queries
2530 $table_status = $wpdb->get_results('SHOW TABLE STATUS'); // phpcs:ignore
2531 if (is_array($table_status)) {
2532 foreach ($table_status as $index => $table) {
2533 if (0 !== stripos($table->Name, $wpdb->prefix)) {
2534 continue;
2535 }
2536 if (empty($table->Engine)) {
2537 continue;
2538 }
2539
2540 if (false === in_array($table->Name, $this->core_tables)) {
2541 $custom_tables[] = array('name' => $table->Name, 'rows' => $table->Rows, 'data_length' => $table->Data_length, 'index_length' => $table->Index_length);
2542 }
2543 } // foreach
2544 }
2545
2546 return $custom_tables;
2547 } // get_custom tables
2548
2549
2550 /**
2551 * Creates snapshot of current tables by copying them in the DB and saving metadata.
2552 *
2553 * @param int $name Optional. Name for the new snapshot.
2554 *
2555 * @return array|WP_Error Snapshot details in array on success, or error object on fail.
2556 */
2557 function do_create_snapshot($name = '')
2558 {
2559 global $wpdb;
2560 $snapshots = $this->get_snapshots();
2561 $snapshot = array();
2562 $uid = $this->generate_snapshot_uid();
2563 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
2564
2565 if (!$uid) {
2566 return new WP_Error(1, 'Unable to generate a valid snapshot UID.');
2567 }
2568
2569 if ($name) {
2570 $snapshot['name'] = substr(trim($name), 0, 64);
2571 } else {
2572 $snapshot['name'] = '';
2573 }
2574 $snapshot['uid'] = $uid;
2575 $snapshot['timestamp'] = current_time('mysql');
2576
2577 // phpcs:ignore db call warning as we are using uncommon queries
2578 $table_status = $wpdb->get_results('SHOW TABLE STATUS'); // phpcs:ignore
2579 if (is_array($table_status)) {
2580 foreach ($table_status as $index => $table) {
2581 if (0 !== stripos($table->Name, $wpdb->prefix)) {
2582 continue;
2583 }
2584 if (empty($table->Engine)) {
2585 continue;
2586 }
2587
2588 $tbl_rows += $table->Rows;
2589 $tbl_size += $table->Data_length + $table->Index_length;
2590 if (in_array($table->Name, $this->core_tables)) {
2591 $tbl_core++;
2592 } else {
2593 $tbl_custom++;
2594 }
2595
2596 $wpdb->wpreset_snapshot_table_name = $table->Name;
2597 $wpdb->wpreset_snapshot_table_copy_name = $uid . '_' . $table->Name;
2598
2599 // phpcs:ignore db call warning as we are using uncommon queries
2600 $wpdb->query("OPTIMIZE TABLE " . $wpdb->wpreset_snapshot_table_name); // phpcs:ignore
2601 $wpdb->query("CREATE TABLE " . $wpdb->wpreset_snapshot_table_copy_name . " LIKE " . $wpdb->wpreset_snapshot_table_name); // phpcs:ignore
2602 $wpdb->query("INSERT " . $wpdb->wpreset_snapshot_table_copy_name . " SELECT * FROM " . $wpdb->wpreset_snapshot_table_name); // phpcs:ignore
2603 } // foreach
2604 } else {
2605 return new WP_Error(1, 'Can\'t get table status data.');
2606 }
2607
2608 $snapshot['tbl_core'] = $tbl_core;
2609 $snapshot['tbl_custom'] = $tbl_custom;
2610 $snapshot['tbl_rows'] = $tbl_rows;
2611 $snapshot['tbl_size'] = $tbl_size;
2612
2613
2614 $snapshots[$uid] = $snapshot;
2615 update_option('wp-reset-snapshots', $snapshots);
2616
2617 do_action('wp_reset_create_snapshot', $uid, $snapshot);
2618
2619 return $snapshot;
2620 } // create_snapshot
2621
2622
2623 /**
2624 * Delete snapshot metadata and tables from DB
2625 *
2626 * @param string $uid Snapshot unique 6-char ID.
2627 *
2628 * @return bool|WP_Error True on success, or error object on fail.
2629 */
2630 function do_delete_snapshot($uid = '')
2631 {
2632 global $wpdb;
2633 $snapshots = $this->get_snapshots();
2634
2635 if (strlen($uid) != 6) {
2636 return new WP_Error(1, 'Invalid UID format.');
2637 }
2638
2639 if (!isset($snapshots[$uid])) {
2640 return new WP_Error(1, 'Unknown snapshot ID.');
2641 }
2642
2643 // phpcs:ignore db call warning as we are using uncommon queries
2644 $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%'))); // phpcs:ignore
2645 foreach ($tables as $table) {
2646 $wpdb->wpreset_snapshot_table = $table;
2647 $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->wpreset_snapshot_table); // phpcs:ignore
2648 }
2649
2650 $snapshot_copy = $snapshots[$uid];
2651 unset($snapshots[$uid]);
2652 update_option('wp-reset-snapshots', $snapshots);
2653
2654 do_action('wp_reset_delete_snapshot', $uid, $snapshot_copy);
2655
2656 return true;
2657 } // delete_snapshot
2658
2659
2660 /**
2661 * Exports snapshot as SQL dump; saved in gzipped file in WP_CONTENT folder.
2662 *
2663 * @param string $uid Snapshot unique 6-char ID.
2664 *
2665 * @return string|WP_Error Export base filename, or error object on fail.
2666 */
2667 function do_export_snapshot($uid = '')
2668 {
2669 global $wp_filesystem;
2670 $this->wp_init_filesystem();
2671
2672 $snapshots = $this->get_snapshots();
2673
2674 if (strlen($uid) != 6) {
2675 return new WP_Error(1, 'Invalid snapshot ID format.');
2676 }
2677
2678 if (!isset($snapshots[$uid])) {
2679 return new WP_Error(1, 'Unknown snapshot ID.');
2680 }
2681
2682 require_once $this->plugin_dir . 'libs/dumper.php';
2683
2684 $snapshot_file_uid = md5($this->generate_snapshot_uid(10));
2685 try {
2686 $world_dumper = WPR_Shuttle_Dumper::create(array(
2687 'host' => DB_HOST,
2688 'username' => DB_USER,
2689 'password' => DB_PASSWORD,
2690 'db_name' => DB_NAME,
2691 ));
2692
2693 $folder = wp_mkdir_p(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder);
2694 if (!$folder) {
2695 return new WP_Error(1, 'Unable to create wp-content/' . $this->snapshots_folder . '/ folder.');
2696 }
2697
2698 $htaccess_content = 'AddType application/octet-stream .gz' . PHP_EOL;
2699 $htaccess_content .= 'Options -Indexes' . PHP_EOL;
2700
2701 $wp_filesystem->put_contents(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder . '/.htaccess', $htaccess_content);
2702
2703 $world_dumper->dump(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder . '/wp-reset-snapshot-' . $snapshot_file_uid . '.sql.gz', $uid . '_');
2704 } catch (Shuttle_Exception $e) {
2705 return new WP_Error(1, 'Couldn\'t create snapshot: ' . $e->getMessage());
2706 }
2707
2708 do_action('wp_reset_export_snapshot', 'wp-reset-snapshot-' . $snapshot_file_uid . '.sql.gz');
2709
2710 return 'wp-reset-snapshot-' . $snapshot_file_uid . '.sql.gz';
2711 } // export_snapshot
2712
2713
2714 /**
2715 * Replace current tables with ones in snapshot.
2716 *
2717 * @param string $uid Snapshot unique 6-char ID.
2718 *
2719 * @return bool|WP_Error True on success, or error object on fail.
2720 */
2721 function do_restore_snapshot($uid = '')
2722 {
2723 // phpcs:ignore db call warning as we are using uncommon queries
2724 global $wpdb;
2725 $new_tables = array();
2726 $snapshots = $this->get_snapshots();
2727
2728 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
2729 return $res;
2730 }
2731
2732 $table_status = $wpdb->get_results('SHOW TABLE STATUS'); // phpcs:ignore
2733 if (is_array($table_status)) {
2734 foreach ($table_status as $index => $table) {
2735 if (0 !== stripos($table->Name, $uid . '_')) {
2736 continue;
2737 }
2738 if (empty($table->Engine)) {
2739 continue;
2740 }
2741
2742 $new_tables[] = $table->Name;
2743 } // foreach
2744 } else {
2745 return new WP_Error(1, 'Can\'t get table status data.');
2746 }
2747
2748 foreach ($table_status as $index => $table) {
2749 if (0 !== stripos($table->Name, $wpdb->prefix)) {
2750 continue;
2751 }
2752 if (empty($table->Engine)) {
2753 continue;
2754 }
2755
2756 $wpdb->wpreset_snapshot_table = $table->Name;
2757 $wpdb->query('DROP TABLE ' . $wpdb->wpreset_snapshot_table); // phpcs:ignore
2758 } // foreach
2759
2760 // copy snapshot tables to original name
2761 foreach ($new_tables as $table) {
2762 $wpdb->wpreset_snapshot_table = $table;
2763 $wpdb->wpreset_snapshot_table_new = str_replace($uid . '_', '', $table);
2764
2765 $wpdb->query("CREATE TABLE " . $wpdb->wpreset_snapshot_table_new . " LIKE " . $wpdb->wpreset_snapshot_table); // phpcs:ignore
2766 $wpdb->query("INSERT " . $wpdb->wpreset_snapshot_table_new . " SELECT * FROM " . $wpdb->wpreset_snapshot_table); // phpcs:ignore
2767 }
2768
2769 wp_cache_flush();
2770 update_option('wp-reset', $this->options);
2771 update_option('wp-reset-snapshots', $snapshots);
2772
2773 do_action('wp_reset_restore_snapshot', $uid);
2774
2775 return true;
2776 } // restore_snapshot
2777
2778
2779 /**
2780 * Verifies snapshot integrity by comparing metadata and data in DB
2781 *
2782 * @param string $uid Snapshot unique 6-char ID.
2783 *
2784 * @return bool|WP_Error True on success, or error object on fail.
2785 */
2786 function verify_snapshot_integrity($uid)
2787 {
2788 // phpcs:ignore db call warning as we are using uncommon queries
2789 global $wpdb;
2790 $tbl_core = $tbl_custom = 0;
2791 $snapshots = $this->get_snapshots();
2792
2793 if (strlen($uid) != 6) {
2794 return new WP_Error(1, 'Invalid snapshot ID format.');
2795 }
2796
2797 if (!isset($snapshots[$uid])) {
2798 return new WP_Error(1, 'Unknown snapshot ID.');
2799 }
2800
2801 $snapshot = $snapshots[$uid];
2802
2803 $table_status = $wpdb->get_results('SHOW TABLE STATUS'); // phpcs:ignore
2804 if (is_array($table_status)) {
2805 foreach ($table_status as $index => $table) {
2806 if (0 !== stripos($table->Name, $uid . '_')) {
2807 continue;
2808 }
2809 if (empty($table->Engine)) {
2810 continue;
2811 }
2812
2813 if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
2814 $tbl_core++;
2815 } else {
2816 $tbl_custom++;
2817 }
2818 } // foreach
2819
2820 if ($tbl_core != $snapshot['tbl_core'] || $tbl_custom != $snapshot['tbl_custom']) {
2821 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.');
2822 }
2823 } else {
2824 return new WP_Error(1, 'Can\'t get table status data.');
2825 }
2826
2827 return true;
2828 } // verify_snapshot_integrity
2829
2830
2831 /**
2832 * Compares a selected snapshot with the current table set in DB
2833 *
2834 * @param string $uid Snapshot unique 6-char ID.
2835 *
2836 * @return string|WP_Error Formatted table with details on success, or error object on fail.
2837 */
2838 function do_compare_snapshots($uid)
2839 {
2840 // phpcs:ignore db call warning as we are using uncommon queries
2841 global $wpdb;
2842 $current = $snapshot = array();
2843 $out = $out2 = $out3 = '';
2844
2845 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
2846 return $res;
2847 }
2848
2849 $table_status = $wpdb->get_results('SHOW TABLE STATUS'); // phpcs:ignore
2850 foreach ($table_status as $index => $table) {
2851 if (empty($table->Engine)) {
2852 continue;
2853 }
2854
2855 if (0 !== stripos($table->Name, $uid . '_') && 0 !== stripos($table->Name, $wpdb->prefix)) {
2856 continue;
2857 }
2858
2859 $info = array();
2860 $info['rows'] = $table->Rows;
2861 $info['size_data'] = $table->Data_length;
2862 $info['size_index'] = $table->Index_length;
2863 $wpdb->wpreset_table_name = $table->Name;
2864 $schema = $wpdb->get_row('SHOW CREATE TABLE ' . $wpdb->wpreset_table_name, ARRAY_N); // phpcs:ignore
2865 $info['schema'] = $schema[1];
2866 $info['engine'] = $table->Engine;
2867 $info['fullname'] = $table->Name;
2868 $basename = str_replace(array($uid . '_'), array(''), $table->Name);
2869 $info['basename'] = $basename;
2870 $info['corename'] = str_replace(array($wpdb->prefix), array(''), $basename);
2871 $info['uid'] = $uid;
2872
2873 if (0 === stripos($table->Name, $uid . '_')) {
2874 $snapshot[$basename] = $info;
2875 }
2876
2877 if (0 === stripos($table->Name, $wpdb->prefix)) {
2878 $info['uid'] = '';
2879 $current[$basename] = $info;
2880 }
2881 } // foreach
2882
2883 $in_both = array_keys(array_intersect_key($current, $snapshot));
2884 $in_current_only = array_diff_key($current, $snapshot);
2885 $in_snapshot_only = array_diff_key($snapshot, $current);
2886
2887 $out .= '<br><br>';
2888 foreach ($in_current_only as $table) {
2889 $out .= '<div class="wpr-table-container in-current-only" data-table="' . esc_attr($table['basename']) . '">';
2890 $out .= '<table>';
2891 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
2892 $out .= '<td><b>' . $table['fullname'] . '</b></td>';
2893 $out .= '<td>table is not present in snapshot<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2894 $out .= '</tr>';
2895 $out .= '<tr class="hidden">';
2896 $out .= '<td>';
2897 $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1 ? '' : 's') . ' totaling ' . WP_Reset_Utility::format_size($table['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($table['size_index']) . ' in index.</p>';
2898 $out .= '<pre>' . $table['schema'] . '</pre>';
2899 $out .= '</td>';
2900 $out .= '<td>&nbsp;</td>';
2901 $out .= '</tr>';
2902 $out .= '</table>';
2903 $out .= '</div>';
2904 } // foreach in current only
2905
2906 foreach ($in_snapshot_only as $table) {
2907 $out .= '<div class="wpr-table-container in-snapshot-only" data-table="' . esc_attr($table['basename']) . '">';
2908 $out .= '<table>';
2909 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
2910 $out .= '<td>table is not present in current tables</td>';
2911 $out .= '<td><b>' . esc_html($table['fullname']) . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2912 $out .= '</tr>';
2913 $out .= '<tr class="hidden">';
2914 $out .= '<td>&nbsp;</td>';
2915 $out .= '<td>';
2916 $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1 ? '' : 's') . ' totaling ' . WP_Reset_Utility::format_size($table['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($table['size_index']) . ' in index.</p>';
2917 $out .= '<pre>' . $table['schema'] . '</pre>';
2918 $out .= '</td>';
2919 $out .= '</tr>';
2920 $out .= '</table>';
2921 $out .= '</div>';
2922 } // foreach in snapshot only
2923
2924 foreach ($in_both as $tablename) {
2925 $tbl_current = $current[$tablename];
2926 $tbl_snapshot = $snapshot[$tablename];
2927
2928 $schema1 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_current['schema'], 1);
2929 $schema2 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_snapshot['schema'], 1);
2930 $tbl_snapshot['tmp_schema'] = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $tbl_snapshot['schema']);
2931 $schema2 = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $schema2);
2932
2933 if ($tbl_current['rows'] == $tbl_snapshot['rows'] && $tbl_current['schema'] == $tbl_snapshot['tmp_schema']) {
2934 $out3 .= '<div class="wpr-table-container identical" data-table="' . esc_attr($tablename) . '">';
2935 $out3 .= '<table>';
2936 $out3 .= '<tr title="Click to show/hide more info" class="wpr-table-match header-row">';
2937 $out3 .= '<td><b>' . $tbl_current['fullname'] . '</b></td>';
2938 $out3 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2939 $out3 .= '</tr>';
2940 $out3 .= '<tr class="hidden">';
2941 $out3 .= '<td>';
2942 $out3 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_current['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_current['size_index']) . ' in index.</p>';
2943 $out3 .= '<pre>' . $tbl_current['schema'] . '</pre>';
2944 $out3 .= '</td>';
2945 $out3 .= '<td>';
2946 $out3 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_snapshot['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_snapshot['size_index']) . ' in index.</p>';
2947 $out3 .= '<pre>' . $tbl_snapshot['schema'] . '</pre>';
2948 $out3 .= '</td>';
2949 $out3 .= '</tr>';
2950 $out3 .= '</table>';
2951 $out3 .= '</div>';
2952 } elseif ($schema1 != $schema2) {
2953 require_once $this->plugin_dir . 'libs/diff.php';
2954 require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
2955 $diff = new WPR_Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
2956 $renderer = new WPR_Diff_Renderer_Html_SideBySide;
2957
2958 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2959 $out2 .= '<table>';
2960 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
2961 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> table schemas do not match</td>';
2962 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> table schemas do not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2963 $out2 .= '</tr>';
2964 $out2 .= '<tr class="hidden">';
2965 $out2 .= '<td>';
2966 $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_current['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_current['size_index']) . ' in index.</p>';
2967 $out2 .= '</td>';
2968 $out2 .= '<td>';
2969 $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_snapshot['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_snapshot['size_index']) . ' in index.</p>';
2970 $out2 .= '</td>';
2971 $out2 .= '</tr>';
2972 $out2 .= '<tr class="hidden">';
2973 $out2 .= '<td colspan="2" class="no-padding">';
2974 $out2 .= $diff->Render($renderer);
2975 $out2 .= '</td>';
2976 $out2 .= '</tr>';
2977 $out2 .= '</table>';
2978 $out2 .= '</div>';
2979 } else {
2980 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2981 $out2 .= '<table>';
2982 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
2983 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> data in tables does not match</td>';
2984 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> data in tables does not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2985 $out2 .= '</tr>';
2986 $out2 .= '<tr class="hidden">';
2987 $out2 .= '<td>';
2988 $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_current['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_current['size_index']) . ' in index.</p>';
2989 $out2 .= '</td>';
2990 $out2 .= '<td>';
2991 $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_snapshot['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_snapshot['size_index']) . ' in index.</p>';
2992 $out2 .= '</td>';
2993 $out2 .= '</tr>';
2994
2995 $out2 .= '<tr class="hidden">';
2996 $out2 .= '<td colspan="2">';
2997 if ($tbl_current['corename'] == 'options') {
2998 $ss_prefix = $tbl_snapshot['uid'] . '_' . $wpdb->prefix;
2999 //Due to the working with various table sets, not always prefixed with the WordPress Database Table prefix we need to use custom unprepared queries
3000 $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;"); //phpcs:ignore
3001 $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;"); //phpcs:ignore
3002 $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;"); //phpcs:ignore
3003 $out2 .= '<table class="table_diff">';
3004 $out2 .= '<tr><td style="width: 100px;"><b>Option Name</b></td><td><b>Current Value</b></td><td><b>Snapshot Value</b></td></tr>';
3005 foreach ($diff_rows as $row) {
3006 $out2 .= '<tr>';
3007 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
3008 $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : $row->current_value) . '</td>';
3009 $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : $row->snapshot_value) . '</td>';
3010 $out2 .= '</tr>';
3011 } // foreach
3012 foreach ($only_current as $row) {
3013 $out2 .= '<tr>';
3014 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
3015 $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : $row->current_value) . '</td>';
3016 $out2 .= '<td><i>not found in snapshot</i></td>';
3017 $out2 .= '</tr>';
3018 } // foreach
3019 foreach ($only_current as $row) {
3020 $out2 .= '<tr>';
3021 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
3022 $out2 .= '<td><i>not found in current tables</i></td>';
3023 $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : $row->snapshot_value) . '</td>';
3024 $out2 .= '</tr>';
3025 } // foreach
3026 $out2 .= '</table>';
3027 } else {
3028 $out2 .= '<p class="textcenter">Detailed data diff is not available for this table.</p>';
3029 }
3030 $out2 .= '</td>';
3031 $out2 .= '</tr>';
3032
3033 $out2 .= '</table>';
3034 $out2 .= '</div>';
3035 }
3036 } // foreach in both
3037
3038 return $out . $out2 . $out3;
3039 } // do_compare_snapshots
3040
3041
3042 /**
3043 * Generates a unique snapshot ID; verified non-existing
3044 *
3045 * @return string
3046 */
3047 function generate_snapshot_uid($length = 6)
3048 {
3049 global $wpdb;
3050 $snapshots = $this->get_snapshots();
3051 $cnt = 0;
3052 $uid = false;
3053
3054 do {
3055 $cnt++;
3056 $uid = substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz', $length)), 0, $length);
3057 // phpcs:ignore db call warning as we are using uncommon queries
3058 $verify_db = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array('%' . $uid . '%'))); // phpcs:ignore
3059 } while (!empty($verify_db) && isset($snapshots[$uid]) && $cnt < 30);
3060
3061 if ($cnt == 30) {
3062 $uid = false;
3063 }
3064
3065 return $uid;
3066 } // generate_snapshot_uid
3067
3068
3069 // auto download / install / activate WP Force SSL plugin
3070 function install_wpfssl()
3071 {
3072 check_ajax_referer('install_wpfssl');
3073
3074 if (false === current_user_can('manage_options')) {
3075 wp_die('Sorry, you have to be an admin to run this action.');
3076 }
3077
3078 $plugin_slug = 'wp-force-ssl/wp-force-ssl.php';
3079 $plugin_zip = 'https://downloads.wordpress.org/plugin/wp-force-ssl.latest-stable.zip';
3080
3081 @include_once ABSPATH . 'wp-admin/includes/plugin.php';
3082 @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
3083 @include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
3084 @include_once ABSPATH . 'wp-admin/includes/file.php';
3085 @include_once ABSPATH . 'wp-admin/includes/misc.php';
3086 echo '<style>
3087 body{
3088 font-family: sans-serif;
3089 font-size: 14px;
3090 line-height: 1.5;
3091 color: #444;
3092 }
3093 </style>';
3094
3095 echo '<div style="margin: 20px; color:#444;">';
3096 echo 'If things are not done in a minute <a target="_parent" href="' . esc_url(admin_url('plugin-install.php?s=force%20ssl%20webfactory&tab=search&type=term')) . '">install the plugin manually via Plugins page</a><br><br>';
3097 echo 'Starting ...<br><br>';
3098
3099 wp_cache_flush();
3100 $upgrader = new Plugin_Upgrader();
3101 echo 'Check if WP Force SSL is already installed ... <br />';
3102 if ($this->is_plugin_installed($plugin_slug)) {
3103 echo 'WP Force SSL is already installed! <br /><br />Making sure it\'s the latest version.<br />';
3104 $upgrader->upgrade($plugin_slug);
3105 $installed = true;
3106 } else {
3107 echo 'Installing WP Force SSL.<br />';
3108 $installed = $upgrader->install($plugin_zip);
3109 }
3110 wp_cache_flush();
3111
3112 if (!is_wp_error($installed) && $installed) {
3113 echo 'Activating WP Force SSL.<br />';
3114 $activate = activate_plugin($plugin_slug);
3115
3116 if (is_null($activate)) {
3117 echo 'WP Force SSL Activated.<br />';
3118
3119 echo '<script>setTimeout(function() { top.location = "tools.php?page=wp-reset"; }, 1000);</script>';
3120 echo '<br>If you are not redirected in a few seconds - <a href="tools.php?page=wp-reset" target="_parent">click here</a>.';
3121 }
3122 } else {
3123 echo 'Could not install WP Force SSL. You\'ll have to <a target="_parent" href="' . esc_url(admin_url('plugin-install.php?s=force%20ssl%20webfactory&tab=search&type=term')) . '">download and install manually</a>.';
3124 }
3125
3126 echo '</div>';
3127 } // install_wpfssl
3128
3129 /**
3130 * Initializes the WordPress filesystem.
3131 *
3132 * @return bool
3133 */
3134 function wp_init_filesystem()
3135 {
3136 if (! $this->filesystem_initialized) {
3137 if (! class_exists('WP_Filesystem')) {
3138 require_once ABSPATH . 'wp-admin/includes/file.php';
3139 }
3140
3141 WP_Filesystem();
3142 $this->filesystem_initialized = true;
3143 }
3144
3145 return true;
3146 }
3147
3148 /**
3149 * Clean up on uninstall; no action on deactive at the moment
3150 *
3151 * @return null
3152 */
3153 static function uninstall()
3154 {
3155 delete_option('wp-reset');
3156 delete_option('wp-reset-snapshots');
3157 } // uninstall
3158
3159
3160 /**
3161 * Disabled; we use singleton pattern so magic functions need to be disabled
3162 *
3163 * @return null
3164 */
3165 function __clone() {}
3166
3167
3168 /**
3169 * Disabled; we use singleton pattern so magic functions need to be disabled
3170 *
3171 * @return null
3172 */
3173 function __sleep() {}
3174
3175
3176 /**
3177 * Disabled; we use singleton pattern so magic functions need to be disabled
3178 *
3179 * @return null
3180 */
3181 function __wakeup() {}
3182 } // WP_Reset class
3183
3184
3185 // Create plugin instance and hook things up
3186 // Only if in admin - plugin has no frontend functionality
3187 if (is_admin() || WP_Reset::is_cli_running()) {
3188 global $wp_reset;
3189 $wp_reset = WP_Reset::getInstance();
3190 add_action('plugins_loaded', array($wp_reset, 'load_textdomain'));
3191 register_uninstall_hook(__FILE__, array('WP_Reset', 'uninstall'));
3192 }
3193