PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.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 1.0.2 All 50 releases
← All changes | thinkrank.php +36 -23 2.4.02.9.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.4.0
7 + * Version: 2.9.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.4.0
18 + * @version 2.9.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.4.0');
30 +define('THINKRANK_VERSION', '2.9.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
@@ -290,29 +290,24 @@
290 290 }
291 291 }
292 292
293 293 /**
294 - * Register the Brand Visibility run-drain listener.
294 + * Unschedule the cron events left behind by the removed Brand Visibility
295 + * feature.
295 296 *
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.
297 + * A site that ran a Brand Visibility check can still carry its drain tick
298 + * and watchdog. Their callbacks, and the watchdog's custom recurrence, no
299 + * longer exist, so the events would only fire into nothing.
300 + * wp_unschedule_hook() walks the autoloaded cron array and writes nothing
301 + * when the hook is absent, so this costs nothing once they are gone.
302 + * Deactivation and uninstall clear the same hooks (cleanup-manifest.php).
300 303 *
301 304 * @return void
302 305 */
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 - });
306 + private function clear_retired_brand_visibility_cron(): void {
307 + foreach (['thinkrank_bv_tick', 'thinkrank_bv_watchdog'] as $hook) {
308 + wp_unschedule_hook($hook);
309 + }
315 310 }
316 311
317 312 /**
318 313 * Check if database schema needs updating and run migrations
@@ -338,9 +333,17 @@
338 333 */
339 334 private function load_components(): void {
340 335 $this->components = [
341 336 'database' => new ThinkRank\Core\Database(),
342 - 'settings' => new ThinkRank\Core\Settings(),
337 + // Settings::instance() rather than a fresh object: the container
338 + // and the singleton were otherwise two different Settings, each
339 + // with its own memo, because instance() resolves the container and
340 + // this array is assigned only after every constructor above has
341 + // run. Anything resolving the singleton during that window got a
342 + // standalone instance that outlived the request, so a hook
343 + // registered from the component's init() ran against an object no
344 + // consumer read through (#516).
345 + 'settings' => ThinkRank\Core\Settings::instance(),
343 346 'role_manager' => new ThinkRank\Core\Role_Manager(),
344 347 'security_headers' => new ThinkRank\Core\Security_Headers(),
345 348 'asset_optimizer' => new ThinkRank\Core\Asset_Optimizer(),
346 349 'usage_tracker' => new ThinkRank\Core\Usage_Tracker_Manager(),
@@ -348,23 +351,33 @@
348 351 'admin' => new ThinkRank\Admin\Manager(),
349 352 'blocks' => new ThinkRank\Editor\Blocks_Manager(),
350 353 'elementor' => new ThinkRank\Editor\Elementor_Manager(),
351 354 'bricks_elements' => new ThinkRank\Editor\Bricks_Elements_Manager(),
355 + 'beaver_modules' => new ThinkRank\Editor\Beaver_Modules_Manager(),
352 356 'ai' => new ThinkRank\AI\Manager(),
353 357 'frontend_seo' => new ThinkRank\Frontend\SEO_Manager(),
354 358 'seo_notice' => new ThinkRank\Admin\SEO_Notice(),
355 359 'search_visibility_notice' => new ThinkRank\Admin\Search_Visibility_Notice(),
360 + 'webroot_writable_notice' => new ThinkRank\Admin\Webroot_Writable_Notice(),
356 361 'performance_collector' => new ThinkRank\SEO\Performance_Data_Collector(),
362 + 'query_guard' => new ThinkRank\SEO\Query_Guard(),
363 + 'feeds' => new ThinkRank\SEO\Feed_Manager(),
364 + 'sitemap_stylesheet' => new ThinkRank\SEO\Sitemap_Stylesheet(),
365 + 'oembed' => new ThinkRank\SEO\Oembed_Manager(),
366 + 'content_visibility' => new ThinkRank\SEO\Content_Visibility(),
357 367 'instant_indexing' => new ThinkRank\SEO\Instant_Indexing_Manager(),
358 368 'instant_indexing_reconciler' => new ThinkRank\SEO\Instant_Indexing_Reconciler(),
359 369 'author_archives' => new ThinkRank\SEO\Author_Archives_Manager(),
360 370 'seo_analyzer' => new ThinkRank\SEO\SEO_Analyzer(),
371 + // Bulk Snippets' persisted issue index: invalidation hooks must run
372 + // on every request, since posts are edited everywhere but there.
373 + 'snippet_index' => new ThinkRank\SEO\Snippet_Index(),
361 374 'email_report' => new ThinkRank\SEO\Email_Report_Manager(),
362 375 'google_oauth' => new ThinkRank\Integrations\Google_OAuth_Proxy(),
363 376 'multilingual' => new ThinkRank\Integrations\Multilingual_Manager(),
364 377 'ai_traffic' => new ThinkRank\SEO\Ai_Traffic_Tracker(),
365 - 'auto_ai' => new ThinkRank\SEO\Auto_Ai_Optimizer(),
366 378 'analytics' => new ThinkRank\SEO\Analytics_Manager(),
379 + 'schema_conflict_health_check' => new ThinkRank\Diagnostics\Schema_Conflict_Health_Check(),
367 380 'abilities' => new ThinkRank\Abilities\Abilities_Registrar(),
368 381 'mcp' => new ThinkRank\Mcp\Mcp_Manager(),
369 382 ];
370 383 }