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

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