235 lines
9.3 KiB
PHP
235 lines
9.3 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' => '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 '<script type="application/ld+json">' . $json . '</script>' . "\n";
|
|
}
|
|
}, 30);
|