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