shkola/wp-content/themes/dekart/functions.php
2026-07-12 08:35:38 +00:00

2143 lines
132 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';
/* --- Удаляем мета из YOASTSEO START--- */
add_filter( 'wpseo_json_ld_output', '__return_false' );
/* --- Удаляем мета из YOASTSEO END --- */
// Отключаем 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() {
// ====================================================
// Настройки темы
// ====================================================
Container::make( 'theme_options', 'Настройка темы ДЕКАРТ' )
->add_tab('Основные', array(
Field::make( 'text', 'site_name', 'Название учебного центра: (используйте в названии только одинарный кавычки)' ),
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', 'Координаты адреса (для яндекc карт):' ),
Field::make( 'text', 'site_frame_reviews', 'Ссылка на страницу с отзывами:' ),
Field::make( 'text', 'site_lk_url', 'Ссылка на личный кабинет (ЛК):' )
->set_default_value( 'https://lk.dekart-school.ru/' )
->set_help_text( 'URL личного кабинета для учеников/родителей. По умолчанию: https://lk.dekart-school.ru/' ),
Field::make( 'textarea', 'site_bitrix_script', 'script (формы) БИТРИКС24:' ),
Field::make( 'textarea', 'site_bitrix_vacancies', 'script (для формы связаться с нами) БИТРИКС24:' ),
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( 'textarea', 'teachers_block', 'Текст в блоке "Наша команда":' ),
))
->add_tab('Цены и скидки', array(
Field::make( 'checkbox', 'early_bird_discount', 'Выводить строку "Скидка за раннюю запись.' )
->set_option_value( 'yes' ),
Field::make( 'text', 'price_hour1', 'Стоимость ак. часа один предмет:' )
->set_attribute( 'type', 'number' ),
Field::make( 'text', 'price_hour2', 'Стоимость ак. часа два предмета:' )
->set_attribute( 'type', 'number' ),
Field::make( 'text', 'price_hour3', 'Стоимость ак. часа три предмета:' )
->set_attribute( 'type', 'number' ),
Field::make( 'text', 'price_hour4', 'Стоимость ак. часа четыре и более предметов:' )
->set_attribute( 'type', 'number' ),
Field::make( 'text', 'price_hour_discount1', 'Стоимость ак. часа один предмет со скидкой:' )
->set_attribute( 'type', 'number' ),
Field::make( 'text', 'price_hour_discount2', 'Стоимость ак. часа два предмета со скидкой:' )
->set_attribute( 'type', 'number' ),
Field::make( 'text', 'price_hour_discount3', 'Стоимость ак. часа три предмета со скидкой:' )
->set_attribute( 'type', 'number' ),
Field::make( 'text', 'price_hour_discount4', 'Стоимость ак. часа четыре и более предметов со скидкой:' )
->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', 'Cсылка:' )
->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_branch_tab_desc', '' )
->set_html( '<p style="color: #64748b; font-size: 13px; margin: 0 0 12px;">Поля для <strong>JSON-LD микроразметки</strong> Schema.org филиала. Если поле пусто — свойство не добавляется в граф.</p>' ),
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 предложения: тип школы, классы, местоположение. Пример: "Частная школа полного дня в Щёлково. Обучение с 1 по 11 класс, подготовка к ЕГЭ и ОГЭ, индивидуальный подход." Если пусто — собирается автоматически из названия и города.' ),
Field::make( 'text', 'schema_postal_code', 'Почтовый индекс (postalCode)' )
->set_width( 50 )
->set_help_text( 'Индекс для address.postalCode в микроразметке.' ),
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( 50 )
->set_help_text( 'Полная ссылка на Яндекс.Карты или Google Maps.' ),
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( 'complex', 'schema_social', 'Социальные сети (sameAs)' )
->set_layout( 'tabbed-horizontal' )
->set_help_text( 'Ссылки на соцсети профилей для sameAs[].' )
->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( 'media_gallery', 'schema_photos', 'Фотографии филиала (image[])' )
->set_help_text( 'Выберите фотографии интерьера/экстерьера для image[].' ),
Field::make( 'textarea', 'schema_area_served', 'Города присутствия (areaServed)' )
->set_width( 100 )
->set_help_text( 'Список городов через запятую, где работает школа. Первый город должен совпадать с городом филиала. Пример: Щёлково, Фрязино, Монино, Ивантеевка, Королёв. Влияет на локальный поиск Google и Яндекса — школа будет показываться по этим городам. Если пусто — используется только город филиала.' ),
));
// (пусто — старые поля 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 ),
));
// ====================================================
// Уникальное имя контейнера Главной Страницы
// ====================================================
Container::make('post_meta', 'Настройки Главной Страницы')
->where('post_template', '=', 'front-page.php')
->add_fields(array(
Field::make( 'textarea', 'block_text_1', 'Текст для блока Частная школа в Щелково "Декарт"' ),
Field::make( 'image', 'main_banner_img_desktop', 'Изображение рекламного баннера (десктопная версия)' )
->help_text( 'Размер изображения (желательно) 1185px на 190px' )
->set_width( 15 ),
Field::make( 'image', 'main_banner_img_mobile', 'Изображение рекламного баннера (мобильная версия)' )
->help_text( 'Размер изображения (желательно) 390px на 550px' )
->set_width( 15 ),
Field::make( 'text', 'main_banner_link', 'УРЛ баннера:' )
->set_default_value('#'),
));
// ====================================================
// Доп поля для страницы начальной школы
// ====================================================
Container::make('post_meta', 'Настройки Начальной Школы')
->where('post_template', '=', 'page-school.php')
->add_fields(array(
Field::make( 'textarea', 'teachers_block', 'Текст в блоке "Наша команда":' ),
Field::make( 'image', 'main_banner_img_desktop', 'Изображение рекламного баннера (десктопная версия)' )
->help_text( 'Размер изображения (желательно) 1185px на 190px' )
->set_width( 15 ),
Field::make( 'image', 'main_banner_img_mobile', 'Изображение рекламного баннера (мобильная версия)' )
->help_text( 'Размер изображения (желательно) 390px на 550px' )
->set_width( 15 ),
Field::make( 'text', 'main_banner_link', 'УРЛ баннера:' )
->set_default_value('#'),
Field::make( 'textarea', 'block_text_1', 'Текст для блока "Персональный план развития для каждого ученика"' ),
Field::make( 'textarea', 'block_text_2', 'Текст для блока - Почему начальная частная школа "Декарт", а не государственная?' ),
Field::make( 'textarea', 'block_text_3', 'Текст для блока "Семейные классы в Щелково"' ),
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 ),
)),
));
// ====================================================
// Доп поля для страницы "ВСЕ КУРСЫ"
// ====================================================
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( 'выбирается только один' ),
));
// ====================================================
// Доп поля для записей teacher
// ====================================================
Container::make( 'post_meta', 'Поля для преподавателя' )
->where( 'post_type', '=', 'teacher' )
->add_fields( array(
Field::make( 'text', 'teacher_direction', 'Направление преподавателя (текст в под фото):' ),
Field::make( 'image', 'teacher_avatar', 'Аватар:' )
->set_width( 15 ),
Field::make( 'text', 'teacher_start_year', 'Год начала работы (только год, например: 1987)' )
->set_width( 30 )
->set_attribute( 'placeholder', '1987' )
->set_help_text( 'Стаж рассчитается автоматически на странице подготовки к школе.' ),
Field::make( 'textarea', 'teacher_description', 'Дескрипшн преподавателя' )
->set_width( 55 ),
Field::make( 'textarea', 'teacher_about', 'О себе:' ),
Field::make( 'textarea', 'teacher_education', 'Образование:' ),
Field::make( 'textarea', 'teacher_add_education', 'Дополнительное образование:' ),
Field::make( 'text', 'teacher_rating', 'Оценка преподавателя' )
->set_width( 10 )
->set_default_value('5'),
Field::make( 'complex', 'teacher_subjects', 'Предметы обучения' )
->set_layout( 'tabbed-horizontal' )
->add_fields( array(
Field::make( 'text', 'teacher_subjects_title', 'Укажите заголовок предмета' )
->set_width( 50 )
->set_required(true)
->help_text( 'Например: Репетитор по математике' ),
Field::make( 'textarea', 'select_direction', 'Укажите все направления преподавателя:' )
->help_text( 'Пример: &lt;span&gt;Математика&lt;/span&gt;&lt;span&gt;Русский язык&lt;/span&gt;'),
Field::make('association', 'select_category', 'Укажите категрии')
->set_width( 100 )
->set_types(array(
array(
'type' => 'term',
'taxonomy' => 'trainingCategory',
)
))
->help_text( 'В списке указаны все возможные категрии в обучении, выберите необходимые' ),
)),
Field::make( 'complex', 'certificates', 'Дипломы и сертификаты' )
->set_layout( 'tabbed-horizontal' )
->add_fields( array(
Field::make( 'image', 'certificates_foto', 'Изображение' )
))
->help_text( 'Добавьте изображение дипломов и сертификатов преподавателя' ),
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;' ),
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 ),
)),
));
// ====================================================
// Доп поля для страницы "Подготовка к школе (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_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;. Текст о срочности/дефиците мест под кнопкой.' ),
))
->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( 'Например: выпускников в топ-школах' ),
)),
))
->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( '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( 'ФИО и фото подтянутся из карточки преподавателя. Если нужного нет — добавьте в разделе Преподаватели.' ),
Field::make( 'text', 'teacher_subject', 'Предмет / должность (на этой странице)' )
->set_width( 100 )
->set_help_text( 'Если оставить пустым — используется значение из карточки преподавателя.' ),
Field::make( 'textarea', 'teacher_about', 'О педагоге (на этой странице)' )
->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_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_urgency', 'Срочность (мест в группе)' )
->set_width( 100 )
->set_default_value( 'Осталось 3 места в группе' ),
))
// ====================================================
// Вкладка: 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", "Письмо печатными буквами"' ),
)),
));
// ====================================================
// Уникальное имя контейнера Продлёнки
// ====================================================
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></p>' ),
Field::make( 'textarea', 'camp_bitrix_script', 'Скрипт формы Битрикс24' )
->set_help_text( 'Вставьте полный JS-код интеграции с CRM-формой Битрикс24. Выводится в модальном окне заявки.' ),
));
// ════════════════════════════════════════════════════════════
// СТРАНИЦА ОТЗЫВОВ (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_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() {
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-after-school.php') ) {
wp_enqueue_style(
'template-after-style',
get_template_directory_uri() . '/assets/css/after-school.css',
array(),
'1.1'
);
}
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')
);
}
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
);
}
}
add_action('wp_enqueue_scripts', 'enqueue_template_specific_styles');
/**
* 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-school.php',
'page-contacts.php',
'page-teachers.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();
}
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() ) {
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://fonts.googleapis.com https://cdn.jsdelivr.net https://alee.bitrix24.ru; img-src 'self' data: https: blob:; font-src 'self' data: https://fonts.gstatic.com; 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);
/* ── 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);
}
}
// ─────────────────────────────────────────────────────────────
// Счетчики — централизованное управление (ТОП-меню)
add_action('admin_menu', 'dekart_counters_menu');
function dekart_counters_menu() {
add_menu_page('Счетчики', 'Счетчики', 'manage_options', 'site-analytics-settings', 'dekart_counters_page', 'dashicons-chart-bar', 3);
}
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";
}
// ─────────────────────────────────────────────────────────────
// Настройки филиала — 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_action( 'admin_init', 'dekart_branch_register_settings' );
function dekart_branch_register_settings() {
$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',
);
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_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_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_remaining_seats', array( 'sanitize_callback' => 'absint' ) );
register_setting( 'dekart_branch_group', 'branch_schema_description', array( 'sanitize_callback' => 'sanitize_textarea_field' ) );
register_setting( 'dekart_branch_group', 'branch_area_served', array( 'sanitize_callback' => 'sanitize_text_field' ) );
// Поля branch_map_url, branch_founded, branch_photos — удалены.
// Они дублировали CF таб «SEO и микроразметка» (schema_*).
// Чтение: inc/schema.php → dekart_schema_option() → CF → Settings API (backward compat).
}
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>
</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>
</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 id="branch-messengers-container">
<?php
$messengers = get_option( 'branch_messengers', array() );
if ( ! empty( $messengers ) && is_array( $messengers ) ) :
foreach ( $messengers as $i => $m ) :
$m_name = $m['name'] ?? '';
$m_url = $m['url'] ?? '';
?>
<div class="branch-social-row">
<input type="text" name="branch_messengers[<?php echo intval( $i ); ?>][name]" value="<?php echo esc_attr( $m_name ); ?>" placeholder="Название (напр. WhatsApp)" style="max-width:160px;">
<input type="url" name="branch_messengers[<?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>
<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 messengerContainer = document.getElementById('branch-messengers-container');
var messengerAdd = document.getElementById('branch-messenger-add');
var idx = messengerContainer.children.length;
if (messengerAdd) {
messengerAdd.addEventListener('click', function() {
var row = document.createElement('div');
row.className = 'branch-social-row';
row.innerHTML = '<input type="text" name="branch_messengers[' + idx + '][name]" placeholder="Название (напр. WhatsApp)" style="max-width:160px;">' +
'<input type="url" name="branch_messengers[' + idx + '][url]" placeholder="https://..." style="max-width:300px;">' +
'<button type="button" class="button branch-messenger-remove"></button>';
messengerContainer.appendChild(row);
idx++;
row.querySelector('.branch-messenger-remove').addEventListener('click', function() { row.remove(); });
});
messengerContainer.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">
<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_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 class="branch-field">
<label>Города присутствия (для Schema.org areaServed)</label>
<input type="text" name="branch_area_served" value="<?php echo esc_attr( get_option( 'branch_area_served', 'Щёлково, Фрязино, Монино' ) ); ?>" style="width:100%;max-width:500px">
<p class="desc">Города через запятую, где работает школа. Первым укажите город филиала. Влияет на локальный поиск — школа будет показываться жителям этих городов. Пример: <code>Щёлково, Фрязино, Монино, Ивантеевка, Королёв</code></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>
</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>Яндекс Карты (ссылка на отзывы)</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>
</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
}
/* ════════════════════════════════════════════════════════════
🎯 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: Отзывы (Лагерь) — группа полей
// ─────────────────────────────────────────────────────────────