第8章 drupal主题系统

老葛的Drupal培训班 Think in Drupal

如果你想修改Drupal生成的HTML或者其它标识字体,那么你需要深入的了解主题系统的各个组成部分。主题系统是个优雅的架构,它使你无需核心代码,就可以得到想要的外观;但是它也有一个很长的学习曲线,特别是你想要完全定制一个站点主题,以与其它drupal站点区别开来,那么你还是需要费点功夫的。我将向你讲述主题系统的工作原理,以及向你展示隐藏在Drupal核心之中的一些最佳实践。首先要记住的是:不要通过编辑模块文件内部的HTML来改变你站点的外观。如果这样做了,你仅仅创建了一个对你个人适用的内容管理系统,这样你就会失去开源软件系统最大的优势之一------社区的支持。覆写,而不是修改!
 
主题系统的组成
       主题系统由多个抽象层次所组成:模板语言,主题引擎和主题。

Drupal版本:

drupal模板语言和主题引擎

老葛的Drupal培训班 Think in Drupal

主题系统可以使用多个模板语言。Smarty, PHPTAL, 和PHPTemplate都可以与Drupal集成,用来向模板文件中添加动态数据。为了使用这些语言,需要一个叫做主题引擎的包装器,用来在模板语言和Drupal之间进行交互。你可以在http://drupal.org/project/Theme+engines中找到对应模板语言的主题引擎。安装主题引擎其实很简单,只需要通过将相应主题引擎的目录放置到你站点的主题引擎目录下面就可以了。如果仅用于单个站点,使用目录sites/sitename/themes/engines;如果用于多个Drupal站点,则使用目录sites/all/themes/engines,如图8-1所示。
    Drupal社区创建了一个自己的引擎,专门对Drupal作了优化。它就是PHPTemplate,它使用PHP作为模板语言,这样它就不需要中间层的解析环节了,而其它模板语言常常需要这一环节。这是Drupal最常用的模板引擎,它是Drupal自带的。它位于themes/engines/phptemplate,如图8-2所示:
 
8-1 为Drupal添加定制主题引擎的目录结构
 
8-1 Drupal核心主题引擎的目录结构。这个位置专门用于放置核心主题引擎。
 
注意 完全可以不使用模板语言,而简单的使用纯php的模板文件。如果你是热衷于追求速度,或者可能仅仅是想折磨一下你的设计人员,那么你可以不使用主题引擎而仅仅整个主题包装在PHP函数中,比如使用函数themename_page()和themename_node()来代替模板文件。一个基于PHP主题的示例,可参看themes/chameleon/chameleon.theme。
 
    当你安装好一个主题引擎后,你不会看到你的站点有了任何改变。这是因为,主题引擎仅仅是一个接口库,在它被使用以前,你仍然需要安装一个依赖于该主题引擎的Drupal主题。
    要使用哪一个模板语言呢?如果你正在转换一个遗留站点,那么可能使用以前的模板语言会更方便一些;也许你的设计团队更倾向于使用所见即所得的编辑器,这样PHPTAL应该是个更好的选择,因为它可以阻止这些编辑器对模板的破坏。你可以发现,大多数的文档和支持都是关于PHPTemplate的,如果你是从头开始建立一个站点的话,那么从长期的维护和社区支持这两个方面来看,PHPTemplate应该是最好的选择。
 

Drupal版本:

drupal主题

 

Drupal的行话来说,主题就是一组负责你站点外观的文件。你可以从http://drupal.org/project/Themes下载第3方主题,或者你可以自己动手创建一个主题,后者正是你在本章将要学习的。作为一个web设计者,主题由你所熟悉的大部分内容所组成:样式表,图片,JavaScript文件,等等。你将发现,在Drupal主题和纯HTML站点之间的区别就是模板文件。这些文件一般都包含大段的静态HTML,和一些小段的用来插入动态内容的代码。它们负责你站点的一个特定部分的外观。模板文件的语法依赖于它所使用的主题引擎。例如,列表8-1,8-2,8-3列出了3个模板文件的代码片段,它们输出的内容是一样但是包含的模板文件内容却完全不同。
 
列表 8-1. Smarty
<div id="top-nav">
    {if count($secondary_links)}
        <ul id="secondary">
        {foreach from=$secondary_links item=link}
            <li>{$link}</li>
        {/foreach}
        </ul>
    {/if}
 
    {if count($primary_links)}
        <ul id="primary">
        {foreach from=$primary_links item=link}
            <li>{$link}</li>
        {/foreach}
        </ul>
    {/if}
</div>
 
列表 8-2. PHPTAL
<div id="top-nav">
    <ul tal:condition="php:is_array(secondary_links)" id="secondary">
        <li tal:repeat="link secondary_links" tal:content="link">secondary link</li>
    </ul>
 
    <ul tal:condition="php:is_array(primary_links)" id="primary">
        <li tal:repeat="link primary_links" tal:content="link">primary link</li>
    </ul>
</div>
 
列表 8-3. PHPTemplate
<div id="top-nav">
    <?php if (count($secondary_links)) : ?>
        <ul id="secondary">
        <?php foreach ($secondary_links as $link): ?>
            <li><?php print $link?></li>
        <?php endforeach; ?>
        </ul>
    <?php endif; ?>
 
    <?php if (count($primary_links)) : ?>
        <ul id="primary">
        <?php foreach ($primary_links as $link): ?>
            <li><?php print $link?></li>
        <?php endforeach; ?>
        </ul>
    <?php endif; ?>
</div>
 
    每一个模板文件,由于它所使用的模板语言的不同,所以看起来也各不相同。模板文件的扩展名指明了它所使用的模板语言,也就是它所依赖的主题引擎(参看表8-1)
 
8-1 模板文件的扩展名指出了它所依赖的模板语言。
模板文件           主题引擎扩展
.theme                   PHP
.tpl.php                PHPTemplate*
.tal                      PHPTAL
.tpl                      Smarty
* PHPTemplate是Drupal的默认主题引擎
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

安装drupal主题

老葛的Drupal培训班 Think in Drupal

为了让一个新的主题显示在Drupal管理界面中,你需要把它放到sites/all/themes下面。这样不仅你的Drupal站点可以使用这个主题,一个多站点系统中的所有站点都可以使用该主题。如果你的是个多站点系统,而你又想把这个主题仅仅用在特定站点上,那么你可以把它放到sites/sitename/themes下面。你可以在你的站点安装多个主题,主题的安装过程和模块的基本相同。将主题文件放到相应的位置后,导航到管理界面“管理➤站点构建➤主题”。你可以安装多个主题,也可以一次启用多个主题。这意味着什么?通过启用多个主题,用户可以在他们的个人资料页面上,从已启用的主题中选择一个作为他们自己的主题。在用户访问站点时,就会使用所选的主题了。
  当下载或者创建一个新的主题时,将新建主题和核心主题以及第3方主题区分开来是个很好的习惯。我们推荐在你的themes文件夹下面创建两个文件夹。将自定义主题放到文件夹custom下,而将从drupal.org下载下来的第3方的主题放到drupal-contrib下。不过这个实践不是特别重要,不像模块目录下面那样特别注重这点,因为一个站点的主题一般只有几个,但是模块的数量却有很多。

Drupal版本:

构建一个PHPTemplate主题

老葛的Dupal培训班 Think in Drupal

创建一个主题,可以有多种方式,这取决于你的起始材料。假定你的设计者已经为你的站点提供了HTML和CSS文件。那么将设计者的设计转化为一个Drupal主题,到底难不难呢?它实际上不是很难,而且你能够轻易的完成工作的80%。不过还有20%---最后的难点了---它是Drupal主题制作高手与新手的分水岭。首先让我们从简单的部分开始。这里有个概括:
 
1. 为站点创建或修改HTML文件。
2. 为站点创建或修改CSS文件。
3. 创建一个.info文件,来向Drupal描述你的新主题。
4. 按照Drupal的标准为文件命名。
5. 在你的模板中,插入可用的变量。
6. 为单独的节点类型,区块,等等创建模板文件。
 
注意 如果你从头开始设计你的主题,那么在开放源代码WEB设计站点http://www.oswd.org里面有很多非常好的设计可供借鉴(注意这些是HTML和CSS设计,而不是Drupal主题)。
 

Drupal版本:

使用已有的HTML和CSS文件

 

我们假设你已经有了HTML页面和CSS样式,如列表8-4和8-5中所给出的,现在让你将它们转化为一个Drupal主题。显然在一个实际的项目中,你所用到的文件应该比这些更详细;我们在这里介绍的是方法,所以示例简单了一些。
 
列表 8-4. page.html
<html>
<head>
    <title>Page Title</title>
    <link rel="stylesheet" href="global.css" type="text/css" />
</head>
<body>
    <div id="container">
        <div id="header">
            <h1>Header</h1>
        </div>
        <div id="sidebar-left">
            <p>
                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam
                nonummy nibh euismod tincidunt ut.
            </p>
        </div>
        <div id="main">
            <h2>Subheading</h2>
            <p>
                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam
                nonummy nibh euismod tincidunt ut.
            </p>
        </div>
 
        <div id="footer">
            Footer
        </div>
    </div>
</body>
</html>
 
列表 8-5. global.css
#container {
    width: 90%;
    margin: 10px auto;
    background-color: #fff;
    color: #333;
    border: 1px solid gray;
    line-height: 130%;
}
#header {
    padding: .5em;
    background-color: #ddd;
    border-bottom: 1px solid gray;
}
#header h1 {
    padding: 0;
    margin: 0;
}
#sidebar-left {
    float: left;
    width: 160px;
    margin: 0;
    padding: 1em;
}
#main {
    margin-left: 200px;
    border-left: 1px solid gray;
    padding: 1em;
    max-width: 36em;
}
#footer {
    clear: both;
    margin: 0;
    padding: .5em;
    color: #333;
    background-color: #ddd;
    border-top: 1px solid gray;
}
#sidebar-left p {
    margin: 0 0 1em 0;
}
#main h2 {
    margin: 0 0 .5em 0;
}
 
    该设计如图8-3所示
 
8-3 在转化为Drupal主题以前的设计
 
    让我们将这个新主题叫作greyscale,在文件夹sites/all/themes/custom下面创建一个子文件夹greyscale。如果sites/all/themes/custom文件夹不存在的话,那么你需要新建一个。将page.html和global.css复制到greyscale文件夹下面。接下来,将page.html重命名为page.tpl.php,这样它将作为一个新的页面模板,为Drupal的每个页面服务了。
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

为你的drupal主题创建一个.info文件

老葛的Drupal培训班 Think in Drupal

每个主题都需要包含一个文件,用来向Drupal描述它的能力。这个文件就是主题的.info文件。由于我们把我们的主题叫作greyscale,所以我们的.info文件就被命名为greyscale.info。创建文件sites/all/themes/custom/greyscale/greyscale.info,并输入列表8-6所示的10行代码。
 
列表 8-6.主题的.info文件
; $Id$
name = Greyscale
core = 6.x
engine = phptemplate
regions[left] = Left sidebar
; We do not have a right sidebar.
; regions[right] = Right sidebar
regions[content] = Content
regions[header] = Header
regions[footer] = Footer
 
    如果我们想要更复杂一些的话,那么我们可以在我们的.info文件中为Drupal提供更多的信息。让我们看看这个文件都可以包含哪些信息,如列表8-7所示。
 
列表 8-7.带有更多信息的.info文件
; $Id$
; Name and core are required; all else is optional.
name = Greyscale
description = Demurely grey tableless theme.
screenshot = screenshot.png
core = 6.x
engine = phptemplate
 
regions[left] = Left sidebar
; We do not have a right sidebar
; regions[right] = Right sidebar
regions[content] = Content
regions[header] = Header
regions[footer] = Footer
 
; Features not commented out here appear as checkboxes
; on the theme configuration page for this theme.
features[] = logo
features[] = name
features[] = slogan
features[] = mission
features[] = node_user_picture
features[] = comment_user_picture
features[] = search
features[] = favicon
features[] = primary_links
features[] = secondary_links
 
; Stylesheets can be declared here or, for more
; control, be added by drupal_add_css() in template.php.
; Add a stylesheet for media="all":
stylesheets[all][] = mystylesheet.css
; Add a stylesheet for media="print":
stylesheets[print][] = printable.css
; Add a stylesheet for media="handheld":
stylesheets[handheld][] = smallscreen.css
; Add a stylesheet for media="screen, projection, tv":
stylesheets[screen, projection, tv][] = screen.css
; Override an existing Drupal stylesheet with our own
; (in this case the forum module's stylesheet):
stylesheets[all][] = forum.css
 
; JavaScript files can be declared here or, for more
; control, be added by drupal_add_js() in template.php.
; scripts.js is added automatically (just like style.css
; is added automatically to stylesheets[]).
scripts[] = custom.js
 
; PHP version is rarely used; you might need it if your
; templates have code that uses very new PHP features.
php = 5.2.0
 
; Themes may be based on other themes; for example, they
; may simply override some of the parent theme's CSS.
; See the Minnelli theme at themes/garland/minnelli for
; an example of a theme that does this in Drupal core.
base theme = garland
 
    由于Greyscale主题现在有了一个.info文件(列表8-6所给的简单的那个)和一个page.tpl.php文件,所以你就可以在管理界面中启用它了。导航到“管理➤站点构建➤主题”,将它设置为默认主题。

Drupal版本:

为你的drupal主题创建一个.info文件(1)

老葛的Drupal培训班 Think in Drupal

恭喜恭喜!现在你应该可以实际的看到你的设计了。外部的样式表还没有加载进来(我们将在后面讨论它),访问你的站点中的任何页面,都会一而再再而三的显示同一个页面,尽管如此,这也是一个了不起的开始!由于访问你的站点中的任何页面,都会显示page.tpl.php中的静态HTML内容,所以现在你无法进入管理界面了。我们将你关到了Drupal站点的门外面!哎哟。一不小心被关到了门外面,对于初学者来说,这是常碰到的事情,下面我们将向你讲述如何解决这个问题。一种方案是对刚才启用的主题进行重命名。在这种情况下,你可以简单的将greyscale重命名为greyscale_,这样你就可以重新返回站点到里面了。那是一个快速解决办法,但是由于你知道问题的真正所在(也就是,我们还没有包含动态内容),这里给出另一种方案:你可以向page.tpl.php中添加适当的变量,从而显示Drupal的动态内容而不是前面的静态内容。
    每一个PHPTemplate模板文件----比如page.tpl.php,node.tpl.phpblock.tpl.php等等----都有一组动态内容的变量传递给它们使用。打开page.tpl.php将相应的静态内容替换为相应的Drupal变量。不要担心,我很快就会对这些变量进行讲解。
 
<html>
<head>
    <title><?php print $head_title ?></title>
    <link rel="stylesheet" href="global.css" type="text/css" />
</head>
<body>
    <div id="container">
        <div id="header">
            <h1><?php print $site_name ?></h1>
            <?php print $header ?>
        </div>
 
        <?php if ($left): ?>
            <div id="sidebar-left">
               <?php print $left ?>
            </div>
        <?php endif; ?>
 
        <div id="main">
            <?php print $breadcrumb ?>
            <h2><?php print $title ?></h2>
            <?php print $content ?>
        </div>
 
        <div id="footer">
            <?php print $footer_message ?>
            <?php print $footer ?>
        </div>
    </div>
<?php print $closure ?>
</body>
</html>
 
    重新加载页面,你将发现,变量被来Drupal的内容替换了。你将注意到没有加载global.css样式表,这是因为指向该文件的路径不对。你可以手工的调整它的路径,或者你可以采用Drupal的方式来完成它,这样更加灵活并且具有其它好处。
  首先,将global.css重命名为style.css。根据规定,Drupal将自动的查找每个主题下面的style.css文件。一旦找到了这个文件,那么Drupal会将其添加到变量$styles里面,从而被传递给page.tpl.php.让我们使用下面的信息来更新page.tpl.php。
<html>
<head>
<title><?php print $head_title ?></title>
<?php print $styles ?>
</head>
...
    保存你的修改并重新加载页面。瞧!如果你查看页面的源代码的话,你将注意到,其它启用的模块所带有的样式表也被加载了进来,这些都是通过变量$styles实现的:
<html>
<head>
    <title>Example | Drupal 6</title>
    <link type="text/css" rel="stylesheet" media="all"
        href="modules/node/node.css?f" />
    <link type="text/css" rel="stylesheet" media="all"
        href="modules/system/defaults.css?f" />
    <link type="text/css" rel="stylesheet" media="all"
        href="modules/system/system.css?f" />
    <link type="text/css" rel="stylesheet" media="all"
        href="modules/system/system-menus.css?f" />
    <link type="text/css" rel="stylesheet" media="all"
        href="modules/user/user.css?f" />
    <link type="text/css" rel="stylesheet" media="all"
        href="sites/all/themes/greyscale/style.css?f" />
</head>
...
 
    通过将你的CSS文件命名为style.css,这样Drupal就可以使用它的CSS预处理引擎来对它进行处理,以消除CSS文件中所有的空白和换行,另外,它还将它们合并到了一起(Drupal没有使用多个样式表),作为一个文件提供给浏览器。关于这一特性的更多细节,参看第22章。
 
注意 Drupal在样式表URL的后面添加了伪查询字符串(在前面例子中的“?f”),这样就可以控制缓存了。当需要的时候,它可以修改字符串,比如运行update.php以后,或者在管理界面“管理➤站点构建 ➤性能”中清空了缓存以后。
 
    在你将global.css重命名为style.css以后,刷新浏览器,你将看到一个与图8-3中主题类似的主题,它包含了页首,页脚,和左边栏。尝试一下,导航到“管理➤站点构建 ➤区块”,将“在线用户”区块指定到左边栏。
  除了前面提到的这些变量以外,还有更多的变量可以添加到page.tpl.php和其它模板文件中。让我们深入的学习一下!如果你没有动手实现前面所给的例子,那么你可以浏览一下themes目录中所带有的核心主题,看看在这些主题中,变量是如何使用的。
 

Drupal版本:

理解drupal模板文件

老葛的Drupal培训班 Think in Drupal

一些主题包含各种模板文件,而有些仅包含page.tpl.php。那么你如何知道,你可以创建哪些Drupal能够识别的模板文件呢?创建模板文件时,所遵循的命名约定有哪些?在接下来的部分中,我将向你讲解使用模板文件的各种技能。
 
大图
    page.tpl.php是所有其它模板文件的祖宗,它负责站点的整体布局。其它模板文件被插入到了page.tpl.php中,如图8-4所说明的。
8-4其它的模板被插入到了page.tpl.php文件中
 
    在页面的构建期间,图8-4中block.tpl.php和node.tpl.php的插入是由主题系统自动完成的。还记不记得,你在前面的例子中创建的page.tpl.php文件?好的,变量$content包含了调用node.tpl.ph的输出,而$left包含了调用block.tpl.php的输出。让我们看看它是怎么工作的。
    让我们向Greyscale主题中添加一个节点模板文件。我们在这里没有从头创建一个,而是拷贝Drupal的默认节点模板文件;也就是,如果一个主题中找不到node.tpl.php文件的话,所使用的节点模板文件。将modules/node/node.tpl.php拷贝到sites/all/themes/custom/greyscale/node.tpl.php。然后访问“管理➤站点构建 ➤模块”页面,这样就会重新构建主题注册表。在重新构建的过程中,Drupal将找到sites/all/themes/custom/greyscale/node.tpl.php文件,并且从现在起,它将使用这个文件作为节点模板。导航到“创建内容➤Page”,来创建一个节点(只输入标题和主体字段就可以了)。现在你可以对你的node.tpl.php文件做一点小的修改(比如在它的最后面加上“你好吗!”)。现在你节点的显示,就使用了你修改的模板文件。
    对于block.tpl.php,方法是一样的(你可以在modules/system/block.tpl.php找到默认的区块模板文件),对于Drupal中的其它模板,也同样适用。
 

Drupal版本:

drupal theme()函数介绍

Drupal想要为一个可主题化的项目(比如节点,区块,面包屑,评论,或者用户签名)生成一些HTML输出时,它将查找用来为该项目生成HTML的主题函数或者模板文件。Drupal的所有部分,基本上都是可主题化的,这意味着,对于为该项目实际生成的HTML,你可以进行覆写。我们一会儿看一些例子。

 
提示 在Drupal中,可主题化的项目的列表,可参看http://api.drupal.org/api/group/themeable/6
 
老葛的Drupal培训班

Drupal版本:

drupal theme()工作原理概览

老葛的Drupal培训班 Think in Drupal

当一个简单的节点页面显示时,比如http://example.com/?q=node/3,都发生了什么呢,这里给出了大致的总结:
 
1. Drupal的菜单系统收到了请求,并将控制权转交给节点模块。
 
2. 在构建了节点数据结构以后,调用theme('node', $node, $teaser, $page)。这将查找合适的主题函数或者模板文件,定义模板文件中所用的各种变量,应用该模板,为节点生成最终的HTML。(如果有多个节点正在被显示,比如一个日志,那么对于每个节点都会调用一遍这个流程。)
 
3. 如果启用了评论模块,节点的评论也将被转化为HTML,并追加在节点的HTML后面。
 
4. 这样就返回了一团HTML(在index.php中,它就是变量$return),使用theme('page', $return),这样就再次传递给了theme()函数。
 
5. 在处理页面模板以前,Drupal作了一些预处理,比如,找出有哪些区域可用,以及在每个区域中显示哪些区块。通过调用theme('blocks', $region),将每个区块转化为HTML,theme('blocks', $region)是用来定义区块变量并应用区块模板的。在这里,你应该可以开始看到一个模式了。
 
6. 最后,Drupal定义了许多供页面模板使用的变量,并将其应用到页面模板中去。
 
    现在,从前面的列表中,你应该能够认识到theme()函数在Drupal中的重要地位。它负责运行预处理函数,来设置模板中所用的变量;它将主题函数的调用,分发给合适的函数或者查找合适的模板文件。而输出的结果就是HTML。这一流程的图示可参看图8-5。我们将在后面更深入的学习一下,这个函数是如何工作的。现在,应该不难理解,当Drupal想将一个节点转化为HTML时,就会调用theme('node')。根据所启用的主题,theme_node()将用来生成HTML,或者使用一个名为node.tpl.php的模板文件来生成HTML。
    可以在多个层次上,对这个流程进行覆写。例如,主题可以覆写内置的主题函数,所以,当调用theme('node')时,那么将会调用greyscale_node(),而不是默认的theme_node()。模板文件也有命名约定,我们将在后对它进行讲解,所以,模板文件node-story.tpl.php将专门负责Story类型的节点。
8-5. theme()函数调用时的执行流程
 

Drupal版本:

覆写可主题化的项目

老葛的Drupal培训班 Think in Drupal

Drupal的主题系统背后的核心哲理和钩子系统的类似。通过遵循命名规范,就可以标识出哪些函数是主题相关的函数,它们负责格式化并返回你站点的内容,或者使用模板文件负责输出HTML内容。
 

Drupal版本:

覆写drupal主题函数

老葛的Drupal培训班 Think in Drupal

正如你看到的,可主题化的项目是通过它们的函数名来标识的,每个函数名前都带有前缀“theme_”,或者还可以通过模板文件来标识。这一命名规范使得Drupal能够为所有的可主题化函数创建一个函数覆写机制。这样,设计者就可以指示Drupal执行一个具有更高优先级的自定义函数,从而替代开发者在模块中给出的默认的主题函数,或者替代Drupal的默认模板文件。例如,让我们检查一下,在构建站点的面包屑时该流程是怎么工作的。
    打开includes/theme.inc文件,并检查里面的函数。这里的许多函数都以theme_开头,这就告诉人们它们是可以被覆写的。特别的,我们看看theme_breadcrumb():
 
/**
 * Return a themed breadcrumb trail.
 *
 * @param $breadcrumb
 * An array containing the breadcrumb links.
 * @return a string containing the breadcrumb output.
 */
function theme_breadcrumb($breadcrumb) {
     if (!empty($breadcrumb)) {
         return '<div class="breadcrumb">'. implode(' » ', $breadcrumb) .'</div>';
     }
}
 
这个函数控制着Drupal中面包屑导航条的HTML输出。当前,它在面包屑的每一项之间添加了一个向右的双箭头分隔符(»)。假定你想将div标签改为span标签,并使用星号(*)来代替双箭头(»)。那么你该怎么办呢?一种方式是在theme.inc中修改这个函数,保存,并调用。这样也能达到目的。(别!别!千万别这样做!)。我们有更好的方式。
你有没有见过Drupal核心中是怎么调用这些主题函数的?你永远都不会看到直接调用theme_breadcrumb()的情况。替代的,它通常包装在帮助函数theme()中。你期望这样调用这个函数:
 
theme_breadcrumb($breadcrumb)
 
    但实际不是这样。替代的,你将看到开发者这样调用:
 
theme('breadcrumb', $breadcrumb);
 
    这个通用的theme()函数负责初始化主题层,并将函数调用分发到合适的位置,这使得我们能够以更优雅的方式来解决我们的问题。图8-5展示了通过调用theme(),指示Drupal按照下面的次序查来找相应的面包屑函数。
假定你使用的主题为Greyscale,,它是基于PHPTemplate的主题,那么Drupal将会查找下面的函数(我们暂且忽略一下breadcrumb.tpl.php):
 
greyscale_breadcrumb()
phptemplate_breadcrumb()
sites/all/themes/custom/greyscale/breadcrumb.tpl.php
theme_breadcrumb()
 
    我们看到函数phptemplate_breadcrumb()可以覆写内置的面包屑函数,那么我们要把这个函数放到哪里呢?
    很简单,那就是你主题的template.php文件,在这里你可以覆写Drupal的默认主题函数,拦截和创建传递给模板文件的自定义变量.
 
注意 在做这些练习的时候,不要使用Garland作为当前主题,因为Garland已经有了一个template.php文件.替代的,在这里可以使用Greyscale或者Bluemarine.
 
    为了修改Drupal的面包屑,创建文件sites/all/themes/custom/greyscale/template.ph,并将theme.inc中的theme_breadcrumb()函数复制并粘贴到该文件里面。记住要包含<?php标签。还有对函数要进行重命名,将theme_breadcrumb改为phptemplate_breadcrumb。接着,导航到“管理➤站点构建 ➤模块”以重新构建主题注册表,这样Drupal就能够找到你的新函数了。
 
<?php
/**
 * Return a themed breadcrumb trail.
 *
 * @param $breadcrumb
 * An array containing the breadcrumb links.
 * @return a string containing the breadcrumb output.
 */
function phptemplate_breadcrumb($breadcrumb) {
     if (!empty($breadcrumb)) {
         return '<span class="breadcrumb">'. implode(' * ', $breadcrumb) .'</span>';
     }
}
 

当下一次Drupal需要生成面包屑时,它就会首先找到你的函数,并使用它来代替默认的theme_breadcrumb()函数,这样面包屑中就会包含你的星号,而不再包含默认的双箭头了。很漂亮,不是么?通过theme()函数来管理所有的主题函数调用,如果当前主题覆写了任何一个theme_ 函数,那么Drupal将使用它们来代替默认的主题函数。开发者,请注意:在你的模块中任何需要输出HTML或者XML的部分都应该使用以“theme_”开头的主题函数,这样设计者就可以对它们进行覆写了。

Drupal版本:

覆写drupal模板文件

老葛的Drupal培训班 Think in Drupal

假定你和一个设计者一同工作,你告诉他/她“从代码中找到主题函数并对其进行覆写”,这是不是有点难为人了?幸运的是,有另一种方式,使得设计者能够更容易的修改外观。你可以将匹配的可主题化项目替换为它们自己的模板文件,我将通过大家熟悉的面包屑例子来说明这一点。
    在我们开始以前,首先确保没有主题函数对heme_breadcrumb()进行了覆写。所以,如果你在前面的一节中,在你主题的template.php文件里面创建了phptemplate_breadcrumb()函数的话,那么把它注释掉。接着创建文件sites/all/themes/custom/greyscale/breadcrumb.tpl.php。这是面包屑的新模板文件。因为我们想将<div>标签替换为<span>标签,继续前进,向该文件中添加以下内容:
 
<?php if (!empty($breadcrumb)): ?>
    <span class="breadcrumb"><?php print implode(' ! ', $breadcrumb) ?></span>
<?php endif; ?>
 
现在设计者就很容易编辑文件了。现在你需要告诉Drupal,在显示面包屑时调用这个模板文件。为了实现这一点,你需要导航到“管理➤站点构建 ➤模块”,来重新构建主题注册表。在重新构建主题注册表的时候,Drupal将找到你的breadcrumb.tpl.php文件,并将面包屑的可主题化项目映射到该模板文件上。
 

Drupal版本:

添加和操纵drupal模板变量

 

问题又来了:如果你可以创建你自己的模板文件并控制传递给它们的参数,那么你如何操纵或者添加传递给页面和节点模板文件的变量呢?
 
注意 只有实现为模板文件的可主题化项目,才有变量的聚合和传递一说。如果可主题化项目采用主题函数的实现方式,那么就不需要向其传递变量了。
 
每次加载一个模板文件都需要调用一系列的预处理函数。这些函数负责聚集变量,以将其传递给合适的模板文件。 让我们继续使用面包屑作为我们的例子。首先,让我们修改sites/all/themes/custom/greyscale/breadcrumb.tpl.php文件,为面包屑分隔符使用一个名为$breadcrumb_delimiter的变量:
 
<?php if (!empty($breadcrumb)): ?>
    <span class="breadcrumb">
        <?php print implode(' '. $breadcrumb_delimiter .' ', $breadcrumb) ?>
    </span>
<?php endif; ?>
 
    那么我们如何设置变量$breadcrumb_delimiter的值呢?一种可选的方式是,在模块中设置。我们可以创建文件sites/all/modules/custom/crumbpicker.info:
 
; $Id$
name = Breadcrumb Picker
description = Provide a character for the breadcrumb trail delimiter.
package = Pro Drupal Development
core = 6.x
 
    这个模块非常小,下面是文件sites/all/modules/custom/crumbpicker.module中的内容:
 
<?php
// $Id$
 
/**
 * @file
 * Provide a character for the breadcrumb trail delimiter.
 */
 
/**
 * Implementation of $modulename_preprocess_$hook().
 */
function crumbpicker_preprocess_breadcrumb(&$variables) {
    $variables['breadcrumb_delimiter'] = '/';
}
 
    导航到“管理➤站点构建 ➤模块”,启用这个模块,这样你的面包屑就变成了这个样子:首页/管理/站点构建。
    前面的例子说明了,如何使用模块来设置模板文件中的变量。如果每设置一个变量,都需要编写一个模块的话,那不是太麻烦了吗?有没有更简单的方式呢?当然有了,那就是使用template.php文件。让我们编写一个函数来设置面包屑分隔符。向你的主题的template.php文件中添加以下代码:
 
/**
 * Implementation of $themeenginename_preprocess_$hook().
 * Variables we set here will be available to the breadcrumb template file.
 */
function phptemplate_preprocess_breadcrumb(&$variables) {
    $variables['breadcrumb_delimiter'] = '#';
}
 
    这种方式与创建模块相比,更加简单明了,而模块的方式通常适用于在已有的模块中向模板提供变量;如果仅仅为了提供变量,就编写一个模块的话,那么就大材小用了。现在,我们使用模块提供了一个变量,还使用template.php文件中的函数提供了一个变量,那么实际中会使用哪个变量呢?
    实际上,预处理函数是有层级结构的,它们按照一定的顺序先后执行,后面的预处理函数可以覆写前面的预处理函数中定义的变量。在前面的例子中,面包屑的分隔符将会是#,这是因为phptemplate_preprocess_breadcrumb()放在crumbpicker_preprocess_breadcrumb()后面执行,前者就对后者中的$breadcrumb_delimiter变量进行了覆写。预处理函数的执行顺序,如图8-6所示。
    对于使用Greyscale主题的面包屑的主题化,实际的执行顺序(从前向后)如下:
 
template_preprocess()
template_preprocess_breadcrumb()
crumbpicker_preprocess()
crumbpicker_preprocess_breadcrumb()
phptemplate_preprocess()
phptemplate_preprocess_breadcrumb()
greyscale_preprocess()
greyscale_preprocess_breadcrumb()
 
    因此,greyscale_preprocess_breadcrumb()可以覆写已经设置的任意变量;它是在变量传递给模板文件以前,最后才调用的。如果这些函数中,只有几个实现了,那么调用所有的函数,会不会浪费时间呢?不错,在构建主题注册表的时候,Drupal将判定有哪些函数实现了,并且只调用这些实现了的函数。
 
8-6.预处理函数的执行顺序
 
注意预处理函数中,你还可以修改的一个变量是$vars['template_file'],它是Drupal将要调用的模板文件的名字。如果你需要基于一个更复杂的条件来加载另一个模板文件的话,那么你可以在这里修改这个变量。
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

适用于所有drupal模板的变量

Drupal在为特定模板设置变量以前,它会预先设置一些通用变量:

 
• $zebra:它的值要么为odd,要么为even,它随着theme('node')的每次调用而不断切换,用来方便的控制节点列表的样式。
 
• $id:模板的编码,模板每被调用一次,它自增1。例如,theme('node')函数每调用一次,$id都会自增1。所以在一个包含了许多节点摘要的列表页面中,应用于节点模板(它负责节点摘要的主题化)的$id变量会随着节点摘要的增加而增加。
 
• $directory:这是指向主题的路径,比如themes/bluemarine(如果一个主题没有一个模板文件,那么该路径就指向提供模板文件的模块,比如modules/node)。
 
    如果数据库可用,并且站点不处于维护模式下,也就是大多数的时候,下面的变量将被预先设置:
 
• $is_admin: user_access('access administration pages')的返回结果
 
• $is_front: 当前页面为首页时,返回TRUE;否则,返回FALSE
 
• $logged_in: 当前用户已经登录时,返回TRUE;否则,返回FALSE
 
• $user: 全局$user对象(不要在主题中,未经安全处理就直接使用这个对象的属性;参看第20章)
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

page.tpl.php

如果你需要创建一个自定义的页面模板,那么你可以复制已有主题中的page.tpl.php文件,或者复制modules/system/page.tpl.php,复制完以后,再按照你的需要对它进行修改。实际上,一个最小的主题,所需要的文件仅仅是.info文件和style.css文件;如果你的主题中没有包含page.tpl.php文件,那么Drupal将使用modules/system/page.tpl.php。对于基本的主题,这可能就是你所想要的了。

    下面是在页面模板中可用的变量:
 
• $base_path: Drupal安装的基本路径。如果安装在根目录下,这是最简单的,它将默认为/。
 
• $body_classes: 一个使用空格分隔的CSS类名的字符串,它将应用于body元素中。使用这些CSS类,可以创建时髦的主题。例如,对于一个节点类型的页面,http://example.com/?q=node/3,$body_classes的值将会是“not-front logged-in page-node node-type-page one-sidebar sidebar-left”。
 
• $breadcrumb:返回页面上用于导航的面包屑的HTML。
 
• $closure:返回hook_footer()的输出,它通常显示在页面的底部。恰好在body的结束标签前面。hook_footer()允许模块在页面的尾部插入HTML或者JavaScript。注意,在hook_footer()中不能使用drupal_add_js()。
 
警告 $closure是一个非常重要的变量,它应该包含在所有的page.tpl.php文件中,这是因为,许多模块都依赖于这个变量。如果没有包含这个变量的话,那么这些模块可能就无法正常工作,因为它们将无法插入它们的HTML或者JavaScript。
 
• $content:返回将要显示的HTML内容。例如,它可以包含一个节点,一组节点,管理界面的内容,等等。
 
• $css:.返回一个数组结构,里面包含了将要添加到页面中的所有css文件。如果你需要$css数组的HTML版本,那么可以使用$styles。
 
• $directory:主题所在的相对路径。例如themes/bluemarine 或者 sites/all/themes/custom/greyscale.。你通常把这个变量和$base_path一起使用,来构建你的站点主题的绝对路径:
 
<?php print $base_path . $directory ?>
将转变为
<?php print '/' . 'sites/all/themes/custom/greyscale' ?>
 
• $feed_icons:为该页面返回RSS种子链接。可以通过drupal_add_feed()来添加RSS种子链接。
 
• $footer: 返回页脚区域的HTML,里面包含了该区域中所有区块的HTML。不要把它与hook_footer()混淆了,后者是一个Drupal钩子函数,用来向变量$closure中添加HTML或者JavaScript,而变量$closure显示在body结束标签的前面。
 
• $footer_message:返回页脚消息文本,可在管理界面“管理➤站点配置➤站点信息”中对它进行设置。
 
• $front_page: 不带参数的url()函数的输出;例如,/drupal/。在链接到一个站点的首页时,使用$front_page来代替$base_path,这是因为在多语言站点上,$front_page变量可以包含语言的域和前缀。
 
• $head:返回放置在<head></head>部分的HTML。模块可以通过调用drupal_set_html_head()来向$head添加额外的标识文本(markup)。
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

page.tpl.php(1)

 

• $head_title:在页面标题中所显示的文本,放在HTML <title></title>标签中。可以使用drupal_get_title()来获取它。
 
• $header: 返回页首区域的HTML,里面包含了该区域中所有区块的HTML。
 
• $help: 帮助文本,大多数用于管理页面。模块可以通过实现hook_help()来填充这个变量。
 
• $is_front: 如果当前显示的为首页的话,返回TRUE。
 
• $language:一个对象,包含了当前站点语言的多个属性。例如,$language->language可能是en,而$language->name可能是English
 
• $layout:这一变量允许你定义不同布局类型的样式,而变量$layout的值则取决于已启用的边栏的数量。包括以下可能值:none, left, right, 和both。
 
• $left:返回左边栏的HTML,包含了该区域中所有区块的HTML。
 
• $logged_in: 当前用户已经登录时,返回TRUE;否则,返回FALSE
 
• $logo: 指向logo图片的路径,可以在已启用主题的配置页面中进行定义。它在Drupal的默认页面模板中,这样使用:
<img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" />
 
• $messages: 返回消息的HTML,消息通常包括:表单的验证错误消息,表单提交成功的通知消息,以及其它各种消息。它通常显示在页面的顶部。
 
• $mission: 返回站点使命的文本,可在管理界面“管理➤站点配置➤站点信息”中输入.只有当$is_front为TRUE时,这个变量才可以使用。
 
• $node:整个节点对象,当查看一个单独的节点页面时可用。
 
• $primary_links: 一个包含了一级链接的数组。可在“管理➤站点构建➤菜单”中定义它们。通常$primary_links通过函数theme('links')来定制输出的样式,如下所示:
 
<?php
    print theme('links', $primary_links, array('class' => 'links primary-links'))
?>
 
• $right:返回右边栏的HTML,包含了该区域中所有区块的HTML。
 
• $scripts: 返回添加到页面的<script>标签中的HTML。jQuery也是通过它加载进来的(关于jQuery的更多信息,可参看第17章)
 
• $search_box: 返回搜索表单的HTML。如果管理员在已启用主题中的配置页面禁止了搜索的显示,或者禁用了搜索模块,那么$site_slogan为空。
 
• $secondary_links: 一个包含了二级链接的数组。可在“管理➤站点构建➤菜单”中定义它们。通常$secondary_links通过函数theme('links')来定制输出的样式,如下所示:
 
<?php
    print theme('links', $secondary_links, array('class' =>
        'links primary-links'))
?>
 
• $show_blocks: 这是theme('page', $content, $show_blocks, $show_messages)中的参数。它默认为TRUE;当它为FALSE时,用来填充左边栏和右边栏的$blocks变量将被设置为空,这样区块就无法显示了。
 
• $show_messages: 这是theme('page', $content, $show_blocks, $show_messages)中的参数。它默认为TRUE;当它为FALSE时,$messages变量(参看前面的$messages变量)将被设置为空,这样消息就无法显示了。
 
• $site_name: 站点的名称。在“管理➤站点配置➤站点信息”中设置。当管理员在已启用主题的配置页面中禁止显示时,$site_ name为空。
 
• $site_slogan: 站点的标语。在“管理➤站点配置➤站点信息”中设置。当管理员在已启用主题的配置页面中禁止显示时,$site_slogan为空。
 
• $styles:返回页面需要的CSS文件链接的HTML。可以通过drupal_add_css(),将CSS文件添加到变量$styles中去。
 
• $tabs: 返回标签(tab)的HTML,比如节点的View/Edit标签。在Drupal的核心主题中,标签通常位于页面的顶部。
 
• $template_files: 当前显示页面可用的模板文件名字的建议。这些名字没有包含扩展名,例如page-node, page-front。查找这些模板文件时,对于它们的默认顺序,可参看“多个页面模板”一节。
 
• $title:主内容标题,与$head_title不同。当查看一个单独的节点页面时,$title就是节点的标题。当常看Drupal的管理界面时,通常由菜单项来设置$title,菜单项对应于当前查看的页面。(菜单项的更多信息,可参看第4章).
 
警告 即便是你没有在page.tpl.php中使用区域变量($header, $footer, $left,$right,它们仍然会被构建。这是一个性能问题,因为Drupal将构建所有的区块,而只对于特定的页面视图,才将它们扔掉。如果自定义页面模板中不需要区块,除了从模板文件中排除该变量以外,还有一个更好的方式,那就是到区块的管理界面中去,禁止这些区块显示在你的自定义页面中去。关于在特定页面禁用区块的更多信息,可参看第9章。
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

node.tpl.php

老葛的Drupal培训班 Think in Drupal

节点模板负责控制一个页面内部的单独的内容片段的显示。节点模板影响的不是整个页面,而仅仅是page.tpl.php中的变量$content。它们负责节点是以摘要视图的方式显示(当多个节点在同一个列表页面时),还是以主体视图的方式显示(当page.tpl.php中的变量$content仅包含一个节点,并且该节点单独成页时)。节点模板文件中的$page变量,当为主体视图时,它为真,当为摘要视图时,它为假。
    文件node.tpl.php是一个通用的模板,可用来处理所有节点的视图。如果你想要一个不同的模板,比如说日志节点模板与论坛节点模板不一致时,那该怎么办呢?你如何才能为特定的节点类型创建一个专有的模板,而不是全都使用一个通用的模板?
  节点模板很容易实现这一点。简单的将node.tpl.php复制一份并重命名为node-nodetype.tpl.php,这样PHPTemplate就会选择这个模板,而不是选择通用模板了。所以对于日志节点的主题化,只需要简单的使用node-blog.tpl.php就可以了。对于你通过管理界面“管理➤内容管理➤内容类型”创建的任何节点类型,都可以使用同样的方式创建一个单独的模板文件。在节点模板中,你可以使用下面的变量:
 
• $build_mode: 构建节点的上下文的相关信息。它的值是下面的常量之一:NODE_BUILD_NORMAL, NODE_BUILD_PREVIEW, NODE_BUILD_SEARCH_INDEX, NODE_BUILD_SEARCH_RESULT, or NODE_BUILD_RSS.
 
• $content: 节点的主体部分,如果是在一个列表页面中显示时,它为节点的摘要部分。
 
• $date: 格式化的节点创建日期。通过使用$created,比如, format_date($created, 'large'),你可以选择一个不同的日期格式。
 
• $links: 与节点相关的链接,比如“阅读全文” 或者“添加新评论”。模块通过实现hook_link()来添加额外的链接。这些链接已经经过了theme_links()的处理。
 
• $name: 该页面的作者的格式化的名称,链接到他/她的个人资料页面。
 
• $node:整个节点对象,还有它的所有属性。
 
• $node_url: URL路径中该节点的部分;例如,对于http://example.com/?q=node/3,它的值为/node/3。
 
• $page: 当节点独自显示为一个页面时,它为TRUE;否则,它为FALSE。
 
• $picture: 如果在管理界面“管理➤站点构建➤主题➤配置”中,选择了“文章中显示用户头像”,并在全局主题设置中的“显示文章的发布信息”选项中选中该节点类型,那么theme('user_picture', $node)的输出将被放到$picture中。
 
• $taxonomy.由节点的分类术语构成的一个数组,术语的格式适用于传递给theme_links()函数。事实上,theme_links()的输出可用在$terms变量中。

Drupal版本:

node.tpl.php(1)

老葛的Drupal培训班 Think in Drupal

• $teaser:布尔值,用来判定是否以摘要的方式显示。当它为假时,意味着节点采用主体方式显示,为真时,表示以摘要方式显示。
 
• $terms:与该节点相关的分类术语的HTML。每个术语都链接到它自己的分类术语页面。
 
• $title:节点的标题。当在多节点列表页面时,这里还会有个链接,指向该节点的主体视图页面。
 
• $submitted: 来自于theme('node_submitted', $node) 的“Submitted by”文本。管理员可以配置这一信息的显示,在一个基于单个节点类型的主题配置页面进行配置。
 
• $picture:用户头像的HTML,如果启用了头像并且设置了用户头像。
 
注意 因为节点属性和传递给节点模板的变量混合在了一起,所以节点属性也可以作为变量。对于节点属性列表,可参看第7章。直接使用节点属性可能会存在安全风险;如何将安全风险降到最低,可参看第20章。
 
    通常节点模板文件中的变量$content,并不会以你期望的方式来构建数据。当使用了一个扩展了节点属性的第3方模块时,比如CCK字段相关模块,这一点尤为明显。
 
 幸运的是,PHPTemplate将整个节点对象传递给了节点模板文件。如果你在你的模板文件中的顶部,使用下面的调试语句,并重新加载包含节点的页面,你将看到构成节点的所有属性。如果查看页面的源代码的话,读起来可能会更容易一些。
 
<pre>
    <?php print_r($node) ?>
</pre>
 
    现在你可以看到构成节点的所有部分了,直接访问它们的属性,像期望的那样为它们添加标识文本,而不需要再使用聚合变量$content了.
 
警告:在直接格式化一个节点对象时,你还必须为你站点的安全负责。参看第20章,学习如何将用户提交的数据包装在适当的函数中,以阻止XSS攻击
 

Drupal版本:

block.tpl.php

老葛的Drupal培训班 Think in Drupal

你可以在“管理站点构建区块”中查看所有的区块,而区块的外观则由block.tpl.php模板负责。如果你对区块不熟悉的话,可以参看第9章以获得更多信息。像页面模板和节点模板文件一样,区块系统按照一定的顺序来查找模板文件。先后顺序如下所示:
 
block-modulename-delta.tpl.php
block-modulename.tpl.php
block-region.tpl.php
block.tpl.php
 
    在上面的序列中,modulename是实现了该区块的模块的名称.例如,区块“在线用户”是由模块user.module实现的(假定区块的delta为1),下面是可用于该区块的模板文件的先后顺序:
 
block-user-1.tpl.php
block-user.tpl.php
block-left.tpl.php
block.tpl.php
 
    由站点管理员在后台创建的区块,它们都与区块模块绑定在了一起,所以它们在前面序列中的modulename都为block。如果你不知道那个模块实现了给定的区块,通过做一些PHP调试,你可以找到所有的原始信息。通过在你的block.tpl.php的顶部键入下面一段代码,你可以输出当前页面启用的每个区块的整个区块对象。
 
<pre>
    <?php print_r($block); ?>
</pre>
 
         如果你查看页面的源文件的话,阅读起来会更容易一些。下面是区块“在线用户”所显示的:
 
stdClass Object
(
    [bid] => 42
    [module] => user
    [delta] => 3
    [theme] => bluemarine
    [status] => 1
    [weight] => 0
    [region] => footer
    [custom] => 0
    [throttle] => 0
    [visibility] => 0
    [pages] =>
    [title] =>
    [cache] => -1
    [subject] => Who's online
    [content] => There are currently ...
)
 
         现在你得到了该区块的所有细节了,根据你需要覆盖的范围,你可以非常容易的构建一个或多个如下所示的区块模板文件:
 
block-user-3.tpl.php // Target just the Who's Online block.
block-user.tpl.php // Target all block output by user module.
block-footer.tpl.php // Target all blocks in the footer region.
block.tpl.php // Target all blocks on any page.
 
下面是在区块模板文件中你可以使用的默认变量:
 
• $block:整个区块对象。一般情况下,你会使用$block->subject和$block->content;具体的示例可参看核心主题中的block.tpl.php文件。
 
• $block_id:一个整数,当每次生成一个区块和调用一次区块模板文件时,自增1。
 
• $block_zebra: 当$block_id自增时,该变量不断的在“odd”和“even.”之间切换。
 

Drupal版本:

comment.tpl.php

老葛的Drupal培训班 Think in Drupal

模板文件comment.tpl.php负责评论的外观显示。下面是评论模板文件中可以使用的变量:
 
• $author: 带有超链接的作者名,如果他/她有一个个人资料页面的话,那么将链接到该页面。
 
• $comment: 评论对象,包含了所有的评论属性。
 
• $content: 评论的内容。
 
• $date: 格式化的发布日期。通过调用format_date(),比如, format_date($comment->timestamp, 'large'),你可以选择一个不同的日期格式。
 
• $links: 与评论相关的上下文链接的HTML,比如“编辑”, “回复”, 和 “删除”。
 
• $new: 对于一个当前登录用户,它将为未读评论返回一个“new”,为一个更新过的评论返回“updated”。通过覆写includes/theme.inc中的theme_mark(),你可修改从$new中返回的文本。对于匿名用户,Drupal将不会为其追踪评论是否读过或者修改过。
 
• $node:这个评论所对应的节点的整个节点对象。
 
• $picture: 用户头像的HTML。你必须在“管理➤用户管理➤用户设置”中启用头像图片支持,你还必须为每个启用的主题,在其配置页面上选中复选框“评论中作者头像”。最后,要么站点管理员上传一个默认图片,或者用户也需要上传一个图片,这样就有可显示的图片了。
 
• $signature: 经过过滤的用户签名HTML。如果你想使用这个变量的话,那么需要在“管理➤用户管理 ➤用户设置”中启用签名支持。
 
• $status: 反映评论的状态,有以下可能值:comment-preview, comment-unpublished,和comment-published。
 
• $submitted: 带有用户名和日期的“Submitted by”字符串,由theme('comment_submitted', $comment)输出。
 
• $title: 带有超链接的标题,链接指向该评论,并且包含URL片段。
 

Drupal版本:

box.tpl.php

 

模板文件 box.tpl.php是Drupal内部最容易引起歧义的模板文件了。它用在Drupal核心中,用来包裹评论的提交表单和查询结果。除了这些,就很少用到它了。它对区块不起作用,这可能是个容易混淆的地方(这是因为由管理员创建的区块,保存在数据库中名为boxes的表中).在盒子模板中,你可使用的默认变量如下所示:
 
• $content:盒子的内容
 
• $region:盒子所在的区域。例如包括header, left,和main。
 
• $title: 盒子的标题。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

其它的.tpl.php文件

老葛的Drupal培训班 Think in Drupal

到目前为止,我们所讲到的模板都是最常用的模板。但是还有许多其它模板可用。为了查看它们,你可以浏览modules目录,在那里查找以.tpl.php结尾的文件。例如,modules/forum下面包含了6个这样的文件。这些文件都包含了很好的文档,你可以直接将它们拷贝到你的自定义主题所在的目录中,根据需要对其进行修改。这比从头创建一个模板要高效很多!
 

Drupal版本:

多页面模板

老葛的Drupal培训班 Think in Drupal

当你想为站点上的不同页面创建不同的布局时,而一个单独的页面布局不能再满足所有需要了,你该怎么办呢?下面有一些好的实践,用来创建其它的页面模板。
  你可以基于站点的当前系统URL,来创建其它的页面模板文件。例如,如果你访问页面http://example.com/?q=user/1,那么PHPTemplate将以下面的顺序来查找页面模板,这里假定你使用的Greyscale主题:
 
sites/all/themes/custom/greyscale/page-user-1.tpl.php
modules/system/page-user-1.tpl.php
sites/all/themes/custom/greyscale/page-user.tpl.php
modules/system/page-user.tpl.php
sites/all/themes/custom/greyscale/page.tpl.php
modules/system/page.tpl.php
 
    PHPTemplate一旦找到一个要包含的模板文件,那么它将会停止查找。page-user.tpl.php适用于所有的用户页面,而page-user-1仅仅可以用在URL为user/1, user/1/edit,等等的页面。如果Drupal在主题系统中的任何位置都找不到页面模板,那么将会调用内置的模板modules/system/page.tpl.php。
 
注意 在这里,Drupal使用的是内部的URL,所以即便是使用了path或者pathauto模板(这两个模块允许你创建URL别名),对于页面模板,你仍然需要使用Drupal的内部URL,而不是使用别名。
   
    让我们使用节点编辑页面http://example.com/?q=node/1/edit作为示例。下面是PHPTemplate查找的页面模板文件的顺序:
 
sites/all/themes/custom/greyscale/page-node-edit.tpl.php
modules/system/page-node-edit.tpl.php
sites/all/themes/custom/greyscale/page-node-1.tpl.php
modules/system/page-node-1.tpl.php
sites/all/themes/custom/greyscale/page-node.tpl.php
modules/system/page-node.tpl.php
sites/all/themes/custom/greyscale/page.tpl.php
modules/system/page.tpl.php
 
    如果你是一个Drupal模块程序员的话,通过学习前面的例子,你应该可以很方便的为你的模块提供一个默认模板;具体的示例,你可以到modules目录下看看。
 
提示 如果想为你站点的首页创建一个自定义页面模板,那么只需要简单的创建一个名为page-front.tpl.php的模板文件就可以了
 

Drupal版本:

高级Drupal主题化

老葛的Drupal培训班 Think in Drupal

如果你想深入的了解Drupal主题化的工作原理的话,有两个重要的方面是需要掌握的。首先,我们先开始学习一下主题系统的背后的引擎:主题注册表。接着,我们对theme()函数逐步进行分析,这样你就可以掌握它的工作原理,并知道在什么地方进行对它覆写了。
 
主题注册表
    主题注册表是Drupal用来追踪所有的主题化函数和模板的地方。在Drupal中,每一个可主题化的项目都是通过函数或者模板来主题化的。当Drupal构建主题注册表时,它为每一项查找和匹配相关信息。这意味着这一过程不需要发生在运行时,从而让Drupal跑得更快。
 

Drupal版本:

注册表是如何构建的

老葛的Drupal培训班 Think in Drupal

当启用一个新的主题时,就会重新构建主题注册表,此时它按照下面的顺序来查找主题钩子:
 
1.首先,它查找有哪些模块实现了hook_theme(),从而找到这些模块提供的主题函数和模板文件。
 
2. 如果该主题是基于另一个主题的,那么首先会调用基主题引擎中的hook_theme()实现。例如,Minnelli是一个基于Garland的主题。它的基主题的主题引擎为PHPTemplate。所以会调用phptemplate_theme()来查找前缀为phptemplate_或garland_的主题函数,以及基主题目录下按照特定方式命名的模板文件。例如,模板文件themes/garland/node.tpl.php就添加到了这里。
 
3. 调用该主题的hook_theme()实现。所以,对于Minnelli,将会调用phptemplate_theme()来查找前缀为phptemplate_ 或minnelli_的主题函数,以及该主题目录下的模板文件。所以,如果Minnelli在themes/garland/minnelli/node.tpl.php提供了一个节点模板,那么它就会被发现。
 
    注意,在每一步中,新发现的主题函数和模板文件,能够覆写注册表中已有的主题函数和模板文件。这就是继承的机制,它允许你覆写任意的主题函数或者模板文件。
    让我们更进一步的检查一下,在模块中是如何实现hook_theme()的。这个主题钩子的任务是,返回一个包含可主题化项目的数组。当一个项目通过主题函数主题化时,函数的参数也会被包含进来。例如,面包屑是由函数theme_breadcrumb($breadcrumb)负责主题化的。在一个假定的foo.module模块的主题钩子中,通过下面的方式来说明面包屑是可以主题化的:
 
/**
 * Implementation of hook_theme().
 */
foo_theme() {
    return array(
        'breadcrumb' => array(
            'arguments' => array ('breadcrumb' => NULL),
        );
    );
}
 
    如果传递过来的参数为空,那么就使用NULL作为默认值。现在你已经描述了该项的名字和它的参数,并给出了参数的默认值。如果你的主题函数,或者模板预处理函数是包含在另外的一个文件中的,那么你需要使用file键把它包含进来:
 
/**
 * Implementation of hook_theme().
 */
function user_theme() {
    return array(
        'user_filter_form' => array(
            'arguments' => array('form' => NULL),
            'file' => 'user.admin.inc',
        ),
        ...
    );
}
 
    如果你需要声明,一个可主题化项目使用的是模板文件,而不是主题函数的话,你可以在主题钩子中定义模板文件的名字(不带扩展名.tpl.php):
 
/**
 * Implementation of hook_theme().
 */
function user_theme() {
    return array(
        'user_profile_item' => array(
            'arguments' => array('element' => NULL),
            'template' => 'user-profile-item',
            'file' => 'user.pages.inc',
        ),
        ...
    );
}
 
    在前面的user_profile_item例子中,模板文件是通过template键指定的,可以在modules/user/user-profile-item.tpl.php找到。模板的预处理函数位于modules/user/user.pages.inc中,名为template_preprocess_user_profile_item()。template_preprocess()中定义的变量,以及在键arguments中定义的变量$element,都会被传递给template_preprocess_user_profile_item()。变量$element的值,在显示期间被指定。

Drupal版本:

逐步分析theme()函数

在本节中,我们将深入幕后,我们将学习theme()实际是如何工作的。假定当前主题为Drupal的核心主题Bluemarine,并进行了下面的主题调用,让我们逐步分析一下调用的执行路径。

 
theme('node', $node, $teaser, $page)
 
    首先,Drupal通过第一个参数来得知当前是什么东西正被主题化。在这里,它是“node”,所以Drupal将在主题注册表中为“node”查找相应的条目。它找到的注册表条目,和图8-7中所给的类似。
8-7.主题为Bluemarine时,节点的注册表条目
 
    如果注册表路径里有一个文件条目,那么Drupal将为该文件运行include_once(),从而将一些需要的主题函数包含进来,但是在这里,没有这样的条目。
    Drupal将检查这个主题调用是由一个函数处理的,还是由一个模板文件处理面的。如果该调用是由函数负责处理的,Drupal将简单的调用这个函数并返回输出。但是在本次调用中,因为在注册表条目中没有定义函数,所以Drupal将准备一些变量,以将它们传递给一个模板文件。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

逐步分析theme()函数(1)

老葛的Drupal培训班 Think in Drupal

首先,传递给theme()函数的参数现在可以使用了。在这里,传递的参数有$node, $teaser, 和$page。所以,对于注册表参数条目中所列的每个参数,Drupal将为其分配一个对应的变量:
 
$variables['node']   = $node;
$variables['teaser'] = $teaser;
$variables['page']   = $page;
 
    接着,默认的呈现函数将设置为theme_render_template(),而默认的扩展名将设置为.tpl.php(PHPTemplate模板的标准文件扩展名)。呈现函数负责将变量传递给模板文件,一会儿你就会看到。
    模板中所用的其它变量则由模板预处理函数提供。首先,对于所有的主题化项目,不管该项目是一个节点,区块,面包屑,或者其它你有的东西,都会调用template_preprocess(),。而第2个预处理函数则是特定于正在呈现的项目的(在这里就是一个节点)。图8-7显示,为节点定义了两个预处理函数,这样调用它们:
 
template_preprocess($variables, 'node');
template_preprocess_node($variables, 'node');
 
    第一个函数就是template_preprocess()。你可以在http://api.drupal.org/api/function/template_preprocess/6中查看这个函数的代码,或者也可以直接在includes/theme.inc中查看。这个函数用来设置所有模板中都适用的变量(参看“实用于所有模板的变量”一节)。
   预处理函数是成对儿出现的。第2个预处理函数名称的最后部分,对应于当前正被主题化的项目。template_preprocess()运行完以后,现在就运行template_preprocess_node()了。它添加了以下变量:$taxonomy, $content, $date, $links, $name, $node_url, $terms, 和$title。这显示在template_preprocess_node()的代码中。注意,数组$variables中的每一个条目,都将变成一个单独的变量,以供模板文件使用。例如,对于$variables['date'],在模板文件中,将简单的作为$date来使用:
 
/**
 * Process variables for node.tpl.php
 *
 * Most themes utilize their own copy of node.tpl.php. The default is located
 * inside "modules/node/node.tpl.php". Look in there for the full list of
 * variables.
 *
 * The $variables array contains the following arguments:
 * $node, $teaser, $page
 */
function template_preprocess_node(&$variables) {
    $node = $variables['node'];
    if (module_exists('taxonomy')) {
        $variables['taxonomy'] = taxonomy_link('taxonomy terms', $node);
    }
    else {
        $variables['taxonomy'] = array();
    }
 
    if ($variables['teaser'] && $node->teaser) {
        $variables['content'] = $node->teaser;
    }
    elseif (isset($node->body)) {
        $variables['content'] = $node->body;
    }
    else {
        $variables['content'] = '';
    }
 
    $variables['date'] = format_date($node->created);
    $variables['links'] = !empty($node->links) ?
        theme('links', $node->links, array('class' => 'links inline')) : '';
    $variables['name'] = theme('username', $node);
    $variables['node_url'] = url('node/'. $node->nid);
    $variables['terms'] = theme('links', $variables['taxonomy'],
    array('class' => 'links inline'));
    $variables['title'] = check_plain($node->title);
 
    // Flatten the node object's member fields.
    $variables = array_merge((array)$node, $variables);
    ...
}
 
    关于这些变量都是干什么的,可以参看本章前面的部分。
    在分配了变量以后,一些疯狂的事情发生了。节点本身从一个对象转化为了一个数组,并与已有的变量合并在了一起。所以,所有的节点属性现在都可以应用在模板文件中了,只需要在属性名前面加个前缀$就可以了。例如,$node->nid现在就可作为$nid来使用。如果一个节点的属性和一个变量拥有相同的名字,那么变量优先。例如,$title包含了$node->title的普通文本版本。当合并发生时,将会在模板文件中使用这个普通文本版本。注意,原始的标题仍然可以通过$variables['node']->title来访问,不过在使用它以前,为了安全考虑,你需要对其进行过滤(参看第20章)。
    好了,Drupal现在运行完了预处理函数。现在需要做一个决定:将所有的这些变量传递给哪个模板文件呢?也就是为节点使用哪个模板文件呢?为了做出决定,Drupal进行以下检查:
 
1. 是不是在$variables['template_files']中定义了一些模板文件?这里定义的条目都是Drupal将要查找的模板文件的名字。在我们的例子中,节点的类型为story,所以node-story定义在这里;Drupal首先会匹配一个特定内容类型的模板文件,找不到的话再使用通用的节点模板。更多详细,参看http://drupal.org/node/190815
 
2.是否设置了$variables['template_file']?如果设置了,优先采用。
 
    函数drupal_discover_template()负责决定采用哪个模板文件。首先,它会找到主题注册表条目中定义的主题路径,然后在这些路径下查找模板文件。在我们的这种情况下,它首先会查找themes/bluemarine/node-story.tpl.php,接着查找modules/node/node-story.tpl.php。如果一个文件也不存在的话(在我们的例子中,一个也不存在:节点模块没有在它的目录中提供特定节点类型的模板文件,而Bluemarine也没有为story节点单独提供一个模板----只有一个通用的节点模板),那么第一轮的模板查找就失败了。接着,Drupal将路径,模板文件,和扩展名串联起来,并对其进行检查:themes/bluemarine/node.tpl.php。嗯,这个文件存在,接着Drupal将调用呈现函数(记住,那就是theme_render_template()),并将选择的模板文件和变量数组传递过来。
    呈现函数将变量传递给模板并执行它,然后将结果返回。在我们的例子中,结果就是执行了themes/bluemarine/node.tpl.php所返回的HTML。

Drupal版本:

自定义drupal区块区域

老葛的Drupal培训班 Think in Drupal

区域是在Drupal的主题中放置区块的地方。通过Drupal的管理界面“管理➤站点构建➤区块”,你可以将区块指定到区域中,并管理它们。
尽管你可以创建你想要的任意区域,但是在主题中,默认的区域有left, right, content, header, 和footer。一旦声明了区域以后,你就可以在页面模板文件中(例如,page.tpl.php)将其作为变量使用了。例如,对于header区域,可以使用<?php print $header ?>。通过在你主题的.info文件中进行定义,你可以创建其它的区域。
 
Drupal表单的主题化
修改Drupal表单的外观不像创建一个模板文件那样容易,因为Drupal中的表单依赖于它们自己的API。关于表单主题化的具体细节,可参看第10章。
 
使用主题开发者模块
    用于制作Drupal主题的一个非常宝贵的资源就是主题开发者模块。它是devel.module的一部分,你可以在http://drupal.org/project/devel下载到它。主题开发者模块,能够让你指定一个页面元素,来查看生成该元素所用到的模板文件和主题函数,以及对于该元素都有哪些变量(和它们的值)可用。图8-8给出了使用该模块的示例。
 
8-8.主题开发者模块
 

Drupal版本:

drupal主题系统 总结

老葛的Drupal培训班 Think in Drupal

读完本章以后,你应该可以
• 理解主题引擎和主题是什么
• 理解Drupal中PHPTemplate的工作原理
• 创建模板文件
• 覆写主题函数
• 操纵模板变量
• 为区块创建新的页面区域
 

Drupal版本: