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

2,423 lines 97.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 site to default installation values without modifying any files. Deletes all customizations and content.
6 Version: 1.60
7 Author: WebFactory Ltd
8 Author URI: https://www.webfactoryltd.com/
9 Text Domain: wp-reset
10
11 Copyright 2015 - 2019 Web factory Ltd (email: wpreset@webfactoryltd.com)
12
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License, version 2, as
15 published by the Free Software Foundation.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 */
26
27 // include only file
28 if (!defined('ABSPATH')) {
29 wp_die(__('Do not open this file directly.', 'wp-reset'));
30 }
31
32
33 // load WP-CLI commands, if needed
34 if (defined('WP_CLI') && WP_CLI) {
35 require_once dirname(__FILE__) . '/wp-reset-cli.php';
36 }
37
38
39 class WP_Reset
40 {
41 protected static $instance = null;
42 public $version = 0;
43 public $plugin_url = '';
44 public $plugin_dir = '';
45 public $snapshots_folder = 'wp-reset-snapshots-export';
46 protected $options = array();
47 private $delete_count = 0;
48 private $licensing_servers = array('https://license1.wpreset.com/', 'https://license2.wpreset.com/');
49 private $core_tables = array('commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'term_relationships', 'term_taxonomy', 'termmeta', 'terms', 'usermeta', 'users');
50
51
52 /**
53 * Creates a new WP_Reset object and implements singleton
54 *
55 * @return WP_Reset
56 */
57 static function getInstance()
58 {
59 if (!is_a(self::$instance, 'WP_Reset')) {
60 self::$instance = new WP_Reset();
61 }
62
63 return self::$instance;
64 } // getInstance
65
66
67 /**
68 * Initialize properties, hook to filters and actions
69 *
70 * @return null
71 */
72 private function __construct()
73 {
74 $this->version = $this->get_plugin_version();
75 $this->plugin_dir = plugin_dir_path(__FILE__);
76 $this->plugin_url = plugin_dir_url(__FILE__);
77 $this->load_options();
78
79 add_action('admin_menu', array($this, 'admin_menu'));
80 add_action('admin_init', array($this, 'do_all_actions'));
81 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
82 add_action('wp_ajax_wp_reset_dismiss_notice', array($this, 'ajax_dismiss_notice'));
83 add_action('wp_ajax_wp_reset_run_tool', array($this, 'ajax_run_tool'));
84 add_action('wp_ajax_wp_reset_submit_survey', array($this, 'ajax_submit_survey'));
85 add_action('admin_action_install_webhooks', array($this, 'install_webhooks'));
86
87 add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'plugin_action_links'));
88 add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
89 add_filter('admin_footer_text', array($this, 'admin_footer_text'));
90 add_filter('install_plugins_table_api_args_featured', array($this, 'featured_plugins_tab'));
91
92 $this->core_tables = array_map(function ($tbl) {
93 global $wpdb;
94 return $wpdb->prefix . $tbl;
95 }, $this->core_tables);
96 } // __construct
97
98
99 /**
100 * Get plugin version from file header
101 *
102 * @return string
103 */
104 function get_plugin_version()
105 {
106 $plugin_data = get_file_data(__FILE__, array('version' => 'Version'), 'plugin');
107
108 return $plugin_data['version'];
109 } // get_plugin_version
110
111
112 /**
113 * Load and prepare the options array
114 * If needed create a new DB entry
115 *
116 * @return array
117 */
118 private function load_options()
119 {
120 $options = get_option('wp-reset', array());
121 $change = false;
122
123 if (!isset($options['meta'])) {
124 $options['meta'] = array('first_version' => $this->version, 'first_install' => current_time('timestamp', true), 'reset_count' => 0);
125 $change = true;
126 }
127 if (!isset($options['dismissed_notices'])) {
128 $options['dismissed_notices'] = array();
129 $change = true;
130 }
131 if (!isset($options['last_run'])) {
132 $options['last_run'] = array();
133 $change = true;
134 }
135 if (!isset($options['options'])) {
136 $options['options'] = array();
137 $change = true;
138 }
139 if ($change) {
140 update_option('wp-reset', $options, true);
141 }
142
143 $this->options = $options;
144 return $options;
145 } // load_options
146
147
148 /**
149 * Get meta part of plugin options
150 *
151 * @return array
152 */
153 function get_meta()
154 {
155 return $this->options['meta'];
156 } // get_meta
157
158
159 /**
160 * Get all dismissed notices, or check for one specific notice
161 *
162 * @param string $notice_name Optional. Check if specified notice is dismissed.
163 *
164 * @return bool|array
165 */
166 function get_dismissed_notices($notice_name = '')
167 {
168 $notices = $this->options['dismissed_notices'];
169
170 if (empty($notice_name)) {
171 return $notices;
172 } else {
173 if (empty($notices[$notice_name])) {
174 return false;
175 } else {
176 return true;
177 }
178 }
179 } // get_dismissed_notices
180
181
182 /**
183 * Get options part of plugin options
184 *
185 * todo: not completed
186 *
187 * @param string $key Optional.
188 *
189 * @return array
190 */
191 function get_options($key = '')
192 {
193 return $this->options['options'];
194 } // get_options
195
196
197 /**
198 * Update plugin options, currently entire array
199 *
200 * todo: this handles the entire options array although it should only do the options part - it's confusing
201 *
202 * @param string $key Data to save.
203 * @param string $data Option key.
204 *
205 * @return bool
206 */
207 function update_options($key, $data)
208 {
209 $this->options[$key] = $data;
210 $tmp = update_option('wp-reset', $this->options);
211
212 return $tmp;
213 } // set_options
214
215
216 /**
217 * Add plugin menu entry under Tools menu
218 *
219 * @return null
220 */
221 function admin_menu()
222 {
223 add_management_page(__('WP Reset', 'wp-reset'), __('WP Reset', 'wp-reset'), 'administrator', 'wp-reset', array($this, 'plugin_page'));
224 } // admin_menu
225
226
227 /**
228 * Dismiss notice via AJAX call
229 *
230 * @return null
231 */
232 function ajax_dismiss_notice()
233 {
234 check_ajax_referer('wp-reset_dismiss_notice');
235
236 if (!current_user_can('administrator')) {
237 wp_send_json_error(__('You are not allowed to run this action.', 'wp-reset'));
238 }
239
240 $notice_name = trim(@$_GET['notice_name']);
241 if (!$this->dismiss_notice($notice_name)) {
242 wp_send_json_error(__('Notice is already dismissed.', 'wp-reset'));
243 } else {
244 wp_send_json_success();
245 }
246 } // ajax_dismiss_notice
247
248
249 /**
250 * Dismiss notice by adding it to dismissed_notices options array
251 *
252 * @param string $notice_name Notice to dismiss.
253 *
254 * @return bool
255 */
256 function dismiss_notice($notice_name)
257 {
258 if ($this->get_dismissed_notices($notice_name)) {
259 return false;
260 } else {
261 $notices = $this->get_dismissed_notices();
262 $notices[$notice_name] = true;
263 $this->update_options('dismissed_notices', $notices);
264 return true;
265 }
266 } // dismiss_notice
267
268
269 /**
270 * Returns all WP pointers
271 *
272 * @return array
273 */
274 function get_pointers()
275 {
276 $pointers = array();
277
278 $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.');
279
280 return $pointers;
281 } // get_pointers
282
283
284 /**
285 * Enqueue CSS and JS files
286 *
287 * @return null
288 */
289 function admin_enqueue_scripts($hook)
290 {
291 // welcome pointer is shown on all pages except WPR to admins, until dismissed
292 $pointers = $this->get_pointers();
293 $dismissed_notices = $this->get_dismissed_notices();
294 $meta = $this->get_meta();
295
296 foreach ($dismissed_notices as $notice_name => $tmp) {
297 if ($tmp) {
298 unset($pointers[$notice_name]);
299 }
300 } // foreach
301
302 if (!empty($pointers) && !$this->is_plugin_page() && current_user_can('administrator')) {
303 $pointers['_nonce_dismiss_pointer'] = wp_create_nonce('wp-reset_dismiss_notice');
304
305 wp_enqueue_style('wp-pointer');
306
307 wp_enqueue_script('wp-reset-pointers', $this->plugin_url . 'js/wp-reset-pointers.js', array('jquery'), $this->version, true);
308 wp_enqueue_script('wp-pointer');
309 wp_localize_script('wp-pointer', 'wp_reset_pointers', $pointers);
310 }
311
312 // exit early if not on WP Reset page
313 if (!$this->is_plugin_page()) {
314 return;
315 }
316
317 // features survey is shown 5min after install or after first reset
318 $survey = false;
319 if ($this->is_survey_active('features')) {
320 $survey = true;
321 }
322
323 $js_localize = array(
324 'undocumented_error' => __('An undocumented error has occurred. Please refresh the page and try again.', 'wp-reset'),
325 'documented_error' => __('An error has occurred.', 'wp-reset'),
326 'plugin_name' => __('WP Reset', 'wp-reset'),
327 'settings_url' => admin_url('tools.php?page=wp-reset'),
328 'icon_url' => $this->plugin_url . 'img/wp-reset-icon.png',
329 'invalid_confirmation' => __('Please type "reset" in the confirmation field.', 'wp-reset'),
330 'invalid_confirmation_title' => __('Invalid confirmation', 'wp-reset'),
331 'cancel_button' => __('Cancel', 'wp-reset'),
332 'open_survey' => $survey,
333 'ok_button' => __('OK', 'wp-reset'),
334 'confirm_button' => __('Reset WordPress', 'wp-reset'),
335 'confirm_title' => __('Are you sure you want to proceed?', 'wp-reset'),
336 'confirm1' => __('Clicking "Reset WordPress" will reset your site to default values. All content will be lost. There is NO UNDO.', 'wp-reset'),
337 'confirm2' => __('Click "Cancel" to abort.', 'wp-reset'),
338 'doing_reset' => __('Resetting in progress. Please wait.', 'wp-reset'),
339 'nonce_dismiss_notice' => wp_create_nonce('wp-reset_dismiss_notice'),
340 'nonce_run_tool' => wp_create_nonce('wp-reset_run_tool'),
341 'nonce_do_reset' => wp_create_nonce('wp-reset_do_reset'),
342 );
343
344 if ($survey) {
345 $js_localize['nonce_submit_survey'] = wp_create_nonce('wp-reset_submit_survey');
346 }
347 if (!$this->is_webhooks_active()) {
348 $js_localize['webhooks_install_url'] = add_query_arg(array('action' => 'install_webhooks'), admin_url('admin.php'));
349 }
350
351 wp_enqueue_style('wp-jquery-ui-dialog');
352 wp_enqueue_style('wp-reset', $this->plugin_url . 'css/wp-reset.css', array(), $this->version);
353 wp_enqueue_style('wp-reset-sweetalert2', $this->plugin_url . 'css/sweetalert2.min.css', array(), $this->version);
354
355 wp_enqueue_script('jquery-ui-dialog');
356 wp_enqueue_script('jquery-ui-tabs');
357 wp_enqueue_script('wp-reset-sweetalert2', $this->plugin_url . 'js/sweetalert2.min.js', array('jquery'), $this->version, true);
358 wp_enqueue_script('wp-reset', $this->plugin_url . 'js/wp-reset.js', array('jquery'), $this->version, true);
359 wp_localize_script('wp-reset', 'wp_reset', $js_localize);
360
361 // fix for aggressive plugins that include their CSS on all pages
362 wp_dequeue_style('uiStyleSheet');
363 wp_dequeue_style('wpcufpnAdmin');
364 wp_dequeue_style('unifStyleSheet');
365 wp_dequeue_style('wpcufpn_codemirror');
366 wp_dequeue_style('wpcufpn_codemirrorTheme');
367 wp_dequeue_style('collapse-admin-css');
368 wp_dequeue_style('jquery-ui-css');
369 wp_dequeue_style('tribe-common-admin');
370 wp_dequeue_style('file-manager__jquery-ui-css');
371 wp_dequeue_style('file-manager__jquery-ui-css-theme');
372 wp_dequeue_style('wpmegmaps-jqueryui');
373 wp_dequeue_style('wp-botwatch-css');
374 } // admin_enqueue_scripts
375
376
377 /**
378 * Submit user selected survey answers to WPR servers
379 *
380 * @return null
381 */
382 function ajax_submit_survey()
383 {
384 check_ajax_referer('wp-reset_submit_survey');
385
386 $meta = $this->get_meta();
387
388 $vars = wp_parse_args($_POST, array('survey' => '', 'answers' => '', 'custom_answer' => '', 'emailme' => ''));
389 $vars['answers'] = trim($vars['answers'], ',');
390 $vars['custom_answer'] = substr(trim(strip_tags($vars['custom_answer'])), 0, 256);
391
392 if (empty($vars['survey']) || empty($vars['answers'])) {
393 wp_send_json_error();
394 }
395
396 $request_params = array('sslverify' => false, 'timeout' => 15, 'redirection' => 2);
397 $request_args = array(
398 'action' => 'submit_survey',
399 'survey' => $vars['survey'],
400 'email' => $vars['emailme'],
401 'answers' => $vars['answers'],
402 'custom_answer' => $vars['custom_answer'],
403 'first_version' => $meta['first_version'],
404 'version' => $this->version,
405 'codebase' => 'free',
406 'site' => get_home_url()
407 );
408
409 $url = add_query_arg($request_args, $this->licensing_servers[0]);
410 $response = wp_remote_get(esc_url_raw($url), $request_params);
411
412 if (is_wp_error($response) || !wp_remote_retrieve_body($response)) {
413 $url = add_query_arg($request_args, $this->licensing_servers[1]);
414 $response = wp_remote_get(esc_url_raw($url), $request_params);
415 }
416
417 $this->dismiss_notice('survey-' . $vars['survey']);
418
419 wp_send_json_success();
420 } // ajax_submit_survey
421
422
423 /**
424 * Check if named survey should be shown or not
425 *
426 * @param [string] $survey_name Name of the survey to check
427 * @return boolean
428 */
429 function is_survey_active($survey_name)
430 {
431 if (empty($survey_name)) {
432 return false;
433 }
434
435 // all surveys are curently disabled
436 return false;
437
438 if ($this->get_dismissed_notices('survey-' . $survey_name)) {
439 return false;
440 }
441
442 $meta = $this->get_meta();
443 if (current_time('timestamp', true) - $meta['first_install'] > 300 || $meta['reset_count'] > 0) {
444 return true;
445 }
446
447 return false;
448 } // is_survey_active
449
450 /**
451 * Check if WP-CLI is available and running
452 *
453 * @return bool
454 */
455 static function is_cli_running()
456 {
457 if (!is_null($value = apply_filters('wp-reset-override-is-cli-running', null))) {
458 return (bool)$value;
459 }
460
461 if (defined('WP_CLI') && WP_CLI) {
462 return true;
463 } else {
464 return false;
465 }
466 } // is_cli_running
467
468
469 /**
470 * Check if core WP Webhooks and WPR addon plugins are installed and activated
471 *
472 * @return bool
473 */
474 function is_webhooks_active()
475 {
476 if (!function_exists('is_plugin_active') || !function_exists('get_plugin_data')) {
477 require_once ABSPATH . 'wp-admin/includes/plugin.php';
478 }
479
480 if (false == is_plugin_active('wp-webhooks/wp-webhooks.php')) {
481 return false;
482 }
483
484 if (false == is_plugin_active('wpwh-wp-reset-webhook-integration/wpwhpro-wp-reset-webhook-integration.php')) {
485 return false;
486 }
487
488 return true;
489 } // is_webhooks_active
490
491
492 /**
493 * Check if given plugin is installed
494 *
495 * @param [string] $slug Plugin slug
496 * @return boolean
497 */
498 function is_plugin_installed($slug)
499 {
500 if (!function_exists('get_plugins')) {
501 require_once ABSPATH . 'wp-admin/includes/plugin.php';
502 }
503 $all_plugins = get_plugins();
504
505 if (!empty($all_plugins[$slug])) {
506 return true;
507 } else {
508 return false;
509 }
510 } // is_plugin_installed
511
512
513 /**
514 * Auto download/install/upgrade/activate WP Webhooks plugin
515 *
516 * @return null
517 */
518 static function install_webhooks()
519 {
520 $plugin_slug = 'wp-webhooks/wp-webhooks.php';
521 $plugin_zip = 'https://downloads.wordpress.org/plugin/wp-webhooks.latest-stable.zip';
522
523 @include_once ABSPATH . 'wp-admin/includes/plugin.php';
524 @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
525 @include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
526 @include_once ABSPATH . 'wp-admin/includes/file.php';
527 @include_once ABSPATH . 'wp-admin/includes/misc.php';
528 echo '<style>
529 body{
530 font-family: sans-serif;
531 font-size: 14px;
532 line-height: 1.5;
533 color: #444;
534 }
535 </style>';
536
537 echo '<div style="margin: 20px; color:#444;">';
538 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>';
539
540 wp_cache_flush();
541 $upgrader = new Plugin_Upgrader();
542 echo 'Check if WP Webhooks plugin is already installed ... <br />';
543 if (self::is_plugin_installed($plugin_slug)) {
544 echo 'WP Webhooks is already installed!<br />Making sure it\'s the latest version.<br />';
545 $upgrader->upgrade($plugin_slug);
546 $installed = true;
547 } else {
548 echo 'Installing WP Webhooks.<br />';
549 $installed = $upgrader->install($plugin_zip);
550 }
551 wp_cache_flush();
552
553 if (!is_wp_error($installed) && $installed) {
554 echo 'Activating WP Webhooks.<br />';
555 $activate = activate_plugin($plugin_slug);
556
557 if (is_null($activate)) {
558 echo 'WP Webhooks activated.<br />';
559 }
560 } else {
561 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>.';
562 }
563
564 $plugin_slug = 'wpwh-wp-reset-webhook-integration/wpwhpro-wp-reset-webhook-integration.php';
565 $plugin_zip = 'https://downloads.wordpress.org/plugin/wpwh-wp-reset-webhook-integration.latest-stable.zip';
566
567 wp_cache_flush();
568 $upgrader = new Plugin_Upgrader();
569 echo '<br>Check if WP Webhooks WPR addon plugin is already installed ... <br />';
570 if (self::is_plugin_installed($plugin_slug)) {
571 echo 'WP Webhooks WPR addon is already installed!<br />Making sure it\'s the latest version.<br />';
572 $upgrader->upgrade($plugin_slug);
573 $installed = true;
574 } else {
575 echo 'Installing WP Webhooks WPR addon.<br />';
576 $installed = $upgrader->install($plugin_zip);
577 }
578 wp_cache_flush();
579
580 if (!is_wp_error($installed) && $installed) {
581 echo 'Activating WP Webhooks WPR addon.<br />';
582 $activate = activate_plugin($plugin_slug);
583
584 if (is_null($activate)) {
585 echo 'WP Webhooks WPR addon activated.<br />';
586
587 echo '<script>setTimeout(function() { top.location = "tools.php?page=wp-reset"; }, 1000);</script>';
588 echo '<br>If you are not redirected in a few seconds - <a href="tools.php?page=wp-reset" target="_parent">click here</a>.';
589 }
590 } else {
591 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>.';
592 }
593
594 echo '</div>';
595 } // install_webhooks
596
597
598 /**
599 * Deletes all transients.
600 *
601 * @return int Number of deleted transient DB entries
602 */
603 function do_delete_transients()
604 {
605 global $wpdb;
606
607 $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'");
608
609 do_action('wp_reset_delete_transients', $count);
610
611 return $count;
612 } // do_delete_transients
613
614
615 /**
616 * Resets all theme options (mods).
617 *
618 * @param bool $all_themes Delete mods for all themes or just the current one
619 *
620 * @return int Number of deleted mod DB entries
621 */
622 function do_reset_theme_options($all_themes = true)
623 {
624 global $wpdb;
625
626 $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE 'theme_mods\_%' OR option_name LIKE 'mods\_%'");
627
628 do_action('wp_reset_reset_theme_options', $count);
629
630 return $count;
631 } // do_reset_theme_options
632
633
634 /**
635 * Deletes all files in uploads folder.
636 *
637 * @return int Number of deleted files and folders.
638 */
639 function do_delete_uploads()
640 {
641 $upload_dir = wp_get_upload_dir();
642 $this->delete_count = 0;
643
644 $this->delete_folder($upload_dir['basedir'], $upload_dir['basedir']);
645
646 do_action('wp_reset_delete_uploads', $this->delete_count);
647
648 return $this->delete_count;
649 } // do_delete_uploads
650
651
652 /**
653 * Recursively deletes a folder
654 *
655 * @param string $folder Recursive param.
656 * @param string $base_folder Base folder.
657 *
658 * @return bool
659 */
660 private function delete_folder($folder, $base_folder)
661 {
662 $files = array_diff(scandir($folder), array('.', '..'));
663
664 foreach ($files as $file) {
665 if (is_dir($folder . DIRECTORY_SEPARATOR . $file)) {
666 $this->delete_folder($folder . DIRECTORY_SEPARATOR . $file, $base_folder);
667 } else {
668 $tmp = @unlink($folder . DIRECTORY_SEPARATOR . $file);
669 $this->delete_count = $this->delete_count + (int)$tmp;
670 }
671 } // foreach
672
673 if ($folder != $base_folder) {
674 $tmp = @rmdir($folder);
675 $this->delete_count = $this->delete_count + (int)$tmp;
676 return $tmp;
677 } else {
678 return true;
679 }
680 } // delete_folder
681
682
683 /**
684 * Deactivate and delete all plugins
685 *
686 * @param bool $keep_wp_reset Keep WP Reset active and installed
687 * @param bool $silent_deactivate Skip individual plugin deactivation functions when deactivating
688 *
689 * @return int Number of deleted plugins.
690 */
691 function do_delete_plugins($keep_wp_reset = true, $silent_deactivate = false)
692 {
693 if (!function_exists('get_plugins')) {
694 require_once ABSPATH . 'wp-admin/includes/plugin.php';
695 }
696 if (!function_exists('request_filesystem_credentials')) {
697 require_once ABSPATH . 'wp-admin/includes/file.php';
698 }
699
700 $wp_reset_basename = plugin_basename(__FILE__);
701
702 $all_plugins = get_plugins();
703 $active_plugins = (array)get_option('active_plugins', array());
704 if (true == $keep_wp_reset) {
705 if (($key = array_search($wp_reset_basename, $active_plugins)) !== false) {
706 unset($active_plugins[$key]);
707 }
708 unset($all_plugins[$wp_reset_basename]);
709 }
710
711 if (!empty($active_plugins)) {
712 deactivate_plugins($active_plugins, $silent_deactivate, false);
713 }
714
715 if (!empty($all_plugins)) {
716 delete_plugins(array_keys($all_plugins));
717 }
718
719 do_action('wp_reset_delete_plugins', $all_plugins, $all_plugins);
720
721 return sizeof($all_plugins);
722 } // do_delete_plugins
723
724
725 /**
726 * Delete all themes
727 *
728 * @param bool $keep_default_theme Keep default theme
729 *
730 * @return int Number of deleted themes.
731 */
732 function do_delete_themes($keep_default_theme = true)
733 {
734 global $wp_version;
735
736 if (!function_exists('delete_theme')) {
737 require_once ABSPATH . 'wp-admin/includes/theme.php';
738 }
739
740 if (!function_exists('request_filesystem_credentials')) {
741 require_once ABSPATH . 'wp-admin/includes/file.php';
742 }
743
744 if (version_compare($wp_version, '5.0', '<') === true) {
745 $default_theme = 'twentyseventeen';
746 } else {
747 $default_theme = 'twentynineteen';
748 }
749
750 $all_themes = wp_get_themes(array('errors' => null));
751
752 if (true == $keep_default_theme) {
753 unset($all_themes[$default_theme]);
754 }
755
756 foreach ($all_themes as $theme_slug => $theme_details) {
757 $res = delete_theme($theme_slug);
758 }
759
760 if (false == $keep_default_theme) {
761 update_option('template', '');
762 update_option('stylesheet', '');
763 update_option('current_theme', '');
764 }
765
766 do_action('wp_reset_delete_themes', $all_themes);
767
768 return sizeof($all_themes);
769 } // do_delete_themes
770
771
772 /**
773 * Truncate custom tables
774 *
775 * @return int Number of truncated tables.
776 */
777 function do_truncate_custom_tables()
778 {
779 global $wpdb;
780 $custom_tables = $this->get_custom_tables();
781
782 foreach ($custom_tables as $tbl) {
783 $wpdb->query('TRUNCATE TABLE ' . $tbl['name']);
784 } // foreach
785
786 do_action('wp_reset_truncate_custom_tables', $custom_tables);
787
788 return sizeof($custom_tables);
789 } // do_truncate_custom_tables
790
791
792 /**
793 * Drop custom tables
794 *
795 * @return int Number of dropped tables.
796 */
797 function do_drop_custom_tables()
798 {
799 global $wpdb;
800 $custom_tables = $this->get_custom_tables();
801
802 foreach ($custom_tables as $tbl) {
803 $wpdb->query('DROP TABLE IF EXISTS ' . $tbl['name']);
804 } // foreach
805
806 do_action('wp_reset_drop_custom_tables', $custom_tables);
807
808 return sizeof($custom_tables);
809 } // do_drop_custom_tables
810
811
812 /**
813 * Delete .htaccess file
814 *
815 * @return bool|WP_Error Action status.
816 */
817 function do_delete_htaccess()
818 {
819 global $wp_filesystem;
820
821 if (empty($wp_filesystem)) {
822 require_once ABSPATH . '/wp-admin/includes/file.php';
823 WP_Filesystem();
824 }
825
826 $htaccess_path = $this->get_htaccess_path();
827 clearstatcache();
828
829 do_action('wp_reset_delete_htaccess', $htaccess_path);
830
831 if (!$wp_filesystem->is_readable($htaccess_path)) {
832 return new WP_Error(1, 'Htaccess file does not exist; there\'s nothing to delete.');
833 }
834
835 if (!$wp_filesystem->is_writable($htaccess_path)) {
836 return new WP_Error(1, 'Htaccess file is not writable.');
837 }
838
839 if ($wp_filesystem->delete($htaccess_path, false, 'f')) {
840 return true;
841 } else {
842 return new WP_Error(1, 'Unknown error. Unable to delete htaccess file.');
843 }
844 } // do_delete_htaccess
845
846
847 /**
848 * Get .htaccess file path.
849 *
850 * @return string
851 */
852 function get_htaccess_path()
853 {
854 if (!function_exists('get_home_path')) {
855 require_once ABSPATH . 'wp-admin/includes/file.php';
856 }
857
858 if ($this->is_cli_running()) {
859 $_SERVER['SCRIPT_FILENAME'] = ABSPATH;
860 }
861
862 $filepath = get_home_path() . '.htaccess';
863
864 return $filepath;
865 } // get_htaccess_path
866
867
868 /**
869 * Run one tool via AJAX call
870 *
871 * @return null
872 */
873 function ajax_run_tool()
874 {
875 check_ajax_referer('wp-reset_run_tool');
876
877 if (!current_user_can('administrator')) {
878 wp_send_json_error(__('You are not allowed to run this action.', 'wp-reset'));
879 }
880
881 $tool = trim(@$_GET['tool']);
882 $extra_data = trim(@$_GET['extra_data']);
883
884 if ($tool == 'delete_transients') {
885 $cnt = $this->do_delete_transients();
886 wp_send_json_success($cnt);
887 } elseif ($tool == 'reset_theme_options') {
888 $cnt = $this->do_reset_theme_options(true);
889 wp_send_json_success($cnt);
890 } elseif ($tool == 'delete_themes') {
891 $cnt = $this->do_delete_themes(false);
892 wp_send_json_success($cnt);
893 } elseif ($tool == 'delete_plugins') {
894 $cnt = $this->do_delete_plugins(true);
895 wp_send_json_success($cnt);
896 } elseif ($tool == 'delete_uploads') {
897 $cnt = $this->do_delete_uploads();
898 wp_send_json_success($cnt);
899 } elseif ($tool == 'delete_htaccess') {
900 $tmp = $this->do_delete_htaccess();
901 if (is_wp_error($tmp)) {
902 wp_send_json_error($tmp->get_error_message());
903 } else {
904 wp_send_json_success($tmp);
905 }
906 } elseif ($tool == 'drop_custom_tables') {
907 $cnt = $this->do_drop_custom_tables();
908 wp_send_json_success($cnt);
909 } elseif ($tool == 'truncate_custom_tables') {
910 $cnt = $this->do_truncate_custom_tables();
911 wp_send_json_success($cnt);
912 } elseif ($tool == 'delete_snapshot') {
913 $res = $this->do_delete_snapshot($extra_data);
914 if (is_wp_error($res)) {
915 wp_send_json_error($res->get_error_message());
916 } else {
917 wp_send_json_success();
918 }
919 } elseif ($tool == 'download_snapshot') {
920 $res = $this->do_export_snapshot($extra_data);
921 if (is_wp_error($res)) {
922 wp_send_json_error($res->get_error_message());
923 } else {
924 $url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
925 wp_send_json_success($url);
926 }
927 } elseif ($tool == 'restore_snapshot') {
928 $res = $this->do_restore_snapshot($extra_data);
929 if (is_wp_error($res)) {
930 wp_send_json_error($res->get_error_message());
931 } else {
932 wp_send_json_success();
933 }
934 } elseif ($tool == 'compare_snapshots') {
935 $res = $this->do_compare_snapshots($extra_data);
936 if (is_wp_error($res)) {
937 wp_send_json_error($res->get_error_message());
938 } else {
939 wp_send_json_success($res);
940 }
941 } elseif ($tool == 'create_snapshot') {
942 $res = $this->do_create_snapshot($extra_data);
943 if (is_wp_error($res)) {
944 wp_send_json_error($res->get_error_message());
945 } else {
946 wp_send_json_success();
947 }
948 } else {
949 wp_send_json_error(__('Unknown tool.', 'wp-reset'));
950 }
951 } // ajax_run_tool
952
953
954 /**
955 * Reinstall / reset the WP site
956 * There are no failsafes in the function - it reinstalls when called
957 * Redirects when done
958 *
959 * @param array $params Optional.
960 *
961 * @return null
962 */
963 function do_reinstall($params = array())
964 {
965 global $current_user, $wpdb;
966
967 // only admins can reset; double-check
968 if (!$this->is_cli_running() && !current_user_can('administrator')) {
969 return false;
970 }
971
972 // make sure the function is available to us
973 if (!function_exists('wp_install')) {
974 require ABSPATH . '/wp-admin/includes/upgrade.php';
975 }
976
977 // save values that need to be restored after reset
978 // todo: use params to determine what gets restored after reset
979 $blogname = get_option('blogname');
980 $blog_public = get_option('blog_public');
981 $wplang = get_option('wplang');
982 $siteurl = get_option('siteurl');
983 $home = get_option('home');
984 $snapshots = $this->get_snapshots();
985
986 $active_plugins = get_option('active_plugins');
987 $active_theme = wp_get_theme();
988
989 if (!empty($params['reactivate_webhooks'])) {
990 $wpwh1 = get_option('wpwhpro_active_webhooks');
991 $wpwh2 = get_option('wpwhpro_activate_translations');
992 $wpwh3 = get_option('ironikus_webhook_webhooks');
993 }
994
995 // for WP-CLI
996 if (!$current_user->ID) {
997 $tmp = get_users(array('role' => 'administrator', 'order' => 'ASC', 'order_by' => 'ID'));
998 if (empty($tmp[0]->user_login)) {
999 return new WP_Error(1, 'Reset failed. Unable to find any admin users in database.');
1000 }
1001 $current_user = $tmp[0];
1002 }
1003
1004 // delete custom tables with WP's prefix
1005 $prefix = str_replace('_', '\_', $wpdb->prefix);
1006 $tables = $wpdb->get_col("SHOW TABLES LIKE '{$prefix}%'");
1007 foreach ($tables as $table) {
1008 $wpdb->query("DROP TABLE $table");
1009 }
1010
1011 // supress errors for WP_CLI
1012 // todo: find a better way to supress errors and send/not send email on reset
1013 $result = @wp_install($blogname, $current_user->user_login, $current_user->user_email, $blog_public, '', md5(rand()), $wplang);
1014 $user_id = $result['user_id'];
1015
1016 // restore user pass
1017 $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));
1018 $wpdb->query($query);
1019
1020 // restore rest of the settings including WP Reset's
1021 update_option('siteurl', $siteurl);
1022 update_option('home', $home);
1023 update_option('wp-reset', $this->options);
1024 update_option('wp-reset-snapshots', $snapshots);
1025
1026 // remove password nag
1027 if (get_user_meta($user_id, 'default_password_nag')) {
1028 update_user_meta($user_id, 'default_password_nag', false);
1029 }
1030 if (get_user_meta($user_id, $wpdb->prefix . 'default_password_nag')) {
1031 update_user_meta($user_id, $wpdb->prefix . 'default_password_nag', false);
1032 }
1033
1034 $meta = $this->get_meta();
1035 $meta['reset_count']++;
1036 $this->update_options('meta', $meta);
1037
1038 // reactivate theme
1039 if (!empty($params['reactivate_theme'])) {
1040 switch_theme($active_theme->get_stylesheet());
1041 }
1042
1043 // reactivate WP Reset
1044 if (!empty($params['reactivate_wpreset'])) {
1045 activate_plugin(plugin_basename(__FILE__));
1046 }
1047
1048 // reactivate WP Webhooks
1049 if (!empty($params['reactivate_webhooks'])) {
1050 activate_plugin('wp-webhooks/wp-webhooks.php');
1051 activate_plugin('wpwh-wp-reset-webhook-integration/wpwhpro-wp-reset-webhook-integration.php');
1052
1053 update_option('wpwhpro_active_webhooks', $wpwh1);
1054 update_option('wpwhpro_activate_translations', $wpwh2);
1055 update_option('ironikus_webhook_webhooks', $wpwh3);
1056 }
1057
1058 // reactivate all plugins
1059 if (!empty($params['reactivate_plugins'])) {
1060 foreach ($active_plugins as $plugin_file) {
1061 activate_plugin($plugin_file);
1062 }
1063 }
1064
1065 if (!$this->is_cli_running()) {
1066 // log out and log in the old/new user
1067 // since the password doesn't change this is potentially unnecessary
1068 wp_clear_auth_cookie();
1069 wp_set_auth_cookie($user_id);
1070
1071 wp_redirect(admin_url() . '?wp-reset=success');
1072 exit;
1073 }
1074 } // do_reinstall
1075
1076
1077 /**
1078 * Checks wp_reset post value and performs all actions
1079 * todo: handle messages for various actions
1080 *
1081 * @return null|bool
1082 */
1083 function do_all_actions()
1084 {
1085 // only admins can perform actions
1086 if (!current_user_can('administrator')) {
1087 return;
1088 }
1089
1090 if (!empty($_GET['wp-reset']) && stristr($_SERVER['HTTP_REFERER'], 'wp-reset')) {
1091 add_action('admin_notices', array($this, 'notice_successful_reset'));
1092 }
1093
1094 // check nonce
1095 if (true === isset($_POST['wp_reset_confirm']) && false === wp_verify_nonce(@$_POST['_wpnonce'], 'wp-reset')) {
1096 add_settings_error('wp-reset', 'bad-nonce', __('Something went wrong. Please refresh the page and try again.', 'wp-reset'), 'error');
1097 return false;
1098 }
1099
1100 // check confirmation code
1101 if (true === isset($_POST['wp_reset_confirm']) && 'reset' !== $_POST['wp_reset_confirm']) {
1102 add_settings_error('wp-reset', 'bad-confirm', __('<b>Invalid confirmation code.</b> Please type "reset" in the confirmation field.', 'wp-reset'), 'error');
1103 return false;
1104 }
1105
1106 // only one action at the moment
1107 if (true === isset($_POST['wp_reset_confirm']) && 'reset' === $_POST['wp_reset_confirm']) {
1108 $defaults = array(
1109 'reactivate_theme' => '0',
1110 'reactivate_plugins' => '0',
1111 'reactivate_wpreset' => '0',
1112 'reactivate_webhooks' => '0'
1113 );
1114 $params = shortcode_atts($defaults, (array)@$_POST['wpr-post-reset']);
1115
1116 $this->do_reinstall($params);
1117 }
1118 } // do_all_actions
1119
1120
1121 /**
1122 * Add "Open WP Reset Tools" action link to plugins table, left part
1123 *
1124 * @param array $links Initial list of links.
1125 *
1126 * @return array
1127 */
1128 function plugin_action_links($links)
1129 {
1130 $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>';
1131
1132 array_unshift($links, $settings_link);
1133
1134 return $links;
1135 } // plugin_action_links
1136
1137
1138 /**
1139 * Add links to plugin's description in plugins table
1140 *
1141 * @param array $links Initial list of links.
1142 * @param string $file Basename of current plugin.
1143 *
1144 * @return array
1145 */
1146 function plugin_meta_links($links, $file)
1147 {
1148 if ($file !== plugin_basename(__FILE__)) {
1149 return $links;
1150 }
1151
1152 $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset" title="' . __('Get help', 'wp-reset') . '">' . __('Support', 'wp-reset') . '</a>';
1153 $home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-reset') . '">' . __('Plugin Homepage', 'wp-reset') . '</a>';
1154 $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 �
1155 �
1156 �
1157 �
1158 �
1159 ', 'wp-reset') . '</a>';
1160
1161 $links[] = $support_link;
1162 $links[] = $home_link;
1163 $links[] = $rate_link;
1164
1165 return $links;
1166 } // plugin_meta_links
1167
1168
1169 /**
1170 * Test if we're on WPR's admin page
1171 *
1172 * @return bool
1173 */
1174 function is_plugin_page()
1175 {
1176 $current_screen = get_current_screen();
1177
1178 if ($current_screen->id == 'tools_page_wp-reset') {
1179 return true;
1180 } else {
1181 return false;
1182 }
1183 } // is_plugin_page
1184
1185
1186 /**
1187 * Add powered by text in admin footer
1188 *
1189 * @param string $text Default footer text.
1190 *
1191 * @return string
1192 */
1193 function admin_footer_text($text)
1194 {
1195 if (!$this->is_plugin_page()) {
1196 return $text;
1197 }
1198
1199 $text = '<i><a href="' . $this->generate_web_link('admin_footer') . '" title="' . __('Visit WP Reset page for more info', 'wp-reset') . '" target="_blank">WP Reset</a> v' . $this->version . ' by <a href="https://www.webfactoryltd.com/" title="' . __('Visit our site to get more great plugins', 'wp-reset') . '" target="_blank">WebFactory Ltd</a>. Please help us out by <a target="_blank" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" title="Rate the plugin">rating the plugin �
1200 �
1201 �
1202 �
1203 �
1204 </a>.</i>';
1205
1206 return $text;
1207 } // admin_footer_text
1208
1209
1210 /**
1211 * Loads plugin's translated strings
1212 *
1213 * @return null
1214 */
1215 function load_textdomain()
1216 {
1217 load_plugin_textdomain('wp-reset');
1218 } // load_textdomain
1219
1220
1221 /**
1222 * Inform the user that WordPress has been successfully reset
1223 *
1224 * @return null
1225 */
1226 function notice_successful_reset()
1227 {
1228 global $current_user;
1229
1230 echo '<div id="message" class="updated fade"><p>' . sprintf(__('<b>Site has been reset</b> to default settings. User "%s" was restored with the password unchanged. Open <a href="%s">WP Reset</a> to do another reset.', 'wp-reset'), $current_user->user_login, admin_url('tools.php?page=wp-reset')) . '</p></div>';
1231 } // notice_successful_reset
1232
1233
1234 /**
1235 * Outputs complete plugin's admin page
1236 *
1237 * @return null
1238 */
1239 function plugin_page()
1240 {
1241 $notice_shown = false;
1242 $meta = $this->get_meta();
1243 $snapshots = $this->get_snapshots();
1244
1245 // double check for admin privileges
1246 if (!current_user_can('administrator')) {
1247 wp_die(__('Sorry, you are not allowed to access this page.', 'wp-reset'));
1248 }
1249
1250 settings_errors();
1251 echo '<div class="wrap">';
1252 echo '<h1><img id="logo-icon" src="' . $this->plugin_url . 'img/wp-reset-logo.png" title="' . __('WP Reset', 'wp-reset') . '" alt="' . __('WP Reset', 'wp-reset') . '"></h1>';
1253 echo '<form id="wp_reset_form" action="' . admin_url('tools.php?page=wp-reset') . '" method="post" autocomplete="off">';
1254
1255 if (false === $notice_shown && is_multisite()) {
1256 echo '<div class="card notice-wrapper notice-error">';
1257 echo '<h2>' . __('WP Reset is not compatible with multisite!', 'wp-reset') . '</h2>';
1258 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>';
1259 echo '</div>';
1260 $notice_shown = true;
1261 }
1262
1263 // ask for review
1264 // disabled due to survey
1265 if (false && (!empty($meta['reset_count']) || !empty($snapshots)) && false === $notice_shown && false == $this->get_dismissed_notices('rate')) {
1266 echo '<div class="card notice-wrapper">';
1267 echo '<h2>' . __('Please help us keep the plugin free &amp; up-to-date', 'wp-reset') . '</h2>';
1268 echo '<p>' . __('If you use &amp; enjoy WP Reset, <b>please rate it on WordPress.org</b>. It only takes a second and helps us keep the plugin free and maintained. Thank you!', 'wp-reset') . '</p>';
1269 echo '<p><a class="button-primary button" title="' . __('Rate WP Reset', 'wp-reset') . '" target="_blank" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post">' . __('Help keep the plugin free - rate it!', 'wp-reset') . '</a> <a href="#" class="wpr-dismiss-notice dismiss-notice-rate" data-notice="rate">' . __('I\'ve already rated it', 'wp-reset') . '</a></p>';
1270 echo '</div>';
1271 $notice_shown = true;
1272 }
1273
1274 // Tidy Repo ad
1275 // disabled for now
1276 if (false && false === $notice_shown && $meta['reset_count'] >= 2 && false == $this->get_dismissed_notices('tidy')) {
1277 echo '<div class="card notice-wrapper">';
1278 echo '<h2>' . __('Are you a plugin author? Get your plugin reviewed on Tidy Repo', 'wp-reset') . '</h2>';
1279 echo '<p>' . __('Since 2013 Tidy Repo has been reviewing the best and most reliable WordPress plugins. <b>Submitting a plugin is free</b>, so you have nothing to lose and a lot of exposure to gain when it gets reviewed.', 'wp-reset') . '</p>';
1280 echo '<p><a class="button-primary button" title="' . __('Rate WP Reset', 'wp-reset') . '" target="_blank" href="https://tidyrepo.com/?utm_source=wp-reset-free&utm_medium=plugin&utm_content=notification&utm_campaign=wp-reset-free-v' . $this->version . '">' . __('Let Tidy Repo know you have a great plugin', 'wp-reset') . '</a> <a href="#" class="wpr-dismiss-notice dismiss-notice-rate" data-notice="tidy">' . __('Thanks, I\'m not interested', 'wp-reset') . '</a></p>';
1281 echo '</div>';
1282 $notice_shown = true;
1283 }
1284
1285 // Product Hunt banner
1286 if (
1287 false
1288 && false == $this->get_dismissed_notices('product-hunt')
1289 && current_time('mysql', false) >= '2019-04-26'
1290 && current_time('mysql', false) <= '2019-05-03'
1291 ) {
1292 echo '<div class="card notice-wrapper product-hunt"><a title="Check out WP Reset on Product Hunt" href="' . $this->generate_web_link('ph-banner', '/product-hunt-redirect/') . '" target="_blank">';
1293 echo '<img src="' . $this->plugin_url . '/img/product-hunt.png" title="Check out WP Reset on Product Hunt" alt="Check out WP Reset on Product Hunt"> ';
1294 echo '<h2>' . __('We\'re on <b>Product Hunt</b> this week! Come check out your favorite dev tool &amp; show support ;) <i>Thanks!</i>', 'wp-reset') . '</h2>';
1295 echo '</a>';
1296 echo '<a href="#" title="Dismiss notice" class="wpr-dismiss-notice dismiss-notice" data-notice="product-hunt"><span class="dashicons dashicons-no"></span></a>';
1297 echo '</div>';
1298 $notice_shown = true;
1299 }
1300
1301 // tabs
1302 echo '<div id="wp-reset-tabs" class' . __('="', 'wp-reset') . 'ui-tabs">';
1303
1304 echo '<ul class="wpr-main-tab">';
1305 echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
1306 echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
1307 echo '<li><a href="#tab-snapshots">' . __('DB Snapshots', 'wp-reset') . '</a></li>';
1308 echo '<li><a href="#tab-collections">' . __('Collections', 'wp-reset') . '</a></li>';
1309 echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
1310 echo '</ul>';
1311
1312 echo '<div style="display: none;" id="tab-reset">';
1313 $this->tab_reset();
1314 echo '</div>';
1315
1316 echo '<div style="display: none;" id="tab-tools">';
1317 $this->tab_tools();
1318 echo '</div>';
1319
1320 echo '<div style="display: none;" id="tab-snapshots">';
1321 $this->tab_snapshots();
1322 echo '</div>';
1323
1324 echo '<div style="display: none;" id="tab-collections">';
1325 $this->tab_collections();
1326 echo '</div>';
1327
1328 echo '<div style="display: none;" id="tab-support">';
1329 $this->tab_support();
1330 echo '</div>';
1331
1332 echo '</div>'; // tabs
1333
1334 echo '</form>';
1335 echo '</div>'; // wrap
1336
1337 // survey
1338 if ($this->is_survey_active('features')) {
1339 echo '<div id="survey-dialog" style="display: none;" title="Help us make WP Reset better for you"><span class="ui-helper-hidden-accessible"><input type="text"/></span>';
1340 echo '<p class="subtitle"><b>What new features do you need the most?</b> Choose one or two;</p>';
1341
1342 $questions = array();
1343 $questions[] = '<div class="question-wrapper" data-value="backup" title="Click to select/unselect answer">' .
1344 '<span class="dashicons dashicons-yes"></span>' .
1345 '<div class="question"><b>Off-site backups</b><br>' .
1346 '<i>Backup the site to Dropbox, FTP or Google Drive before running any tools</i></div>' .
1347 '</div>';
1348
1349 $questions[] = '<div class="question-wrapper" data-value="wpmu" title="Click to select/unselect answer">' .
1350 '<span class="dashicons dashicons-yes"></span>' .
1351 '<div class="question"><b>WordPress Network (WPMU) compatibility</b><br>' .
1352 '<i>Full support &amp; compatibility for all WP Reset tools for all sites in network</i></div>' .
1353 '</div>';
1354
1355 $questions[] = '<div class="question-wrapper" data-value="nothing" title="Click to select/unselect answer">' .
1356 '<span class="dashicons dashicons-yes"></span>' .
1357 '<div class="question"><b>Don\'t add anything</b><br>' .
1358 '<i>WP Reset is perfect as is - I don\'t need any new features</i></div>' .
1359 '</div>';
1360
1361 $questions[] = '<div class="question-wrapper" data-value="nuclear" title="Click to select/unselect answer">' .
1362 '<span class="dashicons dashicons-yes"></span>' .
1363 '<div class="question"><b>Nuclear reset - run all tools at once</b><br>' .
1364 '<i>Besides resetting, delete all files and all other customizations with one click</i></div>' .
1365 '</div>';
1366
1367 $questions[] = '<div class="question-wrapper" data-value="plugin-collections" title="Click to select/unselect answer">' .
1368 '<span class="dashicons dashicons-yes"></span>' .
1369 '<div class="question"><b>Install a set of plugins/themes after reset</b><br>' .
1370 '<i>Save lists of plugins/themes and automatically install them after resetting</i></div>' .
1371 '</div>';
1372
1373 $questions[] = '<div class="question-wrapper" data-value="change-wp-ver" title="Click to select/unselect answer">' .
1374 '<span class="dashicons dashicons-yes"></span>' .
1375 '<div class="question"><b>Change WordPress version - rollback or upgrade</b><br>' .
1376 '<i>Pick a version of WP you need (older or never) and switch to it with one click</i></div>' .
1377 '</div>';
1378
1379 shuffle($questions);
1380 $questions[] = '<div class="question-wrapper" data-value="custom" title="Click to select/unselect answer">' .
1381 '<span class="dashicons dashicons-yes"></span>' .
1382 '<div class="question"><b>Something we missed?</b><br><i>Enter the feature you need below;</i>' .
1383 '<input type="text" class="custom-input"></div>' .
1384 '</div>';
1385
1386 echo implode(' ', $questions);
1387
1388 $current_user = wp_get_current_user();
1389 echo '<div class="footer">';
1390 echo '<input id="emailme" type="checkbox" value="' . $current_user->user_email . '"> <label for="emailme">Email me on ' . $current_user->user_email . ' when new features are added. We hate SPAM and never send it.</label><br>';
1391 echo '<a data-survey="features" class="submit-survey button-primary button button-large" href="#">Add those features ASAP!</a>';
1392 echo '<a href="#" class="dismiss-survey wpr-dismiss-notice" data-notice="survey-features" data-survey="features"><i>Close the survey and never show it again</i></a>';
1393 echo '</div>';
1394
1395 echo '</div>';
1396 } // survey
1397
1398 if (!$this->is_webhooks_active()) {
1399 echo '<div id="webhooks-dialog" style="display: none;" title="Webhooks"><span class="ui-helper-hidden-accessible"><input type="text"/></span>';
1400 echo '<div style="padding: 20px; font-size: 15px;">';
1401 echo '<ul class="plain-list">';
1402 echo '<li>Standard, platform-independant way of connecting WP to any 3rd party system</li>';
1403 echo '<li>Supports actions - WP receives data on 3rd party events</li>';
1404 echo '<li>And triggers - WP sends data on its events</li>';
1405 echo '<li>Works wonders with Zapier</li>';
1406 echo '<li>Compatible with any WordPress theme or plugin</li>';
1407 echo '<li>Available from the official <a href="https://wordpress.org/plugins/wp-webhooks/" target="_blank">WP plugins repository</a></li>';
1408 echo '</ul>';
1409 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>';
1410 echo '</div>';
1411 echo '</div>';
1412 }
1413 } // plugin_page
1414
1415
1416 /**
1417 * Echoes content for reset tab
1418 *
1419 * @return null
1420 */
1421 private function tab_reset()
1422 {
1423 global $current_user, $wpdb;
1424
1425 echo '<div class="card" id="card-description">';
1426 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1427 echo '<h2>' . __('Please read carefully before proceeding. There is NO UNDO!', 'wp-reset') . '</h2>';
1428 echo '<b class="red">' . __('Resetting will delete:', 'wp-reset') . '</b>';
1429 echo '<ul class="plain-list">';
1430 echo '<li>' . __('all posts, pages, custom post types, comments, media entries, users', 'wp-reset') . '</li>';
1431 echo '<li>' . __('all default WP database tables', 'wp-reset') . '</li>';
1432 echo '<li>' . sprintf(__('all custom database tables that have the same prefix "%s" as default tables in this installation', 'wp-reset'), $wpdb->prefix) . '</li>';
1433 echo '</ul>';
1434
1435 echo '<b class="green">' . __('Resetting will not delete:', 'wp-reset') . '</b>';
1436 echo '<ul class="plain-list">';
1437 echo '<li>' . __('media files - they\'ll remain in the <i>wp-uploads</i> folder but will no longer be listed under Media', 'wp-reset') . '</li>';
1438 echo '<li>' . __('no files are touched; plugins, themes, uploads - everything stays', 'wp-reset') . '</li>';
1439 echo '<li>' . __('site title, WordPress address, site address, site language and search engine visibility settings', 'wp-reset') . '</li>';
1440 echo '<li>' . sprintf(__('logged in user "%s" will be restored with the current password', 'wp-reset'), $current_user->user_login) . '</li>';
1441 echo '</ul>';
1442
1443 echo '<b>' . __('What happens when I click the Reset button?', 'wp-reset') . '</b>';
1444 echo '<ul class="plain-list">';
1445 echo '<li>' . __('you will have to confirm the action one more time because there is NO UNDO', 'wp-reset') . '</li>';
1446 echo '<li>' . __('everything will be reset; see bullets above for details', 'wp-reset') . '</li>';
1447 echo '<li>' . __('site title, WordPress address, site address, site language, search engine visibility and current user will be restored', 'wp-reset') . '</li>';
1448 echo '<li>' . __('you will be logged out, automatically logged in and taken to the admin dashboard', 'wp-reset') . '</li>';
1449 echo '<li>' . __('WP Reset plugin will be reactivated if that option is chosen in the <a href="#card-post-reset">post-reset options</a>', 'wp-reset') . '</li>';
1450 echo '</ul>';
1451
1452 echo '<b>' . __('WP-CLI Support', 'wp-reset') . '</b>';
1453 echo '<p>' . 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>');
1454 echo sprintf(__('All actions have to be confirmed. If you want to skip confirmation use the standard %s option. Please be careful - there is NO UNDO.', 'wp-reset'), '<code>--yes</code>') . '</p>';
1455
1456 echo '<b>' . __('WP Webhooks Support', 'wp-reset') . '</b>';
1457 echo '<p>All WP Reset tools are integrated with <a href="https://wordpress.org/plugins/wp-webhooks/" target="_blank">WP Webhooks</a> and available as (receive data) actions. Webhooks are a standard, platform-independent way of connecting WordPress to any 3rd party system. This <a href="https://underconstructionpage.com/wp-webhooks-connect-integrate-wordpress/" target="_blank">article</a> has more info, videos and use-cases so you can see just how powerful and easy to use webhooks are.<br>';
1458 if ($this->is_webhooks_active()) {
1459 echo 'WP Webhooks are active. Make sure you enable WP Reset actions in <a href="' . admin_url('options-general.php?page=wp-webhooks-pro&wpwhvrs=settings') . '">settings</a>.';
1460 } else {
1461 echo '<a href="#" class="open-webhooks-dialog">Install WP Webhooks &amp; WPR addon</a> to automate your workflow, develop faster and connect WordPress to any web app or 3rd party system.';
1462 }
1463 echo '</p></div>'; // card description
1464
1465 $theme = wp_get_theme();
1466
1467 echo '<div class="card" id="card-post-reset">';
1468 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1469 echo '<h2>' . __('Post-reset actions', 'wp-reset') . '</h2>';
1470 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>';
1471 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>';
1472 if ($this->is_webhooks_active()) {
1473 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>';
1474 }
1475 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>';
1476 if ($this->is_webhooks_active()) {
1477 echo '<p><a href="' . admin_url('options-general.php?page=wp-webhooks-pro&wpwhvrs=settings') . '">Configure WP Webhooks</a> to run additional actions after reset, or connect to any 3rd party system.</p>';
1478 } else {
1479 echo '<p>If you need to run additional actions after reset, or connect to any 3rd party system, <a href="#" class="open-webhooks-dialog">install WP Webhooks &amp; WPR addon</a>. It\'s a standard platform-independent way of connecting WordPress to any other web app. It automates complex workflows and saves time when developing. Have a look at this <a href="https://www.youtube.com/watch?v=m8XDFXCNP9g" target="_blank">short video</a> for a demonstration.</p>';
1480 }
1481 echo '</div>';
1482
1483 echo '<div class="card">';
1484 echo '<h2>' . __('Reset', 'wp-reset') . '</h2>';
1485 echo '<p>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress" button. <b>There is NO UNDO. No backups are made by WP Reset.</b>', 'wp-reset') . '</p>';
1486
1487 wp_nonce_field('wp-reset');
1488 echo '<p><input id="wp_reset_confirm" type="text" name="wp_reset_confirm" placeholder="' . esc_attr__('Type in "reset"', 'wp-reset') . '" value="" autocomplete="off"> &nbsp;';
1489 echo '<input id="wp_reset_submit" type="button" class="button-primary" value="' . __('Reset WordPress', 'wp-reset') . '"></p>';
1490 echo '</div>';
1491 } // tab_reset
1492
1493
1494 /**
1495 * Echoes content for tools tab
1496 *
1497 * @return null
1498 */
1499 private function tab_tools()
1500 {
1501 echo '<div class="card">';
1502 echo '<h2>' . __('Delete Transients', 'wp-reset') . '</h2>';
1503 echo '<p>' . __('All transient related database entries will be deleted. Including expired and non-expired transients, and orphaned transient timeout entries.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
1504 echo '<p><a data-btn-confirm="Delete all transients" data-text-wait="Deleting transients. Please wait." data-text-confirm="All database entries related to transients will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n transient database entries have been deleted." data-text-done-singular="One transient database entry has been deleted." class="button button-delete" href="#" id="delete-transients">Delete all transients</a></p>';
1505 echo '</div>';
1506
1507 $upload_dir = wp_upload_dir(date('Y/m'), true);
1508 $upload_dir['basedir'] = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $upload_dir['basedir']);
1509
1510 echo '<div class="card">';
1511 echo '<h2>' . __('Clean Uploads Folder', 'wp-reset') . '</h2>';
1512 echo '<p>' . __('All files in <code>' . $upload_dir['basedir'] . '</code> folder will be deleted. Including folders and subfolders, and files in subfolders. Files associated with <a href="' . admin_url('upload.php') . '">media</a> entries will be deleted too.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
1513 if (false != $upload_dir['error']) {
1514 echo '<p><span style="color:#dd3036;"><b>Tool is not available.</b></span> Folder is not writeable by WordPress. Please check file and folder access rights.</p>';
1515 } else {
1516 echo '<p><a data-btn-confirm="Delete everything in uploads folder" data-text-wait="Deleting uploads. Please wait." data-text-confirm="All files and folders in uploads will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n files &amp; folders have been deleted." 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>';
1517 }
1518 echo '</div>';
1519
1520 echo '<div class="card">';
1521 echo '<h2>' . __('Reset Theme Options', 'wp-reset') . '</h2>';
1522 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><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
1523 echo '<p><a 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. There is NO UNDO. WP Reset will not make any backups." 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></p>';
1524 echo '</div>';
1525
1526 $theme = wp_get_theme();
1527
1528 echo '<div class="card">';
1529 echo '<h2>' . __('Delete Themes', 'wp-reset') . '</h2>';
1530 echo '<p>' . __('All themes will be deleted. Including the currently active theme - ' . $theme->get('Name') . '.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
1531 echo '<p><a data-btn-confirm="Delete all themes" data-text-wait="Deleting all themes. Please wait." data-text-confirm="All themes will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n themes have been deleted." data-text-done-singular="One theme has been deleted." class="button button-delete" href="#" id="delete-themes">Delete all themes</a></p>';
1532 echo '</div>';
1533
1534 echo '<div class="card">';
1535 echo '<h2>' . __('Delete Plugins', 'wp-reset') . '</h2>';
1536 echo '<p>' . __('All plugins will be deleted except for WP Reset which will remain active.<br><b>There is NO UNDO. WP Reset does not make any backups.</b>', 'wp-reset') . '</p>';
1537 echo '<p><a data-btn-confirm="Delete plugins" data-text-wait="Deleting plugins. Please wait." data-text-confirm="All plugins except WP Reset will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n plugins have been deleted." data-text-done-singular="One plugin has been deleted." class="button button-delete" href="#" id="delete-plugins">Delete plugins</a></p>';
1538 echo '</div>';
1539
1540 global $wpdb;
1541 $custom_tables = $this->get_custom_tables();
1542
1543 echo '<div class="card">';
1544 echo '<h2>' . __('Empty or Delete Custom Tables', 'wp-reset') . '</h2>';
1545 echo '<p>' . __('This action affects only custom tables with <code>' . $wpdb->prefix . '</code> prefix. Core WP tables and other tables in the database that do not have that prefix will not be deleted/emptied. Deleting (dropping) tables completely removes them from the database. Emptying (truncating) removes all content from them, but keeps the structure intact.<br><b>There is NO UNDO. WP Reset does not make any backups.</b></p>', 'wp-reset');
1546 if ($custom_tables) {
1547 echo '<p>' . __('The following ' . sizeof($custom_tables) . ' custom tables are affected by this tool: ');
1548 foreach ($custom_tables as $tbl) {
1549 echo '<code>' . $tbl['name'] . '</code>';
1550 if (next($custom_tables)) {
1551 echo ', ';
1552 }
1553 } // foreach
1554 echo '.</p>';
1555 $custom_tables_btns = '';
1556 } else {
1557 echo '<p>' . __('There are no custom tables. There\'s nothing for this tool to empty or delete.', 'wp-reset') . '</p>';
1558 $custom_tables_btns = ' disabled';
1559 }
1560 echo '<p><a data-btn-confirm="Empty custom tables" data-text-wait="Emptying custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be emptied. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n custom tables have been emptied." 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>&nbsp; &nbsp;';
1561 echo '<a data-btn-confirm="Delete custom tables" data-text-wait="Deleting custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="%n custom tables have been deleted." 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></p>';
1562
1563 echo '</div>';
1564
1565 echo '<div class="card">';
1566 echo '<h2>' . __('Delete .htaccess File', 'wp-reset') . '</h2>';
1567 echo '<p>' . __('This action deletes the .htaccess file located in <code>' . $this->get_htaccess_path() . '</code><br><b>There is NO UNDO. WP Reset does not make any backups.</b></p>', 'wp-reset');
1568
1569 echo '<p>If you need to edit .htaccess, install our free <a href="' . admin_url('plugin-install.php?s=htaccess+editor&tab=search&type=term') . '" target="_blank">WP Htaccess Editor</a> plugin. It automatically creates backups when you edit .htaccess. To create the default .htaccess file open <a href="' . admin_url('options-permalink.php') . '">Settings - Permalinks</a> and re-save settings. WordPress will recreate the file.</p>';
1570
1571 echo '<a data-btn-confirm="Delete .htaccess file" data-text-wait="Deleting .htaccess file. Please wait." data-text-confirm="Htaccess file will be deleted. There is NO UNDO. WP Reset will not make any backups." data-text-done="Htaccess file has been deleted." class="button button-delete" href="#" id="delete-htaccess">Delete .htaccess file</a></p>';
1572
1573 echo '</div>';
1574 } // tab_tools
1575
1576
1577 /**
1578 * Echoes content for collections tab
1579 *
1580 * @return null
1581 */
1582 private function tab_collections()
1583 {
1584 echo '<div class="card">';
1585 echo '<h2>' . __('What are Plugin &amp; Theme Collections', 'wp-reset') . '</h2>';
1586 echo '<p>' . __('Another tools that will save your precious time! Have a set of plugins and themes that you install and activate after every reset? Or on every fresh WP installation? Well, no more clicking install/active for five minutes! Build the collection once and install it with one click the next time you need it. Collections are stored in the cloud so they\'re accessible on every site.', 'wp-reset') . '</p>';
1587 echo '</div>';
1588
1589 echo '<div class="card">';
1590 echo '<h2>' . __('So where do I click?!', 'wp-reset') . '</h2>';
1591 echo '<p>' . __('We are working hard on building collections as we speak and plan to have a beta version done by late May. Want to be the first to try the beta? Or tell us to code faster? :) Give us a shout <a href="https://twitter.com/webfactoryltd" target="_blank">@webfactoryltd</a>.', 'wp-reset') . '</p>';
1592 echo '</div>';
1593 } // tab_collections
1594
1595
1596 /**
1597 * Echoes content for support tab
1598 *
1599 * @return null
1600 */
1601 private function tab_support()
1602 {
1603 echo '<div class="card">';
1604 echo '<h2>' . __('Documentation', 'wp-reset') . '</h2>';
1605 echo '<p>' . __('All tools and functions 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 a "average user" level.', 'wp-reset') . '</p>';
1606 echo '</div>';
1607
1608 echo '<div class="card">';
1609 echo '<h2>' . __('Public support forum', 'wp-reset') . '</h2>';
1610 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>';
1611 echo '</div>';
1612
1613 echo '<div class="card">';
1614 echo '<h2>' . __('Private contact', 'wp-reset') . '</h2>';
1615 echo '<p>' . __('If there\'s a need to contact us privately send emails to <a href="mailto:wpreset@webfactoryltd.com">wpreset@webfactoryltd.com</a>. Please know that although we\'ll gladly have a look at issues you are having with any site, we can\'t promise we\'ll fix them. Thank you for understanding.', 'wp-reset') . '</p>';
1616 echo '</div>';
1617
1618 echo '<div class="card">';
1619 echo '<h2>' . __('Care to help out?', 'wp-reset') . '</h2>';
1620 echo '<p>' . __('No need for donations or anything like that :) If you can give us a <a href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" target="_blank">five star rating</a> you\'ll help out more than you can imagine. A public mention <a href="https://twitter.com/webfactoryltd" target="_blank">@webfactoryltd</a> also does wonders. Thank you!', 'wp-reset') . '</p>';
1621 echo '</div>';
1622 } // tab_support
1623
1624
1625 /**
1626 * Echoes content for snapshots tab
1627 *
1628 * @return null
1629 */
1630 private function tab_snapshots()
1631 {
1632 global $wpdb;
1633 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1634
1635 echo '<div class="card" id="card-snapshots">';
1636 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1637 echo '<h2>' . __('Database Snapshots', 'wp-reset') . '</h2>';
1638 echo '<p>A snapshot is a copy of all WP database tables, standard and custom ones, saved in your database. Files are not saved or included in snapshots in any way.<br>
1639 Snapshots are primarily a development tool. Although they can be used for backups (and downloaded), we suggest finding a more suitable tool for live sites, such as <a href="https://wordpress.org/plugins/updraftplus/" target="_blank">UpdraftPlus</a>. Use snapshots to find out what changes a plugin made to your database or to quickly restore the dev environment after testing database related changes.<br>Restoring a snapshot does not affect other snapshots, or WP Reset settings.</p>';
1640
1641 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1642 if (is_array($table_status)) {
1643 foreach ($table_status as $index => $table) {
1644 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1645 continue;
1646 }
1647 if (empty($table->Engine)) {
1648 continue;
1649 }
1650
1651 $tbl_rows += $table->Rows;
1652 $tbl_size += $table->Data_length + $table->Index_length;
1653 if (in_array($table->Name, $this->core_tables)) {
1654 $tbl_core++;
1655 } else {
1656 $tbl_custom++;
1657 }
1658 } // foreach
1659
1660 echo '<p><b>Currently used WordPress tables</b>, prefixed with <i>' . $wpdb->prefix . '</i>, consist of ' . $tbl_core . ' standard and ';
1661 if ($tbl_custom) {
1662 echo $tbl_custom . ' custom table' . ($tbl_custom == 1 ? '' : 's');
1663 } else {
1664 echo 'no custom tables';
1665 }
1666 echo ' totaling ' . $this->format_size($tbl_size) . ' in ' . number_format($tbl_rows) . ' rows.</p>';
1667 }
1668
1669 echo '';
1670 echo '</div>';
1671
1672 echo '<div class="card no-padding-bottom">';
1673 echo '<a id="create-new-snapshot-primary" data-msg-success="Snapshot created!" data-msg-wait="Creating snapshot. Please wait." data-btn-confirm="Create snapshot" data-placeholder="Snapshot name or brief description, ie: before plugin install" data-text="Enter snapshot name or brief description, up to 64 characters." data-title="Create a new snapshot" title="Create a new database snapshot" href="#" class="button button-primary create-new-snapshot create-new-snapshot-corner">' . __('Create new', 'wp-reset') . '</a>';
1674 echo '<h2>' . __('Saved Snapshots', 'wp-reset') . '</h2>';
1675
1676 if ($snapshots = $this->get_snapshots()) {
1677 echo '<table id="wpr-snapshots">';
1678 echo '<tr><th>Name</th><th>Info &amp; Size</th><th class="ss-actions">Actions</th></tr>';
1679 foreach ($snapshots as $ss) {
1680 echo '<tr id="wpr-ss-' . $ss['uid'] . '">';
1681 if (!empty($ss['name'])) {
1682 echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . $ss['name'] . '</td>';
1683 $name = $ss['name'];
1684 } else {
1685 echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . '' . date(get_option('date_format'), strtotime($ss['timestamp'])) . '<br>@ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '</td>';
1686 $name = 'created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
1687 }
1688 echo '<td>' . $ss['tbl_core'] . ' standard &amp; ';
1689 if ($ss['tbl_custom']) {
1690 echo $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1 ? '' : 's');
1691 } else {
1692 echo 'no custom tables';
1693 }
1694 echo ' totaling ' . $this->format_size($ss['tbl_size']) . ' in ' . number_format($ss['tbl_rows']) . ' rows</td>';
1695 echo '<td>';
1696 echo '<a data-title="Current DB tables compared to snapshot %s" data-wait-msg="Comparing. Please wait." data-name="' . $name . '" title="Compare snapshot to current database tables" href="#" class="ss-action compare-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-visibility"></span></a>';
1697 echo '<a data-btn-confirm="Restore snapshot" data-text-wait="Restoring snapshot. Please wait." data-text-confirm="Are you sure you want to restore the selected snapshot? There is NO UNDO.<br>Restoring the snapshot will delete all current standard and custom tables and replace them with tables from the snapshot." data-text-done="Snapshot has been restored. Click OK to reload the page with new data." title="Restore snapshot by overwriting current database tables" href="#" class="ss-action restore-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-backup"></span></a>';
1698 echo '<a data-success-msg="Snapshot export created!<br><a href=\'%s\'>Download it</a>" data-wait-msg="Exporting snapshot. Please wait." title="Download snapshot as gzipped SQL dump" href="#" class="ss-action download-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-download"></span></a>';
1699 echo '<a data-btn-confirm="Delete snapshot" data-text-wait="Deleting snapshot. Please wait." data-text-confirm="Are you sure you want to delete the selected snapshot and all its data? There is NO UNDO.<br>Deleting the snapshot will not affect the active database tables in any way." data-text-done="Snapshot has been deleted." title="Permanently delete snapshot" href="#" class="ss-action delete-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-trash"></span></a></td>';
1700 echo '</tr>';
1701 } // foreach
1702 echo '</table>';
1703 echo '<p id="ss-no-snapshots" class="hidden">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
1704 } else {
1705 echo '<p id="ss-no-snapshots">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
1706 }
1707
1708 echo '</div>';
1709 } // tab_snapshots
1710
1711
1712 /**
1713 * Helper function for generating UTM tagged links
1714 *
1715 * @param string $placement Optional. UTM content param.
1716 * @param string $page Optional. Page to link to.
1717 * @param array $params Optional. Extra URL params.
1718 * @param string $anchor Optional. URL anchor part.
1719 *
1720 * @return string
1721 */
1722 function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '')
1723 {
1724 $base_url = 'https://wpreset.com';
1725
1726 if ('/' != $page) {
1727 $page = '/' . trim($page, '/') . '/';
1728 }
1729 if ($page == '//') {
1730 $page = '/';
1731 }
1732
1733 $parts = array_merge(array('utm_source' => 'wp-reset-free', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-reset-free-v' . $this->version), $params);
1734
1735 if (!empty($anchor)) {
1736 $anchor = '#' . trim($anchor, '#');
1737 }
1738
1739 $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;
1740
1741 return $out;
1742 } // generate_web_link
1743
1744
1745 /**
1746 * Returns all saved snapshots from DB
1747 *
1748 * @return array
1749 */
1750 function get_snapshots()
1751 {
1752 $snapshots = get_option('wp-reset-snapshots', array());
1753
1754 return $snapshots;
1755 } // get_snapshots
1756
1757
1758 /**
1759 * Returns all custom table names, with prefix
1760 *
1761 * @return array
1762 */
1763 function get_custom_tables()
1764 {
1765 global $wpdb;
1766 $custom_tables = array();
1767
1768 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1769 if (is_array($table_status)) {
1770 foreach ($table_status as $index => $table) {
1771 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1772 continue;
1773 }
1774 if (empty($table->Engine)) {
1775 continue;
1776 }
1777
1778 if (false === in_array($table->Name, $this->core_tables)) {
1779 $custom_tables[] = array('name' => $table->Name, 'rows' => $table->Rows, 'data_length' => $table->Data_length, 'index_length' => $table->Index_length);
1780 }
1781 } // foreach
1782 }
1783
1784 return $custom_tables;
1785 } // get_custom tables
1786
1787
1788 /**
1789 * Format file size to human readable string
1790 *
1791 * @param int $bytes Size in bytes to format.
1792 *
1793 * @return string
1794 */
1795 function format_size($bytes)
1796 {
1797 if ($bytes > 1073741824) {
1798 return number_format_i18n($bytes / 1073741824, 2) . ' GB';
1799 } elseif ($bytes > 1048576) {
1800 return number_format_i18n($bytes / 1048576, 1) . ' MB';
1801 } elseif ($bytes > 1024) {
1802 return number_format_i18n($bytes / 1024, 1) . ' KB';
1803 } else {
1804 return number_format_i18n($bytes, 0) . ' bytes';
1805 }
1806 } // format_size
1807
1808
1809 /**
1810 * Creates snapshot of current tables by copying them in the DB and saving metadata.
1811 *
1812 * @param int $name Optional. Name for the new snapshot.
1813 *
1814 * @return array|WP_Error Snapshot details in array on success, or error object on fail.
1815 */
1816 function do_create_snapshot($name = '')
1817 {
1818 global $wpdb;
1819 $snapshots = $this->get_snapshots();
1820 $snapshot = array();
1821 $uid = $this->generate_snapshot_uid();
1822 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1823
1824 if (!$uid) {
1825 return new WP_Error(1, 'Unable to generate a valid snapshot UID.');
1826 }
1827
1828 if ($name) {
1829 $snapshot['name'] = substr(trim($name), 0, 64);
1830 } else {
1831 $snapshot['name'] = '';
1832 }
1833 $snapshot['uid'] = $uid;
1834 $snapshot['timestamp'] = current_time('mysql');
1835
1836 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1837 if (is_array($table_status)) {
1838 foreach ($table_status as $index => $table) {
1839 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1840 continue;
1841 }
1842 if (empty($table->Engine)) {
1843 continue;
1844 }
1845
1846 $tbl_rows += $table->Rows;
1847 $tbl_size += $table->Data_length + $table->Index_length;
1848 if (in_array($table->Name, $this->core_tables)) {
1849 $tbl_core++;
1850 } else {
1851 $tbl_custom++;
1852 }
1853
1854 $wpdb->query('OPTIMIZE TABLE ' . $table->Name);
1855 $wpdb->query('CREATE TABLE ' . $uid . '_' . $table->Name . ' LIKE ' . $table->Name);
1856 $wpdb->query('INSERT ' . $uid . '_' . $table->Name . ' SELECT * FROM ' . $table->Name);
1857 } // foreach
1858 } else {
1859 return new WP_Error(1, 'Can\'t get table status data.');
1860 }
1861
1862 $snapshot['tbl_core'] = $tbl_core;
1863 $snapshot['tbl_custom'] = $tbl_custom;
1864 $snapshot['tbl_rows'] = $tbl_rows;
1865 $snapshot['tbl_size'] = $tbl_size;
1866
1867
1868 $snapshots[$uid] = $snapshot;
1869 update_option('wp-reset-snapshots', $snapshots);
1870
1871 do_action('wp_reset_create_snapshot', $uid, $snapshot);
1872
1873 return $snapshot;
1874 } // create_snapshot
1875
1876
1877 /**
1878 * Delete snapshot metadata and tables from DB
1879 *
1880 * @param string $uid Snapshot unique 6-char ID.
1881 *
1882 * @return bool|WP_Error True on success, or error object on fail.
1883 */
1884 function do_delete_snapshot($uid = '')
1885 {
1886 global $wpdb;
1887 $snapshots = $this->get_snapshots();
1888
1889 if (strlen($uid) != 6) {
1890 return new WP_Error(1, 'Invalid UID format.');
1891 }
1892
1893 if (!isset($snapshots[$uid])) {
1894 return new WP_Error(1, 'Unknown snapshot ID.');
1895 }
1896
1897 $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%')));
1898 foreach ($tables as $table) {
1899 $wpdb->query('DROP TABLE IF EXISTS ' . $table);
1900 }
1901
1902 $snapshot_copy = $snapshots[$uid];
1903 unset($snapshots[$uid]);
1904 update_option('wp-reset-snapshots', $snapshots);
1905
1906 do_action('wp_reset_delete_snapshot', $uid, $snapshot_copy);
1907
1908 return true;
1909 } // delete_snapshot
1910
1911
1912 /**
1913 * Exports snapshot as SQL dump; saved in gzipped file in WP_CONTENT folder.
1914 *
1915 * @param string $uid Snapshot unique 6-char ID.
1916 *
1917 * @return string|WP_Error Export base filename, or error object on fail.
1918 */
1919 function do_export_snapshot($uid = '')
1920 {
1921 $snapshots = $this->get_snapshots();
1922
1923 if (strlen($uid) != 6) {
1924 return new WP_Error(1, 'Invalid snapshot ID format.');
1925 }
1926
1927 if (!isset($snapshots[$uid])) {
1928 return new WP_Error(1, 'Unknown snapshot ID.');
1929 }
1930
1931 require_once $this->plugin_dir . 'libs/dumper.php';
1932
1933 try {
1934 $world_dumper = Shuttle_Dumper::create(array(
1935 'host' => DB_HOST,
1936 'username' => DB_USER,
1937 'password' => DB_PASSWORD,
1938 'db_name' => DB_NAME,
1939 ));
1940
1941 $folder = wp_mkdir_p(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder);
1942 if (!$folder) {
1943 return new WP_Error(1, 'Unable to create wp-content/' . $this->snapshots_folder . '/ folder.');
1944 }
1945
1946 $world_dumper->dump(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder . '/wp-reset-snapshot-' . $uid . '.sql.gz', $uid . '_');
1947 } catch (Shuttle_Exception $e) {
1948 return new WP_Error(1, "Couldn't dump snapshot: " . $e->getMessage());
1949 }
1950
1951 do_action('wp_reset_export_snapshot', 'wp-reset-snapshot-' . $uid . '.sql.gz');
1952
1953 return 'wp-reset-snapshot-' . $uid . '.sql.gz';
1954 } // export_snapshot
1955
1956
1957 /**
1958 * Replace current tables with ones in snapshot.
1959 *
1960 * @param string $uid Snapshot unique 6-char ID.
1961 *
1962 * @return bool|WP_Error True on success, or error object on fail.
1963 */
1964 function do_restore_snapshot($uid = '')
1965 {
1966 global $wpdb;
1967 $new_tables = array();
1968 $snapshots = $this->get_snapshots();
1969
1970 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
1971 return $res;
1972 }
1973
1974 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1975 if (is_array($table_status)) {
1976 foreach ($table_status as $index => $table) {
1977 if (0 !== stripos($table->Name, $uid . '_')) {
1978 continue;
1979 }
1980 if (empty($table->Engine)) {
1981 continue;
1982 }
1983
1984 $new_tables[] = $table->Name;
1985 } // foreach
1986 } else {
1987 return new WP_Error(1, 'Can\'t get table status data.');
1988 }
1989
1990 foreach ($table_status as $index => $table) {
1991 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1992 continue;
1993 }
1994 if (empty($table->Engine)) {
1995 continue;
1996 }
1997
1998 $wpdb->query('DROP TABLE ' . $table->Name);
1999 } // foreach
2000
2001 // copy snapshot tables to original name
2002 foreach ($new_tables as $table) {
2003 $new_name = str_replace($uid . '_', '', $table);
2004
2005 $wpdb->query('CREATE TABLE ' . $new_name . ' LIKE ' . $table);
2006 $wpdb->query('INSERT ' . $new_name . ' SELECT * FROM ' . $table);
2007 }
2008
2009 wp_cache_flush();
2010 update_option('wp-reset', $this->options);
2011 update_option('wp-reset-snapshots', $snapshots);
2012
2013 do_action('wp_reset_restore_snapshot', $uid);
2014
2015 return true;
2016 } // restore_snapshot
2017
2018
2019 /**
2020 * Verifies snapshot integrity by comparing metadata and data in DB
2021 *
2022 * @param string $uid Snapshot unique 6-char ID.
2023 *
2024 * @return bool|WP_Error True on success, or error object on fail.
2025 */
2026 function verify_snapshot_integrity($uid)
2027 {
2028 global $wpdb;
2029 $tbl_core = $tbl_custom = 0;
2030 $snapshots = $this->get_snapshots();
2031
2032 if (strlen($uid) != 6) {
2033 return new WP_Error(1, 'Invalid snapshot ID format.');
2034 }
2035
2036 if (!isset($snapshots[$uid])) {
2037 return new WP_Error(1, 'Unknown snapshot ID.');
2038 }
2039
2040 $snapshot = $snapshots[$uid];
2041
2042 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
2043 if (is_array($table_status)) {
2044 foreach ($table_status as $index => $table) {
2045 if (0 !== stripos($table->Name, $uid . '_')) {
2046 continue;
2047 }
2048 if (empty($table->Engine)) {
2049 continue;
2050 }
2051
2052 if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
2053 $tbl_core++;
2054 } else {
2055 $tbl_custom++;
2056 }
2057 } // foreach
2058
2059 if ($tbl_core != $snapshot['tbl_core'] || $tbl_custom != $snapshot['tbl_custom']) {
2060 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.');
2061 }
2062 } else {
2063 return new WP_Error(1, 'Can\'t get table status data.');
2064 }
2065
2066 return true;
2067 } // verify_snapshot_integrity
2068
2069
2070 /**
2071 * Compares a selected snapshot with the current table set in DB
2072 *
2073 * @param string $uid Snapshot unique 6-char ID.
2074 *
2075 * @return string|WP_Error Formatted table with details on success, or error object on fail.
2076 */
2077 function do_compare_snapshots($uid)
2078 {
2079 global $wpdb;
2080 $current = $snapshot = array();
2081 $out = $out2 = $out3 = '';
2082
2083 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
2084 return $res;
2085 }
2086
2087 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
2088 foreach ($table_status as $index => $table) {
2089 if (empty($table->Engine)) {
2090 continue;
2091 }
2092
2093 if (0 !== stripos($table->Name, $uid . '_') && 0 !== stripos($table->Name, $wpdb->prefix)) {
2094 continue;
2095 }
2096
2097 $info = array();
2098 $info['rows'] = $table->Rows;
2099 $info['size_data'] = $table->Data_length;
2100 $info['size_index'] = $table->Index_length;
2101 $schema = $wpdb->get_row('SHOW CREATE TABLE ' . $table->Name, ARRAY_N);
2102 $info['schema'] = $schema[1];
2103 $info['engine'] = $table->Engine;
2104 $info['fullname'] = $table->Name;
2105 $basename = str_replace(array($uid . '_'), array(''), $table->Name);
2106 $info['basename'] = $basename;
2107 $info['corename'] = str_replace(array($wpdb->prefix), array(''), $basename);
2108 $info['uid'] = $uid;
2109
2110 if (0 === stripos($table->Name, $uid . '_')) {
2111 $snapshot[$basename] = $info;
2112 }
2113
2114 if (0 === stripos($table->Name, $wpdb->prefix)) {
2115 $info['uid'] = '';
2116 $current[$basename] = $info;
2117 }
2118 } // foreach
2119
2120 $in_both = array_keys(array_intersect_key($current, $snapshot));
2121 $in_current_only = array_diff_key($current, $snapshot);
2122 $in_snapshot_only = array_diff_key($snapshot, $current);
2123
2124 $out .= '<br><br>';
2125 foreach ($in_current_only as $table) {
2126 $out .= '<div class="wpr-table-container in-current-only" data-table="' . $table['basename'] . '">';
2127 $out .= '<table>';
2128 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
2129 $out .= '<td><b>' . $table['fullname'] . '</b></td>';
2130 $out .= '<td>table is not present in snapshot<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2131 $out .= '</tr>';
2132 $out .= '<tr class="hidden">';
2133 $out .= '<td>';
2134 $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1 ? '' : 's') . ' totaling ' . $this->format_size($table['size_data']) . ' in data and ' . $this->format_size($table['size_index']) . ' in index.</p>';
2135 $out .= '<pre>' . $table['schema'] . '</pre>';
2136 $out .= '</td>';
2137 $out .= '<td>&nbsp;</td>';
2138 $out .= '</tr>';
2139 $out .= '</table>';
2140 $out .= '</div>';
2141 } // foreach in current only
2142
2143 foreach ($in_snapshot_only as $table) {
2144 $out .= '<div class="wpr-table-container in-snapshot-only" data-table="' . $table['basename'] . '">';
2145 $out .= '<table>';
2146 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
2147 $out .= '<td>table is not present in current tables</td>';
2148 $out .= '<td><b>' . $table['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2149 $out .= '</tr>';
2150 $out .= '<tr class="hidden">';
2151 $out .= '<td>&nbsp;</td>';
2152 $out .= '<td>';
2153 $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1 ? '' : 's') . ' totaling ' . $this->format_size($table['size_data']) . ' in data and ' . $this->format_size($table['size_index']) . ' in index.</p>';
2154 $out .= '<pre>' . $table['schema'] . '</pre>';
2155 $out .= '</td>';
2156 $out .= '</tr>';
2157 $out .= '</table>';
2158 $out .= '</div>';
2159 } // foreach in snapshot only
2160
2161 foreach ($in_both as $tablename) {
2162 $tbl_current = $current[$tablename];
2163 $tbl_snapshot = $snapshot[$tablename];
2164
2165 $schema1 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_current['schema'], 1);
2166 $schema2 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_snapshot['schema'], 1);
2167 $tbl_snapshot['tmp_schema'] = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $tbl_snapshot['schema']);
2168 $schema2 = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $schema2);
2169
2170 if ($tbl_current['rows'] == $tbl_snapshot['rows'] && $tbl_current['schema'] == $tbl_snapshot['tmp_schema']) {
2171 $out3 .= '<div class="wpr-table-container identical" data-table="' . $tablename . '">';
2172 $out3 .= '<table>';
2173 $out3 .= '<tr title="Click to show/hide more info" class="wpr-table-match header-row">';
2174 $out3 .= '<td><b>' . $tbl_current['fullname'] . '</b></td>';
2175 $out3 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2176 $out3 .= '</tr>';
2177 $out3 .= '<tr class="hidden">';
2178 $out3 .= '<td>';
2179 $out3 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
2180 $out3 .= '<pre>' . $tbl_current['schema'] . '</pre>';
2181 $out3 .= '</td>';
2182 $out3 .= '<td>';
2183 $out3 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
2184 $out3 .= '<pre>' . $tbl_snapshot['schema'] . '</pre>';
2185 $out3 .= '</td>';
2186 $out3 .= '</tr>';
2187 $out3 .= '</table>';
2188 $out3 .= '</div>';
2189 } elseif ($schema1 != $schema2) {
2190 require_once $this->plugin_dir . 'libs/diff.php';
2191 require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
2192 $diff = new Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
2193 $renderer = new Diff_Renderer_Html_SideBySide;
2194
2195 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2196 $out2 .= '<table>';
2197 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
2198 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> table schemas do not match</td>';
2199 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> table schemas do not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2200 $out2 .= '</tr>';
2201 $out2 .= '<tr class="hidden">';
2202 $out2 .= '<td>';
2203 $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
2204 $out2 .= '</td>';
2205 $out2 .= '<td>';
2206 $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
2207 $out2 .= '</td>';
2208 $out2 .= '</tr>';
2209 $out2 .= '<tr class="hidden">';
2210 $out2 .= '<td colspan="2" class="no-padding">';
2211 $out2 .= $diff->Render($renderer);
2212 $out2 .= '</td>';
2213 $out2 .= '</tr>';
2214 $out2 .= '</table>';
2215 $out2 .= '</div>';
2216 } else {
2217 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2218 $out2 .= '<table>';
2219 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
2220 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> data in tables does not match</td>';
2221 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> data in tables does not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2222 $out2 .= '</tr>';
2223 $out2 .= '<tr class="hidden">';
2224 $out2 .= '<td>';
2225 $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
2226 $out2 .= '</td>';
2227 $out2 .= '<td>';
2228 $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
2229 $out2 .= '</td>';
2230 $out2 .= '</tr>';
2231
2232 $out2 .= '<tr class="hidden">';
2233 $out2 .= '<td colspan="2">';
2234 if ($tbl_current['corename'] == 'options') {
2235 $ss_prefix = $tbl_snapshot['uid'] . '_' . $wpdb->prefix;
2236 $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;");
2237 $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;");
2238 $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;");
2239 $out2 .= '<table class="table_diff">';
2240 $out2 .= '<tr><td style="width: 100px;"><b>Option Name</b></td><td><b>Current Value</b></td><td><b>Snapshot Value</b></td></tr>';
2241 foreach ($diff_rows as $row) {
2242 $out2 .= '<tr>';
2243 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
2244 $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : $row->current_value) . '</td>';
2245 $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : $row->snapshot_value) . '</td>';
2246 $out2 .= '</tr>';
2247 } // foreach
2248 foreach ($only_current as $row) {
2249 $out2 .= '<tr>';
2250 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
2251 $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : $row->current_value) . '</td>';
2252 $out2 .= '<td><i>not found in snapshot</i></td>';
2253 $out2 .= '</tr>';
2254 } // foreach
2255 foreach ($only_current as $row) {
2256 $out2 .= '<tr>';
2257 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
2258 $out2 .= '<td><i>not found in current tables</i></td>';
2259 $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : $row->snapshot_value) . '</td>';
2260 $out2 .= '</tr>';
2261 } // foreach
2262 $out2 .= '</table>';
2263 } else {
2264 $out2 .= '<p class="textcenter">Detailed data diff is not available for this table.</p>';
2265 }
2266 $out2 .= '</td>';
2267 $out2 .= '</tr>';
2268
2269 $out2 .= '</table>';
2270 $out2 .= '</div>';
2271 }
2272 } // foreach in both
2273
2274 return $out . $out2 . $out3;
2275 } // do_compare_snapshots
2276
2277
2278 /**
2279 * Generates a unique 6-char snapshot ID; verified non-existing
2280 *
2281 * @return string
2282 */
2283 function generate_snapshot_uid()
2284 {
2285 global $wpdb;
2286 $snapshots = $this->get_snapshots();
2287 $cnt = 0;
2288 $uid = false;
2289
2290 do {
2291 $cnt++;
2292 $uid = sprintf('%06x', mt_rand(0, 0xFFFFFF));
2293
2294 $verify_db = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array('%' . $uid . '%')));
2295 } while (!empty($verify_db) && isset($snapshots[$uid]) && $cnt < 30);
2296
2297 if ($cnt == 30) {
2298 $uid = false;
2299 }
2300
2301 return $uid;
2302 } // generate_snapshot_uid
2303
2304
2305 /**
2306 * Helper function for adding plugins to featured list
2307 *
2308 * @return array
2309 */
2310 function featured_plugins_tab($args)
2311 {
2312 add_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);
2313
2314 return $args;
2315 } // featured_plugins_tab
2316
2317
2318 /**
2319 * Add single plugin to featured list
2320 *
2321 * @return object
2322 */
2323 function add_plugin_featured($plugin_slug, $res)
2324 {
2325 // check if plugin is already on the list
2326 if (!empty($res->plugins) && is_array($res->plugins)) {
2327 foreach ($res->plugins as $plugin) {
2328 if (is_object($plugin) && !empty($plugin->slug) && $plugin->slug == $plugin_slug) {
2329 return $res;
2330 }
2331 } // foreach
2332 }
2333
2334 if ($plugin_info = get_transient('wf-plugin-info-' . $plugin_slug)) {
2335 array_unshift($res->plugins, $plugin_info);
2336 } else {
2337 $plugin_info = plugins_api('plugin_information', array(
2338 'slug' => $plugin_slug,
2339 'is_ssl' => is_ssl(),
2340 'fields' => array(
2341 'banners' => true,
2342 'reviews' => true,
2343 'downloaded' => true,
2344 'active_installs' => true,
2345 'icons' => true,
2346 'short_description' => true,
2347 )
2348 ));
2349 if (!is_wp_error($plugin_info)) {
2350 $res->plugins = array_merge(array($plugin_info), $res->plugins);
2351 set_transient('wf-plugin-info-' . $plugin_slug, $plugin_info, DAY_IN_SECONDS * 7);
2352 }
2353 }
2354
2355 return $res;
2356 } // add_plugin_featured
2357
2358
2359 /**
2360 * Add plugins to featured plugins list
2361 *
2362 * @return object
2363 */
2364 function plugins_api_result($res, $action, $args)
2365 {
2366 remove_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);
2367
2368 $res = $this->add_plugin_featured('security-ninja', $res);
2369 $res = $this->add_plugin_featured('under-construction-page', $res);
2370
2371 return $res;
2372 } // plugins_api_result
2373
2374
2375 /**
2376 * Clean up on uninstall; no action on deactive at the moment
2377 *
2378 * @return null
2379 */
2380 static function uninstall()
2381 {
2382 delete_option('wp-reset');
2383 delete_option('wp-reset-snapshots');
2384 } // uninstall
2385
2386
2387 /**
2388 * Disabled; we use singleton pattern so magic functions need to be disabled
2389 *
2390 * @return null
2391 */
2392 private function __clone()
2393 { }
2394
2395
2396 /**
2397 * Disabled; we use singleton pattern so magic functions need to be disabled
2398 *
2399 * @return null
2400 */
2401 private function __sleep()
2402 { }
2403
2404
2405 /**
2406 * Disabled; we use singleton pattern so magic functions need to be disabled
2407 *
2408 * @return null
2409 */
2410 private function __wakeup()
2411 { }
2412 } // WP_Reset class
2413
2414
2415 // Create plugin instance and hook things up
2416 // Only if in admin - plugin has no frontend functionality
2417 if (is_admin() || WP_Reset::is_cli_running()) {
2418 global $wp_reset;
2419 $wp_reset = WP_Reset::getInstance();
2420 add_action('plugins_loaded', array($wp_reset, 'load_textdomain'));
2421 register_uninstall_hook(__FILE__, array('WP_Reset', 'uninstall'));
2422 }
2423