PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.1
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.1
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 5 days ago Backup 5 days ago Basic 1 week ago Component 1 week ago Core 5 days ago Framework 5 days ago Frontend 1 week ago Notifications 1 week ago Staging 5 days ago assets 5 days ago languages 1 week ago resources 1 week ago vendor_wpstg 1 week ago views 5 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 5 days ago freeBootstrap.php 1 week ago install.php 1 week ago opcacheBootstrap.php 5 days ago readme.txt 5 days ago runtimeRequirements.php 1 week ago uninstall.php 5 days ago wp-staging-error-handler.php 1 week ago wp-staging.php 5 days ago
uninstall.php
632 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
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_deactivation_reason',
422 'wpstg_rest_url',
423 'wpstg.run_daily',
424 'wpstg_show_login_notice',
425 'wpstg_user_logged_in_status',
426 'wpstg_auto_login_failed',
427 'wpstg_auto_login_failed_reason',
428 'wpstg_failed_auto_login_attempts',
429 'wpstg_otp_sent',
430 'wpstg_otp_consecutive_failures',
431 'wpstg_otp_locked',
432 'wpstg_redirect_url',
433 'wpstg_remote_sync_session',
434 'wpstg_remote_sync_session_data',
435 'wpstg_remote_sync_session_events_offset',
436 'wpstg.queue.request.get_method',
437 'is_invalid_backup_file_index',
438 'wpstg_permalinks_do_purge',
439 'wpstg_purge_litespeed_cache',
440 'wpstg_activation_redirect',
441 'wpstg_pro_activation_redirect',
442 'wpstg_weekly_version_update',
443 'wpstg_rate_limit_update_check',
444 'wpstg_issue_report_submitted',
445 'wpstg.backup.schedules.slack_report_sent',
446 'wpstg_email_notification_access_token',
447 'wpstg.directory_listing.last_checked',
448 'wpstg_push_size_cache',
449 ];
450 }
451
452
453
454
455
456 private function deleteOptions(array $optionNames)
457 {
458 foreach ($optionNames as $optionName) {
459
460 if (in_array($optionName, $this->preserveOptions, true)) {
461 continue;
462 }
463
464 delete_option($optionName);
465 }
466 }
467
468
469
470
471
472
473
474 private function deleteUserMeta(array $metaKeys)
475 {
476 foreach ($metaKeys as $metaKey) {
477 delete_metadata('user', 0, $metaKey, '', true);
478 }
479 }
480
481
482
483
484 private function deleteTransients()
485 {
486 $transients = $this->getAllTransients();
487 foreach ($transients as $transientName) {
488 delete_transient($transientName);
489 }
490 }
491
492
493
494
495 private function cleanupEmptyPreserveOptions()
496 {
497 $this->cleanupEmptyOptions($this->preserveOptions);
498 }
499
500
501
502
503
504
505 private function cleanupEmptyOptions(array $options, bool $isSiteOptions = false)
506 {
507 foreach ($options as $option) {
508 $value = $isSiteOptions ? get_site_option($option): get_option($option);
509 if (empty($value)) {
510 $isSiteOptions ? delete_site_option($option): delete_option($option);
511 }
512 }
513 }
514
515
516
517
518 private function clearCronEvents()
519 {
520
521 wp_clear_scheduled_hook('wpstg_weekly_event');
522 }
523
524
525
526
527 private function cleanupWpStagingDirectories()
528 {
529 $uploadsBase = $this->getUploadsDirectory() . 'wp-staging/';
530 $directoriesToClean = [
531 $this->getWpContentDirectory() . 'wp-staging',
532 ];
533
534
535 if (!$this->isDirectoryContainsWpstgFiles($uploadsBase . 'backups')) {
536 $directoriesToClean[] = $uploadsBase;
537 } else {
538 $directoriesToClean[] = $uploadsBase . 'cache';
539 $directoriesToClean[] = $uploadsBase . 'logs';
540 $directoriesToClean[] = $uploadsBase . 'tmp';
541 }
542
543 foreach ($directoriesToClean as $directory) {
544 $this->deleteDirectoryRecursively($directory);
545 }
546 }
547
548
549
550
551
552 private function deleteDirectoryRecursively(string $directory)
553 {
554 if (!is_dir($directory)) {
555 return;
556 }
557
558 $absPath = trailingslashit(ABSPATH);
559 if ($directory === $absPath || $directory === dirname($absPath)) {
560 return;
561 }
562
563 foreach (new \DirectoryIterator($directory) as $item) {
564 if ($item->isDot()) {
565 continue;
566 }
567
568 $itemPath = $item->getPathname();
569 if ($item->isDir()) {
570 $this->deleteDirectoryRecursively($itemPath);
571 } else {
572 @unlink($itemPath);
573 }
574 }
575
576 @rmdir($directory);
577 }
578
579
580
581
582 private function deleteNetworkOptions()
583 {
584 delete_site_option('wpstg_license_key');
585 delete_site_option('wpstg_license_status');
586 delete_site_option('wpstgDisableLicenseNotice');
587 $this->cleanupEmptyOptions($this->preserveOptions, true);
588 }
589
590
591
592
593 private function getUploadsDirectory(): string
594 {
595 $uploadDir = wp_upload_dir();
596 return trailingslashit($uploadDir['basedir']);
597 }
598
599
600
601
602 private function getWpContentDirectory(): string
603 {
604 return trailingslashit(WP_CONTENT_DIR);
605 }
606
607
608
609
610
611 private function isDirectoryContainsWpstgFiles(string $backupsDir): bool
612 {
613 if (!is_dir($backupsDir)) {
614 return false;
615 }
616
617 $iterator = new \RecursiveIteratorIterator(
618 new \RecursiveDirectoryIterator($backupsDir, \FilesystemIterator::SKIP_DOTS)
619 );
620
621 foreach ($iterator as $item) {
622 if ($item->isFile() && strcasecmp($item->getExtension(), 'wpstg') === 0) {
623 return true;
624 }
625 }
626
627 return false;
628 }
629 }
630
631 new Uninstall();
632