Skip to content
  1. Компоненты
  2. MyFavorites

MyFavorites

Компонент, который позволяет добавить на сайт списки избранного. Основной упор сделан на работу с анонимными пользователями, аналитикой и защитой от ботов.

Оглавление

Основные возможности компонента

Избранное и списки

  • Создание различных списков избранного.
  • Пользовательские списки избранного (пользователь может сам создавать/переименовывать и удалять свои списки).
  • Публичные ссылки на списки избранного — посторонний посетитель может просмотреть список по ссылке без авторизации, с копированием ссылки в буфер обмена одним кликом.

Идентификация и синхронизация

  • Работа как с анонимными пользователями, так и только с зарегистрированными.
  • Очистка сессий сайта и удаление кук пользователем не влияют на список избранного у анонимных пользователей.
  • Идентификация анонимного пользователя и, как следствие, его списка избранного, посетившего сайт с разных браузеров (метод не срабатывает во всех 100% случаев. На сайте должна быть подключена Google Analytics или Яндекс.Метрика).
  • Привязка списка избранного анонимного пользователя к зарегистрированному пользователю при его авторизации.
  • Синхронизация индикатора избранного: изменение состояния сразу у всех кнопок товара на странице и в других открытых вкладках браузера, без перезагрузки страницы.

Безопасность

  • Черные списки IP.
  • Защита от CSRF-атаки.
  • Защита с помощью reCAPTCHA 3 и Yandex SmartCaptcha.
  • Лимит на запросы для анонимных и зарегистрированных пользователей.
  • Корректная работа с Cloudflare и другими reverse-proxy (доверенные адреса для X-Forwarded-For).
  • Уведомления администратора при срабатывании защиты (превышение лимита запросов, провал капчи, бан по IP) — по умолчанию email, канал доставки заменяем — см. «Уведомления о срабатывании защиты».

Аналитика

  • Передача данных о событиях добавления/удаления и очистки списка избранного в Google Analytics и Яндекс.Метрика.

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

  • Экспорт данных (избранное, посетители) из mgr в CSV/XLSX/ODS — см. «Экспорт данных».
  • Дашборд со сводной статистикой (KPI, графики активности, топ ресурсов, лента событий) — см. «Dashboard (админка)».

Фронтенд

  • Быстрая кастомизация стилей через CSS переменные.
  • Возможность подписываться на JS события компонента для кастомизации его работы.
  • Нативный TypeScript, без внешних JS-зависимостей.

Демонстрация

Видеообзор MyFavorites

Видеообзор снят на версии для MODX 2, но в основном актуален и для текущей версии для MODX 3.

Кнопки избранного в каталоге товаров

Кнопки добавления в избранное на карточках товаров каталога.

Установка

Установите транспортный пакет MyFavorites через стандартный менеджер пакетов MODX (Приложения → Установщик).

Зависимости:

  • pdoTools — обязательно: листинги строятся через pdoPage/pdoResources, чанки компонента рендерятся через pdoTools (Fenom).

Первичная настройка:

  1. Создайте страницу избранного (вызов сниппета MyFavorites) и укажите её ID в настройке myfavorites_favorites_page_id — на неё будет вести ссылка счётчика.
  2. Разместите кнопку MyFavorites.btn в карточке ресурса/товара и счётчик MyFavorites.counter в шапке сайта.
  3. Для автоматической очистки журнала действий и истёкших блокировок IP добавьте в CRON вызов скрипта раз в неделю:
    полный_путь/core/components/myfavorites/cron/clear.php
    Срок хранения журнала задаётся настройкой myfavorites_log_expires.

Быстрый старт

Вызов сниппетов

Для возможности добавления/удаления в список избранного используйте сниппет MyFavorites.btn.

Для вывода количества ресурсов в списке избранного используйте сниппет MyFavorites.counter.

Для получения всех ID ресурсов, находящихся в списке избранного, используйте сниппет MyFavorites.ids.

Для вывода ресурсов, находящихся в списке избранного, используйте сниппет MyFavorites — обёртку над листинг-сниппетом (по умолчанию pdoPage с element=pdoResources), которая сама подставляет ID из списка. Если список пуст, выводится чанк MyFavorites.empty (tplEmpty) с сообщением об этом; иначе результат оборачивается в чанк MyFavorites (tplWrapper) — блок с кнопкой очистки списка и пагинацией (page.nav):

modx
[[!MyFavorites?
&element=`msProducts`
&limit=`12`
]]

Если нужен больший контроль (свой листинг-сниппет без pdoPage, кастомная пагинация и т.п.), соберите вывод вручную через MyFavorites.ids:

modx
[[!MyFavorites.ids? &toPlaceholder=`myf.ids`]]
[[!+myf.ids:is=`-0`:then=`
[[%myfavorites_info_list_empty]]
`:else=`
[[!pdoPage?
&element=`msProducts`
&parents=`0`
&limit=`12`
&resources=`[[!+myf.ids]]`
]]
<button class="btn btn-primary" data-myfavorites-clear>[[%myfavorites_clear_list]]</button>
[[!+page.nav]]
`]]

Подробную информацию по всем параметрам каждого сниппета смотрите в разделе "Сниппеты".

Стилизация

Цветовая гамма и внешний вид элементов настраиваются через CSS-переменные — см. раздел «Настройка CSS стилей».

Сниппеты

MyFavorites - Вывод ресурсов из списка избранного

Обёртка над листинг-сниппетом (по умолчанию pdoPage), которая подставляет в его параметр resources ID элементов списка избранного. Если список пуст, листинг-сниппет не вызывается — сразу выводится чанк tplEmpty. По сути — короткая замена связки MyFavorites.ids + pdoPage, показанной в разделе "Быстрый старт".

Параметры

ИмяОписание
listИдентификатор списка. По умолчанию: default.
snippetИмя вызываемого сниппета. По умолчанию: pdoPage.
elementТолько если snippet=pdoPage (по умолчанию) — имя вложенного листинг-сниппета, который принимает этот параметр. По умолчанию: pdoResources (для товаров miniShop2 — msProducts).
tplWrapperИмя чанка-обёртки результата (параметр pdoTools). По умолчанию: MyFavorites (чанк с кнопкой очистки списка data-myfavorites-clear и пагинацией page.nav). Укажите пустую строку, чтобы вывести результат без обёртки. Плейсхолдеры — см. «Чанки и плейсхолдеры».
tplEmptyИмя чанка, выводимого вместо snippet, если список пуст. По умолчанию: MyFavorites.empty (сообщение [[%myfavorites_info_list_empty]]). Плейсхолдеры — см. «Чанки и плейсхолдеры».
sharePageIdТолько если указан tplWrapper — ID страницы, на которую будет вести публичная ссылка (list.share_url, доступная в чанке tplWrapper). Если параметр не указан, используется системная настройка myfavorites_share_page_id. Если итоговое значение равно 0, ссылка будет вести на текущую страницу. На целевой странице должен быть вызван сниппет MyFavorites.share. По умолчанию: 0.

Остальные параметры вызова передаются в snippet как есть (parents и resources при этом сниппет всегда выставляет сам).

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

modx
[[!MyFavorites?
&element=`msProducts`
&limit=`12`
]]

Пагинация ([[!+page.nav]]) выводится автоматически внутри дефолтного чанка tplWrapper (см. «Чанки и плейсхолдеры») — отдельно вызывать её не нужно.

Если пагинация не нужна, вызовите листинг-сниппет напрямую через snippet (аналогично MyFavorites.share):

modx
[[!MyFavorites? &snippet=`msProducts` &limit=`12`]]

MyFavorites.btn - Вывод кнопки для добавления/удаления избранного

Параметры

ИмяОписание
idID ресурса, для которого нужно вернуть данные. По умолчанию — текущий ресурс.
listИдентификатор списка. По умолчанию: default.
tplИмя чанка для оформления результата. По умолчанию: MyFavorites.btn. Плейсхолдеры — см. «Чанки и плейсхолдеры».
removeНужно ли удалять или перезагрузить страницу при удалении из избранного. Если нужна перезагрузка страницы, укажите 1. Если удаление HTML элемента, то укажите префикс его ID, к которому через дефис будет добавлен ID ресурса.
labelМетка для аналитики. Текст из этого параметра будет передан в событие Google Analytics или Яндекс.Метрика.
classesДополнительные CSS классы для HTML элемента кнопки (класс added, если ресурс уже в списке, проставляется автоматически).

Пример вызова в чанке

[[!MyFavorites.btn? &id=`[[+id]]`]]

Пример вызова в шаблоне

[[!MyFavorites.btn? &id=`[[*id]]`]]

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

[[!MyFavorites.btn? &id=`[[*id]]` &remove=`1`]]

Пример вызова, когда необходимо удалить HTML элемент

modx
<div id="product-item-[[+id]]">
[[!MyFavorites.btn? &id=`[[+id]]` &remove=`product-item`]]
.
.
.
</div>

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

[[!MyFavorites.btn? &id=`[[+id]]` &label=`[[+pagetitle]]`]]

MyFavorites.counter - Вывод счетчика с количеством элементов в избранном

Параметры

ИмяОписание
idID страницы избранного для формирования ссылки. По умолчанию — настройка myfavorites_favorites_page_id.
listИдентификатор списка. По умолчанию: default.
schemeСхема формируемой ссылки (см. параметр scheme modX::makeUrl()). По умолчанию: -1 (авто).
tplИмя чанка для оформления результата. По умолчанию: MyFavorites.counter. Плейсхолдеры — см. «Чанки и плейсхолдеры».

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

[[!MyFavorites.counter? &id=`5`]]

MyFavorites.lists - Вывод списков избранного с количеством в них элементов

Параметры

ИмяОписание
userID пользователя MODX. Если не указан, используются списки текущего анонимного/авторизованного посетителя.
withItemsВ чанке доступен массив ID элементов списка. По умолчанию 1.
onlyEnableВыводить только включённые списки (enable=1). По умолчанию 1.
limitЛимит выборки результатов. По умолчанию: 0.
offsetПропуск результатов с начала выборки. По умолчанию: 0
sortbyСортировка выборки. По умолчанию: id.
sortdirНаправление сортировки. По умолчанию: ASC.
tplИмя чанка для оформления результата. По умолчанию: MyFavorites.lists. Плейсхолдеры — см. «Чанки и плейсхолдеры».
sharePageIdID страницы, на которую будет вести публичная ссылка (share_url). Если параметр не указан, используется системная настройка myfavorites_share_page_id. Если итоговое значение равно 0, ссылка будет вести на текущую страницу. На целевой странице должен быть вызван сниппет MyFavorites.share. По умолчанию: 0.

Сниппет построен на pdoTools (pdoFetch) — поддерживаются и остальные стандартные параметры pdoTools (fastMode, cacheKey и т.д.).

MyFavorites.ids - Возвращает ID элементов списка избранного

Параметры

ИмяОписание
listИдентификатор списка. По умолчанию: default.
returnФормат возвращаемых данных. Допустимые значения: data - массив ID; str - строка ID через запятую. По умолчанию: str.
toPlaceholderЕсли не пусто, сниппет сохранит все данные в плейсхолдер с этим именем, вместо вывода на экран.

Пример вывода списка избранных элементов

modx
[[!MyFavorites.ids? &toPlaceholder=`myf.ids`]]
[[!+myf.ids:is=`-0`:then=`
<div class="alert alert-primary d-flex align-items-center" role="alert">
    <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"></use></svg>
    <div>[[%myfavorites_info_list_empty]]</div>
</div>
`:else=`
<div class="row">
    [[!pdoPage?
    &element=`msProducts`
    &parents=`0`
    &limit=`12`
    &resources=`[[!+myf.ids]]`
    ]]
</div>
<div class="row">
    <div class="col">
        <button class="btn btn-primary" data-myfavorites-clear>[[%myfavorites_clear_list]]</button>
    </div>
</div>
<div class="row">
    <div class="col text-center">
    [[!+page.nav]]
    </div>
</div>
`]]

Fenom-синтаксис

modx

{set $ids = $_modx->runSnippet('!MyFavorites.ids')}

{if $ids != '-0'}
<div class="row">
    {$_modx->runSnippet('!pdoPage', [
    'element'=>'msProducts',
    'parents' => 0,
    'resources' => $ids,
    'limit' => 12,
    ])}
</div>
<div class="row">
    <div class="col">
        <button class="btn btn-primary" data-myfavorites-clear>{'myfavorites_clear_list' | lexicon}</button>
    </div>
</div>
<div class="row">
    <div class="col text-center">
        {'page.nav' | placeholder}
    </div>
</div>
{else}
<div class="alert alert-primary d-flex align-items-center" role="alert">
    <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"></use></svg>
    <div>{'myfavorites_info_list_empty' | lexicon}</div>
</div>
{/if}

MyFavorites.share - Вывод избранного по публичной ссылке

Позволяет постороннему посетителю просмотреть список избранного по значению его публичной ссылки (share_url, которую формирует сниппет MyFavorites.lists).

Параметры

ИмяОписание
keyЗначение ключа публичной ссылки. Если не передано, берётся из GET-параметра с именем из настройки myfavorites_share_key_var (по умолчанию: share).
outputРежим работы. list - вывод ресурсов списка через вложенный сниппет (см. snippet); ids - вернуть только ID ресурсов списка. По умолчанию: list.
snippetТолько если output=list. Имя сниппета, которому будут переданы ID ресурсов списка избранного (в параметре resources) для вывода. По умолчанию: pdoPage.
idsFormatТолько если output=ids. Формат возвращаемых данных: array - массив ID; string - строка ID через запятую. По умолчанию: string.
toPlaceholderТолько если output=ids. Если не пусто, сниппет сохранит ID в плейсхолдер с этим именем, вместо вывода на экран.
tplEmptyТолько если output=list. Имя чанка, выводимого вместо snippet, если ссылка недействительна (ключ не найден, список отключён) или список пуст. По умолчанию: MyFavorites.empty (сообщение [[%myfavorites_err_share_not_found]]). Плейсхолдеры — см. «Чанки и плейсхолдеры».

Если output=list, а ссылка недействительна (ключ не найден, список отключён) или список пуст, сниппет сам выводит tplEmpty вместо вызова snippet. Для output=ids в этом случае возвращается -0 (или пустой массив при idsFormat=array) — сообщать об этом пользователю должен сам вызывающий код, как в примере ниже.

В режиме output=list в вызываемый snippet также передаются все остальные параметры, указанные при вызове MyFavorites.share (кроме parents и resources, которые сниппет всегда выставляет сам).

По умолчанию вызывается pdoPage — постраничный вывод с пагинацией page.nav, как в старой версии. pdoPage, в свою очередь, принимает свой параметр element — имя вложенного листинг-сниппета (по умолчанию pdoResources, для товаров miniShop2 — msProducts):

modx
[[!MyFavorites.share?
&element=`msProducts`
&limit=`12`
]]
[[!+page.nav]]

Если пагинация не нужна, вызовите нужный листинг-сниппет напрямую через snippet (это уже терминальный сниппет, а не обёртка вроде pdoPage, поэтому именно snippet, а не element):

modx
[[!MyFavorites.share? &snippet=`msProducts` &limit=`12`]]

Пример вызова (список ID для дальнейшей сборки страницы вручную)

modx
[[!MyFavorites.share? &output=`ids` &toPlaceholder=`myf.share.ids`]]
[[!+myf.share.ids:is=`-0`:then=`
[[%myfavorites_err_share_not_found]]
`:else=`
[[!pdoPage?
&element=`msProducts`
&limit=`12`
&resources=`[[!+myf.share.ids]]`
]]
`]]

Чанки и плейсхолдеры

Все чанки рендерятся через pdoTools, синтаксис — Fenom. Каждый сниппет принимает имя своего чанка параметром (tpl, tplWrapper, tplEmpty) — создайте копию дефолтного чанка и укажите её имя, чтобы изменить оформление.

MyFavorites.btn

Кнопка добавления/удаления (сниппет MyFavorites.btn, параметр tpl).

ПлейсхолдерОписание
idID ресурса
keyИдентификатор списка (пустой — список default)
addedРесурс уже в избранном (кнопке добавляется класс added)
removeЗначение параметра remove сниппета (атрибут data-remove)
labelМетка для аналитики (атрибут data-label)
classesДополнительные CSS-классы из параметра classes

MyFavorites.counter

Счётчик-ссылка (сниппет MyFavorites.counter, параметр tpl).

ПлейсхолдерОписание
urlURL страницы избранного
keyИдентификатор списка
countКоличество элементов в списке

MyFavorites.lists

Перечень списков посетителя (сниппет MyFavorites.lists, параметр tpl).

ПлейсхолдерОписание
listsМассив списков; у каждого: key, name, items (массив ID элементов при withItems=1), share_url (публичная ссылка)

MyFavorites

Обёртка результата листинга (сниппет MyFavorites, параметр tplWrapper) — блок с кнопкой очистки data-myfavorites-clear, share-ссылкой и пагинацией.

ПлейсхолдерОписание
listМассив полей списка (key, name, share_url, ...)
outputГотовый HTML, который вернул листинг-сниппет

Пагинация выводится через {'page.nav' | placeholder} — это не переданный сниппетом плейсхолдер, а стандартный системный плейсхолдер page.nav, который выставляет pdoPage при рендере (доступен уже к моменту рендера обёртки). Если вызвать MyFavorites с snippet, отличным от pdoPage (без поддержки page.nav), используйте свою копию чанка без этой строки.

MyFavorites.empty

Заглушка пустого результата (сниппеты MyFavorites и MyFavorites.share, параметр tplEmpty).

ПлейсхолдерОписание
shareФлаг контекста публичной ссылки: не пусто — «ссылка не найдена» (MyFavorites.share), пусто — «список пуст»

Системные настройки

Настройки сгруппированы по разделам (namespace myfavorites), значения по умолчанию перенесите из старой установки самостоятельно.

Main

  • myfavorites_custom_favorite_lists — разрешить посетителям сайта создавать, переименовывать и удалять свои списки избранного (по умолчанию запрещено).
  • myfavorites_lists_allowed — список ключей, доступных при выключенных пользовательских списках (список default разрешён всегда).
  • myfavorites_sync_favorite — объединять избранное при опознании одного и того же посетителя на разных устройствах (слияние списков при авторизации или совпадении меток аналитики; выключено — устройства просто привязываются к пользователю без слияния). Подробнее — см. «Синхронизация между устройствами».
  • myfavorites_use_ctx / myfavorites_use_lang / myfavorites_use_host — учитывать контекст / язык (cultureKey) / хост в идентификации списка избранного (для мультиконтекстных/мультиязычных/мультидоменных установок).
  • myfavorites_favorites_page_id — ID ресурса страницы избранного (используется сниппетом MyFavorites.counter для ссылки).
  • myfavorites_log_expires — сколько дней хранить записи журнала действий, 0 — не удалять (используется скриптом cron).

Security

  • myfavorites_access_anonymous — разрешить анонимным посетителям пользоваться избранным.
  • myfavorites_enable_blacklist — отклонять не-GET запросы REST API с забаненных IP (my_favorite_bans).
  • myfavorites_enable_csrf — проверять CSRF-токен на не-GET запросах.
  • myfavorites_enable_rate_limit, myfavorites_rate_limit_max_attempts, myfavorites_rate_limit_decay — лимит запросов на действие с одного IP за период.
  • myfavorites_trusted_proxies — список IP доверенных reverse-proxy/балансировщиков через запятую; только тогда заголовку X-Forwarded-For доверяют для определения реального IP клиента.

reCAPTCHA 3

  • myfavorites_enable_recaptcha, myfavorites_recaptcha_public_key, myfavorites_recaptcha_secret_key, myfavorites_recaptcha_score, myfavorites_recaptcha_hidden, myfavorites_recaptcha_reg_api_script.

Yandex SmartCaptcha

  • myfavorites_enable_ya_smartcaptcha, myfavorites_ya_smartcaptcha_client_key, myfavorites_ya_smartcaptcha_server_key, myfavorites_ya_smartcaptcha_verify_actions — список действий (через запятую) для которых используется проверка; пусто — для всех действий.

Notifications

  • myfavorites_enable_notification — уведомлять администратора при срабатывании защиты (превышение лимита запросов, провал капчи, бан по IP).
  • myfavorites_notification_alerts — типы срабатываний, о которых уведомлять, через запятую: rate_limit, captcha, banned.
  • myfavorites_notification_emails — адреса получателей через запятую (используется дефолтным каналом доставки — email).
  • myfavorites_notifier_class — класс канала доставки уведомлений, реализующий MyFavorites\Notification\NotifierInterface. По умолчанию: MyFavorites\Notification\EmailNotifier; в комплекте также есть MyFavorites\Notification\TelegramNotifier.
  • myfavorites_notification_throttle — минимальный интервал (сек.) между двумя уведомлениями одного типа с одного IP-адреса.
  • myfavorites_telegram_bot_token, myfavorites_telegram_chat_ids — токен бота и список ID чатов/каналов через запятую (используются TelegramNotifier, только если он указан в myfavorites_notifier_class).

Sharing

  • myfavorites_share_key_length — длина случайно генерируемого ключа публичной ссылки на список избранного (минимум 8).
  • myfavorites_share_page_id — ID страницы, на которую ведёт публичная ссылка. 0 — текущий ресурс. На целевой странице должен быть вызван сниппет MyFavorites.share.
  • myfavorites_share_key_var — имя GET-параметра, содержащего ключ публичной ссылки.
  • myfavorites_share_copy_link — перехватывать клик по ссылке "поделиться" в JS-виджете и копировать её в буфер обмена вместо перехода.

Frontend

  • myfavorites_css, myfavorites_js — пути к скомпилированным ассетам фронтенда ({assets_url} поддерживается), пусто — не регистрировать.
  • myfavorites_yandex_metrika, myfavorites_google_analytics — отправлять события виджета (add/remove/...) в Яндекс.Метрику / Google Analytics.
  • myfavorites_tab_sync — передавать изменения избранного в другие открытые вкладки браузера (см. «Синхронизация индикатора избранного»).

Export

  • myfavorites_export_handlers — JSON-реестр доступных отчётов экспорта по гридам mgr, см. «Экспорт данных».
  • myfavorites_export_format — формат файла экспорта по умолчанию: csv, xlsx или ods.
  • myfavorites_export_save_to_file — сохранять экспорт файлом на сервере (в myfavorites_export_path) вместо скачивания в браузер.
  • myfavorites_export_path — путь для сохранения файла при включённой myfavorites_export_save_to_file (поддерживает {core_path} и т.п.).
  • myfavorites_export_add_field_names — добавлять строку с названиями колонок первой строкой файла.

Право mgr, дающее доступ к экспорту: myfavorites_export_execute.

Синхронизация между устройствами

Компонент идентифицирует посетителя без регистрации:

  • основной идентификатор — собственная cookie (_mfuid), дублируемая в localStorage: очистка cookies сайта не теряет избранное — при следующем визите идентификатор восстанавливается;
  • дополнительно — метки систем аналитики, если они подключены на сайте: _ym_uid (Яндекс.Метрика) и _ga (Google Analytics). По ним один и тот же человек опознаётся в разных браузерах/устройствах (метод срабатывает не в 100% случаев);
  • при авторизации анонимное избранное привязывается к пользователю MODX — и далее узнаётся на любом устройстве, где он авторизуется.

Когда два ранее независимых «устройства» опознаны как один посетитель (авторизация под тем же пользователем, совпадение меток аналитики), поведение зависит от настройки myfavorites_sync_favorite:

  • включена — выполняется одноразовое слияние: списки обоих устройств объединяются без потерь (одноимённые списки сливаются, уникальные переносятся), дальше оба устройства работают с общим избранным. Слияние можно отменить или обработать событиями OnMyFavoritesBeforeMergeUsers / OnMyFavoritesMergeUsers (см. «Системные события MODX»);
  • выключена — старое поведение: устройство привязывается к пользователю, но уже накопленные на разных устройствах списки не объединяются.

Изменения, сделанные на одном устройстве, появляются на другом при следующем обращении к сайту — без ручного обновления кэшей.

Уведомления о срабатывании защиты

При срабатывании защиты REST API — превышении лимита запросов, провале капчи (reCAPTCHA/Yandex SmartCaptcha) или запросе с забаненного IP — компонент всегда пишет запись в журнал действий (лог, действие «Срабатывание защиты»), независимо от того, включены ли уведомления. CSRF и запрет анонимного доступа не считаются срабатыванием защиты и записи/уведомления не создают — это штатная блокировка настройками, а не признак атаки или абьюза.

Если включена настройка myfavorites_enable_notification, дополнительно администратору отправляется уведомление — по умолчанию на email (myfavorites_notification_emails), не чаще одного уведомления на пару «тип срабатывания + IP-адрес» за период, заданный myfavorites_notification_throttle (чтобы не заваливать почту при атаке; сама запись в журнале действий при этом создаётся на каждое срабатывание без ограничений). Какие именно типы срабатываний уведомлять — задаётся списком в myfavorites_notification_alerts (rate_limit, captcha, banned).

Канал доставки уведомлений заменяем: реализуйте MyFavorites\Notification\NotifierInterface (notify(string $subject, string $message, array $context): bool) своим классом и укажите полное имя класса в настройке myfavorites_notifier_class — правки кода компонента не требуются. Конструктор канала доставки принимает ровно (modX $modx, Tools $tools) — специфичные для канала параметры (токены, ID чатов и т.п.) читаются из системных настроек внутри notify(), а не через конструктор.

В комплекте, помимо EmailNotifier, есть MyFavorites\Notification\TelegramNotifier — отправляет уведомление через Telegram Bot API (sendMessage) во все чаты/каналы, перечисленные в myfavorites_telegram_chat_ids, используя токен myfavorites_telegram_bot_token. Чтобы включить: создать бота через @BotFather, получить токен, добавить бота в нужный чат/канал и узнать его chat_id, заполнить обе настройки и указать myfavorites_notifier_class = MyFavorites\Notification\TelegramNotifier.

Экспорт данных

В mgr, на страницах гридов избранного и посетителей, доступен экспорт данных в CSV, XLSX или ODS.

Доступные отчёты:

  • Избранное — экспорт по гриду избранного (Favorites): видимые в гриде колонки, с учётом активных фильтров и поиска; если в гриде выбраны конкретные строки — экспортируются только они.
  • Посетители — то же самое по гриду посетителей (Users).

Формат файла по умолчанию, режим «сохранить на сервере / скачать в браузер» и вывод строки заголовков задаются системными настройками (см. «Системные настройки → Export»); состав и порядок колонок, фильтры и выбор конкретных строк — прямо в гриде на момент клика по экспорту.

Список отчётов, доступных для каждого грида, задаётся настройкой myfavorites_export_handlers — JSON-объект вида:

json
{
    "favorites": ["MyFavorites\\Export\\Reports\\FavoritesReport"],
    "users": ["MyFavorites\\Export\\Reports\\UsersReport"],
    "lists": [],
    "logs": [],
    "bans": []
}

Ключ — топик (соответствует гриду), значение — список полных имён PHP-классов отчётов (реализуют интерфейс MyFavorites\Export\ExportReport). Отчёты для гридов Lists/Logs/Bans пока не реализованы — пустые массивы зарезервированы под будущее расширение. Добавление стороннего отчёта не требует правки кода компонента — достаточно реализовать ExportReport и указать полное имя класса в настройке.

Доступ к экспорту в mgr контролируется правом myfavorites_export_execute.

Сторонние плагины могут вмешаться в процесс экспорта через системные события OnMyFavoritesBeforeExport/OnMyFavoritesExport (весь прогон целиком), OnMyFavoritesExportPrepareQuery (доработать запрос выборки данных) и построчные OnMyFavoritesExportBeforePrepareRow/OnMyFavoritesExportAfterPrepareRow/OnMyFavoritesExportBeforeWriteRow/OnMyFavoritesExportAfterWriteRow (изменить или отфильтровать конкретную строку) — см. «Системные события MODX».

Dashboard (админка)

Первая вкладка страницы дополнения — сводный дашборд: KPI-плитки, динамика активности (7/30/90 дней), топ-10 ресурсов, распределения действий/типов пользователей/браузеров и лента последних событий. Видимость блоков настраивается через меню «Блоки» (запоминается на пользователя менеджера). Клик по названию ресурса в «Топ-10 ресурсов» открывает его на редактирование в новой вкладке.

В метриках, графиках и ленте учитываются только успешные действия; неуспешные попытки (отклонённые лимитом запросов, ошибки) видны в гриде «Лог» с фильтром по статусу. Глубина рядов «Добавления/Удаления» ограничена ретеншном логов (myfavorites_log_expires); «Новые пользователи» считаются по таблице пользователей и от ретеншна не зависят.

Отдельный блок «Безопасность» сводит срабатывания защиты REST API (см. «Уведомления о срабатывании защиты»): KPI и распределение по типам (rate_limit, капча, бан IP), «Топ IP по алертам» с кликом для создания бана по выбранному адресу, динамику «Алерты по дням» и ленту последних алертов. Он читает записи журнала «Срабатывание защиты», которые остальной дашборд намеренно не учитывает.

Право доступа: myfavorites_dashboard.

Дашборд: KPI и динамика активности

Дашборд: топ ресурсов и распределения действий, типов пользователей, браузеров

Дашборд: лента последних событий и начало блока «Безопасность»

Дашборд: блок «Безопасность», топ IP по алертам и алерты по дням

Дашборд: алерты по дням и лента последних алертов

Разделы в админке

Управление данными компонента доступно на вкладках страницы дополнения в менеджере MODX.

Пользователи — все посетители (анонимные и авторизованные): идентификаторы (IP, Yandex Client ID, Google Client ID), браузер, число позиций в избранном; фильтры и поиск.

Грид «Пользователи»

Избранное — все позиции избранного по всем посетителям, с группировкой по ресурсам и фильтрами.

Грид «Избранное»

Списки — все списки избранного (default и пользовательские): идентификатор, публичный ключ, контекст, язык и хост.

Грид «Списки»

Бан лист — управление чёрным списком IP: добавление, редактирование и удаление, временные и перманентные баны.

Грид «Бан лист»

Лог — журнал действий (включая «Срабатывание защиты») со статусом, IP и агентом.

Грид «Лог»

Настройка CSS стилей

Изменение стилей элементов осуществляется через CSS переменные.

Быстрое изменение цветовой гаммы элементов:

  • --myf-primary-color
  • --myf-secondary-color
CSS
:root {
--myf-primary-color:red;
--myf-secondary-color:silver;
}

Тонкая настройка

Кнопка добавления/удаления в избранное

  • --myf-btn-size - размер кнопки
  • --myf-btn-icon-color - цвет иконки
  • --myf-btn-added-icon-color - цвет иконки, когда ресурс добавлен в избранное
  • --myf-btn-icon-color-hover - цвет декоративного эффекта при наведении (модификатор .glow-hover в classes)
  • --myf-btn-icon - svg иконка в base64
  • --myf-btn-added-icon - svg иконка виде base64, когда ресурс добавлен в избранное
  • --myf-btn-animate - анимация при клике для добавления в избранное
  • --myf-btn-transition - transition

Счетчик количества в избранном

  • --myf-counter-size - размер иконки счётчика

  • --myf-counter-icon-color - цвет иконки

  • --myf-counter-icon-color-hover - цвет иконки при наведении

  • --myf-counter-icon - svg иконка в base64 (по умолчанию — та же, что у кнопки)

  • --myf-counter-transition - transition

  • --myf-counter-value-size - размер бейджа со значением счетчика

  • --myf-counter-value-offset - смещение бейджа относительно иконки

  • --myf-counter-value-bg - цвет фона бейджа

  • --myf-counter-value-color - цвет текста

  • --myf-counter-font-size - размер шрифта значения

  • --myf-counter-value-opacity - прозрачность

  • --myf-counter-value-opacity-hover - прозрачность при наведении

JavaScript

Виджет — самоинициализирующийся: при наличии window.MyFavoritesConfig (формирует плагин LoadWebDocument) на странице автоматически создаётся window.myFavorites. После этого генерируется CustomEvent('myfavorites:init', { detail: myFavorites }) на document — самая надёжная точка подписки для стороннего кода.

Общий список событий

  • init
  • beforeRequest
  • successRequest
  • failureRequest
  • afterRequest
  • add
  • remove
  • clearList
  • createList
  • renameList
  • removeList
  • share
  • accessDenied — HTTP 401, анонимному посетителю запрещено пользоваться избранным (myfavorites_access_anonymous)
  • banned — HTTP 403, IP посетителя заблокирован (myfavorites_enable_blacklist)
  • tabSync — принято изменение избранного из другой вкладки браузера (см. «Синхронизация индикатора избранного»)

Синхронизация индикатора избранного

Если один и тот же товар выведен на странице несколько раз (например, в каталоге и в блоке «недавно просмотренные»), добавление/удаление из избранного обновляет состояние всех его кнопок с одинаковым ключом списка (data-list; отсутствующий или пустой атрибут равнозначен списку default).

Кроме того, изменения избранного (add/remove/очистка списка) автоматически передаются в другие открытые вкладки этого же сайта через BroadcastChannel: там обновляются кнопки и счётчики, без перезагрузки страницы. В принимающей вкладке события add/remove не генерируются и аналитика не отправляется — вместо этого генерируется событие tabSync с сообщением { action: 'add' | 'remove' | 'clearList', id?, list, count }.

Межвкладочная синхронизация управляется системной настройкой myfavorites_tab_sync (по умолчанию включена); точечно переопределить её можно опцией tabSync в опциях конструктора при ручном создании инстанса (опции конструктора имеют приоритет над серверным конфигом). В браузерах без BroadcastChannel (Safari < 15.4) межвкладочная синхронизация тихо отключается сама; синхронизация кнопок в пределах одной страницы работает всегда.

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

По умолчанию показ уведомлений происходит через window.ms3.message (MiniShop2), а при его отсутствии — через нативный alert. Для того, чтобы добавить свою реализацию, необходимо подписаться на событие init компонента и через вызов метода setNotifier передать объект своего класса уведомлений.

Пример:

JavaScript
class MyNotifier {
    success(msg) {
        console.info(msg);
    }
    error(msg) {
        console.error(msg);
    }
}

document.addEventListener('myfavorites:init', (e) => {
    e.detail.setNotifier(new MyNotifier());
});

Показ окна регистрации/авторизации для анонимных пользователей

Если для анонимных пользователей системной настройкой запрещено добавлять в избранное, то по умолчанию им будет показано соответствующее уведомление. Для того, чтобы для такого случая показать свое модальное окно регистрации/авторизации, необходимо подписаться на событие accessDenied, в котором реализовать всю логику.

Пример:

JavaScript
document.addEventListener('myfavorites:init', (e) => {
    e.detail.on('accessDenied', (self, el, response) => {
        // showAuthModal();
    });
});

Аналогично можно подписаться на событие banned, чтобы кастомизировать сообщение о блокировке IP.

Пользовательские списки избранного

Если в системных настройках пакета разрешены пользовательские списки избранного (по умолчанию запрещены), доступны два способа управления списками:

1. Декларативная разметка (данные-атрибуты) — обработчики навешиваются автоматически:

АтрибутПоведение
data-myfavorites-list-create (форма, поле input[name=name])submit → создание списка; форма сбрасывается при успехе
data-myfavorites-list-rename="key" (форма, поле input[name=name])submit → переименование списка key
data-myfavorites-list-remove="key" (кнопка)click → удаление списка key

2. JS API (методы возвращают Promise с ответом REST — { success, message, data }):

  • createList(name) - создание списка избранного
  • renameList(key, name) - переименование списка избранного
  • removeList(key) - удаление списка избранного

Пример:

JavaScript
document.addEventListener('myfavorites:init', (e) => {
    const myFavorites = e.detail;

    document.getElementById('new-list')?.addEventListener('click', () => {
        myFavorites.createList('my list').then((response) => {
            console.log(response);
        });
    });

    document.getElementById('rename-list')?.addEventListener('click', () => {
        myFavorites.renameList('01hv1yjq6drnrn0hk2vmyn1wez', 'new list name').then((response) => {
            console.log(response);
        });
    });

    document.getElementById('remove-list')?.addEventListener('click', () => {
        myFavorites.removeList('01hv1yjq6drnrn0hk2vmyn1wez').then((response) => {
            console.log(response);
        });
    });
});

По умолчанию, если ни на одно из событий createList/renameList/removeList/clearList нет подписчиков, виджет сам перезагружает страницу (списочную разметку рендерит сервер — сниппет MyFavorites.lists).

Своя передача данных в аналитику

Если вас не устраивает встроенная реализация (отправка в Google Analytics/Яндекс.Метрику через настройки myfavorites_google_analytics/myfavorites_yandex_metrika), выключите соответствующие настройки и реализуйте свою обработку событий:

  • add
  • remove
  • clearList
  • createList / renameList / removeList
  • share

Пример:

JavaScript
document.addEventListener('myfavorites:init', (e) => {
    e.detail
        .on('add', (self, el, data, payload) => {

        })
        .on('remove', (self, el, data, payload) => {

        })
        .on('clearList', (self, el, data, payload) => {

        });
});

Пример обработки событий запроса:

JavaScript
document.addEventListener('myfavorites:init', (e) => {
    e.detail
        .on('beforeRequest', (self, el, payload) => {

        })
        .on('successRequest', (self, el, response, payload) => {

        })
        .on('failureRequest', (self, el, response, payload) => {

        })
        .on('afterRequest', (self, el, payload) => {

        });
});

REST API

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

Базовый URL: /assets/components/myfavorites/api.php?_rest=<действие>. Ответ — JSON вида { "success": bool, "message": string, "data": {...} } (при ошибке добавляется code); исключение — GET lists, который возвращает { "results": [...], "total": N } без этой обёртки. Запросы должны отправляться с cookies (credentials: 'include').

Правила:

  • GET-запросы доступны без проверок.
  • Не-GET запросы требуют CSRF-токен (при включённой myfavorites_enable_csrf): получите его через GET config (data.csrf) и передайте полем csrf в теле запроса. Также к не-GET запросам применяются (по настройкам) чёрный список IP, rate limit и капчи.
  • ID и прочие данные передаются в JSON-теле запроса, а не сегментом в _rest.
  • Коды ошибок: 401 — анонимному посетителю запрещено пользоваться избранным (myfavorites_access_anonymous), 403 — запрос отклонён проверками (невалидный CSRF-токен, бан IP, превышение rate limit, провал капчи).

Действия:

ДействиеМетодТело запросаОписание
configGETКонфиг виджета: csrf-токен, лексикон, ключи капч и т.д.
favoritesPOST{id, list?, csrf}Добавить ресурс id в список (list пуст — default); в data.count — новое количество
favoritesDELETE{id, list?, csrf}Удалить ресурс из списка
listsGETСписки текущего посетителя: {results: [...], total}
listsPOST{name, csrf}Создать пользовательский список (требует myfavorites_custom_favorite_lists)
listsPUT{key, name, csrf}Переименовать список
listsDELETE{key, csrf}Удалить список
lists/clearPOST{list?, csrf}Очистить список
user/recoveryPOST{csrf}Восстановить посетителя по cookie (используется виджетом автоматически)

Пример:

JavaScript
const api = '/assets/components/myfavorites/api.php';
const config = await fetch(`${api}?_rest=config`, { credentials: 'include' }).then((r) => r.json());

const response = await fetch(`${api}?_rest=favorites`, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id: 15, list: '', csrf: config.data.csrf })
}).then((r) => r.json());

Системные события MODX

Все обработчики событий вызываются через Tools::invokeEvent() (обёртка над modX::invokeEvent()): *OnBefore*-события могут отменить операцию (см. документацию по системным событиям MODX — OnBeforeAdd), а также переопределить rid/listId через возвращаемое значение (для OnMyFavoritesBeforeAdd).

OnMyFavoritesBeforeAdd - Запускается перед добавлением ресурса в избранное.

Параметры

ИмяОписание
ridID ресурса
userIdID пользователя
listIdID списка
listДанные списка (массив полей MyFavoriteLists)

OnMyFavoritesAdd - Запускается после добавления ресурса в избранное.

Параметры

ИмяОписание
ridID ресурса
userIdID пользователя
listIdID списка
listДанные списка (массив полей MyFavoriteLists)
countКоличество ресурсов в избранном после операции

OnMyFavoritesBeforeRemove - Запускается перед удалением ресурса из избранного.

Параметры

ИмяОписание
ridID ресурса
userIdID пользователя
listIdID списка
listДанные списка (массив полей MyFavoriteLists)

OnMyFavoritesRemove - Запускается после удаления ресурса из избранного.

Параметры

ИмяОписание
ridID ресурса
userIdID пользователя
listIdID списка
listДанные списка (массив полей MyFavoriteLists)
countКоличество ресурсов в избранном после операции

OnMyFavoritesBeforeClear - Запускается перед очисткой списка избранного.

Параметры

ИмяОписание
userIdID пользователя
listIdID списка
listДанные списка (массив полей MyFavoriteLists)

OnMyFavoritesClear - Запускается после очистки списка избранного.

Параметры

ИмяОписание
userIdID пользователя
listIdID списка
listДанные списка (массив полей MyFavoriteLists)

OnMyFavoritesBeforeCreateUser - Запускается перед созданием посетителя MyFavorites.

Параметры

ИмяОписание
muidID пользователя MODX, если в этот момент авторизирован

OnMyFavoritesCreateUser - Запускается после создания посетителя MyFavorites.

Параметры

ИмяОписание
userДанные объекта MyFavoriteUsers

OnMyFavoritesBeforeUserRecovery - Запускается перед восстановлением посетителя по куке.

Без параметров.

OnMyFavoritesUserRecovery - Запускается после восстановления посетителя по куке.

Параметры

ИмяОписание
userДанные объекта MyFavoriteUsers

OnMyFavoritesBeforeCreateList - Запускается перед созданием списка избранного.

Параметры

ИмяОписание
dataМассив полей для создания списка (до сохранения)

OnMyFavoritesCreateList - Запускается после создания списка избранного.

Параметры

ИмяОписание
listДанные объекта MyFavoriteLists после сохранения

OnMyFavoritesBeforeRenameList - Запускается перед переименованием списка избранного.

Параметры

ИмяОписание
newNameНовое название
listТекущие данные объекта MyFavoriteLists (до переименования)

OnMyFavoritesRenameList - Запускается после переименования списка избранного.

Параметры

ИмяОписание
listДанные объекта MyFavoriteLists после переименования

OnMyFavoritesBeforeRemoveList - Запускается перед удалением списка избранного.

Параметры

ИмяОписание
dataДанные объекта MyFavoriteLists (до удаления)

OnMyFavoritesRemoveList - Запускается после удаления списка избранного.

Параметры

ИмяОписание
dataДанные удалённого объекта MyFavoriteLists

OnMyFavoritesBeforeMergeUsers - Запускается перед слиянием избранного двух посетителей (см. «Синхронизация между устройствами»).

Событие может отменить слияние (см. Tools::invokeEvent() выше).

Параметры

ИмяОписание
targetIdID посетителя-получателя (каноническая строка)
sourceIdID посетителя-источника (его списки будут перенесены)

OnMyFavoritesMergeUsers - Запускается после успешного слияния избранного двух посетителей.

Параметры

ИмяОписание
targetIdID посетителя-получателя
sourceIdID опустевшего посетителя-источника

OnMyFavoritesBeforeExport - Запускается перед началом экспорта данных из mgr-грида (см. «Экспорт данных»).

Событие может отменить экспорт (см. Tools::invokeEvent() выше).

Параметры

ИмяОписание
reportОбъект отчёта экспорта (реализует MyFavorites\Export\ExportReport)
payloadПейлоад запроса из грида: ids (выбранные строки), fields (видимые колонки), params (фильтры/поиск)

OnMyFavoritesExport - Запускается после успешного завершения экспорта.

Параметры

ИмяОписание
reportОбъект отчёта экспорта
countКоличество экспортированных строк

OnMyFavoritesExportPrepareQuery - Запускается перед выполнением запроса на выборку данных для экспорта.

Плагин может доработать запрос, вернув изменённый объект через возвращаемое значение.

Параметры

ИмяОписание
queryОбъект запроса выборки (xPDOQuery)
reportОбъект отчёта экспорта

OnMyFavoritesExportBeforePrepareRow - Запускается перед форматированием очередной строки экспорта, до преобразования значений полей для вывода в файл.

Параметры

ИмяОписание
dataСырые данные строки (результат выборки)
reportОбъект отчёта экспорта

OnMyFavoritesExportAfterPrepareRow - Запускается после форматирования очередной строки экспорта.

Параметры

ИмяОписание
dataОтформатированные данные строки, которые будут записаны в файл
reportОбъект отчёта экспорта

OnMyFavoritesExportBeforeWriteRow - Запускается перед записью очередной строки в файл экспорта.

Параметры

ИмяОписание
dataДанные строки, которые будут записаны
reportОбъект отчёта экспорта

OnMyFavoritesExportAfterWriteRow - Запускается после записи очередной строки в файл экспорта.

Параметры

ИмяОписание
dataЗаписанные данные строки
reportОбъект отчёта экспорта

OnMyFavoritesBeforeSecurityAlert - Запускается перед регистрацией срабатывания защиты (превышение rate-limit, провал капчи, бан по IP — см. «Уведомления о срабатывании защиты»).

Событие может отменить регистрацию алерта целиком — не будет ни записи в audit-лог, ни уведомления (см. Tools::invokeEvent() выше). Также может переопределить message через возвращаемое значение.

Параметры

ИмяОписание
typeТип алерта (rate_limit / captcha / banned)
messageТекст ошибки, показанный посетителю
ipIP-адрес посетителя

OnMyFavoritesSecurityAlert - Запускается после регистрации срабатывания защиты (audit-лог уже записан, уведомление отправлено или отфильтровано настройками/троттлингом).

Параметры

ИмяОписание
typeТип алерта (rate_limit / captcha / banned)
messageТекст ошибки, показанный посетителю
ipIP-адрес посетителя
notifiedtrue, если уведомление было фактически отправлено (не троттлилось и не отфильтровано настройками)