Переменные автоматизации и шаблоны в GLPI 11

Техническое руководство по переменным уведомлений GLPI 11: теги ##...##, которые действительно работают, блок FOREACH, о котором почти никто не знает, теги ##lang.*## для многоязычных писем, почему инициатор выводится пустым и запрос к очереди, который NexTool выполняет, когда уведомление не приходит.

В этой статьеРазделов: 7

Переменная автоматизации — это то, что отличает общее «Ваша заявка обновлена» от «Заявка 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.

Часто задаваемые вопросы

Почти всегда потому, что в шаблоне используется ##user.name##, которого нет в уведомлениях по Ticket (он относится к уведомлениям объекта Пользователь, например сбросу пароля). GLPI не сообщает об ошибке, а просто подставляет пустое значение. Используйте ##ticket.authors## для списка инициаторов или блок ##FOREACHauthors## с ##author.name## и ##author.email##, чтобы обработать каждого.

Используйте модификатор FOREACH: ##FOREACH LAST 1 followups## ... ##ENDFOREACHfollowups##. LAST 1 берёт только самый свежий комментарий; FIRST и большие числа также поддерживаются. Без модификатора цикл при каждой отправке выводит все комментарии, порождая классическое бесконечное письмо.

Да. У каждого тега значения есть парный тег подписи ##lang.*##: для ##ticket.status## существует ##lang.ticket.status##, который выводит подпись, переведённую на язык получателя. Собирая тело из пар ##lang.ticket.field##: ##ticket.field##, один шаблон обслуживает несколько языков.

Очередь, а не шаблон. GLPI ставит каждое сообщение в очередь в glpi_queuednotifications, а отправку выполняет автоматическое действие queuednotification. Посмотрите строки с sent_time NULL и send_time в прошлом: растущий sent_try указывает на отказ SMTP; sent_try на нуле — на остановленный cron.

Нет. Шаблон заявки (TicketTemplate) служит для предзаполнения, скрытия или обязательности полей при создании — он не подставляет ##tag##. Переменные автоматизации ##...## живут в шаблонах уведомлений (тело и тема). Путаница между ними — источник многих жалоб «переменная не работает».

Нужна помощь?