дерево

Перейти к основному содержимомуПерейти к верхней панели

Помощь

Редактировать темы

Редактирование Twenty Twenty-Five (активен)

Файл: functions.phpВыберите тему для изменения:   Twenty Twenty-Five  

Файлы темы

Выбранное содержимое файла:

1

<?php

2

/*

3

Plugin Name: SpiritTrade Digital Modules

4

Description: Единый плагин для формы постов, карты базы знаний, древа категорий, Summer Menu API и AL-Тиндер.

5

Version: 3.1.1

6

Author: Spirit Trade

7

*/

8

​

9

// ==============================================================================

10

// 0. ГЛОБАЛЬНЫЕ КОНСТАНТЫ И НАСТРОЙКИ

11

// ==============================================================================

12

define( 'ST_TG_BOT_TOKEN', '7675966976:AAFmAeowI9eGA5siDvsUy9kPr_SHZIOBI38' );

13

define( 'ST_TG_CHAT_ID', '-1002640050542' );

14

define( 'ST_ADMIN_PIN', '16' );

15

​

16

// ==============================================================================

17

// 1. ИНИЦИАЛИЗАЦИЯ БАЗЫ ДАННЫХ И ПОДКЛЮЧЕНИЕ СКРИПТОВ

18

// ==============================================================================

19

​

20

add_action( 'wp_enqueue_scripts', 'st_enqueue_global_assets' );

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. ФОРМА ПУБЛИКАЦИИ:

Доступ закрыт

Для публикации новых заметок в базу знаний платформы необходимо авторизоваться.

// ============================================================================== add_shortcode(‘frontend_post_form’, ‘render_comic_frontend_form’); function render_comic_frontend_form() { ob_start(); if ( ! is_user_logged_in() ) { return st_get_auth_prompt_html(‘Для публикации новых заметок в базу знаний платформы необходимо авторизоваться.’); } ?> ✏️ Добавить заметку×

+ запись

Заголовок

0)); foreach($categories as $category) { echo » . esc_html($category->name) . ‘

‘; } ?>

‘st_description’, ‘textarea_rows’ => 8, ‘media_buttons’ => true, ‘quicktags’ => true, ‘dfw’ => true, ‘tinymce’ => array( ‘toolbar1’ => ‘bold,italic,link,wp_adv’, ‘toolbar2’ => ‘formatselect,strikethrough,forecolor,bullist,numlist,blockquote,alignleft,aligncenter,alignright,unlink,removeformat,fullscreen’, ‘toolbar3’ => », ‘toolbar4’ => », ‘fontsize_formats’ => ’10px,12px,14px,16px,18px,20px,24px’, ‘content_css’ => false, ‘content_style’ => ‘body { font-family: Georgia, «Times New Roman», Times, serif; font-size: 16px; line-height: 1.5; color: #1a1a1a; }’, ), ‘editor_height’ => 150, ‘editor_class’ => ‘st-editor-content’, ‘drag_drop_upload’ => true, ); wp_editor(», ‘st_description’, $editor_settings); ?>

ПИН-КОД

— — 134679 Опубликовать! ‘Ошибка безопасности. Обновите страницу.’ ) ); } $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. БАЗА ЗНАНИЙ:

Описание

Загрузка…

0 записей по теме
Синхронизация данных…
// ============================================================================== 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 = ‘

‘ . $icon . »; $btn_inner .= ‘

‘; $btn_inner .= » . $main_title . »; if ($sub_title) { $btn_inner .= ‘для: ‘ . $sub_title . ‘‘; } $btn_inner .= »; $sidebar_html .= sprintf( ‘%s’, 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 ); } ?>

<style>
    .st-kb-container {
        --bg-paper: #fdfcf8;
        --ink-black: #1a1a1a;
        --marker-yellow: #ffde59;
        --marker-green: #c1ff00;
        --marker-blue: #38b6ff;
        --border-sketch: 2px solid var(--ink-black);
        --transition: 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
        --font-main: Georgia, 'Times New Roman', Times, serif;
    }

    .st-kb-container .st-workspace {
        display: flex; height: calc(100vh - 60px); min-height: 500px;
        width: 100%; margin: 20px 0; background: var(--bg-paper);
        font-family: var(--font-main); color: var(--ink-black);
        overflow: hidden; box-sizing: border-box;
        background-image: radial-gradient(var(--ink-black) 1px, transparent 1px);
        background-size: 20px 20px; border: var(--border-sketch);
        border-radius: 16px; box-shadow: 6px 6px 0 var(--ink-black);
        position: relative;
    }

    .st-kb-container .st-sidebar {
        width: 280px; background: #fff; border-right: var(--border-sketch);
        display: flex; flex-direction: column;
        transition: width var(--transition), transform var(--transition);
        flex-shrink: 0; z-index: 20;
    }
    .st-kb-container .st-sidebar.collapsed { width: 75px; }

    .st-kb-container .st-sidebar-header {
        padding: 15px; display: flex; align-items: center; gap: 12px;
        border-bottom: 2px dashed rgba(0,0,0,0.15); background: #fff;
        flex-wrap: wrap;
    }

    .st-kb-container .st-toggle-btn {
        background: #f5f5f5; border: 1px solid var(--ink-black); font-size: 1.2rem; cursor: pointer;
        color: var(--ink-black); transition: var(--transition); box-shadow: 2px 2px 0 var(--ink-black);
        width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
        border-radius: 8px; flex-shrink: 0;
    }
    .st-kb-container .st-toggle-btn:active { transform: translateY(2px); box-shadow: 0 0 0 var(--ink-black); }
    .st-kb-container .st-sidebar.collapsed .st-toggle-btn { transform: rotate(180deg); }

    .st-kb-container .st-sidebar-title-group {
        display: flex; align-items: center; gap: 10px;
        flex: 1; min-width: 0;
        transition: opacity 0.2s;
    }
    .st-kb-container .st-sidebar.collapsed .st-sidebar-title-group {
        opacity: 0; pointer-events: none; display: none;
    }
    .st-kb-container .st-sidebar-title-text {
        font-weight: 900; font-size: 0.9rem; line-height: 1.2;
        text-transform: uppercase; letter-spacing: 0.5px;
        background: #f0f0e8; padding: 6px 10px; border-radius: 8px;
        border-left: 3px solid var(--ink-black);
        white-space: normal; word-break: break-word;
    }

    .st-kb-container .st-guide-icon {
        width: 22px; height: 22px; font-size: 0.7rem;
        background: #fff; border: 2px solid var(--ink-black);
        box-shadow: 2px 2px 0 var(--ink-black);
    }
    .st-kb-container .st-guide-icon:hover {
        background: var(--marker-yellow); transform: translateY(-2px) scale(1.1);
        box-shadow: 3px 3px 0 var(--ink-black);
    }

    .st-kb-container .st-cat-btn {
        display: flex; align-items: center; width: 100%; padding: 10px 15px;
        border: none; background: transparent; cursor: pointer;
        text-align: left; border-bottom: 1px dashed rgba(0,0,0,0.05);
        transition: background 0.2s; box-sizing: border-box;
        position: relative;
    }
    .st-kb-container .st-cat-btn:hover { background: #fdfcf8; }
    .st-kb-container .st-cat-btn.active { background: #f4f9f4; box-shadow: inset 4px 0 0 var(--ink-black); }

    .st-kb-container .st-cat-icon {
        width: 40px; height: 40px; min-width: 40px;
        border: 2px solid var(--ink-black); border-radius: 10px 4px 10px 4px;
        display: flex; justify-content: center; align-items: center;
        font-family: var(--font-main); font-weight: 900; font-size: 1.5rem; color: var(--ink-black);
        box-shadow: 2px 2px 0 var(--ink-black); transition: var(--transition);
    }
    .st-kb-container .st-cat-btn.active .st-cat-icon { transform: scale(1.05) rotate(-3deg); border-width: 3px; }
    @media (hover: hover) { .st-kb-container .st-cat-btn:hover .st-cat-icon { transform: translateY(-2px) rotate(2deg); } }

    .st-kb-container .st-cat-text {
        display: flex; flex-direction: column; margin-left: 15px;
        white-space: nowrap; opacity: 1; transition: opacity 0.2s; overflow: hidden;
        flex: 1;
    }
    .st-kb-container .st-sidebar.collapsed .st-cat-text { opacity: 0; display: none; }
    .st-kb-container .st-cat-main { font-weight: 900; font-size: 0.95rem; letter-spacing: 0.5px; text-overflow: ellipsis; overflow: hidden; }

    .st-kb-container .st-cat-sphere {
        font-size: 0.7rem; margin-top: 2px; color: var(--ink-black);
        display: flex; align-items: baseline; gap: 4px;
    }
    .st-kb-container .st-cat-sphere small { color: #777; font-size: 0.6rem; text-transform: uppercase; font-weight: bold; }
    .st-kb-container .st-cat-sphere strong {
        font-weight: 900; letter-spacing: 0.5px;
        background: linear-gradient(to top, rgba(255,222,89, 0.6) 40%, transparent 40%);
        padding: 0 2px;
    }

    .st-kb-container .st-main-content {
        flex: 1; display: flex; flex-direction: column; position: relative;
        background: rgba(253, 252, 248, 0.9); backdrop-filter: blur(2px);
        overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--ink-black) transparent;
    }
    .st-kb-container .st-main-content::-webkit-scrollbar { width: 6px; }
    .st-kb-container .st-main-content::-webkit-scrollbar-thumb { background: var(--ink-black); border-radius: 10px; }

    .st-kb-container .header-group {
        padding: 20px 30px; position: sticky; top: 0;
        background: rgba(253, 252, 248, 0.95); backdrop-filter: blur(5px);
        z-index: 10; border-bottom: 2px dashed rgba(0,0,0,0.15);
        display: flex; flex-direction: column; gap: 12px;
    }

    .st-kb-container .st-header-title-row {
        display: flex; align-items: center; gap: 12px; 
        flex-wrap: nowrap; width: 100%; overflow: hidden;
    }
    .st-kb-container .cat-seq-badge {
        background: var(--marker-yellow); border: 2.5px solid var(--ink-black);
        width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
        font-family: var(--font-main); font-size: 1.4rem; font-weight: 900;
        border-radius: 8px 4px 10px 4px; box-shadow: 3px 3px 0 var(--ink-black);
        transform: rotate(-3deg); flex-shrink: 0;
    }
    .st-kb-container .category-header {
        font-weight: 900; font-size: clamp(1.1rem, 2.5vw, 1.6rem); text-transform: uppercase;
        margin: 0; line-height: 1.1; color: var(--ink-black); letter-spacing: 0.5px;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
        flex-shrink: 1;
    }

    .st-kb-container .cat-info-btn {
        background: #fff; border: 1.5px solid var(--ink-black); border-radius: 50%;
        width: 27px; height: 27px; padding: 0; font-family: var(--font-main);
        font-weight: bold; font-size: 0.84rem; cursor: pointer;
        display: inline-flex; align-items: center; justify-content: center;
        box-shadow: 2px 2px 0 var(--ink-black); transition: var(--transition);
        color: var(--ink-black); line-height: 1;
    }
    .st-kb-container .cat-info-btn:hover {
        background: var(--marker-yellow); transform: translateY(-2px); box-shadow: 3px 3px 0 var(--ink-black);
    }
    .st-kb-container .cat-info-btn:active {
        transform: translateY(1px); box-shadow: 1px 1px 0 var(--ink-black);
    }

    .st-kb-container .st-meta-sentence {
        display: flex; align-items: baseline; gap: 8px; 
        flex-wrap: nowrap;
        background: rgba(255, 255, 255, 0.6); border: 1px dashed rgba(0,0,0,0.2);
        padding: 8px 12px; border-radius: 8px; width: fit-content;
        max-width: 100%; overflow-x: auto;
        scrollbar-width: none; white-space: nowrap;
    }
    .st-kb-container .st-meta-sentence::-webkit-scrollbar { display: none; }
    .st-kb-container .category-stats-number {
        font-size: 1rem; font-weight: 900; background: var(--marker-green);
        display: inline-flex; justify-content: center; align-items: center;
        padding: 2px 8px; min-width: 22px; transform: rotate(-2deg);
        border: 2px solid var(--ink-black); box-shadow: 2px 2px 0 var(--ink-black);
        border-radius: 6px; flex-shrink: 0; position: relative; top: 2px;
    }
    .st-kb-container .category-stats-label {
        font-size: 0.85rem; font-weight: bold; color: #333;
        text-transform: uppercase; letter-spacing: 0.5px; flex-shrink: 0;
    }
    .st-kb-container .st-header-sphere {
        display: inline-flex; align-items: baseline; gap: 6px; flex-shrink: 0;
    }
    .st-kb-container .sphere-value {
        font-size: 0.95rem; font-weight: 900; color: var(--ink-black);
        text-transform: uppercase; letter-spacing: 0.5px;
        background: linear-gradient(to top, rgba(255,222,89, 0.7) 40%, transparent 40%);
        padding: 0 4px;
    }

    .st-kb-container .timeline { padding: 30px 40px 60px 40px; position: relative; width: 100%; box-sizing: border-box; max-width: 900px; }
    .st-kb-container .timeline::before { content: ''; position: absolute; left: 40px; top: 30px; bottom: 0; width: 3px; background: var(--ink-black); }
    .st-kb-container .timeline-item {
        position: relative; margin-bottom: 30px; padding-left: 30px;
        text-decoration: none; display: flex; flex-direction: column;
        align-items: flex-start; gap: 8px; opacity: 0; transform: translateX(-15px);
        transition: var(--transition); width: 100%; box-sizing: border-box;
    }
    .st-kb-container .timeline-item.show { opacity: 1; transform: translateX(0); }
    .st-kb-container .date-box {
        background: var(--marker-blue); border: 2px solid var(--ink-black); padding: 3px 10px;
        font-weight: bold; font-size: 0.75rem; color: var(--ink-black);
        transform: rotate(-1deg); box-shadow: 2px 2px 0px var(--ink-black); z-index: 2;
    }
    .st-kb-container .leaf-box {
        background: #fff; border: var(--border-sketch); padding: 15px 20px;
        border-radius: 0px 15px 4px 15px; box-shadow: 3px 3px 0px var(--ink-black);
        transition: var(--transition); max-width: 100%; width: 100%; box-sizing: border-box; z-index: 1;
    }
    @media (hover: hover) { .st-kb-container .timeline-item:hover .leaf-box { transform: scale(1.01) translateX(5px); background: #fdfdfd; } }
    .st-kb-container .timeline-title { 
        font-weight: bold; font-size: clamp(1rem, 2vw, 1.2rem); margin: 0; 
        line-height: 1.4; color: var(--ink-black); word-break: break-word; 
    }
    .st-kb-container .timeline-excerpt {
        font-size: 0.9rem; line-height: 1.5; color: #444; margin: 10px 0 0 0; padding-top: 10px;
        border-top: 1px dashed rgba(26,26,26,0.2); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;  
        overflow: hidden; text-overflow: ellipsis;
    }
    .st-kb-container .timeline-item::after { content: '📌'; position: absolute; left: -10px; top: 25px; font-size: 20px; filter: drop-shadow(1px 1px 0px white); }
    .st-kb-container #st-kb-loader { text-align: center; font-weight: bold; font-size: 1.1rem; padding: 40px; }

    .st-kb-container .st-mobile-overlay, .st-kb-container .st-desc-modal-overlay {
        position: absolute; top: 0; left: 0; width: 100%; height: 100%;
        background: rgba(0,0,0,0.5); backdrop-filter: blur(2px);
        z-index: 15; opacity: 0; visibility: hidden; transition: 0.3s;
    }
    .st-kb-container .st-mobile-overlay.show { opacity: 1; visibility: visible; }
    .st-kb-container .st-desc-modal-overlay { z-index: 100; display: flex; align-items: center; justify-content: center; }
    .st-kb-container .st-desc-modal-overlay.active { opacity: 1; visibility: visible; }

    .st-kb-container .st-desc-modal-box {
        background: var(--bg-paper); width: 90%; max-width: 450px;
        border: var(--border-sketch); border-radius: 12px; box-shadow: 6px 6px 0 var(--ink-black);
        transform: translateY(20px) rotate(-1deg); transition: 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
        background-image: radial-gradient(var(--ink-black) 1px, transparent 1px); background-size: 15px 15px;
        max-height: 80vh; display: flex; flex-direction: column;
    }
    .st-kb-container .st-desc-modal-overlay.active .st-desc-modal-box { transform: translateY(0) rotate(0); }

    .st-kb-container .st-desc-header {
        padding: 15px 20px; border-bottom: var(--border-sketch); background: var(--marker-yellow);
        border-radius: 10px 10px 0 0; display: flex; justify-content: space-between; align-items: center;
    }
    .st-kb-container .st-desc-header h3 { margin: 0; font-size: 1.1rem; font-weight: 900; text-transform: uppercase; }
    .st-kb-container .st-desc-close {
        background: #ff4444; border: 2px solid var(--ink-black); border-radius: 50%;
        width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
        color: #fff; font-size: 1.2rem; cursor: pointer; box-shadow: 2px 2px 0 var(--ink-black);
        transition: 0.2s; padding: 0; line-height: 1;
    }
    .st-kb-container .st-desc-close:hover { transform: scale(1.1) rotate(10deg); background: #ff3333; }

    .st-kb-container .st-desc-body { 
        padding: 20px; font-size: 0.95rem; line-height: 1.6; font-weight: 500; 
        background: rgba(255,255,255,0.9); border-radius: 0 0 10px 10px; overflow-y: auto;
    }

    @media (max-width: 800px) {
        .st-kb-container .st-workspace { border-radius: 12px; margin: 10px 0; height: calc(100vh - 20px); min-height: 550px; box-shadow: 4px 4px 0 var(--ink-black); }
        .st-kb-container .st-sidebar { position: absolute; height: 100%; z-index: 20; transform: translateX(-100%); width: 280px; box-shadow: 10px 0 25px rgba(0,0,0,0.2); }
        .st-kb-container .st-sidebar.mobile-open { transform: translateX(0); }
        .st-kb-container .st-sidebar-header { justify-content: space-between; }
        .st-kb-container .st-toggle-btn { transform: rotate(180deg); }
        .st-kb-container .mobile-menu-trigger { 
            display: flex; align-items: center; justify-content: space-between;
            width: 100%; margin-bottom: 15px; background: #fff; 
            border: 2px solid var(--ink-black); padding: 8px 12px; 
            border-radius: 12px; box-shadow: 4px 4px 0 var(--ink-black); 
            cursor: pointer; transition: 0.1s; color: var(--ink-black);
        }
        .st-kb-container .mobile-menu-trigger:active { transform: translateY(3px); box-shadow: 1px 1px 0 var(--ink-black); }
        .st-kb-container .trigger-left { display: flex; align-items: center; gap: 12px; }
        .st-kb-container .trigger-icon { 
            display: flex; align-items: center; justify-content: center; width: 38px; height: 38px; background: var(--marker-yellow);
            border: 2px solid var(--ink-black); border-radius: 8px; font-size: 1.4rem; box-shadow: 2px 2px 0 var(--ink-black);
        }
        .st-kb-container .trigger-text-group { display: flex; flex-direction: column; text-align: left; }
        .st-kb-container .trigger-text { font-weight: 900; font-size: 1.05rem; text-transform: uppercase; letter-spacing: 0.5px; line-height: 1.1; }
        .st-kb-container .trigger-sub { font-size: 0.7rem; font-weight: bold; color: #666; margin-top: 2px; }
        .st-kb-container .trigger-arrow { 
            display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: #f4f4f4; border: 2px solid var(--ink-black); border-radius: 50%;
        }
        .st-kb-container .timeline { padding: 15px 15px 40px 20px; }
        .st-kb-container .timeline::before { left: 15px; top: 15px; }
        .st-kb-container .timeline-item::after { left: -10px; top: 22px; font-size: 16px; }
        .st-kb-container .timeline-item { padding-left: 20px; }
        .st-kb-container .header-group { padding: 15px; }
        .st-kb-container .leaf-box { padding: 12px 15px; }
        .st-kb-container .st-header-title-row { gap: 8px; }
        .st-kb-container .cat-seq-badge { width: 32px; height: 32px; font-size: 1.1rem; }
        .st-kb-container .st-meta-sentence { padding: 6px 10px; border: none; background: transparent; }
        .st-kb-container .sphere-value { font-size: 0.85rem; }

        .st-kb-container .st-guide-icon { width: 16px; height: 16px; font-size: 0.6rem; }
        .st-kb-container .cat-info-btn { width: 24px; height: 24px; font-size: 0.7rem; }
    }
    @media (min-width: 801px) {
        .st-kb-container .mobile-menu-trigger { display: none; }
    }
</style>

<div class="st-kb-container" id="st-kb-wrap">
    <div class="st-workspace">
        <div class="st-mobile-overlay" id="st-mobile-overlay"></div>

        <div class="st-desc-modal-overlay" id="st-desc-modal">
            <div class="st-desc-modal-box">
                <div class="st-desc-header">
                    <h3 id="st-desc-modal-title">Описание</h3>
                    <button class="st-desc-close" id="st-desc-close">×</button>
                </div>
                <div class="st-desc-body" id="st-desc-body"></div>
            </div>
        </div>

        <aside class="st-sidebar" id="st-sidebar">
            <div class="st-sidebar-header">
                <button id="st-toggle-btn" class="st-toggle-btn">≡</button>
                <div class="st-sidebar-title-group">
                    <div class="st-sidebar-title-text">НАЖМИ на</div>
                    <span class="st-info-trigger st-guide-icon" data-info-title="Как это работает?" data-info-content="📌 Это окно появляется при нажатии на значок ❔.<br>Оно выводит описание о том объекте, рядом с которым он расположен.<br><br><strong>Пример:</strong> Так как этот значок расположен здесь, он выводит информацию о данном разделе:<br><div style='margin-top:10px; padding:10px; background:rgba(255,222,89,0.15); border-left:3px solid var(--ink-black); font-style:normal;'><strong>Разделы упорядочены по номерам</strong> и содержат информацию, необходимую для реализации проекта в указанной сфере деятельности.<br><br>Нажмите на любой раздел — справа отобразятся все публикации, относящиеся к этой теме.</div>">❔</span>
                </div>
            </div>
            <div class="st-sidebar-menu">
                <?php echo $sidebar_html; ?>
            </div>
        </aside>

        <main class="st-main-content" id="st-main-content">
            <div class="header-group">
                <button class="mobile-menu-trigger" id="st-mobile-trigger">
                    <div class="trigger-left">
                        <span class="trigger-icon">🗂️</span>
                        <div class="trigger-text-group">
                            <span class="trigger-text">Направления</span>
                            <span class="trigger-sub">Показать все разделы</span>
                        </div>
                    </div>
                    <div class="trigger-arrow">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
                    </div>
                </button>

                <div class="st-header-title-row">
                    <div class="cat-seq-badge" id="cat-seq-display" style="display:none;"></div>
                    <h1 class="category-header" id="current-category-title">Загрузка...</h1>
                    <button class="cat-info-btn" id="cat-info-btn" style="display: none;" title="Описание рубрики">📖</button>
                </div>

                <div class="st-meta-sentence">
                    <span class="category-stats-number" id="cat-count-display">0</span>
                    <span class="category-stats-label" id="cat-stats-label">записей по теме</span>
                    <span class="st-header-sphere" id="cat-sphere-display" style="display:none;">
                        <span class="sphere-value" id="cat-sphere-text"></span>
                    </span>
                </div>
            </div>

            <div id="st-kb-loader">🔄 Синхронизация данных...</div>
            <div class="timeline" id="timeline-container"></div>
        </main>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const wrap = document.getElementById('st-kb-wrap');
    const sidebar = wrap.querySelector('#st-sidebar');
    const toggleBtn = wrap.querySelector('#st-toggle-btn');
    const mobileTrigger = wrap.querySelector('#st-mobile-trigger');
    const overlay = wrap.querySelector('#st-mobile-overlay');
    const tabs = wrap.querySelectorAll('.st-cat-btn');
    const container = wrap.querySelector('#timeline-container');

    const titleEl = wrap.querySelector('#current-category-title');
    const seqDisplay = wrap.querySelector('#cat-seq-display');
    const countEl = wrap.querySelector('#cat-count-display');
    const statsLabel = wrap.querySelector('#cat-stats-label');
    const sphereDisplay = wrap.querySelector('#cat-sphere-display');
    const sphereText = wrap.querySelector('#cat-sphere-text');
    const infoBtn = wrap.querySelector('#cat-info-btn');

    const descModal = wrap.querySelector('#st-desc-modal');
    const descModalTitle = wrap.querySelector('#st-desc-modal-title');
    const descBody = wrap.querySelector('#st-desc-body');
    const closeDesc = wrap.querySelector('#st-desc-close');

    const loader = wrap.querySelector('#st-kb-loader');
    const contentArea = wrap.querySelector('#st-main-content');

    const apiUrl = '<?php echo esc_url( rest_url( 'wp/v2' ) ); ?>';

    function openInfoModal(title, contentHtml) {
        descModalTitle.innerHTML = title;
        descBody.innerHTML = contentHtml;
        descModal.classList.add('active');
    }

    closeDesc.addEventListener('click', () => descModal.classList.remove('active'));
    descModal.addEventListener('click', (e) => { if(e.target === descModal) descModal.classList.remove('active'); });

    const infoTriggers = wrap.querySelectorAll('.st-info-trigger');
    infoTriggers.forEach(trigger => {
        trigger.addEventListener('click', (e) => {
            e.stopPropagation();
            const title = trigger.getAttribute('data-info-title') || 'Информация';
            const content = trigger.getAttribute('data-info-content') || '<em>Нет данных</em>';
            openInfoModal(title, content);
        });
    });

    function closeMobileMenu() {
        sidebar.classList.remove('mobile-open');
        overlay.classList.remove('show');
    }

    function openMobileMenu() {
        sidebar.classList.add('mobile-open');
        overlay.classList.add('show');
    }

    toggleBtn.addEventListener('click', () => {
        if (window.innerWidth > 800) sidebar.classList.toggle('collapsed');
        else closeMobileMenu(); 
    });

    mobileTrigger.addEventListener('click', openMobileMenu);
    overlay.addEventListener('click', closeMobileMenu);

    infoBtn.addEventListener('click', () => {
        const activeTab = wrap.querySelector('.st-cat-btn.active');
        if (activeTab) {
            const desc = activeTab.getAttribute('data-desc');
            const mainTitle = activeTab.getAttribute('data-main');
            if (desc && desc.trim() !== '') {
                openInfoModal('О рубрике: ' + mainTitle, desc);
            } else {
                openInfoModal('О рубрике', '<em>Описание отсутствует</em>');
            }
        }
    });

    tabs.forEach(tab => {
        tab.addEventListener('click', function() {
            const catId = this.getAttribute('data-id');
            const catCount = this.getAttribute('data-count') || 0;
            const catNum = this.getAttribute('data-number');
            const mainTitle = this.getAttribute('data-main');
            const subTitle = this.getAttribute('data-sub');
            const catDesc = this.getAttribute('data-desc');

            tabs.forEach(t => t.classList.remove('active'));
            this.classList.add('active');

            if (window.innerWidth <= 800) closeMobileMenu();

            container.innerHTML = '';

            if (catNum && catNum.trim() !== '') {
                seqDisplay.innerText = catNum;
                seqDisplay.style.display = 'flex';
            } else {
                seqDisplay.style.display = 'none';
            }

            titleEl.innerText = mainTitle;
            countEl.innerText = catCount;

            if (subTitle && subTitle.trim() !== '') {
                statsLabel.innerText = 'записей для:';
                sphereText.innerText = subTitle;
                sphereDisplay.style.display = 'inline-flex';
            } else {
                statsLabel.innerText = 'записей по теме';
                sphereDisplay.style.display = 'none';
            }

            if (catDesc && catDesc.trim() !== '') {
                infoBtn.style.display = 'inline-flex';
            } else {
                infoBtn.style.display = 'none';
            }

            if (!catId) {
                loader.style.display = 'none';
                container.innerHTML = `<div class="date-box" style="margin:20px auto; background:#ff4444; color:#fff;">Ошибка: ID не найден.</div>`;
                return;
            }
            fetchPosts(catId);
        });
    });

    if(tabs.length > 0) tabs[0].click();
    else { loader.style.display = 'none'; titleEl.innerText = 'Нет рубрик'; }

    function fetchPosts(categoryId) {
        loader.style.display = 'block';
        loader.innerText = '📌 Подгружаем данные...';
        contentArea.scrollTo({top: 0, behavior: 'smooth'});

        fetch(`${apiUrl}/posts?categories=${categoryId}&per_page=50`)
            .then(response => response.json())
            .then(posts => {
                loader.style.display = 'none';
                if (!Array.isArray(posts) || posts.length === 0) {
                    container.innerHTML = '<div class="date-box" style="margin:20px;">Нет записей в данной категории</div>';
                    return;
                }

                posts.forEach((post, index) => {
                    const dateObj = new Date(post.date);
                    const date = dateObj.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' });

                    let excerptText = '';
                    if (post.excerpt && post.excerpt.rendered) {
                        let tempDiv = document.createElement('div');
                        tempDiv.innerHTML = post.excerpt.rendered;
                        excerptText = tempDiv.textContent || tempDiv.innerText || '';
                        excerptText = excerptText.trim();
                    }

                    let excerptHtml = excerptText ? `<div class="timeline-excerpt">${excerptText}</div>` : '';

                    const item = document.createElement('a');
                    item.href = post.link; 
                    item.className = 'timeline-item';
                    item.innerHTML = `
                        <div class="date-box">${date}</div>
                        <div class="leaf-box">
                            <h3 class="timeline-title">${post.title.rendered}</h3>
                            ${excerptHtml}
                        </div>
                    `;
                    container.appendChild(item);
                    setTimeout(() => { item.classList.add('show'); }, 50 * index);
                });
            })
            .catch(err => {
                loader.style.display = 'none';
                container.innerHTML = '<div class="date-box" style="margin:20px; background:#ff4444; color:#fff;">Ошибка получения данных</div>';
            });
    }
});
</script>

<?php
return ob_get_clean();

}

// ==============================================================================
// 4. ШОРТКОД ДРЕВА/ЛОЗЫ КАТЕГОРИЙ:


// ==============================================================================
add_shortcode(‘st_vine_buds’, ‘st_render_vine_buds_layout’);

function st_render_vine_buds_layout() {
static $buds_assets_loaded = false;
$html = »;

if (!$buds_assets_loaded) {
    $html .= st_get_vine_buds_assets();
    $buds_assets_loaded = true;
}

$html .= '<div class="st-vine-container" id="st-vine-comp">';
$html .= '<div class="st-vine-wrapper">';
$html .= '<div class="st-vine-trunk"></div>';

$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 .= '<div class="st-vine-branch-container st-side-' . $branch_side . '">';
        $html .= '  <div class="st-branch-line"></div>'; 
        $html .= '  <div class="st-branch-content">';
        $html .= '    <h3 class="st-branch-title">' . esc_html($cat->name) . '</h3>';
        $html .= '    <div class="st-buds-row">';

        $hidden_list = '<ul class="st-modal-post-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 .= '<a href="' . $post_url . '" class="st-bud-wrapper ' . $position_class . '" data-tooltip="' . $post_title . '">';
            $html .= '  <span class="st-bud-shape"></span>';
            $html .= '</a>';

            $hidden_list .= '<li><span class="st-post-date">' . $post_date . '</span> <a href="' . $post_url . '">' . $post_title . '</a></li>';
        }

        $hidden_list .= '</ul>';

        $html .= '      <div class="st-branch-end-icon" data-cat="' . esc_attr($cat->name) . '" data-tooltip="Все записи">';
        $html .= '        <svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>';
        $html .= '      </div>';
        $html .= '      <div class="st-hidden-data" style="display: none;">' . $hidden_list . '</div>';
        $html .= '    </div>'; 
        $html .= '  </div>'; 
        $html .= '</div>'; 
    }
}

$html .= '</div>'; 
$html .= '
<div id="st-vine-modal" class="st-vine-overlay">
    <div class="st-vine-modal-box">
        <div class="st-vine-modal-header">
            <h3 id="st-vine-modal-title">Рубрика</h3>
            <button class="st-vine-close" id="st-vine-close-btn">×</button>
        </div>
        <div class="st-vine-modal-body" id="st-vine-modal-body">
        </div>
    </div>
</div>';
$html .= '</div>'; // Конец st-vine-container

return $html;

}

function st_get_vine_buds_assets() {
return ‘

function st_enqueue_global_assets() {

22

    if ( ! is_admin() ) {

23

        wp_enqueue_style( 'dashicons' );

24

        wp_enqueue_style( 'editor-buttons' );

25

        wp_enqueue_style( 'tinymce-css', includes_url( 'css/editor.min.css' ) );

26

        wp_enqueue_script( 'jquery' );

27

        

28

        // Подключаем тяжелые скрипты редактора только если они нужны

29

        if ( shortcode_exists( 'frontend_post_form' ) || shortcode_exists( 'al_profile_setup' ) ) {

30

            wp_enqueue_editor();

31

            wp_enqueue_media();

32

            wp_enqueue_script( 'word-count' );

33

            wp_enqueue_script( 'editor' );

34

            wp_enqueue_script( 'quicktags' );

35

        }

36

    }

37

}

38

​

39

add_action( 'plugins_loaded', 'st_global_install_tables' );

Документация:Название функции…WP_REST_Response()abs()add_action()add_filter()add_options_page()add_shortcode()admin_url()array_fill()array_map()array_merge()array_slice()array_unshift()check_admin_referer()check_ajax_referer()count()crc32()current_time()dbDelta()define()error_log()esc_attr()esc_html()esc_textarea()esc_url()esc_url_raw()explode()file_exists()finfo_close()finfo_file()finfo_open()get_bloginfo()get_categories()get_current_user_id()get_option()get_permalink()get_posts()get_term()get_the_date()get_userdata()home_url()implode()in_array()includes_url()intval()is_admin()is_array()is_user_logged_in()is_wp_error()json_decode()mb_substr()md5()move_uploaded_file()mt_rand()ob_get_clean()ob_start()pathinfo()preg_match()register_rest_route()rest_ensure_response()rest_url()sanitize_email()sanitize_text_field()sanitize_textarea_field()shortcode_exists()sprintf()stripslashes()strtoupper()substr()time()trim()uniqid()update_option()wp_create_nonce()wp_dequeue_style()wp_editor()wp_enqueue_editor()wp_enqueue_media()wp_enqueue_script()wp_enqueue_style()wp_get_current_user()wp_get_image_editor()wp_insert_post()wp_json_encode()wp_kses_post()wp_login_url()wp_logout_url()wp_mkdir_p()wp_nonce_field()wp_registration_url()wp_remote_post()wp_send_json_error()wp_send_json_success()wp_trim_words()wp_upload_dir()wp_verify_nonce()

Спасибо вам за творчество с WordPress.

Версия 7.0

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *