第12章 drupal搜索和索引内容

老葛的Drupal培训班 Think in Drupal

MySQL和PostgreSQL都有内置的全文搜索能力。你可以很容易的使用这些特定于数据库解决方案来建立一个搜索引擎,但是你却牺牲了对搜索机制的控制,同时也无法实现搜索系统与你的应用行为之间的完美整合。在数据库看来一个优先级比较高的词语,可能实际上在你的应用中则被认为是无用数据。
    由于数据库全文搜索不能很好的满足应用需求,所以Drupal社区决定构建一个自定义的搜索引擎,以实现特定于Drupal的索引和页面等级算法。这样就产生了一个与Drupal其余框架相一致的搜索引擎,它具有标准的配置和用户界面----不管后端使用了什么数据库。
    在本章中,我们讨论如何在模块中使用搜索API的钩子和构建自定义的搜索表单。我们还将学习一下Drupal是如何解析和索引内容的,还有就是如何实现索引器钩子。
 
提示 Drupal能够理解复杂的搜索查询语句,比如包含布尔操作符AND/OR,精确短语,或者甚至可以排除词语。一个包含所有这些情况的实际例子如下所示:
Beatles OR John Lennon "Penny Lane" –insect.
 

Drupal版本:

构建一个自定义搜索页面

老葛的Drupal培训班 Think in Drupal

Drupal内置了对节点和用户名的搜索能力。即便是你开发了一个自定义的节点类型,那么Drupal的搜索系统仍然可以索引呈现给节点视图的内容。例如,假定你有一个食谱节点类型,它包含的字段有“原料”和“用法说明”;你创建一个新的食谱节点,其节点ID为22。当有你访问http://example.com/?q=node/22, 只要管理员能够看到这些节点字段,搜索模块将在下次访cron运行期间将会索引食谱节点和它的附加元数据。
    开始你可能会觉得,节点搜索和用户搜索应该使用了同样的底层机制,事实上它们使用了两种单独的方式分别来扩展搜索功能。对于节点搜索,对于每次搜索都没有直接对node表进行查询;它使用一个索引器把内容提早处理为一种结构化的格式。在执行节点搜索时,将会对结构化的索引数据进行查询,这样就会产生更快更准确的结果。我们将在本章的后面部分学习索引器。
    用户搜索一点也不复杂,这是因为用户名只是数据库中的单个字段,搜索查询只需要对该字段进行检查就可以了。还有,用户名中不允许包含HTML,所以也不需要使用HTML索引器。替代的,你只需要使用几行代码直接对user表进行查询就可以了。
    在前面的两种情况下,Drupal的搜索模块都将实际搜索委托给了适当的模块。简单的用户名搜索位于modules/user/user.module的user_search()函数中,而复杂一点的节点搜索则位于modules/node/node.module的node_search()函数中。这里的一个重点是,搜索模块负责搜索的协调工作,它将具体的实现委托给了其它模块,这些模块最了解可搜索的内容。
 

Drupal版本:

drupal默认的搜索表单

老葛的Drupal培训班 Think in Drupal

如果你知道搜索API有一个默认的搜索表单可供使用(如图12-1所示),那么你一定会很高兴。如果这个界面能够满足你的需求,那么你只需要编写相应逻辑就可以了----为搜索请求查找采集数(hits)。这一搜索逻辑通常是对数据库的一个查询。
 
12-1.搜索API的用于搜索的默认用户界面
 
    尽管默认的内容搜索表单看起来很简单,实际上它的功能却非常强大,它可以对你站点节点内容的所有可见元素进行查询。这意味着,通过该界面,可以对节点的标题、正文、附加的自定义属性、评论、分类术语进行搜索。

Drupal版本:

drupal高级搜索表单

老葛的Drupal培训班 Think in Drupal

高级搜索特性,如图12-2所示,是用来过滤搜索结果的另一种方式。类别选择源自于站点已定义的所有词汇表(参看第14章)。而类型则包含了在站点上启用的所有内容类型。
 
12-2. 默认搜索表单提供的高级搜索选项
 
    通过在一个模块中实现搜索钩子,接着对表单ID search_form使用hook_form_alter()(参看第10章)来为用户提供一个界面。在图12-2中,这两种情形都发生了。节点模块实现了搜索钩子从而使得节点可被搜索(参看modules/node/node.module中的node_search()),同时扩展表单来提供一个界面(参看modules/node/node.module中的node_form_alter())。

Drupal版本:

扩展drupal搜索表单

 

让我们看一个例子。假定我们使用了path.module,并想启用对站点上URL别名的搜索。我们将编写一个简略的模块,用来实现hook_search()以使得别名可被搜索,并在Drupal的搜索界面提供一个附加标签。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

介绍hook_search()

 

让我们先看一下我们将要实现的搜索钩子。hook_search()的函数签名如下所示:
 
function hook_search($op = 'search', $keys = NULL)
 
    $op参数用来描述当前正被执行的操作,它有以下可能值:
 
• name: 调用者期望接收到的一个可翻译的名字,表示这个hook_search()实现将要提供的内容类型。例如,节点模块返回了t('Content'),而用户模块返回了t('Users')。该名字用于构建搜索表单上的标签(参看图12-1)。
 
• search: 对这个类型的内容进行一次搜索。模块应该执行一次搜索并返回结果。$keys参数包含了用户在搜索表单中输入的字符串。注意,这是一个字符串,而不是一个数组。在执行了一个搜索以后,你的模块应该返回一个包含搜索结果的数组。每一个结果都应该至少包含linktitle键。可选的额外的键有type, user, date, snippet, 和extra。下面是node.module中hook_search('search')的实现的部分内容,在这里构建了结果数组(对于如何使用extra键,可参看modules/comment/comment.module中的comment_nodeapi()):
 
$extra = node_invoke_nodeapi($node, 'search result');
$results[] = array(
    'link' => url('node/'. $item->sid, array('absolute' => TRUE)),
    'type' => check_plain(node_get_types('name', $node)),
    'title' => $node->title,
    'user' => theme('username', $node),
    'date' => $node->changed,
    'node' => $node,
    'extra' => $extra,
    'score' => $item->score / $total,
    'snippet' => search_excerpt($keys, $node->body),
);
 
• reset: 搜索索引即将被重建。用于同时实现hook_update_index()的模块。如果你的模块正在追踪它的数据有多少被索引了,那么它应该将它的计数器重置为准备重新索引阶段。
 
• status: 用户想知道,这个模块提供的内容中有多少被索引了。这个操作用于同时实现了hook_update_index()的模块。它返回一个数组,其中包含remainingtotal,前者表示还有多少项等待被索引,后者表示当索引完成时被索引的项目的总数。
 
• admin:“管理➤站点配置➤搜索设置”界面即将显示。返回一个表单定义数组,里面包含了你想添加到该页面的任意元素。这个表单使用system_settings_form()方式,所以元素键名必须与用于默认值的持久化变量的名字匹配。如果你想重新回顾一下system_settings_form()是如何工作的,那么可参看第2章的“添加特定于模块的设置”一节。
 
    在我们的路径别名搜索中,只用到了namesearch操作,所以我们只需要实现这两个就可以了。
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

使用hook_search_page()格式化搜索结果

如果你编写了一个提供搜索结果的模块,那么你可能想通过实现hook_search_page()来接管结果页面的外观。如果你没有实现这个钩子,那么就会调用theme('search_results', $results, $type)来进行格式化,它有个默认实现,位于modules/search/search-results.tpl.php。不要将这个与theme('search_result', $result, $type)混淆了,后者用来格式化单个搜索结果,它的默认实现位于modules/search/search-result.tpl.php。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

使得路径别名能被搜索

 

让我们开始我们的例子。我们将实现hook_search()中的namesearch操作。
 
注意 为了使下面的例子能够工作,我们需要启用路径模块,并将一些路径分配给节点(这样就有东西用来搜索了)。在测试这些例子以前,我们还需要重新构建搜索索引数据。导航到“管理➤站点配置➤搜索设置”,点击“重建站点索引”按钮,接着导航到管理➤报告➤状态报告”,手动运行cron 。cron运行时,搜索模块就会执行索引。
 
    在sites/all/modules/custom下面创建一个名为pathfinder的新文件夹,在新目录中创建列表 12-1 和 12-2所示的文件。
 
列表 12-1. pathfinder.info
 
; $Id$
name = Pathfinder
description = Gives administrators the ability to search URL aliases.
package = Pro Drupal Development
core = 6.x
 
列表 12-2. pathfinder.module
 
<?php
// $Id$
 
/**
 * @file
 * Search interface for URL aliases.
 */
 
    在你的文本编辑器中,不要关闭pathfinder.module;我们将继续使用它。接下来要实现的函数是hook_search($op, $keys)。这个钩子基于操作($op)参数的不同而返回不同的信息。
 
/**
 * Implementation of hook_search().
 */
function pathfinder_search($op = 'search', $keys = null) {
    switch ($op) {
        case 'name':
            if (user_access('administer url aliases')) {
                return t('URL aliases');
            }
            break;
        case 'search':
            if (user_access('administer url aliases')) {
                $found = array();
                // Replace wildcards with MySQL/PostgreSQL wildcards.
                $keys = preg_replace('!\*+!', '%', $keys);
                $sql = "SELECT * FROM {url_alias} WHERE LOWER(dst) LIKE LOWER('%%%s%%')";
                $result = pager_query($sql, 50, 0, NULL, $keys);
                while ($path = db_fetch_object($result)) {
                    $found[] = array('title' => $path->dst,
                    'link' => url("admin/build/path/edit/$path->pid"));
                }
                return $found;
            }
    }
}
 
    当搜索API调用hook_search('name')时,它将寻找显示在通用搜索页面的菜单标签的名字(参看图12-3)。在这里,我们返回的是“URL 别名”。通过返回菜单标签的名字,搜索API将为菜单标签的链接创建一个新的搜索表单。
 
12-3.通过从hook_search()中返回菜单标签的名字,这样就可以访问搜索表单了
 
    hook_search('search')是hook_search()中的核心部分。当提交搜索表单时,将调这一操作,它的任务是收集并返回搜索结果。在前面的代码中,我们使用表单中提交的搜索词语对url_alias表进行查询。接着,我们将查询的结果收集到一个数组中并将其返回。返回的结果由搜索模块负责格式化并显示给用户,如图12-4所示。
 
12-4.搜索结果由搜索模块负责格式化。
 
    让我们关注一下搜索结果页面。如果默认的搜索结果页面不能满足你的期望,那么你可以对默认视图进行覆写。在我们这里,我们不想把它只显示为一列匹配的别名,我们想为搜索结果使用一个可排序的表格,其中对于每个匹配的别名都为其添加了一个单独的“编辑”链接。通过对hook_search('search')的返回值进行一些调整,并实现hook_search_page(),从而完成这一工作。
 
/**
 * Implementation of hook_search().
 */
function pathfinder_search($op = 'search', $keys = null) {
    switch ($op) {
        case 'name':
            if (user_access('administer url aliases')) {
                return t('URL aliases');
            }
            break;
        case 'search':
            if (user_access('administer url aliases')) {
              $header = array(
                  array('data' => t('Alias'), 'field' => 'dst'),
                  t('Operations'),
              );
              // Return to this page after an 'edit' operation.
              $destination = drupal_get_destination();
                // Replace wildcards with MySQL/PostgreSQL wildcards.
                $keys = preg_replace('!\*+!', '%', $keys);
              $sql = "SELECT * FROM {url_alias} WHERE LOWER(dst) LIKE LOWER('%%%s%%')" .tablesort_sql($header);
                $result = pager_query($sql, 50, 0, NULL, $keys);
                while ($path = db_fetch_object($result)) {
                  $rows[] = array(
                     l($path->dst, $path->dst),
                     l(t('edit'), "admin/build/path/edit/$path->pid",
                         array('query' => $destination))
                  );
              }
              if (!$rows) {
                  $rows[] = array(array('data' => t('No URL aliases found.'),
                  'colspan' => '2'));
              }
              return $rows;
            }
    }
}
 
/**
 * Implementation of hook_search_page().
 */
function pathfinder_search_page($rows) {
    $header = array(
       array('data' => t('Alias'), 'field' => 'dst'), ('Operations'));
    $output = theme('table', $header, $rows);
    $output .= theme('pager', NULL, 50, 0);
    return $output;
}
 
    在前面的代码中,我们使用drupal_get_destination()来取回我们当前所在的页面位置,如果我们点击“编辑”链接,来编辑一个URL别名,在提交编辑表单以后,我们将自动返回到这一搜索结果页面。由于目的地的路径信息将作为编辑链接的一部分,传递给了编辑表单,所以编辑表单知道将返回到哪个页面。你将在URL中看到一个名为destination的附加参数,它包含的就是表单后所要返回的URL。
    为了对结果表格进行排序,我们将tablesort_sql()函数追加到了搜索查询字符串上,从而确保在查询语句后面追加正确的SQL ORDER BY语句。最后,pathfinder_search_page()是hook_search_page()的一个实现,它允许我们控制搜索结果页面的输出。图12-5显示了最终的搜索结果页面。
 
12-5.搜索结果页面现在将结果呈现为了一个可排序的表格
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

使用搜索HTML索引器

到目前为止,我们通过提供一个简单的hook_search('search')实现,检查了如何与默认搜索表单进行交互。然而,当我们的需求从使用简单的LIKE语句搜索数据库的一个VARCHAR列,提高到了索引网站内容,那么我们需要将该任务外包给Drupal内置的HTML索引器。

    索引器的目标是高效的搜索大块的HTML。在cron运行时(通过http://example.com/cron.php),通过对内容进行处理来实现这一点。因此,从新内容被提交,也就是新内容可被搜索到了,到下次的cron运行之间,会有一个延时。索引器解析数据并把文本切分成词语 (称为分词),基于一个规则集为每个令牌(token)分配一个分数,我们可以使用搜索API来扩展这一规则集。它接着将这些数据保存到数据库中,而当一个搜索被请求时,它没有直接使用节点表,而是使用了这些存储索引数据的表。
 
注意 由于搜索和索引的执行需要使用cron,从新内容被提交,也就是新内容可被搜索到了,到下次的cron运行之间,会有一个延时。还有,索引是个耗费资源的任务。如果你的Drupal站点非常繁忙,在两次cron运行之间会新增数百个节点,那么最好可以使用一个更高级的能与Drupal一同工作的搜索解决方案,比如Solr。(参看http://drupal.org/project/apachesolr。)
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

什么时候使用索引器

 

当搜索引擎的评价方式比标准的“匹配最多词语”方式要求更高时,一般使用索引器。搜索相关度(relevancy),指的是使用一个规则集(通常很复杂)对内容进行处理来判定它在一个索引内部的等级。
    如果你需要对大块的HTML内容进行搜索时,那么你就需要利用索引器的能力了。Drupal的最大优点之一就是,博客、论坛、页面等等都是节点。它们的基本数据结构是相同的,而这个共同纽带也意味着它们还将共享一些基本功能。一个这样的共同特性就是当启用了搜索模块后,所有的节点将被自动索引;而不需要额外的编程工作。即便是你创建了一个自定义节点类型,Drupal也会自动对其内容进行索引,这样你所作的修改在节点呈现时就能显示出来了。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal索引器的工作原理

 

索引器有一个预处理模式,在该模式下将使用一组规则对文本进行过滤从而分配分数。这些规则包括:处理缩略语、URLs、和数字数据。在预处理期间,其它模块有一个机会可用来向这个流程中添加逻辑,从而执行它们自己的数据操作。在针对特定语言调优时,这点会非常方便,如下所示,这里我们使用了第3方模块Porter-Stemmer:
 
• resumé ä resume (accent removal重音符删除)
• skipping ä skip (stemming词干)
• skips ä skip (stemming词干)
 
    另外的一个语言预处理例子就是,对汉语、日语、和韩语所进行的切词,这是为了确保文本被恰当地索引了。
 
提示 Porter-Stemmer模块(http://drupal.org/project/porterstemmer)是一个例子,它通过提供单词词干化来改进英语搜索。同样,中文分词模块(http://drupal.org/project/csplitter)是一个增强的预处理器,用来改进中文、日文、和韩文的搜索。在搜索模块中包含了一个简单的中文分词器,可以在搜索设置页面启用它。
 
    预处理阶段过后,索引器使用HTML标签来查找更重要的字词(称为令牌),基于HTML标签的默认分数和每个令牌出现的次数来为它们分配调整过的分数。这些分数将用来判定令牌的最终相关度。下面是默认HTML标签分数的完整列表(它们定义search_index()中):
 
<h1> = 25
<h2> = 18
<h3> = 15
<h4> = 12
<a> = 10
<h5> = 9
<h6> = 6
<b> = 3
<strong> = 3
<i> = 3
<em> = 3
<u> = 3
 
    让我们摘取一大块HTML,然后使用索引器对其处理,从而来更好的理解索引器的工作原理。图12-6显示了HTML索引器的概览:解析内容,为令牌分配分数,将该信息存储在数据库中。
 
12-6.对一大块HTML进行索引并为令牌分配分数
 
    当索引器碰到一个由标点分隔的数字型数据时,它将删除标点并只对数字进行索引。这使得数字型元素比如日期、版本号、IP地址等将会更容易的被搜索到。图12-6中的中间步骤显示了如何处理一个没有使用HTML标签的字词令牌。这些令牌的重量为1。最后一行显示了使用强调标签<em>的内容。用来决定令牌总分的公式如下:
 
匹配数量 * HTML标签重量
 
    还需要注意的是Drupal索引节点的过滤输出;例如,如果你有一个输入过滤器,它将URL自动转化为了超链接,或者有另外一个过滤器将换行转化为了HTML的<br/>和<p>标签,那么索引器将看到这些带有标签的内容,并会根据这些标签来分配分数。对于使用PHP求值程序过滤器来生成动态内容的节点,对其过滤后的内容进行索引,那么效果会更加明显。索引动态内容可能是非常麻烦的,但是由于Drupal的索引器只看到了由PHP代码生成的内容输出,所以动态内容也是完全可被索引的。
    当索引器碰到内部链接时,也将用一种特殊方式对它们进行处理。如果一个链接指向了另一个节点,那么链接的字词将被添加到目标节点的内容中,这使得能够更方便的搜索常见问题的答案和相关信息。可以使用两种方式钩住索引器:
 
• hook_nodeapi('update index'): 为了调整搜索相关度,你可以向节点中添加在其它情况下不可见的数据。你可以在Drupal核心中看到这方面的实例,比如分类术语和评论,从技术上来讲它们不是节点对象的一部分,但是它们应该能够影响搜索结果。分类模块通过实现nodeapi('update index'),在索引阶段期间,将这些项目添加到了节点中。你应该记起hook_nodeapi()仅用来处理节点。
 
• hook_update_index():通过使用hook_update_index(),你可以使用索引器对那些不属于节点的HTML内容进行索引。Drupal核心中有个hook_update_index()实现,参看modules/node/node.module中的node_update_index()。
 
    在cron运行期间,为了索引新的数据,这两个钩子都将被调用。图12-7显示了这些钩子的运行次序。
 
12-7. HTML索引钩子的概览
 
    我们将在接下来的部分中,来更详细的讨论这些钩子。
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

向节点添加元数据:nodeapi('update index')

老葛的Drupal培训班 Think in Drupal

Drupal为了搜索来索引一个节点时,它首先使用node_view()来运行节点,从而生成与匿名用户在浏览器中看到的完全一样的输出。这就意味着节点中任何可见的部分都将被索引。例如,假定我们有一个节点,它的ID为26。当查看URL http://example.com/?q=node/26时,所看到的节点的各个部分也就是索引器所看到的。
    如果我们有一个自定义节点类型,它包含了能够影响搜索结果的隐藏数据,那该怎么办呢?在book.module中有个不错的现成例子,我们看看它是怎么处理这一点的。我们可以把章节标题和每个子页面索引在一起,从而增加这些子页面的相关度。
 
/**
 * Implementation of hook_nodeapi().
 */
function book_boost_nodeapi($node, $op) {
    switch ($op) {
        case 'update index':
            // Book nodes have a parent link ID attribute.
            // If it's nonzero we can have the menu system retrieve
            // the parent's menu item which gives us the title.
            if ($node->type == 'book' && $node->book['plid']) {
                $item = menu_link_load($node->book['plid']);
                return '<h2>'. $item['title'] .'</h2>';
            }
    }
}
 
    注意,我们在这里把标题包装在了HTML<h2>标签中,来通知索引器为这个文本分配一个相对较高的分数。
 
注意 钩子nodeapi仅用于向节点添加元数据。对于那些不是节点的元素,对它们的索引,可以使用hook_update_index()。
 

Drupal版本:

对非节点的内容进行索引:hook_update_index()(1)

在你需要对非Drupal节点的内容进行搜索时,那么你可以钩住索引器并向其提供你需要的任何文本数据,这样它们在Drupal中就可被搜索了。假定你的小组支持一个遗留应用系统,这个系统可用来输入和查看最近几年的产品技术笔记。由于一些政策原因,你还不能完全使用Drupal的解决方案来替代这个遗留系统,但是你想在Drupal内部能够搜索这些技术笔记。没问题。让我们假定遗留系统将它的数据保存在了technote表中。我们将创建一个简短的模块,在里面使用hook_update_index()把这个数据库中的信息发送给Drupal的索引器,使用hook_search()将搜索结果显示出来。

 
注意 如果你想对非Drupal的数据库的内容进行索引,那么就需要连接多个数据库,关于这方面的更多详细,可参看第5章。
 
    在sites/all/modules/custom下面创建一个名为legacysearch的文件夹。由于我们需要一个用来测试的遗留数据库,所以创建一个名为legacysearch.install的文件,并添加以下内容:
 
<?php
// $Id$
 
/**
 * Implementation of hook_install().
 */
function legacysearch_install() {
    // Create table.
    drupal_install_schema('legacysearch');
    // Insert some data.
    db_query("INSERT INTO technote VALUES (1, 'Web 1.0 Emulator',
        '<p>This handy product lets you emulate the blink tag but in
        hardware...a perfect gift.</p>', 1172542517)");
    db_query("INSERT INTO technote VALUES (2, 'Squishy Debugger',
        '<p>Fully functional debugger inside a squishy gel case.
        The embedded ARM processor heats up...</p>', 1172502517)");
}
 
/**
 * Implementation of hook_uninstall().
 */
function legacysearch_uninstall() {
    drupal_uninstall_schema('legacysearch');
}
 
/**
 * Implementation of hook_schema().
 */
function legacysearch_schema() {
    $schema['technote'] = array(
        'description' => t('A database with some example records.'),
        'fields' => array(
            'id' => array(
                'type' => 'serial',
                'not null' => TRUE,
                'description' => t("The tech note's primary ID."),
            ),
            'title' => array(
                'type' => 'varchar',
                'length' => 255,
                'description' => t("The tech note's title."),
            ),
            'note' => array(
                'type' => 'text',
                'description' => t('Actual text of tech note.'),
            ),
            'last_modified' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'description' => t('Unix timestamp of last modification.'),
            ),
        ),
        'primary key' => array('id'),
    );
    return $schema;
}

老葛的Drupal培训班 Think in Drupal

Drupal版本:

对非节点的内容进行索引:hook_update_index()(2)

老葛的Drupal培训班 Think in Drupal

因为遗留数据库应该已经存在了,所以这个模块实际上不需要这个安装文件。我们这样做,仅仅是为了确保有一个遗留数据库表和数据供我们测试。我们应该在模块中的调整查询语句,让其连接到已有的非Drupal表上。在下面的查询语句中,假定数据存放在一个非Drupal数据库中, 使用settings.php文件中的$db_url['legacy']定义该数据库链接。
    接着,创建sites/all/modules/custom/legacysearch/legacysearch.info,并添加以下内容:
 
; $Id$
name = Legacy Search
description = Example of indexing/searching external content with Drupal.
package = Pro Drupal Development
core = 6.x
 
    最后,创建sites/all/modules/custom/legacysearch/legacysearch.module,并添加以下代码:
 
<?php
// $Id$
 
/**
 * @file
 * Enables searching of non-Drupal content.
 */
 

Drupal版本:

对非节点的内容进行索引:hook_update_index()(3)

老葛的Drupal培训班 Think in Drupal

继续前进,在你的文本编辑器中不要关闭legacysearch.module,我们将添加hook_update_index(),从而将遗留数据提供给HTML索引器。在创建了这些文件以后,现在我们就可以启用这个模块了。
 
/**
 * Implementation of hook_update_index().
 */
function legacysearch_update_index() {
    // We define these variables as global so our shutdown function can
    // access them.
    global $last_change, $last_id;
 
    // If PHP times out while indexing, run a function to save
    // information about how far we got so we can continue at next cron run.
    register_shutdown_function('legacysearch_update_shutdown');
 
    $last_id = variable_get('legacysearch_cron_last_id', 0);
    $last_change = variable_get('legacysearch_cron_last_change', 0);
 
    // Switch database connection to legacy database.
    db_set_active('legacy');
    $result = db_query("SELECT id, title, note, last_modified
                        FROM {technote}
                        WHERE (id > %d) OR (last_modified > %d)
                        ORDER BY last_modified ASC", $last_id, $last_change);
 
    // Switch database connection back to Drupal database.
    db_set_active('default');
 
    // Feed the external information to the search indexer.
    while ($data = db_fetch_object($result)) {
        $last_change = $data->last_modified;
        $last_id = $data->id;
 
        $text = '<h1>' . check_plain($data->title) . '</h1>' . $data->note;
 
        search_index($data->id, 'technote', $text);
    }
}
 
    每片内容都被传递给了search_index(),这里面包括一个标识符(在这里,就是遗留数据库表中的ID列的值),内容的类型(我们将类型设为technote;索引Drupal内容时它一般为节点或者用户),以及要被索引的文本。

Drupal版本:

对非节点的内容进行索引:hook_update_index()(4)

 

register_shutdown_function()分配了一个函数,在为一个请求执行完PHP脚本以后将执行这个函数。因为在索引完所有的内容以前,PHP可能会终止运行,所以使用这个函数来追踪最后索引项目的ID。
 
/**
 * Shutdown function to make sure we remember the last element processed.
 */
function legacysearch_update_shutdown() {
    global $last_change, $last_id;
 
    if ($last_change && $last_id) {
        variable_set('legacysearch_cron_last', $last_change);
        variable_set('legacysearch_cron_last_id', $last_id);
    }
}

老葛的Drupal培训班 Think in Drupal

Drupal版本:

对非节点的内容进行索引:hook_update_index()(5)

老葛的Drupal培训班 Think in Drupal

在这个模块中,我们需要实现的最后一个函数是钩子hook_search(),它允许我们使用内置的用户界面来搜索我们的遗留信息。
 
/**
 * Implementation of hook_search().
 */
function legacysearch_search($op = 'search', $keys = NULL) {
    switch ($op) {
        case 'name':
            return t('Tech Notes'); // Used on search tab.
 
        case 'reset':
            variable_del('legacysearch_cron_last');
            variable_del('legacysearch_cron_last_id');
            return;
 
        case 'search':
            // Search the index for the keywords that were entered.
            $hits = do_search($keys, 'technote');
 
            $results = array();
 
            // Prepend URL of legacy system to each result. Assume a legacy URL
            // for a given tech note is http://technotes.example.com/note.pl?3
            $legacy_url = 'http://technotes.example.com/';
 
            // We now have the IDs of the results. Pull each result
            // from the legacy database.
            foreach ($hits as $item) {
                db_set_active('legacy');
                $note = db_fetch_object(db_query("SELECT * FROM {technote} WHERE
                    id = %d", $item->sid));
                db_set_active('default');
 
                $results[] = array(
                    'link' => url($legacy_url . 'note.pl', array('query' =>                             $item->sid, 'absolute' => TRUE)),
                    'type' => t('Note'),
                    'title' => $note->title,
                    'date' => $note->last_modified,
                    'score' => $item->score,
                    'snippet' => search_excerpt($keys, $note->note));
            }
        return $results;
    }
}
 
    在运行cron并且索引信息以后,就可以搜索技术笔记了,如图12-8所示。索引是在Drupal内部进行的,但是legacysearch_search()返回的搜索结果则来源于(并指向)遗留系统。
 
 
12-8.搜索一个外部的遗留数据库
 

Drupal版本:

总结

 

读完本章后,你应该可以
• 自定义drupal搜索表单。
• 理解如何使用drupal搜索钩子。
• 理解HTML索引器的工作原理。
• 钩住索引器,来索引各种类型的内容。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本: