Переменная автоматизации — это то, что отличает общее «Ваша заявка обновлена» от «Заявка 12345 — Принтер без тонера перешла в статус В работе, специалист Ана Паула». В GLPI такие переменные — это теги уведомлений: маркеры ##...##, которые движок заменяет реальными данными в момент отправки. Они кажутся тривиальными, пока однажды имя инициатора не окажется пустым в письме, и никто не будет знать почему. Это руководство — карта, которой мы пользуемся при сопровождении парков GLPI клиентов, чтобы настраивать шаблоны правильно с первого раза.
Где живут переменные: шаблон — не уведомление
Прежде чем выбирать теги, разберитесь в цепочке, которую строит GLPI, потому что большинство случаев «переменная не работает» на самом деле — ошибка конфигурации, а не синтаксиса. Сообщение отправляется, только когда совпадают четыре элемента:
- Уведомление (Настройка > Уведомления): привязывает событие — новая заявка, комментарий, решение, опрос удовлетворённости.
- Шаблон (Шаблоны уведомлений): текстовое и HTML-тело, где вы пишете теги
##...##. - Получатели: кто получает — инициатор, назначенный специалист, группа, наблюдатель.
- Очередь и отправка: GLPI не отправляет письмо сразу; он ставит его в очередь, а отправляет автоматическое действие
queuednotification.
Правильный тег в шаблоне, который не привязан ни к одному активному уведомлению, не порождает никакого письма — и ошибка тут не в теге.
Теги, которые вы реально используете
Теги контекстны: доступный набор меняется в зависимости от объекта (Ticket, Change, Problem). Для заявки эти теги покрывают большинство шаблонов — все проверены непосредственно в движке уведомлений GLPI 11:
| Тег | Что возвращает |
|---|---|
##ticket.id## | Номер заявки |
##ticket.title## | Заголовок |
##ticket.description## | Описание (содержимое при создании) |
##ticket.url## | Прямая ссылка на заявку (требует входа) |
##ticket.status## | Текущий статус |
##ticket.priority## | Приоритет |
##ticket.category## | Категория |
##ticket.creationdate## | Дата создания |
##ticket.solvedate## | Дата решения |
##ticket.duedate## | Срок решения (SLA) |
##ticket.authors## | Инициатор(ы) — список через запятую |
##ticket.assigntousers## | Назначенный(е) специалист(ы) — список через запятую |
##ticket.assigntogroups## | Назначенная(ые) группа(ы) |
##ticket.solution## | Текст решения |
##followup.author## / ##followup.description## | Внутри ##FOREACHfollowups## |
##task.author## / ##task.description## | Внутри ##FOREACHtasks## |
##author.name## / ##author.email## | Внутри ##FOREACHauthors## |
Обратите внимание, что ##ticket.assigntousers## возвращает уже готовый список через запятую — GLPI выполняет implode внутри. Это не цикл, а обычный текст: он решает задачу «у меня два специалиста, и я хочу видеть обоих в письме» без всяких уловок.
Самая частая ошибка на практике: инициатор — это не ##user.name##
Это, с большим отрывом, проблема, к которой мы чаще всего возвращаемся при аудите шаблонов: имя инициатора приходит в письме пустым. Причина почти всегда одна — кто-то скопировал ##user.name## из руководства по восстановлению пароля в уведомление о заявке. В уведомлении по Ticket тега ##user.name## просто не существует: он относится к уведомлениям объекта Пользователь (сброс пароля, создание учётной записи). GLPI не сообщает об ошибке — он просто подставляет пустое значение, и специалист полдня ищет баг, который на самом деле связан с источником данных. Инициатор заявки — это ##ticket.authors## (готовый список) или, если нужны имя и e-mail каждого по отдельности, блок FOREACH, описанный ниже.
FOREACH: часть, о которой почти никто не знает
У заявки N комментариев, N задач, N инициаторов. Волшебного тега, который выведет всё в отформатированном виде, не существует: GLPI предоставляет коллекции через блок повторения. Вы открываете цикл, один раз пишете тело с тегами элемента и закрываете его — и можно ещё ограничить количество элементов с помощью FIRST или LAST:
Hello ##ticket.authors##,
Your ticket ##ticket.id## - ##ticket.title##
changed to: ##ticket.status##
Technician(s): ##ticket.assigntousers##
Latest followup:
##FOREACH LAST 1 followups##
[##followup.date##] ##followup.author##:
##followup.description##
##ENDFOREACHfollowups##
Open it: ##ticket.url##
LAST 1 (также поддерживаются FIRST и большее число) — это то, что избавляет от бесконечного письма, повторяющего всю историю при каждом новом комментарии, — частая просьба клиентов, которую большинство «решает», грубо вырезая содержимое, хотя достаточно было модификатора. Коллекции, поддерживающие FOREACH, включают followups, tasks, authors, actors, costs и связанные объекты (linkedtickets, linkedchanges, linkedproblems).
Многоязычные уведомления бесплатно: теги ##lang.*##
Деталь, которая видна, только если открыть полный список тегов: у каждого тега значения есть парный тег подписи. Для ##ticket.status## (значение) существует ##lang.ticket.status##, который выводит подпись «Статус», переведённую на язык получателя. Собирая шаблон из пар подпись + значение, одно и то же сообщение уходит одному пользователю на португальском, а другому на испанском, без дублирования шаблона:
##lang.ticket.title##: ##ticket.title##
##lang.ticket.status##: ##ticket.status##
##lang.ticket.priority##: ##ticket.priority##
У клиента с организациями в нескольких странах это сокращает поддержку «по шаблону на каждый язык» до одного шаблона. Значение вы не переводите — данные есть данные; меняется только подпись, и ##lang.*## решает это сам.
Диагностика: переменная верная, но письмо не уходит
Когда шаблон безупречен, а письмо всё равно пропадает, проблема не в теге — она в очереди. GLPI записывает каждое сообщение в glpi_queuednotifications, а фактически отправляет его автоматическое действие queuednotification. Если действие не выполняется (cron остановлен) или SMTP отказывает, сообщения накапливаются с пустым sent_time. Это первое, что мы запускаем по заявке «уведомление не приходит»:
-- Queued notifications that should already have gone out:
-- sent_time NULL = not sent yet; send_time in the past = overdue.
SELECT id, name AS subject, recipient, send_time, sent_try
FROM glpi_queuednotifications
WHERE sent_time IS NULL
AND send_time <= NOW()
ORDER BY send_time ASC
LIMIT 30;
Накапливающиеся строки с растущим sent_try означают, что SMTP отклоняет; растущая очередь с sent_try на нуле означает остановленный cron. В обоих случаях шаблон верен — и вы только что сэкономили полдня, которые потратили бы на бессмысленное переписывание тегов.
Где работает каждая переменная (матрица решений)
Путаница, которую мы получаем в наследство почти в каждом новом парке, — считать, что «шаблон заявки» и «шаблон уведомления» — одно и то же. Это не так — и только один из них подставляет ##tag##:
| Где | Подставляет ##tag##? | Для чего нужен |
|---|---|---|
| Шаблон уведомления (тело и тема) | Да — теги, FOREACH и ##lang.*## | Динамическое тело письма |
| Шаблон заявки (TicketTemplate) | Нет | Предзаполнить, скрыть или сделать обязательными поля при создании |
| Шаблон задачи / комментария | Нет | Многоразовый базовый текст без динамической подстановки |
| Тема (subject) уведомления | Да | Те же теги, что и в теле, в одной строке |
Последний совет, который стоит всего остального: при редактировании шаблона уведомления GLPI показывает вкладку тегов, доступных для конкретного объекта. Это источник истины — контекстный и всегда соответствующий установленной версии. Прежде чем копировать тег из блога (включая этот), убедитесь, что он там есть.
Нужны уведомления, которые действительно информируют, — многоязычные, с правильным инициатором и без писем, застрявших в очереди? Познакомьтесь с сопровождением GLPI от NexTool.
Проверено командой NexTool Solutions.