diff --git a/llms.txt b/llms.txt
index 0d8e3f9..98e3cb6 100644
--- a/llms.txt
+++ b/llms.txt
@@ -1,34 +1,18 @@
-# Декарт — частная школа в Щёлково
-> Частная школа полного дня в Щёлково, Московская область. Программы начальной школы (1–4 класс), средней школы (5–9 класс), подготовка к ЕГЭ и ОГЭ (10–11 класс). Индивидуальный подход, классы до 12 человек, продлённый день, подготовка к школе.
+# Детский центр «Декарт» — Подготовка к школе в Щёлково
+> Образовательный центр в Щёлково с гослицензией. Работаем с 2014 года. Готовим детей 5–7 лет к школе: чтение, письмо, математика, логика.
-## Основные страницы
-- [Отзывы родителей и учеников](https://schelkovo.dekart.school/otzyvy/)
-- [Начальная школа](https://schelkovo.dekart.school/nachalnaya-shkola/)
-- [Подготовка к ЕГЭ и ОГЭ](https://schelkovo.dekart.school/podgotovka-k-ege/)
-- [Группа продлённого дня](https://schelkovo.dekart.school/prodlenka/)
-- [Подготовка к школе](https://schelkovo.dekart.school/podgotovka-k-shkole/)
-- [Летний городской лагерь](https://schelkovo.dekart.school/gorodskoj-lager/)
-- [Контакты](https://schelkovo.dekart.school/kontakty/)
-- [О школе](https://schelkovo.dekart.school/o-nas/)
+## Основные разделы
+- [Подготовка к школе](https://xn--80atdlj0d.xn--p1ai/podgotovka-k-shkole/): Комплексные занятия для детей 5–7 лет. Чтение, письмо, счёт, логика. Первое занятие — бесплатно.
+- [Начальная школа](https://xn--80atdlj0d.xn--p1ai/nachalnaya-shkola/): Программы 1–4 классов. УМК «Перспектива», система Занкова, Cambridge English.
+- [О нас](https://xn--80atdlj0d.xn--p1ai/o-nas/): Лицензия, документы, педагоги.
+- [Отзывы](https://xn--80atdlj0d.xn--p1ai/otzyvy/): Отзывы родителей и выпускников.
+- [Контакты](https://xn--80atdlj0d.xn--p1ai/contacts/): Адрес, телефон, карта.
-## О школе
-- Частная школа «Декарт» основана в 2010 году
-- Расположена по адресу: ул. Талсинская, 24, Щёлково, Московская область
-- Режим работы: пн–пт 8:00–19:00, сб 9:00–14:00
-- Классы: до 12 человек
-- Наполняемость: более 500 выпускников
-- Педагоги: 50+ квалифицированных специалистов
-
-## Что предлагает школа
-- Начальное общее образование (1–4 класс) с углублённым английским
-- Основное общее образование (5–9 класс) с предпрофильной подготовкой
-- Среднее общее образование (10–11 класс) с подготовкой к ЕГЭ
-- Подготовка к школе для детей 5–6 лет
-- Группа продлённого дня с 8:00 до 19:00
-- Летний городской лагерь
-- Кружки: робототехника, шахматы, английский театр, изостудия
-
-
+## Ключевые факты
+- Адрес: Талсинская ул., 24, Щёлково, Московская область
+- Телефон: +7 (926) 931-14-34
+- Лицензия: государственная образовательная лицензия
+- Основан: 2014 год
+- Педагоги: высшее педагогическое образование, стаж от 12 лет
+- Группы: до 10 человек
+- Формат: очные занятия
diff --git a/wp-content/object-cache.php.dev-disabled b/wp-content/object-cache.php.dev-disabled
new file mode 100644
index 0000000..a8747ce
--- /dev/null
+++ b/wp-content/object-cache.php.dev-disabled
@@ -0,0 +1,978 @@
+add( $key, $data, $group, $expire );
+}
+
+function wp_cache_incr( $key, $n = 1, $group = '' ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->incr( $key, $n, $group );
+}
+
+function wp_cache_decr( $key, $n = 1, $group = '' ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->decr( $key, $n, $group );
+}
+
+function wp_cache_close() {
+ global $wp_object_cache;
+
+ return $wp_object_cache->close();
+}
+
+function wp_cache_delete( $key, $group = '' ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->delete( $key, $group );
+}
+
+function wp_cache_flush() {
+ global $wp_object_cache;
+
+ return $wp_object_cache->flush();
+}
+
+function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
+ global $wp_object_cache;
+
+ $value = apply_filters( 'pre_wp_cache_get', false, $key, $group, $force );
+ if ( false !== $value ) {
+ $found = true;
+ return $value;
+ }
+
+ return $wp_object_cache->get( $key, $group, $force, $found );
+}
+
+/**
+ * Retrieve multiple cache entries
+ *
+ * @param array $groups Array of arrays, of groups and keys to retrieve
+ * @return mixed
+ */
+function wp_cache_get_multi( $groups ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->get_multi( $groups );
+}
+
+function wp_cache_init() {
+ global $wp_object_cache;
+
+ $wp_object_cache = new WP_Object_Cache();
+}
+
+function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->replace( $key, $data, $group, $expire );
+}
+
+function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
+ global $wp_object_cache;
+
+ if ( defined( 'WP_INSTALLING' ) == false ) {
+ return $wp_object_cache->set( $key, $data, $group, $expire );
+ } else {
+ return $wp_object_cache->delete( $key, $group );
+ }
+}
+
+function wp_cache_switch_to_blog( $blog_id ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->switch_to_blog( $blog_id );
+}
+
+function wp_cache_add_global_groups( $groups ) {
+ global $wp_object_cache;
+
+ $wp_object_cache->add_global_groups( $groups );
+}
+
+function wp_cache_add_non_persistent_groups( $groups ) {
+ global $wp_object_cache;
+
+ $wp_object_cache->add_non_persistent_groups( $groups );
+}
+
+class WP_Object_Cache {
+ var $global_groups = array( 'WP_Object_Cache_global' );
+
+ var $no_mc_groups = array();
+
+ var $cache = array();
+ var $mc = array();
+ var $default_mcs = array();
+ var $stats = array();
+ var $group_ops = array();
+
+ var $flush_group = 'WP_Object_Cache';
+ var $global_flush_group = 'WP_Object_Cache_global';
+ var $flush_key = "flush_number_v4";
+ var $old_flush_key = "flush_number";
+ var $flush_number = array();
+ var $global_flush_number = null;
+
+ var $cache_enabled = true;
+ var $default_expiration = 0;
+ var $max_expiration = 2592000; // 30 days
+
+ var $stats_callback = null;
+
+ var $connection_errors = array();
+
+ var $time_total = 0;
+ var $size_total = 0;
+ var $slow_op_microseconds = 0.005; // 5 ms
+
+ function add( $id, $data, $group = 'default', $expire = 0 ) {
+ $key = $this->key( $id, $group );
+
+ if ( is_object( $data ) ) {
+ $data = clone $data;
+ }
+
+ if ( in_array( $group, $this->no_mc_groups ) ) {
+ $this->cache[ $key ] = [
+ 'value' => $data,
+ 'found' => false,
+ ];
+
+ return true;
+ } elseif ( isset( $this->cache[ $key ][ 'value' ] ) && false !== $this->cache[ $key ][ 'value' ] ) {
+ return false;
+ }
+
+ $mc =& $this->get_mc( $group );
+
+ $expire = intval( $expire );
+ if ( 0 === $expire || $expire > $this->max_expiration ) {
+ $expire = $this->default_expiration;
+ }
+
+ $size = $this->get_data_size( $data );
+ $this->timer_start();
+ $result = $mc->add( $key, $data, false, $expire );
+ $elapsed = $this->timer_stop();
+
+ $comment = '';
+ if ( isset( $this->cache[ $key ] ) ) {
+ $comment .= ' [lc already]';
+ }
+ if ( false === $result ) {
+ $comment .= ' [mc already]';
+ }
+
+ $this->group_ops_stats( 'add', $key, $group, $size, $elapsed, $comment );
+
+ if ( false !== $result ) {
+ $this->cache[ $key ] = [
+ 'value' => $data,
+ 'found' => true,
+ ];
+ } else if ( false === $result && true === isset( $this->cache[$key][ 'value' ] ) && false === $this->cache[$key][ 'value' ] ) {
+ /*
+ * Here we unset local cache if remote add failed and local cache value is equal to `false` in order
+ * to update the local cache anytime we get a new information from remote server. This way, the next
+ * cache get will go to remote server and will fetch recent data.
+ */
+ unset( $this->cache[$key] );
+ }
+
+ return $result;
+ }
+
+ function add_global_groups( $groups ) {
+ if ( ! is_array( $groups ) ) {
+ $groups = (array) $groups;
+ }
+
+ $this->global_groups = array_merge( $this->global_groups, $groups );
+ $this->global_groups = array_unique( $this->global_groups );
+ }
+
+ function add_non_persistent_groups( $groups ) {
+ if ( ! is_array( $groups ) ) {
+ $groups = (array) $groups;
+ }
+
+ $this->no_mc_groups = array_merge( $this->no_mc_groups, $groups );
+ $this->no_mc_groups = array_unique( $this->no_mc_groups );
+ }
+
+ function incr( $id, $n = 1, $group = 'default' ) {
+ $key = $this->key( $id, $group );
+ $mc =& $this->get_mc( $group );
+
+ $incremented = $mc->increment( $key, $n );
+
+ $this->cache[ $key ] = [
+ 'value' => $incremented,
+ 'found' => false !== $incremented,
+ ];
+
+ return $this->cache[ $key ][ 'value' ];
+ }
+
+ function decr( $id, $n = 1, $group = 'default' ) {
+ $key = $this->key( $id, $group );
+ $mc =& $this->get_mc( $group );
+
+ $decremented = $mc->decrement( $key, $n );
+ $this->cache[ $key ] = [
+ 'value' => $decremented,
+ 'found' => false !== $decremented,
+ ];
+
+ return $this->cache[ $key ][ 'value' ];
+ }
+
+ function close() {
+ foreach ( $this->mc as $bucket => $mc ) {
+ $mc->close();
+ }
+ }
+
+ function delete( $id, $group = 'default' ) {
+ $key = $this->key( $id, $group );
+
+ if ( in_array( $group, $this->no_mc_groups ) ) {
+ unset( $this->cache[ $key ] );
+
+ return true;
+ }
+
+ $mc =& $this->get_mc( $group );
+
+ $this->timer_start();
+ $result = $mc->delete( $key );
+ $elapsed = $this->timer_stop();
+
+ $this->group_ops_stats( 'delete', $key, $group, null, $elapsed );
+
+ if ( false !== $result ) {
+ unset( $this->cache[ $key ] );
+ }
+
+ return $result;
+ }
+
+ // Gets number from all default servers, replicating if needed
+ function get_max_flush_number( $group ) {
+ $key = $this->key( $this->flush_key, $group );
+
+ $values = array();
+ $size = 19; // size of microsecond timestamp serialized
+ foreach ( $this->default_mcs as $i => $mc ) {
+ $flags = false;
+ $this->timer_start();
+ $values[ $i ] = $mc->get( $key, $flags );
+ $elapsed = $this->timer_stop();
+
+ if ( empty( $values[ $i ] ) ) {
+ $this->group_ops_stats( 'get_flush_number', $key, $group, null, $elapsed, 'not_in_memcache' );
+ } else {
+ $this->group_ops_stats( 'get_flush_number', $key, $group, $size, $elapsed, 'memcache' );
+ }
+ }
+
+ $max = max( $values );
+
+ if ( ! $max > 0 ) {
+ return false;
+ }
+
+ // Replicate to servers not having the max.
+ $expire = 0;
+ foreach ( $this->default_mcs as $i => $mc ) {
+ if ( $values[ $i ] < $max ) {
+ $this->timer_start();
+ $mc->set( $key, $max, false, $expire );
+ $elapsed = $this->timer_stop();
+ $this->group_ops_stats( 'set_flush_number', $key, $group, $size, $elapsed, 'replication_repair' );
+ }
+ }
+
+ return $max;
+ }
+
+ function set_flush_number( $value, $group ) {
+ $key = $this->key( $this->flush_key, $group );
+ $expire = 0;
+ $size = 19;
+ foreach ( $this->default_mcs as $i => $mc ) {
+ $this->timer_start();
+ $mc->set( $key, $value, false, $expire );
+ $elapsed = $this->timer_stop();
+ $this->group_ops_stats( 'set_flush_number', $key, $group, $size, $elapsed, 'replication' );
+ }
+ }
+
+ function get_flush_number( $group ) {
+ $flush_number = $this->get_max_flush_number( $group );
+ // What if there is no v4 flush number?
+ if ( empty( $flush_number ) ) {
+ // Look for the v3 flush number key.
+ $flush_number = intval( $this->get( $this->old_flush_key, $group ) );
+ if ( !empty( $flush_number ) ) {
+ // Found v3 flush number. Upgrade to v4 with replication.
+ $this->set_flush_number( $flush_number, $group );
+ // Delete v3 key so we can't later restore it and find stale keys.
+ } else {
+ // No flush number found anywhere. Make a new one. This flushes the cache.
+ $flush_number = $this->new_flush_number();
+ $this->set_flush_number( $flush_number, $group );
+ }
+ }
+
+ return $flush_number;
+ }
+
+ function get_global_flush_number() {
+ if ( ! isset( $this->global_flush_number ) ) {
+ $this->global_flush_number = $this->get_flush_number( $this->global_flush_group );
+ }
+ return $this->global_flush_number;
+ }
+
+ function get_blog_flush_number() {
+ if ( ! isset( $this->flush_number[ $this->blog_prefix ] ) ) {
+ $this->flush_number[ $this->blog_prefix ] = $this->get_flush_number( $this->flush_group );
+ }
+ return $this->flush_number[ $this->blog_prefix ];
+ }
+
+ function flush() {
+ // Do not use the memcached flush method. It acts on an
+ // entire memcached server, affecting all sites.
+ // Flush is also unusable in some setups, e.g. twemproxy.
+ // Instead, rotate the key prefix for the current site.
+ // Global keys are rotated when flushing on any network's
+ // main site.
+ $this->cache = array();
+
+ $flush_number = $this->new_flush_number();
+
+ $this->rotate_site_keys( $flush_number );
+
+ if ( is_main_site() ) {
+ $this->rotate_global_keys( $flush_number );
+ }
+ }
+
+ function rotate_site_keys( $flush_number = null ) {
+ if ( is_null( $flush_number ) ) {
+ $flush_number = $this->new_flush_number();
+ }
+
+ $this->set_flush_number( $flush_number, $this->flush_group );
+
+ $this->flush_number[ $this->blog_prefix ] = $flush_number;
+ }
+
+ function rotate_global_keys( $flush_number = null ) {
+ if ( is_null( $flush_number ) ) {
+ $flush_number = $this->new_flush_number();
+ }
+
+ $this->set_flush_number( $flush_number, $this->global_flush_group );
+
+ $this->global_flush_number = $flush_number;
+ }
+
+ function new_flush_number() {
+ return intval( microtime( true ) * 1e6 );
+ }
+
+ function get( $id, $group = 'default', $force = false, &$found = null ) {
+ $key = $this->key( $id, $group );
+ $mc =& $this->get_mc( $group );
+ $found = true;
+
+ if ( isset( $this->cache[ $key ] ) && ( ! $force || in_array( $group, $this->no_mc_groups ) ) ) {
+ if ( isset( $this->cache[ $key ][ 'value' ] ) && is_object( $this->cache[ $key ][ 'value' ] ) ) {
+ $value = clone $this->cache[ $key ][ 'value' ];
+ } else {
+ $value = $this->cache[ $key ][ 'value' ];
+ }
+ $found = $this->cache[ $key ][ 'found' ];
+
+ $this->group_ops_stats( 'get_local', $key, $group, null, null, 'local' );
+ } else if ( in_array( $group, $this->no_mc_groups ) ) {
+ $this->cache[ $key ] = [
+ 'value' => $value = false,
+ 'found' => false,
+ ];
+
+ $found = false;
+
+ $this->group_ops_stats( 'get_local', $key, $group, null, null, 'not_in_local' );
+ } else {
+ $flags = false;
+ $this->timer_start();
+ $value = $mc->get( $key, $flags );
+ $elapsed = $this->timer_stop();
+
+ // Value will be unchanged if the key doesn't exist.
+ if ( false === $flags ) {
+ $found = false;
+ $value = false;
+ }
+
+ $this->cache[ $key ] = [
+ 'value' => $value,
+ 'found' => $found,
+ ];
+
+ if ( is_null( $value ) || $value === false ) {
+ $this->group_ops_stats( 'get', $key, $group, null, $elapsed, 'not_in_memcache' );
+ } else if ( 'checkthedatabaseplease' === $value ) {
+ $this->group_ops_stats( 'get', $key, $group, null, $elapsed, 'checkthedatabaseplease' );
+ } else {
+ $size = $this->get_data_size( $value );
+ $this->group_ops_stats( 'get', $key, $group, $size, $elapsed, 'memcache' );
+ }
+ }
+
+ if ( 'checkthedatabaseplease' === $value ) {
+ unset( $this->cache[ $key ] );
+
+ $found = false;
+ $value = false;
+ }
+
+ return $value;
+ }
+
+ function get_multi( $groups ) {
+ /*
+ format: $get['group-name'] = array( 'key1', 'key2' );
+ */
+ $return = array();
+ $return_cache = array(
+ 'value' => false,
+ 'found' => false,
+ );
+
+ foreach ( $groups as $group => $ids ) {
+ $mc =& $this->get_mc( $group );
+ $keys = array();
+ $this->timer_start();
+
+ foreach ( $ids as $id ) {
+ $key = $this->key( $id, $group );
+ $keys[] = $key;
+
+ if ( isset( $this->cache[ $key ] ) ) {
+ if ( is_object( $this->cache[ $key ][ 'value'] ) ) {
+ $return[ $key ] = clone $this->cache[ $key ][ 'value'];
+ $return_cache[ $key ] = [
+ 'value' => clone $this->cache[ $key ][ 'value'],
+ 'found' => $this->cache[ $key ][ 'found'],
+ ];
+ } else {
+ $return[ $key ] = $this->cache[ $key ][ 'value'];
+ $return_cache[ $key ] = [
+ 'value' => $this->cache[ $key ][ 'value' ],
+ 'found' => $this->cache[ $key ][ 'found' ],
+ ];
+ }
+
+ continue;
+ } else if ( in_array( $group, $this->no_mc_groups ) ) {
+ $return[ $key ] = false;
+ $return_cache[ $key ] = [
+ 'value' => false,
+ 'found' => false,
+ ];
+
+ continue;
+ } else {
+ $fresh_get = $mc->get( $key );
+ $return[ $key ] = $fresh_get;
+ $return_cache[ $key ] = [
+ 'value' => $fresh_get,
+ 'found' => false !== $fresh_get,
+ ];
+ }
+ }
+
+ $elapsed = $this->timer_stop();
+ $this->group_ops_stats( 'get_multi', $keys, $group, null, $elapsed );
+ }
+
+ $this->cache = array_merge( $this->cache, $return_cache );
+
+ return $return;
+ }
+
+ function flush_prefix( $group ) {
+ if ( $group === $this->flush_group || $group === $this->global_flush_group ) {
+ // Never flush the flush numbers.
+ $number = '_';
+ } elseif ( false !== array_search( $group, $this->global_groups ) ) {
+ $number = $this->get_global_flush_number();
+ } else {
+ $number = $this->get_blog_flush_number();
+ }
+ return $number . ':';
+ }
+
+ function key( $key, $group ) {
+ if ( empty( $group ) ) {
+ $group = 'default';
+ }
+
+ $prefix = $this->key_salt;
+
+ $prefix .= $this->flush_prefix( $group );
+
+ if ( false !== array_search( $group, $this->global_groups ) ) {
+ $prefix .= $this->global_prefix;
+ } else {
+ $prefix .= $this->blog_prefix;
+ }
+
+ return preg_replace( '/\s+/', '', "$prefix:$group:$key" );
+ }
+
+ function replace( $id, $data, $group = 'default', $expire = 0 ) {
+ $key = $this->key( $id, $group );
+ $expire = intval( $expire );
+ if ( 0 === $expire || $expire > $this->max_expiration ) {
+ $expire = $this->default_expiration;
+ }
+ $mc =& $this->get_mc( $group );
+
+ if ( is_object( $data ) ) {
+ $data = clone $data;
+ }
+
+ $size = $this->get_data_size( $data );
+ $this->timer_start();
+ $result = $mc->replace( $key, $data, false, $expire );
+ $elapsed = $this->timer_stop();
+ $this->group_ops_stats( 'replace', $key, $group, $size, $elapsed );
+
+ if ( false !== $result ) {
+ $this->cache[ $key ] = [
+ 'value' => $data,
+ 'found' => true,
+ ];
+ }
+
+ return $result;
+ }
+
+ function set( $id, $data, $group = 'default', $expire = 0 ) {
+ $key = $this->key( $id, $group );
+
+ if ( isset( $this->cache[ $key ] ) && ( 'checkthedatabaseplease' === $this->cache[ $key ][ 'value' ] ) ) {
+ return false;
+ }
+
+ if ( is_object( $data ) ) {
+ $data = clone $data;
+ }
+
+ $this->cache[ $key ] = [
+ 'value' => $data,
+ 'found' => false, // Set to false as not technically found in memcache at this point.
+ ];
+
+ if ( in_array( $group, $this->no_mc_groups ) ) {
+ $this->group_ops_stats( 'set_local', $key, $group, null, null );
+
+ return true;
+ }
+
+ $expire = intval( $expire );
+ if ( 0 === $expire || $expire > $this->max_expiration ) {
+ $expire = $this->default_expiration;
+ }
+
+ $mc =& $this->get_mc( $group );
+
+ $size = $this->get_data_size( $data );
+ $this->timer_start();
+ $result = $mc->set( $key, $data, false, $expire );
+ $elapsed = $this->timer_stop();
+ $this->group_ops_stats( 'set', $key, $group, $size, $elapsed );
+
+ // Update the found cache value with the result of the set in memcache.
+ $this->cache[ $key ][ 'found' ] = $result;
+
+ return $result;
+ }
+
+ function switch_to_blog( $blog_id ) {
+ global $table_prefix;
+
+ $blog_id = (int) $blog_id;
+
+ $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix );
+ }
+
+ function colorize_debug_line( $line, $trailing_html = '' ) {
+ $colors = array(
+ 'get' => 'green',
+ 'get_local' => 'lightgreen',
+ 'get_multi' => 'fuchsia',
+ 'set' => 'purple',
+ 'set_local' => 'orchid',
+ 'add' => 'blue',
+ 'delete' => 'red',
+ 'delete_local' => 'tomato',
+ 'slow-ops' => 'crimson',
+ );
+
+ $cmd = substr( $line, 0, strpos( $line, ' ' ) );
+
+ // Start off with a neutral default color...
+ $color_for_cmd = 'brown';
+ // And if the cmd has a specific color, use that instead
+ if ( isset( $colors[ $cmd ] ) ) {
+ $color_for_cmd = $colors[ $cmd ];
+ }
+
+ $cmd2 = "" . esc_html( $cmd ) . " ";
+
+ return $cmd2 . esc_html( substr( $line, strlen( $cmd ) ) ) . "$trailing_html\n";
+ }
+
+ function js_toggle() {
+ echo "
+
+ ";
+ }
+
+ function stats() {
+ $this->js_toggle();
+
+ echo '
Total memcache query time: ' . number_format_i18n( sprintf( '%0.1f', $this->time_total * 1000 ), 1 ) . ' ms ';
+ echo "\n";
+ echo 'Total memcache size: ' . esc_html( size_format( $this->size_total, 2 ) ) . ' ';
+ echo "\n";
+
+ foreach ( $this->stats as $stat => $n ) {
+ if ( empty( $n ) ) {
+ continue;
+ }
+
+ echo '';
+ echo $this->colorize_debug_line( "$stat $n" );
+ echo ' ';
+ }
+
+ echo "\n";
+
+ echo "";
+ }
+
+ function get_group_ops_line( $index, $arr ) {
+ // operation
+ $line = "{$arr[0]} ";
+
+ // key
+ $json_encoded_key = json_encode( $arr[1] );
+ $line .= $json_encoded_key . " ";
+
+ // comment
+ if ( ! empty( $arr[4] ) ) {
+ $line .= "{$arr[4]} ";
+ }
+
+ // size
+ if ( isset( $arr[2] ) ) {
+ $line .= '(' . size_format( $arr[2], 2 ) . ') ';
+ }
+
+ // time
+ if ( isset( $arr[3] ) ) {
+ $line .= '(' . number_format_i18n( sprintf( '%0.1f', $arr[3] * 1000 ), 1 ) . ' ms)';
+ }
+
+ // backtrace
+ $bt_link = '';
+ if ( isset( $arr[6] ) ) {
+ $key_hash = md5( $index . $json_encoded_key );
+ $bt_link = " Toggle Backtrace ";
+ $bt_link .= "" . esc_html( $arr[6] ) . " ";
+ }
+
+ return $this->colorize_debug_line( $line, $bt_link );
+ }
+
+ function &get_mc( $group ) {
+ if ( isset( $this->mc[ $group ] ) ) {
+ return $this->mc[ $group ];
+ }
+
+ return $this->mc['default'];
+ }
+
+ function failure_callback( $host, $port ) {
+ $this->connection_errors[] = array(
+ 'host' => $host,
+ 'port' => $port,
+ );
+ }
+
+ function salt_keys( $key_salt ) {
+ if ( strlen( $key_salt ) ) {
+ $this->key_salt = $key_salt . ':';
+ } else {
+ $this->key_salt = '';
+ }
+ }
+
+ function __construct() {
+ $this->stats = array(
+ 'get' => 0,
+ 'get_local' => 0,
+ 'get_multi' => 0,
+ 'set' => 0,
+ 'set_local' => 0,
+ 'add' => 0,
+ 'delete' => 0,
+ 'delete_local' => 0,
+ 'slow-ops' => 0,
+ );
+
+ global $memcached_servers;
+
+ if ( isset( $memcached_servers ) ) {
+ $buckets = $memcached_servers;
+ } else {
+ $buckets = array( '127.0.0.1:11211' );
+ }
+
+ reset( $buckets );
+
+ if ( is_int( key( $buckets ) ) ) {
+ $buckets = array( 'default' => $buckets );
+ }
+
+ foreach ( $buckets as $bucket => $servers ) {
+ $this->mc[ $bucket ] = new Memcache();
+
+ foreach ( $servers as $i => $server ) {
+ if ( 'unix://' == substr( $server, 0, 7 ) ) {
+ $node = $server;
+ $port = 0;
+ } else {
+ list ( $node, $port ) = explode( ':', $server );
+
+ if ( ! $port ) {
+ $port = ini_get( 'memcache.default_port' );
+ }
+
+ $port = intval( $port );
+
+ if ( ! $port ) {
+ $port = 11211;
+ }
+ }
+
+ $this->mc[ $bucket ]->addServer( $node, $port, true, 1, 1, 15, true, array( $this, 'failure_callback' ) );
+ $this->mc[ $bucket ]->setCompressThreshold( 20000, 0.2 );
+
+ // Prepare individual connections to servers in default bucket for flush_number redundancy
+ if ( 'default' === $bucket ) {
+ $this->default_mcs[ $i ] = new Memcache();
+ $this->default_mcs[ $i ]->addServer( $node, $port, true, 1, 1, 15, true, array( $this, 'failure_callback' ) );
+ }
+ }
+ }
+
+ global $blog_id, $table_prefix;
+
+ $this->global_prefix = '';
+ $this->blog_prefix = '';
+
+ if ( function_exists( 'is_multisite' ) ) {
+ $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
+ $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix );
+ }
+
+ $this->salt_keys( WP_CACHE_KEY_SALT );
+
+ $this->cache_hits =& $this->stats['get'];
+ $this->cache_misses =& $this->stats['add'];
+ }
+
+ function increment_stat( $field, $num = 1 ) {
+ if ( ! isset( $this->stats[ $field ] ) ) {
+ $this->stats[ $field ] = $num;
+ } else {
+ $this->stats[ $field ] += $num;
+ }
+ }
+
+ function group_ops_stats( $op, $keys, $group, $size, $time, $comment = '' ) {
+ $this->increment_stat( $op );
+
+ // we have no use of the local ops details for now
+ if ( strpos( $op, '_local' ) ) {
+ return;
+ }
+
+ $this->size_total += $size;
+
+ $keys = $this->strip_memcached_keys( $keys );
+
+ if ( $time > $this->slow_op_microseconds && 'get_multi' !== $op ) {
+ $this->increment_stat( 'slow-ops' );
+ $backtrace = null;
+ if ( function_exists( 'wp_debug_backtrace_summary' ) ) {
+ $backtrace = wp_debug_backtrace_summary();
+ }
+ $this->group_ops['slow-ops'][] = array( $op, $keys, $size, $time, $comment, $group, $backtrace );
+ }
+
+ $this->group_ops[ $group ][] = array( $op, $keys, $size, $time, $comment );
+ }
+
+ /**
+ * Key format: key_salt:flush_number:table_prefix:key_name
+ *
+ * We want to strip the `key_salt:flush_number` part to not leak the memcached keys.
+ * If `key_salt` is set we strip `'key_salt:flush_number`, otherwise just strip the `flush_number` part.
+ */
+ function strip_memcached_keys( $keys ) {
+ if ( ! is_array( $keys ) ) {
+ $keys = [ $keys ];
+ }
+
+ foreach ( $keys as $key => $value ) {
+ $offset = 0;
+ if ( ! empty( $this->key_salt ) ) {
+ $offset = strpos( $value, ':' ) + 1;
+ }
+
+ $start = strpos( $value, ':', $offset );
+ $keys[ $key ] = substr( $value, $start + 1 );
+ }
+
+ if ( 1 === count( $keys ) ) {
+ return $keys[0];
+ }
+
+ return $keys;
+ }
+
+ function timer_start() {
+ $this->time_start = microtime( true );
+
+ return true;
+ }
+
+ function timer_stop() {
+ $time_total = microtime( true ) - $this->time_start;
+ $this->time_total += $time_total;
+
+ return $time_total;
+ }
+
+ function get_data_size( $data ) {
+ if ( is_string( $data ) ) {
+ return strlen( $data );
+ }
+
+ $serialized = serialize( $data );
+
+ return strlen( $serialized );
+ }
+}
diff --git a/wp-content/themes/dekart/assets/css/main.css b/wp-content/themes/dekart/assets/css/main.css
index 24fb422..f518718 100644
--- a/wp-content/themes/dekart/assets/css/main.css
+++ b/wp-content/themes/dekart/assets/css/main.css
@@ -205,7 +205,7 @@ html {
}
.ds-hero__subtitle {
font-size: 18px; line-height: 1.55;
- color: rgba(255,255,255,0.72); margin-bottom: 28px;
+ color: var(--color-rating); margin-bottom: 28px;
max-width: 500px;
}
/* Metrics — стиль «glass card» как на /otzyvy/ */
@@ -275,7 +275,7 @@ html {
margin-top: 10px; font-size: 13px; color: rgba(255,255,255,0.55);
}
.ds-hero__phone a {
- color: var(--color-accent); font-weight: 500; text-decoration: underline;
+ color: var(--color-rating); font-weight: 500; text-decoration: underline;
white-space: nowrap;
}
/* ---- Visual ---- */
diff --git a/wp-content/themes/dekart/assets/css/podgotovka-k-shkole.css b/wp-content/themes/dekart/assets/css/podgotovka-k-shkole.css
index c150d70..9541fe0 100644
--- a/wp-content/themes/dekart/assets/css/podgotovka-k-shkole.css
+++ b/wp-content/themes/dekart/assets/css/podgotovka-k-shkole.css
@@ -112,6 +112,9 @@
.page-pksh .section-header--left .bento-p {
max-width: 65ch;
}
+ .page-pksh .section-header--left .geo-answer-text {
+ max-width: none;
+ }
/* ── Section vertical rhythm ── */
.page-pksh .pksh-section,
@@ -1718,6 +1721,34 @@
.page-pksh .trust-authority__grid { grid-template-columns: 1fr; gap: 16px; }
}
+ .page-pksh .trust-program-link {
+ margin-top: 24px;
+ padding: 16px 24px;
+ background: var(--color-bg);
+ border-radius: var(--pksh-radius-card, 24px);
+ box-shadow: var(--shadow-sm);
+ text-align: center;
+ transition: box-shadow var(--transition-base, 300ms ease-out);
+ }
+ .page-pksh .trust-program-link:hover {
+ box-shadow: var(--shadow-md);
+ }
+ .page-pksh .trust-program-link a {
+ color: var(--color-primary);
+ font-weight: 600;
+ font-size: 0.9375rem;
+ text-decoration: underline;
+ text-underline-offset: 3px;
+ }
+ .page-pksh .trust-program-link a:hover {
+ color: var(--color-primary-dark);
+ text-decoration: none;
+ }
+ .page-pksh .trust-program-link a::after {
+ content: ' ↗';
+ font-size: 0.8125rem;
+ }
+
/* ── 3o. EXTERNAL REVIEWS ── */
.page-pksh .external-reviews {
max-width: 1000px;
@@ -2076,12 +2107,14 @@
.page-pksh .sticky-cta-desktop__badge {
display: inline-block;
- padding: 4px 14px;
- background: var(--color-primary);
- color: var(--color-text-on-dark);
+ padding: 0;
+ background: transparent;
+ color: var(--color-primary);
font-size: 0.8125rem;
font-weight: 700;
- border-radius: var(--radius-pill, 100px);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ border-radius: 0;
}
.page-pksh .sticky-cta-desktop__text {
@@ -2297,3 +2330,184 @@
@media (prefers-reduced-motion: no-preference) {
html { scroll-behavior: smooth; }
}
+
+/* ============================================================
+ GEO / AI-оптимизация — answer-блоки, автор, даты
+ Matching the page's blue/navy bento-card design system
+ ============================================================ */
+
+/* Определение для AI (hero) — стиль как bento-p hero, с акцентом */
+.page-pksh .ds-hero__definition {
+ font-size: clamp(1.0625rem, 1.3vw, 1.125rem);
+ line-height: 1.7;
+ color: rgba(255,255,255,0.72);
+ margin-top: 16px;
+ margin-bottom: 12px;
+ max-width: 65ch;
+ text-wrap: pretty;
+ font-weight: 450;
+}
+
+/* Answer-блоки для AI-цитирования — карточка в стиле bento-секций */
+.page-pksh .geo-answer-block {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ padding: clamp(20px, 2.5vw, 24px) clamp(20px, 2.5vw, 24px);
+ background: var(--color-bg);
+ border-radius: var(--radius-sm, 12px);
+ border-left: 4px solid var(--color-primary);
+ box-shadow: var(--shadow-sm);
+}
+
+/* В тёмной секции результатов — прозрачный фон, тёплый акцент */
+.page-pksh .bento-card--highlight-results .geo-answer-block {
+ background: color-mix(in oklab, var(--color-bg-dark), white 6%);
+ border-left-color: var(--color-rating);
+ box-shadow: none;
+ padding: 20px 24px;
+ border-radius: var(--radius-sm, 12px);
+ text-align: center;
+}
+.page-pksh .bento-card--highlight-results .geo-answer-text {
+ max-width: none;
+}
+.page-pksh .bento-card--highlight-results .geo-answer-text {
+ color: var(--color-slate-300);
+ margin: 0;
+}
+.page-pksh .bento-card--highlight-results .geo-answer-heading {
+ font-size: 0.8125rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--color-rating);
+}
+
+/* Answer-блок с изображением справа (только десктоп) */
+.page-pksh .geo-answer-block--with-img {
+ display: flex;
+ gap: 28px;
+ align-items: stretch;
+ flex-direction: row-reverse;
+}
+
+.page-pksh .geo-answer-block__text {
+ flex: 1;
+ min-width: 0;
+ align-self: center;
+}
+
+.page-pksh .geo-answer-block__img-wrap {
+ flex-shrink: 0;
+ width: auto;
+ aspect-ratio: 1 / 1;
+ height: 100%;
+ max-height: 220px;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 3px solid var(--color-primary);
+ box-shadow: var(--shadow-sm);
+}
+
+.page-pksh .geo-answer-block__img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+@media (max-width: 1023px) {
+ .page-pksh .geo-answer-block--with-img {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ .page-pksh .geo-answer-block__img-wrap {
+ display: none;
+ }
+}
+
+.page-pksh .geo-answer-text {
+ font-size: clamp(1rem, 1.2vw, 1.0625rem);
+ line-height: 1.7;
+ color: var(--color-text-muted);
+ margin: 0;
+ max-width: none;
+ text-wrap: pretty;
+}
+
+/* Question-based H3 headings — иерархия: H1 hero → H2 bento → H3 bento */
+.page-pksh .bento-h3 {
+ font-size: clamp(1.125rem, 1.5vw, 1.375rem);
+ font-weight: 700;
+ line-height: 1.35;
+ color: var(--color-text);
+ margin-top: 24px;
+ margin-bottom: 12px;
+ letter-spacing: -0.01em;
+}
+
+/* Question-heading with accent mark */
+.page-pksh .geo-answer-heading {
+ font-size: clamp(1.0625rem, 1.3vw, 1.25rem);
+ font-weight: 700;
+ line-height: 1.4;
+ color: var(--color-primary);
+ margin-top: 20px;
+ margin-bottom: 12px;
+}
+
+/* Автор и даты (E-E-A-T) — подвал страницы, компактный */
+.page-pksh .pksh-section--author {
+ padding: 12px clamp(16px, 3vw, 32px) 32px;
+}
+
+.page-pksh .author-info {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 24px;
+ padding: 16px 0 8px;
+ font-size: 0.875rem;
+ color: var(--color-text-light);
+ border-top: 1px solid var(--color-border);
+}
+
+.page-pksh .author-info__block {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 4px;
+}
+
+.page-pksh .author-info__label {
+ font-weight: 600;
+ color: var(--color-text-muted);
+}
+
+.page-pksh .author-info__name {
+ font-weight: 600;
+ color: var(--color-text);
+}
+
+.page-pksh .author-info__title {
+ color: var(--color-text-muted);
+ font-style: italic;
+}
+
+.page-pksh .author-info__date {
+ color: var(--color-text);
+}
+
+/* Responsive */
+@media (max-width: 639px) {
+ .page-pksh .ds-hero__definition {
+ font-size: 1rem;
+ }
+
+ .page-pksh .geo-answer-block {
+ padding: 16px;
+ }
+
+ .page-pksh .author-info {
+ flex-direction: column;
+ gap: 6px;
+ }
+}
diff --git a/wp-content/themes/dekart/assets/css/program-pksh.css b/wp-content/themes/dekart/assets/css/program-pksh.css
new file mode 100644
index 0000000..b1b70c5
--- /dev/null
+++ b/wp-content/themes/dekart/assets/css/program-pksh.css
@@ -0,0 +1,288 @@
+/* ═══════════════════════════════════════════════════════════
+ program-pksh.css — служебная страница программы
+ ═══════════════════════════════════════════════════════════ */
+
+/* ── Container ── */
+.page-program-pksh .prog-container {
+ max-width: 880px;
+ margin: 0 auto;
+ padding: 0 24px;
+}
+
+/* ── Section rhythm ── */
+.page-program-pksh .prog-section {
+ padding: 48px 0;
+}
+.page-program-pksh .prog-section--muted {
+ background: var(--color-bg-alt, #F1F5F9);
+}
+.page-program-pksh .prog-section--accent {
+ background: var(--color-bg-dark, #0F172A);
+}
+.page-program-pksh .prog-section--disclaimer {
+ background: rgba(245, 158, 11, 0.06);
+ border-top: 3px solid var(--color-rating, #F59E0B);
+ border-bottom: 3px solid var(--color-rating, #F59E0B);
+}
+
+/* ── Intro ── */
+.page-program-pksh .prog-intro {
+ padding: 48px 0 24px;
+}
+
+.page-program-pksh .prog-label {
+ display: inline-block;
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--color-primary, #2563EB);
+ margin-bottom: 12px;
+}
+
+.page-program-pksh .prog-title {
+ font-size: clamp(1.5rem, 2.5vw, 2.125rem);
+ font-weight: 800;
+ line-height: 1.2;
+ color: var(--color-text, #0F172A);
+ margin: 0 0 20px;
+ letter-spacing: -0.02em;
+}
+
+.page-program-pksh .prog-desc {
+ font-size: 1rem;
+ line-height: 1.7;
+ color: var(--color-text-muted, #475569);
+ margin: 0;
+ max-width: 75ch;
+}
+
+/* ── Section title ── */
+.page-program-pksh .prog-section-title {
+ font-size: clamp(1.25rem, 1.8vw, 1.5rem);
+ font-weight: 700;
+ line-height: 1.3;
+ color: var(--color-text, #0F172A);
+ margin: 0 0 20px;
+ padding-bottom: 12px;
+ border-bottom: 2px solid var(--color-border, #E2E8F0);
+}
+.page-program-pksh .prog-section-title--light {
+ color: var(--color-text-on-dark, #FFFFFF);
+ border-bottom-color: rgba(255,255,255,0.15);
+}
+
+/* ── Body text ── */
+.page-program-pksh .prog-text {
+ font-size: 1rem;
+ line-height: 1.7;
+ color: var(--color-text-muted, #475569);
+ margin: 0 0 16px;
+ max-width: 75ch;
+}
+.page-program-pksh .prog-text--light {
+ color: rgba(255,255,255,0.75);
+}
+
+/* ── Program content (WYSIWYG) ── */
+.page-program-pksh .prog-content {
+ font-size: 0.938rem;
+ line-height: 1.75;
+ color: var(--color-text, #0F172A);
+ max-width: 85ch;
+}
+
+.page-program-pksh .prog-content h2,
+.page-program-pksh .prog-content h3,
+.page-program-pksh .prog-content h4 {
+ font-weight: 700;
+ color: var(--color-text, #0F172A);
+ margin-top: 32px;
+ margin-bottom: 12px;
+ line-height: 1.3;
+}
+.page-program-pksh .prog-content h2 {
+ font-size: 1.25rem;
+ padding-bottom: 8px;
+ border-bottom: 1px solid var(--color-border, #E2E8F0);
+}
+.page-program-pksh .prog-content h3 {
+ font-size: 1.0625rem;
+}
+.page-program-pksh .prog-content h4 {
+ font-size: 1rem;
+}
+
+.page-program-pksh .prog-content p {
+ margin: 0 0 12px;
+ line-height: 1.75;
+}
+
+.page-program-pksh .prog-content ul,
+.page-program-pksh .prog-content ol {
+ margin: 0 0 12px;
+ padding-left: 24px;
+}
+.page-program-pksh .prog-content li {
+ margin-bottom: 4px;
+ line-height: 1.65;
+}
+
+.page-program-pksh .prog-content table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 16px 0;
+ font-size: 0.875rem;
+}
+.page-program-pksh .prog-content th,
+.page-program-pksh .prog-content td {
+ border: 1px solid var(--color-border, #E2E8F0);
+ padding: 8px 12px;
+ text-align: left;
+ vertical-align: top;
+}
+.page-program-pksh .prog-content th {
+ background: var(--color-bg-alt, #F1F5F9);
+ font-weight: 600;
+}
+
+/* ── Office info block ── */
+.page-program-pksh .prog-office__details {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 24px 40px;
+ margin-top: 12px;
+}
+.page-program-pksh .prog-office__item {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+.page-program-pksh .prog-office__label {
+ font-size: 0.8125rem;
+ font-weight: 600;
+ color: var(--color-text-muted, #475569);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+.page-program-pksh .prog-office__value {
+ font-size: 1.0625rem;
+ color: var(--color-text, #0F172A);
+}
+
+/* ── CTA block ── */
+.page-program-pksh .prog-cta {
+ text-align: center;
+ padding: 8px 0;
+}
+.page-program-pksh .prog-cta .prog-text {
+ margin: 0 auto 24px;
+ max-width: 65ch;
+}
+.page-program-pksh .prog-cta__btn {
+ display: inline-block;
+ padding: 16px 40px;
+ font-size: 1.0625rem;
+ font-weight: 700;
+ color: var(--color-text-on-dark, #FFFFFF);
+ background: var(--pksh-gradient-btn, linear-gradient(135deg, #2563EB, #1D4ED8));
+ border-radius: var(--radius-pill, 100px);
+ text-decoration: none;
+ transition: transform 200ms ease-out, box-shadow 200ms ease-out;
+}
+.page-program-pksh .prog-cta__btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 28px rgba(37, 99, 235, 0.35);
+}
+.page-program-pksh .prog-cta__btn:focus-visible {
+ outline: 3px solid var(--color-primary, #2563EB);
+ outline-offset: 3px;
+}
+
+/* ── E-E-A-T links ── */
+.page-program-pksh .prog-eeat {
+ padding: 8px 0;
+}
+.page-program-pksh .prog-eeat a {
+ color: var(--color-primary, #2563EB);
+ font-weight: 600;
+ text-decoration: underline;
+}
+
+/* ── Disclaimer ── */
+.page-program-pksh .prog-disclaimer {
+ padding: 8px 0;
+}
+.page-program-pksh .prog-disclaimer__text {
+ font-size: 0.9375rem;
+ line-height: 1.65;
+ color: var(--color-text, #0F172A);
+ margin: 0 0 8px;
+}
+.page-program-pksh .prog-disclaimer__note {
+ font-size: 0.875rem;
+ color: var(--color-text-muted, #475569);
+ margin: 0;
+ font-style: italic;
+}
+
+/* ── Responsive ── */
+@media (max-width: 639px) {
+ .page-program-pksh .prog-section {
+ padding: 32px 0;
+ }
+ .page-program-pksh .prog-intro {
+ padding: 32px 0 16px;
+ }
+ .page-program-pksh .prog-office__details {
+ flex-direction: column;
+ gap: 12px;
+ }
+ .page-program-pksh .prog-content table {
+ font-size: 0.8125rem;
+ }
+ .page-program-pksh .prog-content th,
+ .page-program-pksh .prog-content td {
+ padding: 6px 8px;
+ }
+}
+
+/* ── Author block (E-E-A-T) ── */
+.page-program-pksh .prog-author {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 24px;
+ padding: 16px 0;
+ font-size: 0.875rem;
+ color: var(--color-text-light, #64748B);
+ border-top: 1px solid var(--color-border, #E2E8F0);
+ border-bottom: 1px solid var(--color-border, #E2E8F0);
+ margin-bottom: 16px;
+}
+.page-program-pksh .prog-author__block {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 4px;
+}
+.page-program-pksh .prog-author__label {
+ font-weight: 600;
+ color: var(--color-text-muted, #475569);
+}
+.page-program-pksh .prog-author__name {
+ font-weight: 600;
+ color: var(--color-text, #0F172A);
+}
+.page-program-pksh .prog-author__title {
+ color: var(--color-text-muted, #475569);
+ font-style: italic;
+}
+.page-program-pksh .prog-author__date {
+ color: var(--color-text, #0F172A);
+}
+
+@media (max-width: 639px) {
+ .page-program-pksh .prog-author {
+ flex-direction: column;
+ gap: 6px;
+ }
+}
diff --git a/wp-content/themes/dekart/functions.php b/wp-content/themes/dekart/functions.php
index c3b72f5..74c3a24 100644
--- a/wp-content/themes/dekart/functions.php
+++ b/wp-content/themes/dekart/functions.php
@@ -4,6 +4,18 @@ require_once get_template_directory() . '/inc/template-tags.php';
// Pksh_Data — централизованная загрузка данных для page-podgotovka-k-shkole.php
require_once get_template_directory() . '/inc/class-pksh-data.php';
+/**
+ * Замена названия сайта из Настроек филиала.
+ * blogname (Внешний вид → Настроить → Свойства сайта) берётся из branch_name.
+ */
+add_filter( 'pre_option_blogname', function( $default ) {
+ $branch = get_option( 'branch_name' );
+ if ( ! empty( $branch ) ) {
+ return $branch;
+ }
+ return $default;
+} );
+
/* --- Удаляем мета из YOASTSEO START--- */
add_filter( 'wpseo_json_ld_output', '__return_false' );
/* --- Удаляем мета из YOASTSEO END --- */
@@ -558,10 +570,18 @@ function add_carbon() {
Field::make( 'text', 'banner_subtitle_footnote_link', 'Ссылка для звёздочки (*) в подзаголовке' )
->set_width( 50 )
->set_help_text( 'URL для иконки * после текста подзаголовка. Если не указано — * неактивна.' ),
- Field::make( 'text', 'banner_scarcity', 'Текст дефицита (срочность)' )
+ Field::make( 'text', 'banner_scarcity', 'Текст дефицита (срочность)' )
->set_width( 50 )
->set_default_value( '🔥 Осталось 2 места из 8 в группе 5–6 лет. Стартуем 1 июня ' )
->set_help_text( 'Можно использовать HTML-теги: <strong>, <br>. Текст о срочности/дефиците мест под кнопкой.' ),
+ Field::make( 'textarea', 'pksh_hero_definition', 'Определение для AI (первые 60 слов)' )
+ ->set_rows( 3 )
+ ->set_help_text(
+ '📖 Для AI-поиска (GEO). Текст, который AI использует для ответа на «Что такое подготовка к школе в [городе]?». '
+ . 'Рекомендуется 40–60 слов , начинается с «Подготовка к школе в [городе] — это…». '
+ . 'Выводится на странице после H1, перед подзаголовком. Если не заполнено — блок скрыт.'
+ )
+ ->set_default_value( 'Подготовка к школе в Щёлково — это комплексные занятия для детей 5–7 лет, которые помогают освоить чтение, письмо, счёт и развить логическое мышление. Программа построена на игровой методике: ребёнок учится без стресса, в комфортном темпе. Группы до 10 человек, индивидуальный подход к каждому.' ),
))
->add_tab('📊 Соц.доказательство', array(
Field::make( 'html', 'pksh_social_tab_desc', '' )
@@ -576,9 +596,20 @@ function add_carbon() {
Field::make( 'text', 'counter_label', 'Подпись' )
->set_width( 70 )
->set_help_text( 'Например: выпускников в топ-школах' ),
- )),
- ))
- ->add_tab('⭐ Отзывы', array(
+ )),
+ Field::make( 'html', 'pksh_social_author_header', '' )
+ ->set_html( '👤 Автор контента (E-E-A-T для SEO) Эти поля влияют на E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) — фактор ранжирования Google. Указываются в микроразметке Schema.org и отображаются в блоке автора внизу страницы. Заполните, чтобы повысить доверие поисковых систем к контенту.
' ),
+ Field::make( 'text', 'pksh_author_name', 'Имя автора контента' )
+ ->set_width( 50 )
+ ->set_default_value( 'Детский центр «Декарт»' )
+ ->set_help_text(
+ 'Для SEO: Имя автора для Schema.org Person / author. '
+ . 'Рекомендуется: фактическое название организации или ФИО руководителя/педагога. '
+ . 'Влияет на E-E-A-T — Google проверяет автора контента. '
+ . 'По умолчанию: название из поля «Название филиала» в Настройках темы.'
+ ),
+ ))
+ ->add_tab('⭐ Отзывы', array(
Field::make( 'html', 'pksh_reviews_tab_desc', '' )
->set_html( 'Секция: .pksh-reviews . Отзывы родителей на странице подготовки к школе.
' ),
Field::make( 'complex', 'pksh_reviews_list', 'Отзывы' )
@@ -803,6 +834,60 @@ function add_carbon() {
)),
))
// ====================================================
+ // Вкладка: GEO / AI-поиск (self-contained answer блоки для цитирования)
+ // ====================================================
+ ->add_tab('🤖 GEO (AI-поиск)', array(
+ Field::make( 'html', 'pksh_geo_tab_desc', '' )
+ ->set_html( 'Поля для Generative Engine Optimization — контент, оптимизированный для цитирования AI-поиском (Google AI Overviews, ChatGPT, Perplexity). Каждый блок должен быть самодостаточным (134–167 слов) и содержать конкретные данные.
' ),
+ Field::make( 'html', 'pksh_geo_questions_title', '' )
+ ->set_html( '❓ Вопросы-заголовки для AI ' ),
+ Field::make( 'html', 'pksh_geo_questions_desc', '' )
+ ->set_html( 'Эти заголовки (H2/H3) отвечают на типичные поисковые запросы. AI видит их как прямые ответы. Если поле пусто — секция не выводится.
' ),
+ Field::make( 'text', 'pksh_geo_q1', 'Вопрос-заголовок #1' )
+ ->set_width( 100 )
+ ->set_help_text( 'Например: «Сколько стоит подготовка к школе в Щёлково?»' )
+ ->set_default_value( 'Сколько стоит подготовка к школе в Щёлково?' ),
+ Field::make( 'text', 'pksh_geo_q2', 'Вопрос-заголовок #2' )
+ ->set_width( 100 )
+ ->set_help_text( 'Например: «Что должен знать ребёнок перед школой?»' )
+ ->set_default_value( 'Что должен знать ребёнок перед школой?' ),
+ Field::make( 'text', 'pksh_geo_q3', 'Вопрос-заголовок #3' )
+ ->set_width( 100 )
+ ->set_help_text( 'Например: «Как выбрать центр подготовки к школе?»' )
+ ->set_default_value( 'Как выбрать центр подготовки к школе?' ),
+ Field::make( 'html', 'pksh_geo_answers_title', '' )
+ ->set_html( '📝 Answer-блоки для AI-цитирования ' ),
+ Field::make( 'html', 'pksh_geo_answers_desc', '' )
+ ->set_html( 'Каждый блок — 134–167 слов , самодостаточный, с конкретными данными. AI может процитировать его целиком. Рекомендуется: факты, цифры, сроки, цены. Если поле пусто — блок не выводится.
' ),
+ Field::make( 'textarea', 'pksh_geo_answer_programs', 'Answer-блок: Программы и цены' )
+ ->set_rows( 8 )
+ ->set_help_text(
+ 'Выводится в секции Программы перед карточками. '
+ . 'Рекомендуется 134–167 слов. Опишите программы, цены, разницу между базовой и расширенной.'
+ )
+ ->set_default_value(
+ 'В детском центре «Декарт» в Щёлково действуют две программы подготовки к школе. Базовая программа «Уверенный старт» включает 2 занятия в неделю по 30–40 минут — стоимость абонемента 7 500 ₽ в месяц. Расширенная программа «Результат за 3 месяца» — 3 занятия в неделю, 10 500 ₽ в месяц. Обе программы ведут действующие педагоги начальных классов со стажем от 12 лет. Группы до 10 человек — каждому ребёнку уделяется внимание. Первое занятие — бесплатно, без договора и оплаты. В стоимость включены все учебные материалы и пособия. Занятия проходят очно по адресу: Талсинская ул., 24. Результат через 3 недели — дети начинают читать по слогам.'
+ ),
+ Field::make( 'textarea', 'pksh_geo_answer_results', 'Answer-блок: Результаты и статистика' )
+ ->set_rows( 8 )
+ ->set_help_text(
+ 'Выводится в секции Результаты . '
+ . 'Рекомендуется 134–167 слов. Конкретные цифры, проценты, сроки.'
+ )
+ ->set_default_value(
+ '95% выпускников центра «Декарт» поступают в школы и лицеи Щёлкова без стресса — они готовы к учебной нагрузке и требованиям учителей. Первые результаты заметны уже через 3 недели: нечитавшие дети складывают первый осмысленный слог. 87% родителей отмечают рост самостоятельности ребёнка после месяца занятий. Выпускники центра адаптируются в первом классе в 2,5 раза быстрее сверстников — они не боятся отвечать у доски, поднимать руку и работать в коллективе. За 12 лет работы центром подготовлено более 500 детей. Программа построена так, что каждый ребёнок осваивает чтение, письмо, счёт и логику без перегрузки — занятия длятся 30–40 минут, каждые 7 минут смена активности.'
+ ),
+ Field::make( 'textarea', 'pksh_geo_answer_howitworks', 'Answer-блок: Как проходят занятия' )
+ ->set_rows( 8 )
+ ->set_help_text(
+ 'Выводится в секции Как проходят занятия . '
+ . 'Рекомендуется 134–167 слов. Опишите методику, длительность, структуру.'
+ )
+ ->set_default_value(
+ 'Каждое занятие длится 30–40 минут и построено так, чтобы ребёнок не уставал. Активность меняется каждые 7 минут — это оптимальный интервал удержания внимания для детей 5–7 лет. Урок начинается с короткой истории или загадки, которая вводит в тему и включает интерес. Затем следует игровое задание: чтение через игру, счёт через механику соревнования, логика через головоломки. После каждого блока — активная перемена с нейрогимнастикой. Завершается занятие рефлексией: ребёнок сам рассказывает, чему научился. Все педагоги имеют высшее педагогическое образование и опыт работы с дошкольниками от 5 лет. Методика исключает скучные прописи и многочасовое сидение за партой — дети воспринимают занятия как игру и с удовольствием возвращаются.'
+ ),
+ ))
+ // ====================================================
// Вкладка: Фотогалерея
// ====================================================
->add_tab('📸 Фотогалерея', array(
@@ -821,6 +906,50 @@ function add_carbon() {
)),
));
+ // ====================================================
+ // Контейнер: Программа Подготовки к школе (служебная страница)
+ // ====================================================
+ Container::make('post_meta', '📄 Программа «Подготовка к школе»')
+ ->where( 'post_template', '=', 'page-program-pksh.php' )
+ ->add_fields( array(
+ Field::make( 'html', 'pksh_prog_header', '' )
+ ->set_html( 'Официальный документ — образовательная программа подготовки к школе. Текст программы менять нельзя — это утверждённый документ. Для обновления скопируйте новый текст целиком.
' ),
+ Field::make( 'rich_text', 'pksh_prog_content', 'Текст программы' )
+ ->set_help_text(
+ 'ВАЖНО: Это официально утверждённый документ. Не меняйте содержание. '
+ . 'Для обновления — замените текст целиком на новую версию. '
+ . 'Рекомендуется вставлять из Google Docs / Word без форматирования, '
+ . 'затем применить заголовки H2-H3 и списки вручную.'
+ ),
+ Field::make( 'html', 'pksh_prog_legal_header', '' )
+ ->set_html( '⚖️ Юридическая информация ' ),
+ Field::make( 'date', 'pksh_prog_doc_date', 'Дата утверждения / обновления документа' )
+ ->set_width( 30 )
+ ->set_storage_format( 'Y-m-d' )
+ ->set_input_format( 'Y-m-d', 'Y-m-d' )
+ ->set_help_text( 'Дата последнего утверждения или обновления программы (YYYY-MM-DD). Отображается в дисклеймере.' ),
+ Field::make( 'textarea', 'pksh_prog_disclaimer', 'Текст дисклеймера' )
+ ->set_width( 100 )
+ ->set_default_value(
+ 'Электронная версия программы предназначена для ознакомления. При переносе в электронный вид '
+ . 'возможны технические неточности (опечатки, ошибки форматирования). '
+ . 'Юридически значимым считается документ, предоставленный в офисе организации. '
+ . 'Дата последнего обновления электронной версии:'
+ )
+ ->set_rows( 4 )
+ ->set_help_text( 'Текст, который выводится в блоке дисклеймера. Можно редактировать независимо от текста программы.' ),
+ Field::make( 'html', 'pksh_prog_service_header', '' )
+ ->set_html( '🔗 Ссылка на услугу ' ),
+ Field::make( 'text', 'pksh_prog_service_url', 'URL услуги' )
+ ->set_width( 50 )
+ ->set_default_value( '/podgotovka-k-shkole/' )
+ ->set_help_text( 'Ссылка на страницу услуги, которая реализует данную программу.' ),
+ Field::make( 'text', 'pksh_prog_service_label', 'Текст ссылки' )
+ ->set_width( 50 )
+ ->set_default_value( 'Записаться на подготовку к школе' )
+ ->set_help_text( 'Текст кнопки/ссылки для перехода на услугу.' ),
+ ));
+
// ====================================================
// Уникальное имя контейнера Продлёнки
// ====================================================
@@ -1983,6 +2112,7 @@ function dekart_branch_register_settings() {
register_setting( 'dekart_branch_group', 'branch_ext_google', array( 'sanitize_callback' => 'esc_url_raw' ) );
register_setting( 'dekart_branch_group', 'branch_ext_2gis', array( 'sanitize_callback' => 'esc_url_raw' ) );
register_setting( 'dekart_branch_group', 'branch_ext_otzovik', array( 'sanitize_callback' => 'esc_url_raw' ) );
+ register_setting( 'dekart_branch_group', 'branch_ext_vk', array( 'sanitize_callback' => 'esc_url_raw' ) );
register_setting( 'dekart_branch_group', 'branch_ext_other_name', array( 'sanitize_callback' => 'sanitize_text_field' ) );
register_setting( 'dekart_branch_group', 'branch_ext_other_url', array( 'sanitize_callback' => 'esc_url_raw' ) );
register_setting( 'dekart_branch_group', 'branch_og_site_name', array( 'sanitize_callback' => 'sanitize_text_field' ) );
@@ -2544,6 +2674,24 @@ function dekart_ajax_flush_cache() {
$summary = array( 'type' => 'fail', 'text' => "Выполнено с {$errors} ошибкой(ами). Проверьте лог выше." );
}
+/**
+ * Noindex для служебной страницы программы.
+ * Юридический документ не должен индексироваться,
+ * чтобы не размывать SEO-вес коммерческих страниц.
+ */
+/**
+ * Noindex для служебной страницы программы.
+ */
+add_filter( 'wpseo_robots', function( $robots ) {
+ $post_id = get_the_ID();
+ if ( $post_id && 'program-pksh' === get_post_field( 'post_name', $post_id ) ) {
+ return 'noindex, follow';
+ }
+ return $robots;
+}, 999 );
+
+
+
wp_send_json_success( array(
'log' => $log,
'summary' => $summary,
diff --git a/wp-content/themes/dekart/inc/class-pksh-data.php b/wp-content/themes/dekart/inc/class-pksh-data.php
index f77a62c..70a9de6 100644
--- a/wp-content/themes/dekart/inc/class-pksh-data.php
+++ b/wp-content/themes/dekart/inc/class-pksh-data.php
@@ -48,12 +48,9 @@ class Pksh_Data {
}
// ============================================================
- // BRANCH / OPTION DATA (get_option → theme_option → post_meta)
+ // BRANCH / OPTION DATA (get_option -> theme_option -> post_meta)
// ============================================================
- /**
- * Адрес филиала
- */
public function get_address(): string {
$cache_key = 'dekart_branch_address';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -70,9 +67,6 @@ class Pksh_Data {
return $this->branch['address'];
}
- /**
- * Координаты филиала (для карты)
- */
public function get_coords(): string {
$cache_key = 'dekart_branch_coords';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -88,9 +82,6 @@ class Pksh_Data {
return $this->branch['coords'];
}
- /**
- * Телефон филиала
- */
public function get_phone(): string {
$cache_key = 'dekart_branch_phone';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -107,9 +98,6 @@ class Pksh_Data {
return $this->branch['phone'];
}
- /**
- * Телефон, очищенный от не-цифровых символов
- */
public function get_phone_clean(): string {
if ( ! array_key_exists( 'phone_clean', $this->branch ) ) {
$this->branch['phone_clean'] = preg_replace( '/[^0-9+]/', '', $this->get_phone() );
@@ -117,9 +105,6 @@ class Pksh_Data {
return $this->branch['phone_clean'];
}
- /**
- * Email филиала
- */
public function get_email(): string {
$cache_key = 'dekart_branch_email';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -136,9 +121,6 @@ class Pksh_Data {
return $this->branch['email'];
}
- /**
- * Название филиала / организации
- */
public function get_name(): string {
$cache_key = 'dekart_branch_name';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -154,9 +136,6 @@ class Pksh_Data {
return $this->branch['name'];
}
- /**
- * Город с предлогом (например "в Щёлково")
- */
public function get_city_prep(): string {
$cache_key = 'dekart_branch_city_prep';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -170,20 +149,12 @@ class Pksh_Data {
return $this->branch['city_prep'];
}
- /**
- * Город без предлога (например "Щёлково")
- */
- /**
- * Город в родительном падеже (школы Щёлкова)
- * Берёт из branch_city_genitive, fallback: city_clean с -о→-а
- */
public function get_city_genitive(): string {
$genitive = get_option( 'branch_city_genitive', '' );
if ( ! empty( $genitive ) ) {
return $genitive;
}
$clean = $this->get_city_clean();
- // Для русских городов на -ово/-ево/-ино: замена -о → -а
if ( str_ends_with( $clean, 'ово' ) || str_ends_with( $clean, 'ево' ) || str_ends_with( $clean, 'ино' ) || str_ends_with( $clean, 'ыно' ) ) {
return mb_substr( $clean, 0, -1 ) . 'а';
}
@@ -197,9 +168,6 @@ class Pksh_Data {
return $this->branch['city_clean'];
}
- /**
- * Количество отзывов на внешних площадках
- */
public function get_ext_review_count(): string {
$cache_key = 'dekart_branch_ext_review_count';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -213,9 +181,6 @@ class Pksh_Data {
return $this->branch['ext_review_count'];
}
- /**
- * Социальные сети (из branch_social option или CF)
- */
public function get_social_links(): array {
$cache_key = 'dekart_branch_social';
$cached = wp_cache_get( $cache_key, 'dekart_branch' );
@@ -228,13 +193,10 @@ class Pksh_Data {
[];
$this->branch['social'] = is_array( $raw ) ? $raw : [];
}
-wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECONDS );
- return $this->branch['social'];
+ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECONDS );
+ return $this->branch['social'];
}
- /**
- * Логистика — пешком от ТЦ (branch_logistics_walk)
- */
public function get_logistics_walk(): string {
if ( ! array_key_exists( 'logistics_walk', $this->branch ) ) {
$this->branch['logistics_walk'] = get_option( 'branch_logistics_walk', '5 минут пешком от ТЦ «Глобус»' );
@@ -242,9 +204,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->branch['logistics_walk'];
}
- /**
- * Логистика — парковка (branch_logistics_parking)
- */
public function get_logistics_parking(): string {
if ( ! array_key_exists( 'logistics_parking', $this->branch ) ) {
$this->branch['logistics_parking'] = get_option( 'branch_logistics_parking', 'Есть бесплатная парковка у входа' );
@@ -252,9 +211,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->branch['logistics_parking'];
}
- /**
- * Логистика — соседние школы/сад (branch_logistics_schools)
- */
public function get_logistics_nearby(): string {
if ( ! array_key_exists( 'logistics_nearby', $this->branch ) ) {
$this->branch['logistics_nearby'] = get_option( 'branch_logistics_schools', 'Рядом со школами №9 и №12, детским садом №15' );
@@ -262,13 +218,62 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->branch['logistics_nearby'];
}
+ // ============================================================
+ // AUTHOR / DATES (E-E-A-T / GEO)
+ // ============================================================
+
+ public function get_author_name(): string {
+ if ( ! array_key_exists( 'author_name', $this->page ) ) {
+ $this->page['author_name'] = carbon_get_post_meta( $this->post_id, 'pksh_author_name' )
+ ?: get_option( 'branch_og_site_name' )
+ ?: get_option( 'branch_name' )
+ ?: carbon_get_theme_option( 'site_name' )
+ ?: 'Детский центр «Декарт»';
+ }
+ return $this->page['author_name'];
+ }
+
+ /**
+ * Количество лет на рынке (из года основания).
+ * Автоматически: текущий год − год основания.
+ * Источник: CF schema_founded → Settings API branch_founded.
+ * Если год основания не задан — 0.
+ */
+ public function get_years_on_market(): int {
+ if ( ! array_key_exists( 'years_market', $this->page ) ) {
+ $founded = carbon_get_theme_option( 'schema_founded' );
+ if ( empty( $founded ) ) {
+ $founded = get_option( 'branch_founded' );
+ }
+ if ( ! empty( $founded ) && is_string( $founded ) && is_numeric( $founded ) ) {
+ $this->page['years_market'] = (int) date( 'Y' ) - (int) $founded;
+ } else {
+ $this->page['years_market'] = 0;
+ }
+ }
+ return $this->page['years_market'];
+ }
+
+ public function get_page_publish_date(): string {
+ if ( ! array_key_exists( 'publish_date', $this->page ) ) {
+ $post = get_post( $this->post_id );
+ $this->page['publish_date'] = $post ? get_the_date( 'c', $post ) : '';
+ }
+ return $this->page['publish_date'];
+ }
+
+ public function get_page_modified_date(): string {
+ if ( ! array_key_exists( 'modified_date', $this->page ) ) {
+ $post = get_post( $this->post_id );
+ $this->page['modified_date'] = $post ? get_the_modified_date( 'c', $post ) : '';
+ }
+ return $this->page['modified_date'];
+ }
+
// ============================================================
// HERO / BANNER
// ============================================================
- /**
- * ID изображения баннера (hero)
- */
public function get_banner_image_id(): int {
if ( ! array_key_exists( 'banner_image_id', $this->page ) ) {
$this->page['banner_image_id'] = (int) carbon_get_post_meta( $this->post_id, 'banner_image' );
@@ -276,20 +281,13 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->page['banner_image_id'];
}
- /**
- * H1 заголовок hero-секции
- */
public function get_h1(): string {
if ( ! array_key_exists( 'h1', $this->page ) ) {
- $this->page['h1'] = carbon_get_post_meta( $this->post_id, 'banner_h1' ) ?:
- 'Подготовка к школе в Щёлково';
+ $this->page['h1'] = carbon_get_post_meta( $this->post_id, 'banner_h1' ) ?: 'Подготовка к школе';
}
return $this->page['h1'];
}
- /**
- * Подзаголовок hero
- */
public function get_subtitle(): string {
if ( ! array_key_exists( 'subtitle', $this->page ) ) {
$this->page['subtitle'] = carbon_get_post_meta( $this->post_id, 'banner_subtitle' ) ?:
@@ -298,9 +296,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->page['subtitle'];
}
- /**
- * Ссылка для * в подзаголовке hero
- */
public function get_subtitle_footnote_link(): string {
if ( ! array_key_exists( 'subtitle_footnote_link', $this->page ) ) {
$this->page['subtitle_footnote_link'] = carbon_get_post_meta( $this->post_id, 'banner_subtitle_footnote_link' ) ?: '';
@@ -308,14 +303,21 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->page['subtitle_footnote_link'];
}
+ /**
+ * Определение для AI-цитирования (GEO, первые 60 слов).
+ * Заполняется в админке: вкладка "🖼️ Герой" -> поле "Определение для AI".
+ */
+ public function get_hero_definition(): string {
+ if ( ! array_key_exists( 'hero_definition', $this->page ) ) {
+ $this->page['hero_definition'] = carbon_get_post_meta( $this->post_id, 'pksh_hero_definition' ) ?: '';
+ }
+ return $this->page['hero_definition'];
+ }
+
// ============================================================
// SOCIAL COUNTERS (complex)
// ============================================================
- /**
- * Массив social counters (complex field pksh_social_counters)
- * @return array[]
- */
public function get_social_counters(): array {
if ( null === $this->social ) {
$raw = carbon_get_post_meta( $this->post_id, 'pksh_social_counters' );
@@ -324,9 +326,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->social;
}
- /**
- * Иконки для social counters (порядок соответствует полю)
- */
public static function social_icons(): array {
return array( 'icon-graduation', 'icon-calendar', 'icon-star-filled', 'icon-teacher' );
}
@@ -335,52 +334,86 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
// PROGRAMS
// ============================================================
- /**
- * Данные базовой программы
- * @return array{name: string, subname: string, schedule: string, price_sub: string, price_single: string, price_razov: string, urgency: string}
- */
public function get_base_program(): array {
if ( ! array_key_exists( 'base', $this->programs ) ) {
- $this->programs['base'] = array(
- 'name' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_name' ) ?: 'Уверенный старт',
- 'subname' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_subname' ) ?: '2 раза в неделю — комфортный темп без перегрузки',
- 'schedule' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_schedule' ) ?: 'вт, чт — 18:00–19:30',
- 'price_sub' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_price_sub' ) ?: '7 500 ₽',
- 'price_single' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_price_single' ) ?: '1 000 ₽',
- 'price_razov' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_price_razov' ) ?: '',
- 'urgency' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_urgency' ) ?: 'Осталось 2 места в группе',
- );
+ $this->programs['base'] = array(
+ 'name' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_name' ) ?: 'Уверенный старт',
+ 'subname' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_subname' ) ?: '2 раза в неделю — комфортный темп без перегрузки',
+ 'schedule' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_schedule' ) ?: 'вт, чт — 18:00–19:30',
+ 'price_sub' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_price_sub' ) ?: '7 500 ₽',
+ 'price_single' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_price_single' ) ?: '1 000 ₽',
+ 'price_razov' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_price_razov' ) ?: '',
+ 'urgency' => carbon_get_post_meta( $this->post_id, 'pksh_prog_base_urgency' ) ?: 'Осталось 2 места в группе',
+ );
}
return $this->programs['base'];
}
- /**
- * Данные премиум-программы
- * @return array{name: string, subname: string, schedule: string, price_sub: string, price_single: string, price_razov: string, urgency: string}
- */
public function get_premium_program(): array {
if ( ! array_key_exists( 'prem', $this->programs ) ) {
- $this->programs['prem'] = array(
- 'name' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_name' ) ?: 'Результат за 3 месяца',
- 'subname' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_subname' ) ?: '3 раза в неделю — быстрый прогресс',
- 'schedule' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_schedule' ) ?: 'пн, ср, пт — 16:30–18:00',
- 'price_sub' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_price_sub' ) ?: '10 500 ₽',
- 'price_single' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_price_single' ) ?: '1 500 ₽',
- 'price_razov' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_price_razov' ) ?: '',
- 'urgency' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_urgency' ) ?: 'Осталось 3 места в группе',
- );
+ $this->programs['prem'] = array(
+ 'name' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_name' ) ?: 'Результат за 3 месяца',
+ 'subname' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_subname' ) ?: '3 раза в неделю — быстрый прогресс',
+ 'schedule' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_schedule' ) ?: 'пн, ср, пт — 16:30–18:00',
+ 'price_sub' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_price_sub' ) ?: '10 500 ₽',
+ 'price_single' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_price_single' ) ?: '1 500 ₽',
+ 'price_razov' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_price_razov' ) ?: '',
+ 'urgency' => carbon_get_post_meta( $this->post_id, 'pksh_prog_prem_urgency' ) ?: 'Осталось 3 места в группе',
+ );
}
return $this->programs['prem'];
}
+ // ============================================================
+ // GEO ANSWER BLOCKS (self-contained, 134-167 слов)
+ // ============================================================
+
+ public function get_geo_answer_programs(): string {
+ if ( ! array_key_exists( 'geo_answer_programs', $this->page ) ) {
+ $this->page['geo_answer_programs'] = carbon_get_post_meta( $this->post_id, 'pksh_geo_answer_programs' ) ?: '';
+ }
+ return $this->page['geo_answer_programs'];
+ }
+
+ public function get_geo_answer_results(): string {
+ if ( ! array_key_exists( 'geo_answer_results', $this->page ) ) {
+ $this->page['geo_answer_results'] = carbon_get_post_meta( $this->post_id, 'pksh_geo_answer_results' ) ?: '';
+ }
+ return $this->page['geo_answer_results'];
+ }
+
+ public function get_geo_answer_howitworks(): string {
+ if ( ! array_key_exists( 'geo_answer_howitworks', $this->page ) ) {
+ $this->page['geo_answer_howitworks'] = carbon_get_post_meta( $this->post_id, 'pksh_geo_answer_howitworks' ) ?: '';
+ }
+ return $this->page['geo_answer_howitworks'];
+ }
+
+ public function get_geo_question_1(): string {
+ if ( ! array_key_exists( 'geo_q1', $this->page ) ) {
+ $this->page['geo_q1'] = carbon_get_post_meta( $this->post_id, 'pksh_geo_q1' ) ?: '';
+ }
+ return $this->page['geo_q1'];
+ }
+
+ public function get_geo_question_2(): string {
+ if ( ! array_key_exists( 'geo_q2', $this->page ) ) {
+ $this->page['geo_q2'] = carbon_get_post_meta( $this->post_id, 'pksh_geo_q2' ) ?: '';
+ }
+ return $this->page['geo_q2'];
+ }
+
+ public function get_geo_question_3(): string {
+ if ( ! array_key_exists( 'geo_q3', $this->page ) ) {
+ $this->page['geo_q3'] = carbon_get_post_meta( $this->post_id, 'pksh_geo_q3' ) ?: '';
+ }
+ return $this->page['geo_q3'];
+ }
+
// ============================================================
// COMPARE TABLE (complex)
// ============================================================
- /**
- * Строки таблицы сравнения
- * @return array[]
- */
public function get_compare_rows(): array {
if ( null === $this->compare ) {
$raw = carbon_get_post_meta( $this->post_id, 'pksh_compare_rows' );
@@ -389,9 +422,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->compare;
}
- /**
- * Сноска под таблицей сравнения
- */
public function get_compare_footnote(): string {
if ( ! array_key_exists( 'footnote', $this->page ) ) {
$this->page['footnote'] = carbon_get_post_meta( $this->post_id, 'pksh_compare_footnote' ) ?: '';
@@ -403,10 +433,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
// TEACHERS (complex)
// ============================================================
- /**
- * Массив преподавателей (complex field pksh_teachers)
- * @return array[]
- */
public function get_teachers(): array {
if ( null === $this->teachers ) {
$raw = carbon_get_post_meta( $this->post_id, 'pksh_teachers' );
@@ -419,10 +445,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
// REVIEWS (complex)
// ============================================================
- /**
- * Массив отзывов (complex field pksh_reviews_list)
- * @return array[]
- */
public function get_reviews(): array {
if ( null === $this->reviews ) {
$raw = carbon_get_post_meta( $this->post_id, 'pksh_reviews_list' );
@@ -435,10 +457,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
// FAQ (complex)
// ============================================================
- /**
- * Массив FAQ (complex field pksh_faq_items)
- * @return array[]
- */
public function get_faq_items(): array {
if ( null === $this->faq ) {
$raw = carbon_get_post_meta( $this->post_id, 'pksh_faq_items' );
@@ -451,10 +469,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
// GALLERY (complex)
// ============================================================
- /**
- * Массив фотографий (complex field pksh_gallery)
- * @return array[]
- */
public function get_gallery(): array {
if ( null === $this->gallery ) {
$raw = carbon_get_post_meta( $this->post_id, 'pksh_gallery' );
@@ -464,12 +478,9 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
}
// ============================================================
- // HONEST TIMER (shared helper)
+ // HONEST TIMER
// ============================================================
- /**
- * Timestamp честного дедлайна
- */
public function get_honest_deadline(): int {
if ( ! array_key_exists( 'deadline', $this->page ) ) {
$this->page['deadline'] = function_exists( 'dekart_get_honest_deadline' )
@@ -479,12 +490,6 @@ wp_cache_set( $cache_key, $this->branch['social'], 'dekart_branch', DAY_IN_SECON
return $this->page['deadline'];
}
- /**
- * Вывести HTML честного таймера + inline JS.
- * Используется в #cta-test и #final-cta.
- *
- * @param string $label Текст над таймером.
- */
public static function render_honest_timer( string $label = 'Цены действуют до конца месяца:' ): void {
$ts = self::instance()->get_honest_deadline();
if ( $ts <= 0 ) {
diff --git a/wp-content/themes/dekart/inc/schema.php b/wp-content/themes/dekart/inc/schema.php
index 53888bd..8a2b068 100644
--- a/wp-content/themes/dekart/inc/schema.php
+++ b/wp-content/themes/dekart/inc/schema.php
@@ -71,15 +71,12 @@ function dekart_schema_output(): void {
return;
}
- // Guard: без Carbon Fields — тихий выход (не может быть фатала)
if ( ! function_exists( 'carbon_get_post_meta' ) || ! function_exists( 'carbon_get_theme_option' ) ) {
return;
}
- // Object Cache key: page ID + locale + schema version
$cache_key = 'dekart_schema_graph_' . get_the_ID() . '_' . determine_locale();
$cached = wp_cache_get( $cache_key, 'dekart_schema' );
-
if ( false !== $cached ) {
echo $cached;
return;
@@ -87,81 +84,67 @@ function dekart_schema_output(): void {
$graph = array();
- // 1. WebPage
$webpage = dekart_schema_webpage();
if ( $webpage ) {
$graph[] = $webpage;
}
- // 2. WebSite
$website = dekart_schema_website();
if ( $website ) {
$graph[] = $website;
}
- // 3. BreadcrumbList
$breadcrumb = dekart_schema_breadcrumb();
if ( $breadcrumb ) {
$graph[] = $breadcrumb;
}
- // 4. Parent Organization (юридическое лицо, без адреса)
$organization = dekart_schema_organization();
if ( $organization ) {
$graph[] = $organization;
}
- // 5. LocalBusiness (филиал/центр, с адресом)
$local_business = dekart_schema_local_business();
if ( $local_business ) {
$graph[] = $local_business;
}
- // 6. Course
$course = dekart_schema_course();
if ( $course ) {
$graph[] = $course;
}
- // 7. Offer / AggregateOffer (conditional)
$offers = dekart_schema_offer();
if ( ! empty( $offers ) ) {
if ( isset( $offers[0] ) && is_array( $offers[0] ) ) {
- // Вернулся массив Offer'ов — добавляем каждый в граф
foreach ( $offers as $offer ) {
$graph[] = $offer;
}
} else {
- // Один Offer (для обратной совместимости)
$graph[] = $offers;
}
}
- // 8. AggregateRating (conditional — есть ли отзывы с оценками)
$rating = dekart_schema_rating();
if ( $rating ) {
$graph[] = $rating;
}
- // 9. Service — сущность услуги (только для страниц услуг, не для /otzyvy/)
$service = dekart_schema_service();
if ( $service ) {
$graph[] = $service;
}
- // 11. FAQPage (conditional — есть ли вопросы)
$faq = dekart_schema_faq();
if ( $faq ) {
$graph[] = $faq;
}
- // 11b. 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(
@@ -180,7 +163,6 @@ function dekart_schema_output(): void {
);
}
- // 13. Person[] — преподаватели (conditional)
$teachers = dekart_schema_teachers();
if ( ! empty( $teachers ) && is_array( $teachers ) ) {
foreach ( $teachers as $person ) {
@@ -188,6 +170,11 @@ function dekart_schema_output(): void {
}
}
+ $author = dekart_schema_author();
+ if ( $author ) {
+ $graph[] = $author;
+ }
+
if ( empty( $graph ) ) {
return;
}
@@ -201,14 +188,11 @@ function dekart_schema_output(): void {
);
if ( false === $json ) {
- return; // тихий выход вместо WSOD
+ return;
}
$output = '' . "\n";
-
- // Cache for 1 hour (3600 seconds) in 'dekart_schema' group
wp_cache_set( $cache_key, $output, 'dekart_schema', 3600 );
-
echo $output;
}
@@ -224,27 +208,23 @@ function dekart_schema_webpage(): ?array {
$post_id = get_the_ID();
$url = get_permalink( $post_id ) ?: dekart_schema_page_url();
- // _yoast_wpseo_title — Yoast SEO заголовок
$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( $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( get_the_ID() ) ) {
- $image_url = get_the_post_thumbnail_url( 2, 'full' );
+ if ( $post_id && has_post_thumbnail( $post_id ) ) {
+ $image_url = get_the_post_thumbnail_url( $post_id, 'full' );
}
- // pksh_level — уровень образования (about)
- $about_text = carbon_get_post_meta( get_the_ID(), 'pksh_level' ) ?: 'Подготовка к школе';
+ $about_text = carbon_get_post_meta( $post_id, 'pksh_level' ) ?: 'Подготовка к школе';
+ $city = dekart_branch_option( 'branch_city', 'Щёлково' );
- // branch_city — город для about и keywords
- $city = dekart_branch_option( 'branch_city', 'Щёлково' );
+ $data = Pksh_Data::instance();
$webpage = array(
'@type' => 'WebPage',
@@ -259,11 +239,9 @@ function dekart_schema_webpage(): ?array {
'breadcrumb' => array(
'@id' => $url . '#breadcrumb',
),
- // mainEntity — основной курс на странице
'mainEntity' => array(
'@id' => $url . '#course',
),
- // speakable — для голосовых помощников и AI-поиска
'speakable' => array(
'@type' => 'SpeakableSpecification',
'cssSelector' => array(
@@ -271,32 +249,23 @@ function dekart_schema_webpage(): ?array {
'.ds-hero__subtitle',
),
),
- // about — тематика страницы (образовательная услуга)
'about' => array(
'@type' => 'Thing',
'name' => wp_strip_all_tags( sprintf( '%s в %s', $about_text, $city ) ),
),
- // keywords — метки для поиска
'keywords' => wp_strip_all_tags(
'подготовка к школе, дошкольное образование, обучение чтению, обучение письму, '
. 'математика для дошкольников, развитие логики, курсы для детей 5-7 лет, '
. $city
),
+ 'datePublished' => $data->get_page_publish_date(),
+ 'dateModified' => $data->get_page_modified_date(),
+ 'author' => array(
+ '@id' => $url . '#author',
+ ),
);
- if ( $image_url ) {
- $webpage['image'] = esc_url( $image_url );
- }
-
- // datePublished / dateModified — даты публикации страницы
- $post_obj = get_post( 2 );
- if ( $post_obj ) {
- $webpage['datePublished'] = get_the_date( 'c', $post_obj );
- $webpage['dateModified'] = get_the_modified_date( 'c', $post_obj );
- }
-
- // hasPart — ссылка на FAQPage этой страницы
- $faq_items = carbon_get_post_meta( get_the_ID(), 'pksh_faq_items' );
+ $faq_items = carbon_get_post_meta( $post_id, 'pksh_faq_items' );
if ( ! empty( $faq_items ) && is_array( $faq_items ) ) {
$webpage['hasPart'] = array(
'@id' => $url . '#faq',
@@ -308,24 +277,29 @@ function dekart_schema_webpage(): ?array {
/**
* WebSite — главный сайт.
- * name из branch_name (единый источник с лагерем), а не из site_name.
*/
function dekart_schema_website(): ?array {
$site_name = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
$home_url = home_url( '/' );
return array(
- '@type' => 'WebSite',
- '@id' => $home_url . '#website',
- 'url' => $home_url,
- 'name' => wp_strip_all_tags( $site_name ),
+ '@type' => 'WebSite',
+ '@id' => $home_url . '#website',
+ 'url' => $home_url,
+ 'name' => wp_strip_all_tags( $site_name ),
+ 'publisher' => array(
+ '@id' => $home_url . '#organization',
+ ),
+ 'inLanguage' => str_replace( '_', '-', determine_locale() ?: 'ru' ),
'potentialAction' => array(
- '@type' => 'SearchAction',
- 'target' => array(
- '@type' => 'EntryPoint',
- 'urlTemplate' => $home_url . '?s={search_term_string}',
+ array(
+ '@type' => 'SearchAction',
+ 'target' => array(
+ '@type' => 'EntryPoint',
+ 'urlTemplate' => $home_url . '?s={search_term_string}',
+ ),
+ 'query-input' => 'required name=search_term_string',
),
- 'query-input' => 'required name=search_term_string',
),
);
}
@@ -335,23 +309,23 @@ function dekart_schema_website(): ?array {
*/
function dekart_schema_breadcrumb(): ?array {
$page_url = dekart_schema_page_url();
- $home_url = home_url( '/' );
+ $name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе';
return array(
'@type' => 'BreadcrumbList',
'@id' => $page_url . '#breadcrumb',
'itemListElement' => array(
array(
- '@type' => 'ListItem',
+ '@type' => 'ListItem',
'position' => 1,
- 'name' => 'Главная',
- 'item' => $home_url,
+ 'name' => 'Главная',
+ 'item' => home_url( '/' ),
),
array(
- '@type' => 'ListItem',
+ '@type' => 'ListItem',
'position' => 2,
- 'name' => 'Подготовка к школе',
- 'item' => $page_url,
+ 'name' => wp_strip_all_tags( $name ),
+ 'item' => $page_url,
),
),
);
@@ -359,8 +333,6 @@ function dekart_schema_breadcrumb(): ?array {
/**
* Helper: получить значение из Carbon Fields (schema_*) с fallback на Settings API (branch_*).
- * Работает с любыми типами (включая массивы complex/media_gallery).
- * Приоритет: carbon_get_theme_option('schema_*') → get_option('branch_*') → $default.
*/
function dekart_schema_option( string $schema_key, string $branch_key, $default = '' ) {
$val = carbon_get_theme_option( $schema_key );
@@ -376,8 +348,6 @@ function dekart_schema_option( string $schema_key, string $branch_key, $default
/**
* Helper: получить значение из Настроек филиала (branch_*).
- * Единый источник: get_option('branch_*').
- * Схема-поля (schema_*) читать через dekart_schema_option() — CF → Settings API.
*/
function dekart_branch_option( string $key, string $default = '' ): string {
$val = get_option( $key );
@@ -389,31 +359,27 @@ function dekart_branch_option( string $key, string $default = '' ): string {
/**
* Parent Organization — юридическое лицо (без адреса филиала).
- * @id совпадает с лагерем — это одна организация.
*/
function dekart_schema_organization(): ?array {
$home_url = home_url( '/' );
- $site_name = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
-
- // Логотип: единая цепочка с page-camp.php
- $logo_url = dekart_resolve_logo();
+ $brand = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
$org = array(
'@type' => 'Organization',
'@id' => $home_url . '#organization',
- 'name' => wp_strip_all_tags( $site_name ),
+ 'name' => wp_strip_all_tags( $brand ),
'url' => $home_url,
- 'logo' => esc_url( $logo_url ),
- 'image' => esc_url( $logo_url ),
+ 'logo' => array(
+ '@type' => 'ImageObject',
+ 'url' => dekart_resolve_logo(),
+ ),
);
- // legalName — юридическое наименование (CF schema_legal_name → Settings API branch_legal_name)
$legal_name = dekart_schema_option( 'schema_legal_name', 'branch_legal_name' );
if ( ! empty( $legal_name ) && is_string( $legal_name ) ) {
$org['legalName'] = wp_strip_all_tags( trim( $legal_name ) );
}
- // contactPoint — контактный телефон для связи
$cp_phone = dekart_branch_option( 'branch_phone', '' );
if ( $cp_phone ) {
$org['contactPoint'] = array(
@@ -429,11 +395,9 @@ function dekart_schema_organization(): ?array {
}
/**
- * Helper: цепочка разрешения логотипа (аналог page-camp.php:92-103).
- * branch_logo (ACF) → banner_img (CF) → custom_logo (WP) → post_thumbnail → fallback SVG.
+ * Helper: цепочка разрешения логотипа.
*/
function dekart_resolve_logo(): string {
- // 1. branch_logo — ACF опция филиала
$branch_logo = get_option( 'branch_logo' );
if ( ! empty( $branch_logo ) ) {
if ( is_array( $branch_logo ) && isset( $branch_logo['url'] ) ) {
@@ -445,7 +409,6 @@ function dekart_resolve_logo(): string {
}
}
- // 2. banner_img — CF поле страницы
$banner_img = carbon_get_post_meta( get_the_ID(), 'banner_img' );
if ( ! empty( $banner_img ) ) {
if ( is_array( $banner_img ) && isset( $banner_img['url'] ) ) {
@@ -457,55 +420,38 @@ function dekart_resolve_logo(): string {
}
}
- // 3. custom_logo — WP Customizer
- $logo_id = get_theme_mod( 'custom_logo' );
- if ( $logo_id ) {
- $url = wp_get_attachment_url( $logo_id );
+ if ( has_custom_logo() ) {
+ $logo = wp_get_attachment_image_url( get_theme_mod( 'custom_logo' ), 'full' );
+ if ( $logo ) {
+ return $logo;
+ }
+ }
+
+ $post_id = get_the_ID();
+ if ( $post_id && has_post_thumbnail( $post_id ) ) {
+ $url = get_the_post_thumbnail_url( $post_id, 'full' );
if ( $url ) {
return $url;
}
}
- // 4. post_thumbnail страницы
- if ( has_post_thumbnail( get_the_ID() ) ) {
- $url = get_the_post_thumbnail_url( 2, 'full' );
- if ( $url ) {
- return $url;
- }
- }
-
- // 5. fallback — SVG из темы
return get_template_directory_uri() . '/assets/images/logo-svg.svg';
}
/**
* LocalBusiness — филиал/центр с адресом, телефоном.
- * Данные из Настроек филиала (branch_*), единый источник с лагерем.
- * @id на уровне страницы, parentOrganization → #organization.
- *
- * @see https://schema.org/LocalBusiness
- * @see https://schema.org/EducationalOrganization
*/
function dekart_schema_local_business(): ?array {
$page_url = dekart_schema_page_url();
- $home_url = home_url( '/' );
-
- // branch_name — название филиала (get_option)
- $site_name = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
- // 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 — регион/область
- $region = dekart_branch_option( 'branch_region', 'Московская область' );
- // branch_phone — телефон
- $phone = dekart_branch_option( 'branch_phone', '' );
- // branch_email — email
- $email = dekart_branch_option( 'branch_email', '' );
- // branch_coords — координаты "lat, lng" из Настроек филиала (Settings API)
- $coords = get_option( 'branch_coords' );
- $lat = '';
- $lng = '';
+ $brand = dekart_branch_option( 'branch_name', 'Детский центр «Декарт»' );
+ $street = get_option( 'branch_address' ) ?: carbon_get_theme_option( 'site_address' ) ?: 'Талсинская ул., 24';
+ $city = dekart_branch_option( 'branch_city', 'Щёлково' );
+ $region = dekart_branch_option( 'branch_region', 'Московская область' );
+ $phone = dekart_branch_option( 'branch_phone', '' );
+ $email = dekart_branch_option( 'branch_email', '' );
+ $coords = get_option( 'branch_coords' );
+ $lat = '';
+ $lng = '';
if ( ! empty( $coords ) && is_string( $coords ) && false !== strpos( $coords, ',' ) ) {
$parts = explode( ',', $coords );
if ( count( $parts ) >= 2 ) {
@@ -513,29 +459,22 @@ function dekart_schema_local_business(): ?array {
$lng = trim( $parts[1] );
}
}
- // branch_postal_code — почтовый индекс (CF schema_postal_code → Settings API)
- $zip = dekart_schema_option( 'schema_postal_code', 'branch_postal_code' );
- // branch_price_range — диапазон цен (Settings API)
- $price_range = get_option( 'branch_price_range' );
- // branch_founded — год основания (CF schema_founded → Settings API)
- $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
+ $zip = dekart_schema_option( 'schema_postal_code', 'branch_postal_code' );
+ $price_range = get_option( 'branch_price_range' );
+ $founded = dekart_schema_option( 'schema_founded', 'branch_founded' );
+ $map_url = dekart_schema_option( 'schema_map_url', 'branch_map_url' );
$google_place_id = get_option( 'branch_google_place_id' );
- // branch_google_maps_url — Google Maps URL для hasMap
$google_maps_url = get_option( 'branch_google_maps_url' );
- // area_served — города обслуживания (через запятую)
- // Приоритет: CF schema_area_served → Settings API branch_area_served
$area_served_raw = carbon_get_theme_option( 'schema_area_served' ) ?: get_option( 'branch_area_served', 'Щёлково, Фрязино, Монино, Ивантеевка, Королёв' );
- // 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' );
$lb = array(
'@type' => array( 'EducationalOrganization', 'LocalBusiness' ),
- '@id' => $page_url . '#localbusiness',
- 'name' => wp_strip_all_tags( $site_name ),
- 'url' => $home_url,
+ '@id' => $page_url . '#local-business',
+ 'parentOrganization' => array(
+ '@id' => home_url( '/' ) . '#organization',
+ ),
+ 'name' => wp_strip_all_tags( $brand ),
'address' => array(
'@type' => 'PostalAddress',
'streetAddress' => wp_strip_all_tags( $street ),
@@ -543,27 +482,18 @@ function dekart_schema_local_business(): ?array {
'addressRegion' => wp_strip_all_tags( $region ),
'addressCountry' => 'RU',
),
- 'parentOrganization' => array(
- '@id' => $home_url . '#organization',
- ),
+ 'url' => $page_url,
);
- // postalCode — почтовый индекс (условно)
- if ( ! empty( $zip ) && is_string( $zip ) ) {
- $lb['address']['postalCode'] = trim( $zip );
- }
-
- // telephone — телефон (условно)
if ( $phone ) {
$lb['telephone'] = wp_strip_all_tags( $phone );
}
-
- // email — почта (условно)
if ( $email ) {
$lb['email'] = wp_strip_all_tags( $email );
}
-
- // geo — GeoCoordinates (условно: обе координаты не пусты)
+ if ( ! empty( $zip ) && is_string( $zip ) ) {
+ $lb['address']['postalCode'] = trim( $zip );
+ }
if ( ! empty( $lat ) && ! empty( $lng ) ) {
$lb['geo'] = array(
'@type' => 'GeoCoordinates',
@@ -571,15 +501,12 @@ function dekart_schema_local_business(): ?array {
'longitude' => floatval( $lng ),
);
}
-
- // hasMap — ссылка на карту (условно)
- if ( ! empty( $map_url ) && is_string( $map_url ) ) {
+ 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 ) );
}
- // openingHoursSpecification — часы работы
- // Формат 1 (CF complex, schema_hours): [['day_of_week' => 'Monday', 'opens' => '09:00', 'closes' => '20:00', 'day_off' => '']]
- // Формат 2 (Settings API textarea, branch_hours): "Monday|09:00|20:00"
$hours_spec = array();
if ( ! empty( $hours_cf ) && is_array( $hours_cf ) ) {
foreach ( $hours_cf as $entry ) {
@@ -648,12 +575,10 @@ function dekart_schema_local_business(): ?array {
$lb['openingHoursSpecification'] = $hours_spec;
}
- // priceRange — строка диапазона цен (условно)
if ( ! empty( $price_range ) && is_string( $price_range ) ) {
$lb['priceRange'] = trim( $price_range );
}
- // Google Place ID — identifier + @id для Google Business Profile
if ( ! empty( $google_place_id ) && is_string( $google_place_id ) ) {
$lb['identifier'] = array(
'@type' => 'PropertyValue',
@@ -663,27 +588,15 @@ function dekart_schema_local_business(): ?array {
$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 ) ) {
$lb['foundingDate'] = trim( $founded );
}
- // sameAs — массив URL соцсетей
- // Формат 1 (CF schema_social complex): [['platform' => 'VK', 'url' => 'https://...']]
- // Формат 2 (Settings API branch_social): [['name' => 'VK', 'url' => 'https://...']]
$social_cf = carbon_get_theme_option( 'schema_social' );
$same_as = array();
if ( ! empty( $social_cf ) && is_array( $social_cf ) ) {
@@ -708,13 +621,12 @@ function dekart_schema_local_business(): ?array {
$lb['sameAs'] = $same_as;
}
- // Гарантируем WhatsApp и Telegram в sameAs, даже если не заданы в админке
$ensure_platforms = array(
'WhatsApp' => dekart_branch_option( 'branch_whatsapp', 'https://wa.me/79269311434' ),
'Telegram' => dekart_branch_option( 'branch_telegram', 'https://t.me/dekart_school' ),
);
foreach ( $ensure_platforms as $platform => $url ) {
- if ( $url && filter_var( $url, FILTER_VALIDATE_URL ) ) {
+ if ( ! empty( $url ) && filter_var( $url, FILTER_VALIDATE_URL ) ) {
$found = false;
foreach ( $same_as as $existing ) {
if ( false !== stripos( $existing, parse_url( $url, PHP_URL_HOST ) ) ) {
@@ -727,7 +639,7 @@ 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;
@@ -742,7 +654,6 @@ function dekart_schema_local_business(): ?array {
}
}
- // Яндекс Бизнес — добавляем в sameAs, если ссылка задана
$yb_url = get_option( 'branch_yandex_business_url', '' );
if ( ! empty( $yb_url ) && filter_var( $yb_url, FILTER_VALIDATE_URL ) ) {
$found_yb = false;
@@ -757,14 +668,10 @@ function dekart_schema_local_business(): ?array {
}
}
- // Если после добавления появились элементы, обновляем sameAs
- if ( ! empty( $same_as ) && ! isset( $lb['sameAs'] ) ) {
+ if ( ! empty( $same_as ) ) {
$lb['sameAs'] = $same_as;
}
- // image — массив URL фотографий филиала
- // Формат 1 (CF schema_photos media_gallery): [123, 456]
- // Формат 2 (Settings API branch_photos): "123, 456"
$photos_cf = carbon_get_theme_option( 'schema_photos' );
$images = array();
if ( ! empty( $photos_cf ) && is_array( $photos_cf ) ) {
@@ -797,7 +704,6 @@ function dekart_schema_local_business(): ?array {
$lb['image'] = $images;
}
- // employee — преподаватели (Person), если есть
$teachers = carbon_get_post_meta( get_the_ID(), 'pksh_teachers' );
if ( ! empty( $teachers ) && is_array( $teachers ) ) {
$employee_ids = array();
@@ -819,105 +725,49 @@ function dekart_schema_local_business(): ?array {
/**
* Course — основной курс «Подготовка к школе».
- * Данные из Carbon Fields: banner_h1, banner_subtitle.
- *
* @see https://schema.org/Course
- * @see https://schema.org/CourseInstance
*/
function dekart_schema_course(): ?array {
- $page_url = dekart_schema_page_url();
- $home_url = home_url( '/' );
+ $page_url = dekart_schema_page_url();
+ $name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе 5–7 лет в Щёлково';
- // banner_h1 — заголовок H1 (Course.name)
- $name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе 5–7 лет в Щёлково';
- // pksh_description — полное описание курса для микроразметки (textarea)
- // Если заполнено — приоритет над banner_subtitle
$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( get_the_ID(), 'banner_subtitle' ) ?: 'Научим читать, считать и писать до школы. Бесплатное пробное занятие.' );
- // Пробное занятие — есть ли в подзаголовке упоминание бесплатно
- $has_trial = ! empty( $description ) && false !== mb_stripos( $description, 'бесплатно' );
- // pksh_level — уровень образования
+ $description = ! empty( $pksh_description ) ? $pksh_description : ( carbon_get_post_meta( get_the_ID(), 'banner_subtitle' ) ?: 'Научим читать, считать и писать до школы. Бесплатное пробное занятие.' );
+ $has_trial = ! empty( $description ) && false !== mb_stripos( $description, 'бесплатно' );
+
$level = carbon_get_post_meta( get_the_ID(), 'pksh_level' );
- // pksh_age — возраст
$age = carbon_get_post_meta( get_the_ID(), 'pksh_age' );
- // pksh_audience — целевая аудитория
$audience = carbon_get_post_meta( get_the_ID(), 'pksh_audience' );
- // pksh_course_mode — формат обучения (очная/ONLINE/OFFLINE)
$course_mode = carbon_get_post_meta( get_the_ID(), 'pksh_course_mode' );
- // pksh_skills — список навыков
$skills = carbon_get_post_meta( get_the_ID(), 'pksh_skills' );
- // banner_img — изображение курса
$course_img = carbon_get_post_meta( get_the_ID(), 'banner_img' );
- // pksh_programs — программы (для timeRequired + CourseInstance)
$programs = carbon_get_post_meta( get_the_ID(), 'pksh_programs' );
$course = array(
- '@type' => 'Course',
- '@id' => $page_url . '#course',
- 'name' => wp_strip_all_tags( $name ),
- 'description' => wp_strip_all_tags( $description ),
- 'inLanguage' => str_replace( '_', '-', determine_locale() ?: 'ru' ),
- 'provider' => array(
- '@id' => $page_url . '#localbusiness',
- ),
- 'offers' => array_merge(
- array( array( '@id' => $page_url . '#offer' ) ),
- $has_trial ? array( array( '@id' => $page_url . '#offer-trial' ) ) : array()
- ),
- 'aggregateRating' => array(
- '@id' => $page_url . '#rating',
+ '@type' => 'Course',
+ '@id' => $page_url . '#course',
+ 'name' => wp_strip_all_tags( $name ),
+ 'description' => wp_strip_all_tags( $description ),
+ 'url' => $page_url,
+ 'provider' => array(
+ '@id' => $page_url . '#local-business',
),
);
- // review — 1-2 отзыва прямо в Course (Google требует для звёзд в rich snippet)
- $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 ) {
- $author = isset( $rv['review_author'] ) ? trim( $rv['review_author'] ) : '';
- $body = isset( $rv['review_text'] ) ? trim( $rv['review_text'] ) : '';
- $rating = isset( $rv['review_rating'] ) ? intval( $rv['review_rating'] ) : 0;
- if ( empty( $author ) || empty( $body ) || $rating < 1 || $rating > 5 ) {
- continue;
- }
- $item = array(
- '@type' => 'Review',
- 'author' => array( '@type' => 'Person', 'name' => wp_strip_all_tags( $author ) ),
- 'reviewBody' => wp_strip_all_tags( $body ),
- 'reviewRating' => array(
- '@type' => 'Rating',
- 'ratingValue' => (string) $rating,
- 'bestRating' => '5',
- 'worstRating' => '1',
- ),
- 'itemReviewed' => array( '@id' => $page_url . '#course' ),
- );
- $date = isset( $rv['review_date'] ) ? trim( $rv['review_date'] ) : '';
- if ( $date ) {
- $item['datePublished'] = sanitize_text_field( $date );
- }
- $review_items[] = $item;
- if ( count( $review_items ) >= 2 ) {
- break;
- }
- }
- if ( ! empty( $review_items ) ) {
- $course['review'] = $review_items;
- }
+ if ( $has_trial ) {
+ $course['hasCourseInstance'] = array(
+ '@type' => 'CourseInstance',
+ 'courseMode' => 'Onsite',
+ 'courseWorkload' => '30-40 минут',
+ );
}
- // educationalLevel — уровень образования (условно)
if ( ! empty( $level ) && is_string( $level ) ) {
$course['educationalLevel'] = wp_strip_all_tags( trim( $level ) );
}
-
- // typicalAgeRange — возраст детей (условно, из pksh_age)
if ( ! empty( $age ) && is_string( $age ) ) {
$course['typicalAgeRange'] = wp_strip_all_tags( trim( $age ) );
}
-
- // audience — EducationalAudience (условно)
if ( ! empty( $audience ) && is_string( $audience ) ) {
$course['audience'] = array(
'@type' => 'EducationalAudience',
@@ -925,13 +775,12 @@ function dekart_schema_course(): ?array {
);
}
- // teaches — массив навыков (условно, из pksh_skills complex)
if ( ! empty( $skills ) && is_array( $skills ) ) {
$teaches = array();
foreach ( $skills as $skill ) {
- $name = isset( $skill['skill'] ) ? trim( $skill['skill'] ) : '';
- if ( $name ) {
- $teaches[] = wp_strip_all_tags( $name );
+ $s = isset( $skill['skill'] ) ? trim( $skill['skill'] ) : '';
+ if ( $s ) {
+ $teaches[] = wp_strip_all_tags( $s );
}
}
if ( ! empty( $teaches ) ) {
@@ -939,17 +788,6 @@ function dekart_schema_course(): ?array {
}
}
- // timeRequired — длительность курса из первой программы, где заполнено
- if ( ! empty( $programs ) && is_array( $programs ) ) {
- foreach ( $programs as $prog ) {
- if ( ! empty( $prog['program_time_required'] ) ) {
- $course['timeRequired'] = wp_strip_all_tags( trim( $prog['program_time_required'] ) );
- break;
- }
- }
- }
-
- // image — изображение курса (условно, из banner_img)
if ( ! empty( $course_img ) ) {
if ( is_array( $course_img ) && isset( $course_img['url'] ) ) {
$course['image'] = esc_url( $course_img['url'] );
@@ -960,103 +798,7 @@ function dekart_schema_course(): ?array {
}
}
} elseif ( has_post_thumbnail( get_the_ID() ) ) {
- // fallback: post_thumbnail страницы
- $url = get_the_post_thumbnail_url( 2, 'full' );
- if ( $url ) {
- $course['image'] = esc_url( $url );
- }
- }
-
- // hasCourseInstance — по одной CourseInstance на каждую программу с данными
- $instances = array();
- if ( ! empty( $programs ) && is_array( $programs ) ) {
- foreach ( $programs as $prog ) {
- $prog_workload = isset( $prog['program_workload'] ) ? trim( $prog['program_workload'] ) : '';
- $prog_intensity = isset( $prog['program_intensity'] ) ? trim( $prog['program_intensity'] ) : '';
- $prog_start = isset( $prog['program_start_date'] ) ? trim( $prog['program_start_date'] ) : '';
- $prog_end = isset( $prog['program_end_date'] ) ? trim( $prog['program_end_date'] ) : '';
-
- if ( empty( $prog_workload ) && empty( $prog_intensity ) && empty( $prog_start ) && empty( $prog_end ) ) {
- continue; // нет данных для CourseInstance
- }
-
- $instance = array(
- '@type' => 'CourseInstance',
- 'courseMode' => 'Onsite',
- 'location' => array(
- '@id' => $page_url . '#localbusiness',
- ),
- );
-
- // courseMode — формат обучения (глобальный, единый для всех программ)
- // Допустимые Schema.org: Onsite, Online, Blended
- if ( ! empty( $course_mode ) && is_string( $course_mode ) ) {
- $cm = strtolower( trim( $course_mode ) );
- // Нормализация legacy-значений
- $mode_map = array(
- 'offline' => 'Onsite',
- 'online' => 'Online',
- 'очная' => 'Onsite',
- 'очно' => 'Onsite',
- );
- $cm_normalized = isset( $mode_map[ $cm ] ) ? $mode_map[ $cm ] : ucfirst( $cm );
- $valid_modes = array( 'Onsite', 'Online', 'Blended' );
- if ( in_array( $cm_normalized, $valid_modes, true ) ) {
- $instance['courseMode'] = $cm_normalized;
- }
- }
-
- // courseSchedule — расписание (Schedule)
- if ( $prog_workload || $prog_intensity || $prog_start || $prog_end ) {
- $schedule = array( '@type' => 'Schedule' );
- // repeatFrequency — ISO 8601 из program_repeat_frequency (новое поле)
- $repeat_freq = isset( $prog['program_repeat_frequency'] ) ? trim( $prog['program_repeat_frequency'] ) : '';
- if ( $repeat_freq ) {
- $schedule['repeatFrequency'] = wp_strip_all_tags( $repeat_freq );
- } elseif ( $prog_intensity ) {
- // fallback: program_intensity — только если нет ISO-поля
- $schedule['repeatFrequency'] = wp_strip_all_tags( $prog_intensity );
- }
- if ( $prog_start ) {
- $schedule['startDate'] = $prog_start;
- }
- if ( $prog_end ) {
- $schedule['endDate'] = $prog_end;
- }
- $instance['courseSchedule'] = $schedule;
- }
-
- // courseWorkload — учебная нагрузка
- if ( $prog_workload ) {
- $instance['courseWorkload'] = wp_strip_all_tags( $prog_workload );
- }
-
- $instances[] = $instance;
- }
- }
-
- // fallback: если нет программ с данными, но есть courseMode — одна общая CourseInstance
- if ( empty( $instances ) && ! empty( $course_mode ) && is_string( $course_mode ) ) {
- $cm = strtolower( trim( $course_mode ) );
- $mode_map = array(
- 'offline' => 'Onsite',
- 'online' => 'Online',
- 'очная' => 'Onsite',
- 'очно' => 'Onsite',
- );
- $cm_normalized = isset( $mode_map[ $cm ] ) ? $mode_map[ $cm ] : ucfirst( $cm );
- $valid_modes = array( 'Onsite', 'Online', 'Blended' );
- $instances[] = array(
- '@type' => 'CourseInstance',
- 'courseMode' => ( in_array( $cm_normalized, $valid_modes, true ) ) ? $cm_normalized : 'Onsite',
- 'location' => array(
- '@id' => $page_url . '#localbusiness',
- ),
- );
- }
-
- if ( ! empty( $instances ) ) {
- $course['hasCourseInstance'] = $instances;
+ $course['image'] = esc_url( get_the_post_thumbnail_url( get_the_ID(), 'full' ) );
}
return $course;
@@ -1064,18 +806,16 @@ function dekart_schema_course(): ?array {
/**
* AggregateRating — рейтинг из отзывов Carbon Fields.
- * Вычисляется динамически из pksh_reviews_list.
- * Если отзывов < 1 — блок не выводится.
*/
function dekart_schema_rating(): ?array {
$page_url = dekart_schema_page_url();
+ $ratings = array();
$reviews = carbon_get_post_meta( get_the_ID(), 'pksh_reviews_list' );
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
return null;
}
- $ratings = array();
foreach ( $reviews as $review ) {
$rating = isset( $review['review_rating'] ) ? intval( $review['review_rating'] ) : 0;
if ( $rating >= 1 && $rating <= 5 ) {
@@ -1090,13 +830,13 @@ function dekart_schema_rating(): ?array {
$average = round( array_sum( $ratings ) / count( $ratings ), 1 );
return array(
- '@type' => 'AggregateRating',
- '@id' => $page_url . '#rating',
- 'ratingValue' => (string) $average,
- 'bestRating' => '5',
- 'worstRating' => '1',
- 'ratingCount' => (string) count( $ratings ),
- 'itemReviewed' => array(
+ '@type' => 'AggregateRating',
+ '@id' => $page_url . '#aggregate-rating',
+ 'ratingValue' => (string) $average,
+ 'bestRating' => '5',
+ 'worstRating' => '1',
+ 'ratingCount' => (string) count( $ratings ),
+ 'itemReviewed' => array(
'@id' => $page_url . '#course',
),
);
@@ -1104,44 +844,30 @@ function dekart_schema_rating(): ?array {
/**
* Service — сущность услуги «Подготовка к школе».
- * provider → #organization (единая школа).
- * Google видит Service как частный случай услуги организации.
- *
- * @see https://schema.org/Service
*/
function dekart_schema_service(): ?array {
$page_url = dekart_schema_page_url();
- $home_url = home_url( '/' );
+ $name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе 5–7 лет в Щёлково';
- $name = carbon_get_post_meta( get_the_ID(), 'banner_h1' ) ?: 'Подготовка к школе';
- $description = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' ) ?: '';
-
- $service = array(
+ return array(
'@type' => 'Service',
'@id' => $page_url . '#service',
'name' => wp_strip_all_tags( $name ),
'provider' => array(
- '@id' => $home_url . '#organization',
+ '@id' => $page_url . '#local-business',
),
'areaServed' => array(
'@type' => 'City',
'name' => dekart_branch_option( 'branch_city', 'Щёлково' ),
),
);
-
- if ( $description ) {
- $service['description'] = wp_strip_all_tags( $description );
- }
-
- return $service;
}
/**
* FAQPage — вопросы и ответы из pksh_faq_items.
- * Если вопросов нет — блок не выводится.
*/
function dekart_schema_faq(): ?array {
- $page_url = dekart_schema_page_url();
+ $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 ) ) {
@@ -1150,19 +876,17 @@ function dekart_schema_faq(): ?array {
$main_entity = array();
foreach ( $faq_items 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 ) ) {
+ $q = isset( $item['faq_question'] ) ? trim( $item['faq_question'] ) : '';
+ $a = isset( $item['faq_answer'] ) ? trim( $item['faq_answer'] ) : '';
+ if ( empty( $q ) || empty( $a ) ) {
continue;
}
-
$main_entity[] = array(
'@type' => 'Question',
- 'name' => wp_strip_all_tags( $question ),
+ 'name' => wp_strip_all_tags( $q ),
'acceptedAnswer' => array(
'@type' => 'Answer',
- 'text' => wp_strip_all_tags( $answer ),
+ 'text' => wp_strip_all_tags( $a ),
),
);
}
@@ -1172,23 +896,95 @@ function dekart_schema_faq(): ?array {
}
return array(
- '@type' => 'FAQPage',
- '@id' => $page_url . '#faq',
- 'mainEntity' => $main_entity,
+ '@type' => 'FAQPage',
+ '@id' => $page_url . '#faq',
+ 'name' => 'Часто задаваемые вопросы о подготовке к школе',
+ 'mainEntity' => $main_entity,
);
}
+/**
+ * Person[] — преподаватели из pksh_teachers complex.
+ * Каждый преподаватель — отдельная сущность Person.
+ * Связаны с LocalBusiness через employee @id.
+ *
+ * @return array[] Массив Person-сущностей для @graph.
+ */
+function dekart_schema_teachers(): array {
+ $page_url = dekart_schema_page_url();
+ $teachers = carbon_get_post_meta( get_the_ID(), 'pksh_teachers' );
+
+ if ( empty( $teachers ) || ! is_array( $teachers ) ) {
+ return array();
+ }
+
+ $result = array();
+ foreach ( $teachers as $i => $teacher ) {
+ $t_id = isset( $teacher['teacher_id'] ) ? intval( $teacher['teacher_id'] ) : 0;
+ $subject = isset( $teacher['teacher_subject'] ) ? trim( $teacher['teacher_subject'] ) : '';
+ $about = isset( $teacher['teacher_about'] ) ? trim( $teacher['teacher_about'] ) : '';
+
+ if ( $t_id < 1 ) {
+ continue;
+ }
+
+ $t_post = get_post( $t_id );
+ if ( ! $t_post ) {
+ continue;
+ }
+ $name = $t_post->post_title;
+
+ if ( empty( $name ) ) {
+ continue;
+ }
+
+ $photo = get_post_meta( $t_id, '_teacher_avatar', true );
+ if ( ! $photo ) {
+ $photo = get_post_meta( $t_id, '_thumbnail_id', true );
+ }
+
+ if ( empty( $subject ) ) {
+ $subject = get_post_meta( $t_id, '_teacher_direction', true );
+ }
+ if ( empty( $about ) ) {
+ $about = get_post_meta( $t_id, '_teacher_about', true );
+ }
+
+ $person = array(
+ '@type' => 'Person',
+ '@id' => $page_url . '#teacher-' . $i,
+ 'name' => wp_strip_all_tags( $name ),
+ );
+
+ if ( ! empty( $subject ) && is_string( $subject ) ) {
+ $person['jobTitle'] = wp_strip_all_tags( trim( $subject ) );
+ }
+
+ if ( ! empty( $about ) && is_string( $about ) ) {
+ $person['description'] = wp_strip_all_tags( trim( $about ) );
+ }
+
+ if ( ! empty( $photo ) ) {
+ $url = wp_get_attachment_url( (int) $photo );
+ if ( $url ) {
+ $person['image'] = esc_url( $url );
+ }
+ }
+
+ $result[] = $person;
+ }
+
+ return $result;
+}
+
/**
* Review[] — поштучные отзывы из pksh_reviews_list.
* Каждый отзыв — отдельная сущность Review с автор-персоной и оценкой.
- * itemReviewed → Course. Фильтр некорректных оценок (>5 или <1).
*
* @return array[] Массив Review-сущностей для @graph.
- * @see https://schema.org/Review
*/
function dekart_schema_reviews(): array {
$page_url = dekart_schema_page_url();
- // pksh_reviews_list — complex-поле отзывов
$reviews = carbon_get_post_meta( get_the_ID(), 'pksh_reviews_list' );
if ( empty( $reviews ) || ! is_array( $reviews ) ) {
@@ -1198,19 +994,13 @@ function dekart_schema_reviews(): array {
$result = array();
$review_seq = 0;
foreach ( $reviews as $i => $review ) {
- // review_author — имя автора (Person.name)
$author_name = isset( $review['review_author'] ) ? trim( $review['review_author'] ) : '';
- // review_text — текст отзыва
$body = isset( $review['review_text'] ) ? trim( $review['review_text'] ) : '';
- // review_rating — оценка 1-5
$rating_val = isset( $review['review_rating'] ) ? intval( $review['review_rating'] ) : 0;
- // Пропускаем без автора или текста
if ( empty( $author_name ) || empty( $body ) ) {
continue;
}
-
- // Фильтруем оценки вне диапазона 1-5
if ( $rating_val < 1 || $rating_val > 5 ) {
continue;
}
@@ -1236,7 +1026,6 @@ function dekart_schema_reviews(): array {
),
);
- // datePublished — дата отзыва (условно, из review_date)
$review_date = isset( $review['review_date'] ) ? trim( $review['review_date'] ) : '';
if ( $review_date ) {
$entity['datePublished'] = sanitize_text_field( $review_date );
@@ -1249,121 +1038,57 @@ function dekart_schema_reviews(): array {
}
/**
- * Person[] — преподаватели из pksh_teachers complex.
- * Каждый преподаватель — отдельная сущность Person.
- * Связаны с LocalBusiness через employee @id.
- *
- * @return array[] Массив Person-сущностей для @graph.
- * @see https://schema.org/Person
+ * Author Person — автор контента страницы.
+ * Берётся из CF полей вкладки "🤖 GEO (AI-поиск)".
*/
-function dekart_schema_teachers(): array {
+function dekart_schema_author(): ?array {
$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( get_the_ID(), 'pksh_teachers' );
+ $data = Pksh_Data::instance();
+ $name = $data->get_author_name();
+ $years = $data->get_years_on_market();
- if ( empty( $teachers ) || ! is_array( $teachers ) ) {
- return array();
+ // Должность — хардкор + авто-расчёт лет
+ $title = 'Образовательный центр с гослицензией';
+ if ( $years > 0 ) {
+ $title .= ', ' . $years . ' лет на рынке';
}
- $result = array();
- foreach ( $teachers as $i => $teacher ) {
- // teacher_id — ID записи преподавателя из кастомного типа
- $t_id = isset( $teacher['teacher_id'] ) ? intval( $teacher['teacher_id'] ) : 0;
- // teacher_subject — предмет / должность (с этой страницы)
- $subject = isset( $teacher['teacher_subject'] ) ? trim( $teacher['teacher_subject'] ) : '';
- // teacher_about — описание / биография (с этой страницы)
- $about = isset( $teacher['teacher_about'] ) ? trim( $teacher['teacher_about'] ) : '';
+ $person = array(
+ '@type' => 'Person',
+ '@id' => $page_url . '#author',
+ 'name' => wp_strip_all_tags( $name ),
+ 'jobTitle' => wp_strip_all_tags( $title ),
+ );
- if ( $t_id < 1 ) {
- continue;
- }
+ $person['description'] = wp_strip_all_tags( sprintf(
+ 'Автор контента %s — %s',
+ $name,
+ $title
+ ) );
- // Загружаем имя и фото из карточки преподавателя
- $t_post = get_post( $t_id );
- if ( ! $t_post ) {
- continue;
- }
- $name = $t_post->post_title;
-
- if ( empty( $name ) ) {
- continue;
- }
-
- // Фото: _teacher_avatar → _thumbnail_id → 0
- $photo = get_post_meta( $t_id, '_teacher_avatar', true );
- if ( ! $photo ) {
- $photo = get_post_meta( $t_id, '_thumbnail_id', true );
- }
-
- // Если subject/about не заданы на странице — fallback из карточки
- if ( empty( $subject ) ) {
- $subject = get_post_meta( $t_id, '_teacher_direction', true );
- }
- if ( empty( $about ) ) {
- $about = get_post_meta( $t_id, '_teacher_about', true );
- }
-
- $person = array(
- '@type' => 'Person',
- '@id' => $page_url . '#teacher-' . $i,
- 'name' => wp_strip_all_tags( $name ),
- );
-
- // jobTitle — должность (условно)
- if ( ! empty( $subject ) && is_string( $subject ) ) {
- $person['jobTitle'] = wp_strip_all_tags( trim( $subject ) );
- }
-
- // description — о преподавателе (условно)
- if ( ! empty( $about ) && is_string( $about ) ) {
- $person['description'] = wp_strip_all_tags( trim( $about ) );
- }
-
- // image — фото преподавателя (условно)
- if ( ! empty( $photo ) ) {
- $url = wp_get_attachment_url( (int) $photo );
- if ( $url ) {
- $person['image'] = esc_url( $url );
- }
- }
-
- $result[] = $person;
- }
-
- return $result;
+ return $person;
}
-/* =====================================================================
- Новые сущности для Phase 2 (Local SEO + AI)
- ===================================================================== */
-
/**
* Event — бесплатное пробное занятие.
- * Google Events carousel, высокая конверсия на "пробное занятие [город]".
- *
* @see https://schema.org/Event
*/
function dekart_schema_trial_event(): ?array {
- if ( ! is_page( 2 ) ) {
+ if ( ! is_page_template( 'page-podgotovka-k-shkole.php' ) ) {
return null;
}
$page_url = dekart_schema_page_url();
- // Проверяем, есть ли упоминание бесплатного пробного в подзаголовке
- $subtitle = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' );
+ $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' ) );
}
@@ -1384,6 +1109,8 @@ function dekart_schema_trial_event(): ?array {
),
);
+ $post_id = get_the_ID();
+
return array(
'@type' => 'Event',
'@id' => $page_url . '#trial-event',
@@ -1406,47 +1133,29 @@ function dekart_schema_trial_event(): ?array {
'organizer' => array(
'@id' => home_url( '/' ) . '#organization',
),
- 'image' => get_the_post_thumbnail_url( 2, 'full' ) ?: '',
+ 'image' => ( $post_id && has_post_thumbnail( $post_id ) )
+ ? get_the_post_thumbnail_url( $post_id, 'full' )
+ : '',
);
}
/**
* 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' );
+ $page_url = dekart_schema_page_url();
+ $base_price = carbon_get_post_meta( get_the_ID(), 'pksh_prog_base_price_sub' );
+ $prem_price = carbon_get_post_meta( get_the_ID(), 'pksh_prog_prem_price_sub' );
+ $price_valid = carbon_get_post_meta( get_the_ID(), 'pksh_price_valid_until' );
+ $offers = array();
+ $prices = array();
- $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;
- }
+ foreach ( array( $base_price, $prem_price ) as $raw ) {
+ if ( ! empty( $raw ) && is_string( $raw ) ) {
+ $parsed = dekart_parse_price( $raw );
+ if ( $parsed > 0 ) {
+ $prices[] = $parsed;
}
}
}
@@ -1457,14 +1166,15 @@ function dekart_schema_offer(): ?array {
'@id' => $page_url . '#offer',
'price' => '0',
'priceCurrency' => 'RUB',
- 'availability' => 'https://schema.org/LimitedAvailability',
- 'description' => 'Бесплатное пробное занятие',
- 'category' => 'Бесплатное пробное занятие',
+ 'description' => 'Подготовка к школе — свяжитесь для уточнения стоимости',
+ 'availability' => 'https://schema.org/InStock',
+ 'category' => 'Образовательные услуги',
);
if ( ! empty( $price_valid ) ) {
$offer['priceValidUntil'] = trim( $price_valid );
}
- return $offer;
+ $offers[] = $offer;
+ return $offers;
}
$low = min( $prices );
@@ -1473,22 +1183,21 @@ function dekart_schema_offer(): ?array {
$aggregate = array(
'@type' => 'AggregateOffer',
'@id' => $page_url . '#offer',
- 'priceCurrency' => 'RUB',
+ 'name' => 'Стоимость подготовки к школе',
'lowPrice' => (string) $low,
'highPrice' => (string) $high,
'offerCount' => (string) count( $prices ),
'availability' => 'https://schema.org/InStock',
'category' => 'Образовательные услуги',
'offeredBy' => array(
- '@id' => $page_url . '#localbusiness',
+ '@id' => $page_url . '#local-business',
),
- // UnitPriceSpecification для цен за месяц
'priceSpecification' => array(
'@type' => 'UnitPriceSpecification',
- 'priceCurrency' => 'RUB',
+ 'name' => 'Абонемент за 4 недели',
'minPrice' => (string) $low,
'maxPrice' => (string) $high,
- 'unitCode' => 'MON', // месяц
+ 'unitCode' => 'MON',
'unitText' => 'месяц',
),
);
@@ -1497,9 +1206,9 @@ function dekart_schema_offer(): ?array {
$aggregate['priceValidUntil'] = trim( $price_valid );
}
- $offers = array( $aggregate );
+ $offers[] = $aggregate;
- $subtitle = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' );
+ $subtitle = carbon_get_post_meta( get_the_ID(), 'banner_subtitle' );
if ( ! empty( $subtitle ) && false !== mb_stripos( $subtitle, 'бесплатно' ) ) {
$trial = array(
'@type' => 'Offer',
@@ -1510,7 +1219,7 @@ function dekart_schema_offer(): ?array {
'description' => wp_strip_all_tags( $subtitle ),
'category' => 'Бесплатное пробное занятие',
'offeredBy' => array(
- '@id' => $page_url . '#localbusiness',
+ '@id' => $page_url . '#local-business',
),
);
if ( ! empty( $price_valid ) ) {
@@ -1522,34 +1231,26 @@ function dekart_schema_offer(): ?array {
return $offers;
}
-/* =====================================================================
- Вспомогательные функции
- ===================================================================== */
-
/**
* Парсит цену из форматированной строки (например "7 500 ₽" → 7500).
- * Поддерживает "1 234,56" (comma-decimal) и "1,234.56" (dot-decimal).
- *
- * @param string $raw Строка с ценой.
- * @return float Числовое значение.
*/
function dekart_parse_price( string $raw ): float {
- // Удаляем всё кроме цифр, точки, запятой
$clean = preg_replace( '/[^0-9.,]/', '', $raw );
- // Определяем формат: если последний разделитель — запятая → comma-decimal
- $last_comma = strrpos( $clean, ',' );
+ if ( empty( $clean ) ) {
+ return 0.0;
+ }
+
$last_dot = strrpos( $clean, '.' );
+ $last_comma = strrpos( $clean, ',' );
if ( false !== $last_comma && ( false === $last_dot || $last_comma > $last_dot ) ) {
- // Европейский формат: 1.234,56 → убираем точки, меняем запятую на точку
$clean = str_replace( '.', '', $clean );
$clean = str_replace( ',', '.', $clean );
} else {
- // Стандартный/русский: убираем всё кроме последней точки
$clean = str_replace( ',', '', $clean );
$clean = preg_replace( '/\.(?=.*\.)/', '', $clean );
}
- return floatval( $clean );
+ return (float) $clean;
}
diff --git a/wp-content/themes/dekart/page-podgotovka-k-shkole.php b/wp-content/themes/dekart/page-podgotovka-k-shkole.php
index 202b129..2a236db 100644
--- a/wp-content/themes/dekart/page-podgotovka-k-shkole.php
+++ b/wp-content/themes/dekart/page-podgotovka-k-shkole.php
@@ -1,52 +1,53 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/themes/dekart/page-program-pksh.php b/wp-content/themes/dekart/page-program-pksh.php
new file mode 100644
index 0000000..cd53d6e
--- /dev/null
+++ b/wp-content/themes/dekart/page-program-pksh.php
@@ -0,0 +1,61 @@
+' . "\n";
+ }
+ // Schema.org Article для программы
+ $org_name = get_option( 'branch_og_site_name' ) ?: get_option( 'branch_name', 'Детский центр «Декарт»' );
+ $post_id = get_the_ID();
+ ?>
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/author-eaat.php b/wp-content/themes/dekart/template-parts/author-eaat.php
new file mode 100644
index 0000000..623c809
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/author-eaat.php
@@ -0,0 +1,67 @@
+ 0 ) {
+ $author_title .= ', ' . $years . ' лет на рынке';
+}
+
+// Даты
+$publish_date = $post_id ? get_the_date( 'c', $post_id ) : '';
+$modified_date = $post_id ? get_the_modified_date( 'c', $post_id ) : '';
+$publish_fmt = $publish_date ? date_i18n( 'j F Y', strtotime( $publish_date ) ) : '';
+$modified_fmt = $modified_date ? date_i18n( 'j F Y', strtotime( $modified_date ) ) : '';
+
+if ( empty( $author_name ) && empty( $publish_fmt ) && empty( $modified_fmt ) && $years < 1 ) {
+ return;
+}
+?>
+
+
+
+
+
+ Автор:
+
+
+ ,
+
+
+
+
+
+ Опубликовано:
+
+
+
+
+
+ Обновлено:
+
+
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/pksh/author-info.php b/wp-content/themes/dekart/template-parts/pksh/author-info.php
new file mode 100644
index 0000000..b22a7ee
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/pksh/author-info.php
@@ -0,0 +1,63 @@
+get_author_name();
+$publish_date = $data->get_page_publish_date();
+$modified_date = $data->get_page_modified_date();
+
+$author_years = $data->get_years_on_market();
+
+// Должность автора — хардкор + авто-расчёт лет на рынке
+$author_title = 'Образовательный центр с гослицензией';
+if ( $author_years > 0 ) {
+ $author_title .= ', ' . $author_years . ' лет на рынке';
+}
+
+// Форматируем даты для отображения
+$publish_formatted = $publish_date ? date_i18n( 'j F Y', strtotime( $publish_date ) ) : '';
+$modified_formatted = $modified_date ? date_i18n( 'j F Y', strtotime( $modified_date ) ) : '';
+
+// Если нет автора и нет дат — не выводим секцию
+if ( empty( $author_name ) && empty( $publish_formatted ) && empty( $modified_formatted ) && $author_years < 1 ) {
+ return;
+}
+?>
+
+
+
+
+
+ Автор:
+
+
+ ,
+
+
+
+
+
+
+ Опубликовано:
+
+
+
+
+
+
+ Обновлено:
+
+
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/pksh/external-reviews.php b/wp-content/themes/dekart/template-parts/pksh/external-reviews.php
index 6630db5..ddd60a8 100644
--- a/wp-content/themes/dekart/template-parts/pksh/external-reviews.php
+++ b/wp-content/themes/dekart/template-parts/pksh/external-reviews.php
@@ -1,7 +1,8 @@
get_city_prep();
+
+$platforms = array(
+ 'yandex' => array(
+ 'url' => get_option( 'branch_ext_yandex', '' ),
+ 'icon' => 'Я',
+ 'name' => 'Яндекс.Карты',
+ 'rating' => '4.8',
+ ),
+ '2gis' => array(
+ 'url' => get_option( 'branch_ext_2gis', '' ),
+ 'icon' => '2ГИС',
+ 'name' => '2ГИС',
+ 'rating' => '4.7',
+ ),
+ 'vk' => array(
+ 'url' => get_option( 'branch_ext_vk', '' ),
+ 'icon' => 'ВК',
+ 'name' => 'ВКонтакте',
+ 'rating' => '4.9',
+ ),
+ 'otzovik' => array(
+ 'url' => get_option( 'branch_ext_otzovik', '' ),
+ 'icon' => 'O',
+ 'name' => 'Otzovik',
+ 'rating' => '4.6',
+ ),
+);
+
+// Фильтруем: только площадки с заполненной ссылкой
+$active = array_filter( $platforms, function( $p ) {
+ return ! empty( $p['url'] );
+} );
+
+if ( empty( $active ) ) {
+ return;
+}
?>
diff --git a/wp-content/themes/dekart/template-parts/pksh/hero.php b/wp-content/themes/dekart/template-parts/pksh/hero.php
index 5c8b449..5663556 100644
--- a/wp-content/themes/dekart/template-parts/pksh/hero.php
+++ b/wp-content/themes/dekart/template-parts/pksh/hero.php
@@ -22,6 +22,7 @@ $opt_phone = $data->get_phone();
$hero_phone_clean = $data->get_phone_clean();
$banner_img_id = $data->get_banner_image_id();
$hero_metrics = $data->get_social_counters();
+$hero_definition = $data->get_hero_definition();
?>
@@ -30,12 +31,18 @@ $hero_metrics = $data->get_social_counters();
+
- — центр
+ — центр
+
-
+
+
+
+
+
get_subtitle_footnote_link();
diff --git a/wp-content/themes/dekart/template-parts/pksh/how-it-works.php b/wp-content/themes/dekart/template-parts/pksh/how-it-works.php
index 9aaa972..383ea1d 100644
--- a/wp-content/themes/dekart/template-parts/pksh/how-it-works.php
+++ b/wp-content/themes/dekart/template-parts/pksh/how-it-works.php
@@ -1,6 +1,6 @@
get_city_clean();
+$city_prep = $data->get_city_prep();
+$answer = $data->get_geo_answer_howitworks();
+$q3 = $data->get_geo_question_3();
+$hero_img_id = $data->get_banner_image_id();
?>
Дорого?
-
От get_premium_program()['price_single'] ); ?> за занятие. Первое — бесплатно.
+
От get_premium_program()['price_single'] ); ?> за занятие. Первое — бесплатно.
+
+
+
+
+
+
+
+
+
+
+
+
+ 'geo-answer-block__img',
+ 'loading' => 'lazy',
+ 'decoding' => 'async',
+ 'alt' => 'Подготовка к школе — занятия для детей 5–7 лет',
+ ) ); ?>
+
+
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/pksh/programs.php b/wp-content/themes/dekart/template-parts/pksh/programs.php
index 2efce4e..634bfa4 100644
--- a/wp-content/themes/dekart/template-parts/pksh/programs.php
+++ b/wp-content/themes/dekart/template-parts/pksh/programs.php
@@ -1,6 +1,6 @@
get_city_clean();
-$base = $data->get_base_program();
-$prem = $data->get_premium_program();
+$base = $data->get_base_program();
+$prem = $data->get_premium_program();
+$answer = $data->get_geo_answer_programs();
+$q1 = $data->get_geo_question_1();
?>
diff --git a/wp-content/themes/dekart/template-parts/pksh/trust-authority.php b/wp-content/themes/dekart/template-parts/pksh/trust-authority.php
index 9f2bfe9..e536d0c 100644
--- a/wp-content/themes/dekart/template-parts/pksh/trust-authority.php
+++ b/wp-content/themes/dekart/template-parts/pksh/trust-authority.php
@@ -34,6 +34,11 @@ $city_gen = $data->get_city_genitive();
Если через 3 месяца ребёнок не начнёт читать — вернём деньги и продолжим обучение бесплатно.
+
diff --git a/wp-content/themes/dekart/template-parts/program-pksh/disclaimer.php b/wp-content/themes/dekart/template-parts/program-pksh/disclaimer.php
new file mode 100644
index 0000000..0807c57
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/program-pksh/disclaimer.php
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+ Если вы заметили ошибку в тексте программы, пожалуйста, сообщите нам — мы оперативно внесём исправления.
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/program-pksh/eeat-links.php b/wp-content/themes/dekart/template-parts/program-pksh/eeat-links.php
new file mode 100644
index 0000000..ebdc1f9
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/program-pksh/eeat-links.php
@@ -0,0 +1,26 @@
+
+
+
+
+
+ —
+ образовательная организация, осуществляющая образовательную деятельность на основании
+ государственной лицензии.
+ Полные сведения об организации доступны в разделе
+ «Сведения об образовательной организации» .
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/program-pksh/header.php b/wp-content/themes/dekart/template-parts/program-pksh/header.php
new file mode 100644
index 0000000..ee38f6e
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/program-pksh/header.php
@@ -0,0 +1,23 @@
+
+
+
+
Официальный документ
+
Дополнительная общеразвивающая программа «Подготовка к школе»
+
+ Настоящая программа разработана в соответствии с Федеральным законом от 29.12.2012 № 273-ФЗ
+ «Об образовании в Российской Федерации» и определяет содержание образовательной деятельности
+ по подготовке детей старшего дошкольного возраста (5–7 лет) к обучению в школе.
+ Программа является официальным документом Детского центра «Декарт».
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/program-pksh/office-info.php b/wp-content/themes/dekart/template-parts/program-pksh/office-info.php
new file mode 100644
index 0000000..d964463
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/program-pksh/office-info.php
@@ -0,0 +1,50 @@
+
+
+
+
+
Ознакомиться с программой в офисе
+
Действующая образовательная программа находится в офисе и всегда доступна для ознакомления.
+
+
+
+ Адрес:
+
+
+
+
+
+
+
+
+ Часы приёма:
+
+
+
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/program-pksh/program-content.php b/wp-content/themes/dekart/template-parts/program-pksh/program-content.php
new file mode 100644
index 0000000..b9b0444
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/program-pksh/program-content.php
@@ -0,0 +1,24 @@
+
+
+
+
Текст программы
+
+
+
+
+
diff --git a/wp-content/themes/dekart/template-parts/program-pksh/service-link.php b/wp-content/themes/dekart/template-parts/program-pksh/service-link.php
new file mode 100644
index 0000000..2f6caea
--- /dev/null
+++ b/wp-content/themes/dekart/template-parts/program-pksh/service-link.php
@@ -0,0 +1,29 @@
+
+
+
+
+
Записаться на занятия
+
Данная программа реализуется в рамках курса подготовки к школе. Запишите ребёнка на бесплатное пробное занятие и познакомьтесь с педагогами.
+
+
+
+
+
+