PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.27.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.27.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 / thinkrank.php

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

429 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin Name: ThinkRank
5 * Plugin URI: https://thinkrank.ai/
6 * Description: AI-native SEO plugin for WordPress. Automate and enhance your SEO with cutting-edge AI while maintaining editorial control.
7 * Version: 1.27.0
8 * Author: WPDeveloper
9 * Author URI: https://wpdeveloper.com/
10 * License: GPL v2 or later
11 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12 * Text Domain: thinkrank
13 * Domain Path: /languages
14 * Requires at least: 6.0
15 * Requires PHP: 8.0
16 *
17 * @package ThinkRank
18 * @version 1.27.0
19 * @since 1.0.0
20 */
21
22 declare(strict_types=1);
23
24 // Prevent direct access
25 if (!defined('ABSPATH')) {
26 exit;
27 }
28
29 // Define plugin constants
30 define('THINKRANK_VERSION', '1.27.0');
31 define('THINKRANK_PLUGIN_FILE', __FILE__);
32 define('THINKRANK_PLUGIN_DIR', plugin_dir_path(__FILE__));
33 define('THINKRANK_PLUGIN_URL', plugin_dir_url(__FILE__));
34 define('THINKRANK_PLUGIN_BASENAME', plugin_basename(__FILE__));
35
36 // Minimum requirements check
37 if (version_compare(PHP_VERSION, '8.0', '<')) {
38 add_action('admin_notices', function () {
39 echo '<div class="notice notice-error"><p>';
40 echo esc_html__('ThinkRank requires PHP 8.0 or higher. Please upgrade your PHP version.', 'thinkrank');
41 echo '</p></div>';
42 });
43 return;
44 }
45
46 if (version_compare(get_bloginfo('version'), '6.0', '<')) {
47 add_action('admin_notices', function () {
48 echo '<div class="notice notice-error"><p>';
49 echo esc_html__('ThinkRank requires WordPress 6.0 or higher. Please upgrade your WordPress installation.', 'thinkrank');
50 echo '</p></div>';
51 });
52 return;
53 }
54
55 // Autoloader
56 require_once THINKRANK_PLUGIN_DIR . 'includes/class-autoloader.php';
57
58 /**
59 * Bundled AI Building Blocks (Abilities API + MCP Adapter).
60 *
61 * Loaded through the Jetpack Autoloader so that if the same libraries are also
62 * shipped by another plugin — or land in WordPress core — the newest copy wins
63 * and loads once, with no fatal class collisions. This lets ThinkRank serve its
64 * MCP endpoint out of the box, without requiring the standalone MCP Adapter and
65 * Abilities API plugins. See docs/mcp-server.md for the update procedure.
66 */
67 $thinkrank_mcp_runtime = THINKRANK_PLUGIN_DIR . 'dependencies/vendor/autoload_packages.php';
68 if (is_readable($thinkrank_mcp_runtime)) {
69 require_once $thinkrank_mcp_runtime;
70 }
71 unset($thinkrank_mcp_runtime);
72
73 /**
74 * Main ThinkRank Plugin Class
75 *
76 * Follows Single Responsibility Principle - only handles plugin initialization
77 *
78 * @since 1.0.0
79 */
80 final class ThinkRank {
81
82 /**
83 * Plugin instance (Singleton Pattern)
84 *
85 * @var ThinkRank|null
86 */
87 private static ?ThinkRank $instance = null;
88
89 /**
90 * Plugin components
91 *
92 * @var array
93 */
94 private array $components = [];
95
96 /**
97 * Get plugin instance (Singleton Pattern)
98 *
99 * @return ThinkRank
100 */
101 public static function get_instance(): ThinkRank {
102 if (null === self::$instance) {
103 self::$instance = new self();
104 }
105 return self::$instance;
106 }
107
108 /**
109 * Private constructor to prevent direct instantiation
110 */
111 private function __construct() {
112 $this->init_autoloader();
113 $this->init_hooks();
114 }
115
116 /**
117 * Prevent cloning
118 */
119 private function __clone() {
120 }
121
122 /**
123 * Prevent unserialization
124 */
125 public function __wakeup() {
126 throw new \Exception('Cannot unserialize singleton');
127 }
128
129 /**
130 * Initialize autoloader
131 *
132 * @return void
133 */
134 private function init_autoloader(): void {
135 ThinkRank\Core\Autoloader::register();
136 }
137
138 /**
139 * Initialize WordPress hooks
140 *
141 * @return void
142 */
143 private function init_hooks(): void {
144 register_activation_hook(__FILE__, [$this, 'activate']);
145 register_deactivation_hook(__FILE__, [$this, 'deactivate']);
146
147 add_action('plugins_loaded', [$this, 'init']);
148 add_filter('plugin_action_links_' . THINKRANK_PLUGIN_BASENAME, [$this, 'add_action_links']);
149 }
150
151 /**
152 * Add a "Dashboard" link to the plugin action links on the Plugins page.
153 *
154 * @param array $links Existing plugin action links.
155 * @return array
156 */
157 public function add_action_links(array $links): array {
158 $dashboard_link = sprintf(
159 '<a href="%s">%s</a>',
160 esc_url(admin_url('admin.php?page=thinkrank')),
161 esc_html__('Dashboard', 'thinkrank')
162 );
163
164 array_unshift($links, $dashboard_link);
165
166 return $links;
167 }
168
169 /**
170 * Initialize plugin components
171 *
172 * @return void
173 */
174 public function init(): void {
175 try {
176 $this->maybe_update_database();
177 $this->register_sitemap_cron_listeners();
178 $this->load_components();
179 $this->init_components();
180 $this->load_template_functions();
181
182 do_action('thinkrank_loaded');
183 } catch (\Exception $e) {
184 $this->handle_error($e);
185 }
186 }
187
188 /**
189 * Register the sitemap regeneration WP-Cron listeners.
190 *
191 * Runs on plugins_loaded (via init()), so the callbacks exist on every
192 * request — including WP-Cron, which never fires rest_api_init and therefore
193 * never builds the Sitemap REST endpoint (whose constructor would otherwise
194 * be the only place the scheduled regeneration hooks get a listener). The
195 * generator is built lazily inside the callback so this stays cheap on the
196 * vast majority of requests where no regeneration is due.
197 *
198 * @return void
199 */
200 private function register_sitemap_cron_listeners(): void {
201 add_action('thinkrank_regenerate_sitemap', static function () {
202 (new ThinkRank\SEO\Sitemap_Generator())->auto_regenerate_sitemap();
203 });
204 add_action('thinkrank_regenerate_sitemap_settings', static function () {
205 (new ThinkRank\SEO\Sitemap_Generator())->regenerate_sitemap_from_settings();
206 });
207 }
208
209 /**
210 * Check if database schema needs updating and run migrations
211 *
212 * Standard WordPress pattern: compare stored db_version against current,
213 * run dbDelta if stale. This handles schema changes (new tables, new columns)
214 * without requiring plugin deactivation/reactivation.
215 *
216 * @since 1.10.0
217 * @return void
218 */
219 private function maybe_update_database(): void {
220 $schema = new ThinkRank\Database\Database_Schema();
221 if ($schema->needs_update()) {
222 $schema->create_tables();
223 }
224 }
225
226 /**
227 * Load plugin components (Dependency Injection Container pattern)
228 *
229 * @return void
230 */
231 private function load_components(): void {
232 $this->components = [
233 'database' => new ThinkRank\Core\Database(),
234 'settings' => new ThinkRank\Core\Settings(),
235 'role_manager' => new ThinkRank\Core\Role_Manager(),
236 'security_headers' => new ThinkRank\Core\Security_Headers(),
237 'asset_optimizer' => new ThinkRank\Core\Asset_Optimizer(),
238 'usage_tracker' => new ThinkRank\Core\Usage_Tracker_Manager(),
239 'api' => new ThinkRank\API\Manager(),
240 'admin' => new ThinkRank\Admin\Manager(),
241 'blocks' => new ThinkRank\Editor\Blocks_Manager(),
242 'elementor' => new ThinkRank\Editor\Elementor_Manager(),
243 'ai' => new ThinkRank\AI\Manager(),
244 'frontend_seo' => new ThinkRank\Frontend\SEO_Manager(),
245 'seo_notice' => new ThinkRank\Admin\SEO_Notice(),
246 'performance_collector' => new ThinkRank\SEO\Performance_Data_Collector(),
247 'instant_indexing' => new ThinkRank\SEO\Instant_Indexing_Manager(),
248 'author_archives' => new ThinkRank\SEO\Author_Archives_Manager(),
249 'email_report' => new ThinkRank\SEO\Email_Report_Manager(),
250 'google_oauth' => new ThinkRank\Integrations\Google_OAuth_Proxy(),
251 'multilingual' => new ThinkRank\Integrations\Multilingual_Manager(),
252 'analytics' => new ThinkRank\SEO\Analytics_Manager(),
253 'abilities' => new ThinkRank\Abilities\Abilities_Registrar(),
254 'mcp' => new ThinkRank\Mcp\Mcp_Manager(),
255 ];
256 }
257
258 /**
259 * Initialize all components
260 *
261 * @return void
262 */
263 private function init_components(): void {
264 foreach ($this->components as $component) {
265 if (method_exists($component, 'init')) {
266 $component->init();
267 }
268 }
269 }
270
271 /**
272 * Load template functions for themes
273 *
274 * @return void
275 */
276 private function load_template_functions(): void {
277 require_once THINKRANK_PLUGIN_DIR . 'includes/frontend/template-functions.php';
278 }
279
280 /**
281 * Get component instance
282 *
283 * @param string $component Component name
284 * @return object|null
285 */
286 public function get_component(string $component): ?object {
287 return $this->components[$component] ?? null;
288 }
289
290 /**
291 * Shared Analytics Manager instance (lazy)
292 *
293 * @var ThinkRank\SEO\Analytics_Manager|null
294 */
295 private ?ThinkRank\SEO\Analytics_Manager $analytics_manager = null;
296
297 /**
298 * Get the shared Analytics Manager, with Google clients initialized.
299 *
300 * Consumers (including the Pro plugin, which probes for this accessor)
301 * should use this instead of constructing their own Analytics_Manager:
302 * each fresh construction re-reads/decrypts settings and re-initializes
303 * the Google API clients.
304 *
305 * @since 1.18.0
306 * @return ThinkRank\SEO\Analytics_Manager
307 */
308 public function get_analytics_manager(): ThinkRank\SEO\Analytics_Manager {
309 if ($this->analytics_manager === null) {
310 // Reuse the registered component. Constructing a second instance
311 // here would work, but only the registered one has had init() run
312 // on it, so the token-refresh cron would be scheduled against a
313 // different object than the one callers actually use.
314 $component = $this->components['analytics'] ?? null;
315
316 $this->analytics_manager = $component instanceof ThinkRank\SEO\Analytics_Manager
317 ? $component
318 : new ThinkRank\SEO\Analytics_Manager();
319
320 // init() defers initialize_clients() to the `init` hook; callers
321 // that arrive earlier still need working clients.
322 $this->analytics_manager->initialize_clients();
323 }
324 return $this->analytics_manager;
325 }
326
327 /**
328 * Plugin activation
329 *
330 * @return void
331 */
332 public function activate(): void {
333 try {
334 $activator = new ThinkRank\Core\Activator();
335 $activator->activate();
336
337 // Flush rewrite rules
338 flush_rewrite_rules();
339 } catch (\Exception $e) {
340 $this->handle_error($e);
341 wp_die(
342 esc_html__('ThinkRank activation failed. Please check your server logs.', 'thinkrank'),
343 esc_html__('Plugin Activation Error', 'thinkrank'),
344 ['back_link' => true]
345 );
346 }
347 }
348
349 /**
350 * Plugin deactivation
351 *
352 * @return void
353 */
354 public function deactivate(): void {
355 try {
356 $deactivator = new ThinkRank\Core\Deactivator();
357 $deactivator->deactivate();
358
359 // Flush rewrite rules
360 flush_rewrite_rules();
361 } catch (\Exception $e) {
362 $this->handle_error($e);
363 }
364 }
365
366 /**
367 * Handle errors consistently
368 *
369 * Logs errors and surfaces them via _doing_it_wrong() when
370 * WP_DEBUG is enabled, helping developers diagnose issues.
371 *
372 * @param \Exception $e Exception to handle
373 * @return void
374 */
375 private function handle_error(\Exception $e): void {
376 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
377 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
378 error_log('ThinkRank: ' . $e->getMessage());
379 }
380
381 if (defined('WP_DEBUG') && WP_DEBUG) {
382 _doing_it_wrong(
383 __METHOD__,
384 esc_html($e->getMessage()),
385 esc_html(THINKRANK_VERSION)
386 );
387 }
388 }
389
390 /**
391 * Get plugin version
392 *
393 * @return string
394 */
395 public function get_version(): string {
396 return THINKRANK_VERSION;
397 }
398
399 /**
400 * Get plugin directory path
401 *
402 * @return string
403 */
404 public function get_plugin_dir(): string {
405 return THINKRANK_PLUGIN_DIR;
406 }
407
408 /**
409 * Get plugin URL
410 *
411 * @return string
412 */
413 public function get_plugin_url(): string {
414 return THINKRANK_PLUGIN_URL;
415 }
416 }
417
418 /**
419 * Initialize the plugin
420 *
421 * @return ThinkRank
422 */
423 function thinkrank(): ThinkRank {
424 return ThinkRank::get_instance();
425 }
426
427 // Start the plugin
428 thinkrank();
429