2026-08-09 Преподаватели

This commit is contained in:
Dekart Deploy Bot 2026-09-09 14:09:32 +00:00
parent 545644682d
commit 78b04cd31c
6 changed files with 358 additions and 5 deletions

View File

@ -75,7 +75,7 @@
/* ── Фильтр ── */
.teachers-filter-wrap {
padding: 0 0 var(--space-5, 40px);
padding: 0;
background: var(--color-bg, #FFFFFF);
}
.teachers-filter {
@ -119,7 +119,8 @@
/* ── Карточки ── */
.teachers-cards {
padding: 0 0 var(--space-6, 48px);
/* Desktop: заметный отступ от hero/фильтра. Мобайл переопределён ниже (24px). */
padding: 64px 0 48px;
background: var(--color-bg, #FFFFFF);
}
.teachers-cards__grid {
@ -379,4 +380,79 @@
.teachers-card:hover {
transform: none;
}
}
/* ════════════════════════════════════════════ */
/* Breadcrumbs в hero (тёмный фон), intro-ссылки, */
/* счётчик отзывов на карточке */
/* ════════════════════════════════════════════ */
/* ── Хлебные крошки в hero (как в school.css, но scoped) ── */
.page-teachers .ds-breadcrumbs {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin: 0 0 16px;
padding: 0;
font-size: 0.875rem;
color: rgba(255, 255, 255, 0.70);
}
.page-teachers .ds-breadcrumbs a {
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
transition: color .2s;
}
.page-teachers .ds-breadcrumbs a:hover {
color: #FFFFFF;
}
.page-teachers .ds-breadcrumbs__current {
color: rgba(255, 255, 255, 0.60);
}
/* ── Контекстные ссылки во вступлении ── */
.teachers-intro__links {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: var(--space-3, 16px);
}
.teachers-intro__link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 9px 18px;
border: 1px solid rgba(37, 99, 235, 0.28);
border-radius: var(--radius-pill, 100px);
background: var(--color-primary-light, #EFF6FF);
color: var(--color-primary, #2563EB);
font-size: 0.875rem;
font-weight: 700;
line-height: 1.3;
text-decoration: none;
transition: background .2s, color .2s, border-color .2s;
}
.teachers-intro__link:hover {
background: var(--color-primary, #2563EB);
border-color: var(--color-primary, #2563EB);
color: #FFFFFF;
}
/* ── Ссылка «N отзывов» на карточке ── */
.teachers-card__reviews {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 6px 14px;
border-radius: var(--radius-pill, 100px);
background: var(--color-primary-light, #EFF6FF);
font-size: 0.8125rem;
font-weight: 700;
color: var(--color-primary, #2563EB);
text-decoration: none;
transition: background .2s, color .2s;
}
.teachers-card__reviews:hover {
background: var(--color-primary, #2563EB);
color: #FFFFFF;
}

View File

@ -1519,6 +1519,112 @@ function add_carbon()
Field::make('textarea', 'questions_answer', 'Текст ответа')
->set_width(70),
)),
// ═══════════════ SEO и конверсия личной страницы (Bento 2026) ═══════════════
Field::make('html', 'teacher_conv_seo_desc', '')
->set_html('<h3 style="margin:18px 0 6px;font-size:15px;">SEO-мета (title/description страницы преподавателя)</h3><p style="color:#64748b;font-size:13px;margin:0 0 12px;">Если title/description пустые — собираются автоматически: предмет, мини-группы 2-6 чел., год экзамена, средний балл, остаток мест.</p>'),
Field::make('text', 'teacher_meta_subject', 'Предмет для мета и заголовков')
->set_width(50)
->set_default_value('')
->set_help_text('Например: «Математика». Пусто — берётся «Направление преподавателя».'),
Field::make('text', 'teacher_exam_label', 'Экзамен/формат (для title)')
->set_width(25)
->set_default_value('ОГЭ/ЕГЭ'),
Field::make('text', 'teacher_exam_year', 'Год экзамена (для title)')
->set_width(25)
->set_default_value('')
->set_help_text('Пусто — следующий год. Пример: 2027.'),
Field::make('text', 'teacher_group_size', 'Размер мини-группы')
->set_width(25)
->set_default_value('2-6'),
Field::make('text', 'teacher_seats_left', 'Осталось мест в группе')
->set_width(25)
->set_default_value('4'),
Field::make('text', 'teacher_avg_score', 'Средний балл выпускников')
->set_width(25)
->set_default_value('')
->set_help_text('Пример: 4,7. Пусто — блок скрыт.'),
Field::make('textarea', 'teacher_meta_title_override', 'Свой title (перекроет авто)')
->set_width(50),
Field::make('textarea', 'teacher_meta_desc_override', 'Свой description (перекроет авто)')
->set_width(50),
// ── Hero: второй CTA и риск ──
Field::make('html', 'teacher_conv_hero_desc', '')
->set_html('<h3 style="margin:18px 0 6px;font-size:15px;">Hero: CTA и снятие риска</h3>'),
Field::make('text', 'teacher_cta_primary', 'Текст главной кнопки')
->set_width(50)
->set_default_value('Записаться на диагностику'),
Field::make('text', 'teacher_schedule_label', 'Текст кнопки «Расписание»')
->set_width(50)
->set_default_value('Посмотреть расписание групп'),
Field::make('text', 'teacher_schedule_url', 'Ссылка «Расписание» (URL)')
->set_width(50)
->set_help_text('Например: https://dekart-school.ru/raspisanie/'),
Field::make('text', 'teacher_risk_note', 'Микро-текст под кнопками')
->set_width(100)
->set_default_value('Перезвоним за 15 минут · Без обязательств'),
// ── Цифры и школа ──
Field::make('html', 'teacher_conv_numbers_desc', '')
->set_html('<h3 style="margin:18px 0 6px;font-size:15px;">Цифры (преподаватель + система)</h3>'),
Field::make('text', 'teacher_school_num', 'Цифра про систему/школу')
->set_width(25)
->set_default_value('100%'),
Field::make('text', 'teacher_school_label', 'Подпись к цифре про систему')
->set_width(75)
->set_default_value('выпускников мини-групп поступили на бюджет'),
Field::make('text', 'teacher_exam_date', 'Дата экзамена для счётчика (YYYY-MM-DD)')
->set_width(50)
->set_help_text('Пример: 2027-05-24. Пусто — блок дефицита без счётчика дней.'),
Field::make('text', 'teacher_scarcity_note', 'Текст полосы дефицита')
->set_width(100)
->set_default_value('Набор в группу идёт каждый месяц, но места ограничены.'),
// ── Сегменты / кейсы / шаги ──
Field::make('html', 'teacher_conv_content_desc', '')
->set_html('<h3 style="margin:18px 0 6px;font-size:15px;">Сегменты, кейсы, «Как проходит занятие»</h3>'),
Field::make('complex', 'teacher_segments', 'Сегменты учеников (карточки)')
->set_layout('tabbed-horizontal')
->set_help_text('3 карточки «Для кого занятия». Пусто — покажутся 3 стандартные.')
->add_fields(array(
Field::make('text', 'segment_title', 'Заголовок карточки')
->set_width(30),
Field::make('textarea', 'segment_text', 'Текст карточки')
->set_width(70),
)),
Field::make('complex', 'teacher_cases', 'Кейсы учеников (табы 9/11 класс)')
->set_layout('tabbed-horizontal')
->set_help_text('Кейсы с результатом. tab: 9 или 11 класс.')
->add_fields(array(
Field::make('text', 'case_tab', 'Таб (9 или 11)')
->set_width(20),
Field::make('textarea', 'case_text', 'История кейса')
->set_width(80),
)),
Field::make('complex', 'teacher_steps', 'Шаги «Как проходит первое занятие»')
->set_layout('tabbed-horizontal')
->set_help_text('Таймлайн: от заявки до обратной связи. Пусто — 5 стандартных шагов.')
->add_fields(array(
Field::make('text', 'step_title', 'Название шага')
->set_width(30),
Field::make('textarea', 'step_text', 'Описание шага')
->set_width(70),
)),
// ── FAQ / доверие ──
Field::make('html', 'teacher_conv_faq_desc', '')
->set_html('<h3 style="margin:18px 0 6px;font-size:15px;">FAQ и доверие</h3>'),
Field::make('complex', 'teacher_faq_extra', 'Доп. вопросы (про мини-группы)')
->set_layout('tabbed-horizontal')
->set_help_text('Вопросы про формат: «Чем мини-группа лучше репетитора?» и т.п. Пусто — 2 стандартных.')
->add_fields(array(
Field::make('textarea', 'faq_q', 'Вопрос')
->set_width(30),
Field::make('textarea', 'faq_a', 'Ответ')
->set_width(70),
)),
Field::make('textarea', 'teacher_license', 'Текст блока «Лицензия и доверие»')
->set_default_value('Частная школа «Декарт» работает по лицензии на образовательную деятельность (бессрочная). Копия лицензии и реквизиты — по запросу у администратора. Рейтинг на Яндекс Картах и 2ГИС формируется из реальных отзывов родителей.')
->set_help_text('Показывается в блоке доверия перед картой.'),
Field::make('checkbox', 'teacher_show_social_reviews', 'Показывать «Отзывы о преподавателе» на странице')
->set_option_value('yes')
->set_default_value('yes'),
));
// ====================================================
@ -2961,7 +3067,6 @@ function needs_swiper(): bool
'page-o-nas.php',
'page-elementary-school.php',
'page-contacts.php',
'page-teachers.php',
'page-camp.php',
];
foreach ($swiper_templates as $tmpl) {
@ -2999,6 +3104,93 @@ function sc_teachers($atts, $content = null)
return ob_get_clean();
}
/**
* Карта «преподаватель число опубликованных отзывов» (один запрос на страницу).
* Учитывает CF association review_teacher и легаси-ключ prepodavatel.
*
* @return array<int,int>
*/
function dekart_teacher_review_counts(): array
{
static $map = null;
if (null !== $map) {
return $map;
}
$map = array();
$review_ids = get_posts(array(
'post_type' => 'reviews',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => true,
));
$link_keys = array( '_review_teacher', 'review_teacher', 'prepodavatel' );
foreach ($review_ids as $review_id) {
$teacher_id = 0;
foreach ($link_keys as $key) {
$raw = get_post_meta((int) $review_id, $key, true);
if (empty($raw)) {
continue;
}
$value = maybe_unserialize($raw);
$ids = array();
if (is_array($value)) {
foreach ($value as $entry) {
if (is_object($entry) && isset($entry->id)) {
$ids[] = (int) $entry->id;
} elseif (is_numeric($entry)) {
$ids[] = (int) $entry;
}
}
} elseif (is_numeric($value)) {
$ids[] = (int) $value;
}
if (! empty($ids)) {
$teacher_id = $ids[0];
break;
}
}
if ($teacher_id > 0) {
$map[ $teacher_id ] = isset($map[ $teacher_id ]) ? $map[ $teacher_id ] + 1 : 1;
}
}
return $map;
}
/**
* Число отзывов конкретного преподавателя.
*
* @param int $teacher_id ID записи CPT teacher.
* @return int
*/
function dekart_teacher_review_count(int $teacher_id): int
{
$map = dekart_teacher_review_counts();
return isset($map[ $teacher_id ]) ? (int) $map[ $teacher_id ] : 0;
}
/**
* OG-изображение 1200×630 для страницы «Преподаватели».
* Глобальный дефолт Yoast (banner-dekart-small.webp 355×250) даёт битый сниппет.
*/
add_filter('wpseo_opengraph_image', 'dekart_teachers_og_image', 10, 1);
function dekart_teachers_og_image($image)
{
if (is_page_template('page-teachers.php')) {
// Занятия для детей в Щёлково (медиатека), 1249×943 — релевантно теме страницы.
$og_url = wp_get_attachment_url(1043);
if ($og_url) {
return $og_url;
}
}
return $image;
}
function fix_page_1_in_pagination($link)
{
$reviews_page_id = 91;

View File

@ -28,6 +28,9 @@ if (empty($teachers_list)) {
return;
}
// ─── Отзывы преподавателей (один запрос, карта tid → count) ───
$teacher_review_counts = function_exists('dekart_teacher_review_counts') ? dekart_teacher_review_counts() : array();
$current_year = (int) current_time('Y');
?>
<section class="teachers-cards section-bg reveal" data-bg="teachers-cards" aria-label="Преподаватели школы">
@ -91,6 +94,21 @@ $current_year = (int) current_time('Y');
<?php endif; ?>
<div class="teachers-card__actions">
<?php $teacher_rc = isset($teacher_review_counts[ $tid ]) ? (int) $teacher_review_counts[ $tid ] : 0; ?>
<?php if ($teacher_rc > 0) :
$rc_mod10 = $teacher_rc % 10;
$rc_mod100 = $teacher_rc % 100;
if ($rc_mod10 === 1 && $rc_mod100 !== 11) {
$rc_word = 'отзыв';
} elseif ($rc_mod10 >= 2 && $rc_mod10 <= 4 && ( $rc_mod100 < 12 || $rc_mod100 > 14 )) {
$rc_word = 'отзыва';
} else {
$rc_word = 'отзывов';
} ?>
<a class="teachers-card__reviews" href="<?php echo esc_url(get_permalink($tid)); ?>">
<?php echo esc_html($teacher_rc . ' ' . $rc_word); ?>
</a>
<?php endif; ?>
<a class="teachers-card__btn btn-write open-bitrix-form" href="#" onclick="return false;">
Записаться к <?php echo esc_html(mb_strtolower($full_name)); ?>
</a>

View File

@ -26,6 +26,9 @@ $current_year = (int) current_time('Y');
$next_year = $current_year + 1;
$school_year = $current_year . '/' . $next_year;
// ─── H1 с гео-ключом (SEO): «Преподаватели школы «Декарт» в Щёлково» ───
$teachers_h1 = trim('Преподаватели школы «' . $branch_name . '» ' . $branch_city_prep);
// ─── Фото: первый преподаватель по добавлению ───
$first_teacher = get_posts(array(
'post_type' => 'teacher',
@ -62,6 +65,18 @@ $hero_stats = array(
<div class="ds-hero__content">
<nav class="ds-breadcrumbs teachers-hero__breadcrumbs" aria-label="Хлебные крошки" itemscope itemtype="https://schema.org/BreadcrumbList">
<span itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a itemprop="item" href="<?php echo esc_url(home_url('/')); ?>"><span itemprop="name">Главная</span></a>
<meta itemprop="position" content="1">
</span>
<span aria-hidden="true">/</span>
<span class="ds-breadcrumbs__current" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<span itemprop="name"><?php echo esc_html($teachers_h1); ?></span>
<meta itemprop="position" content="2">
</span>
</nav>
<?php if ($branch_addr || $branch_city) : ?>
<div class="ds-hero__address">
<svg aria-hidden="true" width="16" height="16"><use href="#icon-location-pin"/></svg>
@ -72,7 +87,7 @@ $hero_stats = array(
</div>
<?php endif; ?>
<h1 id="teachers-hero-title" class="ds-hero__h1">Наши преподаватели</h1>
<h1 id="teachers-hero-title" class="ds-hero__h1"><?php echo esc_html($teachers_h1); ?></h1>
<p class="ds-hero__subtitle">Команда педагогов школы «<?php echo esc_html($branch_name); ?>» <?php echo esc_html($branch_city_prep); ?>: начальная школа, средняя школа, подготовка к школе, продлёнка и летний лагерь. Средний стаж — от 10 лет. Открыт набор на <span class="ds-hero__year"><?php echo esc_html($school_year); ?></span> учебный год.</p>

View File

@ -22,12 +22,34 @@ $intro_text = carbon_get_post_meta($post_id, 'teachers_intro_text');
if (empty($intro_text)) {
return;
}
// ─── Контекстные внутренние ссылки (SEO-перелинковка) ───
$teachers_ctx_targets = array(
'nachalnaya-shkola' => 'Начальная школа',
'podgotovka-k-shkole' => 'Подготовка к школе',
'prodlenka' => 'Продлёнка',
'gorodskoj-lager' => 'Летний лагерь',
);
$teachers_ctx_links = array();
foreach ($teachers_ctx_targets as $slug => $label) {
$pg = get_page_by_path($slug);
if ($pg) {
$teachers_ctx_links[] = array( 'url' => get_permalink($pg), 'label' => $label );
}
}
?>
<section class="teachers-intro section-bg reveal" data-bg="teachers-intro" aria-labelledby="teachers-intro-title">
<div class="container">
<div class="section-header--left">
<h2 id="teachers-intro-title" class="bento-h2"><?php echo esc_html($intro_title); ?></h2>
<p class="bento-p"><?php echo esc_html($intro_text); ?></p>
<?php if (! empty($teachers_ctx_links)) : ?>
<div class="teachers-intro__links">
<?php foreach ($teachers_ctx_links as $ctx_link) : ?>
<a class="teachers-intro__link" href="<?php echo esc_url($ctx_link['url']); ?>"><?php echo esc_html($ctx_link['label']); ?> →</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</section>

View File

@ -14,7 +14,22 @@ if (! defined('ABSPATH')) {
$org_name = get_option('branch_name') ?: carbon_get_theme_option('site_name') ?: 'Частная школа «Декарт»';
$org_url = home_url('/');
$org_logo = carbon_get_theme_option('logo') ? wp_get_attachment_url(carbon_get_theme_option('logo')) : '';
// ─── Logo: филиал → тема (site_logo) → тема (logo legacy) ───
$org_logo = '';
$branch_logo_id = absint(get_option('branch_logo'));
if ($branch_logo_id) {
$branch_logo_url = wp_get_attachment_url($branch_logo_id);
if ($branch_logo_url) {
$org_logo = $branch_logo_url;
}
}
if (! $org_logo && carbon_get_theme_option('site_logo')) {
$org_logo = wp_get_attachment_url(carbon_get_theme_option('site_logo'));
}
if (! $org_logo && carbon_get_theme_option('logo')) {
$org_logo = wp_get_attachment_url(carbon_get_theme_option('logo'));
}
$org_addr = get_option('branch_address') ?: carbon_get_theme_option('site_address') ?: '';
$org_phone = get_option('branch_phone') ?: carbon_get_theme_option('site_phone') ?: '';
@ -71,9 +86,24 @@ foreach ($schema_teachers as $st) {
'worksFor' => array( '@type' => 'EducationalOrganization', 'name' => $org_name ),
'description' => $schema_desc,
);
if ($subj) {
$person['knowsAbout'] = $subj;
}
if ($img && wp_get_attachment_url($img)) {
$person['image'] = wp_get_attachment_url($img);
}
// aggregateRating — только при реальных отзывах (ratingCount >= 1)
$rating_val = carbon_get_post_meta($tid, 'teacher_rating');
$rating_num = is_numeric($rating_val) ? (float) $rating_val : 0.0;
$rating_cnt = function_exists('dekart_teacher_review_count') ? dekart_teacher_review_count($tid) : 0;
if ($rating_num > 0 && $rating_cnt > 0) {
$person['aggregateRating'] = array(
'@type' => 'AggregateRating',
'ratingValue' => number_format($rating_num, 1),
'bestRating' => '5',
'ratingCount' => (int) $rating_cnt,
);
}
$teachers_schema[] = $person;
}