PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
← All changes | uninstall.php +130 -12 2.6.20trunk View file →
@@ -2,23 +2,23 @@
2 2
3 3 /**
4 4 * Fired when the plugin is uninstalled.
5 5 *
6 - * When populating this file, consider the following flow
7 - * of control:
6 + * Cleanup policy:
8 7 *
9 - * - This method should be static
10 - * - Check if the $_REQUEST content actually is the plugin name
11 - * - Run an admin referrer check to make sure it goes through authentication
12 - * - Verify the output of $_GET makes sense
13 - * - Repeat with other user roles. Best directly by using the links/query string parameters.
14 - * - Repeat things for multisite. Once for a single site in the network, once sitewide.
8 + * - Credentials (the Google Indexing service account — a plaintext private
9 + * key — and the IndexNow API key) are ALWAYS deleted. They must never be
10 + * left behind in wp_options by a plugin that is no longer installed.
11 + * - Everything else (custom tables, options, transients, per-post SEO meta,
12 + * log files) is only removed when the site owner opted in via the
13 + * "Delete all plugin data when the plugin is uninstalled" toggle in
14 + * Advanced Settings. The default is OFF, per WordPress uninstall guidelines.
15 15 *
16 - * This file may be updated more in future version of the Boilerplate; however, this is the
17 - * general skeleton and outline for how the file should work.
16 + * The opt-in option is read BEFORE any options are deleted, because the
17 + * option itself is plugin data.
18 18 *
19 - * For more information, see the following discussion:
20 - * https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913
19 + * For multisite, the cleanup runs once per site when the plugin is
20 + * network-activated and removed.
21 21 *
22 22 * @link https://searchatlas.com
23 23 * @since 1.0.0
24 24 *
@@ -27,5 +27,123 @@
27 27
28 28 // If uninstall not called from WordPress, then exit.
29 29 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
30 30 exit;
31 +}
32 +
33 +/**
34 + * Remove plugin data for the current site.
35 + *
36 + * @return void
37 + */
38 +function metasync_uninstall_cleanup() {
39 + global $wpdb;
40 +
41 + // Read the opt-in first: the option itself is plugin data and is removed
42 + // by the prefix sweep below.
43 + $delete_all_data = ( 'yes' === get_option( 'metasync_delete_data_on_uninstall', 'no' ) );
44 +
45 + // Credentials are always removed, regardless of the opt-in.
46 + delete_option( 'google_index_service_account' );
47 + delete_option( 'metasync_options_bing_instant_indexing' );
48 + // Legacy instant-indexing option: holds the Google service-account JSON key.
49 + delete_option( 'metasync_options_instant_indexing' );
50 +
51 + if ( ! $delete_all_data ) {
52 + return;
53 + }
54 +
55 + // Scheduled events.
56 + $cron_hooks = array(
57 + 'metasync_daily_cleanup',
58 + 'metasync_otto_js_check_event',
59 + 'metasync_process_otto_batch_cache_job',
60 + 'metasync_process_otto_crawl_url_job',
61 + 'metasync_sitemap_async_warmup_event',
62 + 'metasync_media_batch_optimize_cron',
63 + 'metasync_check_debug_limits',
64 + 'metasync_speed_cache_cleanup',
65 + 'metasync_bing_indexnow_submit_event',
66 + );
67 + foreach ( $cron_hooks as $cron_hook ) {
68 + wp_clear_scheduled_hook( $cron_hook );
69 + }
70 +
71 + // Custom tables.
72 + $tables = array(
73 + 'metasync_404_logs',
74 + 'metasync_redirections',
75 + 'metasync_heartbeat_error_logs',
76 + 'metasync_sync_history',
77 + 'metasync_otto_excluded_urls',
78 + 'metasync_robots_txt_backups',
79 + 'metasync_otto_bot_stats',
80 + 'metasync_otto_bot_logs',
81 + );
82 + foreach ( $tables as $table ) {
83 + $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}{$table}" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- static table names, no user input.
84 + }
85 +
86 + // Plugin options and transients (transients live in the options table
87 + // under _transient_/_transient_timeout_ prefixes when no object cache
88 + // persists them; object-cache-backed transients age out on their own).
89 + $option_patterns = array(
90 + 'metasync\_%',
91 + '\_transient\_metasync\_%',
92 + '\_transient\_timeout\_metasync\_%',
93 + '\_site\_transient\_metasync\_%',
94 + '\_site\_transient\_timeout\_metasync\_%',
95 + );
96 + foreach ( $option_patterns as $option_pattern ) {
97 + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $option_pattern ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from core.
98 + }
99 +
100 + // Per-post SEO values saved by the plugin (custom titles, meta
101 + // descriptions, robots directives, schema, Open Graph, canonical URLs).
102 + // Several metaboxes store their values without the leading underscore
103 + // (metasync_common_robots, metasync_schema_markup, metasync_advance_robots,
104 + // metasync_post, metasync_post_redirection_meta), so both prefixes are
105 + // swept.
106 + $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '\_metasync\_%' OR meta_key LIKE 'metasync\_%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- literal patterns, no user input.
107 +
108 + // On-disk data directory (error logs, zipped logs, cache artifacts).
109 + $metasync_data_dir = WP_CONTENT_DIR . '/metasync_data';
110 + if ( is_dir( $metasync_data_dir ) ) {
111 + metasync_uninstall_rrmdir( $metasync_data_dir );
112 + }
113 +}
114 +
115 +/**
116 + * Recursively delete a directory.
117 + *
118 + * @param string $dir Absolute directory path.
119 + * @return void
120 + */
121 +function metasync_uninstall_rrmdir( $dir ) {
122 + if ( ! is_dir( $dir ) ) {
123 + return;
124 + }
125 + foreach ( scandir( $dir ) as $entry ) {
126 + if ( '.' === $entry || '..' === $entry ) {
127 + continue;
128 + }
129 + $path = $dir . '/' . $entry;
130 + if ( is_dir( $path ) ) {
131 + metasync_uninstall_rrmdir( $path );
132 + } else {
133 + @unlink( $path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- best-effort cleanup on uninstall.
134 + }
135 + }
136 + @rmdir( $dir ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- best-effort cleanup on uninstall.
137 +}
138 +
139 +if ( is_multisite() ) {
140 + $metasync_uninstall_site_ids = get_sites( array( 'fields' => 'ids', 'number' => 0 ) );
141 + foreach ( $metasync_uninstall_site_ids as $metasync_uninstall_site_id ) {
142 + switch_to_blog( $metasync_uninstall_site_id );
143 + metasync_uninstall_cleanup();
144 + }
145 + restore_current_blog();
146 + unset( $metasync_uninstall_site_ids, $metasync_uninstall_site_id );
147 +} else {
148 + metasync_uninstall_cleanup();
31 149 }