Global-2026-07-14
This commit is contained in:
parent
d8f11ba4d1
commit
caecf7da52
3
.gitignore
vendored
3
.gitignore
vendored
@ -126,9 +126,10 @@ wp-content/upgrade-temp-backup/
|
||||
|
||||
# === Agent/Test files — dev-мусор (не в git) ===
|
||||
data/
|
||||
llms.txt
|
||||
test.php
|
||||
.agents/
|
||||
.claude/
|
||||
skills-lock.json
|
||||
|
||||
# === Audit files — dev-мусор (не в git) ===
|
||||
*.audit.txt
|
||||
|
||||
251
AGENTS.md
251
AGENTS.md
@ -1,251 +0,0 @@
|
||||
# Site1 — WordPress Project
|
||||
## Agent
|
||||
Primary agent:
|
||||
- @site1
|
||||
This agent manages the WordPress project architecture, development workflow, frontend changes, SEO safety, and deployment preparation.
|
||||
---
|
||||
# Project Context
|
||||
Project type:
|
||||
- Custom WordPress website.
|
||||
Architecture:
|
||||
- WordPress core.
|
||||
- Custom theme:
|
||||
`wp-content/themes/dekart/`
|
||||
Theme contains:
|
||||
- Templates
|
||||
- Custom frontend styles
|
||||
- Carbon Fields integration
|
||||
- Schema.org JSON-LD logic
|
||||
- Custom business data logic
|
||||
Repository:
|
||||
- Git is the source of truth.
|
||||
- Theme changes are deployed through Git.
|
||||
- Production and development environments are separated.
|
||||
Important:
|
||||
Before any changes, inspect existing architecture.
|
||||
Do not assume standard WordPress structure.
|
||||
---
|
||||
# Critical Rules
|
||||
## Forbidden without explicit confirmation
|
||||
NEVER modify:
|
||||
- wp-config.php
|
||||
- database structure
|
||||
- production environment files
|
||||
- uploads directory
|
||||
- user data
|
||||
- existing production configuration
|
||||
Before executing:
|
||||
- rm
|
||||
- delete
|
||||
- overwrite
|
||||
- database write
|
||||
- git push
|
||||
Ask for confirmation.
|
||||
---
|
||||
# WordPress Architecture Rules
|
||||
## Theme
|
||||
Location:
|
||||
wp-content/themes/dekart/
|
||||
Rules:
|
||||
Preserve:
|
||||
- existing PHP templates
|
||||
- template hierarchy
|
||||
- hooks
|
||||
- filters
|
||||
- Carbon Fields compatibility
|
||||
- schema.org output
|
||||
- SEO logic
|
||||
Do not:
|
||||
- rebuild templates
|
||||
- replace theme architecture
|
||||
- move functionality into plugins without approval
|
||||
- introduce page builders
|
||||
---
|
||||
# Carbon Fields
|
||||
Carbon Fields is intentionally located inside the theme.
|
||||
Rules:
|
||||
Do not:
|
||||
- move Carbon Fields into a plugin
|
||||
- rename fields
|
||||
- change option keys
|
||||
- modify stored data structure
|
||||
Any changes must preserve backward compatibility.
|
||||
---
|
||||
# Frontend / Redesign Rules
|
||||
Goal:
|
||||
Improve visual design without changing markup.
|
||||
Allowed:
|
||||
- CSS
|
||||
- SCSS
|
||||
- CSS variables
|
||||
- typography
|
||||
- colors
|
||||
- spacing
|
||||
- borders
|
||||
- shadows
|
||||
- animations
|
||||
- responsive improvements
|
||||
Forbidden:
|
||||
- changing HTML structure
|
||||
- changing block layout
|
||||
- changing forms
|
||||
- replacing components
|
||||
- adding Elementor
|
||||
- adding visual builders
|
||||
Priority:
|
||||
1. Preserve functionality
|
||||
2. Preserve SEO
|
||||
3. Preserve accessibility
|
||||
4. Improve UI consistency
|
||||
---
|
||||
# Modern Web Development
|
||||
Use Google Chrome Modern Web Guidance principles.
|
||||
Priorities:
|
||||
- Accessibility
|
||||
- Performance
|
||||
- Responsive design
|
||||
- Maintainable CSS
|
||||
- Browser compatibility
|
||||
Prefer:
|
||||
- CSS variables
|
||||
- reusable design tokens
|
||||
- semantic CSS naming
|
||||
- modern CSS compatible with Baseline
|
||||
Avoid:
|
||||
- unnecessary JavaScript
|
||||
- duplicated CSS
|
||||
- layout shifts
|
||||
- heavy dependencies
|
||||
Consider:
|
||||
- Core Web Vitals
|
||||
- CLS
|
||||
- LCP
|
||||
- accessibility contrast
|
||||
- responsive behavior
|
||||
---
|
||||
# Frontend Workflow
|
||||
Before changing styles:
|
||||
1. Audit existing CSS.
|
||||
2. Identify:
|
||||
- colors
|
||||
- typography
|
||||
- spacing
|
||||
- components
|
||||
- duplicated rules
|
||||
- inconsistent UI patterns
|
||||
Create design tokens when possible:
|
||||
Example:
|
||||
```css
|
||||
:root {
|
||||
--color-primary:
|
||||
--color-secondary:
|
||||
--color-text:
|
||||
--color-background:
|
||||
--border-radius:
|
||||
--shadow:
|
||||
}
|
||||
Do not make blind global replacements.
|
||||
SEO Safety
|
||||
Never break:
|
||||
title/meta logic
|
||||
schema.org JSON-LD
|
||||
breadcrumbs
|
||||
canonical URLs
|
||||
indexing behavior
|
||||
structured data
|
||||
Frontend changes must not affect SEO output.
|
||||
Performance
|
||||
Keep:
|
||||
Core Web Vitals
|
||||
minimal CSS overhead
|
||||
optimized assets
|
||||
no unnecessary scripts
|
||||
Avoid:
|
||||
additional libraries without approval
|
||||
duplicate dependencies
|
||||
blocking resources
|
||||
Redis Object Cache
|
||||
Current architecture:
|
||||
Redis Object Cache plugin
|
||||
mu-plugin bootstrap
|
||||
WordPress object cache API
|
||||
Rules:
|
||||
Do not create another caching layer.
|
||||
Do not create custom Redis clients.
|
||||
Do not create custom object-cache.php.
|
||||
Use:
|
||||
wp_cache_get()
|
||||
wp_cache_set()
|
||||
Runtime files:
|
||||
Never commit:
|
||||
wp-content/object-cache.php
|
||||
Development Standards
|
||||
PHP
|
||||
Use:
|
||||
PSR-12
|
||||
type declarations where appropriate
|
||||
WordPress coding standards
|
||||
JavaScript
|
||||
Use:
|
||||
TypeScript strict
|
||||
ESM
|
||||
async/await
|
||||
Avoid unnecessary frameworks.
|
||||
Git Rules
|
||||
Commit format:
|
||||
feat:
|
||||
fix:
|
||||
refactor:
|
||||
style:
|
||||
chore:
|
||||
docs:
|
||||
Before commit:
|
||||
Run:
|
||||
git status
|
||||
git diff
|
||||
Never commit:
|
||||
.env
|
||||
uploads/
|
||||
logs/
|
||||
wp-content/object-cache.php
|
||||
cache files
|
||||
temporary files
|
||||
Testing
|
||||
PHP:
|
||||
vendor/bin/phpunit
|
||||
vendor/bin/phpstan analyse
|
||||
JavaScript:
|
||||
npm run build
|
||||
npm test
|
||||
npm run lint
|
||||
Run only relevant checks for changed area.
|
||||
Deployment Rules
|
||||
Before deployment verify:
|
||||
changed files
|
||||
dependencies
|
||||
environment requirements
|
||||
database impact
|
||||
cache impact
|
||||
Never assume production environment matches development.
|
||||
Required Work Process
|
||||
For complex tasks:
|
||||
Analyze current implementation.
|
||||
Explain planned changes.
|
||||
Wait for confirmation if architecture changes are required.
|
||||
Implement minimal changes.
|
||||
Verify result.
|
||||
Provide report.
|
||||
Final Response Format
|
||||
After completing tasks always provide:
|
||||
Summary
|
||||
What was changed.
|
||||
Files Changed
|
||||
List files.
|
||||
Technical Details
|
||||
Explain implementation.
|
||||
Validation
|
||||
Tests and checks performed.
|
||||
Risks
|
||||
Possible issues.
|
||||
Deployment Notes
|
||||
Required production actions.
|
||||
@ -32,7 +32,7 @@
|
||||
<img width="19" height="32"
|
||||
src="<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/icon-location.svg"
|
||||
alt="" role="presentation" loading="lazy">
|
||||
<span>г. <?php echo esc_html( carbon_get_theme_option('site_city') ); ?> <br class="mobile"> <?php echo esc_html( carbon_get_theme_option('site_address') ); ?></span>
|
||||
<span>г. <?php echo esc_html( get_option('branch_city') ?: carbon_get_theme_option('site_city') ); ?> <br class="mobile"> <?php echo esc_html( get_option('branch_address') ?: carbon_get_theme_option('site_address') ); ?></span>
|
||||
</div>
|
||||
<a class="footer__link-icon"
|
||||
href="tel:<?php echo preg_replace('/[^0-9+]/', '', carbon_get_theme_option('site_phone')); ?>">
|
||||
|
||||
@ -1915,7 +1915,10 @@ function dekart_branch_register_settings() {
|
||||
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' ) );
|
||||
register_setting( 'dekart_branch_group', 'branch_logistics_walk', array( 'sanitize_callback' => 'sanitize_textarea_field' ) );
|
||||
register_setting( 'dekart_branch_group', 'branch_google_place_id', array( 'sanitize_callback' => 'sanitize_text_field' ) );
|
||||
register_setting( 'dekart_branch_group', 'branch_google_maps_url', array( 'sanitize_callback' => 'esc_url_raw' ) );
|
||||
register_setting( 'dekart_branch_group', 'branch_google_business_url', array( 'sanitize_callback' => 'esc_url_raw' ) );
|
||||
register_setting( 'dekart_branch_group', 'branch_logistics_walk', array( 'sanitize_callback' => 'sanitize_textarea_field' ) );
|
||||
register_setting( 'dekart_branch_group', 'branch_logistics_parking', array( 'sanitize_callback' => 'sanitize_textarea_field' ) );
|
||||
register_setting( 'dekart_branch_group', 'branch_logistics_nearby', array( 'sanitize_callback' => 'sanitize_textarea_field' ) );
|
||||
// Поля branch_map_url, branch_founded, branch_photos — удалены.
|
||||
@ -1976,6 +1979,21 @@ function dekart_branch_settings_page() {
|
||||
<label>Почтовый индекс</label>
|
||||
<input type="text" name="branch_postal_code" value="<?php echo esc_attr( get_option( 'branch_postal_code', '' ) ); ?>">
|
||||
</div>
|
||||
<div class="branch-field">
|
||||
<label>Города обслуживания (areaServed)</label>
|
||||
<input type="text" name="branch_area_served" value="<?php echo esc_attr( get_option( 'branch_area_served', 'Щёлково, Фрязино, Монино, Ивантеевка, Королёв' ) ); ?>">
|
||||
<p class="desc">Через запятую. Для Schema.org LocalBusiness.areaServed</p>
|
||||
</div>
|
||||
<div class="branch-field">
|
||||
<label>Google Place ID</label>
|
||||
<input type="text" name="branch_google_place_id" value="<?php echo esc_attr( get_option( 'branch_google_place_id', '' ) ); ?>">
|
||||
<p class="desc">Из Google Business Profile. Для Schema.org identifier + @id</p>
|
||||
</div>
|
||||
<div class="branch-field">
|
||||
<label>Google Maps URL</label>
|
||||
<input type="url" name="branch_google_maps_url" value="<?php echo esc_attr( get_option( 'branch_google_maps_url', '' ) ); ?>">
|
||||
<p class="desc">Полная ссылка на профиль в Google Maps. Для Schema.org hasMap</p>
|
||||
</div>
|
||||
<div class="branch-field">
|
||||
<label>Пешком от ориентира</label>
|
||||
<input type="text" name="branch_logistics_walk" value="<?php echo esc_attr( get_option( 'branch_logistics_walk', '5 минут пешком от ТЦ «Глобус»' ) ); ?>">
|
||||
@ -2169,6 +2187,11 @@ function dekart_branch_settings_page() {
|
||||
<h2>🔗 E-E-A-T: Внешние платформы</h2>
|
||||
<div class="branch-section-body">
|
||||
<p class="desc">Ссылки на внешние площадки с отзывами для повышения E-E-A-T. Отображаются под заголовком «Отзывы на внешних площадках».</p>
|
||||
<div class="branch-field">
|
||||
<label>Google Business Profile (ссылка на профиль)</label>
|
||||
<input type="url" name="branch_google_business_url" value="<?php echo esc_attr( get_option( 'branch_google_business_url', '' ) ); ?>" style="width:100%;max-width:500px">
|
||||
<p class="desc">Полная ссылка на профиль в Google Business Profile. Добавляется в sameAs[] Schema.org и выводится как кнопка на странице. <br><strong>Где взять:</strong> откройте Google Карты → найдите свою организацию → нажмите «Поделиться» → скопируйте ссылку. Пример: https://maps.google.com/?cid=1234567890 или https://g.page/r/your-business-slug</p>
|
||||
</div>
|
||||
<div class="branch-field">
|
||||
<label>Яндекс Карты (ссылка на отзывы)</label>
|
||||
<input type="url" name="branch_ext_yandex" value="<?php echo esc_attr( get_option( 'branch_ext_yandex', '' ) ); ?>" style="width:100%;max-width:500px">
|
||||
|
||||
@ -140,7 +140,7 @@ if ( is_front_page() ) {
|
||||
<img width="15" height="25"
|
||||
src="<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/icon-location.svg"
|
||||
alt="Адрес" loading="lazy">
|
||||
<span><?php echo esc_html( carbon_get_theme_option('site_address') ); ?></span>
|
||||
<span><?php echo esc_html( get_option('branch_address') ?: carbon_get_theme_option('site_address') ); ?></span>
|
||||
</div>
|
||||
<a target="_blank" rel="noopener noreferrer"
|
||||
href="<?php echo esc_url( carbon_get_theme_option('social_header_wt') ); ?>"
|
||||
|
||||
@ -20,24 +20,24 @@ defined( 'ABSPATH' ) || exit;
|
||||
add_filter( 'wpseo_json_ld_output', '__return_false' );
|
||||
|
||||
/**
|
||||
* Принудительно меняем og:type на 'website' для страницы Подготовки к школе (ID=2),
|
||||
* Принудительно меняем 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( 2 ) || is_page_template( 'page-camp.php' ) ) {
|
||||
if ( is_page_template( 'page-podgotovka-k-shkole.php' ) || is_page_template( 'page-camp.php' ) ) {
|
||||
return 'website';
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Убираем article:* OG-теги для страницы Подготовки к школе (ID=2),
|
||||
* Убираем 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( 2 ) ) {
|
||||
if ( ! is_page_template( 'page-podgotovka-k-shkole.php' ) ) {
|
||||
return $presenters;
|
||||
}
|
||||
$presenters = array_filter( $presenters, function( string $p ): bool {
|
||||
@ -53,12 +53,21 @@ add_filter( 'wpseo_frontend_presenter_classes', function( array $presenters ): a
|
||||
|
||||
/**
|
||||
* Главная функция: сборка @graph и вывод в wp_head.
|
||||
* Срабатывает только на странице ID=2 (Подготовка к школе).
|
||||
* Срабатывает только на странице с шаблоном 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( 2 ) ) {
|
||||
if ( ! is_page_template( 'page-podgotovka-k-shkole.php' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -146,12 +155,24 @@ function dekart_schema_output(): void {
|
||||
$graph[] = $faq;
|
||||
}
|
||||
|
||||
// 11b. QAPage — ключевые вопросы для AI citation readiness
|
||||
$qa = dekart_schema_qapage();
|
||||
if ( $qa ) {
|
||||
$graph[] = $qa;
|
||||
}
|
||||
|
||||
// 11c. Event — бесплатное пробное занятие
|
||||
$event = dekart_schema_trial_event();
|
||||
if ( $event ) {
|
||||
$graph[] = $event;
|
||||
}
|
||||
|
||||
// 12. ItemList — обёртка для отзывов (conditional)
|
||||
$reviews = dekart_schema_reviews();
|
||||
if ( ! empty( $reviews ) && is_array( $reviews ) ) {
|
||||
$graph[] = array(
|
||||
'@type' => 'ItemList',
|
||||
'@id' => home_url( '/podgotovka-k-shkole/' ) . '#reviews',
|
||||
'@id' => dekart_schema_page_url() . '#reviews',
|
||||
'name' => 'Отзывы о подготовке к школе',
|
||||
'description' => 'Отзывы родителей о курсе подготовки к школе в Детском центре «Декарт»',
|
||||
'itemListElement' => array_map( function( $review, $index ) {
|
||||
@ -206,26 +227,27 @@ function dekart_schema_output(): void {
|
||||
* @see https://schema.org/WebPage
|
||||
*/
|
||||
function dekart_schema_webpage(): ?array {
|
||||
$url = home_url( '/podgotovka-k-shkole/' );
|
||||
$post_id = get_the_ID();
|
||||
$url = get_permalink( $post_id ) ?: dekart_schema_page_url();
|
||||
|
||||
// _yoast_wpseo_title — Yoast SEO заголовок
|
||||
$yoast_title = get_post_meta( 2, '_yoast_wpseo_title', true );
|
||||
$yoast_title = get_post_meta( $post_id, '_yoast_wpseo_title', true );
|
||||
$name = $yoast_title ?: wp_get_document_title();
|
||||
|
||||
// _yoast_wpseo_metadesc — Yoast SEO описание
|
||||
$description = get_post_meta( 2, '_yoast_wpseo_metadesc', true );
|
||||
$description = $description ?: get_post_meta( 2, 'rank_math_description', true );
|
||||
$description = $description ?: carbon_get_post_meta( 2, 'banner_subtitle' );
|
||||
$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 лет в Щёлково. Чтение, письмо, математика, логика.';
|
||||
|
||||
// has_post_thumbnail — изображение страницы (OG-совместимость)
|
||||
$image_url = '';
|
||||
if ( has_post_thumbnail( 2 ) ) {
|
||||
if ( has_post_thumbnail( get_the_ID() ) ) {
|
||||
$image_url = get_the_post_thumbnail_url( 2, 'full' );
|
||||
}
|
||||
|
||||
// pksh_level — уровень образования (about)
|
||||
$about_text = carbon_get_post_meta( 2, 'pksh_level' ) ?: 'Подготовка к школе';
|
||||
$about_text = carbon_get_post_meta( get_the_ID(), 'pksh_level' ) ?: 'Подготовка к школе';
|
||||
|
||||
// branch_city — город для about и keywords
|
||||
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
|
||||
@ -280,7 +302,7 @@ function dekart_schema_webpage(): ?array {
|
||||
}
|
||||
|
||||
// hasPart — ссылка на FAQPage этой страницы
|
||||
$faq_items = carbon_get_post_meta( 2, 'pksh_faq_items' );
|
||||
$faq_items = carbon_get_post_meta( get_the_ID(), 'pksh_faq_items' );
|
||||
if ( ! empty( $faq_items ) && is_array( $faq_items ) ) {
|
||||
$webpage['hasPart'] = array(
|
||||
'@id' => $url . '#faq',
|
||||
@ -318,7 +340,7 @@ function dekart_schema_website(): ?array {
|
||||
* BreadcrumbList — хлебные крошки для страницы.
|
||||
*/
|
||||
function dekart_schema_breadcrumb(): ?array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$page_url = dekart_schema_page_url();
|
||||
$home_url = home_url( '/' );
|
||||
|
||||
return array(
|
||||
@ -430,7 +452,7 @@ function dekart_resolve_logo(): string {
|
||||
}
|
||||
|
||||
// 2. banner_img — CF поле страницы
|
||||
$banner_img = carbon_get_post_meta( 2, 'banner_img' );
|
||||
$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'];
|
||||
@ -451,7 +473,7 @@ function dekart_resolve_logo(): string {
|
||||
}
|
||||
|
||||
// 4. post_thumbnail страницы
|
||||
if ( has_post_thumbnail( 2 ) ) {
|
||||
if ( has_post_thumbnail( get_the_ID() ) ) {
|
||||
$url = get_the_post_thumbnail_url( 2, 'full' );
|
||||
if ( $url ) {
|
||||
return $url;
|
||||
@ -471,13 +493,13 @@ function dekart_resolve_logo(): string {
|
||||
* @see https://schema.org/EducationalOrganization
|
||||
*/
|
||||
function dekart_schema_local_business(): ?array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$page_url = dekart_schema_page_url();
|
||||
$home_url = home_url( '/' );
|
||||
|
||||
// branch_name — название филиала (get_option)
|
||||
$site_name = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
|
||||
// branch_address — улица
|
||||
$street = dekart_branch_option( 'branch_address', 'Комсомольская ул., 11' );
|
||||
// branch_address — улица (приоритет: Settings API → CF theme option → хардкод)
|
||||
$street = get_option( 'branch_address' ) ?: carbon_get_theme_option( 'site_address' ) ?: 'Комсомольская ул., 11';
|
||||
// branch_city — город
|
||||
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
|
||||
// branch_region — регион/область
|
||||
@ -505,6 +527,12 @@ function dekart_schema_local_business(): ?array {
|
||||
$founded = dekart_schema_option( 'schema_founded', 'branch_founded' );
|
||||
// branch_map_url — ссылка на карту для hasMap (CF schema_map_url → Settings API)
|
||||
$map_url = dekart_schema_option( 'schema_map_url', 'branch_map_url' );
|
||||
// branch_google_place_id — Google Place ID
|
||||
$google_place_id = get_option( 'branch_google_place_id' );
|
||||
// branch_google_maps_url — Google Maps URL для hasMap
|
||||
$google_maps_url = get_option( 'branch_google_maps_url' );
|
||||
// branch_area_served — города обслуживания (через запятую)
|
||||
$area_served_raw = get_option( 'branch_area_served', 'Щёлково, Фрязино, Монино, Ивантеевка, Королёв' );
|
||||
// branch_hours — часы работы: CF complex (schema_hours) → textarea (branch_hours)
|
||||
$hours_cf = carbon_get_theme_option( 'schema_hours' );
|
||||
|
||||
@ -630,11 +658,28 @@ function dekart_schema_local_business(): ?array {
|
||||
$lb['priceRange'] = trim( $price_range );
|
||||
}
|
||||
|
||||
// areaServed — город присутствия (условно)
|
||||
$lb['areaServed'] = array(
|
||||
'@type' => 'City',
|
||||
'name' => wp_strip_all_tags( $city ),
|
||||
);
|
||||
// Google Place ID — identifier + @id для Google Business Profile
|
||||
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 );
|
||||
}
|
||||
|
||||
// areaServed — массив городов (branch_area_served: "Щёлково, Фрязино, Монино...")
|
||||
$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 ) );
|
||||
|
||||
// hasMap — Google Maps URL (приоритет Google, потом Yandex)
|
||||
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 ) );
|
||||
}
|
||||
|
||||
// foundingDate — год основания (условно)
|
||||
if ( ! empty( $founded ) && is_string( $founded ) ) {
|
||||
@ -687,6 +732,21 @@ function dekart_schema_local_business(): ?array {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Google Business Profile — добавляем в sameAs, если ссылка задана
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
|
||||
// Если после добавления появились элементы, обновляем sameAs
|
||||
if ( ! empty( $same_as ) && ! isset( $lb['sameAs'] ) ) {
|
||||
$lb['sameAs'] = $same_as;
|
||||
@ -728,7 +788,7 @@ function dekart_schema_local_business(): ?array {
|
||||
}
|
||||
|
||||
// employee — преподаватели (Person), если есть
|
||||
$teachers = carbon_get_post_meta( 2, 'pksh_teachers' );
|
||||
$teachers = carbon_get_post_meta( get_the_ID(), 'pksh_teachers' );
|
||||
if ( ! empty( $teachers ) && is_array( $teachers ) ) {
|
||||
$employee_ids = array();
|
||||
foreach ( $teachers as $i => $t ) {
|
||||
@ -755,32 +815,32 @@ function dekart_schema_local_business(): ?array {
|
||||
* @see https://schema.org/CourseInstance
|
||||
*/
|
||||
function dekart_schema_course(): ?array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$page_url = dekart_schema_page_url();
|
||||
$home_url = home_url( '/' );
|
||||
|
||||
// banner_h1 — заголовок H1 (Course.name)
|
||||
$name = carbon_get_post_meta( 2, 'banner_h1' ) ?: 'Подготовка к школе 5–7 лет в Щёлково';
|
||||
$name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе 5–7 лет в Щёлково';
|
||||
// pksh_description — полное описание курса для микроразметки (textarea)
|
||||
// Если заполнено — приоритет над banner_subtitle
|
||||
$pksh_description = carbon_get_post_meta( 2, 'pksh_description' );
|
||||
$pksh_description = carbon_get_post_meta( get_the_ID(), 'pksh_description' );
|
||||
// banner_subtitle — подзаголовок (Course.description)
|
||||
$description = ! empty( $pksh_description ) ? $pksh_description : ( carbon_get_post_meta( 2, 'banner_subtitle' ) ?: 'Научим читать, считать и писать до школы. Бесплатное пробное занятие.' );
|
||||
$description = ! empty( $pksh_description ) ? $pksh_description : ( carbon_get_post_meta( get_the_ID(), 'banner_subtitle' ) ?: 'Научим читать, считать и писать до школы. Бесплатное пробное занятие.' );
|
||||
// Пробное занятие — есть ли в подзаголовке упоминание бесплатно
|
||||
$has_trial = ! empty( $description ) && false !== mb_stripos( $description, 'бесплатно' );
|
||||
// pksh_level — уровень образования
|
||||
$level = carbon_get_post_meta( 2, 'pksh_level' );
|
||||
$level = carbon_get_post_meta( get_the_ID(), 'pksh_level' );
|
||||
// pksh_age — возраст
|
||||
$age = carbon_get_post_meta( 2, 'pksh_age' );
|
||||
$age = carbon_get_post_meta( get_the_ID(), 'pksh_age' );
|
||||
// pksh_audience — целевая аудитория
|
||||
$audience = carbon_get_post_meta( 2, 'pksh_audience' );
|
||||
$audience = carbon_get_post_meta( get_the_ID(), 'pksh_audience' );
|
||||
// pksh_course_mode — формат обучения (очная/ONLINE/OFFLINE)
|
||||
$course_mode = carbon_get_post_meta( 2, 'pksh_course_mode' );
|
||||
$course_mode = carbon_get_post_meta( get_the_ID(), 'pksh_course_mode' );
|
||||
// pksh_skills — список навыков
|
||||
$skills = carbon_get_post_meta( 2, 'pksh_skills' );
|
||||
$skills = carbon_get_post_meta( get_the_ID(), 'pksh_skills' );
|
||||
// banner_img — изображение курса
|
||||
$course_img = carbon_get_post_meta( 2, 'banner_img' );
|
||||
$course_img = carbon_get_post_meta( get_the_ID(), 'banner_img' );
|
||||
// pksh_programs — программы (для timeRequired + CourseInstance)
|
||||
$programs = carbon_get_post_meta( 2, 'pksh_programs' );
|
||||
$programs = carbon_get_post_meta( get_the_ID(), 'pksh_programs' );
|
||||
|
||||
$course = array(
|
||||
'@type' => 'Course',
|
||||
@ -801,7 +861,7 @@ function dekart_schema_course(): ?array {
|
||||
);
|
||||
|
||||
// review — 1-2 отзыва прямо в Course (Google требует для звёзд в rich snippet)
|
||||
$reviews_raw = carbon_get_post_meta( 2, 'pksh_reviews_list' );
|
||||
$reviews_raw = carbon_get_post_meta( get_the_ID(), 'pksh_reviews_list' );
|
||||
if ( ! empty( $reviews_raw ) && is_array( $reviews_raw ) ) {
|
||||
$review_items = array();
|
||||
foreach ( $reviews_raw as $rv ) {
|
||||
@ -889,7 +949,7 @@ function dekart_schema_course(): ?array {
|
||||
$course['image'] = esc_url( $url );
|
||||
}
|
||||
}
|
||||
} elseif ( has_post_thumbnail( 2 ) ) {
|
||||
} elseif ( has_post_thumbnail( get_the_ID() ) ) {
|
||||
// fallback: post_thumbnail страницы
|
||||
$url = get_the_post_thumbnail_url( 2, 'full' );
|
||||
if ( $url ) {
|
||||
@ -992,131 +1052,14 @@ function dekart_schema_course(): ?array {
|
||||
return $course;
|
||||
}
|
||||
|
||||
/**
|
||||
* AggregateOffer — цены программ + бесплатное пробное.
|
||||
* Извлекает числовые значения из program_price_subscription / program_price_single.
|
||||
* Если программ нет — возвращает Offer на бесплатное пробное.
|
||||
* Если есть программы — возвращает AggregateOffer + отдельный Offer для free trial.
|
||||
*
|
||||
* @see https://schema.org/AggregateOffer
|
||||
* @see https://schema.org/Offer
|
||||
*/
|
||||
function dekart_schema_offer(): ?array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$programs = carbon_get_post_meta( 2, 'pksh_programs' );
|
||||
|
||||
// priceValidUntil — самая поздняя дата из всех программ
|
||||
$price_valid = '';
|
||||
if ( ! empty( $programs ) && is_array( $programs ) ) {
|
||||
foreach ( $programs as $prog ) {
|
||||
if ( ! empty( $prog['program_price_valid'] ) ) {
|
||||
$d = trim( $prog['program_price_valid'] );
|
||||
if ( $d > $price_valid ) {
|
||||
$price_valid = $d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$prices = array();
|
||||
|
||||
if ( ! empty( $programs ) && is_array( $programs ) ) {
|
||||
foreach ( $programs as $program ) {
|
||||
// program_price_subscription — цена абонемента
|
||||
if ( ! empty( $program['program_price_subscription'] ) ) {
|
||||
$num = dekart_parse_price( $program['program_price_subscription'] );
|
||||
if ( $num > 0 ) {
|
||||
$prices[] = $num;
|
||||
}
|
||||
}
|
||||
// program_price_single — цена разового занятия
|
||||
if ( ! empty( $program['program_price_single'] ) ) {
|
||||
$num = dekart_parse_price( $program['program_price_single'] );
|
||||
if ( $num > 0 ) {
|
||||
$prices[] = $num;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $prices ) ) {
|
||||
// Нет цен в программах — Offer на бесплатное пробное
|
||||
$offer = array(
|
||||
'@type' => 'Offer',
|
||||
'@id' => $page_url . '#offer',
|
||||
'price' => '0',
|
||||
'priceCurrency' => 'RUB',
|
||||
'availability' => 'https://schema.org/LimitedAvailability',
|
||||
'description' => 'Бесплатное пробное занятие',
|
||||
);
|
||||
|
||||
// priceValidUntil — дата действия (условно)
|
||||
if ( ! empty( $price_valid ) ) {
|
||||
$offer['priceValidUntil'] = trim( $price_valid );
|
||||
}
|
||||
|
||||
return $offer;
|
||||
}
|
||||
|
||||
$low = min( $prices );
|
||||
$high = max( $prices );
|
||||
|
||||
$aggregate = array(
|
||||
'@type' => 'AggregateOffer',
|
||||
'@id' => $page_url . '#offer',
|
||||
'priceCurrency' => 'RUB',
|
||||
'lowPrice' => (string) $low,
|
||||
'highPrice' => (string) $high,
|
||||
'offerCount' => (string) count( $prices ),
|
||||
'availability' => 'https://schema.org/InStock',
|
||||
// category — категория услуги
|
||||
'category' => 'Образовательные услуги',
|
||||
// offeredBy — кто предоставляет
|
||||
'offeredBy' => array(
|
||||
'@id' => $page_url . '#localbusiness',
|
||||
),
|
||||
);
|
||||
|
||||
// priceValidUntil — дата действия цен (условно)
|
||||
if ( ! empty( $price_valid ) ) {
|
||||
$aggregate['priceValidUntil'] = trim( $price_valid );
|
||||
}
|
||||
|
||||
// Возвращаем массив: сначала AggregateOffer, потом (опционально) Offer на бесплатное пробное
|
||||
$offers = array( $aggregate );
|
||||
|
||||
// banner_subtitle содержит фразу "бесплатное пробное" — создаём отдельный Offer
|
||||
$subtitle = carbon_get_post_meta( 2, '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 . '#localbusiness',
|
||||
),
|
||||
);
|
||||
if ( ! empty( $price_valid ) ) {
|
||||
$trial['priceValidUntil'] = trim( $price_valid );
|
||||
}
|
||||
$offers[] = $trial;
|
||||
}
|
||||
|
||||
return $offers;
|
||||
}
|
||||
|
||||
/**
|
||||
* AggregateRating — рейтинг из отзывов Carbon Fields.
|
||||
* Вычисляется динамически из pksh_reviews_list.
|
||||
* Если отзывов < 1 — блок не выводится.
|
||||
*/
|
||||
function dekart_schema_rating(): ?array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$reviews = carbon_get_post_meta( 2, 'pksh_reviews_list' );
|
||||
$page_url = dekart_schema_page_url();
|
||||
$reviews = carbon_get_post_meta( get_the_ID(), 'pksh_reviews_list' );
|
||||
|
||||
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
|
||||
return null;
|
||||
@ -1144,7 +1087,7 @@ function dekart_schema_rating(): ?array {
|
||||
'worstRating' => '1',
|
||||
'ratingCount' => (string) count( $ratings ),
|
||||
'itemReviewed' => array(
|
||||
'@id' => home_url( '/' ) . '#organization',
|
||||
'@id' => $page_url . '#course',
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1157,11 +1100,11 @@ function dekart_schema_rating(): ?array {
|
||||
* @see https://schema.org/Service
|
||||
*/
|
||||
function dekart_schema_service(): ?array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$page_url = dekart_schema_page_url();
|
||||
$home_url = home_url( '/' );
|
||||
|
||||
$name = carbon_get_post_meta( 2, 'banner_h1' ) ?: 'Подготовка к школе';
|
||||
$description = carbon_get_post_meta( 2, 'banner_subtitle' ) ?: '';
|
||||
$name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе';
|
||||
$description = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' ) ?: '';
|
||||
|
||||
$service = array(
|
||||
'@type' => 'Service',
|
||||
@ -1188,8 +1131,8 @@ function dekart_schema_service(): ?array {
|
||||
* Если вопросов нет — блок не выводится.
|
||||
*/
|
||||
function dekart_schema_faq(): ?array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$faq_items = carbon_get_post_meta( 2, 'pksh_faq_items' );
|
||||
$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;
|
||||
@ -1234,9 +1177,9 @@ function dekart_schema_faq(): ?array {
|
||||
* @see https://schema.org/Review
|
||||
*/
|
||||
function dekart_schema_reviews(): array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$page_url = dekart_schema_page_url();
|
||||
// pksh_reviews_list — complex-поле отзывов
|
||||
$reviews = carbon_get_post_meta( 2, 'pksh_reviews_list' );
|
||||
$reviews = carbon_get_post_meta( get_the_ID(), 'pksh_reviews_list' );
|
||||
|
||||
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
|
||||
return array();
|
||||
@ -1273,7 +1216,7 @@ function dekart_schema_reviews(): array {
|
||||
),
|
||||
'reviewBody' => wp_strip_all_tags( $body ),
|
||||
'itemReviewed' => array(
|
||||
'@id' => home_url( '/' ) . '#organization',
|
||||
'@id' => $page_url . '#course',
|
||||
),
|
||||
'reviewRating' => array(
|
||||
'@type' => 'Rating',
|
||||
@ -1304,11 +1247,11 @@ function dekart_schema_reviews(): array {
|
||||
* @see https://schema.org/Person
|
||||
*/
|
||||
function dekart_schema_teachers(): array {
|
||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
||||
$page_url = dekart_schema_page_url();
|
||||
// pksh_teachers — complex-поле преподавателей
|
||||
// Subfields: teacher_id (select → post ID), teacher_subject, teacher_about
|
||||
// teacher_name и teacher_photo загружаются из карточки преподавателя
|
||||
$teachers = carbon_get_post_meta( 2, 'pksh_teachers' );
|
||||
$teachers = carbon_get_post_meta( get_the_ID(), 'pksh_teachers' );
|
||||
|
||||
if ( empty( $teachers ) || ! is_array( $teachers ) ) {
|
||||
return array();
|
||||
@ -1382,6 +1325,244 @@ function dekart_schema_teachers(): array {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
Новые сущности для Phase 2 (Local SEO + AI)
|
||||
===================================================================== */
|
||||
|
||||
/**
|
||||
* Event — бесплатное пробное занятие.
|
||||
* Google Events carousel, высокая конверсия на "пробное занятие [город]".
|
||||
*
|
||||
* @see https://schema.org/Event
|
||||
*/
|
||||
function dekart_schema_trial_event(): ?array {
|
||||
if ( ! is_page( 2 ) ) {
|
||||
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 ) ) {
|
||||
// Fallback: следующий понедельник
|
||||
$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',
|
||||
),
|
||||
);
|
||||
|
||||
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' => get_the_post_thumbnail_url( 2, 'full' ) ?: '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* QAPage — ключевые вопросы для AI citation readiness.
|
||||
* Google retired FAQ rich results May 2026, но QAPage важен для AI Overviews/ChatGPT/Perplexity.
|
||||
*
|
||||
* @see https://schema.org/QAPage
|
||||
*/
|
||||
function dekart_schema_qapage(): ?array {
|
||||
if ( ! is_page( 2 ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
// Берём только 3-5 ключевых вопросов (самые частые)
|
||||
$key_questions = array_slice( $faq_items, 0, 5 );
|
||||
|
||||
$main_entity = array();
|
||||
foreach ( $key_questions as $item ) {
|
||||
$question = isset( $item['faq_question'] ) ? trim( $item['faq_question'] ) : '';
|
||||
$answer = isset( $item['faq_answer'] ) ? trim( $item['faq_answer'] ) : '';
|
||||
|
||||
if ( empty( $question ) || empty( $answer ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$main_entity[] = array(
|
||||
'@type' => 'Question',
|
||||
'name' => wp_strip_all_tags( $question ),
|
||||
'acceptedAnswer' => array(
|
||||
'@type' => 'Answer',
|
||||
'text' => wp_strip_all_tags( $answer ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $main_entity ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'@type' => 'QAPage',
|
||||
'@id' => $page_url . '#qapage',
|
||||
'mainEntity' => $main_entity,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer / AggregateOffer с UnitPriceSpecification.
|
||||
* Добавляет priceSpecification с unitCode: 'MON' для цен за месяц.
|
||||
*
|
||||
* @see https://schema.org/UnitPriceSpecification
|
||||
*/
|
||||
function dekart_schema_offer(): ?array {
|
||||
$page_url = dekart_schema_page_url();
|
||||
$programs = carbon_get_post_meta( get_the_ID(), 'pksh_programs' );
|
||||
|
||||
$price_valid = '';
|
||||
if ( ! empty( $programs ) && is_array( $programs ) ) {
|
||||
foreach ( $programs as $prog ) {
|
||||
if ( ! empty( $prog['program_price_valid'] ) ) {
|
||||
$d = trim( $prog['program_price_valid'] );
|
||||
if ( $d > $price_valid ) {
|
||||
$price_valid = $d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$prices = array();
|
||||
|
||||
if ( ! empty( $programs ) && is_array( $programs ) ) {
|
||||
foreach ( $programs as $program ) {
|
||||
if ( ! empty( $program['program_price_subscription'] ) ) {
|
||||
$num = dekart_parse_price( $program['program_price_subscription'] );
|
||||
if ( $num > 0 ) {
|
||||
$prices[] = $num;
|
||||
}
|
||||
}
|
||||
if ( ! empty( $program['program_price_single'] ) ) {
|
||||
$num = dekart_parse_price( $program['program_price_single'] );
|
||||
if ( $num > 0 ) {
|
||||
$prices[] = $num;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $prices ) ) {
|
||||
$offer = array(
|
||||
'@type' => 'Offer',
|
||||
'@id' => $page_url . '#offer',
|
||||
'price' => '0',
|
||||
'priceCurrency' => 'RUB',
|
||||
'availability' => 'https://schema.org/LimitedAvailability',
|
||||
'description' => 'Бесплатное пробное занятие',
|
||||
'category' => 'Бесплатное пробное занятие',
|
||||
);
|
||||
if ( ! empty( $price_valid ) ) {
|
||||
$offer['priceValidUntil'] = trim( $price_valid );
|
||||
}
|
||||
return $offer;
|
||||
}
|
||||
|
||||
$low = min( $prices );
|
||||
$high = max( $prices );
|
||||
|
||||
$aggregate = array(
|
||||
'@type' => 'AggregateOffer',
|
||||
'@id' => $page_url . '#offer',
|
||||
'priceCurrency' => 'RUB',
|
||||
'lowPrice' => (string) $low,
|
||||
'highPrice' => (string) $high,
|
||||
'offerCount' => (string) count( $prices ),
|
||||
'availability' => 'https://schema.org/InStock',
|
||||
'category' => 'Образовательные услуги',
|
||||
'offeredBy' => array(
|
||||
'@id' => $page_url . '#localbusiness',
|
||||
),
|
||||
// UnitPriceSpecification для цен за месяц
|
||||
'priceSpecification' => array(
|
||||
'@type' => 'UnitPriceSpecification',
|
||||
'priceCurrency' => 'RUB',
|
||||
'minPrice' => (string) $low,
|
||||
'maxPrice' => (string) $high,
|
||||
'unitCode' => 'MON', // месяц
|
||||
'unitText' => 'месяц',
|
||||
),
|
||||
);
|
||||
|
||||
if ( ! empty( $price_valid ) ) {
|
||||
$aggregate['priceValidUntil'] = trim( $price_valid );
|
||||
}
|
||||
|
||||
$offers = array( $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 . '#localbusiness',
|
||||
),
|
||||
);
|
||||
if ( ! empty( $price_valid ) ) {
|
||||
$trial['priceValidUntil'] = trim( $price_valid );
|
||||
}
|
||||
$offers[] = $trial;
|
||||
}
|
||||
|
||||
return $offers;
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
Вспомогательные функции
|
||||
===================================================================== */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user