Хуки-хухуки: Исключаем неактивных пользователей из списков

+19
1.43K
Как иногда начинают свой монолог неопытные стендаперы: «У всех в жизни было такое …», когда вывел на главную страницу сайта список пользователей, а там половина – серые тени новичков, не подтвердивших регистрацию. Давайте в качестве ещё одного примера по использованию хуков уберём их оттуда, оставив только реальных пользователей.



Есть два способа это сделать по-простому.
1. Добавить одну строку в код дефолтного виджета «Новые пользователи» для фильтрации по признаку блокирования пользователя. Но это хак, который слетит при обновлении движка. Помнить об этом хаке и следить за обновлением файла очень не хочется.
2. Создать копию этого виджета и внести правку туда. Новый виджет при обновлении не слетит, но и обновлять его придётся вручную. Тоже не подходит.

Попробуем сделать всё без хаков движка.

Подготовка

Чтобы использовать хуки нужно иметь свой компонент для них. Если у вас уже такой есть – отлично. Если нет, можете себе создать компонент для хуков — мы всё будем делать на примере компонента из этого поста.

Включим «Требовать подтверждения e-mail при регистрации», зарегистрируем нового пользователя с ником «Новенький». Переключим стиль виджета на «Список». К Администратору добавился неподтверждённый Новенький, что логично, но нам бы хотелось его скрыть.


Ищем событие для изменения списка пользователей

Начнём с выяснения, откуда в виджете берётся список пользователей и есть ли подходящее событие, чтобы его изменить.
Если вы ещё не знаете что такое события/хуки, для чего они нужны и как работают, то перед тем как читать этот пост дальше, прочитайте Что такое события и хуки (без PHP и с картинками)

Смотрим события отладкой

Используем простой и наглядный вариант поиска событий. В «Расширенной отладке» включим вывод в лог виджетов и в их фильтре напишем имя виджета «Новые пользователи». Также включим вывод событий в лог.
(В этом месте можете почитать Что такое фильтры в отладке и как ими пользоваться))
После обновления главной страницы демо увидим, во-первых, что из всех виджетов в логе остался только один наш, во-вторых, внутри виджета есть три события ‘profiles_list_filter’, ‘profiles_before_list’ и ‘render_widget_users_list_list’.
Третье событие нам не интересно – по названию видно, что там происходит рендеринг html-кода виджета. А вот первые два надо бы изучить…

Парой кликов включим трассировку событий хотя бы на три уровня и сразу увидим, что эти события возникают в строках 20 и 67 нашего виджета /system/controllers/users/widgets/list/widget.php.
Теперь нужно узнать, передаётся ли в этих событиях что-то, что мы можем использовать?
В строке 20 файла виджета видим событие, которое передаёт в хук поля профиля и модель компонента пользователей. Это уже хорошо. Да и по названию события видно, что это для фильтра списка пользователей.
  1. list($fields, $model) = cmsEventsManager::hook('profiles_list_filter', array($fields, $model));
В событии в строке 67 этого же файла передаются уже готовый список пользователей и список выводимых в виджете полей.
  1. list($profiles, $show_fields) = cmsEventsManager::hook('profiles_before_list', [$profiles, $show_fields]);
Нам лучше подойдёт первый хук, в котором к модели списка пользователей можно будет применить дополнительный фильтр по отключённым пользователям и сразу получить нужное количество активных пользователей. При использовании второго хука нам придётся в цикле перебрать отобранных пользователей и оставить только подтверждённых – их суммарное количество может измениться, а нам это не нужно.

Ищем события в коде

Самый главный вопрос при любом поиске: "Где искать?". Если вы читали «официальную документацию по виджетам» или мой пост «Как работают виджеты (без PHP и с картинками)», то ответите на него легко. Виджет (widget) «Новые пользователи» принадлежит контроллеру (controllers) "Пользователи" (users), а значит находится в его папке /system/controllers/users/widgets. Там мы видим только три виджета: avatar, list и online. Понятно, что нас интересует list — список. Итого получаем имя файла /system/controllers/users/widgets/list/widget.php

Если изучить код этого файла, то можно найти в нём пару событий, которые возвращают/обрабатывают список пользователей и о которых мы уже говорили выше. Логика выбора остаётся той же. В данном случае поиск событий по коду оказался очень простым, но так бывает не всегда.

Событие ‘profiles_list_filter’

На всякий случай посмотрим, что реально передаётся в первом событии. Включим «Выводить в лог данные событий». И впишем в строку фильтра по имени события ‘profiles_list_filter’. В выпадающем списке его нет, так как там только события от зарегистрированных хуков, а это событие никто не обрабатывает, хука нет.
Смотрим, что передаётся в хуки в первом событии:

Первое, что видно, в событие действительно передаётся массив описаний полей и объект modelUsers. Это нам подходит.

Второе, это то, что событие ‘profiles_list_filter’ происходит на странице дважды: в нашем виджете «Новые пользователи» и в виджете «Сейчас онлайн». Поиск по коду движка говорит, что это событие возникает в пяти местах: в двух виджетах, в обработке подписок и в списках пользователей, полученных через метод renderProfilesList() компонента Users.
Это значит, что если мы будем менять список пользователей в обработке этого события, то не подтверждённые пользователи будут исключены во всех местах, где оно происходит. Для некоторых проектов это как раз подходит, а для других придётся придумать проверку, из какого места был сделан вызов хука, и фильтровать только в нужном виджете.

Добавляем опцию

В компоненте «Хуки-хухуки» уже есть вкладка «Виджеты». Так как наш хук относится к пользователям, то добавим в файл \system\controllers\webman\backend\forms\form_options.php ещё одну вкладку «Пользователи» с опцией 'users_no_locked' «Исключать неактивных пользователей из списков».
  1. 'type' => 'fieldset',
  2. 'title' => LANG_WEBMAN_OPT_TAB_USERS,
  3. 'childs' => array(
  4.  
  5. new fieldCheckbox('users_no_locked', array(
  6. 'title' => LANG_WEBMAN_OPT_USERS_NO_LOCKED,
  7. )),
  8.  
  9. )
  10. ),
  1. <?php
  2.  
  3. class formWebmanOptions extends cmsForm {
  4.  
  5. public $is_tabbed = true;
  6.  
  7. public function init() {
  8.  
  9. return array(
  10.  
  11. 'type' => 'fieldset',
  12. 'title' => LANG_WEBMAN_OPT_TAB_WIDGETS,
  13. 'childs' => array(
  14.  
  15. new fieldCheckbox('widgets_title_link', array(
  16. 'title' => LANG_WEBMAN_OPT_WD_TITLE_LINK,
  17. )),
  18.  
  19. )
  20. ),
  21.  
  22. 'type' => 'fieldset',
  23. 'title' => LANG_WEBMAN_OPT_TAB_USERS,
  24. 'childs' => array(
  25.  
  26. new fieldCheckbox('users_no_locked', array(
  27. 'title' => LANG_WEBMAN_OPT_USERS_NO_LOCKED,
  28. )),
  29.  
  30. )
  31. ),
  32.  
  33. );
  34.  
  35. }
  36.  
  37. }
Добавим языковые константы в файл \system\languages\ru\controllers\webman\webman.php
  1. define('LANG_WEBMAN_OPT_TAB_USERS', 'Пользователи');
  2. define('LANG_WEBMAN_OPT_USERS_NO_LOCKED', 'Исключать неактивных пользователей из списков');
  1. <?php
  2.  
  3. define('LANG_WEBMAN_CONTROLLER', 'Хуки-хухуки');
  4.  
  5. define('LANG_WEBMAN_OPT_TAB_WIDGETS', 'Виджеты');
  6. define('LANG_WEBMAN_OPT_TAB_USERS', 'Пользователи');
  7.  
  8. define('LANG_WEBMAN_OPT_WD_TITLE_LINK', 'Добавить поле для ссылки под заголовком');
  9.  
  10. define('LANG_WEBMAN_OPT_USERS_NO_LOCKED', 'Исключать неактивных пользователей из списков');
  11.  
  12. define('LANG_WEBMAN_WD_OPT_TITLE_LINK', 'Ссылка под заголовком виджета');

Создаём хук

Создадим новый файл с именем обрабатываемого события /system/controllers/webman/hooks/profiles_list_filter.php.
  1. <?php
  2.  
  3. class onWebmanProfilesListFilter extends cmsAction {
  4.  
  5. public function run($data){
  6.  
  7. list($fields, $model_users) = $data;
  8.  
  9. // --- Поле ссылки под заголовком виджета ------------------------------
  10.  
  11. if ($this->options['users_no_locked']) {
  12.  
  13. // Добавляем к модели пользователей фильтр, исключающий заблокированных пользователей
  14. $model_users->filterIsNull('is_locked');
  15.  
  16. }
  17.  
  18. return [$fields, $model_users];
  19.  
  20. }
  21.  
  22. }
В нём сначала присваиваем элементы массива $data отдельным переменным: описанию полей $fields и модели пользователей $model_users.
Потом проверяем включение опции 'users_no_locked'.
И если она включена, то добавляем к модели пользователей фильтр: только пользователей, у которых поле 'is_locked' равно null, то есть пользователь не заблокирован.
И в конце возвращаем объединённые в массив входные переменные $fields и $model_users, чтобы их могли обработать другие такие же хуки или код после хука в точке его вызова.

Добавляем хук в манифест /system/controllers/webman/manifest.php
  1. <?php
  2.  
  3. return array(
  4.  
  5. 'hooks' => array(
  6.  
  7. 'profiles_list_filter', // Исключение неактивных пользователей из списков
  8.  
  9. 'widget_options_full_form', // Добавление полей в форму опций виджетов
  10.  
  11. 'widgets_before_list', // Изменение списка виджетов
  12. )
  13.  
  14. );
И разрешаем добавить наш хук в список хуков («Админка – Компоненты — Управление событиями»).
Отлично! Заблокированный пользователь исчез из списков в виджете и на странице пользователей.

Фильтр только в виджете

Если мы хотим фильтровать список пользователей только в нашем виджете, то нужно в хуке проверять, откуда сделан его вызов. Я не нашёл в движке стандартного способа получить эту информацию, поэтому используем небольшой лафхак – поищем класс нашего виджета 'widgetUsersList' в трассировке вызовов PHP из функции debug_backtrace()
  1. <?php
  2.  
  3. class onWebmanProfilesListFilter extends cmsAction {
  4.  
  5. public function run($data){
  6.  
  7. list($fields, $model_users) = $data;
  8.  
  9. // --- Поле ссылки под заголовком виджета ------------------------------
  10.  
  11. if ($this->options['users_no_locked']) {
  12.  
  13. // Получаем трассировку
  14. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  15.  
  16. // Ищем в трассировке имя класса нужного виджета
  17. $found = false;
  18.  
  19. foreach ($backtrace as $line) {
  20.  
  21. if ($line['class'] == 'widgetUsersList') {
  22.  
  23. $found = true;
  24.  
  25. break;
  26. }
  27.  
  28. }
  29.  
  30. if ($found) {
  31.  
  32. // Добавляем к модели пользователей фильтр, исключающий заблокированных пользователей
  33. $model_users->filterIsNull('is_locked');
  34.  
  35. }
  36.  
  37. }
  38.  
  39. return [$fields, $model_users];
  40.  
  41. }
  42.  
  43. }
В этом варианте дополнительно получаем массив трассировки вызовов в переменную $backtrace. Перебираем его элементы $line и ищем в них класс 'widgetUsersList'. Если находим, то присваиваем переменной $found значение true, которое является разрешением для добавления нашего фильтра пользователей.

Проверяем. Теперь в виджете «Новые пользователи» остался только Администратор – только активные пользователи. А в списке пользователей на странице httр://site/users показывает и активных, и заблокированных пользователей. Всё как мы и хотели, наш фильтр применяется только при вызове из одного виджета.

Расширение функционала

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

Тогда строка проверки класса в хуке может выглядеть как-то так:
  1. if (in_array($line['class'], explode(',', $this->options['имя_опции_со_списком']))) {

Пакет обновления компонента

Создание пакета обновления компонента описано в предыдущей части про Добавление ссылки под заголовки виджетов.

Для ленивых ссылка на готовый пакет обновления webman_update_1.2.0.zip.

Не забывайте, что имя webman везде нужно заменить на своё имя компонента с сохранением регистров символов

Make Make 3 года назад #
Комментарий удален
+2
T34 T34 3 года назад #
Я когда вижу такие посты в блоге, я прям радуюсь. Всё расписано, всё рассказано и есть пример.
Во первых, поисковику есть что индексировать.
Во вторых новичку есть что читать, и на чем учится.

Я конечно писатель так себе laugh , и кодер тоже так себе )) поэтому не магу себе позволить писать такие статейки scratch а так бы тоже с радостью наполнил блог такими статьями.

WebMan, +++++ однозначно!!!
+2
Scythian Scythian 3 года назад #
Полезно, плюсую в тему и в карму )
0
Pisces Pisces 3 года назад #
В темы и в карму +

Еще от автора

«Расширенная отладка» для InstantCMS 2.14.1 (v.14.1.2) – большое обновление для разработчиков
Новые возможности и удобства, облегчающие разработчикам отладку компонентов и шаблонов.
Использование расширенной отладки. Часть 11. Анализ ошибок 403/404 и редиректов
Одной из неудобных задач при отладке для меня является поиск причины ошибки 403/404.
Использование расширенной отладки. Часть 10. Табличные контрольные точки
Иногда при тестировании кода возникает необходимость как-то более удобно вывести данные из повторяющихся участков кода или наглядно отследить изменени
Используя этот сайт, вы соглашаетесь с тем, что мы используем файлы cookie.