Уникальные 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, не считайте пустые описания дубликатами, и предупреждения станут полезными, а не шумными.

