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

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