Расширенные поля пользователей
Небольшая пошаговая инструкция, как научиться сохранять любые интересующие данные о юзере в специальное поле extended.
На самом деле, это никакой не секрет. Во многих объектах MODX есть специальное поле типа JSON, которое хранится в БД как текст, например, properties у modResource и extended у modUserProfile.
При работе с объектами xPDO, JSON текст из этих полей превращается в массивы. То есть, общий принцип выглядит так:
А теперь давайте напишем плагин, который будет сохранять данные о регистрации и последней активности пользователя, которые MODX сам, почему-то, не сохраняет.
Для плагина мы используем события OnUserSave и OnLoadWebDocument:
Для вывода пользователей я советую использовать сниппет pdoUsers, который обладает всеми основными возможностями pdoTools.
Если вызвать его без чанка, то он распечатает массив всех плейсхолдеров юзера, включая поле extended.
pdoTools выводит массивы плейсхолдеров через точку, поэтому нас интересуют [[+extended.lastactivity]] и [[+extended.registered]].
Можно использовать фильтры вывода или параметр &prepareSnippet, чтобы подготовить эти даты для вывода в более приятном виде.
Вот пример вывода с указанием &prepareSnippet:
А вот и сам сниппет prepareUser:
Из-за того, что в БД данные extended хранятся в виде JSON текста, по ним нельзя нормально осуществлять фильтрацию и сортировку.
Если вам нужно не просто выводить какие-то значения, то придётся написать дополнительную таблицу с id юзера и нужными колонками, чтобы сохранять данные в неё раздельно.
Тогда вы сможете присоединить эту таблицу в вызове сниппета pdoUsers и фильтровать\сортировать пользователяй по дополнительным полям.
На самом деле, это никакой не секрет. Во многих объектах MODX есть специальное поле типа JSON, которое хранится в БД как текст, например, properties у modResource и extended у modUserProfile.
При работе с объектами xPDO, JSON текст из этих полей превращается в массивы. То есть, общий принцип выглядит так:
// id нужного пользователя
$user_id = 15;
// Получаем объект modUser
if ($user = $modx->getObject('modUser', $user_id)) {
// Получаем связанный с ним профиль пользователя
if ($profile = $user->getOne('Profile')) {
// Получаем специальное поле extended
$extended = $profile->get('extended');
// Добавляем новое значение
$extended['mykey'] = 'mydata';
// И сохраняем обратно в профиль
$profile->set('extended', $extended);
$profile->save();
}
}
Самое приятное, что эти данные вы можете не только просмотреть на странице пользователя, но и изменить.Регистрация и активность
А теперь давайте напишем плагин, который будет сохранять данные о регистрации и последней активности пользователя, которые MODX сам, почему-то, не сохраняет.
Для плагина мы используем события OnUserSave и OnLoadWebDocument:
switch ($modx->event->name) {
case 'OnUserSave':
// Сохраняем дату создания нового пользователя
if ($user && $mode == 'new') {
if ($profile = $user->getOne('Profile')) {
$extended = $profile->get('extended');
$extended['registered'] = date('Y-m-d H:i:s');
$profile->set('extended', $extended);
$profile->save();
}
}
break;
case 'OnLoadWebDocument':
// Сохраняем дату открытия любой страницы сайта, если пользователь авторизован
if ($modx->user->isAuthenticated($modx->context->key)) {
// Здесь мы работаем с текущим пользователем - у него профиль уже загружен
$profile = $modx->user->Profile;
$extended = $profile->get('extended');
$extended['lastactivity'] = date('Y-m-d H:i:s');
$profile->set('extended', $extended);
$profile->save();
}
break;
}
И вот, что у нас получается:Вывод полей профиля
Для вывода пользователей я советую использовать сниппет pdoUsers, который обладает всеми основными возможностями pdoTools.
Если вызвать его без чанка, то он распечатает массив всех плейсхолдеров юзера, включая поле extended.
pdoTools выводит массивы плейсхолдеров через точку, поэтому нас интересуют [[+extended.lastactivity]] и [[+extended.registered]].
[[!pdoUsers?
&tpl=`@INLINE <p>[[+fullname]] - Регистрация: [[+extended.registered]], активность: [[+extended.lastactivity]]</p>`
]]
Можно использовать фильтры вывода или параметр &prepareSnippet, чтобы подготовить эти даты для вывода в более приятном виде.
Вот пример вывода с указанием &prepareSnippet:
[[!pdoPage?
&element=`pdoUsers`
&tpl=`tpl.Users.list.row`
&prepareSnippet=`prepareUser`
]]
А вот и сам сниппет prepareUser:
<?php
// Комментарии юзера
$row['comments'] = $modx->getCount('TicketComment', array('createdby' => $row['id'], 'published' => 1));
// Тикеты
$row['tickets'] = $modx->getCount('Ticket', array('createdby' => $row['id'], 'class_key' => 'Ticket', 'published' => 1, 'deleted' => 0));
// Проверка и красивое форматирование дат через сниппет dateAgo
$row['registered'] = !empty($row['extended']['registered'])
? $modx->runSnippet('dateAgo', array('input' => $row['extended']['registered']))
: '-';
$row['lastactivity'] = !empty($row['extended']['lastactivity'])
? $modx->runSnippet('dateAgo', array('input' => $row['extended']['lastactivity']))
: '-';
return serialize($row);
&prepareSnippet — это особенность pdoTools, можно использовать со всеми сниппетами.Недостатки
Из-за того, что в БД данные extended хранятся в виде JSON текста, по ним нельзя нормально осуществлять фильтрацию и сортировку.
Если вам нужно не просто выводить какие-то значения, то придётся написать дополнительную таблицу с id юзера и нужными колонками, чтобы сохранять данные в неё раздельно.
Тогда вы сможете присоединить эту таблицу в вызове сниппета pdoUsers и фильтровать\сортировать пользователяй по дополнительным полям.
Комментарии: 39
Спасибо! ну собственно так и начал делать…
Жаль, что не посортируешь.
Еще такой вопрос. Плагин висит на OnUserSave, а в HybridAuth нет его вызова: github.com/bezumkin/modx-hybridauth/search?q=OnUserSave&ref=cmdform
Зато есть OnUserFormSave — github.com/bezumkin/modx-hybridauth/search?q=OnUserFormSave&type=Code, который судя по описанию, вызывается при сохранению пользователя из админки. Нестыковочка какая-то, или я упустил что?
Жаль, что не посортируешь.
Еще такой вопрос. Плагин висит на OnUserSave, а в HybridAuth нет его вызова: github.com/bezumkin/modx-hybridauth/search?q=OnUserSave&ref=cmdform
Зато есть OnUserFormSave — github.com/bezumkin/modx-hybridauth/search?q=OnUserFormSave&type=Code, который судя по описанию, вызывается при сохранению пользователя из админки. Нестыковочка какая-то, или я упустил что?
OnUserSave вызывается из самого modUser при сохранении.
Василий, добрый день!
Прошу совета: Хочу в Extended поле сохранить многострочный текст. Не понимаю, как сделать так, чтобы символы перевода строки учитывались. А то я получаю весь текст слепленный в одну строку.
Просто я хочу, чтобы в одной из настроек пользователя хранился шаблон письма. Чтобы у каждого пользователя был такой свой и он мог его редактировать как и весь Profile.
Буду очень признателен за помощь, сам ответ не смог найти.
Пытался туда html засунуть, тоже режется при выводе в textarea
Прошу совета: Хочу в Extended поле сохранить многострочный текст. Не понимаю, как сделать так, чтобы символы перевода строки учитывались. А то я получаю весь текст слепленный в одну строку.
Просто я хочу, чтобы в одной из настроек пользователя хранился шаблон письма. Чтобы у каждого пользователя был такой свой и он мог его редактировать как и весь Profile.
Буду очень признателен за помощь, сам ответ не смог найти.
Пытался туда html засунуть, тоже режется при выводе в textarea
А пример JOIN с кол-вом комментариев и публикаций можно попросить?
А сам?
Ticket связан с TicketThread через Ticket.id — TicketThread.resource, а TicketThread с TicketComment через TicketThread.id — TicketComment.thread.
Нужно 2 джойна и если комментариев много — будут тормоза.
Такое присоединение есть в старых версиях getTickets, потом я от него отказался и выбираю количество комментариев отдельно.
Ticket связан с TicketThread через Ticket.id — TicketThread.resource, а TicketThread с TicketComment через TicketThread.id — TicketComment.thread.
Нужно 2 джойна и если комментариев много — будут тормоза.
Такое присоединение есть в старых версиях getTickets, потом я от него отказался и выбираю количество комментариев отдельно.
Да пишу сам, JOIN кол-ва комментариев происходит нормально, а когда еще публикации добавляю — ерунда.
Вот пример:
p.s. если что это вопрос как тут modx.pro/users/ вывести кол-во постов и комментов.
Вот пример:
[[!getPage?
&element=`pdoUsers`
&groups=`Fisher`
&limit=`15`
&tpl=`users.List.Row`
&sortby=`thislogin`
&sortdir=`DESC`
&where=`{"Comments.deleted = 0", "Comments.published = 1", "Publications.deleted = 0", "Publications.published = 1"}`
&fastMode=`1`
&select=`{"Comments":"COUNT(Comments.id) as comments_count", "Publications":"COUNT(Publications.id) as publications_count"}`
&innerJoin = `{"Comments": {"class":"TicketComment","on":"Comments.createdby=modUser.id"}, "Publications": {"class":"modResource","on":"Publications.createdby=modUser.id"}}`
]]
На выходе — откровенная фигня. Числа берутся не пойму откуда… но явно не те)p.s. если что это вопрос как тут modx.pro/users/ вывести кол-во постов и комментов.
Проблема не в вызове сниппета, а в исходной задаче.
Если нужно посчитать количество строк в двух разных таблицах — то нужна группировка и минимум 2 запроса.
Лично я использую &prepareSnippet и считаю строки уже в нём — и быстро и удобно.
Если нужно посчитать количество строк в двух разных таблицах — то нужна группировка и минимум 2 запроса.
Лично я использую &prepareSnippet и считаю строки уже в нём — и быстро и удобно.
Чет я не понял, это по 2 запроса на каждую строчку получится, при выводе 15 строк — лишних 30 запросов, не много?)
А про постановку задачи понял… 1 джойн хорошо проходит, а 2 уже нельзя…
А про постановку задачи понял… 1 джойн хорошо проходит, а 2 уже нельзя…
Важно не количество запросов, а время их выполнения. Здесь показывает 30 запросов за 0.0677 сек. Как считаешь, много это или нет?
А вот если написать запрос с двумя джоинами и подсчетом количества комментариев для каждой страницы — может быть и 5 секунд. Особенно, если 2000 страниц и 30000 комментариев.
Короче, лучше много маленьких запросов, чем один большой.
А вот если написать запрос с двумя джоинами и подсчетом количества комментариев для каждой страницы — может быть и 5 секунд. Особенно, если 2000 страниц и 30000 комментариев.
Короче, лучше много маленьких запросов, чем один большой.
Ага! Сделал, реально 4 строчки и работает шустро. Спасибо.
Молодец.
Ну а я добавил эту информацию в заметку.
Ну а я добавил эту информацию в заметку.
Спасибо! Познавательный мануал. Я с разбегу решил доп таблицу сделать под это дело и встрял на времени…
создал тип datetime но зараза туда только дата залетает…
время (часы, минуты, секунды) почему то все время по нулям присваивает…
ЧЯСНТ?
создал тип datetime но зараза туда только дата залетает…
время (часы, минуты, секунды) почему то все время по нулям присваивает…
ЧЯСНТ?
Вставляешь-то как, через xPDO или SQL?
Если xPDO — то покажи схему таблицы, если SQL — то запрос.
Если xPDO — то покажи схему таблицы, если SQL — то запрос.
xPDO, я уже понял где лоханулся… схему изначально генерировал с типом DATE, а потом просто в базе тип менял. Ну естественно он мне только дату и проставлял…
схема
Теперь хочу присоединить таблицу к сниппету pdoUsers, делаю
<?xml version="1.0" encoding="UTF-8"?>
<model package="Users" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" version="1.1">
<object class="UsersActivityMy" table="users_activity_my" extends="xPDOObject">
<field key="id" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" index="pk" />
<field key="registered" dbtype="datetime" phptype="datetime" null="true" />
<field key="lastactivity" dbtype="datetime" phptype="datetime" null="true" />
<index alias="PRIMARY" name="PRIMARY" primary="true" unique="true" type="BTREE" >
<column key="id" length="" collation="A" null="false" />
</index>
</object>
</model>
плагин<?php
switch ($modx->event->name) {
case 'OnUserSave':
// Сохраняем дату создания нового пользователя
if ($user && $mode == 'new') {
if ($profile = $user->getOne('Profile')) {
$id = $profile->get('id');
//$modx->log(1, "id: '{$id}'");
$item = $modx->newObject('UsersActivityMy');
$item->set('id',$id);
$item->set('registered',date('Y-m-d H:i:s'));
$item->save();
$profile->save();
}
}
break;
case 'OnLoadWebDocument':
// Сохраняем дату открытия любой страницы сайта, если пользователь авторизован
if ($modx->user->isAuthenticated($modx->context->key)) {
// Здесь мы работаем с текущим пользователем - у него профиль уже загружен
$id = $modx->user->id;
$item = $modx->getObject('UsersActivityMy',$id);
$item->set('lastactivity',date('Y-m-d H:i:s'));
$item->save();
}
break;
}
данные в базу записываются, все нормально.Теперь хочу присоединить таблицу к сниппету pdoUsers, делаю
&loadModels=`Users`
и в ответ получаю ругань [2014-04-08 09:46:27] (ERROR @ /index.php) Path specified for package users is not a valid or accessible directory: /var/www/sitename/www/core/components/users/model/
в extension_packages прописано
{"Users":{"path":"[[++core_path]]components/Users/model/"}}
Что еще не хватает?
думал косяк из за буквы заглавной, переименовал в usersactivity, один фиг та же ошибка.
[2014-04-08 11:14:03] (ERROR @ /index.php) Path specified for package usersactivitymy is not a valid or accessible directory: /var/www/sitename/www/core/components/usersactivitymy/model/
pdoTools предполагает, что ты пытаешься загрузить модель стандартного компонента.
Если указываешь
Если же ты прописываешь загрузку модели в системную настройку extension_packages, то loadModels вообще указывать не нужно — должно работать сразу.
Если указываешь
&loadModels=`users`
Это значит, что файлы модели должны лежать в/core/components/users/model/users/Пример.
Если же ты прописываешь загрузку модели в системную настройку extension_packages, то loadModels вообще указывать не нужно — должно работать сразу.
Там и лежат… Просто своим сниппетом данные выводятся. Но я никак теперь не допру как к выводу pdoUsers подключить эту свою таблицу.
p.s. То есть мне не нужно &loadModels=`users`?
а достаточно
а достаточно
[[!pdoPage?
&element=`pdoUsers`
&tpl=`@INLINE [[+username]] - [[+registered]] - [[+lastactivity]]`
&leftJoin=`чего то там и тд`
]]
Да. Если модель в памяти — ее не надо загружать отдельно.
Например таблицы Tickets и MS2 присоединяются свободно и без loadModels.
Например таблицы Tickets и MS2 присоединяются свободно и без loadModels.
почему то &showLog=`1` не пашет в pdoUsers
Ты поди авторизован в web и у юзера нет контекста mgr.
В любом случае, ошибки pdoTools пишутся в системный журнал.
В любом случае, ошибки pdoTools пишутся в системный журнал.
аха… ты прав!
теперь осталось разобраться с лексиконом запроса и дело в шляпе!)
Я ж все обычно тупо копирую с твоих примеров вот эти лефтджойны и селекты..., а че это такое — не вникал.
теперь осталось разобраться с лексиконом запроса и дело в шляпе!)
Я ж все обычно тупо копирую с твоих примеров вот эти лефтджойны и селекты..., а че это такое — не вникал.
вот так заработало, но я один фиг до конца не понял что к чему…
[[!pdoUsers?
&sortby=`modUser.id`
&tpl=`@INLINE [[+username]] - [[+registered]] - [[+lastactivity]]`
&showLog=`1`
&leftJoin=`{"UsersActivityMy":{"class":"UsersActivityMy","alias":"UsersActivityMy","on":"UsersActivityMy.id = modUser.id"}}`
&select=`{"UsersActivityMy":"`UsersActivityMy`.`registered`,`UsersActivityMy`.`lastactivity`"}`
]]
тупо написал, а оно взяло и заработало…
Владимир, как доделаете — напишите публикацию то по расширению modUser ;-)
Да что тут еще писать то? все что есть — выше тут же… Я никакой цели не преследовал, кроме как познавательной. Василий написал что сортировка только по собственной таблице — ну я и попробовал…
Что не ясно спрашивайте, я со своей стороны как малограмотный могу на пальцах обьяснить.)
Что не ясно спрашивайте, я со своей стороны как малограмотный могу на пальцах обьяснить.)
А вы просто таблицу создали, сам объект modUser не расширяли? Тогда да, ничего сложного…
так да… таблицу создал и все…
Молодец!
А у меня пришла в голову мысль, что пора сделать такую таблицу для юзеров в Tickets по умолчанию. Хотя бы активность и дату регистрации фиксировать.
Как будет время — попробую добавить.
А у меня пришла в голову мысль, что пора сделать такую таблицу для юзеров в Tickets по умолчанию. Хотя бы активность и дату регистрации фиксировать.
Как будет время — попробую добавить.
Делаю вывод пользователей. Выводить нужно только тех, у которых в Дополнительном поле профиля, некое значение.
Как мне его вывести в данном куске кода?
Как мне его вывести в данном куске кода?
$q->where(array(
'Profile.state' => "$search",
// Здесь нужно добавить условие для дополнительного поля
));
Искал удобный готовый вариант для вывода какого-нибудь extended-поля в произвольном месте сайта, и в итоге сам написал его.
Для удобного вывода extended-полей пользователя сделал себе сниппет extended в качестве фильтра.
Пример вызова:
Код сниппета extended:
Данный вызов для текущего пользователя, но можно смело подставлять id любого пользователя.
Может кому-нибудь пригодиться.
Для удобного вывода extended-полей пользователя сделал себе сниппет extended в качестве фильтра.
Пример вызова:
[[!+modx.user.id:extended=`companyname`]]
Код сниппета extended:
<?php
$user = $modx->getObject('modUser', $input);
$profile = $user->getOne('Profile');
$extended = $profile->get('extended');
return $extended[$options];
Данный вызов для текущего пользователя, но можно смело подставлять id любого пользователя.
Может кому-нибудь пригодиться.
При таком подходе если значение extended не заполнено, то выводится id пользователя.
Решил это следующим образом в сниппете:
Решил это следующим образом в сниппете:
<?php
$user = $modx->getObject('modUser', $input);
$profile = $user->getOne('Profile');
$extended = $profile->get('extended');
if($extended[$options]) {
return $extended[$options];
}
else {
return ' ';
}
Не знаю, может есть и более красивое решение, чем подсовывать пробел на выводе…
Ошибки и пустой экран, если пользователь неавторизован. Добавил проверку в сниппет:
<?php
$user = $modx->getObject('modUser', $input);
if($user) {
$profile = $user->getOne('Profile');
$extended = $profile->get('extended');
}
if($extended[$options]) {
return $extended[$options];
}
else {
return ' ';
}
Спасибо, выручил )
Приветствую. Было бы совсем здорово еще добавить новые поля в табличку на странице со всеми пользователями, с возможностью поиска и сортировки по ним.
Ошибся топиком, прошу прощения.
Подскажите, как передать extended поля в плагин при регистрации пользователя. Мне нужно при регистрации пользователя передать дополнительные значения из формы. Регистрация осуществляется через [[Login]]
Разобрался.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.