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

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