' . esc_html( $text ) . ' ' . esc_html__( 'Подробнее о Pro', 'yasno' ) . '

'; } private static function toggle( $name, $checked, $label, $desc = '' ) { printf( '', esc_attr( $name ), checked( (bool) $checked, true, false ), esc_html( $label ), $desc ? ' ' . wp_kses_post( $desc ) . '' : '' ); } /** Кнопка запуска проверки с полосой прогресса. */ private static function scan_box( $label ) { echo '
'; echo ''; echo ''; echo '
'; } /** Слово в фрагменте текста — выделено. */ private static function context( $context, $word ) { $html = esc_html( $context ); $re = '/' . preg_quote( esc_html( $word ), '/' ) . '/iu'; return (string) preg_replace( $re, '$0', $html, 1 ); } /* --------------------------------------------------------------------- * Обзор * ------------------------------------------------------------------- */ public static function overview() { $r = YSNO_Scan::results(); $words = YSNO_Scan::words(); if ( ! $r['finished'] ) { YSNO_Admin::card_open( __( 'Сайт ещё не проверялся', 'yasno' ), __( 'Плагин прочитает опубликованные страницы, записи, меню и название сайта и найдёт слова латиницей и англицизмы, которые стоит заменить русскими. Проверка идёт по частям и не нагружает сайт.', 'yasno' ), array( 'icon' => 'dashicons-search' ) ); self::scan_box( __( 'Проверить сайт', 'yasno' ) ); YSNO_Admin::card_close(); } else { $counts = array( 'word' => 0, 'latin' => 0, 'abbr' => 0 ); foreach ( $words as $w ) { ++$counts[ $w['type'] ]; } $issues = count( $r['items'] ); echo '
'; echo '
' . (int) $issues . '' . esc_html( _n( 'страница с замечаниями', 'страниц с замечаниями', $issues, 'yasno' ) ) . '
'; echo '

' . ( $issues ? esc_html__( 'Есть слова, которые стоит заменить', 'yasno' ) : esc_html__( 'Замечаний нет', 'yasno' ) ) . '

'; /* translators: 1: дата, 2: число страниц */ echo '

' . esc_html( sprintf( __( 'Проверка %1$s, просмотрено %2$s.', 'yasno' ), wp_date( 'd.m.Y H:i', (int) $r['finished'] ), sprintf( _n( '%d страница', '%d страниц', (int) $r['checked'], 'yasno' ), (int) $r['checked'] ) ) ) . '

'; echo '

' . esc_html__( 'Открыть результаты', 'yasno' ) . '

'; echo '
'; foreach ( array( array( $counts['word'], __( 'англицизмов', 'yasno' ), __( 'есть привычная русская замена', 'yasno' ) ), array( $counts['latin'], __( 'слов латиницей', 'yasno' ), __( 'перевести или внести в исключения как товарный знак', 'yasno' ) ), array( $counts['abbr'], __( 'аббревиатур', 'yasno' ), __( 'расшифровать или оставить, если общеприняты', 'yasno' ) ), ) as $k ) { echo '
' . esc_html( number_format_i18n( $k[0] ) ) . '' . esc_html( $k[1] ) . '' . esc_html( $k[2] ) . '
'; } echo '
'; if ( $words ) { YSNO_Admin::card_open( __( 'Чаще всего встречается', 'yasno' ), '', array( 'icon' => 'dashicons-editor-ul' ) ); echo ''; YSNO_Admin::card_close(); } } /** * Карточки дополнений на вкладке «Обзор». */ do_action( 'ysno/overview' ); YSNO_Admin::card_open( __( 'Что требует закон', 'yasno' ), '', array( 'icon' => 'dashicons-book' ) ); echo '

' . esc_html__( 'Плагин — помощник, а не юридическая консультация. Он не содержит нормативных словарей: спорные слова сверьте со словарями из перечня Правительства РФ.', 'yasno' ) . '

'; YSNO_Admin::card_close(); if ( ! YSNO_Plugin::has_pro_addon() ) { YSNO_Admin::card_open( __( 'Рубрики, SEO-заголовки, Elementor — в Pro', 'yasno' ), __( 'Бесплатная версия проверяет все публичные типы записей — страницы, записи, товары WooCommerce и другие, — а также меню и название сайта. Pro добавляет атрибуты товаров, рубрики и метки, виджеты, заголовки и описания для поисковиков, подписи к картинкам и страницы Elementor, подсвечивает слова прямо в редакторе и заменяет их по всему сайту в один клик.', 'yasno' ), array( 'icon' => 'dashicons-star-filled', 'class' => 'is-muted' ) ); echo '

' . esc_html__( 'Что даёт Pro', 'yasno' ) . '

'; YSNO_Admin::card_close(); } } /* --------------------------------------------------------------------- * Проверка сайта * ------------------------------------------------------------------- */ public static function check() { $r = YSNO_Scan::results(); // phpcs:disable WordPress.Security.NonceVerification.Recommended $view = isset( $_GET['view'] ) && 'pages' === $_GET['view'] ? 'pages' : 'words'; $only = isset( $_GET['item'] ) ? sanitize_text_field( wp_unslash( $_GET['item'] ) ) : ''; // phpcs:enable YSNO_Admin::card_open( __( 'Проверка сайта', 'yasno' ), self::sources_line(), array( 'icon' => 'dashicons-search' ) ); self::scan_box( $r['finished'] ? __( 'Проверить заново', 'yasno' ) : __( 'Проверить сайт', 'yasno' ) ); /** * Кнопки рядом с проверкой (Pro: отчёт CSV). * * @param array $r Результаты. */ do_action( 'ysno/check_actions', $r ); if ( $r['finished'] ) { /* translators: 1: дата, 2: число страниц */ echo '

' . esc_html( sprintf( __( 'Последняя проверка: %1$s, просмотрено страниц: %2$s. Записи перепроверяются сами при сохранении.', 'yasno' ), wp_date( 'd.m.Y H:i', (int) $r['finished'] ), number_format_i18n( (int) $r['checked'] ) ) ) . '

'; } elseif ( $r['started'] ) { echo '

' . esc_html__( 'Прошлая проверка не закончилась — запустите её заново.', 'yasno' ) . '

'; } YSNO_Admin::card_close(); if ( ! $r['finished'] ) { return; } if ( ! $r['items'] ) { echo '
' . esc_html__( 'Слов латиницей и англицизмов из словаря не найдено.', 'yasno' ) . '
'; return; } echo '
'; foreach ( array( 'words' => __( 'По словам', 'yasno' ), 'pages' => __( 'По страницам', 'yasno' ) ) as $key => $label ) { echo '' . esc_html( $label ) . ''; } echo '
'; if ( 'pages' === $view ) { self::by_pages( $r['items'], $only ); } else { self::by_words( YSNO_Scan::words(), $r['items'] ); } } const PER_PAGE = 40; /** Номер страницы списка. */ private static function paged() { return isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended } /** Ссылки на страницы списка. */ private static function pager( $total, array $args ) { $pages = (int) ceil( $total / self::PER_PAGE ); if ( $pages < 2 ) { return; } $cur = min( self::paged(), $pages ); echo ''; } private static function sources_line() { $names = array(); foreach ( YSNO_Sources::all() as $src ) { $names[] = $src['label']; } /* translators: %s: список источников */ return sprintf( __( 'Что проверяется: %s. Изменить — на вкладке «Настройки и исключения».', 'yasno' ), implode( ', ', $names ) ); } private static function by_words( array $words, array $items ) { $type = isset( $_GET['type'] ) ? sanitize_key( wp_unslash( $_GET['type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $counts = array( '' => count( $words ), 'word' => 0, 'latin' => 0, 'abbr' => 0 ); foreach ( $words as $w ) { ++$counts[ $w['type'] ]; } echo '
'; foreach ( array( '' => __( 'Все', 'yasno' ), 'word' => __( 'Англицизмы', 'yasno' ), 'latin' => __( 'Латиница', 'yasno' ), 'abbr' => __( 'Аббревиатуры', 'yasno' ) ) as $key => $label ) { if ( '' !== $key && ! $counts[ $key ] ) { continue; } echo '' . esc_html( $label ) . ' ' . (int) $counts[ $key ] . ''; } echo '
'; if ( '' !== $type ) { $words = array_filter( $words, static function ( $w ) use ( $type ) { return $w['type'] === $type; } ); } $total = count( $words ); $words = array_slice( $words, ( self::paged() - 1 ) * self::PER_PAGE, self::PER_PAGE, true ); echo ''; foreach ( $words as $w ) { $first = $items[ $w['items'][0] ] ?? null; echo ''; echo ''; echo ''; } echo '
' . esc_html__( 'Слово', 'yasno' ) . '' . esc_html__( 'Где встречается', 'yasno' ) . '' . esc_html__( 'Как заменить', 'yasno' ) . '
' . esc_html( $w['word'] ) . '
' . esc_html( YSNO_Checker::type_label( $w['type'] ) ) . '
'; $ctx = ''; foreach ( ( $first['found'] ?? array() ) as $f ) { if ( $f['word'] === $w['word'] ) { $ctx = $f['context']; break; } } if ( '' !== $ctx ) { echo '

' . self::context( $ctx, $w['word'] ) . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- экранировано в context(). } echo '
'; /* translators: 1: число страниц, 2: число употреблений */ echo esc_html( sprintf( __( 'Страниц: %1$s, употреблений: %2$s', 'yasno' ), count( $w['items'] ), $w['count'] ) ); echo '
'; if ( '' !== $w['suggest'] ) { echo '' . esc_html( $w['suggest'] ) . ''; if ( '' !== $w['note'] ) { echo '
' . esc_html( $w['note'] ) . ''; } } elseif ( 'abbr' === $w['type'] ) { echo '' . esc_html__( 'Расшифруйте по-русски при первом упоминании или оставьте, если это общепринятое обозначение.', 'yasno' ) . ''; } else { echo '' . esc_html__( 'Переведите, напишите по-русски рядом или внесите в исключения, если это товарный знак.', 'yasno' ) . ''; } echo '
'; /** * Действия со словом в таблице результатов (Pro: «Заменить на всём сайте»). * * @param array $w Слово. */ do_action( 'ysno/word_actions', $w ); echo '
'; self::pager( $total, array( 'type' => $type ) ); self::pro_note( __( 'Pro заменит слово на всех страницах сразу — с предпросмотром и сохранением прежних версий.', 'yasno' ) ); } private static function by_pages( array $items, $only ) { if ( '' !== $only && isset( $items[ $only ] ) ) { $items = array( $only => $items[ $only ] ); echo '

' . esc_html__( '← Все страницы', 'yasno' ) . '

'; } uasort( $items, static function ( $a, $b ) { return count( $b['found'] ) <=> count( $a['found'] ); } ); $total = count( $items ); $items = array_slice( $items, ( self::paged() - 1 ) * self::PER_PAGE, self::PER_PAGE, true ); echo ''; foreach ( $items as $it ) { echo ''; } echo '
' . esc_html__( 'Страница', 'yasno' ) . '' . esc_html__( 'Что стоит проверить', 'yasno' ) . '
' . esc_html( $it['title'] ) . '
'; if ( $it['edit'] ) { echo '' . esc_html__( 'Редактировать', 'yasno' ) . ''; } if ( $it['view'] ) { echo ' | ' . esc_html__( 'Открыть', 'yasno' ) . ''; } echo '
'; if ( ( $it['latin'] ?? 0 ) >= 0.5 ) { echo '

' . esc_html__( 'Почти весь текст на иностранном языке — нужен русский вариант страницы.', 'yasno' ) . '

'; } echo '
'; $shown = 0; foreach ( $it['found'] as $f ) { if ( ++$shown > 24 ) { /* translators: %d: число слов */ echo '' . esc_html( sprintf( __( 'и ещё %d', 'yasno' ), count( $it['found'] ) - 24 ) ) . ''; break; } $title = '' !== $f['suggest'] ? '→ ' . $f['suggest'] : YSNO_Checker::type_label( $f['type'] ); echo '' . esc_html( $f['word'] ) . ( $f['count'] > 1 ? ' ×' . (int) $f['count'] . '' : '' ) . ''; } echo '
'; self::pager( $total, array( 'view' => 'pages' ) ); } /* --------------------------------------------------------------------- * Настройки и исключения * ------------------------------------------------------------------- */ public static function settings() { $s = YSNO_Settings::all(); echo '
'; wp_nonce_field( 'ysno_save_settings' ); echo ''; YSNO_Admin::card_open( __( 'Исключения', 'yasno' ), __( 'Товарные знаки, знаки обслуживания и фирменные наименования, которые можно писать как зарегистрированы, — по одному в строке. Регистр не важен, можно вносить фразы целиком: «Apple Watch», «Coca-Cola». Название домена сайта в исключениях всегда.', 'yasno' ), array( 'icon' => 'dashicons-shield' ) ); echo ''; echo '

' . esc_html__( 'Сюда же можно внести слова из словаря англицизмов, которые для вашего сайта допустимы.', 'yasno' ) . '

'; YSNO_Admin::card_close(); YSNO_Admin::card_open( __( 'Что искать', 'yasno' ), '', array( 'icon' => 'dashicons-filter' ) ); self::toggle( 'check_words', $s['check_words'], __( 'Англицизмы, у которых есть привычная русская замена', 'yasno' ), __( '— «дедлайн», «шоп», «фидбэк», «кешбэк» и другие', 'yasno' ) ); self::toggle( 'check_latin', $s['check_latin'], __( 'Слова латиницей', 'yasno' ), __( '— «Sale», «Fast delivery», «Shop now»', 'yasno' ) ); self::toggle( 'check_abbr', $s['check_abbr'], __( 'Аббревиатуры латиницей', 'yasno' ), __( '— «SALE», «NEW», «FAQ»', 'yasno' ) ); self::toggle( 'common_terms', $s['common_terms'], __( 'Не считать нарушением общепринятые обозначения', 'yasno' ), __( '— USB, Wi-Fi, GPS, PDF, QR и другие', 'yasno' ) ); echo '
' . esc_html__( 'Список общепринятых обозначений', 'yasno' ) . '

' . esc_html( implode( ', ', YSNO_Dictionary::common_terms() ) ) . '

' . esc_html__( 'Список — ориентир, а не вывод о законности. Если сомневаетесь, расшифруйте обозначение по-русски.', 'yasno' ) . '

'; YSNO_Admin::card_close(); YSNO_Admin::card_open( __( 'Где искать', 'yasno' ), '', array( 'icon' => 'dashicons-admin-page' ) ); echo '
'; foreach ( YSNO_Settings::available_post_types() as $type => $label ) { self::toggle( 'post_types[' . $type . ']', in_array( $type, (array) $s['post_types'], true ), $label ); } self::toggle( 'menus', $s['menus'], __( 'Меню', 'yasno' ) ); self::toggle( 'site_title', $s['site_title'], __( 'Название и описание сайта', 'yasno' ) ); echo '
'; self::pro_note( __( 'Pro проверяет ещё атрибуты товаров, рубрики и метки, виджеты, SEO-заголовки, подписи к картинкам и страницы Elementor.', 'yasno' ) ); YSNO_Admin::card_close(); /** * Дополнительные настройки (сохраняются фильтром ysno/save_settings). * * @param array $s Настройки. */ do_action( 'ysno/settings_fields', $s ); echo '

'; } }