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 / 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.28.0, at includes/core/class-activator.php

235 lines 7.6 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 $index_results = $schema->add_performance_indexes();
158 if (!$index_results) {
159 // Performance indexes could not be created - activation continues
160 }
161
162 // Database tables created successfully
163
164 } catch (\Exception $e) {
165 throw new \Exception('Database table creation failed: ' . esc_html($e->getMessage()));
166 }
167 }
168
169
170
171 /**
172 * Set default plugin options
173 *
174 * @return void
175 */
176 private function set_default_options(): void {
177 $default_options = [
178 'thinkrank_version' => THINKRANK_VERSION,
179
180 'thinkrank_ai_provider' => 'openai',
181 'thinkrank_cache_duration' => 3600, // 1 hour
182 'thinkrank_max_requests_per_minute' => 10,
183 'thinkrank_enable_logging' => true,
184 'thinkrank_auto_optimize' => false,
185 'thinkrank_seo_score_threshold' => 70,
186 ];
187
188 foreach ($default_options as $option_name => $option_value) {
189 if (get_option($option_name) === false) {
190 add_option($option_name, $option_value);
191 }
192 }
193 }
194
195
196 /**
197 * Schedule cron jobs
198 *
199 * @return void
200 */
201 private function schedule_cron_jobs(): void {
202
203 // Schedule cache cleanup
204 if (!wp_next_scheduled('thinkrank_cache_cleanup')) {
205 wp_schedule_event(time(), 'daily', 'thinkrank_cache_cleanup');
206 }
207
208 // Schedule usage analytics
209 if (!wp_next_scheduled('thinkrank_usage_analytics')) {
210 wp_schedule_event(time(), 'weekly', 'thinkrank_usage_analytics');
211 }
212 }
213
214 /**
215 * Set activation flag for first-time setup
216 *
217 * @return void
218 */
219 private function set_activation_flag(): void {
220 update_option('thinkrank_activated', true);
221 update_option('thinkrank_activation_time', time());
222
223 // Set flag for showing welcome screen
224 update_option('thinkrank_show_welcome', true);
225
226 // Trigger a one-time redirect to the Setup Wizard on the next admin load,
227 // but only when the wizard has not already been completed. A short-lived
228 // transient is used so it auto-expires and never fires for bulk/network
229 // activations that skip the redirect window.
230 if (!get_option('thinkrank_setup_wizard_completed')) {
231 set_transient('thinkrank_setup_wizard_redirect', 1, 60);
232 }
233 }
234 }
235