为 IBM Lotus Domino 构建基于 PHP 的用户界面

了解如何通过用 PHP 编程语言创建的 Web 应用程序与 Lotus Domino 交互。学习利用 COM 对象、Lotus Notes API 及 XML 通过 PHP 页面访问 Domino 应用程序的方法。

Andrei Kouvchinnikov, Domino 高级设计师, Botstation Technologies

Andrei Kouvchinnikov 是一名获得认证的 Domino 高级设计师及管理员。他的从业经验包括运行于多种平台之上的 Lotus Domino 应用程序的整个生命周期的开发,以及 QuickPlace 和 Sametime 应用程序开发。他从 OS2 的 R4.5 起就开始在 Lotus Domino 平台上工作。您可以通过 andrei@botstation.com 联系 Andrei。



2006 年 5 月 29 日

在本文中,您将了解如何通过用 PHP 编程语言创建的 Web 应用程序与 IBM Lotus Domino 数据库交互。还将学习利用 COM 对象、IBM Lotus Notes 应用程序编程接口 (API) 及 XML 通过 PHP 页面访问 Domino 应用程序的方法。末尾处的下载部分中包含各方法的示例代码。对于 XML 方法,还将提供一个带有 IBM Lotus Domino 邮件数据库简单 Web 界面的示例应用程序供下载(参见图 1)。

图 1. 邮件数据库 PHP 用户界面示例
邮件数据库 PHP 用户界面示例

PHP 是一种强大的嵌入式脚本语言,可免费使用,它是开发动态 Web 应用程序的流行工具。PHP 代码不经过编译,而是在运行时加以解释。其语法大多借鉴自 C、Java 和 Perl 编程语言。可将 PHP 作为通用网关接口(Common Gateway Interface,CGI)引擎添加到绝大多数 Web 服务器中。甚至在 Microsoft Windows 和 Linux 操作系统中也可以正常工作。此外,超过 50% 的 Web 托管公司都拥有启用了 PHP 的服务器。PHP 技术的应用如此广泛,不熟悉这种技术显然损失不小。

使用 PHP

借助特殊标签即可在 Web 页面中包含 PHP 代码。所有的 PHP 代码均以 <php 标签开头,以 ?> 标签结束:

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

Web 服务器解释这一简单示例,并将其转换为以下 HTML 输出:

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

PHP Web 页面可通过多种方式与 Domino 数据库交互。集成方法的选择取决于运行 PHP 的操作系统、PHP 与 Domino 服务器之间存在的安全性约束,以及允许在 PHP 服务器上安装的内容。本文介绍的方法为:

  • COM 对象
  • Notes API
  • 利用 XML 通过 Web 交互

脱机运行 PHP 代码

尽管 PHP 主要用于在 Web 服务器而非单机应用程序上运行,但您可将其作为 Microsoft Visual Basic Scripting Edition (VBScript) 那样的 shell 脚本工具,通过命令行运行 PHP。将文件放置在 Web 服务器之前进行脱机测试时,这项特性可能非常有用。

访问 Lotus Domino

访问 Domino 数据库的方法有三种:通过 COM 对象、通过 Notes API 和通过 XML。

使用 COM 对象访问 Lotus Domino

要访问存储于 Domino 数据库中的信息,最简便、最迅速的方法就是使用 Domino COM 对象。在 PHP 中使用 COM 并不比在 LotusScript 中困难。以下代码示例展示了如何在一个视图中输出所有文档的字段值。

<?php
//Initiate Lotus Notes session
$session = new COM( "Lotus.NotesSession" );
$session->Initialize();

//Show the name of the current Notes user
print "Current user: " . $session->CommonUserName . "\n\n";

//Get database handle
$db = $session->getDatabase( "", "mailtest.nsf" );

//Get view handle using previously received database handle.
//Note that the reserved character in the view name must be \-escaped 
$view = $db->getView( "(\$Drafts)" );

//Get first document in view using previously received view handle
$doc = $view->getFirstDocument();

//Loop until all documents in view are processed
while (is_object($doc)) {

//Get handle to a field called "Subject"
$field=$doc->GetFirstItem("Subject"); 

//Get text value of the field
$fieldvalue=$field->text;

//Show the value of the field
print "Subject: " . $fieldvalue . "\n";

//Get next document in the view
$doc = $view->getNextDocument($doc);
}
//Release the session object
$session = null;
?>

图 2 展示了通过命令行运行此 COM 对象的输出结果。

图 2. 在命令提示符中运行 COM 对象代码的结果
在命令提示符中运行 COM 对象代码的结果

使用 COM 对象比较简单,但一些缺点使这种方法具有局限性。主要缺点就是 PHP 服务器上必须安装有 Lotus Notes 客户机软件。如果您的 PHP 应用程序由 Web 托管公司托管,恐怕很难说服托管公司专门为您的 PHP Web 站点安装 Notes 客户机。如果您拥有自己的 PHP 服务器或者 PHP 服务器和 Domino 服务器位于同一台计算机上,那么设置 Domino 服务器的 COM 访问就没有什么问题了。

限制 COM 使用的另一问题就是 COM 对象只在 Windows 平台上受支持。考虑到绝大多数 PHP 服务器都运行在 Linux 平台上,这一需求会大大减少可选择用于托管 COM 解决方案的服务器数量,即便您有着那些服务器的完全管理控制权限。

通过 API 访问 Lotus Domino

COM 访问的替代方案之一就是使用 Lotus Notes/Domino C API。理论上,您可在 Windows 以外的操作系统上使用该解决方案。我们的建议是,只让具备大量 C/C++ 编程经验的开发人员采纳这种方案(如果您有不使用 COM 接口或 API 的理由)。示例 API 解决方案提供的功能有限,基本上,您必须编写自己的 C 语言代码来完成大多数任务。

示例库提供了创建 PHP 扩展的出色例子。但其中仅包含源代码,您必须使用 Visual C 编译器和 Domino C API 工具包来编译代码。为方便起见,我们已将源代码编译成一个 DLL 文件,并将此文件包含于下载部分中。在 php.ini 配置文件中添加以下代码行即可激活 Notes 扩展:

extension=php5_notes.dll

以下代码示例展示了在获得所需 DLL 后使用此技术的方法。

<?php

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

//Perform full text search in the specified database.
//The result is an array of Note IDs
$search=notes_search ( $dbpath, $searchword );

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

//Show number of found documents
print "\nFound " .count($search) . " results for search word '$searchword'\n";

?>

图 3 展示了通过命令行运行 API 代码的输出结果。

图 3. API 代码输出
API 代码输出

使用 XML 访问 Lotus Domino

使用 XML 与 Domino 数据库交互是最和谐的解决方案。无需作出任何特殊调整,也不需要在 PHP Web 服务器上安装任何特殊工具,且在所有操作系统中均有效。我们认为,该解决方案在绝大多数情况下均为最佳方案。

本文的 XML 解决方案所使用的技术适用于多数配置。作为示例,您将创建一个标准 Domino 邮件数据库的 PHP 界面。在 Domino 环境中进行的惟一修改就是用来查看邮件文档字段的 LotusScript 代理。您将使用同一代理解析 XML,以创建和发送邮件。

按以下步骤进行操作即可查看邮件数据库的 Inbox 文件夹:

  1. 询问用户的 Notes 用户名和口令。
  2. 使用所提供的用户名和口令向 Domino 数据库发送一条登录请求(参见图 4)。
    图 4. PHP 示例应用程序的登录屏幕
    PHP 示例应用程序的登录屏幕
  3. 从登录响应中获得会话 cookie。(在向 Domino 数据库发出的后续请求中需要使用此 cookie。)
  4. 使用 ReadViewEntries URL 命令向 Domino 服务器请求 Inbox 文件夹的 XML 源。此命令返回 XML 格式的视图内容,而非 HTML 格式。您的会话 cookie 附于请求头上,以告知 Domino 您的身份。
  5. 在接收到的 XML 格式视图内容上,使用 PHP 的 XML 解析器查找文档和字段。
  6. XML 解析器处理完整个文档后将构造 HTML 代码,并输出到浏览器。为使邮件消息显示为可单击的链接,请使用各文档节点的 UNID 属性。

要访问 Domino 数据库,您需要具备从 Domino 服务器中获取 Web 页面并解析 XML 的功能。为使演示应用程序能正常运行,还必须具备以下条件:

  • Domino 服务器为版本 6 或更新版本,否则无法使用 LotusScript 的 XML 解析功能。(后文中将详细介绍此 LotusScript 代理。)
  • PHP Web 服务器允许外发 Web 请求。
  • 可通过 Internet 或内部网从 PHP Web 服务器处访问 Domino 服务器。

注意:若您无法通过 Internet 访问 Domino 服务器,则可能需要调整防火墙配置,允许来自 PHP 服务器的数据流。


读取基于 Web 的 Domino 文件的 PHP 函数

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。它还支持代理、cookies 及身份验证。遗憾的是,Web 托管公司往往会禁用 CURL。

注意:本文的示例应用程序仅使用 file_get_contents() 函数。

获得 Domino 会话 cookie 是此流程的重要部分,您可将相同的代码用于其他与 Domino 相关的应用程序中。例如,您可以使用 STLinks 检索 cookie,以登录到 IBM Lotus Sametime 服务器。

通过向 Domino 服务器发送 POST 请求获取会话 cookie。在该请求中,需要将您的用户名和口令包含在内。服务器的响应包括一个 cookie,供您在后续向 Domino 服务器发送请求时使用。通过包含会话 cookie,Domino 服务器会将您的脚本视为最近登录过、现正访问数据库的实际人类用户。

之所以使用 POST 方法而不是 GET 方法,是因为 GET 方法的 URL 会被记录到 Domino 日志中,任何访问日志的用户都能看到您的用户名和口令 —— 而且是明文。使用 POST 方法,用户名和口令不被作为 URL 的一部分,因此也不会显示在常规日志中。

您可在 Set-Cookie 响应头中找到 Domino 会话 cookie。会话 cookie 名为 DomAuthSessId 或 LtpaToken。当 Domino 服务器配置为单点登录 (SSO) 时使用 LtpaToken 作为名称。实际上,您不必在意 cookie 的名称,只需保存整个 cookie 字符串即可。

以下代码示例展示了如何检索 cookie。

    $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)

使用 stream_get_meta_data($fp) 函数从 Domino 服务器返回的响应中获取所有头,遍历直至找到包含 Set-Cookie 字符串的头。随后,将 cookie 存储在会话变量中来保存:

$_SESSION["DominoCookie"]=$cookie

$_SESSION 在您关闭 Web 浏览器之前一直保存 cookie 值。

现在,将 cookie 应用到您的下一个请求中,该请求从 Domino 邮件数据库中检索 Inbox 视图/文件夹。

$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);

这次使用了 GET 方法而不是 POST 方法来下载 Web 页面。该方法在 HTTP 选项数组中的 'method'=>"GET" 代码行中指定。使用 file_get_contents(URL, false, context) 函数读取整个响应,而不仅仅是读取头。此操作的结果是一个 XML 长字符串,它包含 Inbox 视图中的所有列。通过以下代码将会话 cookie 附在头上:

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

获取 XML 数据后,就可以在 PHP XML 解析器中处理这些数据了。接下来将为 Inbox 视图创建您自己的用户界面。


处理 XML 的 PHP 函数

与 LotusScript 和 Java 编程语言相同,在 PHP 中处理 XML 代码的方法有两种:Simple API for XML (SAX) 及 Document Object Model (DOM)。SAX 是一种基于事件的编程模型,绝大多数 PHP Web 服务器在默认情况下都包含并启用了 SAX。Web 服务器管理员必须激活 DOM 扩展。尽管 DOM 更易于编程,但在这些示例中,我们将使用 SAX,以便最大化与大多数 PHP 配置的兼容性。

处理 Domino 视图的技术只需略加更改即可用于处理其他 XML 源,如 RSS feed(RSS 提要)乃至 Web 服务。如下代码示例展示了包含一个文档的 Inbox 视图的 XML 源。

<?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_parser_create 函数启动 XML 解析器对象:

$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");

您必须为 PHP 代码添加 startElement、endElement 和 textData 函数:

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?"

这意味着 Universal ID 为 38B16601EC8B42BAC12571440068335C 的文档的 Inbox 视图中的第 8 列为 "seen Mickey?",这恰好是邮件消息的主题。

XML 解析器是“智能化”的,它知道若开始了一个元素,此元素必然会在某处终止。因此它会查找元素的结尾,并触发 endElement 函数(正是您指定为第二个回调函数的那个 endElement 函数)向您发出信号。由于不再需要列编号,故此函数将重置列编号。您还要检查是否必须增加文档计数器的值。

我们将 $doc_counter 变量作为存储 Universal ID 的数组的计数器。必须存储 Universal ID,主要是为了向邮件文档添加链接。当您获知 VIEWENTRY 元素已终止时(也就是说接下来将出现一个新的 Universal ID),最好将当前数组号加 1,使 $doc_coll 和 $unids 数组保持原样:

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

下载示例应用程序并在您自己的服务器上运行时,您所用版本的 Domino 邮件数据库的邮件视图内的列布局可能有所不同。如果出现这样的情况,则必须在 PHP 代码中调整列编号(docfunctions.php)。


示例 PHP 应用程序与 LotusScript 代理

本节介绍名为 ProcessDocWebAction 的 Domino 代理,在本文下载部分提供的示例应用程序中可找到此代理。ProcessDocWebAction 以 LotusScript 编写,并使用 DOM 来解析进入的 XML 请求。此应用程序使用了全新的 XML 脚本类,因而无法在 Domino 5 服务器上使用,但可在 Domino 6 和 Domino 7 服务器上使用。

此代理处理的请求是通过 PHP 示例应用程序启动的,可能为以下类型之一:

  • 创建并发送一个邮件消息
  • 保存一个现有邮件文档
  • 将一个新邮件文档保存为草稿
  • 请求关于用户文件位置的信息
  • 确定用户依然处于已登录状态的伪 Ping 请求

按以下步骤部署代理:

  1. 复制 xmlagent.lss 文件中的所有文本。
  2. 自选数据库,在其中创建新代理。
  3. 将代理的属性设置为“Run as Web user”,将 target 设置为 None。
  4. 将之前复制的代码粘贴到这个新代理中。
  5. 将代理保存为 ProcessDocWebAction。
  6. 在 PHP 文件(dominomail.php)中,指向代理所在数据库的 URL 位置。

在 PHP 示例应用程序的创建过程中,我们的首要考虑事项之一就是确保此应用程序适用于所有标准邮件数据库,而无需修改设计。您可用此代理来处理用户邮件数据库之外的 XML,可用同一代理为所有用户提供服务。

用户首次进行身份验证时,PHP 应用程序首先检索会话 cookie,随后要求此代理提供用户邮件文件的位置。代理知道代理调用者是谁,这是因为它是在设置了“Run as Web user”属性的情况下运行的。在完成初始请求之后,用户邮件数据库的位置就会被保存下来,以便重用,一直保存到用户关闭 Web 浏览器或会话过期为止。

用户总是使用实际的 Domino 身份验证进行验证。用户不能访问那些使用 Web 浏览器客户机无法访问的资源,即便他(或她)能在 Web 服务器上修改 PHP 源代码。


结束语

如果您是一名 Domino 开发人员或管理员,并且您的 Domino 服务器不能通过 Internet 访问,您依然可以为用户提供一种访问存储于 Domino 数据库中的数据的方法,允许他们读写电子邮件消息。通过本文示例应用程序中介绍的 XML 技术,您可以轻而易举地在自己的 PHP 应用程序上实现 Domino 身份验证,根据 Domino Directory 进行登录验证。

基于 Web 的 XML 解决方案未重写用户的访问权限,用户在每步操作中都要使用自己的凭证。这可确保用户无法访问未经授权的数据。为进一步增强安全性,您还可实现登录尝试失败的日志记录。


下载

描述名字大小
Source code of PHP applicationphp_app_sample.zip10KB
LotusScript agent used for processing XMLxmlagent.zip2KB
Compiled version of Notes API extensionphp_notes_dll.zip21KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Lotus
ArticleID=124709
ArticleTitle=为 IBM Lotus Domino 构建基于 PHP 的用户界面
publish-date=05292006