PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.30.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.30.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 / includes / core / class-activator.php

class-activator.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.30.0, at includes/core/class-activator.php

245 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin Activator Class
5 *
6 * Handles plugin activation tasks
7 *
8 * @package ThinkRank\Core
9 * @since 1.0.0
10 */
11
12 declare(strict_types=1);
13
14 namespace ThinkRank\Core;
15
16 use ThinkRank\Database\Database_Schema;
17
18 // Prevent direct access
19 if (!defined('ABSPATH')) {
20 exit;
21 }
22
23 /**
24 * Activator Class
25 *
26 * Single Responsibility: Handle plugin activation tasks only
27 *
28 * @since 1.0.0
29 */
30 class Activator {
31
32 /**
33 * Option the uninstaller sets to record a deliberate removal.
34 *
35 * Pro's Free_Plugin_Installer skips its silent auto-install while this is
36 * set, so activating again — the user asking for the plugin back — has to
37 * clear it. Keep in sync with uninstall.php.
38 */
39 public const UNINSTALLED_OPTION = 'thinkrank_uninstalled';
40
41 /**
42 * Plugin activation tasks
43 *
44 * @return void
45 * @throws \Exception If activation fails
46 */
47 public function activate(): void {
48 delete_option(self::UNINSTALLED_OPTION);
49
50 $this->check_requirements();
51 $this->create_database_tables();
52 $this->set_default_options();
53 $this->setup_indexnow_key();
54 $this->schedule_cron_jobs();
55 $this->set_activation_flag();
56
57 // Grant the admin capabilities here rather than waiting for the `init`
58 // hook Role_Manager registers, so the menu is reachable on the very
59 // first admin request after activation.
60 Capability_Manager::ensure();
61 }
62
63 /**
64 * Setup IndexNow API Key
65 *
66 * Generates a unique 128-bit key and creates the key file in the root directory.
67 *
68 * @return void
69 */
70 private function setup_indexnow_key(): void {
71 $option_name = 'thinkrank_instant_indexing_settings';
72 $settings = get_option($option_name, []);
73
74 // Check if key exists
75 if (empty($settings['api_key'])) {
76 try {
77 // Generate 128-bit key (32 hex characters)
78 // Using bin2hex(random_bytes(16)) as requested
79 $key = bin2hex(random_bytes(16));
80
81 // Save to options
82 $settings['api_key'] = $key;
83
84 // Initialize default post types if not set
85 if (!isset($settings['auto_submit_post_types'])) {
86 $settings['auto_submit_post_types'] = ['post', 'page'];
87 }
88
89 update_option($option_name, $settings);
90
91 // Create the key file in WordPress root using WP_Filesystem
92 $file_path = ABSPATH . $key . '.txt';
93 global $wp_filesystem;
94 if (!function_exists('WP_Filesystem')) {
95 require_once ABSPATH . 'wp-admin/includes/file.php';
96 }
97 WP_Filesystem();
98 if ($wp_filesystem && $wp_filesystem->is_writable(ABSPATH)) {
99 $wp_filesystem->put_contents($file_path, $key, FS_CHMOD_FILE);
100 }
101 } catch (\Exception $e) {
102 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
103 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
104 error_log('ThinkRank: Failed to create IndexNow key file: ' . $e->getMessage());
105 }
106 }
107 }
108 }
109
110 /**
111 * Check system requirements
112 *
113 * @return void
114 * @throws \Exception If requirements not met
115 */
116 private function check_requirements(): void {
117 // PHP version check
118 if (version_compare(PHP_VERSION, '8.0', '<')) {
119 throw new \Exception('ThinkRank requires PHP 8.0 or higher');
120 }
121
122 // WordPress version check
123 if (version_compare(get_bloginfo('version'), '6.0', '<')) {
124 throw new \Exception('ThinkRank requires WordPress 6.0 or higher');
125 }
126
127 // Required PHP extensions
128 $required_extensions = ['curl', 'json', 'mbstring'];
129 foreach ($required_extensions as $extension) {
130 if (!extension_loaded($extension)) {
131 throw new \Exception(sprintf("Required PHP extension '%s' is not loaded", esc_html($extension)));
132 }
133 }
134
135 // Check if we can write to WordPress root directory (for robots.txt, llms.txt, sitemaps)
136 if (!wp_is_writable(ABSPATH)) {
137 // Log warning but don't block activation — some hosts restrict ABSPATH writes
138 // and file-writing features will gracefully degrade via WP_Filesystem checks
139 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
140 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
141 error_log('ThinkRank: WordPress root directory is not writable. Some features (robots.txt, llms.txt, sitemaps) may not work.');
142 }
143 }
144 }
145
146 /**
147 * Create database tables
148 *
149 * Uses the consolidated Database_Schema class to create all 11 ThinkRank tables:
150 * - SEO Tables (7): Settings, Analysis, Keywords, Schema, Social, Performance, Local
151 * - AI/Core Tables (4): AI Cache, AI Usage, Content Briefs, SEO Scores
152 *
153 * @return void
154 * @throws \Exception If table creation fails
155 */
156 private function create_database_tables(): void {
157 try {
158 // Use the Database_Schema class to create all 11 ThinkRank tables
159 $schema = new Database_Schema();
160 $results = $schema->create_tables();
161
162 if (!$results['success']) {
163 $error_message = 'Failed to create database tables: ' . implode(', ', $results['errors']);
164 throw new \Exception($error_message);
165 }
166
167 // Add performance indexes for Phase 2 optimization
168 // Best effort: activation continues if the performance indexes
169 // cannot be created.
170 $schema->add_performance_indexes();
171
172 // Database tables created successfully
173
174 } catch (\Exception $e) {
175 throw new \Exception('Database table creation failed: ' . esc_html($e->getMessage()));
176 }
177 }
178
179
180
181 /**
182 * Set default plugin options
183 *
184 * @return void
185 */
186 private function set_default_options(): void {
187 $default_options = [
188 'thinkrank_version' => THINKRANK_VERSION,
189
190 'thinkrank_ai_provider' => 'openai',
191 'thinkrank_cache_duration' => 3600, // 1 hour
192 'thinkrank_max_requests_per_minute' => 10,
193 'thinkrank_enable_logging' => true,
194 'thinkrank_auto_optimize' => false,
195 'thinkrank_seo_score_threshold' => 70,
196 ];
197
198 foreach ($default_options as $option_name => $option_value) {
199 if (get_option($option_name) === false) {
200 add_option($option_name, $option_value);
201 }
202 }
203 }
204
205
206 /**
207 * Schedule cron jobs
208 *
209 * @return void
210 */
211 private function schedule_cron_jobs(): void {
212
213 // Schedule cache cleanup
214 if (!wp_next_scheduled('thinkrank_cache_cleanup')) {
215 wp_schedule_event(time(), 'daily', 'thinkrank_cache_cleanup');
216 }
217
218 // Schedule usage analytics
219 if (!wp_next_scheduled('thinkrank_usage_analytics')) {
220 wp_schedule_event(time(), 'weekly', 'thinkrank_usage_analytics');
221 }
222 }
223
224 /**
225 * Set activation flag for first-time setup
226 *
227 * @return void
228 */
229 private function set_activation_flag(): void {
230 update_option('thinkrank_activated', true);
231 update_option('thinkrank_activation_time', time());
232
233 // Set flag for showing welcome screen
234 update_option('thinkrank_show_welcome', true);
235
236 // Trigger a one-time redirect to the Setup Wizard on the next admin load,
237 // but only when the wizard has not already been completed. A short-lived
238 // transient is used so it auto-expires and never fires for bulk/network
239 // activations that skip the redirect window.
240 if (!get_option('thinkrank_setup_wizard_completed')) {
241 set_transient('thinkrank_setup_wizard_redirect', 1, 60);
242 }
243 }
244 }
245