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

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