PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.0
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 1.27.0 All 56 releases
← All changes | includes/seo/class-sitemap-generator.php +44 -111 2.14.1 → 2.14.0 View file →
@@ -409,105 +409,11 @@
409 409 add_action($hook, static function (int $term_id, int $tt_id, string $taxonomy): void {
410 410 self::listener()->handle_taxonomy_change($term_id, $tt_id, $taxonomy);
411 411 }, 20, 3);
412 412 }
413 -
414 - // The sitemap leaves out what the robots tag noindexes and what
415 - // ThinkRank redirects (#911), so a change to either decides what the
416 - // published files should list. Neither is a post or term save, and
417 - // without these the files kept the old answer until unrelated content
418 - // changed.
419 - foreach (self::ROBOTS_SETTINGS_OPTIONS as $option) {
420 - add_action('update_option_' . $option, static function ($old_value, $value): void {
421 - self::handle_robots_settings_change($old_value, $value);
422 - }, 20, 2);
423 - add_action('add_option_' . $option, static function ($name, $value): void {
424 - self::handle_robots_settings_change([], $value);
425 - }, 20, 2);
426 - }
427 -
428 - add_action('thinkrank_object_redirect_saved', static function (): void {
429 - self::listener()->schedule_regeneration();
430 - }, 20, 0);
431 413 }
432 414
433 415 /**
434 - * Options holding robots directives the sitemap's item filter reads.
435 - *
436 - * @since 2.15.0
437 - * @var string[]
438 - */
439 - private const ROBOTS_SETTINGS_OPTIONS = [
440 - 'thinkrank_global_seo_settings',
441 - 'thinkrank_global_robot_meta_settings',
442 - ];
443 -
444 - /**
445 - * Queue a rebuild when a saved robots setting changes a noindex decision.
446 - *
447 - * Both options carry far more than robots directives (titles, schema,
448 - * feature switches), so only a change to a noindex outcome rebuilds.
449 - *
450 - * @since 2.15.0
451 - *
452 - * @param mixed $old_value Previous option value.
453 - * @param mixed $value New option value.
454 - * @return void
455 - */
456 - public static function handle_robots_settings_change($old_value, $value): void {
457 - if (self::noindex_fingerprint($old_value) === self::noindex_fingerprint($value)) {
458 - return;
459 - }
460 -
461 - // The matrix memoises the option for the request, and a rebuild that
462 - // runs in this request must read the value just saved.
463 - Content_Type_Settings::flush_cache();
464 -
465 - self::listener()->schedule_regeneration();
466 - }
467 -
468 - /**
469 - * The noindex decisions a robots option makes, keyed by what they apply to.
470 - *
471 - * Reads both shapes: the flat site-wide directives, and the per-entity
472 - * rows, where a row's directives apply only while its robots switch is on.
473 - * A row that is on but stores no `noindex` key changes nothing, matching
474 - * the array_merge() the robots tag does.
475 - *
476 - * @since 2.15.0
477 - *
478 - * @param mixed $value Option value.
479 - * @return array<string, bool|null>
480 - */
481 - private static function noindex_fingerprint($value): array {
482 - if (!is_array($value)) {
483 - return [];
484 - }
485 -
486 - $decisions = [];
487 -
488 - if (array_key_exists('noindex', $value) && !is_array($value['noindex'])) {
489 - $decisions['*'] = !empty($value['noindex']);
490 - }
491 -
492 - foreach ($value as $key => $row) {
493 - if (!is_array($row)) {
494 - continue;
495 - }
496 -
497 - $robots = $row['robots_meta'] ?? null;
498 -
499 - $decisions[(string) $key] = !empty($row['robots_meta_enabled']) && is_array($robots) && array_key_exists('noindex', $robots)
500 - ? !empty($robots['noindex'])
501 - : null;
502 - }
503 -
504 - ksort($decisions);
505 -
506 - return $decisions;
507 - }
508 -
509 - /**
510 416 * The generator the content-change listeners share.
511 417 *
512 418 * @since 2.10.1
513 419 * @return self
@@ -1157,12 +1063,12 @@
1157 1063 continue;
1158 1064 }
1159 1065
1160 1066 foreach ($terms as $term) {
1161 - // A term whose archive says noindex (its own override or its
1162 - // taxonomy's), or that redirects, must not be advertised: the
1163 - // sitemap would contradict the page's own signal (#911).
1164 - if (!Indexability::is_indexable_term($term)) {
1067 + // A term the user marked noindex must not be advertised in the
1068 + // sitemap: the robots tag now honours term meta, so listing it
1069 + // here would have the sitemap contradict the page's own tag.
1070 + if ($this->term_is_noindexed((int) $term->term_id)) {
1165 1071 continue;
1166 1072 }
1167 1073
1168 1074 $url = get_term_link($term);
@@ -1467,15 +1373,14 @@
1467 1373 if (is_wp_error($all_terms)) {
1468 1374 return [];
1469 1375 }
1470 1376
1471 - // Drop terms that are not indexable destinations. This path feeds the
1472 - // single general sitemap while collect_taxonomy_entries_iter() feeds
1473 - // the segmented ones, so both need the filter or the two disagree about
1474 - // the same term.
1377 + // Drop terms the user marked noindex. This path feeds the single general
1378 + // sitemap while collect_taxonomy_entries_iter() feeds the segmented ones,
1379 + // so both need the filter or the two disagree about the same term.
1475 1380 $all_terms = array_values(array_filter(
1476 1381 $all_terms,
1477 - static fn($term) => $term instanceof \WP_Term && Indexability::is_indexable_term($term)
1382 + fn($term) => !$this->term_is_noindexed((int) $term->term_id)
1478 1383 ));
1479 1384
1480 1385 // Group terms by taxonomy
1481 1386 return $this->group_terms_by_taxonomy($all_terms);
@@ -1657,16 +1562,17 @@
1657 1562 if (!in_array($post->post_status, ['publish', 'private'], true)) {
1658 1563 return false;
1659 1564 }
1660 1565
1661 - // A URL whose page says noindex, or that ThinkRank redirects, is not a
1662 - // destination. Only the per-post noindex used to be read here, so a
1663 - // post type set to No-index still had every item listed, and redirected
1664 - // posts were submitted as "Page with redirect" (#911). The password
1665 - // check stays with the setting above: listing protected posts is a
1666 - // choice this sitemap has always offered.
1667 - if (Indexability::is_post_noindexed($post) || Indexability::is_post_redirected($post)) {
1668 - return false;
1566 + // Check if post overrides robots and sets noindex.
1567 + if ((bool) get_post_meta($post->ID, '_thinkrank_robots_meta_enabled', true)) {
1568 + $raw = get_post_meta($post->ID, '_thinkrank_robots_meta', true);
1569 + if (is_string($raw) && $raw !== '') {
1570 + $robots = json_decode($raw, true);
1571 + if (is_array($robots) && !empty($robots['noindex'])) {
1572 + return false;
1573 + }
1574 + }
1669 1575 }
1670 1576
1671 1577 return true;
1672 1578 }
@@ -1702,8 +1608,35 @@
1702 1608
1703 1609 $this->woocommerce_excluded_page_ids = $ids;
1704 1610
1705 1611 return $ids;
1612 + }
1613 +
1614 + /**
1615 + * Whether a term carries an explicit noindex override.
1616 + *
1617 + * Mirrors the post-side check in should_include_post(); terms store the same
1618 + * `_thinkrank_robots_meta_enabled` / `_thinkrank_robots_meta` keys, written
1619 + * by the update-term-seo ability and by the SEO importer.
1620 + *
1621 + * @since 1.31.0
1622 + *
1623 + * @param int $term_id Term to test.
1624 + * @return bool True when the term is marked noindex.
1625 + */
1626 + private function term_is_noindexed(int $term_id): bool {
1627 + if (!(bool) get_term_meta($term_id, '_thinkrank_robots_meta_enabled', true)) {
1628 + return false;
1629 + }
1630 +
1631 + $raw = get_term_meta($term_id, '_thinkrank_robots_meta', true);
1632 + if (!is_string($raw) || $raw === '') {
1633 + return false;
1634 + }
1635 +
1636 + $robots = json_decode($raw, true);
1637 +
1638 + return is_array($robots) && !empty($robots['noindex']);
1706 1639 }
1707 1640
1708 1641 /**
1709 1642 * Count total URLs in sitemap