PluginProbe
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.12.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.12.0
4.12.0 4.11.2 4.11.1 4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 All 67 releases
wp-staging / uninstall.php
uninstall.php
642 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('WP_UNINSTALL_PLUGIN')) {
5 exit;
6 }
7
8 /**
9 * Handles plugin uninstallation and cleanup of WP Staging data
10 *
11 * This class manages the complete uninstallation process including:
12 * - Detecting single site vs multisite/network uninstall scenarios
13 * - Distinguishing between Basic and Pro version uninstallation
14 * - Preserving data when both versions are installed
15 * - Cleaning up options, transients, and cron events
16 * - Removing plugin directories (except those containing backups)
17 * - Respecting user's "Remove Data on Uninstall" setting
18 *
19 * The class runs in standalone context without the plugin's autoloader,
20 * so it must be self-contained with no external dependencies.
21 *
22 * Note: Avoids using class constants to prevent loading the whole plugin.
23 * @package WPSTG
24 * @subpackage Uninstall
25 * @copyright Copyright (c) 2015, René Hermenau
26 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
27 * @since 0.9.0
28 */
29 class Uninstall
30 {
31
32
33
34
35 private $preserveOptions = [
36 'wpstg_existing_clones',
37 'wpstg_existing_clones_beta',
38 'wpstg_staging_sites',
39 'wpstg_connection',
40 ];
41
42 public function __construct()
43 {
44 if (!is_multisite()) {
45 $this->runForSingleSite();
46 return;
47 }
48
49 if ($this->isNetworkUninstall()) {
50 $this->runForNetwork();
51 } else {
52 $this->runForSingleSite();
53 }
54 }
55
56
57
58
59 private function runForNetwork()
60 {
61 $siteIds = get_sites(['fields' => 'ids']);
62 foreach ($siteIds as $siteId) {
63 switch_to_blog($siteId);
64 $this->runForSingleSite();
65 restore_current_blog();
66 }
67
68 $this->deleteNetworkOptions();
69 }
70
71
72
73
74 private function runForSingleSite()
75 {
76 $settings = $this->getSettings();
77
78 if (empty($settings['unInstallOnDelete']) || $settings['unInstallOnDelete'] !== '1') {
79 return;
80 }
81
82
83
84 if ($this->isProInstalled() && $this->isUninstallingBasic()) {
85 return;
86 }
87
88
89 if ($this->isBasicInstalled() && $this->isUninstallingPro()) {
90 $this->deleteOptions($this->getProOptions());
91 return;
92 }
93
94
95 if (!$this->isBasicInstalled() && $this->isUninstallingPro()) {
96 $this->performCompleteCleanup(true);
97 return;
98 }
99
100
101 if (!$this->isProInstalled() && $this->isUninstallingBasic()) {
102 $this->performCompleteCleanup(false);
103 }
104 }
105
106
107
108
109
110 private function performCompleteCleanup(bool $isPro)
111 {
112 $this->deleteOptions($this->getBasicOptions());
113 if ($isPro) {
114 $this->deleteOptions($this->getProOptions());
115 }
116
117 $this->deleteUserMeta($this->getBasicUserMeta());
118 $this->dropWpStagingSettingsTable();
119 $this->deleteTransients();
120 $this->cleanupEmptyPreserveOptions();
121 $this->clearCronEvents();
122 $this->cleanupWpStagingDirectories();
123 }
124
125
126
127
128 private function dropWpStagingSettingsTable()
129 {
130 global $wpdb;
131
132 if (!($wpdb instanceof \wpdb)) {
133 return;
134 }
135
136 $tableName = str_replace('`', '', $wpdb->prefix . 'wpstg_settings');
137 $wpdb->query("DROP TABLE IF EXISTS `{$tableName}`");
138 }
139
140
141
142
143 private function isNetworkUninstall(): bool
144 {
145 return (is_multisite() && is_network_admin());
146 }
147
148
149
150
151 private function isUninstallingBasic(): bool
152 {
153 $pluginDirs = ['wp-staging', 'wp-staging-1'];
154 return $this->isUninstallingPlugin($pluginDirs);
155 }
156
157
158
159
160 private function isUninstallingPro(): bool
161 {
162 $pluginDirs = ['wp-staging-pro', 'wp-staging-pro-1'];
163 return $this->isUninstallingPlugin($pluginDirs);
164 }
165
166
167
168
169
170 private function isUninstallingPlugin(array $pluginDirs): bool
171 {
172 return in_array(basename(__DIR__), $pluginDirs);
173 }
174
175
176
177
178 private function isProInstalled(): bool
179 {
180
181 if ($this->isProInstalledByHeaders()) {
182 return true;
183 }
184
185
186 $plugins = [
187 'wp-staging-pro-1/wp-staging-pro.php',
188 'wp-staging-pro/wp-staging-pro.php',
189 ];
190 foreach ($plugins as $plugin) {
191 if ($this->isPluginInstalled($plugin)) {
192 return true;
193 }
194 }
195
196 return false;
197 }
198
199
200
201
202 private function isBasicInstalled(): bool
203 {
204
205 if ($this->isBasicInstalledByHeaders()) {
206 return true;
207 }
208
209
210 $plugins = [
211 'wp-staging-1/wp-staging.php',
212 'wp-staging/wp-staging.php',
213 ];
214 foreach ($plugins as $plugin) {
215 if ($this->isPluginInstalled($plugin)) {
216 return true;
217 }
218 }
219
220 return false;
221 }
222
223
224
225
226
227 private function isPluginInstalled($pluginName): bool
228 {
229 return file_exists( WP_PLUGIN_DIR . '/' . $pluginName );
230 }
231
232
233
234
235
236 private function findPluginByIdentifiers(array $identifiers): bool
237 {
238 if (!function_exists('get_plugins')) {
239 require_once ABSPATH . 'wp-admin/includes/plugin.php';
240 }
241
242 $plugins = get_plugins();
243 $searchCriteria = array_change_key_case($identifiers, CASE_LOWER);
244
245 foreach ($plugins as $file => $data) {
246 $name = strtolower($data['Name'] ?? '');
247 $slug = strtolower(dirname($file));
248 $mainFile = strtolower(basename($file, '.php'));
249
250 if (isset($searchCriteria['file']) && strtolower($searchCriteria['file']) === strtolower($file)) {
251 return true;
252 }
253
254 if (isset($searchCriteria['slug']) && ($slug === $searchCriteria['slug'] || $mainFile === $searchCriteria['slug'])) {
255 return true;
256 }
257
258 if (isset($searchCriteria['name']) && $name === strtolower($searchCriteria['name'])) {
259 return true;
260 }
261 }
262
263 return false;
264 }
265
266
267
268
269 private function isBasicInstalledByHeaders(): bool
270 {
271 return $this->findPluginByIdentifiers([
272 'slug' => 'wp-staging',
273 'name' => 'WP Staging',
274 'file' => 'wp-staging/wp-staging.php',
275 ]);
276 }
277
278
279
280
281 private function isProInstalledByHeaders(): bool
282 {
283 return $this->findPluginByIdentifiers([
284 'slug' => 'wp-staging-pro',
285 'name' => 'WP Staging Pro',
286 'file' => 'wp-staging-pro/wp-staging-pro.php',
287 ]);
288 }
289
290
291
292
293 private function getSettings(): array
294 {
295 return (array)get_option('wpstg_settings', []);
296 }
297
298
299
300
301 private function getBasicOptions(): array
302 {
303 return [
304 'wpstg_settings',
305 'wpstg_backup_before_update_mode',
306 'wpstg_backup_before_update_intro_seen',
307 'wpstg_backup_before_update_request',
308 'wpstg_update_protection_health',
309 'wpstg_clone_settings',
310 'wpstg_free_install_date',
311 'wpstg_installDate',
312 'wpstg_version',
313 'wpstg_version_upgraded_from',
314 'wpstg_free_upgrade_date',
315 'wpstg_rating',
316 'wpstg_rating_snooze_count',
317 'wpstg_unique_identifier',
318 'wpstg_is_staging_site',
319 'wpstg_resave_permalinks_executed',
320 'wpstg_rmpermalinks_executed',
321 'wpstg_connection',
322 'wpstg_staging_sites',
323 'wpstg_existing_clones',
324 'wpstg_existing_clones_beta',
325 'wpstg_execute',
326 'wpstg_emails_disabled',
327 'wpstg_woo_scheduler_disabled',
328 'wpstg_optimizer_disabled_after_fatal',
329 'wpstg_clone_excluded_files_list',
330
331 'wpstg_clone_excluded_gd_files_list',
332 'wpstg_clone_excluded_hosting_files',
333 'wpstg_freemius_notice',
334 'wpstg_queue_table_structure_version',
335 'wpstg_settings_table_version',
336 'wpstg_q_feature_detection_ajax_available',
337 'wpstg_analytics_has_consent',
338 'wpstg_analytics_modal_dismissed',
339 'wpstg_analytics_notice_dismissed',
340 'wpstg_analytics_consent_remind_me',
341 'wpstg_experiments',
342 'wpstg_first_install',
343 'wpstg_onboarding_completed',
344 'wpstg_onboarding_exposure',
345 'wpstg_onboarding_journey',
346 'wpstg_onboarding_queued_backup',
347 'wpstg_onboarding_restarted',
348 'wpstg_default_color_mode',
349 'wpstg_default_os_color_mode',
350 'wpstg_last_backup_info',
351 'wpstg_backups_retention',
352 'wpstg_otps',
353 'wpstg_access_token',
354 'wpstg_disabled_notice',
355 'wpstg_send_email_as_html',
356 'wpstg_cli_notice_hidden_forever',
357 'wpstg_cli_dock_cta_shown',
358 'wpstg_cli_notice_dismissed_until',
359 'wpstg_backup_notice_is_closed',
360 'wpstg_backup_notice_remind_me',
361 'wpstg_completed_upgrades',
362 'wpstg_next_gen_engine_notice',
363 'wpstg_staging_engine_preference',
364 'wpstg_staging_engine_preferences',
365 ];
366 }
367
368
369
370
371
372
373 private function getBasicUserMeta(): array
374 {
375 return [
376 'wpstg_user_general_pro_card_snoozed_until',
377 ];
378 }
379
380
381
382
383 private function getProOptions(): array
384 {
385 return [
386 'wpstgpro_version',
387 'wpstgpro_version_upgraded_from',
388 'wpstgpro_install_date',
389 'wpstgpro_upgrade_date',
390 'wpstg_license_key',
391 'wpstg_license_status',
392 'wpstg_pro_latest_version',
393 'wpstg_googledrive',
394 'wpstg_google-drive',
395 'wpstg_dropbox',
396 'wpstg_one-drive',
397 'wpstg_pcloud',
398 'wpstg_amazons3',
399 'wpstg_amazon-s3',
400 'wpstg_sftp',
401 'wpstg_digitalocean',
402 'wpstg_digitalocean-spaces',
403 'wpstg_wasabi',
404 'wpstg_wasabi-s3',
405 'wpstg_generic-s3',
406 'wpstg_backup_schedules',
407 'wpstg_backup_schedules_send_error_report',
408 'wpstg_backup_schedules_report_email',
409 'wpstg_backup_schedules_send_slack_error_report',
410 'wpstg_backup_schedules_report_slack_webhook',
411 'wpstg_current_site_login_links',
412 'wpstg_remote_sync_api_token',
413 'wpstg_remote_sync_password',
414 ];
415 }
416
417
418
419
420 private function getAllTransients(): array
421 {
422 return [
423 'wpstg_current_job',
424 'wpstg_last_job_outcome',
425 'wpstg_deactivation_reason',
426 'wpstg_rest_url',
427 'wpstg.run_daily',
428 'wpstg_show_login_notice',
429 'wpstg_user_logged_in_status',
430 'wpstg_auto_login_failed',
431 'wpstg_auto_login_failed_reason',
432 'wpstg_failed_auto_login_attempts',
433 'wpstg_otp_sent',
434 'wpstg_otp_consecutive_failures',
435 'wpstg_otp_locked',
436 'wpstg_redirect_url',
437 'wpstg_oauth_state_dropbox',
438 'wpstg_oauth_state_google-drive',
439 'wpstg_oauth_state_one-drive',
440 'wpstg_oauth_state_pcloud',
441 'wpstg_remote_sync_session',
442 'wpstg_remote_sync_session_data',
443 'wpstg_remote_sync_session_events_offset',
444 'wpstg.queue.request.get_method',
445 'is_invalid_backup_file_index',
446 'wpstg_permalinks_do_purge',
447 'wpstg_purge_litespeed_cache',
448 'wpstg_activation_redirect',
449 'wpstg_optimizer_check_secret',
450 'wpstg_pro_activation_redirect',
451 'wpstg_weekly_version_update',
452 'wpstg_rate_limit_update_check',
453 'wpstg_issue_report_submitted',
454 'wpstg.backup.schedules.slack_report_sent',
455 'wpstg_email_notification_access_token',
456 'wpstg.directory_listing.last_checked',
457 'wpstg_push_size_cache',
458 'wpstg_magic_login_check_failures',
459 ];
460 }
461
462
463
464
465
466 private function deleteOptions(array $optionNames)
467 {
468 foreach ($optionNames as $optionName) {
469
470 if (in_array($optionName, $this->preserveOptions, true)) {
471 continue;
472 }
473
474 delete_option($optionName);
475 }
476 }
477
478
479
480
481
482
483
484 private function deleteUserMeta(array $metaKeys)
485 {
486 foreach ($metaKeys as $metaKey) {
487 delete_metadata('user', 0, $metaKey, '', true);
488 }
489 }
490
491
492
493
494 private function deleteTransients()
495 {
496 $transients = $this->getAllTransients();
497 foreach ($transients as $transientName) {
498 delete_transient($transientName);
499 }
500 }
501
502
503
504
505 private function cleanupEmptyPreserveOptions()
506 {
507 $this->cleanupEmptyOptions($this->preserveOptions);
508 }
509
510
511
512
513
514
515 private function cleanupEmptyOptions(array $options, bool $isSiteOptions = false)
516 {
517 foreach ($options as $option) {
518 $value = $isSiteOptions ? get_site_option($option): get_option($option);
519 if (empty($value)) {
520 $isSiteOptions ? delete_site_option($option): delete_option($option);
521 }
522 }
523 }
524
525
526
527
528 private function clearCronEvents()
529 {
530
531 wp_clear_scheduled_hook('wpstg_weekly_event');
532 }
533
534
535
536
537 private function cleanupWpStagingDirectories()
538 {
539 $uploadsBase = $this->getUploadsDirectory() . 'wp-staging/';
540 $directoriesToClean = [
541 $this->getWpContentDirectory() . 'wp-staging',
542 ];
543
544
545 if (!$this->isDirectoryContainsWpstgFiles($uploadsBase . 'backups')) {
546 $directoriesToClean[] = $uploadsBase;
547 } else {
548 $directoriesToClean[] = $uploadsBase . 'cache';
549 $directoriesToClean[] = $uploadsBase . 'logs';
550 $directoriesToClean[] = $uploadsBase . 'tmp';
551 }
552
553 foreach ($directoriesToClean as $directory) {
554 $this->deleteDirectoryRecursively($directory);
555 }
556 }
557
558
559
560
561
562 private function deleteDirectoryRecursively(string $directory)
563 {
564 if (!is_dir($directory)) {
565 return;
566 }
567
568 $absPath = trailingslashit(ABSPATH);
569 if ($directory === $absPath || $directory === dirname($absPath)) {
570 return;
571 }
572
573 foreach (new \DirectoryIterator($directory) as $item) {
574 if ($item->isDot()) {
575 continue;
576 }
577
578 $itemPath = $item->getPathname();
579 if ($item->isDir()) {
580 $this->deleteDirectoryRecursively($itemPath);
581 } else {
582 @unlink($itemPath);
583 }
584 }
585
586 @rmdir($directory);
587 }
588
589
590
591
592 private function deleteNetworkOptions()
593 {
594 delete_site_option('wpstg_license_key');
595 delete_site_option('wpstg_license_status');
596 delete_site_option('wpstgDisableLicenseNotice');
597 $this->cleanupEmptyOptions($this->preserveOptions, true);
598 }
599
600
601
602
603 private function getUploadsDirectory(): string
604 {
605 $uploadDir = wp_upload_dir();
606 return trailingslashit($uploadDir['basedir']);
607 }
608
609
610
611
612 private function getWpContentDirectory(): string
613 {
614 return trailingslashit(WP_CONTENT_DIR);
615 }
616
617
618
619
620
621 private function isDirectoryContainsWpstgFiles(string $backupsDir): bool
622 {
623 if (!is_dir($backupsDir)) {
624 return false;
625 }
626
627 $iterator = new \RecursiveIteratorIterator(
628 new \RecursiveDirectoryIterator($backupsDir, \FilesystemIterator::SKIP_DOTS)
629 );
630
631 foreach ($iterator as $item) {
632 if ($item->isFile() && strcasecmp($item->getExtension(), 'wpstg') === 0) {
633 return true;
634 }
635 }
636
637 return false;
638 }
639 }
640
641 new Uninstall();
642