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(); ?>