diff --git a/wp-content/themes/hantico/functions.php b/wp-content/themes/hantico/functions.php
index 30205d0..7fb0801 100644
--- a/wp-content/themes/hantico/functions.php
+++ b/wp-content/themes/hantico/functions.php
@@ -374,3 +374,4 @@ require_once get_template_directory() . '/inc/ajax.php';
require_once get_template_directory() . '/inc/menu.php';
require_once get_template_directory() . '/inc/breadcrumbs.php';
require_once get_template_directory() . '/inc/article-urls.php';
+require_once get_template_directory() . '/inc/schema.php';
diff --git a/wp-content/themes/hantico/inc/schema.php b/wp-content/themes/hantico/inc/schema.php
new file mode 100644
index 0000000..ebab475
--- /dev/null
+++ b/wp-content/themes/hantico/inc/schema.php
@@ -0,0 +1,234 @@
+ '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' => 'BlogPosting', '@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;
+ }
+ $author_id = (int) get_post_field('post_author', $post_id);
+ $author = get_userdata($author_id);
+ if ($author && $author->display_name) {
+ $article['author'] = strcasecmp($author->display_name, 'Hantico') === 0
+ ? ['@id' => $organization_id]
+ : ['@type' => 'Person', '@id' => home_url('/#author-' . $author_id), 'name' => $author->display_name];
+ }
+ 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 '' . "\n";
+ }
+}, 30);
diff --git a/wp-content/themes/hantico/templates/template-blog.php b/wp-content/themes/hantico/templates/template-blog.php
index 0969559..6c88270 100644
--- a/wp-content/themes/hantico/templates/template-blog.php
+++ b/wp-content/themes/hantico/templates/template-blog.php
@@ -20,14 +20,7 @@ Template Name: Блог
];
$blog_title = get_field('blog_title');
$blog_descr = get_field('blog_descr');
- $blog_args = [
- 'post_type' => 'article',
- 'posts_per_page' => 6,
- 'orderby' => 'date',
- 'order' => 'DESC'
- ];
- $blog_args['tax_query'] = hantico_blog_tax_query($current_section);
- $blog_query = new WP_Query($blog_args);
+ $blog_query = hantico_blog_query(true);
if ($blog_query->have_posts()) :
?>
@@ -110,14 +103,7 @@ Template Name: Блог
'taxonomy' => 'article_category',
'hide_empty' => true,
]);
- $blog_args = [
- 'post_type' => 'article',
- 'posts_per_page' => 12,
- 'orderby' => 'date',
- 'order' => 'DESC'
- ];
- $blog_args['tax_query'] = hantico_blog_tax_query($current_section, $current_category);
- $blog_query = new WP_Query($blog_args);
+ $blog_query = hantico_blog_query();
?>