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

2,411 lines 97.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: WP Reset
4 Plugin URI: https://wpreset.com/
5 Description: Reset the site to default installation values without modifying any files. Deletes all customizations and content.
6 Version: 1.65
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 wp_cache_flush();
610
611 do_action('wp_reset_delete_transients', $count);
612
613 return $count;
614 } // do_delete_transients
615
616
617 /**
618 * Resets all theme options (mods).
619 *
620 * @param bool $all_themes Delete mods for all themes or just the current one
621 *
622 * @return int Number of deleted mod DB entries
623 */
624 function do_reset_theme_options($all_themes = true)
625 {
626 global $wpdb;
627
628 $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE 'theme_mods\_%' OR option_name LIKE 'mods\_%'");
629
630 do_action('wp_reset_reset_theme_options', $count);
631
632 return $count;
633 } // do_reset_theme_options
634
635
636 /**
637 * Deletes all files in uploads folder.
638 *
639 * @return int Number of deleted files and folders.
640 */
641 function do_delete_uploads()
642 {
643 $upload_dir = wp_get_upload_dir();
644 $this->delete_count = 0;
645
646 $this->delete_folder($upload_dir['basedir'], $upload_dir['basedir']);
647
648 do_action('wp_reset_delete_uploads', $this->delete_count);
649
650 return $this->delete_count;
651 } // do_delete_uploads
652
653
654 /**
655 * Recursively deletes a folder
656 *
657 * @param string $folder Recursive param.
658 * @param string $base_folder Base folder.
659 *
660 * @return bool
661 */
662 private function delete_folder($folder, $base_folder)
663 {
664 $files = array_diff(scandir($folder), array('.', '..'));
665
666 foreach ($files as $file) {
667 if (is_dir($folder . DIRECTORY_SEPARATOR . $file)) {
668 $this->delete_folder($folder . DIRECTORY_SEPARATOR . $file, $base_folder);
669 } else {
670 $tmp = @unlink($folder . DIRECTORY_SEPARATOR . $file);
671 $this->delete_count = $this->delete_count + (int)$tmp;
672 }
673 } // foreach
674
675 if ($folder != $base_folder) {
676 $tmp = @rmdir($folder);
677 $this->delete_count = $this->delete_count + (int)$tmp;
678 return $tmp;
679 } else {
680 return true;
681 }
682 } // delete_folder
683
684
685 /**
686 * Deactivate and delete all plugins
687 *
688 * @param bool $keep_wp_reset Keep WP Reset active and installed
689 * @param bool $silent_deactivate Skip individual plugin deactivation functions when deactivating
690 *
691 * @return int Number of deleted plugins.
692 */
693 function do_delete_plugins($keep_wp_reset = true, $silent_deactivate = false)
694 {
695 if (!function_exists('get_plugins')) {
696 require_once ABSPATH . 'wp-admin/includes/plugin.php';
697 }
698 if (!function_exists('request_filesystem_credentials')) {
699 require_once ABSPATH . 'wp-admin/includes/file.php';
700 }
701
702 $wp_reset_basename = plugin_basename(__FILE__);
703
704 $all_plugins = get_plugins();
705 $active_plugins = (array)get_option('active_plugins', array());
706 if (true == $keep_wp_reset) {
707 if (($key = array_search($wp_reset_basename, $active_plugins)) !== false) {
708 unset($active_plugins[$key]);
709 }
710 unset($all_plugins[$wp_reset_basename]);
711 }
712
713 if (!empty($active_plugins)) {
714 deactivate_plugins($active_plugins, $silent_deactivate, false);
715 }
716
717 if (!empty($all_plugins)) {
718 delete_plugins(array_keys($all_plugins));
719 }
720
721 do_action('wp_reset_delete_plugins', $all_plugins, $all_plugins);
722
723 return sizeof($all_plugins);
724 } // do_delete_plugins
725
726
727 /**
728 * Delete all themes
729 *
730 * @param bool $keep_default_theme Keep default theme
731 *
732 * @return int Number of deleted themes.
733 */
734 function do_delete_themes($keep_default_theme = true)
735 {
736 global $wp_version;
737
738 if (!function_exists('delete_theme')) {
739 require_once ABSPATH . 'wp-admin/includes/theme.php';
740 }
741
742 if (!function_exists('request_filesystem_credentials')) {
743 require_once ABSPATH . 'wp-admin/includes/file.php';
744 }
745
746 if (version_compare($wp_version, '5.0', '<') === true) {
747 $default_theme = 'twentyseventeen';
748 } else {
749 $default_theme = 'twentynineteen';
750 }
751
752 $all_themes = wp_get_themes(array('errors' => null));
753
754 if (true == $keep_default_theme) {
755 unset($all_themes[$default_theme]);
756 }
757
758 foreach ($all_themes as $theme_slug => $theme_details) {
759 $res = delete_theme($theme_slug);
760 }
761
762 if (false == $keep_default_theme) {
763 update_option('template', '');
764 update_option('stylesheet', '');
765 update_option('current_theme', '');
766 }
767
768 do_action('wp_reset_delete_themes', $all_themes);
769
770 return sizeof($all_themes);
771 } // do_delete_themes
772
773
774 /**
775 * Truncate custom tables
776 *
777 * @return int Number of truncated tables.
778 */
779 function do_truncate_custom_tables()
780 {
781 global $wpdb;
782 $custom_tables = $this->get_custom_tables();
783
784 foreach ($custom_tables as $tbl) {
785 $wpdb->query('TRUNCATE TABLE ' . $tbl['name']);
786 } // foreach
787
788 do_action('wp_reset_truncate_custom_tables', $custom_tables);
789
790 return sizeof($custom_tables);
791 } // do_truncate_custom_tables
792
793
794 /**
795 * Drop custom tables
796 *
797 * @return int Number of dropped tables.
798 */
799 function do_drop_custom_tables()
800 {
801 global $wpdb;
802 $custom_tables = $this->get_custom_tables();
803
804 foreach ($custom_tables as $tbl) {
805 $wpdb->query('DROP TABLE IF EXISTS ' . $tbl['name']);
806 } // foreach
807
808 do_action('wp_reset_drop_custom_tables', $custom_tables);
809
810 return sizeof($custom_tables);
811 } // do_drop_custom_tables
812
813
814 /**
815 * Delete .htaccess file
816 *
817 * @return bool|WP_Error Action status.
818 */
819 function do_delete_htaccess()
820 {
821 global $wp_filesystem;
822
823 if (empty($wp_filesystem)) {
824 require_once ABSPATH . '/wp-admin/includes/file.php';
825 WP_Filesystem();
826 }
827
828 $htaccess_path = $this->get_htaccess_path();
829 clearstatcache();
830
831 do_action('wp_reset_delete_htaccess', $htaccess_path);
832
833 if (!$wp_filesystem->is_readable($htaccess_path)) {
834 return new WP_Error(1, 'Htaccess file does not exist; there\'s nothing to delete.');
835 }
836
837 if (!$wp_filesystem->is_writable($htaccess_path)) {
838 return new WP_Error(1, 'Htaccess file is not writable.');
839 }
840
841 if ($wp_filesystem->delete($htaccess_path, false, 'f')) {
842 return true;
843 } else {
844 return new WP_Error(1, 'Unknown error. Unable to delete htaccess file.');
845 }
846 } // do_delete_htaccess
847
848
849 /**
850 * Get .htaccess file path.
851 *
852 * @return string
853 */
854 function get_htaccess_path()
855 {
856 if (!function_exists('get_home_path')) {
857 require_once ABSPATH . 'wp-admin/includes/file.php';
858 }
859
860 if ($this->is_cli_running()) {
861 $_SERVER['SCRIPT_FILENAME'] = ABSPATH;
862 }
863
864 $filepath = get_home_path() . '.htaccess';
865
866 return $filepath;
867 } // get_htaccess_path
868
869
870 /**
871 * Run one tool via AJAX call
872 *
873 * @return null
874 */
875 function ajax_run_tool()
876 {
877 check_ajax_referer('wp-reset_run_tool');
878
879 if (!current_user_can('administrator')) {
880 wp_send_json_error(__('You are not allowed to run this action.', 'wp-reset'));
881 }
882
883 $tool = trim(@$_GET['tool']);
884 $extra_data = trim(@$_GET['extra_data']);
885
886 if ($tool == 'delete_transients') {
887 $cnt = $this->do_delete_transients();
888 wp_send_json_success($cnt);
889 } elseif ($tool == 'reset_theme_options') {
890 $cnt = $this->do_reset_theme_options(true);
891 wp_send_json_success($cnt);
892 } elseif ($tool == 'delete_themes') {
893 $cnt = $this->do_delete_themes(false);
894 wp_send_json_success($cnt);
895 } elseif ($tool == 'delete_plugins') {
896 $cnt = $this->do_delete_plugins(true);
897 wp_send_json_success($cnt);
898 } elseif ($tool == 'delete_uploads') {
899 $cnt = $this->do_delete_uploads();
900 wp_send_json_success($cnt);
901 } elseif ($tool == 'delete_htaccess') {
902 $tmp = $this->do_delete_htaccess();
903 if (is_wp_error($tmp)) {
904 wp_send_json_error($tmp->get_error_message());
905 } else {
906 wp_send_json_success($tmp);
907 }
908 } elseif ($tool == 'drop_custom_tables') {
909 $cnt = $this->do_drop_custom_tables();
910 wp_send_json_success($cnt);
911 } elseif ($tool == 'truncate_custom_tables') {
912 $cnt = $this->do_truncate_custom_tables();
913 wp_send_json_success($cnt);
914 } elseif ($tool == 'delete_snapshot') {
915 $res = $this->do_delete_snapshot($extra_data);
916 if (is_wp_error($res)) {
917 wp_send_json_error($res->get_error_message());
918 } else {
919 wp_send_json_success();
920 }
921 } elseif ($tool == 'download_snapshot') {
922 $res = $this->do_export_snapshot($extra_data);
923 if (is_wp_error($res)) {
924 wp_send_json_error($res->get_error_message());
925 } else {
926 $url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
927 wp_send_json_success($url);
928 }
929 } elseif ($tool == 'restore_snapshot') {
930 $res = $this->do_restore_snapshot($extra_data);
931 if (is_wp_error($res)) {
932 wp_send_json_error($res->get_error_message());
933 } else {
934 wp_send_json_success();
935 }
936 } elseif ($tool == 'compare_snapshots') {
937 $res = $this->do_compare_snapshots($extra_data);
938 if (is_wp_error($res)) {
939 wp_send_json_error($res->get_error_message());
940 } else {
941 wp_send_json_success($res);
942 }
943 } elseif ($tool == 'create_snapshot') {
944 $res = $this->do_create_snapshot($extra_data);
945 if (is_wp_error($res)) {
946 wp_send_json_error($res->get_error_message());
947 } else {
948 wp_send_json_success();
949 }
950 } else {
951 wp_send_json_error(__('Unknown tool.', 'wp-reset'));
952 }
953 } // ajax_run_tool
954
955
956 /**
957 * Reinstall / reset the WP site
958 * There are no failsafes in the function - it reinstalls when called
959 * Redirects when done
960 *
961 * @param array $params Optional.
962 *
963 * @return null
964 */
965 function do_reinstall($params = array())
966 {
967 global $current_user, $wpdb;
968
969 // only admins can reset; double-check
970 if (!$this->is_cli_running() && !current_user_can('administrator')) {
971 return false;
972 }
973
974 // make sure the function is available to us
975 if (!function_exists('wp_install')) {
976 require ABSPATH . '/wp-admin/includes/upgrade.php';
977 }
978
979 // save values that need to be restored after reset
980 // todo: use params to determine what gets restored after reset
981 $blogname = get_option('blogname');
982 $blog_public = get_option('blog_public');
983 $wplang = get_option('wplang');
984 $siteurl = get_option('siteurl');
985 $home = get_option('home');
986 $snapshots = $this->get_snapshots();
987
988 $active_plugins = get_option('active_plugins');
989 $active_theme = wp_get_theme();
990
991 if (!empty($params['reactivate_webhooks'])) {
992 $wpwh1 = get_option('wpwhpro_active_webhooks');
993 $wpwh2 = get_option('wpwhpro_activate_translations');
994 $wpwh3 = get_option('ironikus_webhook_webhooks');
995 }
996
997 // for WP-CLI
998 if (!$current_user->ID) {
999 $tmp = get_users(array('role' => 'administrator', 'order' => 'ASC', 'order_by' => 'ID'));
1000 if (empty($tmp[0]->user_login)) {
1001 return new WP_Error(1, 'Reset failed. Unable to find any admin users in database.');
1002 }
1003 $current_user = $tmp[0];
1004 }
1005
1006 // delete custom tables with WP's prefix
1007 $prefix = str_replace('_', '\_', $wpdb->prefix);
1008 $tables = $wpdb->get_col("SHOW TABLES LIKE '{$prefix}%'");
1009 foreach ($tables as $table) {
1010 $wpdb->query("DROP TABLE $table");
1011 }
1012
1013 // supress errors for WP_CLI
1014 // todo: find a better way to supress errors and send/not send email on reset
1015 $result = @wp_install($blogname, $current_user->user_login, $current_user->user_email, $blog_public, '', md5(rand()), $wplang);
1016 $user_id = $result['user_id'];
1017
1018 // restore user pass
1019 $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));
1020 $wpdb->query($query);
1021
1022 // restore rest of the settings including WP Reset's
1023 update_option('siteurl', $siteurl);
1024 update_option('home', $home);
1025 update_option('wp-reset', $this->options);
1026 update_option('wp-reset-snapshots', $snapshots);
1027
1028 // remove password nag
1029 if (get_user_meta($user_id, 'default_password_nag')) {
1030 update_user_meta($user_id, 'default_password_nag', false);
1031 }
1032 if (get_user_meta($user_id, $wpdb->prefix . 'default_password_nag')) {
1033 update_user_meta($user_id, $wpdb->prefix . 'default_password_nag', false);
1034 }
1035
1036 $meta = $this->get_meta();
1037 $meta['reset_count']++;
1038 $this->update_options('meta', $meta);
1039
1040 // reactivate theme
1041 if (!empty($params['reactivate_theme'])) {
1042 switch_theme($active_theme->get_stylesheet());
1043 }
1044
1045 // reactivate WP Reset
1046 if (!empty($params['reactivate_wpreset'])) {
1047 activate_plugin(plugin_basename(__FILE__));
1048 }
1049
1050 // reactivate WP Webhooks
1051 if (!empty($params['reactivate_webhooks'])) {
1052 activate_plugin('wp-webhooks/wp-webhooks.php');
1053 activate_plugin('wpwh-wp-reset-webhook-integration/wpwhpro-wp-reset-webhook-integration.php');
1054
1055 update_option('wpwhpro_active_webhooks', $wpwh1);
1056 update_option('wpwhpro_activate_translations', $wpwh2);
1057 update_option('ironikus_webhook_webhooks', $wpwh3);
1058 }
1059
1060 // reactivate all plugins
1061 if (!empty($params['reactivate_plugins'])) {
1062 foreach ($active_plugins as $plugin_file) {
1063 activate_plugin($plugin_file);
1064 }
1065 }
1066
1067 if (!$this->is_cli_running()) {
1068 // log out and log in the old/new user
1069 // since the password doesn't change this is potentially unnecessary
1070 wp_clear_auth_cookie();
1071 wp_set_auth_cookie($user_id);
1072
1073 wp_redirect(admin_url() . '?wp-reset=success');
1074 exit;
1075 }
1076 } // do_reinstall
1077
1078
1079 /**
1080 * Checks wp_reset post value and performs all actions
1081 * todo: handle messages for various actions
1082 *
1083 * @return null|bool
1084 */
1085 function do_all_actions()
1086 {
1087 // only admins can perform actions
1088 if (!current_user_can('administrator')) {
1089 return;
1090 }
1091
1092 if (!empty($_GET['wp-reset']) && stristr($_SERVER['HTTP_REFERER'], 'wp-reset')) {
1093 add_action('admin_notices', array($this, 'notice_successful_reset'));
1094 }
1095
1096 // check nonce
1097 if (true === isset($_POST['wp_reset_confirm']) && false === wp_verify_nonce(@$_POST['_wpnonce'], 'wp-reset')) {
1098 add_settings_error('wp-reset', 'bad-nonce', __('Something went wrong. Please refresh the page and try again.', 'wp-reset'), 'error');
1099 return false;
1100 }
1101
1102 // check confirmation code
1103 if (true === isset($_POST['wp_reset_confirm']) && 'reset' !== $_POST['wp_reset_confirm']) {
1104 add_settings_error('wp-reset', 'bad-confirm', __('<b>Invalid confirmation code.</b> Please type "reset" in the confirmation field.', 'wp-reset'), 'error');
1105 return false;
1106 }
1107
1108 // only one action at the moment
1109 if (true === isset($_POST['wp_reset_confirm']) && 'reset' === $_POST['wp_reset_confirm']) {
1110 $defaults = array(
1111 'reactivate_theme' => '0',
1112 'reactivate_plugins' => '0',
1113 'reactivate_wpreset' => '0',
1114 'reactivate_webhooks' => '0'
1115 );
1116 $params = shortcode_atts($defaults, (array)@$_POST['wpr-post-reset']);
1117
1118 $this->do_reinstall($params);
1119 }
1120 } // do_all_actions
1121
1122
1123 /**
1124 * Add "Open WP Reset Tools" action link to plugins table, left part
1125 *
1126 * @param array $links Initial list of links.
1127 *
1128 * @return array
1129 */
1130 function plugin_action_links($links)
1131 {
1132 $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>';
1133
1134 array_unshift($links, $settings_link);
1135
1136 return $links;
1137 } // plugin_action_links
1138
1139
1140 /**
1141 * Add links to plugin's description in plugins table
1142 *
1143 * @param array $links Initial list of links.
1144 * @param string $file Basename of current plugin.
1145 *
1146 * @return array
1147 */
1148 function plugin_meta_links($links, $file)
1149 {
1150 if ($file !== plugin_basename(__FILE__)) {
1151 return $links;
1152 }
1153
1154 $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset" title="' . __('Get help', 'wp-reset') . '">' . __('Support', 'wp-reset') . '</a>';
1155 $home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-reset') . '">' . __('Plugin Homepage', 'wp-reset') . '</a>';
1156 $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 �
1157 �
1158 �
1159 �
1160 �
1161 ', 'wp-reset') . '</a>';
1162
1163 $links[] = $support_link;
1164 $links[] = $home_link;
1165 $links[] = $rate_link;
1166
1167 return $links;
1168 } // plugin_meta_links
1169
1170
1171 /**
1172 * Test if we're on WPR's admin page
1173 *
1174 * @return bool
1175 */
1176 function is_plugin_page()
1177 {
1178 $current_screen = get_current_screen();
1179
1180 if ($current_screen->id == 'tools_page_wp-reset') {
1181 return true;
1182 } else {
1183 return false;
1184 }
1185 } // is_plugin_page
1186
1187
1188 /**
1189 * Add powered by text in admin footer
1190 *
1191 * @param string $text Default footer text.
1192 *
1193 * @return string
1194 */
1195 function admin_footer_text($text)
1196 {
1197 if (!$this->is_plugin_page()) {
1198 return $text;
1199 }
1200
1201 $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 �
1202 �
1203 �
1204 �
1205 �
1206 </a>.</i>';
1207
1208 return $text;
1209 } // admin_footer_text
1210
1211
1212 /**
1213 * Loads plugin's translated strings
1214 *
1215 * @return null
1216 */
1217 function load_textdomain()
1218 {
1219 load_plugin_textdomain('wp-reset');
1220 } // load_textdomain
1221
1222
1223 /**
1224 * Inform the user that WordPress has been successfully reset
1225 *
1226 * @return null
1227 */
1228 function notice_successful_reset()
1229 {
1230 global $current_user;
1231
1232 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>';
1233 } // notice_successful_reset
1234
1235
1236 /**
1237 * Outputs complete plugin's admin page
1238 *
1239 * @return null
1240 */
1241 function plugin_page()
1242 {
1243 $notice_shown = false;
1244 $meta = $this->get_meta();
1245 $snapshots = $this->get_snapshots();
1246
1247 // double check for admin privileges
1248 if (!current_user_can('administrator')) {
1249 wp_die(__('Sorry, you are not allowed to access this page.', 'wp-reset'));
1250 }
1251
1252 settings_errors();
1253 echo '<div class="wrap">';
1254 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>';
1255 echo '<form id="wp_reset_form" action="' . admin_url('tools.php?page=wp-reset') . '" method="post" autocomplete="off">';
1256
1257 if (false === $notice_shown && is_multisite()) {
1258 echo '<div class="card notice-wrapper notice-error">';
1259 echo '<h2>' . __('WP Reset is not compatible with multisite!', 'wp-reset') . '</h2>';
1260 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>';
1261 echo '</div>';
1262 $notice_shown = true;
1263 }
1264
1265 // ask for review
1266 // disabled due to survey
1267 if ((!empty($meta['reset_count']) || !empty($snapshots)) && false === $notice_shown && false == $this->get_dismissed_notices('rate')) {
1268 echo '<div class="card notice-wrapper notice-info">';
1269 echo '<h2>' . __('Please help us keep the plugin free &amp; up-to-date', 'wp-reset') . '</h2>';
1270 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>';
1271 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>';
1272 echo '</div>';
1273 $notice_shown = true;
1274 }
1275
1276 // Tidy Repo ad
1277 // disabled for now
1278 if (false && false === $notice_shown && $meta['reset_count'] >= 2 && false == $this->get_dismissed_notices('tidy')) {
1279 echo '<div class="card notice-wrapper">';
1280 echo '<h2>' . __('Are you a plugin author? Get your plugin reviewed on Tidy Repo', 'wp-reset') . '</h2>';
1281 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>';
1282 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>';
1283 echo '</div>';
1284 $notice_shown = true;
1285 }
1286
1287 // tabs
1288 echo '<div id="wp-reset-tabs" class' . __('="', 'wp-reset') . 'ui-tabs">';
1289
1290 echo '<ul class="wpr-main-tab">';
1291 echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
1292 echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
1293 echo '<li><a href="#tab-snapshots">' . __('DB Snapshots', 'wp-reset') . '</a></li>';
1294 echo '<li><a href="#tab-collections">' . __('Collections', 'wp-reset') . '</a></li>';
1295 echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
1296 echo '</ul>';
1297
1298 echo '<div style="display: none;" id="tab-reset">';
1299 $this->tab_reset();
1300 echo '</div>';
1301
1302 echo '<div style="display: none;" id="tab-tools">';
1303 $this->tab_tools();
1304 echo '</div>';
1305
1306 echo '<div style="display: none;" id="tab-snapshots">';
1307 $this->tab_snapshots();
1308 echo '</div>';
1309
1310 echo '<div style="display: none;" id="tab-collections">';
1311 $this->tab_collections();
1312 echo '</div>';
1313
1314 echo '<div style="display: none;" id="tab-support">';
1315 $this->tab_support();
1316 echo '</div>';
1317
1318 echo '</div>'; // tabs
1319
1320 echo '</form>';
1321 echo '</div>'; // wrap
1322
1323 // survey
1324 if ($this->is_survey_active('features')) {
1325 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>';
1326 echo '<p class="subtitle"><b>What new features do you need the most?</b> Choose one or two;</p>';
1327
1328 $questions = array();
1329 $questions[] = '<div class="question-wrapper" data-value="backup" title="Click to select/unselect answer">' .
1330 '<span class="dashicons dashicons-yes"></span>' .
1331 '<div class="question"><b>Off-site backups</b><br>' .
1332 '<i>Backup the site to Dropbox, FTP or Google Drive before running any tools</i></div>' .
1333 '</div>';
1334
1335 $questions[] = '<div class="question-wrapper" data-value="wpmu" title="Click to select/unselect answer">' .
1336 '<span class="dashicons dashicons-yes"></span>' .
1337 '<div class="question"><b>WordPress Network (WPMU) compatibility</b><br>' .
1338 '<i>Full support &amp; compatibility for all WP Reset tools for all sites in network</i></div>' .
1339 '</div>';
1340
1341 $questions[] = '<div class="question-wrapper" data-value="nothing" title="Click to select/unselect answer">' .
1342 '<span class="dashicons dashicons-yes"></span>' .
1343 '<div class="question"><b>Don\'t add anything</b><br>' .
1344 '<i>WP Reset is perfect as is - I don\'t need any new features</i></div>' .
1345 '</div>';
1346
1347 $questions[] = '<div class="question-wrapper" data-value="nuclear" title="Click to select/unselect answer">' .
1348 '<span class="dashicons dashicons-yes"></span>' .
1349 '<div class="question"><b>Nuclear reset - run all tools at once</b><br>' .
1350 '<i>Besides resetting, delete all files and all other customizations with one click</i></div>' .
1351 '</div>';
1352
1353 $questions[] = '<div class="question-wrapper" data-value="plugin-collections" title="Click to select/unselect answer">' .
1354 '<span class="dashicons dashicons-yes"></span>' .
1355 '<div class="question"><b>Install a set of plugins/themes after reset</b><br>' .
1356 '<i>Save lists of plugins/themes and automatically install them after resetting</i></div>' .
1357 '</div>';
1358
1359 $questions[] = '<div class="question-wrapper" data-value="change-wp-ver" title="Click to select/unselect answer">' .
1360 '<span class="dashicons dashicons-yes"></span>' .
1361 '<div class="question"><b>Change WordPress version - rollback or upgrade</b><br>' .
1362 '<i>Pick a version of WP you need (older or never) and switch to it with one click</i></div>' .
1363 '</div>';
1364
1365 shuffle($questions);
1366 $questions[] = '<div class="question-wrapper" data-value="custom" title="Click to select/unselect answer">' .
1367 '<span class="dashicons dashicons-yes"></span>' .
1368 '<div class="question"><b>Something we missed?</b><br><i>Enter the feature you need below;</i>' .
1369 '<input type="text" class="custom-input"></div>' .
1370 '</div>';
1371
1372 echo implode(' ', $questions);
1373
1374 $current_user = wp_get_current_user();
1375 echo '<div class="footer">';
1376 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>';
1377 echo '<a data-survey="features" class="submit-survey button-primary button button-large" href="#">Add those features ASAP!</a>';
1378 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>';
1379 echo '</div>';
1380
1381 echo '</div>';
1382 } // survey
1383
1384 if (!$this->is_webhooks_active()) {
1385 echo '<div id="webhooks-dialog" style="display: none;" title="Webhooks"><span class="ui-helper-hidden-accessible"><input type="text"/></span>';
1386 echo '<div style="padding: 20px; font-size: 15px;">';
1387 echo '<ul class="plain-list">';
1388 echo '<li>Standard, platform-independant way of connecting WP to any 3rd party system</li>';
1389 echo '<li>Supports actions - WP receives data on 3rd party events</li>';
1390 echo '<li>And triggers - WP sends data on its events</li>';
1391 echo '<li>Works wonders with Zapier</li>';
1392 echo '<li>Compatible with any WordPress theme or plugin</li>';
1393 echo '<li>Available from the official <a href="https://wordpress.org/plugins/wp-webhooks/" target="_blank">WP plugins repository</a></li>';
1394 echo '</ul>';
1395 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>';
1396 echo '</div>';
1397 echo '</div>';
1398 }
1399 } // plugin_page
1400
1401
1402 /**
1403 * Echoes content for reset tab
1404 *
1405 * @return null
1406 */
1407 private function tab_reset()
1408 {
1409 global $current_user, $wpdb;
1410
1411 echo '<div class="card" id="card-description">';
1412 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1413 echo '<h2>' . __('Please read carefully before proceeding. There is NO UNDO!', 'wp-reset') . '</h2>';
1414 echo '<b class="red">' . __('Resetting will delete:', 'wp-reset') . '</b>';
1415 echo '<ul class="plain-list">';
1416 echo '<li>' . __('all posts, pages, custom post types, comments, media entries, users', 'wp-reset') . '</li>';
1417 echo '<li>' . __('all default WP database tables', 'wp-reset') . '</li>';
1418 echo '<li>' . sprintf(__('all custom database tables that have the same prefix "%s" as default tables in this installation', 'wp-reset'), $wpdb->prefix) . '</li>';
1419 echo '</ul>';
1420
1421 echo '<b class="green">' . __('Resetting will not delete:', 'wp-reset') . '</b>';
1422 echo '<ul class="plain-list">';
1423 echo '<li>' . __('media files - they\'ll remain in the <i>wp-uploads</i> folder but will no longer be listed under Media', 'wp-reset');
1424 echo __('; use the <a href="#clean-uploads-folder" data-tab="1" class="change-tab">Clean Uploads Folder</a> tool to remove media files', 'wp-reset') . '</li>';
1425 echo '<li>' . __('no files are touched; plugins, themes, uploads - everything stays', 'wp-reset');
1426 echo __('; if needed use the <a href="#delete-themes" class="change-tab" data-tab="1">Delete Themes</a> &amp; <a href="#delete-plugins" class="change-tab" data-tab="1">Delete Plugins</a> tools', 'wp-reset') . '</li>';
1427 echo '<li>' . __('site title, WordPress address, site address, site language and search engine visibility settings', 'wp-reset') . '</li>';
1428 echo '<li>' . sprintf(__('logged in user "%s" will be restored with the current password', 'wp-reset'), $current_user->user_login) . '</li>';
1429 echo '</ul>';
1430
1431 echo '<b>' . __('What happens when I click the Reset button?', 'wp-reset') . '</b>';
1432 echo '<ul class="plain-list">';
1433 echo '<li>' . __('you will have to confirm the action one more time because there is NO UNDO', 'wp-reset') . '</li>';
1434 echo '<li>' . __('everything will be reset; see bullets above for details', 'wp-reset') . '</li>';
1435 echo '<li>' . __('site title, WordPress address, site address, site language, search engine visibility and current user will be restored', 'wp-reset') . '</li>';
1436 echo '<li>' . __('you will be logged out, automatically logged in and taken to the admin dashboard', 'wp-reset') . '</li>';
1437 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>';
1438 echo '</ul>';
1439
1440 echo '<b>' . __('WP-CLI Support', 'wp-reset') . '</b>';
1441 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>');
1442 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>';
1443
1444 echo '<b>' . __('WP Webhooks Support', 'wp-reset') . '</b>';
1445 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>';
1446 if ($this->is_webhooks_active()) {
1447 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>.';
1448 } else {
1449 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.';
1450 }
1451 echo '</p></div>'; // card description
1452
1453 $theme = wp_get_theme();
1454
1455 echo '<div class="card" id="card-post-reset">';
1456 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1457 echo '<h2>' . __('Post-reset actions', 'wp-reset') . '</h2>';
1458 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>';
1459 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>';
1460 if ($this->is_webhooks_active()) {
1461 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>';
1462 }
1463 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>';
1464 if ($this->is_webhooks_active()) {
1465 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>';
1466 } else {
1467 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>';
1468 }
1469 echo '</div>';
1470
1471 echo '<div class="card">';
1472 echo '<h2>' . __('Reset', 'wp-reset') . '</h2>';
1473 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>';
1474
1475 wp_nonce_field('wp-reset');
1476 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;';
1477 echo '<input id="wp_reset_submit" type="button" class="button-primary" value="' . __('Reset WordPress', 'wp-reset') . '"></p>';
1478 echo '</div>';
1479 } // tab_reset
1480
1481
1482 /**
1483 * Echoes content for tools tab
1484 *
1485 * @return null
1486 */
1487 private function tab_tools()
1488 {
1489 echo '<div class="card">';
1490 echo '<h2 id="delete-transients">' . __('Delete Transients', 'wp-reset') . '</h2>';
1491 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>';
1492 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>';
1493 echo '</div>';
1494
1495 $upload_dir = wp_upload_dir(date('Y/m'), true);
1496 $upload_dir['basedir'] = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $upload_dir['basedir']);
1497
1498 echo '<div class="card">';
1499 echo '<h2 id="clean-uploads-folder">' . __('Clean Uploads Folder', 'wp-reset') . '</h2>';
1500 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>';
1501 if (false != $upload_dir['error']) {
1502 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>';
1503 } else {
1504 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>';
1505 }
1506 echo '</div>';
1507
1508 echo '<div class="card">';
1509 echo '<h2 id="reset-theme-options">' . __('Reset Theme Options', 'wp-reset') . '</h2>';
1510 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>';
1511 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>';
1512 echo '</div>';
1513
1514 $theme = wp_get_theme();
1515
1516 echo '<div class="card">';
1517 echo '<h2 id="delete-themes">' . __('Delete Themes', 'wp-reset') . '</h2>';
1518 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>';
1519 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>';
1520 echo '</div>';
1521
1522 echo '<div class="card">';
1523 echo '<h2 id="delete-plugins">' . __('Delete Plugins', 'wp-reset') . '</h2>';
1524 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>';
1525 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>';
1526 echo '</div>';
1527
1528 global $wpdb;
1529 $custom_tables = $this->get_custom_tables();
1530
1531 echo '<div class="card">';
1532 echo '<h2 id="empty-delete-custom-tables">' . __('Empty or Delete Custom Tables', 'wp-reset') . '</h2>';
1533 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');
1534 if ($custom_tables) {
1535 echo '<p>' . __('The following ' . sizeof($custom_tables) . ' custom tables are affected by this tool: ');
1536 foreach ($custom_tables as $tbl) {
1537 echo '<code>' . $tbl['name'] . '</code>';
1538 if (next($custom_tables)) {
1539 echo ', ';
1540 }
1541 } // foreach
1542 echo '.</p>';
1543 $custom_tables_btns = '';
1544 } else {
1545 echo '<p>' . __('There are no custom tables. There\'s nothing for this tool to empty or delete.', 'wp-reset') . '</p>';
1546 $custom_tables_btns = ' disabled';
1547 }
1548 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;';
1549 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>';
1550
1551 echo '</div>';
1552
1553 echo '<div class="card">';
1554 echo '<h2 id="delete-htaccess-file">' . __('Delete .htaccess File', 'wp-reset') . '</h2>';
1555 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');
1556
1557 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>';
1558
1559 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>';
1560
1561 echo '</div>';
1562 } // tab_tools
1563
1564
1565 /**
1566 * Echoes content for collections tab
1567 *
1568 * @return null
1569 */
1570 private function tab_collections()
1571 {
1572 echo '<div class="card">';
1573 echo '<h2>' . __('What are Plugin &amp; Theme Collections', 'wp-reset') . '</h2>';
1574 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>';
1575 echo '</div>';
1576
1577 echo '<div class="card">';
1578 echo '<h2>' . __('So where do I click?!', 'wp-reset') . '</h2>';
1579 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>';
1580 echo '</div>';
1581 } // tab_collections
1582
1583
1584 /**
1585 * Echoes content for support tab
1586 *
1587 * @return null
1588 */
1589 private function tab_support()
1590 {
1591 echo '<div class="card">';
1592 echo '<h2>' . __('Documentation', 'wp-reset') . '</h2>';
1593 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>';
1594 echo '</div>';
1595
1596 echo '<div class="card">';
1597 echo '<h2>' . __('Public support forum', 'wp-reset') . '</h2>';
1598 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>';
1599 echo '</div>';
1600
1601 echo '<div class="card">';
1602 echo '<h2>' . __('Private contact', 'wp-reset') . '</h2>';
1603 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>';
1604 echo '</div>';
1605
1606 echo '<div class="card">';
1607 echo '<h2>' . __('Care to help out?', 'wp-reset') . '</h2>';
1608 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>';
1609 echo '</div>';
1610 } // tab_support
1611
1612
1613 /**
1614 * Echoes content for snapshots tab
1615 *
1616 * @return null
1617 */
1618 private function tab_snapshots()
1619 {
1620 global $wpdb;
1621 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1622
1623 echo '<div class="card" id="card-snapshots">';
1624 echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1625 echo '<h2>' . __('Database Snapshots', 'wp-reset') . '</h2>';
1626 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. <a href="https://www.youtube.com/watch?v=xBfMmS12vMY" target="_blank">Watch a short video</a> overview and tutorial about Snapshots.<br>
1627 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>';
1628
1629 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1630 if (is_array($table_status)) {
1631 foreach ($table_status as $index => $table) {
1632 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1633 continue;
1634 }
1635 if (empty($table->Engine)) {
1636 continue;
1637 }
1638
1639 $tbl_rows += $table->Rows;
1640 $tbl_size += $table->Data_length + $table->Index_length;
1641 if (in_array($table->Name, $this->core_tables)) {
1642 $tbl_core++;
1643 } else {
1644 $tbl_custom++;
1645 }
1646 } // foreach
1647
1648 echo '<p><b>Currently used WordPress tables</b>, prefixed with <i>' . $wpdb->prefix . '</i>, consist of ' . $tbl_core . ' standard and ';
1649 if ($tbl_custom) {
1650 echo $tbl_custom . ' custom table' . ($tbl_custom == 1 ? '' : 's');
1651 } else {
1652 echo 'no custom tables';
1653 }
1654 echo ' totaling ' . $this->format_size($tbl_size) . ' in ' . number_format($tbl_rows) . ' rows.</p>';
1655 }
1656
1657 echo '';
1658 echo '</div>';
1659
1660 echo '<div class="card no-padding-bottom">';
1661 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>';
1662 echo '<h2>' . __('Saved Snapshots', 'wp-reset') . '</h2>';
1663
1664 if ($snapshots = $this->get_snapshots()) {
1665 echo '<table id="wpr-snapshots">';
1666 echo '<tr><th>Name</th><th>Info &amp; Size</th><th class="ss-actions">Actions</th></tr>';
1667 foreach ($snapshots as $ss) {
1668 echo '<tr id="wpr-ss-' . $ss['uid'] . '">';
1669 if (!empty($ss['name'])) {
1670 echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . $ss['name'] . '</td>';
1671 $name = $ss['name'];
1672 } else {
1673 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>';
1674 $name = 'created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
1675 }
1676 echo '<td>' . $ss['tbl_core'] . ' standard &amp; ';
1677 if ($ss['tbl_custom']) {
1678 echo $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1 ? '' : 's');
1679 } else {
1680 echo 'no custom tables';
1681 }
1682 echo ' totaling ' . $this->format_size($ss['tbl_size']) . ' in ' . number_format($ss['tbl_rows']) . ' rows</td>';
1683 echo '<td>';
1684 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>';
1685 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>';
1686 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>';
1687 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>';
1688 echo '</tr>';
1689 } // foreach
1690 echo '</table>';
1691 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>';
1692 } else {
1693 echo '<p id="ss-no-snapshots">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
1694 }
1695
1696 echo '</div>';
1697 } // tab_snapshots
1698
1699
1700 /**
1701 * Helper function for generating UTM tagged links
1702 *
1703 * @param string $placement Optional. UTM content param.
1704 * @param string $page Optional. Page to link to.
1705 * @param array $params Optional. Extra URL params.
1706 * @param string $anchor Optional. URL anchor part.
1707 *
1708 * @return string
1709 */
1710 function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '')
1711 {
1712 $base_url = 'https://wpreset.com';
1713
1714 if ('/' != $page) {
1715 $page = '/' . trim($page, '/') . '/';
1716 }
1717 if ($page == '//') {
1718 $page = '/';
1719 }
1720
1721 $parts = array_merge(array('utm_source' => 'wp-reset-free', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-reset-free-v' . $this->version), $params);
1722
1723 if (!empty($anchor)) {
1724 $anchor = '#' . trim($anchor, '#');
1725 }
1726
1727 $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;
1728
1729 return $out;
1730 } // generate_web_link
1731
1732
1733 /**
1734 * Returns all saved snapshots from DB
1735 *
1736 * @return array
1737 */
1738 function get_snapshots()
1739 {
1740 $snapshots = get_option('wp-reset-snapshots', array());
1741
1742 return $snapshots;
1743 } // get_snapshots
1744
1745
1746 /**
1747 * Returns all custom table names, with prefix
1748 *
1749 * @return array
1750 */
1751 function get_custom_tables()
1752 {
1753 global $wpdb;
1754 $custom_tables = array();
1755
1756 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1757 if (is_array($table_status)) {
1758 foreach ($table_status as $index => $table) {
1759 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1760 continue;
1761 }
1762 if (empty($table->Engine)) {
1763 continue;
1764 }
1765
1766 if (false === in_array($table->Name, $this->core_tables)) {
1767 $custom_tables[] = array('name' => $table->Name, 'rows' => $table->Rows, 'data_length' => $table->Data_length, 'index_length' => $table->Index_length);
1768 }
1769 } // foreach
1770 }
1771
1772 return $custom_tables;
1773 } // get_custom tables
1774
1775
1776 /**
1777 * Format file size to human readable string
1778 *
1779 * @param int $bytes Size in bytes to format.
1780 *
1781 * @return string
1782 */
1783 function format_size($bytes)
1784 {
1785 if ($bytes > 1073741824) {
1786 return number_format_i18n($bytes / 1073741824, 2) . ' GB';
1787 } elseif ($bytes > 1048576) {
1788 return number_format_i18n($bytes / 1048576, 1) . ' MB';
1789 } elseif ($bytes > 1024) {
1790 return number_format_i18n($bytes / 1024, 1) . ' KB';
1791 } else {
1792 return number_format_i18n($bytes, 0) . ' bytes';
1793 }
1794 } // format_size
1795
1796
1797 /**
1798 * Creates snapshot of current tables by copying them in the DB and saving metadata.
1799 *
1800 * @param int $name Optional. Name for the new snapshot.
1801 *
1802 * @return array|WP_Error Snapshot details in array on success, or error object on fail.
1803 */
1804 function do_create_snapshot($name = '')
1805 {
1806 global $wpdb;
1807 $snapshots = $this->get_snapshots();
1808 $snapshot = array();
1809 $uid = $this->generate_snapshot_uid();
1810 $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
1811
1812 if (!$uid) {
1813 return new WP_Error(1, 'Unable to generate a valid snapshot UID.');
1814 }
1815
1816 if ($name) {
1817 $snapshot['name'] = substr(trim($name), 0, 64);
1818 } else {
1819 $snapshot['name'] = '';
1820 }
1821 $snapshot['uid'] = $uid;
1822 $snapshot['timestamp'] = current_time('mysql');
1823
1824 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1825 if (is_array($table_status)) {
1826 foreach ($table_status as $index => $table) {
1827 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1828 continue;
1829 }
1830 if (empty($table->Engine)) {
1831 continue;
1832 }
1833
1834 $tbl_rows += $table->Rows;
1835 $tbl_size += $table->Data_length + $table->Index_length;
1836 if (in_array($table->Name, $this->core_tables)) {
1837 $tbl_core++;
1838 } else {
1839 $tbl_custom++;
1840 }
1841
1842 $wpdb->query('OPTIMIZE TABLE ' . $table->Name);
1843 $wpdb->query('CREATE TABLE ' . $uid . '_' . $table->Name . ' LIKE ' . $table->Name);
1844 $wpdb->query('INSERT ' . $uid . '_' . $table->Name . ' SELECT * FROM ' . $table->Name);
1845 } // foreach
1846 } else {
1847 return new WP_Error(1, 'Can\'t get table status data.');
1848 }
1849
1850 $snapshot['tbl_core'] = $tbl_core;
1851 $snapshot['tbl_custom'] = $tbl_custom;
1852 $snapshot['tbl_rows'] = $tbl_rows;
1853 $snapshot['tbl_size'] = $tbl_size;
1854
1855
1856 $snapshots[$uid] = $snapshot;
1857 update_option('wp-reset-snapshots', $snapshots);
1858
1859 do_action('wp_reset_create_snapshot', $uid, $snapshot);
1860
1861 return $snapshot;
1862 } // create_snapshot
1863
1864
1865 /**
1866 * Delete snapshot metadata and tables from DB
1867 *
1868 * @param string $uid Snapshot unique 6-char ID.
1869 *
1870 * @return bool|WP_Error True on success, or error object on fail.
1871 */
1872 function do_delete_snapshot($uid = '')
1873 {
1874 global $wpdb;
1875 $snapshots = $this->get_snapshots();
1876
1877 if (strlen($uid) != 6) {
1878 return new WP_Error(1, 'Invalid UID format.');
1879 }
1880
1881 if (!isset($snapshots[$uid])) {
1882 return new WP_Error(1, 'Unknown snapshot ID.');
1883 }
1884
1885 $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%')));
1886 foreach ($tables as $table) {
1887 $wpdb->query('DROP TABLE IF EXISTS ' . $table);
1888 }
1889
1890 $snapshot_copy = $snapshots[$uid];
1891 unset($snapshots[$uid]);
1892 update_option('wp-reset-snapshots', $snapshots);
1893
1894 do_action('wp_reset_delete_snapshot', $uid, $snapshot_copy);
1895
1896 return true;
1897 } // delete_snapshot
1898
1899
1900 /**
1901 * Exports snapshot as SQL dump; saved in gzipped file in WP_CONTENT folder.
1902 *
1903 * @param string $uid Snapshot unique 6-char ID.
1904 *
1905 * @return string|WP_Error Export base filename, or error object on fail.
1906 */
1907 function do_export_snapshot($uid = '')
1908 {
1909 $snapshots = $this->get_snapshots();
1910
1911 if (strlen($uid) != 6) {
1912 return new WP_Error(1, 'Invalid snapshot ID format.');
1913 }
1914
1915 if (!isset($snapshots[$uid])) {
1916 return new WP_Error(1, 'Unknown snapshot ID.');
1917 }
1918
1919 require_once $this->plugin_dir . 'libs/dumper.php';
1920
1921 try {
1922 $world_dumper = Shuttle_Dumper::create(array(
1923 'host' => DB_HOST,
1924 'username' => DB_USER,
1925 'password' => DB_PASSWORD,
1926 'db_name' => DB_NAME,
1927 ));
1928
1929 $folder = wp_mkdir_p(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder);
1930 if (!$folder) {
1931 return new WP_Error(1, 'Unable to create wp-content/' . $this->snapshots_folder . '/ folder.');
1932 }
1933
1934 $world_dumper->dump(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder . '/wp-reset-snapshot-' . $uid . '.sql.gz', $uid . '_');
1935 } catch (Shuttle_Exception $e) {
1936 return new WP_Error(1, "Couldn't dump snapshot: " . $e->getMessage());
1937 }
1938
1939 do_action('wp_reset_export_snapshot', 'wp-reset-snapshot-' . $uid . '.sql.gz');
1940
1941 return 'wp-reset-snapshot-' . $uid . '.sql.gz';
1942 } // export_snapshot
1943
1944
1945 /**
1946 * Replace current tables with ones in snapshot.
1947 *
1948 * @param string $uid Snapshot unique 6-char ID.
1949 *
1950 * @return bool|WP_Error True on success, or error object on fail.
1951 */
1952 function do_restore_snapshot($uid = '')
1953 {
1954 global $wpdb;
1955 $new_tables = array();
1956 $snapshots = $this->get_snapshots();
1957
1958 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
1959 return $res;
1960 }
1961
1962 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1963 if (is_array($table_status)) {
1964 foreach ($table_status as $index => $table) {
1965 if (0 !== stripos($table->Name, $uid . '_')) {
1966 continue;
1967 }
1968 if (empty($table->Engine)) {
1969 continue;
1970 }
1971
1972 $new_tables[] = $table->Name;
1973 } // foreach
1974 } else {
1975 return new WP_Error(1, 'Can\'t get table status data.');
1976 }
1977
1978 foreach ($table_status as $index => $table) {
1979 if (0 !== stripos($table->Name, $wpdb->prefix)) {
1980 continue;
1981 }
1982 if (empty($table->Engine)) {
1983 continue;
1984 }
1985
1986 $wpdb->query('DROP TABLE ' . $table->Name);
1987 } // foreach
1988
1989 // copy snapshot tables to original name
1990 foreach ($new_tables as $table) {
1991 $new_name = str_replace($uid . '_', '', $table);
1992
1993 $wpdb->query('CREATE TABLE ' . $new_name . ' LIKE ' . $table);
1994 $wpdb->query('INSERT ' . $new_name . ' SELECT * FROM ' . $table);
1995 }
1996
1997 wp_cache_flush();
1998 update_option('wp-reset', $this->options);
1999 update_option('wp-reset-snapshots', $snapshots);
2000
2001 do_action('wp_reset_restore_snapshot', $uid);
2002
2003 return true;
2004 } // restore_snapshot
2005
2006
2007 /**
2008 * Verifies snapshot integrity by comparing metadata and data in DB
2009 *
2010 * @param string $uid Snapshot unique 6-char ID.
2011 *
2012 * @return bool|WP_Error True on success, or error object on fail.
2013 */
2014 function verify_snapshot_integrity($uid)
2015 {
2016 global $wpdb;
2017 $tbl_core = $tbl_custom = 0;
2018 $snapshots = $this->get_snapshots();
2019
2020 if (strlen($uid) != 6) {
2021 return new WP_Error(1, 'Invalid snapshot ID format.');
2022 }
2023
2024 if (!isset($snapshots[$uid])) {
2025 return new WP_Error(1, 'Unknown snapshot ID.');
2026 }
2027
2028 $snapshot = $snapshots[$uid];
2029
2030 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
2031 if (is_array($table_status)) {
2032 foreach ($table_status as $index => $table) {
2033 if (0 !== stripos($table->Name, $uid . '_')) {
2034 continue;
2035 }
2036 if (empty($table->Engine)) {
2037 continue;
2038 }
2039
2040 if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
2041 $tbl_core++;
2042 } else {
2043 $tbl_custom++;
2044 }
2045 } // foreach
2046
2047 if ($tbl_core != $snapshot['tbl_core'] || $tbl_custom != $snapshot['tbl_custom']) {
2048 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.');
2049 }
2050 } else {
2051 return new WP_Error(1, 'Can\'t get table status data.');
2052 }
2053
2054 return true;
2055 } // verify_snapshot_integrity
2056
2057
2058 /**
2059 * Compares a selected snapshot with the current table set in DB
2060 *
2061 * @param string $uid Snapshot unique 6-char ID.
2062 *
2063 * @return string|WP_Error Formatted table with details on success, or error object on fail.
2064 */
2065 function do_compare_snapshots($uid)
2066 {
2067 global $wpdb;
2068 $current = $snapshot = array();
2069 $out = $out2 = $out3 = '';
2070
2071 if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
2072 return $res;
2073 }
2074
2075 $table_status = $wpdb->get_results('SHOW TABLE STATUS');
2076 foreach ($table_status as $index => $table) {
2077 if (empty($table->Engine)) {
2078 continue;
2079 }
2080
2081 if (0 !== stripos($table->Name, $uid . '_') && 0 !== stripos($table->Name, $wpdb->prefix)) {
2082 continue;
2083 }
2084
2085 $info = array();
2086 $info['rows'] = $table->Rows;
2087 $info['size_data'] = $table->Data_length;
2088 $info['size_index'] = $table->Index_length;
2089 $schema = $wpdb->get_row('SHOW CREATE TABLE ' . $table->Name, ARRAY_N);
2090 $info['schema'] = $schema[1];
2091 $info['engine'] = $table->Engine;
2092 $info['fullname'] = $table->Name;
2093 $basename = str_replace(array($uid . '_'), array(''), $table->Name);
2094 $info['basename'] = $basename;
2095 $info['corename'] = str_replace(array($wpdb->prefix), array(''), $basename);
2096 $info['uid'] = $uid;
2097
2098 if (0 === stripos($table->Name, $uid . '_')) {
2099 $snapshot[$basename] = $info;
2100 }
2101
2102 if (0 === stripos($table->Name, $wpdb->prefix)) {
2103 $info['uid'] = '';
2104 $current[$basename] = $info;
2105 }
2106 } // foreach
2107
2108 $in_both = array_keys(array_intersect_key($current, $snapshot));
2109 $in_current_only = array_diff_key($current, $snapshot);
2110 $in_snapshot_only = array_diff_key($snapshot, $current);
2111
2112 $out .= '<br><br>';
2113 foreach ($in_current_only as $table) {
2114 $out .= '<div class="wpr-table-container in-current-only" data-table="' . $table['basename'] . '">';
2115 $out .= '<table>';
2116 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
2117 $out .= '<td><b>' . $table['fullname'] . '</b></td>';
2118 $out .= '<td>table is not present in snapshot<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2119 $out .= '</tr>';
2120 $out .= '<tr class="hidden">';
2121 $out .= '<td>';
2122 $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>';
2123 $out .= '<pre>' . $table['schema'] . '</pre>';
2124 $out .= '</td>';
2125 $out .= '<td>&nbsp;</td>';
2126 $out .= '</tr>';
2127 $out .= '</table>';
2128 $out .= '</div>';
2129 } // foreach in current only
2130
2131 foreach ($in_snapshot_only as $table) {
2132 $out .= '<div class="wpr-table-container in-snapshot-only" data-table="' . $table['basename'] . '">';
2133 $out .= '<table>';
2134 $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
2135 $out .= '<td>table is not present in current tables</td>';
2136 $out .= '<td><b>' . $table['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2137 $out .= '</tr>';
2138 $out .= '<tr class="hidden">';
2139 $out .= '<td>&nbsp;</td>';
2140 $out .= '<td>';
2141 $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>';
2142 $out .= '<pre>' . $table['schema'] . '</pre>';
2143 $out .= '</td>';
2144 $out .= '</tr>';
2145 $out .= '</table>';
2146 $out .= '</div>';
2147 } // foreach in snapshot only
2148
2149 foreach ($in_both as $tablename) {
2150 $tbl_current = $current[$tablename];
2151 $tbl_snapshot = $snapshot[$tablename];
2152
2153 $schema1 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_current['schema'], 1);
2154 $schema2 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_snapshot['schema'], 1);
2155 $tbl_snapshot['tmp_schema'] = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $tbl_snapshot['schema']);
2156 $schema2 = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $schema2);
2157
2158 if ($tbl_current['rows'] == $tbl_snapshot['rows'] && $tbl_current['schema'] == $tbl_snapshot['tmp_schema']) {
2159 $out3 .= '<div class="wpr-table-container identical" data-table="' . $tablename . '">';
2160 $out3 .= '<table>';
2161 $out3 .= '<tr title="Click to show/hide more info" class="wpr-table-match header-row">';
2162 $out3 .= '<td><b>' . $tbl_current['fullname'] . '</b></td>';
2163 $out3 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2164 $out3 .= '</tr>';
2165 $out3 .= '<tr class="hidden">';
2166 $out3 .= '<td>';
2167 $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>';
2168 $out3 .= '<pre>' . $tbl_current['schema'] . '</pre>';
2169 $out3 .= '</td>';
2170 $out3 .= '<td>';
2171 $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>';
2172 $out3 .= '<pre>' . $tbl_snapshot['schema'] . '</pre>';
2173 $out3 .= '</td>';
2174 $out3 .= '</tr>';
2175 $out3 .= '</table>';
2176 $out3 .= '</div>';
2177 } elseif ($schema1 != $schema2) {
2178 require_once $this->plugin_dir . 'libs/diff.php';
2179 require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
2180 $diff = new Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
2181 $renderer = new Diff_Renderer_Html_SideBySide;
2182
2183 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2184 $out2 .= '<table>';
2185 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
2186 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> table schemas do not match</td>';
2187 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> table schemas do not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2188 $out2 .= '</tr>';
2189 $out2 .= '<tr class="hidden">';
2190 $out2 .= '<td>';
2191 $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>';
2192 $out2 .= '</td>';
2193 $out2 .= '<td>';
2194 $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>';
2195 $out2 .= '</td>';
2196 $out2 .= '</tr>';
2197 $out2 .= '<tr class="hidden">';
2198 $out2 .= '<td colspan="2" class="no-padding">';
2199 $out2 .= $diff->Render($renderer);
2200 $out2 .= '</td>';
2201 $out2 .= '</tr>';
2202 $out2 .= '</table>';
2203 $out2 .= '</div>';
2204 } else {
2205 $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2206 $out2 .= '<table>';
2207 $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
2208 $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> data in tables does not match</td>';
2209 $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> data in tables does not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
2210 $out2 .= '</tr>';
2211 $out2 .= '<tr class="hidden">';
2212 $out2 .= '<td>';
2213 $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>';
2214 $out2 .= '</td>';
2215 $out2 .= '<td>';
2216 $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>';
2217 $out2 .= '</td>';
2218 $out2 .= '</tr>';
2219
2220 $out2 .= '<tr class="hidden">';
2221 $out2 .= '<td colspan="2">';
2222 if ($tbl_current['corename'] == 'options') {
2223 $ss_prefix = $tbl_snapshot['uid'] . '_' . $wpdb->prefix;
2224 $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;");
2225 $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;");
2226 $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;");
2227 $out2 .= '<table class="table_diff">';
2228 $out2 .= '<tr><td style="width: 100px;"><b>Option Name</b></td><td><b>Current Value</b></td><td><b>Snapshot Value</b></td></tr>';
2229 foreach ($diff_rows as $row) {
2230 $out2 .= '<tr>';
2231 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
2232 $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : $row->current_value) . '</td>';
2233 $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : $row->snapshot_value) . '</td>';
2234 $out2 .= '</tr>';
2235 } // foreach
2236 foreach ($only_current as $row) {
2237 $out2 .= '<tr>';
2238 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
2239 $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : $row->current_value) . '</td>';
2240 $out2 .= '<td><i>not found in snapshot</i></td>';
2241 $out2 .= '</tr>';
2242 } // foreach
2243 foreach ($only_current as $row) {
2244 $out2 .= '<tr>';
2245 $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
2246 $out2 .= '<td><i>not found in current tables</i></td>';
2247 $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : $row->snapshot_value) . '</td>';
2248 $out2 .= '</tr>';
2249 } // foreach
2250 $out2 .= '</table>';
2251 } else {
2252 $out2 .= '<p class="textcenter">Detailed data diff is not available for this table.</p>';
2253 }
2254 $out2 .= '</td>';
2255 $out2 .= '</tr>';
2256
2257 $out2 .= '</table>';
2258 $out2 .= '</div>';
2259 }
2260 } // foreach in both
2261
2262 return $out . $out2 . $out3;
2263 } // do_compare_snapshots
2264
2265
2266 /**
2267 * Generates a unique 6-char snapshot ID; verified non-existing
2268 *
2269 * @return string
2270 */
2271 function generate_snapshot_uid()
2272 {
2273 global $wpdb;
2274 $snapshots = $this->get_snapshots();
2275 $cnt = 0;
2276 $uid = false;
2277
2278 do {
2279 $cnt++;
2280 $uid = sprintf('%06x', mt_rand(0, 0xFFFFFF));
2281
2282 $verify_db = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array('%' . $uid . '%')));
2283 } while (!empty($verify_db) && isset($snapshots[$uid]) && $cnt < 30);
2284
2285 if ($cnt == 30) {
2286 $uid = false;
2287 }
2288
2289 return $uid;
2290 } // generate_snapshot_uid
2291
2292
2293 /**
2294 * Helper function for adding plugins to featured list
2295 *
2296 * @return array
2297 */
2298 function featured_plugins_tab($args)
2299 {
2300 add_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);
2301
2302 return $args;
2303 } // featured_plugins_tab
2304
2305
2306 /**
2307 * Add single plugin to featured list
2308 *
2309 * @return object
2310 */
2311 function add_plugin_featured($plugin_slug, $res)
2312 {
2313 // check if plugin is already on the list
2314 if (!empty($res->plugins) && is_array($res->plugins)) {
2315 foreach ($res->plugins as $plugin) {
2316 if (is_object($plugin) && !empty($plugin->slug) && $plugin->slug == $plugin_slug) {
2317 return $res;
2318 }
2319 } // foreach
2320 }
2321
2322 if ($plugin_info = get_transient('wf-plugin-info-' . $plugin_slug)) {
2323 array_unshift($res->plugins, $plugin_info);
2324 } else {
2325 $plugin_info = plugins_api('plugin_information', array(
2326 'slug' => $plugin_slug,
2327 'is_ssl' => is_ssl(),
2328 'fields' => array(
2329 'banners' => true,
2330 'reviews' => true,
2331 'downloaded' => true,
2332 'active_installs' => true,
2333 'icons' => true,
2334 'short_description' => true,
2335 )
2336 ));
2337 if (!is_wp_error($plugin_info)) {
2338 $res->plugins = array_merge(array($plugin_info), $res->plugins);
2339 set_transient('wf-plugin-info-' . $plugin_slug, $plugin_info, DAY_IN_SECONDS * 7);
2340 }
2341 }
2342
2343 return $res;
2344 } // add_plugin_featured
2345
2346
2347 /**
2348 * Add plugins to featured plugins list
2349 *
2350 * @return object
2351 */
2352 function plugins_api_result($res, $action, $args)
2353 {
2354 remove_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);
2355
2356 $res = $this->add_plugin_featured('security-ninja', $res);
2357 $res = $this->add_plugin_featured('under-construction-page', $res);
2358
2359 return $res;
2360 } // plugins_api_result
2361
2362
2363 /**
2364 * Clean up on uninstall; no action on deactive at the moment
2365 *
2366 * @return null
2367 */
2368 static function uninstall()
2369 {
2370 delete_option('wp-reset');
2371 delete_option('wp-reset-snapshots');
2372 } // uninstall
2373
2374
2375 /**
2376 * Disabled; we use singleton pattern so magic functions need to be disabled
2377 *
2378 * @return null
2379 */
2380 private function __clone()
2381 { }
2382
2383
2384 /**
2385 * Disabled; we use singleton pattern so magic functions need to be disabled
2386 *
2387 * @return null
2388 */
2389 private function __sleep()
2390 { }
2391
2392
2393 /**
2394 * Disabled; we use singleton pattern so magic functions need to be disabled
2395 *
2396 * @return null
2397 */
2398 private function __wakeup()
2399 { }
2400 } // WP_Reset class
2401
2402
2403 // Create plugin instance and hook things up
2404 // Only if in admin - plugin has no frontend functionality
2405 if (is_admin() || WP_Reset::is_cli_running()) {
2406 global $wp_reset;
2407 $wp_reset = WP_Reset::getInstance();
2408 add_action('plugins_loaded', array($wp_reset, 'load_textdomain'));
2409 register_uninstall_hook(__FILE__, array('WP_Reset', 'uninstall'));
2410 }
2411