Вывод тикетов пользователя

Сегодня задали вопрос: -а как выводить тикеты определённого пользователя, типа как персональный блог?

Ну, тут минимум 2 варианта: 1. Создать отдельный раздел для юзера. 2. Выделять тикеты юзера из общей кучи.

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

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

Поэтому, мы пойдем вторым путём - выводом тикетов юзера по уникальному url. Я предлагаю site_name/section_name/user_name/.

Первым делом, нам нужно подготовить **getPage** к фильтрации по юзеру. Пиишем сниппет-обёртку **getPageWrapper**:

if (!empty($_REQUEST['username'])) {
    $username = trim($_REQUEST['username']);
    if ($user = $modx->getObject('modUser', array('username' => $username))) {
        $uid = $user->get('id');
    }
    else {$uid = 0;}
    $scriptProperties['where'] = '{"createdby":'.$uid.'}';
}

return $modx->runSnippet('getPage', $scriptProperties);

В разделе всех тикетов заменяем вызов getPage на getPageWrapper. Теперь, если в массиве $_REQUEST передать username существующего юзера - то выведутся только его тикеты.

Уже можно обращаться к странице site_name/section_name/?username=k07n. Обратите внимание, мы заодно нашли решение распространённого вопроса - а как передать через $_GET данные в getPage? Ничего сложного.

В принципе, всё. Можно делать ссылки на блог юзера и при переходе туда, будут выводиться только его статьи. Но это не красиво, мы ведь хотим дружественные url, да?

Эта задача также решается довольно просто. Пишем плагин redirectToUser:


if ($modx->event->name == 'OnPageNotFound') {
    $request = $_REQUEST['q'].'/';
    preg_match('/(.*?)\/(.*?)(?:\/|\.html)/', $request, $matches); // выбираем имя секции и юзера
    //echo '<pre>';print_r($matches);die; // Можно активировать для отладке
    if (!empty($matches[1]) && !empty($matches[2])) {
        $q = array(
            'alias' => $matches[1]
            ,'class_key' => 'TicketsSection'
            ,'deleted' => 0
            ,'published' => 1
        );
        // Ищем секцию тикетов и выводим её вместе с параметром фильтрации по юзеру
        if ($section = $modx->getObject('TicketsSection', $q)) {
            $section_id = $section->get('id');
            $_REQUEST['username'] = $matches[2];
            
            $modx->sendForward($section_id);
        }
    }
}

Плагин сработает, если не найдена страница по указанному адресу, затем попытается выделить из адреса запроса имя секции тикетов и юзера. Если секцию тикетов существует, то плагин загрузит ее, поместив в $_REQUEST имя юзера, чтобы обёртка getPage добавила его в условие выборки.

Если где-то в этом процессе произойдет сбой\несоответствие, то работа плагина остановится и вы получите стандартную страницу 404, которая и должна выводиться в таком случае. Конечно, вы можете добавить собственные дополнительные услоия\проверки и расширить мой пример как угодно.

Задача решена. С этого момента у меня в разделе вопросов можно кликать на имя юзера и получать все его тикеты. Например: http://bezumkin.ru/user/topics/bezumkin/.

Данный пример является класическим роутером дружественных url. Приблительно так же работают и friendly urls самого MODX.

Комментарии (49)
k07nAndrei Kilin
18.12.2012 11:22

k07nAndrei Kilin
18.12.2012 11:46

Дзен наступит, если еще сделать ссылки на сами тикеты в такой форме:

http://bezumkin.ru/help/Hanami/467

.

bezumkinВасилий Наумкин
18.12.2012 13:09

Вы уже знаете всё нужное для этого.

k07nAndrei Kilin
18.12.2012 13:34

дада, это не вопрос был, а предложение по функционалу сюда.

Бохонов Андрей
25.10.2013 20:30

Большое спасибо за статью! Появился вопрос, если добавить случайные символы в конец url то сервер отвечает кодом 200 вместо вывода 404 ошибки

Например ссылка на комментарии bezumkin-а - код 200

http://bezumkin.ru/user/comments/bezumkin/

добавили к ссылке 123 - так же код 200 (почему не 404 ошибка?)

http://bezumkin.ru/user/comments/bezumkin/123

Может кто-нибудь сможет подсказать как это исправить?

k07nAndrei Kilin
28.10.2013 08:06

404 ошибка в том и том случае, просто она плагином обрабатывается. В обоих случаях урл подпадает под условие проверок первых 3-х секций. Если ты хочешь 404, когда в конце лишнее написано - проверяй 4-ую секцию на отсутсвие знаков. Если в 4-ой пусто тогда форвард, если нет то форвард надо пропустить и будет 404

unman64Даниил
13.01.2014 04:27

Что то не хотят у меня урлы работать(( Сниппет getPageWrapper создал, плагин redirectToUser тоже создал (именно плагин же должен быть, не сниппет, верно?), на странице, на которой предполагается вывод, вызываю так:

[[!getPageWrapper?  &element=`getTickets` &parents=`8` &includeContent=`1`]]

Ссылка с именем пользователя на эту страничку (ее id у меня 69) следующая

<a href="[[~69]]/[[+username]]">[[+fullname]]</a>

В чем может быть ошибка?

unman64Даниил
13.01.2014 04:28

Если пишу через обычный GET имя пользователя вручную в адресной строке - работает.

bezumkinВасилий Наумкин
13.01.2014 04:55

У меня здесь старый код, в котором показан общий принцип.

Попробуй вот такой плагин:


if ($modx->event->name == 'OnPageNotFound') {
    $q = trim($_REQUEST['q']);
    $matches = explode('/', $q);
    $count = count($matches);

    if ($count < 3) {return;}
    // Редирект на тикеты юзера
    elseif ($matches[0] == 'user' && $matches[1] == 'topics' && $user = $modx->getObject('modUser', array('username' => @$matches[2]))) {
        $_REQUEST['uid'] = $user->id;
        $modx->setPlaceholder('username', $user->username);
        $modx->sendForward(529); // id страницы с getPageWrapper
    }
    // Редирект на комменты юзера
    elseif ($matches[0] == 'user' && $matches[1] == 'comments' && $user = $modx->getObject('modUser', array('username' => @$matches[2]))) {
        $_REQUEST['uid'] = $user->id;
        $modx->setPlaceholder('username', $user->username);
        $modx->sendForward(530); // id страницы с getPageWrapper
    }
}
unman64Даниил
13.01.2014 05:04

Увы и ах, не хочет даже так срабатывать... А в настройках плагина разве не нужно выставлять никаких системных событий?

bezumkinВасилий Наумкин
13.01.2014 05:05

Естественно нужно, в первой строке же проверка OnPageNotFound.

unman64Даниил
13.01.2014 05:31

Все, заработало! Ура!))))) Спасибо большущее! На самом деле это я протупил и не посмотрел в код детально изначально, у меня адрес страницы с getPageWrapper был произвольный, а надо было чтоб он был адрес-сайта/user/topics (ну либо адрес-сайта/user/comments - во втором случае) в соответствие с условием

$matches[0] == 'user' && $matches[1] == 'topics' && $user = $modx->getObject('modUser', array('username' => @$matches[2]))
bezumkinВасилий Наумкин
13.01.2014 06:34

На здоровье!

susliktНиколай
07.02.2014 04:37

А как правильно должен выглядеть сниппет getPageWrapper,чтобы работал с этим плагином? У меня заработал только когда добавил в плагин

$_REQUEST['username'] = $matches[2];

но это как-то странно по моему

inetloverАлександр Наумов
13.01.2014 11:04

Василий, объясни, пожалуйста, почему у тебя не срабатывает запрос?

http://bezumkin.ru/user/topics/?username=bezumkin
bezumkinВасилий Наумкин
13.01.2014 11:07

Сниппет реагирует на uid - bezumkin.ru/user/topics/?uid=2

А плагин переводит username в uid.

inetloverАлександр Наумов
13.01.2014 12:03

Получается у тебя в добавок по ссылке http://bezumkin.ru/user/topics/ работает какой-то сниппет, который выводит надпись: Подходящих записей не найдено.?

bezumkinВасилий Наумкин
13.01.2014 12:39

Обычный фильтр вывода default, при вызове сниппета.

inetloverАлександр Наумов
13.01.2014 12:43

Спасибо, понял!

Чикин Артур
13.01.2014 12:33

Василий, скрести ссылки, что бы получилось вот так:

bezumkinВасилий Наумкин
13.01.2014 12:39

Один раздел доступен только для юзеров, а другой для всех.

Их нельзя скрещивать.

Чикин Артур
13.01.2014 12:54

Блин было бы гораздо удобнее. Так сказать появилась бы иллюзия единого кабинета.

unman64Даниил
14.01.2014 08:02

Ну да, пусть все видят две вкладки, а юзеры четыре.

bezumkinВасилий Наумкин
14.01.2014 09:06

Это разные разделы сайта: личный кабинет и публичный профиль. В личный кабинет без авторизации даже зайти нельзя, в профиль индексируют поисковики.

Вы если не понимаете - не нужно давать советов. Я вроде не дурной, осознаю, что делаю.

unman64Даниил
14.01.2014 09:13

После озвучивания конкретных названий разделов стал понимать логику твоих мыслей. Да, согласен - объединять нельзя.

unman64Даниил
14.01.2014 08:00

Что-то не пойму как должен использоваться TicketComments через getPageWrapper... Комментарии это же по сути не ресурсы. Можно пример такого вызова?

inetloverАлександр Наумов
14.01.2014 10:30
[[!getPageWrapper?
    &element=`pdoResources`
    &class=`TicketComment`
    &sortby=``
    &sortdir=`DESC`
    &limit=`10`
    &tpl=`tpl.Tickets.comments`
]]

В чанке tpl.Tickets.comments можно выводить

Текст комментария - [[+text]], Полное имя - [[+name]], И дату создания - [[+createdon]]

unman64Даниил
14.01.2014 11:01

Спасибо, Александр! Я уже пробовал pdoResources подставлять, но что-то видимо все-таки не так делал, сейчас попробую этот вызов.

inetloverАлександр Наумов
14.01.2014 11:27

Я тоже кучу разных вариантов перепробовал и заработало, только тогда, когда поставил &sortby=``, так и не пойму почему без этого параметра не работает.

bezumkinВасилий Наумкин
14.01.2014 12:55

Потому что иначе сортировка по первичному ключу, а он у комментов на два поля, то есть массив.

Через это ошибка в запросе pdo. Смотрите логи, это полезно.

inetloverАлександр Наумов
14.01.2014 13:34

Спасибо, буду знать!

unman64Даниил
14.01.2014 23:59

При таком выводе интересно несколько моментов, да [[+text]], [[+name]] и [[+createdon]] в чанке выводят все как положено. Фото пользователя из соцсети, через которую он залогинен также выводится без проблем, через конструкцию [[+createdby:userinfo=photo]]. А как вывести имя тикета, к которому относится комментарий и граватар пользователя в чанке tpl.Tickets.comments?

unman64Даниил
15.01.2014 03:55

И как еще можно вывести на одной странице общее число публикаций и комментариев? Для того раздела где находимся понятное дело [[+total]] выводит, а как для другого находясь в этом же разделе?? Я то конечно вывел, но по-моему это большущий костыль... На странице комментариев общее количество публикаций вот так:

[[!Profile? &user=`[[!+userid]]` &prefix=`user.`]]

[[!pdoResources:is=``:then=``:else=``?
    &class=`TicketComment`
    &where=`{"createdby":"[[!+userid]]"}`
    &sortby=``
    &sortdir=`DESC`
    &limit=`0`
    &totalVar=`totalcomments`
]]

[[+totalcomments]]

И, соответственно, на странице публикаций общее количество комментариев тем же способом, только используя getTickets:

[[!Profile? &user=`[[!+userid]]` &prefix=`user.`]]

[[!getTickets:is=``:then=``:else=``?
    &parents=`8`
    &user=`[[!+userid]]`
    &limit=`0`
]]

[[+total]]

Как бы это правильнее сделать, чтоб не запускать целый "завод", ради производства одной "зубочистки"?

bezumkinВасилий Наумкин
15.01.2014 04:11

Я написал свой сниппет:

<?php
if (empty($_REQUEST['uid'])) {return '';}
$uid = intval($_REQUEST['uid']);

// Limit by specified parents
if (!isset($depth)) {$depth = 10;}
if (!empty($parents)) {
    $pids = array_map('trim', explode(',', $parents));
    $parents = array();
    foreach ($pids as $v) {
        $parents = array_merge($parents, $modx->getChildIds($v, $depth, array('context_key' => $modx->context->key)));
    }
}

// Tickets
$where = array('createdby' => $uid, 'deleted' => 0, 'published' => 1, 'class_key' => 'Ticket', 'privateweb' => 0);
if (!empty($parents)) {$where['parent:IN'] = $parents;}
$q = $modx->newQuery('Ticket', $where);
$topics = $modx->getCount('Ticket', $q);

// Comments
$where = array('createdby' => $uid, 'deleted' => 0);
if (!empty($parents)) {$where['Ticket.parent:IN'] = $parents;}
$q = $modx->newQuery('TicketComment', $where);
$q->leftJoin('TicketThread','Thread','Thread.id = TicketComment.thread');
$q->leftJoin('Ticket','Ticket','Ticket.id = Thread.resource');
if (!$modx->hasPermission('ticket_view_private')) {
    $q->where('privateweb = 0');
}
$comments = $modx->getCount('TicketComment', $q);

// Placeholders
$modx->setPlaceholder('total.topics', "($topics)");
$modx->setPlaceholder('total.comments', "($comments)");

$pdo = $modx->getService('pdoFetch');
if ($user = $pdo->getObject('modUser', $uid)) {
    $profile = $pdo->getObject('modUserProfile', array('internalKey' => $uid));
    
    $modx->setPlaceholder('username', $user['username']);
    $modx->setPlaceholder('fullname', $profile['fullname']);
    $modx->setPlaceholder('avatar', 'http://www.gravatar.com/avatar/'.md5($profile['email']).'?s=64&d=mm');
}

Выставляет плейсхолдеры total.topics, total.comments, username, fullname и avatar.

unman64Даниил
15.01.2014 04:57

Огого как тут все круто оказывается) Спасибо, Василий, сейчас буду пробовать!

А аватар у меня кстать легко вот так вывелся:


[[!Profile? &user=`[[!+userid]]` &prefix=`user.`]]
[[+user.gravatar]]?s=64&d=[[++site_url]]/assets/img/system/no-photo200.jpg
unman64Даниил
15.01.2014 04:59

Твой сниппет кстати можно включить даже в стандартный набор Tickets, я думаю многим такой функционал был бы полез!

unman64Даниил
15.01.2014 03:15

Оказалось у меня getPageWrapper по какой-то причине не хотел воспринимать имя пользователя, использую теперь getPage без него. Для этого создаю в плагине redirectToUser еще один плейсхолдер, содержащий id юзера. Получается вот такой код:

if ($modx->event->name == 'OnPageNotFound') {
    $q = trim($_REQUEST['q']);
    $matches = explode('/', $q);
    $count = count($matches);

    if ($count < 3) {return;}
    // Редирект на тикеты юзера
    elseif ($matches[0] == 'форум' && $matches[1] == 'темы-пользователя' && $user = $modx->getObject('modUser', array('username' => @$matches[2]))) {
        $_REQUEST['uid'] = $user->id;
        $modx->setPlaceholder('username', $user->username);
        $modx->setPlaceholder('userid', $user->id);
        $modx->sendForward(69); // id страницы с getPageWrapper
    }
    // Редирект на комменты юзера
    elseif ($matches[0] == 'форум' && $matches[1] == 'комментарии-пользователя' && $user = $modx->getObject('modUser', array('username' => @$matches[2]))) {
        $_REQUEST['uid'] = $user->id;
        $modx->setPlaceholder('username', $user->username);
        $modx->setPlaceholder('userid', $user->id);
        $modx->sendForward(76); // id страницы с getPageWrapper
    }
}

А на страницах получаю все необходимые данные через:

[[!Profile? &user=`[[!+userid]]` &prefix=`user.`]]

Соответственно и в вызовы getPage потом подставляю.

Для тикетов:

[[!getPage:default=`Данный пользователь еще не создал ни одной темы`?  
    &element=`getTickets`
    &parents=`8`
    &includeContent=`1`
    &user=`[[!+userid]]`
]]

И для комментариев:

[[!getPage:default=`У этого пользователя еще нет комментариев`?
    &element=`pdoResources`
    &class=`TicketComment`
    &where=`{"createdby":"[[!+userid]]"}`
    &sortby=``
    &sortdir=`DESC`
    &limit=`10`
        &tpl=`tpl.Tickets.comments`
]]
unman64Даниил
16.01.2014 04:33

Не пойму, почему при моем вызове thread, parent, text и name попадают в выборку, а id не попадает (вроде в той же самой таблице находится)?

[[!getPage:default=`У этого пользователя еще нет комментариев`?
    &element=`pdoResources`
    &class=`TicketComment`
    &where=`{"createdby":"[[!+userid]]"}`
    &sortby=``
    &sortdir=`DESC`
    &limit=`10`
    &includeTVs=`id`
    &processTVs=`1`
    &tvPrefix=``
    &totalVar=`totalcomments`
    &tpl=`tpl.Tickets.comments`
]]
bezumkinВасилий Наумкин
16.01.2014 04:53
&includeTVs=`id`

Имя ТВ не должно совпадать с именем поля ресурса.

unman64Даниил
16.01.2014 05:00

А, да то я уже извращался, если вот так делаю тоже не выбирает:

[[!getPage:default=`У этого пользователя еще нет комментариев`?
    &element=`pdoResources`
    &class=`TicketComment`
    &where=`{"createdby":"[[!+userid]]"}`
    &sortby=``
    &sortdir=`DESC`
    &limit=`10`
    &tpl=`tpl.Tickets.comments`
]]
unman64Даниил
16.01.2014 06:49

Добавил параметр select в котором перечислил все нужные мне поля, в том числе и id, так попадает в выборку

&select=`id,name,text,thread,createdon,createdby`

Странно, что без select никак его не получается вытащить. Это как то связано с тем что у поля id в таблице AUTO_INCREMENT стоит? По крайней мере больше я никаких отличий этого поля от других не нашел...

unman64Даниил
16.01.2014 08:16

А чем мы можем получить информацию о тикете находясь на произвольной странице, но зная id тикета?

unman64Даниил
16.01.2014 17:25

Может быть можно для этих целей как-то использовать pdoField? Единственное не могу понять как переключить его логику работы с ресурсов на комментарии((

unman64Даниил
17.01.2014 11:59

Маленький модификатор threadid который из id тикета получает resource, т.е. на выходе получаем id ресурса, являющегося тикетом

if ($thread = $modx->getObject('TicketThread', array('id' => $input))) {
    echo $thread->get('resource');
}

Применяем в чанке tpl.Tickets.comments (о котором выше говорили) так

[[+thread:threadid]]

Соответственно отсюда можем уже и pagetitle тикета узнать, к которому относится комментарий и pagetitle деда ну и т.д.

unman64Даниил
17.01.2014 14:13

Сам задал вопрос, сам нашел ответ, поразительно как действует отсутствие внешних подсказок :DDD Вот только времени очень много лишнего тратится на понимание...

bezumkinВасилий Наумкин
17.01.2014 14:39

Зато потом экономится.

Иначе так и будешь постоянно ждать подсказок.

unman64Даниил
17.01.2014 14:53

Да я ж не против никогда учиться, у меня этот веб весь хоть и сидит в печенках уже за несколько лет, но узнавать что-то новое и осваивать всегда приятно и интересно... Тут просто ситуация другая, когда над тобой висит заказчик проекта и орет "быстрее-быстрее" и ты понимаешь, что да, надо бы действительно быстрее, т.к. обещал сайт за 3 недели сделать, а уже полтора месяца "пилишь" его и при этом тратишь 2 дня на то чтоб только понять как вытянуть этот самый pagetitle, вот это расстраивает... Но я не в коем случае никого не виню, тебе, Василий, вообще отдельная и пожизненная благодарность за то, чем мы пользуемся все, и со временем я представляю как проблематично у тебя, тут нас таких вопрошающих не одна сотня, а ты один. Эт я сам дурак что не могу чего-то быстро сообразить, матчасть учить мне больше надо

bezumkinВасилий Наумкин
17.01.2014 15:18

Ну так нужно оценивать свои силы и не обещать за 3 недели =)

unman64Даниил
17.01.2014 15:22

А если не обещать, то никто и не закажет, кушать то хочется ;)

ЕвгенийК
09.04.2022 03:35
Это хорошо, что такая возможность есть и может быть использована. А то тенденция, мания, что-то в по...
begoodco1
07.04.2022 05:49
Зарегистрировался чтобы выразить благодарность за доступное и подробное описание процесса. Была возм...
bezumkin
Василий Наумкин
18.03.2022 12:35
Авторизация есть из коробки, для входа в базовую админку. Можно установить через composer и собрать ...
bezumkin
Василий Наумкин
10.03.2022 12:08
Ну, я имел в виду, что по закону можно =) А в реальности с валютой очевидные проблемы.
Сергей Лелеко
04.03.2022 06:12
О как! не знал! спасибо
bezumkin
Василий Наумкин
01.03.2022 15:32
Я делал одного бота на botman/botman, но из-за своей универсальности конкретно с Телеграм на нём раб...
bezumkin
Василий Наумкин
25.02.2022 09:22
P.S. Кажется цитаты у тебя никак не стилизуются в комментариях... Спасибо, поправил!
Electrica
Михаил
08.02.2022 11:19
Работает!
Алексей
09.01.2019 10:55
Насыщенный год ) От души поздравляю с ДР! Счастья, успехов и семейного благополучия! Жаль лимит заме...
septa rose
28.05.2018 22:16
hmmm, keren abis