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

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

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