Проверка дубликатов заголовков и описаний с The SEO Framework

Уникальные title и meta description помогают поиску и снижают риск «размытых» сниппетов. Плагин The SEO Framework хранит описание в метаполе _genesis_description (историческое имя из экосистемы Genesis). Ниже небольшой хук на admin_notices: при редактировании записи или страницы в админке показывается предупреждение, если такой заголовок или описание уже есть у другого опубликованного материала.

Куда вставлять

Код размещают в дочерней теме (functions.php) или в небольшом mu-plugin / своём плагине. На проде лучше не править родительскую тему: обновление затрёт правки.

Логика по шагам

1. Только post и page

На экранах медиа, товаров и прочих CPT функция сразу выходит:

if ( ! $post || ( $post->post_type !== 'post' && $post->post_type !== 'page' ) ) {
    return;
}

2. Title и description

$title       = get_the_title( $post->ID );
$description = get_post_meta( $post->ID, '_genesis_description', true ); // The SEO Framework

Если в SEO Framework задано другое хранение описания (редко), ключ метаполя нужно сверить в БД или в коде плагина.

3. Дубликаты заголовков

Ищем другие publish-записи того же типа с тем же post_title, исключая текущий ID. Важно: условие должно быть post_title = … AND ID != …. Вариант с OR ID != даёт почти всегда ложные срабатывания (почти все посты «не равны» текущему ID).

$dup_titles = (int) $wpdb->get_var( $wpdb->prepare(
    "SELECT COUNT(*) FROM {$wpdb->posts}
     WHERE post_type = %s
       AND post_title = %s
       AND ID != %d
       AND post_status = 'publish'",
    $post->post_type,
    $title,
    $post->ID
) );

4. Дубликаты meta description

По таблице postmeta и ключу _genesis_description. Пустую строку лучше не проверять: иначе все материалы без описания «дублируют» друг друга.

$dup_desc = 0;
if ( $description !== '' && $description !== false ) {
    $dup_desc = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->postmeta}
         WHERE meta_key = '_genesis_description'
           AND meta_value = %s
           AND post_id != %d",
        $description,
        $post->ID
    ) );
}

Полный код

function check_duplicate_titles_and_descriptions() {
    global $post, $wpdb, $pagenow;

    // Только экраны редактирования записи/страницы
    if ( ! in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) ) {
        return;
    }

    if ( ! $post || ! isset( $post->ID ) ) {
        return;
    }

    if ( $post->post_type !== 'post' && $post->post_type !== 'page' ) {
        return;
    }

    $title       = get_the_title( $post->ID );
    $description = get_post_meta( $post->ID, '_genesis_description', true );

    $dup_titles = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->posts}
         WHERE post_type = %s
           AND post_title = %s
           AND ID != %d
           AND post_status = 'publish'",
        $post->post_type,
        $title,
        $post->ID
    ) );

    if ( $dup_titles > 0 ) {
        echo '<div class="notice notice-warning"><p>Внимание: заголовок «'
            . esc_html( $title )
            . '» уже используется у других опубликованных материалов.</p></div>';
    }

    if ( $description !== '' && $description !== false ) {
        $dup_desc = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->postmeta} pm
             INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
             WHERE pm.meta_key = '_genesis_description'
               AND pm.meta_value = %s
               AND pm.post_id != %d
               AND p.post_status = 'publish'",
            $description,
            $post->ID
        ) );

        if ( $dup_desc > 0 ) {
            echo '<div class="notice notice-warning"><p>Внимание: это meta description уже используется на других страницах.</p></div>';
        }
    }
}
add_action( 'admin_notices', 'check_duplicate_titles_and_descriptions' );

Ограничения

  • Сравнение title идёт по точному совпадению post_title, без нормализации пробелов и регистра.
  • Черновики и private по умолчанию не учитываются (только publish).
  • На очень больших сайтах сырой COUNT на каждый заход в редактор можно заменить фоновой проверкой или индексом/кэшем; для средних объёмов обычно хватает.
  • Плагин не заменяет аудит всего сайта: это подсказка редактору в момент работы с материалом.

Итог

С The SEO Framework и хуком на admin_notices можно ловить повторяющиеся заголовки и _genesis_description прямо в админке. Исправьте SQL на AND вместо ошибочного OR по ID, не считайте пустые описания дубликатами, и предупреждения станут полезными, а не шумными.

Источники и ссылки


Комментарии загружаются…