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 +112 -24 1.28.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: 1.28.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
@@ -11,12 +11,12 @@
11 11 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12 12 * Text Domain: thinkrank
13 13 * Domain Path: /languages
14 14 * Requires at least: 6.0
15 - * Requires PHP: 8.0
15 + * Requires PHP: 7.4
16 16 *
17 17 * @package ThinkRank
18 - * @version 1.28.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', '1.28.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__));
@@ -33,12 +33,12 @@
33 33 define('THINKRANK_PLUGIN_URL', plugin_dir_url(__FILE__));
34 34 define('THINKRANK_PLUGIN_BASENAME', plugin_basename(__FILE__));
35 35
36 36 // Minimum requirements check
37 -if (version_compare(PHP_VERSION, '8.0', '<')) {
37 +if (version_compare(PHP_VERSION, '7.4', '<')) {
38 38 add_action('admin_notices', function () {
39 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');
40 + echo esc_html__('ThinkRank requires PHP 7.4 or higher. Please upgrade your PHP version.', 'thinkrank');
41 41 echo '</p></div>';
42 42 });
43 43 return;
44 44 }
@@ -120,8 +120,10 @@
120 120 }
121 121
122 122 /**
123 123 * Prevent unserialization
124 + *
125 + * @throws \Exception On failure.
124 126 */
125 127 public function __wakeup() {
126 128 throw new \Exception('Cannot unserialize singleton');
127 129 }
@@ -174,9 +176,9 @@
174 176 public function init(): void {
175 177 try {
176 178 $this->maybe_update_database();
177 179 $this->register_sitemap_cron_listeners();
178 - $this->register_brand_visibility_cron();
180 + $this->clear_retired_brand_visibility_cron();
179 181 $this->load_components();
180 182 $this->init_components();
181 183 $this->load_template_functions();
182 184
@@ -204,35 +206,111 @@
204 206 });
205 207 add_action('thinkrank_regenerate_sitemap_settings', static function () {
206 208 (new ThinkRank\SEO\Sitemap_Generator())->regenerate_sitemap_from_settings();
207 209 });
210 +
211 + add_action('shutdown', [$this, 'maybe_take_over_sitemap_regeneration'], 100);
208 212 }
209 213
210 214 /**
211 - * Register the Brand Visibility run-drain listener.
215 + * Rebuild the sitemap in-request when WP-Cron has not delivered.
212 216 *
213 - * Runs on plugins_loaded (via init()) rather than from the REST endpoint,
214 - * because the ticks that drain a run are WP-Cron requests — they never
215 - * reach rest_api_init, so registering the listener there would mean a run
216 - * 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.
217 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
218 230 * @return void
219 231 */
220 - private function register_brand_visibility_cron(): void {
221 - add_action(ThinkRank\AI\Brand_Visibility_Runner::TICK_HOOK, static function () {
222 - (new ThinkRank\AI\Brand_Visibility_Runner())->tick();
223 - });
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 + );
224 246
225 - // Safety net: a tick killed by a fatal or a worker timeout never
226 - // reaches its own reschedule, which would strand the run. The
227 - // watchdog re-arms the drain and unschedules itself when idle.
228 - add_filter('cron_schedules', [ThinkRank\AI\Brand_Visibility_Runner::class, 'add_cron_interval']); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
229 - add_action(ThinkRank\AI\Brand_Visibility_Runner::WATCHDOG_HOOK, static function () {
230 - (new ThinkRank\AI\Brand_Visibility_Runner())->watchdog();
231 - });
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();
232 267 }
233 268
234 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 + /**
235 313 * Check if database schema needs updating and run migrations
236 314 *
237 315 * Standard WordPress pattern: compare stored db_version against current,
238 316 * run dbDelta if stale. This handles schema changes (new tables, new columns)
@@ -264,19 +342,28 @@
264 342 'api' => new ThinkRank\API\Manager(),
265 343 'admin' => new ThinkRank\Admin\Manager(),
266 344 'blocks' => new ThinkRank\Editor\Blocks_Manager(),
267 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(),
268 348 'ai' => new ThinkRank\AI\Manager(),
269 349 'frontend_seo' => new ThinkRank\Frontend\SEO_Manager(),
270 350 'seo_notice' => new ThinkRank\Admin\SEO_Notice(),
351 + 'search_visibility_notice' => new ThinkRank\Admin\Search_Visibility_Notice(),
271 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(),
272 358 'instant_indexing' => new ThinkRank\SEO\Instant_Indexing_Manager(),
359 + 'instant_indexing_reconciler' => new ThinkRank\SEO\Instant_Indexing_Reconciler(),
273 360 'author_archives' => new ThinkRank\SEO\Author_Archives_Manager(),
361 + 'seo_analyzer' => new ThinkRank\SEO\SEO_Analyzer(),
274 362 'email_report' => new ThinkRank\SEO\Email_Report_Manager(),
275 363 'google_oauth' => new ThinkRank\Integrations\Google_OAuth_Proxy(),
276 364 'multilingual' => new ThinkRank\Integrations\Multilingual_Manager(),
277 365 'ai_traffic' => new ThinkRank\SEO\Ai_Traffic_Tracker(),
278 - 'auto_ai' => new ThinkRank\SEO\Auto_Ai_Optimizer(),
279 366 'analytics' => new ThinkRank\SEO\Analytics_Manager(),
280 367 'abilities' => new ThinkRank\Abilities\Abilities_Registrar(),
281 368 'mcp' => new ThinkRank\Mcp\Mcp_Manager(),
282 369 ];
@@ -446,8 +533,9 @@
446 533 * Initialize the plugin
447 534 *
448 535 * @return ThinkRank
449 536 */
537 +// phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed -- plugin bootstrap: the accessor belongs next to the class it returns.
450 538 function thinkrank(): ThinkRank {
451 539 return ThinkRank::get_instance();
452 540 }
453 541