PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.2
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.2
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 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / uninstall.php
wp-staging Last commit date
Backend 2 days ago Backup 2 days ago Basic 1 week ago Component 1 week ago Core 2 days ago Framework 2 days ago Frontend 2 days ago Notifications 1 week ago Staging 2 days ago assets 2 days ago languages 2 days ago resources 1 week ago vendor_wpstg 2 days ago views 2 days ago CONTRIBUTING.md 2 years ago Deactivate.php 1 week ago README.md 5 months ago SECURITY.md 1 month ago autoloader.php 1 week ago bootstrap.php 1 week ago commonBootstrap.php 1 week ago constantsFree.php 2 days ago freeBootstrap.php 1 week ago install.php 1 week ago opcacheBootstrap.php 2 days ago readme.txt 2 days ago runtimeRequirements.php 1 week ago uninstall.php 2 days ago wp-staging-error-handler.php 1 week ago wp-staging.php 2 days ago
uninstall.php
637 lines
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_clone_excluded_files_list',
329
330 'wpstg_clone_excluded_gd_files_list',
331 'wpstg_clone_excluded_hosting_files',
332 'wpstg_freemius_notice',
333 'wpstg_queue_table_structure_version',
334 'wpstg_settings_table_version',
335 'wpstg_q_feature_detection_ajax_available',
336 'wpstg_analytics_has_consent',
337 'wpstg_analytics_modal_dismissed',
338 'wpstg_analytics_notice_dismissed',
339 'wpstg_analytics_consent_remind_me',
340 'wpstg_experiments',
341 'wpstg_first_install',
342 'wpstg_onboarding_completed',
343 'wpstg_onboarding_exposure',
344 'wpstg_onboarding_journey',
345 'wpstg_onboarding_queued_backup',
346 'wpstg_onboarding_restarted',
347 'wpstg_default_color_mode',
348 'wpstg_default_os_color_mode',
349 'wpstg_last_backup_info',
350 'wpstg_backups_retention',
351 'wpstg_otps',
352 'wpstg_access_token',
353 'wpstg_disabled_notice',
354 'wpstg_send_email_as_html',
355 'wpstg_cli_notice_hidden_forever',
356 'wpstg_cli_dock_cta_shown',
357 'wpstg_cli_notice_dismissed_until',
358 'wpstg_completed_upgrades',
359 'wpstg_next_gen_engine_notice',
360 'wpstg_staging_engine_preference',
361 'wpstg_staging_engine_preferences',
362 ];
363 }
364
365
366
367
368
369
370 private function getBasicUserMeta(): array
371 {
372 return [
373 'wpstg_user_general_pro_card_snoozed_until',
374 ];
375 }
376
377
378
379
380 private function getProOptions(): array
381 {
382 return [
383 'wpstgpro_version',
384 'wpstgpro_version_upgraded_from',
385 'wpstgpro_install_date',
386 'wpstgpro_upgrade_date',
387 'wpstg_license_key',
388 'wpstg_license_status',
389 'wpstg_pro_latest_version',
390 'wpstg_googledrive',
391 'wpstg_google-drive',
392 'wpstg_dropbox',
393 'wpstg_one-drive',
394 'wpstg_pcloud',
395 'wpstg_amazons3',
396 'wpstg_amazon-s3',
397 'wpstg_sftp',
398 'wpstg_digitalocean',
399 'wpstg_digitalocean-spaces',
400 'wpstg_wasabi',
401 'wpstg_wasabi-s3',
402 'wpstg_generic-s3',
403 'wpstg_backup_schedules',
404 'wpstg_backup_schedules_send_error_report',
405 'wpstg_backup_schedules_report_email',
406 'wpstg_backup_schedules_send_slack_error_report',
407 'wpstg_backup_schedules_report_slack_webhook',
408 'wpstg_current_site_login_links',
409 'wpstg_remote_sync_api_token',
410 'wpstg_remote_sync_password',
411 ];
412 }
413
414
415
416
417 private function getAllTransients(): array
418 {
419 return [
420 'wpstg_current_job',
421 'wpstg_last_job_outcome',
422 'wpstg_deactivation_reason',
423 'wpstg_rest_url',
424 'wpstg.run_daily',
425 'wpstg_show_login_notice',
426 'wpstg_user_logged_in_status',
427 'wpstg_auto_login_failed',
428 'wpstg_auto_login_failed_reason',
429 'wpstg_failed_auto_login_attempts',
430 'wpstg_otp_sent',
431 'wpstg_otp_consecutive_failures',
432 'wpstg_otp_locked',
433 'wpstg_redirect_url',
434 'wpstg_oauth_state_dropbox',
435 'wpstg_oauth_state_google-drive',
436 'wpstg_oauth_state_one-drive',
437 'wpstg_oauth_state_pcloud',
438 'wpstg_remote_sync_session',
439 'wpstg_remote_sync_session_data',
440 'wpstg_remote_sync_session_events_offset',
441 'wpstg.queue.request.get_method',
442 'is_invalid_backup_file_index',
443 'wpstg_permalinks_do_purge',
444 'wpstg_purge_litespeed_cache',
445 'wpstg_activation_redirect',
446 'wpstg_pro_activation_redirect',
447 'wpstg_weekly_version_update',
448 'wpstg_rate_limit_update_check',
449 'wpstg_issue_report_submitted',
450 'wpstg.backup.schedules.slack_report_sent',
451 'wpstg_email_notification_access_token',
452 'wpstg.directory_listing.last_checked',
453 'wpstg_push_size_cache',
454 ];
455 }
456
457
458
459
460
461 private function deleteOptions(array $optionNames)
462 {
463 foreach ($optionNames as $optionName) {
464
465 if (in_array($optionName, $this->preserveOptions, true)) {
466 continue;
467 }
468
469 delete_option($optionName);
470 }
471 }
472
473
474
475
476
477
478
479 private function deleteUserMeta(array $metaKeys)
480 {
481 foreach ($metaKeys as $metaKey) {
482 delete_metadata('user', 0, $metaKey, '', true);
483 }
484 }
485
486
487
488
489 private function deleteTransients()
490 {
491 $transients = $this->getAllTransients();
492 foreach ($transients as $transientName) {
493 delete_transient($transientName);
494 }
495 }
496
497
498
499
500 private function cleanupEmptyPreserveOptions()
501 {
502 $this->cleanupEmptyOptions($this->preserveOptions);
503 }
504
505
506
507
508
509
510 private function cleanupEmptyOptions(array $options, bool $isSiteOptions = false)
511 {
512 foreach ($options as $option) {
513 $value = $isSiteOptions ? get_site_option($option): get_option($option);
514 if (empty($value)) {
515 $isSiteOptions ? delete_site_option($option): delete_option($option);
516 }
517 }
518 }
519
520
521
522
523 private function clearCronEvents()
524 {
525
526 wp_clear_scheduled_hook('wpstg_weekly_event');
527 }
528
529
530
531
532 private function cleanupWpStagingDirectories()
533 {
534 $uploadsBase = $this->getUploadsDirectory() . 'wp-staging/';
535 $directoriesToClean = [
536 $this->getWpContentDirectory() . 'wp-staging',
537 ];
538
539
540 if (!$this->isDirectoryContainsWpstgFiles($uploadsBase . 'backups')) {
541 $directoriesToClean[] = $uploadsBase;
542 } else {
543 $directoriesToClean[] = $uploadsBase . 'cache';
544 $directoriesToClean[] = $uploadsBase . 'logs';
545 $directoriesToClean[] = $uploadsBase . 'tmp';
546 }
547
548 foreach ($directoriesToClean as $directory) {
549 $this->deleteDirectoryRecursively($directory);
550 }
551 }
552
553
554
555
556
557 private function deleteDirectoryRecursively(string $directory)
558 {
559 if (!is_dir($directory)) {
560 return;
561 }
562
563 $absPath = trailingslashit(ABSPATH);
564 if ($directory === $absPath || $directory === dirname($absPath)) {
565 return;
566 }
567
568 foreach (new \DirectoryIterator($directory) as $item) {
569 if ($item->isDot()) {
570 continue;
571 }
572
573 $itemPath = $item->getPathname();
574 if ($item->isDir()) {
575 $this->deleteDirectoryRecursively($itemPath);
576 } else {
577 @unlink($itemPath);
578 }
579 }
580
581 @rmdir($directory);
582 }
583
584
585
586
587 private function deleteNetworkOptions()
588 {
589 delete_site_option('wpstg_license_key');
590 delete_site_option('wpstg_license_status');
591 delete_site_option('wpstgDisableLicenseNotice');
592 $this->cleanupEmptyOptions($this->preserveOptions, true);
593 }
594
595
596
597
598 private function getUploadsDirectory(): string
599 {
600 $uploadDir = wp_upload_dir();
601 return trailingslashit($uploadDir['basedir']);
602 }
603
604
605
606
607 private function getWpContentDirectory(): string
608 {
609 return trailingslashit(WP_CONTENT_DIR);
610 }
611
612
613
614
615
616 private function isDirectoryContainsWpstgFiles(string $backupsDir): bool
617 {
618 if (!is_dir($backupsDir)) {
619 return false;
620 }
621
622 $iterator = new \RecursiveIteratorIterator(
623 new \RecursiveDirectoryIterator($backupsDir, \FilesystemIterator::SKIP_DOTS)
624 );
625
626 foreach ($iterator as $item) {
627 if ($item->isFile() && strcasecmp($item->getExtension(), 'wpstg') === 0) {
628 return true;
629 }
630 }
631
632 return false;
633 }
634 }
635
636 new Uninstall();
637