Пользователь
0,0
рейтинг
17 ноября 2011 в 12:48

Разработка → Личные сообщения в MODx Revolution из песочницы

MODX*
Задумали мы с другом один сервис, решили реализовать на MODx Revolution. Причины такого решения, равно как и сам проект лежат далеко за рамками этой статьи, возможно (читай обязательно) я напишу об этом позже. Сегодня я хочу поведать, как решалась конкретная задача.

Итак, необходимо реализовать “социальный” элемент в виде личных сообщений юзеров. Поиски готовых дополнений для MODx ничего толкового не дали, как и гугление на эту тему. Правда, некие проблески все-таки были, но явно не в том направлении. Ну совсем не хотелось использовать ресурсы (которые документы) не по назначению. И тут я обратил внимание на то, что в самом MODx, что называется “из коробки”, уже реализована система сообщений, с одним маленьким “но”: пользоваться ими можно только в админке, куда пускать юзеров вообще не предполагается. Даже никаких намеков на сниппеты для использования во фронтэнде. Тут-то я и решил копнуть глубже.

1. Что сделано до нас


Итак, что мы имеем. База данных MODx содержит таблицу modx_user_messages и соответствующий xPDO-класс modUserMessage. В принципе, ничто не мешает нам их использовать для нашей системы сообщений, что мы и сделаем.

Для доступа к классу используются методы getObject, getCollection, newObject и другие, в зависимости от того, что нам нужно сделать.

Да, и пару слов о конфиденциальности сообщений, все-таки они личные и кроме адресата не должны быть видны никому. Поэтому сначала придется настроить на нашем сайте авторизацию (в репозитории MODx есть замечательный пакет Login, да и информацию по этой теме найти несложно).

UPD. Не успел я опубликовать статью, как эту тему сегодня же осветили

2. Учимся читать сообщения


Пойдем от простого к сложному. Предположим, у нас есть система авторизации и юзеры могут регистрироваться и логиниться в систему. Хорошо, давайте сделаем страницу inbox, на которую будут выводиться входящие сообщения для конкретного пользователя.

Создаем новый ресурс и назначаем ему права доступа только для зарегистрированных пользователей, чтобы незалогиненные “прохожие” даже не подозревали об этой странице. Я опять же не буду подробно останавливаться на этом моменте, ведь статья не об этом.

В MODx в любом чанке или ресурсе доступен плейсхолдер [[+modx.user.id]], который вернет id текущего залогиненного пользователя. Для “прохожих” он вернет ”0”. Его-то мы и будем использовать в качестве адресата (если кто-либо из хабрасообщества знает более надежный способ определения текущего пользователя, с удовольствием перейму его опыт).

Итак, в нашем новом ресурсе разместим вызов сниппета, который мы сейчас напишем:

[[!msg-inbox? &userId=`[[+modx.user.id]]`]]


Обратите внимание, мы передаем в качестве параметра userId id текущего пользователя. Теперь создадим сниппет с названием msg-inbox и добавим в него следующий код:

<?php

$output = '';

$outputSeparator = isset($outputSeparator) ? $outputSeparator : "\n";

$userId = isset($userId) ? $userId : 0;

$limit = isset($limit) ? (integer) $limit : 10;

$offset = isset($offset) ? (integer) $offset : 0;

$tpl = isset($tpl) ? $tpl : 'inbox_tpl';

if ($userId == 0) {

 return;

}

$c = $modx->newQuery('modUserMessage');

$c->where(array(

    'recipient' => $userId

   

));

$c->sortby('date_sent','DESC');

$c->limit($offset,$limit);

$messages = $modx->getCollection('modUserMessage',$c);

foreach ($messages as $msg){

   $msgarray = $msg->toArray();

     $output .= $modx->getChunk($tpl, $msgarray) . $outputSeparator;

     $msg->set('read',1);

     $msg->save();

}

return $output;



Как видите, ничего особо выдающегося. Здесь мы вначале принимаем параметры, если таковые будут указаны, или указываем их значения по умолчанию. Потом мы на всякий случай еще раз проверяем id пользователя (вдруг кто-то шаловливыми ручками в ACL забрался), если 0, прекращаем выполнение сниппета. Кстати, при вызове сниппета всегда нужно указывать userId, иначе он по умолчанию принимает значение 0 и сниппет прекращает работу на этом шаге.

Далее мы готовим xPDO к запросу в БД. Нам нужны сообщения, у который хадресат имеет id равный userId. Также мы передаем другие параметры, которые нам понадобятся при постраничном выводе сообщений — $limit и $offset. Ну и сортируем в порядке убывания даты. Строка “$messages = $modx->getCollection('modUserMessage',$c);” делает непосредственно выборку из modUserMessage по критерию $c. После чего проходим по полученному массиву и используем чанк вместо шаблона. Так как при отображении мы по сути читаем сообщения, ставим им статус read равным “1”, т.е. “прочитано”.

Теперь надо создать шаблон для вывода сообщений. Для этого создадим новый чанк с именем “inbox_tpl” и добавим в него следующее:

<div class=”inbox-message [[+read:isequalto=`0`:then=`unread`]]”>

<p><b>От:</b> [[+sender:userinfo=`username`]] </p>

 <p><b>Дата:</b> [[+date_sent]] </p>

 <p><b>Тема:</b> [[+subject]] </p>

 <p><b>Сообщение:</b>

 [[+message]]</p>

</div>



В принципе, HTML-разметка может быть совершенно любой, на ваш вкус. Поэтому я даже не указываю здесь стили. Единственное, на что хотелось бы обратить внимание, это индикация непрочитанных сообщений. Я использовал для этого класс, который назначается контейнеру сообщения, если у него значение read равно 0: class=”inbox-message[[+read:isequalto=`0`:then=` unread`]]”. В противном случае класс unread просто не добавится. Таким образом, непрочитанные сообщения можно “подсветить” с помощью CSS. Если хотите, можно выводить текст “не прочитано” или соответствующую иконку.

Теперь протестируем. Регистрируем нового пользователя и логинимся во фронтэнде. Заходим на страницу inbox, и естественно, ничего не видим, так как никто нам пока не написал. Что ж, давайте отправим тестовое сообщение. Идем в админку, меню “Пользователи” -> “Сообщения”. Жмем кнопку “Новое сообщение”, выбираем пользователя, заполняем поля “Тема” и “Сообщения” и отправляем. Теперь вернемся на страницу inbox, и вуаля — перед нами новое непрочитанное сообщение от пользователя admin!

3. Отправляем сообщения


Для полноценного общения недостаточно чтения сообщений, их нужно как-то отправлять. Что ж, добавим на нашу страницу форму отправки сообщения. Откроем наш ресурс inbox и добавим форму:

<form action="[[*id]]" method="post">

<label>Кому </label><input type="text" name="to" value=""/>

          <label>Тема </label><input type="text" name="subj" value=""/>

             <label>Сообщение </label><textarea name="msg"></textarea>             

             

             <input type="submit" name="send" value="Отправить"/>

           </form>



Опять же все на примитивном уровне. Обрабатывать форму будем с помощью сниппета FormIt. На мой взгляд, удобнейший инструмент обработки форм. Для обработки данных FormIt может вызывать цепочку сниппетов — “хуков” (hooks). Хуки могут выполнять самые различные действия — защиту от спама, валидацию форм и другие. Если какой-либо хук забракует форму (например, при валидации), он вернет false и следующие за ним в цепочке хуки не выполнятся. Плюс можно передать сообщение об ошибке.

Для отправки сообщения мы напишем свой хук и скормим его FormIt. Для начала добавим в ресурс inbox вызов FormIt. Рекомендуется располагать его как можно выше на странице, то есть мы разместим его до формы и вызова сниппета msg-inbox:

[[!FormIt? &hooks=`msg-send` &submitVar=`send`]]



Обратите внимание на второй параметр submitVar. В нем мы указываем name кнопки “Отправить”, так FormIt будет точно знать, какую форму на странице и как обрабатывать. Излишне говорить, что форм на странице может быть несколько.

Теперь создадим новый сниппет с именем msg-send и добавим в него следующее:

<?php

$userid = isset($userId) ? $userId: 0;

if ($userid == 0) {

 return false; // анонимусы не пройдут

};  

$to = $hook->getValue('to');

$message = $hook->getValue('msg');

$subj = $hook->getValue('subj');

$msg = $modx->newObject('modUserMessage');

$msg->fromArray(array(

   'sender' => $userid,

     'recipient' => $to,

     'message' => $message,

     'subject' => $subj,

     'read' => 0,

     'private' => 1,

     'date_sent'=> strftime('%Y-%m-%d %T'),

));

$msg->save();

return true;



Здесь мы снова сначала проверяем на анонимность, потом забираем данные из формы. FormIt хранит их в объекте $hook. Получить их можно с помощью метода $hook -> getValue(), а метод $hook -> getValues() вернет массив со всеми данными формы. Далее мы создаем новый объект modUserMessage и заполняем его свойства данными. Мы также добавляем дату отправки сообщения и тип — private=1.

Сохраняем, идем проверять. На данном этапе в поле “Кому” нужно писать id получателя. Попробуем отправить сообщение самому себе. Если вы залогинены во фронтэнде как админ, то id получателя будет равен 1. Заполняем остальные поля и жмем “отправить”. Если все сделали правильно, наше сообщение появится на нашей же странице. Теперь можно попробовать нарегистрировать несколько юзеров и попробовать кидаться сообщениями друг другу.

Заключение


Я намеренно не стал включать в статью определение пользователя по имени или выбор адресата из списка — все это зависит непосредственно от ваших задач и пожеланий. К тому же я оставляю вам большой простор для творчества, да и чтобы бездумной копипасты было поменьше. А насчет имен пользователей даю подсказочку — юзеры в MODx по сути такие же объекты, только класс их называется modUser.

Эта статья не претендует на полноценное решение, ведь здесь нет ни управления сообщениями, ни сортировки, и прочего. Я всего лишь показал, что расширять функционал MODx кастомными сниппетами не так уж и сложно, как кажется на первый взгляд.

Удачи!

P.S. Благодарю сами знаете за что!
@Goobs
карма
3,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (12)

  • 0
    Вопрос. А получается, что можно самому себе отправить сообщение? Проверка на ID пользователя (есть ли такой пользователь) в данных сниппетах нету. Спасибо за топик.
    • 0
      Ну я всего лишь объяснил принцип, как это делается. А добавить условие if ($user_id != $to) ... — дело техники. Согласен, по-хорошему надо попытаться сделать getObject('modUser', $to).
      Если вернулся null, облом. Кстати, оттуда же можно подтягивать юзернейм.
      Прежде всего я это писал для своей системы, а там юзер заведомо существует, иначе ты даже не увидишь формы отправки сообщения. И id адресата подставляется автоматом.
  • 0
    И ещё, переместите топик в блог MODx :)
    • 0
      Могу я попросить более опытного товарища помочь с переносом?
      • 0
        Я, как и вы, тут только второй день :) Сначала вступите в блог habrahabr.ru/blogs/modx/, затем отредактируйте топик (В какой блог публикуем? — MODx). Вот такие вот дела.
        • 0
          спасибо, разобрался! Топик уже там.
  • 0
    Зачем параметр для сниппета [[+modx.user.id]]?
    Во-первых, не надо лишний раз нагружать парсер MODx, во-вторых, такие параметры вообще НЕЛЬЗЯ создавать, ибо ничего не мешает хитроумному пользователю передать в него сторонний параметр. Надо на уровне PHP использовать $modx->user->id без каких-либо изменений.
    • +1
      Спасибо, учту. Вот только как пользователь передаст туда параметр? Ведь парсер как раз и работает на уровне PHP?
      • +1
        Так пользователь и не должен ничего передавать. При обращении к сайту $modx производит инициализацию пользователя.
        $userId = isset($userId) ? $userId : 0;
        $userId = $modx->getAuthenticatedUser()->get('id');


        А правильней вообще проверять есть ли авторизованный пользователь, чтобы не вываливалось ошибок, типа объекта нет
        if(!$user = $modx->getAuthenticatedUser()){
        return false;
        }
        $userId = $user->get('id');


        И нельзя напрямую обращаться к $modx->user;
        Во-первых, $modx->user существует всегда (он автоматом его создает, даже если пользователя нет, просто дефолтовый объект с id 0);
        Во-вторых, $modx->user легко подменить в любом блоке кода $modx->user = $modx->newObject(), getObject(), ->set('id')= $id и т.п.
        Тупо в целях безопасности
        • 0
          Категорически согласен.
          В свое оправдание скажу лишь, что в моем проекте пользователь-адресат заведомо существует. И форма отправки ему сообщения доступна только зарегистрированным, закрыта от посторонних ACL. Но тем не менее, лучше перебдеть))
          • +1
            Это не перебдеть. Это боле-менее правильный метод программирования. Это же не чистый PHP, а фреймворк, и надо писать с учетом его стандартов и специфики. Тем более вы делитесь своим кодом. Кто-то воткнет его на проект посерьезней, а там дырка…

            У меня сейчас проект, в котором вообще не допустимы какие-то оплошности с пользователями. А проблема MODx-а в том, что $modx->user — публичный объект, и с ним делать можно что угодно. При чем бывает так, что и сам перегружаешь этого пользователя.
            Я во избежание таких конфликтов и случайностей вообще использую дополнительный класс (привожу несколько измененный пример).

            <?
            class User{
            static $modx;
            private static $user = false;
            private static $errors = array();

            public static function init(& $modx){
            self::$modx = $modx;
            self::$user = self::$modx->getAuthenticatedUser();
            }

            static public function get($param){
            if(!self::$user || !is_object(self::$user)){
            return false;
            }
            switch($param){
            // Обрубаем получение некоторых данных
            case 'password':
            case 'hash_class':
            case 'salt':
            return false;
            break;
            // Получаем тип тользователя
            case 'type':
            return self::getUserType();
            break;
            // Получаем кол-во бонусов
            case 'bonuses':
            return self::countBonuses();
            break;
            // Получаем кол-во дней
            case 'subscribe_time':
            return self::countSubscribe_time();
            break;
            default: return self::$user->get($param);
            }

            } .................


            В момент обращения к сайту происходит инициализация данного класса с передачей в него инициализированного пользователя. А так как переменная $user приватная, исключается вероятность ее перегрузки.
            • 0
              Интересно, как говорится, хозяйке на заметку. Я, пожалуй, наберусь наглости и реквестирую статью на эту тему!

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.