| @@ -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.1.1 | |
| 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.1.1 | |
| 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.1.1'); | |
| 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 | ]; |