这本书已经发布了许久,本来没有打算升级,春节期间,读了一遍以后,发现里面变动很大.而drupalbar上面的升级版,也迟迟没有动态.
为了不影响我的培训班的进行,将这个drupal5的版本升级到drupal6中,从今天开始吧,不过我是从后往前升级的,以避免重复的劳动.
Drupal6版,比5有了很多的改动,大多数地方被重写了,而且又新增了一些知识点.慢慢的升级,希望在我的培训班开始以后的3个月内,把它升级完成.
技术书籍的翻译,有一个难点,那就是你需要懂得它说得什么,首先要理解,其次要实践,它讲的东西,你会用,然后才能翻译出来,刚开始会觉得很容易.比如,但是后来你会发现越来越难,我翻译5的时候,打算2个月内结束战斗的,希望能够速战速决,2个月,每周10个工作日,加在一起,就是20天,全书400页,一天只需要翻译20页,就可以搞定了,但是实际上一直持续了半年之久.
所以不敢确定这个升级版,什么时候能够完全出来.希望越快越好吧.
最后在这里感谢,lullabot的VnDyk,虽然这里直接利用了他们的成果,但是应该没有损害他们的利益。
鉴于译文无法出版,也带来不了任何收益,本站开始公开连载译文,希望这份资料能够帮助更多的在Drupal门外徘徊的人.
老葛的Drupal培训班 http://www.thinkindrupal.com
在一年多以前,我为本书的第一版写了序言。那时,在Drupal世界里缺少的就是一本开发用书。通过编写本书的第一版,John VanDyk 和Matt Westgate为Drupal的持续发展,做出了难以置信的贡献。在我遇到的Drupal开发者中,每人都有一本第一版的Drupal专业开发指南。
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
作为Acquia的高级顾问,ROBERT努力让各种各样的个人、组织能够方便高效的使用Drupal。ROBERT喜欢经典音乐和开源软件,并把它们看作是动力和精神的源泉。
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
最后,限于译者的水平、精力有限,对于译文的不对不妥之处,欢迎指正。
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
Drupal使用的编程语言是PHP。因为PHP比较好学,所以大量的PHP代码都是由新手编写的。而新手的水平大家也知道,他们所写的代码总是存在这样或者那样的问题,这就给PHP的名声带来了比较坏的影响。然而, PHP也可以用于构建严谨的代码。Drupal核心中的所有代码都遵守了严格的编码规范(http://drupal.org/nodes/318),通过开源,其代码也经过了成千上万人的锤炼。对于Drupal来讲,PHP的入门门槛比较低,这就意味着有更多的人能够为Drupal贡献代码,通过开源,会有很多人对这些代码进行检查,这样就保证了代码的质量。通过向社区贡献代码,这样就可以收到他人的反馈,帮助,从而提高大家的技能。
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
Drupal的后台管理界面与站点的前台部分紧密的集成在了一起,并且在默认情况下,使用相同的主题。第一个用户,也就是用户1,是一个超级用户,他对站点拥有全部权限。以用户1的身份登录后,你将在你的用户区块(参看“区块”部分)中看到管理站点的一个链接。点击这一链接,你将进入到Drupal的后台管理界面。根据用户对站点访问权限的不同,每个用户的区块都会有一个不同的链接。
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
在路径阶段,将加载处理路径和路径别名的代码。该阶段使得用户可读的URL被转化为Drupal路径,并处理Drupal内部路径的缓存和查找操作。
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://www.thinkindrupal.com
老葛的Drupal培训班 http://zhupou.cn
在许多开源的应用中,你可以通过修改源代码来定制应用。这是一种方法, 用来获得你想要的功能;但是在drupal中,一般不赞成这样做,只有在万不得已的情况下才使用这一手段。修改源代码,意味着随着Drupal的每次更新,你必须要做更多的工作----你必须测试一下你的修改是否还能正常工。代替的,Drupal的设计从底层便考虑了模块化和扩展性。
在本章,我们将从头开始创建一个模块;在你创建模块时,你将学到模块必须遵守的一些标准。我们需要一个现实的目标,所以让我们考虑以下现实中的注释问题。当用户在Drupal网站上浏览内容时,如果管理员启用了评论模块,那么用户可能会对内容发表评论。但是如果是在一个网页上添加一个注释(一种仅有用户可见的笔记类型),那该怎么样?这对私密的内容评审可能非常有用(我知道这看起来可能有点做作,但是大家还是容忍一下吧)。
老葛的Drupal培训班 Think in Drupal
首先我们要做的是为模块起一个名字。名字“annotate”看起来还是比较合适的—简洁而生动。接着,我们需要找个地方来放置这个模块。我们可以把这个模块放在核心模块所在的目录中去,不过这样的话,我们需要记住哪些是核心模块,哪些是我们的模块,这样一来,就增加了维护成本。让我们把它放在目录sites/all/modules下面,以将其与核心模块区分开来。
老葛的Drupal培训班 Think in Drupal
Drupal有多种不同的节点类型(在用户界面称之为内容类型),比如Story和Page。我们想将注释的使用限定在特定的一些节点类型上。为了实现这一点,我们需要创建一个页面,在里面告诉我们的模块我们想注释哪些节点类型。在该页面,我们将呈现一组复选框,每一个复选框就对应一个已有的内容类型。这样终端用户就可以通过选中或者取消选中复选框(如图2-1所示),就可以决定哪些内容类型可被注释。这样的页面就是一个管理页面,只有在需要的时候才加载和解析合成该页面的代码。因此,我们把代码放在了一个单独的文件中,而不是放在我们的annotate.module文件里,而对于每个web请求,都会加载和运行annotate.module文件。由于我们告诉了Drupal,在文件annotate.admin.inc中查找我们的设置表单,所以创建文件sites/all/modules/custom/annotate/annotate.admin.inc,并向里面添加以下代码:
在Drupal中,表单被表示为一个嵌套的树状结构;也就是说,一个数组的数组。这个结构向Drupal的表单呈现引擎(rendering engine)描述了表单是如何表示的。为了可读性,我们将数组中的每个元素单独成行。每一个表单属性都以”#”开头,并作为数组的键。我们首先声明了表单元素的类型为checkboxes,这意味着通过使用一个带键的数组来构建多个复选框。我们在变量$options中已经得到了带键的数组。
老葛的Drupal培训班 Think in Drupal
这里值得讨论一下#default_value的值:
仅用了几行代码,我们就为我们的模块提供了一个可用的配置表单,它将自动的保存和记住我们的设置!好的,尽管代码中的一行有点太长了,但是没有关系,你现在应该能够感受到撬动Drupal的力量了。
为了让用户可以为一个web页面输入笔记,我们需要为它提供一个地方,专门用来输入笔记。下面我们在annotate.module中为笔记添加一个表单:
老葛的Drupal培训班 Think in Drupal
这个看起来有点复杂,所以让我们详细的分析一下。首先要注意的是,我们在这里实现了Drupal的另一个钩子。这次是nodeapi钩子,在drupal对节点进行各种处理时将会调用该钩子,这样其它的模块(比如我们的)在处理继续往下以前可以修改节点。我们通过变量$node将节点传递过来。注意第一个参数前面的&,这意味着它实际上是对$node对象的一个引用,这点非常好,因为我们在这里对$node所做的任何修改都将被保存下来。由于我们的目标是追加一个表单,所以我们非常高兴地看到我们可以修改节点。
我们最后要做的就是创建表单,并把它添加到$node对象中。首先,我们需要定义一个表单,这样我们就有了要添加的东西。我们将在annotate.module中的一个单独的函数中完成这件事,它唯一的责任就是定义表单:
当我们点击更新按钮时,将会发生什么呢?什么都没有,因为我们还没有为输入的表单内容编写任何逻辑代码呢。现在就让我们添加它。但是在我们继续以前,我们需要考虑一下,我们将把用户输入的数据存储到哪里呢?
老葛的Drupal培训班 Think in Drupal
存储模块所用数据的最常用方式,就是为这个模块的数据创建一个单独的数据库表。这将使得该数据与drupal核心数据库表独立开来。当你决定为模块创建哪些字段时,你应该问问自己:需要存储什么数据呢?如果我要对这个表进行查询,那么我需要使用什么字段和索引?最后,还要考虑一下,我在将来对这个模块可能会作哪些扩展?
老葛的Drupal培训班 Think in Drupal
我们可以把这段sql语句放到我们模块的README.txt文件中,这样我们就省事了,但是想要安装这个模块的其他用户就麻烦了,他们需要手工的将数据库表添加到他们的数据库中。换种方式,我们知道,在你启用你的模块时,Drupal能帮你创建相应的数据库表;我们这里将利用Drupal的这一点。我们将创建一个特殊的文件;文件的名字将使用你的模块名,而后缀则使用.install,所以对于annotate.module,这个文件名应该为annotate.install。创建文件sites/all/modules/custom/annotate/annotate.install,并输入以下代码:
老葛的Drupal培训班 Think in Drupal
当Drupal创建了用来存储数据的annotations表以后,我们需要修改一下我们的代码。其一,我们将需要添加一些逻辑代码,这样在用户输入注释并且点击更新按钮以后,它可以用来负责对输入数据的处理工作。我们的表单提交函数如下所示:
最后,我们需要修改nodeapi钩子中代码,这样,如果已经存在了一个注释,那么它将被从数据库中取出,并用来预先填充我们的表单。在我们把我们的表单分配给$node->content的代码前面,我们添加以下代码,这里用粗体将其标出了:
测试一下你的模块。它现在应该能够保存和取回注释了。现在可以喘口气了---你已经从头创建了一个模块。在通往Drupal专业开发者的道路上,你已经迈出了关键的一步。
老葛的Drupal培训班 Think in Drupal
Drupal有多个管理设置的类别,比如内容管理和用户管理,都出现在主管理页面上。如果你的模块需要一个自己的类别,那么你可以非常容易的创建一个。在这个例子中,我们创建一个名为“Node annotation”的新类别。为了实现这一点,我们修改我们的菜单钩子以定义新类别:
在注释模块中,我们允许管理员选择哪些节点类型支持注释(如图2-1所示)。让我们深入学习一下这是如何工作的。
老葛的Drupal培训班 Think in Drupal
下面让我们看一下定义表单的函数,它为节点类型定义了一个复选框(参看图2-1),并且增加了另外两个选项。函数位于sites/all/modules/custom/annotate/annotate.admin.inc中:
如果由函数system_settings_form()为我们负责保存表单数值,那么我们如何才能判定在“Annotations per node”字段中输入的是一个数字?我们可以钩住表单提交的处理过程么?当然可以了。我们只需在sites/all/modules/custom/annotate/annotate.admin.inc中定义一个验证函数,如果我们发现有任何异常的话,就使用这个函数来设置一个错误消息。
在前面的例子中,修改设置并点击“保存配置”按钮,可以正常工作。如果点击了“重置为默认值”按钮,那么各个字段将被重置为它们的默认值。下面部分将描述如何实现这一点。
老葛的Drupal培训班 Think in Drupal
当你的模块取回已存储的设置时,应该使用variable_get():
我们将与开源社区分享这一模块,这是自然的,所以需要创建一个README.txt文件,然后把它放到annotation的目录下,和annotate.info,annotate.module,annotate.install文件放在一起。README.txt文件一般包含的信息有,谁编写了这个模块,以及如何使用这个模块。这里不需要包含许可证信息,这是因为所有上传到drupal.org的模块都将采用GPL许可,而drupal.org上的打包脚本将会为模块自动添加一个LICENSE.txt文本。接下来,你就可以把它上传到drupal.org上的第3方模块资源库中了,然后创建一个项目页面,用来追踪社区中其他用户的反馈。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
当读完这一章后,你应该可以处理以下任务:
使用Drupal时,一个常见的目标就是,当一个事件发生时需要做些东西。例如,站点管理员可能希望在一个消息发布以后收到一封电子邮件。或者当用户在评论中使用了违禁词语,那么就会被自动封号。本章将描述如何使用Drupal的事件钩子,从而当那些事件发生时,能够运行自己的代码。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
• 触发器:它指的是,一个具体的钩子操作联合体与一个或多个动作的关联。例如,嘟嘟这个动作可以与用户钩子的登录操作关联起来。
一个动作就是Drupal要做的一些事情。下面是一些例子:
老葛的Drupal培训班 Think in Drupal
如果将我们的嘟嘟函数转化为一个完整的动作,那么我们需要做哪些工作呢?这有两个步骤:
现在,让我们重新回到“管理➤站点构建 ➤触发器”。如果你正确的完成了前面所给的这些东西,那么你的动作将会出现在这个用户界面,如图3-2所示。
老葛的Drupal培训班 Think in Drupal
如果你修改了该动作所支持的触发器,那么你就能够在用户界面中看到相应的变化。例如,如果你将beep_action_info()改成如下的内容,那么你的“Beep”动作只能用于触发器“在删除文章之后”:
动作主要有两种类型:带有参数的动作和不带参数的动作。我们前面所写的“嘟嘟”动作就是不带参数的动作。当动作执行时,它嘟嘟一下,这就完事了。但是许多时候,动作可能需要更多一点的上下文。例如,一个“发送电子邮件”动作,需要知道将电子邮件的发收件人以及邮件的标题和正文。这种需要为其在配置表单中做些设定的动作,就是高级动作,也称为可配置动作。
让我们快速的检查一下,我们的实现是否正确,导航到“管理➤站点配置➤动作”。不错,动作出现在了高级动作的下拉选择框中了,如图3-3所示。
老葛的Drupal培训班 Think in Drupal
第一个函数向Drupal描述了表单。我们只定义了一个文本输入框字段,这样管理员就可以输入嘟嘟的次数了。当管理员选择添加一个高级动作“嘟嘟多次”时,如图3-3所示,Drupal将会使用我们的表单字段来呈现一个完整的动作配置表单,如图3-4所示。
老葛的Drupal培训班 Think in Drupal
我们在前面已经看到,动作的函数签名的一般形式为example_action($object,$context)。下面让我们学习一下这些参数的具体含义。
老葛的Drupal培训班 Think in Drupal
当Drupal运行action_info钩子时,每个模块都可以声明它所提供动作,Drupal还给了模块一个机会,让它们修改该信息----包括其它模块提供的信息。下面让我们修改“阻止当前用户”这个动作,让它可以用于评论插入这个触发器:
由于我们已经分配了动作,所以当一个新评论被发布时,当前用户将被阻止。让我们仔细的看一下,这里都发生了什么。我们已经知道,Drupal是通过触发钩子来向模块通知特定事件的。在这里触发的就是评论钩子。由于当前是一个新评论正被添加进来,所以当前的特定操作就是insert操作。触发器模块实现了评论钩子。在这个钩子内部,它对数据库进行查询,来获取分配到这个特定触发器上的所有动作。数据库就会将我们分配的动作“阻止当前用户”返回给该钩子。现在,触发器模块就可以执行该动作了,它符合标准的动作函数签名example_action($object, $context)。
将钩子和操作放在上下文中,这一点非常有用。我们举个例子,动作“发送电子邮件”就大量的利用了这一点。这个动作的类型为system,它可以被分配给许多不同的触发器。
老葛的Drupal培训班 Think in Drupal
触发器模块仅是调用动作的一种方式。你可能想写一个单独的模块,它需要自己负责动作的调用和参数的准备。如果是这样的话,那么推荐使用actions_do()来调用动作。函数的签名如下:
Drupal是怎么知道,有哪些触发器是可以显示在触发器用户界面的?按照典型的方式,它能够让模块通过钩子声明该模块所实现的钩子。例如,这里是来自comment.module的hook_hook_info()实现。定义触发器描述的地方就是hook_hook_info()的实现:
老葛的Drupal培训班 Think in Drupal
有时候,如果你的代码新增了一个操作的话,那么你可能想要在一个已有的钩子上添加触发器。例如,你可能想向nodeapi钩子添加一个操作。假定你编写了一个模块,用来存档旧节点并将其迁移到数据仓库中。由于这个操作是作用于节点的,所以你可能想在nodeapi钩子下面添加一个archive操作,这样对于内容的所有操作,都会显示在触发器界面的同一个标签下。下面的代码用来添加一个额外的触发器:
读完本章后,你应该能够
老葛的Drupal培训班 http://zhupou.cn
当一个Web浏览器向Drupal发送一个请求时,它向Drupal传递了一个URL。通过这一信息,Drupal必须指出要运行哪段代码以及如何处理这一请求。这也就是通常所说的路由或者分发。Drupal截掉URL的基部分并使用后面的部分,后者被称之为Drupal路径。举例来说,如果URL是http://example.com/?q=node/3,则Drupal路径就为node/3。如果你使用了Drupal的简洁URL特性,那么在浏览器中的URL就是http://example.com/node/3,但是在你的web服务器中,在Drupal收到这个URL以前,web服务器已将其重写为http://example.com/?q=node/3;所以,对于Drupal来说,这两者是一样的。在前面的例子中,不管是否启用了简洁URL,其Drupal路径都是node/3。此方面的更多详细,可参看第一章中的“Web服务器的角色”。
老葛的Drupal培训班 Think in Drupal
通常采用的方式如下所示:Drupal请求所有启用的模块来提供一个菜单项数组,每个菜单项都包含了一个数组,其中以路径为键,里面还包含了路径的一些相关信息。一个模块必须提供的一段信息就是页面回调(page callback)。在这里,回调就是一个PHP函数的名称,当一个浏览器请求一个特定的路径时就会调用它。当一个请求到达时,Drupal将执行以下步骤:
老葛的Drupal培训班 Think in Drupal
通过在你的模块中使用菜单钩子来钩住这一流程。这样你就可以定义包含在menu_router表中的菜单项。让我们构建一个名为menufun.module的示例模块,通过它来学习菜单系统。我们将Drupal路径menufun映射到PHP函数menufun _hello()上。首先,我们需要一个名为menufun.info的文件,位于sites/all/modules/custom/menufun/menufun.info:
前面所写的hook_menu()实现,还是非常简单的。让我们在里面添加一些键,这样就和我们通常所用的差不多了。
有时,你可能希望向映射到该路径上的函数提供更多的信息。首先,路径中的其它部分将会自动传递过来。让我们修改一下我们的函数:
老葛的Drupal培训班 Think in Drupal
如果你没有特别指定的话,那么Drupal会假定你把页面回调放在了.module文件中。在Drupal6中,对于每个页面请求,为了尽可能的降低为其加载的代码总量,许多模块被拆分成了多个部分。如果回调函数不在当前的.module文件中的话,可以使用菜单项中的file键,来指定哪个文件包含了该函数。我们在第2章中编写注释模块的时候,就用到了file键。
如果你定义了file键,那么Drupal将会在你的模块目录下查找该文件。如果你的页面回调是由其它模块提供的话,也就是说该文件不在你的模块目录中,那么你需要告诉Drupal在查找该文件时所用的文件路径。使用file path键,就可以轻松的实现这一点了。我们在第2章的“定义你自己的管理部分”就用到了它。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
到目前为止,在前面的所有例子中,我们都简单的将菜单项的access callback键设置为了TRUE,这意味着所有的用户都可以访问我们的菜单。一般情况下,通过在模块中使用hook_perm()来定义权限,并使用一个函数来测试这些权限,从而实现对菜单的访问控制。这里所用的函数的名字定义在菜单项的access callback键中,它一般为user_access。让我们定义一个名为receive greeting的权限;如果用户没有哪个角色具有该权限的话,当他/她访问页面http://example.com/?q=menufun时,将会收到一个“拒绝访问”消息。
Drupal支持多语言,它使用t()函数来翻译字符串。所以你可能会想,菜单项中的title键应该是这样定义的:
如果没有在菜单项中定义标题回调的话,Drupal将默认使用t()函数。我们也可以明确地给出这个回调函数的名字,在title callback键中声明它就是了:
Drupal的翻译函数,可以接受一个字符串和一个用来替换的数组(更多关于t()函数的详细,参看第18章),例如:
到目前为止,我们在菜单项中所用的都是普通的Drupal路径名字,比如menufun 和menufun/farewell。但是Drupal还经常使用这样的路径,比如user/4/track或node/15/edit,在这些路径中,有一部分是动态的。现在,让我们来看看动态路径是如何工作的。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
在实际中,一个Drupal路径的一部分通常是用来查看或者修改一个对象的,比如一个节点对象或者一个用户对象。例如,路径node/%/edit用来编辑一个节点,而路径user/%则用来根据用户ID来查看用户的相关信息。让我们看一下后者的菜单项,你可以在modules/user/user.module中的hook_menu()实现中找到它。这个路径匹配的URL应该看起来是这样的http://example.com/?q=user/2375。在你的Drupal站点上点击查看“我的帐号”页面,就能看到这样的URL了。
如果需要向加载函数传递额外的参数,那么可以使用load arguments键来定义它们。下面是来自节点模块的例子:一个用来查看节点修订本的菜单项。在这里需要向加载函数,也就是node_load(),传递节点ID和修订本的ID。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
模块通过实现hook_menu_link_alter(),就可以在Drupal将一个菜单项保存到menu_link表时,来修改该链接了。下面是如何将“登出”菜单项的标题改为“Sign off”(“结束”)的。
当你在菜单钩子中添加一个菜单项时,经常用到的一个键就是type。如果你没有定义类型,那么将会使用默认类型MENU_NORMAL_ITEM。Drupal将根据你指定的类型来对菜单项进行不同的处理。每一个菜单项类型都有一系列的标记或者属性组成。表4-2列出了菜单项类型的标记。
老葛的Drupal培训班 Think in Drupal
在这一部分,我们给出了一些常见问题的典型解决办法,这些问题都是程序员使用菜单时会经常遇到的。
用Drupal的公认晦涩的菜单行话来说,一个显示为标签的回调被认为是一个本地任务,它的菜单类型为MENU_LOCAL_TASK或者MENU_DEFAULT_LOCAL_TASK.本地任务的标题应该是一个简短的动词,比如“添加”或者“列出”。它通常作用在一些对象上,比如节点,或者用户。你可以把一个本地任务想象为一个关于菜单项的语义声明,通常显示为一个标签(tab)----这和<strong>标签类似,后者也是一个语义声明,通常用来显示加粗的文本。
老葛的Drupal培训班 Think in Drupal
启用Drupal的菜单模块,就可以为管理员提供一个方便的用户界面,来定制已有的菜单,比如导航菜单或者一级/二级链接菜单,或者来添加新菜单。当位于includes/menu.inc中的menu_rebuild()函数运行时,表示菜单树的数据结构将被存储在数据库中。当你启用模块时,当你禁用模块时,或者当其它的一些能够影响菜单树结构的事情发生时,都会运行menu_rebuild()函数。数据将被保存到数据库的menu_router表中,而关于链接的信息则被保存到了menu_links表中。
现在你刚刚在你的模块中实现了菜单钩子,但是你的回调没有响应,你的菜单没有显示,或者就是不能正常工作,也就是出了问题。那么对于下面这些地方,你需要好好的检查一下:
老葛的Drupal培训班 Think in Drupal
当读完这一章后,你应该可以:
老葛的Drupal培训班 http://zhupou.cn
在建立数据库连接时,通过查看你站点的settings.php文件,Drupal就会知道需要连接哪个数据库以及所用的用户名和密码。这个文件一般位于sites/example.com/settings.php 或者sites/default/settings.php。定义数据库连接的代码,如下所示:
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
有多种方式用于取回查询结果,这依赖于你的需求,你是需要单独的一行还是需要整个结果集,或者你打算获得一定范围内的结果集,是为了内部使用还是想将其分页显示。
前面的代码片段将输出类型为blog的所有已发布节点(表node中的字段status的值,为0时意味着未发布,为1时意味着已发布)。我们接下来就会讲解db_rewrite_sql()。函数db_fetch_object()从结果集中取出一行作为一个对象。如果想将取出的结果作为一个数组的话,那么可以使用db_fetch_array()。前者更为常用,因为与数组相比,大多数开发者都绝前者的语法更简明一些。
老葛的Drupal培训班 Think in Drupal
你可能会想,对于一个有很多日志条目的站点,比如说有10,000个,那么运行前面的代码将会非常危险。我们将对这个语句的结果进行限制,仅仅取回10个最新发布的日志:
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
(译者注:Schema被翻译成了模式, Schema module 翻译成了模式模块, schema definition翻译成了模式定义,这里面的句子有点绕口^_^)
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
在模式定义中声明的字段类型,将会映射成数据库中的本地字段类型。例如,一个size为tiny的整数字段将映射为MySQL中的TINYINT字段,或者PostgreSQL中的smallint字段。实际的映射可查看数据库驱动文件中的db_type_map()函数,比如includes/database.pgsql.php(参看表5-2, 本章后面讲到)。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
让我们看一个hook_db_rewrite_sql()的具体实现。下面的例子利用了node表中moderate列来覆写节点查询。在我们修改了查询以后,那些不具有“管理内容”权限的用户,就会看不到处于待审核状态的节点(也就是,moderate列为1)。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
假定你想为一个新生的未来的名为DNAbase的数据库编写一个数据库抽象层,该数据库使用分子计算来提升性能。我们不需要从头开始,而是复制一份已有的抽象层,接着修改它。我们将使用PostgreSQL的实现,这是因为MySQL的驱动器被拆分成了,一个includes/database.mysql-common.inc文件,和两个单独的mysql、mysqli驱动器文件。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
默认情况下,Drupal站点的用户注册,只需要一个用户名和一个有效的e-mail地址就可以了。模块可以通过实现用户钩子,来向用户注册表单中添加它们自己的字段。让我们编写一个名为legalagree.module的模块,它提供了一个便捷的方式,以使你的站点适应今天这个法制社会。
列表 6-2. legalagree.module
老葛的Drupal培训班 Think in Drupal
当用户填完登录表单(一般位于http://example.com/?q=user 或者在一个区块中)并点击登录按钮时,登录流程开始。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
• user_login_final_validate():如果没有成功的加载用户,那么设置一个错误信息“对不起,无效的用户名或密码。你是不是忘记密码了?”,并向系统写入一个日志记录“用户尝试登录失败”。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
用户、区块和评论不是节点。在这些特定的数据结构中,为了适应它们各自的特定目的,它们每一个都拥有自己的钩子系统。节点一般有“标题”和“正文”两部分,而在表示用户的数据结构中则不需要这些。用户需要的是,e-mail地址、用户名称、一种安全的存储密码的方式。当要存储的内容片段更小一些时,比如存的是导航菜单、搜索框、最新评论列表等等,我们此时可以使用轻量级的存储解决方案---区块。评论也不是节点,它们也属于轻量级的内容。一个页面可能会有100或者更多的评论,试想,如果所有的这些评论在被加载时都使用节点钩子系统的话,那么会给系统带来多大的负担呢.
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
你在hook_perm()中定义了权限,但是它们是如何起作用的呢?节点模块可以使用hook_access()来限制对它们定义的节点类型的访问。超级用户(用户ID 1)将绕过所有的访问权限检查,所以对于超级用户则不会调这个钩子函数。如果没有为你的节点类型定义这个钩子函数,那么所有的访问权限检查都会失败,这样就只有超级用户和具有“管理节点”权限的用户,才能够创建、编辑、或删除该类型的内容。
在钩子函数hook_access()中, $op另一个可用的值是“view”(查看),它允许你控制谁可以查看该节点。然而我们需要提醒一下:当查看的页面仅有一个节点时,才调用钩子hook_access()。当节点处于摘要视图状态时,比如位于一个多节点列表页面,在这种情况下, hook_access()就无法阻止用户对该节点的访问。你可以创建一些其它的钩子函数,并直接操纵$node->teaser的值来控制对它的访问,但是这有点黑客的味道了。一个比较好的解决方案是,使用我们在后面即将讨论的函数hook_node_grants()和hook_db_rewrite_sql()。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
由于主体字段是一个textarea,并且对于节点主体字段可以使用过滤器格式,所以上面的表单中包含Drupal的标准内容过滤器,代码如下(过滤转换文本;使用过滤器的更多信息,可参看第11章):
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
当一个新节点被保存时,会调用钩子insert()。在这个钩子中你可以将自定义数据存储到相关的表中。只有对于在节点类型元数据中定义的模块,才为其调用这一钩子。该信息定义在hook_node_info()的“module”键中(参看“提供我们节点类型的相关信息”一节)。例如,如果“module”键的值为joke,那么就会调用joke_insert()。如果你启用了书籍模块,并且新加了一个书籍类型的节点,此时就不会调用joke_insert();这里将调用的是book_insert(),这是因为book.module使用“module”键为book来定义了它的节点类型。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
读完本章后,你应该可以
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Dupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
当Drupal想要为一个可主题化的项目(比如节点,区块,面包屑,评论,或者用户签名)生成一些HTML输出时,它将查找用来为该项目生成HTML的主题函数或者模板文件。Drupal的所有部分,基本上都是可主题化的,这意味着,对于为该项目实际生成的HTML,你可以进行覆写。我们一会儿看一些例子。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
当下一次Drupal需要生成面包屑时,它就会首先找到你的函数,并使用它来代替默认的theme_breadcrumb()函数,这样面包屑中就会包含你的星号,而不再包含默认的双箭头了。很漂亮,不是么?通过theme()函数来管理所有的主题函数调用,如果当前主题覆写了任何一个theme_ 函数,那么Drupal将使用它们来代替默认的主题函数。开发者,请注意:在你的模块中任何需要输出HTML或者XML的部分都应该使用以“theme_”开头的主题函数,这样设计者就可以对它们进行覆写了。
老葛的Drupal培训班 Think in Drupal
Drupal在为特定模板设置变量以前,它会预先设置一些通用变量:
如果你需要创建一个自定义的页面模板,那么你可以复制已有主题中的page.tpl.php文件,或者复制modules/system/page.tpl.php,复制完以后,再按照你的需要对它进行修改。实际上,一个最小的主题,所需要的文件仅仅是.info文件和style.css文件;如果你的主题中没有包含page.tpl.php文件,那么Drupal将使用modules/system/page.tpl.php。对于基本的主题,这可能就是你所想要的了。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
在本节中,我们将深入幕后,我们将学习theme()实际是如何工作的。假定当前主题为Drupal的核心主题Bluemarine,并进行了下面的主题调用,让我们逐步分析一下调用的执行路径。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
如果你想扩展approval.module,你可以添加另一个区块,以显示等待管理员批准的用户帐号列表。这将作为作业留给读者,自己动手将其放到approval.module模块中去。这里给出了一个在假定的userapproval.module模块中的区块:
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
函数form_builder()每次遇到$form树中的一个新分支时(例如,一个新的字段集或表单元素),它都寻找一个名为#after_build的可选属性。这是一个可选的数组,里面包含了当前表单元素被构建后会立即调用的函数。当整个表单被构建后,最后将调用可选属性$form[‘#after_build’]中定义的函数。$form和$form_state将作为参数传递给所有的#after_build函数。Drupal核心中有一个实际例子,那就是在“管理➤站点配置➤文件系统”中,文件系统路径的显示。这里使用了一个#after_build函数(在这里就是system_check_directory()),用来判定目录是否存在或者是否可写,如果不存在或不可写,那么将为该表单元素设置一个错误消息。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
生成的HTML将会返回给drupal_get_form()的调用者。这就是呈现表单所要做的全部工作!我们到达了图10-1中的终点“返回HTML”。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
有些属性是通用的,而有些则特定于一个元素,比如一个按钮。对于属性的完整列表,可参看本章的最后部分。下面这个表单是比前面的例子中所给的表单要复杂一点:
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
主题化表单的最灵活的方式,就是为表单或者表单元素使用一个特定的主题函数。这里涉及到了两个步骤。首先,Drupal需要知道我们的模块将实现哪些主题函数。这可以通过hook_theme()(详细请参看第8章)来完成。下面是我们模块的hook_theme()的一个快速实现,它主要说的是“我们的模块提供了两个主题函数,无须额外参数就可以调用它们”:
通过为一个表单声明#theme属性,你可以命令Drupal使用一个不匹配“‘theme_’+表单ID名字”格式的主题函数:
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
你已经看到,在Drupal中,有多个地方可以用来放置你的主题、验证、提交函数。拥有这么多的选项会让人选择,到底要选择哪个函数呢?下面是Drupal查找位置的总结,这里按先后顺序排列,对于一个主题函数,假定你使用基于PHPTemplate的名为bluemarine的主题,并且你正在调用drupal_get_form('formexample_nameform')。然而,这还取决于你的hook_theme()实现。
注意,表单可以有多个验证和提交函数。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
验证函数应该专注于验证。一般来讲,它们不应该修改数据。然而,它们可以向$form_state数组添加一些信息,在下面的一节中,我们将看到这一点。
老葛的Drupal培训班 Think in Drupal
一个更简单一点的方式是使用$form_state存储该值。由于$form_state在验证和提交函数中都是通过引用传递的,所以在验证函数中,可以将数值存储在这里,而在提交函数中,就可以使用它了。最好在$form_state中加上你模块的命名空间,而不是仅仅使用一个键。
你可能会问,“为什么不把该值存储在$form_state['values']中,这样不就和表单字段值保持一致了吗?”你说的这种方式也能工作,但是要记住,$form_state['values']是用来放置表单字段值的,而不是放置模块存储的随机数据。还记不记得,Drupal允许任意的模块将验证和提交函数附加在任意的表单上,因此你不能假定只有你的模块使用了表单状态,所以应该采用一种兼容的可预期的方式来存储数据。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal'培训班 Think in Drupal
老葛的Drupal'培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
按钮元素除了属性#executes_submit_callback默认为FALSE以外,其它属性与提交按钮元素完全相同。属性#executes_submit_callback告诉Drupal是否需要处理表单,为TRUE时处理表单,为FALSE时则简单的重新呈现表单。和提交按钮元素一样,可以将特定的验证和提交函数直接分配给这个按钮元素。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
接着,我们将编写实际的模块。该模块将显示两个页面:一个用来输入数据(我们将重复使用这一页面),一个最终页面,用来显示用户的输入以及对用户输入的致谢。这里是sites/all/modules/custom/formwizard.module:
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
如果你编写了一个提供搜索结果的模块,那么你可能想通过实现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培训班 Think in Drupal
到目前为止,我们通过提供一个简单的hook_search('search')实现,检查了如何与默认搜索表单进行交互。然而,当我们的需求从使用简单的LIKE语句搜索数据库的一个VARCHAR列,提高到了索引网站内容,那么我们需要将该任务外包给Drupal内置的HTML索引器。
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal
老葛的Drupal培训班 Think in Drupal