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 +121 -6 1.25.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.25.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.25.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.25.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,8 +176,9 @@
174 176 public function init(): void {
175 177 try {
176 178 $this->maybe_update_database();
177 179 $this->register_sitemap_cron_listeners();
180 + $this->clear_retired_brand_visibility_cron();
178 181 $this->load_components();
179 182 $this->init_components();
180 183 $this->load_template_functions();
181 184
@@ -203,11 +206,111 @@
203 206 });
204 207 add_action('thinkrank_regenerate_sitemap_settings', static function () {
205 208 (new ThinkRank\SEO\Sitemap_Generator())->regenerate_sitemap_from_settings();
206 209 });
210 +
211 + add_action('shutdown', [$this, 'maybe_take_over_sitemap_regeneration'], 100);
207 212 }
208 213
209 214 /**
215 + * Rebuild the sitemap in-request when WP-Cron has not delivered.
216 + *
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.
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
230 + * @return void
231 + */
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 + );
246 +
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();
267 + }
268 +
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 + /**
210 313 * Check if database schema needs updating and run migrations
211 314 *
212 315 * Standard WordPress pattern: compare stored db_version against current,
213 316 * run dbDelta if stale. This handles schema changes (new tables, new columns)
@@ -239,17 +342,28 @@
239 342 'api' => new ThinkRank\API\Manager(),
240 343 'admin' => new ThinkRank\Admin\Manager(),
241 344 'blocks' => new ThinkRank\Editor\Blocks_Manager(),
242 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(),
243 348 'ai' => new ThinkRank\AI\Manager(),
244 349 'frontend_seo' => new ThinkRank\Frontend\SEO_Manager(),
245 350 'seo_notice' => new ThinkRank\Admin\SEO_Notice(),
351 + 'search_visibility_notice' => new ThinkRank\Admin\Search_Visibility_Notice(),
246 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(),
247 358 'instant_indexing' => new ThinkRank\SEO\Instant_Indexing_Manager(),
359 + 'instant_indexing_reconciler' => new ThinkRank\SEO\Instant_Indexing_Reconciler(),
248 360 'author_archives' => new ThinkRank\SEO\Author_Archives_Manager(),
361 + 'seo_analyzer' => new ThinkRank\SEO\SEO_Analyzer(),
249 362 'email_report' => new ThinkRank\SEO\Email_Report_Manager(),
250 363 'google_oauth' => new ThinkRank\Integrations\Google_OAuth_Proxy(),
251 364 'multilingual' => new ThinkRank\Integrations\Multilingual_Manager(),
365 + 'ai_traffic' => new ThinkRank\SEO\Ai_Traffic_Tracker(),
252 366 'analytics' => new ThinkRank\SEO\Analytics_Manager(),
253 367 'abilities' => new ThinkRank\Abilities\Abilities_Registrar(),
254 368 'mcp' => new ThinkRank\Mcp\Mcp_Manager(),
255 369 ];
@@ -419,8 +533,9 @@
419 533 * Initialize the plugin
420 534 *
421 535 * @return ThinkRank
422 536 */
537 +// phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed -- plugin bootstrap: the accessor belongs next to the class it returns.
423 538 function thinkrank(): ThinkRank {
424 539 return ThinkRank::get_instance();
425 540 }
426 541