PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.28.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.28.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / uninstall.php

uninstall.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.28.0, at uninstall.php

258 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Uninstall Script
4 *
5 * Handles complete plugin removal and cleanup
6 *
7 * @package ThinkRank
8 * @since 1.0.0
9 */
10
11 declare(strict_types=1);
12
13 // Prevent direct access
14 if (!defined('WP_UNINSTALL_PLUGIN')) {
15 exit;
16 }
17
18 /**
19 * ThinkRank Uninstaller Class
20 *
21 * Single Responsibility: Handle complete plugin removal
22 */
23 class ThinkRank_Uninstaller {
24
25 /**
26 * Run uninstall process
27 *
28 * @return void
29 */
30 public static function uninstall(): void {
31 // Check if user wants to keep data. Default to TRUE (preserve) to match the
32 // Settings class default and the documented UI behavior — only nuke data when
33 // the user has explicitly enabled "Delete all data on uninstall".
34 $keep_data = (bool) get_option('thinkrank_keep_data_on_uninstall', true);
35
36 if (!$keep_data) {
37 self::remove_database_tables();
38 self::remove_options();
39 self::remove_user_meta();
40 self::remove_post_meta();
41 }
42
43 self::clear_caches();
44 self::remove_cron_jobs();
45 self::remove_capabilities();
46 }
47
48 /**
49 * Remove custom database tables
50 *
51 * @return void
52 */
53 private static function remove_database_tables(): void {
54 global $wpdb;
55
56 $tables = [
57 // AI/Core Tables (from Database class)
58 $wpdb->prefix . 'thinkrank_ai_cache',
59 $wpdb->prefix . 'thinkrank_ai_usage',
60 $wpdb->prefix . 'thinkrank_content_briefs',
61 $wpdb->prefix . 'thinkrank_seo_scores',
62 $wpdb->prefix . 'thinkrank_seo_performance',
63 $wpdb->prefix . 'thinkrank_instant_indexing_logs',
64 // SEO Tables (from Database_Schema)
65 $wpdb->prefix . 'thinkrank_seo_settings',
66 $wpdb->prefix . 'thinkrank_seo_analysis',
67 $wpdb->prefix . 'thinkrank_seo_keywords',
68 $wpdb->prefix . 'thinkrank_seo_schema',
69 $wpdb->prefix . 'thinkrank_seo_social',
70 $wpdb->prefix . 'thinkrank_seo_local',
71 $wpdb->prefix . 'thinkrank_email_report_logs',
72 // Rank Tracker Tables
73 $wpdb->prefix . 'thinkrank_rank_tracking',
74 $wpdb->prefix . 'thinkrank_tracked_keywords',
75 // Pro feature tables (redirections, broken links, local SEO)
76 $wpdb->prefix . 'thinkrank_redirects',
77 $wpdb->prefix . 'thinkrank_404_logs',
78 $wpdb->prefix . 'thinkrank_broken_links',
79 $wpdb->prefix . 'thinkrank_locations',
80 ];
81
82 foreach ($tables as $table) {
83 // Check WordPress version for %i support (introduced in 6.2)
84 if (version_compare($GLOBALS['wp_version'], '6.2', '>=')) {
85 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
86 $wpdb->query($wpdb->prepare("DROP TABLE IF EXISTS %i", $table));
87 // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
88 } else {
89 // Fallback for older WordPress versions - table name is from our controlled list
90 $escaped_table = esc_sql($table);
91 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
92 $wpdb->query("DROP TABLE IF EXISTS `{$escaped_table}`");
93 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
94 }
95 }
96 }
97
98 /**
99 * Remove plugin options
100 *
101 * @return void
102 */
103 private static function remove_options(): void {
104 // Remove all ThinkRank options using wildcard delete for completeness
105 global $wpdb;
106 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Uninstall cleanup requires direct database access to remove all plugin options
107 $wpdb->query(
108 $wpdb->prepare(
109 "DELETE FROM {$wpdb->options}
110 WHERE option_name LIKE %s
111 OR option_name LIKE %s",
112 $wpdb->esc_like('thinkrank_') . '%',
113 // Appsero / WP Insights telemetry options (wpins_thinkrank_*)
114 $wpdb->esc_like('wpins_thinkrank_') . '%'
115 )
116 );
117 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
118
119 // Remove transients (prefixed with _transient_, not caught by options wildcard)
120 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Core table name is safe, uninstall cleanup requires direct database access
121 $wpdb->query(
122 $wpdb->prepare(
123 "DELETE FROM {$wpdb->options}
124 WHERE option_name LIKE %s
125 OR option_name LIKE %s",
126 $wpdb->esc_like('_transient_thinkrank_') . '%',
127 $wpdb->esc_like('_transient_timeout_thinkrank_') . '%'
128 )
129 );
130 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
131 }
132
133 /**
134 * Remove user meta data
135 *
136 * @return void
137 */
138 private static function remove_user_meta(): void {
139 global $wpdb;
140
141 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Core table name is safe, uninstall cleanup requires direct database access
142 $wpdb->query(
143 $wpdb->prepare(
144 "DELETE FROM {$wpdb->usermeta}
145 WHERE meta_key LIKE %s
146 OR meta_key LIKE %s",
147 $wpdb->esc_like('thinkrank_') . '%',
148 $wpdb->esc_like('_thinkrank_') . '%'
149 )
150 );
151 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
152 }
153
154 /**
155 * Remove post meta data
156 *
157 * @return void
158 */
159 private static function remove_post_meta(): void {
160 global $wpdb;
161
162 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Core table name is safe, uninstall cleanup requires direct database access
163 $wpdb->query(
164 $wpdb->prepare(
165 "DELETE FROM {$wpdb->postmeta}
166 WHERE meta_key LIKE %s
167 OR meta_key LIKE %s",
168 $wpdb->esc_like('thinkrank_') . '%',
169 $wpdb->esc_like('_thinkrank_') . '%'
170 )
171 );
172 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
173 }
174
175
176 /**
177 * Clear all caches
178 *
179 * @return void
180 */
181 private static function clear_caches(): void {
182 // Clear ThinkRank-specific object cache group
183 if (function_exists('wp_cache_flush_group')) {
184 wp_cache_flush_group('thinkrank');
185 }
186 }
187
188 /**
189 * Remove scheduled cron jobs
190 *
191 * @return void
192 */
193 private static function remove_cron_jobs(): void {
194 $cron_jobs = [
195 'thinkrank_cache_cleanup',
196 'thinkrank_usage_analytics',
197 ];
198
199 foreach ($cron_jobs as $job) {
200 wp_clear_scheduled_hook($job);
201 }
202 }
203
204 /**
205 * Remove custom capabilities
206 *
207 * @return void
208 */
209 private static function remove_capabilities(): void {
210 // Runs even when the user keeps their data, so the capability-sync marker
211 // has to go with it. Leaving it behind would make Capability_Manager::ensure()
212 // short-circuit on the next install and never re-grant thinkrank_access,
213 // locking administrators out of the admin menu.
214 delete_option('thinkrank_caps_version');
215
216 // Keep in sync with ThinkRank\Core\Capability_Manager::capabilities().
217 // The plugin autoloader isn't available during uninstall, so the slugs are
218 // mirrored here. The trailing legacy slugs were granted by older versions
219 // and are removed too so historical roles are fully cleaned.
220 $capabilities = [
221 'thinkrank_access',
222 'thinkrank_site_identity',
223 'thinkrank_analytics',
224 'thinkrank_performance',
225 'thinkrank_global_seo',
226 'thinkrank_image_seo',
227 'thinkrank_schema',
228 'thinkrank_social_media',
229 'thinkrank_crawling',
230 'thinkrank_instant_indexing',
231 'thinkrank_author_archives',
232 'thinkrank_content_tools',
233 'thinkrank_internal_links',
234 'thinkrank_settings',
235 'thinkrank_manage_roles',
236 // Legacy slugs (pre-Role Manager) — remove if still present.
237 'thinkrank_manage_settings',
238 'thinkrank_view_analytics',
239 'thinkrank_manage_credits',
240 'thinkrank_use_ai_features',
241 ];
242
243 $roles = wp_roles();
244
245 foreach ($roles->roles as $role_name => $role_info) {
246 $role = get_role($role_name);
247 if ($role) {
248 foreach ($capabilities as $cap) {
249 $role->remove_cap($cap);
250 }
251 }
252 }
253 }
254 }
255
256 // Run the uninstaller
257 ThinkRank_Uninstaller::uninstall();
258