Skip to content
msViewCounter
msViewCounter
Счётчик просмотров и активных посетителей товара для MiniShop3
  • MODX 3
  • PHP 8.2
  • miniShop3
  1. Компоненты
  2. msViewCounter
  3. Интеграция на сайте

Интеграция и режимы ​

Плагины ​

ПлагинСобытиеНазначение
msViewCounterBootstrapOnMODXInitПодключает bootstrap.php: автозагрузчик классов и функцию msvc_get_service()
msViewCounterTrackOnLoadWebDocumentЗапись просмотра на странице товара, подключение CSS/JS

Оба плагина должны быть включены. Трекинг привязан к OnLoadWebDocument, а не к OnWebPageInit: на раннем событии ресурс может быть ещё не загружен.

Сервиса в терминах MODX пакет не регистрирует: msviewcounter в $modx->services не появляется. Вместо этого есть PHP-функция msvc_get_service(), которая собирает ViewCounter с репозиторием и помощниками и запоминает экземпляр в статической переменной. Функция объявлена в include/factory.php и становится доступна после подключения bootstrap.php. Путей подключения три: плагин msViewCounterBootstrap на OnMODXInit, сам сниппет msViewCounter (с версии 1.0.1 подключает bootstrap.php сам, если функция ещё не объявлена) и напрямую из connector.php.

msViewCounterBootstrap больше не обязателен

До версии 1.0.1 плагин был обязателен: без него msvc_get_service() не объявлялась, и страница товара отдавала HTTP 500 с Call to undefined function msvc_get_service().

Теперь сам сниппет подключает bootstrap.php, если функция ещё не объявлена, поэтому страница не падает. Но если msViewCounterBootstrap выключен, не подключатся viewcounter.js и CSS: блок и числа появятся, однако без оформления и без heartbeat, то есть online не будет продлеваться. Плагин msViewCounterTrack при этом тоже не работает — он регистрирует просмотры и ассеты на странице товара, и без него счётчик не считает вовсе.

Режимы работы ​

Настройка: msviewcounter_mode. Режим влияет на запись в БД, подключение JS и формулу чисел на витрине. Сниппет и чанк одинаковы для всех режимов.

realboostfake
Запись просмотров в БДДаДаНет
Heartbeat / online в БДДаДаНет
viewcounter.js на карточкеДа*Да*Нет
Числа на витринеКак в БДУсиленныеСинтетические
Нужен трафик для правдоподобияДаЧастичноНет

* JS подключается, если включён msviewcounter_show_online и режим не fake. Подключает его плагин msViewCounterTrack, а не наоборот.

boost с настройками по умолчанию (base = 0, multiplier = 1, jitter_max = 0) считает ровно то же, что и real, — см. Системные настройки.


real — честная статистика ​

Режим по умолчанию. На витрине показываются те же числа, что хранятся в базе.

Запись просмотра (total) ​

  1. Плагин msViewCounterTrack на OnLoadWebDocument определяет, что открыта страница товара. Признака два, и срабатывает любой из них: class_key ресурса содержит msProduct или шаблон ресурса равен значению системной настройки MiniShop3 ms3_template_product_default. Второй признак объясняет записи на «не-товарных» страницах: если шаблон карточки назначен чему-то ещё, плагин посчитает это товаром.
  2. Вызывается recordVisit(productId):
    • если block_bots и User-Agent похож на бота — выход;
    • если dedup_session и этот товар уже был в текущей PHP-сессии — total не увеличивается;
    • иначе в msviewcounter_totals выполняется INSERT … ON DUPLICATE KEY UPDATE total_views + 1.
  3. Отметка о просмотре кладётся в $_SESSION['msviewcounter_viewed_products'] как карта [productId => true]. Это флаги просмотренных товаров, а не идентификатор сессии. Идентификатор для online лежит отдельно, в $_SESSION['msviewcounter_session_id'], и создаётся как bin2hex(random_bytes(16)) с обрезкой до 64 символов.

Один посетитель = один total на товар за сессию (при включённой дедупликации). Обновление страницы (F5) total не накручивает.

«Сессия» здесь — обычная PHP-сессия сайта. Компонент сам вызывает session_start(), если сессия не запущена, поэтому дедупликация живёт примерно до session.gc_maxlifetime. Если на сайте свой обработчик сессий, совместимость стоит проверить.

Online (active-сессии) ​

  1. На карточке товара плагин подключает viewcounter.js и передаёт конфиг: URL connector, productId, sessionId, интервал heartbeat.
  2. JS периодически (по heartbeat_interval, по умолчанию 30 с, минимум 10) шлёт ping в connector.php.
  3. Connector вызывает upsertActive: строка (product_id, session_id) в msviewcounter_active, поле last_seen обновляется.
  4. При выводе сниппета online = число строк по товару, у которых last_seen не старше online_ttl секунд (по умолчанию 120).

online считает уникальные PHP-сессии, а не посетителей: несколько вкладок одного браузера дают одну строку, потому что session_id у них общий.

Устаревшие строки удаляются пачками во время heartbeat: порог устаревания задаёт online_ttl, периодичность прохода — cleanup_interval, размер батча — cleanup_batch_limit. Очистка запускается только из heartbeat, поэтому при выключенном msviewcounter_show_online или в режиме fake она не идёт.

Connector и защита от накрутки ​

connector.php принимает только action=ping. Права и авторизация ему не нужны: он не читает и не отдаёт данных, а только продлевает life сессии посетителя.

Идентификатор сессии не берётся из тела запроса. Коннектор стартует сессию на сервере, получает собственный session_id посетителя и сверяет присланный через hash_equals. Несовпадение даёт {"success": false, "message": "Session mismatch"} и ничего не записывает, поэтому подделать чужую сессию нельзя.

Ограничение частоты — 30 ping в минуту на сессию, при превышении 429 с заголовком Retry-After: 60; клиент этот заголовок учитывает и в паузу не шлёт запросы. Счётчик намеренно привязан к сессии, а не к IP: за одним адресом мобильного оператора или офисного NAT сидят сотни посетителей, и лимит по IP резал бы их запросы.

Порядок работы: старт сессии → проверка action → разбор списка товаров → сверка сессии → лимит частоты → ping() для каждого товара → запись в msviewcounter_active и периодическая очистка протухших строк → отдача счётчиков.

За один запрос можно передать несколько товаров: параметр product_ids[] или список через запятую, максимум 100 на запрос. Это позволяет обновить счётчики всей страницы каталога одним POST вместо запроса на каждый товар. Ответ содержит актуальные значения:

json
{
  "success": true,
  "products": {
    "42": {"total": 128, "online": 3},
    "43": {"total": 12, "online": 1}
  },
  "total": 128,
  "online": 3
}

Поля total и online верхнего уровня дублируют первый товар — они оставлены для одиночного запроса.

Подробности и остаточные ограничения — в FAQ.

Вывод на витрине ​

Стратегия RealCounterStrategy возвращает raw-значения из БД без преобразований:

  • total — из msviewcounter_totals.total_views;
  • online — COUNT из msviewcounter_active с учётом TTL.

Когда выбирать ​

  • Магазин уже получает органический трафик.
  • Нужны реальные цифры для аналитики и доверия без «накрутки» на выводе.
  • Готовы к тому, что у новых товаров total и online будут малыми.

Пример поведения ​

Событиеtotal в БДonline
Первый визит пользователя A11 (после heartbeat)
F5 в той же сессии11
Одновременно пользователь B на той же карточке22
B закрыл вкладку, прошло > TTL21

boost — реальные данные, усиленный вывод ​

Запись и online работают как в real: просмотры и сессии пишутся в БД, heartbeat активен. Меняется только отображение — стратегия BoostCounterStrategy преобразует raw-числа перед выводом.

Формула total ​

jitter = crc32("{productId}:{YYYYMMDD}:total") % (boost_total_jitter_max + 1)
boosted = floor(raw_total × boost_total_multiplier) + jitter
display_total = max(boost_total_base, boosted)
НастройкаРоль
boost_total_multiplierМножитель реального total (по умолчанию 1)
boost_total_jitter_maxДобавка 0…N, стабильная в течение календарного дня
boost_total_baseНижняя граница: итог не опустится ниже этого значения

Jitter зависит от ID товара и текущей даты (Ymd). В один день число не меняется при refresh; на следующий день jitter пересчитывается — небольшое «живое» изменение без привязки к каждому запросу.

Формула online ​

jitter = crc32("{productId}:{YYYYMMDD}:online") % (boost_online_jitter_max + 1)
boosted = raw_online + jitter
display_online = max(boost_online_base, boosted)

К реальному числу active-сессий добавляется дневной разброс и применяется минимум boost_online_base.

Пример расчёта total ​

Настройки: multiplier = 2, base = 100, jitter_max = 5. Raw total = 12, jitter для товара сегодня = 3:

boosted = floor(12 × 2) + 3 = 27
display = max(100, 27) = 100

Raw total = 80, jitter = 2:

boosted = floor(80 × 2) + 2 = 162
display = max(100, 162) = 162

Пример расчёта online ​

Настройки: base = 2, jitter_max = 2. Raw online = 0, jitter = 1:

display = max(2, 0 + 1) = 2

Raw online = 4, jitter = 0:

display = max(2, 4) = 4

Когда выбирать ​

  • Нужна честная запись в БД, но на витрине не хочется показывать «3 просмотра» на новом товаре.
  • Маркетинговый social proof с контролируемым минимумом (boost_*_base).
  • Планируете позже переключиться на real, не теряя накопленную статистику.

Важно ​

  • В БД остаются реальные raw-значения; boost не подменяет агрегат в таблице.
  • При смене настроек boost отображение меняется сразу; raw в БД — нет.
  • Если show_total или show_online выключены, соответствующая формула не применяется (в стратегии подставляется 0).

fake — синтетика без БД ​

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

Как режим превращается в число, видно на схеме: в fake в БД не идёт ничего, а значение рождается из product_id и соли.

Что не происходит ​

  • recordVisit не пишет в msviewcounter_totals.
  • Heartbeat не вызывается, viewcounter.js не подключается.
  • Connector для online в этом режиме на карточке не нужен.

Метод usesDatabase() возвращает false — компонент не читает и не пишет счётчики в БД.

Как считаются числа ​

Стратегия FakeCounterStrategy. Для каждого товара и типа (total / online) значение стабильно и лежит в заданном диапазоне:

range = fake_*_max - fake_*_min + 1
offset = hash(productId, suffix, fake_salt)   // SHA-256 + детерминированное смешивание
display = fake_*_min + (offset % range)
НастройкаРоль
fake_total_min / fake_total_maxДиапазон просмотров (по умолчанию 50–500)
fake_online_min / fake_online_maxДиапазон online (по умолчанию 1–8)
fake_saltСоль: смена пересчитывает все fake-числа

Hash зависит от productId и salt, но не от даты — в отличие от boost-jitter, fake-числа не меняются каждый день сами по себе.

Стабильность ​

ДействиеМеняются ли числа?
Refresh страницыНет
Другой товарДа (другой productId → другое значение в диапазоне)
Смена fake_saltДа (полный пересчёт)
Смена min/maxДа (другой диапазон)

Соседние товары получают разные значения внутри диапазона, а не одно и то же число на всех карточках.

Пример ​

Настройки: fake_total_min = 50, fake_total_max = 500, salt по умолчанию.

  • Товар ID 10 → например, 187 просмотров (фиксированно, пока не меняли salt/диапазон).
  • Товар ID 11 → другое число, например 342.
  • Online для ID 10 → число от 1 до 8, тоже стабильное.

Когда выбирать ​

  • Магазин только запущен, реальных просмотров почти нет.
  • Демо / превью без «пустых» счётчиков.
  • Не хотите разрастания таблиц статистики до появления трафика.

Переход на real или boost ​

После переключения режима:

  • fake-числа исчезают;
  • начинается (или продолжается) запись в БД с текущего момента;
  • старые строки в msviewcounter_totals / msviewcounter_active, если были, сохраняются, но в fake они не использовались.

Сравнение на одном примере ​

Товар ID 25. В БД после недели работы: raw total = 34, raw online = 2.

РежимНастройки (пример)total на витринеonline на витрине
realпо умолчанию342
boostbase 100, mult 1.5, jitter 0max(100, 51) = 100max(2, 2+jitter) ≥ 2
fake50–500, online 1–8стабильное из диапазонастабильное из диапазона

Переключение режима ​

  1. Система → Системные настройки → msviewcounter_mode.
  2. Очистить кэш MODX.
  3. Открыть карточку товара и проверить HTML:
    • real / boost + online → есть viewcounter.js;
    • fake → JS нет, числа не растут от визитов.

Подробнее про ключи boost/fake: Системные настройки — Boost и Системные настройки — Fake.

Стилизация ​

Базовый CSS ​

Сниппет и плагин регистрируют assets/components/msviewcounter/css/viewcounter.css. Дефолтный блок .msvc-counter — компактная карточка с фоном, рамкой, тенью и status-dot у строк total/online.

CSS-переменные ​

Все переменные задаются на контейнере .msvc-counter и влияют на карточку и строки total/online.

ПеременнаяПо умолчаниюНазначение
--msvc-gap0.5remРасстояние между строками «просмотрели» и «сейчас смотрят» (CSS gap у grid)
--msvc-margin0.875rem 0Внешний отступ блока счётчика
--msvc-padding0.875rem 1remВнутренние отступы карточки
--msvc-font-size0.9375remРазмер текста в блоке
--msvc-line-height1.45Межстрочный интервал
--msvc-color#1e293bБазовый цвет текста (если не переопределён у строк)
--msvc-total-colorvar(--msvc-color)Цвет текста строки просмотров (.msvc-counter__total)
--msvc-online-color#047857Цвет текста строки online (.msvc-counter__online)
--msvc-backgroundградиентФон карточки
--msvc-border1px solid …Рамка карточки (полное значение свойства border)
--msvc-border-radius0.875remСкругление углов карточки
--msvc-shadowbox-shadowТень карточки
--msvc-accent#2563ebЦвет status-dot у строки total (кружок ::before)
--msvc-accent-glowrgba«Ореол» вокруг dot total (box-shadow псевдоэлемента)
--msvc-online-accent#10b981Цвет status-dot у строки online
--msvc-online-glowrgba«Ореол» вокруг dot online
--msvc-dot-size0.5remДиаметр status-dot у обеих строк

Переопределяйте в CSS темы:

css
.product-card .msvc-counter {
    /* Расстояние между строками total и online */
    --msvc-gap: 0.5rem;
    /* Внешний отступ блока */
    --msvc-margin: 0.875rem 0;
    /* Внутренние отступы карточки */
    --msvc-padding: 0.875rem 1rem;
    /* Типографика */
    --msvc-font-size: 0.9375rem;
    --msvc-line-height: 1.45;
    /* Цвета текста */
    --msvc-color: #1e293b;
    --msvc-total-color: var(--msvc-color);
    --msvc-online-color: #047857;
    /* Оформление карточки */
    --msvc-background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
    --msvc-border: 1px solid rgba(148, 163, 184, 0.28);
    --msvc-border-radius: 0.875rem;
    --msvc-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
    /* Индикаторы (status-dot) у строки total */
    --msvc-accent: #2563eb;
    --msvc-accent-glow: rgba(37, 99, 235, 0.14);
    /* Индикаторы у строки online */
    --msvc-online-accent: #10b981;
    --msvc-online-glow: rgba(16, 185, 129, 0.16);
    --msvc-dot-size: 0.5rem;
}

Акцент на online ​

css
.product-card .msvc-counter {
    /* Фон, рамка, скругление и тень карточки */
    --msvc-background: #fff7ed;
    --msvc-border: 1px solid #fed7aa;
    --msvc-border-radius: 1rem;
    --msvc-shadow: 0 12px 28px rgba(154, 52, 18, 0.1);
    /* Status-dot строки total */
    --msvc-accent: #f97316;
    --msvc-accent-glow: rgba(249, 115, 22, 0.16);
    /* Текст, dot и ореол строки online */
    --msvc-online-color: #dc2626;
    --msvc-online-accent: #ef4444;
    --msvc-online-glow: rgba(239, 68, 68, 0.16);
}

Только текст без карточки ​

css
.product-card .msvc-counter {
    --msvc-padding: 0;              /* без внутренних отступов */
    --msvc-background: transparent; /* без фона */
    --msvc-border: 0;               /* без рамки */
    --msvc-border-radius: 0;        /* без скругления */
    --msvc-shadow: none;            /* без тени */
}

CrawlerDetect ​

Если установлен CrawlerDetect, компонент использует его сервис. Иначе — встроенная проверка User-Agent (поисковые роботы, headless-клиенты).

При msviewcounter_block_bots = Да боты не участвуют в:

  • записи просмотра (recordVisit);
  • heartbeat active-сессии (ping).

В режиме fake запись в БД не выполняется в любом случае.

База данных ​

ТаблицаНазначение
msviewcounter_totalsАгрегат просмотров: одна строка на product_id
msviewcounter_activeТекущие online-сессии с last_seen

msviewcounter_totals ​

КолонкаТипНазначение
product_idINT UNSIGNEDПервичный ключ, ID товара
total_viewsINT UNSIGNEDСчётчик, по умолчанию 0
updated_atTIMESTAMPОбновляется при каждом изменении счётчика

Одна строка на товар, дополнительных индексов нет.

msviewcounter_active ​

КолонкаТипНазначение
idINT UNSIGNEDИдентификатор строки, растёт автоматически; первичный ключ
product_idINT UNSIGNEDID товара
session_idVARCHAR(64)Идентификатор сессии
last_seenTIMESTAMPВремя последнего heartbeat
ИндексПоляЗачем
product_session (UNIQUE)product_id, session_idНе даёт одной сессии создать несколько строк, ON DUPLICATE KEY UPDATE обновляет last_seen
product_last_seenproduct_id, last_seenПодсчёт online по товару с фильтром по TTL
last_seenlast_seenУскоряет удаление устаревших строк

Требования к серверу БД ​

Обе таблицы создаются с ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci, используется синтаксис ON DUPLICATE KEY UPDATE. Нужна MySQL 5.7+ или MariaDB 10.2+ с включённым InnoDB.

msviewcounter_active чистится во время heartbeat: порог задаёт online_ttl, период — cleanup_interval, лимит — cleanup_batch_limit.

При удалении пакета системные настройки удаляются; таблицы статистики сохраняются, чтобы не потерять данные магазина.

Типовые сценарии ​

Честная статистика ​

  1. msviewcounter_mode = real
  2. Сниппет на шаблоне товара
  3. Плагин msViewCounterTrack включён

Новый магазин ​

  1. msviewcounter_mode = fake
  2. Диапазоны fake_total_min / fake_total_max, fake_online_min / fake_online_max

Boost для маркетинга ​

  1. msviewcounter_mode = boost
  2. boost_total_base = 100, boost_online_base = 2, boost_online_jitter_max = 2

Счётчик в каталоге ​

Передайте pid из строки товара — см. Каталог товаров.

См. также ​