PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.10.0 2.9.0 2.8.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 All 51 releases
← All changes | thinkrank.php +52 -23 2.5.0 → 2.10.0 View file →
@@ -3,9 +3,9 @@
3 3 /**
4 4 * Plugin Name: ThinkRank
5 5 * Plugin URI: https://thinkrank.ai/
6 6 * Description: AI-native SEO plugin for WordPress. Automate and enhance your SEO with cutting-edge AI while maintaining editorial control.
7 - * Version: 2.5.0
7 + * Version: 2.10.0
8 8 * Author: WPDeveloper
9 9 * Author URI: https://wpdeveloper.com/
10 10 * License: GPL v2 or later
11 11 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -14,9 +14,9 @@
14 14 * Requires at least: 6.0
15 15 * Requires PHP: 7.4
16 16 *
17 17 * @package ThinkRank
18 - * @version 2.5.0
18 + * @version 2.10.0
19 19 * @since 1.0.0
20 20 */
21 21
22 22 declare(strict_types=1);
@@ -26,9 +26,9 @@
26 26 exit;
27 27 }
28 28
29 29 // Define plugin constants
30 -define('THINKRANK_VERSION', '2.5.0');
30 +define('THINKRANK_VERSION', '2.10.0');
31 31 define('THINKRANK_PLUGIN_FILE', __FILE__);
32 32 define('THINKRANK_PLUGIN_DIR', plugin_dir_path(__FILE__));
33 33 define('THINKRANK_PLUGIN_URL', plugin_dir_url(__FILE__));
34 34 define('THINKRANK_PLUGIN_BASENAME', plugin_basename(__FILE__));
@@ -176,9 +176,9 @@
176 176 public function init(): void {
177 177 try {
178 178 $this->maybe_update_database();
179 179 $this->register_sitemap_cron_listeners();
180 - $this->register_brand_visibility_cron();
180 + $this->clear_retired_brand_visibility_cron();
181 181 $this->load_components();
182 182 $this->init_components();
183 183 $this->load_template_functions();
184 184
@@ -234,8 +234,20 @@
234 234 // core routes, both of which anonymous front-end traffic reaches — so
235 235 // without the logged-in test a visitor could still pay for the rebuild
236 236 // this method documents as never being theirs to pay for. WP-CLI has no
237 237 // user, and is trusted by definition.
238 + //
239 + // An MCP call counts too. It is an admin-equivalent action, but it is
240 + // dispatched from `parse_request` on the pretty /thinkrank/mcp route,
241 + // so it is neither is_admin() nor REST_REQUEST and never qualified —
242 + // which meant an agent could change sitemap settings on a site with no
243 + // working cron and leave the served file stale indefinitely, having
244 + // already reported success (#764). The logged-in test still applies:
245 + // Mcp_Server sets the current user only after a credential validates,
246 + // so an anonymous POST to that route is not eligible.
247 + $is_mcp_request = class_exists('ThinkRank\Mcp\Mcp_Manager')
248 + && ThinkRank\Mcp\Mcp_Manager::is_serving_request();
249 +
238 250 $eligible = (defined('WP_CLI') && WP_CLI)
239 251 || (
240 252 is_user_logged_in()
241 253 && (
@@ -240,8 +252,9 @@
240 252 is_user_logged_in()
241 253 && (
242 254 is_admin()
243 255 || (defined('REST_REQUEST') && REST_REQUEST)
256 + || $is_mcp_request
244 257 )
245 258 );
246 259
247 260 if (!$eligible) {
@@ -290,29 +303,24 @@
290 303 }
291 304 }
292 305
293 306 /**
294 - * Register the Brand Visibility run-drain listener.
307 + * Unschedule the cron events left behind by the removed Brand Visibility
308 + * feature.
295 309 *
296 - * Runs on plugins_loaded (via init()) rather than from the REST endpoint,
297 - * because the ticks that drain a run are WP-Cron requests — they never
298 - * reach rest_api_init, so registering the listener there would mean a run
299 - * starts and then never progresses.
310 + * A site that ran a Brand Visibility check can still carry its drain tick
311 + * and watchdog. Their callbacks, and the watchdog's custom recurrence, no
312 + * longer exist, so the events would only fire into nothing.
313 + * wp_unschedule_hook() walks the autoloaded cron array and writes nothing
314 + * when the hook is absent, so this costs nothing once they are gone.
315 + * Deactivation and uninstall clear the same hooks (cleanup-manifest.php).
300 316 *
301 317 * @return void
302 318 */
303 - private function register_brand_visibility_cron(): void {
304 - add_action(ThinkRank\AI\Brand_Visibility_Runner::TICK_HOOK, static function () {
305 - (new ThinkRank\AI\Brand_Visibility_Runner())->tick();
306 - });
307 -
308 - // Safety net: a tick killed by a fatal or a worker timeout never
309 - // reaches its own reschedule, which would strand the run. The
310 - // watchdog re-arms the drain and unschedules itself when idle.
311 - add_filter('cron_schedules', [ThinkRank\AI\Brand_Visibility_Runner::class, 'add_cron_interval']); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
312 - add_action(ThinkRank\AI\Brand_Visibility_Runner::WATCHDOG_HOOK, static function () {
313 - (new ThinkRank\AI\Brand_Visibility_Runner())->watchdog();
314 - });
319 + private function clear_retired_brand_visibility_cron(): void {
320 + foreach (['thinkrank_bv_tick', 'thinkrank_bv_watchdog'] as $hook) {
321 + wp_unschedule_hook($hook);
322 + }
315 323 }
316 324
317 325 /**
318 326 * Check if database schema needs updating and run migrations
@@ -338,9 +346,17 @@
338 346 */
339 347 private function load_components(): void {
340 348 $this->components = [
341 349 'database' => new ThinkRank\Core\Database(),
342 - 'settings' => new ThinkRank\Core\Settings(),
350 + // Settings::instance() rather than a fresh object: the container
351 + // and the singleton were otherwise two different Settings, each
352 + // with its own memo, because instance() resolves the container and
353 + // this array is assigned only after every constructor above has
354 + // run. Anything resolving the singleton during that window got a
355 + // standalone instance that outlived the request, so a hook
356 + // registered from the component's init() ran against an object no
357 + // consumer read through (#516).
358 + 'settings' => ThinkRank\Core\Settings::instance(),
343 359 'role_manager' => new ThinkRank\Core\Role_Manager(),
344 360 'security_headers' => new ThinkRank\Core\Security_Headers(),
345 361 'asset_optimizer' => new ThinkRank\Core\Asset_Optimizer(),
346 362 'usage_tracker' => new ThinkRank\Core\Usage_Tracker_Manager(),
@@ -353,19 +369,32 @@
353 369 'ai' => new ThinkRank\AI\Manager(),
354 370 'frontend_seo' => new ThinkRank\Frontend\SEO_Manager(),
355 371 'seo_notice' => new ThinkRank\Admin\SEO_Notice(),
356 372 'search_visibility_notice' => new ThinkRank\Admin\Search_Visibility_Notice(),
373 + 'webroot_writable_notice' => new ThinkRank\Admin\Webroot_Writable_Notice(),
357 374 'performance_collector' => new ThinkRank\SEO\Performance_Data_Collector(),
375 + 'query_guard' => new ThinkRank\SEO\Query_Guard(),
376 + 'feeds' => new ThinkRank\SEO\Feed_Manager(),
377 + 'sitemap_stylesheet' => new ThinkRank\SEO\Sitemap_Stylesheet(),
378 + 'oembed' => new ThinkRank\SEO\Oembed_Manager(),
379 + 'content_visibility' => new ThinkRank\SEO\Content_Visibility(),
358 380 'instant_indexing' => new ThinkRank\SEO\Instant_Indexing_Manager(),
359 381 'instant_indexing_reconciler' => new ThinkRank\SEO\Instant_Indexing_Reconciler(),
360 382 'author_archives' => new ThinkRank\SEO\Author_Archives_Manager(),
361 383 'seo_analyzer' => new ThinkRank\SEO\SEO_Analyzer(),
384 + // Bulk Snippets' persisted issue index: invalidation hooks must run
385 + // on every request, since posts are edited everywhere but there.
386 + 'snippet_index' => new ThinkRank\SEO\Snippet_Index(),
387 + // The thin content report's word counts: same reason, and a
388 + // separate index because a word count and a snippet verdict go
389 + // stale for different reasons (#565).
390 + 'word_count_index' => new ThinkRank\SEO\Word_Count_Index(),
362 391 'email_report' => new ThinkRank\SEO\Email_Report_Manager(),
363 392 'google_oauth' => new ThinkRank\Integrations\Google_OAuth_Proxy(),
364 393 'multilingual' => new ThinkRank\Integrations\Multilingual_Manager(),
365 394 'ai_traffic' => new ThinkRank\SEO\Ai_Traffic_Tracker(),
366 - 'auto_ai' => new ThinkRank\SEO\Auto_Ai_Optimizer(),
367 395 'analytics' => new ThinkRank\SEO\Analytics_Manager(),
396 + 'schema_conflict_health_check' => new ThinkRank\Diagnostics\Schema_Conflict_Health_Check(),
368 397 'abilities' => new ThinkRank\Abilities\Abilities_Registrar(),
369 398 'mcp' => new ThinkRank\Mcp\Mcp_Manager(),
370 399 ];
371 400 }