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) ===
|
# === Agent/Test files — dev-мусор (не в git) ===
|
||||||
data/
|
data/
|
||||||
llms.txt
|
|
||||||
test.php
|
test.php
|
||||||
.agents/
|
.agents/
|
||||||
|
.claude/
|
||||||
|
skills-lock.json
|
||||||
|
|
||||||
# === Audit files — dev-мусор (не в git) ===
|
# === Audit files — dev-мусор (не в git) ===
|
||||||
*.audit.txt
|
*.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"
|
<img width="19" height="32"
|
||||||
src="<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/icon-location.svg"
|
src="<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/icon-location.svg"
|
||||||
alt="" role="presentation" loading="lazy">
|
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>
|
</div>
|
||||||
<a class="footer__link-icon"
|
<a class="footer__link-icon"
|
||||||
href="tel:<?php echo preg_replace('/[^0-9+]/', '', carbon_get_theme_option('site_phone')); ?>">
|
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_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_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_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_parking', array( 'sanitize_callback' => 'sanitize_textarea_field' ) );
|
||||||
register_setting( 'dekart_branch_group', 'branch_logistics_nearby', 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 — удалены.
|
// Поля branch_map_url, branch_founded, branch_photos — удалены.
|
||||||
@ -1976,6 +1979,21 @@ function dekart_branch_settings_page() {
|
|||||||
<label>Почтовый индекс</label>
|
<label>Почтовый индекс</label>
|
||||||
<input type="text" name="branch_postal_code" value="<?php echo esc_attr( get_option( 'branch_postal_code', '' ) ); ?>">
|
<input type="text" name="branch_postal_code" value="<?php echo esc_attr( get_option( 'branch_postal_code', '' ) ); ?>">
|
||||||
</div>
|
</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">
|
<div class="branch-field">
|
||||||
<label>Пешком от ориентира</label>
|
<label>Пешком от ориентира</label>
|
||||||
<input type="text" name="branch_logistics_walk" value="<?php echo esc_attr( get_option( 'branch_logistics_walk', '5 минут пешком от ТЦ «Глобус»' ) ); ?>">
|
<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>
|
<h2>🔗 E-E-A-T: Внешние платформы</h2>
|
||||||
<div class="branch-section-body">
|
<div class="branch-section-body">
|
||||||
<p class="desc">Ссылки на внешние площадки с отзывами для повышения E-E-A-T. Отображаются под заголовком «Отзывы на внешних площадках».</p>
|
<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">
|
<div class="branch-field">
|
||||||
<label>Яндекс Карты (ссылка на отзывы)</label>
|
<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">
|
<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"
|
<img width="15" height="25"
|
||||||
src="<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/icon-location.svg"
|
src="<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/icon-location.svg"
|
||||||
alt="Адрес" loading="lazy">
|
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>
|
</div>
|
||||||
<a target="_blank" rel="noopener noreferrer"
|
<a target="_blank" rel="noopener noreferrer"
|
||||||
href="<?php echo esc_url( carbon_get_theme_option('social_header_wt') ); ?>"
|
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' );
|
add_filter( 'wpseo_json_ld_output', '__return_false' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Принудительно меняем og:type на 'website' для страницы Подготовки к школе (ID=2),
|
* Принудительно меняем og:type на 'website' для страницы Подготовки к школе,
|
||||||
* чтобы избежать несоответствия article/website в социальных сниппетах.
|
* чтобы избежать несоответствия article/website в социальных сниппетах.
|
||||||
*/
|
*/
|
||||||
add_filter( 'wpseo_opengraph_type', 'dekart_schema_og_type', 10, 1 );
|
add_filter( 'wpseo_opengraph_type', 'dekart_schema_og_type', 10, 1 );
|
||||||
function dekart_schema_og_type( string $type ): string {
|
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 'website';
|
||||||
}
|
}
|
||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Убираем article:* OG-теги для страницы Подготовки к школе (ID=2),
|
* Убираем article:* OG-теги для страницы Подготовки к школе,
|
||||||
* чтобы не было конфликта og:type=website с article:modified_time.
|
* чтобы не было конфликта og:type=website с article:modified_time.
|
||||||
* Аналог page-camp.php:15-27.
|
* Аналог page-camp.php:15-27.
|
||||||
*/
|
*/
|
||||||
add_filter( 'wpseo_frontend_presenter_classes', function( array $presenters ): array {
|
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;
|
return $presenters;
|
||||||
}
|
}
|
||||||
$presenters = array_filter( $presenters, function( string $p ): bool {
|
$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.
|
* Главная функция: сборка @graph и вывод в wp_head.
|
||||||
* Срабатывает только на странице ID=2 (Подготовка к школе).
|
* Срабатывает только на странице с шаблоном page-podgotovka-k-shkole.php.
|
||||||
*/
|
*/
|
||||||
add_action( 'wp_head', 'dekart_schema_output', 1 );
|
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 {
|
function dekart_schema_output(): void {
|
||||||
if ( ! is_page( 2 ) ) {
|
if ( ! is_page_template( 'page-podgotovka-k-shkole.php' ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,12 +155,24 @@ function dekart_schema_output(): void {
|
|||||||
$graph[] = $faq;
|
$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)
|
// 12. ItemList — обёртка для отзывов (conditional)
|
||||||
$reviews = dekart_schema_reviews();
|
$reviews = dekart_schema_reviews();
|
||||||
if ( ! empty( $reviews ) && is_array( $reviews ) ) {
|
if ( ! empty( $reviews ) && is_array( $reviews ) ) {
|
||||||
$graph[] = array(
|
$graph[] = array(
|
||||||
'@type' => 'ItemList',
|
'@type' => 'ItemList',
|
||||||
'@id' => home_url( '/podgotovka-k-shkole/' ) . '#reviews',
|
'@id' => dekart_schema_page_url() . '#reviews',
|
||||||
'name' => 'Отзывы о подготовке к школе',
|
'name' => 'Отзывы о подготовке к школе',
|
||||||
'description' => 'Отзывы родителей о курсе подготовки к школе в Детском центре «Декарт»',
|
'description' => 'Отзывы родителей о курсе подготовки к школе в Детском центре «Декарт»',
|
||||||
'itemListElement' => array_map( function( $review, $index ) {
|
'itemListElement' => array_map( function( $review, $index ) {
|
||||||
@ -206,26 +227,27 @@ function dekart_schema_output(): void {
|
|||||||
* @see https://schema.org/WebPage
|
* @see https://schema.org/WebPage
|
||||||
*/
|
*/
|
||||||
function dekart_schema_webpage(): ?array {
|
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_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();
|
$name = $yoast_title ?: wp_get_document_title();
|
||||||
|
|
||||||
// _yoast_wpseo_metadesc — Yoast SEO описание
|
// _yoast_wpseo_metadesc — Yoast SEO описание
|
||||||
$description = get_post_meta( 2, '_yoast_wpseo_metadesc', true );
|
$description = get_post_meta( $post_id, '_yoast_wpseo_metadesc', true );
|
||||||
$description = $description ?: get_post_meta( 2, 'rank_math_description', true );
|
$description = $description ?: get_post_meta( $post_id, 'rank_math_description', true );
|
||||||
$description = $description ?: carbon_get_post_meta( 2, 'banner_subtitle' );
|
$description = $description ?: carbon_get_post_meta( $post_id, 'banner_subtitle' );
|
||||||
$description = $description ?: 'Подготовка к школе для детей 5–7 лет в Щёлково. Чтение, письмо, математика, логика.';
|
$description = $description ?: 'Подготовка к школе для детей 5–7 лет в Щёлково. Чтение, письмо, математика, логика.';
|
||||||
|
|
||||||
// has_post_thumbnail — изображение страницы (OG-совместимость)
|
// has_post_thumbnail — изображение страницы (OG-совместимость)
|
||||||
$image_url = '';
|
$image_url = '';
|
||||||
if ( has_post_thumbnail( 2 ) ) {
|
if ( has_post_thumbnail( get_the_ID() ) ) {
|
||||||
$image_url = get_the_post_thumbnail_url( 2, 'full' );
|
$image_url = get_the_post_thumbnail_url( 2, 'full' );
|
||||||
}
|
}
|
||||||
|
|
||||||
// pksh_level — уровень образования (about)
|
// 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
|
// branch_city — город для about и keywords
|
||||||
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
|
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
|
||||||
@ -280,7 +302,7 @@ function dekart_schema_webpage(): ?array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hasPart — ссылка на FAQPage этой страницы
|
// 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 ) ) {
|
if ( ! empty( $faq_items ) && is_array( $faq_items ) ) {
|
||||||
$webpage['hasPart'] = array(
|
$webpage['hasPart'] = array(
|
||||||
'@id' => $url . '#faq',
|
'@id' => $url . '#faq',
|
||||||
@ -318,7 +340,7 @@ function dekart_schema_website(): ?array {
|
|||||||
* BreadcrumbList — хлебные крошки для страницы.
|
* BreadcrumbList — хлебные крошки для страницы.
|
||||||
*/
|
*/
|
||||||
function dekart_schema_breadcrumb(): ?array {
|
function dekart_schema_breadcrumb(): ?array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
$home_url = home_url( '/' );
|
$home_url = home_url( '/' );
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
@ -430,7 +452,7 @@ function dekart_resolve_logo(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. banner_img — CF поле страницы
|
// 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 ( ! empty( $banner_img ) ) {
|
||||||
if ( is_array( $banner_img ) && isset( $banner_img['url'] ) ) {
|
if ( is_array( $banner_img ) && isset( $banner_img['url'] ) ) {
|
||||||
return $banner_img['url'];
|
return $banner_img['url'];
|
||||||
@ -451,7 +473,7 @@ function dekart_resolve_logo(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. post_thumbnail страницы
|
// 4. post_thumbnail страницы
|
||||||
if ( has_post_thumbnail( 2 ) ) {
|
if ( has_post_thumbnail( get_the_ID() ) ) {
|
||||||
$url = get_the_post_thumbnail_url( 2, 'full' );
|
$url = get_the_post_thumbnail_url( 2, 'full' );
|
||||||
if ( $url ) {
|
if ( $url ) {
|
||||||
return $url;
|
return $url;
|
||||||
@ -471,13 +493,13 @@ function dekart_resolve_logo(): string {
|
|||||||
* @see https://schema.org/EducationalOrganization
|
* @see https://schema.org/EducationalOrganization
|
||||||
*/
|
*/
|
||||||
function dekart_schema_local_business(): ?array {
|
function dekart_schema_local_business(): ?array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
$home_url = home_url( '/' );
|
$home_url = home_url( '/' );
|
||||||
|
|
||||||
// branch_name — название филиала (get_option)
|
// branch_name — название филиала (get_option)
|
||||||
$site_name = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
|
$site_name = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
|
||||||
// branch_address — улица
|
// branch_address — улица (приоритет: Settings API → CF theme option → хардкод)
|
||||||
$street = dekart_branch_option( 'branch_address', 'Комсомольская ул., 11' );
|
$street = get_option( 'branch_address' ) ?: carbon_get_theme_option( 'site_address' ) ?: 'Комсомольская ул., 11';
|
||||||
// branch_city — город
|
// branch_city — город
|
||||||
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
|
$city = dekart_branch_option( 'branch_city', 'Щёлково' );
|
||||||
// branch_region — регион/область
|
// branch_region — регион/область
|
||||||
@ -505,6 +527,12 @@ function dekart_schema_local_business(): ?array {
|
|||||||
$founded = dekart_schema_option( 'schema_founded', 'branch_founded' );
|
$founded = dekart_schema_option( 'schema_founded', 'branch_founded' );
|
||||||
// branch_map_url — ссылка на карту для hasMap (CF schema_map_url → Settings API)
|
// branch_map_url — ссылка на карту для hasMap (CF schema_map_url → Settings API)
|
||||||
$map_url = dekart_schema_option( 'schema_map_url', 'branch_map_url' );
|
$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)
|
// branch_hours — часы работы: CF complex (schema_hours) → textarea (branch_hours)
|
||||||
$hours_cf = carbon_get_theme_option( 'schema_hours' );
|
$hours_cf = carbon_get_theme_option( 'schema_hours' );
|
||||||
|
|
||||||
@ -630,11 +658,28 @@ function dekart_schema_local_business(): ?array {
|
|||||||
$lb['priceRange'] = trim( $price_range );
|
$lb['priceRange'] = trim( $price_range );
|
||||||
}
|
}
|
||||||
|
|
||||||
// areaServed — город присутствия (условно)
|
// Google Place ID — identifier + @id для Google Business Profile
|
||||||
$lb['areaServed'] = array(
|
if ( ! empty( $google_place_id ) && is_string( $google_place_id ) ) {
|
||||||
'@type' => 'City',
|
$lb['identifier'] = array(
|
||||||
'name' => wp_strip_all_tags( $city ),
|
'@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 — год основания (условно)
|
// foundingDate — год основания (условно)
|
||||||
if ( ! empty( $founded ) && is_string( $founded ) ) {
|
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
|
// Если после добавления появились элементы, обновляем sameAs
|
||||||
if ( ! empty( $same_as ) && ! isset( $lb['sameAs'] ) ) {
|
if ( ! empty( $same_as ) && ! isset( $lb['sameAs'] ) ) {
|
||||||
$lb['sameAs'] = $same_as;
|
$lb['sameAs'] = $same_as;
|
||||||
@ -728,7 +788,7 @@ function dekart_schema_local_business(): ?array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// employee — преподаватели (Person), если есть
|
// 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 ) ) {
|
if ( ! empty( $teachers ) && is_array( $teachers ) ) {
|
||||||
$employee_ids = array();
|
$employee_ids = array();
|
||||||
foreach ( $teachers as $i => $t ) {
|
foreach ( $teachers as $i => $t ) {
|
||||||
@ -755,32 +815,32 @@ function dekart_schema_local_business(): ?array {
|
|||||||
* @see https://schema.org/CourseInstance
|
* @see https://schema.org/CourseInstance
|
||||||
*/
|
*/
|
||||||
function dekart_schema_course(): ?array {
|
function dekart_schema_course(): ?array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
$home_url = home_url( '/' );
|
$home_url = home_url( '/' );
|
||||||
|
|
||||||
// banner_h1 — заголовок H1 (Course.name)
|
// 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)
|
// pksh_description — полное описание курса для микроразметки (textarea)
|
||||||
// Если заполнено — приоритет над banner_subtitle
|
// Если заполнено — приоритет над 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)
|
// 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, 'бесплатно' );
|
$has_trial = ! empty( $description ) && false !== mb_stripos( $description, 'бесплатно' );
|
||||||
// pksh_level — уровень образования
|
// pksh_level — уровень образования
|
||||||
$level = carbon_get_post_meta( 2, 'pksh_level' );
|
$level = carbon_get_post_meta( get_the_ID(), 'pksh_level' );
|
||||||
// pksh_age — возраст
|
// pksh_age — возраст
|
||||||
$age = carbon_get_post_meta( 2, 'pksh_age' );
|
$age = carbon_get_post_meta( get_the_ID(), 'pksh_age' );
|
||||||
// pksh_audience — целевая аудитория
|
// 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)
|
// 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 — список навыков
|
// pksh_skills — список навыков
|
||||||
$skills = carbon_get_post_meta( 2, 'pksh_skills' );
|
$skills = carbon_get_post_meta( get_the_ID(), 'pksh_skills' );
|
||||||
// banner_img — изображение курса
|
// 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)
|
// pksh_programs — программы (для timeRequired + CourseInstance)
|
||||||
$programs = carbon_get_post_meta( 2, 'pksh_programs' );
|
$programs = carbon_get_post_meta( get_the_ID(), 'pksh_programs' );
|
||||||
|
|
||||||
$course = array(
|
$course = array(
|
||||||
'@type' => 'Course',
|
'@type' => 'Course',
|
||||||
@ -801,7 +861,7 @@ function dekart_schema_course(): ?array {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// review — 1-2 отзыва прямо в Course (Google требует для звёзд в rich snippet)
|
// 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 ) ) {
|
if ( ! empty( $reviews_raw ) && is_array( $reviews_raw ) ) {
|
||||||
$review_items = array();
|
$review_items = array();
|
||||||
foreach ( $reviews_raw as $rv ) {
|
foreach ( $reviews_raw as $rv ) {
|
||||||
@ -889,7 +949,7 @@ function dekart_schema_course(): ?array {
|
|||||||
$course['image'] = esc_url( $url );
|
$course['image'] = esc_url( $url );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ( has_post_thumbnail( 2 ) ) {
|
} elseif ( has_post_thumbnail( get_the_ID() ) ) {
|
||||||
// fallback: post_thumbnail страницы
|
// fallback: post_thumbnail страницы
|
||||||
$url = get_the_post_thumbnail_url( 2, 'full' );
|
$url = get_the_post_thumbnail_url( 2, 'full' );
|
||||||
if ( $url ) {
|
if ( $url ) {
|
||||||
@ -992,131 +1052,14 @@ function dekart_schema_course(): ?array {
|
|||||||
return $course;
|
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.
|
* AggregateRating — рейтинг из отзывов Carbon Fields.
|
||||||
* Вычисляется динамически из pksh_reviews_list.
|
* Вычисляется динамически из pksh_reviews_list.
|
||||||
* Если отзывов < 1 — блок не выводится.
|
* Если отзывов < 1 — блок не выводится.
|
||||||
*/
|
*/
|
||||||
function dekart_schema_rating(): ?array {
|
function dekart_schema_rating(): ?array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
$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 ) ) {
|
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
|
||||||
return null;
|
return null;
|
||||||
@ -1144,7 +1087,7 @@ function dekart_schema_rating(): ?array {
|
|||||||
'worstRating' => '1',
|
'worstRating' => '1',
|
||||||
'ratingCount' => (string) count( $ratings ),
|
'ratingCount' => (string) count( $ratings ),
|
||||||
'itemReviewed' => array(
|
'itemReviewed' => array(
|
||||||
'@id' => home_url( '/' ) . '#organization',
|
'@id' => $page_url . '#course',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1157,11 +1100,11 @@ function dekart_schema_rating(): ?array {
|
|||||||
* @see https://schema.org/Service
|
* @see https://schema.org/Service
|
||||||
*/
|
*/
|
||||||
function dekart_schema_service(): ?array {
|
function dekart_schema_service(): ?array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
$home_url = home_url( '/' );
|
$home_url = home_url( '/' );
|
||||||
|
|
||||||
$name = carbon_get_post_meta( 2, 'banner_h1' ) ?: 'Подготовка к школе';
|
$name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе';
|
||||||
$description = carbon_get_post_meta( 2, 'banner_subtitle' ) ?: '';
|
$description = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' ) ?: '';
|
||||||
|
|
||||||
$service = array(
|
$service = array(
|
||||||
'@type' => 'Service',
|
'@type' => 'Service',
|
||||||
@ -1188,8 +1131,8 @@ function dekart_schema_service(): ?array {
|
|||||||
* Если вопросов нет — блок не выводится.
|
* Если вопросов нет — блок не выводится.
|
||||||
*/
|
*/
|
||||||
function dekart_schema_faq(): ?array {
|
function dekart_schema_faq(): ?array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
$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 ) ) {
|
if ( empty( $faq_items ) || ! is_array( $faq_items ) ) {
|
||||||
return null;
|
return null;
|
||||||
@ -1234,9 +1177,9 @@ function dekart_schema_faq(): ?array {
|
|||||||
* @see https://schema.org/Review
|
* @see https://schema.org/Review
|
||||||
*/
|
*/
|
||||||
function dekart_schema_reviews(): array {
|
function dekart_schema_reviews(): array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
// pksh_reviews_list — complex-поле отзывов
|
// 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 ) ) {
|
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
|
||||||
return array();
|
return array();
|
||||||
@ -1273,7 +1216,7 @@ function dekart_schema_reviews(): array {
|
|||||||
),
|
),
|
||||||
'reviewBody' => wp_strip_all_tags( $body ),
|
'reviewBody' => wp_strip_all_tags( $body ),
|
||||||
'itemReviewed' => array(
|
'itemReviewed' => array(
|
||||||
'@id' => home_url( '/' ) . '#organization',
|
'@id' => $page_url . '#course',
|
||||||
),
|
),
|
||||||
'reviewRating' => array(
|
'reviewRating' => array(
|
||||||
'@type' => 'Rating',
|
'@type' => 'Rating',
|
||||||
@ -1304,11 +1247,11 @@ function dekart_schema_reviews(): array {
|
|||||||
* @see https://schema.org/Person
|
* @see https://schema.org/Person
|
||||||
*/
|
*/
|
||||||
function dekart_schema_teachers(): array {
|
function dekart_schema_teachers(): array {
|
||||||
$page_url = home_url( '/podgotovka-k-shkole/' );
|
$page_url = dekart_schema_page_url();
|
||||||
// pksh_teachers — complex-поле преподавателей
|
// pksh_teachers — complex-поле преподавателей
|
||||||
// Subfields: teacher_id (select → post ID), teacher_subject, teacher_about
|
// Subfields: teacher_id (select → post ID), teacher_subject, teacher_about
|
||||||
// teacher_name и teacher_photo загружаются из карточки преподавателя
|
// 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 ) ) {
|
if ( empty( $teachers ) || ! is_array( $teachers ) ) {
|
||||||
return array();
|
return array();
|
||||||
@ -1382,6 +1325,244 @@ function dekart_schema_teachers(): array {
|
|||||||
return $result;
|
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