译者:老葛
Drupal的菜单(menu)系统是一个黑暗的地方之一,很少有人有勇气去探索它。披上你的盔甲---我们马上就进入了。
单词“菜单系统”有些用词不当。如果将菜单系统作为一个拥有三个主要责任的话,可能会更恰当一些:1,回调映射,2,访问控制,3,菜单定制。关于菜单系统的主要代码位于includes/menu.inc里,而比如启用定制菜单这些特性的可选代码位于menu.module.
在本章,我们将探索一下什么是回调映射以及它是如何工作的,看一下如何通过访问控制来保护菜单项,并逐条列出了所有的各种内建的菜单项。最后通过检验如何覆写,添加,和删除存在的菜单项来结束本章,这样你就可以随心所欲的定制Drupal了。
回调映射(Callback Mapping)
当一个Web浏览器向Drupal发送一个请求时,它向Drupal传递一个URL。通过这一信息,Drupal必须指出要运行哪段代码以及如何处理这一请求。这通常称为分发。Drupal截取URL的基础部分并使用后面的部分,称之为路径。举例来说,如果URL是http://example.com/?q=node/3,则Drupal路径为node/3。
将URLs映射为函数
通常采用的方式如下所示:Drupal向所有提供了一个菜单项数组的模块(module)查询,这些菜单项就是路径和关于路径的一些信息。一个模块(module)必须提供的一段信息就是回调(callback)。在这里回调就是一个PHP函数的名称,当一个浏览器请求一个特定的路径时它就会运行。当一个请求到达的时候Drupal将执行以下步骤:
1,如果路径是一个真实路径的别名,Drupal找出真实路径并使用它来代替别名。比如,如果管理员使用别名http://example.com/?q=cats 来代替http://example.com/?q=node/3, Drupal使用node/3作为路径
2,执行钩子方法hook_menu,这样所有的模块(module)都可以提供他们的回调。
3,创建一个从路径(比如 node/add)到回调(php函数 比如 node_page())的一个映射
4,如果启用了menu.module模块,将应用站点管理员对映射所做的任何修改和添加(比如覆写一个菜单项的标题)。
5,使用映射来查找请求URL对应的回调函数,并调用它。如果同时声明了回调参数的话,Drupal将其一同传送。
6,返回函数的执行结果,如果用户没有对这个URL的访问权限的话返回一个“拒绝访问”信息,如果路径没有映射到一个函数的话返回一个404相应。
图4-1和4-2图形表示了这一流程。
图4-1菜单分发流程概貌
注意图4-1中的获取菜单数组(Get menu array)这一部分。在这一流程中Drupal构造了数组$menu,它包含了每一菜单项的详细信息,比如菜单项的路径,允许那些人访问,子菜单项,等等。图4-2展示了Drupal如何构造菜单数组的概貌。
图4-2菜单数组构造流程概貌
通过在你的模块中使用菜单钩子来为这一流程设置钩子。这允许你定义可被包含到菜单树中的菜单项,让我们看一个名为mymenu.module的样例模块,它在Drupal的默认导航菜单中添加了一个菜单项。我们映射Drupal路径mymenu到php函数mymenu_hello().首先,我们需要一个文件mymenu.info:
; $Id $
name = "Mymenu Module"
description = "Adds a menu to the navigation block."
version = "$Name$"
然后我们需要文件mymenu.module:
<?php
// $Id$
/**
* Implementation of hook_menu().
*/
function mymenu_menu($may_cache) {
// Create an array to hold the menu items we'll define.
$items = array();
if ($may_cache) {
// Define a static menu item.
$items[] = array(
'title' => t('Greeting'),
'path' => 'mymenu',
'callback' => 'mymenu_hello',
'access' => TRUE
);
}
return $items;
}
function mymenu_hello() {
return t('Hello!');
}
创建子目录sites/all/modules/mymenu,然后将mymenu.info和mymenu.module文件放到里面,在管理员页面Administer ➤ Site building ➤ Modules启用这一模块,这样在导航区块(block)中就会出现这一菜单项,如图4-3所示。
需要注意的最重要的事是我们定义了一个路径并将其映射到一个函数上。路径是一个Drupal路径;也就是说,它是相对于你的Drupal的安装的基础URL而言的。但是在这里,还有一些其他的有趣事情发生。我们为我们的菜单项给出了一个标题,当页面在浏览器中显示时它会作为页面标题被自动使用(如果你想在以后的代码执行中覆写页面标题,你可以通过使用drupal_set_title()来设置它)。
图4-3 菜单项(Greeting)出现在导航区块(block)中。
菜单钩子实际被调用了两次:一次将$may_cache设置为TRUE,一次将$may_cache设置为FALSE。在本例中我们创建的菜单项是一个静态的菜单项。它不可改变,因此它是可缓存的。一旦为一给定用户构造了整个菜单树,Drupal将该树缓存为一个序列化的数组放到表cache_menu中去。在接下来的请求中,该树将被回显和反序列化,而不是重新构造。
如果我们想创建一个动态的菜单项(比如,一个使用当前时间作为菜单项标题的),我们添加一个else语句:
<?php
// $Id$
/**
* Implementation of hook_menu().
*/
function mymenu_menu($may_cache) {
// Create an array to hold the menu items we'll define.
$items = array();
if ($may_cache) {
// Define a static menu item.
$items[] = array(
'title' => t('Greeting'),
'path' => 'mymenu',
'callback' => 'mymenu_hello',
'access' => TRUE
);
}
else {
// Define a dynamic menu item.
$timestamp = format_date(time(), 'small');
$items[] = array(
'title' => t('Stock quote at @time', array('@time' => $timestamp)),
'path' => 'stockquote',
'callback' => 'mymenu_stock_quote',
'access' => TRUE
);
}
return $items;
}
function mymenu_hello() {
return t('Hello!');
}
现在菜单项在请求时被创建,如图4-4所示。
图4-4 动态创建菜单项
动态菜单项应该尽可能的禁止使用,因为在每次HTTP请求中它们必须被检查和添加上去,而不是从一个缓存中回显出来。如果你有一个经常被网络爬虫访问的繁忙的站点或者一个大站点,这将添加大量的处理。
秘密消息:当你开发你的模块时,你将需要安装devel.module模块,因为它使你快速和方便的清楚菜单缓存,这样,你就可以立即看到代码修改后的结果。如果没有安装devel.module模块,你可以通过写出用于截去表cache_menu的sql来手工清理缓存(比如TRUNCATE TABLE 'cache_menu')。另一种方式是开发一个动态菜单项(!$may_cache),当开发完成时将其更改为一个静态菜单项($may_cache)。