| @@ -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 |