现在我们再看看另一个函数——这个函数从我们创建的 SimpleXML 对象中抽取数据并格式化以便显示。
我们这里看到的函数是基本的,没有利用 Drupal 的主题引擎。通常,格式化显示数据是由主题引擎(theming engine)处理的。主题(theme)是我们下一章要讨论的话题。
下面是我们的 _goodreads_block_content() 函数:
/** * Generate the contents of a block from a SimpleXML object. * Given a SimpleXML object and the maximum number of * entries to be displayed, generate some content. * * @param $doc * SimpleXML object containing Goodreads XML. * @param $num_items * Number of items to format for display. * @return * Formatted string. */ function _goodreads_block_content($doc, $num_items=3) { $items = $doc->channel->item; $count_items = count($items); $len = ($count_items < $num_items) ? $count_items : $num_items; $template = '<div class="goodreads-item">' .'<img src="%s"/><br/>%s<br/>by %s</div>'; // Default image: 'no cover' $default_img = 'http://www.goodreads.com/images/nocover-60x80.jpg'; $default_link = 'http://www.goodreads.com'; $out = ''; foreach ($items as $item) { $author = check_plain($item->author_name); $title = strip_tags($item->title); $link = check_url(trim($item->link)); $img = check_url(trim($item->book_image_url)); if (empty($author)) $author = ''; if (empty($title)) $title = ''; if (empty($link) !== 0) $link = $default_link; if (empty($img)) $img = $default_img; $book_link = l($title, $link); $out .= sprintf($template, $img, $book_link, $author); } $out .= '<br/><div class="goodreads-more">' . l('Goodreads.com', 'http://www.goodreads.com') .'</div>'; return $out; }
与上一个函数一样,这个函数实现的也不是 Drupal钩子。实际上,正如开头的下划线 (_) 字符指明的,这是一个私有函数,是用来被本模块中的其它函数调用的。
同样,函数以一个文档块开头,解释其目的、参数、和返回值。随后是函数体:
function _goodreads_block_content($doc, $num_items=3) {
$items = $doc->channel->item;
这个函数做的第一件事就是从 XML 数据中获取一个 <item/> 元素的列表。为了理解这里的处理方式,我们来看看从 Goodreads 返回的 XML(进行了适当简化):
<?xml version="1.0"?> <rss version="2.0"> <channel> <title>Matthew's bookshelf: history-of-philosophy</title> <copyright> <![CDATA[ Copyright (C) 2006 Goodreads Inc. All rights reserved.]]> </copyright> <link>http://www.goodreads.com/review/list_rss/398385</link> <item> <title> <![CDATA[Thought's Ego in Augustine and Descartes]]> </title> <link>http://www.goodreads.com/review/show/6895959? utm_source=rss&utm_medium=api</link> <book_image_url> <![CDATA[ <a href="http://www.goodreads.com/images/books/96/285/<br /> " title="http://www.goodreads.com/images/books/96/285/<br /> ">http://www.goodreads.com/images/books/96/285/<br /> </a> 964285-s-1179856470.jpg ]]> </book_image_url> <author_name><![CDATA[Gareth B. Matthews]]></author_name> </item> <item> <title> <![CDATA[Augustine: On the Trinity Books 8-15 (Cambridge Texts in the History of Philosophy)]]> </title> <link>http://www.goodreads.com/review/show/6895931? utm_source=rss&utm_medium=api </link> <book_image_url> <![CDATA[ <a href="http://www.goodreads.com/images/books/35/855/<br /> " title="http://www.goodreads.com/images/books/35/855/<br /> ">http://www.goodreads.com/images/books/35/855/<br /> </a> 352855-s-1174007852.jpg ]]> </book_image_url> <author_name><![CDATA[Gareth B. Matthews]]></author_name> </item> <item> <title> <![CDATA[ A Treatise Concerning the Principles of Human Knowledge (Oxford Philosophical Texts)]]> </title> <link>http://www.goodreads.com/review/show/6894329? utm_source=rss&utm_medium=api </link> <book_image_url> <![CDATA[ <a href="http://www.goodreads.com/images/books/10/138/<br /> " title="http://www.goodreads.com/images/books/10/138/<br /> ">http://www.goodreads.com/images/books/10/138/<br /> </a> 1029138-s-1180349380.jpg ]]> </book_image_url> <author_name><![CDATA[George Berkeley]]></author_name> </item> </channel> </rss>
上面的 XML 符合我们熟悉的 RSS 文档结构。 <channel/> 首先包含了一个描述书架的域的列表,然后是一些 <item/> 元素,其中每个都描述了书架上的一本书。
我们对<item/>元素的内容感兴趣,因此我们从抓取 item 列表开始:
$items = $doc->channel->item;
SimpleXML 的 $doc 对象含有指向子元素的属性。<rss/>元素(用$doc表示)只有一个元素: <channel/>。依次下来,<channel>有几个子元素:<title/>, <copyright/>, <link/>, 和几个 <item/> 元素。这些用 $doc->title, $doc->copyright, 等表示。
如果有几个元素的名字相同会怎么样呢?比如<item>。
它们将被存放在数组中。因此在上面的代码中,变量 $items 指向一个 <item/> 元素的数组。
接下来,我们确定要显示多少元素;指定一个基本的模板,稍后用于为我们的区块创建 HTML;并且设置几个默认值:
$count_items = count($items); $len = ($count_items < $num_items) ? $count_items : $num_items; $template = '<div class="goodreads-item">' .'<img src="%s"/><br/>%s<br/>by %s</div>'; // Default image: 'no cover' $default_img = 'http://www.goodreads.com/images/nocover-60x80.jpg'; $default_link = 'http://www.goodreads.com';
在第一行,我们确保不会使用大于 $num_items 的值。接下来,我们给$template 变量设置了一个 sprintf() 样式的模板。我们稍后用它格式化条目。最后,我们为 logo 图像($default_img) 设置了一个默认值,设置了一个指向Goodreads ($default_link) 的链接。
做好这些之后,我们准备循环处理数组元素并生成一些HTML:
$out = ''; foreach ($items as $item) { $author = check_plain($item->author_name); $title = strip_tags($item->title); $link = check_url(trim($item->link)); $img = check_url(trim($item->book_image_url)); if (empty($author)) $author = 'Unknown'; if (empty($title)) $title = 'Untitled'; if (empty($link)) $link = $default_link; if (empty($img)) $img = $default_img; $book_link = l($title, $link); $out .= sprintf( $template, $img, $book_link, $author); }
使用一个 foreach 循环,我们遍历 $items 列表中的每个元素。每个元素看起来都像这样:
<item> <title> <![CDATA[Book Title]]> </title> <link>http://www.goodreads.com/something/</link> <book_image_url> <![CDATA[ <a href="http://www.goodreads.com/images/something.jpg<br /> " title="http://www.goodreads.com/images/something.jpg<br /> ">http://www.goodreads.com/images/something.jpg<br /> </a> ]]> </book_image_url> <author_name><![CDATA[Author Name]]></author_name> </item>
我们像抽取出 title, link, author name, 和一个书的图像(image)。我们从$item 对象中抽取这些信息:
$author = check_plain($item->author_name); $title = strip_tags($item->title); $link = check_url(trim($item->link)); $img = check_url(trim($item->book_image_url));
尽管我们信任 Goodreads, 不过我们还是想把它发送给我们的数据消消毒,以增加安全性。在上面,我们用函数check_plain() 和 strip_tags() 检查了$author 和 $title 的值。
strip_tags()函数是PHP内建的。它只是简单地读取字符串,然后去掉所有看起来像 HTML 或 XML标记的东西。这提供了一个基本的安全层,因为它将去掉可能在我们的网页上插入 script, applet, 或 ActiveX 对象的标记。但是这种检查仍然允许 HTML实体,比如 $amp; 或 »te;。
Drupal 包含几个字符串编码函数,提供与 strip_tags()不同的服务。在上面,我们使用 check_plain() 对 $item->author_name 进行某些转义操作。与strip_tags() 不同,check_plain() 不会去除什么东西。相反,它把 HTML标记编码成实体(类似 t()函数中的 @修饰符) 。因此,check_plain('<em>Example</em>') 将返回字符串<em>Example</em>.
提示:check_plain()函数在 Drupal 安全性中扮演很重要的角色。它提供了一种避免跨站点脚本攻击(cross-site scripting attacks,XSS)的方式,也可以避免恶意的 HTML 侵入。
不过,使用 check_plain() 也有不利之处。如果 check_plain() 遇到一个HTML 实体,比如 <,将对它进行再次编码。因此,< 将变成 &lt;。开头的 & 编码为 &。
因此,对于 $item->link 和 $item->book_image_url 对象,我们得做两件事。首先,我们必须先对结果进行 trim() 处理,去除开头和结尾的空格字符。这很重要,我们一会儿将看到,如果 URL 以空格开头,Drupal 的 l() 函数将无法正确处理它们。
我们也用了 Drupal 的 check_url() 函数来验证 URL 的合法性。check_url()进行一系列检查试图逮住恶意的 URL。例如,它将阻止URL中使用 javascript: 协议。我们这样做是一种安全预防措施。
接下来,我们检查每个新分配的变量。我们想要确保,如果一个变量为 null 或空值,它将得到一个默认值。
if (empty($author)) $author = 'Unknown'; if (empty($title)) $title = 'Untitled'; if (empty($link)) $link = $default_link; if (empty($img)) $img = $default_img;
在这个 foreach 循环中,我们做的最后一件事是,把此条目格式化为 HTML 去显示:
$book_link = l($title, $link); $out .= sprintf( $template, $img, $book_link, $author);
首先,我们创建一个指向 Goodreads 站点的书评页面的链接。这是用 Drupal 的 l() 函数(单个小写L字符)实现的。l() 是另一个重要的 Drupal 函数。这个函数创建一个超链接。在上面的代码中,它接受两个参数: 书名($title),和一个 URL ($link), 然后创建一个类似这样的 HTML 标记:
<a href="http://www.goodreads.com/review/show/6894329
?utm_source=rss&amp;utm_medium=api">
A Treatise Concerning the Principles of Human Knowledge
(Oxford Philosophical Texts)
</a>
这个字符串存储在 $book_link 中。然后我们调用 PHP sprintf() 函数进行HTML 格式化:
$out .= sprintf( $template, $img, $book_link, $author);
sprintf() 函数接受一个模板($template)作为第一个参数。我们是在 foreach 循环的外面定义的 $template,它是个这样的字符串:
<div class="goodreads-item"><img src="%s"/><br/>%s<br/>by %s</div>
sprintf() 将从头到尾读入这个字符串,每次遇到一个占位符,比如 %s,它就用一个参数值替代。
字符串中有三个串占位符(%s)。sprintf 将按顺序用传入的另外三个参数的值代替它们:$img, $book_link, 和 $author。
因此,sprintf() 最终返回一个类似这样的字符串:
<div class="goodreads-item">
<img src="http://www.goodreads.com/something.jpg"/>
<br/><a href="http://www.goodreads.com/somepath">
Thought's Ego in Augustine and Descartes
</a><br/>by Gareth B. Matthews</div>
然后那个字符串被加入到 $output 中。当 foreach 循环完成时,对于 $items 中的每个条目,$output 中都应该包含一个相应的 HTML 片段。
提示:PHP 的 sprintf() 和 printf() 函数功能很强大,并且可以使 PHP 代码更容易编写、维护和阅读。更多信息请参阅 PHP 文档:http://php.net/manual/en/function.sprintf.php.
一旦我们完成了 foreach 循环,要做的事就所剩无几了。我们需要在 $out HTML 中添加一个链回 Goodreads 的链接,然后返回整个输出:
$out .= '<br/><div class="goodreads-more">' . l('Goodreads.com', 'http://www.goodreads.com') .'</div>'; return $out; }
我们的区块钩子(goodreads_block())将接受 _goodreads_block_content() 返回的格式化 HTML,并把它保存在区块内容中。Drupal将在右边栏上显示结果,正如我们前一节配置的那样:

插图 2-8
我们的 Goodreads 哲学史书架上的前三个条目,现在在 Drupal 中显示成一个区块。
还可以做很多事来改进这个模块。我们可以添加缓存支持,那么就不会每个请求都重新获取 Goodreads XML 了。我们可以创建另外的安全手段检查 XML 内容。我们可以增加一个管理界面,使我们能够设定书架的 URL 而不是把 URL 的值硬编码在模块中。我们还可以使用主题系统创建 HTML 并把外观风格作用于它,而不是把 HTML 标记硬编码在我们的代码中。
实际上,在下一章中,我们将细细审视主题系统,并且看到这种特别改进是如何做到的。
不过,为了完成我们的模块,我们还需要最后一击。我们需要添加一些帮助文本。
评论
发表新评论