'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 = '' . "\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 ?: 'Подготовка к школе для детей 5–7 лет в Щёлково. Чтение, письмо, математика, логика.'; $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' ) ?: 'Подготовка к школе 5–7 лет в Щёлково'; $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' ) ?: 'Подготовка к школе 5–7 лет в Щёлково'; 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; }