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

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