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

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