PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.2
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.2
2.14.2 2.14.1 2.14.0 2.13.0 2.12.0 2.11.0 2.10.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 All 57 releases
← All changes | includes/seo/class-instant-indexing-manager.php +85 -15 2.13.0 → 2.14.2 View file →
@@ -53,8 +53,16 @@
53 53 */
54 54 public const MAX_URLS_PER_SUBMISSION = 100;
55 55
56 56 /**
57 + * Posts that transitioned to publish in this request and await the end of
58 + * their save, keyed by ID, with what was true of them before it.
59 + *
60 + * @var array<int, array{was_published: bool, was_excluded: bool}>
61 + */
62 + private array $pending_transitions = [];
63 +
64 + /**
57 65 * Initialize the component
58 66 *
59 67 * @since 1.1.0
60 68 * @return void
@@ -60,8 +68,9 @@
60 68 * @return void
61 69 */
62 70 public function init(): void {
63 71 add_action('transition_post_status', [$this, 'handle_post_transition'], 10, 3);
72 + add_action('wp_after_insert_post', [$this, 'handle_after_insert_post'], 10, 4);
64 73 add_action('delete_post', [$this, 'handle_post_deletion'], 10, 2);
65 74
66 75 // Serve the IndexNow key file from PHP when no physical file exists.
67 76 // The key is normally written to the WordPress root, but on managed and
@@ -249,9 +258,9 @@
249 258 }
250 259
251 260 return sprintf(
252 261 /* translators: %d: HTTP status code returned by the key URL. */
253 - __('The key file could not be verified (HTTP %d). Open it in a browser — it should show the key and nothing else.', 'thinkrank'),
262 + __('The key file could not be verified (HTTP %d). Open it in a browser: it should show the key and nothing else.', 'thinkrank'),
254 263 $code
255 264 );
256 265 }
257 266
@@ -381,23 +390,84 @@
381 390 if (!$this->should_process_post($post)) {
382 391 return;
383 392 }
384 393
385 - // We only care if the new status is publish (created or updated)
386 - // OR if we are unpublishing (publish -> something else), we might want to update (though IndexNow is mostly for crawling new/updated content)
387 - // For now, let's focus on published content.
388 - if ($new_status === 'publish') {
389 - $url = get_permalink($post->ID);
394 + // Only content that is (still) published is submitted.
395 + if ($new_status !== 'publish') {
396 + return;
397 + }
390 398
391 - // Check for duplicate submission using short-lived cache (15 seconds)
392 - if ($this->is_recently_submitted_cache($url)) {
393 - return;
394 - }
399 + // Whether to submit is decided in handle_after_insert_post(), not
400 + // here. This hook fires inside wp_insert_post() before the post's meta
401 + // is written: the block editor saves the robots override through
402 + // REST after the insert, the classic metabox on save_post. Deciding
403 + // here read the previous robots value, so a post published with
404 + // noindex was submitted (#911).
405 + //
406 + // What this hook can still see is the previous state, which the
407 + // decision needs: a post that was an indexable destination and is no
408 + // longer one is submitted once more, so engines recrawl it and drop it.
409 + $this->pending_transitions[(int) $post->ID] = [
410 + 'was_published' => $old_status === 'publish',
411 + 'was_excluded' => Indexability::is_post_noindexed($post) || Indexability::is_post_redirected($post),
412 + ];
413 + }
395 414
396 - // Defer the outbound IndexNow call to WP-Cron so publishing doesn't
397 - // block on a third-party HTTP request.
398 - $this->schedule_url_submission([$url]);
415 + /**
416 + * Submit a published post once its save is complete.
417 + *
418 + * Runs on `wp_after_insert_post`, which fires after meta and terms are
419 + * saved on every path (classic editor, REST, Quick Edit, WP-CLI, and the
420 + * scheduled-post publish in wp_publish_post()).
421 + *
422 + * A post that is not an indexable destination (noindexed by its own
423 + * override or its type, password-protected, or redirected) is skipped,
424 + * unless it was one before this save: that transition is submitted so
425 + * engines recrawl the page and drop it.
426 + *
427 + * @since 2.15.0
428 + *
429 + * @param int $post_id Post ID.
430 + * @param \WP_Post $post Post object after the save.
431 + * @param bool $update Whether this was an update.
432 + * @param \WP_Post|null $post_before Post object before the save, null for a new post.
433 + * @return void
434 + */
435 + public function handle_after_insert_post(int $post_id, \WP_Post $post, bool $update, ?\WP_Post $post_before): void {
436 + if (!isset($this->pending_transitions[$post_id])) {
437 + return;
399 438 }
439 +
440 + $before = $this->pending_transitions[$post_id];
441 + unset($this->pending_transitions[$post_id]);
442 +
443 + if ($post->post_status !== 'publish') {
444 + return;
445 + }
446 +
447 + $was_indexable = $before['was_published']
448 + && !$before['was_excluded']
449 + && !($post_before instanceof \WP_Post && Indexability::is_password_protected($post_before));
450 +
451 + // Decided before the dedupe check below, which claims its 15-second
452 + // slot as a side effect: a skipped post must not occupy it.
453 + if (!Indexability::is_indexable_post($post) && !$was_indexable) {
454 + return;
455 + }
456 +
457 + $url = get_permalink($post->ID);
458 + if (!$url) {
459 + return;
460 + }
461 +
462 + // Check for duplicate submission using short-lived cache (15 seconds)
463 + if ($this->is_recently_submitted_cache($url)) {
464 + return;
465 + }
466 +
467 + // Defer the outbound IndexNow call to WP-Cron so publishing doesn't
468 + // block on a third-party HTTP request.
469 + $this->schedule_url_submission([$url]);
400 470 }
401 471
402 472 /**
403 473 * Check if URL was recently submitted using transient cache
@@ -469,9 +539,9 @@
469 539 return false;
470 540 }
471 541
472 542 // 3. Check autosave/revision
473 - if (wp_is_post_autosave($post) || wp_is_post_revision($post)) {
543 + if (wp_is_post_autosave((int) $post->ID) || wp_is_post_revision((int) $post->ID)) {
474 544 return false;
475 545 }
476 546
477 547 return true;
@@ -587,9 +657,9 @@
587 657 // actually means, and name the URL to check.
588 658 if ($response_code === 403) {
589 659 $response_message = sprintf(
590 660 /* translators: %s: public URL of the IndexNow key file. */
591 - __('Key file could not be verified. Search engines must be able to read your key at %s — open it in a browser: it should show the key and nothing else.', 'thinkrank'),
661 + __('Key file could not be verified. Search engines must be able to read your key at %s. Open it in a browser: it should show the key and nothing else.', 'thinkrank'),
592 662 $key_location
593 663 );
594 664 }
595 665 }