get_bloginfo( 'name' ), 'home_description' => get_bloginfo( 'description' ), 'separator' => '|', 'person_name' => '', 'person_image' => '', 'default_image' => '', 'twitter_site' => '', 'same_as' => '', 'noindex_category' => 1, 'noindex_tag' => 1, 'noindex_author' => 1, 'noindex_date' => 1, 'indexnow' => 1, 'indexnow_key' => '', ); } function seojak_opt( string $key ) { static $opts = null; if ( null === $opts ) { $opts = wp_parse_args( (array) get_option( SEOJAK_OPTION, array() ), seojak_defaults() ); } return $opts[ $key ] ?? null; } /* ===================== Данные записи ===================== */ const SEOJAK_META = array( '_seojak_title' => 'string', '_seojak_description' => 'string', '_seojak_noindex' => 'boolean', ); add_action( 'init', function () { foreach ( array( 'post', 'page' ) as $type ) { foreach ( SEOJAK_META as $key => $kind ) { register_post_meta( $type, $key, array( 'type' => $kind, 'single' => true, 'show_in_rest' => true, 'auth_callback' => function ( $allowed, $meta_key, $post_id ) { return current_user_can( 'edit_post', $post_id ); }, ) ); } } } ); function seojak_post_description( WP_Post $post ): string { $custom = trim( (string) get_post_meta( $post->ID, '_seojak_description', true ) ); if ( '' !== $custom ) { return $custom; } $text = '' !== trim( $post->post_excerpt ) ? $post->post_excerpt : strip_shortcodes( $post->post_content ); if ( function_exists( 'trim_text' ) ) { return trim_text( $text, 160 ); } return wp_trim_words( wp_strip_all_tags( $text ), 25, '…' ); } /* ===================== Что за страница сейчас ===================== */ /** Всё, что нужно для
текущей страницы. */ function seojak_context(): array { static $ctx = null; if ( null !== $ctx ) { return $ctx; } $sep = seojak_opt( 'separator' ); $site = get_bloginfo( 'name' ); $ctx = array( 'title' => '', 'description' => '', 'url' => '', 'type' => 'website', 'image' => seojak_opt( 'default_image' ), 'noindex' => false, 'post' => null, ); if ( is_front_page() || is_home() ) { $ctx['title'] = seojak_opt( 'home_title' ); $ctx['description'] = seojak_opt( 'home_description' ); $ctx['url'] = home_url( '/' ); } elseif ( is_singular() ) { $post = get_queried_object(); $custom = trim( (string) get_post_meta( $post->ID, '_seojak_title', true ) ); $ctx['post'] = $post; $ctx['title'] = '' !== $custom ? $custom : single_post_title( '', false ); $ctx['description'] = seojak_post_description( $post ); $ctx['url'] = get_permalink( $post ); $ctx['type'] = 'article'; $ctx['noindex'] = (bool) get_post_meta( $post->ID, '_seojak_noindex', true ); if ( has_post_thumbnail( $post ) ) { $ctx['image'] = get_the_post_thumbnail_url( $post, 'large' ); } } elseif ( is_category() || is_tag() || is_tax() ) { $term = get_queried_object(); $ctx['name'] = $term->name; $ctx['description'] = wp_strip_all_tags( term_description( $term ) ); $ctx['url'] = get_term_link( $term ); $ctx['noindex'] = ( is_category() && seojak_opt( 'noindex_category' ) ) || ( is_tag() && seojak_opt( 'noindex_tag' ) ); } elseif ( is_author() ) { $ctx['name'] = get_the_author_meta( 'display_name', get_queried_object_id() ); $ctx['url'] = get_author_posts_url( get_queried_object_id() ); $ctx['noindex'] = (bool) seojak_opt( 'noindex_author' ); } elseif ( is_date() ) { $ctx['name'] = wp_strip_all_tags( get_the_archive_title() ); $ctx['noindex'] = (bool) seojak_opt( 'noindex_date' ); $ctx['url'] = is_year() ? get_year_link( get_query_var( 'year' ) ) : ( is_month() ? get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ) : get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) ) ); } elseif ( is_search() ) { $ctx['name'] = get_search_query(); $ctx['noindex'] = true; } if ( isset( $ctx['name'] ) ) { $ctx['title'] = $ctx['name'] . " $sep $site"; } // Вторая и дальше страницы списков: свой адрес, без индексации, ссылки по ним — да. $paged = (int) get_query_var( 'paged' ); if ( $paged > 1 && '' !== $ctx['title'] ) { $ctx['noindex'] = true; if ( $ctx['url'] ) { $ctx['url'] = get_pagenum_link( $paged ); } $ctx['title'] .= " $sep Страница $paged"; } if ( ! $ctx['image'] && seojak_opt( 'person_image' ) ) { $ctx['image'] = seojak_opt( 'person_image' ); } return $ctx; } /* ===================== ===================== */ // Пока активен All in One SEO, фронтенд не трогаем: два плагина — два набора тегов. function seojak_front_enabled(): bool { return ! defined( 'AIOSEO_VERSION' ) && ! is_admin() && ! is_feed(); } add_filter( 'pre_get_document_title', function ( $title ) { if ( ! seojak_front_enabled() ) { return $title; } $t = seojak_context()['title']; return '' !== $t ? $t : $title; }, 20 ); add_filter( 'wp_robots', function ( array $robots ) { if ( seojak_front_enabled() && seojak_context()['noindex'] ) { $robots['noindex'] = true; $robots['follow'] = true; } return $robots; } ); add_action( 'wp', function () { if ( seojak_front_enabled() ) { remove_action( 'wp_head', 'rel_canonical' ); // свой canonical — для всех страниц, а не только записей } } ); add_action( 'wp_head', function () { if ( ! seojak_front_enabled() || is_404() ) { return; } $c = seojak_context(); $tags = array(); if ( $c['description'] ) { $tags[] = array( 'name', 'description', $c['description'] ); } if ( $c['url'] && ! $c['noindex'] ) { echo '\n"; } foreach ( array( 'prev' => get_previous_posts_page_link(), 'next' => get_next_posts_page_link() ) as $rel => $link ) { if ( ! is_singular() && $link && ( 'prev' === $rel ? get_query_var( 'paged' ) > 1 : max( 1, get_query_var( 'paged' ) ) < $GLOBALS['wp_query']->max_num_pages ) ) { echo '\n"; } } $tags[] = array( 'property', 'og:locale', get_locale() ); $tags[] = array( 'property', 'og:site_name', get_bloginfo( 'name' ) ); $tags[] = array( 'property', 'og:type', $c['type'] ); $tags[] = array( 'property', 'og:title', $c['title'] ); if ( $c['description'] ) { $tags[] = array( 'property', 'og:description', $c['description'] ); } if ( $c['url'] ) { $tags[] = array( 'property', 'og:url', $c['url'] ); } if ( $c['image'] ) { $tags[] = array( 'property', 'og:image', $c['image'] ); } if ( $c['post'] && 'article' === $c['type'] ) { $tags[] = array( 'property', 'article:published_time', get_post_time( 'c', true, $c['post'] ) ); $tags[] = array( 'property', 'article:modified_time', get_post_modified_time( 'c', true, $c['post'] ) ); } $tags[] = array( 'name', 'twitter:card', $c['image'] ? 'summary_large_image' : 'summary' ); if ( seojak_opt( 'twitter_site' ) ) { $tags[] = array( 'name', 'twitter:site', seojak_opt( 'twitter_site' ) ); } $tags[] = array( 'name', 'twitter:title', $c['title'] ); if ( $c['description'] ) { $tags[] = array( 'name', 'twitter:description', $c['description'] ); } if ( $c['image'] ) { $tags[] = array( 'name', 'twitter:image', $c['image'] ); } echo "\n"; foreach ( $tags as list( $attr, $name, $content ) ) { printf( "\n", $attr, esc_attr( $name ), esc_attr( $content ) ); } echo '\n"; echo "\n"; }, 1 ); /** Разметка Schema.org: сайт, автор, страница, статья, хлебные крошки. */ function seojak_schema( array $c ): array { $home = home_url( '/' ); $person = array( '@type' => 'Person', '@id' => $home . '#person', 'name' => seojak_opt( 'person_name' ) ?: get_bloginfo( 'name' ), 'url' => $home, ); if ( seojak_opt( 'person_image' ) ) { $person['image'] = seojak_opt( 'person_image' ); } $same = array_values( array_filter( array_map( 'trim', explode( "\n", (string) seojak_opt( 'same_as' ) ) ) ) ); if ( $same ) { $person['sameAs'] = $same; } $graph = array( array( '@type' => 'WebSite', '@id' => $home . '#website', 'url' => $home, 'name' => get_bloginfo( 'name' ), 'inLanguage' => get_bloginfo( 'language' ), 'publisher' => array( '@id' => $home . '#person' ), ), $person, ); if ( ! $c['url'] ) { return array( '@context' => 'https://schema.org', '@graph' => $graph ); } $page = array( '@type' => 'WebPage', '@id' => $c['url'] . '#webpage', 'url' => $c['url'], 'name' => $c['title'], 'isPartOf' => array( '@id' => $home . '#website' ), 'inLanguage' => get_bloginfo( 'language' ), ); if ( $c['description'] ) { $page['description'] = $c['description']; } $crumbs = array( array( 'Главная', $home ) ); if ( $c['post'] && 'post' === $c['post']->post_type ) { $cat = get_the_category( $c['post']->ID ); if ( $cat ) { foreach ( array_reverse( get_ancestors( $cat[0]->term_id, 'category' ) ) as $parent ) { $crumbs[] = array( get_cat_name( $parent ), get_category_link( $parent ) ); } $crumbs[] = array( $cat[0]->name, get_category_link( $cat[0] ) ); } } if ( ! is_front_page() && ! is_home() ) { $crumbs[] = array( $c['post'] ? get_the_title( $c['post'] ) : ( $c['name'] ?? $c['title'] ), $c['url'] ); $items = array(); foreach ( $crumbs as $i => list( $name, $url ) ) { $items[] = array( '@type' => 'ListItem', 'position' => $i + 1, 'name' => wp_strip_all_tags( $name ), 'item' => $url ); } $graph[] = array( '@type' => 'BreadcrumbList', '@id' => $c['url'] . '#breadcrumbs', 'itemListElement' => $items ); $page['breadcrumb'] = array( '@id' => $c['url'] . '#breadcrumbs' ); } $graph[] = $page; if ( $c['post'] && 'post' === $c['post']->post_type ) { $article = array( '@type' => 'BlogPosting', '@id' => $c['url'] . '#article', 'headline' => wp_strip_all_tags( get_the_title( $c['post'] ) ), 'datePublished' => get_post_time( 'c', true, $c['post'] ), 'dateModified' => get_post_modified_time( 'c', true, $c['post'] ), 'author' => array( '@id' => $home . '#person' ), 'publisher' => array( '@id' => $home . '#person' ), 'mainEntityOfPage' => array( '@id' => $c['url'] . '#webpage' ), 'inLanguage' => get_bloginfo( 'language' ), ); if ( $c['image'] ) { $article['image'] = $c['image']; } if ( $c['description'] ) { $article['description'] = $c['description']; } $graph[] = $article; } return array( '@context' => 'https://schema.org', '@graph' => $graph ); } /* ===================== Карта сайта (встроенная в WordPress) ===================== */ add_filter( 'wp_sitemaps_post_types', function ( $types ) { return array_intersect_key( $types, array_flip( array( 'post', 'page' ) ) ); } ); // Рубрики — в карту, только если открыты для индексации; метки не нужны. add_filter( 'wp_sitemaps_taxonomies', function ( $taxonomies ) { return seojak_opt( 'noindex_category' ) ? array() : array_intersect_key( $taxonomies, array( 'category' => 1 ) ); } ); add_filter( 'wp_sitemaps_add_provider', function ( $provider, $name ) { return 'users' === $name ? false : $provider; }, 10, 2 ); add_filter( 'wp_sitemaps_posts_query_args', function ( $args ) { $args['meta_query'] = array( 'relation' => 'OR', array( 'key' => '_seojak_noindex', 'compare' => 'NOT EXISTS' ), array( 'key' => '_seojak_noindex', 'value' => '1', 'compare' => '!=' ), ); return $args; } ); // Старый адрес карты сайта и ключ IndexNow. add_action( 'template_redirect', function () { if ( defined( 'AIOSEO_VERSION' ) ) { return; } $path = trim( (string) wp_parse_url( $_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH ), '/' ); if ( in_array( $path, array( 'sitemap.xml', 'sitemap_index.xml', 'post-sitemap.xml', 'page-sitemap.xml' ), true ) ) { wp_safe_redirect( home_url( '/wp-sitemap.xml' ), 301 ); exit; } $key = seojak_indexnow_key(); if ( $key && $path === $key . '.txt' ) { status_header( 200 ); header( 'Content-Type: text/plain; charset=utf-8' ); echo $key; // phpcs:ignore WordPress.Security.EscapeOutput -- ключ из [a-f0-9] exit; } }, 0 ); /* ===================== IndexNow ===================== */ function seojak_indexnow_key(): string { if ( ! seojak_opt( 'indexnow' ) ) { return ''; } $key = (string) seojak_opt( 'indexnow_key' ); if ( ! preg_match( '/^[a-f0-9]{32}$/', $key ) ) { $key = md5( wp_generate_password( 32, true, true ) ); $opts = (array) get_option( SEOJAK_OPTION, array() ); $opts['indexnow_key'] = $key; update_option( SEOJAK_OPTION, $opts ); } return $key; } /** Сообщить Яндексу (и всем участникам IndexNow), что адрес изменился. */ function seojak_indexnow_ping( string $url ) { $key = seojak_indexnow_key(); if ( ! $key || 'production' !== wp_get_environment_type() && ! defined( 'SEOJAK_INDEXNOW_FORCE' ) ) { return; } $response = wp_remote_post( 'https://yandex.com/indexnow', array( 'timeout' => 5, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'body' => wp_json_encode( array( 'host' => wp_parse_url( home_url(), PHP_URL_HOST ), 'key' => $key, 'keyLocation' => home_url( "/$key.txt" ), 'urlList' => array( $url ), ) ), ) ); $code = is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_response_code( $response ); update_option( 'seojak_indexnow_last', array( 'time' => time(), 'url' => $url, 'result' => $code ), false ); } add_action( 'transition_post_status', function ( $new, $old, $post ) { if ( 'publish' !== $new || ! in_array( $post->post_type, array( 'post', 'page' ), true ) || wp_is_post_revision( $post ) ) { return; } // Не чаще раза в 10 минут на запись: «Обновить» жмут по нескольку раз подряд. if ( get_transient( 'seojak_in_' . $post->ID ) ) { return; } set_transient( 'seojak_in_' . $post->ID, 1, 10 * MINUTE_IN_SECONDS ); // После сохранения, чтобы адрес был уже окончательным. add_action( 'shutdown', function () use ( $post ) { if ( ! get_post_meta( $post->ID, '_seojak_noindex', true ) ) { seojak_indexnow_ping( get_permalink( $post->ID ) ); } } ); }, 10, 3 ); /* ===================== Блок в редакторе записи ===================== */ add_action( 'add_meta_boxes', function () { add_meta_box( 'seojak', 'SEO', 'seojak_meta_box', array( 'post', 'page' ), 'normal', 'high' ); } ); function seojak_meta_box( WP_Post $post ) { wp_nonce_field( 'seojak_save', 'seojak_nonce' ); $title = get_post_meta( $post->ID, '_seojak_title', true ); $desc = get_post_meta( $post->ID, '_seojak_description', true ); $noindex = get_post_meta( $post->ID, '_seojak_noindex', true ); $auto = seojak_post_description( get_post( $post->ID ) ); ?>