Files
hantico/wp-content/themes/hantico/inc/schema.php
T
web_55 39e8d5a8e1 task-17075__opdate-blog (#1)
Сделал
1. Сделать пункты в "ТЗ Статья"
2. Сделать пункты в "ТЗ Хабовая страница"
3. Добавить микроразметку для статьи и блога

https://docs.google.com/document/d/1ouFTKIXGk44hKONDDNWFGXBVqG06LWb7Jt28yFxklME/edit?tab=t.f4g9ytbmj2o8#heading=h.91km96rm8vln

Reviewed-on: #1
Co-authored-by: WEB № 55 - Кирилл | Верстка <web_55@good-production.xyz>
2026-10-26 13:36:52 +03:00

234 lines
9.1 KiB
PHP

<?php
function hantico_schema_is_blog() {
return is_page('blog') || is_page_template('templates/template-blog.php');
}
function hantico_schema_enabled() {
return !is_404() && !is_preview() && !is_feed()
&& (hantico_schema_is_blog() || is_singular('article'));
}
// Общая выборка для HTML и JSON-LD. На странице сейчас выводится первая
// порция; дальнейшие порции загружаются AJAX, без отдельного URL пагинации.
function hantico_blog_query($featured = false) {
static $queries = [];
$key = $featured ? 'featured' : 'list';
if (!isset($queries[$key])) {
$category = !$featured && isset($_GET['category']) && is_string($_GET['category'])
? sanitize_title(wp_unslash($_GET['category'])) : '';
$queries[$key] = new WP_Query([
'post_type' => 'article',
'posts_per_page' => $featured ? 6 : 12,
'orderby' => 'date',
'order' => 'DESC',
'tax_query' => hantico_blog_tax_query(hantico_blog_section(), $category),
]);
}
return $queries[$key];
}
function hantico_schema_url() {
if (is_singular('article')) {
return get_permalink(get_queried_object_id());
}
$section = hantico_blog_section();
$url = home_url(user_trailingslashit('/' . ($section && $section !== 'general' ? 'blog-' . $section : 'blog')));
return $section === 'general' ? add_query_arg('section', 'general', $url) : $url;
}
function hantico_schema_text($text) {
return trim(html_entity_decode(wp_strip_all_tags((string) $text), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
}
function hantico_schema_section_name($section) {
$term = $section ? get_term_by('slug', $section, 'article_chapter') : false;
return $term && !is_wp_error($term) ? $term->name : get_the_title(get_queried_object_id());
}
// Yoast хранит прежний permalink в индексе: корректируем только вывод,
// не меняя настройки плагина или сохранённые SEO-поля.
add_filter('wpseo_canonical', function ($url) {
return hantico_schema_enabled() ? hantico_schema_url() : $url;
});
add_filter('wpseo_opengraph_url', function ($url) {
return hantico_schema_enabled() ? hantico_schema_url() : $url;
});
function hantico_schema_has_type($node, $types) {
return (bool) array_intersect((array) ($node['@type'] ?? []), (array) $types);
}
function hantico_schema_graph($graph) {
if (!hantico_schema_enabled()) {
return $graph;
}
$url = hantico_schema_url();
$post_id = get_queried_object_id();
$is_blog = hantico_schema_is_blog();
$page_index = null;
// Обновляем также связанные ID изображений, крошек и действий Yoast.
foreach ($graph as $node) {
if (hantico_schema_has_type($node, ['WebPage', 'CollectionPage', 'ItemPage']) && !empty($node['url'])) {
$old_url = $node['url'];
array_walk_recursive($graph, function (&$value) use ($old_url, $url) {
if (is_string($value) && ($value === $old_url || strpos($value, $old_url . '#') === 0)) {
$value = $url . substr($value, strlen($old_url));
}
});
break;
}
}
$website_id = home_url('/#website');
$organization_id = home_url('/#organization');
$organization_found = false;
foreach ($graph as $index => $node) {
if (hantico_schema_has_type($node, ['WebPage', 'CollectionPage', 'ItemPage'])) {
$page_index = $index;
}
if (hantico_schema_has_type($node, 'WebSite')) {
$website_id = $node['@id'];
}
if (hantico_schema_has_type($node, 'Organization')) {
$organization_id = $node['@id'];
$organization_found = true;
}
}
if (!$organization_found) {
$graph[] = [
'@type' => 'Organization', '@id' => $organization_id,
'name' => 'Hantico', 'url' => home_url('/'),
'logo' => ['@type' => 'ImageObject', 'url' => get_theme_file_uri('assets/img/icons/logo.svg')],
];
}
$website_found = false;
foreach ($graph as &$node) {
if (hantico_schema_has_type($node, 'WebSite')) {
$node['publisher'] = ['@id' => $organization_id];
$website_found = true;
}
}
unset($node);
if (!$website_found) {
$graph[] = ['@type' => 'WebSite', '@id' => $website_id, 'url' => home_url('/'),
'name' => get_bloginfo('name'), 'publisher' => ['@id' => $organization_id]];
}
if ($page_index === null) {
$graph[] = ['@type' => 'WebPage', '@id' => $url];
$page_index = array_key_last($graph);
}
$page = &$graph[$page_index];
$page['url'] = $url;
$page['isPartOf'] = ['@id' => $website_id];
$page['inLanguage'] = get_bloginfo('language');
$page['breadcrumb'] = ['@id' => $url . '#breadcrumb'];
$description = $page['description'] ?? '';
if ($is_blog) {
$section = hantico_blog_section();
$page['@type'] = 'CollectionPage';
$page['name'] = hantico_schema_text($section ? hantico_schema_section_name($section) : (get_field('blog_title', $post_id) ?: get_the_title($post_id)));
$term = $section ? get_term_by('slug', $section, 'article_chapter') : false;
$description = $term && !is_wp_error($term) && $term->description
? $term->description : ($description ?: get_field('blog_descr', $post_id));
// Два списка соответствуют двум реальным блокам, без повторных запросов
// и без включения статей из следующих AJAX-порций.
$page['mainEntity'] = [];
foreach ([true, false] as $featured) {
$list_id = $url . ($featured ? '#featured-articles' : '#articles');
$items = [];
foreach (hantico_blog_query($featured)->posts as $post) {
$items[] = ['@type' => 'ListItem', 'position' => count($items) + 1,
'url' => get_permalink($post), 'name' => hantico_schema_text(get_the_title($post))];
}
$graph[] = ['@type' => 'ItemList', '@id' => $list_id,
'numberOfItems' => count($items), 'itemListElement' => $items];
$page['mainEntity'][] = ['@id' => $list_id];
}
$crumbs = [['name' => 'Главная', 'item' => home_url('/')], ['name' => $page['name'], 'item' => $url]];
} else {
$description = $description ?: (get_field('brief_description', $post_id) ?: get_field('article_descr', $post_id));
$article = [
'@type' => 'Article', '@id' => $url . '#article', 'url' => $url,
'headline' => hantico_schema_text(get_the_title($post_id)),
'datePublished' => get_post_time('c', true, $post_id),
'dateModified' => get_post_modified_time('c', true, $post_id),
'publisher' => ['@id' => $organization_id],
'mainEntityOfPage' => ['@id' => $page['@id']],
'inLanguage' => get_bloginfo('language'),
];
$image = get_the_post_thumbnail_url($post_id, 'full');
if ($image) {
$article['image'] = $image;
}
$article['author'] = [
'@type' => 'Organization',
'@id' => home_url('/about/#editorial'),
'name' => 'Редакция Hantico',
'url' => home_url('/about/'),
];
if ($description) {
$article['description'] = hantico_schema_text($description);
}
$article_index = null;
foreach ($graph as $index => $node) {
if (hantico_schema_has_type($node, ['Article', 'BlogPosting', 'NewsArticle'])) {
$article_index = $index;
$article['@id'] = $node['@id'];
break;
}
}
if ($article_index === null) {
$graph[] = $article;
} else {
$graph[$article_index] = array_merge($graph[$article_index], $article);
}
$page['mainEntity'] = ['@id' => $article['@id']];
$base = hantico_article_url_base($post_id);
$section = $base === 'blog' ? '' : substr($base, 5);
$blog_page = get_page_by_path('blog');
$crumbs = [
['name' => 'Главная', 'item' => home_url('/')],
['name' => $section ? hantico_schema_section_name($section) : ($blog_page ? get_the_title($blog_page) : 'Блог'),
'item' => home_url(user_trailingslashit('/' . $base))],
['name' => $article['headline'], 'item' => $url],
];
}
if ($description) {
$page['description'] = hantico_schema_text($description);
}
unset($page);
$breadcrumb = ['@type' => 'BreadcrumbList', '@id' => $url . '#breadcrumb', 'itemListElement' => []];
foreach ($crumbs as $crumb) {
$breadcrumb['itemListElement'][] = array_merge(['@type' => 'ListItem', 'position' => count($breadcrumb['itemListElement']) + 1], $crumb);
}
$breadcrumb_index = null;
foreach ($graph as $index => $node) {
if (hantico_schema_has_type($node, 'BreadcrumbList')) {
$breadcrumb_index = $index;
break;
}
}
if ($breadcrumb_index === null) {
$graph[] = $breadcrumb;
} else {
$graph[$breadcrumb_index] = $breadcrumb;
}
return $graph;
}
// Yoast выводит граф в wp_head и безопасно кодирует JSON самостоятельно.
add_filter('wpseo_schema_graph', 'hantico_schema_graph', 20);
// Резервный вывод, если Yoast деактивирован. При активном плагине второй
// script не создаётся, даже если его разметка отключена настройкой.
add_action('wp_head', function () {
if (defined('WPSEO_VERSION') || !hantico_schema_enabled()) {
return;
}
$json = wp_json_encode(['@context' => 'https://schema.org', '@graph' => hantico_schema_graph([])],
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE);
if ($json) {
echo '<script type="application/ld+json">' . $json . '</script>' . "\n";
}
}, 30);