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

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