shkola/wp-content/themes/dekart/functions.php
2026-09-13 20:35:16 +00:00

4709 lines
325 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once get_template_directory() . '/inc/template-tags.php';
// Pksh_Data — централизованная загрузка данных для page-podgotovka-k-shkole.php
require_once get_template_directory() . '/inc/class-pksh-data.php';
// ─── Carbon Fields helper (безопасный вызов с fallback) ───
if (! function_exists('crb_get_theme_option')) {
function crb_get_theme_option(string $key, $default = '')
{
return function_exists('carbon_get_theme_option')
? carbon_get_theme_option($key) ?: $default
: $default;
}
}
if (! function_exists('crb_get_post_meta')) {
function crb_get_post_meta(int $post_id, string $key, $default = '')
{
return function_exists('carbon_get_post_meta')
? carbon_get_post_meta($post_id, $key) ?: $default
: $default;
}
}
// ─── CRO-трекинг для data-track кнопок на hero (hero-front.php) ───
add_action('wp_footer', function () {
if (wp_script_is('front-page-script', 'enqueued')) {
wp_add_inline_script('front-page-script', '
document.addEventListener("DOMContentLoaded", function() {
document.querySelectorAll("[data-track]").forEach(function(el) {
el.addEventListener("click", function() {
if (typeof gtag === "function") {
gtag("event", "hero_click", {
"event_category": "engagement",
"event_label": this.dataset.track
});
}
if (typeof ym === "function") {
ym(' . ( defined('YM_COUNTER_ID') ? esc_js(YM_COUNTER_ID) : 'null' ) . ', "reachGoal", this.dataset.track);
}
});
});
});
');
}
}, 100);
/**
* Замена названия сайта из Настроек филиала.
* blogname (Внешний вид → Настроить → Свойства сайта) берётся из branch_name.
*/
add_filter('pre_option_blogname', function ($default) {
if (is_admin()) {
return $default;
}
$branch = get_option('branch_name');
if (! empty($branch)) {
return $branch;
}
return $default;
});
/* --- Удаляем мета из YOASTSEO START--- */
add_filter('wpseo_json_ld_output', '__return_false');
/* --- Удаляем мета из YOASTSEO END --- */
// ────────────────────────────────────────────────────
// SEO: страница КЛАССА (page-class.php) — переопределение
// Yoast-мета из вкладки «📊 SEO» (Настройки Страницы Класса)
// ────────────────────────────────────────────────────
add_filter('wpseo_title', function ($title) {
if (! is_page_template('page-class.php')) {
return $title;
}
$seo_title = carbon_get_post_meta(get_the_ID(), 'class_seo_title');
return $seo_title ?: $title;
}, 20);
add_filter('wpseo_metadesc', function ($desc) {
if (! is_page_template('page-class.php')) {
return $desc;
}
$seo_desc = carbon_get_post_meta(get_the_ID(), 'class_seo_description');
return $seo_desc ?: $desc;
}, 20);
add_filter('wpseo_opengraph_image', 'dekart_class_og_image', 20);
add_filter('wpseo_twitter_image', 'dekart_class_og_image', 20);
function dekart_class_og_image($img)
{
if (! is_page_template('page-class.php')) {
return $img;
}
$og_id = carbon_get_post_meta(get_the_ID(), 'class_seo_og_image');
if ($og_id) {
$src = wp_get_attachment_image_url((int) $og_id, 'full');
if ($src) {
return $src;
}
}
return $img;
}
add_filter('wpseo_robots', function ($robots) {
if (! is_page_template('page-class.php')) {
return $robots;
}
if (carbon_get_post_meta(get_the_ID(), 'class_seo_noindex')) {
$robots['index'] = 'noindex';
}
return $robots;
}, 20);
// ────────────────────────────────────────────────────
// Hero-изображение как OG/Twitter image для главной
// ────────────────────────────────────────────────────
add_filter('wpseo_opengraph_image', 'dekart_front_hero_og_image');
add_filter('wpseo_twitter_image', 'dekart_front_hero_og_image');
function dekart_front_hero_og_image($img)
{
if (! is_front_page()) {
return $img;
}
$foto_id = carbon_get_post_meta(get_the_ID(), 'banner_img_desktop');
if ($foto_id) {
$url = wp_get_attachment_image_url($foto_id, 'full');
if ($url) {
return $url;
}
}
return $img;
}
// ────────────────────────────────────────────────────
// CRO: UTM-метки → cookie (30 дней)
// ────────────────────────────────────────────────────
add_action('init', function () {
if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
return;
}
if (! isset($_COOKIE['utm_source']) && ! empty($_GET['utm_source'])) {
$utm_params = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content' ];
foreach ($utm_params as $param) {
if (! empty($_GET[ $param ])) {
setcookie($param, sanitize_text_field($_GET[ $param ]), time() + 30 * DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
}
}
}
});
// Отключаем Reading Time для страницы Подготовки к школе
add_filter('wpseo_frontend_presenter_classes', 'dekart_disable_reading_time_pksh');
function dekart_disable_reading_time_pksh(array $presenters): array
{
if (is_page_template('page-podgotovka-k-shkole.php')) {
return array_filter($presenters, function ($class) {
return false === strpos($class, 'ReadingTime');
});
}
return $presenters;
}
add_theme_support('title-tag');
/* --- #SEND Review Form --- */
add_action('template_redirect', 'author_archive_redirect');
add_filter('author_link', 'remove_author_pages_link');
// редиректим на главную со страниц авторов
function author_archive_redirect()
{
if (is_author()) {
wp_redirect(home_url(), 301);
exit;
}
}
// удаляем ссылку
function remove_author_pages_link($content)
{
return home_url();
}
/*
/* --- Courses --- */
add_action('init', 'create_courses_hierarchical_taxonomy', 0);
function create_courses_hierarchical_taxonomy()
{
$labels = array(
'name' => _x('Категории курсов', 'taxonomy general name'),
'singular_name' => _x('Категория курсов', 'taxonomy singular name'),
'search_items' => __('Поиск курсов по категории'),
'all_items' => __('Все категории курсов'),
'parent_item' => __('Родительская категория курсов'),
'parent_item_colon' => __('Родителькая категория курсов:'),
'edit_item' => __('Редактировать категорию'),
'update_item' => __('Обновить категорию'),
'add_new_item' => __('Добавить новую категорию'),
'new_item_name' => __('Новая категория'),
'menu_name' => __('Категории курсов'),
);
register_taxonomy('courses', array('course'), array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'courses' ),
));
}
add_action('init', 'create_course');
function create_course()
{
register_post_type(
'course',
array(
'labels' => array(
'name' => 'Курсы',
'singular_name' => 'Курс',
'add_new' => 'Добавить курс',
'add_new_item' => 'Добавить курс',
'edit' => 'Редактировать курс',
'edit_item' => 'Редактировать курс',
'new_item' => 'Новый курс',
'view' => 'Смотреть курс',
'view_item' => 'Смотреть курс',
'search_items' => 'Поиск курсов',
'not_found' => 'Курсы не найдены',
'not_found_in_trash' => 'В корзине нет курсов',
'parent' => 'Родительский курс'
),
'public' => true,
'exclude_from_search' => false,
'menu_position' => 12,
'supports' => array( 'title' ),
'publicly_queryable' => true,
'menu_icon' => 'dashicons-welcome-learn-more',
'taxonomies' => array( 'courses' ),
'has_archive' => false
)
);
}
/* --- ##Cources## --- */
/* --- trainingCategory --- */
add_action('init', 'create_trainingCategory_hierarchical_taxonomy', 0);
function create_trainingCategory_hierarchical_taxonomy()
{
$labels = array(
'name' => _x('Категории', 'Категория обучения учителя'),
'singular_name' => _x('Категория', 'Категория'),
'search_items' => __('Поиск категории'),
'all_items' => __('Все категории'),
'parent_item' => __('Родительская категория'),
'parent_item_colon' => __('Родительская категория:'),
'edit_item' => __('Редактировать категорию'),
'update_item' => __('Обновить категорию'),
'add_new_item' => __('Добавить новую категорию'),
'new_item_name' => __('Новая категория'),
'menu_name' => __('Категории'),
);
register_taxonomy('trainingCategory', array('teacher'), array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => false, // Отключаем перезапись URL
));
}
// ✅ Приоритет 5 — регистрация CPT teacher ДО инициализации Carbon Fields
add_action('init', 'create_teacher', 5);
function create_teacher()
{
register_post_type(
'teacher',
array(
'labels' => array(
'name' => 'Преподаватели',
'singular_name' => 'Преподаватель',
'add_new' => 'Добавить преподавателя',
'add_new_item' => 'Добавить преподавателя',
'edit' => 'Редактировать преподавателя',
'edit_item' => 'Редактировать преподавателя',
'new_item' => 'Новый преподаватель',
'view' => 'Смотреть преподавателя',
'view_item' => 'Смотреть преподавателя',
'search_items' => 'Поиск преподавателей',
'not_found' => 'Преподаватели не найдены',
'not_found_in_trash' => 'В корзине нет преподавателей',
'parent' => 'Родительский преподаватель'
),
'public' => true,
'exclude_from_search' => false,
'menu_position' => 12,
'show_in_rest' => true,
'supports' => array( 'title', 'thumbnail' ),
'publicly_queryable' => true,
'menu_icon' => 'dashicons-admin-users',
'taxonomies' => array( 'subject' ),
'has_archive' => false
)
);
}
/* --- ##Teacher## --- */
/* --- Reviews --- */
add_action('init', 'create_reviews');
function create_reviews()
{
register_post_type(
'reviews',
array(
'labels' => array(
'name' => 'Отзывы',
'singular_name' => 'Отзыв',
'add_new' => 'Добавить отзыв',
'add_new_item' => 'Добавить отзыв',
'edit' => 'Редактировать отзыв',
'edit_item' => 'Редактировать отзыв',
'new_item' => 'Новый отзыв',
'view' => 'Смотреть отзыв',
'view_item' => 'Смотреть отзыв',
'search_items' => 'Поиск отзывов',
'not_found' => 'Отзывы не найдены',
'not_found_in_trash' => 'В корзине нет отзывов',
'parent' => 'Родительский отзыв'
),
'public' => true,
'exclude_from_search' => true,
'menu_position' => 13,
'supports' => array( 'title' ),
'publicly_queryable' => false,
'menu_icon' => 'dashicons-format-status',
'has_archive' => false
)
);
}
use Carbon_Fields\Container;
use Carbon_Fields\Field;
add_action('carbon_fields_register_fields', 'add_carbon');
function add_carbon()
{
// ====================================================
// Настройки темы — перегруппировано 2026-07-27
// ====================================================
Container::make('theme_options', 'Настройка темы ДЕКАРТ')
->add_tab('📞 Контакты', array(
Field::make('text', 'site_name', 'Название учебного центра')
->set_help_text('Используйте только одинарные кавычки в названии.'),
Field::make('text', 'site_phone', 'Телефон'),
Field::make('text', 'site_phone_multi', 'Телефон (многоканальный)'),
Field::make('text', 'site_mail', 'Почта для связи'),
Field::make('text', 'site_index', 'Почтовый индекс'),
Field::make('text', 'site_region', 'Область'),
Field::make('text', 'site_city', 'Город'),
Field::make('text', 'site_address', 'Адрес'),
Field::make('text', 'site_map_link', 'Ссылка на карту'),
Field::make('text', 'site_location', 'Координаты адреса (для Яндекс.Карт)'),
Field::make('text', 'site_lk_url', 'Ссылка на личный кабинет (ЛК)')
->set_default_value('https://lk.dekart-school.ru/')
->set_help_text('URL личного кабинета для учеников/родителей. По умолчанию: https://lk.dekart-school.ru/'),
))
->add_tab('🖥️ Интеграции', array(
Field::make('textarea', 'site_bitrix_script', 'Скрипт Bitrix24 (формы)')
->set_help_text('JS-скрипт для форм Bitrix24 на страницах.'),
Field::make('textarea', 'site_bitrix_vacancies', 'Скрипт Bitrix24 (форма "Связаться с нами")'),
Field::make('textarea', 'site_frame_reviews', 'Код/ссылка iframe для отзывов'),
Field::make('html', 'int_banner_sep', '')
->set_html('<hr><h3>🪧 Плашка со скидкой</h3>'),
Field::make('checkbox', 'check_banner', 'Показывать плашку со скидкой на формах')
->set_option_value('yes'),
Field::make('checkbox', 'check_btn_discont', 'Скрыть кнопку "Стоимость ак. часа со скидкой"')
->set_option_value('yes'),
Field::make('text', 'text_banner', 'Текст в плашке')
->set_default_value('Всем записавшимся до 10 сентября<br class="desktop"><span>скидка 10%</span> на весь учебный год!'),
))
->add_tab('💳 Цены курсов', array(
Field::make('checkbox', 'early_bird_discount', 'Показывать "Скидка за раннюю запись"')
->set_option_value('yes'),
Field::make('html', 'price_hours_sep', '')
->set_html('<hr><h3>💰 Полная стоимость</h3>'),
Field::make('text', 'price_hour1', '1 предмет')
->set_attribute('type', 'number'),
Field::make('text', 'price_hour2', '2 предмета')
->set_attribute('type', 'number'),
Field::make('text', 'price_hour3', '3 предмета')
->set_attribute('type', 'number'),
Field::make('text', 'price_hour4', '4 и более предметов')
->set_attribute('type', 'number'),
Field::make('html', 'price_discount_sep', '')
->set_html('<hr><h3>🏷️ Со скидкой</h3>'),
Field::make('text', 'price_hour_discount1', '1 предмет со скидкой')
->set_attribute('type', 'number'),
Field::make('text', 'price_hour_discount2', '2 предмета со скидкой')
->set_attribute('type', 'number'),
Field::make('text', 'price_hour_discount3', '3 предмета со скидкой')
->set_attribute('type', 'number'),
Field::make('text', 'price_hour_discount4', '4 и более предметов со скидкой')
->set_attribute('type', 'number'),
))
->add_tab('🔗 Соцсети', array(
Field::make('text', 'social_header_wt', 'WhatsApp — ссылка в шапке сайта'),
Field::make('text', 'social_wt', 'WhatsApp — ссылка на странице "Контакты"'),
Field::make('complex', 'social_media', 'Другие соцсети')
->add_fields(array(
Field::make('text', 'social_media_name', 'Название')
->set_width(100),
Field::make('image', 'social_media_img', 'Логотип')
->set_width(20),
Field::make('text', 'social_media_link', 'Ссылка')
->set_width(80),
)),
))
->add_tab('📸 Галерея', array(
Field::make('complex', 'slider_img', 'Изображения учебного центра')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('image', 'img', 'Фотография'),
)),
))
->add_tab('📊 SEO + Схема', array(
Field::make('html', 'schema_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Поля для <strong>JSON-LD микроразметки</strong> Schema.org. Если поле пусто — свойство не добавляется в @graph. Часть полей дублируется в Настройках филиала (для обратной совместимости).</p>'),
Field::make('html', 'schema_org_sep', '')
->set_html('<hr><h3>🏢 Организация</h3>'),
Field::make('text', 'schema_legal_name', 'Юридическое название (legalName)')
->set_width(50)
->set_help_text('Полное юр. название. Например: "ИП Иванов Иван Иванович"'),
Field::make('textarea', 'schema_description', 'Описание организации (description)')
->set_width(100)
->set_help_text('Описание для Schema.org Organization → School. Рекомендуется 12 предложения.'),
Field::make('text', 'schema_postal_code', 'Почтовый индекс (postalCode)')
->set_width(50),
Field::make('text', 'schema_founded', 'Год основания (foundingDate)')
->set_width(50)
->set_help_text('Год или дата: "2015" или "2015-09-01".'),
Field::make('text', 'schema_map_url', 'Ссылка на карту (hasMap)')
->set_width(100)
->set_help_text('Полная ссылка на Яндекс.Карты или Google Maps.'),
Field::make('html', 'schema_hours_sep', '')
->set_html('<hr><h3>🕐 Часы работы</h3>'),
Field::make('complex', 'schema_hours', 'График работы (openingHoursSpecification)')
->set_layout('tabbed-horizontal')
->set_help_text('Режим работы на каждый день недели.')
->add_fields(array(
Field::make('select', 'day_of_week', 'День недели')
->set_width(30)
->set_options(array(
'Monday' => 'Понедельник',
'Tuesday' => 'Вторник',
'Wednesday' => 'Среда',
'Thursday' => 'Четверг',
'Friday' => 'Пятница',
'Saturday' => 'Суббота',
'Sunday' => 'Воскресенье',
)),
Field::make('time', 'opens', 'Открытие')
->set_width(25),
Field::make('time', 'closes', 'Закрытие')
->set_width(25),
Field::make('checkbox', 'day_off', 'Выходной')
->set_width(20)
->set_help_text('Отметьте, если в этот день не работаем.'),
)),
Field::make('html', 'schema_social_sep', '')
->set_html('<hr><h3>🌐 Социальные сети (sameAs)</h3>'),
Field::make('complex', 'schema_social', 'Ссылки на профили')
->set_layout('tabbed-horizontal')
->set_help_text('Ссылки на соцсети для sameAs[] Schema.org.')
->add_fields(array(
Field::make('text', 'platform', 'Платформа')
->set_width(30)
->set_help_text('Например: VK, Telegram, YouTube'),
Field::make('text', 'url', 'Ссылка')
->set_width(70)
->set_help_text('Полный URL профиля.'),
)),
Field::make('html', 'schema_media_sep', '')
->set_html('<hr><h3>🖼️ Медиа</h3>'),
Field::make('media_gallery', 'schema_photos', 'Фотографии филиала (image[])')
->set_help_text('Фотографии интерьера/экстерьера для image[] в микроразметке.'),
Field::make('html', 'schema_area_sep', '')
->set_html('<hr><h3>📍 Регион</h3>'),
Field::make('textarea', 'schema_area_served', 'Города присутствия (areaServed)')
->set_width(100)
->set_help_text('Список городов через запятую. Первый город должен совпадать с городом филиала. Влияет на локальный поиск.'),
Field::make('html', 'schema_content_sep', '')
->set_html('<hr><h3>📝 Контент</h3>'),
Field::make('textarea', 'teachers_block', 'Текст в блоке "Наша команда"'),
));
// (пусто — старые поля banner_img_desktop, banner_img_mobile,
// banner_h1, banner_text, page_breadcrumbs удалены 31 мая 2026)
// ====================================================
// Доп поля для категории Курсов
// ====================================================
Container::make('term_meta', 'Дополнительные поля к Курсам')
->where('term_taxonomy', '=', 'courses')
->show_on_level(1)
->where('term_level', '=', 1)
->add_fields(array(
Field::make('image', 'snippet', 'Изображение для сниппета:')
->set_width(100),
Field::make('image', 'banner_img_desktop', 'Изображение для баннера (десктопная версия)')
->help_text('Размер изображения (желательно) 1920px на 1030px')
->set_width(15),
Field::make('image', 'banner_img_mobile', 'Изображение для баннера (мобильная версия)')
->help_text('Размер изображения (желательно) 390px на 550px')
->set_width(15),
Field::make('textarea', 'banner_content', 'Текстовый контент баннера')
->set_width(70),
Field::make('text', 'title_product', 'Заголовок блока курсов:')
->set_width(100),
));
// ====================================================
// Главная Страница — tabbed-horizontal (как pksh)
// ====================================================
Container::make('post_meta', 'Настройки Главной Страницы')
->where('post_template', '=', 'front-page.php')
->add_tab('🖼️ HERO', array(
Field::make('html', 'front_hero_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:12px;margin:0 0 8px;">Фото — квадратное 600×600, в круглой рамке справа.</p>'),
Field::make('image', 'banner_img_desktop', 'Фото')
->set_width(50),
Field::make('text', 'banner_h1', 'Заголовок H1')
->set_width(50)
->set_attribute('placeholder', 'Частная школа в Щёлково'),
Field::make('html', 'front_hero_stats_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">📊 Показатели статистики</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">Добавьте/удалите карточки через +/-.</p>'),
Field::make('complex', 'hero_stats', 'Показатели')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'stat_number', 'Число')
->set_width(30)
->set_help_text('Например: 12+, 500+, до 12'),
Field::make('text', 'stat_label', 'Подпись')
->set_width(70)
->set_help_text('Например: лет опыта, выпускников'),
)),
))
->add_tab('📄 О ШКОЛЕ', array(
Field::make('text', 'about_school_title', 'Заголовок секции')
->set_default_value('О школе «Декарт»'),
Field::make('rich_text', 'block_text_1', 'Текст секции')
->set_help_text('Основной текст о школе. Используйте абзацы, списки, жирный текст.'),
))
->add_tab('👨‍🏫 Преподаватели', array(
Field::make('html', 'front_teachers_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция преподавателей на главной странице. Выберите преподавателей и отредактируйте текст под заголовком.</p>'),
Field::make('textarea', 'front_teachers_text', 'Текст под заголовком')
->set_default_value('Каждый педагог имеет опыт работы с дошкольниками от 5 лет. Все — действующие учителя начальных классов с высшим образованием.')
->set_help_text('Текст описания перед карточками преподавателей.'),
Field::make('complex', 'front_teachers', 'Преподаватели на странице')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте преподавателей для главной страницы. Если не добавить ни одного — покажутся все.')
->add_fields(array(
Field::make('select', 'teacher_id', 'Выберите преподавателя')
->set_options(array( 0 => '— Выберите преподавателя —' ) + dekart_get_teacher_options())
->set_width(100)
->set_help_text('ФИО и фото подтянутся из карточки преподавателя.'),
)),
))
->add_tab('🎥 Видео-тур', array(
Field::make('text', 'front_video_url', 'Ссылка на видео (YouTube/VK)')
->set_help_text('Вставьте ссылку на видео-экскурсию по школе. Если поле пустое — секция не показывается.'),
))
->add_tab('📅 День из жизни', array(
Field::make('html', 'front_daylife_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Таймлайн «День из жизни ученика». Добавьте 46 этапов с фото и текстом.</p>'),
Field::make('complex', 'front_daylife', 'Этапы дня')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'time', 'Время')
->set_width(20)
->set_help_text('Например: 8:30, 10:00'),
Field::make('text', 'title', 'Заголовок')
->set_width(30)
->set_help_text('Например: Зарядка, Уроки, Обед'),
Field::make('textarea', 'desc', 'Описание')
->set_width(50),
Field::make('image', 'photo', 'Фото')
->set_width(30),
)),
))
->add_tab('🌟 Истории успеха', array(
Field::make('html', 'front_stories_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Персональные кейсы учеников. Добавьте 23 истории с фото.</p>'),
Field::make('complex', 'front_stories', 'Истории')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'student_name', 'Имя ученика')
->set_width(25),
Field::make('text', 'age', 'Возраст / класс')
->set_width(15),
Field::make('textarea', 'problem', 'Проблема / запрос')
->set_width(60),
Field::make('textarea', 'result', 'Результат / достижение')
->set_width(60),
Field::make('image', 'photo', 'Фото (до/после)')
->set_width(30)
->set_help_text('Фото ребёнка (с согласия родителей)'),
)),
))
->add_tab('❓ FAQ', array(
Field::make('complex', 'front_faq', 'Вопросы и ответы')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте частые вопросы родителей. Если не добавить ни одного — секция не покажется.')
->add_fields(array(
Field::make('text', 'faq_question', 'Вопрос')
->set_width(30),
Field::make('textarea', 'faq_answer', 'Ответ')
->set_width(70),
)),
))
->add_tab('💰 Стоимость', array(
Field::make('html', 'front_price_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Карточки стоимости обучения. Добавьте для каждого класса/программы свою карточку с ценой и списком критериев.</p>'),
Field::make('complex', 'price_cards', 'Карточки стоимости')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте карточки для каждого класса (например: «Начальная школа», «Подготовка к школе», «Продлёнка»). Если не добавить ни одной — секция не покажется.')
->add_fields(array(
Field::make('text', 'class_name', 'Название класса / программы')
->set_width(30)
->set_help_text('Например: «Начальная школа (14 класс)»'),
Field::make('text', 'price', 'Стоимость')
->set_width(30)
->set_help_text('Например: «от 25 000 ₽/мес»'),
Field::make('textarea', 'features', 'Критерии (по одному на строку)')
->set_width(40)
->set_help_text('Каждая строка — отдельный критерий с галочкой. Пример для начальной школы:<br><br>Классы до 10 человек<br>Уроки до 13:00, продлёнка до 19:00<br>Продлёнка: обед, полдник, ДЗ, кружки<br>Английский язык с 1 класса<br>Лицензия Минобр МО<br>Помощь с домашними заданиями<br>Спортивные и творческие кружки<br><br>Пример для продлёнки:<br><br>Присмотр с 12:00 до 19:00<br>Горячий обед и полдник<br>Помощь с уроками<br>Прогулки на свежем воздухе<br>Творческие занятия'),
)),
))
->add_tab('📊 Сравнение', array(
Field::make('html', 'front_compare_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Таблица сравнения «Декарт vs Государственная школа». Каждая строка — отдельная запись. Порядок = порядок вывода.</p>'),
Field::make('text', 'compare_header_param', 'Заголовок колонки «Параметр»')
->set_width(33)
->set_default_value('Параметр'),
Field::make('text', 'compare_header_dekart', 'Заголовок колонки «Декарт»')
->set_width(33)
->set_default_value('🎓 «Декарт»'),
Field::make('text', 'compare_header_state', 'Заголовок колонки «Гос. школа»')
->set_width(33)
->set_default_value('📚 Государственная'),
Field::make('complex', 'compare_rows', 'Строки сравнения')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте строки сравнения. Для каждой строки: параметр, текст для Декарт, текст для государственной школы.')
->add_fields(array(
Field::make('text', 'param', 'Параметр')
->set_width(100)
->set_help_text('Название параметра, например: «Число учеников в классе»'),
Field::make('html', 'compare_dekart_sep', '')
->set_html('<hr style="margin:12px 0;border:none;border-top:1px solid #e2e8f0;"><h4 style="margin:4px 0;">🔸 Декарт</h4>'),
Field::make('textarea', 'dekart', 'Текст (Декарт)')
->set_width(100)
->set_help_text('Текст для колонки «Декарт» с галочкой. Например: «До 10 человек — малые классы»'),
Field::make('html', 'compare_state_sep', '')
->set_html('<h4 style="margin:8px 0 4px;">🔸 Государственная школа</h4>'),
Field::make('textarea', 'state', 'Текст (Гос. школа)')
->set_width(100)
->set_help_text('Текст для колонки «Государственная школа» с крестиком. Например: «3035 человек»'),
)),
))
->add_tab('💬 Слово руководителю', array(
Field::make('html', 'front_expert_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Блок цитаты руководителя школы для повышения доверия (E-E-A-T).</p>'),
Field::make('textarea', 'expert_quote_text', 'Текст цитаты')
->set_width(100)
->set_help_text('Основная цитата руководителя. Если поле пустое — блок не показывается.'),
Field::make('text', 'expert_quote_author', 'Имя руководителя')
->set_width(50)
->set_help_text('Оставьте пустым, чтобы использовать имя из Настроек филиала.'),
Field::make('text', 'expert_quote_role', 'Должность / регалии')
->set_width(50)
->set_help_text('Оставьте пустым, чтобы использовать данные из Настроек филиала.'),
Field::make('image', 'expert_quote_photo', 'Фото руководителя')
->set_width(50)
->set_help_text('Оставьте пустым, чтобы использовать фото из Настроек филиала. Рекомендуемый размер: 200×200px.'),
Field::make('text', 'expert_quote_title', 'Заголовок секции')
->set_width(50)
->set_default_value('Почему родители выбирают «Декарт»'),
Field::make('textarea', 'expert_quote_subtitle', 'Подзаголовок секции')
->set_width(50)
->set_default_value('Руководитель школы делится ценностями и подходом к обучению'),
))
->add_tab('👁️ Видимость секций', array(
Field::make('html', 'front_visibility_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Включите/отключите показ секций на главной странице. Секции HERO, КОНТАКТЫ и Sticky CTA — всегда показаны.</p>'),
Field::make('checkbox', 'section_visible_services', '📋 Услуги')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_comparison', '📊 Сравнение')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_faq', '❓ FAQ')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_video', '🎥 Видео-тур')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_daylife', '📅 День из жизни')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_stories', '🌟 Истории успеха')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_cta', '📞 CTA-консультация')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_reviews', '⭐ Отзывы / ссылка')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_teachers', '👨‍🏫 Преподаватели')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_expert_quotes', '💬 Слово руководителю')
->set_option_value('yes')
->set_default_value('yes'),
Field::make('checkbox', 'section_visible_price', '💰 Стоимость (price cards)')
->set_option_value('yes')
->set_default_value('yes'),
));
// ====================================================
// Страница начальной школы — вкладки (как главная)
// ====================================================
// Общий список иконок для карточек (файлы assets/images/icons/*.svg)
$school_icon_options = array(
'users' => '👥 Ученики',
'book' => '📖 Книга',
'book-2' => '📚 Книги',
'school' => '🏫 Школа',
'shield-check' => '🛡️ Защита',
'bulb' => '💡 Идея',
'chart-bar' => '📊 График',
'star' => '⭐ Звезда',
'calendar-check' => '📅 Календарь',
'camera' => '📷 Камера',
'heart' => '❤️ Сердце',
'file-description' => '📄 Документ',
'check' => '✅ Галочка',
'cash' => '💵 Деньги',
'medal-2' => '🏅 Медаль',
'fold-up' => '📂 Папка',
'scroll-hand' => '📜 Свиток',
'icon-education' => '🎓 Образование',
'icon-experience' => '🧠 Опыт',
'icon-certificate' => '📜 Сертификат',
);
// Категории фильтра для карточки преподавателя (из таксономии trainingCategory)
$teacher_category_options = array(
'' => '— Без категории —',
);
$teacher_cats = get_terms(array(
'taxonomy' => 'trainingCategory',
'hide_empty' => false,
));
if (! is_wp_error($teacher_cats) && ! empty($teacher_cats)) {
foreach ($teacher_cats as $teacher_cat) {
$teacher_category_options[ $teacher_cat->slug ] = $teacher_cat->name;
}
}
// Программы из каталога филиала (Настройки → Филиал → «Программы и направления»).
// Преподаватель только выбирает программу; цены/ссылки — из каталога.
$teacher_program_options = array(
'' => '— Выберите программу —',
);
$branch_programs_catalog = (array) get_option('branch_programs', array());
foreach ($branch_programs_catalog as $bp_item) {
$bp_name = trim((string) ( $bp_item['name'] ?? '' ));
if ('' !== $bp_name && ! isset($teacher_program_options[ $bp_name ])) {
$teacher_program_options[ $bp_name ] = $bp_name;
}
}
// Иконки для плашек hero — только из SVG-спрайта (use href="#icon-*")
$school_hero_float_icons = array(
'teacher' => '👨‍🏫 Учитель',
'users' => '👥 Ученики',
'clock' => '🕐 Часы',
'book' => '📖 Книга',
'graduation' => '🎓 Выпуск',
'target' => '🎯 Цель',
'game' => '🎮 Игра',
'gift' => '🎁 Подарок',
'coin' => '🪙 Монета',
'document' => '📄 Документ',
'calendar' => '📅 Календарь',
'check-circle' => '✅ Проверено',
'star-filled' => '⭐ Звезда',
'trending-up' => '📈 Рост',
'location-pin' => '📍 Пин',
);
Container::make('post_meta', 'Настройки Начальной Школы')
->where('post_template', '=', 'page-elementary-school.php')
->add_tab('🖼️ HERO', array(
Field::make('html', 'school_hero_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Главный экран страницы. Фото — квадратное 600×600 (круглая рамка справа). Заголовок H1 и статистика.</p>'),
Field::make('image', 'banner_img_desktop', 'Фото героя')
->set_width(40)
->set_help_text('Квадратное фото 600×600px. Показывается справа в круглой рамке.'),
Field::make('text', 'banner_h1', 'Заголовок H1')
->set_width(60)
->set_help_text('Главный заголовок страницы. Пример: «Начальная школа «Декарт» — 14 классы в Щёлково».'),
Field::make('textarea', 'school_hero_subtitle', 'Подзаголовок')
->set_default_value('Мини-классы до 10 учеников, английский с 1 класса и продлёнка до 19:00. Официальное прикрепление к московской школе — аттестат государственного образца.')
->set_help_text('Короткий текст под заголовком. Можно выделять важное <strong>жирным</strong>.'),
Field::make('html', 'school_hero_stats_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">📊 Показатели статистики</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">3 карточки с цифрами под заголовком.</p>'),
Field::make('complex', 'hero_stats', 'Показатели')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'stat_number', 'Число')
->set_width(30)
->set_help_text('Например: до 10, 14, с 1 кл.'),
Field::make('text', 'stat_label', 'Подпись')
->set_width(70)
->set_help_text('Например: человек в классе, классы начальной школы.'),
)),
Field::make('html', 'school_hero_floats_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">💬 Плашки (мобильные USP + плавающие блоки)</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">До 3 коротких преимуществ. На десктопе — плавающие карточки на фото, на мобильном — под заголовком.</p>'),
Field::make('complex', 'school_hero_floats', 'Плашки')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('select', 'icon', 'Иконка')
->set_width(30)
->set_options($school_hero_float_icons)
->set_help_text('Выберите иконку из списка.'),
Field::make('text', 'text', 'Текст')
->set_width(70)
->set_help_text('Короткая фраза. Пример: «Педагоги с опытом от 10 лет».'),
)),
Field::make('html', 'school_hero_assurance_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">🤝 Пункты доверия</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">Галочки под кнопкой записи. Снимают возражения.</p>'),
Field::make('complex', 'school_hero_assurance', 'Пункты доверия')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'text', 'Текст')
->set_help_text('Пример: «Можно просто прийти и посмотреть».'),
)),
Field::make('html', 'school_cta_label_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">🔘 Текст кнопок записи</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">Один текст для всех кнопок «Записаться» на странице.</p>'),
Field::make('text', 'school_cta_label', 'Текст кнопки записи')
->set_default_value('Записаться на экскурсию')
->set_width(100),
))
->add_tab('🏫 Классы 14', array(
Field::make('html', 'school_classes_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Навигационные карточки классов сразу под героем. Каждая ведёт на свою страницу.</p>'),
Field::make('text', 'school_classes_title', 'Заголовок секции')
->set_default_value('Классы начальной школы 14')
->set_width(50),
Field::make('text', 'school_classes_subtitle', 'Подзаголовок секции')
->set_default_value('Выберите класс — узнайте о программе, особенностях обучения и поступлении.')
->set_width(50),
Field::make('complex', 'school_classes', 'Карточки классов')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте 4 карточки (14 класс). Порядок = порядок вывода.')
->add_fields(array(
Field::make('text', 'num', 'Номер класса')
->set_width(15)
->set_help_text('Цифра на карточке. Пример: 1'),
Field::make('text', 'title', 'Заголовок')
->set_width(30)
->set_help_text('Пример: 1 класс'),
Field::make('textarea', 'text', 'Описание')
->set_width(55)
->set_help_text('12 предложения о программе класса.'),
Field::make('text', 'url', 'Ссылка на страницу класса')
->set_help_text('Начинается с /. Пример: /1-klass/'),
)),
))
->add_tab('⭐ Преимущества', array(
Field::make('html', 'school_advantages_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Карточки преимуществ школы. Цвет рамки и иконка подбираются автоматически по порядку.</p>'),
Field::make('text', 'school_advantages_title', 'Заголовок секции')
->set_default_value('Наши преимущества')
->set_width(100),
Field::make('complex', 'advantages', 'Карточки преимуществ')
->set_layout('tabbed-horizontal')
->set_help_text('Рекомендуется 6 карточек (2 ряда по 3).')
->add_fields(array(
Field::make('text', 'advantages_title', 'Заголовок')
->set_width(50),
Field::make('textarea', 'advantages_text', 'Текст')
->set_width(50),
Field::make('text', 'advantages_link', 'Ссылка «Узнать подробнее» (URL или #)')
->set_help_text('Начинается с /. Оставьте пустым, чтобы убрать ссылку.'),
)),
))
->add_tab('🚪 День открытых дверей', array(
Field::make('html', 'school_openday_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция записи на экскурсию: список пунктов + фото школы. Телефон берётся из настроек филиала.</p>'),
Field::make('text', 'school_open_day_lead', 'Вводный текст')
->set_default_value('Хотите убедиться, что наша школа идеально подойдёт вашему ребёнку?')
->set_width(100),
Field::make('text', 'school_open_day_title', 'Заголовок секции')
->set_default_value('Записаться на день открытых дверей')
->set_width(100),
Field::make('complex', 'school_open_day_points', 'Пункты (что будет на встрече)')
->set_layout('tabbed-horizontal')
->set_help_text('Список с галочками. Пример: «Экскурсия по школе».')
->add_fields(array(
Field::make('text', 'text', 'Пункт'),
)),
Field::make('image', 'school_open_day_photo', 'Фото школы')
->set_width(50)
->set_help_text('Рекомендуемый размер: 550×390px. Справа от текста.'),
Field::make('text', 'school_open_day_footnote', 'Примечание под кнопкой')
->set_default_value('Бесплатно · 30 минут · Без обязательств')
->set_width(50),
))
->add_tab('📋 Персональный план', array(
Field::make('html', 'school_plan_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция «Персональный план развития»: основной текст + 3 выгоды + плашка с цифрой.</p>'),
Field::make('text', 'school_plan_title', 'Заголовок секции')
->set_default_value('Персональный план развития для каждого ученика')
->set_width(100),
Field::make('textarea', 'block_text_1', 'Основной текст секции')
->set_help_text('12 абзаца о персональном плане развития.'),
Field::make('complex', 'school_plan_benefits', 'Выгоды (3 пункта с галочками)')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'text', 'Текст выгоды'),
)),
Field::make('html', 'school_plan_badge_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">🔢 Плашка на фото</h3>'),
Field::make('text', 'school_plan_badge_num', 'Число в плашке')
->set_default_value('до 10')
->set_width(30),
Field::make('text', 'school_plan_badge_label', 'Подпись плашки')
->set_default_value('учеников в классе')
->set_width(70),
))
->add_tab('📚 Программа и кружки', array(
Field::make('html', 'school_program_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Программа обучения по классам + список кружков.</p>'),
Field::make('text', 'school_program_title', 'Заголовок секции')
->set_default_value('Программа обучения 14 классов')
->set_width(50),
Field::make('text', 'school_program_subtitle', 'Подзаголовок секции')
->set_default_value('Программы «Перспектива» и система Занкова, углублённый английский — и кружки для всестороннего развития.')
->set_width(50),
Field::make('complex', 'school_programs', 'Программа по классам')
->set_layout('tabbed-horizontal')
->set_help_text('4 карточки (14 класс).')
->add_fields(array(
Field::make('text', 'title', 'Класс')
->set_width(20)
->set_help_text('Пример: 1 класс'),
Field::make('textarea', 'items', 'Предметы')
->set_width(80)
->set_help_text('Список предметов через запятую.'),
)),
Field::make('complex', 'school_clubs', 'Кружки и доп. занятия')
->set_layout('tabbed-horizontal')
->set_help_text('Каждый кружок — отдельная запись. Пример: «Ментальная арифметика».')
->add_fields(array(
Field::make('text', 'name', 'Название кружка'),
)),
))
->add_tab('🧩 Проблемы родителей', array(
Field::make('html', 'school_problems_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция «Решаем главные проблемы»: карточки «проблема → решение».</p>'),
Field::make('text', 'school_problems_title', 'Заголовок секции')
->set_default_value('Решаем главные проблемы родителей и учеников в начальной школы')
->set_width(50),
Field::make('text', 'school_problems_subtitle', 'Подзаголовок секции')
->set_default_value('Мы продумали каждую деталь обучения, чтобы вы были спокойны за ребёнка.')
->set_width(50),
Field::make('complex', 'school_problems', 'Карточки проблем')
->set_layout('tabbed-horizontal')
->set_help_text('5 карточек. Заголовок — тревога родителя, текст — как школа её решает.')
->add_fields(array(
Field::make('text', 'title', 'Заголовок')
->set_width(40),
Field::make('textarea', 'text', 'Текст')
->set_width(60),
)),
))
->add_tab('🌱 Адаптация', array(
Field::make('html', 'school_adaptation_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция «Мягкая адаптация первоклассника» — 4 карточки с иконками.</p>'),
Field::make('text', 'school_adaptation_title', 'Заголовок секции')
->set_default_value('Мягкая адаптация первоклассника')
->set_width(50),
Field::make('text', 'school_adaptation_subtitle', 'Подзаголовок секции')
->set_default_value('Первый класс — важный этап. Мы делаем его спокойным и радостным для ребёнка и родителей.')
->set_width(50),
Field::make('complex', 'school_adaptation', 'Карточки адаптации')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('select', 'icon', 'Иконка')
->set_width(30)
->set_options($school_icon_options),
Field::make('text', 'title', 'Заголовок')
->set_width(70),
Field::make('textarea', 'text', 'Текст')
->set_width(100),
)),
))
->add_tab('🛡️ Безопасность', array(
Field::make('html', 'school_safety_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция «Безопасность ребёнка» — 4 карточки с иконками.</p>'),
Field::make('text', 'school_safety_title', 'Заголовок секции')
->set_default_value('Безопасность ребёнка — наш приоритет')
->set_width(50),
Field::make('text', 'school_safety_subtitle', 'Подзаголовок секции')
->set_default_value('Мы создали среду, в которой дети чувствуют себя защищённо — и могут спокойно учиться.')
->set_width(50),
Field::make('complex', 'school_safety', 'Карточки безопасности')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('select', 'icon', 'Иконка')
->set_width(30)
->set_options($school_icon_options),
Field::make('text', 'title', 'Заголовок')
->set_width(70),
Field::make('textarea', 'text', 'Текст')
->set_width(100),
)),
))
->add_tab('⚖️ Сравнение с госшколой', array(
Field::make('html', 'school_compare_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Таблица «Декарт vs Государственная школа» + яркая цифра. Вводный текст — «лид» справа от заголовка.</p>'),
Field::make('text', 'school_compare_title', 'Заголовок секции')
->set_default_value('Частная школа «Декарт» или государственная?')
->set_width(100),
Field::make('textarea', 'block_text_2', 'Вводный текст (лид)')
->set_help_text('Короткий абзац справа от заголовка «Частная школа или государственная?».'),
Field::make('html', 'school_compare_hl_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">🔆 Яркая цифра (highlight)</h3>'),
Field::make('text', 'school_compare_hl_num', 'Цифра')
->set_default_value('до 30')
->set_width(20),
Field::make('text', 'school_compare_hl_text', 'Пояснение к цифре')
->set_default_value('учеников на одного учителя в государственной школе — и персональное внимание каждому ребёнку в «Декарт»')
->set_width(80),
Field::make('complex', 'school_compare_rows', 'Строки таблицы')
->set_layout('tabbed-horizontal')
->set_help_text('5 строк. Для каждой: критерий, значение в госшколе, значение в «Декарт».')
->add_fields(array(
Field::make('text', 'criteria', 'Критерий')
->set_width(100),
Field::make('text', 'gov', 'Государственная школа')
->set_width(50),
Field::make('text', 'private', 'Школа «Декарт»')
->set_width(50),
)),
))
->add_tab('🤝 Гарантии', array(
Field::make('html', 'school_guarantees_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция «На что вы можете рассчитывать» — 4 карточки-гарантии.</p>'),
Field::make('text', 'school_guarantees_title', 'Заголовок секции')
->set_default_value('На что вы можете рассчитывать')
->set_width(50),
Field::make('text', 'school_guarantees_subtitle', 'Подзаголовок секции')
->set_default_value('Мы отвечаем за качество обучения и комфорт вашего ребёнка.')
->set_width(50),
Field::make('complex', 'school_guarantees', 'Карточки гарантий')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('select', 'icon', 'Иконка')
->set_width(30)
->set_options($school_icon_options),
Field::make('text', 'title', 'Заголовок')
->set_width(70),
Field::make('textarea', 'text', 'Текст')
->set_width(100),
)),
))
->add_tab('📈 Результаты', array(
Field::make('html', 'school_results_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция «Результаты наших учеников» — 4 цифры с подписями.</p>'),
Field::make('text', 'school_results_title', 'Заголовок секции')
->set_default_value('Результаты наших учеников')
->set_width(50),
Field::make('text', 'school_results_subtitle', 'Подзаголовок секции')
->set_default_value('Цифры, которые говорят сами за себя: ваши дети опережают сверстников и учатся с удовольствием.')
->set_width(50),
Field::make('complex', 'school_results', 'Цифры-результаты')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'num', 'Цифра')
->set_width(30)
->set_help_text('Пример: 98%, до 10, 612 мес'),
Field::make('text', 'label', 'Подпись')
->set_width(70),
)),
))
->add_tab('🚀 Как поступить', array(
Field::make('html', 'school_steps_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция «Как поступить» — 4 шага поступления.</p>'),
Field::make('text', 'school_steps_title', 'Заголовок секции')
->set_default_value('Как поступить в начальную школу «Декарт»')
->set_width(50),
Field::make('text', 'school_steps_subtitle', 'Подзаголовок секции')
->set_default_value('Всего 4 простых шага — и ваш ребёнок учится в мини-классе с индивидуальным подходом.')
->set_width(50),
Field::make('complex', 'school_steps', 'Шаги поступления')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'num', 'Номер')
->set_width(15)
->set_help_text('Формат 01, 02, 03, 04'),
Field::make('select', 'icon', 'Иконка')
->set_width(25)
->set_options($school_icon_options),
Field::make('text', 'title', 'Заголовок')
->set_width(60),
Field::make('textarea', 'text', 'Текст')
->set_width(100),
)),
))
->add_tab('📸 Галерея', array(
Field::make('html', 'school_gallery_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Мини-галерея 24 фото. Если пусто — секция не показывается.</p>'),
Field::make('complex', 'school_gallery', 'Фотографии')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('image', 'photo', 'Фотография')
->set_help_text('Рекомендуемый размер: 868×1156px (портретная).'),
)),
))
->add_tab('❓ FAQ', array(
Field::make('html', 'school_faq_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Вопросы и ответы. Разметка FAQPage JSON-LD генерируется автоматически. Если пусто — секция скрыта.</p>'),
Field::make('text', 'school_faq_title', 'Заголовок секции')
->set_default_value('Часто задаваемые вопросы')
->set_width(50),
Field::make('text', 'school_faq_subtitle', 'Подзаголовок секции')
->set_default_value('Ответы на главные вопросы родителей о начальной школе «Декарт»')
->set_width(50),
Field::make('complex', 'questions', 'Вопросы и ответы')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('textarea', 'questions_question', 'Вопрос')
->set_width(30),
Field::make('textarea', 'questions_answer', 'Ответ')
->set_width(70),
)),
))
->add_tab('✅ Готовность к школе', array(
Field::make('html', 'school_readiness_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Секция-ссылка на интерактивный тест готовности к школе.</p>'),
Field::make('text', 'school_readiness_title', 'Заголовок')
->set_default_value('Готов ли ребёнок к школе?')
->set_width(50),
Field::make('textarea', 'school_readiness_text', 'Текст')
->set_default_value('Пройдите короткий тест из 12 вопросов и получите персональные рекомендации по подготовке ребёнка к 1 классу. Это бесплатно и займёт 5 минут.')
->set_width(50),
Field::make('text', 'school_readiness_btn_label', 'Текст кнопки')
->set_default_value('Пройти тест')
->set_width(30),
Field::make('text', 'school_readiness_btn_url', 'URL кнопки')
->set_default_value('/test-gotovnost-k-shkole/')
->set_width(30),
Field::make('text', 'school_readiness_time', 'Подпись «сколько времени»')
->set_default_value('~5 минут · 12 вопросов · Бесплатно')
->set_width(40),
))
->add_tab('🏁 CTA и фото', array(
Field::make('html', 'school_cta_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Акцентная CTA-секция с фото (после сравнения). Фото широкое.</p>'),
Field::make('text', 'school_accent_eyebrow', 'Надзаголовок (eyebrow)')
->set_default_value('Приходите знакомиться')
->set_width(50),
Field::make('text', 'school_accent_title', 'Заголовок')
->set_default_value('Увидеть школу — лучше, чем прочитать о ней')
->set_width(50),
Field::make('textarea', 'school_accent_text', 'Текст')
->set_default_value('Посмотрите классы, познакомьтесь с педагогами и почувствуйте атмосферу лично. Экскурсия бесплатна, длится 30 минут и ни к чему не обязывает.'),
Field::make('image', 'school_accent_photo', 'Фото для CTA-секции')
->set_help_text('Широкий баннер. Рекомендуемый размер: 1295×454px.'),
Field::make('html', 'school_cta_why_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">📝 Секция «Почему частная школа»</h3>'),
Field::make('text', 'school_why_private_title', 'Заголовок секции «Почему частная школа»')
->set_default_value('Почему начальная частная школа «Декарт», а не государственная?')
->set_width(100),
Field::make('textarea', 'school_why_private_text', 'Текст секции «Почему частная школа»')
->set_help_text('Абзац с аргументами в пользу частной школы.'),
Field::make('image', 'school_why_private_photo', 'Фото ученика')
->set_width(50)
->set_help_text('Рекомендуемый размер: 273×285px.'),
Field::make('html', 'school_cta_final_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">🏁 Финальный CTA</h3>'),
Field::make('text', 'school_final_cta_title', 'Заголовок финального CTA')
->set_default_value('Готовы записать ребёнка в начальную школу?')
->set_width(50),
Field::make('textarea', 'school_final_cta_text', 'Текст финального CTA')
->set_default_value('Позвоните нам или оставьте заявку — ответим на все вопросы и пригласим на экскурсию.')
->set_width(50),
Field::make('html', 'school_cta_family_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">🔗 Перелинковка (семейные классы)</h3>'),
Field::make('complex', 'school_family_links', 'Ссылки-рекомендации')
->set_layout('tabbed-horizontal')
->set_help_text('До 3 ссылок в блоке «Узнайте больше о форматах обучения».')
->add_fields(array(
Field::make('text', 'text', 'Текст ссылки')
->set_width(70),
Field::make('text', 'url', 'Ссылка (URL или #якорь)')
->set_width(30),
)),
))
->add_tab('📝 Прочее', array(
Field::make('html', 'school_misc_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Служебные поля. Обычно не требуют редактирования.</p>'),
Field::make('textarea', 'teachers_block', 'Текст в блоке «Наша команда»'),
Field::make('textarea', 'block_text_3', 'Текст для блока «Семейные классы в Щёлково» (зарезервировано)'),
));
// ====================================================
// Страница КЛАССА начальной школы (page-class.php)
// ====================================================
Container::make('post_meta', '🏫 Настройки Страницы Класса')
->where('post_template', '=', 'page-class.php')
->add_tab('🖼️ HERO', array(
Field::make('html', 'class_hero_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Главный экран страницы класса. Заголовок H1 — под поисковый запрос «частная школа [N] класс [город]». Фото — горизонтальное 1200×800px, WebP/JPEG, до 2 МБ.</p>'),
Field::make('text', 'class_hero_title', 'Заголовок H1')
->set_width(60)
->set_default_value('Частная школа 1 класс в Щёлково')
->set_help_text('Главный заголовок. Пример: «Частная школа 2 класс в Щёлково: набор 2026/27».'),
Field::make('image', 'class_hero_image', 'Фото героя')
->set_width(40)
->set_help_text('Горизонтальное фото 1200×800px. Показывается в правой части героя.'),
Field::make('textarea', 'class_hero_subtitle', 'Подзаголовок')
->set_default_value('Мини-класс до 15 учеников, учитель начальных классов и английский каждый день. Официальное прикрепление к московской школе — аттестат государственного образца.')
->set_help_text('Короткий текст под заголовком (23 предложения). Можно выделять важное <strong>жирным</strong>.'),
Field::make('html', 'class_hero_btn_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">🔘 Кнопка записи</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">Единая CTA на странице. Ведите на форму записи или мессенджер.</p>'),
Field::make('text', 'class_hero_btn_text', 'Текст кнопки')
->set_width(50)
->set_default_value('Записаться на пробный урок')
->set_help_text('Конверсионный интент: «Записаться на пробный урок» / «Записаться на экскурсию».'),
Field::make('text', 'class_hero_btn_url', 'Ссылка кнопки')
->set_width(50)
->set_default_value('#form')
->set_help_text('URL формы записи, страницы контактов или мессенджера. Начинается с /.'),
Field::make('html', 'class_hero_stats_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">📊 Показатели статистики</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">3 карточки с цифрами под заголовком.</p>'),
Field::make('complex', 'class_hero_stats', 'Показатели')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'stat_number', 'Число')
->set_width(30)
->set_help_text('Например: до 15, 5, 8:30.'),
Field::make('text', 'stat_label', 'Подпись')
->set_width(70)
->set_help_text('Например: человек в классе, дней в неделю, старт занятий.'),
)),
))
->add_tab('📚 Программа', array(
Field::make('html', 'class_program_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Программа обучения класса: описание + список предметов (68 карточек) + галерея фото класса. Рекомендуемый объём текста 200300 символов.</p>'),
Field::make('text', 'class_program_title', 'Заголовок блока')
->set_default_value('Программа обучения'),
Field::make('rich_text', 'class_program_text', 'Описание программы')
->set_help_text('23 абзаца об учебном плане класса. Укажите ключевые предметы и особенности.'),
Field::make('complex', 'class_program_items', 'Предметы')
->set_layout('tabbed-horizontal')
->set_help_text('Рекомендуется 68 карточек. Иконка — название из набора (book, math, pen, globe, music, art, sport, science, computer, shield).')
->add_fields(array(
Field::make('text', 'item_icon', 'Иконка')
->set_width(20)
->set_help_text('book, math, pen, globe, music, art, sport, science, computer, shield.'),
Field::make('text', 'item_title', 'Название предмета')
->set_width(40),
Field::make('textarea', 'item_text', 'Краткое описание')
->set_width(40)
->set_help_text('12 предложения. Что изучают, результат.'),
)),
Field::make('html', 'class_gallery_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">📸 Фото класса</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">34 фото 800×600px, WebP/JPEG. Выводятся галереей.</p>'),
Field::make('complex', 'class_program_gallery', 'Галерея')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('image', 'gallery_image', 'Фото')
->set_help_text('Рекомендуемый размер 800×600px.'),
)),
))
->add_tab('👨‍🏫 Преподаватели', array(
Field::make('html', 'class_teachers_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Учителя класса. Выберите преподавателей из картотеки «Преподаватели». Если ничего не выбрано — покажутся все преподаватели школы.</p>'),
Field::make('text', 'class_teachers_title', 'Заголовок блока')
->set_default_value('Преподаватели класса'),
Field::make('text', 'class_teachers_subtitle', 'Подзаголовок')
->set_default_value('Учитель начальных классов и педагоги-предметники, которые ведут уроки в этом классе')
->set_help_text('Короткий текст под заголовком.'),
Field::make('association', 'class_teachers', 'Преподаватели')
->set_types(array(
array('type' => 'post', 'post_type' => 'teacher'),
))
->set_max(12)
->set_help_text('Выберите учителей для этого класса. Пусто = показать всех преподавателей школы.'),
))
->add_tab('🗓 Расписание и стоимость', array(
Field::make('html', 'class_schedule_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Расписание занятий класса и стоимость обучения. Если стоимость не заполнена — подтянется глобальная цена из настроек филиала.</p>'),
Field::make('text', 'class_schedule_title', 'Заголовок блока')
->set_default_value('Расписание и стоимость'),
Field::make('complex', 'class_schedule', 'Расписание')
->set_layout('tabbed-horizontal')
->set_help_text('510 строк: день недели, время, предмет.')
->add_fields(array(
Field::make('select', 'schedule_day', 'День')
->set_width(25)
->add_options(array(
'Понедельник' => 'Понедельник',
'Вторник' => 'Вторник',
'Среда' => 'Среда',
'Четверг' => 'Четверг',
'Пятница' => 'Пятница',
)),
Field::make('text', 'schedule_time', 'Время')
->set_width(25)
->set_help_text('Например: 9:009:45.'),
Field::make('text', 'schedule_subject', 'Предмет')
->set_width(50)
->set_help_text('Например: Математика.'),
)),
Field::make('html', 'class_price_sep', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:0 0 8px;font-size:13px;color:#475569;">💰 Стоимость</h3><p style="color:#94a3b8;font-size:12px;margin:0 0 8px;">Оставьте пустым, чтобы взять глобальную цену из настроек филиала.</p>'),
Field::make('text', 'class_price_text', 'Стоимость обучения')
->set_width(60)
->set_help_text('Например: 45 000 ₽ / месяц. Пусто = глобальная цена филиала.'),
Field::make('text', 'class_price_note', 'Примечание к цене')
->set_width(40)
->set_help_text('Например: «включая питание и продлёнку».'),
))
->add_tab('💬 Отзывы', array(
Field::make('html', 'class_reviews_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Отзывы родителей о школе. Выберите из картотеки «Отзывы». Если ничего не выбрано — покажутся последние отзывы школы.</p>'),
Field::make('text', 'class_reviews_title', 'Заголовок блока')
->set_default_value('Отзывы родителей'),
Field::make('association', 'class_reviews', 'Отзывы')
->set_types(array(
array('type' => 'post', 'post_type' => 'reviews'),
))
->set_max(6)
->set_help_text('Выберите 36 отзывов. Пусто = последние отзывы из картотеки.'),
))
->add_tab('❓ FAQ', array(
Field::make('html', 'class_faq_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Вопросы-ответы о классе. Выводятся на странице и в микроразметке FAQPage (для поисковой выдачи). Рекомендуется 46 пар.</p>'),
Field::make('text', 'class_faq_title', 'Заголовок блока')
->set_default_value('Часто задаваемые вопросы'),
Field::make('complex', 'class_faq_items', 'Вопросы')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('textarea', 'faq_question', 'Вопрос')
->set_width(50),
Field::make('textarea', 'faq_answer', 'Ответ')
->set_width(50)
->set_help_text('Развёрнутый ответ 13 предложения.'),
)),
))
->add_tab('📊 SEO', array(
Field::make('html', 'class_seo_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">SEO-поля страницы класса. Основные title/description задаёт Yoast SEO (блок ниже редактора). Эти поля переопределяют их и используются в Schema.org разметке. Заполнять по желанию — при пустых значениях берутся Yoast/стандартные.</p>'),
Field::make('text', 'class_seo_title', 'SEO-заголовок (title)')
->set_width(60)
->set_help_text('Пример: «Частная школа 3 класс в Щёлково — набор 2026/27». 5060 символов.'),
Field::make('textarea', 'class_seo_description', 'SEO-описание (meta description)')
->set_width(40)
->set_help_text('140160 символов, с ключевым запросом и призывом.'),
Field::make('image', 'class_seo_og_image', 'Open Graph картинка')
->set_help_text('1200×630px, JPEG/PNG. Для соцсетей.'),
Field::make('checkbox', 'class_seo_noindex', 'Не индексировать (noindex)')
->set_help_text('Включите, если страница-черновик или дубль.'),
));
// ====================================================
// Доп поля для страницы "ВСЕ КУРСЫ"
// ====================================================
Container::make('post_meta', 'Настройки Страницы Все Курсы')
->where('post_template', '=', 'page-course.php')
->add_fields(array(
Field::make('image', 'banner_img_desktop', 'Изображение для баннера (десктопная версия)')
->help_text('Размер изображения (желательно) 1920px на 440px')
->set_width(15),
Field::make('image', 'banner_img_mobile', 'Изображение для баннера (мобильная версия)')
->help_text('Размер изображения (желательно) 450px на 225px')
->set_width(15),
));
// ====================================================
// Доп поля для страницы "ВАКАНСИИ"
// ====================================================
Container::make('post_meta', 'Настройки Страницы ВАКАНСИИ')
->where('post_template', '=', 'page-vacancies.php')
->add_fields(array(
Field::make('complex', 'vacancies', 'Вакансии учебного центра:')
->add_fields(array(
Field::make('text', 'vacancie_name', 'Название вакансии:'),
Field::make('text', 'vacancie_link', 'Ссылка на страницу вакансии:'),
))
));
// ====================================================
// Доп поля для страницы "КОНТАКТЫ"
// ====================================================
Container::make('post_meta', 'Настройки Страницы Контакты')
->where('post_template', '=', 'page-contacts.php')
->add_fields(array(
Field::make('image', 'img_school', 'Изображение школы:')
));
// ====================================================
// Доп поля для категории (ПРЕДМЕТ)
// ====================================================
Container::make('term_meta', 'Category Properties')
->where('term_taxonomy', '=', 'subject')
->show_on_level(1)
->where('term_level', '=', 1)
->add_fields(array(
Field::make('text', 'subject_h1', 'Заголовок H1:')
->set_width(100)
));
// ====================================================
// Доп поля для сущностей reviews (ОТЗЫВ)
// ====================================================
Container::make('post_meta', 'Поля для отзыва')
->where('post_type', '=', 'reviews')
->add_fields(array(
Field::make('radio', 'review_type', 'Тип отзыва')
->set_options([
'camp' => 'О лагере',
'school' => 'О школе',
'primary' => 'О начальной школе',
])
->set_default_value('school')
->set_help_text('Выберите тип отзыва'),
Field::make('image', 'review_avatar', 'Автар:')
->set_width(15),
Field::make('text', 'review_star', 'Оценка:')
->set_attribute('type', 'number'),
Field::make('textarea', 'review_text', 'Текст отзыва:')
->set_width(100),
Field::make('date', 'review_date', 'Дата отзыва')
->set_storage_format('d.m.Y')
->set_input_format('d.m.Y', 'd.m.Y')
->set_picker_options(array(
'altInput' => false,
))
->set_help_text('Дата отзыва. Формат: ДД.ММ.ГГГГ'),
Field::make('association', 'review_teacher', 'Выберите преподавателя:')
->set_width(32)
->set_types(array(
array(
'type' => 'post',
'post_type' => 'teacher',
)
))
->set_required(true)
->set_max(1)
->help_text('выбирается только один'),
Field::make('checkbox', 'review_show_on_front', 'Показывать на главной')
->set_option_value('yes')
->set_help_text('Отметьте, чтобы отзыв отображался в секции отзывов на главной странице'),
));
// ====================================================
// Доп поля для записей teacher
// ====================================================
Container::make('post_meta', 'Поля для преподавателя')
->where('post_type', '=', 'teacher')
->add_tab('🎬 Hero', array(
Field::make('html', 'teacher_hero_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Фото и текст под заголовком на личной странице преподавателя. Если текст пуст — подставляется автоматически (направление + мини-группа + экзамен + школа и город).</p>'),
Field::make('image', 'teacher_avatar', 'Фото преподавателя (hero/карточка):')
->set_width(30),
Field::make('textarea', 'teacher_hero_subtitle', 'Текст под H1 (subtitle)')
->set_width(100)
->set_help_text('Пример: «Учитель начальных классов — мини-группы 2-6 чел. · ОГЭ/ЕГЭ · школа «Декарт» в Щёлково». Пусто — соберётся автоматически.'),
Field::make('text', 'teacher_rating', 'Оценка преподавателя (для hero и микроразметки)')
->set_width(25)
->set_default_value('5')
->set_help_text('Пример: <strong>5</strong> или <strong>4,5</strong>. Звёзды в hero и aggregateRating в Schema.org.'),
Field::make('text', 'teacher_group_size', 'Размер мини-группы (для hero, курсов, шагов и CTA)')
->set_width(25)
->set_default_value('2-6')
->set_help_text('Пример: <strong>2-6</strong>. Участвует в hero, курсах, шагах, финальном CTA и микроразметке — заполняйте всегда.'),
))
->add_tab('👤 Профиль и данные', array(
Field::make('text', 'teacher_direction', 'Направление преподавателя (текст в под фото):'),
Field::make('textarea', 'teacher_description', 'Дескрипшн преподавателя (коротко — для карточек)')
->set_width(100)
->set_help_text('Выводится в карточке преподавателя на главной и на странице «Наши преподаватели». Максимум вывода в карточке — 3 строки. Рекомендуемый размер: до 140 знаков. Более длинный текст в карточке обрезается многоточием.'),
Field::make('text', 'teacher_badge', 'Бейдж на карточке (например: 👶 Работает с дошкольниками)')
->set_width(30)
->set_help_text('Текст бейджа на карточке преподавателя. Если пусто — бейдж скрыт.'),
Field::make('text', 'teacher_start_year', 'Год начала работы (только год, например: 1987)')
->set_width(30)
->set_attribute('placeholder', '1987')
->set_help_text('Стаж рассчитается автоматически на странице подготовки к школе.'),
))
->add_tab('📄 О преподавателе', array(
Field::make('textarea', 'teacher_about', 'О себе:')
->set_width(100)
->help_text('Обычный текст, вёрстка не нужна — абзацы разделяйте пустой строкой (Enter + пустая строка). Рекомендуемый объём: 6001200 знаков (24 коротких абзаца).'),
Field::make('textarea', 'teacher_education', 'Образование:'),
Field::make('textarea', 'teacher_add_education', 'Дополнительное образование:'),
Field::make('text', 'teacher_diploma', 'Учебное заведение (для микроразметки alumniOf)')
->set_width(50)
->help_text('Одно главное учебное заведение (напр. «Московский педагогический государственный университет (МПГУ)»). Используется в Schema.org для свойства alumniOf. Если пусто — свойство не выводится.'),
Field::make('text', 'teacher_video', 'Ссылка на видеопрезентацию')
->set_width(100)
->help_text('Укажите ссылку на видео в youtube. Пример: &lt;iframe src="https://vk.com/video_ext.php?oid=-43228812&id=456240170hd=2" width="853" height="480" allow="autoplay; encrypted-media; fullscreen; picture-in-picture;" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;'),
))
->add_tab('📚 Предметы и документы', array(
Field::make('complex', 'teacher_subjects', 'Предметы обучения')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('select', 'teacher_subjects_title', 'Программа (направление)')
->set_options($teacher_program_options)
->set_width(50)
->set_required(true)
->help_text('Выберите из каталога программ филиала (Настройки → Филиал → «Программы и направления»). Цена и ссылка на страницу подтянутся из каталога.'),
Field::make('textarea', 'teacher_subjects_topics', 'Темы направления (каждая с новой строки)')
->set_help_text('Кликабельный список: «Математика», «Русский язык»… Каждая тема — с новой строки. Пусто — блок тем скрыт.'),
)),
Field::make('select', 'teacher_category', 'Категория для фильтра на странице преподавателей')
->set_width(50)
->set_options($teacher_category_options)
->set_help_text('Значение должно совпадать со slug фильтра на странице преподавателей. Пусто — карточка видна только во «Все».'),
Field::make('complex', 'certificates', 'Дипломы и сертификаты')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('image', 'certificates_foto', 'Изображение')
))
->help_text('Добавьте изображение дипломов и сертификатов преподавателя'),
))
->add_tab('📊 Цифры и дефицит', array(
Field::make('html', 'teacher_metrics_help')
->set_html('<div style="background:#F8FAFC;border:1px solid #E2E8F0;border-radius:8px;padding:12px 14px;margin:0 0 14px;color:#334155;font-size:13px;line-height:1.6"><strong>Блок «Цифры»</strong> — карточки под hero (рейтинг, средний балл, цифра про систему, размер группы и любые другие факты). Кнопкой <strong>«+ Добавить»</strong> создаёте карточку, крестиком рядом с полями — удаляете. В каждой карточке два поля: <strong>цифра</strong> и <strong>подпись</strong>. Порядок карточек = порядок вывода на странице. Пустые карточки не выводятся. Если карточек нет — блок «Цифры» скрыт.</div>'),
Field::make('complex', 'teacher_metrics', 'Карточки цифр')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'metric_num', 'Цифра / факт')
->set_width(25)
->set_required(true)
->set_help_text('Короткое значение-результат. Примеры: <strong>5/5</strong>, <strong>100%</strong>, <strong>2-6</strong>, <strong>4,7</strong>, <strong>45 минут</strong>, <strong>500+</strong>.'),
Field::make('text', 'metric_text', 'Подпись под цифрой')
->set_width(75)
->set_help_text('Что означает цифра. Примеры: <strong>«Рейтинг преподавателя · 5 отзывов»</strong>, <strong>«учеников продолжают обучение на следующий учебный год»</strong>, <strong>«человек в мини-группе — внимание как на репетиторе»</strong>, <strong>«средний балл учеников»</strong>.'),
))
->set_help_text('«+ Добавить» — новая карточка (цифра + подпись). Нет карточек — блок «Цифры» на странице скрыт, используйте Hero-таб для рейтинга и размера группы.'),
Field::make('html', 'teacher_scarcity_sep', '')
->set_html('<hr><h3>⏳ Полоса дефицита</h3><p style="color:#64748B;font-size:12px;margin:4px 0 10px;">Жёлтая полоса под цифрами: счётчик дней до экзамена + места в группе + пояснение. Пустое поле — пункт скрыт.</p>'),
Field::make('text', 'teacher_seats_left', 'Осталось мест в группе')
->set_width(25)
->set_default_value('4')
->set_help_text('Пример: <strong>4</strong>. Полоса дефицита: «4 места в группе». Пусто/0 — пункт скрыт.'),
Field::make('text', 'teacher_exam_label', 'Экзамен/формат')
->set_width(25)
->set_default_value('ОГЭ/ЕГЭ')
->set_help_text('Пример: <strong>«ОГЭ/ЕГЭ»</strong>. Полоса дефицита: «дней до ОГЭ/ЕГЭ».'),
Field::make('text', 'teacher_exam_date', 'Дата экзамена для счётчика (YYYY-MM-DD)')
->set_width(25)
->set_default_value('')
->set_help_text('Пример: <strong>2027-05-24</strong>. Пусто — счётчик дней скрыт.'),
Field::make('text', 'teacher_scarcity_note', 'Текст полосы дефицита')
->set_width(100)
->set_default_value('Набор в группу идёт каждый месяц, но места ограничены.')
->set_help_text('Пример: <strong>«Набор в группу идёт каждый месяц, но места ограничены»</strong>. Пусто — скрыто.'),
))
->add_tab('🎨 Сегменты, кейсы, шаги', array(
Field::make('complex', 'teacher_segments', 'Сегменты учеников (карточки)')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'segment_title', 'Заголовок карточки')
->set_width(30),
Field::make('textarea', 'segment_text', 'Текст карточки')
->set_width(70),
))
->set_help_text('3 карточки «Для кого занятия». Пусто — покажутся 3 стандартные.'),
Field::make('complex', 'teacher_cases', 'Кейсы учеников (табы 9/11 класс)')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'case_tab', 'Таб (9 или 11)')
->set_width(20),
Field::make('textarea', 'case_text', 'История кейса')
->set_width(80),
))
->set_help_text('Кейсы с результатом. tab: 9 или 11 класс.'),
Field::make('complex', 'teacher_steps', 'Шаги «Как проходит первое занятие»')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'step_title', 'Название шага')
->set_width(30),
Field::make('textarea', 'step_text', 'Описание шага')
->set_width(70),
))
->set_help_text('Таймлайн: от заявки до обратной связи. Пусто — 5 стандартных шагов.'),
))
->add_tab('📈 Результаты учеников', array(
Field::make('html', 'teacher_results_help')
->set_html('<div style="background:#F8FAFC;border:1px solid #E2E8F0;border-radius:8px;padding:12px 14px;margin:0 0 14px;color:#334155;font-size:13px;line-height:1.6"><strong>Блок «Измеримые результаты»</strong> — отдельная секция на странице (не путать с «Кому подойдут»). 36 пунктов: короткий измеримый результат + пояснение. Пусто — блок соберётся из заполненных метрик преподавателя (рейтинг, стаж, средний балл, отзывы).</div>'),
Field::make('complex', 'teacher_results', 'Измеримые результаты')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('text', 'result_title', 'Короткий результат (цифра/факт)')
->set_width(30)
->set_required(true)
->help_text('Например: «+2 балла», «100%», «45 минут».'),
Field::make('textarea', 'result_text', 'Пояснение')
->set_width(70)
->help_text('Например: «средний рост успеваемости за полугодие».'),
))
->set_help_text('36 пунктов. Пусто — собирается из метрик преподавателя.'),
Field::make('html', 'teacher_results_fallback_sep', '')
->set_html('<hr><h3>⚙ Авто-пункты (если «Измеримые результаты» пустые)</h3><p style="color:#64748B;font-size:12px;margin:4px 0 10px;">Подставляются в секцию результатов, пока complex выше не заполнен. Заполните — complex перекроет авто-пункты.</p>'),
Field::make('text', 'teacher_avg_score', 'Средний балл учеников')
->set_width(25)
->set_default_value('')
->set_help_text('Авто-пункт. Пример: <strong>4,7</strong>. Пусто — пункт скрыт.'),
Field::make('text', 'teacher_avg_label', 'Подпись среднего балла')
->set_width(25)
->set_default_value('Средний балл учеников')
->set_help_text('Пример: <strong>«Средний балл учеников»</strong>.'),
Field::make('text', 'teacher_school_num', 'Цифра про систему/школу')
->set_width(25)
->set_default_value('100%')
->set_help_text('Авто-пункт. Пример: <strong>100%</strong>. Пусто — пункт скрыт.'),
Field::make('text', 'teacher_school_label', 'Подпись к цифре про систему')
->set_width(25)
->set_default_value('учеников продолжают обучение на следующий учебный год')
->set_help_text('Пример: <strong>«учеников продолжают обучение на следующий учебный год»</strong>.'),
))
->add_tab('❓ Вопросы и отзывы', array(
Field::make('complex', 'questions', 'Блок вопросы и ответы (страница)')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('textarea', 'questions_question', 'Текст вопроса')
->set_width(30),
Field::make('textarea', 'questions_answer', 'Текст ответа')
->set_width(70),
)),
Field::make('complex', 'teacher_faq_extra', 'Доп. вопросы (про мини-группы)')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('textarea', 'faq_q', 'Вопрос')
->set_width(30),
Field::make('textarea', 'faq_a', 'Ответ')
->set_width(70),
))
->set_help_text('Вопросы про формат: «Чем мини-группа лучше репетитора?» и т.п. Пусто — 2 стандартных.'),
Field::make('checkbox', 'teacher_show_social_reviews', 'Показывать «Отзывы о преподавателе» на странице')
->set_option_value('yes')
->set_default_value('yes'),
))
->add_tab('📜 Лицензия и доверие', array(
Field::make('textarea', 'teacher_license', 'Текст блока «Лицензия и доверие»')
->set_default_value('Частная школа «Декарт» работает по лицензии на образовательную деятельность (бессрочная). Копия лицензии и реквизиты — по запросу у администратора. Рейтинг на Яндекс Картах и 2ГИС формируется из реальных отзывов родителей.')
->set_help_text('Показывается в блоке доверия перед картой.'),
))->set_layout('tabbed-vertical');
// ====================================================
// Доп поля для страницы "Преподаватели" (Bento 2026)
// ====================================================
Container::make('post_meta', '📚 Настройки страницы Преподаватели')
->where('post_template', '=', 'page-teachers.php')
->add_tab('🖼️ Введение (SEO)', array(
Field::make('html', 'teachers_intro_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Блок под баннером. H1 берётся из баннера (banner_h1), здесь — вводный текст с гео-ключами.</p>'),
Field::make('textarea', 'banner_text', 'Подзаголовок в баннере (под H1)')
->set_width(100)
->set_help_text('12 предложения: направления школы и гео-ключи.'),
Field::make('textarea', 'teachers_intro_title', 'Заголовок блока')
->set_default_value('Преподаватели школы «Декарт» в Щёлково')
->set_help_text('Второй заголовок с ключевым запросом.'),
Field::make('textarea', 'teachers_intro_text', 'Вводный текст')
->set_help_text('24 предложения: сколько учителей, какие направления закрывают, первое вхождение гео-ключей.'),
))
->add_tab('🔍 Фильтры', array(
Field::make('html', 'teachers_filters_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Фильтр карточек преподавателей. <strong>Динамический:</strong> какие фильтры добавлены — такие и выводятся, в этом порядке. Карточки получают категорию из поля «Категория» на странице преподавателя.</p>'),
Field::make('text', 'teachers_filters_title', 'Заголовок секции фильтров')
->set_default_value('Выберите направление')
->set_width(100),
Field::make('complex', 'teachers_filters', 'Фильтры')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте нужные фильтры в нужном порядке. Пример: Начальная школа, Подготовка к школе, Продлёнка, Летний лагерь.')
->add_fields(array(
Field::make('text', 'filter_label', 'Название фильтра')
->set_width(60)
->set_required(true)
->set_help_text('Показывается на кнопке. Пример: «Начальная школа».'),
Field::make('text', 'filter_slug', 'Slug (техническое имя)')
->set_width(40)
->set_help_text('Латинскими буквами, без пробелов. Пример: nachalnaya-shkola. Должен совпадать со значением поля «Категория» у карточки преподавателя.'),
)),
))
->add_tab('📊 Блок доверия', array(
Field::make('html', 'teachers_trust_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Цифры под карточками: стаж, категория, текучка, соотношение учитель/ученик. Показывается, если добавлены данные.</p>'),
Field::make('complex', 'teachers_trust', 'Цифры')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте 24 цифры. Пример: «10+» / «лет средний стаж», «70%» / «учителей с высшей категорией».')
->add_fields(array(
Field::make('text', 'trust_number', 'Число')
->set_width(30)
->set_help_text('Пример: 10+, 70%, 1:15'),
Field::make('text', 'trust_label', 'Подпись')
->set_width(70)
->set_help_text('Пример: лет средний стаж'),
)),
))
->add_tab('🎓 FAQ', array(
Field::make('html', 'teachers_faq_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Частые вопросы про учителей. Из полей строится FAQPage JSON-LD. Показывается, если есть вопросы.</p>'),
Field::make('complex', 'teachers_faq', 'Вопросы и ответы')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('textarea', 'faq_question', 'Вопрос')
->set_width(35)
->set_required(true),
Field::make('textarea', 'faq_answer', 'Ответ')
->set_width(65)
->set_required(true),
)),
))
->add_tab('🤝 CTA', array(
Field::make('html', 'teachers_cta_tab_desc', '')
->set_html('<p style="color:#64748b;font-size:13px;margin:0 0 12px;">Блок перед формой записи. Форма записи и карта — общие, не трогаются.</p>'),
Field::make('text', 'teachers_cta_title', 'Заголовок')
->set_default_value('Познакомьтесь с педагогами лично')
->set_width(100),
Field::make('textarea', 'teachers_cta_text', 'Текст')
->set_default_value('Запишитесь на экскурсию или пробный день — увидите, как проходят занятия, и зададите вопросы учителям напрямую.'),
Field::make('text', 'teachers_cta_btn', 'Текст кнопки')
->set_default_value('Записаться на экскурсию')
->set_help_text('Кнопка открывает форму записи (та же, что ниже).'),
));
// ====================================================
// Доп поля для страницы "Подготовка к школе (Bento 2026)"
// ====================================================
Container::make('post_meta', 'Настройки Подготовки к школе')
->where('post_template', '=', 'page-podgotovka-k-shkole.php')
->add_tab('🖼️ Герой (баннер)', array(
Field::make('html', 'pksh_hero_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.hero-pksh</strong>. Первый экран страницы подготовки к школе.</p>'),
Field::make('image', 'banner_image', 'Фото баннера')
->set_width(50)
->set_help_text('Рекомендуемый размер: 689×689 px, квадратное. Если не указано — используется фото по умолчанию.'),
Field::make('text', 'banner_h1', 'Заголовок H1')
->set_width(50)
->set_default_value('Ваш ребёнок готов к первому классу? Научим читать, считать и писать за 4 месяца — без слёз и стресса')
->set_help_text('Главный заголовок на первом экране.'),
Field::make('textarea', 'banner_subtitle', 'Подзаголовок')
->set_width(50)
->set_default_value('Первое занятие — бесплатно. Результат через 3 недели — или вернём деньги')
->set_help_text('Текст под заголовком. Опционально.'),
Field::make('text', 'banner_subtitle_footnote_link', 'Ссылка для звёздочки (*) в подзаголовке')
->set_width(50)
->set_help_text('URL для иконки * после текста подзаголовка. Если не указано — * неактивна.'),
Field::make('text', 'banner_scarcity', 'Текст дефицита (срочность)')
->set_width(50)
->set_default_value('🔥 Осталось <strong>2 места из 8</strong> в группе 56 лет. Стартуем <strong>1 июня</strong>')
->set_help_text('Можно использовать HTML-теги: &lt;strong&gt;, &lt;br&gt;. Текст о срочности/дефиците мест под кнопкой.'),
Field::make('textarea', 'pksh_hero_definition', 'Определение для AI (первые 60 слов)')
->set_rows(3)
->set_help_text(
'📖 <strong>Для AI-поиска (GEO).</strong> Текст, который AI использует для ответа на «Что такое подготовка к школе в [городе]?». '
. 'Рекомендуется <strong>4060 слов</strong>, начинается с «Подготовка к школе в [городе] — это…». '
. 'Выводится на странице после H1, перед подзаголовком. Если не заполнено — блок скрыт.'
)
->set_default_value('Подготовка к школе в Щёлково — это комплексные занятия для детей 57 лет, которые помогают освоить чтение, письмо, счёт и развить логическое мышление. Программа построена на игровой методике: ребёнок учится без стресса, в комфортном темпе. Группы до 10 человек, индивидуальный подход к каждому.'),
))
->add_tab('📊 Соц.доказательство', array(
Field::make('html', 'pksh_social_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.social-counters</strong>. Блок цифр социального доказательства (4 карточки). Иконки заданы в шаблоне, здесь только текст.</p>'),
Field::make('complex', 'pksh_social_counters', 'Показатели')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте до 4 показателей. Порядок = порядок вывода. Иконки не редактируются.')
->add_fields(array(
Field::make('text', 'counter_number', 'Число')
->set_width(30)
->set_help_text('Например: 500+, 12, 4.8, 20+'),
Field::make('text', 'counter_label', 'Подпись')
->set_width(70)
->set_help_text('Например: выпускников в топ-школах'),
)),
Field::make('html', 'pksh_social_author_header', '')
->set_html('<hr style="margin:16px 0;border:none;border-top:1px solid #e2e8f0;"><h3 style="margin:8px 0;color:#2563EB;">👤 Автор контента (E-E-A-T для SEO)</h3><p style="color:#64748b;font-size:12px;margin:0 0 8px;">Эти поля влияют на <strong>E-E-A-T</strong> (Experience, Expertise, Authoritativeness, Trustworthiness) — фактор ранжирования Google. Указываются в микроразметке Schema.org и отображаются в блоке автора внизу страницы. Заполните, чтобы повысить доверие поисковых систем к контенту.</p>'),
Field::make('text', 'pksh_author_name', 'Имя автора контента')
->set_width(50)
->set_default_value('Частная школа «Декарт»')
->set_help_text(
'<strong>Для SEO:</strong> Имя автора для Schema.org Person / author. '
. 'Рекомендуется: фактическое название организации или ФИО руководителя/педагога. '
. 'Влияет на E-E-A-T — Google проверяет автора контента. '
. 'По умолчанию: название из поля «Название филиала» в Настройках темы.'
),
))
->add_tab('⭐ Отзывы', array(
Field::make('html', 'pksh_reviews_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.pksh-reviews</strong>. Отзывы родителей на странице подготовки к школе.</p>'),
Field::make('complex', 'pksh_reviews_list', 'Отзывы')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте отзывы родителей. Показываются на странице подготовки к школе.')
->add_fields(array(
Field::make('image', 'review_avatar', 'Аватар')
->set_width(15)
->set_help_text('Опционально. Если не указано — генерируется из имени.'),
Field::make('text', 'review_author', 'Имя автора')
->set_width(25),
Field::make('text', 'review_rating', 'Оценка (15)')
->set_width(10)
->set_attribute('type', 'number'),
Field::make('date', 'review_date', 'Дата отзыва')
->set_width(15)
->set_storage_format('d.m.Y')
->set_input_format('d.m.Y', 'd.m.Y')
->set_picker_options(array( 'altInput' => false ))
->set_help_text('Для Schema.org Review.datePublished. Формат: ДД.ММ.ГГГГ'),
Field::make('association', 'review_teacher', 'Преподаватель')
->set_width(25)
->set_types(array(
array(
'type' => 'post',
'post_type' => 'teacher',
),
))
->set_max(1)
->set_help_text('Если родитель хвалит конкретного учителя — привяжите отзыв к нему. Необязательно.'),
Field::make('textarea', 'review_text', 'Текст отзыва')
->set_width(35),
)),
))
->add_tab('❓ FAQ', array(
Field::make('html', 'pksh_faq_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>#pksh-faq</strong>. Вопросы и ответы в стиле лагеря, две колонки.</p>'),
Field::make('complex', 'pksh_faq_items', 'Вопросы и ответы')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте вопросы и ответы. Показываются в секции FAQ на странице подготовки к школе.')
->add_fields(array(
Field::make('textarea', 'faq_question', 'Вопрос')
->set_width(30),
Field::make('textarea', 'faq_answer', 'Ответ')
->set_width(70),
)),
))
->add_tab('👨‍🏫 Преподаватели', array(
Field::make('html', 'pksh_teachers_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>#teachers</strong>. Выберите преподавателей из добавленных в разделе <strong>Преподаватели</strong> главного меню админки. ФИО и фото берутся из карточки преподавателя.</p>'),
Field::make('textarea', 'pksh_teachers_text', 'Текст под заголовком')
->set_default_value('Каждый педагог имеет опыт работы с дошкольниками от 5 лет. Все — действующие учителя начальных классов с высшим образованием.')
->set_help_text('Текст описания перед карточками преподавателей.'),
Field::make('complex', 'pksh_teachers', 'Преподаватели на странице')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте преподавателей для этой страницы. Выберите из списка существующих.')
->add_fields(array(
Field::make('select', 'teacher_id', 'Выберите преподавателя')
->set_options(array( 0 => '— Выберите преподавателя —' ) + dekart_get_teacher_options())
->set_width(100)
->set_help_text('ФИО и фото подтянутся из карточки преподавателя. Если нужного нет — добавьте в разделе Преподаватели.'),
)),
))
// ====================================================
// Вкладка: Программы подготовки к школе
// ====================================================
->add_tab('📚 Программы', array(
Field::make('html', 'pksh_programs_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>#programs</strong>. Две фиксированные карточки: базовая и расширенная программа.</p>'),
Field::make('html', 'pksh_prog_base_title', '')
->set_html('<h3 style="margin:16px 0 8px;">🔵 Базовая — «Уверенный старт»</h3>'),
Field::make('text', 'pksh_prog_base_name', 'Название программы')
->set_width(100)
->set_default_value('Уверенный старт'),
Field::make('text', 'pksh_prog_base_subname', 'Подзаголовок')
->set_width(100)
->set_default_value('2 раза в неделю — комфортный темп без перегрузки'),
Field::make('text', 'pksh_prog_base_schedule', 'Расписание')
->set_width(100)
->set_default_value('вт, чт — 18:0019:30'),
Field::make('text', 'pksh_prog_base_price_sub', 'Стоимость абонемента за 4 недели')
->set_width(50)
->set_default_value('7 500 ₽'),
Field::make('text', 'pksh_prog_base_price_single', 'Стоимость разового занятия')
->set_width(50)
->set_default_value('1 000 ₽'),
Field::make('text', 'pksh_prog_base_price_razov', 'Стоимость разового занятия ВНЕ абонемента')
->set_width(50)
->set_default_value('1 200 ₽')
->set_help_text('Отдельная цена для «Разовое занятие вне абонемента: …». Если пусто — используется price_single.'),
Field::make('text', 'pksh_prog_base_urgency', 'Срочность (мест в группе)')
->set_width(100)
->set_default_value('Осталось 2 места в группе'),
Field::make('html', 'pksh_prog_prem_title', '')
->set_html('<h3 style="margin:20px 0 8px;">🟣 Расширенная — «Результат за 3 месяца»</h3>'),
Field::make('text', 'pksh_prog_prem_name', 'Название программы')
->set_width(100)
->set_default_value('Результат за 3 месяца'),
Field::make('text', 'pksh_prog_prem_subname', 'Подзаголовок')
->set_width(100)
->set_default_value('3 раза в неделю — быстрый прогресс'),
Field::make('text', 'pksh_prog_prem_schedule', 'Расписание')
->set_width(100)
->set_default_value('пн, ср, пт — 16:3018:00'),
Field::make('text', 'pksh_prog_prem_price_sub', 'Стоимость абонемента за 4 недели')
->set_width(50)
->set_default_value('10 500 ₽'),
Field::make('text', 'pksh_prog_prem_price_single', 'Стоимость разового занятия')
->set_width(50)
->set_default_value('1 500 ₽'),
Field::make('text', 'pksh_prog_prem_price_razov', 'Стоимость разового занятия ВНЕ абонемента')
->set_width(50)
->set_default_value('2 000 ₽')
->set_help_text('Отдельная цена для «Разовое занятие вне абонемента: …». Если пусто — используется price_single.'),
Field::make('text', 'pksh_prog_prem_urgency', 'Срочность (мест в группе)')
->set_width(100)
->set_default_value('Осталось 3 места в группе'),
))
// ====================================================
// Вкладка: Сравнение (Как мы отличаемся)
// ====================================================
->add_tab('📊 Как мы отличаемся', array(
Field::make('html', 'pksh_compare_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.pksh-compare</strong>. Таблица сравнения Декарт vs Репетитор vs Кружок. Каждая строка — отдельная запись. Порядок = порядок вывода.</p>'),
Field::make('complex', 'pksh_compare_rows', 'Строки таблицы')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте строки сравнения. Для каждой строки: критерий, текст для трёх колонок, иконка ✓/✗ для каждой колонки.')
->add_fields(array(
Field::make('text', 'criteria', 'Критерий')
->set_width(100)
->set_help_text('Название строки, например: «Гослицензия»'),
Field::make('html', 'pksh_compare_dekart_header', '')
->set_html('<hr style="margin: 12px 0; border: none; border-top: 1px solid #e2e8f0;"><h4 style="margin: 4px 0; color: #7C3AED;">🔸 Декарт</h4>'),
Field::make('select', 'dekart_icon', 'Иконка')
->set_width(20)
->set_options(array(
'' => '— нет —',
'yes' => '✓ (да)',
'no' => '✗ (нет)',
)),
Field::make('textarea', 'dekart_text', 'Текст')
->set_width(80)
->set_help_text('Например: «Есть»'),
Field::make('html', 'pksh_compare_repetitor_header', '')
->set_html('<h4 style="margin: 8px 0 4px;">🔸 Репетитор</h4>'),
Field::make('select', 'repetitor_icon', 'Иконка')
->set_width(20)
->set_options(array(
'' => '— нет —',
'yes' => '✓ (да)',
'no' => '✗ (нет)',
)),
Field::make('textarea', 'repetitor_text', 'Текст')
->set_width(80),
Field::make('html', 'pksh_compare_kruzhok_header', '')
->set_html('<h4 style="margin: 8px 0 4px;">🔸 Обычный кружок</h4>'),
Field::make('select', 'kruzhok_icon', 'Иконка')
->set_width(20)
->set_options(array(
'' => '— нет —',
'yes' => '✓ (да)',
'no' => '✗ (нет)',
)),
Field::make('textarea', 'kruzhok_text', 'Текст')
->set_width(80),
)),
Field::make('textarea', 'pksh_compare_footnote', 'Сноска под таблицей')
->set_help_text('Текст под таблицей. Можно использовать HTML.')
->set_default_value('<strong>Важно:</strong> Данные по репетиторам и кружкам — рыночная оценка по городу и требует уточнения.'),
))
// ====================================================
// Вкладка: JSON-LD Schema (данные для микроразметки)
// ====================================================
->add_tab('🧠 JSON-LD (схема)', array(
Field::make('html', 'pksh_schema_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Поля для <strong>JSON-LD микроразметки</strong> Schema.org. Заполняются опционально — если поле пусто, свойство не добавляется в граф.</p>'),
Field::make('textarea', 'pksh_description', 'Описание курса (для микроразметки)')
->set_help_text(
'Подробное описание программы для Schema.org Course.description. '
. 'Если заполнено — заменяет banner_subtitle в микроразметке. '
. 'Рекомендуется минимум 150 символов. НЕ влияет на отображение страницы.'
)
->set_rows(4)
->set_attribute('placeholder', 'Комплексная программа подготовки детей 57 лет к первому классу...'),
Field::make('text', 'pksh_level', 'Уровень образования (educationalLevel)')
->set_width(50)
->set_help_text('Например: "дошкольное образование" или "5-7 лет"'),
Field::make('text', 'pksh_age', 'Возраст детей')
->set_width(50)
->set_help_text('Например: "57 лет"'),
Field::make('text', 'pksh_audience', 'Целевая аудитория (audience)')
->set_help_text('Например: "Дети 57 лет, будущие первоклассники"'),
Field::make('select', 'pksh_course_mode', 'Формат обучения (courseMode)')
->set_width(50)
->set_help_text('Формат занятий для Schema.org CourseInstance.courseMode.')
->add_options(array(
'' => '— выберите —',
'Onsite' => 'Очно (в центре)',
'Online' => 'Онлайн',
'Blended' => 'Смешанный (очно + онлайн)',
)),
Field::make('complex', 'pksh_skills', 'Чему учат на курсе')
->set_layout('tabbed-horizontal')
->set_help_text('Навыки, которые освоит ребёнок. Каждая запись — отдельный навык (дисциплина).')
->add_fields(array(
Field::make('text', 'skill', 'Навык / дисциплина')
->set_width(100)
->set_attribute('placeholder', 'Чтение, Счёт до 100, Письмо печатными буквами')
->set_help_text('Например: "Чтение", "Счёт до 100", "Письмо печатными буквами"'),
)),
))
// ====================================================
// Вкладка: GEO / AI-поиск (self-contained answer блоки для цитирования)
// ====================================================
->add_tab('🤖 GEO (AI-поиск)', array(
Field::make('html', 'pksh_geo_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Поля для <strong>Generative Engine Optimization</strong> — контент, оптимизированный для цитирования AI-поиском (Google AI Overviews, ChatGPT, Perplexity). Каждый блок должен быть <strong>самодостаточным (134167 слов)</strong> и содержать конкретные данные.</p>'),
Field::make('html', 'pksh_geo_questions_title', '')
->set_html('<h3 style="margin:16px 0 8px;">❓ Вопросы-заголовки для AI</h3>'),
Field::make('html', 'pksh_geo_questions_desc', '')
->set_html('<p style="color: #64748b; font-size: 12px; margin: 0 0 8px;">Эти заголовки (H2/H3) отвечают на типичные поисковые запросы. AI видит их как прямые ответы. Если поле пусто — секция не выводится.</p>'),
Field::make('text', 'pksh_geo_q1', 'Вопрос-заголовок #1')
->set_width(100)
->set_help_text('Например: «Сколько стоит подготовка к школе в Щёлково?»')
->set_default_value('Сколько стоит подготовка к школе в Щёлково?'),
Field::make('text', 'pksh_geo_q2', 'Вопрос-заголовок #2')
->set_width(100)
->set_help_text('Например: «Что должен знать ребёнок перед школой?»')
->set_default_value('Что должен знать ребёнок перед школой?'),
Field::make('text', 'pksh_geo_q3', 'Вопрос-заголовок #3')
->set_width(100)
->set_help_text('Например: «Как выбрать центр подготовки к школе?»')
->set_default_value('Как выбрать центр подготовки к школе?'),
Field::make('html', 'pksh_geo_answers_title', '')
->set_html('<h3 style="margin:20px 0 8px;">📝 Answer-блоки для AI-цитирования</h3>'),
Field::make('html', 'pksh_geo_answers_desc', '')
->set_html('<p style="color: #64748b; font-size: 12px; margin: 0 0 8px;">Каждый блок — <strong>134167 слов</strong>, самодостаточный, с конкретными данными. AI может процитировать его целиком. Рекомендуется: факты, цифры, сроки, цены. Если поле пусто — блок не выводится.</p>'),
Field::make('textarea', 'pksh_geo_answer_programs', 'Answer-блок: Программы и цены')
->set_rows(8)
->set_help_text(
'Выводится в секции <strong>Программы</strong> перед карточками. '
. 'Рекомендуется 134167 слов. Опишите программы, цены, разницу между базовой и расширенной.'
)
->set_default_value(
'В детском центре «Декарт» в Щёлково действуют две программы подготовки к школе. Базовая программа «Уверенный старт» включает 2 занятия в неделю по 3040 минут — стоимость абонемента 7 500 ₽ в месяц. Расширенная программа «Результат за 3 месяца» — 3 занятия в неделю, 10 500 ₽ в месяц. Обе программы ведут действующие педагоги начальных классов со стажем от 12 лет. Группы до 10 человек — каждому ребёнку уделяется внимание. Первое занятие — бесплатно, без договора и оплаты. В стоимость включены все учебные материалы и пособия. Занятия проходят очно по адресу: Талсинская ул., 24. Результат через 3 недели — дети начинают читать по слогам.'
),
Field::make('textarea', 'pksh_geo_answer_results', 'Answer-блок: Результаты и статистика')
->set_rows(8)
->set_help_text(
'Выводится в секции <strong>Результаты</strong>. '
. 'Рекомендуется 134167 слов. Конкретные цифры, проценты, сроки.'
)
->set_default_value(
'95% выпускников центра «Декарт» поступают в школы и лицеи Щёлкова без стресса — они готовы к учебной нагрузке и требованиям учителей. Первые результаты заметны уже через 3 недели: нечитавшие дети складывают первый осмысленный слог. 87% родителей отмечают рост самостоятельности ребёнка после месяца занятий. Выпускники центра адаптируются в первом классе в 2,5 раза быстрее сверстников — они не боятся отвечать у доски, поднимать руку и работать в коллективе. За 12 лет работы центром подготовлено более 500 детей. Программа построена так, что каждый ребёнок осваивает чтение, письмо, счёт и логику без перегрузки — занятия длятся 3040 минут, каждые 7 минут смена активности.'
),
Field::make('textarea', 'pksh_geo_answer_howitworks', 'Answer-блок: Как проходят занятия')
->set_rows(8)
->set_help_text(
'Выводится в секции <strong>Как проходят занятия</strong>. '
. 'Рекомендуется 134167 слов. Опишите методику, длительность, структуру.'
)
->set_default_value(
'Каждое занятие длится 3040 минут и построено так, чтобы ребёнок не уставал. Активность меняется каждые 7 минут — это оптимальный интервал удержания внимания для детей 57 лет. Урок начинается с короткой истории или загадки, которая вводит в тему и включает интерес. Затем следует игровое задание: чтение через игру, счёт через механику соревнования, логика через головоломки. После каждого блока — активная перемена с нейрогимнастикой. Завершается занятие рефлексией: ребёнок сам рассказывает, чему научился. Все педагоги имеют высшее педагогическое образование и опыт работы с дошкольниками от 5 лет. Методика исключает скучные прописи и многочасовое сидение за партой — дети воспринимают занятия как игру и с удовольствием возвращаются.'
),
))
// ====================================================
// Вкладка: Фотогалерея
// ====================================================
->add_tab('📸 Фотогалерея', array(
Field::make('html', 'pksh_gallery_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>#gallery</strong>. Фотографии занятий, 3 в ряд. Если фото не добавлены — секция не выводится.</p>'),
Field::make('complex', 'pksh_gallery', 'Фотографии')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте фотографии с занятий. Оптимальная ширина: 6001200 px.')
->add_fields(array(
Field::make('image', 'gallery_image', 'Фото')
->set_width(60)
->set_help_text('Рекомендуемый размер: 800×600 px, соотношение 4:3 или 3:2.'),
Field::make('text', 'gallery_caption', 'Подпись (опционально)')
->set_width(40)
->set_help_text('Короткая подпись под фото. Если не указана — только фото.'),
)),
));
// ====================================================
// Контейнер: Программа Подготовки к школе (служебная страница)
// ====================================================
Container::make('post_meta', '📄 Программа «Подготовка к школе»')
->where('post_template', '=', 'page-program-pksh.php')
->add_fields(array(
Field::make('html', 'pksh_prog_header', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 8px;">Официальный документ — образовательная программа подготовки к школе. <strong>Текст программы менять нельзя</strong> — это утверждённый документ. Для обновления скопируйте новый текст целиком.</p>'),
Field::make('rich_text', 'pksh_prog_content', 'Текст программы')
->set_help_text(
'<strong>ВАЖНО:</strong> Это официально утверждённый документ. Не меняйте содержание. '
. 'Для обновления — замените текст целиком на новую версию. '
. 'Рекомендуется вставлять из Google Docs / Word без форматирования, '
. 'затем применить заголовки H2-H3 и списки вручную.'
),
Field::make('html', 'pksh_prog_legal_header', '')
->set_html('<hr><h3 style="margin:12px 0 4px;">⚖️ Юридическая информация</h3>'),
Field::make('date', 'pksh_prog_doc_date', 'Дата утверждения / обновления документа')
->set_width(30)
->set_storage_format('Y-m-d')
->set_input_format('Y-m-d', 'Y-m-d')
->set_help_text('Дата последнего утверждения или обновления программы (YYYY-MM-DD). Отображается в дисклеймере.'),
Field::make('textarea', 'pksh_prog_disclaimer', 'Текст дисклеймера')
->set_width(100)
->set_default_value(
'Электронная версия программы предназначена для ознакомления. При переносе в электронный вид '
. 'возможны технические неточности (опечатки, ошибки форматирования). '
. 'Юридически значимым считается документ, предоставленный в офисе организации. '
. 'Дата последнего обновления электронной версии:'
)
->set_rows(4)
->set_help_text('Текст, который выводится в блоке дисклеймера. Можно редактировать независимо от текста программы.'),
Field::make('html', 'pksh_prog_service_header', '')
->set_html('<hr><h3 style="margin:12px 0 4px;">🔗 Ссылка на услугу</h3>'),
Field::make('text', 'pksh_prog_service_url', 'URL услуги')
->set_width(50)
->set_default_value('/podgotovka-k-shkole/')
->set_help_text('Ссылка на страницу услуги, которая реализует данную программу.'),
Field::make('text', 'pksh_prog_service_label', 'Текст ссылки')
->set_width(50)
->set_default_value('Записаться на подготовку к школе')
->set_help_text('Текст кнопки/ссылки для перехода на услугу.'),
));
// ====================================================
// Уникальное имя контейнера Продлёнки
// ====================================================
Container::make('post_meta', 'Настройки Продлёнки')
->where('post_template', '=', 'page-after-school.php')
->add_tab('Общие', array(
Field::make('text', 'as_address', 'Наш адрес:'),
Field::make('text', 'as_price', 'Цена:'),
))
->add_tab('Педагоги', array(
Field::make('complex', 'as_mentors', 'Педагоги')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('image', 'mentor_foto', 'Фотография:')
->set_width(15),
Field::make('text', 'mentor_name', 'ФИО:')
->set_width(30),
Field::make('text', 'mentor_directions', 'Описание:')
->set_width(55),
))
))
->add_tab('Распорядок дня', array(
Field::make('textarea', 'as_routine', 'Блок "Распорядок дня":'),
))
->add_tab('Отзывы', array(
Field::make('complex', 'as_reviews', 'Отзывы клиентов:')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('image', 'review_avatar', 'Автар:')
->set_width(15),
Field::make('text', 'review_name', 'Имя:')
->set_width(30),
Field::make('text', 'review_star', 'Оценка:')
->set_width(15)
->set_attribute('type', 'number'),
Field::make('textarea', 'review_text', 'Текст отзыва:')
->set_width(100),
)),
))
->add_tab('Вопросы-Ответы', array(
Field::make('complex', 'as_questions', 'Блок вопросы и ответы')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('textarea', 'questions_question', 'Текст вопроса')
->set_width(30),
Field::make('textarea', 'questions_answer', 'Текст ответа')
->set_width(70),
)),
))
->add_tab('Фото', array(
Field::make('complex', 'as_fotos', 'Фотографии:')
->set_layout('tabbed-horizontal')
->add_fields(array(
Field::make('image', 'foto', 'Фото')
)),
))
->add_tab('Битрикс', array(
Field::make('textarea', 'as_bitrix_script', 'script (формы) БИТРИКС24:'),
));
// ====================================================
// Доп поля для страницы "ГОРОДСКОЙ ЛАГЕРЬ"
// ====================================================
Container::make('post_meta', '🏰 Настройки Городской Лагерь')
->where('post_template', '=', 'page-camp.php')
->add_tab('🖼️ Первый экран', array(
Field::make('html', 'hero_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Шаблон: <strong>page-camp.php</strong> · Секция: <strong>.hero-media-search</strong></p>'),
Field::make('separator', 'hero_media_sep', 'Изображения'),
Field::make('image', 'banner_img', 'Фото на весь экран (десктоп)')
->set_help_text('Оптимально: 1920×1080 px, WebP/JPG. Дети в движении, эмоции.')
->set_width(50),
Field::make('separator', 'hero_titles_sep', 'Заголовки и текст'),
Field::make('text', 'hero_title_h1', 'Заголовок H1')
->set_default_value('Лето, которое он запомнит навсегда')
->set_width(50)
->set_help_text('Основной заголовок страницы. Эмоциональный, зацепляющий.'),
Field::make('text', 'hero_title_span', 'Заголовок (вторая часть, цветная)')
->set_default_value('Big Summer Fun!')
->set_width(50)
->set_help_text('Выделяется цветом на фоне основного.'),
Field::make('separator', 'hero_cta_sep', 'Кнопка'),
Field::make('text', 'hero_btn_primary', 'Текст главной кнопки (CTA)')
->set_default_value('Записаться на пробный день')
->set_width(100)
->set_help_text('Главное действие, к которому призываем. Лучше: конкретная выгода, а не действие.'),
Field::make('separator', 'hero_metrics_sep', 'Метрики доверия (блок достижений)'),
Field::make('image', 'hero_metric_1_img', 'Иконка метрики 1 (изображение)')
->set_width(30)
->set_help_text('Оптимально: 1200x800px, WebP/JPG'),
Field::make('text', 'hero_metric_1_number', 'Число метрики 1')
->set_default_value('90+')
->set_width(30),
Field::make('text', 'hero_metric_1_label', 'Подпись метрики 1')
->set_default_value('Количество клубов')
->set_width(40),
Field::make('image', 'hero_metric_2_img', 'Иконка метрики 2 (изображение)')
->set_width(30)
->set_help_text('Оптимально: 1200x800px, WebP/JPG'),
Field::make('text', 'hero_metric_2_number', 'Число метрики 2')
->set_default_value('570K+')
->set_width(30),
Field::make('text', 'hero_metric_2_label', 'Подпись метрики 2')
->set_default_value('Юных инноваторов')
->set_width(40),
Field::make('image', 'hero_metric_3_img', 'Иконка метрики 3 (изображение)')
->set_width(30)
->set_help_text('Оптимально: 1200x800px, WebP/JPG'),
Field::make('text', 'hero_metric_3_number', 'Число метрики 3')
->set_default_value('49K+')
->set_width(30),
Field::make('text', 'hero_metric_3_label', 'Подпись метрики 3')
->set_default_value('Стипендий')
->set_width(40),
))
->add_tab(' О нас', array(
Field::make('html', 'about_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.about-us</strong></p>'),
Field::make('separator', 'about_us_sep', 'Текст и фото'),
Field::make('image', 'about_us_img', 'Фото')
->set_help_text('Оптимально: 1200x800px, WebP/JPG. Реальные дети, эмоции.')
->set_width(30),
Field::make('textarea', 'about_us_text', 'Текст о нас')
->set_help_text('Краткий текст о лагере. Выводится на странице лагеря после Hero-секции.')
->set_default_value('Dekart — это сеть детских клубов, где наука, творчество и спорт объединяются в одну захватывающую программу. Мы помогаем детям раскрывать таланты, находить друзей и проводить лето с пользой. Наши вожатые — профессиональные педагоги и наставники, которые создают атмосферу доверия и вдохновения. Каждый день в лагере — это новое открытие.'),
))
->add_tab('🏭 Преимущества', array(
Field::make('html', 'suggestions_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.suggestions</strong></p>'),
Field::make('complex', 'suggestions', 'Блок «Почему выбирают нас»')
->set_layout('tabbed-horizontal')
->set_help_text('36 преимуществ с заголовком и описанием.')
->add_fields(array(
Field::make('text', 'suggestions_title', 'Заголовок')
->set_width(100),
Field::make('textarea', 'suggestions_text', 'Описание')
->set_width(100),
)),
))
->add_tab('📅 Программы', array(
Field::make('html', 'programs_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.programs</strong></p>'),
Field::make('complex', 'programs', 'Список программ (смен)')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте каждую тематическую смену отдельной записью.')
->add_fields(array(
Field::make('image', 'programs_img', 'Изображение смены')
->set_help_text('Оптимально: 1200x800px, WebP/JPG')
->set_width(100),
Field::make('text', 'programs_title', 'Название смены')
->set_width(50)
->set_help_text('Например: «Космический десант»'),
Field::make('text', 'programs_date', 'Даты проведения')
->set_width(50)
->set_help_text('Например: «26 июня»'),
Field::make('textarea', 'programs_text', 'Краткое описание (в карточке)')
->set_width(100)
->set_help_text('23 предложения.'),
Field::make('text', 'programs_price', 'Стоимость (в рублях)')
->set_attribute('type', 'number')
->set_width(50),
Field::make('text', 'programs_days', 'Количество дней')
->set_width(50)
->set_help_text('Например: «5 дней» или «7 дней»'),
Field::make('rich_text', 'programs_content', 'Полное описание (в модальном окне)')
->set_width(100)
->set_help_text('Полный текст программы для всплывающего окна.'),
)),
))
->add_tab('💰 Цены', array(
Field::make('html', 'pricing_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.bring-child</strong></p>'),
Field::make('text', 'price_half_day', 'Стоимость за половину дня (в рублях)')
->set_attribute('type', 'number')
->set_width(50)
->set_help_text('Цена за 5 дней, например: 12900'),
Field::make('text', 'price_full_day', 'Стоимость за полный день (в рублях)')
->set_attribute('type', 'number')
->set_width(50)
->set_help_text('Цена за 5 дней, например: 17900'),
))
->add_tab('👨‍🏫 Наставники', array(
Field::make('html', 'mentors_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.mentors</strong></p>'),
Field::make('complex', 'mentors', 'Список наставников лагеря')
->set_layout('tabbed-horizontal')
->set_help_text('Каждый наставник — отдельная запись.')
->add_fields(array(
Field::make('text', 'mentor_name', 'ФИО наставника')
->set_width(50),
Field::make('image', 'mentor_foto', 'Фото наставника')
->set_help_text('Оптимально: 1200x800px, WebP/JPG')
->set_width(50),
Field::make('text', 'mentor_job', 'Должность в лагере')
->set_width(100),
Field::make('textarea', 'mentor_about', 'О себе')
->set_help_text('Кратко о наставнике (показывается на карточке).'),
))
))
->add_tab('⏰ Распорядок дня', array(
Field::make('html', 'routine_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.routine</strong></p>'),
Field::make('complex', 'routine', 'Расписание по времени')
->set_help_text('Добавьте пункты распорядка по порядку следования.')
->add_fields(array(
Field::make('text', 'routine_time', 'Время')
->set_width(15)
->set_help_text('Формат: 08:30'),
Field::make('text', 'routine_title', 'Название активности')
->set_width(40)
->set_help_text('Например: «Научный блок»'),
Field::make('textarea', 'routine_text', 'Описание')
->set_width(45),
))
))
->add_tab('📷 Фотографии', array(
Field::make('html', 'fotos_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.foto</strong></p>'),
Field::make('complex', 'fotos', 'Галерея фотографий')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте реальные фото детей из лагеря. Формат: WebP, 1:1 или 4:3.')
->add_fields(array(
Field::make('image', 'foto', 'Фотография')
->set_help_text('Оптимально: 1200x800px, WebP/JPG, до 300Кб'),
)),
))
->add_tab('❓ Вопросы и ответы (FAQ)', array(
Field::make('html', 'faq_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.camp-faq</strong></p>'),
Field::make('complex', 'questions', 'Список вопросов и ответов')
->set_layout('tabbed-horizontal')
->set_help_text('Чем больше вопросов, тем меньше возражений у родителей. Минимум 78 вопросов.')
->add_fields(array(
Field::make('textarea', 'questions_question', 'Текст вопроса')
->set_width(30),
Field::make('textarea', 'questions_answer', 'Текст ответа')
->set_width(70),
)),
))
->add_tab('⭐ Отзывы', array(
Field::make('html', 'reviews_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.camp-reviews</strong>. Отзывы выводятся на странице лагеря.</p>'),
Field::make('complex', 'camp_reviews_list', 'Отзывы о лагере')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте отзывы родителей. Показываются на странице лагеря.')
->add_fields(array(
Field::make('image', 'review_avatar', 'Аватар')
->set_width(15)
->set_help_text('Опционально. Если не указано — генерируется из имени.'),
Field::make('text', 'review_author', 'Имя автора')
->set_width(25),
Field::make('text', 'review_rating', 'Оценка (15)')
->set_width(10)
->set_attribute('type', 'number'),
Field::make('date', 'review_date', 'Дата отзыва')
->set_width(15)
->set_storage_format('d.m.Y')
->set_input_format('d.m.Y', 'd.m.Y')
->set_picker_options(array( 'altInput' => false ))
->set_help_text('Для Schema.org Review.datePublished. Формат: ДД.ММ.ГГГГ'),
Field::make('association', 'review_teacher', 'Преподаватель')
->set_width(25)
->set_types(array(
array(
'type' => 'post',
'post_type' => 'teacher',
),
))
->set_max(1)
->set_help_text('Если родитель хвалит конкретного учителя — привяжите отзыв к нему. Необязательно.'),
Field::make('textarea', 'review_text', 'Текст отзыва')
->set_width(35),
)),
))
->add_tab('📍 Контакты и карта', array(
Field::make('html', 'contacts_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">⚠️ Данные филиала (телефон, email, адрес, соцсети) — в <strong>Настройки филиала</strong> (меню слева). Здесь только подпись метки карты.</p>'),
Field::make('text', 'contact_location_text', 'Подпись метки на карте')
->set_width(50)
->set_help_text('Что написано в баллуне на карте.'),
))
->add_tab('🗂️ Битрикс24', array(
Field::make('html', 'bitrix_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.modal-camp-request-form</strong><br>Скрипт кнопок перенесён в <strong>Настройки филиала → 🎯 СКРИПТ ДЛЯ КНОПОК → для лагеря</strong>.</p>'),
));
// ════════════════════════════════════════════════════════════
// СТРАНИЦА ОТЗЫВОВ (page-reviews.php)
// ════════════════════════════════════════════════════════════
Container::make('post_meta', '⭐ Настройки страницы Отзывы')
->where('post_template', '=', 'page-reviews.php')
->add_tab('🧑‍🏫 Hero (первый экран)', array(
Field::make('html', 'reviews_hero_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">H1 формируется автоматически из города. Подзаголовок, метрики и CTA настраиваются ниже.</p>'),
Field::make('text', 'reviews_hero_subtitle', 'Подзаголовок (под H1)')
->set_default_value('Родители доверяют нам самое главное — вот что они говорят о нас')
->set_width(100)
->set_help_text('Текст под заголовком H1. Отображается белым на тёмном фоне hero.'),
Field::make('separator', 'reviews_metric_sep', 'Метрики доверия'),
Field::make('text', 'reviews_hero_metric_1_number', 'Метрика 1 — число')
->set_default_value('500+')
->set_width(30)
->set_help_text('Крупная цифра, которая сразу привлекает внимание. Выбирайте самую сильную — ту, что впечатляет больше всего: количество учеников, лет на рынке, % поступления. Добавьте + или % для эффекта. Примеры: «500+», «98%», «15».'),
Field::make('text', 'reviews_hero_metric_1_label', 'Метрика 1 — подпись')
->set_default_value('счастливых семей')
->set_width(70)
->set_help_text('Подпись к цифре из левого поля. Объясняет, что именно посчитано. Совет: используйте родительный падеж — звучит весомее. Примеры: «счастливых семей», «выпускников», «учеников». Не пишите просто «семей» — добавьте «счастливых», это продаёт эмоцию.'),
Field::make('text', 'reviews_hero_metric_2_number', 'Метрика 2 — число')
->set_default_value('12+')
->set_width(30)
->set_help_text('Вторая по силе цифра. Не ставьте 0, 1, 2 — они не впечатляют. Примеры: «12+», «97%», «50».'),
Field::make('text', 'reviews_hero_metric_2_label', 'Метрика 2 — подпись')
->set_default_value('лет в образовании')
->set_width(70)
->set_help_text('Подпись к цифре 2. Примеры: «лет на рынке», «лет в образовании», «преподавателей». Старайтесь, чтобы подпись читалась вместе с цифрой как законченная фраза: «12+ лет в образовании».'),
Field::make('text', 'reviews_hero_metric_3_number', 'Метрика 3 — число')
->set_default_value('4.9')
->set_width(30)
->set_help_text('Третья цифра. Если сильных цифр больше нет — поставьте рейтинг или оценку. Примеры: «4.9», «5.0», «топ-3». Дробные числа пишите через точку.'),
Field::make('text', 'reviews_hero_metric_3_label', 'Метрика 3 — подпись')
->set_default_value('рейтинг на Яндексе')
->set_width(70)
->set_help_text('Подпись к цифре 3. Примеры: «рейтинг на Яндексе», «звёзд», «место в рейтинге области». Если указали рейтинг — обязательно добавьте «из 5» или площадку: «рейтинг на Яндексе» звучит убедительнее, чем просто «рейтинг».'),
Field::make('text', 'reviews_hero_metric_4_number', 'Метрика 4 — число')
->set_default_value('14')
->set_width(30)
->set_help_text('Четвёртая цифра. Например: количество отзывов, количество педагогов, лет опыта.'),
Field::make('text', 'reviews_hero_metric_4_label', 'Метрика 4 — подпись')
->set_default_value('отзывов о школе')
->set_width(70)
->set_help_text('Подпись к цифре 4. Пример: «отзывов о школе», «педагогов», «лет на рынке».'),
Field::make('separator', 'reviews_hero_badges_sep', '🏷️ Плавающие подписи (у фото)'),
Field::make('complex', 'reviews_hero_badges', 'Подписи-плашки вокруг фото')
->set_layout('tabbed-horizontal')
->set_help_text('До 4 подписей, которые отображаются вокруг фото в hero-секции. Каждая запись: иконка (emoji) + текст. Позиции фиксированы: 1 = верх-право, 2 = лево-середина, 3 = низ-право, 4 = лево-верх. Если записей меньше 4 — отсутствующие не выводятся.')
->add_fields(array(
Field::make('text', 'badge_icon', 'Иконка (emoji)')
->set_width(20)
->set_help_text('Один символ emoji для плашки. Примеры: ⭐ 🎓 ❤️ 🌟 🎮 📚 👩‍🏫 🏆'),
Field::make('textarea', 'badge_text', 'Текст подписи')
->set_width(80)
->set_help_text('Короткий текст плашки. Можно использовать HTML: &lt;strong&gt;, &lt;br&gt;. Примеры: «Рейтинг 4.9 на Яндексе», «500+ выпускников», «Родители рекомендуют», «Школа &lt;strong&gt;№1&lt;/strong&gt; в районе».'),
)),
// CTA кнопка — статическая в шаблоне, открывает модалку Bitrix24
))
->add_tab('📝 Вступление (SEO-текст)', array(
Field::make('html', 'reviews_intro_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.reviews-intro</strong> — SEO-текст в начале страницы. Управляется из админки, чтобы можно было править текст без редактирования шаблона.</p>'),
Field::make('text', 'reviews_intro_price_text', 'Стоимость обучения (текст)')
->set_default_value('от 40 000 ₽ в месяц')
->set_width(50)
->set_help_text('Текст стоимости в блоке .reviews-intro__info. Пример: «от 40 000 ₽ в месяц» или «от 35 000 ₽/мес». Если поле пусто — строка не выводится.'),
Field::make('text', 'reviews_intro_hours', 'Режим работы (текст)')
->set_default_value('ежедневно с 8:00 до 19:00, кроме воскресенья')
->set_width(50)
->set_help_text('Текст режима работы в блоке .reviews-intro__info. Отображается на странице отзывов под SEO-текстом. Пример: «ежедневно с 8:00 до 19:00, кроме воскресенья» или «Пн–Пт: 08:0019:00, Сб: 09:0014:00». Если пусто — строка не выводится.'),
Field::make('textarea', 'reviews_intro_lead', 'Лид-абзац (первый, крупный)')
->set_default_value('Частная школа Декарт в Щёлково — это образовательное пространство, где каждый ребёнок получает внимание, заботу и качественные знания. Мы работаем в Щёлковском городском округе с 2010 года и за это время помогли раскрыть потенциал сотням учеников. Отзывы родителей о школе Декарт — это реальные истории семей, которые доверили нам образование своих детей. Мы публикуем их, чтобы вы могли принять взвешенное решение.')
->set_help_text('Первый, самый заметный абзац. Отображается крупнее остальных, отделён линией. 150180 символов — оптимально. Должен вовлекать и отвечать на главный вопрос посетителя.'),
Field::make('textarea', 'reviews_intro_card_1', 'Блок «О школе» (карточка 1)')
->set_default_value('В нашей частной школе в Щёлково учатся дети с 1 по 11 класс. Мы предлагаем программы начального общего образования, подготовки к ОГЭ и ЕГЭ, углублённое изучение английского языка. Классы небольшие — до 12 человек, поэтому каждый ученик получает индивидуальный подход. Родители, которые ищут качественное образование в Щёлково и Московской области, выбирают Декарт за профессионализм педагогов и тёплую атмосферу.')
->set_help_text('Левая карточка в сетке из двух колонок. Здесь — факты о школе: классы, программы, преимущества. 100200 символов.'),
Field::make('textarea', 'reviews_intro_card_2', 'Блок «О странице» (карточка 2)')
->set_default_value('Мы ценим каждое мнение и постоянно работаем над улучшением обучения. Ниже вы найдёте реальные отзывы родителей о нашей частной школе, достижения учеников и ответы на частые вопросы. Приятного чтения!')
->set_help_text('Правая карточка в сетке. Объясняет, что пользователь найдёт на странице. 80120 символов.'),
))
->add_tab('🏠 Почему выбирают (локальный)', array(
Field::make('html', 'reviews_local_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.reviews-local</strong> — список причин выбрать школу с привязкой к городу. Заголовок секции статический в шаблоне, подставляет город из настроек филиала.</p>'),
Field::make('complex', 'reviews_local_items', 'Причины')
->set_layout('tabbed-horizontal')
->set_help_text('46 причин, почему родители выбирают именно Декарт. Каждая — жирный заголовок + описание. Текст должен быть с гео-привязкой (упоминания города, района, местных реалий).')
->add_fields(array(
Field::make('text', 'title', 'Заголовок')
->set_width(30)
->set_help_text('Короткий, ёмкий заголовок. 25 слов. ✅ «Удобное расположение» ❌ «Мы находимся в удобном месте с хорошей транспортной доступностью».'),
Field::make('textarea', 'text', 'Описание')
->set_width(70)
->set_help_text('23 предложения с гео-привязкой. Упомяните город, район, местные реалии. Родитель должен почувствовать: «Это про мой город, это для меня».'),
)),
))
->add_tab('📊 Наши результаты (цифрами)', array(
Field::make('html', 'reviews_results_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.results</strong></p>'),
Field::make('text', 'reviews_results_title', 'Заголовок секции')
->set_default_value('Декарт в цифрах')
->set_width(100)
->set_help_text('Заголовок блока с результатами. Должен привлекать внимание и обещать пользу. Примеры: «Декарт в цифрах», «Наши достижения», «Почему нам доверяют родители». Избегайте канцеляризмов: «Статистические показатели» — плохо.'),
Field::make('complex', 'reviews_results', 'Показатели')
->set_layout('tabbed-horizontal')
->set_help_text('4-6 цифр, которые доказывают качество школы. Каждый показатель должен отвечать на вопрос родителя: «Почему мы должны вам доверять?». Примеры: % поступивших в вузы, средний балл учеников, стаж преподавателей, количество учеников. Избегайте абстрактных цифр вроде «более 50 довольных клиентов» — звучит неубедительно.')
->add_fields(array(
Field::make('text', 'number', 'Число')
->set_width(25)
->set_help_text('Сама цифра — должна выглядеть эффектно и убедительно. ✅ «100%», «85%», «500+», «50». ❌ «много», «больше 10», «каждый второй» — размытые формулировки не работают. Используйте % и + чтобы цифра казалась больше.'),
Field::make('select', 'icon', 'Иконка')
->set_width(20)
->set_help_text('Иконка рядом с цифрой. Подбирайте по смыслу: 🏆 достижения, 📖 учёба, 👥 ученики, ❤️ любовь/забота. Иконка помогает быстро понять суть.')
->add_options(array(
'ph-graduation-cap' => '🎓 Диплом / шапочка',
'ph-chats' => '💬 Речевой пузырь',
'ph-users-three' => '👥 Люди / группа',
'ph-star' => '⭐ Звезда',
'ph-shield-check' => '🛡️ Галочка / щит',
'ph-book' => '📖 Книга',
'ph-heart' => '❤️ Сердце',
'ph-clock' => '🕐 Часы',
'ph-house' => '🏠 Дом',
'ph-trophy' => '🏆 Кубок',
)),
Field::make('text', 'label', 'Подпись')
->set_width(55)
->set_help_text('Пояснение к цифре. Текст должен быть понятен САМ ПО СЕБЕ, даже без цифры. ✅ «учеников сдают ВПР на 4 и 5», ❌ «сдают ВПР» (что? кто?). Длина — 3-5 слов. Примеры: «лет на рынке», «выпускников поступают в топ-вузы», «средний балл учеников по математике».'),
)),
))
->add_tab('❓ FAQ (вопросы-ответы)', array(
Field::make('html', 'reviews_faq_tab_desc', '')
->set_html('<div style="background:#f0f6fc;border-left:4px solid #72aee6;padding:16px 20px;margin:0 0 16px;border-radius:4px;font-size:13px;line-height:1.6;max-width:680px">
<p style="margin:0 0 8px;font-weight:700;font-size:14px">🎯 Какие вопросы добавлять на эту страницу</p>
<p style="margin:0 0 12px">Эта страница — последний шаг перед записью. Человек уже почти решился, он читает отзывы чтобы окончательно убедиться. Вопросы и ответы здесь должны <strong>снимать последние сомнения</strong>, а не рассказывать о центре в целом.</p>
<p style="margin:0 0 6px;font-weight:600;color:#2c3338">✅ Правильные вопросы — про доверие и финальные сомнения:</p>
<ul style="margin:0 0 12px;padding-left:20px">
<li>— Что если после первого занятия не понравится?</li>
<li>— Можно ли сначала прийти на пробный урок?</li>
<li>— Мой ребёнок сильно отстаёт от программы — вам это не помешает?</li>
<li>— Как быстро будет виден результат?</li>
<li>— Что если мы пропустим занятие?</li>
</ul>
<p style="margin:0 0 6px;font-weight:600;color:#b32d2e">❌ Неправильные вопросы — их размещайте на других страницах:</p>
<ul style="margin:0 0 12px;padding-left:20px">
<li>— Какие предметы вы преподаёте? → <em>на главную или страницу курсов</em></li>
<li>— Сколько стоит обучение? → <em>на страницу цен</em></li>
<li>— Где вы находитесь? → <em>на страницу контактов</em></li>
<li>— Чем вы отличаетесь от других центров? → <em>на страницу «О нас»</em></li>
</ul>
<p style="margin:0 0 4px;font-weight:600">📐 Сколько вопросов добавлять:</p>
<p style="margin:0">Оптимально <strong>46</strong>. Меньше — не закрывает сомнения, больше — отвлекает от записи.</p>
</div>'),
// Заголовок секции — статический в шаблоне
Field::make('complex', 'reviews_faq', 'Вопросы и ответы')
->set_layout('tabbed-horizontal')
->set_help_text('Вопросы — снимают последние сомнения перед записью. 46 штук.')
->add_fields(array(
Field::make('textarea', 'question', 'Вопрос')
->set_width(30)
->set_help_text('Вопрос должен звучать так, как его задаст реальный родитель — простыми словами, без канцелярита. ✅ «А если ребёнок не захочет ходить?» ❌ «Как преодолеть сопротивление ребёнка к посещению занятий?». Первое — вызывает доверие, второе — выглядит как копипаста с другого сайта.'),
Field::make('textarea', 'answer', 'Ответ')
->set_width(70)
->set_help_text('Ответ должен снимать страх или сомнение, а не просто сухо информировать. Используйте слова-усилители: «да», «конечно», «бесплатно», «попробуйте», «всегда можно». Структура: 1) Поймите чувства родителя («Это нормально, что вы переживаете…»). 2) Дайте решение («…поэтому у нас есть пробный урок»). 3) Позовите к действию («Приходите познакомиться — это бесплатно»).'),
)),
))
->add_tab('⭐ Отзывы (карточки)', array(
Field::make('html', 'reviews_cards_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.reviews-grid</strong>. Карточки отзывов, которые видят посетители.</p>'),
Field::make('complex', 'reviews_cards', 'Отзывы')
->set_layout('tabbed-horizontal')
->set_help_text('Карточки отзывов родителей — главный элемент доверия на странице. Лучше 3-4 подробных отзыва, чем 10 коротких «всё нравится». Хороший отзыв содержит: с чего начали → что изменилось → какой результат. Эмоциональные отзывы продают лучше фактов.')
->add_fields(array(
Field::make('text', 'review_author', 'Имя автора')
->set_width(22)
->set_help_text('Реальное имя родителя: «Анна», «Сергей», «Екатерина М.». Имена повышают доверие. Можно добавить статус: «Анна, мама Артёма». НЕ используйте анонимные «Родитель» или «Гость» — это вызывает подозрение.'),
Field::make('image', 'review_photo', 'Фото автора')
->set_width(13)
->set_value_type('url')
->set_help_text('Фото родителя (реальное лицо). Если не загружено — показывается аватар-заглушка с первой буквой имени. Фото повышает доверие к отзыву. Рекомендуемый размер: 96×96px, формат: WebP/JPG.'),
Field::make('text', 'review_rating', 'Оценка (15)')
->set_width(10)
->set_attribute('type', 'number')
->set_help_text('Оценка от 1 до 5. Ставьте 5 — если отзыв восторженный. 4 — если есть мелкие замечания (это добавляет правдоподобности). Оценки 3 и ниже публиковать не стоит — они снижают доверие.'),
Field::make('date', 'review_date', 'Дата отзыва')
->set_storage_format('d.m.Y')
->set_input_format('d.m.Y', 'd.m.Y')
->set_picker_options(array(
'altInput' => false,
))
->set_help_text('Дата отзыва. Свежие отзывы (2024-2025) работают лучше старых. Формат: ДД.ММ.ГГГГ (например: 10.03.2025). Можно открыть календарь — иконка справа.')
->set_width(20),
Field::make('association', 'review_teacher', 'Преподаватель')
->set_width(30)
->set_max(1)
->set_types(array(
array(
'type' => 'post',
'post_type' => 'teacher',
),
))
->set_help_text('Если родитель хвалит конкретного учителя — привяжите отзыв к нему. Тогда на странице преподавателя тоже появится этот отзыв. Необязательно.'),
Field::make('textarea', 'review_text', 'Текст отзыва')
->set_width(35)
->set_help_text('Полный текст отзыва. 3-5 предложений — оптимально. Хорошая структура: 1) С чего начали («Пришли с проблемой…»), 2) Что изменилось («Через месяц ребёнок…»), 3) Результат («Сейчас читает лучше всех в классе»). Эмоции и конкретные детали убеждают сильнее общих фраз. Орфографию можно поправить, но сохраните живую речь.'),
)),
))
// 📝 Форма отзыва — тексты статические в шаблоне page-reviews.php
// 🎯 Финальный CTA — статический в шаблоне page-reviews.php.
// Скрипт для кнопок — branch_button_script (Settings API → Настройки филиала).
->add_tab('🔗 Перелинковка', array(
Field::make('html', 'reviews_cross_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.cross-links</strong> — ссылки на другие разделы сайта. Улучшает навигацию и SEO.</p>'),
Field::make('text', 'reviews_cross_title', 'Заголовок')
->set_default_value('Наши программы')
->set_width(100)
->set_help_text('Заголовок блока со ссылками. По умолчанию «Наши программы». Альтернативы: «Узнайте больше», «Другие направления», «Выберите программу». Важно: заголовок должен подталкивать перейти по ссылкам.'),
Field::make('complex', 'reviews_cross_links', 'Ссылки на другие страницы')
->set_layout('tabbed-horizontal')
->set_help_text('5-7 ссылок на другие страницы сайта. Это помогает родителям найти нужную программу и улучшает SEO (перелинковка). Обязательно добавьте: подготовка к школе, начальная школа, средняя школа, ВПР, ОГЭ/ЕГЭ, продлёнка, летний лагерь.')
->add_fields(array(
Field::make('text', 'title', 'Название')
->set_width(50)
->set_help_text('Текст ссылки, который увидит посетитель. ✅ «Подготовка к школе», «Группа продлённого дня». Название должно быть понятно без контекста.'),
Field::make('text', 'url', 'Ссылка')
->set_width(50)
->set_help_text('URL страницы. Можно указать сокращённо: /podgotovka-k-shkole/ или полный адрес. Скопируйте из адресной строки браузера при просмотре нужной страницы.'),
)),
))
->add_tab('💎 Видеоотзывы', array(
Field::make('html', 'reviews_video_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.video-reviews</strong>. Видеоотзывы — самый убедительный формат контента. Добавьте 2-4 коротких видео (1-3 минуты).</p>'),
Field::make('text', 'reviews_video_title', 'Заголовок секции')
->set_default_value('Видеоотзывы родителей')
->set_width(100)
->set_help_text('Заголовок секции с видео. По умолчанию «Видеоотзывы родителей». Можно изменить на «Смотрите, что говорят родители», «Истории наших семей» — более эмоциональные варианты работают лучше.'),
Field::make('complex', 'reviews_video', 'Видеоотзывы')
->set_layout('tabbed-horizontal')
->set_help_text('Добавьте видеоотзывы — это повышает доверие сильнее, чем текст. Видео должны быть на YouTube, в открытом доступе, длительностью 1-3 минуты. Хорошо, если родитель рассказывает историю: пришли с проблемой → как школа помогла → результат.')
->add_fields(array(
Field::make('text', 'name', 'Имя автора')
->set_width(30)
->set_help_text('Имя родителя из видео. Например: «Мария, мама Артёма». Если в видео несколько человек — укажите главного героя.'),
Field::make('oembed', 'url', 'Ссылка на видео (YouTube)')
->set_width(70)
->set_help_text('Ссылка на видео. Поддерживаются: YouTube (https://www.youtube.com/watch?v=...), VK Видео (https://vk.com/video-... или https://vkvideo.ru/...), Rutube и другие oEmbed-провайдеры. Видео должно быть в открытом доступе. Проверьте ссылку перед публикацией.'),
)),
))
->add_tab('📸 Фото с занятий', array(
Field::make('html', 'reviews_photos_tab_desc', '')
->set_html('<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Секция: <strong>.reviews-photos</strong> — фотогалерея с занятий. 4-8 фото повышают доверие и показывают атмосферу школы. Рекомендуемый размер: 800×600px, формат WebP.</p>'),
Field::make('text', 'reviews_photos_title', 'Заголовок секции')
->set_default_value('Фото с занятий')
->set_width(100)
->set_help_text('Заголовок галереи. По умолчанию «Фото с занятий». Альтернативы: «Как проходят занятия», «Наши будни», «Атмосфера школы».'),
Field::make('media_gallery', 'reviews_photos', 'Фотографии')
->set_type(array( 'image' ))
->set_help_text('Загрузите 4-8 фотографий. РЕКОМЕНДУЕМЫЙ РАЗМЕР: 800×600px (4:3), формат WebP. Все фото в галерее автоматически обрезаются до одинакового размера на странице (4:3), соотношение сторон сохраняется.'),
));
}
// функция получения id курса по его названию
function get_post_by_title($page_title)
{
$query = new WP_Query(
[
'post_type' => 'course',
'title' => $page_title,
'post_status' => 'publish',
'posts_per_page' => 1,
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'orderby' => 'post_date ID',
'order' => 'ASC',
'no_found_rows' => true,
'ignore_sticky_posts' => true,
]
);
wp_reset_postdata();
if ($query->post) {
return $query->post->ID;
} else {
return null;
}
}
// функция случайного цвета
function rand_color()
{
return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
}
add_theme_support('post-thumbnails');
// Навигационные меню: возвращает пункт «Внешний вид → Меню» в админке
add_theme_support('menus');
register_nav_menus(array(
'top' => __('Основное меню (шапка/подвал)', 'dekart'),
));
add_action('pre_get_posts', 'exclude_custom_taxonomy_posts');
function exclude_custom_taxonomy_posts($query)
{
if (!is_admin() && $query->is_main_query() && ($query->is_tax('trainingCategory') || $query->is_tax('direction'))) {
$query->set_404();
}
}
function add_og_image()
{
$term = get_queried_object();
if (! is_a($term, 'WP_Term') || empty($term->term_id)) {
return;
}
$snippet = carbon_get_term_meta($term->term_id, 'snippet');
if ($snippet) {
echo '<meta property="og:image" content="' . esc_url(wp_get_attachment_url($snippet)) . '" >' . "\n";
}
}
add_action('wp_head', 'add_og_image');
function yoastVariableToTitle($post_id)
{
$yoast_title = get_post_meta($post_id, '_yoast_wpseo_title', true);
$title = strstr($yoast_title, '%%', true);
if (empty($title)) {
$title = get_the_title($post_id);
}
$wpseo_titles = get_option('wpseo_titles');
$sep_options = WPSEO_Option_Titles::get_instance()->get_separator_options();
if (isset($wpseo_titles['separator']) && isset($sep_options[$wpseo_titles['separator']])) {
$sep = $sep_options[$wpseo_titles['separator']];
} else {
$sep = '-';
}
$site_title = get_bloginfo('name');
$meta_title = $title . ' ' . $sep . ' ' . $site_title;
return $meta_title;
}
function replaceQuotes($text)
{
$text = str_replace(['"'], "'", $text);
return $text;
}
/**
* Санитация для поля branch_button_script.
* Сохраняет <script> тэги (в отличие от wp_kses_post).
*/
function dekart_sanitize_script_field($value)
{
$allowed = wp_kses_allowed_html('post');
$allowed['script'] = array(
'src' => true,
'async' => true,
'defer' => true,
'data-b24-form' => true,
'data-skip-moving' => true,
'type' => true,
'charset' => true,
);
return wp_kses($value, $allowed);
}
/**
* Парсит Bitrix24 скрипт из настроек филиала, извлекая data-b24-form и URL загрузчика.
* Используется для динамической загрузки формы в модальном окне.
*
* @param string $script Полный HTML скрипта Bitrix24.
* @return array{form: string, loader: string} Массив с ключами 'form' и 'loader'.
*/
function dekart_parse_b24_script(string $script): array
{
$result = array( 'form' => '', 'loader' => '' );
if (preg_match('/data-b24-form="([^"]+)"/', $script, $m)) {
$result['form'] = $m[1];
}
if (preg_match("/\\(window,document,'([^']+)'\\)/", $script, $m)) {
$result['loader'] = $m[1];
}
return $result;
}
function enqueue_template_specific_styles()
{
// Yandex Maps — загружается на всех страницах.
// JS сам проверяет наличие #ya-map[data-coords] перед активацией.
wp_enqueue_script(
'yandex-map',
get_template_directory_uri() . '/assets/js/yandex-map.js',
array(),
filemtime(get_template_directory() . '/assets/js/yandex-map.js'),
true
);
if (is_page_template('page-camp.php')) {
wp_enqueue_style(
'template-specific-style',
get_template_directory_uri() . '/assets/css/camp.css',
array(),
'1.8'
);
wp_enqueue_script(
'camp-script',
get_template_directory_uri() . '/assets/js/camp.js',
null,
'1.0.0',
true
);
}
if (is_page_template('page-reviews.php')) {
// Phosphor Icons загружаются через preload+onload в wp_head (page-reviews.php:46)
// Не используем wp_enqueue_style — избегаем render-blocking
wp_enqueue_style(
'reviews-page-style',
get_template_directory_uri() . '/assets/css/reviews-page.css',
array(),
filemtime(get_template_directory() . '/assets/css/reviews-page.css')
);
wp_enqueue_script(
'reviews-page-script',
get_template_directory_uri() . '/assets/js/reviews-page.js',
array(),
filemtime(get_template_directory() . '/assets/js/reviews-page.js'),
true
);
}
if (is_page_template('page-teachers.php')) {
wp_enqueue_style(
'teachers-page-style',
get_template_directory_uri() . '/assets/css/page-teachers.css',
array(),
filemtime(get_template_directory() . '/assets/css/page-teachers.css')
);
wp_enqueue_script(
'teachers-page-script',
get_template_directory_uri() . '/assets/js/page-teachers.js',
array(),
filemtime(get_template_directory() . '/assets/js/page-teachers.js'),
true
);
}
if (is_singular('teacher')) {
wp_enqueue_style(
'single-teacher-style',
get_template_directory_uri() . '/assets/css/single-teacher.css',
array(),
filemtime(get_template_directory() . '/assets/css/single-teacher.css')
);
wp_enqueue_script(
'single-teacher-script',
get_template_directory_uri() . '/assets/js/single-teacher.js',
array(),
filemtime(get_template_directory() . '/assets/js/single-teacher.js'),
true
);
}
if (is_page_template('page-after-school.php')) {
wp_enqueue_style(
'template-after-style',
get_template_directory_uri() . '/assets/css/after-school.css',
array(),
'1.1'
);
}
// PKS: Подготовка к школе — CSS и JS
if (is_page_template('page-podgotovka-k-shkole.php')) {
wp_enqueue_style(
'template-pksh-style',
get_template_directory_uri() . '/assets/css/podgotovka-k-shkole.css',
array(),
'2.1.' . filemtime(get_template_directory() . '/assets/css/podgotovka-k-shkole.css')
);
wp_enqueue_script(
'pksh-timer',
get_template_directory_uri() . '/assets/js/pksh-timer.js',
array(),
filemtime(get_template_directory() . '/assets/js/pksh-timer.js'),
true
);
wp_enqueue_script(
'pksh-sticky-cta',
get_template_directory_uri() . '/assets/js/pksh-sticky-cta.js',
array(),
filemtime(get_template_directory() . '/assets/js/pksh-sticky-cta.js'),
true
);
}
// Front page
if (is_page_template('front-page.php') || is_front_page()) {
wp_enqueue_style(
'front-page-style',
get_template_directory_uri() . '/assets/css/front-page.css',
array(),
filemtime(get_template_directory() . '/assets/css/front-page.css')
);
wp_enqueue_script(
'front-page-script',
get_template_directory_uri() . '/assets/js/front-page.js',
array(),
filemtime(get_template_directory() . '/assets/js/front-page.js'),
true
);
wp_enqueue_script(
'sticky-cta',
get_template_directory_uri() . '/assets/js/sticky-cta.js',
array(),
filemtime(get_template_directory() . '/assets/js/sticky-cta.js'),
true
);
}
// Страница начальной школы — hero в стиле главной + cta-секция
if (is_page_template('page-elementary-school.php')) {
wp_enqueue_style(
'front-page-style',
get_template_directory_uri() . '/assets/css/front-page.css',
array(),
filemtime(get_template_directory() . '/assets/css/front-page.css')
);
wp_enqueue_style(
'school-style',
get_template_directory_uri() . '/assets/css/school.css',
array(),
filemtime(get_template_directory() . '/assets/css/school.css')
);
wp_enqueue_script(
'school-script',
get_template_directory_uri() . '/assets/js/school.js',
array(),
filemtime(get_template_directory() . '/assets/js/school.js'),
true
);
wp_enqueue_script(
'sticky-cta',
get_template_directory_uri() . '/assets/js/sticky-cta.js',
array(),
filemtime(get_template_directory() . '/assets/js/sticky-cta.js'),
true
);
}
// Страница КЛАССА начальной школы — наследует стили школы + свои
if (is_page_template('page-class.php')) {
wp_enqueue_style(
'front-page-style',
get_template_directory_uri() . '/assets/css/front-page.css',
array(),
filemtime(get_template_directory() . '/assets/css/front-page.css')
);
wp_enqueue_style(
'school-style',
get_template_directory_uri() . '/assets/css/school.css',
array(),
filemtime(get_template_directory() . '/assets/css/school.css')
);
wp_enqueue_style(
'class-style',
get_template_directory_uri() . '/assets/css/class.css',
array(),
filemtime(get_template_directory() . '/assets/css/class.css')
);
wp_enqueue_script(
'class-script',
get_template_directory_uri() . '/assets/js/class.js',
array(),
filemtime(get_template_directory() . '/assets/js/class.js'),
true
);
wp_enqueue_script(
'sticky-cta',
get_template_directory_uri() . '/assets/js/sticky-cta.js',
array(),
filemtime(get_template_directory() . '/assets/js/sticky-cta.js'),
true
);
}
}
/**
* IndexNow — автоматический пинг поисковых систем при обновлении контента.
*
* @see https://www.indexnow.org/
*/
add_action('wp_head', function () {
$key = get_option('indexnow_api_key', 'b8a7c3d9e1f204856a7b9c0d1e2f3a4b');
$loc = home_url('/.well-known/' . $key . '.txt');
echo '<link rel="alternate" type="application/json" href="' . esc_url($loc) . '" />' . "\n";
});
/**
* Пинг IndexNow при публикации/обновлении поста.
*/
add_action('publish_post', 'dekart_ping_indexnow', 10, 2);
add_action('publish_page', 'dekart_ping_indexnow', 10, 2);
function dekart_ping_indexnow($post_id, $post)
{
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
$key = get_option('indexnow_api_key', 'b8a7c3d9e1f204856a7b9c0d1e2f3a4b');
$urls = array( get_permalink($post_id) );
$body = wp_json_encode(array(
'host' => wp_parse_url(home_url(), PHP_URL_HOST),
'key' => $key,
'urlList' => $urls,
));
wp_remote_post('https://api.indexnow.org/indexnow', array(
'body' => $body,
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
'timeout' => 5,
'blocking' => false,
));
}
/**
* CrUX — Chrome User Experience Report.
* Добавляет заголовок для мониторинга реальных пользовательских метрик.
*/
add_action('send_headers', function () {
if (! is_admin()) {
header('Link: <https://chromeuxreport.googleapis.com/>; rel=preconnect', false);
}
});
// Очистка метабоксов на странице Подготовка к школе — оставить только CF и Yoast
add_action('load-post.php', function () {
$post_id = isset($_GET['post']) ? (int) $_GET['post'] : 0;
if (! $post_id) {
return;
}
if ('page-podgotovka-k-shkole.php' !== get_page_template_slug($post_id)) {
return;
}
add_action('add_meta_boxes', function () {
foreach (
array(
'postcustom' => array( 'page', 'normal' ),
'commentstatusdiv' => array( 'page', 'normal' ),
'commentsdiv' => array( 'page', 'normal' ),
'slugdiv' => array( 'page', 'normal' ),
'authordiv' => array( 'page', 'normal' ),
'revisionsdiv' => array( 'page', 'normal' ),
'postexcerpt' => array( 'page', 'normal' ),
'pageparentdiv' => array( 'page', 'side' ),
) as $box_id => $args
) {
remove_meta_box($box_id, $args[0], $args[1]);
}
}, 100);
});
add_action('wp_enqueue_scripts', 'enqueue_template_specific_styles');
// Удаляем ненужные inline-стили WordPress Block Library — тема кастомная, блоки не используются
add_action('wp_enqueue_scripts', static function () {
wp_dequeue_style('wp-block-library');
wp_dequeue_style('classic-theme-styles');
wp_dequeue_style('global-styles');
// WP 6.8+ re-enqueue global-styles на wp_footer (priority 1) для hoist-механизма
// (wp_hoist_late_printed_styles). Удаляем re-enqueue — иначе dequeue выше бесполезен.
remove_action('wp_footer', 'wp_enqueue_global_styles', 1);
}, 100);
// Enqueue вынесенный global-styles CSS внешним файлом (кэшируемый)
add_action('wp_enqueue_scripts', 'enqueue_block_library_global_styles');
function enqueue_block_library_global_styles(): void
{
$file_path = get_template_directory() . '/assets/css/block-library-global-styles.css';
if (! file_exists($file_path)) {
return;
}
wp_enqueue_style(
'block-library-global-styles',
get_template_directory_uri() . '/assets/css/block-library-global-styles.css',
array(),
filemtime($file_path)
);
}
/**
* Helper: determine if current page needs Swiper slider assets.
*
* @return bool True if the current page uses Swiper directly or via template-parts.
*/
function needs_swiper(): bool
{
if (is_singular([ 'course', 'teacher' ])) {
return true;
}
$swiper_templates = [
'page-after-school.php',
'page-foto.php',
'page-o-nas.php',
'page-elementary-school.php',
'page-contacts.php',
'page-camp.php',
];
foreach ($swiper_templates as $tmpl) {
if (is_page_template($tmpl)) {
return true;
}
}
return false;
}
add_shortcode('get_form_bitrix', 'form_bitrix');
function form_bitrix($atts, $content = null)
{
return carbon_get_theme_option('site_bitrix_script');
}
add_shortcode('get_reviews', 'sc_reviews');
function sc_reviews($atts)
{
$atts = shortcode_atts([
'type' => 'school',
], $atts);
set_query_var('type', $atts['type']);
ob_start();
load_template(locate_template('/template-parts/reviews.php'), false);
return ob_get_clean();
}
add_shortcode('get_teachers', 'sc_teachers');
function sc_teachers($atts, $content = null)
{
ob_start();
set_query_var('sc_teachers', '1');
load_template(locate_template('/template-parts/teachers.php'), false);
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;
// Основной путь: Carbon Fields Association (ключи вида _review_teacher|||0|id)
if (function_exists('carbon_get_post_meta')) {
$cached = carbon_get_post_meta((int) $review_id, 'review_teacher');
if (is_array($cached)) {
foreach ($cached as $entry) {
if (is_array($entry) && ! empty($entry['id'])) {
$teacher_id = (int) $entry['id'];
break;
}
if (is_string($entry) && preg_match('/post:teacher:(\d+)/', $entry, $m)) {
$teacher_id = (int) $m[1];
break;
}
}
}
}
// Legacy fallback: сырые ключи меты
if (! $teacher_id) {
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_array($entry) && isset($entry['id'])) {
$ids[] = (int) $entry['id'];
} elseif (is_string($entry) && preg_match('/post:teacher:(\d+)/', $entry, $m)) {
$ids[] = (int) $m[1];
} 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;
$root_url = get_permalink($reviews_page_id);
if (substr($root_url, -1) !== '/') {
$root_url .= '/';
}
if (isset($_GET['filter'])) {
$root_url = add_query_arg('filter', esc_attr($_GET['filter']), $root_url);
}
$page_1_url_full = get_pagenum_link(1);
if (substr($page_1_url_full, -1) !== '/') {
$page_1_url_full .= '/';
}
$canonical_root_url = get_permalink($reviews_page_id);
if (substr($canonical_root_url, -1) !== '/') {
$canonical_root_url .= '/';
}
if (isset($_GET['filter'])) {
$canonical_root_url = add_query_arg('filter', esc_attr($_GET['filter']), $canonical_root_url);
}
$link = str_replace(
'href="' . esc_url($page_1_url_full) . '"',
'href="' . esc_url($canonical_root_url) . '"',
$link
);
$link = str_replace(
'href="' . esc_url(get_pagenum_link(1)) . '"',
'href="' . esc_url($canonical_root_url) . '"',
$link
);
return $link;
}
add_filter('paginate_links', 'fix_page_1_in_pagination');
remove_action('wp_head', 'wp_generator');
remove_action('wp_head', 'rest_output_link_wp_head');
remove_action('wp_head', 'wp_oembed_add_discovery_links');
remove_action('wp_head', 'wp_shortlink_wp_head');
remove_action('wp_head', 'adjacent_posts_rel_link_wp_head');
remove_action('template_redirect', 'rest_output_link_header', 11);
function dekart_disable_emojis()
{
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_action('wp_print_styles', 'print_emoji_styles');
remove_action('admin_print_styles', 'print_emoji_styles');
remove_filter('the_content_feed', 'wp_staticize_emoji');
remove_filter('comment_text_rss', 'wp_staticize_emoji');
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
add_filter('tiny_mce_plugins', 'dekart_disable_emojis_tinymce');
add_filter('wp_resource_hints', 'dekart_disable_emojis_remove_dns_prefetch', 10, 2);
}
add_action('init', 'dekart_disable_emojis');
function dekart_disable_emojis_tinymce($plugins)
{
if (is_array($plugins)) {
return array_diff($plugins, array('wpemoji'));
}
return array();
}
function dekart_disable_emojis_remove_dns_prefetch($urls, $relation_type)
{
if ('dns-prefetch' === $relation_type) {
$emoji_svg_url = apply_filters('emoji_svg_url', 'https://s.w.org/images/core/emoji/');
$urls = array_diff($urls, array($emoji_svg_url));
}
return $urls;
}
// ====================================================
// ✅ Оптимизация скриптов — defer только на фронтенде
// ====================================================
function dekart_add_defer_to_scripts($tag, $handle, $src)
{
// Не деферим скрипты в админке, AJAX и REST запросах
if (is_admin() || wp_doing_ajax() || ( defined('REST_REQUEST') && REST_REQUEST )) {
return $tag;
}
$no_defer = array(
'jquery-core',
'jquery',
'carbon-fields-script',
'wp-api-request',
'wp-element',
'wp-components',
);
if (in_array($handle, $no_defer)) {
return $tag;
}
if (strpos($tag, 'defer') !== false || strpos($tag, 'async') !== false) {
return $tag;
}
return str_replace('<script ', '<script defer ', $tag);
}
add_filter('script_loader_tag', 'dekart_add_defer_to_scripts', 10, 3);
// ====================================================
// ✅ Заголовки безопасности — только на фронтенде
// ====================================================
add_action('send_headers', 'dekart_security_headers');
function dekart_security_headers(): void
{
if (! is_admin()) {
if (is_ssl()) {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: strict-origin-when-cross-origin');
// Content-Security-Policy — базовый, без блокировки легаси-скриптов
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn-ru.bitrix24.ru https://alee.bitrix24.ru https://api-maps.yandex.ru https://yastatic.net https://cdn.jsdelivr.net https://www.google.com https://www.gstatic.com https://www.googletagmanager.com https://mc.yandex.ru; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://alee.bitrix24.ru; img-src 'self' data: https: blob:; font-src 'self' data:; frame-src 'self' https://cdn-ru.bitrix24.ru https://alee.bitrix24.ru https://www.google.com; connect-src 'self' https://api-maps.yandex.ru https://yastatic.net https://cdn-ru.bitrix24.ru https://alee.bitrix24.ru https://mc.yandex.ru wss://mc.yandex.ru; object-src 'none'; base-uri 'self'; form-action 'self' https://alee.bitrix24.ru;");
}
}
function dekart_add_preload_hints()
{
// резерв для preload hints
}
add_action('wp_head', 'dekart_add_preload_hints', 1);
// ====================================================
// ✅ Hardening: XML-RPC + REST user enumeration (пред-продакшен)
// ====================================================
// XML-RPC полностью выключен (защита от pingback DDoS и брутфорса через system.multicall)
add_filter('xmlrpc_enabled', '__return_false');
// Убрать ВСЕ XML-RPC методы (включая анонимные pingback.* / system.* — вектор амплификации).
// WP 7.1: xmlrpc_enabled=false не блокирует анонимные методы, поэтому дублируем.
add_filter('xmlrpc_methods', '__return_empty_array');
// Закрыть REST-эндпоинты пользователей для анонимов (wp/v2/users раскрывает логины)
add_filter('rest_endpoints', static function (array $endpoints): array {
if (is_user_logged_in()) {
return $endpoints;
}
foreach (array_keys($endpoints) as $route) {
if (preg_match('#^/wp/v2/users(/|$)#', $route)) {
unset($endpoints[$route]);
}
}
return $endpoints;
});
/* ── Honest timer: end-of-month deadline ── */
/**
* Return end-of-current-month as ISO 8601 string.
* Stored in wp_option 'dekart_honest_deadline' so same value
* is used by PHP (template) and exposed to JS.
*/
function dekart_get_honest_deadline(): string
{
$cached = get_option('dekart_honest_deadline', '');
if ($cached && (int) $cached > time()) {
return $cached;
}
return dekart_recalc_honest_deadline();
}
function dekart_recalc_honest_deadline(): string
{
$end = new DateTime('last day of this month 23:59:59');
$ts = (int) $end->format('U'); // Unix timestamp — безопасен для JSON/JS
update_option('dekart_honest_deadline', $ts, false);
return (string) $ts;
}
add_action('dekart_daily_refresh_deadline', 'dekart_recalc_honest_deadline');
if (! wp_next_scheduled('dekart_daily_refresh_deadline')) {
wp_schedule_event(time(), 'daily', 'dekart_daily_refresh_deadline');
}
// === ACF JSON Auto-save ===
// Автоматически сохраняет группы полей в JSON при редактировании
add_filter('acf/settings/save_json', function ($path) {
return get_stylesheet_directory() . '/acf-json';
});
// Загружает группы полей из JSON
add_filter('acf/settings/load_json', function ($paths) {
$paths[] = get_stylesheet_directory() . '/acf-json';
return $paths;
});
// Автоматическое удаление JSON при удалении группы полей
add_action('trashed_post', 'dekart_acf_delete_json', 10, 1);
add_action('deleted_post', 'dekart_acf_delete_json', 10, 1);
function dekart_acf_delete_json($post_id)
{
if (get_post_type($post_id) !== 'acf-field-group') {
return;
}
$key = get_post_field('post_name', $post_id);
$json_file = get_stylesheet_directory() . '/acf-json/' . $key . '.json';
if (file_exists($json_file)) {
unlink($json_file);
}
}
// ─────────────────────────────────────────────────────────────
// Счетчики — перенесены во вкладку «Настройки филиала»
// (ТОП-меню удалено; опции зарегистрированы в dekart_branch_group;
// вывод на фронте — те же хуки wp_head/wp_body_open/wp_footer)
// ─────────────────────────────────────────────────────────────
add_action('admin_init', 'dekart_counters_settings');
function dekart_counters_settings()
{
register_setting('dekart_counters_group', 'dekart_counter_yandex');
register_setting('dekart_counters_group', 'dekart_counter_gtm_head');
register_setting('dekart_counters_group', 'dekart_counter_gtm_body');
register_setting('dekart_counters_group', 'dekart_counter_footer');
$yandex_default = "<!-- Yandex.Metrika counter -->\n<script>\n (function(m,e,t,r,i,k,a){\n m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};\n m[i].l=1*new Date();\n for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}\n k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)\n })(window, document,'script','https://mc.yandex.ru/metrika/tag.js', 'ym');\n\n ym(99835482, 'init', {webvisor:true, clickmap:true, referrer: document.referrer, url: location.href, accurateTrackBounce:true, trackLinks:true});\n</script>\n<!-- /Yandex.Metrika counter -->";
$gtm_head_default = "<!-- Google Tag Manager -->\n<script>\n(function(w,d,s,l,i){\n w[l]=w[l]||[];\n w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});\n var f=d.getElementsByTagName(s)[0],\n j=d.createElement(s),\n dl=l!='dataLayer'?'&l='+l:'';\n j.async=true;\n j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;\n f.parentNode.insertBefore(j,f);\n})(window,document,'script','dataLayer','GTM-WJVZ4MKN');\n</script>\n<!-- /Google Tag Manager -->";
$gtm_body_default = "<!-- Google Tag Manager (noscript) -->\n<noscript><iframe src=\"https://www.googletagmanager.com/ns.html?id=GTM-WJVZ4MKN\"\nheight=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe></noscript>\n<!-- /Google Tag Manager (noscript) -->";
$footer_default = "<!-- Yandex.Metrika counter -->\n<noscript><div><img src=\"https://mc.yandex.ru/watch/99835482\" style=\"position:absolute; left:-9999px;\" alt=\"\"></div></noscript>\n<!-- /Yandex.Metrika counter -->";
if (get_option('dekart_counter_yandex') === false) {
add_option('dekart_counter_yandex', $yandex_default);
}
if (get_option('dekart_counter_gtm_head') === false) {
add_option('dekart_counter_gtm_head', $gtm_head_default);
}
if (get_option('dekart_counter_gtm_body') === false) {
add_option('dekart_counter_gtm_body', $gtm_body_default);
}
if (get_option('dekart_counter_footer') === false) {
add_option('dekart_counter_footer', $footer_default);
}
}
function dekart_counters_page()
{
?>
<div class="wrap">
<h1>Счетчики</h1>
<form method="post" action="options.php">
<?php settings_fields('dekart_counters_group'); ?>
<h2 class="title">Яндекс.Метрика / Tag Manager</h2>
<table class="form-table">
<tr>
<th scope="row">Yandex Code</th>
<td><textarea name="dekart_counter_yandex" rows="10" class="large-text code"><?php echo esc_textarea(get_option('dekart_counter_yandex', '')); ?></textarea>
<p class="description">Код счетчика. Выводится в <code>&lt;head&gt;</code>.</p></td>
</tr>
</table>
<h2 class="title">Google Tag Manager (GTM)</h2>
<table class="form-table">
<tr>
<th scope="row">GTM Head</th>
<td><textarea name="dekart_counter_gtm_head" rows="10" class="large-text code"><?php echo esc_textarea(get_option('dekart_counter_gtm_head', '')); ?></textarea>
<p class="description">Основной код GTM. Выводится в <code>&lt;head&gt;</code>.</p></td>
</tr>
<tr>
<th scope="row">GTM Body</th>
<td><textarea name="dekart_counter_gtm_body" rows="6" class="large-text code"><?php echo esc_textarea(get_option('dekart_counter_gtm_body', '')); ?></textarea>
<p class="description">Код <code>&lt;noscript&gt;</code>. Выводится сразу после открытия <code>&lt;body&gt;</code> через хук <code>wp_body_open</code>.</p></td>
</tr>
</table>
<h2 class="title">Произвольные скрипты</h2>
<table class="form-table">
<tr>
<th scope="row">Site-wide Footer Scripts</th>
<td><textarea name="dekart_counter_footer" rows="10" class="large-text code"><?php echo esc_textarea(get_option('dekart_counter_footer', '')); ?></textarea>
<p class="description">Для чатов, пикселей и прочих скриптов. Выводится в <code>&lt;footer&gt;</code>.</p></td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
add_action('wp_head', 'dekart_output_counters', 1);
function dekart_output_counters()
{
$yandex = stripslashes(get_option('dekart_counter_yandex', ''));
$gtmhead = stripslashes(get_option('dekart_counter_gtm_head', ''));
if ($yandex) {
echo "\n" . $yandex . "\n";
}
if ($gtmhead) {
echo "\n" . $gtmhead . "\n";
}
}
add_action('wp_body_open', 'dekart_output_gtm_body', 1);
function dekart_output_gtm_body()
{
$gtmbody = stripslashes(get_option('dekart_counter_gtm_body', ''));
if ($gtmbody) {
echo "\n" . $gtmbody . "\n";
}
}
add_action('wp_footer', 'dekart_output_counters_footer', 100);
function dekart_output_counters_footer()
{
$footer = stripslashes(get_option('dekart_counter_footer', ''));
if ($footer) {
echo "\n" . $footer . "\n";
}
}
// ─────────────────────────────────────────────────────────────
// 🎨 Тёмная тема для Carbon Fields + Настройки филиала
// ─────────────────────────────────────────────────────────────
add_action('admin_head', function () {
$screen = get_current_screen();
if (! $screen) { return; }
$is_carbon = strpos($screen->id, 'carbon_fields') !== false;
if (! $is_carbon) { return; }
?>
<style>
:root {
--cf-bg: #1a1a2e;
--cf-bg-card: #2a2a3e;
--cf-bg-input: #363650;
--cf-border: #3e3e5a;
--cf-text: #e0e0f0;
--cf-text-muted: #9090b0;
--cf-primary: #7c5cfc;
--cf-primary-bg: rgba(124,92,252,0.12);
--cf-success: #4ade80;
--cf-warning: #fbbf24;
}
/* Общий фон страницы */
body.branch-settings_page_carbon_fields,
body.toplevel_page_carbon_fields {
background: var(--cf-bg) !important;
}
#wpbody-content .wrap {
background: transparent !important;
}
/* Carbon Fields dark */
#carbon_fields_container_Настройки_темы_ДЕКАРТ {
background: var(--cf-bg) !important;
color: var(--cf-text) !important;
}
.cf-container__tabs-list {
background: var(--cf-bg-card) !important;
border-color: var(--cf-border) !important;
}
.cf-container__tabs-item {
color: var(--cf-text-muted) !important;
}
.cf-container__tabs-item--current {
background: var(--cf-bg) !important;
color: var(--cf-text) !important;
border-color: var(--cf-primary) !important;
}
.cf-container__tabs-item:hover {
color: var(--cf-text) !important;
background: var(--cf-primary-bg) !important;
}
.cf-field {
background: var(--cf-bg-card) !important;
border-color: var(--cf-border) !important;
}
.cf-field__label {
color: var(--cf-text) !important;
}
.cf-field input[type="text"],
.cf-field input[type="email"],
.cf-field input[type="url"],
.cf-field input[type="number"],
.cf-field textarea,
.cf-field select {
background: var(--cf-bg-input) !important;
color: var(--cf-text) !important;
border-color: var(--cf-border) !important;
}
.cf-field input:focus,
.cf-field textarea:focus,
.cf-field select:focus {
border-color: var(--cf-primary) !important;
box-shadow: 0 0 0 1px var(--cf-primary) !important;
}
.cf-complex__groups {
background: var(--cf-bg) !important;
}
.cf-complex__group {
background: var(--cf-bg-card) !important;
border-color: var(--cf-border) !important;
}
.cf-complex__group__title {
background: var(--cf-bg-input) !important;
color: var(--cf-text) !important;
}
.cf-help-text {
color: var(--cf-text-muted) !important;
}
.cf-field__help {
color: var(--cf-text-muted) !important;
}
.cf-container__text {
color: var(--cf-text-muted) !important;
}
.cf-container h1, .cf-container h2, .cf-container h3 {
color: var(--cf-text) !important;
}
hr {
border-color: var(--cf-border) !important;
}
/* Compact Carbon Fields */
.cf-field {
padding: 12px 16px !important;
margin: 4px 0 !important;
}
.cf-field__label {
font-size: 12px !important;
margin-bottom: 4px !important;
}
.cf-container__tabs {
margin-bottom: 8px !important;
}
.cf-container__tabs-item {
padding: 8px 14px !important;
font-size: 12px !important;
}
.cf-complex__group {
margin: 4px 0 !important;
}
.cf-field input, .cf-field textarea, .cf-field select {
font-size: 13px !important;
padding: 6px 10px !important;
}
/* Settings API dark + compact */
.branch-section {
background: var(--cf-bg-card) !important;
border-color: var(--cf-border) !important;
padding: 12px 16px !important;
margin: 8px 0 !important;
}
.branch-section.collapsed {
background: var(--cf-bg) !important;
border-color: var(--cf-border) !important;
}
.branch-section h2 {
color: var(--cf-text) !important;
font-size: 14px !important;
padding-bottom: 8px !important;
}
.branch-section label {
color: var(--cf-text) !important;
font-size: 12px !important;
}
.branch-section input,
.branch-section textarea,
.branch-section select {
background: var(--cf-bg-input) !important;
color: var(--cf-text) !important;
border-color: var(--cf-border) !important;
font-size: 13px !important;
padding: 6px 10px !important;
}
.branch-section .desc {
color: var(--cf-text-muted) !important;
font-size: 11px !important;
}
.branch-field {
margin-bottom: 10px !important;
}
.wrap h1 {
color: var(--cf-text) !important;
}
.wrap p, .wrap label {
color: var(--cf-text) !important;
}
.branch-section.collapsed {
border-color: var(--cf-border) !important;
}
/* submit button */
.wrap .button-primary {
background: var(--cf-primary) !important;
border-color: var(--cf-primary) !important;
color: #fff !important;
}
</style>
<?php
}, 999);
// ─────────────────────────────────────────────────────────────
// Настройки филиала — WordPress Settings API (под Счетчиками)
// ─────────────────────────────────────────────────────────────
add_action('admin_menu', 'dekart_branch_add_menu');
function dekart_branch_add_menu()
{
add_menu_page(
'Настройки филиала',
'Настройки филиала',
'edit_posts',
'branch-settings',
'dekart_branch_settings_page',
'dashicons-location-alt',
3.1
);
add_submenu_page(
'branch-settings',
'Сбросить кэш',
'🗄️ Сбросить кэш',
'manage_options',
'branch-cache-flush',
'dekart_flush_cache_page'
);
}
add_action('admin_init', 'dekart_branch_register_settings');
function dekart_branch_register_settings()
{
// Кастомная валидация branch_city от опечаток и лишних пробелов
add_filter('sanitize_option_branch_city', function ($value) {
$value = trim($value);
$value = preg_replace('/\s+/', ' ', $value); // множественные пробелы → один
$value = mb_strtoupper(mb_substr($value, 0, 1)) . mb_substr($value, 1); // первая заглавная
return $value;
});
$text_fields = array(
'branch_city_prep' => 'text',
'branch_city' => 'text',
'branch_region' => 'text',
'branch_address' => 'text',
'branch_coords' => 'text',
'branch_postal_code' => 'text',
'branch_phone' => 'text',
'branch_email' => 'email',
'branch_name' => 'text',
'branch_price_range' => 'text',
'branch_price' => 'text',
'branch_price_note' => 'text',
'branch_logistics_walk' => 'text',
'branch_logistics_parking' => 'text',
'branch_logistics_schools' => 'text',
'branch_area_served' => 'text',
'branch_director_name' => 'text',
'branch_director_creds' => 'text',
);
foreach ($text_fields as $slug => $type) {
register_setting('dekart_branch_group', $slug, array( 'sanitize_callback' => $type === 'email' ? 'sanitize_email' : ( $type === 'url' ? 'esc_url_raw' : 'sanitize_text_field' ) ));
}
register_setting('dekart_branch_group', 'branch_logo', array( 'sanitize_callback' => 'absint' ));
register_setting('dekart_branch_group', 'branch_social');
register_setting('dekart_branch_group', 'branch_messengers');
register_setting('dekart_branch_group', 'branch_button_script', array( 'sanitize_callback' => 'dekart_sanitize_script_field' ));
register_setting('dekart_branch_group', 'branch_camp_script', array( 'sanitize_callback' => 'dekart_sanitize_script_field' ));
register_setting('dekart_branch_group', 'branch_myreviews_widget', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_ext_yandex', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_ext_google', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_ext_2gis', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_ext_otzovik', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_ext_vk', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_ext_other_name', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_ext_other_url', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_og_site_name', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_ext_review_count', array( 'sanitize_callback' => 'absint' ));
register_setting('dekart_branch_group', 'branch_avg_rating', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_rating_yandex', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_rating_2gis', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_rating_vk', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_rating_otzovik', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_remaining_seats', array( 'sanitize_callback' => 'absint' ));
register_setting('dekart_branch_group', 'branch_messenger_whatsapp', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_messenger_telegram', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_messenger_max', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_messenger_whatsapp_on', array( 'sanitize_callback' => 'absint' ));
register_setting('dekart_branch_group', 'branch_messenger_telegram_on', array( 'sanitize_callback' => 'absint' ));
register_setting('dekart_branch_group', 'branch_messenger_max_on', array( 'sanitize_callback' => 'absint' ));
register_setting('dekart_branch_group', 'branch_messenger_extra');
register_setting('dekart_branch_group', 'branch_schema_description', array( 'sanitize_callback' => 'sanitize_textarea_field' ));
register_setting('dekart_branch_group', 'branch_license', array( 'sanitize_callback' => 'sanitize_textarea_field' ));
register_setting('dekart_branch_group', 'branch_area_served', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_programs', array( 'sanitize_callback' => 'dekart_sanitize_branch_programs' ));
register_setting('dekart_branch_group', 'branch_price_school', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_price_preschool', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_price_afterschool', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_price_exam', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_price_camp', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_price_clubs', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_work_hours', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_hours', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_google_place_id', array( 'sanitize_callback' => 'sanitize_text_field' ));
register_setting('dekart_branch_group', 'branch_google_maps_url', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_google_business_url', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_yandex_business_url', array( 'sanitize_callback' => 'esc_url_raw' ));
register_setting('dekart_branch_group', 'branch_logistics_walk', array( 'sanitize_callback' => 'sanitize_textarea_field' ));
register_setting('dekart_branch_group', 'branch_logistics_parking', array( 'sanitize_callback' => 'sanitize_textarea_field' ));
register_setting('dekart_branch_group', 'branch_logistics_nearby', array( 'sanitize_callback' => 'sanitize_textarea_field' ));
// Поля branch_map_url, branch_founded, branch_photos — удалены.
// Они дублировали CF таб «SEO и микроразметка» (schema_*).
// Чтение: inc/schema.php → dekart_schema_option() → CF → Settings API (backward compat).
// Счётчики (перенесены из ТОП-меню «Счетчики» во вкладку филиала).
// Регистрация в группе филиала, чтобы сохранялись одной кнопкой.
// Старая группа dekart_counters_group остаётся для обратной совместимости.
foreach (array( 'dekart_counter_yandex', 'dekart_counter_gtm_head', 'dekart_counter_gtm_body', 'dekart_counter_footer' ) as $counter_option) {
register_setting('dekart_branch_group', $counter_option);
}
}
function dekart_branch_settings_page()
{
?>
<div class="wrap">
<h1>Настройки филиала</h1>
<form method="post" action="options.php">
<?php settings_fields('dekart_branch_group'); ?>
<style>
.branch-section{background:#fff;padding:20px;margin:20px 0;border:1px solid #c3c4c7;border-radius:4px;max-width:800px}
.branch-section h2{margin-top:0;padding-bottom:10px;border-bottom:1px solid #eee;cursor:pointer;user-select:none;display:flex;align-items:center;justify-content:space-between}
.branch-section h2::after{content:'';font-size:20px;line-height:1;color:#999;transition:transform .2s}
.branch-section.collapsed h2::after{content:'+'}
.branch-section.collapsed .branch-section-body{display:none}
.branch-section.collapsed{border-color:#e0e0e0;background:#fcfcfc}
.branch-field{margin-bottom:16px}
.branch-field label{display:block;font-weight:600;margin-bottom:4px}
.branch-field input[type=text],.branch-field input[type=email],.branch-field input[type=url]{width:100%;max-width:400px}
.branch-field .desc{color:#646970;font-size:12px;margin:4px 0 0}
.branch-media-wrap{display:flex;align-items:center;gap:12px}
.branch-media-wrap img{max-width:120px;max-height:80px;border:1px solid #ddd;border-radius:4px}
.branch-social-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}
.branch-social-row input{flex:1}
</style>
<div class="branch-section collapsed">
<h2>🌍 ГЕО</h2>
<div class="branch-section-body">
<div class="branch-field">
<label>Город (с предлогом)</label>
<input type="text" name="branch_city_prep" value="<?php echo esc_attr(get_option('branch_city_prep', 'в Щёлково')); ?>">
<p class="desc">Например: "в Щёлково", "в Королёве". Для alt-текстов и описаний.</p>
</div>
<div class="branch-field">
<label>Город (чистый)</label>
<input type="text" name="branch_city" value="<?php echo esc_attr(get_option('branch_city', 'Щёлково')); ?>">
<p class="desc">Для JSON-LD addressLocality.</p>
</div>
<div class="branch-field">
<label>Регион</label>
<input type="text" name="branch_region" value="<?php echo esc_attr(get_option('branch_region', 'Московская область')); ?>">
</div>
<div class="branch-field">
<label>Адрес (улица, дом)</label>
<input type="text" name="branch_address" value="<?php echo esc_attr(get_option('branch_address', 'ул. Талсинская, 24')); ?>">
</div>
<div class="branch-field">
<label>Координаты для карты</label>
<input type="text" name="branch_coords" value="<?php echo esc_attr(get_option('branch_coords', '55.9217, 37.9914')); ?>">
<p class="desc">Формат: "55.9217, 37.9914"</p>
</div>
<div class="branch-field">
<label>Почтовый индекс</label>
<input type="text" name="branch_postal_code" value="<?php echo esc_attr(get_option('branch_postal_code', '')); ?>">
</div>
<div class="branch-field">
<label>Часы работы (текст — контакты)</label>
<input type="text" name="branch_work_hours" value="<?php echo esc_attr(get_option('branch_work_hours', 'пн-пт 8:3019:00, сб 9:0015:00')); ?>">
<p class="desc">Показывается в блоке контактов. Например: "пн-пт 8:3019:00, сб 9:0015:00"</p>
</div>
<div class="branch-field">
<label>Часы работы (текст — хедер)</label>
<input type="text" name="branch_hours" value="<?php echo esc_attr(get_option('branch_hours', 'ежедневно с 8:00 до 19:00, кроме субботы')); ?>">
<p class="desc">Показывается на главной странице в hero-секции, рядом с адресом. Например: "ежедневно с 8:00 до 19:00, кроме субботы" или "пн-пт 8:0019:00, сб 9:0014:00"</p>
</div>
<div class="branch-field">
<label>Google Place ID</label>
<input type="text" name="branch_google_place_id" value="<?php echo esc_attr(get_option('branch_google_place_id', '')); ?>">
<p class="desc">Из Google Business Profile. Для Schema.org identifier + @id</p>
</div>
<div class="branch-field">
<label>Google Maps URL</label>
<input type="url" name="branch_google_maps_url" value="<?php echo esc_attr(get_option('branch_google_maps_url', '')); ?>">
<p class="desc">Полная ссылка на профиль в Google Maps. Для Schema.org hasMap</p>
</div>
<div class="branch-field">
<label>Пешком от ориентира</label>
<input type="text" name="branch_logistics_walk" value="<?php echo esc_attr(get_option('branch_logistics_walk', '5 минут пешком от ТЦ «Глобус»')); ?>">
<p class="desc">Например: «5 минут пешком от ТЦ «Глобус»</p>
</div>
<div class="branch-field">
<label>Парковка</label>
<input type="text" name="branch_logistics_parking" value="<?php echo esc_attr(get_option('branch_logistics_parking', 'Есть бесплатная парковка у входа')); ?>">
<p class="desc">Например: «Есть бесплатная парковка у входа»</p>
</div>
<div class="branch-field">
<label>Рядом с школами/садами</label>
<input type="text" name="branch_logistics_schools" value="<?php echo esc_attr(get_option('branch_logistics_schools', 'Рядом со школами №9 и №12, детским садом №15')); ?>">
<p class="desc">Например: «Рядом со школами №9 и №12, детским садом №15»</p>
</div>
<div class="branch-field">
<label>Гео — города обслуживания</label>
<input type="text" name="branch_area_served" value="<?php echo esc_attr(get_option('branch_area_served', 'Щёлково, Фрязино и Ивантеевки')); ?>">
<p class="desc">Регион работы школы: перечислите города через запятую. Используется в текстах «Принимаем детей из ...».<br>
<strong>Пример:</strong> «Щёлково, Фрязино, Ивантеевка, Королёв»<br>
<strong>Формат:</strong> названия городов в именительном падеже, через запятую.</p>
</div>
</div>
</div>
<div class="branch-section collapsed">
<h2>📞 КОНТАКТЫ</h2>
<div class="branch-section-body">
<div class="branch-field">
<label>Телефон</label>
<input type="text" name="branch_phone" value="<?php echo esc_attr(get_option('branch_phone', '+7 (495) 120-00-78')); ?>">
</div>
<div class="branch-field">
<label>Email</label>
<input type="email" name="branch_email" value="<?php echo esc_attr(get_option('branch_email', 'info@dekart.school')); ?>">
</div>
<div class="branch-field">
<label>Автор контента — ФИО</label>
<input type="text" name="branch_director_name" value="<?php echo esc_attr(get_option('branch_director_name', 'Елена Сергеевна Дегтярёва')); ?>">
<p class="desc">Для E-E-A-T: имя автора страниц школ. Выводится в подвале.</p>
</div>
<div class="branch-field">
<label>Автор контента — должность и опыт</label>
<input type="text" name="branch_director_creds" value="<?php echo esc_attr(get_option('branch_director_creds', 'директор частной школы «Декарт», педагогический стаж 18 лет')); ?>">
<p class="desc">Должность и достижения. Например: «директор частной школы «Декарт», педагогический стаж 18 лет»</p>
</div>
<div class="branch-field">
<label>Лицензия (для Schema.org hasCredential)</label>
<textarea name="branch_license" rows="2" style="width:100%;"><?php echo esc_textarea(get_option('branch_license', '')); ?></textarea>
<p class="desc">Текст про лицензию на образовательную деятельность. Выводится в микроразметке (hasCredential) и в блоке «Работаем официально».</p>
</div>
</div>
</div>
<div class="branch-section collapsed">
<h2>🌐 СОЦСЕТИ И МЕССЕНДЖЕРЫ</h2>
<div class="branch-section-body">
<p class="desc">Добавьте как соцсети (VK, YouTube, Instagram*), так и мессенджеры (WhatsApp, Telegram, Viber и др.). Все они отображаются в блоке «Наши соцсети» на странице. *Meta признана экстремистской организацией.</p>
<div class="branch-field">
<label>Социальные сети</label>
<p class="desc" style="margin-bottom:8px;">Название + ссылка на профиль.</p>
<div id="branch-social-repeater">
<?php
$social = get_option('branch_social', array());
if (! is_array($social) || empty($social)) {
$social = array( array( 'name' => '', 'url' => '' ) );
}
foreach ($social as $i => $item) {
$name = esc_attr($item['name'] ?? '');
$url = esc_attr($item['url'] ?? '');
echo '<div class="branch-social-row"><input type="text" name="branch_social[' . $i . '][name]" value="' . $name . '" placeholder="Название"><input type="url" name="branch_social[' . $i . '][url]" value="' . $url . '" placeholder="https://..."><button type="button" class="button branch-social-remove">—</button></div>';
}
?>
</div>
<button type="button" class="button" id="branch-social-add">+ Добавить соцсеть</button>
</div>
<div class="branch-field">
<label>Мессенджеры</label>
<p class="desc" style="margin-bottom:8px;">Заполните ссылки и отметьте галочку чтобы показывать на сайте.</p>
<div class="branch-msg-row">
<span class="branch-msg-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="#25D366"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413"/></svg>
</span>
<input type="url" name="branch_messenger_whatsapp" value="<?php echo esc_attr(get_option('branch_messenger_whatsapp', '')); ?>" placeholder="https://wa.me/79991234567" style="flex:1;min-width:180px;">
<label><input type="checkbox" name="branch_messenger_whatsapp_on" value="1" <?php checked(get_option('branch_messenger_whatsapp_on', 1), 1); ?>> Показывать</label>
</div>
<div class="branch-msg-row">
<span class="branch-msg-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="#0088CC"><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 1.698 6.147L.02 24l6.469-1.796A12 12 0 0 0 12 24a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm7.502 17.21a7.498 7.498 0 0 1-1.356.807 8.486 8.486 0 0 1-1.82.6 7.44 7.44 0 0 1-2.266.13c-2.09-.27-4.082-1.05-5.61-2.103a18.57 18.57 0 0 1-3.967-3.751A14.8 14.8 0 0 1 2.8 6.106a7.68 7.68 0 0 1 .93-2.905 5.26 5.26 0 0 1 1.08-1.426c.078-.08.157-.149.246-.215.113-.081.226-.161.343-.226.397-.214.838-.221 1.252-.101.21.06.415.139.607.23.285.137.568.278.84.435.09.05.173.115.252.176.326.252.52.572.656.946.08.22.132.448.155.68.053.539.05 1.078-.003 1.611a4.12 4.12 0 0 1-.246.873 5.38 5.38 0 0 1-.34.715c-.104.172-.226.333-.387.446a3.09 3.09 0 0 0-.418.377c-.137.17-.139.375-.028.549a13.482 13.482 0 0 0 1.474 2.005c.018.018.036.035.054.053a13.659 13.659 0 0 0 2.058 1.603c.214.133.398.08.55-.067a4.37 4.37 0 0 1 .902-.72c.397-.24.778-.186 1.125.12.334.294.661.596.983.903.204.195.404.394.589.607.031.036.053.078.077.112a1.81 1.81 0 0 1 .324.603c.062.184.09.375.062.565a2.63 2.63 0 0 1-.218.68 4.98 4.98 0 0 1-.346.639.977.977 0 0 1-.333.322c-.233.148-.479.266-.72.378a3.12 3.12 0 0 1-.525.208c-.214.065-.43.123-.649.163"/></svg>
</span>
<input type="url" name="branch_messenger_telegram" value="<?php echo esc_attr(get_option('branch_messenger_telegram', '')); ?>" placeholder="https://t.me/your_channel" style="flex:1;min-width:180px;">
<label><input type="checkbox" name="branch_messenger_telegram_on" value="1" <?php checked(get_option('branch_messenger_telegram_on', 1), 1); ?>> Показывать</label>
</div>
<div class="branch-msg-row">
<span class="branch-msg-icon">
<svg width="18" height="18" viewBox="0 0 32 32" fill="#7360F2"><path d="M16 2C8.268 2 2 8.268 2 16s6.268 14 14 14 14-6.268 14-14S23.732 2 16 2zm7.066 19.445a3.408 3.408 0 0 1-.65.746 4.19 4.19 0 0 1-.874.574 5.054 5.054 0 0 1-1.352.374 3.02 3.02 0 0 0-.472.058 2.986 2.986 0 0 1-.64-.03c-.138-.03-.268-.072-.398-.118-.154-.058-.304-.132-.45-.208-.282-.15-.54-.346-.778-.562a19.364 19.364 0 0 1-2.306-2.412 16.543 16.543 0 0 1-.906-1.188c-.06-.086-.12-.172-.178-.26a1.8 1.8 0 0 1-.146-.264c-.058-.122-.072-.256-.034-.38.036-.116.1-.222.166-.322a5.08 5.08 0 0 1 .332-.422c.084-.102.176-.2.252-.31.114-.164.218-.336.288-.52.05-.128.07-.264.04-.396a1.56 1.56 0 0 0-.102-.312c-.054-.124-.116-.246-.176-.368l-.384-.82c-.152-.326-.308-.652-.46-.978l-.466-1c-.124-.266-.252-.53-.38-.794-.12-.252-.246-.5-.37-.75-.106-.216-.216-.43-.326-.644-.166-.324-.34-.644-.51-.966-.068-.128-.134-.26-.202-.388-.078-.148-.144-.302-.21-.456-.108-.26-.168-.542-.182-.822-.014-.268.002-.542.058-.8.104-.47.3-.912.568-1.304.156-.226.33-.434.532-.614.07-.06.15-.112.228-.166.126-.09.264-.164.398-.232.218-.114.454-.188.694-.246a6.25 6.25 0 0 1 .88-.138c.306-.032.614-.036.92-.012.22.018.434.07.64.14.122.042.238.094.35.156l.19.112c.078.046.15.098.22.158.14.122.264.262.37.414.048.068.09.138.13.21l.062.112c.044.08.082.164.114.25.064.174.11.354.138.538.016.098.024.198.03.298.008.11.01.222.008.332v.158c0 .094-.004.188-.012.28-.008.09-.02.18-.036.268a3.46 3.46 0 0 1-.256.7 3.52 3.52 0 0 1-.242.458 1.86 1.86 0 0 1-.26.322c-.082.084-.17.16-.24.252-.11.146-.154.332-.12.514.022.11.07.214.12.316.058.116.12.23.186.342.254.43.536.844.842 1.24.214.278.442.544.686.79.114.116.234.226.356.334.068.06.14.118.212.174.14.108.3.182.47.216.176.036.36-.002.518-.094.134-.078.26-.168.382-.264.084-.066.164-.138.248-.202.058-.044.118-.086.176-.126.12-.082.246-.156.376-.22.114-.056.23-.106.35-.148a2.22 2.22 0 0 1 .578-.13c.17-.02.342-.006.504.056.164.06.312.152.44.27.128.114.23.25.312.4.046.082.082.168.112.256.034.096.06.196.078.296.022.114.032.23.032.346 0 .134-.008.274-.024.41a4.76 4.76 0 0 1-.228.864c-.104.308-.25.6-.43.874-.07.108-.146.212-.226.314z"/></svg>
</span>
<input type="url" name="branch_messenger_max" value="<?php echo esc_attr(get_option('branch_messenger_max', '')); ?>" placeholder="https://..." style="flex:1;min-width:180px;">
<label><input type="checkbox" name="branch_messenger_max_on" value="1" <?php checked(get_option('branch_messenger_max_on', 1), 1); ?>> Показывать</label>
</div>
<div id="branch-messengers-container">
<?php
$extra_msgs = get_option('branch_messenger_extra', array());
if (! empty($extra_msgs) && is_array($extra_msgs)) :
foreach ($extra_msgs as $i => $m) :
$m_name = $m['name'] ?? '';
$m_url = $m['url'] ?? '';
?>
<div class="branch-social-row">
<input type="text" name="branch_messenger_extra[<?php echo intval($i); ?>][name]" value="<?php echo esc_attr($m_name); ?>" placeholder="Название" style="max-width:160px;">
<input type="url" name="branch_messenger_extra[<?php echo intval($i); ?>][url]" value="<?php echo esc_attr($m_url); ?>" placeholder="https://..." style="max-width:300px;">
<button type="button" class="button branch-messenger-remove"></button>
</div>
<?php endforeach;
endif; ?>
</div>
<button type="button" class="button" id="branch-messenger-add" style="margin-top:4px;">+ Добавить ещё</button>
</div>
<style>
.branch-msg-row {
display:flex; align-items:center; gap:8px; margin-bottom:6px; flex-wrap:wrap;
}
.branch-msg-icon {
display:inline-flex; width:32px; height:32px; align-items:center; justify-content:center; flex-shrink:0;
}
.branch-msg-icon svg { display:block; }
</style>
<script>
(function() {
var socialContainer = document.getElementById('branch-social-repeater');
var socialAdd = document.getElementById('branch-social-add');
if (socialAdd) {
socialAdd.addEventListener('click', function() {
var idx = socialContainer.children.length;
var row = document.createElement('div');
row.className = 'branch-social-row';
row.innerHTML = '<input type="text" name="branch_social[' + idx + '][name]" placeholder="Название"><input type="url" name="branch_social[' + idx + '][url]" placeholder="https://..."><button type="button" class="button branch-social-remove">—</button>';
socialContainer.appendChild(row);
row.querySelector('.branch-social-remove').addEventListener('click', function() { row.remove(); });
});
socialContainer.addEventListener('click', function(e) {
if (e.target.classList.contains('branch-social-remove')) {
e.target.parentElement.remove();
}
});
}
var extraContainer = document.getElementById('branch-messengers-container');
var extraAdd = document.getElementById('branch-messenger-add');
if (extraAdd) {
var idx = extraContainer.children.length;
extraAdd.addEventListener('click', function() {
var row = document.createElement('div');
row.className = 'branch-social-row';
row.innerHTML = '<input type="text" name="branch_messenger_extra[' + idx + '][name]" placeholder="Название" style="max-width:160px;">' +
'<input type="url" name="branch_messenger_extra[' + idx + '][url]" placeholder="https://..." style="max-width:300px;">' +
'<button type="button" class="button branch-messenger-remove"></button>';
extraContainer.appendChild(row);
idx++;
row.querySelector('.branch-messenger-remove').addEventListener('click', function() { row.remove(); });
});
extraContainer.addEventListener('click', function(e) {
if (e.target.classList.contains('branch-messenger-remove')) {
e.target.parentElement.remove();
}
});
}
})();
</script>
</div>
</div>
<div class="branch-section collapsed">
<h2>🎓 ПРОГРАММЫ И НАПРАВЛЕНИЯ</h2>
<div class="branch-section-body">
<p class="desc">Единый каталог программ/направлений школы: <strong>название, цена, пояснение к цене, ссылка на страницу программы, теги каталога</strong>. Цены отсюда попадают в карточки преподавателей и в микроразметку Offer. Преподаватели <strong>выбирают</strong> программу из каталога (Настройки → преподаватель → «Предметы обучения») — свои теги направления у них задаются облаком тегов отдельно.</p>
<div id="branch-programs-repeater">
<?php
$programs = get_option('branch_programs', array());
if (! is_array($programs) || empty($programs)) {
$programs = array( array( 'name' => '', 'price' => '', 'price_note' => '', 'url' => '', 'tags' => '' ) );
}
foreach ($programs as $i => $p) {
echo '<div class="branch-program-row">'
. '<input type="text" name="branch_programs[' . $i . '][name]" value="' . esc_attr($p['name'] ?? '') . '" placeholder="Название (напр. Нейропсихолог)">'
. '<input type="text" name="branch_programs[' . $i . '][price]" value="' . esc_attr($p['price'] ?? '') . '" placeholder="Цена (напр. от 1 290 ₽)" style="max-width:170px;">'
. '<input type="text" name="branch_programs[' . $i . '][price_note]" value="' . esc_attr($p['price_note'] ?? '') . '" placeholder="Пояснение к цене" style="max-width:200px;">'
. '<input type="url" name="branch_programs[' . $i . '][url]" value="' . esc_attr($p['url'] ?? '') . '" placeholder="https://… (страница программы)" style="max-width:200px;">'
. '<input type="text" name="branch_programs[' . $i . '][tags]" value="' . esc_attr($p['tags'] ?? '') . '" placeholder="Теги каталога (слова через пробел)" style="max-width:240px;">'
. '<button type="button" class="button branch-program-remove">—</button>'
. '</div>';
}
?>
</div>
<button type="button" class="button" id="branch-programs-add">+ Добавить программу</button>
<style>
.branch-program-row {
display:flex; align-items:center; gap:8px; margin-bottom:6px; flex-wrap:wrap;
}
.branch-program-row input[type="text"],
.branch-program-row input[type="url"] {
min-width:160px;
}
</style>
<script>
(function() {
var container = document.getElementById('branch-programs-repeater');
var addBtn = document.getElementById('branch-programs-add');
if (! addBtn) { return; }
addBtn.addEventListener('click', function() {
var idx = container.children.length;
var row = document.createElement('div');
row.className = 'branch-program-row';
row.innerHTML = '<input type="text" name="branch_programs[' + idx + '][name]" placeholder="Название (напр. Нейропсихолог)">'
+ '<input type="text" name="branch_programs[' + idx + '][price]" placeholder="Цена (напр. от 1 290 ₽)" style="max-width:170px;">'
+ '<input type="text" name="branch_programs[' + idx + '][price_note]" placeholder="Пояснение к цене" style="max-width:200px;">'
+ '<input type="url" name="branch_programs[' + idx + '][url]" placeholder="https://…" style="max-width:200px;">'
+ '<input type="text" name="branch_programs[' + idx + '][tags]" placeholder="Теги каталога" style="max-width:240px;">'
+ '<button type="button" class="button branch-program-remove">—</button>';
container.appendChild(row);
});
container.addEventListener('click', function(e) {
if (e.target.classList.contains('branch-program-remove')) {
e.target.parentElement.remove();
}
});
})();
</script>
</div>
</div>
<div class="branch-section collapsed">
<h2>🏢 ОРГАНИЗАЦИЯ</h2>
<div class="branch-section-body">
<div class="branch-field">
<label>Полное название организации</label>
<input type="text" name="branch_name" value="<?php echo esc_attr(get_option('branch_name', 'Декарт')); ?>">
</div>
<div class="branch-field">
<label>OG: Site Name (для мета-тега og:site_name)</label>
<input type="text" name="branch_og_site_name" value="<?php echo esc_attr(get_option('branch_og_site_name', 'Частная школа Декарт в Щёлково')); ?>" style="width:100%;max-width:500px">
<p class="desc">Значение тега <code>og:site_name</code> — отображается в сниппетах Яндекса и Google. Рекомендуется: «Частная школа Декарт в Щёлково». Если оставить пустым — будет использован заголовок сайта из настроек WordPress.</p>
</div>
<div class="branch-field">
<label>Ценовой диапазон</label>
<input type="text" name="branch_price_range" value="<?php echo esc_attr(get_option('branch_price_range', '$$')); ?>">
<p class="desc">Для микроразметки. Например: "$", "$$", "$$$".</p>
</div>
<div class="branch-field">
<label>Цена занятия (для страниц преподавателей)</label>
<input type="text" name="branch_price" value="<?php echo esc_attr(get_option('branch_price', '')); ?>" style="width:100%;max-width:220px">
<p class="desc">Стоимость занятия в мини-группе. Пример: «от 1 290 ₽». Выводится в блоке стоимости на странице преподавателя и в микроразметке Offer (price).<br><strong>Пусто</strong> — на странице преподавателя покажется «Стоимость — по запросу», в микроразметке price не выводится.</p>
</div>
<div class="branch-field">
<label>Пояснение к цене</label>
<input type="text" name="branch_price_note" value="<?php echo esc_attr(get_option('branch_price_note', 'за занятие 60 минут в мини-группе')); ?>" style="width:100%;max-width:420px">
<p class="desc">Подпись под ценой. Пример: «за занятие 60 минут в мини-группе». Пусто — подпись скрыта.</p>
</div>
<div class="branch-field">
<label>Средний рейтинг школы (для метрик)</label>
<input type="text" name="branch_avg_rating" value="<?php echo esc_attr(get_option('branch_avg_rating', '4.9')); ?>" style="width:100%;max-width:120px">
<p class="desc">Отображается в блоке метрик на странице отзывов. Пример: 4.9</p>
</div>
<div class="branch-field">
<label>Оставшиеся места (для триггера срочности)</label>
<input type="number" name="branch_remaining_seats" value="<?php echo esc_attr(get_option('branch_remaining_seats', '12')); ?>" style="width:100%;max-width:120px" min="0">
<p class="desc">Отображается в финальном CTA на странице отзывов: «осталось N мест».</p>
</div>
<div class="branch-field">
<label>Логотип организации (ID вложения)</label>
<input type="number" name="branch_logo" value="<?php echo esc_attr(get_option('branch_logo', '')); ?>" min="0" style="max-width:120px">
<p class="desc">ID изображения из Медиафайлы. Для JSON-LD image.</p>
</div>
<div class="branch-field">
<label>Описание организации (для Schema.org)</label>
<textarea name="branch_schema_description" rows="3" style="width:100%;max-width:500px"><?php echo esc_textarea(get_option('branch_schema_description', '')); ?></textarea>
<p class="desc">Появляется в сниппете Google и Яндекса как описание организации. Рекомендуется 12 предложения: тип школы, какие классы, особенности. Пример: «Частная школа в Щёлково с углублённым английским. Обучение с 1 по 11 класс, подготовка к ЕГЭ и ОГЭ, продлёнка, индивидуальный подход.» Если пусто — собирается автоматически из названия и города филиала.</p>
</div>
</div>
</div>
<div class="branch-section collapsed">
<h2>🎯 СКРИПТ ДЛЯ КНОПОК</h2>
<div class="branch-section-body">
<p class="desc">Bitrix24-скрипт, который выполняется при клике на CTA-кнопки (например, «Записаться на экскурсию»). Вставьте полный JS-код формы.</p>
<div class="branch-field">
<label>Скрипт (JS/HTML) — общий</label>
<textarea name="branch_button_script" rows="8" style="width:100%;max-width:700px;font-family:monospace;font-size:13px"><?php echo esc_textarea(get_option('branch_button_script', '')); ?></textarea>
</div>
<div class="branch-field">
<label>Скрипт (JS/HTML) — для лагеря</label>
<textarea name="branch_camp_script" rows="6" style="width:100%;max-width:700px;font-family:monospace;font-size:13px"><?php echo esc_textarea(get_option('branch_camp_script', '')); ?></textarea>
<p class="desc">Скрипт для кнопок на странице <strong>/gorodskoj-lager/</strong>. Если не заполнен — используется общий скрипт выше.</p>
</div>
</div>
</div>
<div class="branch-section collapsed">
<h2>⭐ MYREVIEWS</h2>
<div class="branch-section-body">
<p class="desc">Виджет отзывов от MyReviews. Вставьте ссылку на фид.</p>
<div class="branch-field">
<label>Ссылка фида MyReviews</label>
<input type="text" name="branch_myreviews_widget" value="<?php echo esc_attr(get_option('branch_myreviews_widget', '')); ?>" style="width:100%;max-width:500px">
<p class="desc">Например: https://feed.myreviews.ru/27645</p>
</div>
</div>
</div>
<div class="branch-section collapsed">
<h2>🔗 E-E-A-T: Внешние платформы</h2>
<div class="branch-section-body">
<p class="desc">Ссылки на внешние площадки с отзывами для повышения E-E-A-T. Отображаются под заголовком «Отзывы на внешних площадках».</p>
<div class="branch-field">
<label>Google Business Profile (ссылка на профиль)</label>
<input type="url" name="branch_google_business_url" value="<?php echo esc_attr(get_option('branch_google_business_url', '')); ?>" style="width:100%;max-width:500px">
<p class="desc">Полная ссылка на профиль в Google Business Profile. Добавляется в sameAs[] Schema.org и выводится как кнопка на странице. <br><strong>Где взять:</strong> откройте Google Карты → найдите свою организацию → нажмите «Поделиться» → скопируйте ссылку. Пример: https://maps.google.com/?cid=1234567890 или https://g.page/r/your-business-slug</p>
</div>
<div class="branch-field">
<label>Яндекс Бизнес / Карты (профиль организации)</label>
<input type="url" name="branch_yandex_business_url" value="<?php echo esc_attr(get_option('branch_yandex_business_url', '')); ?>" style="width:100%;max-width:500px">
<p class="desc">Ссылка на профиль организации в Яндекс Бизнес / Яндекс Картах. Добавляется в sameAs[] Schema.org. <br><strong>Где взять:</strong> откройте Яндекс Карты → найдите организацию → скопируйте URL из адресной строки. Пример: https://yandex.ru/maps/org/.../</p>
</div>
<div class="branch-field">
<label>Яндекс Карты (ссылка на отзывы)</label>
<input type="url" name="branch_ext_yandex" value="<?php echo esc_attr(get_option('branch_ext_yandex', '')); ?>" style="width:100%;max-width:500px">
<p class="desc">https://yandex.ru/maps/org/.../reviews/</p>
</div>
<div class="branch-field">
<label>Google Карты (ссылка на отзывы)</label>
<input type="url" name="branch_ext_google" value="<?php echo esc_attr(get_option('branch_ext_google', '')); ?>" style="width:100%;max-width:500px">
<p class="desc">https://maps.google.com/maps/place/.../ ...</p>
</div>
<div class="branch-field">
<label>2ГИС (ссылка на отзывы)</label>
<input type="url" name="branch_ext_2gis" value="<?php echo esc_attr(get_option('branch_ext_2gis', '')); ?>" style="width:100%;max-width:500px">
<p class="desc">https://2gis.ru/.../</p>
</div>
<div class="branch-field">
<label>Отзовик / IRecommend</label>
<input type="url" name="branch_ext_otzovik" value="<?php echo esc_attr(get_option('branch_ext_otzovik', '')); ?>" style="width:100%;max-width:500px">
</div>
<div class="branch-field">
<label>Другая платформа (название)</label>
<input type="text" name="branch_ext_other_name" value="<?php echo esc_attr(get_option('branch_ext_other_name', '')); ?>" style="width:100%;max-width:500px">
</div>
<div class="branch-field">
<label>Другая платформа (ссылка)</label>
<input type="url" name="branch_ext_other_url" value="<?php echo esc_attr(get_option('branch_ext_other_url', '')); ?>" style="width:100%;max-width:500px">
</div>
<div class="branch-field">
<label>Количество отзывов на внешних площадках</label>
<input type="number" name="branch_ext_review_count" value="<?php echo esc_attr(get_option('branch_ext_review_count', '247')); ?>" style="width:100%;max-width:200px" min="0">
<p class="desc">Отображается в бейдже под метриками: «4.9 из 5 на основе N отзывов на внешних площадках».</p>
</div>
<div class="branch-field">
<label>Рейтинг на Яндекс.Картах</label>
<input type="text" name="branch_rating_yandex" value="<?php echo esc_attr(get_option('branch_rating_yandex', '5.0')); ?>" style="width:100%;max-width:80px" placeholder="4.8">
<p class="desc">Отображается в блоке «Нас рекомендуют» на странице /podgotovka-k-shkole/. Пример: 5.0</p>
</div>
<div class="branch-field">
<label>Рейтинг на 2ГИС</label>
<input type="text" name="branch_rating_2gis" value="<?php echo esc_attr(get_option('branch_rating_2gis', '4.7')); ?>" style="width:100%;max-width:80px" placeholder="4.7">
</div>
<div class="branch-field">
<label>Рейтинг ВКонтакте</label>
<input type="text" name="branch_rating_vk" value="<?php echo esc_attr(get_option('branch_rating_vk', '4.9')); ?>" style="width:100%;max-width:80px" placeholder="4.9">
</div>
<div class="branch-field">
<label>Рейтинг на Otzovik</label>
<input type="text" name="branch_rating_otzovik" value="<?php echo esc_attr(get_option('branch_rating_otzovik', '4.6')); ?>" style="width:100%;max-width:80px" placeholder="4.6">
</div>
</div>
</div>
<div class="branch-section collapsed">
<h2>📊 СЧЕТЧИКИ</h2>
<div class="branch-section-body">
<div class="branch-field">
<label>Яндекс.Метрика (head)</label>
<textarea name="dekart_counter_yandex" rows="8" style="width:100%;max-width:700px;font-family:monospace;font-size:13px"><?php echo esc_textarea(get_option('dekart_counter_yandex', '')); ?></textarea>
<p class="desc">Код счётчика. Выводится в <code>&lt;head&gt;</code>.</p>
</div>
<div class="branch-field">
<label>Google Tag Manager (head)</label>
<textarea name="dekart_counter_gtm_head" rows="8" style="width:100%;max-width:700px;font-family:monospace;font-size:13px"><?php echo esc_textarea(get_option('dekart_counter_gtm_head', '')); ?></textarea>
<p class="desc">Основной код GTM. Выводится в <code>&lt;head&gt;</code>.</p>
</div>
<div class="branch-field">
<label>Google Tag Manager (body / noscript)</label>
<textarea name="dekart_counter_gtm_body" rows="5" style="width:100%;max-width:700px;font-family:monospace;font-size:13px"><?php echo esc_textarea(get_option('dekart_counter_gtm_body', '')); ?></textarea>
<p class="desc">Код <code>&lt;noscript&gt;</code>. Выводится сразу после открытия <code>&lt;body&gt;</code> через хук <code>wp_body_open</code>.</p>
</div>
<div class="branch-field">
<label>Произвольные скрипты (footer)</label>
<textarea name="dekart_counter_footer" rows="8" style="width:100%;max-width:700px;font-family:monospace;font-size:13px"><?php echo esc_textarea(get_option('dekart_counter_footer', '')); ?></textarea>
<p class="desc">Для чатов, пикселей и прочих скриптов. Выводится в <code>&lt;footer&gt;</code>.</p>
</div>
</div>
</div>
<script>
// Сворачивание/разворачивание блоков
document.querySelectorAll('.branch-section > h2').forEach(function(h2){
h2.addEventListener('click', function(){
this.parentElement.classList.toggle('collapsed');
});
});
// Соцсети и мессенджеры: их JS инициализируется внутри секции «СОЦСЕТИ И МЕССЕНДЖЕРЫ»
</script>
<?php submit_button('Сохранить настройки филиала'); ?>
</form>
</div>
<?php
}
/**
* Страница: Сброс кеша (подменю Настройки филиала)
*/
function dekart_flush_cache_page()
{
?>
<div class="wrap">
<h1>🗄️ Сброс кеша</h1>
<p class="desc" style="margin:16px 0;color:#646970;font-size:13px">Полная диагностика и очистка: проверка файлов, установка drop-in, сброс кеша и транзиентов.</p>
<button type="button" id="dekart-flush-cache-btn" class="button button-hero button-secondary" style="display:inline-flex;align-items:center;gap:8px">
<span class="dashicons dashicons-update" style="font-size:20px;width:20px;height:20px"></span> Выполнить проверку и сброс
</button>
<div id="dekart-flush-cache-log" style="margin-top:20px;max-width:700px;display:none"></div>
<script>
(function() {
var btn = document.getElementById('dekart-flush-cache-btn');
var log = document.getElementById('dekart-flush-cache-log');
if (!btn) return;
function addLog(type, text) {
var icons = { ok: '✅', fail: '❌', warn: '⚠️', info: '', doing: '🔄' };
var colors = { ok: '#00a32a', fail: '#d63638', warn: '#dba617', info: '#2271b1', doing: '#646970' };
var line = document.createElement('div');
line.style.cssText = 'padding:6px 10px;margin:2px 0;border-radius:4px;background:#f6f7f7;font-size:13px;line-height:1.5';
line.innerHTML = (icons[type] || '') + ' <span style="color:' + (colors[type] || '#333') + '">' + text + '</span>';
log.appendChild(line);
}
btn.addEventListener('click', function() {
if (btn.disabled) return;
btn.disabled = true;
btn.innerHTML = '<span style="display:inline-block;animation:spin 1s linear infinite">↻</span> Выполнение...';
log.style.display = 'block';
log.innerHTML = '';
addLog('doing', 'Запуск диагностики...');
var data = new FormData();
data.set('action', 'dekart_flush_cache');
data.set('nonce', '<?php echo wp_create_nonce('dekart_flush_cache_nonce'); ?>');
fetch(ajaxurl, { method: 'POST', body: data })
.then(function(r) { return r.json(); })
.then(function(j) {
log.innerHTML = '';
if (j.success && j.data && j.data.log) {
j.data.log.forEach(function(item) {
addLog(item.type, item.text);
});
if (j.data.summary) {
var s = document.createElement('div');
s.style.cssText = 'margin-top:12px;padding:10px 14px;border-radius:6px;font-weight:600;font-size:14px;background:' + (j.data.summary.type === 'ok' ? '#edfaef' : '#fcf0f1') + ';color:' + (j.data.summary.type === 'ok' ? '#00a32a' : '#d63638');
s.innerHTML = (j.data.summary.type === 'ok' ? '✅' : '❌') + ' ' + j.data.summary.text;
log.appendChild(s);
}
} else {
addLog('fail', j.data && j.data.message ? j.data.message : 'Неизвестная ошибка');
}
})
.catch(function() {
log.innerHTML = '';
addLog('fail', 'Ошибка соединения с сервером');
})
.finally(function() {
btn.disabled = false;
btn.innerHTML = '<span class="dashicons dashicons-update" style="font-size:20px;width:20px;height:20px"></span> Выполнить проверку и сброс';
});
});
})();
</script>
<style>
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</div>
<?php
}
/* ════════════════════════════════════════════════════════════
🎯 REDESIGN 2026 — Schema + H1 filter + Bento CSS/JS
════════════════════════════════════════════════════════════ */
/**
* Подключаем динамический генератор Schema.org JSON-LD
* Единый @graph в wp_head. Только Carbon Fields данные.
*/
require_once __DIR__ . '/inc/schema.php';
/**
* Фильтр: замена H1 для страницы "Подготовка к школе" (ID 2)
* Добавляет Risk Reversal в заголовок
*/
// podgotovka-k-shkole.css enqueued in enqueue_template_specific_styles()
/**
* Счётчики доверия в админку Carbon Fields — добавляем поля
* "выпускники", "лет опыта", "рейтинг"
* (настройки темы уже есть — используем статические)
*/
// ─────────────────────────────────────────────────────────────
// Helper: список преподавателей для select поля
// ─────────────────────────────────────────────────────────────
function dekart_get_teacher_options()
{
$options = array();
$teachers = get_posts(array(
'post_type' => 'teacher',
'posts_per_page' => -1,
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
));
foreach ($teachers as $t) {
$options[ $t->ID ] = esc_html($t->post_title);
}
return $options;
}
// ─────────────────────────────────────────────────────────────
// ACF: Отзывы (Лагерь) — группа полей
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// 🗄️ AJAX: Сброс кеша (кнопка в Настройки филиала)
// ─────────────────────────────────────────────────────────────
add_action('wp_ajax_dekart_flush_cache', 'dekart_ajax_flush_cache');
function dekart_ajax_flush_cache()
{
check_ajax_referer('dekart_flush_cache_nonce', 'nonce');
if (! current_user_can('manage_options')) {
wp_send_json_error(array( 'message' => 'Нет прав' ));
}
$log = array();
$errors = 0;
$abspath = defined('ABSPATH') ? ABSPATH : dirname(__DIR__, 4) . '/';
// ── 1. Проверка файлов ──
$files = array(
'MU-плагин (object-cache-bootstrap)' => WP_CONTENT_DIR . '/mu-plugins/object-cache-bootstrap.php',
'Плагин Memcached (object-cache.php)' => WP_CONTENT_DIR . '/plugins/memcached/object-cache.php',
'Тема (functions.php)' => get_template_directory() . '/functions.php',
'wp-config.php' => $abspath . 'wp-config.php',
);
$log[] = array( 'type' => 'info', 'text' => '🔍 Проверка файлов системы:' );
foreach ($files as $label => $path) {
if (file_exists($path)) {
$size = size_format(filesize($path));
$log[] = array( 'type' => 'ok', 'text' => "{$label} — найден ({$size})" );
} else {
$log[] = array( 'type' => 'warn', 'text' => "{$label} — отсутствует" );
}
}
// ── 2. PHP extension Memcache ──
$log[] = array( 'type' => 'info', 'text' => '🔍 Проверка PHP-расширения:' );
if (class_exists('Memcache') || class_exists('Memcached')) {
$ext = class_exists('Memcached') ? 'memcached' : 'memcache';
$log[] = array( 'type' => 'ok', 'text' => "PHP-расширение {$ext} — установлено" );
} else {
$log[] = array( 'type' => 'warn', 'text' => 'PHP-расширение memcache/memcached — не найдено (обратитесь в хостинг)' );
}
// ── 3. Memcached drop-in ──
$source = WP_CONTENT_DIR . '/plugins/memcached/object-cache.php';
$target = WP_CONTENT_DIR . '/object-cache.php';
$log[] = array( 'type' => 'info', 'text' => '🔍 Проверка drop-in Memcached:' );
if (file_exists($target)) {
$size = size_format(filesize($target));
$log[] = array( 'type' => 'ok', 'text' => "Drop-in уже установлен: wp-content/object-cache.php ({$size})" );
} else {
if (file_exists($source)) {
$copied = copy($source, $target);
if ($copied) {
$size = size_format(filesize($target));
$log[] = array( 'type' => 'ok', 'text' => "Drop-in скопирован: plugins/memcached/object-cache.php → wp-content/object-cache.php ({$size})" );
} else {
$log[] = array( 'type' => 'fail', 'text' => 'Ошибка копирования! Проверьте права на wp-content/' );
$errors++;
}
} else {
$log[] = array( 'type' => 'warn', 'text' => 'Плагин Memcached не установлен. Установите: wp plugin install memcached' );
}
}
// ── 4. Проверка WP_CACHE_KEY_SALT ──
$log[] = array( 'type' => 'info', 'text' => '🔍 Проверка изоляции ключей кеша:' );
if (defined('WP_CACHE_KEY_SALT') && WP_CACHE_KEY_SALT !== '') {
$log[] = array( 'type' => 'ok', 'text' => 'WP_CACHE_KEY_SALT = ' . WP_CACHE_KEY_SALT . ' (изоляция активна)' );
} else {
$log[] = array( 'type' => 'fail', 'text' => 'WP_CACHE_KEY_SALT не задан! Добавьте в wp-config.php: define( "WP_CACHE_KEY_SALT", DB_NAME );' );
$errors++;
}
// ── 5. Versioned cache bump (MU-плагин) ──
$log[] = array( 'type' => 'info', 'text' => '🔍 Инвалидация versioned cache:' );
if (function_exists('my_cache_bump_version')) {
my_cache_bump_version();
$log[] = array( 'type' => 'ok', 'text' => 'Версия кеша обновлена (my_cache_bump_version)' );
} else {
$log[] = array( 'type' => 'warn', 'text' => 'my_cache_bump_version() не найдена — MU-плагин не активен' );
}
// ── 6. WP object cache flush ──
$log[] = array( 'type' => 'info', 'text' => '🔍 Очистка объектного кеша:' );
if (function_exists('wp_cache_flush')) {
wp_cache_flush();
$log[] = array( 'type' => 'ok', 'text' => 'wp_cache_flush() — выполнен (ключи ротированы)' );
} else {
$log[] = array( 'type' => 'warn', 'text' => 'wp_cache_flush() — функция не найдена (drop-in не установлен)' );
}
// ── 7. Transients ──
$log[] = array( 'type' => 'info', 'text' => '🔍 Очистка транзиентов:' );
global $wpdb;
$deleted = $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_%' OR option_name LIKE '_transient_timeout_%'");
$log[] = array( 'type' => 'ok', 'text' => "Удалено {$deleted} записей _transient_* из wp_options" );
// ── Итог ──
if ($errors === 0) {
$summary = array( 'type' => 'ok', 'text' => 'Всё выполнено успешно. Ошибок нет.' );
} else {
$summary = array( 'type' => 'fail', 'text' => "Выполнено с {$errors} ошибкой(ами). Проверьте лог выше." );
}
wp_send_json_success(array(
'log' => $log,
'summary' => $summary,
));
}
/**
* program-pksh: разрешаем индексацию — страница содержит
* официальную образовательную программу со ссылками на ФЗ №273, ФГОС ДО.
* Это сильный E-E-A-T сигнал + точка входа по запросам "программа подготовка к школе фгос".
* noindex был ошибочным — он убивал E-E-A-T и терял трафик по длинным образовательным запросам.
*/
// ════════════════════════════════════════════
// Админ: вертикальные стабильные вкладки CF для преподавателя
// (нативный layout 'tabbed-vertical' + страховка от «списка вместо вкладок»)
// ════════════════════════════════════════════
function dekart_admin_teacher_tab_assets($hook)
{
if (! in_array($hook, array( 'post.php', 'post-new.php' ), true)) {
return;
}
$screen = get_current_screen();
if (! $screen || 'teacher' !== (string) $screen->post_type) {
return;
}
wp_enqueue_style(
'dekart-admin-teacher-tabs',
get_template_directory_uri() . '/assets/css/admin-teacher-tabs.css',
array( 'carbon-fields-core' ),
filemtime(get_template_directory() . '/assets/css/admin-teacher-tabs.css')
);
wp_enqueue_script(
'dekart-admin-teacher-tabs',
get_template_directory_uri() . '/assets/js/admin-teacher-tabs.js',
array(),
filemtime(get_template_directory() . '/assets/js/admin-teacher-tabs.js'),
true
);
}
/**
* Санитайзер каталога программ филиала (branch_programs).
* Каждая запись: name, price, price_note, url, tags.
*/
function dekart_sanitize_branch_programs($value)
{
if (! is_array($value)) {
return array();
}
$out = array();
foreach ($value as $item) {
if (! is_array($item)) {
continue;
}
$name = trim(sanitize_text_field((string) ( $item['name'] ?? '' )));
if ('' === $name) {
continue; // пустая строка — пропускаем
}
$out[] = array(
'name' => $name,
'price' => trim(sanitize_text_field((string) ( $item['price'] ?? '' ))),
'price_note' => trim(sanitize_text_field((string) ( $item['price_note'] ?? '' ))),
'url' => esc_url_raw(trim((string) ( $item['url'] ?? '' ))),
'tags' => trim(sanitize_text_field((string) ( $item['tags'] ?? '' ))),
);
}
return $out;
}
add_action('admin_enqueue_scripts', 'dekart_admin_teacher_tab_assets');
// ─────────────────────────────────────────────
// Админ: Настройки филиала — вертикальные вкладки
// ─────────────────────────────────────────────
function dekart_admin_branch_tab_assets($hook)
{
if (! isset($_GET['page']) || $_GET['page'] !== 'branch-settings') { // phpcs:ignore WordPress.Security.NonceVerification
return;
}
wp_enqueue_style(
'dekart-admin-branch-tabs',
get_template_directory_uri() . '/assets/css/admin-branch-tabs.css',
array(),
filemtime(get_template_directory() . '/assets/css/admin-branch-tabs.css')
);
wp_enqueue_script(
'dekart-admin-branch-tabs',
get_template_directory_uri() . '/assets/js/admin-branch-tabs.js',
array(),
filemtime(get_template_directory() . '/assets/js/admin-branch-tabs.js'),
true
);
}
add_action('admin_enqueue_scripts', 'dekart_admin_branch_tab_assets');