Liker

Лайки, звезды, рейтинги, оценки
для 1С-Битрикс

Описание модуля

Модуль Лайки, звезды, рейтинги, оценки (devnseo.liker) — это универсальная система реакций (лайков, оценок, звёзд, эмодзи) для сайтов на 1С-Битрикс. Модуль позволяет добавлять интерактивные виджеты для пользователей: голосование за контент, рейтинг товаров, звездочки, оценка статей и другое.

Ключевые возможности:

  • Гибкая архитектура типов реакций – из коробки поддерживаются: like, stars, thumbs, emoji. Можно расширить новыми типами.
  • Голосование для авторизованных и гостей – уникальность по IP или токену.
  • Административный интерфейс – просмотр и редактирование контейнеров голосования, указание своих значений и пересчёт статистики.
  • Поддержка композита – виджеты совместимы с технологией композитного сайта, а так же имеют свой адаптер в стиле композита, для больших коллекций объектов.

⚠️ Внимание!

Модуль сделан на основе моего js-модуля liker (https://github.com/qujs-dev/liker). Поэтому подключение возможно только через qu.js (https://github.com/qujs-dev/core).

Предварительно необходимо скачать базовый модуль для 1С-Битрикс Qu расширенная загрузка ассетов async/defer/media

Установка

Стандартная установка через Маркетплейс:

  • Перейдите в раздел «Marketplace» → «Каталог решений».
  • Найдите базовый модуль Qu расширенная загрузка ассетов async/defer/media (devnseo.qu) и нажмите «Установить».
  • Найдите модуль Лайки, звезды, рейтинги, оценки (devnseo.liker) и нажмите «Установить».

Важно: модуль требует установленного модуля devnseo.qu (Qu расширенная загрузка ассетов async/defer/media). При установке это будет проверено.

Настройка

Перейдите в «Сервисы» → «Лайки, звезды, рейтинги, оценки» → вкладка «Настройки».

Доступные опции (для каждого сайта отдельно):

Параметр Описание
allow_guests Разрешить голосование гостям (неавторизованным пользователям).
guest_check_ip Проверять уникальность гостя по IP-адресу. Если выключено, используется токен.
allow_container_create Разрешить создание контейнеров при голосовании.
allowed_target_keys Список разрешённых TARGET_KEY (через запятую или с новой строки). Если поле пустое – ограничение не действует.
debug_enabled Включить отладочный лог (AddMessage2Log) для отладки работы модуля.

⚠️ Внимание!

Определитесь с определением по ip (guest_check_ip) сразу, т.к. от этой настройки технически зависит голосование за лайки. Если активировать позже, то могут быть некоторые артефакты, связанные с тем что использовались разные схемы голосования.

Типы реакций

Модуль поддерживает четыре типа реакций "из коробки". Каждый тип определяет логику агрегации, формат ответа и способ отображения.

Тип Код Описание Поля ответа
Лайк like Классический лайк (сердечко). Пользователь может поставить или убрать лайк. total, total_likes, user_value
Звёзды stars Рейтинг по звёздам (1–5, настраиваемый максимум). Поддерживает распределение голосов. avg_rating, max_stars, stats, stars_distribution, stars_percents
Пальцы thumbs Голосование «палец вверх» / «палец вниз». Значения: +1 и -1. total_likes, total_dislikes, avg_rating, score_class
Эмодзи emoji Выбор одного из нескольких эмодзи
(по умолчанию 5). Хранит распределение.
stats, emoji_percents, user_value

Типы реакций можно расширить и добавить свои.

пример лайки, звезды, рейтинги, оценки для 1С-Битрикс

Обзор компонента devnseo:liker

Компонент для быстрого вывода виджета реакции на странице.

Параметры

Параметр Тип По умолчанию Описание
SITE_ID string текущий сайт Идентификатор сайта
TARGET_KEY string обязательно Тип объекта (например, catalog_product, blog_post)
TARGET_ID string обязательно Идентификатор объекта
TYPE string like Тип реакции: like, stars, thumbs, emoji
TAG string '' Дополнительный тег для разграничения контекстов
THEME string default Тема оформления (может использоваться в шаблоне)
ALLOW_GUESTS boolean null Разрешить гостям голосовать для этого конкретного контейнера (переопределяет глобальную настройку)
ACTIVE boolean true Активен ли контейнер (если false – виджет больше не доступен для голосования)
MAX_STARS int 5 Максимальное количество звёзд (для типа stars)
MAX_EMOJI int 5 Максимальное количество эмодзи (для типа emoji)
LEXICON_JSON string '' JSON-объект с переопределениями текстов для уведомлений
Обзор компонента devnseo:liker Лайки, звезды, рейтинги, оценки для 1С-Битрикс

Пример вызова

<?php $APPLICATION->IncludeComponent(
    "devnseo:liker",
    "stars",
    [
        "TARGET_KEY" => "catalog_product",
        "TARGET_ID" => $arResult["ID"],
        "TYPE" => "stars",
        "MAX_STARS" => 7,
        "ALLOW_GUESTS" => null
    ]
); ?>

Структура вывода

Компонент рендерит HTML-обёртку с атрибутами data-qu-liker-*, которая затем инициализируется JS-скриптом модуля. В зависимости от шаблона могут выводиться различные элементы: кнопки, звёзды, эмодзи, виджет со статистикой.

Поддерживаются пользовательские шаблоны – скопируйте папку /bitrix/components/devnseo/liker/templates/.default в /local/templates/ваш_шаблон/components/devnseo/liker/ и модифицируйте template.php и view.php. Разделение на template и view необходимо для того, чтобы не дублировать верстку на композитных страницах, где необходимо загружать большие коллекции объектов.

Как привязываются голоса к объекту

Чтобы на одном сайте было много разных рейтингов (товары, статьи, комментарии, страницы) и они не путались между собой, модуль создаёт для каждого объекта уникальный ключ. Этот ключ строится из комбинации ваших настроек.

Принцип простой: если вы измените хотя бы один из перечисленных ниже параметров – создастся новый контейнер для голосования, а старые голоса останутся в старом.

Из чего состоит ключ

SHA1( SITE_ID + TARGET_KEY + TARGET_ID + TYPE + TAG )

SHA1 – это хеш-функция, которая превращает строку в уникальный идентификатор.

Разберём каждый параметр

Параметр Что задаёт Пример
SITE_ID Сайт, на котором работает виджет. Голоса с разных сайтов не смешиваются. s1, s2
TARGET_KEY Тип объекта, к которому относится голосование. Это как категория. catalog_product, blog_post, comment, page
TARGET_ID Конкретный объект внутри типа. Это может быть ID товара, символьный код или даже URL страницы. 123, product-456, /about/
TYPE Тип реакции: like, stars, thumbs, emoji. like, stars
TAG Дополнительный тег для разграничения контекстов. Например, чтобы отделить голоса из мобильной версии от десктопной. mobile, sidebar, main

⚠️ Важно!

TARGET_KEY + TARGET_ID - основная связка. Категория объектов TARGET_KEY, а в нее вложены объекты TARGET_ID, которые должны быть уникальны в рамках категории. Например категория TARGET_KEY: pages и внутри нее TARGET_ID: с урлами страниц. Обратите внимание что урлы которые отличаются регистром или даже одним символом — будут разными объектами, лучше получать например: $APPLICATION->GetCurPage(). Там где возможно привязаться к инфоблоку — лучше привязываться к числовым id. При смене урл — это будет уже другой контейнер! Так же если ваша страница доступна по разным адресам — это разные сущности!

Примеры

Вот как могут выглядеть разные контейнеры для одного и того же товара:

// Рейтинг товара 123 через звёзды
SITE_ID = s1
TARGET_KEY = catalog_product
TARGET_ID = 123
TYPE = stars
TAG = 
// → создаётся контейнер для звёзд товара 123

// Лайки для этого же товара
SITE_ID = s1
TARGET_KEY = catalog_product
TARGET_ID = 123
TYPE = like
TAG = 
// → создаётся другой контейнер (уже для лайков)

// А это – отдельный тег для того же товара для создания списка избранного у пользователя
SITE_ID = s1
TARGET_KEY = catalog_product
TARGET_ID = 123
TYPE = like
TAG = favorite
// → третий контейнер, потому что тег другой


// Рейтинг страницы /about/service/ через звёзды
SITE_ID = s1
TARGET_KEY = pages
TARGET_ID = /about/service/
TYPE = stars
TAG = 
// → создаётся контейнер для страницы /about/service/

⚠️ Важно понимать

  • Если вы поменяете любой из этих параметров после того, как уже собрали голоса – старые голоса не пропадут, но они будут привязаны к старому контейнеру. Новый контейнер начнёт собирать статистику с нуля.
  • Регистр букв имеет значение. catalog_product и Catalog_Product – это разные ключи.
  • В TARGET_KEY и TAG лучше использовать только латинские буквы, цифры, подчёркивания и дефисы.
  • Если вы не знаете, какой TARGET_KEY использовать – просто придумайте свой (например, my_object). Главное, чтобы он был осмысленным и не менялся в будущем.

Как это помогает

  • Вы можете разместить несколько виджетов на одной странице – для товара, для его отзывов, для комментариев – и у каждого будет своя статистика.
  • Вы можете переиспользовать один и тот же компонент – например, показывать рейтинг товара в карточке и в списке – голоса будут общими.
  • Если вы переносите сайт на другой домен, но SITE_ID остаётся тем же – голоса сохранятся.

Синхронизация состояний

Модуль использует три независимых механизма синхронизации, каждый из которых решает свою задачу. Все они работают автоматически и не требуют ручного вмешательства.

Механизм Компонент Когда срабатывает Что делает
Голосование liker.js
(библиотека qu.js)
При клике на виджет Отправляет запрос на vote.php, обновляет конкретный виджет и сохраняет состояние в localStorage.
Пакетная синхронизация liker.sync.js
(отдельный скрипт)
При загрузке страницы (один раз) Собирает все виджеты с data-qu-liker-wrapper-cached, отправляет один запрос на syncBatch и обновляет их.
Смена авторизации Inline-скрипт с id
liker-auth-events-inline
При входе или выходе пользователя Отслеживает изменение BX.message.USER_ID и вызывает Qu.Liker.syncFromServer() для обновления всех виджетов.

1. Голосование (клик) – liker.js

Это основная библиотека (https://github.com/qujs-dev/liker), которая подключается по умолчанию, если компонент присутствует на странице. При клике на виджет отправляется AJAX-запрос на /bitrix/tools/devnseo.liker/vote.php. Сервер обрабатывает голос, обновляет контейнер и возвращает актуальные данные. Виджет обновляет счётчики, состояние (user_value) и сохраняет данные в localStorage.

2. Пакетная синхронизация для адаптера – liker.sync.js

Этот скрипт подключается отдельно через Assets::include('liker-init-sync') и используется только для виджетов, выведенных через адаптер (с атрибутом data-qu-liker-wrapper-cached).

  1. Скрипт находит все виджеты с селектором [data-qu-liker-wrapper-cached] [data-qu-liker].
  2. Группирует их по контексту (site_id, target_id, target_key, type, tag).
  3. Отправляет один POST-запрос на /bitrix/tools/devnseo.liker/liker.php?action=syncBatch с массивом контекстов.
  4. Получает данные для всех виджетов и обновляет их (счётчики, состояние голоса, атрибуты).
  5. Генерирует событие qu:Liker:adapter:sync на window с данными { items, grouped, timestamp }.

Событие qu:Liker:adapter:sync

// Через Que
Que(function (Qu, detail) {
    console.log('Синхронизация завершена', detail);
}, 'qu:Liker:adapter:sync');

Важно: событие генерируется только при успешной синхронизации.

3. Смена авторизации – inline-скрипт liker-auth-events

Это небольшой inline-скрипт, который регистрируется в /lib/Handler.php ассетом liker-auth-events и подключается автоматически как зависимость liker-init-js. Он подписывается на изменение BX.message.USER_ID через localStorage.

  • При входе пользователя BX.message.USER_ID меняется с 0 на ID пользователя.
  • При выходе – с ID на 0.
  • Скрипт видит изменение и вызывает Qu.Liker.syncFromServer().
  • Все виджеты обновляются в зависимости от авторизации и настроек.

Ручная синхронизация

Если требуется принудительно обновить все виджеты на странице:

Qu.Liker.syncFromServer();

Важно

  • liker.sync.js нужен только для виджетов через адаптер. Если используете обычный компонент devnseo:liker – этот скрипт не нужен.
  • Смена авторизации работает автоматически через liker-auth-events, который подключается вместе с liker-init-js.
  • Все три механизма работают независимо и дополняют друг друга.

Переопределение ассетов через Qu

Все клиентские файлы модуля (JS, CSS, inline-скрипты) управляются через систему ассетов devnseo.qu. Это позволяет переопределить любой ассет без изменения кода модуля — достаточно создать файл-оверрайд в проекте.

Приоритеты

  1. Базовый конфиг — из модуля или события OnQuAssetsBuild.
  2. Оверрайд из /local/php_interface/qu/common/ — для всех сайтов.
  3. Оверрайд из /local/php_interface/qu/SITE_ID/ — для конкретного сайта (высший приоритет).

Важно: оверрайд полностью заменяет конфиг ассета.

Пример: переопределение инлайн-CSS

В модуле Liker CSS подключён как инлайн-стили в ассете liker-init-css (файл /lib/Handler.php). Чтобы заменить их на свои стили:

  1. Создайте папку /local/php_interface/qu/common/ (или /local/php_interface/qu/SITE_ID/ для конкретного сайта).
  2. Создайте файл liker-init-css.php:
<?php
// /local/php_interface/qu/common/liker-init-css.php
return [
    'aliases' => ['latest' => '1.0'],
    'versions' => [
        '1.0' => [
            'files' => [
                'liker-css-inline' => [
                    'type' => 'inline',
                    'inlineType' => 'css',
                    'content' => '
                        [data-qu-liker-wrapper] {
                            display: inline-flex;
                        }
                        .qu-liker-wrapper {
                            opacity: 1;
                            transition: opacity 0.3s ease;
                        }
                        
                        .qu-liker-wrapper--loading {
                            opacity: 0;
                            pointer-events: none;
                        }
                        
                        .qu-liker-wrapper--no-anim * {
                            transition: none !important;
                            animation: none !important;
                        }
                        
                        @starting-style {
                            .qu-liker-wrapper {
                                opacity: 0;
                            }
                        }
                    ',
                    'attrs' => [],
                ],
            ],
        ],
    ],
];

Теперь вместо стандартных стилей будут подключаться ваши.

События

Модуль генерирует события, на которые можно подписаться для расширения функциональности.

Событие Описание
OnBuildReactionRegistry Позволяет зарегистрировать собственные типы реакций.
OnBeforeVote Вызывается перед выполнением голосования. Можно отменить голосование, изменить контекст, актора или значение.
OnAfterVote Вызывается после успешного голосования. Можно выполнить дополнительные действия (логирование, уведомления и т.д.).

Пример подписки на OnBeforeVote

EventManager::getInstance()->addEventHandler(
    'devnseo.liker',
    'OnBeforeVote',
    static function (Event $event) {
        $context = $event->getParameter('context');
        $actor   = $event->getParameter('actor');
        $value   = $event->getParameter('value');

        // Логируем для проверки
        AddMessage2Log('[OnBeforeVote] targetKey=' . $context->targetKey . ', old value=' . $value);

        // Пример модификации: если значение < 5, увеличиваем до 5
        if ((int)$value < 5) {
            $newValue = 5;
        } else {
            $newValue = (int)$value;
        }

        return new EventResult(
            EventResult::SUCCESS,
            [
                'value' => $newValue,
            ]
        );
    }
);

Адаптер для массовой загрузки данных (LikerViewAdapter)

При работе с большими списками (каталог товаров, новости, блог, комментарии) каждый элемент должен отображать свой виджет реакций. Если для каждого элемента делать отдельный запрос к БД – это приведёт к проблеме N+1 и сильной нагрузке на сервер, даже при использовании технологии композитный сайт, т.к. каждый вызов компонента будет создавать отдельный запрос. Модуль предоставляет адаптер \devNseo\Liker\Service\LikerViewAdapter, который позволяет загрузить данные для всех элементов на странице за один ajax и один SQL запрос, так же как делает это композитный сайт. Для адаптера необходимо вывести только верстку, без вызова компонента, это так же позволяет не создавать объекты на каждый вывод виджета, до их реальной инициализации (первого голоса).

Методы адаптера

Метод Описание
build_product_map(array $targets, string $site_id): array Принимает массив целей (каждый элемент – массив с полями target_id, target_key, type, tag, theme) и возвращает карту, где ключ – уникальный идентификатор цели, а значение – узел с данными контейнера и готовым ответом.
make_target_map_key(array $target): string Генерирует уникальный ключ для цели (используется как индекс в карте).
build_empty_node(int $target_id, string $target_key, string $type, string $tag, string $theme): array Создаёт узел для цели, у которой ещё нет контейнера (голосов). Используется как fallback.
build_view(array $node): array Преобразует узел в массив, готовый для передачи в шаблон (LIKER_STARS_VIEW и т.д.). Включает все необходимые поля для рендеринга виджета.
resolve_template_view(string $template_name, string $file_name = 'view.php'): string Возвращает путь к файлу шаблона для указанного имени шаблона. Используется для подключения нужного view.php.

Пример использования в компоненте каталога (и других)

1. В result_modifier.php собираем цели и строим карты

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();

use Bitrix\Main\Loader;
use devNseo\Liker\Service\LikerViewAdapter;

$component = $this->getComponent();
$arParams = $component->applyTemplateModifications();

if (!Loader::includeModule('devnseo.liker')) {
    return;
}

$adapter = new LikerViewAdapter();

$allTargets = [];

foreach ($arResult['ITEMS'] as $item) {
    $targetId = (int)($item['ID'] ?? 0);
    if ($targetId <= 0) {
        continue;
    }

    // Звёзды
    $allTargets[] = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'stars',
        'tag' => '',
        'theme' => '',
    ];

    // Лайки
    $allTargets[] = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'like',
        'tag' => '',
        'theme' => '',
    ];

    // Эмодзи
    $allTargets[] = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'emoji',
        'tag' => '',
        'theme' => '',
    ];

    // Thumbs
    $allTargets[] = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'thumbs',
        'tag' => '',
        'theme' => '',
    ];
}

// Один запрос на все цели
$allMap = $adapter->build_product_map($allTargets, SITE_ID);

foreach ($arResult['ITEMS'] as &$item) {
    $targetId = (int)($item['ID'] ?? 0);
    if ($targetId <= 0) {
        continue;
    }

    // Stars
    $starsTarget = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'stars',
        'tag' => '',
        'theme' => '',
    ];
    $starsKey = $adapter->make_target_map_key($starsTarget);
    $starsNode = $allMap[$starsKey] ?? $adapter->build_empty_node(
        $starsTarget['target_id'],
        $starsTarget['target_key'],
        $starsTarget['type'],
        $starsTarget['tag'],
        $starsTarget['theme']
    );
    
    $item['LIKER_STARS_NODE'] = $starsNode;
    $item['LIKER_STARS_VIEW'] = $adapter->build_view($starsNode);

    // Like
    $likeTarget = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'like',
        'tag' => '',
        'theme' => '',
    ];
    $likeKey = $adapter->make_target_map_key($likeTarget);
    $likeNode = $allMap[$likeKey] ?? $adapter->build_empty_node(
        $likeTarget['target_id'],
        $likeTarget['target_key'],
        $likeTarget['type'],
        $likeTarget['tag'],
        $likeTarget['theme']
    );
    
    /*
    // lexicon лучше передавать на враппер коллекции элементов, чтобы не дублировать
    $likeNode['lexicon'] = [
        'like_success_message' => 'Добавлено в избранное!',
    ];
    */
    $item['LIKER_LIKE_NODE'] = $likeNode;
    $item['LIKER_LIKE_VIEW'] = $adapter->build_view($likeNode);

    // Emoji
    $emojiTarget = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'emoji',
        'tag' => '',
        'theme' => '',
    ];
    $emojiKey = $adapter->make_target_map_key($emojiTarget);
    $emojiNode = $allMap[$emojiKey] ?? $adapter->build_empty_node(
        $emojiTarget['target_id'],
        $emojiTarget['target_key'],
        $emojiTarget['type'],
        $emojiTarget['tag'],
        $emojiTarget['theme']
    );
    $item['LIKER_EMOJI_NODE'] = $emojiNode;
    $item['LIKER_EMOJI_VIEW'] = $adapter->build_view($emojiNode);

    // Thumbs
    $thumbsTarget = [
        'target_id' => $targetId,
        'target_key' => 'catalog_product',
        'type' => 'thumbs',
        'tag' => '',
        'theme' => '',
    ];
    $thumbsKey = $adapter->make_target_map_key($thumbsTarget);
    $thumbsNode = $allMap[$thumbsKey] ?? $adapter->build_empty_node(
        $thumbsTarget['target_id'],
        $thumbsTarget['target_key'],
        $thumbsTarget['type'],
        $thumbsTarget['tag'],
        $thumbsTarget['theme']
    );
    $item['LIKER_THUMBS_NODE'] = $thumbsNode;
    $item['LIKER_THUMBS_VIEW'] = $adapter->build_view($thumbsNode);
}
unset($item);

2. В шаблоне (template.php) выводим виджет

Вызываем не компонент, а просто выводим верстку из шаблона компонента, которую оборачиваем в контейнер с data-qu-liker-wrapper-cached.

<?php
use devNseo\Liker\Service\LikerViewAdapter;
// например при выводе карточки товара
$adapter = new LikerViewAdapter();

$starsView = is_array($item['LIKER_STARS_VIEW'] ?? null) ? $item['LIKER_STARS_VIEW'] : [];
$starsView['source'] = 'catalog_card';
$starsView['max_stars'] = 3; // обратите внимание что для того чтобы значение передалось на сервер при голосовании его надо установить на этот тип объекта через событие onBuildReactionContext

$starsTemplateFile = $adapter->resolve_template_view('stars_extended', 'view.php');

if ($starsView && $starsTemplateFile !== '') {
    extract($starsView, EXTR_OVERWRITE);
    ?>
    <div class="catalog-item-stars qu-liker-wrapper qu-liker-wrapper--loading" data-qu-liker-wrapper data-qu-liker-wrapper-cached>
        <?php include $starsTemplateFile; ?>
    </div>
    <?php
}
?>
<hr>
<?php

$adapter = new LikerViewAdapter();

$likeView = is_array($item['LIKER_LIKE_VIEW'] ?? null) ? $item['LIKER_LIKE_VIEW'] : [];
$likeView['source'] = 'catalog_card';
$likeTemplateFile = $adapter->resolve_template_view('like', 'view.php');

if ($likeView && $likeTemplateFile !== '') {
    extract($likeView, EXTR_OVERWRITE);
?>
    <div class="catalog-item-like qu-liker-wrapper qu-liker-wrapper--loading" data-qu-liker-wrapper data-qu-liker-wrapper-cached>
        <?php include $likeTemplateFile; ?>
    </div>
    <?php
}
?>
<hr>
<?php
$adapter = new LikerViewAdapter();

$emojiView = is_array($item['LIKER_EMOJI_VIEW'] ?? null) ? $item['LIKER_EMOJI_VIEW'] : [];
$emojiView['source'] = 'catalog_card';
$emojiTemplateFile = $adapter->resolve_template_view('emoji', 'view.php');

if ($emojiView && $emojiTemplateFile !== '') {
    extract($emojiView, EXTR_OVERWRITE);
?>
    <div class="catalog-item-emoji qu-liker-wrapper qu-liker-wrapper--loading"
         data-qu-liker-wrapper
         data-qu-liker-wrapper-cached>
        <?php include $emojiTemplateFile; ?>
    </div>
    <?php
}
?>
<hr>
<?php
$adapter = new LikerViewAdapter();

$thumbsView = is_array($item['LIKER_THUMBS_VIEW'] ?? null) ? $item['LIKER_THUMBS_VIEW'] : [];
$thumbsView['source'] = 'catalog_card';
$thumbsTemplateFile = $adapter->resolve_template_view('thumbs', 'view.php');

if ($thumbsView && $thumbsTemplateFile !== '') {
    extract($thumbsView, EXTR_OVERWRITE);
?>
    <div class="catalog-item-thumbs qu-liker-wrapper qu-liker-wrapper--loading"
         data-qu-liker-wrapper
         data-qu-liker-wrapper-cached>
        <?php include $thumbsTemplateFile; ?>
    </div>
    <?php
}
?>

3. Подключаем скрипт синхронизации

В component_epilog.php добавляем:

use Bitrix\Main\Loader;
if (Loader::includeModule('devnseo.liker')) {
    \devNseo\Qu\Assets::include('liker-init-sync');
}

Это подключит JS-скрипт только на той странице где это действительно необходимо, и, который обработает все виджеты data-qu-liker-wrapper-cached и синхронизирует их одним запросом.

Преимущества использования адаптера

  • Минимальное количество запросов к БД — собирает данные для всех виджетов на странице и делает один запрос к БД, а не по одному на каждый вызов компонента.
  • При этом добавляется один дополнительный AJAX-запрос (аналогично композитному) для синхронизации состояний всех виджетов на странице.
  • Простота кастомизации – не нужно копировать верстку в шаблоны каталога/новостей/комментариев и т.д. используется resolve_template_view, который подтягивает стандартную верстку компонента.

Итог: один SQL-запрос вместо N на каждый вызов компонента + один AJAX-запрос на всю страницу — оптимальная стратегия для масштабируемых проектов.

Событие onBuildReactionContext

Событие onBuildReactionContext вызывается при каждом AJAX-запросе голосования (в файле /bitrix/tools/devnseo.liker/vote.php) перед вызовом ReactionManager::vote(). Оно позволяет модифицировать $context->meta, и эти изменения влияют на контейнер при каждом голосовании, а не только при создании.

Как это работает

  1. В vote.php вызывается событие, вы меняете $context->meta['max_stars'] (или другие поля).
  2. Затем вызывается ReactionManager::vote($context, $actor, $value).
  3. Внутри vote() для каждого типа реакции вызывается метод applyToContainer() соответствующего класса.
  4. Некоторые типы реакций (например, StarsReactionType, EmojiReactionType) используют $context->meta для обновления JSON_META контейнера:
    • StarsReactionType – читает $context->meta['max_stars'] и обновляет максимальное количество звёзд.
    • EmojiReactionType – читает $context->meta['max_emoji'] и обновляет максимальное количество эмодзи.
    • Другие поля – сохраняются в JSON_META только если они явно обработаны в applyToContainer() соответствующего типа. Если вы хотите добавить свои поля, вам нужно модифицировать код типа реакции, чтобы он их использовал (например, добавить чтение $context->meta['my_field'] в applyToContainer()).
  5. После обновления $container['JSON_META'] вызывается ContainerRepository::saveAggregateState(), которая сохраняет изменения в БД.

Таким образом, изменения через событие применяются при каждом голосовании.

Какие параметры можно менять через событие

  • Для типа starsmax_stars (меняет максимальное количество звёзд).
  • Для типа emojimax_emoji (меняет максимальное количество эмодзи).
  • allowGuests – отдельное свойство контекста (не meta), разрешающее или запрещающее голосование гостям для данного контейнера.
  • Для других типов – изменения в $context->meta не будут сохранены, если они не обработаны в applyToContainer() соответствующего типа.

Важно: не все типы реакций используют meta для обновления контейнера. Например, LikeReactionType не использует meta в applyToContainer(), поэтому изменения через событие не повлияют на него.

Где это реализовано в коде

  • lib/Reaction/StarsReactionType.php – метод applyToContainer():
    $maxStars = (int)($context->meta['max_stars'] ?? $meta['max_stars'] ?? 5);
    $meta = $this->normalizeMeta($meta, $maxStars);
    $container['JSON_META'] = $meta;
  • lib/Reaction/EmojiReactionType.php – аналогично для max_emoji.
  • lib/Service/ReactionManager.php – вызывает $type->applyToContainer() для каждого типа.
  • lib/Service/ContainerRepository.php – метод saveAggregateState() сохраняет обновлённый JSON_META.

Пример обработчика

В /local/php_interface/init.php:

use devNseo\Liker\Service\ReactionContext;

EventManager::getInstance()->addEventHandler(
    'devnseo.liker',
    'onBuildReactionContext',
    static function (Event $event) {
        $context = $event->getParameter('context');

        if (!$context instanceof ReactionContext) {
            return;
        }

        $source = (string)($context->meta['source'] ?? '');

        // Для звёзд в каталоге – максимум 3
        if ($context->type === 'stars' && $source === 'catalog_card') {
            $context->meta['max_stars'] = 3;
            $context->allowGuests = 0; // и например передать параметр только для авторизрованных
        }

        // Для звёзд на детальной странице – максимум 10
        if ($context->type === 'stars' && $source === 'product_detail') {
            $context->meta['max_stars'] = 10;
        }

        // Для эмодзи в каталоге – максимум 6
        if ($context->type === 'emoji' && $source === 'catalog_card') {
            $context->meta['max_emoji'] = 6;
        }

        // Другие поля не сохранятся в JSON_META, если их не обработает тип реакции
        // Чтобы добавить своё поле, нужно модифицировать applyToContainer() соответствующего типа.
    }
);

Когда это полезно

  • Устанавливать дефолтные значения и динамически менять max_stars или max_emoji для виджетов подключаемых через адаптер.
  • Добавлять служебные метаданные — но только если вы доработаете тип реакции, чтобы он их сохранял.

Событие onBuildReactionContext позволяет динамически менять параметры контейнера при каждом голосовании, но только те, которые явно обрабатываются в методе applyToContainer() конкретного типа реакции. Для stars и emoji это max_stars и max_emoji соответственно. Для добавления своих полей нужно модифицировать соответствующий тип реакции.

Расширение и создание своих типов реакций

Модуль позволяет не только использовать встроенные типы (like, stars, thumbs, emoji), но и расширять их поведение или добавлять совершенно новые типы.

Структура файлов для кастомизации

Все пользовательские классы и обработчики событий рекомендуется размещать в папке /local/php_interface/devnseo.liker/:

  • /local/php_interface/init.php – главный файл для регистрации событий.
  • /local/php_interface/devnseo.liker/LikeReactionTypeOverride.php – класс для переопределения LikeReactionType.
  • /local/php_interface/devnseo.liker/SmileReactionType.php – класс для нового типа «Смайлы».

1. Расширение существующего типа (переопределение Like)

Создайте файл /local/php_interface/devnseo.liker/LikeReactionTypeOverride.php:

<?php
namespace Custom\devNseo\Liker;

use devNseo\Liker\Reaction\LikeReactionType;
use devNseo\Liker\Service\ReactionContext;

class LikeReactionTypeOverride extends LikeReactionType
{
    public function applyToContainer( 
        array $container,
        string $action,
        $newValue,
        $oldValue,
        ReactionContext $context
    ): array {
        // Инициализируем значения
        $container['TOTAL_VOTES'] = (int)($container['TOTAL_VOTES'] ?? 0);
        $container['TOTAL_LIKES'] = (int)($container['TOTAL_LIKES'] ?? 0);

        if ($action === 'create') {
            // Добавляем 2 лайка
            $container['TOTAL_VOTES'] += 2;
            $container['TOTAL_LIKES'] += 2;
        } elseif ($action === 'delete') {
            // Удаляем 2 лайка (но не меньше 0)
            $container['TOTAL_VOTES'] = max(0, $container['TOTAL_VOTES'] - 2);
            $container['TOTAL_LIKES'] = max(0, $container['TOTAL_LIKES'] - 2);
        }
        // Для like update не используется, но если понадобится – можно добавить

        return $container;
    }
}

2. Создание нового типа (Смайлы)

Создайте файл /local/php_interface/devnseo.liker/SmileReactionType.php:

<?php

namespace Custom\devNseo\Liker;

use devNseo\Liker\Reaction\AbstractReactionType;
use devNseo\Liker\Service\ReactionContext;

class SmileReactionType extends AbstractReactionType
{
    public function getCode(): string
    {
        return 'smile';
    }

    public function applyToContainer(
        array $container,
        string $action,
        $newValue,
        $oldValue,
        ReactionContext $context
    ): array {
        $meta = $this->extractMeta($container['JSON_META'] ?? []);
        $stats = $meta['stats'] ?? ['smiles' => [1 => 0, 2 => 0, 3 => 0]];

        if ($action === 'create') {
            $stats['smiles'][(int)$newValue]++;
        } elseif ($action === 'delete') {
            $stats['smiles'][(int)$oldValue] = max(0, $stats['smiles'][(int)$oldValue] - 1);
        } elseif ($action === 'update') {
            $stats['smiles'][(int)$oldValue] = max(0, $stats['smiles'][(int)$oldValue] - 1);
            $stats['smiles'][(int)$newValue]++;
        }

        $meta['stats'] = $stats;
        $container['JSON_META'] = $meta;
        $container['TOTAL_VOTES'] = array_sum($stats['smiles']);
        $container['TOTAL_LIKES'] = $container['TOTAL_VOTES'];
        return $container;
    }

    public function buildResponse(array $container, ?array $record = null): array
    {
        $data = parent::buildResponse($container, $record);
        $meta = $this->extractMeta($container['JSON_META'] ?? []);
        $stats = $meta['stats']['smiles'] ?? [1 => 0, 2 => 0, 3 => 0];
        $totalVotes = (int)($container['TOTAL_VOTES'] ?? 0);

        $percents = [];
        foreach ($stats as $value => $count) {
            $percents[$value] = $totalVotes > 0 ? (int)round(($count / $totalVotes) * 100) : 0;
        }

        $data['smiles_distribution'] = $stats;
        $data['smiles_percents'] = $percents;
        return $data;
    }

    public function recountFromRecords(array $container, array $records, ReactionContext $context): array
    {
        $stats = [1 => 0, 2 => 0, 3 => 0];
        foreach ($records as $record) {
            $value = (int)($record['VALUE'] ?? 0);
            if (in_array($value, [1, 2, 3])) {
                $stats[$value]++;
            }
        }
        $container['JSON_META'] = ['stats' => ['smiles' => $stats]];
        $container['TOTAL_VOTES'] = array_sum($stats);
        $container['TOTAL_LIKES'] = $container['TOTAL_VOTES'];
        return $container;
    }

    private function extractMeta($meta): array
    {
        if (is_string($meta)) {
            $decoded = json_decode($meta, true);
            return is_array($decoded) ? $decoded : [];
        }
        return is_array($meta) ? $meta : [];
    }
}

3. Регистрация кастомных типов

В файле /local/php_interface/init.php добавьте:

EventManager::getInstance()->addEventHandler(
    'devnseo.liker',
    'OnBuildReactionRegistry',
    static function (Event $event) {
        $registry = $event->getParameter('registry');

        require_once __DIR__ . '/devnseo.liker/LikeReactionTypeOverride.php';
        require_once __DIR__ . '/devnseo.liker/SmileReactionType.php';
        
        // Заменяем стандартный like на переопределённый
        $registry->register(new Custom\devNseo\Liker\LikeReactionTypeOverride(), true);
        
        // Добавляем новый тип «Смайлы»
        $registry->register(new Custom\devNseo\Liker\SmileReactionType());
    }
);

⚠️ Важно!

  • При переопределении существующего типа вы полностью заменяете его логику. Убедитесь, что ваш класс корректно обрабатывает все случаи (создание, обновление, удаление, пересчёт).
  • Если вы добавляете новый тип, укажите для него уникальный code, который не совпадает со стандартными (like, stars, thumbs, emoji).
  • Для корректной работы в административном интерфейсе и в ответах виджетов ваш тип должен возвращать все необходимые поля в buildResponse().
  • При использовании адаптера LikerViewAdapter вам нужно будет доработать шаблоны для отображения нового типа, так как стандартные шаблоны рассчитаны на встроенные типы.

Раздел «Избранное»

Модуль позволяет реализовать персональное избранное для пользователей с помощью механизма tag. Для этого используется обычный тип реакции (TYPE) like, но с тегом (TAG) например favorite, который отделяет избранное от обычных лайков и выберите шаблон компонента favorite.

И, например, укажите словарь (JSON) для компонента:

{
    "like_success_title": "Добавлено в избранное!",
    "like_success_message": "Товар добавлен в ваш список избранного.",
    "like_delete_title": "Удалено из избранного",
    "like_delete_message": "Товар удалён из списка избранного."
}
Создание избранного для 1С-Битрикс

Или при вызове в шаблоне страницы/другого компонента.

<?php
$APPLICATION->IncludeComponent(
    "devnseo:liker",
    "favorite",
    [
        "TARGET_KEY" => "products",
        "TARGET_ID" => $arResult["ID"],
        "TYPE" => "like",
        "TAG" => "favorite",
        "LEXICON_JSON" => '{
            "like_success_title": "Добавлено в избранное!",
            "like_success_message": "Товар добавлен в ваш список избранного.",
            "like_delete_title": "Удалено из избранного",
            "like_delete_message": "Товар удалён из списка избранного."
        }',
    ]
);
?>

Страница избранного (список объектов пользователя)

На отдельной странице избранного нужно показать только те товары, которые пользователь добавил в избранное. Для этого используется запрос к RecordTable по USER_ID и TAG = 'favorite'.

Получение ID объектов, добавленных пользователем:

<?php
use devNseo\Liker\Model\RecordTable;

global $USER;
$userId = (int)$USER->GetID();

$records = RecordTable::getList([
    'select' => ['CONTAINER_TARGET_ID' => 'CONTAINER.TARGET_ID'],
    'filter' => [
        '=USER_ID' => $userId,
        '=CONTAINER.TAG' => 'favorite',
        '=CONTAINER.TYPE' => 'like',
        '=CONTAINER.SITE_ID' => SITE_ID,
    ],
    'order' => ['ID' => 'DESC'], // ASC – сначала старые, DESC – сначала новые
])->fetchAll();

$favoriteIds = array_column($records, 'CONTAINER_TARGET_ID');
$GLOBALS['favoriteIds'] = $favoriteIds; 

Передача ID в компонент каталога:

<?php
        
if (!empty($favoriteIds)) {
    global $arrFilterFavorite;
    $arrFilterFavorite = ['=ID' => $favoriteIds];
 
    $APPLICATION->IncludeComponent(
        "bitrix:catalog.section",
        "", // шаблон
        [
            // Обязательный фильтр по ID
            "FILTER_NAME" => "arrFilterFavorite",
 
            "IBLOCK_ID" => 1, // ID инфоблока
            "SECTION_ID" => 0, // 0 – все элементы (или ID раздела)
        ],
        false
    );
} else {
    echo "

У вас пока нет избранных товаров.

"; }

В result_modifier.php пробрасываем сортировку по id.

<?php
if (!empty($GLOBALS['favoriteIds'])) {
    $orderMap = array_flip(array_map('intval', $GLOBALS['favoriteIds']));
    usort($arResult['ITEMS'], function($a, $b) use ($orderMap) {
        $idA = (int)$a['ID'];
        $idB = (int)$b['ID'];
        $posA = isset($orderMap[$idA]) ? $orderMap[$idA] : PHP_INT_MAX;
        $posB = isset($orderMap[$idB]) ? $orderMap[$idB] : PHP_INT_MAX;
        return $posA - $posB;
    });
}

Все можно адаптировать под свой проект и под любой другой компонент. Создавайте уникальные TARGET_KEY для каждого типа объектов, а в идеале привязывайтесь к числовым id, что позволит избежать дублирования механизма голосований.

Словарь JSON (LEXICON_JSON)

Параметр LEXICON_JSONСловарь (JSON) позволяет переопределить тексты уведомлений, которые показываются пользователю при голосовании (успех, удаление, обновление, ошибки). Это полезно для кастомизации языка интерфейса, создания собственных сценариев (например, избранное) или адаптации под разные типы реакций.

Где задаётся: в параметрах компонента devnseo:liker или в верстке parent элемента контейнера через атрибут data-qu-liker-lexicon.

Формат: JSON-объект, где ключи — строковые идентификаторы, значения — строки с текстом. Все ключи необязательны.

Ключи для успешных действий

Ключ Описание Значение по умолчанию Действие
like_success_title Заголовок уведомления при добавлении голоса «Спасибо!» create
like_success_message Текст уведомления при добавлении голоса «Ваш голос учтён.» create
like_delete_title Заголовок уведомления при удалении голоса «Голос отменён!» delete
like_delete_message Текст уведомления при удалении голоса «Вы отменили свой голос.» delete
like_update_title Заголовок уведомления при изменении голоса «Голос изменён!» update
like_update_message Текст уведомления при изменении голоса «Ваш голос изменён.» update

Ключи для ошибок

При возникновении ошибки используются соответствующие ключи. Если ключ не задан – используется значение по умолчанию.

Ключ Описание Значение по умолчанию
error_title_guest_voting_disabled Заголовок при отключении голосования для гостей «Голосование недоступно»
error_message_guest_voting_disabled Текст при отключении голосования для гостей «Гостевое голосование отключено»
error_title_guest_ip_required Заголовок при отсутствии IP у гостя «Не удалось определить гостя»
error_message_guest_ip_required Текст при отсутствии IP у гостя «Для гостя не определён IP»
error_title_guest_token_required Заголовок при отсутствии токена у гостя «Не удалось определить гостя»
error_message_guest_token_required Текст при отсутствии токена у гостя «Для гостя не определён токен»
error_title_voting_closed Заголовок при завершении голосования «Голосование завершено»
error_message_voting_closed Текст при завершении голосования «Голосование для этого объекта завершено»
error_title_target_key_not_allowed Заголовок при запрещённом TARGET_KEY «Доступ запрещён»
  • Все ключи необязательны — если не указаны, используются стандартные тексты.
  • Для кастомных типов реакций можно добавить свои ключи, обработав их в событиях или в шаблоне.
  • Ключи чувствительны к регистру (используйте нижний регистр).

Наследование параметров (data-qu-liker-inherit)

Вместо того чтобы дублировать одинаковые параметры (например, lexicon) для каждого виджета, вы можете задать их один раз на родительском контейнере и передать дочерним виджетам через механизм наследования.

Как?

  • На дочернем элементе (виджете) установите атрибут data-qu-liker-inherit.
  • На родительском элементе укажите нужные атрибуты, например data-qu-liker-lexicon.
  • Виджет скопирует все параметры с родителя, которые не заданы у него самого.

Важно: наследование работает только для атрибутов data-qu-liker-*. Сам атрибут data-qu-liker-inherit не наследуется — его нужно ставить на каждом виджете.

Пример разметки

<div data-qu-liker-lexicon='{"like_success_title":"Добавлено!","like_success_message":"Товар в избранном"}'>

    <?php $APPLICATION->IncludeComponent(
        "devnseo:liker",
        "favorite",
        [
            "TARGET_KEY" => "catalog_product",
            "TARGET_ID" => 123,
            "TYPE" => "like",
            "TAG" => "favorite",
            "INHERIT" => "Y",  // ← виджет унаследует lexicon от родителя
        ]
    ); ?>

    <?php $APPLICATION->IncludeComponent(
        "devnseo:liker",
        "favorite",
        [
            "TARGET_KEY" => "catalog_product",
            "TARGET_ID" => 456,
            "TYPE" => "like",
            "TAG" => "favorite",
            "INHERIT" => "Y",  // ← тоже унаследует
        ]
    ); ?>

</div>

// предпочтительно использовать в адаптерах
<?php
$adapter = new LikerViewAdapter();

$likeView = is_array($item['LIKER_LIKE_VIEW'] ?? null) ? $item['LIKER_LIKE_VIEW'] : [];
$likeView['source'] = 'catalog_card';
$likeView['inherit'] = true; // ← создаст аттрибут data-qu-liker-inherit
$likeTemplateFile = $adapter->resolve_template_view('like', 'view.php');

if ($likeView && $likeTemplateFile !== '') {
    extract($likeView, EXTR_OVERWRITE);
    ?>
    <div class="catalog-item-like qu-liker-wrapper qu-liker-wrapper--loading" data-qu-liker-wrapper data-qu-liker-wrapper-cached>
        <?php include $likeTemplateFile; ?>
    </div>
    <?php
}
?>

⚠️ Внимание!

Это нужно в основном для использования в адаптерах, чтобы не дублировать lexicon для каждого элемента по отдельности.

Какие параметры можно наследовать

  • lexicon – словарь текстов
  • theme – тема оформления
  • source – источник
  • Любые другие атрибуты data-qu-liker-*

Наследование работает для всех параметров, которые передаются через data-атрибуты. Это удобно для массового задания общих настроек на странице.

Важно

  • Атрибут data-qu-liker-inherit должен быть установлен на самом виджете (data-qu-liker).
  • Если на виджете уже есть какой-то параметр, он не будет перезаписан из родителя.
  • Поиск родителя с атрибутами останавливается на <body>.

Подробнее про html-js логику на странице https://qujs.ru/liker/.

API

\devNseo\Liker\Bootstrap\ModuleBootstrapFacade

Фасад для доступа к основным сервисам модуля. Все методы статические.

getRegistry()

Возвращает реестр типов реакций (ReactionTypeRegistry).

$registry = \devNseo\Liker\Bootstrap\ModuleBootstrapFacade::getRegistry();
$types = $registry->all(); // массив всех типов

getVotePolicy()

Возвращает политику голосования (VotePolicy) – определяет, разрешено ли голосование гостям, проверка IP и т.д.

$policy = \devNseo\Liker\Bootstrap\ModuleBootstrapFacade::getVotePolicy();

getContainerRepository()

Возвращает репозиторий контейнеров (ContainerRepository) – для работы с контейнерами голосования.

$repo = \devNseo\Liker\Bootstrap\ModuleBootstrapFacade::getContainerRepository();
$container = $repo->findOneById(123);

getReactionManager()

Возвращает менеджер реакций (ReactionManager) – для выполнения голосования.

$manager = \devNseo\Liker\Bootstrap\ModuleBootstrapFacade::getReactionManager();
$result = $manager->vote($context, $actor, $value);

getRecountManager()

Возвращает менеджер пересчёта (RecountManager) – для ручного пересчёта статистики контейнера.

$recount = \devNseo\Liker\Bootstrap\ModuleBootstrapFacade::getRecountManager();
$result = $recount->recountByContainerId(123);

\devNseo\Liker\Service\ReactionContext

Контекст голосования – идентифицирует объект, к которому относится реакция.

$context = new ReactionContext(
    's1',             // siteId
    'catalog_product', // targetKey
    '12345',          // targetId
    'like',           // type (like, stars, thumbs, emoji)
    'main',           // tag (дополнительный идентификатор)
    ['max_stars' => 5] // meta-данные
);

\devNseo\Liker\Service\ReactionActor

Актор голосования – пользователь или гость.

$actor = new ReactionActor(
    $userId,            // int|null – ID авторизованного пользователя
    $_SERVER['REMOTE_ADDR'], // IP гостя
    $guestToken         // токен гостя из куки
);

\devNseo\Liker\Service\ReactionManager

Основной класс для управления голосами.

vote()

Проголосовать.

$result = $manager->vote($context, $actor, $value);
if ($result->isSuccess()) {
    $data = $result->getData(); // содержит response, container, record, action
} else {
    $errors = $result->getErrors();
}

removeVote()

Отменить голосование.

$result = $manager->removeVote($context, $actor);

\devNseo\Liker\Service\ReactionRenderService

Сервис для получения состояния реакции (с кешированием).

build()

Возвращает состояние контейнера и готовый ответ для рендеринга.

$renderService = new ReactionRenderService();
$config = new ReactionRenderConfig('s1', 'catalog_product', '123', 'like', '');
$result = $renderService->build($config, $actor);
if ($result->isSuccess()) {
    $state = $result->getData();
    // $state['container'] – данные контейнера
    // $state['response'] – подготовленный ответ для виджета
}

\devNseo\Liker\Reaction\ReactionTypeRegistry

Реестр типов реакций. Позволяет регистрировать новые типы и получать существующие.

$registry = new ReactionTypeRegistry();
$registry->registerDefaults(); // регистрирует like, stars, thumbs, emoji

// Регистрация кастомного типа
$registry->register(new MyCustomReactionType());

// Получение типа
$typeResult = $registry->require('like');
$type = $typeResult->getData()['type'];

\devNseo\Liker\Service\ContainerRepository

Репозиторий для работы с контейнерами голосования.

$repo = new ContainerRepository();
// Найти контейнер по контексту
$container = $repo->findOneByContext($context);
// Получить или создать контейнер
$result = $repo->getOrCreate($context);
// Сохранить агрегированное состояние
$result = $repo->saveAggregateState($containerId, $fields);

Администрирование

Модуль предоставляет административные страницы для управления контейнерами и записями голосования, а также ручного пересчёта статистики.

Список контейнеров

Перейдите в «Сервисы» → «Лайки, звезды, рейтинги, оценки» → «Список лайков». Здесь отображаются все контейнеры с возможностью фильтрации по сайту, типу, target_key и другим полям.

Вы можете просмотреть детали контейнера, отредактировать его настройки (allow_guests, active, meta), а также выполнить пересчёт статистики.

Список записей голосования

На странице «Список голосов» перечислены все голоса с указанием контейнера, значения, пользователя (или IP/токен для гостей).

Пересчёт статистики

В процессе работы модуля статистика контейнера обновляется инкрементально:

  • при добавлении голоса — увеличивается счётчик;
  • при удалении — уменьшается;
  • при изменении значения — корректируется.

Это сделано для производительности: не нужно каждый раз перебирать все записи, чтобы пересчитать рейтинг.

Кроме того, инкрементальный режим даёт гибкость администратору — вы можете вносить правки напрямую (добавлять недостающие голоса, переносить данные из других систем, исправлять ошибки), а статистика продолжит обновляться относительно текущего состояния.

Чтобы привести статистику контейнера в соответствие с фактическими данными, на странице его редактирования нажмите кнопку «Пересчитать из голосов».

Совместимость

  • Необходим установленный модуль denvseo.qu (бесплатный).
  • PHP: версия 7.4 и выше
  • 1С-Битрикс: тестировал на 24.0.500 и выше
  • Композитный режим: +

Задать вопрос

Техническая поддержка бесплатных решений (а также помощь в установке/настройке) осуществляется на платной основе.

Поддержка платных решений:

  • Базовые консультации по работе модуля — бесплатно.
  • Любые доработки, кастомизация, помощь в настройке и интеграции — на платной основе.

Поддержка осуществляется: Пн-Пт с 18:00 до 21:00 по московскому времени (суббота, воскресенье и праздничные дни - выходные).

Время реагирования: в порядке живой очереди.

Обращаться по e-mail: info@devnseo.ru.

При обращении укажите:

  • Название модуля
  • Опишите проблему максимально подробно: шаги для воспроизведения, ожидаемый и фактический результат. Приложите скриншоты.
  • Адрес сайта
  • Доступы к админке (логин и пароль)
  • FTP или SFTP доступ (хост, логин, пароль и порт)