87
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Интеграция с внешними системами через электронную почту (PHP IMAP)

Время чтения 8 минут
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
87
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Привет, меня зовут Александр Ададуров. Я руководитель проектов ФГБУ «Центр информационно-технического обеспечения». В этой статье рассмотрю пример использования библиотеки php-imap для интеграции внутреннего портала на базе Битрикса с внешним сервисом видео-конференц-связи. 

В качестве связующего звена в интеграции использую электронную почту. Покажу примеры листингов агента Битрикса, который через заданные интервалы парсит email-сообщения на IMAP-сервере и при нахождении нужных писем решает какие-то задачи на стороне портала.

Интеграция с внешними системами через электронную почту (PHP IMAP)

Зачем всё это нужно

Иногда PHP-проект необходимо интегрировать с внешними информационными системами, которые не имеют программного интерфейса или не дают к нему доступ. Бывает наоборот: ваша политика информационной безопасности не позволяет организовать подключение к этим внешним системам.

Таким примером может быть служба поддержки какого-то внешнего сервиса, которая работает со своими клиентами исключительно через электронную почту. 

Допустим, у вас есть закрытый интранет-портал на Битриксе или другой PHP-платформе. Сотрудники бронируют на портале переговорные и в некоторых случаях отмечают в заявке, что им требуется, кроме переговорной, еще организация видео-конференц-связи (ВКС). У компании заключен договор на поддержку ВКС, но по условиям заявка на создание сессии видеосвязи должна подаваться только от нескольких согласованных ответственных лиц, которые модерируют бронирование переговорных, и только с их адреса электронной почты. При такой схеме ответственные лица, помимо модерации бронирования переговорных, вынуждены решать рутинные задачи: оформлять заявки подрядчику ВКС, получать ссылки на подключение и пересылать их сотрудникам.

Рутинные задачи из примера можно автоматизировать, используя PHP-библиотеку php-imap. Интранет-портал при бронировании переговорной с ВКС будет отправлять заявку по форме подрядчику ВКС, а потом через некоторые интервалы проверять согласованный почтовый ящик, который парсит новые email-адреса. А при появлении письма от подрядчика ВКС найдет в нем ссылку для подключения и разошлет ее всем участникам встречи.

Этот способ при некоторой доработке подойдет и для других задач, например для оформления командировок, заказа билетов, пропусков или парковочных мест с оповещением постов охраны. Электронная почта в таком случае выступает как брокер сообщений, задача разработчика — обеспечить интерфейс связи информационной системы с системой электронной почты SMTP- и IMAP- или POP-серверам. Тогда процессы можно автоматизировать.

Интеграция портала на базе Битрикса с IMAP-сервером 

Рассмотрим пример, когда нужно интегрировать портал на базе Битрикса с IMAP-сервером электронной почты и настроить парсинг почтовых сообщений от поддержки сервиса ВКС. Предположим, в портале есть сущность «встреча» в виде информационного блока Битрикса (IBLOCK). В свойствах этого блока будут храниться параметры встречи и то, что для примера важно, — ссылка на ВКС.

Информационный блок Битрикса, содержащий данные о встрече

Для регулярных запусков нашего кода воспользуемся встроенным механизмом Битрикса — создадим агента с функцией VKSLink(), выберем дату запуска и желаемый интервал запуска, то есть то, как часто будет проверяться почта. Например, 600 секунд (10 минут).

Агент Битрикса VKSLink

Чтобы функция VKSLink() была видна ядру Битрикса, необходимо подключить файл с агентом. Для этого воспользуемся файлом init.php.

Листинг файла init.php


<?php
// Подключаем код агента VKSLink
include 'vkslink.php';

 

В Битриксе файл init.php используется для инициализации обработчиков событий или подключения дополнительных функций, потому что он вызывается при каждом обращении к сайту. В моей инсталляции Битрикса init.php находится в /local/php_interface/init.php. Как видно из кода, файл с агентом должен располагаться рядом в /local/php_interface/vkslink.php. Получается следующая файловая структура.

Файловая структура решения

Рассмотрим письма, которые отправляются в службу поддержки сервиса ВКС и возвращаются. Письма отправляются с единого почтового ящика vks@test.ru, и ответ приходит на тот же ящик.

Примерное письмо в службу поддержки сервиса ВКС

Тема: Заявка на создание ВКС #intranet_561#
Здравствуйте!

ООО «Тест.ру» запрашивает ВКС для проведения онлайн-встречи. Чтобы участники получили ссылку на ВКС, пожалуйста, при ответе не удаляйте из темы письма номер заявки, заключенный в решетки (#) intranet_561. Это позволит автоматически обработать ваше письмо и направить ссылку на ВКС участникам мероприятия.

Лучше всего отправить ссылку на ВКС, нажав в почтовой программе кнопку «Ответить» на это письмо, тогда тема будет дополнена фразой Re: или «Ответ:», но первоначальная тема письма сохранится.

Дата и время ВКС: 19.06.2024 09:30:00.
Длительность: 1 час 30 минут.
Тема мероприятия: Встреча по проектам.
Кол-во участников: 20.
Ведущий: Иванов Иван.
Запись: не требуется.

С уважением, ответственный за ВКС
Пётр Петров

Письмо сформировано автоматически

Письмо из службы поддержки сервиса ВКС

Тема: Re: Заявка на создание ВКС #intranet_561#
Здравствуйте!

Направляем ссылку на подключение к видео-конференц-связи:
https://vksservice.ru/meeting/aRiaF0vwcO1crGbrddk

Инструкция по подключению доступна по ссылке https://vksservice.ru/manual.pdf

В письмах обратите внимание на метку #intranet_561#. 561 — это ID «встречи» (элемента IBLOCK Битрикса) в интранет-портале, а intranet_ — это префикс, введенный, чтобы ITSM-система поддержки ВКС не распарсила наш внутренний номер как свой и не привязала бы наше письмо к другому сообщению или заявке. ID с префиксом заключены в решетки # для удобства поиска в заголовках письма. С помощью метки #intranet_561# почтовые сообщения связываются с конкретной заявкой и «встречей» в базе данных портала. Это будет видно дальше из кода в файле vsklink.php.

Итак, у нас уже есть:

  • Почтовый сервер IMAP и доступ к нему.
  • На почтовом сервере лежит письмо из службы поддержки ВКС со ссылкой вида https://vksservice.ru/meeting/_hash_ и меткой в теме сообщения вида #intranet_NNN#, где NNN — это ID элементов IBLOCK или встреч в базе данных интранет-портала.
  • В Битриксе создан агент для регулярного запуска кода функции VKSLink().
  • PHP на сервере запускается с подключенным модулем php-imap.

Рассмотрим код самого агента — функции VKSLink().

Листинг 2. Код агента Битрикса VKSLink


<?php

function VKSLink()
{
 // Данные доступа к IMAP-серверу
 $server = '{mail.test.ru:993/imap/ssl}INBOX';
 $username = 'vks@test.ru';
 $password = '0ZOwNytIkqYecS4Olxw';

 // Подключение к IMAP-серверу
 $mailbox = imap_open($server, $username, $password);

 if ($mailbox) {
     // Просмотр непрочитанных сообщений
     $search_query = 'UNSEEN TEXT "https://vksservice.ru"';
     $emails = imap_search($mailbox, $search_query, null, 'UTF-8');

     // Объявляем массив для URL, содержащих https://vksservice
     $found_values = [];

     if ($emails) { // Если в почтовом ящике есть письма, соответствующие условию, смотрим их
         // Объявляем массив для хранения последних писем на каждый ID встречи,
         // так как нас интересуют только последние пришедшие и не интересуют старые, которые,
         // например, могли быть ошибочно отправлены
         $latest_emails = [];

         // Перебираем письма в почтовом ящике
         foreach ($emails as $email_id) {
             // Получаем заголовки письма
             $header = imap_headerinfo($mailbox, $email_id);

             // Получаем тему письма и в ней ищем нашу метку #intranet_id#
             $subject = imap_utf8($header->subject);
             preg_match('/#intranet_(d+)#/', $subject, $matches);
             $intranet_id = isset($matches[1]) ? $matches[1] : '';

             // Получаем дату письма
             $email_date = strtotime($header->date);

             // Получаем тело письма
             $body = imap_fetchbody($mailbox, $email_id, 1);

             // Ищем в теле письма ссылку на ВКС
             preg_match('/https://vksservice.[^s]+/', $body, $vksservice_url_matches);
             $vksservice_url = isset($vksservice_url_matches[0]) ? $vksservice_url_matches[0] : '';

             // Выбираем только те письма, где в теме есть метка #intranet_N# и в теле — искомая ссылка на ВКС https://vksservice.
             if (!empty($intranet_id) && !empty($vksservice_url)) {
                 // Проверяем массив найденных писем, не было ли найдено писем с такой же меткой
                 if (isset($latest_emails[$intranet_id])) {
                     // Если обрабатываемое письмо новее ранее найденного, заменяем предыдущее на новое
                     if ($email_date > $latest_emails[$intranet_id]['date']) {
                         $latest_emails[$intranet_id] = [
                             'id' => $email_id,
                             'date' => $email_date,
                         ];
                     }
                 } else {
                     // Если писем с одинаковыми метками не найдено, сохраняем в массив текущее
                     $latest_emails[$intranet_id] = [
                         'id' => $email_id,
                         'date' => $email_date,
                     ];
                 }
             }
         }

         // Обрабатываем последние пришедшие письма с уникальными метками, ищем в них ссылки https://vksservice
         // и сохраняем в массив $found_values array
         foreach ($latest_emails as $intranet_id => $email_info) {
             $email_id = $email_info['id'];

             // Получаем тему письма
             $header = imap_headerinfo($mailbox, $email_id);
             $subject = imap_utf8($header->subject);

             // Получаем тело письма
             $body = imap_fetchbody($mailbox, $email_id, 1);

             // Извлекаем ссылку на ВКС https://vksservice из тела письма
             preg_match('/https://vksservice.[^s]+/', $body, $vksservice_url_matches);
             $vksservice_url = isset($vksservice_url_matches[0]) ? $vksservice_url_matches[0] : '';

             // Сохраняем полученные результаты в массив
             $found_values[$email_id] = [
                 'intranet_id' => $intranet_id,
                 'subject' => $subject,
                 'url' => $vksservice_url,
             ];
         }
     }

     // Закрываем IMAP-соединение
     imap_close($mailbox);


     // Обрабатываем полученный массив
     foreach ($found_values as $key => $value){
         // Код, отвечающий за сохранение ссылки на ВКС в БД и рассылку уведомлений участникам ВКС
         $arSelect = array( // Формируем список важных нам полей для запроса к БД
             "ID",
             "IBLOCK_SECTION_ID",
             "IBLOCK_ID",
             "IBLOCK_ELEMENT_ID",
             "NAME",
             "CREATED_BY",
             "DATE_ACTIVE_FROM",
             "DATE_ACTIVE_TO",
             "DETAIL_TEXT",
             "PROPERTY_UF_USER",
             "PROPERTY_UF_SPEAKER",
             "PROPERTY_UF_PARTICIPANTS_COUNT",
             "PROPERTY_UF_EVENT_LENGTH",
             "PROPERTY_UF_STATUS",
             "PROPERTY_UF_VKSREQUEST_SENT",
             "PROPERTY_UF_VKSEMAIL_IDS",
             "PROPERTY_UF_VKSLINK",
             "PROPERTY_UF_ROOM_ID",
         );
         // Делаем запрос в БД о записи с ID из метки #intranet_ID#
         $arFilter = Array("IBLOCK_ID"=>IntVal(IBLOCK_ID), "ID"=>IntVal($value['intranet_id']));
         $res = CIBlockElement::GetList(Array(), $arFilter, false, Array("nPageSize"=>50), $arSelect);
         $results = $res->arResult;

         // Чтобы не удалились старые записи, собираем весь массив свойств, подставляя значение UF_VKSLINK,         // иначе метод $iblockElementObject->Update затрет старые значения
         $arFields = array(
             "NAME" => $results[0]["NAME"],
             "DATE_ACTIVE_FROM" => $results[0]["DATE_ACTIVE_FROM"],
             "DATE_ACTIVE_TO" => $results[0]["DATE_ACTIVE_TO"],
             "CREATED_BY" => $results[0]["CREATED_BY"],
             "DETAIL_TEXT" => $results[0]["DETAIL_TEXT"],
             "PROPERTY_VALUES" => array(
                 'UF_USER' => array(
                     $results[0]["PROPERTY_UF_USER_VALUE"]
                 ),
                 'UF_SPEAKER' => array(
                     $results[0]["PROPERTY_UF_SPEAKER_VALUE"]
                 ),
                 'UF_PARTICIPANTS_COUNT' => array(
                     $results[0]["PROPERTY_UF_PARTICIPANTS_COUNT_VALUE"]
                 ),
                 'UF_EVENT_LENGTH' => array(
                     $results[0]["PROPERTY_UF_EVENT_LENGTH_VALUE"]
                 ),
                 'UF_STATUS' => array(
                     $results[0]["PROPERTY_UF_STATUS_ENUM_ID"]
                 ),
                 'UF_VKSREQUEST_SENT' => array(
                     $results[0]["PROPERTY_UF_VKSREQUEST_SENT_VALUE"]
                 ),
                 'UF_VKSEMAIL_IDS' => array(
                   is_null($results[0]["PROPERTY_UF_VKSEMAIL_IDS_VALUE"]) ? $key : $results[0]["PROPERTY_UF_VKSEMAIL_IDS_VALUE"] .', ' . $key
                 ),
                 'UF_VKSLINK' => array(
                     $value['url']
                 ),
                 'UF_ROOM_ID' => array(
                     $results[0]["PROPERTY_UF_ROOM_ID_VALUE"]
                 ),
             ),
             "ACTIVE" => "Y",
         );

         // Обновляем "встречу" с ID из письма в БД — добавляем туда ссылку на ВКС
         $eventId = $value['intranet_id'];
         $iblockElementObject = new CIBlockElement;
         $res = $iblockElementObject->Update($eventId, $arFields);

         // Вычисляем ID пользователя-заявителя и далее получаем данные о нем для отправки письма со ссылкой на ВКС
         if ($user[0] === 'U') { // Если пользователь корректно занесен в формате компонента main.user.link, вычисляем его ID
             $user_id = substr($results[0]["PROPERTY_UF_USER"], 1);
             // Запрашиваем данные пользователя из БД
             $dbUser = CUser::GetByID($user_id);
             if ($arUser = $dbUser->GetNext()) {
                 // Отправляем письмо со ссылкой на ВКС пользователю-заявителю
                 // Формируем переменные для почтового шаблона
                 $letterFields['EVENT_ID'] = $results[0]["ID"];
                 $letterFields['USER_EMAIL'] = $arUser['EMAIL'];
                 $letterFields['DATE_FROM'] = $results[0]["DATE_ACTIVE_FROM"];
                 $letterFields['VKSLINK'] = $vks_link;
                 $msg = CEvent::sendImmediate('VKSLINK', SITE_ID, $letterFields, "N", EMAIL_TEMPLATE);
             }
         }
     }
 } else {
     // Если подключение к IMAP-серверу не удалось, запишем в лог сообщение
     AddMessage2Log("Не удалось подключиться к IMAP-серверу.", "VKSLink");
 }

 // Обязательная часть агента Битрикса — функция агента должна возвращать свое имя или, точнее, PHP-код своего запуска
 return "VKSLink();";
}

Файл vkslink.php подробно откомментирован, но на отдельные его части стоит обратить внимание.

После успешного подключения к IMAP-серверу нужно сформировать запрос, чтобы почтовый сервер вернул нам определенные сообщения, а не все. Для этого используется PHP-функция imap_search(), в ней нас интересует поисковая строка $search_query. 

В примере она задана как UNSEEN TEXT https://vksservice.ru, что означает непрочитанные сообщения, содержащие текст https://vksservice.ru. Такой выбор гарантирует, что не будут обрабатываться письма, которые уже были в выдаче. Параметры поиска, возможно, придется скорректировать под конкретную задачу и ваш почтовый сервер. Например, Microsoft Exchange не поддерживает все стандарты IMAP и параметры могут не работать или называться иначе. Все возможные ключевые слова поисковой строки можно посмотреть в документации PHP по функции imap_search(). Приведу наиболее интересные из них в таблице.

При работе с почтовыми сообщениями на IMAP-сервере учитывайте, что сообщения передаются в разных кодировках и могут быть наслоения MIME, UTF и других способов передачи сложного текста, особенно кириллицы. Иногда письма содержат много дополнительной нагрузки и отличаются от писем из примеров выше. В приведенном файле vkslink.php данные проблемы решаются совместным использованием функций imap_headerinfo() вместо распространенной imap_fetchheader()) и imap_utf8. 

Если возникнут проблемы, когда нужные метки не находятся или поиск не выдает нужные письма, поэтапно просмотрите приходящие с почтового сервера ответы и данные.

Пробуйте и дорабатывайте систему

Для некоторых интеграций вполне подойдут забытые, но по-прежнему эффективные способы взаимодействия между системами, где в качестве брокера сообщений может выступать электронная почта.

Битрикс-агент — это просто PHP-функция, подключение к серверу IMAP и парсинг почтовых сообщений можно легко перенести на любой другой PHP-фреймворк или CMS.

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники