PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.32.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.32.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.32.0, at uninstall.php

293 lines 11.7 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 * Option that records a deliberate uninstall.
27 *
28 * Keep in sync with ThinkRank\Core\Activator::UNINSTALLED_OPTION and with
29 * ThinkRank Pro's Free_Plugin_Installer.
30 */
31 private const UNINSTALLED_OPTION = 'thinkrank_uninstalled';
32
33 /**
34 * Run uninstall process
35 *
36 * @return void
37 */
38 public static function uninstall(): void {
39 // Check if user wants to keep data. Default to TRUE (preserve) to match the
40 // Settings class default and the documented UI behavior — only nuke data when
41 // the user has explicitly enabled "Delete all data on uninstall".
42 $keep_data = (bool) get_option('thinkrank_keep_data_on_uninstall', true);
43
44 if (!$keep_data) {
45 self::remove_database_tables();
46 self::remove_options();
47 self::remove_user_meta();
48 self::remove_post_meta();
49 }
50
51 self::clear_caches();
52 self::remove_cron_jobs();
53 self::remove_capabilities();
54 self::mark_uninstalled();
55 }
56
57 /**
58 * Record that the user deliberately uninstalled the plugin.
59 *
60 * ThinkRank Pro auto-installs and activates the free plugin whenever it
61 * finds it missing, which silently re-ran the activator and recreated every
62 * table this uninstall had just dropped. Pro reads this marker and stops
63 * auto-installing, falling back to its "Install ThinkRank" notice, so a
64 * deliberate removal stays removed until the user asks for it back.
65 *
66 * Written last, after remove_options() has wiped the `thinkrank_` namespace,
67 * and cleared again by Activator on the next activation.
68 *
69 * @return void
70 */
71 private static function mark_uninstalled(): void {
72 delete_option(self::UNINSTALLED_OPTION);
73 add_option(self::UNINSTALLED_OPTION, time(), '', 'no');
74 }
75
76 /**
77 * Remove custom database tables
78 *
79 * @return void
80 */
81 private static function remove_database_tables(): void {
82 global $wpdb;
83
84 $tables = [
85 // AI/Core Tables (from Database class)
86 $wpdb->prefix . 'thinkrank_ai_cache',
87 $wpdb->prefix . 'thinkrank_ai_usage',
88 $wpdb->prefix . 'thinkrank_content_briefs',
89 $wpdb->prefix . 'thinkrank_seo_scores',
90 $wpdb->prefix . 'thinkrank_seo_performance',
91 $wpdb->prefix . 'thinkrank_instant_indexing_logs',
92 // SEO Tables (from Database_Schema)
93 $wpdb->prefix . 'thinkrank_seo_settings',
94 $wpdb->prefix . 'thinkrank_seo_analysis',
95 $wpdb->prefix . 'thinkrank_seo_keywords',
96 $wpdb->prefix . 'thinkrank_seo_schema',
97 $wpdb->prefix . 'thinkrank_seo_social',
98 $wpdb->prefix . 'thinkrank_seo_local',
99 $wpdb->prefix . 'thinkrank_email_report_logs',
100 // AI Visibility Tables. These were registered in Database_Schema but
101 // never listed here, so an uninstall left them behind — bv_tasks in
102 // particular holds the full text of every AI answer (#302).
103 $wpdb->prefix . 'thinkrank_ai_traffic',
104 $wpdb->prefix . 'thinkrank_brand_visibility_checks',
105 $wpdb->prefix . 'thinkrank_bv_runs',
106 $wpdb->prefix . 'thinkrank_bv_tasks',
107 // Rank Tracker Tables
108 $wpdb->prefix . 'thinkrank_rank_tracking',
109 $wpdb->prefix . 'thinkrank_tracked_keywords',
110 // Pro feature tables (redirections, broken links, local SEO)
111 $wpdb->prefix . 'thinkrank_redirects',
112 $wpdb->prefix . 'thinkrank_404_logs',
113 $wpdb->prefix . 'thinkrank_broken_links',
114 $wpdb->prefix . 'thinkrank_locations',
115 ];
116
117 foreach ($tables as $table) {
118 // Check WordPress version for %i support (introduced in 6.2)
119 if (version_compare($GLOBALS['wp_version'], '6.2', '>=')) {
120 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
121 $wpdb->query($wpdb->prepare("DROP TABLE IF EXISTS %i", $table));
122 // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
123 } else {
124 // Fallback for older WordPress versions - table name is from our controlled list
125 $escaped_table = esc_sql($table);
126 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
127 $wpdb->query("DROP TABLE IF EXISTS `{$escaped_table}`");
128 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
129 }
130 }
131 }
132
133 /**
134 * Remove plugin options
135 *
136 * @return void
137 */
138 private static function remove_options(): void {
139 // Remove all ThinkRank options using wildcard delete for completeness
140 global $wpdb;
141 // 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
142 $wpdb->query(
143 $wpdb->prepare(
144 "DELETE FROM {$wpdb->options}
145 WHERE option_name LIKE %s
146 OR option_name LIKE %s",
147 $wpdb->esc_like('thinkrank_') . '%',
148 // Appsero / WP Insights telemetry options (wpins_thinkrank_*)
149 $wpdb->esc_like('wpins_thinkrank_') . '%'
150 )
151 );
152 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
153
154 // Remove transients (prefixed with _transient_, not caught by options wildcard)
155 // 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
156 $wpdb->query(
157 $wpdb->prepare(
158 "DELETE FROM {$wpdb->options}
159 WHERE option_name LIKE %s
160 OR option_name LIKE %s",
161 $wpdb->esc_like('_transient_thinkrank_') . '%',
162 $wpdb->esc_like('_transient_timeout_thinkrank_') . '%'
163 )
164 );
165 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
166 }
167
168 /**
169 * Remove user meta data
170 *
171 * @return void
172 */
173 private static function remove_user_meta(): void {
174 global $wpdb;
175
176 // 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
177 $wpdb->query(
178 $wpdb->prepare(
179 "DELETE FROM {$wpdb->usermeta}
180 WHERE meta_key LIKE %s
181 OR meta_key LIKE %s",
182 $wpdb->esc_like('thinkrank_') . '%',
183 $wpdb->esc_like('_thinkrank_') . '%'
184 )
185 );
186 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
187 }
188
189 /**
190 * Remove post meta data
191 *
192 * @return void
193 */
194 private static function remove_post_meta(): void {
195 global $wpdb;
196
197 // 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
198 $wpdb->query(
199 $wpdb->prepare(
200 "DELETE FROM {$wpdb->postmeta}
201 WHERE meta_key LIKE %s
202 OR meta_key LIKE %s",
203 $wpdb->esc_like('thinkrank_') . '%',
204 $wpdb->esc_like('_thinkrank_') . '%'
205 )
206 );
207 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
208 }
209
210
211 /**
212 * Clear all caches
213 *
214 * @return void
215 */
216 private static function clear_caches(): void {
217 // Clear ThinkRank-specific object cache group
218 if (function_exists('wp_cache_flush_group')) {
219 wp_cache_flush_group('thinkrank');
220 }
221 }
222
223 /**
224 * Remove scheduled cron jobs
225 *
226 * @return void
227 */
228 private static function remove_cron_jobs(): void {
229 $cron_jobs = [
230 'thinkrank_cache_cleanup',
231 'thinkrank_usage_analytics',
232 ];
233
234 foreach ($cron_jobs as $job) {
235 wp_clear_scheduled_hook($job);
236 }
237 }
238
239 /**
240 * Remove custom capabilities
241 *
242 * @return void
243 */
244 private static function remove_capabilities(): void {
245 // Runs even when the user keeps their data, so the capability-sync marker
246 // has to go with it. Leaving it behind would make Capability_Manager::ensure()
247 // short-circuit on the next install and never re-grant thinkrank_access,
248 // locking administrators out of the admin menu.
249 delete_option('thinkrank_caps_version');
250
251 // Keep in sync with ThinkRank\Core\Capability_Manager::capabilities().
252 // The plugin autoloader isn't available during uninstall, so the slugs are
253 // mirrored here. The trailing legacy slugs were granted by older versions
254 // and are removed too so historical roles are fully cleaned.
255 $capabilities = [
256 'thinkrank_access',
257 'thinkrank_site_identity',
258 'thinkrank_analytics',
259 'thinkrank_performance',
260 'thinkrank_global_seo',
261 'thinkrank_image_seo',
262 'thinkrank_schema',
263 'thinkrank_social_media',
264 'thinkrank_crawling',
265 'thinkrank_instant_indexing',
266 'thinkrank_author_archives',
267 'thinkrank_content_tools',
268 'thinkrank_internal_links',
269 'thinkrank_settings',
270 'thinkrank_manage_roles',
271 // Legacy slugs (pre-Role Manager) — remove if still present.
272 'thinkrank_manage_settings',
273 'thinkrank_view_analytics',
274 'thinkrank_manage_credits',
275 'thinkrank_use_ai_features',
276 ];
277
278 $roles = wp_roles();
279
280 foreach ($roles->roles as $role_name => $role_info) {
281 $role = get_role($role_name);
282 if ($role) {
283 foreach ($capabilities as $cap) {
284 $role->remove_cap($cap);
285 }
286 }
287 }
288 }
289 }
290
291 // Run the uninstaller
292 ThinkRank_Uninstaller::uninstall();
293