Вопрос/ответ с редактированием в отдельном модуле

Здравствуйте. Нужно реализовать следующее: неавторизованный пользователь на сайте оставляет отзыв и в админке, в отдельном модуле админ выбирает отправить его на публикацию или нет, а также при желании редактирует. Соответственно на странице отзывов выводятся одобренные отзывы.
Прошу подсказать модуль или решения, на базе которых можно самостоятельно осуществить подобный функционал. Если таковых нет, то сколько стоит решить мою задачу.
Сергей
15 октября 2014, 16:49
modx.pro
4
4 273
0

Комментарии: 13

Василий Наумкин
15 октября 2014, 22:42
0
Думаю, это можно сделать с помощью Tickets.

Только не спрашивай, как именно — читай сразу историю изменений с первой версии.
    Наумов Алексей
    16 октября 2014, 08:51
    0
    Я уже не на первом сайте это делаю через MIGx db, пару сниппетов, формы на Formit и хука к ней.

    В админке вопросы появляются в виде таблицы MIGx, как TV поле, там их можно удалять, публиковать, редактировать.

    Если интересно — напишу попродробнее.
      Сергей
      16 октября 2014, 12:06
      0
      Напишите, пожалуйста.
        Сергей
        17 октября 2014, 14:03
        0
        Алексей, если не сложно, прошу вас ответить по поводу
        MIGx. Спасибо.
          Наумов Алексей
          17 октября 2014, 15:25
          0
          Извиняюсь, забыл. Сейчас в общих чертах накидаю код с одного из сайтов.
        Наумов Алексей
        17 октября 2014, 15:46
        4
        +2
        Пример с MIGx db, в общих чертах, код взят с одного из сайтов кусками, и для вашего сайта его надо будет переработать.

        1. Настраиваем MIGx

        1.1. Создаем таблицы в БД и объекты
        — Ставим MIGx, как написано в документации к нему!
        Я назвал этот «полукомпонент» xqa, все имена/префиксы делал с этим названием…
        — Открываем MIGx в админке, и на первой вкладке создаем package xqa
        — берем эту xml
        <?xml version="1.0" encoding="UTF-8"?>
        <model package="xqa" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" version="1.1">
        	<object class="xQa" table="xqa" extends="xPDOSimpleObject" >
        	<field key="resource_id" dbtype="int" precision="11" phptype="integer" null="false" default="0" />
        	<field key="author" dbtype="varchar" precision="255" phptype="string" null="false" default="" />
        	<field key="author_email" dbtype="varchar" precision="255" phptype="string" null="false" default="" />
        	<field key="question" dbtype="text" phptype="string" />
        	<field key="answer" dbtype="text" phptype="string" />
        	<field key="notify" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        	<field key="published" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        	<field key="createdby" dbtype="int" precision="10" phptype="integer" null="false" default="0" />
        	<field key="createdon" dbtype="datetime" phptype="datetime" null="false" />
        	<field key="editedby" dbtype="int" precision="10" phptype="integer" null="false" default="0" />
        	<field key="editedon" dbtype="datetime" phptype="datetime" null="false" />
        	<field key="deleted" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        	<field key="deletedon" dbtype="datetime" phptype="datetime" null="false" />
        	<field key="deletedby" dbtype="int" precision="10" phptype="integer" null="false" default="0" />
        	<field key="publishedon" dbtype="datetime" phptype="datetime" null="false" />
        	<field key="publishedby" dbtype="int" precision="10" phptype="integer" null="false" default="0" />
        	<index alias="PRIMARY" name="PRIMARY" primary="true" unique="true">
        		<column key="id" collation="A" null="false" />
        	</index>
        	<aggregate alias="Resource" class="modResource" local="resource_id" foreign="id" cardinality="one" owner="foreign" />
        	</object>
        </model>
        и на вкладке xml schema ждем save, сохраняя ее.

        — теперь создаем таблицы в БД на вкладке create Tables и создаем классы на вкладке parse Schema.

        Таким образом мы получаем таблицу в БД и xpdo классы для работы с этой таблицей.

        Структура моей таблицы:
        resource_id - документ, к которому "привязан" вопрос
        author - имя автора
        author_email - его email
        question - текст вопроса
        answer - текст ответа
        notify - галочка, символизирующая что нужно отправить уведомление автору о том, что не его вопрос появился ответ
        
        ---- с колонками ниже MIGx работает САМ, т.е. мы в коде ничего с ними не делаем, они сами заполняются
        published
        createdby
        createdon
        editedby
        editedon
        deleted
        deletedon
        deletedby
        publishedon
        publishedby

        1.2 Создаем MIGx настройку.
        Идем в MIGx — вкладка MIGx (вторая вкладка с настройками)

        Делаем новую xqa и по ней правой кнопкой — Экспорт/Импорт, вставляем следующий код:
        {
          "formtabs":[
            {
              "MIGX_id":1,
              "caption":"\u0412\u043e\u043f\u0440\u043e\u0441-\u043e\u0442\u0432\u0435\u0442",
              "print_before_tabs":"0",
              "fields":[
                {
                  "MIGX_id":1,
                  "field":"author",
                  "caption":"\u0410\u0432\u0442\u043e\u0440",
                  "description":"",
                  "description_is_code":"0",
                  "inputTV":"",
                  "inputTVtype":"",
                  "validation":"",
                  "configs":"",
                  "sourceFrom":"config",
                  "sources":"[]",
                  "inputOptionValues":"",
                  "default":""
                },
                {
                  "MIGX_id":4,
                  "field":"author_email",
                  "caption":"E-mail",
                  "description":"",
                  "description_is_code":"0",
                  "inputTV":"",
                  "inputTVtype":"",
                  "validation":"",
                  "configs":"",
                  "sourceFrom":"config",
                  "sources":"[]",
                  "inputOptionValues":"",
                  "default":""
                },
                {
                  "MIGX_id":2,
                  "field":"question",
                  "caption":"\u0412\u043e\u043f\u0440\u043e\u0441",
                  "description":"",
                  "description_is_code":"0",
                  "inputTV":"",
                  "inputTVtype":"textarea",
                  "validation":"",
                  "configs":"",
                  "sourceFrom":"config",
                  "sources":"[]",
                  "inputOptionValues":"",
                  "default":""
                },
                {
                  "MIGX_id":3,
                  "field":"answer",
                  "caption":"\u041e\u0442\u0432\u0435\u0442",
                  "description":"",
                  "description_is_code":"0",
                  "inputTV":"",
                  "inputTVtype":"textarea",
                  "validation":"",
                  "configs":"",
                  "sourceFrom":"config",
                  "sources":"[]",
                  "inputOptionValues":"",
                  "default":""
                },
                {
                  "MIGX_id":6,
                  "field":"notify",
                  "caption":"\u0423\u0432\u0435\u0434\u043e\u043c\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0430 \u043e\u0431 \u043e\u0442\u0432\u0435\u0442\u0435?",
                  "description":"",
                  "description_is_code":"0",
                  "inputTV":"",
                  "inputTVtype":"listbox",
                  "validation":"",
                  "configs":"",
                  "sourceFrom":"config",
                  "sources":"[]",
                  "inputOptionValues":"\u041d\u0435\u0442==||\u0414\u0430==1",
                  "default":""
                },
                {
                  "MIGX_id":5,
                  "field":"published",
                  "caption":"\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d",
                  "description":"",
                  "description_is_code":"0",
                  "inputTV":"",
                  "inputTVtype":"listbox",
                  "validation":"",
                  "configs":"",
                  "sourceFrom":"config",
                  "sources":"[]",
                  "inputOptionValues":"\u041d\u0435\u0442==||\u0414\u0430==1",
                  "default":""
                }
              ]
            }
          ],
          "contextmenus":"update||publish||unpublish||recall_remove_delete",
          "actionbuttons":"addItem||toggletrash",
          "columnbuttons":"",
          "filters":"[]",
          "extended":{
            "migx_add":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441",
            "formcaption":"",
            "update_win_title":"\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441",
            "win_id":"xqa",
            "maxRecords":"",
            "addNewItemAt":"top",
            "multiple_formtabs":"",
            "extrahandlers":"",
            "packageName":"xqa",
            "classname":"xQa",
            "task":"",
            "getlistsort":"createdon",
            "getlistsortdir":"DESC",
            "use_custom_prefix":"0",
            "prefix":"",
            "grid":"",
            "gridload_mode":2,
            "check_resid":1,
            "check_resid_TV":"",
            "join_alias":"",
            "has_jointable":"yes",
            "getlistwhere":"",
            "joins":"",
            "cmpmaincaption":"",
            "cmptabcaption":"",
            "cmptabdescription":"",
            "cmptabcontroller":"",
            "winbuttons":"",
            "onsubmitsuccess":"",
            "submitparams":""
          },
          "columns":[
            {
              "MIGX_id":7,
              "header":"ID",
              "dataIndex":"id",
              "width":"",
              "sortable":"false",
              "show_in_grid":"0",
              "renderer":"",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":6,
              "header":"\u0414\u0430\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f",
              "dataIndex":"createdon",
              "width":"",
              "sortable":"false",
              "show_in_grid":1,
              "renderer":"this.renderDate",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":1,
              "header":"\u0410\u0432\u0442\u043e\u0440",
              "dataIndex":"author",
              "width":"",
              "sortable":"false",
              "show_in_grid":1,
              "renderer":"",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":8,
              "header":"E-mail",
              "dataIndex":"author_email",
              "width":"",
              "sortable":"false",
              "show_in_grid":1,
              "renderer":"",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":2,
              "header":"\u0412\u043e\u043f\u0440\u043e\u0441",
              "dataIndex":"question",
              "width":"",
              "sortable":"false",
              "show_in_grid":1,
              "renderer":"",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":3,
              "header":"\u041e\u0442\u0432\u0435\u0442",
              "dataIndex":"answer",
              "width":"",
              "sortable":"false",
              "show_in_grid":1,
              "renderer":"",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":9,
              "header":"\u0423\u0432\u0435\u0434\u043e\u043c\u0438\u0442\u044c?",
              "dataIndex":"notify",
              "width":"",
              "sortable":"false",
              "show_in_grid":1,
              "renderer":"this.renderCrossTick",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":4,
              "header":"\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d",
              "dataIndex":"published",
              "width":"",
              "sortable":true,
              "show_in_grid":1,
              "renderer":"this.renderCrossTick",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            },
            {
              "MIGX_id":5,
              "header":"\u0423\u0434\u0430\u043b\u0435\u043d",
              "dataIndex":"deleted",
              "width":"",
              "sortable":"false",
              "show_in_grid":"0",
              "renderer":"",
              "clickaction":"",
              "selectorconfig":"",
              "renderchunktpl":"",
              "renderoptions":"[]"
            }
          ]
        }
        Сохраняем и ради интереса жмем редактировать, смотрим какие поля и колонки у нас есть.

        2. Делаем стандартную форму на Formit, в которой должны быть поля author, author_email, question. Как вы ее реализуете — ваши заботы.

        Что важно для нас, у Formit указываем хук xQaAdd:
        &hooks=`email,xQaAdd`
        Хук email я тоже использую, что бы пришло администратору сайта уведомление «Добавлен новый вопрос...».

        Код хука примерно такой, т.е. мы просто создаем новый объект xQa (вопрос):
        $resource_id = $modx->resource->get('id');
        
        $modx->addPackage('xqa', MODX_CORE_PATH.'components/xqa/model/');
        
        $qa = $modx->newObject('xQa');
        $qa->set('author', strip_tags($hook->getValue('author')));
        $qa->set('author_email', strip_tags($hook->getValue('author_email')));
        $qa->set('question', strip_tags($hook->getValue('question')));
        $qa->set('resource_id', $resource_id);
        $qa->set('published', 0);
        $qa->set('createdby', 1);
        $qa->set('createdon', strtotime("now"));
        $qa->save();
        
        return true;
        3. Делаем TV поле с типом ввода migxdb, указываем Конфигурацию xqa (мы ее выше создавали), назначаем TV нужному шаблону.

        У ресурса должна появится таблица:


        4. Уведомление пользователя об ответе:
        Создаем плагин notifyVisitors на событие OnDocFormSave:

        <?php
        if($modx->event->name != 'OnDocFormSave'){
          return false;
        }
        
        $template = $resource->get('template');
        
        switch($template){
          case 7:
          case 15:
            // Вопрос-ответ
            $modx->addPackage('xqa', MODX_CORE_PATH.'components/xqa/model/');
            $qas = $modx->getCollection('xQa', array('deleted' => 0, 'notify' => 1, 'resource_id' => $resource->get('id')));
            foreach($qas as $qa){
              $to = $qa->get('author_email');
              if(!empty($to)){
        	// ЗДЕСЬ отправляем письмо, объект $qa у нас есть
              }
              $qa->set('notify', 0);
              $qa->save();
            }
            break;
          default:
            return false;
        }
        Что делает плагин: при сохранении ресурса получаем все xQa, у которых стоит галочка «Оповестить автора об ответе», далее, например, отправляем письмо (я НЕ ПРИВОЖУ этот код, напишите его сами), ставим notify в 0.
          Сергей
          21 октября 2014, 21:15
          0
          Спасибо большое!
            Сергей
            25 ноября 2014, 21:24
            0
            Подскажите, а как лучше вывести ответы и вопросы?
            getImageList почему-то не работает.
              Сергей
              25 ноября 2014, 21:55
              0
              Решено.
              Использовал
              [[!pdoPage?
                                      &elementClass=`modSnippet`
                                      &element=`migxLoopCollection`
                                      &limit=`1`
                                      &packageName=`xqa`
                                      &classname=`xQa`
                                      &tpl=`return-questions`
                                      &where=`{"published":"1","deleted":"0"}`
                                  ]]  
                                  [[!+page.nav]]
              Сергей
              02 января 2015, 23:05
              0
              Подскажите, а как выполнять сортировку вопросов при выводе по дате (полю published)?
              Илья Уткин
              17 октября 2014, 16:13
              1
              +1
              Можно сделать свой собственный компонент. Либо по урокам Василия Наумкина, либо по этим кратким описаниям: фронтенд, бэкенд.
                Сергей
                21 октября 2014, 21:15
                0
                Спасибо, у вас очень интересный сайт.
              Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
              13