Drupal–基本概念和架构。

从头学习Drupal–基本概念一

从头学习Drupal–基本概念一
节点(Node)
我们知道Drupal是一个内容管理系统(CMS), 而一般我们所管理的内容, 就是新闻或博文之类的文章; 在通常意义上, 这些就是Drupal中的节点, 但Drupal并不是只能管理文章类内容, 它对所管理的内容进行了抽象, 形成了节点的概念. 如果我们学过面向对象的知识, 那我们就知道节点其实就是Drupal这个系统所管理内容的虚基类.
Drupal的核心定义了节点这个对象的各种抽象行为和基本属性, 同时它页实现了基于文本方式来表达内容的两种节点类型, :Page 和 Story. 自然节点类型就是节点的具体实现了, 也就是节点的子类. 虽然Drupal的核心仅实现了简单的文本描述节点, 但它允许你可以自己扩展更多的节点类型. 目前Drupal社区已经有很多扩展模块实现了图像或视频内容的管理, 同时也有大名鼎鼎的CCK模块允许你定义更为节点的更多细节, 这里我们略过不表, 下面为了更进一步了解节点的概念, 我们到数据库(node表)看看它的主要字段:

  • 节点ID, 自动增加的唯一索引;
  • 版本ID, 用来追溯内容修订;
  • 属性 比如发布状态, 语言, 类型等等;
  • 操作信息 创建或修改时间, 操作者等;
  • 内容 标题部分, 内容部分, 可能有的节点类型没有此实际数据;

评论(Comment)
评论虽然也是文章形式, 而且它和普通文章也很象, 但实际上它不是一种节点类型, 它被实现为一种依附于节点的用户反馈机制. 它与节点是否存在本质区别, 是否有必要分成两类对象, 这个好像也一直在讨论之中, 不知道在Drupal 7中会不会把它合入节点框架中. 但我个人认为, 目前Drupal似乎还没有节点与节点发生关联的情况, 这样把评论与节点分开是非常清晰的, 如果后续有此类需求, 则把评论纳入节点管理是顺理成章的事.

分类(taxonomy)
形象的说就是给节点分类, 它由一系列术语表组成, 每个术语表可以定义n多的术语, 这样你可以用术语对你的内容进行标记, 从而达到分门别类的作用. 当然你的分类是否合理, 是否有效, 还得看你的术语表定义了. 比如说一篇技术文章, 你可以从其使用的技术领域来划分, 也可以从其应用的领域来划分, 甚至可以从作者的性别进行划分. 总之, 分类就是个门纲目科属的工作, 不过这可不是个简单的活, 记得专门好像还有图书管学来专门研究图书的分类呢.

边学习边总结, 又补充一个分类的概念, 因为这个也是Drupal核心中针对内容的相关概念 — xeopn

从头学习Drupal–基本概念

区域(Region)
这个概念其实只涉及到表现层, 简单来说就是把页面进行划分, 分成一块块独立的空间, 比如装修做二室一厅, 那就有厅, 厨房, 卫生间, 主卧, 辅卧等, 当然页面不是毛坯房了, 一般它分为五个区域: header, footer, conten, right sidebar, left sidebar.

区块(Block)
在Drupal的世界里, 什么东西都是围绕节点(Node)展开的, 那节点又是为谁呢, 当然是用户, Drupal的用户管理并没有太多特别的地方, 所以我们也就不谈了. 绕了半天, 到底谈嘛吗?
“谈理想”, “谈恋爱”, 错嘛, 我们还是谈节点. 用户访问你的网站干嘛? 想看点内容嘛, 内容是嘛吗, 是节点. 那光看节点吗? 打比方说了, 你肚子饿了进饭店, 你光叫2碗米饭麽? 当然还得点几个小菜, 兴致到了再喝点小酒, 在Drupal眼里, 这些其他的就是区块咯. 可能有较真的看官说话了, 小菜和酒也都可以看做是一种派生内容类型(Content Type)啊, 它们也都可以看作节点啊. 那就借一步说话了, 饭店的招牌, 墙上的推荐菜, 你点菜看的菜单, 这些围绕在你吃的主题周围的露出和谐笑容的东东那就是区块(Block). 某书有云: a block is a chunk of auxiliary information that is displayed on a page alongside the main page content.
放到我们经典的文章页面上来看: 中间大块的是文章内容, 而在其他区域也放着东西呢, 比如一个站点菜单, 最新评论列表, 日历等等. 在Drupal中, 这些附加的辅助性的信息就是区块.
区块定义在数据库表(block)中, 从表结构可以看出, 区块由bid标识, 与模块(Module)和主题(Theme)都是强关联, 甚至Drupal已经为block表建立了tmd(theme module delta)唯一索引. 其实block也是一种辅助的内容形式, 所以它由模块定义实现, 而block只能算是页面上的可选元素, 所以在展现上, 它必须与主题挂钩, 以决定是否在该主题中被显示. 同时区块还与角色勾三搭四, 以便于页面显示信息与用户挂钩.

菜单(Menu)
从实质上说, 菜单就是一个区块(Block), 当然菜单扩展了很多属性和行为, 使得它成为Drupal中一个非常强大的导航系统. 可以说当一个区块成为菜单后, 它就不是一个人在战斗了… 这个暂时雾里看花, 先不深究.

从头学习Drupal–基本架构

前面学习了Drupal的一些基本概念, 其实我们在构建一个系统的时候, 一般都需要从两个方面来考虑问题:

  1. 业务模型也就是领域模型, 是面向我们所要解决的问题域所构建的模型, 前面我们说的关于内容描述方面的几个概念, 其实就是对领域内概念,元素进行概括,抽象得出的业务模型基类. 构建良好的业务模型, 能有效地将问题域中的对象进行分类,综合, 理清他们间的关联, 阐明他们相互间的协作, 并为最终形成系统的对象数据模型打下基础. 我们所说的面向对象(OO), 其实主要就是一个建模的思想.
  2. 系统架构它是从系统的实现角度出发的, 涉及的概念更多, 它主要是用来解决系统如何构建, 以实现业务的需求, 它还涉及系统的健壮性, 性能, 可扩展性, 可操作性, 可获得性等一些其他质量属性. 比如经典的MVC架构, WEB的二层, 三层, 四层架构, 这些都是架构实现的一种形式.

这两方面相辅相成, 任何一方面设计不好都将极大的影响系统的质量(Quality). 很多人在系统设计时出于某种主观或客观原因要么从单方面考虑, 要么是把两者混淆起来考虑, 这都不是好的系统设计方法, 都有很大的局限性.

说了这么些没人感兴趣的东西, 还是回到我们的主题, Drupal的架构. 其实我目前还是粗略看了Drupal, 主要是爬爬洋文, 大概看了一下bootstrap的代码; 所以下面的描述主要关注Drupal的主体框架, 对其内部不进行深入描述(能力问题, 象国足看齐); 而且因为我本身的多语言支持(Internationalization)实现的并不太好, 这里记录下来的就是仅我自己的理解, 供你们参考和指正.

什么是Drupal的架构?
简单的说, Drupal是一个基于B/S架构的内容管理系统(CMS), 它用PHP语言实现, 并以关系数据库为存储机制. 与其说它是一个CMS, 不如说它是一个CMS框架更好. 单纯做博客(Blog), 它不如WordPress简单快速; 直接当CMS, 它不如Joomla美观方便, 甚至不如某些国产, 但其实经过扩展, Drupal能够做得与它们一样好, 甚至更好, 这就取决于Drupal幽雅的架构设计. 这里我为啥用幽雅不用优雅呢? 其实就在于Drupal的具体实现, PHP本身是支持面向对象(OO)的, 但Drupal却为了兼容老版本并没有使用, 而是通过一系列的约定俗成的编码约定(Convention)来达到类似的效果. (期待Drupal 7的到来…)
我们看到, Drupal主要是一个三层结构: 表现层, 逻辑层数据持久层. 顾名思义, 数据持久层主要是处理数据的持久化, 它是领域模型在具体数据库中的实现; 而逻辑层则基于领域模型进行数据的业务逻辑处理, 它是整个结构的核心; 表现层则侧重于领域数据的呈现和工作流在用户侧的控制表现, 它主要基于以用户为中心来设计(UCD).
逻辑层, 包括两部分:Drupal的核心库和模块组(Modules).
核心库主要包含Druapl的请求流程(Bootstrap)和一系列常用的公共支撑库, 比如数据库抽象接口, 多语言支持, 邮件处理, 图像优化等. 当然还有最关键的钩子(hook)框架, 钩子是贯穿Drupal核心的一个重要特性, 它使得Drupal能高效灵活地协调模块们的工作. 这里把它叫做核心库确实不妥当, 以为它不仅仅是一个库, 更是一个公共框架.
模块就是Drupal的功能组件, 它处理具体的业务逻辑, 模块如何划分, 取决于你的业务划分, 你的工作流, 你的设计思想.Drupal安装包里包涵了10多个模块, 但它最核心的只有5个模块: Block, Filter, Node, System, User. 如果说核心库是刘备的话, 那这五个就是他的五虎上将了. 看看他们的功能吧, System使得系统具备了系统管理的能力, User使得系统具备了安全管理的能力, Node+Block+Filter使得系统具备了基本内容管理的能力.
天哪, Drupal难吗, 真的不难, 这就是一个系统的完整原型, 这架构就是多少人心目中的完美架构啊! 从这里出发, 你就能实现任何你想要的了, 想要什么就实现个什么模块, 什么自己做不了, 怕什么, 社区里有的是人在做, 拿来主义就行啊.
表现层, 有多少人是冲着Drupa的l外观来的, 应该不多吧, 你要真冲这个来, 还真得劝你去选个别的CMS, 没必要和自己骄矜.
但是,对一个CMS系统来说, 可定制的外观是必须提供的能力的. 因此, Drupal提供了相当强大的内容表现扩展机制–主题(Theme)系统, 真的是很黄很暴力, 它包含主题引擎和主题两个子层, 允许开发者全方位控制内容的表现. 系统对最终用户的输出, 你可以从主题层输出, 也可以从主题引擎层输出, 还可以直接从逻辑层里的模块层输出; 什么, 你不想实现表现层? OK, 完成一个机机接口的纯后台系统也是你的选择. 不过太灵活并不一定是好事, 特别在一个具体的项目开发过程中, 记住, 制定必要的开发规约是保证项目质量和进度的有效手段.
还是回到正题, 虽然Drupal的整个主题机制强大复杂, 但在机制的最顶层–主题, 却并不复杂. 它简单到由一系列的模板文件, CSS文件, JS文件和图片即可定义出美仑美奂的外观.

持久层, 支持关系型数据库, 模块一般通过核心的数据库API访问, 你想直接访问数据库也支持, 但最好还是通过API, 这样便于迁移和扩展.

从头学习Drupal–基本架构二

前面说了, Drupal的逻辑层由一个核心框架和一系列的功能模块(Module,可以看成插件)构成, 框架与模块间的协调就是通过钩子机制来实现, 所以钩子机制是Drupal模块化系统的关键。什么是钩子机制, 它与面向对象的接口(Interface)类似, 就是为规范定义了两个实体间的功能界面, 使得实现了该界面的实体能进行交互. 在Drupal里, 钩子就是Drupal框架与模块间进行交互的接口, 但由于Drupal没有面向对象的机制, 所以它采用编程规约来讨巧地实现.
在Drupal里, 钩子就是一系列需要实现特定功能的函数, 它们约定好了输入参数和返回类型, 同时约定以xxx_yyy() 的形式来命名, 其中xxx是要实现功能的模块名称,而yyy则是具体的钩子名称 . 举个例子, 你就更清楚了. 例如我有个模块叫myexamplemodule, 现在我想在footer中添加一些Javascript代码, 于是我在我的模块代码中实现一个叫myexamplemodule_footer()的函数, 在此函数中我实现了我想要功能; 于是用户请求页面时, Drupal的框架会检测到我的模块有myexamplemodule_footer()函数, 并且它会自动调用它, 是不是很简单明了呢! Drupal核心框架的这种调度机制, 屏蔽了用户自定义模块与框架间的实际交互, 简化了的开发, 因为你只要实现一个函数即可, 别的事情系统已经替你完成了. 更多的hook请查看Drupal的文档库.

Hook机制主要在模块管理(includes/module.inc)中实现, 主要有的四个主要函数, 非常简单.

module_hook($module, $hook)
判断某模块是否实现某钩子
module_implements($hook, $sort = FALSE, $refresh = FALSE)
判断哪些模块实现某钩子, 返回模块名数组
module_invoke()
调用具体模块的具体钩子, 变长参数, 参数包括模块名, 钩子名, 还有钩子函数的参数
module_invoke_all()
调用所有模块的具体钩子, 变长参数, 参数包括钩子名, 钩子函数的参数

下面看一下module_invoke_all()函数的具体实现, 该函数会在Bootstrap过程中被框架调用.

<?php
function module_invoke_all
() {
      $args = func_get_args
();
      $hook = $args[0
];
      unset($args[0
]);
      $return
= array();
      foreach (module_implements($hook) as $module
) {
         $function = $module .'_'. $hook
;
         $result = call_user_func_array($function, $args
);
         if (isset($result) && is_array($result
)) {
             $return = array_merge_recursive($return, $result
);
         }
        else if (isset($result
)) {
            $return[] = $result
;
        }
}
return $return
;
}
?>

Drupal的钩子实现起来也比较灵活, 不想实现某钩子, 不写那个函数即可, 这样可以减少PHP文件的代码, 提高效率. 不过看起来还是有些庞大的感觉, 因为只要实现了该钩子函数的模块, 就会被调用, 这样应该会导致在输出某内容时, 系统还会加载并执行其他无关的模块. 总之, 粗浅一看, 这里应该是个性能瓶颈.

从头学习Drupal–基本架构三

菜单(Menu)
前面曾经简单提到过Drupal的菜单, 今天稍微深入来探讨一下. 菜单能用来显示导航信息, 我们安装的系统, 默认安装有3个菜单, 让我们查看一下数据库吧, 以menu_开头的总共有三张表: menu_custom, menu_links, menu_router. 其中menu_custom表存放菜单定义信息, 但想知道他们都是由哪个模块定义的麽? 别忘记了菜单如果要显示就是区块哦, 打开区块表(blocks)看看吧. Here it is! 用户模块(User Module)定义了Navigation菜单(没看数据库我以为是系统模块(System Module)定义的呢), 菜单模块(Menu Module)定义了Primary linksSecondary links两个空菜单. 所以从表现层来看, 一个菜单就对应一个区块(Block), 它被放置在页面的某个区域(Region)来显示给用户进行导航.
其实Drupal的菜单机制不仅要能把导航显示给用户, 更重要的是在用户点击这些导航的时候, 能够准确快速定位到相应的业务逻辑. 有人会问, 难道这也是个问题吗? 要知道导航其实都对应他们具体的URI, 而传统的URI的定位是先按目录结构找处理文件, 然后根据Request参数对应业务逻辑,同时还要在业务逻辑中判断用户权限; 而Drupal有一套自己的内部路径, 它是基于模块化构建的, 与目录结构一点关系也没有了, 所以必须要有一套机制能在URI和业务逻辑间进行映射, 而Drupal的菜单机制就是完成这项工作的, 用户点击菜单项链接时, Drupal解析出内部路径, 并根据内部路径找到对应的业务逻辑, 并再完成判断权限后转交给业务逻辑进行处理, 这个过程Drupal称之为分发.
Drupal核心框架中的菜单api(includes/menu.inc文件)实现了上述功能, 它成功地解决了动态URL路径到具体执行函数间映射, 对用户屏蔽了系统预定义的Request参数的复杂处理, 在路径和功能建立了必由之路. 啥也别说了, 看看分发函数的实现:

<?php
>
function menu_execute_active_handler($path = NULL
) {
if (_menu_site_is_offline
()) {
   return MENU_SITE_OFFLINE
;
}
if (variable_get('menu_rebuild_needed', FALSE
)) {
   menu_rebuild
();
}
if ($router_item = menu_get_item($path
)) {
   if ($router_item['access'
]) {
     if ($router_item['file'
]) {
       require_once($router_item['file'
]);
     }
     return <strong>call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);</strong
>
   }
   else {
     return MENU_ACCESS_DENIED
;
   }
}
return MENU_NOT_FOUND
;
}
?>

用户URL请求到达后, Drupal先进行Bootstrap初始化, 然后调用分发函数menu_execute_active_handler, 该函数根据解析出的内部路径, 在系统构建出的菜单路由表中查找, 如果找到则判断可访问权限, 然后调用路由表中对应路径注册的Pange_callback回调函数, 这样就完成一个URL请求到具体页面逻辑的过程. 流程非常简单清晰, 学过计算机原理, 熟悉中断调用的对这流程应该都非常熟悉.

菜单路由(Menu Router)
Drupal系统主要依据menu_router表构建系统菜单路由, 而menu_router表的内容则是基于各模块的hook_menu钩子来获得, 这个钩子较少被调用, 一般都在模块初始化或其他菜单需要重建的情况. 下面我们选一段Book模块的menu钩子代码来看看:

<?php
function book_menu
() {
$items
= array();
$items['admin/content/book/%node'
] = array(              
   'title' => 'Re-order book pages and change titles'
,
   'page callback' => 'drupal_get_form'
,
   'page arguments' => array('book_admin_edit', 3
),
   'access callback' => '_book_outline_access'
,
   'access arguments' => array(3
),
   'type' => MENU_CALLBACK
,
   'file' => 'book.admin.inc'
,
);
}
?>

book模块所定义的所有菜单路由表由一个二维数组表示, 其中每一项为一个菜单路由, 下标即为该路由的入口路径, 这里为’admin/content/book/%node’. 菜单路由项的各属性看命名已经比较清晰了, 想了解可以参考Drupal.
我们看上面用的路径中包含一段’%node’, 对了这是使用了通配符, 比如你访问admin/content/book/7的时候, 会自动把node7装载进来, 太多细节, 就此打住. 

菜单项(Menu Item)
保存在menu _links表中, 定义了每个条目的名字, 条目间父子关系, 对应路径, 所属模块等等很多属性, 它应不像菜单路由项一样隐藏在背后干活, 它是可见的; 同时由于有通配符的存在, 它与菜单路由项并不是一一对应关系. (比较奇怪的是默认的Navigation菜单是用户模块创建的, 但它里面的所有菜单项却是系统模块定义的.) 既然router表的数据从钩子函数而来, 那link表是否也有对应的钩子呢, 实际上菜单项是由菜单模块(Menu Moudle)进行管理, 通过GUI界面直接配置, 当然菜单API也有对应接口,比如menu_link_save(). ( 实在不行你直接写数据库也行, 那不就是hack菜单模块了麽)

Drupal6.x增加了两个alter钩子函数对应这两张表, 它们是hook_menu_alter()<—>menu_router, hook_menu_link_alter()<—>menu_links, 它们主要处理内容变化时的处理逻辑, 由Drupal_alter()函数调用, 看代码注释说这个函数非常Ugly, 要在7中把它解决掉. 头晕了一天, 今天也没有心思再看下去了.

总结:
现在我们有点明白Drupal的菜单机制了吧, 它主要由菜单api和菜单模块组成, 提供一种框架, 使得其他功能模块能过注册菜单路由项, 并在分发过程中, 通过该菜单路由表完成用户页面请求(具体URL)到功能模块业务逻辑的映射. 当然Drupal的菜单机制还有很多复杂特性, 来日方长, 有空继续钻研.

另:有一点不明白的是menu_router为啥要用钩子函数, 不能直接用初始数据库脚本麽, 不过我也没研究过安装过程, 似乎好像没有一个地方用了数据库脚本, 有人清楚这一块麽?

从头学习Drupal–基本架构四

主题(Theme)
主题是什么呢? 其实这个词已经被用的很泛了, 所有涉及外观定制的地方都充斥着这个词, 比如我们最常见到的windows桌面主题, QQ的界面皮肤等. 说白了主题就是外观, 可定制主题就是允许用户自定义应用的外观.
Drupal的外观也是由其主题(Theme)来表现的, 一个CMS只有强大的业务逻辑处理和扩展能力, 而没有好的外表, 一样会流失大量的用户. 所幸Drupal的外观表现机制同样十分强大, 它允许网站开发人员为其网站重新设计开发个性化主题, 同时Drupal社区还有大量共享的主题, 下载下来后, 解压到站点对应的主题目录(sites/all/themes,或sites/指定站点/themes)下即完成安装, 在管理界面直接热插拔应用主题, 非常方便.

主题机制(Theme System)
为了支持这么灵活,方便又强大的可插拔主题, Drupal有一整套设计实现, 我们叫它主题机制(Theme System), 它被设计用来分离界面元素(比如HTML, CSS等)与核心业务逻辑. 通过这种分离, 有效的减少界面元素对核心代码的影响, 从而能使核心代码尽快稳定; 同时更换新的界面也不需要去动复杂的业务逻辑, 便于分配给专业的界面设计人员去开发, 从而降低用户界面这种需求易变性模块的开发复杂性和依赖性. 下面我们就到Drupal的主题机制内部去探索一番.
Drupal的主题机制(Theme System)有由三大部分组成: 主题(Themes), 主题引擎(Theme Engines)主题api(includes/theme.inc).
主题(Themes)主要包含一组文件, 系统可以利用这些文件能展示对应的外观, 以获得不同的用户感受。一个主题主要由以下内容组成:模板文件, 样式表(CSS), 图像(images), Javascript文件等; 对于一个web设计者来说, 样式表什么的应该都比较熟悉, 但模板文件是什么呢? 模板就是一个HTML片断模子, 我们可以用它来生成有具体含义的HTML片断. 举个例子, 就好像一张空的贺卡, 你填上”xx生日快乐”就成了生日贺卡, 你写上”情人节一起happy吧”那就成了节日祝福贺卡. 因为一般人都比较聪明, 所以空的贺卡大家也知道哪该写姓名, 哪该写祝福语; 万一碰到个比较傻的要写怎么办, 于是聪明人可以先用铅笔在该写的作些提示标记, 这样傻子虽然不聪明但在有提示的地方把原来的铅笔标记替换成他自己的话还是能做到的. OK, 这个带有铅笔标记的贺卡我们就可以称之为一张贺卡模板.
Drupal的主题模板文件也是同样道理, 它是一种带有标记(Tags)的HTML模板, 系统能够识别标记, 并根据标记进行内容替换, 生成实际的HTML页面. 那我靠什么识别标记? 当然就是看标记所用的语言了. 话说如果是美国傻子, 那我们就用英语写标记, 日本傻子则用日语. 回到Drupal世界, 写它的模板文件中标记的语言我们称之为模板语言(Template Language), Drupal6.x目前支持三种模板语言: php(默认), smarty, Xtemplate; 光有语言还不行, 还要有傻子的大脑来能识别这些语言啊, 那么系统中对应解释这些语言的工具就是主题引擎(Theme Engine).

Drupal默认的主题引擎是phptemplate, 它使用PHP语言, 直接内签在Drupal中, 不存在任何外部依赖. 下面看一个phptemplate的模板例子:

<?php
// $Id: block.tpl.php,v 1.3 2007/08/07 08:39:36 goba Exp $
?>
;
<div id="block-<?php print $block->module .'-'. $block->delta; ?>;" class="clear-block block block-<?php print $block->module ?>">

<?php if (!empty($block->subject)): ?>;
<h2><?php print $block->subject ?></h2>
<?php endif;?>;

<div class="content"><?php print $block->content ?></div>
</div>

因为phptemplate引擎使用的模板语言是PHP, 则里面php代码部分都是标记, 如果我们传入blcok变量{0,xx区块,xx区块内容}, 则经过主题机制(Theme system)处理后,上面这段模板代码将生成如下HTML片断:

<div id="block-book-0" class="clear-block block-book">
<h2>xx区块</h2>
<div class="content">xx区块的内容</div>
</div>

主题api
主题api提供的强大机制使得分离出去的主题层能有机地和核心协同工作, 完成内容的表现. 该机制又是通过钩子(hooks)技术实现的, 看来hooks不仅仅用于模块(Module)与核心的交互, 它被应用到所有需要与核心交互的场景. 与普通的业务逻辑类的钩子不同, 主题钩子(theme hooks)具备以下的特性: 

  1. 主题钩子是按显示组件(components)来定义的, 即一个显示组件定义一个钩子名;
  2. 主题钩子不仅能靠注册函数实现, 还能靠模板形式实现, 通过模板文件名的命名规约, 能实现与函数钩子类似的效果
  3. 主题钩子不仅是只能在模块中实现, 而且在表现层的主题和主题引擎都能实现; 这里注意了, 前面我们只说了主题中的模板文件, 这些文件是无法实现钩子函数, 而表现层中的钩子函数在哪里实现呢? 对了, 在主题目录下的template.php文件中实现.
  4. 主题钩子必须有默认实现, 要么在模块中实现, 即模块默认实现, 要么直接使用主题api中的theme_HOOK(), 它定义了核心默认的显示组件的外观.
  5. 主题钩子是纵向的, 是覆盖式的, 也就是说只有最上层的能起作用, 底下的都被屏蔽了, 这种钩子实现是不是更像函数重载呢


上面这张图是从drupal.org网站下载的, 它清楚地描述了主题机制的核心架构. 整个网页由几部分组成, 它们的不同颜色代表从不同钩子输出, 其中a点输出代表默认主题实现(核心和模块的默认钩子实现, 对应两种青灰的颜色); b点是主题引擎的钩子重载点, 主题引擎钩子实现的输出为褐色; c点则是主题的钩子重载点, 它的钩子输出为红色, 这三层的输出最终形成了给终端用户的网页.

主题钩子的注册是在模块的hook_theme()中实现的(一个业务钩子), 而主题钩子有两种形式: 回调函数和模板文件, 但主题层钩子形式必须与业务逻辑层(Core和Module)的形式保持一直, 下面我们分开进行阐述(下面的描述都基于PHPTemplate引擎):
(1)模板文件形式
模板文件允许你仅用css和的修改预定义的模板即可变换站点的外观, 非常简介和直接, 是我们自定义外观的主要方式. 常用的模板文件主要有以下几个:page.tpl.php; node.tpl.php; block.tpl.php; comment.tpl.php; box.tpl.php;

page.tpl.php
该模板描述一个HTML页面的主要元素, 包括, 和 元素, 它非常复杂, 大概能使用30多个Tag变量
node.tpl.php
该模板负责node内容的显示, 它的Tag变量都与node的属性相关, 而page.tpl.php的$content变量实际就代表它
block.tpl.php
该模板负责block的显示, 它有一个变量$block
box.tpl.php
该模板负责画一个简单的盒子, 原来好像被用作搜索结果和form, 但这个版本已经很少被使用了, 它与block有什么关系我到目前还没搞清楚
comment.tpl.php
该模板负责comment的显示, 其他没啥好说的

当处理页面显示请求时, 区块的内容, 节点的内容, 评论的内容等等都会先被放到区域(Region)中, 然后通过page.tpl.php中的区域变量$content, $header, $left, $right, and $footer等拼接成实际页面. 所以为了减少显示数据的生成, 可以禁用区域内的block而不是仅仅修改page模板.

前面说了, 模块也可以注册模板钩子, 主题也可以注册模板钩子, 那到底哪一个钩子生效呢? 这是由主题机制的调度函数分发的. 下图描述了几种关键模板的查顺序, 它们完全依赖模板名字的特殊命名约定来实现, 如果文件名相同, 则优先选择上层的模板.

更详细的关于模板文件内的Tag变量, 可以参考《Pro_Drupal_Development》和Drupal.org.

(2)回调函数形式
回调函数形式是一种用coding来处理自定义界面的方式, 说白了就是表现层没剥离完全的那部分, 留了一个后门, 以便处理更特殊的显示需求. 主题和主题引擎的钩子函数都在template.php文件中实现, 它们的命名规范是mytheme_hook()和themeengine_hook(). 尽管采用回调函数, 比模板方式提高5倍的速度, 但我们在定制外观的时候, 还是建议优先考虑定制模板钩子. 如果确实没有模板可用, 也尽量在主题引擎层实现函数钩子, 这样子主题也可以共享该函数钩子.

最后, 整个上面的流程, 不论是模板还是回调函数, 全部都是靠主题api中的theme()函数来实现的, 该函数堪称Drupal主题机制的中场发动机.

编后语:
我花了三天时间来看主题, 每多看一次都会对自己的否定几次, 确实太复杂, 而且Drupal6主题变化很大, 每天都不停的修改这篇文章, 真的以为这篇文章发不出来了, 但是为了给我的学习进程做个见证, 在我还没有完全理解它之前, 还是post出来了, 虽然不是很系统, 但它阐明我目前对主题机制的理解, 希望大家的指正.

从头学习Drupal–基本架构五

任何一个开放系统(Open System), 只要它与外界有接口, 就存在安全问题, 越是商业级应用越注重安全. 安全管理涉及很广, 大到整个网络安全的设定,小到具体按钮的访问, 如果你的系统哪天出现安全问题, 没准追根溯源能找到机房看门老头, 呵呵, 玩笑话略过, 今天我们主要看看Drupal的用户权限管理, 也就是访问控制系统.

权限管理的要素
我认为, 一个权限管理系统主要由以下四要素组成: 访问者, 管理对象, 操作和规则.

  • 访问者: 谁干? who. 一般来说是人, 严格来说应该叫主动体, 这要看你的系统面向哪些用户, 一般系统人机接口,机机接口都是存在的. 反正就是那些通过你系统开发的接口与你交互的东东.
  • 管理对象: 干谁? which. 就是被管理的东西, 比如仓库里的粮食, 博客系统的文章, 总之, 任何有管理要求的东西. 千万要记住的一点是: 就算垃圾也是有管理要求的.
  • 操作: 干什么? what. 对应具体操作, 比如写文章, 运粮食, 倒垃圾…
  • 规则: 怎么干? how, when, where, 定义操作实施的附加条件, 比如登陆时间等.

权限管理的系统功能
可以说, 自从有了安全问题, 大家就在围绕这几个要素伤脑筋, 针对各种不同的管理需求, 也出现了各种不同的权限管理的方案, 如何在安全性, 操控性, 性能以及开发复杂性间取得平衡是恒古不变的主题. 为了聚焦今天的话题, 我们主要从系统功能角度来窥视一下, 看看一个权限系统到底是如何工作的.

  1. 首先当然权限的定义, 即识别出系统中需要进行访问控制的要素(管理对象, 操作, 规则), 并定义成相应的权限, 这是整个权限系统的核心, 针对不同的管理粒度和管理要求, 会出现截然不同的实现方式.
  2. 授权, 即把权限与用户关联, 最直接的方法是给用户直接关联, 而最常见最有效的方式是使用角色(也有叫组的)
  3. 鉴权, 根据规则执行权限的检查, 以实施访问控制, 看你的检查点实现在哪呢, 同样也是不同的要求有不同的实现
  4. 最后, 应该要能监控, 一般记日志

是不是比较清楚, 把这几个系统功能考虑完全, 并实现好, 你的系统就安全了一半; 另一半是什么, 是你的安全意识, 天干物燥, 防火防盗, 只有真正树立安全意识, 才能有效地去应用各种安全措施, 防范危险. 不幸的是, 我们大多数人都疏予管理, 闲置, 不管不顾.

Drupal的权限管理框架
首先我们来识别系统中具备哪些要素: Drupal是一个基于web的CMS系统, 所以访问者主要局限在管理员和普通web访问用户(User); 而管理对象则是CMS中的核心–内容, 在drupal中它被抽象为节点(Node). (应该还会有些其他的管理对象). 操作呢? 菜单, 按钮, 在web系统里是不是都对应到页面, 也就是内部路径(例如: “node/1/view”), 规则没什么特殊的: 操作时出现禁止页面, 列表中不出现无权限内容等.

Drupa采用角色来关联用户和操作, 每个角色是从方便系统管理角度出发来定义的一类具备相同操作界面或行为的访问者, 但它不是简单的用户或操作的分组, 而是他们的有机结合, 形象的说就是一组权力的代名词或者叫Permission Scheme. Drupall针对操作是否涉及到管理对象, 设计了两个不同的鉴权流程, 以满足不同的权限管理要求.

(1)与内容无关的操作权限
权限定义
钩子hook_perm, 定义了每个模块的操作权限, 用操作名描述.

<?php

function book_perm
() {
return array('add content to books', 'administer book outlines', 'create new books', 'access printer-friendly version'
);
}
?>

授权

按角色授权, 权限被分配后, 存储在数据库表{permission}中.

鉴权

由函数user_access进行实际的鉴权工作, 它的参数是账号信息与操作权限名, 返回ture或false. 你可以在任何需要的地方调用该函数进行鉴权, 但系统提供了一套默认的回调机制, 使得鉴权点实现简单一致. 这就是菜单路由注册函数中的access_callback参数, 该参数标明了此菜单路由需要进行访问控制, 只有访问回调函数验证通过, 才会被正确路由到对应页面.

<?php
function forum_menu
() {
$items['forum'
] = array(
   'title' => 'Forums'
,
   'page callback' => 'forum_page'
,
   'access callback' => '_example_test_access', 
//这里指定访问验证回调函数, 默认为hook_access()钩子,
   'access arguments' => array('access content'
),
   'type' => MENU_SUGGESTED_ITEM
,
   'file' => 'forum.pages.inc'
,
);
?>

鉴权钩子, 模块可以处理一些特殊的规则

<?php

function forum_access($op, $node, $account
) {
switch ($op
) {
   case 'create'
:
     return user_access('create forum topics', $account
);
   case 'update'
:
     return user_access('edit any forum topic', $account) || (user_access('edit own forum topics', $account) && ($account->uid == $node->uid
));
   case 'delete'
:
     return user_access('delete any forum topic', $account) || (user_access('delete own forum topics', $account) && ($account->uid == $node->uid
));
}
}
?>

(2)节点访问机制(Node Access System)

drupal还提供对管理对象的访问控制–节点访问机制(Node Access System), 这是一套复杂的机制, 我还没完全摸清. 它主要是在权限定义方面进行了增强, 它通过定义用户, 操作与节点的绑定, 来建立一个节点访问表, 这样当需要进行管理对象关联鉴权时, 系统会执行node_access函数, 该函数会恰当地查询节点访问表获得用户的权限, 以完成鉴权.

我们再来看看这张节点访问表, 用户信息, 操作信息, 节点信息, OMG! m*n*P=? 如果还是按以前方式来定义, 那性能的低下可想而知, 所以综合一般的权限控制需求, Drupal仅定义了三种的原始操作: view, update, delete, 而对于用户则仅定义了一个抽象的二元组(realm, grant), 它与实际用户间的映射由你自定义的节点访问控制模块行来决定, 这样达到对用户归纳的效果, 减小节点访问表纪录的数量级.

这套机制主要通过节点模块(Node Module)提供的一系列api和hook_node_grants钩子和hook_node_access_records钩子来实现. 我们这次知道有这套东西即可, 不铺开叙述.

Drupal的权限管理方法
1, 基本权限管理
尽管Drupal的权限管理机制是强大复杂的, 但其默认的基本权限管理却非常简单, 基于角色(role)和操作权限, 把操作赋予角色, 而把角色赋予用户, 这样用户就能使用相应的权限. 基本权限管理粒度较粗, 只能满足一般的安全需求.

2, 按内容类型鉴权
如果我们对管理对象有更精细的管理要求, 比如我想让A角色能访问page, B角色只能访问story, 咋办? 基本权限管理做不到了, 那我们就扩展模块. 内容访问模块(Content Access )允许你对内容类型按角色设置权限(查看, 编辑, 删除). 其实Drupal6.2基本权限管理里也能设置按内容类型进行鉴权, 因为drupal把所有节点类型的几个基本操作都注册了, 看下面这个丑陋的循环, 呵呵, 这几个也是汉化不到位的地方.

<?php
function node_perm
() {
$perms = array('administer content types', 'administer nodes', 'access content', 'view revisions', 'revert revisions', 'delete revisions'
);

foreach (node_get_types() as $type) {
   if ($type->module == 'node'
) {
     $name = check_plain($type->type
);
     $perms[] = 'create '. $name .' content'
;
     $perms[] = 'delete own '. $name .' content'
;
     $perms[] = 'delete any '. $name .' content'
;
     $perms[] = 'edit own '. $name .' content'
;
     $perms[] = 'edit any '. $name .' content'
;
   }
}

return $perms;
}
?>

3, 按组鉴权
如果是个大公司, 有很多部门, 相互间都有安全需求, 比如:
1. 能按不同的部门创建不同的组, 它们都能管理其私有内容
2. 组成员拥有组内的权限
3. 跨组间允许指定某些特定的权限共享
4. 匿名用户只能访问标识为”public”的内容, 注册用户能访问public和Restricted, 仅组成员能访问private等
看到这么多需求, 则我们需要一个更复杂的权限管理, drupal目前有两个扩展模块都能提供类似的功能:
(a) taxonomy_access modules
使用drupal的分类模块的基本功能, 完成以上的权限控制, 这里有英文的教程, 步骤描述较清楚.
i, 定义一个词汇表(Groups), 用来定义组(即公司部门); 把它应用到所有内容类型, 再添加2个条目(比如财务, 研发)
ii, 再定义一个词汇表(Access), 用来定义访问级别; 同样应用到所有内容类型, 同时还要设置”必须”的选项, 这样保证创建内容时必须选一个该
词汇表中的条目. 加三个条目(public, Restricted, Private)
iii, 按基本权限管理的方法创建2个角色(财务人员, 开发人员), 再创建几个用户, 并分辨赋予对应角色(张三->财务, 李四->开发)
iv, 前面都是基本管理,没涉及到扩展模块, 现在打开”administer > user management > taxonomy access: permissions”, 按照步骤开始设吧, 一个组鉴权系统完成, 祝你好运.

(b) 著名的OG
Organic Groups功能太强了, 上面的需求全部能满足, 组或圈的概念是大型社区不可缺少的, 别犹豫了, 能用就用吧.

总之, drupal的权限管理机制非常灵活, 可简可繁, 每个系统都安全的需求都是不一样的, 你们的权限管理有哪些特殊的好实现, 拿出来show绣把.

评论

(required. But it will not be published)