PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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
← All changes | thinkrank.php +104 -21 2.0.02.7.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.0.0
7 + * Version: 2.7.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.0.0
18 + * @version 2.7.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.0.0');
30 +define('THINKRANK_VERSION', '2.7.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
@@ -206,35 +206,111 @@
206 206 });
207 207 add_action('thinkrank_regenerate_sitemap_settings', static function () {
208 208 (new ThinkRank\SEO\Sitemap_Generator())->regenerate_sitemap_from_settings();
209 209 });
210 +
211 + add_action('shutdown', [$this, 'maybe_take_over_sitemap_regeneration'], 100);
210 212 }
211 213
212 214 /**
213 - * Register the Brand Visibility run-drain listener.
215 + * Rebuild the sitemap in-request when WP-Cron has not delivered.
214 216 *
215 - * Runs on plugins_loaded (via init()) rather than from the REST endpoint,
216 - * because the ticks that drain a run are WP-Cron requests — they never
217 - * reach rest_api_init, so registering the listener there would mean a run
218 - * starts and then never progresses.
217 + * WP-Cron only runs when a request arrives, so with DISABLE_WP_CRON set, a
218 + * host blocking loopback requests, or very little traffic, the scheduled
219 + * regeneration never fires and the sitemap silently stops updating (#629).
220 + * Once the event is overdue by the generator's grace period, an admin, REST
221 + * or WP-CLI request takes the work over so the site converges on its own.
219 222 *
223 + * Front-end requests are deliberately excluded: this runs on `shutdown`,
224 + * after the response, but generation on a large site is not free and a
225 + * visitor should never pay for it. Admin traffic is what a site with broken
226 + * cron reliably still has — the sitemap goes stale right after someone
227 + * publishes something, and that someone is in wp-admin.
228 + *
229 + * @since 2.2.1
220 230 * @return void
221 231 */
222 - private function register_brand_visibility_cron(): void {
223 - add_action(ThinkRank\AI\Brand_Visibility_Runner::TICK_HOOK, static function () {
224 - (new ThinkRank\AI\Brand_Visibility_Runner())->tick();
225 - });
232 + public function maybe_take_over_sitemap_regeneration(): void {
233 + // is_admin() is true for admin-ajax.php and REST_REQUEST for public
234 + // core routes, both of which anonymous front-end traffic reaches — so
235 + // without the logged-in test a visitor could still pay for the rebuild
236 + // this method documents as never being theirs to pay for. WP-CLI has no
237 + // user, and is trusted by definition.
238 + $eligible = (defined('WP_CLI') && WP_CLI)
239 + || (
240 + is_user_logged_in()
241 + && (
242 + is_admin()
243 + || (defined('REST_REQUEST') && REST_REQUEST)
244 + )
245 + );
226 246
227 - // Safety net: a tick killed by a fatal or a worker timeout never
228 - // reaches its own reschedule, which would strand the run. The
229 - // watchdog re-arms the drain and unschedules itself when idle.
230 - add_filter('cron_schedules', [ThinkRank\AI\Brand_Visibility_Runner::class, 'add_cron_interval']); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
231 - add_action(ThinkRank\AI\Brand_Visibility_Runner::WATCHDOG_HOOK, static function () {
232 - (new ThinkRank\AI\Brand_Visibility_Runner())->watchdog();
233 - });
247 + if (!$eligible) {
248 + return;
249 + }
250 +
251 + // Cheap autoloaded-option read, so the vast majority of requests stop
252 + // here without building the generator.
253 + if (!ThinkRank\SEO\Sitemap_Generator::has_overdue_regeneration()) {
254 + return;
255 + }
256 +
257 + // `shutdown` runs after the output buffers are flushed, but flushed is
258 + // not delivered: on FPM the connection stays open until the process
259 + // ends, so without this the browser — or the REST call the sitemap
260 + // screen just made — waits out the whole generation. Hand the response
261 + // back first, then rebuild.
262 + $this->close_request();
263 +
264 + // Read-only construction: the auto-generation hooks are pointless this
265 + // late in the request and would only add duplicate callbacks.
266 + (new ThinkRank\SEO\Sitemap_Generator(false))->run_overdue_regeneration();
234 267 }
235 268
236 269 /**
270 + * Deliver the response and let the request keep working without the client.
271 + *
272 + * A no-op on SAPIs that cannot do it, where the caller simply pays for the
273 + * work as before.
274 + *
275 + * @since 2.2.1
276 + * @return void
277 + */
278 + private function close_request(): void {
279 + if (defined('WP_CLI') && WP_CLI) {
280 + return;
281 + }
282 +
283 + if (function_exists('fastcgi_finish_request')) {
284 + fastcgi_finish_request();
285 + return;
286 + }
287 +
288 + if (function_exists('litespeed_finish_request')) {
289 + litespeed_finish_request();
290 + }
291 + }
292 +
293 + /**
294 + * Unschedule the cron events left behind by the removed Brand Visibility
295 + * feature.
296 + *
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).
303 + *
304 + * @return void
305 + */
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 + }
310 + }
311 +
312 + /**
237 313 * Check if database schema needs updating and run migrations
238 314 *
239 315 * Standard WordPress pattern: compare stored db_version against current,
240 316 * run dbDelta if stale. This handles schema changes (new tables, new columns)
@@ -266,21 +342,28 @@
266 342 'api' => new ThinkRank\API\Manager(),
267 343 'admin' => new ThinkRank\Admin\Manager(),
268 344 'blocks' => new ThinkRank\Editor\Blocks_Manager(),
269 345 'elementor' => new ThinkRank\Editor\Elementor_Manager(),
346 + 'bricks_elements' => new ThinkRank\Editor\Bricks_Elements_Manager(),
347 + 'beaver_modules' => new ThinkRank\Editor\Beaver_Modules_Manager(),
270 348 'ai' => new ThinkRank\AI\Manager(),
271 349 'frontend_seo' => new ThinkRank\Frontend\SEO_Manager(),
272 350 'seo_notice' => new ThinkRank\Admin\SEO_Notice(),
273 351 'search_visibility_notice' => new ThinkRank\Admin\Search_Visibility_Notice(),
274 352 'performance_collector' => new ThinkRank\SEO\Performance_Data_Collector(),
353 + 'query_guard' => new ThinkRank\SEO\Query_Guard(),
354 + 'feeds' => new ThinkRank\SEO\Feed_Manager(),
355 + 'sitemap_stylesheet' => new ThinkRank\SEO\Sitemap_Stylesheet(),
356 + 'oembed' => new ThinkRank\SEO\Oembed_Manager(),
357 + 'content_visibility' => new ThinkRank\SEO\Content_Visibility(),
275 358 'instant_indexing' => new ThinkRank\SEO\Instant_Indexing_Manager(),
276 359 'instant_indexing_reconciler' => new ThinkRank\SEO\Instant_Indexing_Reconciler(),
277 360 'author_archives' => new ThinkRank\SEO\Author_Archives_Manager(),
361 + 'seo_analyzer' => new ThinkRank\SEO\SEO_Analyzer(),
278 362 'email_report' => new ThinkRank\SEO\Email_Report_Manager(),
279 363 'google_oauth' => new ThinkRank\Integrations\Google_OAuth_Proxy(),
280 364 'multilingual' => new ThinkRank\Integrations\Multilingual_Manager(),
281 365 'ai_traffic' => new ThinkRank\SEO\Ai_Traffic_Tracker(),
282 - 'auto_ai' => new ThinkRank\SEO\Auto_Ai_Optimizer(),
283 366 'analytics' => new ThinkRank\SEO\Analytics_Manager(),
284 367 'abilities' => new ThinkRank\Abilities\Abilities_Registrar(),
285 368 'mcp' => new ThinkRank\Mcp\Mcp_Manager(),
286 369 ];