Всем привет.
Сегодня речь пойдёт о том, как я написал плагин GLPI для работы с новыми заявками, созданными на основе уже закрытых заявок, а точнее переоткрытии закрытых заявок, если по ним поступают дополнительные запросы от пользователя.
Есть helpdesk-система GLPI. Заявки от пользователей поступают на специально выделенный почтовый ящик, подключённый к системе. Ответ службы поддержки также прилетает на почту. Вообще все уведомления о ходе движения заявки от первого обращения до решения и закрытия поступают на почту.
Однако если пользователь посчитает, что его проблема должным образом не решена и решит ответить на письмо о закрытии заявки, то вместо переоткрытия создаётся новое обращение, а старый тикет просто к нему привязывается. Собственно, эту проблему и нужно было решить.
Итак, поехали.
Первым делом нужно установить генератор плагинов — он существенно упрощает жизнь путём создания каркаса нашего будущего плагина. Сделать это можно с помощью следующего алгоритма:
И всё, каркас готов.
Зайдя в директорию созданного плагина, вы увидите кучу файлов, из которых реально нужны только setup.php и hook.php. Если планируете использовать дополнительные библиотеки, можете оставить composer.json. Мне оные не понадобились, поэтому я оставил только 2 необходимых файла.
В файле setup.php обязательными являются две функции — plugin_init_yourpluginname и plugin_version_yourpluginname. Первая инициализирует плагин, вторая возвращает информацию о нём — имя, автор, версия и т.д.
Со второй всё более-менее понятно. Поэтому ненадолго остановимся на первой функции. Она работает с глобальной переменной $PLUGIN_HOOKS. Наверняка все знают, что такое хуки, но если кто-то не в курсе, то это т.н. системные крючки, которые дёргает плагин. Более подробно про хуки можно почитать в официальном мануале.
Каждый плагин должен в обязательном порядке зарегистрировать хук csrf_compliant, иначе вы его просто не сможете активировать, будет вот такая ошибка:
Помимо обязательного хука можно зарегистрировать многие другие, например, задать ссылку на страницу настроек:
Также в этой функции регистрируются классы плагина:
В итоге в моём случае две обязательные функции файла setup.php выглядят следующим образом:
В документации написано, что можно в качестве обработчика регистрировать статические методы классов, в таком случае они должны иметь следующий вид (кстати, совсем необязательно передавать какие-то параметры в обработчик):
Сами же функции-обработчики хранятся в файле hook.php. Там же хранятся функции установки и удаления плагина. К именам функций те же требования — plugin_yourpluginname_function_name.
Кстати, я пробовал для обработки хука регистрировать статический метод и передавать в параметры объект Ticket, но почему-то у меня ничего не сработало. А вот с обычной функцией всё получилось. Поэтому, чтобы не засорять hook.php лишним кодом, я зарегистрировал класс, содержащий метод-обработчик хука, а в нужной функции этот метод просто вызывал:
Если вам нужно устанавливать таблицы в базу данных, то в официальной документации есть отдельный пункт на эту тему. Мне же таблицы не понадобились, поэтому я данный момент пропускаю.
Ну что же, с установкой/удалением плагина, регистрацией и вызовом обработчиков хуков вроде разобрались, теперь самое время разобраться, как мы новую заявку будем обрабатывать.
Для этого, как уже говорилось выше, я зарегистрировал класс PluginAdvticketsEvent (файл inc/event.php), содержащий один единственный метод pre_item_add_ticket.
На этом всё. Спасибо за внимание. Исходный код, как всегда на гитхабе.
Полезные ссылки:
Официальный сайт GLPI
Официальные форумы
Телеграм-чат
Example-плагин
Документация для разработчиков
APIDoc
Документация по созданию плагинов
Статья по созданию плагинов GLPI
Сегодня речь пойдёт о том, как я написал плагин GLPI для работы с новыми заявками, созданными на основе уже закрытых заявок, а точнее переоткрытии закрытых заявок, если по ним поступают дополнительные запросы от пользователя.
ПредысторияВходные параметры следующие.
Вот уже без малого месяц я копаюсь в исходном коде helpdesk-системы GLPI. И при изучении данного опенсорс-решения обратил внимание на ничтожно малое количество информации для разработчиков. Подавляющее большинство материала связано с настройкой GLPI внутри интерфейса. Немного меньше, но ещё можно встретить информацию по настройке сервера. И всего одна статья (и та на английском) повествует о разработке плагинов для этой чудо-системы.
Из официальных мануалов — пара ресурсов на readthedocs, плюс мануал, сгенерированный из phpdoc'а.
Из сообщества — чат в телеграме и забугорный форум.
Есть helpdesk-система GLPI. Заявки от пользователей поступают на специально выделенный почтовый ящик, подключённый к системе. Ответ службы поддержки также прилетает на почту. Вообще все уведомления о ходе движения заявки от первого обращения до решения и закрытия поступают на почту.
Однако если пользователь посчитает, что его проблема должным образом не решена и решит ответить на письмо о закрытии заявки, то вместо переоткрытия создаётся новое обращение, а старый тикет просто к нему привязывается. Собственно, эту проблему и нужно было решить.
Итак, поехали.
Первым делом нужно установить генератор плагинов — он существенно упрощает жизнь путём создания каркаса нашего будущего плагина. Сделать это можно с помощью следующего алгоритма:
- Идём на гитхаб и клонируем репозиторий в папку с плагинами (/path/to/glpi/plugins)
- В консоли запускаем команду:
$ ./plugin.sh YourPluginName 1.0
где YourPluginName — название вашего плагина, 1.0 — версия плагина.
И всё, каркас готов.
Зайдя в директорию созданного плагина, вы увидите кучу файлов, из которых реально нужны только setup.php и hook.php. Если планируете использовать дополнительные библиотеки, можете оставить composer.json. Мне оные не понадобились, поэтому я оставил только 2 необходимых файла.
В файле setup.php обязательными являются две функции — plugin_init_yourpluginname и plugin_version_yourpluginname. Первая инициализирует плагин, вторая возвращает информацию о нём — имя, автор, версия и т.д.
Со второй всё более-менее понятно. Поэтому ненадолго остановимся на первой функции. Она работает с глобальной переменной $PLUGIN_HOOKS. Наверняка все знают, что такое хуки, но если кто-то не в курсе, то это т.н. системные крючки, которые дёргает плагин. Более подробно про хуки можно почитать в официальном мануале.
Каждый плагин должен в обязательном порядке зарегистрировать хук csrf_compliant, иначе вы его просто не сможете активировать, будет вот такая ошибка:
Не знаю, что это за параметр, пока не разобрался с системой до конца. Если среди читателей найдутся знатоки GLPI, напишите в комментариях.
Помимо обязательного хука можно зарегистрировать многие другие, например, задать ссылку на страницу настроек:
$PLUGIN_HOOKS['config_page']['yourpluginname'] = 'config.php';
Также в этой функции регистрируются классы плагина:
Plugin::registerClass(PluginYourpluginnameConfig::class);
И, кстати, да, в GLPI есть стандарты по именованию классов и файлов, их содержащих. Название класса складывается из трёх частей: Plugin + Названиевашегоплагина + Назначениекласса. В названии плагина, состоящего из нескольких слов, с заглавной буквы пишется только первое слово.Вернёмся к нашим хукам. Мне нужно отслеживать создание новой заявки с возможностью отмены, поэтому я зарегистрирую обработчик хука pre_item_add. В обработчик можно передавать параметры, делается это с помощью ассоциативного массива, где ключом является объект нужной сущности (в моём случае Ticket), а значением название функции-обработчика.
Файлы именуются следующим образом: если класс носит название PluginYourpluginnameConfig, то файл должен называться config.class.php. И все классы плагина нужно складывать в папку inc (не в ту, которая в корне сайта, а в ту, которая в корне плагина, и если её в папке плагина нет — а её там не будет — то нужно создать, если вы планируете писать какие-то классы).
Обо всём этом можно узнать из официальной документации.
В итоге в моём случае две обязательные функции файла setup.php выглядят следующим образом:
/**
* Init hooks of the plugin.
* REQUIRED
*
* @return void
*/
function plugin_init_advtickets() {
global $PLUGIN_HOOKS;
Plugin::registerClass(PluginAdvticketsEvent::class);
$PLUGIN_HOOKS['csrf_compliant']['advtickets'] = true;
$PLUGIN_HOOKS['pre_item_add']['advtickets'] = [
Ticket::class => 'plugin_advtickets_pre_item_add'
];
}
/**
* Get the name and the version of the plugin
* REQUIRED
*
* @return array
*/
function plugin_version_advtickets() {
return [
'name' => 'Adv Tickets',
'version' => PLUGIN_ADVTICKETS_VERSION,
'author' => 'Roman Gonyukov',
'license' => '',
'homepage' => 'https://github.com/stayfuneral/advtickets',
'requirements' => [
'glpi' => [
'min' => '9.2',
]
]
];
}
В документации написано, что можно в качестве обработчика регистрировать статические методы классов, в таком случае они должны иметь следующий вид (кстати, совсем необязательно передавать какие-то параметры в обработчик):
/* пример из официальной документации */
//call a function
$PLUGIN_HOOKS['hook_name']['plugin_name'] = 'function_name';
//call a static method from an object
$PLUGIN_HOOKS['other_hook']['plugin_name'] = ['ObjectName', 'methodName'];
Сами же функции-обработчики хранятся в файле hook.php. Там же хранятся функции установки и удаления плагина. К именам функций те же требования — plugin_yourpluginname_function_name.
Кстати, я пробовал для обработки хука регистрировать статический метод и передавать в параметры объект Ticket, но почему-то у меня ничего не сработало. А вот с обычной функцией всё получилось. Поэтому, чтобы не засорять hook.php лишним кодом, я зарегистрировал класс, содержащий метод-обработчик хука, а в нужной функции этот метод просто вызывал:
// hook.php
/**
* @param Ticket $ticket
*
* @return bool
*/
function plugin_advtickets_pre_item_add(Ticket $ticket)
{
return PluginAdvticketsEvent::pre_item_add_ticket($ticket);
}
Если вам нужно устанавливать таблицы в базу данных, то в официальной документации есть отдельный пункт на эту тему. Мне же таблицы не понадобились, поэтому я данный момент пропускаю.
Ну что же, с установкой/удалением плагина, регистрацией и вызовом обработчиков хуков вроде разобрались, теперь самое время разобраться, как мы новую заявку будем обрабатывать.
Для этого, как уже говорилось выше, я зарегистрировал класс PluginAdvticketsEvent (файл inc/event.php), содержащий один единственный метод pre_item_add_ticket.
class PluginAdvticketsEvent extends CommonDBTM
{
static function pre_item_add_ticket(Ticket $ticket)
{
global $DB;
// Т.к. заявка ещё не создана, то данные передаются в свойстве input
$fields = $ticket->input;
// Если тикет создан на основе существующего, то в данном параметре передаётся его (существующего тикета) идентификатор
if($fields['_link']['tickets_id_2']) {
$relatedTicketId = $fields['_link']['tickets_id_2'];
$relatedTicketParamsForUpdate = [
'itemtype' => \Ticket::class, // тип сущности, в моём случае заявка
'items_id' => $relatedTicketId, // id заявки
'users_id' => $fields['_users_id_requester'], // id написавшего пользователя
'users_id_editor' => 0,
'content' => $fields['content'], содержимое заявки
'date' => date('c'),
'date_mod' => date('c'),
'date_creation' => date('c'),
'is_private' => 0,
'requesttypes_id' => $fields['requesttypes_id'], // источник (helpdesk, email и т.д.)
'timeline_position' => 4,
'sourceitems_id' => 0,
'sourceof_items_id' => 0
];
// В исходном коде пока не нашёл методы для добавления заявок (как в том же битриксе, например), поэтому приходится практически напрямую вставлять данные в БД. Кстати, все комментарии к заявкам хранятся в таблице glpi_itilfollowups
$DB->insert('glpi_itilfollowups', $relatedTicketParamsForUpdate);
// Заодно поменяем статус существующей заявки, присвоив ему значение "Новый". Для этого обновим соответствующую запись в таблице glpi_tickets
$DB->update('glpi_tickets', [
'status' => 1
], [
'id' => $relatedTicketId
]);
// Т.к. новая заявка связана с существующей, присвоим параметру input значение false. В таком случае обращение создано не будет.
$ticket->input = false;
return $ticket->input;
}
}
}
На этом всё. Спасибо за внимание. Исходный код, как всегда на гитхабе.
Полезные ссылки:
Официальный сайт GLPI
Официальные форумы
Телеграм-чат
Example-плагин
Документация для разработчиков
APIDoc
Документация по созданию плагинов
Статья по созданию плагинов GLPI