Фильтрация пользователей с помощью mFilter2
Делая очередной тематический каталог организаций, где каждый пользователь это отдельная организация, которая размещает информацию о себе и своих услугах, я обычно применял классическое решение, когда при регистрации пользователя с помощью Office, создается отдельный ресурс и тогда можно без проблем просматривать карточки (ресурсы) организаций, осуществлять поиск и фильтровать их по разным параметрам используя готовые коробочные решения mSearch2 и mFilter2.
Плюсов тут несколько, в основном это возможность применять классические решения, ничего не допиливая. Но есть и минусы — приходится писать плагины, которые отслеживают изменение профиля пользователя и дублируют эти изменения в ресурс, определенные сложности модерирования, проблемы со скоростью сохранения и вывода информации и т.д.
Я решил заморочиться и сделал решение без дублирования информации, на основе таблицы пользователей.
В числе плюсов этого решения, могу привести следующие:
Прежде всего я понял, что стандартных полей таблицы пользователей мне не хватит, поэтому расширил таблицу, добавив свои дополнительные поля, с которыми MODX теперь работает как с родными. Об этом я писал здесь. Таким образом появился существенный прирост скорости, за счет того, что мне не нужно собирать и выводить многочисленные TV поля или присоединять еще какие то таблицы.
Далее появился вопрос, а как же этих пользователей фильтровать по различным общим признакам (специализация, рейтинг, PRO режим, страна, город и т.п.).
Само собой, чтобы не писать велосипеды было бы очень желательно использовать возможности mFilter2, но mFilter2 из коробки умеет фильтровать только ресурсы modResource и msProduct и связанные с ними таблицы (tv, msOption). Но спасибо Василию, за то, что он предусмотрел кастомизацию.
Задача, которую я перед собой поставил.
в таблице класса modUserProfile я имею добавленное мною дополнительное поле country_id, где лежит id страны из отдельной географической таблицы. Пользователи указывают в какой стране они работают, соответственно попробуем группировать и фильтровать пользователей по стране в качестве примера. Почему у меня хранится id страны, а не ее название? Ну просто сайт мультиязычный, и в зависимости от языка системы я выбираю из своей географической таблицы название на нужном языке. Из за этого придется делать дополнительный запрос к базе данных. Если у Вас нет необходимости в нескольких языках, то Вы можете хранить сразу название страны (такое поле кстати уже есть в стандартной таблице).
Шаг первый
Создаем страницу фильтрации и вызываем сниппет mFilter2 со следующими параметрами.
&tpl — просто выводим хоть что-то, чтобы видеть результат, если вы не укажете ничего, то сниппет распечатает результат запроса просто на экран в виде print_r
&element — вместо того, чтобы использовать стандартный сниппет mSearch2 который по умолчанию ищет в таблице site_content я указал pdoUsers, который как раз и выберет всех пользователей из таблицы пользователей (впервые пригодился). На данный момент наша конструкция уже выберет и выведет всех зарегистрированных пользователей в системе, автоматически разбив их постранично с помощью pdoPage.
&groups — Так как у меня кроме зарегистрированных компаний в таблице есть еще и другие пользователи, например администратор, мне нужно указать программе, что выбрать нужно только определенных пользователей. Для этого я добавляю нужных пользователей в отдельную группу shopkeepers и указываю сниппету, что выбрать нужно только их. Именно поэтому я и использую pdoUsers. Он уже из коробки умеет фильтровать пользователей по определенным параметрам. Далее мы будем просто передавать в него различные параметры &where и получать списки пользователей. Вот и все.
Так как мы договорились сортировать пользователей по странам (поле country_id), нужно построить соответствующий фильтр, выведя все указанные пользователями страны (предварительно нужно убедиться, что мы эти страны пользователям указали)
Стандартные коробочные фильтры mFilter2 нам не подходят, так как они могут построить фильтры только по ресурсам и связанными с ними полями. Придется переопределять стандартный класс и придумывать свои фильтры. Василий Наумкин уже предусмотрел такую возможность. Читаем Как работаю методы фильтрации и пример расширения фильтров
Согласно инструкции я создал свой класс, расширяющий стандартный класс фильтрации, указал его в системной настройке и осталось написать свои методы построения фильтра.
Добавляю в вызов сниппета новый фильтр &filters=`user|country_id:country`, где user это метод собирающий id пользователей, country_id — поле по которому строим фильтр, country — новый метод фильтрации
Первый метод выбирает всех пользователей, у которых есть заполненное поле country_id, затем собирает уникальные значения (id стран). На выходе получаем список стран, которые встречаются в базе.
В итоге мы получили первоначальный список пользователей которые входят в указанную группу (вы можете указать дополнительные условия выборки через &where) и построенный фильтр содержащий список всех встречающихся у пользователей стран.
Шаг третий
Если вы попробуете выбрать одну из стран, отметив ее в фильтре, то заметите, что сортировка не происходит. Нужный параметр в адресной строке добавляется, но список пользователей остается тем же.
Давайте разберемся почему так происходит.
После отметки страны, происходит ajax обращение к сниппету pdoUsers, но параметр из адресной строки никак не учитывается (pdoUsers не умеет читать эти параметры), поэтому мы получаем первоначальный список пользователей и не видим изменений. Нам нужно считать параметр из адресной строки ?user|country_id=93, содержащий id страны, по которой нужно сделать сортировку и передать ее в запрос через параметр $where
Для этого нужно сделать так называемый сниппет обертку, в котором мы считываем параметры адресной строки, записываем их в параметр $where и вызываем pdoUsers с обновленными параметрами
Жду ваших комментариев и оценок проделанной работы, наверняка будут советы по оптимизации логики.
Плюсов тут несколько, в основном это возможность применять классические решения, ничего не допиливая. Но есть и минусы — приходится писать плагины, которые отслеживают изменение профиля пользователя и дублируют эти изменения в ресурс, определенные сложности модерирования, проблемы со скоростью сохранения и вывода информации и т.д.
Я решил заморочиться и сделал решение без дублирования информации, на основе таблицы пользователей.
В числе плюсов этого решения, могу привести следующие:
- отсутствие дублирование информации и как следствие не нужно следить за связанными таблицами
- возможность использовать группы и роли для сортировки пользователей и разграничения вывода информации (на основе групп можно реализовать разные тарифные планы например)
Прежде всего я понял, что стандартных полей таблицы пользователей мне не хватит, поэтому расширил таблицу, добавив свои дополнительные поля, с которыми MODX теперь работает как с родными. Об этом я писал здесь. Таким образом появился существенный прирост скорости, за счет того, что мне не нужно собирать и выводить многочисленные TV поля или присоединять еще какие то таблицы.
Далее появился вопрос, а как же этих пользователей фильтровать по различным общим признакам (специализация, рейтинг, PRO режим, страна, город и т.п.).
Само собой, чтобы не писать велосипеды было бы очень желательно использовать возможности mFilter2, но mFilter2 из коробки умеет фильтровать только ресурсы modResource и msProduct и связанные с ними таблицы (tv, msOption). Но спасибо Василию, за то, что он предусмотрел кастомизацию.
Задача, которую я перед собой поставил.
в таблице класса modUserProfile я имею добавленное мною дополнительное поле country_id, где лежит id страны из отдельной географической таблицы. Пользователи указывают в какой стране они работают, соответственно попробуем группировать и фильтровать пользователей по стране в качестве примера. Почему у меня хранится id страны, а не ее название? Ну просто сайт мультиязычный, и в зависимости от языка системы я выбираю из своей географической таблицы название на нужном языке. Из за этого придется делать дополнительный запрос к базе данных. Если у Вас нет необходимости в нескольких языках, то Вы можете хранить сразу название страны (такое поле кстати уже есть в стандартной таблице).
Шаг первый
Создаем страницу фильтрации и вызываем сниппет mFilter2 со следующими параметрами.
&tpl — просто выводим хоть что-то, чтобы видеть результат, если вы не укажете ничего, то сниппет распечатает результат запроса просто на экран в виде print_r
&element — вместо того, чтобы использовать стандартный сниппет mSearch2 который по умолчанию ищет в таблице site_content я указал pdoUsers, который как раз и выберет всех пользователей из таблицы пользователей (впервые пригодился). На данный момент наша конструкция уже выберет и выведет всех зарегистрированных пользователей в системе, автоматически разбив их постранично с помощью pdoPage.
&groups — Так как у меня кроме зарегистрированных компаний в таблице есть еще и другие пользователи, например администратор, мне нужно указать программе, что выбрать нужно только определенных пользователей. Для этого я добавляю нужных пользователей в отдельную группу shopkeepers и указываю сниппету, что выбрать нужно только их. Именно поэтому я и использую pdoUsers. Он уже из коробки умеет фильтровать пользователей по определенным параметрам. Далее мы будем просто передавать в него различные параметры &where и получать списки пользователей. Вот и все.
[[!mFilter2?
&element=`pdoUsers`
&groups=`shopkeepers`
&tpl=`@INLINE Клиент - [[+fullname]]`
]]
Шаг второй. Так как мы договорились сортировать пользователей по странам (поле country_id), нужно построить соответствующий фильтр, выведя все указанные пользователями страны (предварительно нужно убедиться, что мы эти страны пользователям указали)
Стандартные коробочные фильтры mFilter2 нам не подходят, так как они могут построить фильтры только по ресурсам и связанными с ними полями. Придется переопределять стандартный класс и придумывать свои фильтры. Василий Наумкин уже предусмотрел такую возможность. Читаем Как работаю методы фильтрации и пример расширения фильтров
Согласно инструкции я создал свой класс, расширяющий стандартный класс фильтрации, указал его в системной настройке и осталось написать свои методы построения фильтра.
Добавляю в вызов сниппета новый фильтр &filters=`user|country_id:country`, где user это метод собирающий id пользователей, country_id — поле по которому строим фильтр, country — новый метод фильтрации
[[!mFilter2?
&element=`pdoUsers`
&groups=`shopkeepers`
&tpl=`@INLINE [[+fullname]]`
&filters=`
user|country_id:country
`
]]
Ниже привожу методы моего файла core/components/msearch2/custom/filters/custom.class.phpПервый метод выбирает всех пользователей, у которых есть заполненное поле country_id, затем собирает уникальные значения (id стран). На выходе получаем список стран, которые встречаются в базе.
public function getUserValues(array $fields, array $ids) {
$filters = array();
$no_id = false;
if (!in_array('id', $fields)) {
$fields[] = 'id';
$no_id = true;
}
$q = $this->modx->newQuery('modUser');
$q->leftJoin('modUserProfile','profile', 'profile.internalKey = modUser.id');
$q->orCondition(array('profile.internalKey:IN' => $ids));
foreach($fields as $field){
$q->select('profile.'.$field);
}
if ($q->prepare() && $q->stmt->execute()) {
while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
// $row = один пользователь и выборка его полей
foreach ($row as $k => $v) {
// $k Название поля например country_id
// $v = его значение например 31
$v = str_replace('"', '"', trim($v));
if ($v == '' || $v == '0' || $k == 'id' ) {
continue;
}
elseif (isset($filters[$k][$v])) {
$filters[$k][$v][$row['id']] = $row['id'];
}
else {
$filters[$k][$v] = array($row['id'] => $row['id']);
}
}
}
}
else {
$this->modx->log(modX::LOG_LEVEL_ERROR, "[mSearch2] Error on get filter params.\nQuery: ".$q->toSql()."\nResponse: ".print_r($q->stmt->errorInfo(),1));
}
return $filters;
}
Второй метод отвечает за построение фильтра. На входе получаем массив с ids пользователей и ids стран. На выходе получаем готовый фильтр включающий в себя названия стран и счетчик показывающий сколько пользователей указали ту или иную страну. public function buildCountryFilter(array $values, $name = '', $field = 'country_id') {
// $values - массив где key - страна, value - список пользователей у которых указана эта страна
$q = $this->modx->newQuery('modUserProfile');
$q->select('id,'.$field);
if ($q->prepare() && $q->stmt->execute()) {
// Здесь имею массив пользователей с заполненной страной, где key user id а value country id
foreach ($values as $country_id => $ids) {
// Здесь делаю дополнительный запрос для получения названия страны по ее id
$sql = "SELECT ru_title FROM `modx_geo_content` WHERE id = :id";
$q = $this->modx->prepare($sql);
$q->bindParam(':id', $country_id);
$q->execute();
$arr = $q->fetchAll(PDO::FETCH_ASSOC);
$title = $arr[0]['ru_title'];
$results[$title] = array(
'title' => $title,
'value' => $country_id,
'type' => $field,
'resources' => $ids
);
}
}
ksort($results);
return $results;
}
В итоге мы получили первоначальный список пользователей которые входят в указанную группу (вы можете указать дополнительные условия выборки через &where) и построенный фильтр содержащий список всех встречающихся у пользователей стран.
Шаг третий
Если вы попробуете выбрать одну из стран, отметив ее в фильтре, то заметите, что сортировка не происходит. Нужный параметр в адресной строке добавляется, но список пользователей остается тем же.
Давайте разберемся почему так происходит.
После отметки страны, происходит ajax обращение к сниппету pdoUsers, но параметр из адресной строки никак не учитывается (pdoUsers не умеет читать эти параметры), поэтому мы получаем первоначальный список пользователей и не видим изменений. Нам нужно считать параметр из адресной строки ?user|country_id=93, содержащий id страны, по которой нужно сделать сортировку и передать ее в запрос через параметр $where
Для этого нужно сделать так называемый сниппет обертку, в котором мы считываем параметры адресной строки, записываем их в параметр $where и вызываем pdoUsers с обновленными параметрами
<?php
if(isset($_GET['user|country_id'])){
$country_ids = $_GET['user|country_id'];
if (!empty($where)) {
if (!is_array($where)) {
$where = $modx->fromJSON($where);
}
} else {
$where = array();
}
$where['modUserProfile.country_id:IN'] = !is_array($country_ids)
? array_map('trim', explode(',', $country_ids))
: $country_ids;
if (!empty($where)) {
$scriptProperties['where'] = $modx->toJSON($where);
}
}
return $modx->runSnippet('pdoUsers', $scriptProperties);
Ну и теперь в параметре &element нужно вместо pdoUsers нужно вызвать наш сниппет обертку. В таком случае мы все равно вызываем pdoUsers, но еще и учитываем GET параметры. [[!mFilter2?
&element=`getUsersFilter`
&groups=`shopkeepers`
&tpl=`@INLINE [[+fullname]]`
&filters=`
user|country_id:country
`
]]
Теперь сортировка по стране у меня заработала. Остается только подготовить чанк для вывода информации. Кстати, для того, чтобы просмотреть подробную информацию о отдельной карточке пользователя я буду использовать вот эту техникуЖду ваших комментариев и оценок проделанной работы, наверняка будут советы по оптимизации логики.
Поблагодарить автора
Отправить деньги
Комментарии: 22
По идее, не нужно что-то получать из $_GET и фильтровать самостоятельно.
Сниппет-обёртка должен получать id подходящих юзеров в параметре &resources, через запятую.
Сниппет-обёртка должен получать id подходящих юзеров в параметре &resources, через запятую.
Можно, кстати, прямо скопипастить из вот этой заметки
<?php
/**@var array $scriptProperties */
if (!empty($resources)) {
if (!empty($where)) {
if (!is_array($where)) {
$where = $modx->fromJSON($where);
}
} else {
$where = array();
}
$where['id:IN'] = !is_array($resources)
? array_map('trim', explode(',', $resources))
: $resources;
if (!empty($where)) {
$scriptProperties['where'] = $modx->toJSON($where);
}
}
return $modx->runSnippet('pdoUsers', $scriptProperties);
Спасибо работает. На днях буду допиливать другие параметры сортировки, протестирую сниппет на универсальность.
Интересный материал.
Только вот проблема в том что данный метод только для полей которые заданы для таблицы UserProfile
Для встроенных полей пользователя возможно сделать фильтрацию?
К примеру поле country, city которые в таблице user_atributes
Только вот проблема в том что данный метод только для полей которые заданы для таблицы UserProfile
Для встроенных полей пользователя возможно сделать фильтрацию?
К примеру поле country, city которые в таблице user_atributes
Нет такой таблицы UserProfile, все данные о которых идет речь, лежат как раз в таблице user_atributes.
А UserProfile это объект, который получается из таблицы и содержит данные таблицы user_atributes.
Так что фильтация по дефолтным полям пользователя (city, country) через этот способ тоже поддерживается
А UserProfile это объект, который получается из таблицы и содержит данные таблицы user_atributes.
Так что фильтация по дефолтным полям пользователя (city, country) через этот способ тоже поддерживается
Не могли бы помочь не могу понять что необходимо заменить в файле который вы создали
Сейчас файл custom.class.php выглядит следующим образом
Сейчас файл custom.class.php выглядит следующим образом
<?php
class myCustomFilter extends mse2FiltersHandler {
public function getUserValues(array $fields, array $ids) {
$filters = array();
$no_id = false;
if (!in_array('id', $fields)) {
$fields[] = 'id';
$no_id = true;
}
$q = $this->modx->newQuery('modUser');
$q->leftJoin('modUserProfile','profile', 'profile.internalKey = modUser.id');
$q->orCondition(array('profile.internalKey:IN' => $ids));
foreach($fields as $field){
$q->select('profile.'.$field);
}
if ($q->prepare() && $q->stmt->execute()) {
while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
// $row = один пользователь и выборка его полей
foreach ($row as $k => $v) {
// $k Название поля например country_id
// $v = его значение например 31
$v = str_replace('"', '"', trim($v));
if ($v == '' || $v == '0' || $k == 'id' ) {
continue;
}
elseif (isset($filters[$k][$v])) {
$filters[$k][$v][$row['id']] = $row['id'];
}
else {
$filters[$k][$v] = array($row['id'] => $row['id']);
}
}
}
}
else {
$this->modx->log(modX::LOG_LEVEL_ERROR, "[mSearch2] Error on get filter params.\nQuery: ".$q->toSql()."\nResponse: ".print_r($q->stmt->errorInfo(),1));
}
return $filters;
}
public function buildCountryFilter(array $values, $name = '', $field = 'country_id') {
// $values - массив где key - страна, value - список пользователей у которых указана эта страна
$q = $this->modx->newQuery('modUserProfile');
$q->select('id,'.$field);
if ($q->prepare() && $q->stmt->execute()) {
// Здесь имею массив пользователей с заполненной страной, где key user id а value country id
foreach ($values as $country_id => $ids) {
// Здесь делаю дополнительный запрос для получения названия страны по ее id
$sql = "SELECT ru_title FROM `modx_geo_content` WHERE id = :id";
$q = $this->modx->prepare($sql);
$q->bindParam(':id', $country_id);
$q->execute();
$arr = $q->fetchAll(PDO::FETCH_ASSOC);
$title = $arr[0]['ru_title'];
$results[$title] = array(
'title' => $title,
'value' => $country_id,
'type' => $field,
'resources' => $ids
);
}
}
ksort($results);
return $results;
}
}
Что здесь необходимо заменить что бы фильтр работал по полю country и city
Заменив country_id на country ничего не выводит.
Сделал вот так но в фильтре отображаются id пользователей
<?php
class myCustomFilter extends mse2FiltersHandler {
public function getUserValues(array $fields, array $ids) {
$filters = array();
$no_id = false;
if (!in_array('id', $fields)) {
$fields[] = 'id';
$no_id = true;
}
$q = $this->modx->newQuery('modUser');
$q->leftJoin('modUserProfile','profile', 'profile.internalKey = modUser.id');
$q->orCondition(array('profile.internalKey:IN' => $ids));
foreach($fields as $field){
$q->select('profile.'.$field);
}
if ($q->prepare() && $q->stmt->execute()) {
while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
// $row = один пользователь и выборка его полей
foreach ($row as $k => $v) {
// $k Название поля например country_id
// $v = его значение например 31
$v = str_replace('"', '"', trim($v));
if ($v == '' || $v == '0' || $k == 'country' ) {
continue;
}
elseif (isset($filters[$k][$v])) {
$filters[$k][$v][$row['country']] = $row['id'];
}
else {
$filters[$k][$v] = array($row['country'] => $row['id']);
}
}
}
}
else {
$this->modx->log(modX::LOG_LEVEL_ERROR, "[mSearch2] Error on get filter params.\nQuery: ".$q->toSql()."\nResponse: ".print_r($q->stmt->errorInfo(),1));
}
return $filters;
}
public function buildCountryFilter(array $values, $name = '', $field = 'country') {
// $values - массив где key - страна, value - список пользователей у которых указана эта страна
$q = $this->modx->newQuery('modUserProfile');
$q->select('country,'.$field);
if ($q->prepare() && $q->stmt->execute()) {
// Здесь имею массив пользователей с заполненной страной, где key user id а value country id
foreach ($values as $country => $country) {
// Здесь делаю дополнительный запрос для получения названия страны по ее id
$sql = "SELECT country FROM `modx_user_atributes` WHERE id = :country";
$q = $this->modx->prepare($sql);
$q->bindParam(':id', $country);
$q->execute();
$arr = $q->fetchAll(PDO::FETCH_ASSOC);
$title = $arr[0]['country'];
$results[$title] = array(
'title' => $title,
'value' => $country,
'type' => $field,
'resources' => $ids
);
}
}
ksort($results);
return $results;
}
}
Может кто то помочь?
Попробуй так, метод фильтрации не прописывается или прописывается :default
[[!mFilter2?
&element=`getUsersFilter`
&groups=`shopkeepers`
&tpl=`@INLINE [[+fullname]]`
&filters=`
user|city
`
]]
Появилось в фильтре mse2_filter_user_city и город отображается
И второй фильтр mse2_filter_user_id вместо страны в нем отображаются id пользователей
И второй фильтр mse2_filter_user_id вместо страны в нем отображаются id пользователей
Пользователи отображаются но при нажатии не фильтрует их.
Шаг третий из статьи делал? Только там учти что у тебя Get параметр другой будет
Шаг третий делал
Но переделать не получается под свои требования. Изменил все country_id uf country но не заработало.
Но переделать не получается под свои требования. Изменил все country_id uf country но не заработало.
С этим сниппетом заработала фильтрация
<?php
/**@var array $scriptProperties */
if (!empty($resources)) {
if (!empty($where)) {
if (!is_array($where)) {
$where = $modx->fromJSON($where);
}
} else {
$where = array();
}
$where['id:IN'] = !is_array($resources)
? array_map('trim', explode(',', $resources))
: $resources;
if (!empty($where)) {
$scriptProperties['where'] = $modx->toJSON($where);
}
}
return $modx->runSnippet('pdoUsers', $scriptProperties);
Вот только хотел его скинуть. Молодец что сам нашел.
Помогите фильтр по стране сделать
Так заработала фильтрация или нет?
Фильтрация зарабтала только вот страны не выводятся вместо них id пользователей
А сортировка по городам я так понимаю работает?
Для сортировки по странам, придется писать свой отдельный метод фильтрации, мой не подойдет, я использовал собственную базу стран, не стандартную базу стран MODX.
Вызов метода будет примерно таким
Для сортировки по странам, придется писать свой отдельный метод фильтрации, мой не подойдет, я использовал собственную базу стран, не стандартную базу стран MODX.
Вызов метода будет примерно таким
[[!mFilter2?
&element=`getUsersFilter`
&groups=`shopkeepers`
&tpl=`@INLINE [[+fullname]]`
&filters=`
user|country:country
`
]]
В классе фильтрации нужно будет написать свой отдельный метод buildCountryFilter, который будет выводить названия стран. Мой метод из примера не подойдет, потому что я брал данные из своей отдельной таблицы, но можно его просто допилить под использование стандартной таблицы стран
Можете помочь в этом? Допилить ваш метод.
К сожалению перегружен работой. Даже за деньги некогда. Попробуй разместить заявку в разделе работа с заголовком Доработать метод фильтрации и ссылкой сюда.
Вернул все значения какие были в вашем файле и все заработало.
Спасибо за помощь.
Спасибо за помощь.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.