Создание UI на PHP для IBM Lotus Domino (исходники)

Андрей Кувшинников, главный разработчик Domino-приложений, Botstation

В данной статье вы узнаете, как взаимодействовать с базами данных IBM Lotus Domino из Web-приложений, созданных на языке программирования PHP. Вы также узнаете, как обращаться к Domino-приложениям из PHP-страниц, используя COM-объект, интерфейс прикладного программирования IBM Lotus Notes (application programming interface - API) и XML. В разделе "Загрузка" приведены ссылки на примеры кода для каждого метода. Для XML-метода можно также загрузить пример приложения с простым Web-интерфейсом к почтовой базе IBM Lotus Domino (см. рисунок 1).

Рисунок 1. Пример пользовательского интерфейса на PHP к почтовой базе данных
Рисунок 1. Пример пользовательского интерфейса на PHP к почтовой базе данных

PHP - это мощный встроенный язык программирования. Он свободно доступен для использования и популярен при разработке динамических Web-приложений. PHP-код не компилируется; он интерпретируется во время выполнения. Большая часть синтаксиса позаимствована из языков программирования C, Java и Perl. Можно добавить PHP как CGI-механизм (Common Gateway Interface) на большинстве Web-серверов. Он работает на операционных системах Microsoft Windows и Linux. Кроме того, более 50% компаний, предоставляющих Web-хостинг, имеют сервера, работающие с PHP. Из-за такого широкого распространения технологии вы просто не можете оставаться не знакомыми с ней.

Работа с PHP

PHP-код включается в Web-страницы при помощи специальных тегов. PHP-код начинается с тега <php и заканчивается тегом ?>:

<html>
 <body>
   <?php echo "Hello World!"; ?>
 </body>
</html>

Web-сервер интерпретирует этот простой пример и преобразовывает его в следующий HTML-код:

<html>
 <body>
   Hello World!
 </body>
</html>

Web-страницы с PHP могут взаимодействовать с базами данных Domino несколькими способами. Выбор метода интеграции зависит от используемой для запуска PHP операционной системы, имеющихся ограничений системы защиты между серверами PHP и Domino, и того, что вам разрешено устанавливать на PHP-сервере. В данной статье рассматриваются следующие методы:

  • COM-объекты
  • The Notes API
  • XML через Web

Автономное выполнение PHP-кода

Хотя PHP изначально предназначался для работы на Web-сервере, а не для автономных приложений, его можно запустить из командной строки как программу сценариев командного процессора, аналогичную Microsoft Visual Basic Scripting Edition (VBScript). Эта возможность может быть полезна для автономного тестирования перед размещением файлов на Web-сервере.

Доступ к Lotus Domino

Для доступа к базе данных Domino доступны три метода: через COM-объекты, через Notes API и через XML.

Доступ к Lotus Domino с использованием Domino COM-объектов

Самым простым и наиболее быстро реализуемым методом доступа к информации, хранящейся в базах данных Domino, являются Domino COM-объекты. Использование COM из PHP не намного сложнее, чем из LotusScript. В следующем примере кода показано, как распечатать значения полей всех документов в виде.

<?php
//Инициализация сессии Lotus Notes 
$session = new COM( "Lotus.NotesSession" );
$session->Initialize();

//Отобразить имя текущего пользователя Notes 
print "Current user: " . $session->CommonUserName . "\n\n";

//Получить дескриптор базы данных 
$db = $session->getDatabase( "", "mailtest.nsf" );

//Получить дескриптор вида, используя предварительно полученный
//дескриптор базы данных.
//Обратите внимание на то, что зарезервированный символ в имени вида
//должен быть предварен знаком $view = $db->getView( "(\$Drafts)" );

//Получить первый документ в виде, используя предварительно
//полученный дескриптор вида
$doc = $view->getFirstDocument();

//Выполнить цикл для обработки всех документов, присутствующих в виде
while (is_object($doc)) {

//Получить дескриптор для поля под названием "Subject"
$field=$doc->GetFirstItem("Subject"); 

//Получить текстовое значение поля
$fieldvalue=$field->text;

//Показать значение поля
print "Subject: " . $fieldvalue . "\n";

//Получить следующий документ 
$doc = $view->getNextDocument($doc);
}
//Освободить объект session 
$session = null;
?>

На рисунке 2 показана информация, выводимая при выполнении этого COM-объекта в командной строке.

Рисунок 2. Результат выполнения кода COM-объекта в командной строке
Рисунок 2. Результат выполнения кода COM-объекта в командной строке

Использовать COM-объекты легко, но существуют некоторые препятствия, ограничивающие применение COM. Главным препятствием является необходимость установки клиента Lotus Notes на PHP-сервере. Если вы работаете с Web-хостинговой компанией для размещения PHP-приложений, не просто убедить ее установить Notes-клиент только для вашего PHP Web-сайта. Если вы имеете собственный PHP-сервер или, возможно, серверы PHP и Domino на одном и том же компьютере, у вас не должно возникнуть проблем в настройке COM-доступа к серверу Domino.

Второй проблемой, ограничивающей применение COM, является поддержка COM-объектов только на платформе Windows. Принимая во внимание, что большинство PHP-серверов работают на Linux-платформе, это требование значительно уменьшает количество доступных серверов, способных разместить ваше COM-решение, даже если вы имеете полный административный контроль над этими серверами.

Доступ к Lotus Domino через API

Альтернативой COM-доступу является использование Lotus Notes/Domino C API. Теоретически, вы можете использовать это решение на операционных системах, отличных от Windows. Мы рекомендуем это только программистам, имеющим хороший опыт работы с языками программирования C/C++ (если имеется причина не использовать COM-интерфейс или XML). Предоставляемая в примере API-решения функциональность ограничена, и вы должны написать свой собственный код на языке C для выполнения большинства задач.

Пример библиотеки предоставляет хороший пример для создания PHP-расширений. Он поставляется только в исходном коде, и для компилирования его у вас должен иметься компилятор Visual C и набор инструментальных программ Domino C API. Для удобства мы скомпилировали исходный код в DLL-файл и включили ссылку на него в раздел "Загрузка". Активировать Notes-расширение можно, добавив следующую строку в конфигурационный файл php.ini:

extension=php5_notes.dll

Следующий пример кода показывает, как использовать эту методику после создания требуемых DLL.

<?php

$dbpath="mailtest.nsf";
$searchword="demo";

//Выполнить полнотекстовый поиск в указанной базе данных.
//Результат - массив идентификаторов Note ID
$search=notes_search ( $dbpath, $searchword );

If($search[0]==NULL){
die("\nNo results were found for search word '$searchword'\n");
}

//Показать количество найденных документов 
print "\nFound " .count($search) . " results for search word '$searchword'\n";

?>

На рисунке 3 показаны результаты выполнения API-кода в командной строке.

Рисунок 3. Результат выполнения API-кода
Рисунок 3. Результат выполнения API-кода

Доступ к Lotus Domino с использованием XML

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

В XML-решении, рассматриваемом в данной статье, используются технические приемы, которые наверняка будут работать в большинстве конфигураций. В качестве примера создадим PHP-интерфейс к стандартной почтовой базе данных Domino. Единственным изменением в среде Domino является LotusScript-агент для просмотра полей из почтовых документов. Этот же агент используется для синтаксического анализа XML, для создания и передачи почтовых сообщений.

Для просмотра папки Inbox почтовой базы данных выполните следующие действия:

  1. Запросите у пользователя его или ее имя и пароль в Notes.
  2. Передайте запрос login в базу данных Domino, используя предоставленное имя пользователя и пароль (см. рисунок 4).

    Рисунок 4. Экран входа в пример PHP-приложения
    Рисунок 4. Экран входа в пример PHP-приложения

  3. Из ответа получите сессионные куки (session cookie). Они необходимы для последовательных запросов к базе данных Domino.
  4. Запросите у сервера Domino XML-код папки Inbox, используя URL-команду ReadViewEntries. Эта команда возвращает содержимое вида, представленное в XML-формате вместо HTML. Ваша подлинность указывается серверу Domino путем добавления в заголовок запросов сессионных куки.
  5. Для принятого содержимого вида в формате XML примените PHP XML-анализатор, чтобы найти документы и поля.
  6. После обработки XML-анализатором всего документа сформируйте HTML-код и выведите его в браузер. Чтобы сделать почтовое сообщение нажимаемой ссылкой, используйте атрибут UNID для каждого узла документа.

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

  • Сервер Domino должен быть версии 6 или выше; в противном случае вы не сможете использовать в LotusScript функцию XML-анализа. Этот LotusScript-агент обсуждается более подробно позднее.
  • PHP Web-сервер должен разрешать исходящие Web-запросы.
  • Сервер Domino должен быть доступен из PHP Web-сервера либо по Интернет, либо по интранет-сети.

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

PHP-функции для чтения Web-файлов Domino

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

  • file_get_contents(). Эта функция проста в использовании и поддерживает как GET-запросы, так и POST-запросы. При помощи file_get_contents() вы получаете весь Web-файл сразу в виде строки.
  • fopen(). Этот метод читает Web-файл кусками.
  • file(). Этот метод читает Web-файл полностью и разделяет содержимое файла по строкам. Он занимает как бы промежуточное положение между file_get_contents() и fopen().
  • fsockopen(). Эта функция имеет наибольший шанс активизации на Web-сервере. Основным отличием ее от других функций является то, что с fsockopen() вы должны создать весь Web-запрос самостоятельно вместо установки различных параметров.
  • CURL. Библиотека Client URL Library (CURL) обладает самой богатой функциональностью. Используя CURL, вы можете подключаться и взаимодействовать со многими различными типами серверов по протоколам различных типов. CURL в настоящее время поддерживает HTTP, HTTPS, FTP, Gopher, Telnet, DICT, File и LDAP. Она также поддерживает прокси, куки и аутентификацию. К сожалению, CURL часто запрещается компаниями, предлагающими Web-хостинг.

Примечание: В примере приложения в данной статье вы встретите исключительно функцию file_get_contents().

Получение сессионных куки Domino является важной частью процесса, и можно использовать аналогичный код в других Domino-приложениях. Например, можно извлечь куки для входа на сервер IBM Lotus Sametime, используя STLinks.

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

Метод POST используется вместо GET потому, что URL-адреса метода GET записываются в log-файл Domino, и любой, кто имеет доступ к нему, может увидеть имя пользователя и пароль в виде обычного текста. При использовании метода POST имена пользователей и их пароли не являются частью URL; следовательно, они не видимы в обычных log-файлах.

Сессионные куки Domino можно найти в заголовке ответа Set-Cookie. Сессионные куки называются либо DomAuthSessId, либо LtpaToken. Имя LtpaToken используется, если сервер Domino настроен на единую регистрацию (single sign-on - SSO). Вам не нужно заботиться о том, как называются куки; вы просто сохраняете всю строку.

В следующем примере кода показано, как извлечь куки.

    $req="username=john+doe&password=john123";
$opts = array(
  "http"=>array(
    "method"=>"POST",
    "content" => $req,
    "header"=>"Accept-language: en\r\n" . 
    "User-Agent: Mozilla/4.0 (compatible; 
           MSIE 6.0; Windows NT 5.1)\r\n"
    )
);
$context = stream_context_create($opts);
if (!($fp = fopen("http://server.com/maildb.nsf?login", 
                  "r", false, $context))) {
 die("Could not open login URL");
}
$meta = stream_get_meta_data($fp);
for ($j = 0; isset($meta["wrapper_data"][$j]); $j++)
{
 if (strstr(strtolower($meta["wrapper_data"][$j]), 'set-cookie'))
{
$cookie = substr($meta["wrapper_data"][$j],12); 
break;
}
}
fclose($fp);
$_SESSION["DominoCookie"]=$cookie;

В строке кода $req="username=john+doe&password=john123" указывается корректное имя пользователя и пароль, которые будут использоваться для регистрации на сервере Domino. Затем настраиваются дополнительные параметры, такие как тип метода POST и другие заголовки. После этого к исходящему HTTP-запросу применяются ваши дополнительные параметры:

fopen("http://server.com/mydb.nsf?login", "r", false, $context)

Из полученного от сервера Domino ответа извлекаются все заголовки при помощи функции stream_get_meta_data($fp), а также выполняется цикл по всем заголовкам, пока не найдется заголовок, содержащий строку Set-Cookie. После этого куки сохраняются путем записи в сессионную переменную:

$_SESSION["DominoCookie"]=$cookie

$_SESSION хранит значения куки до закрытия Web-браузера.

Теперь нужно использовать эти куки в следующем запросе, извлекающем вид/папку Inbox из почтовой базы данных Domino.

$opts = array(
  'http'=>array(
    'method'=>"GET",
    'header'=>"Accept-language: en\r\n" .
    "User-Agent: Mozilla/4.0 (compatible;
          MSIE 6.0; Windows NT 5.1)\r\n" .
    "Cookie: " . $_SESSION["DominoCookie"] . "\r\n"
)
);
$context = stream_context_create($opts);
$xml = file_get_contents(
"http://server.com/maildb.nsf/(\$Inbox)?ReadViewEntries", 
          false, $context);

В этот раз для загрузки Web-страницы используется метод GET вместо метода POST. Этот метод указывается строкой 'method'=>"GET" в массиве HTTP-параметров. Вместо чтения только заголовков при помощи функции file_get_contents(URL, false, context) читается ответ полностью. Результатом этой операции является длинная XML-строка, содержащая все столбцы из вида Inbox. К заголовку добавляются сессионные куки:

"Cookie: " . $_SESSION["DominoCookie"] . "\r\n"

После получения XML-данных можно обработать их в PHP XML-анализаторе. Затем создается ваш собственный пользовательский интерфейс для вида Inbox.

PHP-функции для обработки XML

В PHP, также как и в языках программирования LotusScript и Java, существует два способа обработки XML-кода: SAX (Simple API for XML) и DOM (Document Object Model). SAX - это ориентированная на события программная модель. Она включена и разрешена по умолчанию на большинстве PHP Web-серверов. Расширение DOM должно быть активировано администратором Web-сервера. Хотя с DOM намного легче работать, в наших примерах используется SAX-анализатор, поскольку мы хотим достичь максимальной совместимости с большинством PHP-конфигураций.

Аналогичные обработке Domino-видов методики могут (с небольшими изменениями) быть использованы для работы с другими источниками XML-данных, такими как RSS-фиды и даже Web-службы. Следующий пример кода отображает XML в виде Inbox, содержащем один документ.

<?xml version="1.0" encoding="UTF-8"?>
<viewentries toplevelentries="6">
<viewentry position="1" 
unid="38B16601EC8B42BAC12571440068335C" noteid="8FE" siblings="6">
	<entrydata columnnumber="0" name="$109">
		<text></text>
	</entrydata>
	<entrydata columnnumber="1" name="$86">
		<numberlist><number>0</number><number>0</number>
		</numberlist>
	</entrydata>
	<entrydata columnnumber="2" name="$93">
		<text>Donald Duck</text>
	</entrydata>
	<entrydata columnnumber="3" name="$102">
		<number>178</number>
	</entrydata>
	<entrydata columnnumber="4" name="$70">
		<datetime dst="true">20060402T205929,52+02</datetime>
	</entrydata>
	<entrydata columnnumber="5" name="$99">
		<datetime dst="true">20060402T205929,52+02</datetime>
	</entrydata>
	<entrydata columnnumber="6" name="$106">
		<number>823</number>
	</entrydata>
	<entrydata columnnumber="7" name="$97">
		<number>0</number>
	</entrydata>
	<entrydata columnnumber="8" name="$73">
		<text>seen Mickey?</text>
	</entrydata>
</viewentry>
</viewentries>

Как видите, документы в XML-источнике представлены тегом <viewentry>, а столбцы (поля) представлены тегами <entrydata>. Universal ID документа доступен как атрибут узла <viewentry>.

Все, что нужно сделать, - определить способ программно найти значения между этими тегами для получения всех нужных данных. Звучит просто и также просто реализуется. Прежде всего, инициализируйте объект XML-анализатора, используя функцию xml_parser_create:

$xml_parser = xml_parser_create();

Затем настройте функции обратного вызова, которые отвечают за обработку начальных и конечных элементов XML. Эти функции указываются процессору при помощи функций xml_set_element_handler и xml_set_character_data_handler. Каждая функция принимает дескриптор анализатора и имена функций обратного вызова:

xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, "textData");

Необходимо добавить функции startElement, endElement и textData в ваш PHP-код:

function startElement($parser, $name, $attributes) {
switch($name) {
  case "VIEWENTRY":
    $main = "VIEWENTRY";
    if($attributes["UNID"]!=""){
      $current_doc=$attributes["UNID"];
      $unids[$doc_counter]=$current_doc;
    }
    break;
  case "ENTRYDATA":
    $main = "ENTRYDATA";
    if($attributes["COLUMNNUMBER"]!=""){
      $current_column=$attributes["COLUMNNUMBER"];
    }
    break;
  case "DATETIME":
    $main = "DATETIME";
    break;
  default:
    break;
 } }

function endElement($parser, $name) {
if ($name == "VIEWENTRY") {
  $doc_counter++;  //increase document counter by 1
}
$current_column="";
}


function textData($parser, $data) {
switch($main) {
  case "ENTRYDATA":
    if (isset($doc_coll[$current_doc][$main])) {
      $doc_coll[$current_doc][$main][$current_column] .= "$data";
    } else {
      $doc_coll[$current_doc][$main][$current_column] = "$data";
    }
    break;
  case "DATETIME":
    if (isset($doc_coll[$current_doc][$main])) {
      $doc_coll[$current_doc][$main] .= "$data";
    } else {
      $doc_coll[$current_doc][$main] = "$data";
    }
  break;
  case "VIEWENTRY":
   break;
} }

Итак, давайте посмотрим, как это работает. Когда XML-анализатор проходит по XML-источнику и находит начало нового элемента, он активизирует функцию startElement, которая была указана как функция обратного вызова. В данном случае таким начальным элементом является <ENTRYDATA>. Теперь вы знаете, что текущим обрабатываемым элементом является новый столбец, поэтому можно установить переменную $current_column в номер строки. В данном случае столбцы представляют поля:

case "ENTRYDATA":
    $main = "ENTRYDATA";
    if($attributes["COLUMNNUMBER"]!=""){
      $current_column=$attributes["COLUMNNUMBER"];
    }
    break;

После этого анализатор находит текст внутри узла <ENTRYDATA> и передает его в функцию textData, которая была указана как функция обратного вызова. Цель данной функции - разрешить вам заполнить массив значениями, находящимися внутри узла <ENTRYDATA>:

switch($main) {
  case "ENTRYDATA":
    if (isset($doc_coll[$current_doc][$main])) {
      $doc_coll[$current_doc][$main][$current_column] .= "$data";
    } else {
      $doc_coll[$current_doc][$main][$current_column] = "$data";
    }
    break;

В примере XML-источника строкой кода $doc_coll[$current_doc][$main][$current_column] = "$data" является:

$doc_coll["38B16601EC8B42BAC12571440068335C "]["ENTRYDATA"]["8"] = "seen Mickey?"

Это означает, что восьмым столбцом в виде Inbox для документа с Universal ID 38B16601EC8B42BAC12571440068335C является "seen Mickey?", что, кстати, является темой почтового сообщения.

XML-анализатор "умный" и знает, что если элемент имеет начало, где-то должен быть конец. Поэтому, когда он находит конец элемента, он сигнализирует вам, активизируя функцию endElement (все верно - это та же функция, которую вы указали в качестве второй функции обратного вызова). В ней номер столбца сбрасывается, поскольку он больше не нужен. Также проверяется, нужно ли увеличить счетчик документов.

В качестве счетчика для массива, хранящего идентификаторы Universal ID, используется переменная $doc_counter. Вы должны сохранять Universal ID вручную, для того чтобы добавить ссылки в почтовые документы. Когда вы знаете, что элемент VIEWENTRY завершился, это означает, что на подходе новый Universal ID, и лучше увеличить текущий индекс массива на 1 для сохранения массивов $doc_coll и $unids неповрежденными:

if ($name == "VIEWENTRY") {
  $doc_counter++; 
}
$current_column="";

Если вы загрузите пример приложения и запустите на вашем собственном сервере, ваша версия почтовой базы данных Domino может иметь другое расположение столбцов в видах почтовой системы. Если это так, нужно скорректировать номера столбцов в PHP-коде (docfunctions.php).

Пример PHP-приложения и LotusScript-агента

В данном разделе рассматривается Domino-агент ProcessDocWebAction (включенный как часть примера приложения), который вы можете загрузить. Агент программируется на LotusScript и использует DOM для анализа входящих XML-запросов. Поскольку он использует новые классы XML-сценариев, можно его запускать на серверах Domino версий 6 и 7, но не на серверах версии 5.

Запросы, которые обрабатывает этот агент, формируются в примере PHP-приложения и могут быть следующих типов:

  • Создать и передать почтовое сообщение
  • Сохранить существующий почтовый документ
  • Сохранить новый почтовый документ как черновую копию
  • Запросить информацию о месторасположении пользовательского файла
  • Фиктивный Ping-запрос для проверки активности пользователя

Для развертывания агента выполните следующие действия:

  1. Скопируйте весь текст из файла xmlagent.lss.
  2. Создайте нового агента в базе данных по вашему выбору.
  3. Установите свойства агента в "Run as Web user" а target - в None.
  4. Вставьте скопированный код в этот новый агент.
  5. Назовите агента ProcessDocWebAction и сохраните его.
  6. В PHP-файле (dominomail.php) укажите URL месторасположения базы данных, в которой размещен агент.

Одним из основных приоритетов при создании примера PHP-приложения была гарантия его работы на всех стандартных почтовых базах данных без изменения дизайна. Вот почему это приложение не требует каких-либо изменений в дизайне почтовой базы данных. Вы можете разместить агент для обработки XML не в пользовательской почтовой базе данных, и этот же агент может быть использован для обслуживания всех пользователей.

Когда пользователь выполняет процесс аутентификации первый раз, PHP-приложение после получения сессионных куки запрашивает у этого агента месторасположение пользовательского почтового файла. Агент знает, кто его запустил, поскольку он выполняется с активизированным свойством "Run as Web user". После этого начального запроса путь к пользовательской базе данных сохраняется для повторного использования до закрытия Web-браузера или завершения времени жизни сессии.

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

Заключение

Если вы являетесь разработчиком Domino-приложений или администратором сервера Domino, и ваш сервер недоступен из Интернета, вы все равно можете предоставить пользователям способ доступа к данным, хранящимся в базах данных Domino, и разрешить им читать и писать почтовые сообщения. Используя приемы работы с XML, описанные в данной статье и примененные в примере приложения, вы можете легко реализовать Domino-аутентификацию в ваших собственных PHP-приложениях для проверки регистрационной информации в Domino Directory.

Здесь нет переопределения прав доступа пользователей; пользователь использует свои полномочия в каждой операции. Это гарантирует недоступность для пользователей неавторизованных данных. Для дальнейшего повышения защищенности вы можете реализовать регистрацию неудачных попыток входа в систему.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=4843