get_charset_collate();
$prefix = $wpdb->prefix;
require_once( ABSPATH . ‘wp-admin/includes/upgrade.php’ );
$tables = [
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_user_profile (
user_id BIGINT UNSIGNED NOT NULL, nickname VARCHAR(100) DEFAULT », age INT DEFAULT NULL, engagement_level VARCHAR(50) DEFAULT », bio TEXT, platform_goal VARCHAR(100) DEFAULT », languages LONGTEXT, current_vibe_id INT DEFAULT NULL, vibe_updated_at DATETIME DEFAULT NULL, PRIMARY KEY (user_id), FOREIGN KEY (user_id) REFERENCES {$prefix}users(ID) ON DELETE CASCADE
) $charset_collate;»,
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_user_locations (
user_id BIGINT UNSIGNED NOT NULL, last_coordinates POINT DEFAULT NULL, habitat_zones LONGTEXT, saved_venues LONGTEXT, PRIMARY KEY (user_id), FOREIGN KEY (user_id) REFERENCES {$prefix}users(ID) ON DELETE CASCADE
) $charset_collate;»,
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_user_beverage_preferences (
user_id BIGINT UNSIGNED NOT NULL, spirits LONGTEXT, wine LONGTEXT, beer_cider LONGTEXT, fermented LONGTEXT, PRIMARY KEY (user_id), FOREIGN KEY (user_id) REFERENCES {$prefix}users(ID) ON DELETE CASCADE
) $charset_collate;»,
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_user_lifestyle (
user_id BIGINT UNSIGNED NOT NULL, favorite_time VARCHAR(50) DEFAULT », occupation VARCHAR(100) DEFAULT », event_format VARCHAR(50) DEFAULT », outing_frequency VARCHAR(50) DEFAULT », venue_choice VARCHAR(50) DEFAULT », company_pref VARCHAR(50) DEFAULT », venue_atmosphere VARCHAR(50) DEFAULT », comm_style VARCHAR(50) DEFAULT », rating_style VARCHAR(50) DEFAULT », explorer_type VARCHAR(50) DEFAULT », PRIMARY KEY (user_id), FOREIGN KEY (user_id) REFERENCES {$prefix}users(ID) ON DELETE CASCADE
) $charset_collate;»,
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_user_media_prompts (
user_id BIGINT UNSIGNED NOT NULL, photos LONGTEXT, interest_tags LONGTEXT, text_prompts LONGTEXT, spotify_anthem VARCHAR(255) DEFAULT », social_links LONGTEXT, PRIMARY KEY (user_id), FOREIGN KEY (user_id) REFERENCES {$prefix}users(ID) ON DELETE CASCADE
) $charset_collate;»,
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_vibes_dictionary (
id INT UNSIGNED NOT NULL AUTO_INCREMENT, category VARCHAR(50) NOT NULL, code VARCHAR(10) NOT NULL, name VARCHAR(100) NOT NULL, emoji VARCHAR(10) NOT NULL, description TEXT, PRIMARY KEY (id), UNIQUE KEY code (code)
) $charset_collate;»,
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_user_vibe_history (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, vibe_id INT UNSIGNED NOT NULL, set_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY user_id (user_id), FOREIGN KEY (user_id) REFERENCES {$prefix}users(ID) ON DELETE CASCADE, FOREIGN KEY (vibe_id) REFERENCES {$prefix}st_al_vibes_dictionary(id) ON DELETE RESTRICT
) $charset_collate;»,
«CREATE TABLE IF NOT EXISTS {$prefix}st_al_vibe_recommendation_rules (
vibe_id INT UNSIGNED NOT NULL, target_beverage_category LONGTEXT, target_venue_atmosphere LONGTEXT, alternative_options LONGTEXT, PRIMARY KEY (vibe_id), FOREIGN KEY (vibe_id) REFERENCES {$prefix}st_al_vibes_dictionary(id) ON DELETE CASCADE
) $charset_collate;»,
// Кастомная таблица для данных Summer Menu (изолирует wp_options)
«CREATE TABLE IF NOT EXISTS {$prefix}st_sm_user_data (
phone VARCHAR(50) NOT NULL, checks LONGTEXT, completed LONGTEXT, notes LONGTEXT, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (phone)
) $charset_collate;»
];
foreach ($tables as $sql) {
dbDelta( $sql );
}
if ( ! $wpdb->get_var( «SELECT COUNT(*) FROM {$prefix}st_al_vibes_dictionary» ) ) {
st_al_seed_vibes( «{$prefix}st_al_vibes_dictionary» );
}
update_option( ‘st_global_tables_installed_v3’, 1 );
}
// ==============================================================================
// 2. ФОРМА ПУБЛИКАЦИИ:
Для публикации новых заметок в базу знаний платформы необходимо авторизоваться.
function render_comic_frontend_form() {
ob_start();
if ( ! is_user_logged_in() ) {
return st_get_auth_prompt_html(‘Для публикации новых заметок в базу знаний платформы необходимо авторизоваться.’);
}
?>
‘Ошибка безопасности. Обновите страницу.’ ) );
}
$pin = isset( $_POST[‘st_admin_pass’] ) ? sanitize_text_field( $_POST[‘st_admin_pass’] ) : »;
if ( $pin !== ST_ADMIN_PIN ) {
wp_send_json_error( array( ‘message’ => ‘Неверный ПИН-код.’ ) );
}
$title = sanitize_text_field( $_POST[‘st_title’] );
$content = wp_kses_post( $_POST[‘st_description’] );
$category_id = intval( $_POST[‘st_category’] );
if ( empty( $title ) ) {
wp_send_json_error( array( ‘message’ => ‘Заполните заголовок.’ ) );
}
if ( $category_id === 0 ) {
wp_send_json_error( array( ‘message’ => ‘Выберите категорию.’ ) );
}
$post_data = array(
‘post_title’ => $title,
‘post_content’ => $content,
‘post_status’ => ‘publish’,
‘post_type’ => ‘post’,
‘post_category’ => array( $category_id ),
);
$post_id = wp_insert_post( $post_data, true );
if ( is_wp_error( $post_id ) ) {
wp_send_json_error( array( ‘message’ => ‘Ошибка создания записи: ‘ . $post_id->get_error_message() ) );
} else {
st_send_telegram_notification( $post_id, $title, $category_id );
wp_send_json_success( array( ‘message’ => ‘ Запись опубликована! ID: ‘ . $post_id ) );
}
}
function st_send_telegram_notification( $post_id, $title, $category_id ) {
$category_name = ‘Без категории’;
$cat = get_term( $category_id, ‘category’ );
if ( $cat && ! is_wp_error( $cat ) ) {
$category_name = $cat->name;
}
$post_url = get_permalink( $post_id );
$message = » *Spirit Trade Distribution | Новая запись*\n\n»;
$message .= » *{$title}*\n»;
$message .= » Категория: {$category_name}\n»;
$message .= » [Читать запись]({$post_url})\n»;
$message .= » » . current_time( ‘d.m.Y H:i’ );
$url = «https://api.telegram.org/bot» . ST_TG_BOT_TOKEN . «/sendMessage»;
$args = array(
‘body’ => array(
‘chat_id’ => ST_TG_CHAT_ID,
‘text’ => $message,
‘parse_mode’ => ‘Markdown’,
‘disable_web_page_preview’ => false,
),
‘timeout’ => 5,
);
$response = wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
error_log( ‘Telegram notification error: ‘ . $response->get_error_message() );
}
}
// ==============================================================================
// 3. БАЗА ЗНАНИЙ:
add_shortcode(‘knowledge_base_map’, ‘st_render_knowledge_base_map’);
function st_render_knowledge_base_map() {
ob_start();
$categories = get_categories(array(‘hide_empty’ => 0));
$sidebar_html = »;
$colors = [‘#ffde59’, ‘#c1ff00’, ‘#38b6ff’, ‘#ff914d’, ‘#cb6ce6’, ‘#ff6b6b’, ‘#48dbfb’];
foreach ($categories as $cat) {
$raw_name = esc_html($cat->name);
$number = »;
$rest_of_name = $raw_name;
if (preg_match(‘/^\[(\d+)\]\s*(.*)$/u’, $raw_name, $matches)) {
$number = $matches[1];
$rest_of_name = $matches[2];
}
$parts = explode(‘|’, $rest_of_name);
$main_title = trim($parts[0]);
$sub_title = isset($parts[1]) ? trim($parts[1]) : »;
$hash = abs(crc32($main_title));
$bg_color = $colors[$hash % count($colors)];
$icon = !empty($number) ? $number : mb_substr($main_title, 0, 1, ‘UTF-8’);
$desc = trim($cat->description);
$btn_inner = ‘
‘;
$btn_inner .= ‘
if ($sub_title) {
$btn_inner .= ‘для: ‘ . $sub_title . ‘‘;
}
$btn_inner .= ‘
‘;
$sidebar_html .= sprintf(
‘‘,
esc_attr($cat->term_id),
esc_attr($cat->count),
esc_attr($number),
esc_attr($main_title),
esc_attr($sub_title),
esc_attr($desc),
esc_attr($raw_name),
$btn_inner
);
}
?>
‘;
$html .= ‘
‘;
$categories = get_categories(array(‘hide_empty’ => true));
$branch_side = ‘left’;
foreach ($categories as $index => $cat) {
$posts = get_posts(array(
‘category’ => $cat->term_id,
‘numberposts’ => 20,
‘post_status’ => ‘publish’
));
if ($posts) {
$branch_side = ($index % 2 === 0) ? ‘left’ : ‘right’;
$html .= ‘
‘;
$html .= ‘
‘;
$html .= ‘
$hidden_list = ‘
foreach ($posts as $post_index => $p) {
$post_url = esc_url(get_permalink($p->ID));
$post_title = esc_html($p->post_title);
$post_date = get_the_date(‘d.m.Y’, $p->ID);
$is_top = ($post_index % 2 === 0);
$position_class = $is_top ? ‘st-bud-top’ : ‘st-bud-bottom’;
$html .= ‘‘;
$html .= ‘ ‘;
$html .= ‘‘;
$hidden_list .= ‘
‘;
}
$hidden_list .= ‘
‘;
$html .= ‘
‘;
$html .= ‘
‘;
$html .= ‘
‘;
$html .= ‘
‘;
$html .= ‘
‘;
}
}
$html .= ‘
‘;
$html .= ‘
‘;
$html .= ‘
‘; // Конец st-vine-container
return $html;
}
function st_get_vine_buds_assets() {
return ‘
‘;
}
// ==============================================================================
// 5. SUMMER MENU REST API (БЕЗОПАСНОСТЬ)
// ==============================================================================
add_action( ‘rest_api_init’, function () {
register_rest_route( ‘summer-menu/v1’, ‘/get-user-data’, array(
‘methods’ => ‘POST’,
‘callback’ => ‘summer_menu_api_get_data’,
‘permission_callback’ => ‘__return_true’
));
register_rest_route( ‘summer-menu/v1’, ‘/save-user-data’, array(
‘methods’ => ‘POST’,
‘callback’ => ‘summer_menu_api_save_data’,
‘permission_callback’ => ‘__return_true’
));
});
function summer_menu_api_save_data( $request ) {
global $wpdb;
$params = $request->get_json_params();
$user_phone = isset($params[‘user_phone’]) ? sanitize_text_field($params[‘user_phone’]) : »;
if (empty($user_phone)) {
return rest_ensure_response( array( ‘success’ => false, ‘message’ => ‘Телефон не указан’ ) );
}
// Сохранение индивидуальных данных через REPLACE INTO в кастомную таблицу (исключает переполнение wp_options)
$table = $wpdb->prefix . ‘st_sm_user_data’;
$wpdb->replace( $table, [
‘phone’ => $user_phone,
‘checks’ => wp_json_encode($params[‘checks’] ?? []),
‘completed’ => wp_json_encode($params[‘completed’] ?? []),
‘notes’ => wp_json_encode($params[‘notes’] ?? []),
]);
// Глобальные настройки (можно хранить в wp_options, так как это общие данные)
if ( isset($params[‘basketsData’]) ) update_option( ‘sm_global_baskets’, wp_json_encode($params[‘basketsData’]) );
if ( isset($params[‘weeksCalendar’]) ) update_option( ‘sm_global_calendar’, wp_json_encode($params[‘weeksCalendar’]) );
if ( isset($params[‘fullRecipes’]) ) update_option( ‘sm_global_recipes’, wp_json_encode($params[‘fullRecipes’]) );
if ( isset($params[‘dessertsList’]) ) update_option( ‘sm_global_desserts’, wp_json_encode($params[‘dessertsList’]) );
if ( isset($params[‘globalInfo’]) ) update_option( ‘sm_global_info’, wp_json_encode($params[‘globalInfo’]) );
return rest_ensure_response( array( ‘success’ => true, ‘message’ => ‘Дані успішно збережено в базі’ ) );
}
function summer_menu_api_get_data( $request ) {
global $wpdb;
$params = $request->get_json_params();
$user_phone = isset($params[‘user_phone’]) ? sanitize_text_field($params[‘user_phone’]) : »;
$table = $wpdb->prefix . ‘st_sm_user_data’;
$user_data = $wpdb->get_row($wpdb->prepare(«SELECT checks, completed, notes FROM $table WHERE phone = %s», $user_phone), ARRAY_A);
$response_data = array(
‘checks’ => $user_data && !empty($user_data[‘checks’]) ? $user_data[‘checks’] : ‘{}’,
‘completed’ => $user_data && !empty($user_data[‘completed’]) ? $user_data[‘completed’] : ‘{}’,
‘notes’ => $user_data && !empty($user_data[‘notes’]) ? $user_data[‘notes’] : ‘{}’,
‘basketsData’ => get_option( ‘sm_global_baskets’, null ),
‘weeksCalendar’ => get_option( ‘sm_global_calendar’, null ),
‘fullRecipes’ => get_option( ‘sm_global_recipes’, null ),
‘dessertsList’ => get_option( ‘sm_global_desserts’, null ),
‘globalInfo’ => get_option( ‘sm_global_info’, null ),
);
return rest_ensure_response( $response_data );
}
// ==============================================================================
// 6. МОДУЛЬ AL-ТИНДЕР: АНКЕТА ПОКУПАТЕЛЯ, ВАЙБЫ, РЕКОМЕНДАЦИИ (ОБНОВЛЕНО)
// ==============================================================================
function st_al_seed_vibes( $table ) {
global $wpdb;
$vibes = [
// Положительные (20)
[‘category’ => ‘Положительные’, ‘code’ => ‘П1’, ‘name’ => ‘Радость’, ’emoji’ => », ‘description’ => ‘Чувство удовольствия, веселья и наслаждения позитивными моментами.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П2’, ‘name’ => ‘Любовь’, ’emoji’ => », ‘description’ => ‘Глубокая привязанность, нежность и забота о другом человеке.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П3’, ‘name’ => ‘Удовлетворение’, ’emoji’ => », ‘description’ => ‘Ощущение успеха, гармонии и довольства достигнутыми результатами.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П4’, ‘name’ => ‘Восхищение’, ’emoji’ => », ‘description’ => ‘Сильное удивление, восторг и вдохновение чем‑то или кем‑то.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П5’, ‘name’ => ‘Благодарность’, ’emoji’ => », ‘description’ => ‘Чувство признательности за полученное, уважение и осознание ценности.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П6’, ‘name’ => ‘Умиротворение’, ’emoji’ => », ‘description’ => ‘Внутреннее спокойствие, доверие к миру и ощущение стабильности.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П7’, ‘name’ => ‘Спокойствие’, ’emoji’ => », ‘description’ => ‘Отсутствие тревог, расслабленность и ощущение контроля над ситуацией.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П8’, ‘name’ => ‘Довольство’, ’emoji’ => », ‘description’ => ‘Приятные ощущения, наслаждение и блаженство от текущего момента.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П9’, ‘name’ => ‘Воодушевление’, ’emoji’ => », ‘description’ => ‘Прилив энергии, желание действовать и мотивация к свершениям.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П10’, ‘name’ => ‘Вдохновение’, ’emoji’ => », ‘description’ => ‘Озарение, творческий подъём и стремление создавать новое.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П11’, ‘name’ => ‘Уверенность’, ’emoji’ => », ‘description’ => ‘Ощущение собственной силы, смелость и решительность.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П12’, ‘name’ => ‘Надежда’, ’emoji’ => », ‘description’ => ‘Вера в лучшее будущее, позитивные ожидания и оптимистичный настрой.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П13’, ‘name’ => ‘Лёгкость’, ’emoji’ => », ‘description’ => ‘Чувство свободы от напряжения, беззаботность и непринуждённость.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П14’, ‘name’ => ‘Свобода’, ’emoji’ => », ‘description’ => ‘Ощущение независимости, отсутствия ограничений и возможности выбора.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П15’, ‘name’ => ‘Азарт’, ’emoji’ => », ‘description’ => ‘Стремление испытать свои силы, вызов и жажда победы.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П16’, ‘name’ => ‘Интерес’, ’emoji’ => », ‘description’ => ‘Любопытство, желание исследовать и узнавать новое.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П17’, ‘name’ => ‘Преданность’, ’emoji’ => », ‘description’ => ‘Верность, готовность поддерживать и неразрывная связь с близкими.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П18’, ‘name’ => ‘Дружелюбие’, ’emoji’ => », ‘description’ => ‘Доброжелательность, открытость и желание делиться радостью.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П19’, ‘name’ => ‘Принятие’, ’emoji’ => », ‘description’ => ‘Смирение перед реальностью, согласие с собой и готовность отпускать.’],
[‘category’ => ‘Положительные’, ‘code’ => ‘П20’, ‘name’ => ‘Оптимизм’, ’emoji’ => », ‘description’ => ‘Уверенность в хорошем исходе, вера в себя и светлый взгляд в будущее.’],
// Отрицательные (11)
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О1’, ‘name’ => ‘Страх’, ’emoji’ => », ‘description’ => ‘Опасение перед угрозой или неизвестностью, боязнь потерять контроль.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О2’, ‘name’ => ‘Тревога’, ’emoji’ => », ‘description’ => ‘Напряжённое ожидание неприятностей, беспокойство о будущем.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О3’, ‘name’ => ‘Паника’, ’emoji’ => », ‘description’ => ‘Крайняя тревога, хаос в мыслях и чувство полной беспомощности.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О4’, ‘name’ => ‘Гнев’, ’emoji’ => », ‘description’ => ‘Сильное раздражение, возмущение несправедливостью и готовность к активному противостоянию.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О5’, ‘name’ => ‘Обида’, ’emoji’ => », ‘description’ => ‘Чувство несправедливости, разочарование и душевная боль от действий других.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О6’, ‘name’ => ‘Зависть’, ’emoji’ => », ‘description’ => ‘Недовольство чужим успехом, желание обладать тем, что есть у другого.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О7’, ‘name’ => ‘Ревность’, ’emoji’ => », ‘description’ => ‘Страх потерять внимание или любовь, подозрительность и собственнические чувства.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О8’, ‘name’ => ‘Стыд’, ’emoji’ => », ‘description’ => ‘Ощущение унижения, неловкости и опасение осуждения окружающими.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О9’, ‘name’ => ‘Вина’, ’emoji’ => », ‘description’ => ‘Сожаление о совершённой ошибке, раскаяние и желание всё исправить.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О10’, ‘name’ => ‘Презрение’, ’emoji’ => », ‘description’ => ‘Чувство превосходства, высокомерие и отстранённость от тех, кого считают ниже.’],
[‘category’ => ‘Отрицательные’, ‘code’ => ‘О11’, ‘name’ => ‘Ненависть’, ’emoji’ => », ‘description’ => ‘Крайнее отторжение, враждебность и отвращение к кому‑либо.’],
// Смешанные (7)
[‘category’ => ‘Смешанные’, ‘code’ => ‘С1’, ‘name’ => ‘Смущение’, ’emoji’ => », ‘description’ => ‘Растерянность, неуверенность из‑за неожиданной или неловкой ситуации.’],
[‘category’ => ‘Смешанные’, ‘code’ => ‘С2’, ‘name’ => ‘Неловкость’, ’emoji’ => », ‘description’ => ‘Дискомфорт в общении, стеснение и ощущение «не на своём месте».’],
[‘category’ => ‘Смешанные’, ‘code’ => ‘С3’, ‘name’ => ‘Амбивалентность’, ’emoji’ => », ‘description’ => ‘Одновременное существование противоположных чувств или желаний.’],
[‘category’ => ‘Смешанные’, ‘code’ => ‘С4’, ‘name’ => ‘Шок’, ’emoji’ => », ‘description’ => ‘Оцепенение, внезапное потрясение и неспособность сразу осознать произошедшее.’],
[‘category’ => ‘Смешанные’, ‘code’ => ‘С5’, ‘name’ => ‘Изумление’, ’emoji’ => », ‘description’ => ‘Крайняя степень удивления, восхищённое потрясение от невероятного.’],
[‘category’ => ‘Смешанные’, ‘code’ => ‘С6’, ‘name’ => ‘Противоречивость’, ’emoji’ => », ‘description’ => ‘Внутренний конфликт между разными оценками или решениями.’],
[‘category’ => ‘Смешанные’, ‘code’ => ‘С7’, ‘name’ => ‘Колебание’, ’emoji’ => », ‘description’ => ‘Неуверенность, сомнения и трудность сделать окончательный выбор.’],
];
foreach ( $vibes as $v ) {
$wpdb->insert( $table, $v );
}
}
add_shortcode( ‘al_profile_setup’, ‘st_al_render_profile_form’ );
function st_al_render_profile_form() {
if ( ! is_user_logged_in() ) {
return st_get_auth_prompt_html(‘Чтобы заполнить анкету гурмана, находить идеальные заведения и свою компанию, вам необходимо войти в систему.’);
}
$current_user = wp_get_current_user();
$user_email = $current_user->user_email;
$user_nickname = $current_user->display_name ?: $current_user->user_login;
ob_start();
?>
‘;
}
ob_start();
?>
prefix;
$wpdb->update( $prefix . ‘st_al_user_profile’, [ ‘current_vibe_id’ => $vibe_id, ‘vibe_updated_at’ => current_time( ‘mysql’ ) ], [ ‘user_id’ => $user_id ] );
$wpdb->insert( $prefix . ‘st_al_user_vibe_history’, [ ‘user_id’ => $user_id, ‘vibe_id’ => $vibe_id, ‘set_at’ => current_time( ‘mysql’ ) ] );
wp_send_json_success( ‘ok’ );
}
// ==============================================================================
// УНИВЕРСАЛЬНЫЙ ЭКРАН АВТОРИЗАЦИИ ДЛЯ НЕАВТОРИЗОВАННЫХ ПОЛЬЗОВАТЕЛЕЙ
// ==============================================================================
function st_get_auth_prompt_html( $message = ‘Для доступа к этому разделу необходимо авторизоваться.’ ) {
$login_url = wp_login_url( get_permalink() );
$register_url = wp_registration_url();
ob_start();
?>
prefix;
$exclude_ids = [];
if ( isset($_POST[‘exclude_ids’]) ) {
$decoded = json_decode(stripslashes($_POST[‘exclude_ids’]), true);
if ( is_array($decoded) ) $exclude_ids = array_map(‘intval’, $decoded);
}
$exclude_sql = «»;
if ( !empty($exclude_ids) ) {
$exclude_ids_str = implode(‘,’, array_map(‘intval’, $exclude_ids));
$exclude_sql = «AND p.user_id NOT IN ($exclude_ids_str)»;
}
$query = »
SELECT
p.user_id, p.nickname, p.age, p.engagement_level, p.bio, p.platform_goal, p.languages,
m.photos, m.interest_tags, m.spotify_anthem, m.text_prompts,
b.spirits, b.wine, b.beer_cider, b.fermented,
l.favorite_time, l.occupation, l.event_format, l.outing_frequency, l.venue_choice, l.company_pref, l.venue_atmosphere, l.comm_style, l.rating_style, l.explorer_type,
v.emoji, v.name as vibe_name, v.description as vibe_description
FROM {$prefix}st_al_user_profile p
LEFT JOIN {$prefix}st_al_user_media_prompts m ON p.user_id = m.user_id
LEFT JOIN {$prefix}st_al_user_beverage_preferences b ON p.user_id = b.user_id
LEFT JOIN {$prefix}st_al_user_lifestyle l ON p.user_id = l.user_id
LEFT JOIN {$prefix}st_al_vibes_dictionary v ON p.current_vibe_id = v.id
$exclude_sql
ORDER BY RAND()
LIMIT 10
«;
$users = $wpdb->get_results( $query, ARRAY_A );
$formatted_users = [];
if ($users) {
foreach ( $users as $u ) {
$photos = json_decode( $u[‘photos’], true );
$photos_array = ( is_array($photos) && !empty($photos) ) ? $photos : [‘https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?q=80&w=350&h=600&fit=crop’];
$tags = json_decode( $u[‘interest_tags’], true ) ?: [];
if ( !empty($u[‘platform_goal’]) ) array_unshift($tags, ‘ ‘ . $u[‘platform_goal’]);
$vibe = !empty($u[‘vibe_name’]) ? [‘name’ => $u[‘vibe_name’], ’emoji’ => $u[’emoji’], ‘description’ => $u[‘vibe_description’]] : null;
$spirits = json_decode($u[‘spirits’], true) ?: [];
$wine = json_decode($u[‘wine’], true) ?: [];
$beer = json_decode($u[‘beer_cider’], true) ?: [];
$raw_prefs = array_merge($spirits, $wine, $beer);
$extra_fields = [];
if (!empty($u[‘engagement_level’])) $extra_fields[‘Уровень’] = $u[‘engagement_level’];
$langs = json_decode($u[‘languages’], true);
if (!empty($langs)) $extra_fields[‘Языки’] = implode(‘, ‘, $langs);
if (!empty($spirits)) $extra_fields[‘Крепкий алкоголь’] = implode(‘, ‘, $spirits);
if (!empty($wine)) $extra_fields[‘Вино’] = implode(‘, ‘, $wine);
if (!empty($beer)) $extra_fields[‘Пиво и сидр’] = implode(‘, ‘, $beer);
$ferm = json_decode($u[‘fermented’], true);
if (!empty($ferm)) $extra_fields[‘Ферментированные’] = implode(‘, ‘, $ferm);
$lifestyle_map = [
‘favorite_time’ => ‘Любимое время суток’, ‘occupation’ => ‘Сфера’,
‘event_format’ => ‘Формат ивентов’, ‘outing_frequency’ => ‘Частота выходов’,
‘venue_choice’ => ‘Выбор заведения’, ‘company_pref’ => ‘Компания’,
‘venue_atmosphere’ => ‘Атмосфера’, ‘comm_style’ => ‘Стиль общения’,
‘rating_style’ => ‘Оценка’, ‘explorer_type’ => ‘Тип исследователя’
];
foreach ($lifestyle_map as $db_key => $label) {
if (!empty($u[$db_key])) $extra_fields[$label] = $u[$db_key];
}
$prompts = json_decode($u[‘text_prompts’], true);
if (!empty($prompts) && is_array($prompts)) {
foreach ($prompts as $q => $a) {
if (!empty($a)) $extra_fields[‘ ‘ . $q] = $a;
}
}
$formatted_users[] = [
‘id’ => $u[‘user_id’],
‘name’ => !empty($u[‘nickname’]) ? $u[‘nickname’] : ‘Аноним’,
‘age’ => !empty($u[‘age’]) ? $u[‘age’] : »,
‘bio’ => !empty($u[‘bio’]) ? wp_trim_words($u[‘bio’], 15) : ‘Гурман исследует новые вкусы…’,
‘full_bio’ => !empty($u[‘bio’]) ? $u[‘bio’] : »,
‘photos’ => $photos_array,
‘tags’ => array_slice($tags, 0, 4),
‘vibe’ => $vibe,
‘raw_prefs’ => $raw_prefs,
‘extra_fields’=> $extra_fields
];
}
}
wp_send_json_success( $formatted_users );
}
add_shortcode( ‘al_tinder_search’, ‘st_render_tinder_search’ );
function st_render_tinder_search() {
ob_start();
?>
ID ) ) {
return $redirect_to;
}
// Перенаправляем всех на https://i.spirittrade.kz/at-reg-pp/
$custom_url = home_url( ‘/at-reg-pp/’ ); // ← заменено на вашу страницу
return $custom_url;
}
// ==============================================================================
// ПРЯМОЙ ЗАГРУЗЧИК ФАЙЛОВ ДЛЯ АНКЕТЫ (ШАГ 4)
// ==============================================================================
add_action( ‘wp_ajax_st_upload_user_photo’, ‘st_ajax_upload_user_photo’ );
function st_ajax_upload_user_photo() {
// Проверка авторизации
if ( ! is_user_logged_in() ) {
wp_send_json_error( array( ‘message’ => ‘Вы не авторизованы’ ) );
}
// Проверка nonce
if ( ! isset( $_POST[‘nonce’] ) || ! wp_verify_nonce( $_POST[‘nonce’], ‘st_al_nonce_action’ ) ) {
wp_send_json_error( array( ‘message’ => ‘Неверный nonce’ ) );
}
$user_id = get_current_user_id();
if ( empty( $_FILES[‘photo’] ) || $_FILES[‘photo’][‘error’] !== UPLOAD_ERR_OK ) {
wp_send_json_error( array( ‘message’ => ‘Файл не загружен’ ) );
}
$file = $_FILES[‘photo’];
$allowed_mimes = array( ‘image/jpeg’, ‘image/png’, ‘image/gif’, ‘image/webp’ );
$finfo = finfo_open( FILEINFO_MIME_TYPE );
$mime_type = finfo_file( $finfo, $file[‘tmp_name’] );
finfo_close( $finfo );
if ( ! in_array( $mime_type, $allowed_mimes ) ) {
wp_send_json_error( array( ‘message’ => ‘Разрешены только JPEG, PNG, GIF или WEBP’ ) );
}
$upload_dir = wp_upload_dir();
$user_subdir = ‘st-user-uploads/’ . $user_id;
$user_path = $upload_dir[‘basedir’] . ‘/’ . $user_subdir;
if ( ! file_exists( $user_path ) ) {
wp_mkdir_p( $user_path );
}
$ext = pathinfo( $file[‘name’], PATHINFO_EXTENSION );
$new_filename = ‘photo_’ . time() . ‘_’ . mt_rand( 1000, 9999 ) . ‘.’ . $ext;
$dest_path = $user_path . ‘/’ . $new_filename;
if ( ! move_uploaded_file( $file[‘tmp_name’], $dest_path ) ) {
wp_send_json_error( array( ‘message’ => ‘Не удалось сохранить файл’ ) );
}
// Оптимизация изображения
$image = wp_get_image_editor( $dest_path );
if ( ! is_wp_error( $image ) ) {
$size = $image->get_size();
if ( $size[‘width’] > 1200 || $size[‘height’] > 1200 ) {
$image->resize( 1200, 1200, false );
}
$image->set_quality( 85 );
$image->save( $dest_path );
}
$file_url = $upload_dir[‘baseurl’] . ‘/’ . $user_subdir . ‘/’ . $new_filename;
wp_send_json_success( array( ‘url’ => $file_url ) );
}
// ==============================================================================
// 8. МОДУЛЬ ПРЕЗЕНТАЦИИ УСЛУГ (SERVICE TINDER) — v5.4 (UI & Telegram Patch)
// ==============================================================================
// 1. Инициализация таблиц
add_action( ‘init’, ‘st_srv_install_tables_v52’ );
function st_srv_install_tables_v52() {
global $wpdb;
$table_deck = $wpdb->prefix . ‘st_services_deck_v2’;
$table_swipes = $wpdb->prefix . ‘st_srv_swipes’;
$charset_collate = $wpdb->get_charset_collate();
require_once( ABSPATH . ‘wp-admin/includes/upgrade.php’ );
$sql_deck = «CREATE TABLE IF NOT EXISTS $table_deck (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
processes LONGTEXT NOT NULL,
cover_image VARCHAR(255) DEFAULT »,
icon VARCHAR(10) DEFAULT »,
category TEXT DEFAULT »,
price VARCHAR(30) DEFAULT »,
old_price VARCHAR(30) DEFAULT »,
extra_data LONGTEXT,
PRIMARY KEY (id)
) $charset_collate;»;
dbDelta( $sql_deck );
$sql_swipes = «CREATE TABLE IF NOT EXISTS $table_swipes (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
service_id INT UNSIGNED NOT NULL,
action VARCHAR(20) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY user_service (user_id, service_id)
) $charset_collate;»;
dbDelta( $sql_swipes );
$cols = $wpdb->get_col(«DESCRIBE $table_deck»);
if (!in_array(‘extra_data’, $cols)) $wpdb->query(«ALTER TABLE $table_deck ADD COLUMN extra_data LONGTEXT»);
$wpdb->query(«ALTER TABLE $table_deck MODIFY COLUMN category TEXT»);
}
function st_srv_get_alcohol_categories() {
return [
‘Виски’ => », ‘Водка’ => », ‘Джин’ => », ‘Ром’ => »,
‘Текила’ => », ‘Мескаль’ => », ‘Коньяк’ => », ‘Арманьяк’ => »,
‘Бренди’ => », ‘Ликеры’ => », ‘Настойки’ => », ‘Тихие вина’ => »,
‘Игристые вина’ => », ‘Крепленые вина’ => », ‘Вермуты’ => »,
‘Пиво’ => », ‘Сидр’ => »
];
}
// ==============================================================================
// 8.1 AJAX ОБРАБОТЧИКИ
// ==============================================================================
add_action( ‘wp_ajax_st_srv_get_deck’, ‘st_srv_ajax_get_deck’ );
add_action( ‘wp_ajax_nopriv_st_srv_get_deck’, ‘st_srv_ajax_get_deck’ );
function st_srv_ajax_get_deck() {
global $wpdb;
$table_name = $wpdb->prefix . ‘st_services_deck_v2’;
$results = $wpdb->get_results( «SELECT * FROM $table_name ORDER BY id DESC LIMIT 100″, ARRAY_A );
if (empty($results)) wp_send_json_error([‘message’ => ‘Нет данных’]);
foreach ($results as &$r) {
$r[‘processes’] = json_decode($r[‘processes’], true) ?: [];
$r[‘category’] = json_decode($r[‘category’], true) ?: [];
$r[‘extra_data’] = json_decode($r[‘extra_data’], true) ?: [];
}
wp_send_json_success( $results );
}
add_action(‘wp_ajax_st_srv_save_service’, ‘st_srv_ajax_save_service’);
add_action(‘wp_ajax_nopriv_st_srv_save_service’, ‘st_srv_ajax_save_service’);
function st_srv_ajax_save_service() {
global $wpdb;
$table_name = $wpdb->prefix . ‘st_services_deck_v2’;
$title = sanitize_text_field($_POST[‘srv_title’] ?? »);
if (empty($title)) wp_send_json_error([‘message’ => ‘Название обязательно’]);
$extra_keys = $_POST[‘extra_key’] ?? [];
$extra_vals = $_POST[‘extra_val’] ?? [];
$extra_data = [];
for ($i = 0; $i < count($extra_keys); $i++) {
if (!empty($extra_keys[$i])) $extra_data[sanitize_text_field($extra_keys[$i])] = sanitize_text_field($extra_vals[$i] ?? '');
}
// Авто-генерация артикула, если он не передан
if (!isset($extra_data['Артикул']) || empty($extra_data['Артикул'])) {
$extra_data['Артикул'] = 'ST-' . strtoupper(substr(md5(uniqid()), 0, 6));
}
$data = [
'title' => $title,
‘icon’ => sanitize_text_field($_POST[‘srv_icon’] ?? »),
‘cover_image’=> esc_url_raw($_POST[‘srv_image’] ?? »),
‘price’ => sanitize_text_field($_POST[‘srv_price’] ?? »),
‘old_price’ => sanitize_text_field($_POST[‘srv_old_price’] ?? »),
‘category’ => wp_json_encode(isset($_POST[‘srv_category’]) ? array_map(‘sanitize_text_field’, $_POST[‘srv_category’]) : []),
‘extra_data’ => wp_json_encode($extra_data),
‘processes’ => wp_json_encode([])
];
if ($wpdb->insert($table_name, $data) === false) wp_send_json_error([‘message’ => ‘Ошибка БД’]);
wp_send_json_success([‘id’ => $wpdb->insert_id]);
}
add_action(‘wp_ajax_st_srv_bulk_save’, ‘st_srv_ajax_bulk_save’);
add_action(‘wp_ajax_nopriv_st_srv_bulk_save’, ‘st_srv_ajax_bulk_save’);
function st_srv_ajax_bulk_save() {
if (!isset($_POST[‘st_srv_bulk_nonce’]) || !wp_verify_nonce($_POST[‘st_srv_bulk_nonce’], ‘st_srv_bulk_import’)) wp_send_json_error([‘message’ => ‘Security fail’]);
global $wpdb;
$table_name = $wpdb->prefix . ‘st_services_deck_v2’;
$items = json_decode(stripslashes($_POST[‘bulk_data’] ?? »), true);
if (!is_array($items)) wp_send_json_error([‘message’ => ‘Invalid JSON’]);
$inserted = 0;
foreach ($items as $item) {
$title = sanitize_text_field($item[‘title’] ?? »);
if (empty($title)) continue;
$extra_data = $item[‘extra_data’] ?? [];
if (!isset($extra_data[‘Артикул’]) || empty($extra_data[‘Артикул’])) {
$extra_data[‘Артикул’] = ‘ST-‘ . strtoupper(substr(md5(uniqid()), 0, 6));
}
$res = $wpdb->insert($table_name, [
‘title’ => $title, ‘icon’ => sanitize_text_field($item[‘icon’] ?? »), ‘cover_image’ => esc_url_raw($item[‘cover_image’] ?? »),
‘price’ => sanitize_text_field($item[‘price’] ?? »), ‘old_price’ => sanitize_text_field($item[‘old_price’] ?? »),
‘category’ => wp_json_encode($item[‘category’] ?? [], JSON_UNESCAPED_UNICODE),
‘extra_data’ => wp_json_encode($extra_data, JSON_UNESCAPED_UNICODE),
‘processes’ => wp_json_encode($item[‘processes’] ?? [], JSON_UNESCAPED_UNICODE)
]);
if ($res) $inserted++;
}
wp_send_json_success([‘inserted’ => $inserted, ‘total’ => count($items)]);
}
// Запись свайпа
add_action(‘wp_ajax_st_srv_record_swipe’, ‘st_srv_ajax_record_swipe’);
add_action(‘wp_ajax_nopriv_st_srv_record_swipe’, ‘st_srv_ajax_record_swipe’);
function st_srv_ajax_record_swipe() {
if (!$user_id = get_current_user_id()) wp_send_json_success([‘saved_to_cache’ => true]);
global $wpdb;
$wpdb->replace($wpdb->prefix . ‘st_srv_swipes’, [
‘user_id’ => $user_id, ‘service_id’ => intval($_POST[‘service_id’]),
‘action’ => sanitize_text_field($_POST[‘swipe_action’]), ‘created_at’ => current_time(‘mysql’)
]);
wp_send_json_success();
}
add_action(‘wp_ajax_st_srv_get_history’, ‘st_srv_ajax_get_history’);
add_action(‘wp_ajax_nopriv_st_srv_get_history’, ‘st_srv_ajax_get_history’);
function st_srv_ajax_get_history() {
if (!$user_id = get_current_user_id()) wp_send_json_error();
global $wpdb;
$results = $wpdb->get_results($wpdb->prepare(»
SELECT d.*, s.action, s.created_at FROM {$wpdb->prefix}st_srv_swipes s
JOIN {$wpdb->prefix}st_services_deck_v2 d ON s.service_id = d.id
WHERE s.user_id = %d ORDER BY s.created_at DESC
«, $user_id), ARRAY_A);
foreach ($results as &$r) $r[‘category’] = json_decode($r[‘category’], true) ?: [];
wp_send_json_success($results);
}
// Отправка запроса в Telegram
add_action(‘wp_ajax_st_srv_send_request’, ‘st_srv_ajax_send_request’);
add_action(‘wp_ajax_nopriv_st_srv_send_request’, ‘st_srv_ajax_send_request’);
function st_srv_ajax_send_request() {
$product = sanitize_text_field($_POST[‘product’] ?? »);
$email = sanitize_email($_POST[’email’] ?? »);
$note = sanitize_textarea_field($_POST[‘note’] ?? »);
// === ВАЖНО: Вставьте сюда данные вашего бота ===
$tg_token = ‘ВАШ_ТОКЕН_БОТА’;
$tg_chat_id = ‘ВАШ_CHAT_ID’;
// ===============================================
$message = » *Новый запрос из Tinder!*\n\n»;
$message .= » *Товар:* {$product}\n»;
$message .= » *Email:* {$email}\n»;
if($note) $message .= » *Примечание:* {$note}»;
if($tg_token != ‘ВАШ_ТОКЕН_БОТА’) {
$url = «https://api.telegram.org/bot{$tg_token}/sendMessage»;
wp_remote_post($url, [‘body’ => [‘chat_id’ => $tg_chat_id, ‘text’ => $message, ‘parse_mode’ => ‘Markdown’]]);
}
wp_send_json_success([‘message’ => ‘Запрос успешно отправлен!’]);
}
// Удаление сервиса (таблица)
add_action(‘wp_ajax_st_srv_delete_service’, ‘st_srv_ajax_delete_service’);
add_action(‘wp_ajax_nopriv_st_srv_delete_service’, ‘st_srv_ajax_delete_service’);
function st_srv_ajax_delete_service() {
global $wpdb;
$id = intval($_POST[‘id’] ?? 0);
if (!$id || $wpdb->delete($wpdb->prefix . ‘st_services_deck_v2’, [‘id’ => $id]) === false) wp_send_json_error([‘message’ => ‘Ошибка’]);
wp_send_json_success();
}
// ==============================================================================
// 8.2 ШОРТКОД TINDER
| Фото | Позиция ↕ | Категория ↕ | Цена ↕ | Действия | |
|---|---|---|---|---|---|
| Загрузка данных… | |||||
if (!empty($val) && trim((string)$val) !== ») {
echo ‘
‘;
}
}
}
?>