- Регистрация
- 21.07.20
- Сообщения
- 40.408
- Реакции
- 1
- Репутация
- 0
Всем привет.
Сегодня речь пойдёт о том, как я написал плагин GLPI для работы с новыми заявками, созданными на основе уже закрытых заявок, а точнее переоткрытии закрытых заявок, если по ним поступают дополнительные запросы от пользователя.
Входные параметры следующие:
Есть helpdesk-система GLPI. Заявки от пользователей поступают на специально выделенный почтовый ящик, подключённый к системе. Ответ службы поддержки также прилетает на почту. Вообще все уведомления о ходе движения заявки от первого обращения до решения и закрытия поступают на почту.
Однако если пользователь посчитает, что его проблема должным образом не решена и решит ответить на письмо о закрытии заявки, то вместо переоткрытия создаётся новое обращение, а старый тикет просто к нему привязывается. Собственно, эту проблему и нужно было решить.
Итак, поехали.
Первым делом нужно установить
И всё, каркас готов.
Зайдя в директорию созданного плагина, вы увидите кучу файлов, из которых реально нужны только setup.php и hook.php. Если планируете использовать дополнительные библиотеки, можете оставить composer.json. Мне оные не понадобились, поэтому я оставил только 2 необходимых файла.
В файле setup.php обязательными являются две функции — plugin_init_yourpluginname и plugin_version_yourpluginname. Первая инициализирует плагин, вторая возвращает информацию о нём — имя, автор, версия и т.д.
Со второй всё более-менее понятно. Поэтому ненадолго остановимся на первой функции. Она работает с глобальной переменной $PLUGIN_HOOKS. Наверняка все знают, что такое хуки, но если кто-то не в курсе, то это т.н. системные крючки, которые дёргает плагин. Более подробно про хуки можно почитать в
Каждый плагин должен в обязательном порядке зарегистрировать хук csrf_compliant, иначе вы его просто не сможете активировать, будет вот такая ошибка:
Помимо обязательного хука можно зарегистрировать многие другие, например, задать ссылку на страницу настроек:
$PLUGIN_HOOKS['config_page']['yourpluginname'] = 'config.php';
Также в этой функции регистрируются классы плагина:
Plugin::registerClass(PluginYourpluginnameConfig::class);
Вернёмся к нашим хукам. Мне нужно отслеживать создание новой заявки с возможностью отмены, поэтому я зарегистрирую обработчик хука pre_item_add. В обработчик можно передавать параметры, делается это с помощью ассоциативного массива, где ключом является объект нужной сущности (в моём случае Ticket), а значением название функции-обработчика.
В итоге в моём случае две обязательные функции файла 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' => '
'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:
re_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 для работы с новыми заявками, созданными на основе уже закрытых заявок, а точнее переоткрытии закрытых заявок, если по ним поступают дополнительные запросы от пользователя.
Предыстория
Вот уже без малого месяц я копаюсь в исходном коде helpdesk-системы GLPI. И при изучении данного опенсорс-решения обратил внимание на ничтожно малое количество информации для разработчиков. Подавляющее большинство материала связано с настройкой GLPI внутри интерфейса. Немного меньше, но ещё можно встретить информацию по настройке сервера. И всего одна статья (и та на английском) повествует о разработке плагинов для этой чудо-системы.
Из официальных мануалов — пара ресурсов на readthedocs, плюс мануал, сгенерированный из phpdoc'а.
Из сообщества — чат в телеграме и забугорный форум.
Вот уже без малого месяц я копаюсь в исходном коде helpdesk-системы GLPI. И при изучении данного опенсорс-решения обратил внимание на ничтожно малое количество информации для разработчиков. Подавляющее большинство материала связано с настройкой GLPI внутри интерфейса. Немного меньше, но ещё можно встретить информацию по настройке сервера. И всего одна статья (и та на английском) повествует о разработке плагинов для этой чудо-системы.
Из официальных мануалов — пара ресурсов на readthedocs, плюс мануал, сгенерированный из phpdoc'а.
Из сообщества — чат в телеграме и забугорный форум.
Входные параметры следующие:
Есть helpdesk-система GLPI. Заявки от пользователей поступают на специально выделенный почтовый ящик, подключённый к системе. Ответ службы поддержки также прилетает на почту. Вообще все уведомления о ходе движения заявки от первого обращения до решения и закрытия поступают на почту.
Однако если пользователь посчитает, что его проблема должным образом не решена и решит ответить на письмо о закрытии заявки, то вместо переоткрытия создаётся новое обращение, а старый тикет просто к нему привязывается. Собственно, эту проблему и нужно было решить.
Итак, поехали.
Первым делом нужно установить
You must be registered for see links
— он существенно упрощает жизнь путём создания каркаса нашего будущего плагина. Сделать это можно с помощью следующего алгоритма:- Идём на
You must be registered for see linksи клонируем репозиторий в папку с плагинами (/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. Наверняка все знают, что такое хуки, но если кто-то не в курсе, то это т.н. системные крючки, которые дёргает плагин. Более подробно про хуки можно почитать в
You must be registered for see links
.Каждый плагин должен в обязательном порядке зарегистрировать хук csrf_compliant, иначе вы его просто не сможете активировать, будет вот такая ошибка:

Не знаю, что это за параметр, пока не разобрался с системой до конца. Если среди читателей найдутся знатоки GLPI, напишите в комментариях.
Помимо обязательного хука можно зарегистрировать многие другие, например, задать ссылку на страницу настроек:
$PLUGIN_HOOKS['config_page']['yourpluginname'] = 'config.php';
Также в этой функции регистрируются классы плагина:
Plugin::registerClass(PluginYourpluginnameConfig::class);
И, кстати, да, в GLPI есть стандарты по именованию классов и файлов, их содержащих. Название класса складывается из трёх частей: Plugin + Названиевашегоплагина + Назначениекласса. В названии плагина, состоящего из нескольких слов, с заглавной буквы пишется только первое слово.
Файлы именуются следующим образом: если класс носит название PluginYourpluginnameConfig, то файл должен называться config.class.php. И все классы плагина нужно складывать в папку inc (не в ту, которая в корне сайта, а в ту, которая в корне плагина, и если её в папке плагина нет — а её там не будет — то нужно создать, если вы планируете писать какие-то классы).
Обо всём этом можно узнать из
Файлы именуются следующим образом: если класс носит название PluginYourpluginnameConfig, то файл должен называться config.class.php. И все классы плагина нужно складывать в папку inc (не в ту, которая в корне сайта, а в ту, которая в корне плагина, и если её в папке плагина нет — а её там не будет — то нужно создать, если вы планируете писать какие-то классы).
Обо всём этом можно узнать из
You must be registered for see links
.Вернёмся к нашим хукам. Мне нужно отслеживать создание новой заявки с возможностью отмены, поэтому я зарегистрирую обработчик хука pre_item_add. В обработчик можно передавать параметры, делается это с помощью ассоциативного массива, где ключом является объект нужной сущности (в моём случае Ticket), а значением название функции-обработчика.
В итоге в моём случае две обязательные функции файла 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' => '
You must be registered for see links
','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:
}
Если вам нужно устанавливать таблицы в базу данных, то в
You must be registered for see links
есть отдельный пункт на эту тему. Мне же таблицы не понадобились, поэтому я данный момент пропускаю.Ну что же, с установкой/удалением плагина, регистрацией и вызовом обработчиков хуков вроде разобрались, теперь самое время разобраться, как мы новую заявку будем обрабатывать.
Для этого, как уже говорилось выше, я зарегистрировал класс 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;
}
}
}
На этом всё. Спасибо за внимание. Исходный код, как всегда на
You must be registered for see links
.Полезные ссылки:
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links