shkola/wp-content/themes/dekart/inc/schema.php
2026-07-18 16:05:15 +00:00

1257 lines
37 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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
/**
* Schema.org JSON-LD Generator — единый @graph
*
* Генерирует микроразметку для страницы «Подготовка к школе».
* Только динамические данные из Carbon Fields. Никаких хардкод-значений.
*
* @package Dekart
* @since 2026-05-31
* @see https://schema.org/Course
*/
defined( 'ABSPATH' ) || exit;
/**
* Отключаем Yoast SEO JSON-LD на странице Подготовки к школе,
* чтобы избежать дублирования WebPage + Organization.
* Аналогично page-camp.php.
*/
add_filter( 'wpseo_json_ld_output', '__return_false' );
/**
* Принудительно меняем og:type на 'website' для страницы Подготовки к школе,
* чтобы избежать несоответствия article/website в социальных сниппетах.
*/
add_filter( 'wpseo_opengraph_type', 'dekart_schema_og_type', 10, 1 );
function dekart_schema_og_type( string $type ): string {
if ( is_page_template( 'page-podgotovka-k-shkole.php' ) || is_page_template( 'page-camp.php' ) ) {
return 'website';
}
return $type;
}
/**
* Убираем article:* OG-теги для страницы Подготовки к школе,
* чтобы не было конфликта og:type=website с article:modified_time.
* Аналог page-camp.php:15-27.
*/
add_filter( 'wpseo_frontend_presenter_classes', function( array $presenters ): array {
if ( ! is_page_template( 'page-podgotovka-k-shkole.php' ) ) {
return $presenters;
}
$presenters = array_filter( $presenters, function( string $p ): bool {
return strpos( $p, 'Article_Modified_Time' ) === false
&& strpos( $p, 'Article_Published_Time' ) === false
&& strpos( $p, 'Article_Author' ) === false
&& strpos( $p, 'Article_Publisher' ) === false
&& strpos( $p, 'Label1' ) === false
&& strpos( $p, 'Data1' ) === false;
} );
return $presenters;
} );
/**
* Главная функция: сборка @graph и вывод в wp_head.
* Срабатывает только на странице с шаблоном page-podgotovka-k-shkole.php.
*/
add_action( 'wp_head', 'dekart_schema_output', 1 );
/**
* Возвращает URL текущей страницы для Schema.org @id.
* Динамически через get_permalink() — не зависит от хардкода ID.
*/
function dekart_schema_page_url(): string {
$id = get_the_ID();
return $id ? get_permalink( $id ) : home_url( '/podgotovka-k-shkole/' );
}
function dekart_schema_output(): void {
if ( ! is_page_template( 'page-podgotovka-k-shkole.php' ) ) {
return;
}
if ( ! function_exists( 'carbon_get_post_meta' ) || ! function_exists( 'carbon_get_theme_option' ) ) {
return;
}
$cache_key = 'dekart_schema_graph_' . get_the_ID() . '_' . determine_locale();
$cached = wp_cache_get( $cache_key, 'dekart_schema' );
if ( false !== $cached ) {
echo $cached;
return;
}
$graph = array();
$webpage = dekart_schema_webpage();
if ( $webpage ) {
$graph[] = $webpage;
}
$website = dekart_schema_website();
if ( $website ) {
$graph[] = $website;
}
$breadcrumb = dekart_schema_breadcrumb();
if ( $breadcrumb ) {
$graph[] = $breadcrumb;
}
$organization = dekart_schema_organization();
if ( $organization ) {
$graph[] = $organization;
}
$local_business = dekart_schema_local_business();
if ( $local_business ) {
$graph[] = $local_business;
}
$course = dekart_schema_course();
if ( $course ) {
$graph[] = $course;
}
$offers = dekart_schema_offer();
if ( ! empty( $offers ) ) {
if ( isset( $offers[0] ) && is_array( $offers[0] ) ) {
foreach ( $offers as $offer ) {
$graph[] = $offer;
}
} else {
$graph[] = $offers;
}
}
$rating = dekart_schema_rating();
if ( $rating ) {
$graph[] = $rating;
}
$service = dekart_schema_service();
if ( $service ) {
$graph[] = $service;
}
$faq = dekart_schema_faq();
if ( $faq ) {
$graph[] = $faq;
}
$event = dekart_schema_trial_event();
if ( $event ) {
$graph[] = $event;
}
$reviews = dekart_schema_reviews();
if ( ! empty( $reviews ) && is_array( $reviews ) ) {
$graph[] = array(
'@type' => 'ItemList',
'@id' => dekart_schema_page_url() . '#reviews',
'name' => 'Отзывы о подготовке к школе',
'description' => 'Отзывы родителей о курсе подготовки к школе в Детском центре «Декарт»',
'itemListElement' => array_map( function( $review, $index ) {
return array(
'@type' => 'ListItem',
'position' => $index + 1,
'item' => $review,
);
}, $reviews, array_keys( $reviews ) ),
'numberOfItems' => count( $reviews ),
);
}
$teachers = dekart_schema_teachers();
if ( ! empty( $teachers ) && is_array( $teachers ) ) {
foreach ( $teachers as $person ) {
$graph[] = $person;
}
}
$author = dekart_schema_author();
if ( $author ) {
$graph[] = $author;
}
if ( empty( $graph ) ) {
return;
}
$json = wp_json_encode(
array(
'@context' => 'https://schema.org',
'@graph' => $graph,
),
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
if ( false === $json ) {
return;
}
$output = '<script type="application/ld+json">' . "\n" . $json . "\n" . '</script>' . "\n";
wp_cache_set( $cache_key, $output, 'dekart_schema', 3600 );
echo $output;
}
/* =====================================================================
Сущности @graph
===================================================================== */
/**
* WebPage — текущая страница.
* @see https://schema.org/WebPage
*/
function dekart_schema_webpage(): ?array {
$post_id = get_the_ID();
$url = get_permalink( $post_id ) ?: dekart_schema_page_url();
$yoast_title = get_post_meta( $post_id, '_yoast_wpseo_title', true );
$name = $yoast_title ?: wp_get_document_title();
$description = get_post_meta( $post_id, '_yoast_wpseo_metadesc', true );
$description = $description ?: get_post_meta( $post_id, 'rank_math_description', true );
$description = $description ?: carbon_get_post_meta( $post_id, 'banner_subtitle' );
$description = $description ?: 'Подготовка к школе для детей 57 лет в Щёлково. Чтение, письмо, математика, логика.';
$image_url = '';
if ( $post_id && has_post_thumbnail( $post_id ) ) {
$image_url = get_the_post_thumbnail_url( $post_id, 'full' );
}
$about_text = carbon_get_post_meta( $post_id, 'pksh_level' ) ?: 'Подготовка к школе';
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
$data = Pksh_Data::instance();
$webpage = array(
'@type' => 'WebPage',
'@id' => $url . '#webpage',
'url' => $url,
'name' => wp_strip_all_tags( $name ),
'description' => wp_strip_all_tags( $description ),
'inLanguage' => str_replace( '_', '-', determine_locale() ?: 'ru' ),
'isPartOf' => array(
'@id' => home_url( '/' ) . '#website',
),
'breadcrumb' => array(
'@id' => $url . '#breadcrumb',
),
'mainEntity' => array(
'@id' => $url . '#course',
),
'speakable' => array(
'@type' => 'SpeakableSpecification',
'cssSelector' => array(
'h1.ds-hero__h1',
'.ds-hero__subtitle',
),
),
'about' => array(
'@type' => 'Thing',
'name' => wp_strip_all_tags( sprintf( '%s в %s', $about_text, $city ) ),
),
'keywords' => wp_strip_all_tags(
'подготовка к школе, дошкольное образование, обучение чтению, обучение письму, '
. 'математика для дошкольников, развитие логики, курсы для детей 5-7 лет, '
. $city
),
'datePublished' => $data->get_page_publish_date(),
'dateModified' => $data->get_page_modified_date(),
'author' => array(
'@id' => $url . '#author',
),
);
$faq_items = carbon_get_post_meta( $post_id, 'pksh_faq_items' );
if ( ! empty( $faq_items ) && is_array( $faq_items ) ) {
$webpage['hasPart'] = array(
'@id' => $url . '#faq',
);
}
return $webpage;
}
/**
* WebSite — главный сайт.
*/
function dekart_schema_website(): ?array {
$site_name = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
$home_url = home_url( '/' );
return array(
'@type' => 'WebSite',
'@id' => $home_url . '#website',
'url' => $home_url,
'name' => wp_strip_all_tags( $site_name ),
'publisher' => array(
'@id' => $home_url . '#organization',
),
'inLanguage' => str_replace( '_', '-', determine_locale() ?: 'ru' ),
'potentialAction' => array(
array(
'@type' => 'SearchAction',
'target' => array(
'@type' => 'EntryPoint',
'urlTemplate' => $home_url . '?s={search_term_string}',
),
'query-input' => 'required name=search_term_string',
),
),
);
}
/**
* BreadcrumbList — хлебные крошки для страницы.
*/
function dekart_schema_breadcrumb(): ?array {
$page_url = dekart_schema_page_url();
$name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе';
return array(
'@type' => 'BreadcrumbList',
'@id' => $page_url . '#breadcrumb',
'itemListElement' => array(
array(
'@type' => 'ListItem',
'position' => 1,
'name' => 'Главная',
'item' => home_url( '/' ),
),
array(
'@type' => 'ListItem',
'position' => 2,
'name' => wp_strip_all_tags( $name ),
'item' => $page_url,
),
),
);
}
/**
* Helper: получить значение из Carbon Fields (schema_*) с fallback на Settings API (branch_*).
*/
function dekart_schema_option( string $schema_key, string $branch_key, $default = '' ) {
$val = carbon_get_theme_option( $schema_key );
if ( ! empty( $val ) ) {
return $val;
}
$val = get_option( $branch_key );
if ( ! empty( $val ) ) {
return $val;
}
return $default;
}
/**
* Helper: получить значение из Настроек филиала (branch_*).
*/
function dekart_branch_option( string $key, string $default = '' ): string {
$val = get_option( $key );
if ( ! empty( $val ) && is_string( $val ) ) {
return trim( $val );
}
return $default;
}
/**
* Parent Organization — юридическое лицо (без адреса филиала).
*/
function dekart_schema_organization(): ?array {
$home_url = home_url( '/' );
$brand = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
$org = array(
'@type' => 'Organization',
'@id' => $home_url . '#organization',
'name' => wp_strip_all_tags( $brand ),
'url' => $home_url,
'logo' => array(
'@type' => 'ImageObject',
'url' => dekart_resolve_logo(),
),
);
$legal_name = dekart_schema_option( 'schema_legal_name', 'branch_legal_name' );
if ( ! empty( $legal_name ) && is_string( $legal_name ) ) {
$org['legalName'] = wp_strip_all_tags( trim( $legal_name ) );
}
$cp_phone = dekart_branch_option( 'branch_phone', '' );
if ( $cp_phone ) {
$org['contactPoint'] = array(
'@type' => 'ContactPoint',
'telephone' => wp_strip_all_tags( $cp_phone ),
'contactType' => 'customer service',
'areaServed' => 'RU',
'availableLanguage' => 'Russian',
);
}
return $org;
}
/**
* Helper: цепочка разрешения логотипа.
*/
function dekart_resolve_logo(): string {
$branch_logo = get_option( 'branch_logo' );
if ( ! empty( $branch_logo ) ) {
if ( is_array( $branch_logo ) && isset( $branch_logo['url'] ) ) {
return $branch_logo['url'];
}
$url = wp_get_attachment_url( $branch_logo );
if ( $url ) {
return $url;
}
}
$banner_img = carbon_get_post_meta( get_the_ID(), 'banner_img' );
if ( ! empty( $banner_img ) ) {
if ( is_array( $banner_img ) && isset( $banner_img['url'] ) ) {
return $banner_img['url'];
}
$url = wp_get_attachment_url( $banner_img );
if ( $url ) {
return $url;
}
}
if ( has_custom_logo() ) {
$logo = wp_get_attachment_image_url( get_theme_mod( 'custom_logo' ), 'full' );
if ( $logo ) {
return $logo;
}
}
$post_id = get_the_ID();
if ( $post_id && has_post_thumbnail( $post_id ) ) {
$url = get_the_post_thumbnail_url( $post_id, 'full' );
if ( $url ) {
return $url;
}
}
return get_template_directory_uri() . '/assets/images/logo-svg.svg';
}
/**
* LocalBusiness — филиал/центр с адресом, телефоном.
*/
function dekart_schema_local_business(): ?array {
$page_url = dekart_schema_page_url();
$brand = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
$street = get_option( 'branch_address' ) ?: carbon_get_theme_option( 'site_address' ) ?: 'Талсинская ул., 24';
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
$region = dekart_branch_option( 'branch_region', 'Московская область' );
$phone = dekart_branch_option( 'branch_phone', '' );
$email = dekart_branch_option( 'branch_email', '' );
$coords = get_option( 'branch_coords' );
$lat = '';
$lng = '';
if ( ! empty( $coords ) && is_string( $coords ) && false !== strpos( $coords, ',' ) ) {
$parts = explode( ',', $coords );
if ( count( $parts ) >= 2 ) {
$lat = trim( $parts[0] );
$lng = trim( $parts[1] );
}
}
$zip = dekart_schema_option( 'schema_postal_code', 'branch_postal_code' );
$price_range = get_option( 'branch_price_range' );
$founded = dekart_schema_option( 'schema_founded', 'branch_founded' );
$map_url = dekart_schema_option( 'schema_map_url', 'branch_map_url' );
$google_place_id = get_option( 'branch_google_place_id' );
$google_maps_url = get_option( 'branch_google_maps_url' );
$area_served_raw = carbon_get_theme_option( 'schema_area_served' ) ?: get_option( 'branch_area_served', 'Щёлково, Фрязино, Монино, Ивантеевка, Королёв' );
$hours_cf = carbon_get_theme_option( 'schema_hours' );
$lb = array(
'@type' => array( 'EducationalOrganization', 'LocalBusiness' ),
'@id' => $page_url . '#local-business',
'parentOrganization' => array(
'@id' => home_url( '/' ) . '#organization',
),
'name' => wp_strip_all_tags( $brand ),
'address' => array(
'@type' => 'PostalAddress',
'streetAddress' => wp_strip_all_tags( $street ),
'addressLocality' => wp_strip_all_tags( $city ),
'addressRegion' => wp_strip_all_tags( $region ),
'addressCountry' => 'RU',
),
'url' => $page_url,
);
if ( $phone ) {
$lb['telephone'] = wp_strip_all_tags( $phone );
}
if ( $email ) {
$lb['email'] = wp_strip_all_tags( $email );
}
if ( ! empty( $zip ) && is_string( $zip ) ) {
$lb['address']['postalCode'] = trim( $zip );
}
if ( ! empty( $lat ) && ! empty( $lng ) ) {
$lb['geo'] = array(
'@type' => 'GeoCoordinates',
'latitude' => floatval( $lat ),
'longitude' => floatval( $lng ),
);
}
if ( ! empty( $google_maps_url ) && is_string( $google_maps_url ) ) {
$lb['hasMap'] = esc_url( trim( $google_maps_url ) );
} elseif ( ! empty( $map_url ) && is_string( $map_url ) ) {
$lb['hasMap'] = esc_url( trim( $map_url ) );
}
$hours_spec = array();
if ( ! empty( $hours_cf ) && is_array( $hours_cf ) ) {
foreach ( $hours_cf as $entry ) {
$day = isset( $entry['day_of_week'] ) ? trim( $entry['day_of_week'] ) : '';
if ( empty( $day ) ) {
continue;
}
$is_off = ! empty( $entry['day_off'] );
if ( $is_off ) {
$hours_spec[] = array(
'@type' => 'OpeningHoursSpecification',
'dayOfWeek' => $day,
'opens' => '00:00',
'closes' => '00:00',
);
} else {
$from = isset( $entry['opens'] ) ? trim( $entry['opens'] ) : '';
$to = isset( $entry['closes'] ) ? trim( $entry['closes'] ) : '';
if ( $from && $to ) {
$hours_spec[] = array(
'@type' => 'OpeningHoursSpecification',
'dayOfWeek' => $day,
'opens' => $from,
'closes' => $to,
);
}
}
}
} else {
$hours_raw = get_option( 'branch_hours' );
if ( ! empty( $hours_raw ) && is_string( $hours_raw ) ) {
$lines = explode( "\n", $hours_raw );
foreach ( $lines as $line ) {
$line = trim( $line );
if ( empty( $line ) ) {
continue;
}
$parts = explode( '|', $line );
$day = isset( $parts[0] ) ? trim( $parts[0] ) : '';
if ( empty( $day ) ) {
continue;
}
if ( isset( $parts[2] ) && 'closed' === strtolower( trim( $parts[2] ) ) ) {
$hours_spec[] = array(
'@type' => 'OpeningHoursSpecification',
'dayOfWeek' => $day,
'opens' => '00:00',
'closes' => '00:00',
);
continue;
}
$from = isset( $parts[1] ) ? trim( $parts[1] ) : '';
$to = isset( $parts[2] ) ? trim( $parts[2] ) : '';
if ( $day && $from && $to ) {
$hours_spec[] = array(
'@type' => 'OpeningHoursSpecification',
'dayOfWeek' => $day,
'opens' => $from,
'closes' => $to,
);
}
}
}
}
if ( ! empty( $hours_spec ) ) {
$lb['openingHoursSpecification'] = $hours_spec;
}
if ( ! empty( $price_range ) && is_string( $price_range ) ) {
$lb['priceRange'] = trim( $price_range );
}
if ( ! empty( $google_place_id ) && is_string( $google_place_id ) ) {
$lb['identifier'] = array(
'@type' => 'PropertyValue',
'name' => 'Google Place ID',
'value' => wp_strip_all_tags( $google_place_id ),
);
$lb['@id'] = 'https://search.google.com/local/writereview?placeid=' . urlencode( $google_place_id );
}
$area_cities = array_map( 'trim', explode( ',', $area_served_raw ) );
$lb['areaServed'] = array_map( function( $c ) {
return array( '@type' => 'City', 'name' => wp_strip_all_tags( $c ) );
}, array_filter( $area_cities ) );
if ( ! empty( $founded ) && is_string( $founded ) ) {
$lb['foundingDate'] = trim( $founded );
}
$social_cf = carbon_get_theme_option( 'schema_social' );
$same_as = array();
if ( ! empty( $social_cf ) && is_array( $social_cf ) ) {
foreach ( $social_cf as $entry ) {
$url = isset( $entry['url'] ) ? trim( $entry['url'] ) : '';
if ( $url && filter_var( $url, FILTER_VALIDATE_URL ) ) {
$same_as[] = esc_url( $url );
}
}
} else {
$social_raw = get_option( 'branch_social' );
if ( ! empty( $social_raw ) && is_array( $social_raw ) ) {
foreach ( $social_raw as $entry ) {
$url = isset( $entry['url'] ) ? trim( $entry['url'] ) : '';
if ( $url && filter_var( $url, FILTER_VALIDATE_URL ) ) {
$same_as[] = esc_url( $url );
}
}
}
}
if ( ! empty( $same_as ) ) {
$lb['sameAs'] = $same_as;
}
$ensure_platforms = array(
'WhatsApp' => dekart_branch_option( 'branch_whatsapp', 'https://wa.me/79269311434' ),
'Telegram' => dekart_branch_option( 'branch_telegram', 'https://t.me/dekart_school' ),
);
foreach ( $ensure_platforms as $platform => $url ) {
if ( ! empty( $url ) && filter_var( $url, FILTER_VALIDATE_URL ) ) {
$found = false;
foreach ( $same_as as $existing ) {
if ( false !== stripos( $existing, parse_url( $url, PHP_URL_HOST ) ) ) {
$found = true;
break;
}
}
if ( ! $found ) {
$same_as[] = esc_url( $url );
}
}
}
$gbp_url = get_option( 'branch_google_business_url', '' );
if ( ! empty( $gbp_url ) && filter_var( $gbp_url, FILTER_VALIDATE_URL ) ) {
$found_gbp = false;
foreach ( $same_as as $existing ) {
if ( false !== stripos( $existing, parse_url( $gbp_url, PHP_URL_HOST ) ) ) {
$found_gbp = true;
break;
}
}
if ( ! $found_gbp ) {
$same_as[] = esc_url( $gbp_url );
}
}
$yb_url = get_option( 'branch_yandex_business_url', '' );
if ( ! empty( $yb_url ) && filter_var( $yb_url, FILTER_VALIDATE_URL ) ) {
$found_yb = false;
foreach ( $same_as as $existing ) {
if ( false !== stripos( $existing, parse_url( $yb_url, PHP_URL_HOST ) ) ) {
$found_yb = true;
break;
}
}
if ( ! $found_yb ) {
$same_as[] = esc_url( $yb_url );
}
}
if ( ! empty( $same_as ) ) {
$lb['sameAs'] = $same_as;
}
$photos_cf = carbon_get_theme_option( 'schema_photos' );
$images = array();
if ( ! empty( $photos_cf ) && is_array( $photos_cf ) ) {
foreach ( $photos_cf as $id ) {
if ( ! is_numeric( $id ) || $id <= 0 ) {
continue;
}
$url = wp_get_attachment_url( (int) $id );
if ( $url ) {
$images[] = esc_url( $url );
}
}
} else {
$photos_raw = get_option( 'branch_photos' );
if ( ! empty( $photos_raw ) && is_string( $photos_raw ) ) {
$ids = explode( ',', $photos_raw );
foreach ( $ids as $id ) {
$id = trim( $id );
if ( ! is_numeric( $id ) || $id <= 0 ) {
continue;
}
$url = wp_get_attachment_url( (int) $id );
if ( $url ) {
$images[] = esc_url( $url );
}
}
}
}
if ( ! empty( $images ) ) {
$lb['image'] = $images;
}
$teachers = carbon_get_post_meta( get_the_ID(), 'pksh_teachers' );
if ( ! empty( $teachers ) && is_array( $teachers ) ) {
$employee_ids = array();
foreach ( $teachers as $i => $t ) {
$t_id = isset( $t['teacher_id'] ) ? intval( $t['teacher_id'] ) : 0;
if ( $t_id > 0 ) {
$employee_ids[] = array(
'@id' => $page_url . '#teacher-' . $i,
);
}
}
if ( ! empty( $employee_ids ) ) {
$lb['employee'] = $employee_ids;
}
}
return $lb;
}
/**
* Course — основной курс «Подготовка к школе».
* @see https://schema.org/Course
*/
function dekart_schema_course(): ?array {
$page_url = dekart_schema_page_url();
$name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе 57 лет в Щёлково';
$pksh_description = carbon_get_post_meta( get_the_ID(), 'pksh_description' );
$description = ! empty( $pksh_description ) ? $pksh_description : ( carbon_get_post_meta( get_the_ID(), 'banner_subtitle' ) ?: 'Научим читать, считать и писать до школы. Бесплатное пробное занятие.' );
$has_trial = ! empty( $description ) && false !== mb_stripos( $description, 'бесплатно' );
$level = carbon_get_post_meta( get_the_ID(), 'pksh_level' );
$age = carbon_get_post_meta( get_the_ID(), 'pksh_age' );
$audience = carbon_get_post_meta( get_the_ID(), 'pksh_audience' );
$course_mode = carbon_get_post_meta( get_the_ID(), 'pksh_course_mode' );
$skills = carbon_get_post_meta( get_the_ID(), 'pksh_skills' );
$course_img = carbon_get_post_meta( get_the_ID(), 'banner_img' );
$programs = carbon_get_post_meta( get_the_ID(), 'pksh_programs' );
$course = array(
'@type' => 'Course',
'@id' => $page_url . '#course',
'name' => wp_strip_all_tags( $name ),
'description' => wp_strip_all_tags( $description ),
'url' => $page_url,
'provider' => array(
'@id' => $page_url . '#local-business',
),
);
if ( $has_trial ) {
$course['hasCourseInstance'] = array(
'@type' => 'CourseInstance',
'courseMode' => 'Onsite',
'courseWorkload' => '30-40 минут',
);
}
if ( ! empty( $level ) && is_string( $level ) ) {
$course['educationalLevel'] = wp_strip_all_tags( trim( $level ) );
}
if ( ! empty( $age ) && is_string( $age ) ) {
$course['typicalAgeRange'] = wp_strip_all_tags( trim( $age ) );
}
if ( ! empty( $audience ) && is_string( $audience ) ) {
$course['audience'] = array(
'@type' => 'EducationalAudience',
'name' => wp_strip_all_tags( trim( $audience ) ),
);
}
if ( ! empty( $skills ) && is_array( $skills ) ) {
$teaches = array();
foreach ( $skills as $skill ) {
$s = isset( $skill['skill'] ) ? trim( $skill['skill'] ) : '';
if ( $s ) {
$teaches[] = wp_strip_all_tags( $s );
}
}
if ( ! empty( $teaches ) ) {
$course['teaches'] = $teaches;
}
}
if ( ! empty( $course_img ) ) {
if ( is_array( $course_img ) && isset( $course_img['url'] ) ) {
$course['image'] = esc_url( $course_img['url'] );
} else {
$url = wp_get_attachment_url( $course_img );
if ( $url ) {
$course['image'] = esc_url( $url );
}
}
} elseif ( has_post_thumbnail( get_the_ID() ) ) {
$course['image'] = esc_url( get_the_post_thumbnail_url( get_the_ID(), 'full' ) );
}
return $course;
}
/**
* AggregateRating — рейтинг из отзывов Carbon Fields.
*/
function dekart_schema_rating(): ?array {
$page_url = dekart_schema_page_url();
$ratings = array();
$reviews = carbon_get_post_meta( get_the_ID(), 'pksh_reviews_list' );
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
return null;
}
foreach ( $reviews as $review ) {
$rating = isset( $review['review_rating'] ) ? intval( $review['review_rating'] ) : 0;
if ( $rating >= 1 && $rating <= 5 ) {
$ratings[] = $rating;
}
}
if ( count( $ratings ) < 1 ) {
return null;
}
$average = round( array_sum( $ratings ) / count( $ratings ), 1 );
return array(
'@type' => 'AggregateRating',
'@id' => $page_url . '#aggregate-rating',
'ratingValue' => (string) $average,
'bestRating' => '5',
'worstRating' => '1',
'ratingCount' => (string) count( $ratings ),
'itemReviewed' => array(
'@id' => $page_url . '#course',
),
);
}
/**
* Service — сущность услуги «Подготовка к школе».
*/
function dekart_schema_service(): ?array {
$page_url = dekart_schema_page_url();
$name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе 57 лет в Щёлково';
return array(
'@type' => 'Service',
'@id' => $page_url . '#service',
'name' => wp_strip_all_tags( $name ),
'provider' => array(
'@id' => $page_url . '#local-business',
),
'areaServed' => array(
'@type' => 'City',
'name' => dekart_branch_option( 'branch_city', 'Щёлково' ),
),
);
}
/**
* FAQPage — вопросы и ответы из pksh_faq_items.
*/
function dekart_schema_faq(): ?array {
$page_url = dekart_schema_page_url();
$faq_items = carbon_get_post_meta( get_the_ID(), 'pksh_faq_items' );
if ( empty( $faq_items ) || ! is_array( $faq_items ) ) {
return null;
}
$main_entity = array();
foreach ( $faq_items as $item ) {
$q = isset( $item['faq_question'] ) ? trim( $item['faq_question'] ) : '';
$a = isset( $item['faq_answer'] ) ? trim( $item['faq_answer'] ) : '';
if ( empty( $q ) || empty( $a ) ) {
continue;
}
$main_entity[] = array(
'@type' => 'Question',
'name' => wp_strip_all_tags( $q ),
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => wp_strip_all_tags( $a ),
),
);
}
if ( empty( $main_entity ) ) {
return null;
}
return array(
'@type' => 'FAQPage',
'@id' => $page_url . '#faq',
'name' => 'Часто задаваемые вопросы о подготовке к школе',
'mainEntity' => $main_entity,
);
}
/**
* Person[] — преподаватели из pksh_teachers complex.
* Каждый преподаватель — отдельная сущность Person.
* Связаны с LocalBusiness через employee @id.
*
* @return array[] Массив Person-сущностей для @graph.
*/
function dekart_schema_teachers(): array {
$page_url = dekart_schema_page_url();
$teachers = carbon_get_post_meta( get_the_ID(), 'pksh_teachers' );
if ( empty( $teachers ) || ! is_array( $teachers ) ) {
return array();
}
$result = array();
foreach ( $teachers as $i => $teacher ) {
$t_id = isset( $teacher['teacher_id'] ) ? intval( $teacher['teacher_id'] ) : 0;
$subject = isset( $teacher['teacher_subject'] ) ? trim( $teacher['teacher_subject'] ) : '';
$about = isset( $teacher['teacher_about'] ) ? trim( $teacher['teacher_about'] ) : '';
if ( $t_id < 1 ) {
continue;
}
$t_post = get_post( $t_id );
if ( ! $t_post ) {
continue;
}
$name = $t_post->post_title;
if ( empty( $name ) ) {
continue;
}
$photo = get_post_meta( $t_id, '_teacher_avatar', true );
if ( ! $photo ) {
$photo = get_post_meta( $t_id, '_thumbnail_id', true );
}
if ( empty( $subject ) ) {
$subject = get_post_meta( $t_id, '_teacher_direction', true );
}
if ( empty( $about ) ) {
$about = get_post_meta( $t_id, '_teacher_about', true );
}
$person = array(
'@type' => 'Person',
'@id' => $page_url . '#teacher-' . $i,
'name' => wp_strip_all_tags( $name ),
);
if ( ! empty( $subject ) && is_string( $subject ) ) {
$person['jobTitle'] = wp_strip_all_tags( trim( $subject ) );
}
if ( ! empty( $about ) && is_string( $about ) ) {
$person['description'] = wp_strip_all_tags( trim( $about ) );
}
if ( ! empty( $photo ) ) {
$url = wp_get_attachment_url( (int) $photo );
if ( $url ) {
$person['image'] = esc_url( $url );
}
}
$result[] = $person;
}
return $result;
}
/**
* Review[] — поштучные отзывы из pksh_reviews_list.
* Каждый отзыв — отдельная сущность Review с автор-персоной и оценкой.
*
* @return array[] Массив Review-сущностей для @graph.
*/
function dekart_schema_reviews(): array {
$page_url = dekart_schema_page_url();
$reviews = carbon_get_post_meta( get_the_ID(), 'pksh_reviews_list' );
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
return array();
}
$result = array();
$review_seq = 0;
foreach ( $reviews as $i => $review ) {
$author_name = isset( $review['review_author'] ) ? trim( $review['review_author'] ) : '';
$body = isset( $review['review_text'] ) ? trim( $review['review_text'] ) : '';
$rating_val = isset( $review['review_rating'] ) ? intval( $review['review_rating'] ) : 0;
if ( empty( $author_name ) || empty( $body ) ) {
continue;
}
if ( $rating_val < 1 || $rating_val > 5 ) {
continue;
}
$review_seq++;
$entity = array(
'@type' => 'Review',
'@id' => $page_url . '#review-' . $review_seq,
'author' => array(
'@type' => 'Person',
'name' => wp_strip_all_tags( $author_name ),
),
'reviewBody' => wp_strip_all_tags( $body ),
'itemReviewed' => array(
'@id' => $page_url . '#course',
),
'reviewRating' => array(
'@type' => 'Rating',
'ratingValue' => (string) $rating_val,
'bestRating' => '5',
'worstRating' => '1',
),
);
$review_date = isset( $review['review_date'] ) ? trim( $review['review_date'] ) : '';
if ( $review_date ) {
$entity['datePublished'] = sanitize_text_field( $review_date );
}
$result[] = $entity;
}
return $result;
}
/**
* Author Person — автор контента страницы.
* Берётся из CF полей вкладки "🤖 GEO (AI-поиск)".
*/
function dekart_schema_author(): ?array {
$page_url = dekart_schema_page_url();
$data = Pksh_Data::instance();
$name = $data->get_author_name();
$years = $data->get_years_on_market();
// Должность — хардкор + авто-расчёт лет
$title = 'Образовательный центр с гослицензией';
if ( $years > 0 ) {
$title .= ', ' . $years . ' лет на рынке';
}
$person = array(
'@type' => 'Person',
'@id' => $page_url . '#author',
'name' => wp_strip_all_tags( $name ),
'jobTitle' => wp_strip_all_tags( $title ),
);
$person['description'] = wp_strip_all_tags( sprintf(
'Автор контента %s — %s',
$name,
$title
) );
return $person;
}
/**
* Event — бесплатное пробное занятие.
* @see https://schema.org/Event
*/
function dekart_schema_trial_event(): ?array {
if ( ! is_page_template( 'page-podgotovka-k-shkole.php' ) ) {
return null;
}
$page_url = dekart_schema_page_url();
$subtitle = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' );
$has_trial = ! empty( $subtitle ) && false !== mb_stripos( $subtitle, 'бесплатно' );
if ( ! $has_trial ) {
return null;
}
$start_date = carbon_get_post_meta( get_the_ID(), 'pksh_trial_event_start_date' );
if ( empty( $start_date ) ) {
$start_date = date( 'c', strtotime( 'next monday 10:00' ) );
}
$end_date = carbon_get_post_meta( get_the_ID(), 'pksh_trial_event_end_date' );
if ( empty( $end_date ) ) {
$end_date = date( 'c', strtotime( $start_date . ' +1 hour' ) );
}
$location = array(
'@type' => 'Place',
'name' => 'Детский центр «Декарт»',
'address' => array(
'@type' => 'PostalAddress',
'streetAddress' => get_option( 'branch_address' ) ?: carbon_get_theme_option( 'site_address' ),
'addressLocality' => get_option( 'branch_city', 'Щёлково' ),
'addressRegion' => get_option( 'branch_region', 'Московская область' ),
'addressCountry' => 'RU',
),
);
$post_id = get_the_ID();
return array(
'@type' => 'Event',
'@id' => $page_url . '#trial-event',
'name' => 'Бесплатное пробное занятие: Подготовка к школе',
'description' => 'Полноценное занятие + диагностика готовности к школе. Ребёнок познакомится с педагогом, выполнит тестовые задания. Родители получат план подготовки.',
'eventStatus' => 'https://schema.org/EventScheduled',
'eventAttendanceMode' => 'https://schema.org/OfflineEventAttendanceMode',
'startDate' => $start_date,
'endDate' => $end_date,
'location' => $location,
'offers' => array(
'@type' => 'Offer',
'@id' => $page_url . '#offer-trial',
'price' => '0',
'priceCurrency' => 'RUB',
'availability' => 'https://schema.org/LimitedAvailability',
'description' => 'Бесплатное пробное занятие',
'category' => 'Бесплатное пробное занятие',
),
'organizer' => array(
'@id' => home_url( '/' ) . '#organization',
),
'image' => ( $post_id && has_post_thumbnail( $post_id ) )
? get_the_post_thumbnail_url( $post_id, 'full' )
: '',
);
}
/**
* Offer / AggregateOffer с UnitPriceSpecification.
* @see https://schema.org/UnitPriceSpecification
*/
function dekart_schema_offer(): ?array {
$page_url = dekart_schema_page_url();
$base_price = carbon_get_post_meta( get_the_ID(), 'pksh_prog_base_price_sub' );
$prem_price = carbon_get_post_meta( get_the_ID(), 'pksh_prog_prem_price_sub' );
$price_valid = carbon_get_post_meta( get_the_ID(), 'pksh_price_valid_until' );
$offers = array();
$prices = array();
foreach ( array( $base_price, $prem_price ) as $raw ) {
if ( ! empty( $raw ) && is_string( $raw ) ) {
$parsed = dekart_parse_price( $raw );
if ( $parsed > 0 ) {
$prices[] = $parsed;
}
}
}
if ( empty( $prices ) ) {
$offer = array(
'@type' => 'Offer',
'@id' => $page_url . '#offer',
'price' => '0',
'priceCurrency' => 'RUB',
'description' => 'Подготовка к школе — свяжитесь для уточнения стоимости',
'availability' => 'https://schema.org/InStock',
'category' => 'Образовательные услуги',
);
if ( ! empty( $price_valid ) ) {
$offer['priceValidUntil'] = trim( $price_valid );
}
$offers[] = $offer;
return $offers;
}
$low = min( $prices );
$high = max( $prices );
$aggregate = array(
'@type' => 'AggregateOffer',
'@id' => $page_url . '#offer',
'name' => 'Стоимость подготовки к школе',
'lowPrice' => (string) $low,
'highPrice' => (string) $high,
'offerCount' => (string) count( $prices ),
'availability' => 'https://schema.org/InStock',
'category' => 'Образовательные услуги',
'offeredBy' => array(
'@id' => $page_url . '#local-business',
),
'priceSpecification' => array(
'@type' => 'UnitPriceSpecification',
'name' => 'Абонемент за 4 недели',
'minPrice' => (string) $low,
'maxPrice' => (string) $high,
'unitCode' => 'MON',
'unitText' => 'месяц',
),
);
if ( ! empty( $price_valid ) ) {
$aggregate['priceValidUntil'] = trim( $price_valid );
}
$offers[] = $aggregate;
$subtitle = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' );
if ( ! empty( $subtitle ) && false !== mb_stripos( $subtitle, 'бесплатно' ) ) {
$trial = array(
'@type' => 'Offer',
'@id' => $page_url . '#offer-trial',
'price' => '0',
'priceCurrency' => 'RUB',
'availability' => 'https://schema.org/LimitedAvailability',
'description' => wp_strip_all_tags( $subtitle ),
'category' => 'Бесплатное пробное занятие',
'offeredBy' => array(
'@id' => $page_url . '#local-business',
),
);
if ( ! empty( $price_valid ) ) {
$trial['priceValidUntil'] = trim( $price_valid );
}
$offers[] = $trial;
}
return $offers;
}
/**
* Парсит цену из форматированной строки (например "7 500 ₽" → 7500).
*/
function dekart_parse_price( string $raw ): float {
$clean = preg_replace( '/[^0-9.,]/', '', $raw );
if ( empty( $clean ) ) {
return 0.0;
}
$last_dot = strrpos( $clean, '.' );
$last_comma = strrpos( $clean, ',' );
if ( false !== $last_comma && ( false === $last_dot || $last_comma > $last_dot ) ) {
$clean = str_replace( '.', '', $clean );
$clean = str_replace( ',', '.', $clean );
} else {
$clean = str_replace( ',', '', $clean );
$clean = preg_replace( '/\.(?=.*\.)/', '', $clean );
}
return (float) $clean;
}