目录

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

第一章Drupal7主题制作 6

静态HTML分析 6

静态页面效果预览 7

静态页面源码分析 12

创建自己的主题 23

创建主题的info文件 24

默认的模板文件 26

创建自己的page.tpl.php 33

启用主题 36

创建静态区块 37

控制区块的HTML输出 39

控制菜单区块的输出 41

区块的可见性设置 49

第二章  Views的覆写 51

创建内容类型“新闻” 51

使用Views创建新闻列表区块 52

Views自带的样式设置 56

格式设置 57

Views模板文件覆写 61

views-view.tpl.php 62

views-view-list.tpl.php 67

Attachement 70

3  表单覆写 76

区域的调整 76

表单覆写 79

Remember me模块 79

hook_theme中注册表单 80

表单预处理函数 81

为表单创建模板文件 81

使用form_alter控制表单元素的theme_wrappers 83

为表单元素添加Class 83

去掉页面的标题/标签 85

表单数组结构分析 85

表单覆写示例2 88

首页的制作 92

创建page--front.tpl.php文件 93

在页面模板中直接输出区块 95

使用views_embed_view 95

覆写html--front.tpl.php 97

节点占位符 99

4 Drupal源码分析 101

路径分析 101

index.php文件 102

引导指令分析 104

drupal_bootstrap 104

配置阶段 108

页面缓存阶段 109

数据库阶段 111

变量阶段 113

会话阶段 115

页面头部阶段 117

语言阶段 118

完成阶段 119

页面内容的生成与组装 121

菜单回调机制 121

主内容的生成 124

页面内容组装 132

使用drupal_render呈现页面数组 139

drupal_page_footer负责收尾工作 142

5 Drupal企业站 144

企业站需求分析 144

首页 144

产品列表页面 146

产品详细页面 147

联系我们页面 148

主题的选型 149

从头搭建企业站 150

创建内容类型“关于我们” 150

使用webform创建“联系我们” 152

添加百度地图 162

创建内容类型 165

添加测试内容 168

使用Views创建新闻列表 168

Views附件显示 172

创建产品列表视图 177

完善主菜单 178

Superfish 178

CKEditor + IMCE 182

设置默认的文本格式 186

创建一个新的文本格式 187

6 企业站常用模块 189

主题制作 189

主菜单样式的调整 189

Menu Block 192

Menu Position模块 196

使用Nodequeue控制产品列表 201

样式调整 205

Menu block样式调整 205

对面包屑和标题样式调整 206

调整页脚区域 207

Site map模块 209

创建“联系我们”区块 211

首页 212

首页幻灯 212

Features模块 214

使用Panels创建首页 217

Mini Panel的使用 220

主导航样式调整 222

7 Drupal多语言网站 224

界面的多语言化 224

添加语言 224

导入简体中文语言包 225

Localization update模块 228

Localization client模块 232

内容的多语言化 234

Content translation VS Entity translation 234

使用Entity translation模块翻译节点 235

使用Entity translation模块翻译分类术语 243

语言切换器区块 246

i18n模块 247

变量的翻译 249

菜单的翻译 253

区块的翻译 256

使用Menu block输出菜单 260

Views的翻译 261

使用jQuery解决无法翻译的字符串 263

首页的多语言化 263

Webform的翻译 269

字段标签的翻译 271

面包屑的翻译 271

Translation Management Tool 272

 

 



Drupal版本:

​第三章 表单覆写

Drupal版本:

1 区域的调整


我们来看登录页面HTML的中间部分,其实在内容和面包屑的之间,每个页面还有一张图片,对于这张图片,我们改如何处理呢?

1.png 

有两种办法,一种是,把它放到一个区块里面,然后把区块放到主内容区域里面,也就是放在正文的上面。还有一种办法,就是将它处理成一个独立的区域,然后里面放置一个同样的区块;此时我们需要调整info文件里面区域的定义,调整page.tpl.php里面区域的输出。

如果在以前,我们可以采用后者,这是我比较喜欢的一种方式。不过在Drupal7中,我们可以采用前者。为什么呢?在Drupal6里面,我们向主内容区域添加区块,区块内容的显示,总是放在主内容的下面,但是在Drupal7里面,却是这样的:

2.png 

主内容只是一个普通的区块,所以我们可以在区块管理界面,调整内容区域里面,区块之间的先后顺序,也就是说,我们可以将这里的图片区块放在主内容的上面。

导航到admin/structure/block,点击这里的添加区块链接,我们创建一个区块,这是区块的配置:

3.png 

我们将文本格式选择为PHP code,区域选择SNT主题的内容区域。其它采用默认配置,保存区块。

现在我们访问user/login页面,或者访问一个节点页面,我们看到这样的内容:

4.png 

我们发现了什么问题,就是页面的标题,还有tabs标签,全部显示在了图片区块的上面。解决办法是有的,就是我们不在page.tpl.php里面输出标题,把它放在节点模板里面。对于page.tpl.php里面的tabs变量,我们也可以去掉对它的输出。如果你觉得,这些都不是问题,都是可以接受的,那样这就可以了。

如果你觉得这些无法接受,标题和标签必须放在图片区块的下面。此时我们,可以添加一个新的区域。我们不妨把它命名为ziyebanner。步骤如下:

1) 修改snt.info文件,在区域定义里面,输入以下内容:

regions[ziyebanner]        = Ziye banner

2) 修改page.tpl.php文件,这是修改后的对应代码:

       <div class="ziye-left">

  <?php if ($breadcrumb): ?>

           <p class="daohang"><?php print $breadcrumb; ?></p>

  <?php endif; ?>

  <p><?php print render($page['ziyebanner']);  ?> </p>

  <div class="nei">

  <?php print $messages; ?>

        <?php if ($page['highlighted']): ?><div id="highlighted"><?php print render($page['highlighted']); ?></div><?php endif; ?>

        <a id="main-content"></a>

        <?php print render($title_prefix); ?>

        <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>

        <?php print render($title_suffix); ?>

        <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>

        <?php print render($page['help']); ?>

        <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>

        <?php print render($page['content']); ?>

        <?php print $feed_icons; ?>

        </div>

      </div>

我们主要做的工作,就是输出了ziyebanner区域,还有就是在主内容区域外面加了一个<div class="nei">

3) 现在清除缓存,在区块管理界面,就可以看到我们新增的ziyebanner区域了,我们把刚才创建的图片区块,放到这个区域。

5.png 

保存区块设置,使用匿名用户访问user/login页面,刚才的问题已经不存在了。

6.png



Drupal版本:

2 表单覆写

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

表单覆写

Drupal版本:

2.1 Remember me模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在来看,登录表单页面的覆写,美工所给的表单元素里面,还包含一个复选框“两周内自动登录”,对于这个功能,我们可以使用Remember me模块。

1) 安装、启用Remember me模块。有时候,我觉得Remember me模块,仅仅是起到了一个心理安慰作用,因为本身Drupal就会记住你的用户名/登录状态,下次访问通常不需要登录。但是很多网站,还是需要这个功能。

2) 启用后,访问登录页面,我们看到多了一个复选框“Remember me”,并且默认是选中的。

1.png 

      如何将这段文本,改造为我们想要的中文?有两种办法,一个就是使用form_alter这个钩子函数,修改对应表单元素的标签;第二个方法,就是将对应的英文翻译成我们想要的中文。我在实际的很多项目中,都采用后一种办法。我们导航到admin/config/regional/translate,点击翻译标签,进入页面admin/config/regional/translate/translate,在这里,输入“Remember me”,过滤。

2.png 

点击文本右边的编辑链接,即可将“Remember me”翻译成“两周内自动登录”。

3.png 

   点击这里的“保存翻译”按钮。现在登录页面的内容,已经是我们想要的了。

 

我们现在来覆写这个表单页面,控制Drupal表单的输出,这是Drupal主题制作里面最富有技巧性质的。需要的知识比较多。我们这里的要求也比较简单,希望大家遇到了类似的问题,可以以我们的例子为参考,依葫芦画瓢,满足自己的需求即可。


Drupal版本:

2.2 在hook_theme中注册表单

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

1) 打开sites\all\themes\snt下面的template.php文件,向里面添加以下代码:

function snt_theme(&$existing, $typ

e, $theme, $path){

  $hooks = array();

  $hooks['user_login'] = array (

     'template' => 'user-login',

     'render element' => 'form', 

 'path' => drupal_get_path('theme','snt').'/templates',

      //'preprocess functions' => array('snt_preprocess_user_login'),   

  );

 

  return $hooks;

}

这段代码,实现了钩子hook_theme,在这里我们为user_login表单,声明了一个模板文件user-login.tpl.php,使用这个模板文件负责表单的输出,模板文件放在当前主题的templates目录里面。user_login是表单的ID,通过查看表单的HTML源代码,可以获得。


Drupal版本:

2.3 表单预处理函数

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接着添加对应的预处理函数:

function snt_preprocess_user_login(&$variables) {  

//$variables['intro_text'] = t('This is my awesome login form'); 

    //$variables['form']['actions']['submit']['#attributes'] = array('class' => 'but');

$variables['name'] = drupal_render($variables['form']['name']);

$variables['pass'] = drupal_render($variables['form']['pass']);

$variables['submit'] = drupal_render($variables['form']['actions']['submit']);

//$variables['links'] = drupal_render($variables['form']['links']);

$variables['remember_me'] = drupal_render($variables['form']['remember_me']);

$variables['hidden'] = drupal_render_children($variables['form']);

}

在预处理函书里面,我们为模板文件提供变量,其实我们也可以不提供变量,在模板文件中直接输出表单元素。


Drupal版本:

2.4 为表单创建模板文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

1) 我们在sites\all\themes\snt\templates下面,创建user-login.tpl.php,里面放置以下内容:

<div class="login">

  <table width="295" border="0" align="center">

    <tr>

      <td class="yhzc" colspan="3">用户登陆</td>

    </tr>

    <tr>

      <td width="60" class="mima">账号:</td>

      <td><label><?php  print $name; ?></label></td>

      <td class="hb"> </td>

    </tr>

    <tr>

      <td class="mima">密码:</td>

      <td><label><?php  print $pass; ?></label></td>

      <td class="hb"> </td>

    </tr>

    <tr>

      <td class="mima"></td>

      <td><?php print $remember_me;?></td>

      <td class="hb"></td>

    </tr>

    <tr>

      <td class="xib" colspan="3"><?php print $submit;?></td>

    </tr>

  </table>

  <?php print $hidden ;?>

</div>

现在清除缓存,使用匿名用户访问user/login页面,我们看到的登录表单:

1.png 

已经比较接近我们的需求了,只不过,还存在一些问题,比如表单元素的标签的重复输出,表单元素的描述不应该显示的问题,还有就是表单元素过长。

现在,如果使用Firebug查看页面源代码的话,我们发现,表单整体的HTML,已经被我们控制住了,但是表单元素的HTML,还没有控制住。

我们以用户名为例,我们的目标输出,是这个样子的:

<label><input class="wbk" type="text" name="textfield" id="textfield" /></label>

而实际,这里的输出,则有很多:

2.png


Drupal版本:

2.5 使用form_alter控制表单元素的theme_wrappers

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

1) 我们在template.php文件添加以下代码:

function snt_form_alter(&$form, &$form_state, $form_id) {

  if ($form_id == 'user_login') {      

$form['name']['#theme_wrappers'] = array();

$form['pass']['#theme_wrappers'] = array();

$form['submit']['#theme_wrappers'] = array();

$form['remember_me']['#theme_wrappers'] = array();

  }

}

现在样式干净了很多。

1.png


Drupal版本:

2.6 为表单元素添加Class

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

问题还是存在,表单元素过长了,我们以用户名表单元素为例,分析一下目标代码和当前输出。

目标代码:

<label><input class="wbk" type="text" name="textfield" id="textfield" /></label>

当前输出:

<label>

<input id="edit-name" class="form-text required" type="text" maxlength="60" size="60" value="" name="name" tabindex="1">

</label>

在这里,idname我们是必须要要使用Drupal默认的了,不然的话,就没有办法正常工作了。我想,我们应该把目标代码里面的class="wbk"给加上。这是我们修改后的form_alter函数,粗体表示新增的。

function snt_form_alter(&$form, &$form_state, $form_id) {

  if ($form_id == 'user_login') {      

$form['name']['#theme_wrappers'] = array();

$form['pass']['#theme_wrappers'] = array();

$form['submit']['#theme_wrappers'] = array();

$form['remember_me']['#theme_wrappers'] = array();

$form['name']['#attributes'] = array('class' => array('wbk'));

$form['pass']['#attributes'] = array('class' => array('wbk'));

$form['actions']['submit']['#attributes'] = array('class' => array('anniu2'));

  }

}

现在,样式正常了。

1.png 

还有问题,登陆按钮,里面的文字重复显示了,因为背景图片里面已经包含了这两个字。我们在form_alter里面增加这么一行代码:

$form['actions']['submit']['#value'] = t('');

这样,就可以完全使用背景图片的样式了。

复选框后面的文本被去掉了,没有关系,我们直接在user-login.tpl.php里面,给加上来。这是修改后的对应代码:

<td><?php print $remember_me;?><span class="zidong">两周内自动登录</span></td>

现在,使用匿名用户访问登录页面,样式已经完全满足我们的需要了。

2.png


Drupal版本:

2.7 去掉页面的标题/标签

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

如何想去掉这个页面的标题/标签,也就是这部分:

1.png 

我们可以这样,在sites\all\themes\snt\templates\override下面,创建一个文件夹page,然后将sites\all\themes\snt\templates下面的page.tpl.php复制过来,重命名为page--user--login.tpl.php

打开page--user--login.tpl.php,找到这段代码:

        <?php print render($title_prefix); ?>

        <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>

        <?php print render($title_suffix); ?>

        <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>

将其删除掉。清楚缓存,现在的登录页面,已经和美工所给的HTML完全保持一致了。

对于表单的覆写,我们最多只能控制它的90%的输出,还是有一部分我们控制不了的,因为Drupal表单里面,有些东西是必须要要有的,不然的话就不能正常工作。但是,我们最需要覆写的输出,我们控制住了,而我们控制不了的那10%,对样式的影响,通常不大。


Drupal版本:

2.8 表单数组结构分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

可能会有读者问,我是怎么知道的使用$form['name']['#attributes'],使用$form['name']['#theme_wrappers']的。我们可以在form_alter里面加上,我们熟悉这段代码:

print debug($form);

就可以查看表单的结构。

array (

  'name' => 

  array (

    '#type' => 'textfield',

    '#title' => '用户名',

    '#size' => 60,

    '#maxlength' => 60,

    '#required' => true,

    '#description' => '输入您在 SNT 的用户名。',

    '#attributes' => 

    array (

      'tabindex' => 1,

    ),

  ),

  'pass' => 

  array (

    '#type' => 'password',

    '#title' => '密码',

    '#description' => '输入与您用户名相匹配的密码。',

    '#required' => true,

    '#attributes' => 

    array (

      'tabindex' => 1,

    ),

  ),

  '#validate' => 

  array (

    0 => 'user_login_name_validate',

    1 => 'user_login_authenticate_validate',

    2 => 'user_login_final_validate',

  ),

  'remember_me' => 

  array (

    '#title' => '两周内自动登录',

    '#type' => 'checkbox',

    '#default_value' => 1,

    '#attributes' => 

    array (

      'tabindex' => 1,

    ),

  ),

  'actions' => 

  array (

    '#type' => 'actions',

    'submit' => 

    array (

      '#type' => 'submit',

      '#value' => '登录',

      '#attributes' => 

      array (

        'tabindex' => 1,

      ),

    ),

  ),

  '#form_id' => 'user_login',

  '#type' => 'form',

  '#build_id' => 'form-q3SaShBE9RJOLYFhQD5E9Dqm3dccXRyxB_EpYlZgyVQ',

  'form_build_id' => 

  array (

    '#type' => 'hidden',

    '#value' => 'form-q3SaShBE9RJOLYFhQD5E9Dqm3dccXRyxB_EpYlZgyVQ',

    '#id' => 'form-q3SaShBE9RJOLYFhQD5E9Dqm3dccXRyxB_EpYlZgyVQ',

    '#name' => 'form_build_id',

    '#parents' => 

    array (

      0 => 'form_build_id',

    ),

  ),

  'form_id' => 

  array (

    '#type' => 'hidden',

    '#value' => 'user_login',

    '#id' => 'edit-user-login',

    '#parents' => 

    array (

      0 => 'form_id',

    ),

  ),

  '#id' => 'user-login',

  '#method' => 'post',

  '#action' => '/snt2/user/login',

  '#theme_wrappers' => 

  array (

    0 => 'form',

  ),

  '#tree' => false,

  '#parents' => 

  array (

  ),

  '#submit' => 

  array (

    0 => 'user_login_submit',

  ),

  '#theme' => 

  array (

    0 => 'user_login',

  ),

)

snt_preprocess_user_login里面,使用print debug($variables['form']);,也是同样的效果。


Drupal版本:

2.9 表单覆写示例2

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们最后,看注册页面,这也是一个表单,更复杂一点,我们这里给出代码,和具体的实现。我们的目标样式:

1.png 

1) 我们需要为用户添加一个手机字段,并将它显示在注册表单页面。导航到admin/config/people/accounts/fields,添加手机字段,字段配置。

属性

标签

手机

机读名称

field_telephone

字段类型

文本

控件

文本字段

必填字段

选中

在用户注册表单中显示

选中

值的数量

1

      其它采用默认配置即可。现在的注册表单,是这个样子的:

2.png 

为了让用户能够在注册的时候,设置自己的密码,我们需要在admin/config/people/accounts页面,账号设置页面,配置一下,这里是我的配置:

3.png 

现在注册页面,用户可以设置自己的密码了:

4.png 

2) 我们开始写代码了,这里给出来的是我调试好的代码。在template.phpsnt_theme函数里面,添加以下代码:

  $hooks['user_register_form'] = array(      

    'template' => 'user-register-form',

    'render element' => 'form',

    'path' => drupal_get_path('theme','snt').'/templates',

    //'preprocess functions' => array('snt_preprocess_user_register_form'),

  );

添加预处理函数snt_preprocess_user_register_form

function snt_preprocess_user_register_form(&$variables) {  

//print debug($variables['form']);

$variables['form']['account']['pass']['pass1']['#theme_wrappers'] = array();

$variables['form']['account']['pass']['pass2']['#theme_wrappers'] = array();

$variables['form']['account']['pass']['pass1']['#attributes'] = array('class' => array('wbk'));

$variables['form']['account']['pass']['pass2']['#attributes'] = array('class' => array('wbk'));

}

   添加函数snt_form_user_register_form_alter

function snt_form_user_register_form_alter(&$form, &$form_state) {

   //drupal_set_message('123456');

   //print debug($form['account']['pass']['pass1']);

   $form['account']['name']['#theme_wrappers'] = array();

   $form['account']['mail']['#theme_wrappers'] = array();

   $form['account']['pass']['#theme_wrappers'] = array();

   //$form['account']['pass']['pass1']['#theme_wrappers'] = array();

   //$form['account']['pass']['pass2']['#theme_wrappers'] = array();

   $form['field_telephone']['und'][0]['value']['#theme_wrappers'] = array();

   $form['account']['name']['#attributes'] = array('class' => array('wbk'));

   $form['account']['mail']['#attributes'] = array('class' => array('wbk'));

   //$form['account']['pass']['pass1']['#attributes'] = array('class' => array('wbk'));

   $form['field_telephone']['und'][0]['value']['#attributes'] = array('class' => array('wbk'));

   

   $form['actions']['submit']['#attributes'] = array('class' => array('anniu'));

   $form['actions']['submit']['#value'] = t('');   

  // print debug($form['account']['pass']);

}

最后创建user-register-form.tpl.php文件,里面的代码如下:

<div class="zhuce">

  <table width="489" border="0" align="center">

    <tr>

      <td class="yhzc" colspan="3">用户注册</td>

    </tr>

    <tr>

      <td class="mima"><span>*</span>用户名:</td>

      <td><label><?php  print render($form['account']['name']);  ?></label></td>

      <td class="hb">账号不能为空</td>

    </tr>

    <tr>

      <td class="mima"><span>*</span>密码:</td>

      <td><label><?php  print render($form['account']['pass']['pass1']);  ?></label></td>

      <td class="hb">密码不能为空</td>

    </tr>

    <tr>

      <td class="mima"><span>*</span>确认密码:</td>

      <td><label><?php  print render($form['account']['pass']['pass2']);  ?></label></td>

      <td class="hb">确认密码不能为空</td>

    </tr>

    <tr>

      <td class="mima"><span>*</span>手机:</td>

      <td><label><?php  print render($form['field_telephone']['und'][0]['value']);  ?></label></td>

      <td class="hb">手机号码不能为空</td>

    </tr>

    <tr>

      <td class="mima"><span>*</span>电子邮件:</td>

      <td><label><?php  print render($form['account']['mail']);  ?></label></td>

      <td class="hb">电子邮箱地址不能为空</td>

    </tr>

    <tr>

  <td class="xib" colspan="3"><label><?php  print drupal_render($form['actions']);  ?></label></td>

    </tr>

  </table>

  <?php drupal_render_children($form)?>

</div>

这里面的代码,实现方式,与前面我们介绍的有所不同,我们没有在预处理函数里面定义变量,而是直接在模板里面输出对应的表单元素,注意这句代码的应用:

<?php  print render($form['account']['name']);  ?>

还有,就是密码确认表单元素,实际上由两部分组成pass1pass2,不过在form_alter里面,我们获取不到,所以把对它们的修改放在了预处理函数里面了。

再有就是字段field_telephone的用法,需要特别的注意,对应的数组结构。

最终样式,完全和目标一致:

5.png 

如何去掉这里的标题和标签呢?前面已经有个例子了,这里就不多说了。


Drupal版本:

3 首页的制作

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看首页的制作,以前我们对于首页的HTML做过分析的,截图在前面也有,可以回过头来看一下。使用Panels制作首页?这里不会讲解这个,这是比较高级的技术,我们讲最简单的办法。最简单的办法?是的,这是学习成本最低的办法,有点土哦。


Drupal版本:

3.1 创建page--front.tpl.php文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

办法是这样的,就是为首页单独的创建page--front.tpl.php文件,然后将首页区块里面的内容,保存到Drupal的区块里面,或者通过Views实现出来。然后在page--front.tpl.php文件,直接输出这些区块,或者Views

我们再来分析一下首页和普通页面结构的对比,首先是首页的:

1.png 

这个是普通页面的:

2.png 

我们看到,首页的bannerjishu两个div,就对应于普通页面的ziye,我们首先这样做,在sites\all\themes\snt\templates\override\page下面创建page--front.tpl.php,直接复制现有的重命名即可,然后将ziye的内容,替换为bannerjishu两个div里面的静态内容。

这是替换后的样子。

3.png 

我们替换后,需要将文件的编码格式转为UTF-8,不然看到的会是乱码。现在访问首页,已经可以看到内容了。

4.png 

不过图片都没有显示出来,在复制过来的代码中,很多都是这个样子的:

<img src="images/003.gif" width="128" height="42" />

我们只需要把它替换为

<img src="<?php print base_path() . path_to_theme() ?>/images/003.gif" width="128" height="42" />

这样就可以了。凡是有图片的,我们都这样替换一遍。现在图片的显示都正常了。

5.png


Drupal版本:

3.2在页面模板中直接输出区块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们知道,右上角的立即下载区块,我们已经放到了Drupal的区块里面了,我们这里实际上直接调用这个区块即可。此外,最新公告/最新新闻,我们也实现了。

我们在区块列表中找到banner-right这个区块,我们看到它的IDDelta)为5。我们可以将首页对应的静态HTML部分删除掉,替换为这样的代码。

<?php

  $block = module_invoke('block', 'block_view', 5);

  print $block['content'];

?>

然后,刷新首页,这部分内容的显示,还是正常的。这段代码,就是在模板中的任何位置,输出区块的代码。module_invoke里面的第一个参数,是模块的机读名字,这里是block,第2个参数,是钩子,这里是block_view,第三个参数,是传递给钩子函数的,这里是区块的delta


Drupal版本:

3.3 使用views_embed_view

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们将<div id="jishu-right">和它里面的所有内容全部删除,在对应的位置,添加以下代码:

<?php print views_embed_view('news','block'); ?>

这样就把“最新公告/最新新闻”替换为我们Views的实现了。刷新首页,发现没有正常显示出来。

1.png 

我仔细检查了一下,原来首页是<div id="jishu-right">,而其它页面则是<div id="jishu-right2">,就这一点差别。怎么办?我们可以将视图news里面的区块显示克隆一份,对于克隆出来的区块,我们使用同样的配置,同样的模板文件,只有一点不同,就是为它单独创建一个views-view--news--block-1.tpl.php,里面的内容也是复制过来的,只不过,我们将<div id="jishu-right2">替换为了<div id="jishu-right">。当然,对于附件显示,我们也需要将它附加到新建的区块上面来。

最后,我们将嵌入视图的代码修改为:

<?php print views_embed_view('news','block_1'); ?>

这样就正常了。

我们来看左边,幻灯下面的两段HTML

2.png 

<div id="jishu-left">下面包括<div class="tupo"><div class="zhichi">,我们分别为它们两个创建两个自定义区块,然后在对应的位置输出对应的区块。这是创建好的区块,我们没有将它们放到任何区域:

3.png 

这是替换后的代码:

         <div id="jishu-left">

<?php

  $block = module_invoke('block', 'block_view', 12);

  print $block['content'];

?>

<?php

  $block = module_invoke('block', 'block_view', 13);

  print $block['content'];

?>

         </div>

到目前为止,一切都好,只有一点小问题:

4.png 

技术支持,这部分,没有显示全,出现了滚动条。我们的解决办法,是添加一段CSS代码,添加到sites\all\themes\snt\css下面的custom.css文件中:

#jishu #jishu-left  .zhichi ul{

  width:699px;

}

#jishu #jishu-left  .zhichi{

  overflow-x:hidden;

  overflow-y:hidden;

}

这样就搞定了,有时候CSS覆写还是很方便的。



Drupal版本:

3.4 覆写html--front.tpl.php

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

首页的幻灯,目前还是一片空白,没有显示出来:

1.png 

幻灯是加载了JS的,怎么办?办法很简单。

sites\all\themes\snt\templates\override下面创建文件夹html,然后将sites\all\themes\snt\templates下面的html.tpl.php复制过来,重命名为html--front.tpl.php,然后呢,将index.html里面head部分中的JS部分复制过来。这是我修改后的html--front.tpl.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"

  "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language; ?>" version="XHTML+RDFa 1.0" dir="<?php print $language->dir; ?>"<?php print $rdf_namespaces; ?>>

 

<head profile="<?php print $grddl_profile; ?>">

  <?php print $head; ?>

  <title><?php print $head_title; ?></title>

  <?php print $styles; ?>

  <?php print $scripts; ?>

 <script src="<?php print base_path() . path_to_theme() ?>/js/jquery-1.4a2.min.js" type="text/javascript"></script>

<script src="<?php print base_path() . path_to_theme() ?>/js/jquery.KinSlideshow-1.2.1.min.js" type="text/javascript"></script>

<script type="text/javascript">

$(function(){

$("#KinSlideshow").KinSlideshow({

moveStyle:"down",

intervalTime:8,

mouseEvent:"mouseover",

titleBar_height:40,

titleFont:{TitleFont_size:14,TitleFont_color:"#fdac11"}

});

})

</script>

<script type="text/javascript">

<!--

function MM_swapImgRestore() { //v3.0

  var i,x,a=document.MM_sr; for(i=0;a&&i<a.length&&(x=a[i])&&x.oSrc;i++) x.src=x.oSrc;

}

function MM_preloadImages() { //v3.0

  var d=document; if(d.images){ if(!d.MM_p) d.MM_p=new Array();

    var i,j=d.MM_p.length,a=MM_preloadImages.arguments; for(i=0; i<a.length; i++)

    if (a[i].indexOf("#")!=0){ d.MM_p[j]=new Image; d.MM_p[j++].src=a[i];}}

}

 

function MM_findObj(n, d) { //v4.01

  var p,i,x;  if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {

    d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}

  if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];

  for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document);

  if(!x && d.getElementById) x=d.getElementById(n); return x;

}

 

function MM_swapImage() { //v3.0

  var i,j=0,x,a=MM_swapImage.arguments; document.MM_sr=new Array; for(i=0;i<(a.length-2);i+=3)

   if ((x=MM_findObj(a[i]))!=null){document.MM_sr[j++]=x; if(!x.oSrc) x.oSrc=x.src; x.src=a[i+2];}

}

//-->

</script>

</head>

<body class="<?php print $classes; ?>" <?php print $attributes;?>>

  <div id="skip-link">

    <a href="#main-content" class="element-invisible element-focusable"><?php print t('Skip to main content'); ?></a>

  </div>

  <?php print $page_top; ?>

  <?php print $page; ?>

  <?php print $page_bottom; ?>

</body>

</html>

没有什么难的,代码直接复制过来了。清除缓存,刷新首页,幻灯的显示正常了。就这么简单。

2.png 

如果需要的话,我们还可以将幻灯的HTML片段放到Drupal区块里面,然后在page--front.tpl.php里面动态的输出这个静态区块。

这样整个首页就制作完成了。


Drupal版本:

3.5 节点占位符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们最后讲一点,创建一个内容类型“路由”(route),专门用来提供路径的,我以前经常把它叫做“节点占位符”。然后,创建一篇类型为“路由”(route)的节点,节点标题“首页占位符(请勿删除)”,正文里面不输入任何内容。创建后,我们看到这个节点的ID13

接着,我们导航到=admin/config/system/site-information,在这里,将站点首页设置为“node/13”。保存。

这样有什么好处呢? 当我们访问首页时,其实加载的是node/13这个节点,只不过我们没有显示这个节点的内容,我们显示的内容,都放到了page--front.tpl.php里面。如果我们不使用这种技术的话,首页加载的实际是node页面,这是节点的列表页面,加载的东西更多一些,性能损失更大一点。所以这样做的一个好处就是,稍微的提升性能。

第二个好处,首页我们可以使用page--front.tpl.php,如果一个站点有比较多的这样的动态复杂页面的话,我们又不想使用Panels,或者不会用,此时就可以使用这种办法,创建一个节点提供路经,然后覆写page--node--[nid].tpl.php,不输出内容区域,将对应的部分替换成想要的。

在实际的项目中,我经常采用这种技术,直到我后来熟悉了Panels模块。即便如此,这种办法,仍然实用。其中还有一个好处,就是简单易学,比较容易上手。

 

我们这里面介绍的很多办法,都不是特别标准,但是在实际的项目中,还经常用,能够解决实际问题的办法,就是好办法。我们的主题制作就介绍到这里,关于节点模板文件的覆写,可以参看Drupal实战一书中的主题制作部分,关于Panels的自定义布局,可以参看Drupal实战一书中制作首页一章。关于核心模板文件可用变量,可用模板建议,参看Think in Drupal4集的主题模板文件一章。

 

 

 



Drupal版本:

第一章 企业站常用模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在来做主题,做Drupal网站的时候,可以一开始就做主题,也可以配置一些工作以后再做主题。每个人的习惯是不一样的,我们还基于Zen来实现想要的功能。

Drupal版本:

1 主题制作

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们将zen目录下的STARTERKIT复制到sites\all\themes下面,然后将其重命名为yaiyuan;将sites\all\themes\yaiyuan下面的STARTERKIT.info.txt重命名为yaiyuan.info,注意文件后缀名,对里面的信息做以下修改:

name        = Yaiyuan

description = Yaiyuan theme.

打开template.php文件,将里面的STARTERKIT替换为yaiyuan;打开theme-settings. php文件,将里面的STARTERKIT替换为yaiyuan。这样我们的这个子主题就初步制作完成了。

导航到主题的管理界面,admin/appearance,将我们新建的这个主题启用并设置为默认主题。现在访问首页,看到样式还是乱的。


Drupal版本:

第一章Drupal7主题制作

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

今天是201343日,我们的think in Drupal5集正式开工了,从第4集到现在已经过去了3个月的时间了,中间事情很多,让我们耽误了一段时间,不过还好,我们还会继续下去的。

我们在第5集里面,主要讲解Drupal的主题制作,以及多语言网站建设。如果篇幅允许的话,可能再加点别的。开始我是这样计划的,做一个公司的网站,从设计,到转成HTML,再转成Drupal主题,然后再做出来网站,最后再做出来中英文版的。我们这里的多语言,就是指的中英文网站。

不过我找的美工比较忙,一直没有帮我制作HTML,不过我们的教程不会因此而停止的,还有,我朋友公司的静态HTML愿意提供给我作为素材,在此,我们对lonlife.net提供的素材表示感谢。制作Drupal主题的原理是一样的,我的意思是说,每个主题的外观都是不一样的,但是从静态HTMLDrupal主题的转换过程,大致是一样的。

我们这一集讲两个例子,一个就是从静态HTML,转为Drupal主题;另一个就是基于Omega,制作自己的子主题。从而也让大家看到两种制作Drupal主题的方式。


Drupal版本:

1 静态HTML分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们先来看一下,第一个例子,从静态HTML,转为Drupal主题。首先来看做好的静态HTML,这是朋友网站的静态HTML

 1.png

   我们看到,这里面包含6个静态HTML页面。Drupal主题制作的正常流程是这样的:

需要经过设计、切图、转Drupal主题。现在前面两步已经完成了,我们接下来要做的是把静态HTML转为Drupal主题。

2.png


Drupal版本:

10 控制区块的HTML输出

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在回到首页,发现有了内容,不过这里的区块标题不是我们想要的:

1.png

我们在输入区块标题,区块描述的时候,两个地方都输入了。区块标题默认会显示出来的。有人可能会问,区块描述为什么是必填的?而区块标题则允许为空?

区块标题,是在页面上面显示出来的区块的标题,区块描述是在后台显示给管理员看的,一个是给普通用户看的,一个是给管理员看的。

2png.png 

在区块的配置页面,我们将区块标题置为空,就可以解决这个问题了。还有一个办法,就是在区块的模板文件里面,不输出区块标题变量。


Drupal版本:

10.10 hook_theme和theme_hook之间的区别

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们这里顺带讲解一下hook_themetheme_hook之间的区别。在Drupal7下,所有的主题函数,又被称为主题钩子函数,比如

theme_breadcrumb

theme_links

theme_menu_tree

这些函数,属于theme_hook的范畴,theme_后面跟的是具体的钩子。

hook_theme,我们举个例子,比如node_theme()block_theme,这是它在节点、区块里面的具体实现,它是一个普通的钩子函数,和我们平时所用的hook_form_alter是一个概念,这个钩子函数是用来注册主题函数和模板文件的。在Drupal中使用的主题函数和模板文件都需要在这里注册一下,不然的话Drupal无法识别这些主题函数或者模板文件。


Drupal版本:

10.11 主题函数建议

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们向template.php文件中添加以下代码:

function snt_menu_tree__main_menu($variables) {

  return '<ul>' . $variables['tree'] . '</ul>';

}

注意我们这里的函数名字,我们是可以使用snt_menu_tree,不过这将作用于所有的菜单,我们只想作用于主菜单,所以在后面加了__main_menu,这是主题函数建议,和模板建议是一个概念。

对于模板建议,我们知道,模板建议规则,是在模板文件的预处理函数中,通过'theme_hook_suggestions'定义的。很多人可能会问,主题函数的建议,是在什么地方定义的?在Drupal6的时候,主题函数是没有建议这么一说的,只有模板建议,在Drupal7下面改进了一下,增加了主题函数的建议,不是每个主题函数都支持建议的,需要具体情况具体分析,这取决于不断地实践。对于theme_menu_tree,我们在menu_tree_output函数里面,代码的最后部分:

    // Add the theme wrapper for outer markup.

    // Allow menu-specific theme overrides.

    $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');

theme_menu_tree的建议规则,就是在这里定义的,所以我们可以使用snt_menu_tree__main_menu这个函数,它只作用于主菜单。

现在清除缓存,访问首页,通过Firebug查看源代码:

1.png 

ul标签上面的class属性已经被我们删除了,不过主导航的样式,还没有调整过来。我们现在覆写theme_menu_link,将这个函数从menu.inc中,复制到template.php中,我们来看一下默认的样子:

function theme_menu_link(array $variables) {

  $element = $variables['element'];

  $sub_menu = '';

 

  if ($element['#below']) {

    $sub_menu = drupal_render($element['#below']);

  }

  $output = l($element['#title'], $element['#href'], $element['#localized_options']);

  return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";

}

我们对它做相应的调整,这是调整后的代码:

function snt_menu_link__main_menu(array $variables) {

  $element = $variables['element'];

  $sub_menu = '';

 

  if ($element['#below']) {

    $sub_menu = drupal_render($element['#below']);

  }

  $output = l('<span>'.$element['#title'].'</span>', $element['#href'], array('html' => TRUE,));

  return '<li>' . $output . $sub_menu . "</li>\n";

}

这里面有两处修改,首先是将函数名字改为了snt_menu_link__main_menu,这里面的__main_menu,和前面我们用的,一样,都是主题函数建议;其次,就是将li里面的属性输出,给删除了。

可能还会有人问,我是怎么知道这个主题函数的建议规则的?在menu_tree_output函数里面,中间部分,有这样两行代码:

    // Allow menu-specific theme overrides.

$element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');

这里定义了theme_menu_link的建议规则。

 

现在清除缓存,查看源代码,和目标输出已经完全一致了:

2png.png 

当然,此时的样式,也正常了:

3.png 

对于下面的友情链接,我们也可以将它改造为菜单的形式,覆写的办法和这里一样,我们这里采用静态区块的形式就可以了。


Drupal版本:

10.1通过代码去除区块标题

我们将modules\block下面的block.tpl.php复制到sites\all\themes\snt\templates目录下面,同时我们将代码里面区块标题的输出给注释掉,这是调整后的代码:

<div id="<?php print $block_html_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>>

 

  <?php print render($title_prefix); ?>

<?php if ($block->subject): ?>

  <h2<?php print $title_attributes; ?>><?php //print $block->subject ?></h2>

<?php endif;?>

  <?php print render($title_suffix); ?>

 

  <div class="content"<?php print $content_attributes; ?>>

    <?php print $content ?>

  </div>

</div>

清除缓存数据,现在所有的区块标题,都不见了。


Drupal版本:

10.2 图片URL解决方案

我们现在继续,我们看到图片的显示不正常,如图所示:

1.png 

这是因为图片的路径不对,这是我们使用的html片段:

<img src="images/br.gif" width="253" height="8" />

<img src="images/snt.gif" width="217" height="26" />

<img src="images/xiazai.gif" width="222" height="76" />

我们现在需要将它们调整为这样的形式:

<img src="http://localhost/snt2/sites/all/themes/snt/images/br.gif" width="253" height="8" />

<img src="http://localhost/snt2/sites/all/themes/snt/images/snt.gif" width="217" height="26" />

<img src="http://localhost/snt2/sites/all/themes/snt/images/xiazai.gif" width="222" height="76" />

这是可以工作的,但是,如果我们这样将路径写死在里面的话,将来如果我们更换了域名,此时需要重新的调整这些地址,如果要调整的地方很多,也是非常费力的。

我们可以采用这样的方案,启用核心自带的PHP过滤器模块,在banner-right区块里面,将对应的代码修改为:

<img src="<?php print base_path() . path_to_theme(); ?>/images/br.gif" width="253" height="8" />

<img src="<?php print base_path() . path_to_theme(); ?>/images/snt.gif" width="217" height="26" />

<img src="<?php print base_path() . path_to_theme(); ?>/images/xiazai.gif" width="222" height="76" />

然后将区块正文的文本格式设置为PHP Code,保存。图片的显示就正常了:

2png.png 

注意这里面,这段代码的使用,<?php print base_path() . path_to_theme(); ?>,这是在主题制作、Drupal开发中经常使用的代码。

对于页脚的snt-left区块,我们也做类似的调整:

<img src="<?php print base_path() . path_to_theme(); ?>/images/snt2.gif" width="66" height="25" />




Drupal版本:

10.3 控制菜单区块的输出

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在的主导航区块,还是静态的,如图所示:

1.png 

我们想使用Drupal的主菜单来管理这里的内容,这样更方便一点。


Drupal版本:

10.4 创建主菜单

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们导航到admin/structure/menu,在这里找到主菜单,进入它的菜单链接列表页面admin/structure/menu/manage/main-menu

1.png 

我们对默认的Home菜单链接进行编辑,将“Home”修改为“首页”,保存。然后依次添加菜单链接:下载、充值、我的账户、业务支持、新手指引。我们知道,在创建菜单链接的时候,必须提供一个有效的路径,所以我们为这些菜单链接默认使用路径“node/1”,这里面只有一个例外,就是“我的账户”,我们为它使用了路径“user”。开始的时候,我们可以随便的指定一个路径,在项目的后期,当我们有了真实可用的路径以后,我们再将它修改过来。通过拖拽,我们调整一下菜单链接之间的相对位置,这是调整后的样子。

2png.png


Drupal版本:

10.5 放置主菜单区块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在导航到区块管理界面admin/structure/block,找到主菜单区块,我们将它放到Navigation区域,然后将原来的nav区块的区域设置为None,这样我们就使用了主菜单做我们的导航了。调整完了以后,记得点击下面的保存区块按钮。

1.png 

我们现在访问首页,来观察一下这个导航样式的变化,这是我在Firefox下面看到的效果:

2png.png 

我们看到样式变了,我们可以通过调整CSS,调整成原来的样子,我们也可以覆写HTML输出,让DrupalHTML输出和美工切图后的HTML保持一致,这里我们采用后者。

3.png 

这是当前的HTML,我们看到,与美工所给的HTML相比,Drupal输出了太多我们不想要的HTML markup。我们现在逐个的去掉这些多余的输出。

我们在firefox下面打开美工所给的静态页面,通过Firebug查看对应部分的源代码:

4.png


Drupal版本:

10.6 覆写region.tpl.php

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们的目标就是覆写Drupal的输出,使其与美工所给的输出保持一致。我们首先来看最外面的<div class="region region-nav">,这个div是在region.tpl.php里面输出的。我们首先找到默认的region.tpl.php文件,它位于modules\system目录下面,我们将它复制到sites\all\themes\snt\templates目录下面。我们来看一下这个模板文件:

<?php if ($content): ?>

  <div class="<?php print $classes; ?>">

    <?php print $content; ?>

  </div>

<?php endif; ?>

我们这里只需要修改这个模板文件里面的内容,将div标签给删除即可,如果这样做的话,所有区域的div标签都会被删除,我们这里不这样做,首先完整的保留这个模板文件,然后我们新建一个模板建议文件,专门用于这里的导航区域。

我们在sites\all\themes\snt\templates下面新建一个文件夹override,里面用来放置模板建议文件,然后在这个文件夹下面,再创建一个子文件夹region,用来放置region.tpl.php的模板建议。

首先,这里除了templates的文件夹名字是固定的以外,里面子文件夹的名字,可以随便起,Drupal会自动的遍历templates里面的所有文件、文件夹、子文件夹。当然,我们需要起一个含义明确的名字,如果我们把所有的模板文件、模板建议文件,都直接放到sites\all\themes\snt\templates下面,这也是可以的;不过我们在实际的项目中,需要覆写的模板文件比较多,为了管理的方便,我们将模板建议文件统一放在了override下面;在override下面,又新建若干个文件夹,分别放置对应模板文件的覆写。

我们通过Google搜索一下预处理函数template_preprocess_region,我们可以在region.tpl.php模板文件中的注释里面找到这个函数名字。通常在搜索结果的第一条,就指向了api.drupal.org上面的对应代码了,我们点击对应的链接。

2png.png 

来看一下这个函数:

function template_preprocess_region(&$variables) {

  // Create the $content variable that templates expect.

  $variables['content'] = $variables['elements']['#children'];

  $variables['region'] = $variables['elements']['#region'];

 

  $variables['classes_array'][] = drupal_region_class($variables['region']);

  $variables['theme_hook_suggestions'][] = 'region__' . $variables['region'];

}

注意代码里面的最后一行,表示它支持region--[region_name].tpl.php形式的模板建议。代码里面使用的是下划线,文件名字里面使用的是连字符。我们这里区域的名字,就是nav,这是在主题的snt.info文件中定义的。所以我们只需要创建region--nav.tpl.php就可以了。对于熟悉Drupal6主题开发的读者,这里需要注意,这里面是两个连字符--,不是一个,这是Drupal7Drupal6之间的一个区别。

我们将region.tpl.php复制到sites\all\themes\snt\templates\override\region,然后将它重命名为region--nav.tpl.php,最后打开region--nav.tpl.php,删除里面的div代码,这是删除后的样子:

<?php if ($content): ?>

    <?php print $content; ?>

<?php endif; ?>

其实这里面的if语句,在我们这里也没有什么特别的用出了,因此把if语句,也删除掉,这是删除后的代码:

<?php print $content; ?>

现在清除缓存,在浏览器中打开新标签页,访问http://localhost/snt2/,使用firebug查看页面源代码。我们看到,我们去掉region相关的<div>了。

1.png 

但是此时,样式还没有任何变化。我们继续前进,我们把区块里面的多余HTML标签给删除掉。


Drupal版本:

10.7 覆写block.tpl.php

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在sites\all\themes\snt\templates\override下面创建一个文件夹block,将sites\all\themes\snt\templates里面的block.tpl.php复制过来。我们打开block.tpl.php,在注释里面找到函数template_preprocess_block,通过Google搜索这个函数,点击打开api.drupal.org上面的链接,找到该函数的代码,我们看这一部分:

1.png 

这里的英文注视,说的就是下划线、连字符两者的用法,我们在模块的代码里面,也就是这里的预处理函书中,使用下划线,在文件名字里面使用连字符。如果在代码的'theme_hook_suggestions'数组里面使用了连字符,它就不会正常工作。我们看到,block.tpl.php支持以下形式的模板建议:

    block--[region].tpl.php

    block--[module].tpl.php

block--[module]--[delta].tpl.php

注意,这里需要解释一下的是这里的deltadelta是区块在当前模块里面的唯一标识,它在当前模块里面是唯一的,但是不是在所有区块里面都是唯一的。所以模块名,加上delta,合在一起,就是区块的唯一ID

在我们这里,使用的区域为nav,模块名为systemdeltamain-menu。我们导航到区块的管理界面,点击配置链接,进入区块的配置页面。此时的配置链接里面,就包含模块名和Delta信息:admin/structure/block/manage/system/main-menu/configure

因此我们可以使用:

block--nav.tpl.php

block--system--main-menu.tpl.php

我们这里采用后者,将sites\all\themes\snt\templates\override\block下面的block.tpl.php重命名为block--system--main-menu.tpl.php,然后修改模板文件里面的代码,这是修改后的:

<?php print $content ?>

是的,就是这样,除了区块内容以外,我们没有输出任何的东西。这是我在实际的项目中,经常采用的方法。但是这种方式也有一种局限性,就是说,区块的配置链接没有了,所以当你使用的时候,需要注意一下,到底要不要输出区块的配置链接。区块的配置链接,是通过变量$title_suffix输出的。

我们现在清除缓存,使用Firebug查看源代码,现在干净了很多:

2png.png 

不过样式还没有没有变化:

3.png 

与美工所给的输出相比的,我们的ulli上面多了class属性,我们来看ulDrupal默认的输出为<ul class="menu">,为了和美工所给的HTML保持一致,我们需要将它覆写为<ul>。在哪个模板文件里面覆写这里的输出呢?实际上这里的HTML是由Drupal的菜单系统控制,而且是由主题函数控制的。


Drupal版本:

10.8 覆写主题函数

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

打开Drupal核心的includes文件夹,在里面找到menu.inc文件,打开这个文件,在这里面可以找到函数theme_menu_treetheme_menu_link,这两个主题函数用来控制菜单和菜单链接的HTML输出。我们需要对这两个函数进行覆写。

首先来看theme_menu_tree,代码如下:

function theme_menu_tree($variables) {

  return '<ul class="menu">' . $variables['tree'] . '</ul>';

}

这里的class="menu",就是我们想要去除的。


Drupal版本:

10.9 template.php文件的作用

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们回到sites\all\themes\snt文件夹下,在这里面创建文件template.php,这里的template.php,就相当于模块的.module文件,它对于Drupal的主题是非常重要的。我们在template.php文件中,可以做很多事情,比如:

1) 自定义函数,可以放到这里,然后在模板文件中调用,这样模板文件中,就不用放置太多的PHP逻辑代码。

2) 实现预处理、处理函数,为模板文件添加变量或者修改变量。

3) 实现钩子函数,比如hook_form_alterhook_theme,在这一点上完全等同于.module文件,这是Drupal7Drupal6的一个改进。

4) 实现主题函数,在当前主题下覆写默认的输出。

我们这里,当前用到的是第4条,实现主题函数的覆写。对于创建template.php文件,我仍然是喜欢复制一个已有的文件,然后修改。我这里是从网上书店里面复制过来的,里面包含了很多注释掉的代码。


Drupal版本:

11 区块的可见性设置

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在来看右上角的区块top right,在登录状态下,显示的内容仍然是:

1.png 

我们配置这个静态区块,首先让这里的注册、登陆两个链接可以工作,我们修改区块里面的相应内容,这是修改后的样子:

<a href="<?php print base_path(); ?>user/register">注册</a></span>    <span class="zd"><a href="<?php print base_path(); ?>user/login">登陆</a>

   我们这里只使用了<?php print base_path(); ?>,后面紧跟着内部路径,两者之间没有使用“/”。

然后,我们把文本格式修改为PHP code

在区块的可见性设置里面,点击“Roles”(角色)标签,选中匿名用户。

2png.png 

     然后将区块描述修改为:

3.png 

保存区块。

我们新建一个区块,将区块描述设置为“top-right  authenticated user”,区块的正文设置为:

<p><span class="weibo"><a href="#">官方微博</a></span> | <span class="weibo"><a href="#">帮助中心</a></span>     <span class="zd"> 欢迎 <?php global $user; print l($user->name, 'user') ?></span></p>

<p class="kefu">客服电话:400-680-6666</p>

这里,除了注册、登陆所在的地方被替换掉了以外,其它地方和匿名用户的区块保持一致。我们这里只看变动的地方:

<span class="zd"> 欢迎 <?php global $user; print l($user->name, 'user') ?></span>

对于路径,我们使用'user'即可,不需要使用'user/' . $user->uid,两者的效果是一样的。

文本格式我们继续使用PHP code

对于区域设置,我们将它指定到同一个区域“top right”。

对于可见性设置,我们只让它显示给登录用户:

4.png 

这是登录用户看到的区块:

5.png 

和匿名用户看到的,已经有了变化。我们这里充分利用Drupal核心区块已有的可见性设置,很方便的解决了匿名用户、登录用户看到不同内容,这个常见问题。我们也可以创建一个区块,在一个区块里面,使用PHP代码搞定这样的问题,如果这样的话,也是可行的,不过PHP代码稍微复杂一点。借助于Drupal核心功能,轻松解决这个问题,何乐而不为呢?



Drupal版本:

2 静态页面效果预览

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们拿到HTML以后,首先需要看一下,静态页面的样子,这里有6个页面,我们来看一下。首先是首页index.html页面。


Drupal版本:

2.1 index.html页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

1.png

2.png 

这就是首页,我这里截了上下两个图,QQ的抓图,只能抓当前屏幕的,而页面比较长,所以只能分成两部分截图。

我们看到首页其实是比较复杂的,我们大致可以将其分为三部分,上中下三部分。导航条上面的是一部分,友情链接下面的是一部分,中间是一部分。虽然布局稍微复杂一点,不过还是很有中国特色的,很多中国的网站都是这样的风格。


Drupal版本:

2.2 newhand.html页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来,我们使用浏览器打开下一个页面,newhand.html。我们来看一下这个页面的样子:

1.png

2.png 

在这里我们看到,这个页面和首页相比,头部和尾部都是相同的,对于中间的部分,右边栏也是相同的。我在看这些界面的时候,在查看的过程中,大致的确定一下Drupal里面的区域。我假定你对Drupal的主题的基础知识已经有了解。

对于头部,我们大致确定三个区域,分别为:

3.png

4.png 

还有:

5.png 

而对于页脚,我们大致可以确定出来两个区域,分别为:

 

6.png 

注意,我们这里只是大致的确定。就是从界面上面确定,我们后面还需要进一步的根据HTML代码,更准确的确定区域。


Drupal版本:

2.3 recharge.html页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在来看recharge.html

1.png 

再往下的内容,和前一个页面一致。只有中间的内容有变化,而中间部分又可以分为左右两部分,右边是一个边栏,里面的内容不变,只有左边的内容变。所以我们可以把右边部分确定成为一个Drupal区域,左边部分采用内容区域,这是一种方案。还有一种方案,把中间的这一大部分,处理成内容区域,左右两部分处理成Panels的布局。大家都知道,作者是比较喜欢Panels 这种方式,不过在这里面,我们首先讲解第一种方式。

第一种方式,就是完全基于Drupal核心的主题机制,这种方式的学习成本低,易于学习;Panels的方式,有好的一方面,但是也有坏的一方面,坏的一方面,就是你需要学习Panels模块,学习成本上来了,就是比第一种方式学习成本高。我建议刚开始接触Drupal,使用Drupal做网站的朋友,先采用简单的方式。先学会怎么用了以后,在发现了Drupal核心主题机制的种种限制以后,再去尝试Panels。这样可以降低项目的风险。


Drupal版本:

2.4 register.html页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在我们打开register.html,头部和尾部和前面的一样,只有中间部分不同:

1.png


Drupal版本:

2.5 support.html页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

打开support.html,头部和尾部还是一样,只有中间部分不同:

1.png


Drupal版本:

2.6 login.html页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

还有login.html页面,中间部分:

1.png 

到现在为止,我们对于美工所给的HTML已经有了一个初步的印象。接下来,我们需要去阅读所给的这些HTML代码。是的,我们需要每个HTML页面都读一遍,分析里面结构。从里面找出相同的部分。


Drupal版本:

3 静态页面源码分析

Drupal版本:

3.1 index.html的代码

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是index.html的代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>SNT</title>

<link href="css/css.css" rel="stylesheet" type="text/css" />

<script src="js/jquery-1.4a2.min.js" type="text/javascript"></script>

<script src="js/jquery.KinSlideshow-1.2.1.min.js" type="text/javascript"></script>

<script type="text/javascript">

$(function(){

$("#KinSlideshow").KinSlideshow({

moveStyle:"down",

intervalTime:8,

mouseEvent:"mouseover",

titleBar_height:40,

titleFont:{TitleFont_size:14,TitleFont_color:"#fdac11"}

});

})

</script>

<script type="text/javascript">

<!--

function MM_swapImgRestore() { //v3.0

  var i,x,a=document.MM_sr; for(i=0;a&&i<a.length&&(x=a[i])&&x.oSrc;i++) x.src=x.oSrc;

}

function MM_preloadImages() { //v3.0

  var d=document; if(d.images){ if(!d.MM_p) d.MM_p=new Array();

    var i,j=d.MM_p.length,a=MM_preloadImages.arguments; for(i=0; i<a.length; i++)

    if (a[i].indexOf("#")!=0){ d.MM_p[j]=new Image; d.MM_p[j++].src=a[i];}}

}

 

function MM_findObj(n, d) { //v4.01

  var p,i,x;  if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {

    d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}

  if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];

  for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document);

  if(!x && d.getElementById) x=d.getElementById(n); return x;

}

 

function MM_swapImage() { //v3.0

  var i,j=0,x,a=MM_swapImage.arguments; document.MM_sr=new Array; for(i=0;i<(a.length-2);i+=3)

   if ((x=MM_findObj(a[i]))!=null){document.MM_sr[j++]=x; if(!x.oSrc) x.oSrc=x.src; x.src=a[i+2];}

}

//-->

</script>

</head>

 

<body onload="MM_preloadImages('images/anniu.gif')">

  <div id="main">

      <div id="top">

          <div id="top-left">

             <div class="logo"><span>SNT</span>代理</div>

             <div class="logoright">

                <p>super network tunnel</p>

                <p>突破所有网络限制</p>

             </div>

          </div>

          <div id="top-right">

            <p><span class="weibo"><a href="#">官方微博</a></span> | <span class="weibo"><a href="#">帮助中心</a></span>     <span class="zd"><a href="#">注册</a></span>    <span class="zd"><a href="#">登陆</a></span></p>

            <p class="kefu">客服电话:400-680-6666</p>

          </div>

      </div>

      <div id="nav">

          <div id="nav-left"></div>

          <div id="nav-middle">

             <ul>

                <li><a href="#">首页</a></li>         

                <li><a href="#">下载</a></li>       

                <li><a href="#">充值</a></li>        

                <li><a href="#">我的账户</a></li>       

                <li><a href="#">业务支持</a></li>        

                <li><a href="#">新手指引</a></li>

             </ul>

          </div>

          <div id="nav-right"></div>

      </div>

      <div id="banner">

         <div id="banner-left">

 <div id="KinSlideshow" style="visibility:hidden;">

        <a href="1" target="_blank"><img src="images/banner1.gif"  width="701" height="260" /></a>

        <a href="2" target="_blank"><img src="images/banner2.gif"  width="701" height="260" /></a>

        <a href="3" target="_blank"><img src="images/banner3.gif" width="701" height="260" /></a>

    </div>

</div>

         <div id="banner-right">

            <p><img src="images/br.gif" width="253" height="8" /></p>

            <div class="br-middle">

             <p class="xiazai"><img src="images/snt.gif" width="217" height="26" /></p>

               <p class="banben">软件版本:2.36.116</p>

<p class="banben">软件大小:4.95M</p>

<p class="banben">适用平台:所有windows</p>

<p class="banben2">最近更新:2012-12-3</p><p><a href="#"><img src="images/xiazai.gif" width="222" height="76" /></a></p>

            </div>

            <div class="br-bottom"></div>

        </div>

      </div>

      <div id="jishu">

         <div id="jishu-left">

            <div class="tupo">

              <div class="tp-left">

                <h2 class="blue">突破所有网络限制</h2>

                 <p><img src="images/01.gif" width="201" height="80" /></p>

                <p>只要能开网页就能上QQ,玩游戏</p>

                <p> 穿透各种防火墙                  </p>

                <p>突破一切网络限制</p>

                <p> 让您尽情网上冲浪</p>

                <p class="more"><a href="1" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Image9','','images/anniu.gif',1)"><img src="images/anniu2.gif" name="Image9" width="133" height="28" border="0" id="Image9" /></a></p>

              </div>

              <div class="tp-left">

                <h2 class="green">数据加密确保安全</h2>

                 <p><img src="images/02.gif" width="201" height="80" /></p>

                <p>可选使用AES+RAS数据加密</p> 

<p>确保所有通信数据安全</p> 

<p>但速度会较慢</p>

<p>----------</p>

                <p class="more"><a href="2" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Image10','','images/anniu.gif',1)"><img src="images/anniu2.gif" name="Image10" width="133" height="28" border="0" id="Image10" /></a></p>

              </div>

              <div class="tp-left">

                <h2  class="org"> 一键登陆使用方便</h2>

                 <p><img src="images/03.gif" width="201" height="80" /></p>

                <p>使用方法:安装完成后</p>

                <p> 输入用户名密码后即可登陆</p>

                <p>登陆成功后,直接启动QQ或其它网游</p>

                <p> 即可突破网络限制 </p>

                <p class="more"><a href="3" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Image11','','images/anniu.gif',1)"><img src="images/anniu2.gif" name="Image11" width="133" height="28" border="0" id="Image11" /></a></p>

              </div>

            </div>

           <div class="zhichi">

             <h2>技术支持</h2>

               <ul>

                  <li>

                     <p><a href="#"><img src="images/001.gif" width="128" height="42" /></a></p>

                     <p><span><a href="#">诺基亚</a></span></p>

                     <p>提供最新的手机服务</p>

                  </li>

                    <li>

                     <p><a href="#"><img src="images/002.gif" width="128" height="42" /></a></p>

                     <p><span><a href="#">摩托罗拉</a></span></p>

                     <p>三大业务集团</p>

                  </li>

                    <li>

                     <p><a href="#"><img src="images/003.gif" width="128" height="42" /></a></p>

                     <p><span><a href="#">诺基亚</a></span></p>

                     <p>提供最新的手机服务</p>

                  </li>

                    <li>

                     <p><a href="#"><img src="images/002.gif" width="128" height="42" /></a></p>

                     <p><span><a href="#">诺基亚</a></span></p>

                     <p>提供最新的手机服务</p>

                  </li>

                    <li>

                     <p><a href="#"><img src="images/003.gif" width="128" height="42" /></a></p>

                     <p><span><a href="#">诺基亚</a></span></p>

                     <p>提供最新的手机服务</p>

                  </li>

                  

               </ul>

            </div>

         </div>

         <div id="jishu-right">

              <div class="news">

                 <h2>最新公告</h2>

                 <ul>

                    <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                     <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                      <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                       <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                         <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                 </ul>

              </div>

         <div class="news">

                 <h2>最新新闻</h2>

                 <ul>

                    <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                     <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                      <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                       <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                         <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                 </ul>

              </div>

         </div>

      </div>

</div>

<div id="footer">

   <div class="link">

      <div class="link-left">

         友情链接:

      </div>

      <div class="link-right">

        <p><a href="#">多玩暗黑3专区</a> |<a href="#"> 盛世三国</a> |<a href="#"> 西游3</a> | <a href="#">多玩魔兽世界专区</a> | <a href="#">仙妮蕾德</a> | <a href="#">07073动漫网</a> | <a href="#">宅人网下载</a> |<a href="#"> 免费网站申请</a> | <a href="#">魔兽世界中文网</a> |<a href="#"> 神仙道</a> |<a href="#"> 178新手卡 </a>| <a href="#">三界游戏网</a></p>

      </div>

   </div>

   <div class="ft"></div>

   <div class="snt">

      <div class="snt-left"><img src="images/snt2.gif" width="66" height="25" /></div>

      <div class="snt-right">

         <p><a href="#">返回首页</a> | <a href="#">关于我们</a> | <a href="#">隐私政策</a> <a href="#">|充值中心</a> | <a href="#">在线客服</a></p>

         <p>版权所有:lonlife 网游加速器 豫ICP11008357</p>

      </div>

   </div>

</div>

</body>

</html>


Drupal版本:

3.2 login.html源代码

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

比较长。我们来看login.html的代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>SNT</title>

<link href="css/css.css" rel="stylesheet" type="text/css" />

</head>

 

<body>

  <div id="main">

      <div id="top">

          <div id="top-left">

             <div class="logo"><span>SNT</span>代理</div>

             <div class="logoright">

                <p>super network tunnel</p>

                <p>突破所有网络限制</p>

             </div>

          </div>

          <div id="top-right">

            <p><span class="weibo"><a href="#">官方微博</a></span> | <span class="weibo"><a href="#">帮助中心</a></span>     <span class="zd"><a href="#">注册</a></span>    <span class="zd"><a href="#">登陆</a></span></p>

            <p class="kefu">客服电话:400-680-6666</p>

          </div>

      </div>

      <div id="nav">

          <div id="nav-left"></div>

          <div id="nav-middle">

             <ul>

                <li><a href="#">首页</a></li>         

                <li><a href="#">下载</a></li>       

                <li><a href="#">充值</a></li>        

                <li><a href="#">我的账户</a></li>       

                <li><a href="#">业务支持</a></li>        

                <li><a href="#">新手指引</a></li>

             </ul>

          </div>

          <div id="nav-right"></div>

      </div>

 

      <div id="ziye">

         <div class="ziye-left">

           <p class="daohang"><a href="#">首页</a> > <a href="#">业务支持</a></p>

           <p><img src="images/ziyebanner.gif" width="701" height="109" /></p>

           <div class="nei">

               <div class="login">

                     <table width="295" border="0" align="center">

  <tr>

    <td class="yhzc" colspan="3">用户登陆</td>

    </tr>

  <tr>

    <td width="60" class="mima">账号:</td>

    <td><label>

      <input class="wbk" type="text" name="textfield" id="textfield" />

    </label></td>

    <td class="hb"> </td>

  </tr>

  <tr>

    <td class="mima">密码:</td>

    <td><label>

      <input class="wbk" type="text" name="textfield2" id="textfield2" />

    </label></td>

    <td class="hb"> </td>

  </tr>

 

 

  <tr>

    <td class="mima"></td>

    <td><input name="" type="checkbox" value="" /><span class="zidong">两周内自动登录</span></td>

    <td class="hb"></td>

  </tr>

  <tr>

    <td class="xib" colspan="3"><input name="" type="submit" class="anniu2" value="" /></td>

    </tr>

</table>

 

             </div>

           </div>

         </div>

         <div class="ziye-right">

           <div id="banner-right">

              <p><img src="images/br.gif" width="253" height="8" /></p>

            <div class="br-middle">

               <p class="xiazai"><img src="images/snt.gif" width="217" height="26" /></p>

               <p class="banben">软件版本:2.36.116</p>

<p class="banben">软件大小:4.95M</p>

<p class="banben">适用平台:所有windows</p>

<p class="banben2">最近更新:2012-12-3</p><p><a href="#"><img src="images/xiazai.gif" width="222" height="76" /></a></p>

            </div>

            <div class="br-bottom"></div>

        </div>

        

        <div id="jishu-right2">

              <div class="news">

                 <h2>最新公告</h2>

                 <ul>

                    <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                     <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                      <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                       <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                         <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                 </ul>

              </div>

         <div class="news">

                 <h2>最新新闻</h2>

                 <ul>

                    <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                     <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                      <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                       <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                        <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                         <li><a href="#">SNT V2.7.3 20121106 正式版发布</a></li>

                 </ul>

              </div>

         </div>

         </div>

      </div>

</div>

<div id="footer">

   <div class="link">

      <div class="link-left">

         友情链接:

      </div>

      <div class="link-right">

        <p><a href="#">多玩暗黑3专区</a> |<a href="#"> 盛世三国</a> |<a href="#"> 西游3</a> | <a href="#">多玩魔兽世界专区</a> | <a href="#">仙妮蕾德</a> | <a href="#">07073动漫网</a> | <a href="#">宅人网下载</a> |<a href="#"> 免费网站申请</a> | <a href="#">魔兽世界中文网</a> |<a href="#"> 神仙道</a> |<a href="#"> 178新手卡 </a>| <a href="#">三界游戏网</a></p>

      </div>

   </div>

   <div class="ft"></div>

   <div class="snt">

      <div class="snt-left"><img src="images/snt2.gif" width="66" height="25" /></div>

      <div class="snt-right">

         <p><a href="#">返回首页</a> | <a href="#">关于我们</a> | <a href="#">隐私政策</a> <a href="#">|充值中心</a> | <a href="#">在线客服</a></p>

         <p>版权所有:lonlife 网游加速器 豫ICP11008357</p>

      </div>

   </div>

</div>

</body>

</html>

其它页面我就不粘贴代码了,它们的结构和login.html一致。我们接下来需要对这些页面的HTML代码进行分析,就是打开所有的页面。我这里使用的是nodepad++。你也可以使用类似的工具。


Drupal版本:

3.3 HTML布局结构分析


这是login.html的结构:

1.png 

这是index.html的结构:

2png.png 

我们看到,在首页的中间部分,采用的是bannerjishu两个div的上下结构,而不是像其它页面的ziye-leftziye-right的左右结构。希望读者读到这里的时候,认真的分析一下首页中间部分和其它页面的中间部分的不同之处。

我们这里是按照美工给什么,我们就输出什么的。但是我们会发现,美工在切图的时候,有的地方处理的有点小问题,它给我们程序员带来了更多的挑战。我们在这里只需要知道,首页并不是我们所想的左右结构,而是首先上下结构,然后左右结构。

我们这里先不去深究这一点,来分析一下topnavfooter的结构。它们里面的内容在所有页面都是相同的,在实际项目当中,通常也都是这样的。

我们先来分析top的结构:

3.png 

它里面包括top-lefttop-right两个div;展开top-left,里面还是有结构的:

4.png 

在这里面,我们看到,这个Logo,是一段文字,而不是一个图片,所以我们这里面,不需要使用Drupal页面模板里面的logo变量,我们只需要把它处理成为一个区块就可以了。我们这里面,在划分区域的时候,有三种方案:

1) 分一个区域:top

2) 分两个区域:top-lefttop-right

3) 分三个区域:logologorighttop-right

每个人的喜好不同,习惯也不一样。这三种方案,都是可行的,在这里面,我喜欢第三种方案。我喜欢多分几个区域,反正是固定不变的,区域分的越细,区块里面的HTML markup就越少。

我们来看nav的结构:

5.png 

我们看到里面分为nav-leftnav-middlenav-right,实际上这里的nav-leftnav-right里面是没有内容的,即便是把它们去掉,也不影响。所以我们这里只设置一个区域nav

我们来看ziye的结构:

6.png 

我们前期可以先把ziye-left处理成为content区域,也就是主内容区域。我在相当长的时间内,一直以为Drupal7里面,content区域是必须要有的,这在Drupal6下面是正确的,但是在Drupal7下,content区域已经不是必须的,我也是在前两天研究Drupal7主题的时候,才发现的。

我们看到ziye-right下面,分为banner-rightjishu-right2;而jishu-right2里面又包含两个news,其实我觉得,这里面的两个news,应该和banner-right并列才对,不知道美工在这里是怎么想的,我们总不能把banner-rightjishu-right2分成两个独立的区域吧。这里的HTML结构有点不合理的。

我们这里,把中间的部分,分成两个区域,contentziye-right。后面我们会继续细化、调整的。

现在来看footer,它的HTML结构:

7.png 

这里面,footer里面包含linkftsnt;其中link里面包含link-leftlink-rightft里面为空;snt里面又分为snt-leftsnt-right。区域的划分,也有多种方案:

1) 分一个区域:footer

2) 分两个区域:linksnt

3) 分三个区域:linksnt-leftsnt-right

4) 分四个区域:link-leftlink-rightsnt-leftsnt-right

这里面,我们采用第3种方案,也就是分三个区域的方案。这样我们普通页面的区域划分,就基本确定了下来,对于首页,我们暂时不去考虑它,因为我们后面会单独的为它制作页面模板。

实际上,对于这种静态HTML结构的分析,是必不可少的,尽管这里占了不小的篇幅。就是在转Drupal主题的前期,我们要充分的了解美工提供的静态HTML。我们现在继续。


Drupal版本:

4 创建自己的主题

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们新建一个站点,把这个站点命名为snt1吧,因为我本地已经有了一个snt,是的,这个例子我已经给很多人讲了不止一次了。不过snt1已经用了,所以我们这里就用snt2了。安装的过程在这里就不说了,此外,简体中文也不装了。我们只讲主题制作。

现在导航到snt2\sites\all\themes目录下面,创建一个子文件夹snt,然后将我们的静态页面复制过来。把静态页面复制过来,只是为了方便。如图所示。

1.png


Drupal版本:

5 创建主题的info文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来,我们创建snt.info文件,实际上,我从来都是复制重命名的。就是将一个现有的主题的info文件复制过来,然后重命名为snt.info。由于前面我们已经初步的确定了区域。我是从bookstore.info复制过来的。

这是我做好的样子:

screenshot  = screenshot.png

name        = SNT

description = SNT 演示主题.

core        = 7.x

 

stylesheets[all][] = css/css.css

stylesheets[all][] = css/custom.css

 

;scripts[] = js/script.js

 

regions[logo]         = Logo

regions[logoright]     = Logo right

regions[top_right]    = Top right

regions[nav]    = Navigation

regions[content]        = Content

regions[ziye_right]  = Ziye right

regions[link] = Link

regions[snt_left]         = Snt left

regions[snt_right]         = Snt right

 

; The page_top and page_bottom regions are hidden, which means they will not

; show up on the blocks administration page. But they are required in order for

; the html.tpl.php to work properly, so do not delete them.

 

regions[page_top]       = Page top

regions[page_bottom]    = Page bottom

前面的四个键值对,我就不多讲了。

 

stylesheets[all][]是用来指定CSS文件的,这里指定的CSS文件在当前主题的所有页面都会加载,我们这里只有一个CSS文件css/css.css,这是美工提供的CSS文件;在这里,我们在sites\all\themes\snt\css文件夹下面创建一个custom.css文件,这个文件里面放置我们程序员的CSS代码。这样的好处,就是说,程序员不需要去修改美工的CSS文件,我们在后面覆写主题模板文件的时候,很多时候,是需要调整CSS。将后期的CSS覆写代码,单独放在custom.css文件中,能够给我们带来管理上的方便。不然的话,程序员修改了CSS,美工又调整了自己的CSS,此时会遇到工作的合并问题。

 

scripts[]用来指定主题加载的JavaScript文件,我们这里前期不需要加载JS文件,只有在制作首页的时候,可能才会加载,所以我们保留了scripts[]这行代码,这里把它注释掉了,将来用的时候会比较方便。

 

再往下,就是我们设置的区域了,我们在前面分析静态HTML区域的时候,已经分析了要设置哪些区域,所以到这里就水到渠成了。

regions[logo]         = Logo

regions[logoright]     = Logo right

regions[top_right]    = Top right

regions[nav]    = Navigation

regions[content]        = Content

regions[ziye_right]  = Ziye right

regions[link] = Link

regions[snt_left]         = Snt left

regions[snt_right]         = Snt right

注意这里面,中括号里面,是没有加引号的;中括号里面的是机读名字,除了content以外,这里的机读名字,和静态HTML里面的ID或者Class是保持一致的;有一点需要说明一下,就是这里使用的是下划线,在静态HTML里面使用的是连字符;还有就是这里的机读名字,你想怎么起,就怎么起,只要符合PHP的变量规范就可以了,即便是content区域,也不是必须的,我们也可以将它替换掉,只不过我们这里沿用了传统的方式。右边是区域的用户可读名字,首字母这里大写了。

 

最后,两行代码:

regions[page_top]       = Page top

regions[page_bottom]    = Page bottom

这是必不可少的,隐藏的两个区域,在所有的Drupal主题里面都要定义,不过如果你不定义的话,问题可能也不大,自己可以尝试一下。有兴趣的读者,可以打开snt2\modules\system下面的html.tpl.php文件,里面有这么几行代码:

  <?php print $page_top; ?>

  <?php print $page; ?>

  <?php print $page_bottom; ?>

这里的$page_top$page_bottom两个变量,就是由前面我们定义的两个区域来负责的。

 

这样我们主题的info文件就创建好了,info文件里面还有很多其它可以使用的键值对,我们这里用不到,所以就不去讲解了。我们现在,在主题文件夹下面,创建一个templates文件夹,里面用来放置我们的模板文件。


Drupal版本:

6 默认的模板文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来,我们将modules\system下面的html.tpl.php文件,复制到templates文件夹下面,如果不复制过来,默认仍然会使用modules\system下面的html.tpl.php,复制过来有个好处,将来覆写这个模板文件的时候方便。


Drupal版本:

6.1 html.tpl.php

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看一下这个模板文件:

/**

 * @file

 * Default theme implementation to display the basic html structure of a single

 * Drupal page.

 *

 * Variables:

 * - $css: An array of CSS files for the current page.

 * - $language: (object) The language the site is being displayed in.

 *   $language->language contains its textual representation.

 *   $language->dir contains the language direction. It will either be 'ltr' or 'rtl'.

 * - $rdf_namespaces: All the RDF namespace prefixes used in the HTML document.

 * - $grddl_profile: A GRDDL profile allowing agents to extract the RDF data.

 * - $head_title: A modified version of the page title, for use in the TITLE

 *   tag.

 * - $head_title_array: (array) An associative array containing the string parts

 *   that were used to generate the $head_title variable, already prepared to be

 *   output as TITLE tag. The key/value pairs may contain one or more of the

 *   following, depending on conditions:

 *   - title: The title of the current page, if any.

 *   - name: The name of the site.

 *   - slogan: The slogan of the site, if any, and if there is no title.

 * - $head: Markup for the HEAD section (including meta tags, keyword tags, and

 *   so on).

 * - $styles: Style tags necessary to import all CSS files for the page.

 * - $scripts: Script tags necessary to load the JavaScript files and settings

 *   for the page.

 * - $page_top: Initial markup from any modules that have altered the

 *   page. This variable should always be output first, before all other dynamic

 *   content.

 * - $page: The rendered page content.

 * - $page_bottom: Final closing markup from any modules that have altered the

 *   page. This variable should always be output last, after all other dynamic

 *   content.

 * - $classes String of classes that can be used to style contextually through

 *   CSS.

 *

 * @see template_preprocess()

 * @see template_preprocess_html()

 * @see template_process()

 *

 * @ingroup themeable

 */

?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"

  "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language; ?>" version="XHTML+RDFa 1.0" dir="<?php print $language->dir; ?>"<?php print $rdf_namespaces; ?>>

 

<head profile="<?php print $grddl_profile; ?>">

  <?php print $head; ?>

  <title><?php print $head_title; ?></title>

  <?php print $styles; ?>

  <?php print $scripts; ?>

</head>

<body class="<?php print $classes; ?>" <?php print $attributes;?>>

  <div id="skip-link">

    <a href="#main-content" class="element-invisible element-focusable"><?php print t('Skip to main content'); ?></a>

  </div>

  <?php print $page_top; ?>

  <?php print $page; ?>

  <?php print $page_bottom; ?>

</body>

</html>

在这个模板文件的上面,是注释,核心自带的模板文件里面,都有注释的,注释里面给出了当前模板文件里面可用的变量;在注释的结尾处,给出了预处理函数、处理函数,我们这里使用的变量都是在这些预处理函数、处理函数里面定义的。

我们看到,html.tpl.php模板文件里面,主要负责head里面变量的输出,里面包含$head$head_title$styles$scripts,后面的两个变量,分别负责样式、脚本的输出,我们在info文件里面定义的stylesheetsscripts就是通过这两个变量输出的。而在body里面,主要是输出$page,其它都是起辅助作用的。

Drupal6里面,这些变量内容,其实是放在page.tpl.php里面,由于每个页面的这部分内容,基本上是比较固定的,所在Drupal7里面,就将其独立成一个单独的文件了,这样page.tpl.php里面的内容,就少了很多。

template_preprocesstemplate_process里面定义的变量,在所有的模板文件里面都可以使用。在里面主要定义了下面四个变量:

$variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even';

  $variables['id'] = $count[$hook]++;

 

  // Tell all templates where they are located.

  $variables['directory'] = path_to_theme();

 

  // Initialize html class attribute for the current hook.

  $variables['classes_array'] = array(drupal_html_class($hook));

此外,还有一些默认变量是通过_template_preprocess_default_variables定义,这些变量大致包括:

$variables = array(

    'attributes_array' => array(),

    'title_attributes_array' => array(),

    'content_attributes_array' => array(),

    'title_prefix' => array(),

    'title_suffix' => array(),

    'user' => $user,

    'db_is_active' => !defined('MAINTENANCE_MODE'),

    'is_admin' => FALSE,

    'logged_in' => FALSE,

  );

 

$variables['is_front'] = drupal_is_front_page();

template_process里面,也可以定义变量,不过这些变量,都是直接来源于预处理函书中的,比如:

$variables['classes'] = implode(' ', $variables['classes_array']);

处理函数中的classes变量,就是直接根源于预处理函数中的classes_array,只不过将它从数组的形式转成了字符串。

 

template_preprocess_html定义了一些变量:

$variables['classes_array'][] = $variables['is_front'] ? 'front' : 'not-front';

$variables['rdf_namespaces'] = drupal_get_rdf_namespaces();

$variables['grddl_profile'] = 'http://www.w3.org/1999/xhtml/vocab';

$variables['language'] = $GLOBALS['language'];

$variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';

$variables['head_title_array'] = $head_title;

$variables['head_title'] = implode(' | ', $head_title);

$variables['theme_hook_suggestions'] = $suggestions;

 

我们注意到,好几个变量都没有在这里定义,此时我们可以尝试Google一下template_process_html,在api.drupal.org找到这个函数的定义,里面就有我们在模板里面用到的变量:

  // Render page_top and page_bottom into top level variables.

  $variables['page_top'] = drupal_render($variables['page']['page_top']);

  $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']);

  // Place the rendered HTML for the page body into a top level variable.

  $variables['page'] = $variables['page']['#children'];

  $variables['page_bottom'] .= drupal_get_js('footer');

 

  $variables['head'] = drupal_get_html_head();

  $variables['css'] = drupal_add_css();

  $variables['styles'] = drupal_get_css();

  $variables['scripts'] = drupal_get_js();

我们在这里,只需要知道变量是在预处理函数、处理函数里面定义的即可,用到的时候再去查找相应的函数。还有一个问题,Drupal是怎么把模板文件和对应的变量合并成HTML片段的,经常有人问这个问题,这时通过theme_render_template函数完成的,我们在第4集的时候,简单的介绍过这个函数。

我们在html.tpl.php里面使用这么两句话输出CSSJS文件:

  <?php print $styles; ?>

  <?php print $scripts; ?>

在实际的项目中,我们还经常这样用:

  <?php print $styles; ?>

  <link href="<?php print base_path().path_to_theme(); ?>/css/mycss.css" rel="stylesheet" type="text/css" />

  <?php print $scripts; ?>

  <script src="<?php print base_path().path_to_theme(); ?>/js/jquery-1.4a2.min.js" type="text/javascript"></script>

我们直接在模板文件里面输出了CSSJS文件,而不是通过主题的info文件,这不是什么Drupal高手推荐的方式,但是却是在实际项目当中,用的最多的一种方式。另外,就是这里的base_path().path_to_theme(),这两个函数,我们在后面还会用到。我们这里也可以使用$base_path.$directory,效果是一样的,不过我更喜欢直接使用函数的形式。注意,变量的形式,只能用于模板文件中,而函数的形式,适用的范围更广泛一些。



Drupal版本:

6.2 page.tpl.php

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来,我们将modules\system下面的page.tpl.php文件,复制到sites\all\themes\snt\templates文件夹下面。我建议每个学习Drupal的用户,都把这个默认模板文件看上几遍。

<?php

 

/**

 * @file

 * Default theme implementation to display a single Drupal page.

 *

 * The doctype, html, head and body tags are not in this template. Instead they

 * can be found in the html.tpl.php template in this directory.

 *

 * Available variables:

 *

 * General utility variables:

 * - $base_path: The base URL path of the Drupal installation. At the very

 *   least, this will always default to /.

 * - $directory: The directory the template is located in, e.g. modules/system

 *   or themes/bartik.

 * - $is_front: TRUE if the current page is the front page.

 * - $logged_in: TRUE if the user is registered and signed in.

 * - $is_admin: TRUE if the user has permission to access administration pages.

 *

 * Site identity:

 * - $front_page: The URL of the front page. Use this instead of $base_path,

 *   when linking to the front page. This includes the language domain or

 *   prefix.

 * - $logo: The path to the logo image, as defined in theme configuration.

 * - $site_name: The name of the site, empty when display has been disabled

 *   in theme settings.

 * - $site_slogan: The slogan of the site, empty when display has been disabled

 *   in theme settings.

 *

 * Navigation:

 * - $main_menu (array): An array containing the Main menu links for the

 *   site, if they have been configured.

 * - $secondary_menu (array): An array containing the Secondary menu links for

 *   the site, if they have been configured.

 * - $breadcrumb: The breadcrumb trail for the current page.

 *

 * Page content (in order of occurrence in the default page.tpl.php):

 * - $title_prefix (array): An array containing additional output populated by

 *   modules, intended to be displayed in front of the main title tag that

 *   appears in the template.

 * - $title: The page title, for use in the actual HTML content.

 * - $title_suffix (array): An array containing additional output populated by

 *   modules, intended to be displayed after the main title tag that appears in

 *   the template.

 * - $messages: HTML for status and error messages. Should be displayed

 *   prominently.

 * - $tabs (array): Tabs linking to any sub-pages beneath the current page

 *   (e.g., the view and edit tabs when displaying a node).

 * - $action_links (array): Actions local to the page, such as 'Add menu' on the

 *   menu administration interface.

 * - $feed_icons: A string of all feed icons for the current page.

 * - $node: The node object, if there is an automatically-loaded node

 *   associated with the page, and the node ID is the second argument

 *   in the page's path (e.g. node/12345 and node/12345/revisions, but not

 *   comment/reply/12345).

 *

 * Regions:

 * - $page['help']: Dynamic help text, mostly for admin pages.

 * - $page['highlighted']: Items for the highlighted content region.

 * - $page['content']: The main content of the current page.

 * - $page['sidebar_first']: Items for the first sidebar.

 * - $page['sidebar_second']: Items for the second sidebar.

 * - $page['header']: Items for the header region.

 * - $page['footer']: Items for the footer region.

 *

 * @see template_preprocess()

 * @see template_preprocess_page()

 * @see template_process()

 * @see html.tpl.php

 *

 * @ingroup themeable

 */

?>

 

  <div id="page-wrapper"><div id="page">

 

    <div id="header"><div class="section clearfix">

 

      <?php if ($logo): ?>

        <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo">

          <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" />

        </a>

      <?php endif; ?>

 

      <?php if ($site_name || $site_slogan): ?>

        <div id="name-and-slogan">

          <?php if ($site_name): ?>

            <?php if ($title): ?>

              <div id="site-name"><strong>

                <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>

              </strong></div>

            <?php else: /* Use h1 when the content title is empty */ ?>

              <h1 id="site-name">

                <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>

              </h1>

            <?php endif; ?>

          <?php endif; ?>

 

          <?php if ($site_slogan): ?>

            <div id="site-slogan"><?php print $site_slogan; ?></div>

          <?php endif; ?>

        </div> <!-- /#name-and-slogan -->

      <?php endif; ?>

 

      <?php print render($page['header']); ?>

 

    </div></div> <!-- /.section, /#header -->

 

    <?php if ($main_menu || $secondary_menu): ?>

      <div id="navigation"><div class="section">

        <?php print theme('links__system_main_menu', array('links' => $main_menu, 'attributes' => array('id' => 'main-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Main menu'))); ?>

        <?php print theme('links__system_secondary_menu', array('links' => $secondary_menu, 'attributes' => array('id' => 'secondary-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Secondary menu'))); ?>

      </div></div> <!-- /.section, /#navigation -->

    <?php endif; ?>

 

    <?php if ($breadcrumb): ?>

      <div id="breadcrumb"><?php print $breadcrumb; ?></div>

    <?php endif; ?>

 

    <?php print $messages; ?>

 

    <div id="main-wrapper"><div id="main" class="clearfix">

 

      <div id="content" class="column"><div class="section">

        <?php if ($page['highlighted']): ?><div id="highlighted"><?php print render($page['highlighted']); ?></div><?php endif; ?>

        <a id="main-content"></a>

        <?php print render($title_prefix); ?>

        <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>

        <?php print render($title_suffix); ?>

        <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>

        <?php print render($page['help']); ?>

        <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>

        <?php print render($page['content']); ?>

        <?php print $feed_icons; ?>

      </div></div> <!-- /.section, /#content -->

 

      <?php if ($page['sidebar_first']): ?>

        <div id="sidebar-first" class="column sidebar"><div class="section">

          <?php print render($page['sidebar_first']); ?>

        </div></div> <!-- /.section, /#sidebar-first -->

      <?php endif; ?>

 

      <?php if ($page['sidebar_second']): ?>

        <div id="sidebar-second" class="column sidebar"><div class="section">

          <?php print render($page['sidebar_second']); ?>

        </div></div> <!-- /.section, /#sidebar-second -->

      <?php endif; ?>

 

    </div></div> <!-- /#main, /#main-wrapper -->

 

    <div id="footer"><div class="section">

      <?php print render($page['footer']); ?>

    </div></div> <!-- /.section, /#footer -->

 

  </div></div> <!-- /#page, /#page-wrapper -->

 

这仍然是HTML里面嵌套了对应的变量,这里面的变量很多,但是实际上,最常用的是区域变量,它们的调用方式都是一样的:

<?php print render($page['footer']); ?>

page.tpl.php里面特有的变量,比如$logo$breadcrumb,以及主导航链接,二级导航链接的输出,正在被区块的形式所取代。统一采用区块的形式,学习起来更易于掌握。


Drupal版本:

7 创建自己的page.tpl.php

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在我们要做的工作,是将静态HTML页面body里面的代码全部复制到page.tpl.php文件里面来,这里的HTML是从login.html里面复制过来的。我通常将这些静态的HTML放到注释的后面,原来代码的前面。我们要将里面的静态HTML,替换成对应的变量了。

这是没有替换前的样子:

1.png 

   我们里面的代码折叠了起来。

 

我们现在开始替换,首先看logo,原来是这样的:

<div class="logo"><span>SNT</span>代理</div>

我们看到logo是文字形式的,我们这里把它处理成为区域的形式了,这是替换后的样子:

<div class="logo"><?php print render($page['logo']); ?></div>

 

我们依次这样替换,这是第一轮替换后的样子

2png.png 

3.png 

我总是把主内容区域的输出,放在最后才替换。先易后难。我们现在来看主内容这块,首先是面包屑导航。

<p class="daohang"><a href="#">首页</a> > <a href="#">业务支持</a></p>

我们将其替换为:

  <?php if ($breadcrumb): ?>

           <p class="daohang"><?php print $breadcrumb; ?></p>

  <?php endif; ?>

Drupal里面,常使用这样的if语句,来输出变量,这样在变量为空的时候,相关的HTML也就可以不输出了。我们来看一下核心的代码:

4.png 

这里面输出了变量$messages,我们这里也把这个变量输出来,然后放到面包屑代码片段的下面。很多人做项目的时候,经常把这个给忘记了。

接下来,我们将这段HTML删除

5.png 

这就是主内容区域所在的位置。然后在同样的位置,将这段代码复制粘贴进来:

        <?php if ($page['highlighted']): ?><div id="highlighted"><?php print render($page['highlighted']); ?></div><?php endif; ?>

        <a id="main-content"></a>

        <?php print render($title_prefix); ?>

        <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>

        <?php print render($title_suffix); ?>

        <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>

        <?php print render($page['help']); ?>

        <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>

        <?php print render($page['content']); ?>

        <?php print $feed_icons; ?>

如果你对这些变量不熟悉的话,没有关系,我建议开始的时候把它们都输出出来,当我们发现信息显示的多了的时候,再删除相应的变量。$title是负责输出标题的;$title_prefix$title_suffix是标题的前缀和后缀;$tabs用来输出标签,我们平时看到的查看、编辑标签,就是通过这个变量输出的;$action_links是动作链接,比如管理内容页面的添加内容链接,就是通过这个变量输出的;'highlighted''help'是两个辅助性质的区域,核心模板文件里面带的,我建议,我们这里把这两个区域也加上;$feed_icons是用来输出RSS订阅链接的。

我们现在,就在snt.info文件里面把'highlighted''help'这两个区域给加上:

regions[highlighted]           = Highlighted

regions[help]           = Help

到此为止,我们的这个page.tpl.php基本上就替换完了。现在我们可以把这个模板文件中的原来复制过来的代码给删除了,不然内容会重复的显示。删除完毕,保存page.tpl.php

讲到page.tpl.php的时候,有人问过这样的问题,render的用法,以及与drupal_render之间的区别。其实我们可以看一下render的源代码,比较简单:

function render(&$element) {

  if (is_array($element)) {

    show($element);

    return drupal_render($element);

  }

  else {

    // Safe-guard for inappropriate use of render() on flat variables: return

    // the variable as-is.

    return $element;

  }

}

   如果是数组的话,就是用drupal_render输出,否则直接返回。在Drupal6的时候,对于页面回调,我们通常这样写代码:

$output = '';

$output .= '123456';

return $output;

 

但是在Drupal7下,标准的格式是这样的:

  $render_array = array();

  $render_array['#markup'] = '123456';

  return $render_array;

返回的是数组的形式,不过在Drupal7下,也可以返回字符串的形式,这是对Drupal6原有用法的一个兼容,render函数其实就是负责这一兼容工作的,就是说,在返回字符串的情况下,也能正常工作。


Drupal版本:

8 启用主题


现在我们就可以启用这个新作的主题了,导航到admin/appearance,在这里找到SNT主题:

1.png 

我们将它启用并设置为默认主题。现在我们回到首页,样式是比较乱的:

2png.png 

    我们添加一篇文章“联系我们”,我们看到的样子:

3.png


Drupal版本:

9 创建静态区块


我们现在做一件事情,就是把头部、尾部、还有导航,还有右边栏的内容,把它们以静态区块的形式添加进来,这是我做Drupal主题的时候,采用的办法,就是说,先把样子显示出来,不至于在开始阶段,样子太乱,没有内容。

我们导航到admin/structure/block,在这里找到添加区块链接:

1.png 

点击这个链接,进入区块的添加页面,这里面我们输入以下内容:

2png.png 

此外,对于文本格式,我们选择Full HTML

3.png 

对区域设置,我们将它放在Logo区域:

4.png 

保存这个区块。这个时候,我们看到Logo区域里面还有很多其它区块,我们把这些无关的区块都去掉。我们添加了9个静态区块,里面的内容,就是我们在替换page.tpl.php时,替换掉的HTML。不要怕麻烦,以前我做Drupal主题的时候,都是这样,先放静态的,除了立即能够看到效果以外,还有一个好处,就是能够给我们确定一个目标,我们在后面,可以对这些静态区块逐一的改造升级。这是添加完的样子:

5.png


Drupal版本:

第七章 Drupal多语言网站

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,我们来实现多语言,现在的默认语言为简体中文,我们还需要支持英文。实际上,原来默认的就是英文,只不过我们将它改为了简体中文。


Drupal版本:

1 界面的多语言化

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

界面的多语言化

Drupal版本:

1.1 添加语言

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来回顾一下一个简单的问题,如何添加一个语言?这个比较简单,我们在添加简单中文的时候,就已经做过了。首先是启用Locale模块,接着导航到语言的管理界面,在这里点击添加语言链接。

1.png 

在我们添加语言的时候,除了预定义好的语言以外,我们还可以自定义语言,实际上,在实际项目中,从来没有用过这个自定义语言。

2.png 

点击右上角的“检测与选择”,进入页面admin/config/regional/language/configure,我们在这里可以配置,按照那种方式选择界面语言。

3.png 

这里的检测方法包括:网址、会话、用户、浏览器、默认。我们这里使用的是默认。对于网址,它是有配置选项的:

4.png 

会话的配置选项:

5.png 

如果同时支持多个语言的话,我们通常都采用网址(路径前缀)的探测方法。我们后面会用到这个。


Drupal版本:

1.2 导入简体中文语言包

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

启用了简体中文以后,如何导入简体中文语言包?我们知道,Drupal的界面翻译,采用的是PO文件的格式,我们需要下载这些语言包,并将它们导入到Drupal中来。Drupal的翻译管理界面,位于admin/config/regional/translate

1.png 

在这里可以查看每个语言的翻译状态,我们看到简体中文的翻译率不足60%

后台的翻译界面:

2.png 

可以对界面语言进行翻译,在上面可以通过查找过滤功能,找到对应的字符串。找到以后,如果已经有了翻译,觉得翻译的不好的话,可以对它进行编辑,替换成我们自己想要的;如果尚未翻译的话,那么我们可以添加自己的翻译。

PO文件的导入界面:

3.png 

可以导入PO文件,在导入的时候,需要选择语言,还需要选择导入的模式,这里有两种模式,一种是将原来的翻译覆盖掉,另一种是保持原有翻译不变,只添加新字符串,我们通常选择后者即可。

将本地的翻译,导出成PO文件,这个功能是有的,但是实际项目中很少用到。语言包可以在https://localize.drupal.org/translate下载到,简体中文的语言包位于https://localize.drupal.org/translate/languages/zh-hans。需要说明一下的是,简体中文小组的汉化工作比较混乱,我去年十月份汉化的Ubercart,到现在都没有人审批。

4.png 

     这段话写的很好,期待简体中文小组的汉化工作,在将来会得到改善。


Drupal版本:

1.3 Localization update模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

一个Drupal网站,通常会安装比较多的第3方模块,一个模块一个模块的导入汉化包,也是一件很麻烦的事情。很多时候,我都是这样的,只导入Drupal核心的汉化包,我很少导入第三方模块的。因为网站主要看的前台嘛。如果你对后台的界面的汉化,有更多的要求,而且还想自动化,此时有这样的一个模块,可以帮我们做这件事情。Localization update模块。

我们下载、安装、启用Localization update模块,我这里的版本是l10n_update-7.x-1.0-beta3。这个模块的作用,就是自动的帮我们下载这些语言包。模块启用后,直接导入该模块本身的语言包:

1.png 

导入成功后:

2.png 

在翻译的管理界面,多了一个“更新”标签:

3.png 

点击这个标签,进入页面admin/config/regional/translate/update

4.png 

这里的状态大部分都是“Uninstalled translation available”,这说明了很多Drupal的第三方模块都没有最新的汉化包可用。这里以CKEditor模块为例,展开后:

5.png 

     右下角有一个下载链接,点击这个链接,就可以下载这个模块的PO文件。打开下载好的PO文件,发现里面的文本都已经翻译了。PO文件里面的内容大致如下:

6.png 

     在这个页面的下面,是一个更新模式选项和两个按钮:

7.png 

点击这个更新翻译按钮:

8.png 

     我刚才还犯愁呢,没有想到点了这个按钮就自动导入了。这是我第一次使用这个模块。我能够手动做的,很少去装模块。这是更新成功后的样子:

9.png 

10.png 

汉化就这么简单。不过,美中不足:

11.png 

12.png 

不是100%的汉化了,只汉化了78%。不过很多都已经翻译了,翻译的还不错:

13.png 

在语言的管理界面,多了一个“更新翻译”标签:

14.png 

点击这个标签,进入页面admin/config/regional/language/update,这里是Localization update模块的配置选项:

15.png 

16.png 

     这里面的简体中文,是从繁体中文中复制过来的,都没有转换一下,人真懒。如果我们在“保存下载的文件到”里面输入“sites/all/translations”,那么我们从服务器上下载下来的PO文件就会保存在这个目录下,默认为空时,不会保存这些文件的。


Drupal版本:

1.4 Localization client模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

与界面翻译相关的还有Localization client模块,使用它可以在当前页面就能够完成翻译,并且能够将本地的翻译,上传到localize.drupal.org

我们下载、安装、启用Localization client模块,我这里用的版本是l10n_client-7.x-1.2。启用后,在每个页面的下面,会有这个一个链接:

1.png 

点击后,会展开更多内容:

2.png 

左边是该页面上的所有界面文本,绿色的表示已经翻译好的,白色的表示尚未翻译的。双击一段白色的字符串,它就会在中间的“源”里面显示出来,比如这里的“Configure menu block.”,在右边的文本域中输入它的翻译,保存,就完成了翻译。这个比核心自带的翻译功能强大很多,方便很多。

如果我们在本地做了很多汉化工作,想将这些工作分享出来,学一下雷锋。在语言的管理界面,admin/config/regional/language,右边多了一个“分享”标签。

3.png 

点击,进入页面,这是我的配置:

4.png 

这并不意味着我们本地的汉化工作,可以自动的上传到服务器上了,我们需要进一的配置。在用户的编辑页面,有这么一个配置选项:

5.png 

我们需要输入API密钥,点击右下角的链接https://localize.drupal.org/translate/remote/userkey/c060c2fd8de97f4b620b808390d89f0a 如果你拥有drupal.org账号,并且已经登录的话,此时就会为你生成一个密钥:

6.png 

将这个密钥复制过来,并保存。当我们在本地保存一个翻译时,系统就会尝试将这个翻译提交到服务器上:

7.png 

遗憾的是,没有提交上,原因很可能是这个已经有人提交了,但是还没有被批准。又尝试了一下,提交成功:

8.png 

     我把Menu position模块的部分界面文本翻译了一下,并上传到了服务器上。如果所有的中国Drupal开发者,每人贡献10条翻译的话,汉化工作就会比较出色了。


Drupal版本:

2 内容的多语言化

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

问题出在了哪里?这些工作对于我们来说,费力不讨好,所以没有人愿意去做,即便是有人去做多语言网站,开发者关注的重点,也不是界面的翻译。重点在哪里?当然是内容的翻译。


Drupal版本:

2.1 Content translation VS Entity translation

     作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Drupal核心自带了一个Content translation的模块,能够翻译节点;再加上i18n模块,这是Drupal多语言网站建设里面的一个标准解决方案。在Drupal6里面,这是标准方案,在Drupal7里面,也可以说是一个标准方案。不过在Drupal7里面,出现了另一种选择,这就是Entity translation模块,后者进入了Drupal8的内核,并在Drupal8取代了前者。所以,我们在搭建Drupal7下的多语言网站的时候,有两种选择。我们这里将会选用Entity translation模块和i18n模块做我们我们的解决方案。

1.png

Content translation的基本架构图

2.png

Entity translation的基本架构图

 

从上面的两个示意图中可以看出,Content translation是基于节点的,它为每种语言创建一个节点,并且维护不同语言节点之间的对应关系。Entity translation是基于字段的,在同一个节点内,同一字段在不同语言下,分别创建一个版本。


Drupal版本:

3 使用Entity translation模块翻译节点

    作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们下载、安装、启用Entity translation模块,我这里使用的版本是entity_translation-7.x-1.0-beta3。我们知道,Drupal7里面,标题不是一个字段,而是一个属性,为了实现标题的翻译,我们还需要安装Title模块。我这里使用的版本是title-7.x-1.0-alpha7

启用后,我们可以导航到Entity translation模块的配置界面,admin/config/regional/entity_translation。这里有三个配置选项;

1.png 

一个是启用语言回退,一个是显示共享标签,最后一个是启用翻译工作流权限。我们这里采用默认的配置即可。

再往下是配置哪些实体类型可被翻译:

2.png 

默认选择了节点,我们这里把分类术语也选中。我想知道Tittle模块是否支持分类术语的标签。选中分类保存后,下面多出来了一些与分类相关的配置选项。

3.png 

展开后:

4.png 

我们这里采用默认的即可。

除了这里的配置以外,在admin/config/regional/language/configure,语言的检测与选择页面,此时多出来了“内容语言检测”的配置选项。

5.png 

在这里,我们启用网址、浏览器两种方法。对于网址的配置,我们采用路径前缀的方式即可。

我们回到内容类型的管理界面,编辑“关于我们”内容类型,进入页面zh-hans/admin/structure/types/manage/about-us。这个时候路径前缀已经显示出来了。在“发布选项”里面,有这么一个配置选项:

6.png 

我们这里选中“Enabled, with field translation”,就是让这个内容类型支持多语言,通过字段翻译的方式。选中后,下面会显示出来进一步的配置选项:

7.png 

隐藏内容翻译链接,这个我们采用默认的不选中就可以了。保存这里的配置。现在访问Entity translation的配置界面,我们可以看到“关于我们”内容类型的配置选项了,和前面分类术语的一样,只是默认值不同。

8.png 

下面有四个配置选项,隐藏语言选择器,从可用语言中排除语言中立,一旦实体创建后就阻止修改语言属性,在翻译表单中隐藏共享元素。我们还是采用默认值。上面的默认语言,是这个节点类型的默认语言,可以与站点的默认语言不同。

现在,我们去编辑节点1,访问页面node/1/edit,此时在节点的编辑表单中,我们可以看到语言的下拉选择框:

9.png 

这里有三个语言选项:

10.png 

在右上角的标签中,多了一个“翻译”:

11.png 

我们点击这个标签,进入页面node/1/translate

12.png 

这里显示的是“No translatable fields”,没有可翻译的字段,我想可能是这样的,原来我们保存节点的时候,字段的语言都是采用und的形式存储的,我们这里对原始内容编辑一下,保存,此时会有语言属性的。

我们导航到“关于我们”内容类型的管理字段页面,admin/structure/types/manage/about-us/fields

13.png 

Title的右边,有一个replace链接,点击这个链接,进入页面:

14.png 

我们选中这个复选框,并点击保存设置按钮。这样标题就会被一个字段取代:

15.png 

我们编辑这个字段,在字段实例的配置页面,我们看到这个配置选项:

16.png 

这是这个字段的翻译设置,当前这个字段是可被翻译的,下面有一个“Disable translation”链接,可以用来禁用这个字段的翻译。

此时我们终于有了眉目,编辑Body字段,我们可以看到:

17.png 

点击下面的“Enable translation”(启用翻译)链接。点击后,会看到这个配置选项:

18.png 

我们采用默认的选中即可。点击这个页面的确认按钮。这样我们就为正文字段启用了翻译功能。

现在回到页面node/1/translate

19.png 

     英文右边的操作,现在变为了“添加”,我们点击这个链接,进入页面en/node/1/edit/add/zh-hans/en。现在这里面是有内容的:

20.png 

还是原来的,我们需要把它们替换为英文。

再往下是:

21.png 

这个下拉选择框是被禁用了的,无法修改,原始语言为简体中文,我们这里实际情况下也不需要修改这个。

再往下是节点的配置选项:

22.png 

很多配置选项后面都多了一个“(all languages)”,表明里面的配置是作用所有语言的。不过这里面有两个不是,其中一个是URL路径设置,这意味着我们可以为同一节点,在不同语言下设置不同的别名。

对于“翻译”,展开后,里面包含四个配置选项:

23.png 

我们这里采用默认的即可。我们使用Google翻译,将标题和正文翻译一下,然后输入进来。

24.png 

这是保存后的样子:

25.png 

在正文的下面,有一个语言切换链接:

26.png 

点击后,可以方便的切换到简体中文。在简体中文的版本下,有同样一个链接,可以用来切换到英文。

前面我们提到可以为单个语言分别设置路径别名,我们在保存英文翻译的时候,没有为node/1设置别名,此时英文的路径为en/node/1,并没有为它应用别名aboutus。我们还需要单独为它设置一下,不然出不来。

为其余的内容类型,重复前面的操作,直到所有内容类型都可被正确的翻译。


Drupal版本:

6 首页的多语言化 你在这里

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    第一个问题是首页的产品区块的标题,在英文下没有正常显示:

1.png 

这个原因是,我们默认使用的是Title,现在变成字段title_field了,我们在Views里面,需要添加字段title_field,来代替原来的。修改后,显示正常。

2.png 

新闻标题显示不正常: 

3.png 

这个也是同样的问题。在Views里面,使用title_field替换原来的标题即可。需要注意的是,添加的时候,两个的名字相同。如果一次选不对的话,可以选两次,多试一下。

4.png 

Content: 标题 (title_field:language)”上面的这两个就是。

 

首页的“联系我们”区块的标题显示不正常:

5.png 

它和左边的新闻区块,使用的是Mini panel,我们导航到admin/structure/mini-panels,对它进行编辑,

6.png 

点击配置按钮, 

7.png 

在弹出的链接中点击设置链接:

8.png 

原来我们这里覆写了标题。取消对复选框“覆写标题”的选中。保存。不过这样还不行。我最终这样解决的,将这个区块添加了两次。

9.png 

    第一个:

10.png 

并将它限制在英文环境下显示:

11.png 

    第二个:

12.png 

并将它限制在中文环境下显示:

13.png 

 

现在,首页只有幻灯和企业愿景没有翻译。对于幻灯,我们使用的是节点类型,只需要将对应节点翻译一下即可。不过都是图片,我们这里就不翻译了。企业愿景,是一个自定义窗格内容,它本身不是一个区块。我们的解决办法,是创建两个企业愿景,一个用于中文,一个用于英文。下面截了几张图,第一个是内容窗格的配置链接:

14.png 

    这是点击了添加可见性规则后的配置表单,这里我选择了“用户:语言”:

15.png 

    将窗格的显示,限制在英文环境下:

16.png 

保存后,显示正常了:

 

我将首页幻灯的第一张图片翻译了一下,上传了一张带有“English version”字样的,翻译后,英文版的图片没有显示出来,检查了一下幻灯视图,在设置中的“高级〉其它”部分:

 

上面的字段语言配置,默认是没有问题的。原因是这里我启用了缓存,去掉缓存,显示正常了。

 

这证明了幻灯也是可以翻译的。



Drupal版本:

6.1 Webform的翻译

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

前面,我们翻译联系我们的时候,这个节点上面的Webform表单还没有翻译。主要是表单元素的标签。不过只有几个,我们可以采用前面所使用的jQuery的方式,搞定这个问题。不过我觉得不是很好。搜索Webform + i18n,还真有现成的模块可用,这就是Webform Localization

Webform Localization有两个版本,一个1.x,一个4.x,我们使用1.x的开发版。下载、安装、并启用。启用后,我首先来到“联系我们”的编辑页面,然后访问node/4/webform/,这里检查一下,看有没有与翻译相关的配置,还真被我找到了,在node/4/webform/configure

1.png 

Webform表单的多语言化,有两种方式,一种是使用一个表单,将表单元素的属性作为i18n字符串进行翻译;另一种是为每种语言创建一个表单,然后保持多个表单之间的同步。我们采用第一种方式:

2.png 

其实,第二个复选框,是用于Content translation的。不过我们这里都选中了。现在,导航到admin/config/regional/translate/translate,这里我们将字符串限制在:

3.png 

点击过滤按钮:

4.png 

我们可以对这些字符串进行翻译了。翻译后,联系我们表单,在英文下正常:

5.png 


Drupal版本:

6.2 字段标签的翻译

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

当我们显示一个新闻节点的时候,新闻分类字段的标签,显示还是中文的形式,这个我们也可以在admin/config/regional/translate/translate对其进行翻译,首先将字符串限制在“字段”上,过滤:

1.png 

此时会搜索到很多字段的标签:

2.png 

     我们对它们分别翻译一下。就解决了这个问题。

如果,你想将语言切换器改成下拉选择框的形式,可以安装Language Switcher Dropdown模块;如果想加上语言图标的话,可以安装Language icons。与多语言相关的模块,还有很多。我们这里就不逐一介绍了。这是安装Language icons模块后的效果:

3.png 

整个网站,基本功能已经搭建完毕,不过还有很多需要完善的,与多语言相关的部分,我们暂时讲解到这里,希望大家读完以后,对于多语言网站的搭建过程,有个比较好的了解。而我们的Think in Drupal5集,到这里也就结束了,感谢大家的支持。


Drupal版本:

7 面包屑的翻译

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

最后加一句,我想说的是,前面通过jQuery解决的那个问题,后来我回去又想了想,终于想到新闻列表页面和产品列表页面的不同了。原来我为products页面添加了一个菜单位置规则(menu position rule,只不过这个规则后来被禁用了。我们为新闻添加一个同样的规则,并禁用,然后注释掉前面的JS代码。面包屑显示仍然正常。

1.png 


Drupal版本:

8 Translation Management Tool

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

发现页数不够272页,想了一下,这个模块还没有介绍,我们这里简单介绍一下。这是用来做翻译管理工具的模块,我这里使用的版本是tmgmt-7.x-1.0-beta1。这个模块依赖于entity模块,我们这里使用entity-7.x-1.1。这个模块的目的,是用来实现翻译自动化的,我们去翻译一个节点的时候,里面的内容能够预先的翻译好,比如使用Google翻译,这样我们就不用去Google翻译的页面复制粘贴了。不过令人遗憾的是,TMGMT Translator Google所依赖的Google翻译服务是收费的,没有免费版。还好,微软的提供免费的,我们拿微软的作为例子。我们使用TMGMT Translator Microsoft模块,这里使用的版本是tmgmt_microsoft-7.x-1.0-alpha2。为了启用子模块Translation Management UI,我们还需要安装VBORules模块。我启用了这些模块:Entity SourceEntity Source User InterfaceMicrosoft TranslatorTranslation Management CoreTranslation Management FieldTranslation Management UI,以及它们所依赖的模块。

它的配置界面位于admin/config/regional/tmgmt_settings,包含两个配置选项:

1.png 

流程设定,和性能设置。我们这里采用默认的即可。

另外一个配置页面位于/admin/config/regional/tmgmt_translator,这是翻译器设置:

2.png 

这里只有一个翻译器,就是微软的这个。还是微软大方,提供免费的给我们用。我们对它进行编辑,在编辑页面,有微软翻译器的特有配置:

3.png 

这里有一篇文档,http://blogs.msdn.com/b/translation/p/gettingstarted1.aspx,介绍了如何获取这些信息。

1, 我们需要在https://datamarket.azure.com上面注册一个账号。

2, 然后订阅微软的翻译器API,我这里定的是免费版的,定购的地址为:https://datamarket.azure.com/dataset/bing/microsofttranslator

4.png 

3, 获取开发者密钥信息,访问https://datamarket.azure.com/,在我的账号里面,可以获取到密钥信息:

5.png 

我们将这里获取的客户ID和密钥,填到Drupal里面对应的配置中来。

6.png 

我们现在来创建一个静态页面节点:

7.png 

我们对它进行翻译:

8.png 

刚才在这里,我点击了添加链接,发现里面显示的还是中文。仔细看了一下这个页面,左边比以前多了一个复选框,下面好像多了一个“Request translation”按钮,我们选中“英文”,点击这个“请求翻译”按钮。

9.png 

自动翻译好了,需要我们的审核。点击这里的“Needs review”链接,进入页面admin/tmgmt/items/1?destination=node/22/translate。我们在这里看到了:

10.png 

11.png 

12.png 

13.png 

对于这里的翻译,我们可以进一步的编辑。如果满意了,那么可以点击“Save as completed(保存为已完成)。我们这里点击这个按钮。

14.png 

    这个节点的英文版已经翻译好了。省事吧。我们不用再把节点标题、正文,复制过去,翻译好了以后再复制回来,然后再保存了。只需要轻轻的点击几下按钮,就能自动完成翻译。


Drupal版本:

4使用Entity translation模块翻译分类术语

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com


我们现在来看分类术语的翻译,导航到分类的管理界面,找到“新闻分类”词汇表,admin/structure/taxonomy/news_category,编辑它下面的第一个分类术语“公司新闻”:

1.png 

我们看到,左上角有一个“翻译”标签,点击这个标签,进入页面taxonomy/term/4/translate

2.png 

这里英文版本的操作,仍然是“无可翻译的字段”。和前面遇到的情况一样。我们现在进入“新闻分类”词汇表的管理字段页面:

3.png 

我们发现,“名称”和“描述”的右边都有一个“替换”链接,也就是我们可以把它替换成字段的形式。我们替换一下,这是替换后的样子:

4.png 

注意,这里字段的机读名字分别为name_fielddescription_field,前面的节点标题替换后的为title_field。看来Title模块的功能,不仅仅支持节点标题,还支持分类术语的名称和描述。这真是一个不小的惊喜。

现在,对于“公司新闻”这个分类术语,我们可以为它添加英文翻译了:

5.png 

点击这里的添加链接,在对应的名称、描述里面输入英文版本。我们这里只输入名称的英文版本即可:

6.png 

我们重复这一操作,将新闻分类下面其余两个分类术语也翻译一下。将产品分类下面的分类术语也翻译一下。

不过令人遗憾的是,当我们创建新闻的英文版本时,新闻分类里面的分类术语,显示的还是简体中文:

7.png 

保存后,才会显示英文的形式:

8.png 

不过这里,字段的标签,没有被翻译过来。尽管有这么一个小问题,但是在显示的时候,没有问题,所以我觉得,分类术语的这种翻译方式,还是比较不错的。I18N模块里面,也有分类术语的子模块,如果你觉得这里的这种方式,对你来说有点问题的话,可以尝试一下I18N模块下面的分类术语翻译。

我们安装Title模块的时候,忘记说了,这个模块是有一个配置页面的,

9.png 

就是创建一个bundle的时候,能够将实体的标题(名称)自动替换成字段的形式。对我们的影响不大。我们可以手动的完成。至于Label替换,这个到底怎么起作用的,我也没有弄清楚。我把它们选中以后,在英文版本下面,新闻分类显示的还是中文。


Drupal版本:

5语言切换器区块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们导航到区块的管理界面,找到“语言切换器(内容)”区块,将它放在主题的“标题”区域中,同时禁用区块标题。默认是这样显示的:

1.png 

我们调整一下CSS

.region-header{

  float:right;

  clear:none;

}

.region-header #block-locale-language-content .language-switcher-locale-url li{

  display:block;

  float:left;

  margin-right:20px;

}

.region-header  a {

    text-decoration: none;

}

这是调整后的样子:

2.png 

值得一提的是,如果把新闻翻译好了以后,在新闻列表页面的英文版本下,显示也是基本正常的,这是en/news/4路经下的样子:

3.png 

除了节点和分类术语以外,我们还有很多工作要做,比如菜单、区块、系统变量,字段的标签,等等,需要翻译。


Drupal版本:

5.1 i18n模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们下载、安装、启用i18n模块,我这里使用的版本是i18n-7.x-1.9。这个模块还依赖于Variable模块,我们还需要先安装Variable模块,我这里使用的版本是variable-7.x-2.2。 i18n模块下面包含很多个子模块,从上到下:

1.png 

我这里启用了Block languagesField translationInternationalization,这里的Contact translation是负责Contact这个核心模块的翻译的,不过我们这个站点没有用到这个模块。

2.png 

这里,我启用了Menu translation模块;Multilingual content这个是基于content translation的,我们这里没有采用这种方案;Multilingual forum解决的是论坛模块的翻译,我们这里没有使用论坛模块;Multilingual select,这个可能有用,后面我们不妨启用测试一下,看是否能够解决我们前面所说的分类术语选择时的问题。

3.png 

这里,启用了String translationSynchronize translations是基于content translation的,我们这里没有采用这种方案;Taxonomy translation,我们已经解决了分类术语的翻译,如果你觉得我们前面的方式不够好的话,可以尝试一下这个模块;Path translation,用于翻译路经,有时候有用。

4.png 

我们这里启用了Translation setsVariable translation,前者为多个子模块所依赖,后者用于变量的翻译,很有用;Translation redirect主要用于SEO,我们这里对SEO的要求不高;User mail translation用于用户电子邮件的翻译,我们这个站点用处不大。


Drupal版本:

5.2 变量的翻译

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

首先,我们来看站点名称的翻译,我们站点的名称为“亚艾元”,在英文环境下,我们想显示拼音的形式。怎么翻译呢?导航到admin/config/system/site-information,这是站点名称所在的配置界面:

1.png 

在这个页面的上面有提示,这个表单中包含多语言的变量,我们将它切换到英文的环境下,admin/config/system/site-information?variable_realm_key_language=en,在这种情况下,输入拼音,保存:

2.png 

此时,我又遇到了一个问题,那就是虽然我这里输入了变量的英文版本,但是在首页的中英文切换的过程中,站点名称始终是中文。我尝试了比较多的办法,比如分别使用路经:zh-hans/admin/config/system/site-information,和en/admin/config/system/site-information访问,然后输入对应的中文、英文版本,还是不行。

Google 了一下,找到一篇文档,https://drupal.org/node/1113374,这里讲的就是变量翻译,我的配置没有任何问题。通过这篇文章,我了解到admin/config/regional/i18n的存在,这是i18n的配置界面,变量的多语言设置可以在admin/config/regional/i18n/variable完成。

然后我想到了在预处理函数中,搞定这个问题,在当前主题的template.php文件中添加这么一个函数:

function yaiyuan_preprocess_page(&$variables, $hook) {

  global $language;

  $lang_name = $language->language ;

  $variables['site_name'] = (theme_get_setting('toggle_name') ? filter_xss_admin(i18n_variable_get('site_name', 'en')) : '');

  drupal_set_message($lang_name);

}

注意,这里的$lang_name始终都是zh-hans,即便是我切换到了英文下面。我这里使用i18n_variable_get来替代默认的variable_get,以便支持多语言。

昨天晚上一直在想解决方案,如果我能够获取到当前语言,那么通过预处理函数的方式,就能够解决问题,Drupal核心自带的语言切换器区块对应的代码里面,应该有如何获取当前语言的代码。这样一想,感觉问题是可以通过预处理函数的方式解决的。

不过,我突然想到了一件事情,即便是使用en/admin/config/system/site-information访问后台页面的时候,后台显示的也是简体中文。我突然想到,Drupal7中,界面语言和内容语言的检测方法是分开配置的,界面语言的检测方法我们还是用的默认的,很可能是这里出了问题。

我们先沿着第一种方案继续前进,我们去配置“语言切换器(内容)”的时候,可以看出这个区块是由locale模块提供的,这样我们就可以顺藤摸瓜,找到对应的代码。

function locale_block_view($type) {

  if (drupal_multilingual()) {

    $path = drupal_is_front_page() ? '<front>' : $_GET['q'];

    $links = language_negotiation_get_switch_links($type, $path);

这里面,我觉得比较关键是language_negotiation_get_switch_links,通过搜索,找到这个函数的源代码。在这个函数里面,有这么一段代码:

  if (count(language_list()) >= 2) {

    $language = language_initialize($type);

  }

给了我很大的提示,这里的$type有多种情况,我们在前面分析核心源代码的时候,曾经提到过,打开bootstrap.inc文件,找到:

define('LANGUAGE_TYPE_CONTENT', 'language_content');

 

/**

 * The type of language used to select the user interface.

 */

define('LANGUAGE_TYPE_INTERFACE', 'language');

 

/**

 * The type of language used for URLs.

 */

define('LANGUAGE_TYPE_URL', 'language_url');

我们这里使用LANGUAGE_TYPE_CONTENT。根据这些信息,我最终使用下面的代码,解决了这个多语言问题:

function yaiyuan_preprocess_page(&$variables, $hook) {

  //global $language;

 //$lang_name = $language->language ;

  $language = language_initialize(LANGUAGE_TYPE_CONTENT);

  $variables['site_name'] = (theme_get_setting('toggle_name') ? filter_xss_admin(i18n_variable_get('site_name', $language->language)) : '');

  //drupal_set_message($lang_name);

  //print debug($language);

}

这里面,如何获取当前语言,我首先尝试了一下$language,总是不对。使用language_initialize(LANGUAGE_TYPE_CONTENT),则可以成功获取。注意我们这里获取的是当前内容的语言。不是当前的界面语言。此外,我对这个语言变量还debug了一下,查看了它的结构。它的结构是这个样子的:

stdClass::__set_state(array(

   'language' => 'en',

   'name' => 'English',

   'native' => 'English',

   'direction' => '0',

   'enabled' => '1',

   'plurals' => '0',

   'formula' => '',

   'domain' => '',

   'prefix' => 'en',

   'weight' => '0',

   'javascript' => '',

   'provider' => 'locale-url',

))

我们使用预处理函数的方式解决了问题。不过,总不能每次都这样解决吧。或许真的是界面语言的检测与选择的配置问题。导航到admin/config/regional/language/configure,对于界面语言的检测方法,我们与内容的保持一致。

3.png 

保存这里的配置。注释掉,我们前面所写的预处理函数。站点名称的中英文切换,正常。当然,前面的工作,也不是没有白做,如果你跟着阅读了源代码,会对Drupal7多语言的背后机制有更深的理解。比如这个语言,它分为:内容、界面、路径。我们这里对这个概念有了更深的理解。我们前面的获取当前语言,其实是获取当前内容语言,而不是获取当前界面语言。

4.png 

5.png 

如果,我们这里是一个Logo,中文Logo和英文Logo不一样,怎么解决呢?导航到zh-hans/admin/config/regional/i18n/variable

6.png 

选中这里的主题设置复选框即可,这样主题的Logo就支持多语言了,分别为每个语言上传一个Logo,解决了Logo的多语言问题。

 


Drupal版本:

5.3 菜单的翻译

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看一下如何翻译主菜单,导航到主菜单的编辑页面,admin/structure/menu/manage/main-menu/edit。这里面有一个多语言的配置选项:

 

1.png 

第一个选项,菜单项没有多语言选项,只有菜单本身可被翻译。菜单本身可被翻译?如何理解,看这个菜单右上角的标签,里面有一个“翻译”标签:

2.png 

点击这个“翻译”标签,就可以对菜单进行翻译。只是我们这里没有必要。

第二个选项,翻译和本地化。注意,这里的翻译(translate)和本地化(Localize)之间是有区别的。如果一个菜单项,它具有语言属性,此时会允许翻译;如果它没有设置语言,则可被本地化。

第三个选项,固定语言,每个菜单项都有一个语言属性,它们只会显示在自己的语言下。

我们这里选择第二个选项,保存。我们现在对主菜单的菜单项,进行编辑:

3.png 

点击“首页”菜单项右边的编辑链接。进入菜单项的编辑页面。

4.png 

此时,右上角有了一个“翻译”标签,我们点击这个标签,就可以对这个菜单项进行翻译了。

5.png 

点击操作里面的“翻译”链接,我们将菜单项的标题翻译一下:

6.png 

翻译好了保存结果。这是翻译后的样子:

7.png 

菜单项的编辑页面,还有一个语言选项:

8.png 

我们可以设置,这个菜单项是属于哪个语言下的。这样的话,我们可以为每种语言都创建一个对应的菜单项,也能解决问题。

因为,我们这里面,中文、英文的节点ID是相同的,所以我们没有必要创建多个菜单项。直接对菜单项的标题进行翻译即可。

重复前面的操作,直到把主菜单里面剩余的菜单项都翻译完毕为止。需要说明一下的是,当我们翻译到“关于我们”下面的第一个子菜单项“公司历程”的时候,菜单项编辑页面的右上角没有“翻译”标签。如果我们直接输入对应的路径的话,此时会提示没有权限。这是因为,我们还没有翻译菜单项“公司历程”所对应的节点,我们将对应的节点翻译成英文后,这里的菜单项,也可以翻译了。

到最后,只剩下“联系我们”这个webform页面了,这也是一个普通的节点。我们首先让内容类型Webform支持字段翻译,将它的Title替换成字段的形式。这个时候,我们就可以将“联系我们”这个节点的标题和正文翻译成英文了。之后,就可以翻译对应的菜单项了。只不过这个页面的表单还无法翻译,主要是表单元素的标签,在英文环境下还是显示的中文。

9.png 

我们现在先不管这个,到此,整个主菜单里面的所有菜单项,都被翻译成为了英文。注意,

10.png 

     注意,这里不仅仅是主菜单,左边的Menu block,以及面包屑都变成英文的。这说明了Menu blockMenu Position模块对多语言的支持是比较友好的。

 


Drupal版本:

5.4区块的翻译

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

不过,左下角的“联系我们”区块,仍然是中文的,我们来看一下,如何将它翻译成英文。点击它的配置链接,进入它的编辑页面en/admin/structure/block/manage/block/2/configure。在这里,可见性设置里面,多了一个语言选项。

1.png 

     这里有两种方式,第一种方式,是将这个区块标记为可翻译的,也就是选中“Make this block translatable”。选中后,下面的按钮多出来了一个:

2.png 

我们可以点击“保存并翻译”按钮。

3.png 

我们点击英文右边的翻译链接,来翻译这个区块:

4.png 

遗憾的是,我们这里面只能翻译区块标题,不能翻译区块的正文,这是因为区块正文所使用的文本格式不支持多语言。如何让它支持多语言呢?导航到i18n的管理界面,点击右上角的“字符串”标签:

5.png 

这里有两个配置选项:

6.png 

7.png 

对于源语言,我们使用默认的简体中文即可。对于可翻译的文本格式,我们这里选中所有的,保存。回到,刚才的区块的翻译页面,现在就可以翻译区块正文了:

8.png 

在这里,输入对应的英文。注意,对于区块正文,应该保留原来的HTML标签。

我们可以创建两个区块,一个显示在中文下,一个显示在英文下,两个区块显示在同一个区域的同一位置。这也是一种常见的解决方案,我以前就非常喜欢这种方式。

我发现一个问题,面包屑没有正确显示:

9.png 

具体原因不明。我把新闻、产品所有节点也都翻译了一遍,使用的Google翻译,没有人工校对。

现在,我们重复前面的操作,将“页脚菜单”、“版权信息”也翻译成英文。

10.png 

     令人遗憾的是,我们将所有的菜单项翻译后,在英文环境下,显示的还是中文。这是我的翻译:

11.png 

原因未明。我们暂时先不管它。来看“版权所有”区块,我们对它进行配置,将它只显示在中文下:

12.png 

接着,创建一个新的静态区块,是版权所有区块的英文版:

13.png 

也放在页脚区域,这里只显示在英文环境下; 

14.png 

     保存。这个区块的多语言,我们也解决了。来看页脚菜单区块,这个区块我们用的是核心自带的,我们前面讲到左边的菜单区块(menu block)的中英文显示是正常的,我们将这个页脚菜单导航替换成Menu block模块提供的方式显示。


Drupal版本:

5.5 使用Menu block输出菜单

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

导航到区块的管理界面,创建一个新的menu block(菜单区块),做以下配置:

1.png 

我们将这个区块放在页脚区域,测试一下。中英文显示正常了。把原来的核心自带的区块禁用。现在页脚也正常了。

2.png 

我们没有必要去深究,为什么核心自带的对多语言支持不友好,只要解决问题就好。有时候换种方式,绕过去,可能会更好。


Drupal版本:

5.6 Views的翻译

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在回到新闻列表页面,或者一个新闻节点页面,此时的面包屑是不正常的。我们前面已经提到过了。我想这里之所以显示“新闻”两个字,是因为Views里面的配置问题。我们编辑对应的Views,将标题设置为英文的形式:

1.png 

现在,在英文环境下,面包屑显示正常了,但是在中文环境下,又不正常了。

2.png 

可是,这个面包屑是基于菜单的,而我们的菜单项中,已经完成了“新闻”到“News”的翻译。经过测试,发现,新闻节点页面的面包屑,是基于菜单的;新闻列表页面,是基于Views的。

为了解决这个问题,我们下载、安装、启用Internationalization Views模块,我这里使用的版本是i18nviews-7.x-3.x-dev。这个模块只有开发版,不过Drupal7下的开发版的安装量也超过了1万。安装后,报了一个错:

3.png 

这样的错误消息很多,我们这里不去管它。我最终成功解决这个问题,解决办法如下:

导航到admin/config/regional/translate/translate,在过滤条件中:

4.png 

将搜索限制在视图上,也就是Views上。这个时候,会列出Views中的很多需要翻译的字符串:

5.png 

这里的“新闻”,也就是news视图的标题,我们把它翻译一下:

6.png 

翻译后,测试了一下,发现不行。我又导航到admin/config/regional/translate/i18n_string,在这里刷新了一下字符串:

7.png 

这样新闻列表的面包屑,就显示正常了。

8.png 

用同样的版本,解决产品列表的面包屑显示。需要说明的是,上面的刷新字符串在这里没有起作用,真正起作用的是清除缓存。

9.png 

不过令人遗憾的是,新闻节点页面,在英文环境下的面包屑显示不正常:

10.png 

但是产品节点的显示就正常。两者之间的配置,我记得没有任何不同。我对这个问题研究了很久,始终找不到解决办法。


Drupal版本:

5.7 使用jQuery解决无法翻译的字符串

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我是这样解决的,解决的办法不是很地道。首先是编辑当前主题的info文件yaiyuan.info,取消注释这句代码:

scripts[] = js/script.js

接着向script.js这个文件中添加以下代码:

jQuery.noConflict();

(function($) { 

$(function() {

    

  $('.i18n-en .breadcrumb li a').each(function(index){ 

    if($(this).attr('href') == '/yaiyuan1/en/news'){

   //$(this).addClass('abc');

   $(this).html('News');

}

  });

 

});

})(jQuery);

这段JS的作用,就是将面包屑中“新闻”这个链接的文本替换为“News”。

有时候,问题实在解决不了的话,可以借助于jQuery, Hack一下,也没有关系。


Drupal版本:

第二章 Views的覆写

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在发现,右边栏的样式是乱的:

1.png 

最新公告、最新新闻,需要使用下面的滚动条,向右滚动才能显示出来,我们并没有覆写这里的输出。这里的输出,仍然是使用的默认区域、默认区块模板文件。

sites\all\themes\snt\templates\override\block下,将block--system--main-menu.tpl.php复制一份,然后重命名为block--ziye-right.tpl.php,模板文件里面的内容不变:

<?php print $content ?>

然后清除缓存,样式恢复了正常。

如果要进一步的话,我们可以这样,在sites\all\themes\snt\templates\override\region下面,将region--nav.tpl.php复制一份,然后重命名为region--ziye-right.tpl.php,模板文件里面的内容不变:

<?php print $content ?>

有时候,我会把region.tpl.phpblock.tpl.php模板文件里面的代码修改为:

<?php print $content ?>

其它部分全部删除。这样,就不会带有额外的输出了。


Drupal版本:

1 创建内容类型“新闻”

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在来将右边的这部分动态化,这里我们使用Views,来列出这里的最新新闻、最新公告。我们创建内容类型“新闻”,配置如下:

资源内容类型配置表

属性

名称

新闻

机读名称

news

标题字段标签

标题

发布选项

已发表

展示设置

显示作者和日期信息

评论设置

开放/非主题式/30/回复表单在单独页面显示

菜单设置

无可用菜单

为了后面配置的方便,我们安装locale模块、启用简体中文,导入简体中文语言包、做一些其它常见配置。具体配置参看Think in Drupal的第2集(Drupal实战)。

接下来,我们创建10篇新闻,这里都是测试数据。现在测试数据已经创建好了。

1.png


Drupal版本:

2 使用Views创建新闻列表区块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来,我们创建Views,我们首先需要启用ViewsViews UI模块。启用后,我们导航到Views的管理界面“首页 » 管理 » 结构 » Views”。

1.png 

   点击这里的“Add new view”链接,创建视图。这里是我的配置:

2png.png 

我们点击下面的继续并编辑按钮,进入Views的配置界面。然后点击右上角的保存按钮。这是我们配置好的样子:

3.png 

现在导航到区块的管理界面“首页 » 管理 » 结构 » 区块”,我们找到通过Views创建的区块“View: news: Block”,然后将它放到区域“Ziye right”里面。

4.png 

   保存。然后访问前台页面,比如首页。看右边区块的样式。

5.png 

我们看到这个样式,和目标还有不小的差距。我们现在来覆写Views的输出,让它与我们的静态HTML保持一致。我们来看一下对应的HTML

6.png 

我们这里将其与美工所给的HTML做一下对比:

7.png 

这里都是Views的默认输出,很多人刚学Drupal时,抱怨最多的就是,Views的输出没有办法控制。实际上是这样的,是初学者,不懂得如何控制Views的输出。


Drupal版本:

3 Views自带的样式设置

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在就去覆写Views的模板文件,让Views的输出,和目标输出保持一致。来看第一个,通过字段的样式设置,控制字段的HTML输出。

我们编辑刚才创建的视图,admin/structure/views/view/news/edit,在这里点击字段里面的“内容标题”,此时会弹出字段的配置对话框。我们展开里面的样式配置,做一下配置:

1.png 

这是我比较喜欢的配置。配置好了以后,保存。访问网站的首页,查看源代码,我们看一下这个区块的变化。

默认为:

2png.png 

配置后,变为:

3.png 

这里删除了两层标签,一个div,一个span


Drupal版本:

4 格式设置

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们注意到,目标代码里面使用的是ulli,所以我们可以调整一下views的格式,当前为Unformatted list

1.png 

我们点击“Unformatted list”链接,我们将其调整为“HTML list”:

2png.png 

点击下面的应用按钮,这样会弹出来的格式的配置对话框。

3.png 

这里我们采用默认的即可。保存。访问首页,观察区块HTML的变化,现在变为了:

4.png 

现在,我们这里也采用了ulli标签,只不过li里面有多余的class,我们想去掉它,这样内部的输出就基本保持一致了。我们还看到了,这里面有个<div class="item-list">,这个"item-list"我好像有印象,在前面的格式设置里面,有对应的配置选项:

5.png 

我们点击格式的配置链接,将这个配置选项置为空。保存。然后,观察一下对应HTML的变化。

6.png 

这个<div class="item-list">竟然被整个的去掉了。

我们调整一下Row classList class

7.png 

观察一下,HTML的变化:

8.png 

注意这里的ulliclass的变化,与Row classList Class之间的对应关系,我们看到Row class作用于li标签,List Class作用于ul标签。

我们将Row classList Class置为空。

在格式里面,我们当前的配置为:

9.png 

我们点击Fields右边的设置,在弹出的对话框里面,看最上面的复选框。

10.png 

如果我们取消了对这个复选框的选中,Views就会去掉默认的字段包装元素,我们去掉对它的选中。保存。观察HTML的变化: 

没有任何变化。但是这并不意味着,这个复选框不起作用。有可能是我们在字段的样式设置里面,已经去除了字段包装元素的原因。

我们可以在字段的样式设置、视图的格式设置上面,多测试几下,观察一下HTML的变化,通过在这里配置样式,可以减轻我们的模板覆写工作。


Drupal版本:

5 Views模板文件覆写

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

通过Views的后台配置,我们可以控制字段层级的输出,甚至可以控制Row层级的输出,甚至再外面一层的输出。但是后台的配置,还是有局限性的。距离完全控制Views的输出,还有一点距离。不过没有关系,我们可以覆写Views的模板文件,在模板文件里面,搞定这些问题。

Views的编辑页面,展开右边的高级选项,找到其它部分,最下面,找到“Theme”配置选项:

1.png 

点击它右边的“Information”链接,这样就会弹出当前显示(Display)的主题信息对话框。其实我们在Think in Drupal2集里面,讲过Views模板文件的覆写了。我们这里再重复一遍。

2png.png 

首先,我们需要弄清楚:Display outputStyle output、 Row style ouput的含义,是什么意思?很多人经常问这个问题。我这里简单解释一下,Display output负责最外层的输出,Style output负责次一层的输出,Row style ouput负责最小单元的输出。这里面有一个从外到内的关系。这里的解释,可能不是很给力,你如果想搞明白的话,最好看看对应的模板文件,看看模板文件里面的HTML片段,然后和自己建立的views的输出,做一下对应。这样,你一下子就理解了,恍然大悟。我在这里解释半天,也不如你自己动手看看。

Display output后面,跟随的是对应的模板文件的名字。第一个名字,是默认的模板文件名字,后面的,都是该模板文件的模板建议。黑体部分,表示当前起作用的模板文件名字。这里的名字,有个先后顺序的,越靠后的,越具体,优先级越高。“Style output”、 “Row style ouput”、“Field 内容标题 (ID: title)”后面的模板文件名字,也是这样的。


Drupal版本:

6 views-view.tpl.php

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在我们来覆写views-view.tpl.php,默认的模板文件位于sites\all\modules\views\theme目录下面,这里还包含了其它默认的views模板文件。

1.png 4.png

我们在sites\all\themes\snt\templates\override下面创建一个文件夹views,然后在新建的文件夹下面,再创建一个子文件夹news;将sites\all\modules\views\theme下面的views-view.tpl.php复制到sites\all\themes\snt\templates\override\views\news,并将文件名重命名为views-view--news--block.tpl.php,这个文件名,可以在Views的主题信息对话框中复制过来的。

我们打开views-view--news--block.tpl.php,现在的代码还是默认的:

<div class="<?php print $classes; ?>">

  <?php print render($title_prefix); ?>

  <?php if ($title): ?>

    <?php print $title; ?>

  <?php endif; ?>

  <?php print render($title_suffix); ?>

  <?php if ($header): ?>

    <div class="view-header">

      <?php print $header; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($exposed): ?>

    <div class="view-filters">

      <?php print $exposed; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($attachment_before): ?>

    <div class="attachment attachment-before">

      <?php print $attachment_before; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($rows): ?>

    <div class="view-content">

      <?php print $rows; ?>

    </div>

  <?php elseif ($empty): ?>

    <div class="view-empty">

      <?php print $empty; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($pager): ?>

    <?php print $pager; ?>

  <?php endif; ?>

 

  <?php if ($attachment_after): ?>

    <div class="attachment attachment-after">

      <?php print $attachment_after; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($more): ?>

    <?php print $more; ?>

  <?php endif; ?>

 

  <?php if ($footer): ?>

    <div class="view-footer">

      <?php print $footer; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($feed_icon): ?>

    <div class="feed-icon">

      <?php print $feed_icon; ?>

    </div>

  <?php endif; ?>

 

</div><?php /* class view */ ?>

这里面的第一行,<div class="<?php print $classes; ?>">,所负责的输出,就是我们区块最外面的div

里面的这段代码:

    <div class="view-content">

      <?php print $rows; ?>

    </div>

里面包含的<div class="view-content">,就对应区块HTML里面的:

2png.png 

最主要的是搞清楚这里面的对应关系。而目标HTML,在列表外面,也有两层DIV,只是IDCLASS不同,我们这里不妨简单的调整一下。这是调整后的。

<div id="jishu-right2">

  <?php print render($title_prefix); ?>

  <?php if ($title): ?>

    <?php print $title; ?>

  <?php endif; ?>

  <?php print render($title_suffix); ?>

  <?php if ($header): ?>

    <div class="view-header">

      <?php print $header; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($exposed): ?>

    <div class="view-filters">

      <?php print $exposed; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($attachment_before): ?>

    <div class="attachment attachment-before">

      <?php print $attachment_before; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($rows): ?>

    <div class="news">

      <?php print $rows; ?>

    </div>

  <?php elseif ($empty): ?>

    <div class="view-empty">

      <?php print $empty; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($pager): ?>

    <?php print $pager; ?>

  <?php endif; ?>

 

  <?php if ($attachment_after): ?>

    <div class="attachment attachment-after">

      <?php print $attachment_after; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($more): ?>

    <?php print $more; ?>

  <?php endif; ?>

 

  <?php if ($footer): ?>

    <div class="view-footer">

      <?php print $footer; ?>

    </div>

  <?php endif; ?>

 

  <?php if ($feed_icon): ?>

    <div class="feed-icon">

      <?php print $feed_icon; ?>

    </div>

  <?php endif; ?>

 

</div><?php /* class view */ ?>

   现在,回到区块视图的主题对话框,找到下面的重新扫描模板文件按钮,点击这个按钮:

3.png 

这是重新扫描后的样子:

5.png 

我们看到,Display output起作用的模板文件,已经变成了views-view--news--block.tpl.php。现在访问首页,观察区块HTML输出的变化。

6.png 

和目标已经非常接近了,而且现在的样式,也变了:

7.png 

其实我觉得,li里面的class,对于样式,没有任何影响,不过我们这里也通过模板文件覆写的方式,将它替换成我们想要的。


Drupal版本:

7 Attachement

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

对于最新公告,我们可以创建一个内容类型“公告”,然后创建对应的views区块,我们这里主要讲解的是主题制作,为了方便,我们就不再创建这个内容类型的,直接使用新闻的,然后将对应的标题改一下即可。我们这里这样操作:

1) 在视图的编辑页面admin/structure/views/view/news/edit,添加一个新的显示,显示类型选择“附件”。

1.png 

2) 在新显示里面,找到附件设置:

2png.png 

我们点击Attach to右边的“Not defined”链接,在弹出的对话框中选择区块:

3.png 

3) Global: Text area里面,我们将“最新新闻”修改为“最新公告”。

4.png 

4) 在分页器设置里面,将显示的条目数量,修改为7

现在,访问首页,观察样式的变化,“最新公告”已经显示出来的,但是样式和HTML都没有对上。

5.png 

   对应的HTML

6.png 

我们按照前面的办法,对附件的模板文件进行覆写。导航到我们刚才创建的目录sites\all\themes\snt\templates\override\views\news下面,创建文件views-view--news--attachment-1.tpl.php,里面的代码如下:

  <?php if ($rows): ?>

    <div class="news">

  <h2><?php print $header; ?></h2>

      <?php print $rows; ?>

    </div>

  <?php elseif ($empty): ?>

    <div class="news">

      <?php print $empty; ?>

    </div>

  <?php endif; ?>

创建文件views-view-list--news--attachment-1.tpl.php,里面的代码如下:

<?php print $wrapper_prefix; ?>

  <?php if (!empty($title)) : ?>

    <h3><?php print $title; ?></h3>

  <?php endif; ?>

  <?php print $list_type_prefix; ?>

    <?php foreach ($rows as $id => $row): ?>

      <li><?php print $row; ?></li>

    <?php endforeach; ?>

  <?php print $list_type_suffix; ?>

<?php print $wrapper_suffix; ?>

重新扫描模板文件,访问首页,样式已经完全达到了我们的要求:

7.png 

只不过,HTML输出,还有一点不同,就是最新公告外面,包了一层<div class="attachment attachment-before">,你知道怎么去除它么?在views-view--news--block.tpl.php文件中去除对应的代码即可。

对于这个例子,我曾经这样给人演示过,视图的格式,选用“Unformatted list”,然后最新公告,采用区块的形式,在模板使用views_embed_view嵌套到最新新闻里面。此外还使用了模板覆写views-view-fields--news--block.tpl.php

8.png 

对应的代码,views-view--news--block.tpl.php的为:

<div id="jishu-right2">

<?php print views_embed_view('news','block_1'); ?>

 

<div class="news">

  <?php if ($header): ?>

    <h2> <?php print $header; ?></h2>

  <?php endif; ?>

 

  <?php if ($rows): ?>

 

      <?php print $rows; ?>

 

  <?php elseif ($empty): ?>

 

      <?php print $empty; ?>

 

  <?php endif; ?>

</div>

</div>

views-view--news--block-1.tpl.php的:

<div class="news">

  <?php if ($header): ?>

    <h2> <?php print $header; ?></h2>

  <?php endif; ?>

 

  <?php if ($rows): ?>

 

      <?php print $rows; ?>

 

  <?php elseif ($empty): ?>

 

      <?php print $empty; ?>

 

  <?php endif; ?>

</div>

views-view-unformatted--news--block.tpl文件的:

<ul>

<?php foreach ($rows as $id => $row): ?>

  <li>

    <?php print $row; ?>

  </li>

<?php endforeach; ?>

</ul>

 

views-view-fields--news--block.tpl.php文件的:

<?php print $fields['title']->content ?>

 

这里将这些代码列出,供大家参考。同样的功能,解决办法可以有多个。对于views-view-fields.tpl.php,我们这里掌握这句代码即可:

<?php print $fields['field_name']->content ?>

这里的field_name就是主题信息对话框,对应字段的ID

9.png 

掌握这里的知识,就可以完全控制Views的输出了,没有多少难的。对于Views列表页面的覆写,和这里所讲的技术是一样的,希望大家看完以后,可以举一反三、触类旁通。现在,我们将右边的静态区块,去掉,因为动态的已经添加好了。



Drupal版本:

第五章 Drupal企业站

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

企业网站建设,它的复杂程度,远小于网上书店这个例子,而且这里的很多配置,在前面我们也都讲过了。但是,我还是想较为详细的记录整个过程,这样方便那些水平比较低的Drupal学习者,学习Drupal。在读者对象方面,我会优先满足Drupal水平比较低的读者群,优先为他们服务,这是出于市场的考虑,水平比较低的,更愿意购买我的资料;水平高的,我写的再好,他们也不买。


Drupal版本:

10 添加百度地图

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

此外,在“联系我们”页面,我还想加上百度地图,在地图上,让用户看到公司的所在位置。以前都是使用Google地图的,不过毕竟Google退出了中国,所以这里决定使用百度的。Drupal官方网站是有两个Gmap模块的,不过一个百度的也没有,我觉得Drupal在中国发展,离不开与本土系统的集成,这些都不是一个人可以完成的。

进入百度地图页面,http://map.baidu.com,在这里找到链接“地图API”,点击这个链接,进入页面http://developer.baidu.com/map,这是百度地图的开发者主界面。

1.png 

点击左边的“插件与工具”,找到“地图快速生成器”,点击这个链接。在这里,选好中心点,在对应的位置添加标注,保存标注。

2.png 

操作完成后,现在获取对应的代码:

3.png 

点击这里的“获取代码”按钮,我们就可以看到生成好的HTML源代码:

4.png 

    复制这些代码,直接将其粘贴到“联系我们”节点的正文字段里面,保存。现在地图就在Drupal中显示出来了。

5.png 

我们这里,也可以这样做,分析一下复制过来的源代码,把JS的部分,放到html.tpl.php文件中,在正文字段中,只保留生成地图所用的div,即可。


Drupal版本:

11 创建内容类型

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我做网站的时候,根据需要,通常首先创建内容类型,这样可以方便的向里面添加内容。接下来,导航到admin/structure/types,我们创建内容类型“新闻”,配置如下:

 

名字

新闻

机读名字

news

发布选项

已发表/多语言禁用

展示设置

显示 “作者和日期信息。”

评论设置 

关闭

菜单设置

在“新闻”的管理字段页面,admin/structure/types/manage/news/fields,为它添加一个已有字段“field_image”,注意,这两个地方的配置:
1.png

2.png 

对于文件、图片字段,我们通常会为它设置一个文件目录,这样文件上传后,都会存储在这个目录下,这样的好处就是文件夹结构比较清晰。很多刚学Drupal的人不知道配置这个地方。

值的数量,我们这里设置为了“不限”。由于一个字段可以同时追加到多个实体上去,对于字段本身的配置,你在这里修改了,其它地方也会跟着改,这个需要注意。

导航到分类的管理界面,admin/structure/taxonomy,在这里添加一个新的词汇表,配置如下:

名字

新闻分类

机读名字

news_category

然后向这个词汇表,添加三个分类术语,这是添加好的样子:

3.png 

现在,回到admin/structure/types/manage/news/fields,为“新闻”内容类型,添加一个“新闻分类”字段,具体配置如下:

标签

新闻分类

机读名称

field_news_category

字段类型

术语来源

控件

选择列表

词汇表

新闻分类

值的数量

1

 

创建内容类型“产品”,具体配置如下:

名字

产品

机读名字

product

发布选项

已发表/多语言禁用

展示设置

不显示 “作者和日期信息。”

评论设置 

关闭

菜单设置

在“新闻”的管理字段页面,admin/structure/types/manage/news/fields,为它添加一个已有字段“field_image”。具体配置和前面新闻的一样。我们可以创建一个新的图片字段,也可以共用一个。

导航到分类的管理界面,admin/structure/taxonomy,在这里添加一个新的词汇表,配置如下:

名字

产品分类

机读名字

product_category

然后向“产品分类”词汇表,添加三个分类术语,这是添加好的样子:

4.png 

  现在,回到admin/structure/types/manage/product/fields,为“产品”内容类型,添加一个“产品分类”字段,具体配置如下:

标签

产品分类

机读名称

field_product_category

字段类型

术语来源

控件

选择列表

词汇表

产品分类

值的数量

1

 

创建内容类型“解决方案”,具体配置如下:

名字

解决方案

机读名字

solution

发布选项

已发表/多语言禁用

展示设置

不显示 “作者和日期信息。”

评论设置 

关闭

菜单设置

主菜单

 

创建内容类型“服务”,具体配置如下:

名字

服务

机读名字

service

发布选项

已发表/多语言禁用

展示设置

不显示 “作者和日期信息。”

评论设置 

关闭

菜单设置

 

创建内容类型“成功案例”,具体配置如下:

名字

成功案例

机读名字

portfolio

发布选项

已发表/多语言禁用

展示设置

不显示 “作者和日期信息。”

评论设置 

关闭

菜单设置

对于内容类型上面的字段,我们在后面的项目建设中,还可以进一步的完善。



Drupal版本:

12 添加测试内容

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

进一步的添加内容,添加新闻,产品,解决方案。这是我添加的内容:

1.png

2.png 

3.png 


Drupal版本:

13 使用Views创建新闻列表

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

创建新闻列表页面

1.png 

     我们这里采用了内容摘要的形式,而没有采用字段的形式。在Views里面,字段的形式,是我更喜欢的,这个后面根据实际需要可以调整。我们在这里,最主要的是生成一个页面,有内容显示出来。我们这里使用了页面显示(page display)。

对于新闻列表,我通常采用这样的解决办法:

news

news/category/[tid]

这个时候,可以创建两个页面显示,一个负责所有的,一个负责具体分类下的。不过我们也可以采用这样的办法,只创建news一个页面显示,然后向它传递tid参数,这个时候只有一个内部路径,下面的路径都是有效的:

news

news/[tid]

这是两种不同的解决方式,注意两者之间的区别。除此以外,还有基于Panels的解决方式,我们在Drupal实战一书中,有了详细的介绍。

现在,我们向创建的视图里面添加一个上下文过滤器(contextual filter),在Drupal7里面,分类术语是以字段的形式出现的,新闻下面有一个新闻分类字段,我们按照这个分类字段过滤就可以了。找到这个上下文过滤器:

2.png 

选中,并点击应用按钮。注意,在接下来的对话框中,有这么几个配置选项:

3.png 

我们知道,新闻有三个分类:公司新闻、国内新闻、国际新闻。当显示对应的页面时,我们想让标题也显示成对应的。这个时候,我们可以使用这里的配置选项“覆写标题”(Override title)。这是我的配置:

4.png 

%1表示第一个参数,%2表示第二个参数。

我们将这个视图的标题从“news”改为“新闻”,保存视图。

现在访问news页面,就可以看到内容了:

5.png 

如果我们访问路径“news/4”,我们将会看到:

6.png 

页面标题,在这里显示出来的是对应的tid,而不是对应的分类术语名称,这个以前的时候好像介绍过,也是很多人容易犯的小错误。

我们导航到视图的编辑页面admin/structure/views/view/news/edit/page,找到上下文过滤器,添加一个新的上下文过滤器:

7.png 

在这里,“内容: Has taxonomy term ID”和“内容: Has taxonomy term ID (with depth)”都可以解决我们这里的问题,我更喜欢带有深度的这个。“内容: Has taxonomy term ID depth modifier”,这个是深度修正器,我很少用这个。

这里选中“内容: Has taxonomy term ID (with depth)”,点击应用按钮。这是我喜欢的配置:

8.png 

9.png 

我们将前面添加的上下文过滤器删除,只保留我们新增的这一个。保存视图。

现在访问news/4,此时将会得到:

10.png 

此时的页面标题显示正确。

我们这里,注意体会这两个上下文过滤器之间的区别,一个不行换用另一个即可。


Drupal版本:

14 Views附件显示

2.png

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们这里顺带讲解Views里面的一个基本配置,这个配置在前面的文章中没有介绍过。我们访问drupal.org,在它的首页,新闻部分,是这个样子的:

第一条,显示标题、时间、内容摘要,其余三条只显示标题。这是实际项目中,经常遇到的一个需求,使用Panels就可以很好的解决这个问题,创建两个view,一个取第一条,另一个取234条,两个加在一块,就可以解决这里的问题了。这是解决这个问题的基本思路,就是把它分成两个view。我们这里,给大家演示一下,具体的解决办法。

    这是我的初始配置,保存。

 

这是预览的效果,此时只列出来了三条,因为我们只有三条测试数据。

3.png 

我们先做一个区块显示,列出只包含标题的最新新闻。接下来,我们添加一个新的显示,这次,我们对于显示类型,我们选择附件。

4.png 

对于这个附件显示,我们首先为它添加更多的字段,注意这里只作用当前显示,这是添加后的样子:

5.png 

这里面,对于body字段,我们覆写了它的输出,截取150字显示,只显示纯文本。不显示字段标签。对于Post date字段,这是我喜欢的一种配置:

6.png 

对于“内容:链接”字段,我们这里主要配置了“Text to display(要显示的文本)

7.png 

在分页器的配置里面,我是这样配置的:

8.png 

实时的预览效果:

9.png 

在“附件设置”里面,找到“追加到”(Attach to)配置选项。

 

当前是未定义状态,我们修改一下,将它追加到区块显示上:

10.png11.png 

附件的位置默认是“Before”(在前面),除此以外,还有另外两个配置选项:

12.png 

我们这里,使用“Before”就可以了。现在切换到区块显示,实时预览的效果:

13.png 

只不过,这里面,第一条新闻重复显示了。现在我们对区块显示的配置做出调整,这是原来的分页器的配置选项。

14.png 

这是我修改后的:

15.png 

此时,区块只负责显示3条,并且跳过第一条。这里,跳过的这条,就是附件所负责显示的那条。保存这里的配置,这是保存后的实时预览:

16.png 

这个就是我们想要的。这里面,注意附件的用法,特别注意的是分页器里面的Offset的用法。Offset是跳过的意思:

17.png


Drupal版本:

15 创建产品列表视图

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们继续前进,按照同样的办法,创建产品列表视图,我这里是克隆现有的news视图,然后重命名修改而成。

1.png 


Drupal版本:

16 完善主菜单

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,导航到菜单的管理界面,找到主菜单,我们可以为它添加更多地菜单项了。

1.png 

我们这里主要添加产品新闻相关的菜单项,并调整了顺序。比如添加公司新闻这个菜单项的时候,输入路径时,要对准了,具体是哪个tid,不要写错了。


Drupal版本:

17 Superfish

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,菜单显示出来,还是一级的,没有显示二级子项。很多时候,我们想显示动态下拉菜单,这个时候有两个模块可供选择,一个是Nice menu模块,另一个就是Superfish模块,我们这里采用后者。在Drupal7下,Superfish模块更流行一些。

我这里下载的版本是superfish-7.x-1.9。这个模块,依赖于一个jQuery库,下载地址:https://github.com/mehrpadin/Superfish-for-Drupal/zipball/master。在sites\all下面创建libraries目录,然后将下载的jQuery库解压缩,然后复制到sites\all\libraries\superfish目录下。

1.png 

superfish模块解压缩后复制到sites\all\modules目录下。现在启用superfish模块。启用后,导航到区块管理界面admin/structure/block,可以看到:

2.png 

这是superfish模块提供的四个默认区块,我们这里使用第一个。点击它的配置链接,进入对应的区块配置页面,这里的配置选项很多。

3.png 

我们在这里,可以选择使用哪个菜单。如果菜单比较大的话,我们可以选择一个菜单项。菜单深度,-1表示所有,我们采用默认的即可。“Take "Expanded" option into effect.”,这个采用默认的不选中即可,可能很多人对于这个“展开”选项不是很了解,对于一个菜单链接,它有这么一个配置选项,你编辑一个菜单链接的时候,会找到的:

4.png 

我们这里不考虑核心的“展开”选项即可。

区块配置里面,接下来是superfish设置,里面的设置项很多:

5.png 

我们首先看到的是菜单类型,它分三种:水平、垂直、导航条。我们采用水平即可。对于样式,我们暂时先采用默认的“无”即可,这里面自带了多个样式可供选择,选择“无”的话,我们可以自定义样式。

再往下是有关速度的两个配置选项:

6.png 

我们采用默认的配置即可。

再往下是Path classPath levels

7.png 

对于拿不准的配置选项,我们使用默认的即可。往下继续看:

8.png 

Slide-in effect”就是鼠标移到包含子菜单项的菜单项上时,隐藏的子菜单项显示出来所用的效果,包括垂直、水平、对话框,如果安装了jQuery Easing 插件的话,这里将会包含更多的效果选项。Auto-arrows应该表示自动加上箭头;Drop shadows应该表示下拉阴影,从字面意思理解。下面还有一个“more options”,展开后有更多两个配置选项。

再往下,还有:

9.png 

我们这里就不逐个展开一一介绍了。采用默认的即可。我们把这个区块放到BartikFeatured区域,保存。预览一下效果先:

10.png 

功能有了,我们需要把原来的主菜单替换成现在的superfish区块。这又涉及到主题制作了。


Drupal版本:

18 CKEditor + IMCE

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在Drupal实战一书里面,安装过CKEditor模块,我们这里仍然使用这个模块,只不过对于文件上传,我们使用IMCE模块。这里简单的重复一下安装过程,我这次使用的版本是ckeditor-7.x-1.13,下载解压缩后,放到sites\all\modules目录下,这里原来已经有了一个,我们替换为最新的。接下来,去http://ckeditor.com/download下载最新的JS,我这里下载的是ckeditor_4.2_full,将它解压缩,复制到sites\all\libraries目录下。导航到模块的管理界面,启用CKEditor模块。现在,创建内容时,就可以使用所见即所得编辑器了。

此时,还有文件上传的问题,以前我们曾经使用过CKFinder,但是这个是收费的,破解也是有办法的,不过有时候还是需要替代的解决办法,其中一个是使用Insert模块,另一个就是使用IMCE模块。关于Insert模块,我曾经写过一篇教程,有兴趣的可以参考一下http://www.thinkindrupal.com/node/4993。我们这里使用IMCE。我这里使用的版本是imce-7.x-1.7。解压缩后,将其放到sites\all\modules目录下。启用后,我们可以导航到admin/config/media/imce,对它进行配置。

1.png 

它默认包含两个配置好的profile,一个是专为用户1准备的,另一个应该是为普通用户准备的。如果这两个不够用的话,可以新建一个。

2.png 

我们可以按照用户角色,为它分配profile,对于管理员,我们可以为它分配User-1;对于注册用户,可以为它分配Sample profile;对于匿名用户,可以不分配。实际上,对于一个企业站,特别是小企业站,里面只有一个用户。

我们编辑User-1,进入页面admin/config/media/imce/profile/edit/1,此时可以看到很多的配置选项。

3.png 

这里可以设置单个文件的大下,目录的大小限制,单个用户的文件总大小。以前曾经有朋友问过我类似的问题,就是为每个用户分配一个文件夹,这个文件夹是有大小限制的,和这里的很类似。

4.png 

再往下,可以配置文件的扩展名,允许上传哪些文件;文件的最大尺寸限制;一个操作可以作用于多少个文件。我们这里采用默认的即可。

5.png 

目录,对于用户1,默认使用跟目录,这个我们可以改一下,将它改为:

6.png 

定义文件夹名字的时候,除了%uid可用以外,还可以使用PHP,比如“php: return 'users/'.$user->name;”,就定义了目录“users/USER-NAME”。我觉得这里可以改进一下,支持token,这样会更方便一些。

再往下,是定义好的缩略图,但是我不知道这和核心自带的图片样式是否有直接关系。

7.png 

除了Profile的设置以外,在admin/config/media/imce页面,这里还有IMCE的常见设置:

8.png 

一般情况下,我们采用默认的即可,我觉得这里比较有用的是绝对路径这个配置选项。

我们讲了这么多,现在还是无法上传图片的,我们把这个问题解决了,导航到admin/config/content/ckeditor,找到:

9.png 

我们对“Advanced”进行编辑,在编辑页面,找到文件浏览器设置,展开后,可以看到

10.png 

我们这里,在文件浏览器类型里面选择IMCE,保存即可。为CKEditorFull profile做同样的操作。

如果使用IMCE,在所见即所得编辑器里面,点击图片按钮,我们将会看到这么一个弹出框:

11.png 

点击这里的浏览服务器按钮,有一个探出框:

12.png 

在这里,我们可以上传、编辑、删除、插入图片。


Drupal版本:

19 设置默认的文本格式

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

有一个小问题,是关于文本格式的,默认总是使用Filtered HTML,怎么才能将它改为Full HTML呢?

1.png 

以前在Drupal6的时候,有这样一个模块Better Formats可以解决这个问题。在Drupal7下,为了解决这个问题,我首先想到的就是这个模块。但是后来,通过实践,我发现,不装这个模块,也可以配置出来。这里介绍一下我的解决办法,导航到admin/config/content/formats,在这里可以看到:

2.png 

注意,这里,我们是可以通过拖拽调整文本格式之间的相对位置的。这是调整后的样子:

3.png 

调整后,保存即可。这样“Full HTML”,就成为了默认的文本格式了。

4.png 


Drupal版本:

20 创建一个新的文本格式

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

有时候,我们创建区块的时候,想使用“Full HTML”,但是又不想使用CKEditor,因为CKEditor会帮我们做一些我们不想要的工作。此时,我们可以创建一个新的文本格式:

1.png 

不为它启用任何的过滤器:

2.png 

如果,我们不在CKEditor的配置界面,做相应的配置的话,当我们选用“区块专用Full HTML”文本格式的时候,就不会加载CKEditor

3.png 

     这个在实际项目中,也是很有用的。



Drupal版本:

8 创建内容类型“关于我们”

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们导航到admin/structure/types,在这里添加一个新的内容类型“关于我们”,我喜欢为菜单上的每个栏目创建一个内容类型。具体配置如下:

 

16:10:45

关于我们


机读名字

about_us

发布选项

已发表/多语言禁用

展示设置

不显示 “作者和日期信息。”

评论设置 

关闭

菜单设置

主菜单

对于字段,我们使用默认的即可,现在导航到node/add/about-us,创建第一篇节点。在正文、标题里面输出相应的内容;文本格式选用Full HTML

1.png 

为这个节点指定一个菜单链接,菜单链接标题设置为“关于我们”;

2.png 

将它的URL别名设置为“aboutus”。OK,保存。

3.png 

这是保存后的效果:

4.png 

是的,这是真实的内容,我们在搭建一个真实的网站。接下来,创建“我们的团队”节点,还是使用“关于我们”内容类型。对于这个节点,它的配置,和前面的基本一样。注意在设置菜单链接的时候,我们把它的上级菜单项设置为了“关于我们”(node/1)。

5.png 

“我们的团队”,现在还只是一个静态页面,将来可以进一步的改进,比较加上团队成员的照片,采用更好的样式。如果将来公司发展起来了,人员比较多,可以专门的创建一个内容类型,比如说“员工(staff)”,用来列出员工的详细信息,而这个“我们的团队”页面,则可以替换成一个列表页面。这些都是后话。

这是创建好的样子。

 

6.png 

创建“公司历程节点”:

7.png 


Drupal版本:

9 使用webform创建“联系我们”

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来创建“联系我们”节点,这个节点里面包含一个表单。我们可以考虑使用webform模块,我这里使用的版本是webform-7.x-3.19。安装启用好了webform模块以后,我们就可以导航到node/add/webform,添加内容了。

首先,我们输入标题“联系我们”,正文里面输入以下内容:

<h3>地址:</h3>

北京市海淀区上地十街辉煌国际4号楼1308室。

地铁13号线,西二旗站下车,A口出,即辉煌国际大厦。

<h3>联系人:崔克俊 </h3>

Mobile: 13681445347

QQ: 372948992

文本格式选择“Full HTML”。指定一个菜单链接,并将上级菜单项设置为“关于我们”。这样就进入了webform的配置界面。

1.png 


Drupal版本:

9.1 为webform添加一个表单组件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

在这里面,添加一个表单组件(component),就像添加一个字段一样方便,所不同的是,这里的表单元素组件,不是字段。我们输入以下信息:

1.png 

然后,点击“添加”按钮。就进入了这个字段的配置界面。

2.png 6.png

这里的标签,就是我们在前面输入的“您的姓名”;这里的Field Key,就是这个表单元素的机读名字,系统会根据标签尝试自动生成这个机读名字,我们这里输入的是中文,而且没有装“transliteration”模块,所以我们需要在这里手动的输入Field Key,我这里输入了“name”;默认值,就是这个表单元素的默认值,我们这里不设置默认值,留空。

在输入默认值的下面,有“Token values”(令牌值)字段集。展开,可以看到详细信息:

3.png 

有时候,会用到这些。不过平时很少用。

再往下,是表单元素的描述,我们这里仍然留空,这里也有“Token values”可用。

4.png 

再往下,是表单元素的验证规则:

5.png 

这里面,有3个配置选项,必填、唯一、最大长度。我们这里只设置了必填。如果你觉得这里的验证规则不够用的话,可以下载安装Webform validation模块,我所写的Field validation模块,最初就是从这个模块迁移过来的。我们在实际的项目中,是用过Webform validation模块的。

最后是“显示”配置选项,上面的三个选项为:

 

这里面包括三项:宽度、前缀、后缀。从字面意思,就可以看出它的用途了。不过,对于表单元素的宽度控制,我比较喜欢使用CSS的方式,而不是在这里配置。

后面三项:

7.png 

标签显示,里面包含“位于上方”、“行内”、“无”三个配置选项,配置过Drupal字段显示的用户,对这三个选项应该不会陌生,和字段里面的配置类似;禁用,就是使得这个字段无法编辑,有时候我们想设置一个默认值,而这个默认值又不想让用户修改的时候,这个时候非常有用;私有,只有具有结果访问权限的用户,才能查看私有字段。

我们保存这些配置,即可。接下来,添加“邮箱”,配置如下:

标签

邮箱

Field key

email

类型

E-mail

必填

选中

标签显示

行内

其它采用默认即可。

添加“标题”,配置如下:

标签

标题

Field key

subject

类型

Textfield

必填

选中

标签显示

行内

 

添加“正文”,配置如下:

标签

正文

Field key

body

类型

Textfield

必填

选中

Resizable(可调整大小

选中

隐藏标签

选中

   其它采用默认即可。

这是添加好的样子:

8.png 

前台的显示效果:

9.png


Drupal版本:

9.2 配置邮件发送

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们点击webform标签下面的“电子邮件”子标签,进入页面node/4/webform/emails

1.png2.png 

我们在这里输入以下信息:

 

也就是输入站点的接收邮箱地址,这里使用了我自己的邮箱。点击添加按钮。进入页面node/4/webform/emails/new%3Foption%3Dcustom%26email%3Dg089h515r806%2540gmail.com。这里可以进一步的配置。

可以配置email的标题,这是我的配置:

3.png 

可以配置发件人的邮箱地址,这是我的配置:

 

可以配置发件人的名字,这是4.png我的配置:

5.png 

当然,还可以配置邮件的正文,这是默认的模板:

6.png 


这是我的配置:

7.png 

有时候,收件人不仅仅有一个,所以这里可以添加更多的收件人地址。比如抄送给发件人。


Drupal版本:

9.3 表单设置

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,点击“Webform”标签下的“Form settings”子标签,进入页面node/4/webform/configure

首先看到的是确认消息设置,这是我的配置:

1.png 

上面的文本域中,可以输入具体的确认消息,无非是一些感谢的话;下面是文本格式,我们这里选择了Full HTML;再往下是重定向的地址,默认为确认页面,也可以自定义URL,当然,还可以在当前页面显示,如果在当前页面显示的话,确认消息是以Drupal消息的形式显示出来的。

再往下,是提交配置选项:

2.png 

我们可以控制总提交次数,单位时间内可以有多少次提交,我们这里使用了默认的不限次数;也可以控制单个用户的单位时间内的总提交次数,我们这里使用了默认的不限;还可以设置这个表单的状态,开放还是关闭,这里当然是开放了,如果是一个问卷调查,有时间限制的,到了特定的时间,我们可以选择关闭表单。

再往下是控制哪些角色的用户可以提交这个表单:

3.png 

我们这里选择了“匿名用户”和“注册用户”,这表示所有的用户都可以提交表单。

最后,是高级设置,我们点击这个字段集,展开里面的内容:

4.png 

Available as block”,表示将这个表单以区块的形式显示。

Show complete form in teaser”,表示在节点摘要中显示完整的表单。

Show "Save draft" button”,表示显示“保存草稿”按钮。

Automatically save as draft between pages”,表示在多步表单中自动保存为草稿。

最后是提交按钮文本设置,我们这里输入“提交”两个字即可。很多人,使用Webform的时候,经常想修改这个提交按钮上的文本,但是不知道在哪里修改。在这里修改,记住了。另外一个常用的功能,就是以区块的形式显示,这个也经常用。


Drupal版本:

1企业站需求分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们首先,来看这个企业站。这个网站,就是为我自己的公司做的,所以做好做坏都一样,没有特别大的压力。做网站,首先需要有个需求,你这个网站,有哪些页面组成,每个页面的结构,最好写个文档。然后由设计人员做出设计图,然后再由美工人员做出静态页面,最后将它们转到Drupal上来。

 

有个软件Axure,用来画网站的原型图,比较方便,我安装了一个试用版,画了几个原型图,这样设计人员、美工人员以这些原型图为参考,做静态页面的话,就比较方便了。

主要内容包括:产品列表、产品详细信息、新闻列表、新闻详细信息、联系我们、解决方案、首页等页面。将来根据需要会有一些的调整,总体来讲比较简单。


Drupal版本:

2首页

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们先来看首页,它主要包含以下内容:

1),菜单是可以下拉的,鼠标移上,显示子菜单项。

1.png 

2.png 

 

2),首页主导航下面是一个幻灯效果,4张图片轮播,包括一个THINK in Drupal中文资料的图片、Drupal培训班的图片、开源模块的图片、一个有关网上书店系统的图片。

3),在下面是产品列表,三个推荐到首页的产品,包括图片,简单的描述。

4)再往下,左边是新闻列表,右边是两个静态区块。

5),最下面是页脚导航链接、页脚的版权信息等等。


Drupal版本:

2.1首页具体的原型图

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

首页具体的原型图: 上半部分:

1.png 

下半部分:

2.png


Drupal版本:

6主题的选型


我开始是这样想的,找个美工,做出静态页面,以这个为例子,写主题制作的内容,这样更完美一些,不过我找的美工朋友一直比较忙,没有时间。后来,我就以一个现成的例子来讲解主题制作,所以我们这里,就不用从静态页面转Drupal主题了。

我在实验的过程中,尝试了多个Drupal主题,比如omegabootstrapcorporatecleansimplecorp。但是,我最终决定,仍然使用Zen,基于Zen实现我们的主题制作,学会多个,不如学精一个。虽然有很多人说,Omega是最有前途的,bootstrap是最炫的,corporateclean是最漂亮的拿来就可以用的;但是它们也是有缺点的,Omega的用户量毕竟没有Zen的多,bootstrap不支持IE7IE8,在国外可以,在国内这是不行的,corporateclean里面的很多都是写死在里面的。一招鲜,吃遍天。

1.png 

最有前途的Omega

2.png 

干净漂亮的corporateclean

如果我们去阅读一下Zen的文档的话,发现里面有很多东西我们都没有用到,比如对HTML5的支持、对响应式的支持、对手机的支持,等等。从功能上来说,我觉得ZenOmega差不多。我之所以不喜欢Omega的原因,就是它里面添加了一个新的概念section,没有具体的用过,所以干脆不用这个。做项目是需要控制风险的。


Drupal版本:

3产品列表页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

产品列表页面的原型图:

1.png 

2.png 

 

注意:上面的绿线,是我截图的时候,不小心加上去的,应该没有这条线的。上半部分和下半部分是连在一起的,我截图的时候,屏幕太小,只能分开截取。后面都是这样的,分成上下两部分。

 

    这里面,左边是一个产品分类导航链接,包括三个分类:Think in DrupalDrupal培训班、开源模块。我把自己写的模块也算作产品了,虽然不赚钱,但是也是自己的作品。将来这个产品分类有可能会调整。左边的这个区块,就是主菜单里面“产品”菜单项的子菜单项。我们这里面,可以考虑使用menu block模块实现这个功能。右边是一个产品列表,每个产品项都有三部分组成,图片、标题、摘要描述。


Drupal版本:

4产品详细页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

产品详细页面的原型图:

1.png 

2.png 

 

这里面,左边仍然是一个产品分类导航链接;右边是产品详细,里面包括产品的标题、图片、正文。如果有多张图片的话,可以加个幻灯效果。


Drupal版本:

5联系我们页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

联系我们页面的原型图:

1.png 

2.png 

在这个页面的左边,是“关于我们”下面的子菜单项,包括公司历程、团队建设、联系我们等等。右边是联系信息,联系信息下面,是一个表单,用户可以通过这个表单,发送邮件信息。这里,我们可以考虑使用webform模块。

除此之外,还有解决方案,新闻列表页面、新闻详细页面,我们采用相同的结构即可。这个企业网站搭建好了以后,然后再多语言化,也就是支持英文版的。


Drupal版本:

7从头搭建企业站

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我下载了一个新的Drupal,解压缩后,放到了htdocs下面的yaiyuan1目录下面,这个名字后面加了一个1,是因为我前面已经做过一些测试用了,前面使用了yaiyuan这个名字。创建一个名为yaiyuan1的数据库,将Drupal安装起来。这个过程我们都熟悉。

接下来是,一些常见的配置,这些配置在Drupal实战一书里面都有介绍。比如,配置时区、配置日期格式、配置文件系统,特别是临时文件目录。

还有,就是安装Locale模块,然后启用简体中文,将简体中文设置为默认语言,导入简体中文的语言包。


Drupal版本:

第六章 企业站常用模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们现在来做主题,做Drupal网站的时候,可以一开始就做主题,也可以配置一些工作以后再做主题。每个人的习惯是不一样的,我们还基于Zen来实现想要的功能。


Drupal版本:

1 主题制作

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们将zen目录下的STARTERKIT复制到sites\all\themes下面,然后将其重命名为yaiyuan;将sites\all\themes\yaiyuan下面的STARTERKIT.info.txt重命名为yaiyuan.info,注意文件后缀名,对里面的信息做以下修改:

name        = Yaiyuan

description = Yaiyuan theme.

打开template.php文件,将里面的STARTERKIT替换为yaiyuan;打开theme-settings. php文件,将里面的STARTERKIT替换为yaiyuan。这样我们的这个子主题就初步制作完成了。

导航到主题的管理界面,admin/appearance,将我们新建的这个主题启用并设置为默认主题。现在访问首页,看到样式还是乱的。


Drupal版本:

10首页幻灯

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

创建内容类型“首页幻灯”,并为它添加字段“幻灯图片”和“指向链接”,和Drupal实战里面的一样。

1.png 

接着,安装Views slideshow模块。这个模块依赖于libraries模块,我们首先下载、安装、启用libraries模块,我这里使用的版本是libraries-7.x-2.1。接着安装Views Slideshow和它的子模块Views Slideshow: Cycle,这里使用的版本是views_slideshow-7.x-3.0。下载jquery.cycle,将其复制到了sites\all\libraries\jquery.cycle目录下面。

其实,我们这里涉及到一个软件复用问题,我们这里面的配置,在别的网站上已经配置好了,如何将它复制过来呢?这样,我们新建一个站点的时候,就不需要重复这里的配置了。在Drupal7下,最佳的解决方案是使用Features模块,比如这里的首页幻灯,我们就可以将它导出成feature。这样,新建一个Drupal站点的时候,通过安装这个Feature,所有的配置都会帮我们安装好了。对于内容类型的导入、导出,还有一个替代方案,就是使用Bundle copy,这个也不错,与Features模块相比,比较轻量级。

我们现在从网上书店中,将首页幻灯的视图导出:

2.png 

   导出后的样子:

3.png 

我们将文本域中的代码复制下来,进入yaiyuan1站点的Views管理界面,这里有一个导入链接:

4.png 

点击这个链接,进入页面admin/structure/views/import,我们将刚才复制的代码粘贴过来即可:

5.png 

之后点击导入按钮。进入已导入视图的编辑界面,点击视图的保存按钮。导入过程完成。原来的视图中,图片的显示是使用了图片样式的,而这个样式我们这里没有定义,所以我们这里采用了默认的原始图片,这个没有关系。我们现在定义这个图片样式。

以前,我们在网上书店的例子中,是这样定义图片样式的,660_250,这种方式有这种方式的好处,就是说一看就知道图片的大小。我们这里换种方式。导航到图像样式的管理页面admin/config/media/image-styles,在这里添加一个样式frontbanner,专门用于首页幻灯。添加好这个样式后,我们暂时不为它添加任何效果。对于前面视图中,幻灯图片的显示,我们就可以为它使用这个图片样式了。

6.png 


Drupal版本:

11 Features模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们这里顺带介绍一下Features模块的用法。下载、安装、启用Features模块,当前使用版本为features-7.x-2.0-rc1。启用后,导航到admin/structure/features,这是Features模块的管理界面:

1.png 

当前还没有任何已经创建的features,我们可以点击右上角的“创建特性”(create feature)链接,进入页面admin/structure/features/create

2.png 

在名称里面输入“frontbanner”,版本里面输入“7.x-1.0-beta1”,在右边的组件中,为内容类型、views、图像样式选择frontbanner对应的部分:

3.png 

此时,Feature会自动的为我们选好字段、字段实例、依赖关系:

4.png 

点击左边的“Download feature”(下载特性)按钮,我们就可以下载这个特性了。下载后,我们得到这样一个文件:

5.png 

将它解压缩,我们得到一个完整的模块:

6.png 

我们现在可以将这个模块安装到我们的站点上了,或者当一个站点需要幻灯时,我们就可以直接安装这个模块。

Features模块,能够很好的解决将配置信息从数据库中导出成Drupal代码,方便功能在不同站点之间的迁移。现在,在Features模块的基础之上,又出现了Apps模块,现在的很多发行版都基于Apps的概念。


Drupal版本:

12 使用Panels创建首页

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们继续前进,下载、安装、启用Panels模块,我用的版本是panels-7.x-3.3.zip。我同时下载、安装了panelizer模块,使用的版本是panelizer-7.x-3.1Panelizer能够做很多Display suite模块做的工作,有机会的话,会简单的介绍一下Panelizer。我们这次启用以下模块:Page manager, Views content panes, Mini panels, Panelizer, Panels。注意Page manager, Views content panesCtools模块里面的子模块。

我们创建一个Panels页面,名字、路径都设置为frontpage,同时把这个页面设置为首页;布局采用1列通栏布局;禁用Drupal区块/区域;内容里面先后添加:首页幻灯、企业愿景、产品推荐、新闻等。

1.png 

我已经做好了,先来看幻灯的效果:

2.png 

首先是为图片样式frontbanner添加了一个缩放并裁切的效果,大小开始为950x300,后来调整为960x300。然后就是调整CSS,这里参考的是京东的效果。

 

#content .view-frontbanner  .views-slideshow-controls-bottom {

  float: right;

  margin-top: -35px;

  margin-right: 10px;

  position: relative;

  width: auto;

  z-index: 9;

}

#content .view-frontbanner  .views-slideshow-controls-bottom .views-slideshow-pager-fields {

  float: right;

  width: auto;

}

#content .view-frontbanner  .views-slideshow-controls-bottom .views-slideshow-pager-field-item {

  display: block;

  float: left;

  height: 20px;

  margin-right: 10px;

  padding: 0;

  width: 20px;

}

 

#content .view-frontbanner  .views-slideshow-controls-bottom .views-content-counter {

    background: none repeat scroll 0 0 #999999;

    border-radius: 12px 12px 12px 12px;

    color: #FFFFFF;

    cursor: default;

    display: inline-block;

    text-align: center;

    height: 22px;

    line-height: 22px;

    width: 22px;

margin-right:20px;

margin-bottom: 10px;

}

 

#content .view-frontbanner  .views-slideshow-controls-bottom .active .views-content-counter{

    background: none repeat scroll 0 0 #E4393C;

}

主要是把背景设置为了圆形,这里面没有使用背景图片,使用的是border-radius

再往下是企业愿景,这里使用了Panels的自定义内容功能,没有使用静态区块;还使用了Panel窗格自带的圆角样式:

3.png 

    可能部分读者不知道“自定义内容”在哪里,为Panels区域添加内容的时候,弹出框的左边下面部分的这个链接就是:

4.png 

再往下是首页的产品列表,我在products视图里面创建了一个区块显示,用来显示首页的内容。我们先来看效果:

5.png 

这是对应的视图显示:

6.png 

这里使用了我们常见的Views字段,格式为Grid(格子),一行显示两个,总共显示4个。注意字段之间的先后顺序。我把图像放在了前面,其次是标题,最后是正文,这主要是为了调整CSS时方便;对于正文的输出,我们截取前面50个字;此外,我们还加了一个过滤条件,把产品分类中的开源模块部分过滤掉了。对应的CSS如下:

.view-display-id-block_1 td {

  padding-right:20px;

}

.view-display-id-block_1 td  .views-field-field-image{

  float:left;

  margin-right:10px;

}

.view-display-id-block_1 td  .views-field-field-image img{ 

  border: 1px solid #E0E0E0;

}


Drupal版本:

13 Mini Panel的使用

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

再往下是首页的新闻和联系我们:

1.png 

我把这两部分放在了一个Mini Panel里面了。一个Mini Panel就是一个区块,只不过对于这个区块,我们也可以进一步的细化它的布局。

这是Mini panel的配置:

2.png 

3.png 

4.png 

对于左边的这个新闻区块,我们使用的是test news这个视图,稍微做了一下调整,主要就是隐藏了时间、read more这两个字段,调整了一下正文截取的长度,这里取80个字。


Drupal版本:

14 主导航样式调整

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我后来对主导航菜单的样式做了进一步的调整:

#navigation .sf-menu.sf-style-simple.sf-vertical li, #navigation .sf-menu.sf-style-simple.sf-horizontal li li, #navigation .sf-menu.sf-style-simple.sf-navbar li li li {

    border-bottom: 1px solid #E0E0E0;

}

 

#navigation .sf-menu.sf-style-simple.sf-horizontal > li:first-child {

    border-bottom-left-radius: 0px;

    border-top-left-radius: 0px;

}

 

#navigation .sf-menu.sf-style-simple.sf-horizontal > li:last-child, #navigation .sf-menu.sf-style-simple.sf-horizontal li li > ul > li.firstandlast, #navigation .sf-menu.sf-style-simple.sf-vertical li li > ul > li.firstandlast, #navigation .sf-menu.sf-style-simple.sf-navbar li li li > ul > li.firstandlast {

    border-bottom-right-radius: 0px;

    border-top-right-radius: 0px;

}

 

#navigation .sf-menu.sf-style-simple li {

background-image : none;

height:45px;

}

主要是因为菜单项部分的背景图片高了一小点,把下面的幻灯图片遮住了一点。通过调整解决了这个问题。我有一个客户告诉我说,做Drupal开发,主要就是调CSS比较麻烦。说得很有道理。

最后说一个问题,前面我们在联系我们页面加了一个百度地图不是,现在这个地图显示不出来了:


     原来可以显示,现在显示不了了。怎么办?我们可以把主题切换到默认的Bartik上去,结果可以正确显示。遇到问题,我们首先清除缓存,其次可能就是切换主题了。那现在怎么解决呢?需要在主题层解决。不过对于这个问题,我们不必深究。再变通一下,使用QQ截图功能,把生成的地图转成图片的形式,我们这里通过图片来显示地理位置,同样可以解决问题。

    这样,我们的这个企业站,基本上就做完了。还有几个功能,比如客户、成功案例没有实现。此外产品列表页面,产品详细页面也需要美化一下,这些从技术上来讲,都已经讲过了。我们接下来,把精力放在多语言功能的实现上面来。


Drupal版本:

2 主菜单样式的调整

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

1.png 

我们两次输出了主菜单,可以把默认的输出给禁用。导航到默认主题的配置界面,admin/appearance/settings/yaiyuan,找到配置选项:

2.png

将这两个禁用,保存。这样就只显示一次了。

3.png 

我们配置这个区块,将区块的标题设置为“<none>”,这样就可以不显示“Main menu”两个字了。此外,将这个区块,放到“Navigation bar”区域里面。

导航到默认主题的配置界面,admin/appearance/settings/yaiyuan,找到配置选项:

4.png 

Logo禁用,我不想显示“禅”。现在整个界面,还是比较朴素的。

5.png 

我们要调CSS样式。首先来调这个主菜单的样式,我们前面提到Superfish自带了,多个样式,我们配置这个superfish区块,将它的样式设置为“Simple”,保存。效果如下:

6.png 

这是我最满意的一个效果了。有多满意,90%的都是我想要的。我是怎么知道使用“Simple”这个样式的呢?我是一个一个的测试出来的,它自带了这么多的样式:

7.png 

我是从上到下,逐一测试后,感觉Simple这个样式,最符合我们这里的需要。除了一些常用的和我用过的模块以外,对于其余的模块,我并不比大家高明多少,很多时候就是这样,对于所有的配置选项,逐一测试,然后从中选出来自己认为最合适的。

此外,我觉得Spring这个样式,也还不错,只不过不是我这里想要的。

打开yaiyuan.info文件,找到CSS的部分,向里面追加这么一段话:

stylesheets[all][] = css/custom.css

然后在sites\all\themes\yaiyuan\css下面创建文件custom.css。我们将我们覆写的CSS代码都放在这个文件中。向里面添加以下代码:

#main #navigation #superfish-1 {

 margin-left:300px; 

 

}

#main #navigation{

  background: url("../images/simple-background-active.png") repeat-x scroll left top #EEEEEE;

   border-bottom-right-radius: 8px;

   border-top-right-radius: 8px;

   border-bottom-left-radius: 8px;

   border-top-left-radius: 8px;

       -moz-border-bottom-colors: none;

    -moz-border-left-colors: none;

    -moz-border-right-colors: none;

    -moz-border-top-colors: none;

    border-color: #E0E0E0 #E0E0E0 #E0E0E0 #FFFFFF;

    border-image: none;

    border-style: solid;

    border-width: 1px;

height:51px;

}

 

我们还需要把simple-background-active.png图片,从sites\all\libraries\superfish\style\simple\images目录下,复制到sites\all\themes\yaiyuan\images。我原来还以为这个图片是放在modules目录下面的,找了一下没有找到,通过Firebug,查看CSS代码的出处,才找到了这里。

其实,我就是想让主导航居中显示,同时为整个导航条加个背景。也就是在默认的基础上,再美化一点而已。我第一次见到border-bottom-right-radius的用法,以前真不知道可以这样设置圆角。这是调整后的样子。

8.png 

做到这里的时候,主题的响应式已经被我破坏掉了,所以我们还采用固定宽度布局,960,这种最常见的布局。

打开yaiyuan.info文件,找到CSS里面的这段代码:

stylesheets[all][] = css/layouts/responsive-sidebars.css

将它注释掉,然后在它的下面,加上:

stylesheets[all][] = css/layouts/fixed-width.css

然后将主导航的margin-left300改为250。先这样吧,后面会继续调整样式。


Drupal版本:

3 Menu Block

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看另一个功能,当我们访问产品页面products,希望左边显示一个区块,这个区块里面列出产品分类的链接。就是把这部分内容:

1.png 

同时显示在左边栏。这个是一个非常常见的需求,在很多企业站里面,随处可见。Drupal里面,有一个现成的模块,可以做这件事。这就是Menu Block模块。

我们下载、安装、启用这个模块,我这里使用的版本是menu_block-7.x-2.3。启用后,导航到区块的管理界面admin/structure/block,此时会看到这样的一个链接:

2.png 

我们点击“Add menu block”链接,创建一个菜单区块。

3.png 

这是菜单区块的基本配置选项,里面包括选用哪个菜单,开始的层级,最大深度。如果点击上面的高级选项标签,将会显示更多的配置选项,比如:

4.png 

5.png 

6.png 

对于这些,我们采用默认的即可。我们将这个新建的区块放在第一边栏区域里面,保存。

7.png 

     这里的“主菜单 (levels 1+)”就是我们新建的菜单区块。我们把这个区域里面的其它区块禁用,我们不想把它们显示在这里。现在,访问产品页面products,左边是这个样子。8.png

和预期的有区别。我们对这个区块进行再配置,将开始层级改为2

9.png 

这下好了:

10.png 

访问新闻页面,我们得到:

11.png 

访问“关于我们”页面,访问“解决方案”页面,左边都做相应的变化。这就是我们想要的,只不过样式需要调整。

以前的时候,我是怎么解决这个问题的呢?为产品、新闻、关于我们、解决方案,分别创建一个菜单,然后将这个菜单对应的区块放在左边栏,根据区块的可见性设置,控制每个区块的显示,也能够实现同样的效果。但是都没有我们这里的这种方式简洁。

如果,我们多点几下的话,还是会发现更多一些的问题的。比如我们访问产品分类页面products/1products/2products/3,这些都是没有问题的,左边都会显示。

12.png 


Drupal版本:

4 Menu Position模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

如果,我们点击进入一个产品节点,或者一个新闻节点的时候,左边的这个区块消失了。左边栏(第一边栏)没有了。

1.png 

这个不是我们想要的,如果采用前面我们所说的土办法,没有任何问题,不过现在就不行了。怎么解决这个问题呢?此时我们可以尝试一下Menu Position模块。

我们下载、安装、启用Menu Position模块,我这里使用的版本是menu_position-7.x-1.1。安装后,我们可以导航到它的管理界面admin/structure/menu-position

2.png 

我们点击这里的添加链接,来添加一个“菜单位置规则”(menu position rule)。这样就进入了页面admin/structure/menu-position/add

3.png

     首先看到的是管理标题和父菜单项。

4.png 

接着看到的是条件设置,也就是在什么条件下激活上面选中的菜单项。这里又分5种情况,内容类型、页面、用户角色、语言、分类。

我们先测试一个简单的,在管理标题里面输入“产品”,父菜单项选择“产品”,内容类型里面选中“产品”,保存这个规则。现在访问一个产品节点,左边的区块显示了出来:

5.png 

只不过,除了显示出来以外,还多显示了一些信息,竟然把节点标题也显示在左边的区块里面了。

我们回到admin/structure/menu-position,看右上角,有两个标签链接:

6.png 

点击这里的“设置”标签,进入页面admin/structure/menu-position/settings

7.png 

这里只有这么一个配置,里面包含三个选项:把当前页面的标题插入到菜单树中;把规则的父菜单项标记为“active”;不为任何菜单项标记“active”。默认选中的是第一项,我们把它改为第二项,保存。这样访问刚才的页面,问题解决了:

8.png 

如果细心一下,可以使用Firebug查看一下页面源代码,此时我们可以看到,主菜单里面的“产品”菜单项,里面多了一个类“active”。 

9.png 

这是Menu position模块追加进来的。我们这里只是没有为active定义样式而已,在实际项目中,访问一个产品节点页面,激活产品列表页面对应的菜单项,这个需求也很常见,Menu position模块就是用来解决这个问题的。

我们现在再进一步,当访问节点“Drupal培训班--------Drupal主题制作”(node/9)时,我们不去激活“产品”菜单项,而是去激活它的子菜单项“Drupal培训班”(products/2)。

我们创建一个新的menu position规则,在管理标题中输入“Drupal培训班”,在父菜单项中选择“Drupal培训班”,内容类型中选择“产品”,我们这里除了内容类型以外,还设置一下分类,具体配置如下:

10.png 

设置好了以后,保存。重新访问node/9页面。没有任何变化,和原来一样。这个时候我猜测,是前面创建的规则在起作用。所以可以把它禁用(或者删除)了先。

11.png 

在规则列表中,有“启用”这么一列,取消选中,保存即可。现在访问node/9,一切正常了:

12.png 

通过Firebug可以看到这个菜单项的链接上面,有了active: 

13.png 

这里值得一提的是,面包屑也正常了,就是我们想要的。我们已经知道具体的实现方法了。接下来,重复这一过程。将产品下面剩余的两个分类和新闻下面的三个分类,都创建类似的规则。这是添加好的样子:

14.png 

经测试,新闻节点页面,左边区块正常,面包屑正常:

15.png 

这里顺便说一下,北大图书馆的网站,也是使用的Drupal,左边区块也是使用的Menu Block,但是他们没有使用menu position模块,比如他们的图书馆新闻节点页面,就无法显示左边区块。

我点击多个页面,所有的面包屑都是正确设置了的,这个时候,我们就不需要去安装Custom breadcrums, path breadcrumbs这样的模块了。

在当前主题的配置页面,admin/appearance/settings/yaiyuan,里面有面包屑的配置选项: 

16.png 

这是主题层提供的配置选项,包括是否显示面包屑,面包屑的分隔符,在面包屑中显示首页链接,在面包屑的最后,追加一个分隔符,在面包屑的结尾追加内容标题。我们把最后这个复选框选中,保存。


Drupal版本:

5 使用Nodequeue控制产品列表

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

做企业网站的时候,比如对于产品列表页面,很多客户有这样的需求,想控制列表中的产品的位置,能够随心所欲的控制先后顺序,把重要的产品显示在前面。比如对于公司的团队成员,如果每个成员对应一个节点页面的话,那么对于成员的列表页面,这个先后顺序也是很重要的,大领导排在前面,小领导排在后面。这个时候,就无法按照节点的发布日期、修改日期排序了。

这一问题的解决方案很多,常见的有Weight模块、DraggableViews模块、Nodequeue模块。我们这里面介绍Nodequeue模块,我在实际的项目中,使用过这个模块,而且这个模块的安装量比前面两个要高出一些。

我们下载、安装、启用Nodequeue模块,我这里使用的版本是nodequeue-7.x-2.0-beta1。启用后,我们导航到它的管理界面admin/structure/nodequeue

 

点击这里的添加链接“Add simple queue”,进入页面,admin/structure/nodequeue/add/nodequeue,这是我的配置:

1.png 

2.png 

3.png 

其它采用默认配置。保存。现在,当我们访问一个产品节点的时候,就会看到一个nodequeue标签:

4.png 

而在这个节点的下面,我们可以看到这样的一个链接:

5.png 

点击这个链接,就会刷新这个页面,当前节点就会被添加到我们创建的队列中来。此时,这里的链接就变成了:

6.png 

现在点击“Nodequeue”标签,进入页面node/6/nodequeue,我们会看到:

7.png 

也可以在这个标签页面下面,把节点添加到队列中或者从队列中删除。我们把所有的产品节点,都添加到我们创建的产品队列中来。现在查看这个产品队列。我们看到:

8.png 

此时,可以通过拖动,来调整节点之间的相对位置,这个我们就比较熟悉了。此外,在这个页面,还有下面的这些按钮:

9.png 

Reverse表示反向的意思,shuffle表示随机的意思。

现在,导航到Views的管理界面,我们可以看到与Nodequeue相关的视图。

10.png 

每当创建一个队列的时候,系统会自动的创建一个视图。我们编辑一下这个视图,看一下它有什么特别的配置。有两个地方需要注意:

11.png 

首先是关联关系这里,加了一个从节点到队列的关联关系。其次是排序标准:

12.png 

明白了这两点以后,我们完全可以不使用这个视图,而采用修改原有视图的办法。我们编辑视图products,为它添加关联关系“Nodequeue: Queue”,做以下配置:

13.png 

为它添加排序标准“Nodequeue: Position”,并按照升序排列,删除原来的排序标准“内容: Post date (desc) ”。保存视图。现在产品列表页面中,产品就会按照我们队列中的先后位置排序了。

每当创建一个队列的时候,会自动的创建一个视图,如果你觉得自动创建的这些视图没有什么用处的话,我们可以取消这个自动创建视图的功能。在Nodequeue的配置页面,admin/structure/nodequeue/settings: 

14.png 

取消对最后一个复选框的选中即可。


Drupal版本:

6 样式调整

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

样式调整


Drupal版本:

6.1 Menu block​样式调整

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,我们简单的调整一下左边区块的样式,向custom.css里面添加以下CSS代码:

#main #block-menu-block-1 h2{

    background: none repeat scroll 0 0 #E9E9E9;

    border-bottom: 1px solid #D5D5D5;

    border-top: 1px solid #F2F2F2;

    font-size: 100%;

    font-weight: bold;

    text-align: center;

}

#main #block-menu-block-1 ul li {

list-style-image: none;

    list-style-type: none;

}

 

#main #block-menu-block-1 ul li  a {

    color: #222222;

    text-decoration: none;

}

#main #block-menu-block-1 ul li  a.active {

    color: #0041A0;

}

调整后的效果:

1.png 

不是很专业,但是比没有样式的时候,好了很多。


Drupal版本:

6.2 对面包屑和标题样式调整

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

对面包屑和标题的样式,也做一下调整,添加以下CSS代码:

#main #content .breadcrumb{

  margin-top: 20px;

  margin-left: 20px;

  margin-bottom: 10px;

}

 

#main #content  #page-title{

  text-align: center;

  margin-top: 20px;

  margin-bottom: 20px;

}

#main  a {

    text-decoration: none;

}

调整后,效果如下:

1.png


Drupal版本:

6.3 调整页脚区域

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,页脚区域还是比较单薄了,除了“Powered by Drupal”以外,什么都没有。我们首先创建一个“Footer menu”菜单,并为它添加以下菜单项:

1.png 

接着,导航到区块管理界面,添加一个静态区块“版权所有”,并将它放到页脚区域里面。将“Footer menu”菜单对应的区块,也放到页脚区域中。将“Powered by Drupal”区块禁用。去除“Footer menu”和“版权所有”的区块标题。现在是这个样子的:

2.png 

而且是居左显示。我们需要在样式上做出调整。向custom.css文件中添加以下代码:

#footer{

  background-color: #F6F6F2;

  font-size: 0.92307em;

  line-height: 1.5em;

  padding: 30px 0 20px;

}

 

#footer .block{

  color: #666666;

  text-align: center;

}

#footer ul.menu {

  margin-bottom: 10px;

  overflow: hidden;

}

#footer ul {

  clear: both;

  list-style: none outside none;

  text-align: center;

  padding: 0 0 0 0px;

}

 

#footer li {

  display: inline;

}

#footer a {

  font-size: 14px;

  margin: 0 10px;

text-decoration: none;

}

 

#footer li.leaf {

  list-style-image: none;

  list-style-type: none;

  margin: 0;

  padding: 0;

}

此外,对于版权所有区块,我们为它加上两个<p>标签。

3.png 

上面的那段CSS代码其实是从网上书店那个例子中复制过来的。最后,样式基本符合预期的要求:

4.png 

说到站点地图了,其实我们可以创建一个普通的page页面,手工的编辑好站点地图后,放进去。还有一个办法,是安装Site map模块,由这个模块生成站点地图页面。生成好了以后,我们可以将这个页面上面的内容复制下来,然后粘贴到一个静态页面中来,这样我们就可以禁用这个模块了。



Drupal版本:

7 Site map模块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们下载、安装、启用Site map模块,我这里使用的版本是site_map-7.x-1.0。导航到admin/config/search/sitemap,这是我的配置:

 

1.png2.png 

其余的采用默认配置。Site map模块能够基于菜单、分类生成站点地图,在生成站点地图时,我们可以选择包含哪些菜单、哪些分类,对于分类的话,可以进一步的控制。我们这里是基于主菜单生成的站点地图。这个时候访问页面sitemap,就能够看到站点地图了。我们的站点地图是根据主菜单生成的。显示出来的效果是这样的:

3.png 

在这个页面的左边,没有区块,所以通栏只有主内容,有点单薄。此外,上面的“Main menu”也不是我们想要的。现在,我们可以将这个页面的链接内容复制下来,创建一个page类型的节点,复制过去,保存。然后将站点地图这个菜单链接的URL指向新建节点。禁用Site map模块。


Drupal版本:

8 创建“联系我们”区块“

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们创建“联系我们”区块,将它放在第一边栏里面,区块的正文中输入以下信息:

1.png 

保存后,调整一下CSS代码:

#main #block-block-2 {

    border-right: 1px dashed #B4BDC4;

margin-top: 20px;

}

 

#main #block-block-2 h2{

    background: none repeat scroll 0 0 #E9E9E9;

    border-bottom: 1px solid #D5D5D5;

    border-top: 1px solid #F2F2F2;

    font-size: 100%;

    font-weight: bold;

    text-align: center;

}

#main #block-block-2 ul li {

  font-size:12px;

}

这是调整后的样子:

2.png 

这个基本满意了,我看到站点名字“亚艾元”三个字比较小,我们这里也没有使用Logo图片,不够显眼。这里通过CSS将字体调的更大一点:

#header #site-name {

    font-size: 4em;

margin-left:20px;

}


Drupal版本:

9 首页

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

整个网站,我已经基本满意了,唯一的不足是字体的配置比较灰,这个不影响功能。除此以外,还差什么呢?首页还没有做呢。是的,做网站的时候,可以先做首页,也可以放在后面。首页用什么?我还是用Panels。幻灯用什么?我还是用Views slideshow



Drupal版本:

第四章 Drupal源码分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com 

     很多人,刚学Drupal的时候,常常会问这样的问题,Drupal是怎么生成这个页面的。比如,我们创建了第一个Drupal节点页面,“关于我们”,很多人,想知道这个页面是怎么组装起来的。想知道大致的流程。

问这个问题的人很多,所以每当我给人讲解Drupal模块开发的时候,首先带领学生分析的,就是这个节点页面的组装过程。有的人听着听着,就睡着了,而有的人则会听得津津有味。我们这里所讲的这些东西,就是这样,对你使用Drupal没有直接的帮助,但是能够帮助你更好的理解Drupal背后的机理,帮你建立学习Drupal的信心。

1.png 

这就是我们创建的第一个节点页面,除了节点本身,还有导航链接,左边栏的区块,页脚的区块,这些东西共同的组成了一个页面。这个节点的路径为node/1,我们也可以使用别名aboutus


Drupal版本:

10 完成阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看阶段7,也就是完成阶段,对应的代码:

        case DRUPAL_BOOTSTRAP_FULL:

          require_once DRUPAL_ROOT . '/includes/common.inc';

          _drupal_bootstrap_full();

          break;

在这里,首先加载includes/common.inc文件,接着将这个阶段的工作,委托给了_drupal_bootstrap_full函数,注意这个函数位于common.inc文件中。我们打开这个文件,找到这个函数的定义:

function _drupal_bootstrap_full() {

  static $called = FALSE;

 

  if ($called) {

    return;

  }

  $called = TRUE;

  require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc');

  require_once DRUPAL_ROOT . '/includes/theme.inc';

  require_once DRUPAL_ROOT . '/includes/pager.inc';

  require_once DRUPAL_ROOT . '/' . variable_get('menu_inc', 'includes/menu.inc');

  require_once DRUPAL_ROOT . '/includes/tablesort.inc';

  require_once DRUPAL_ROOT . '/includes/file.inc';

  require_once DRUPAL_ROOT . '/includes/unicode.inc';

  require_once DRUPAL_ROOT . '/includes/image.inc';

  require_once DRUPAL_ROOT . '/includes/form.inc';

  require_once DRUPAL_ROOT . '/includes/mail.inc';

  require_once DRUPAL_ROOT . '/includes/actions.inc';

  require_once DRUPAL_ROOT . '/includes/ajax.inc';

  require_once DRUPAL_ROOT . '/includes/token.inc';

  require_once DRUPAL_ROOT . '/includes/errors.inc';

 

  // Detect string handling method

  unicode_check();

  // Undo magic quotes

  fix_gpc_magic();

  // Load all enabled modules

  module_load_all();

  // Make sure all stream wrappers are registered.

  file_get_stream_wrappers();

 

  $test_info = &$GLOBALS['drupal_test_info'];

  if (!empty($test_info['in_child_site'])) {

    // Running inside the simpletest child site, log fatal errors to test

    // specific file directory.

    ini_set('log_errors', 1);

    ini_set('error_log', 'public://error.log');

  }

 

  // Initialize $_GET['q'] prior to invoking hook_init().

  drupal_path_initialize();

 

  // Let all modules take action before the menu system handles the request.

  // We do not want this while running update.php.

  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {

    // Prior to invoking hook_init(), initialize the theme (potentially a custom

    // one for this page), so that:

    // - Modules with hook_init() implementations that call theme() or

    //   theme_get_registry() don't initialize the incorrect theme.

    // - The theme can have hook_*_alter() implementations affect page building

    //   (e.g., hook_form_alter(), hook_node_view_alter(), hook_page_alter()),

    //   ahead of when rendering starts.

    menu_set_custom_theme();

    drupal_theme_initialize();

    module_invoke_all('init');

  }

}

这个函数里面做的第一个工作,就是加载文件,path.inctheme.incpager.incmenu.inctablesort.incfile.incunicode.incimage.incform.incmail.incactions.incajax.inctoken.incerrors.inc就是在这个时候加载的。

unicode_check用来判断字符串处理方法,Drupal能够处理各种字符,这里需要检查一下PHPunicode的支持。fix_gpc_magic,这个函数比较陌生,我后来查看函数的具体定义,和相关的文档,才弄明白的这个函数的作用,这个函数最终调用的是stripslashes,它的作用是去除转义反斜线,比如:

\') 转换为('

双反斜线(\\)被转换为单个反斜线(\

 

module_load_all负责加载所有的module文件。file_get_stream_wrappers负责注册Drupal的流包装器(stream wrappers)。

$test_info这段代码,与simpletest有关,我们这里没有用到,所以跳过。

drupal_path_initialize负责路径的初始化,具体工作就是正确的设置$_GET['q']

最后设置当前的主题,初始化主题,触发hook_init钩子。到此,整个引导指令就全部完成了,Drupal已经启动起来了。


Drupal版本:

11 页面内容的生成与组装


Drupal启动以后,接下来做什么?我们回到index.php文件,接下来执行的是menu_execute_active_handler函数,就执行了这么一个函数,就完了。我们现在要做的是分析这个函数,看看在这个函数里面,Drupal具体做了什么。这个函数以menu开头,所以我推测它位于includes里面menu.inc文件中。我们也可以使用Google,搜索一下这个函数,这样会印证我前面的猜测。其实第一次的时候,我也不知道,也是Google出来的。


Drupal版本:

12 菜单回调机制

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

打开menu.inc文件,找到menu_execute_active_handler函数,阅读这个函数的源代码。

/**

 * Execute the page callback associated with the current path.

 *

 * @param $path

 *   The drupal path whose handler is to be be executed. If set to NULL, then

 *   the current path is used.

 * @param $deliver

 *   (optional) A boolean to indicate whether the content should be sent to the

 *   browser using the appropriate delivery callback (TRUE) or whether to return

 *   the result to the caller (FALSE).

 */

function menu_execute_active_handler($path = NULL, $deliver = TRUE) {

  // Check if site is offline.

  $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;

 

  // Allow other modules to change the site status but not the path because that

  // would not change the global variable. hook_url_inbound_alter() can be used

  // to change the path. Code later will not use the $read_only_path variable.

  $read_only_path = !empty($path) ? $path : $_GET['q'];

  drupal_alter('menu_site_status', $page_callback_result, $read_only_path);

 

  // Only continue if the site status is not set.

  if ($page_callback_result == MENU_SITE_ONLINE) {

    if ($router_item = menu_get_item($path)) {

      if ($router_item['access']) {

        if ($router_item['include_file']) {

          require_once DRUPAL_ROOT . '/' . $router_item['include_file'];

        }

        $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);

      }

      else {

        $page_callback_result = MENU_ACCESS_DENIED;

      }

    }

    else {

      $page_callback_result = MENU_NOT_FOUND;

    }

  }

 

  // Deliver the result of the page callback to the browser, or if requested,

  // return it raw, so calling code can do more processing.

  if ($deliver) {

    $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;

    drupal_deliver_page($page_callback_result, $default_delivery_callback);

  }

  else {

    return $page_callback_result;

  }

}

这个函数,执行当前路径的对应页面回调,向浏览器返回具体的页面。这个函数包含两个参数$path$deliver,不过这两个参数都是可选的;在index.php里面调用这个函数的时候,也没有传递参数,所以这两个参数现在是默认值。

第一行代码,是检查当前站点是否是在线状态。Drupal支持离线状态,什么时候用到离线状态,网站升级的时候,通常把站点配置为离线状态。离线配置地址位于admin/config/development/maintenance

1.png 

我们这里访问的node/1,当前状态为在线状态。_menu_site_is_offline是一个帮助函数,用来检查站点是否离线。

接下来的代码:

drupal_alter('menu_site_status', $page_callback_result, $read_only_path)

用来允许其它模块修改站点状态,这里面只允许修改站点状态,不允许修改当前路径。如果要修改路径的话,可以使用hook_url_inbound_alter

再往下是if语句,我们的站点当前为在线,所以进入了if语句里面,里面还是一个if语句:

if ($router_item = menu_get_item($path)) {

在我们这里,$path的值为NULLmenu_get_item会获取当前路径对应的菜单项。当前路径为node/1,对应的菜单项node/%,这个是在node_menu里面定义的,我们打开node.module文件,找到node_menu,找到node/%对应的菜单项。对应的定义如下:

  $items['node/%node'] = array(

    'title callback' => 'node_page_title',

    'title arguments' => array(1),

    // The page callback also invokes drupal_set_title() in case

    // the menu router's title is overridden by a menu link.

    'page callback' => 'node_page_view',

    'page arguments' => array(1),

    'access callback' => 'node_access',

    'access arguments' => array('view', 1),

  );

menu_get_item里面返回的信息,和hook_menu里面定义的菜单项,存在着一一对应的关系,当然menu_get_item里面包含更多一些属性。

此时,$router_item不为空,所以我们继续往下执行。接下来检查路由项的访问权限,看当前用户是否有权限访问这个页面。

if ($router_item['access'])

我们这里是有权限的,所以继续执行里面的代码:

        if ($router_item['include_file']) {

          require_once DRUPAL_ROOT . '/' . $router_item['include_file'];

        }

        $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);

$router_item['include_file']指的是什么?我们看node_menu,找到下面的代码:

  $items['node/%node/edit'] = array(

    'title' => 'Edit',

    'page callback' => 'node_page_edit',

    'page arguments' => array(1),

    'access callback' => 'node_access',

    'access arguments' => array('update', 1),

    'weight' => 0,

    'type' => MENU_LOCAL_TASK,

    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,

    'file' => 'node.pages.inc',

  );

  $items['node/%node/delete'] = array(

    'title' => 'Delete',

    'page callback' => 'drupal_get_form',

    'page arguments' => array('node_delete_confirm', 1),

    'access callback' => 'node_access',

    'access arguments' => array('delete', 1),

    'weight' => 1,

    'type' => MENU_LOCAL_TASK,

    'context' => MENU_CONTEXT_INLINE,

    'file' => 'node.pages.inc',

  );

这里的file键,就对应于$router_item['include_file'],如果路由里面指定了这个文件,Drupal在执行回调函数之前,会尝试加载这个文件。比如我们访问node/1/edit,对应的菜单项为'node/%node/edit',在菜单项的定义里面,存在'file' => 'node.pages.inc',当程序执行到这里的时候,Drupal就会尝试加载文件node.pages.inc

这样做有什么好处?好处,就是将页面回调函数的逻辑代码,从module文件中分离出来,使得module文件尽可能的小。Drupal是很吃内存的,其中的一个重要的原因,就是Drupal启动后,会加载所有的module文件,如果每个module文件都比较小的话,那么消耗的内存就比较小。我们在think in Drupal的第一集里面,介绍过这个问题,我们今天通过阅读Drupal核心的源代码,进一步了解到,核心的具体实现办法。


Drupal版本:

13 主内容的生成

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

下面来看这句抽象的代码:

$page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);

首先我们需要知道,$router_item['page_callback']$router_item['page_arguments']的值在当前情况下是什么。现在的路径为node/1。它对应的:

$router_item['page_callback']     :      node_page_view

$router_item['page_arguments']   :     array(1)

注意,这里的array(1),表示arg(1)Drupalnode_page_view传递参数的时候,会做以下工作:

$nid = arg(1);

$node = node_load($nid);

对于node/1/editarg(0)就是nodearg(1)就是1arg(2)就是edit,依次类推。这个参数的传递过程,是Drupal菜单系统的一种特有机制,我们了解就可以了,知道这里传递过来的是$node对象。

 

我们把前面抽象的函数,换成具体的情况:

$page_callback_result  =  call_user_func_array(‘node_page_view’, array($node));

call_user_func_array是一个PHP函数,我刚开始看到这个函数的时候,感到头大。查了一下文档,明白了它是用来动态的调用函数的。上面的这行代码,就等价于:

call_user_func_array(‘node_page_view’, array($node)) = node_page_view($node);

 

实际上,就是这样的:

$page_callback_result = node_page_view($node);

 

这是当路径为node/1时的情况。当路径为node/1/edit的时候,执行到这里,实际情况是这样的:

$page_callback_result = node_page_edit($node);

这里具体执行的这个回调函数,是动态的,路径不一样,对应的回调函数也不一样。


Drupal版本:

14 node_page_view代码追踪

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在让我们来看一下node_page_view返回了什么,我们在node.module里面找到这个函数。

/**

 * Menu callback: Displays a single node.

 *

 * @param $node

 *   The node object.

 *

 * @return

 *   A page array suitable for use by drupal_render().

 *

 * @see node_menu()

 */

function node_page_view($node) {

  // If there is a menu link to this node, the link becomes the last part

  // of the active trail, and the link name becomes the page title.

  // Thus, we must explicitly set the page title to be the node title.

  drupal_set_title($node->title);

  $uri = entity_uri('node', $node);

  // Set the node path as the canonical URL to prevent duplicate content.

  drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);

  // Set the non-aliased path as a default shortlink.

  drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);

  return node_show($node);

}

这里面具体复杂的是node_show($node),我们来看node_show的定义:

/**

 * Generates an array which displays a node detail page.

 *

 * @param $node

 *   A node object.

 * @param $message

 *   A flag which sets a page title relevant to the revision being viewed.

 *

 * @return

 *   A $page element suitable for use by drupal_render().

 */

function node_show($node, $message = FALSE) {

  if ($message) {

    drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);

  }

 

  // For markup consistency with other pages, use node_view_multiple() rather than node_view().

  $nodes = node_view_multiple(array($node->nid => $node), 'full');

 

  // Update the history table, stating that this user viewed this node.

  node_tag_new($node);

 

  return $nodes;

}

这里返回的是一个数组,Drupal将具体的实现又交给了node_view_multiple,只不过当前只有一个节点。我们来看node_view_multiple

/**

 * Constructs a drupal_render() style array from an array of loaded nodes.

 *

 * @param $nodes

 *   An array of nodes as returned by node_load_multiple().

 * @param $view_mode

 *   View mode, e.g. 'full', 'teaser'...

 * @param $weight

 *   An integer representing the weight of the first node in the list.

 * @param $langcode

 *   (optional) A language code to use for rendering. Defaults to NULL which is

 *   the global content language of the current request.

 *

 * @return

 *   An array in the format expected by drupal_render().

 */

function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {

  field_attach_prepare_view('node', $nodes, $view_mode, $langcode);

  entity_prepare_view('node', $nodes, $langcode);

  $build = array();

  foreach ($nodes as $node) {

    $build['nodes'][$node->nid] = node_view($node, $view_mode, $langcode);

    $build['nodes'][$node->nid]['#weight'] = $weight;

    $weight++;

  }

  $build['nodes']['#sorted'] = TRUE;

  return $build;

}

这里返回的是一个数组,包含节点的数组,可以使用drupal_render呈现这个数组。而这里,单个节点的构件,则是由node_view完成的。

/**

 * Generates an array for rendering the given node.

 *

 * @param $node

 *   A node object.

 * @param $view_mode

 *   View mode, e.g. 'full', 'teaser'...

 * @param $langcode

 *   (optional) A language code to use for rendering. Defaults to the global

 *   content language of the current request.

 *

 * @return

 *   An array as expected by drupal_render().

 */

function node_view($node, $view_mode = 'full', $langcode = NULL) {

  if (!isset($langcode)) {

    $langcode = $GLOBALS['language_content']->language;

  }

 

  // Populate $node->content with a render() array.

  node_build_content($node, $view_mode, $langcode);

 

  $build = $node->content;

  // We don't need duplicate rendering info in node->content.

  unset($node->content);

 

  $build += array(

    '#theme' => 'node',

    '#node' => $node,

    '#view_mode' => $view_mode,

    '#language' => $langcode,

  );

 

  // Add contextual links for this node, except when the node is already being

  // displayed on its own page. Modules may alter this behavior (for example,

  // to restrict contextual links to certain view modes) by implementing

  // hook_node_view_alter().

  if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) {

    $build['#contextual_links']['node'] = array('node', array($node->nid));

  }

 

  // Allow modules to modify the structured node.

  $type = 'node';

  drupal_alter(array('node_view', 'entity_view'), $build, $type);

 

  return $build;

}

这里我们可以看一下,这个数组的具体结构,如粗体代码所示。node_build_content,这个我们就不往下追踪了,有兴趣的可以继续往下分析下去。


Drupal版本:

15 SQL语句的调用

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

曾经有人在这里,问过我一个这样的问题,我怎么没有看到SQL语句啊?我们前面讲了,在向node_page_view传递参数之前,调用了node_load函数,将节点ID转为了节点对象。我们来看一下node_load函数的定义。

/**

 * Loads a node object from the database.

 *

 * @param $nid

 *   The node ID.

 * @param $vid

 *   The revision ID.

 * @param $reset

 *   Whether to reset the node_load_multiple cache.

 *

 * @return

 *   A fully-populated node object, or FALSE if the node is not found.

 */

function node_load($nid = NULL, $vid = NULL, $reset = FALSE) {

  $nids = (isset($nid) ? array($nid) : array());

  $conditions = (isset($vid) ? array('vid' => $vid) : array());

  $node = node_load_multiple($nids, $conditions, $reset);

  return $node ? reset($node) : FALSE;

}

它将单个节点的加载委托给了node_load_multiple,只不过这里传递过来一个节点ID而已。我们往下看node_load_multiple

/**

 * Loads node entities from the database.

 *

 * This function should be used whenever you need to load more than one node

 * from the database. Nodes are loaded into memory and will not require database

 * access if loaded again during the same page request.

 *

 * @see entity_load()

 * @see EntityFieldQuery

 *

 * @param $nids

 *   An array of node IDs.

 * @param $conditions

 *   (deprecated) An associative array of conditions on the {node}

 *   table, where the keys are the database fields and the values are the

 *   values those fields must have. Instead, it is preferable to use

 *   EntityFieldQuery to retrieve a list of entity IDs loadable by

 *   this function.

 * @param $reset

 *   Whether to reset the internal node_load cache.

 *

 * @return

 *   An array of node objects indexed by nid.

 *

 * @todo Remove $conditions in Drupal 8.

 */

function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {

  return entity_load('node', $nids, $conditions, $reset);

}

node_load_multiple又将具体的实现,委托给了entity_load。我们通过Google搜索一下entity_load,发现这个函数位于includes/common.inc里面,这让人有点意外。Fago对这一点就提出了批评,说entity的函数放的哪都是,Drupal8在这方面做了改进,Fago的有关实体的想法进入了内核。

1.png 

     不过这里面仍然没有SQL语句。我们在这个api.drupal.org上,在代码的上面,有这么一部分:

2.png 

我们点击DrupalDefaultEntityController,进入页面https://api.drupal.org/api/drupal/includes%21entity.inc/class/DrupalDefaultEntityController/7,找到:

3.png 

点击这里的DrupalDefaultEntityController::load。阅读这个成员函数,在代码里面找到:

4.png 

我们看到SQL语句的构建,是由DrupalDefaultEntityController::buildQuery完成的,我们点击这个成员函数,查看对应的源代码,这里面,我们看到了SQL语句:

protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {

  $query = db_select($this->entityInfo['base table'], 'base');

 

  $query->addTag($this->entityType . '_load_multiple');

 

  if ($revision_id) {

    $query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $revision_id));

  }

  elseif ($this->revisionKey) {

    $query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");

  }

 

  // Add fields from the {entity} table.

  $entity_fields = $this->entityInfo['schema_fields_sql']['base table'];

 

  if ($this->revisionKey) {

    // Add all fields from the {entity_revision} table.

    $entity_revision_fields = drupal_map_assoc($this->entityInfo['schema_fields_sql']['revision table']);

    // The id field is provided by entity, so remove it.

    unset($entity_revision_fields[$this->idKey]);

 

    // Remove all fields from the base table that are also fields by the same

    // name in the revision table.

    $entity_field_keys = array_flip($entity_fields);

    foreach ($entity_revision_fields as $key => $name) {

      if (isset($entity_field_keys[$name])) {

        unset($entity_fields[$entity_field_keys[$name]]);

      }

    }

    $query->fields('revision', $entity_revision_fields);

  }

 

  $query->fields('base', $entity_fields);

 

  if ($ids) {

    $query->condition("base.{$this->idKey}", $ids, 'IN');

  }

  if ($conditions) {

    foreach ($conditions as $field => $value) {

      $query->condition('base.' . $field, $value);

    }

  }

  return $query;

}

这段代码仍然很抽象,从这里面,我们可以看出,DrupalPHP的基础之上,为我们封装了很多层。


Drupal版本:

16 页面内容组装

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,包含节点对象的可呈现数组,已经返回来了。这个页面是怎么构件出来的呢?让我们回到函数menu_execute_active_handler上面来。

$page_callback_result = MENU_ACCESS_DENIED;

$page_callback_result = MENU_NOT_FOUND;

这两种特殊情况我们就不分析了。往下看。

  if ($deliver) {

    $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;

    drupal_deliver_page($page_callback_result, $default_delivery_callback);

  }

  else {

    return $page_callback_result;

  }

我们没有向menu_execute_active_handler传递参数,所以这里的$deliverTRUE,将会执行if语句里面的代码。


Drupal版本:

16.1 drupal_deliver_html_page

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在node_menu里面,并没有定义'delivery callback',所以这里的$default_delivery_callback将会使用Drupal的默认值。$page_callback_result就是刚才返回的包含节点对象的呈现数组。现在,让我们来看一下drupal_deliver_page,是怎么将这个数组转换成整个页面,并返回给浏览器的。

function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) {

  if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) {

    $default_delivery_callback = $router_item['delivery_callback'];

  }

  $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page';

  // Give modules a chance to alter the delivery callback used, based on

  // request-time context (e.g., HTTP request headers).

  drupal_alter('page_delivery_callback', $delivery_callback);

  if (function_exists($delivery_callback)) {

    $delivery_callback($page_callback_result);

  }

  else {

    // If a delivery callback is specified, but doesn't exist as a function,

    // something is wrong, but don't print anything, since it's not known

    // what format the response needs to be in.

    watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR);

  }

}

实际上我们并没有在任何地方设置$delivery_callback,所以将会使用默认的'drupal_deliver_html_page'。这里的

drupal_alter('page_delivery_callback', $delivery_callback);

用来允许第三方模块修改这里的$delivery_callback,但是实际上,我基本上没有见过哪个第三方模块实现了这个钩子函数。Drupal提供了太多的钩子函数,有些可能从来没有被使用过。

function_exists负责检查函数是否存在,drupal_deliver_html_page这个函数是存在的,所以这里实际调用的是:

drupal_deliver_html_page($page_callback_result);

为什么不直接调用drupal_deliver_html_page呢?我们要的就是html页面啊。Drupal核心的开发者是这样考虑的,这个地方留个接口,这样第三方模块就可以实现,比如说:

drupal_deliver_json_page

drupal_deliver_xml_page

也就是说,Drupal不仅仅支持HTML,返回有可能还是jsonxml等其它格式。我们这里只用到了html,所以让我们来看一下默认的具体实现。

/**

 * Packages and sends the result of a page callback to the browser as HTML.

 *

 * @param $page_callback_result

 *   The result of a page callback. Can be one of:

 *   - NULL: to indicate no content.

 *   - An integer menu status constant: to indicate an error condition.

 *   - A string of HTML content.

 *   - A renderable array of content.

 *

 * @see drupal_deliver_page()

 */

function drupal_deliver_html_page($page_callback_result) {

  // Emit the correct charset HTTP header, but not if the page callback

  // result is NULL, since that likely indicates that it printed something

  // in which case, no further headers may be sent, and not if code running

  // for this page request has already set the content type header.

  if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) {

    drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');

  }

 

  // Send appropriate HTTP-Header for browsers and search engines.

  global $language;

  drupal_add_http_header('Content-Language', $language->language);

 

  // Menu status constants are integers; page content is a string or array.

  if (is_int($page_callback_result)) {

    // @todo: Break these up into separate functions?

    switch ($page_callback_result) {

      case MENU_NOT_FOUND:

        // Print a 404 page.

        drupal_add_http_header('Status', '404 Not Found');

 

        watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);

 

        // Check for and return a fast 404 page if configured.

        drupal_fast_404();

 

        // Keep old path for reference, and to allow forms to redirect to it.

        if (!isset($_GET['destination'])) {

          $_GET['destination'] = $_GET['q'];

        }

 

        $path = drupal_get_normal_path(variable_get('site_404', ''));

        if ($path && $path != $_GET['q']) {

          // Custom 404 handler. Set the active item in case there are tabs to

          // display, or other dependencies on the path.

          menu_set_active_item($path);

          $return = menu_execute_active_handler($path, FALSE);

        }

 

        if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {

          // Standard 404 handler.

          drupal_set_title(t('Page not found'));

          $return = t('The requested page "@path" could not be found.', array('@path' => request_uri()));

        }

 

        drupal_set_page_content($return);

        $page = element_info('page');

        print drupal_render_page($page);

        break;

 

      case MENU_ACCESS_DENIED:

        // Print a 403 page.

        drupal_add_http_header('Status', '403 Forbidden');

        watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);

 

        // Keep old path for reference, and to allow forms to redirect to it.

        if (!isset($_GET['destination'])) {

          $_GET['destination'] = $_GET['q'];

        }

 

        $path = drupal_get_normal_path(variable_get('site_403', ''));

        if ($path && $path != $_GET['q']) {

          // Custom 403 handler. Set the active item in case there are tabs to

          // display or other dependencies on the path.

          menu_set_active_item($path);

          $return = menu_execute_active_handler($path, FALSE);

        }

 

        if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {

          // Standard 403 handler.

          drupal_set_title(t('Access denied'));

          $return = t('You are not authorized to access this page.');

        }

 

        print drupal_render_page($return);

        break;

 

      case MENU_SITE_OFFLINE:

        // Print a 503 page.

        drupal_maintenance_theme();

        drupal_add_http_header('Status', '503 Service unavailable');

        drupal_set_title(t('Site under maintenance'));

        print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',

          t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));

        break;

    }

  }

  elseif (isset($page_callback_result)) {

    // Print anything besides a menu constant, assuming it's not NULL or

    // undefined.

    print drupal_render_page($page_callback_result);

  }

 

  // Perform end-of-request tasks.

  drupal_page_footer();

}

最前面的两端代码,是用来添加http header的(drupal_add_http_header),我们不用去管它。

再往下是对$page_callback_result做了整数判断。

if (is_int($page_callback_result)) {

什么时候会是整数呢,当MENU_NOT_FOUNDMENU_ACCESS_DENIEDMENU_SITE_OFFLINE的时候,会是整数。我们这里面返回的是数组,不是整数,我们这里也就跳过了这三种特殊情况,继续往下看代码。

  elseif (isset($page_callback_result)) {

    // Print anything besides a menu constant, assuming it's not NULL or

    // undefined.

    print drupal_render_page($page_callback_result);

  }

 

  // Perform end-of-request tasks.

  drupal_page_footer();


Drupal版本:

16.2 页面数组的合成

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是我们这里实际执行的语句。用来合成页面的是drupal_render_page函数。这个时候,传递过来的$page_callback_result,只是一个包含节点对象的呈现数组,节点外面的区域、区块是怎么加进来的呢?这是很多初学者的疑问。让我们来看这个函数的定义:

/**

 * Renders the page, including all theming.

 *

 * @param $page

 *   A string or array representing the content of a page. The array consists of

 *   the following keys:

 *   - #type: Value is always 'page'. This pushes the theming through

 *     page.tpl.php (required).

 *   - #show_messages: Suppress drupal_get_message() items. Used by Batch

 *     API (optional).

 *

 * @see hook_page_alter()

 * @see element_info()

 */

function drupal_render_page($page) {

  $main_content_display = &drupal_static('system_main_content_added', FALSE);

 

  // Allow menu callbacks to return strings or arbitrary arrays to render.

  // If the array returned is not of #type page directly, we need to fill

  // in the page with defaults.

  if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) {

    drupal_set_page_content($page);

    $page = element_info('page');

  }

 

  // Modules can add elements to $page as needed in hook_page_build().

  foreach (module_implements('page_build') as $module) {

    $function = $module . '_page_build';

    $function($page);

  }

  // Modules alter the $page as needed. Blocks are populated into regions like

  // 'sidebar_first', 'footer', etc.

  drupal_alter('page', $page);

 

  // If no module has taken care of the main content, add it to the page now.

  // This allows the site to still be usable even if no modules that

  // control page regions (for example, the Block module) are enabled.

  if (!$main_content_display) {

    $page['content']['system_main'] = drupal_set_page_content();

  }

 

  return drupal_render($page);

}

这个时候会组装$page这个数组,开始的时候,只包含节点部分,注意往下执行的时候,粗体部分的代码,定义了hook_page_build钩子函数,允许其它模块向$page数组追加内容。难道block模块实现了这个钩子函数?


Drupal版本:

16.3 通过hook_page_build组装区域区块

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

让我们来试一下,打开block.module文件,搜索block_page_build,还真找到了。真聪明,你猜对了。我们来看一下block模块的具体实现。

/**

 * Implements hook_page_build().

 *

 * Renders blocks into their regions.

 */

function block_page_build(&$page) {

  global $theme;

 

  // The theme system might not yet be initialized. We need $theme.

  drupal_theme_initialize();

 

  // Fetch a list of regions for the current theme.

  $all_regions = system_region_list($theme);

 

  $item = menu_get_item();

  if ($item['path'] != 'admin/structure/block/demo/' . $theme) {

    // Load all region content assigned via blocks.

    foreach (array_keys($all_regions) as $region) {

      // Assign blocks to region.

      if ($blocks = block_get_blocks_by_region($region)) {

        $page[$region] = $blocks;

      }

    }

    // Once we've finished attaching all blocks to the page, clear the static

    // cache to allow modules to alter the block list differently in different

    // contexts. For example, any code that triggers hook_page_build() more

    // than once in the same page request may need to alter the block list

    // differently each time, so that only certain parts of the page are

    // actually built. We do not clear the cache any earlier than this, though,

    // because it is used each time block_get_blocks_by_region() gets called

    // above.

    drupal_static_reset('block_list');

  }

  else {

    // Append region description if we are rendering the regions demo page.

    $item = menu_get_item();

    if ($item['path'] == 'admin/structure/block/demo/' . $theme) {

      $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE));

      foreach ($visible_regions as $region) {

        $description = '<div class="block-region">' . $all_regions[$region] . '</div>';

        $page[$region]['block_description'] = array(

          '#markup' => $description,

          '#weight' => 15,

        );

      }

      $page['page_top']['backlink'] = array(

        '#type' => 'link',

        '#title' => t('Exit block region demonstration'),

        '#href' => 'admin/structure/block' . (variable_get('theme_default', 'bartik') == $theme ? '' : '/list/' . $theme),

        // Add the "overlay-restore" class to indicate this link should restore

        // the context in which the region demonstration page was opened.

        '#options' => array('attributes' => array('class' => array('block-demo-backlink', 'overlay-restore'))),

        '#weight' => -10,

      );

    }

  }

}

Block模块,在这里会找到当前主题的所有的区域,对于每个区域,会加载这个区域里面的所有的当前可用的区块。以区域的机读名字为键,把区域里面的内容追加到$page数组上来。

代码里面的

if ($item['path'] == 'admin/structure/block/demo/' . $theme) {

这是一种非常特殊的情况,只有在区块的管理界面,显示一个主题的演示区域的时候,才会用到,所以里面的代码,我们这里不用深究。


Drupal版本:

16.4 使用drupal_render呈现页面数组

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

现在,整个$page数组已经构建完成了,Drupal是怎么把它转为HTML页面的呢?注意这里的这个数组的类型是page,这个和表单元素数组,是一样的。Drupal6里面的表单数组,在Drupal7下,概念上做了进一步的扩充,除了表单元素以外,区块、节点、页面都是以呈现数组的形式出现。所有的呈现数组,经过drupal_render函数处理,就会转换成对应HTML形式。我们来看一下drupal_render的定义。

function drupal_render(&$elements) {

  // Early-return nothing if user does not have access.

  if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {

    return;

  }

 

  // Do not print elements twice.

  if (!empty($elements['#printed'])) {

    return;

  }

 

  // Try to fetch the element's markup from cache and return.

  if (isset($elements['#cache'])) {

    $cached_output = drupal_render_cache_get($elements);

    if ($cached_output !== FALSE) {

      return $cached_output;

    }

  }

 

  // If #markup is set, ensure #type is set. This allows to specify just #markup

  // on an element without setting #type.

  if (isset($elements['#markup']) && !isset($elements['#type'])) {

    $elements['#type'] = 'markup';

  }

 

  // If the default values for this element have not been loaded yet, populate

  // them.

  if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {

    $elements += element_info($elements['#type']);

  }

 

  // Make any final changes to the element before it is rendered. This means

  // that the $element or the children can be altered or corrected before the

  // element is rendered into the final text.

  if (isset($elements['#pre_render'])) {

    foreach ($elements['#pre_render'] as $function) {

      if (function_exists($function)) {

        $elements = $function($elements);

      }

    }

  }

 

  // Allow #pre_render to abort rendering.

  if (!empty($elements['#printed'])) {

    return;

  }

 

  // Get the children of the element, sorted by weight.

  $children = element_children($elements, TRUE);

 

  // Initialize this element's #children, unless a #pre_render callback already

  // preset #children.

  if (!isset($elements['#children'])) {

    $elements['#children'] = '';

  }

  // Call the element's #theme function if it is set. Then any children of the

  // element have to be rendered there.

  if (isset($elements['#theme'])) {

    $elements['#children'] = theme($elements['#theme'], $elements);

  }

  // If #theme was not set and the element has children, render them now.

  // This is the same process as drupal_render_children() but is inlined

  // for speed.

  if ($elements['#children'] == '') {

    foreach ($children as $key) {

      $elements['#children'] .= drupal_render($elements[$key]);

    }

  }

 

  // Let the theme functions in #theme_wrappers add markup around the rendered

  // children.

  if (isset($elements['#theme_wrappers'])) {

    foreach ($elements['#theme_wrappers'] as $theme_wrapper) {

      $elements['#children'] = theme($theme_wrapper, $elements);

    }

  }

 

  // Filter the outputted content and make any last changes before the

  // content is sent to the browser. The changes are made on $content

  // which allows the output'ed text to be filtered.

  if (isset($elements['#post_render'])) {

    foreach ($elements['#post_render'] as $function) {

      if (function_exists($function)) {

        $elements['#children'] = $function($elements['#children'], $elements);

      }

    }

  }

 

  // Add any JavaScript state information associated with the element.

  if (!empty($elements['#states'])) {

    drupal_process_states($elements);

  }

 

  // Add additional libraries, CSS, JavaScript an other custom

  // attached data associated with this element.

  if (!empty($elements['#attached'])) {

    drupal_process_attached($elements);

  }

 

  $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';

  $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';

  $output = $prefix . $elements['#children'] . $suffix;

 

  // Cache the processed element if #cache is set.

  if (isset($elements['#cache'])) {

    drupal_render_cache_set($output, $elements);

  }

 

  $elements['#printed'] = TRUE;

  return $output;

}

这里面注意缓存的相关代码,Drupal缓存无处不在。另外需要注意的是,呈现数组,就是一个树状结构,一个枝干下面会有子枝干,子枝干下面又有子枝干,一直递归下去,最后是树叶。将呈现数组转为HTML的时候,也是这样递归调用的,最先转成HTML的,就是最小的单元组成部分,慢慢地合成,最后整个呈现数组彻底转为HTML


Drupal版本:

16.5 drupal_page_footer负责收尾工作

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Drupal执行完HTTP请求后,调用drupal_page_footer

/**

 * Performs end-of-request tasks.

 *

 * This function sets the page cache if appropriate, and allows modules to

 * react to the closing of the page by calling hook_exit().

 */

function drupal_page_footer() {

  global $user;

 

  module_invoke_all('exit');

 

  // Commit the user session, if needed.

  drupal_session_commit();

 

  if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) {

    drupal_serve_page_from_cache($cache);

  }

  else {

    ob_flush();

  }

 

  _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);

  drupal_cache_system_paths();

  module_implements_write_cache();

  system_run_automated_cron();

}

这里提供了一个hook_exit钩子函数,允许第三方模块在HTTP请求结束时,与Drupal进行交互。然后就是会话提交,页面缓存设置。这里的ob_flush,用来刷新输出缓冲区,可以看作引导指令页面头部阶段ob_start()的一个回应。

再往下面就是注册表检查、缓存、自动运行定时任务。我们看到,定时任务的运行,是在HTTP请求结束后,运行的,这样就不会影响返回当前页面的性能。

通过阅读Drupal核心的代码,我们弄明白了,Drupal一个普通节点页面的生成过程。如果你还有不明白的地方,可以多看几遍,把每个函数都认真的读一遍,遇到不懂的函数,查一下文档。


Drupal版本:

2 引导指令分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

你是不是觉得,你已经学会了?还是觉得,我想知道背后发生了什么?四行代码的背后,Drupal都做了什么。这四行代码里面,前面两行代码,都是做的准备工作,里面没有什么弯弯绕绕,很好理解。我们来看第三行,drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL),打开includes目录下面的bootstrap.inc文件,找到drupal_bootstrap函数。


Drupal版本:

2.1 drupal_bootstrap

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

/**

 * Ensures Drupal is bootstrapped to the specified phase.

 *

 * In order to bootstrap Drupal from another PHP script, you can use this code:

 * @code

 *   define('DRUPAL_ROOT', '/path/to/drupal');

 *   require_once DRUPAL_ROOT . '/includes/bootstrap.inc';

 *   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

 * @endcode

 *

 * @param $phase

 *   A constant telling which phase to bootstrap to. When you bootstrap to a

 *   particular phase, all earlier phases are run automatically. Possible

 *   values:

 *   - DRUPAL_BOOTSTRAP_CONFIGURATION: Initializes configuration.

 *   - DRUPAL_BOOTSTRAP_PAGE_CACHE: Tries to serve a cached page.

 *   - DRUPAL_BOOTSTRAP_DATABASE: Initializes the database layer.

 *   - DRUPAL_BOOTSTRAP_VARIABLES: Initializes the variable system.

 *   - DRUPAL_BOOTSTRAP_SESSION: Initializes session handling.

 *   - DRUPAL_BOOTSTRAP_PAGE_HEADER: Sets up the page header.

 *   - DRUPAL_BOOTSTRAP_LANGUAGE: Finds out the language of the page.

 *   - DRUPAL_BOOTSTRAP_FULL: Fully loads Drupal. Validates and fixes input

 *     data.

 * @param $new_phase

 *   A boolean, set to FALSE if calling drupal_bootstrap from inside a

 *   function called from drupal_bootstrap (recursion).

 *

 * @return

 *   The most recently completed phase.

 */

function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {

  // Not drupal_static(), because does not depend on any run-time information.

  static $phases = array(

    DRUPAL_BOOTSTRAP_CONFIGURATION,

    DRUPAL_BOOTSTRAP_PAGE_CACHE,

    DRUPAL_BOOTSTRAP_DATABASE,

    DRUPAL_BOOTSTRAP_VARIABLES,

    DRUPAL_BOOTSTRAP_SESSION,

    DRUPAL_BOOTSTRAP_PAGE_HEADER,

    DRUPAL_BOOTSTRAP_LANGUAGE,

    DRUPAL_BOOTSTRAP_FULL,

  );

  // Not drupal_static(), because the only legitimate API to control this is to

  // call drupal_bootstrap() with a new phase parameter.

  static $final_phase;

  // Not drupal_static(), because it's impossible to roll back to an earlier

  // bootstrap state.

  static $stored_phase = -1;

 

  // When not recursing, store the phase name so it's not forgotten while

  // recursing.

  if ($new_phase) {

    $final_phase = $phase;

  }

  if (isset($phase)) {

    // Call a phase if it has not been called before and is below the requested

    // phase.

    while ($phases && $phase > $stored_phase && $final_phase > $stored_phase) {

      $current_phase = array_shift($phases);

 

      // This function is re-entrant. Only update the completed phase when the

      // current call actually resulted in a progress in the bootstrap process.

      if ($current_phase > $stored_phase) {

        $stored_phase = $current_phase;

      }

 

      switch ($current_phase) {

        case DRUPAL_BOOTSTRAP_CONFIGURATION:

          _drupal_bootstrap_configuration();

          break;

 

        case DRUPAL_BOOTSTRAP_PAGE_CACHE:

          _drupal_bootstrap_page_cache();

          break;

 

        case DRUPAL_BOOTSTRAP_DATABASE:

          _drupal_bootstrap_database();

          break;

 

        case DRUPAL_BOOTSTRAP_VARIABLES:

          _drupal_bootstrap_variables();

          break;

 

        case DRUPAL_BOOTSTRAP_SESSION:

          require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'includes/session.inc');

          drupal_session_initialize();

          break;

 

        case DRUPAL_BOOTSTRAP_PAGE_HEADER:

          _drupal_bootstrap_page_header();

          break;

 

        case DRUPAL_BOOTSTRAP_LANGUAGE:

          drupal_language_initialize();

          break;

 

        case DRUPAL_BOOTSTRAP_FULL:

          require_once DRUPAL_ROOT . '/includes/common.inc';

          _drupal_bootstrap_full();

          break;

      }

    }

  }

  return $stored_phase;

}

在函数的注释里面,我们看到,Drupal的引导指令分为多个阶段,DRUPAL_BOOTSTRAP_FULL是最后一个阶段,启动Drupal引导指令的时候,可以指定具体的阶段,而不是仅仅限于最后这个阶段。

如果要在别的PHP脚本中,启动Drupal引导指令的话,可以使用下面的代码:

    define('DRUPAL_ROOT', '/path/to/drupal');

    require_once DRUPAL_ROOT . '/includes/bootstrap.inc';

drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

 

我们来看函数里面的代码,首先是:

  static $phases = array(

    DRUPAL_BOOTSTRAP_CONFIGURATION,

    DRUPAL_BOOTSTRAP_PAGE_CACHE,

    DRUPAL_BOOTSTRAP_DATABASE,

    DRUPAL_BOOTSTRAP_VARIABLES,

    DRUPAL_BOOTSTRAP_SESSION,

    DRUPAL_BOOTSTRAP_PAGE_HEADER,

    DRUPAL_BOOTSTRAP_LANGUAGE,

    DRUPAL_BOOTSTRAP_FULL,

  );

这里面列出来了,引导指令的所有阶段,我们在think in Drupal的第一集里面,介绍过这些阶段。我们看到,这些阶段都是使用的大写字母,表示这是定义好的常量。在bootstrap.inc文件中,有这样的代码。

/**

 * First bootstrap phase: initialize configuration.

 */

define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0);

 

/**

 * Second bootstrap phase: try to serve a cached page.

 */

define('DRUPAL_BOOTSTRAP_PAGE_CACHE', 1);

 

/**

 * Third bootstrap phase: initialize database layer.

 */

define('DRUPAL_BOOTSTRAP_DATABASE', 2);

 

/**

 * Fourth bootstrap phase: initialize the variable system.

 */

define('DRUPAL_BOOTSTRAP_VARIABLES', 3);

 

/**

 * Fifth bootstrap phase: initialize session handling.

 */

define('DRUPAL_BOOTSTRAP_SESSION', 4);

 

/**

 * Sixth bootstrap phase: set up the page header.

 */

define('DRUPAL_BOOTSTRAP_PAGE_HEADER', 5);

 

/**

 * Seventh bootstrap phase: find out language of the page.

 */

define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);

 

/**

 * Final bootstrap phase: Drupal is fully loaded; validate and fix input data.

 */

define('DRUPAL_BOOTSTRAP_FULL', 7);

我们看到这些常量,从上到下,分别对应于01234567。引导指令共分为8个阶段。当我们明白了这些常量就是整数的时候,理解下面的这段代码,就很简单了。   

while ($phases && $phase > $stored_phase && $final_phase > $stored_phase) {

这句话的意思使用,如果传过来的阶段为7,那么系统将会从0开始,123456这样一直执行下去,直到执行到阶段7为止。如果传过来的阶段为3,那么将会执行0123。就是说,从0开始,一直执行到传递过来阶段为止。引导指令是按照先后顺序,依次执行的。


Drupal版本:

3 配置阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看阶段0,也就是配置阶段,对应的代码:

        case DRUPAL_BOOTSTRAP_CONFIGURATION:

          _drupal_bootstrap_configuration();

          break;

我们看到,Drupal将这个阶段要做的工作,委托给了_drupal_bootstrap_configuration函数。这个函数就位于bootstrap.inc文件中,我们通过文本查找,很快就找到了这个函数的定义:

/**

 * Sets up the script environment and loads settings.php.

 */

function _drupal_bootstrap_configuration() {

  // Set the Drupal custom error handler.

  set_error_handler('_drupal_error_handler');

  set_exception_handler('_drupal_exception_handler');

 

  drupal_environment_initialize();

  // Start a page timer:

  timer_start('page');

  // Initialize the configuration, including variables from settings.php.

  drupal_settings_initialize();

}

在这个函数里面,做了这样几件事情,设置Drupal自定义的错误处理器;Drupal环境的初始化;初始化配置,加载settings.php文件中的变量。timer_start这个函数,我也没有弄明白是做什么的,这里的字面意思是说,启动一个页面计时器。对于这个阶段,我们只需要知道,这里面做了环境初始化和加载settings.php文件两样工作就可以了。


Drupal版本:

4 页面缓存阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看阶段1,也就是页面缓存阶段,对应的代码:

        case DRUPAL_BOOTSTRAP_PAGE_CACHE:

          _drupal_bootstrap_page_cache();

          break;

Drupal将这个阶段要做的工作,委托给了_drupal_bootstrap_page_cache函数。这个函数也位于bootstrap.inc文件中,我们通过文本查找,很快就找到了这个函数的定义:

/**

 * Attempts to serve a page from the cache.

 */

function _drupal_bootstrap_page_cache() {

  global $user;

 

  // Allow specifying special cache handlers in settings.php, like

  // using memcached or files for storing cache information.

  require_once DRUPAL_ROOT . '/includes/cache.inc';

  foreach (variable_get('cache_backends', array()) as $include) {

    require_once DRUPAL_ROOT . '/' . $include;

  }

  // Check for a cache mode force from settings.php.

  if (variable_get('page_cache_without_database')) {

    $cache_enabled = TRUE;

  }

  else {

    drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE);

    $cache_enabled = variable_get('cache');

  }

  drupal_block_denied(ip_address());

  // If there is no session cookie and cache is enabled (or forced), try

  // to serve a cached page.

  if (!isset($_COOKIE[session_name()]) && $cache_enabled) {

    // Make sure there is a user object because its timestamp will be

    // checked, hook_boot might check for anonymous user etc.

    $user = drupal_anonymous_user();

    // Get the page from the cache.

    $cache = drupal_page_get_cache();

    // If there is a cached page, display it.

    if (is_object($cache)) {

      header('X-Drupal-Cache: HIT');

      // Restore the metadata cached with the page.

      $_GET['q'] = $cache->data['path'];

      drupal_set_title($cache->data['title'], PASS_THROUGH);

      date_default_timezone_set(drupal_get_user_timezone());

      // If the skipping of the bootstrap hooks is not enforced, call

      // hook_boot.

      if (variable_get('page_cache_invoke_hooks', TRUE)) {

        bootstrap_invoke_all('boot');

      }

      drupal_serve_page_from_cache($cache);

      // If the skipping of the bootstrap hooks is not enforced, call

      // hook_exit.

      if (variable_get('page_cache_invoke_hooks', TRUE)) {

        bootstrap_invoke_all('exit');

      }

      // We are done.

      exit;

    }

    else {

      header('X-Drupal-Cache: MISS');

    }

  }

}

    很多人经常抱怨Drupal的性能,但是我们看到,在Drupal引导指令的第2个阶段,就支持了页面缓存。这段代码的意思是说,如果存在缓存:

    $user = drupal_anonymous_user();

    // Get the page from the cache.

    $cache = drupal_page_get_cache();

    // If there is a cached page, display it.

if (is_object($cache)) {

 

那么就把缓存页面,返回给客户端:

drupal_serve_page_from_cache($cache);

 

除此之外,还做了一些其它工作,从缓存中恢复元数据,设置默认时区,当然,(有可能)还触发了两个钩子函数:

bootstrap_invoke_all('boot');

bootstrap_invoke_all('exit');

hook_boothook_exit有可能在这里触发。触发钩子函数的途径很多,比如module_invoke_allmodule_invokedrupal_altermodule_implements等等。bootstrap_invoke_all是用来在引导指令阶段就触发钩子函数。

 

在这个函数里面,我们来看前面几句代码:

  require_once DRUPAL_ROOT . '/includes/cache.inc';

  foreach (variable_get('cache_backends', array()) as $include) {

    require_once DRUPAL_ROOT . '/' . $include;

  }

这段代码的含义是,加载includes/cache.inc文件;如果cache_backends不为空的话,那么尝试加载对应include文件。这段代码,我以前很难理解,后来在Drupal实战一书里面,使用memcached模块的时候,才明白这里的用法。我们在使用memcached模块的时候,需要修改settings.php文件,对应的配置如下:

$conf['cache_backends'][] = 'sites/all/modules/memcache/memcache.inc';

$conf['cache_default_class'] = 'MemCacheDrupal';

$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';

这里面有$conf['cache_backends'][],它就对应于页面缓存阶段的variable_get('cache_backends'', array())

我们看到,Drupal与其它缓存技术的集成,是不需要去修改Drupal核心代码的,Drupal核心本身就支持这些潜在的缓存技术。当我们了解到Drupal提供的各种缓存后,我们对于Drupal的性能问题,就没有必要再去担心什么了。


Drupal版本:

5 数据库阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看阶段2,也就是数据库阶段,对应的代码:

        case DRUPAL_BOOTSTRAP_DATABASE:

          _drupal_bootstrap_database();

          break;

Drupal将这个阶段要做的工作,委托给了__drupal_bootstrap_database函数。这个函数就位于bootstrap.inc文件中,我们通过文本查找,很快就找到了这个函数的定义:

/**

 * Initializes the database system and registers autoload functions.

 */

function _drupal_bootstrap_database() {

  // Redirect the user to the installation script if Drupal has not been

  // installed yet (i.e., if no $databases array has been defined in the

  // settings.php file) and we are not already installing.

  if (empty($GLOBALS['databases']) && !drupal_installation_attempted()) {

    include_once DRUPAL_ROOT . '/includes/install.inc';

    install_goto('install.php');

  }

 

  // The user agent header is used to pass a database prefix in the request when

  // running tests. However, for security reasons, it is imperative that we

  // validate we ourselves made the request.

  if ($test_prefix = drupal_valid_test_ua()) {

    // Set the test run id for use in other parts of Drupal.

    $test_info = &$GLOBALS['drupal_test_info'];

    $test_info['test_run_id'] = $test_prefix;

    $test_info['in_child_site'] = TRUE;

 

    foreach ($GLOBALS['databases']['default'] as &$value) {

      // Extract the current default database prefix.

      if (!isset($value['prefix'])) {

        $current_prefix = '';

      }

      elseif (is_array($value['prefix'])) {

        $current_prefix = $value['prefix']['default'];

      }

      else {

        $current_prefix = $value['prefix'];

      }

 

      // Remove the current database prefix and replace it by our own.

      $value['prefix'] = array(

        'default' => $current_prefix . $test_prefix,

      );

    }

  }

 

  // Initialize the database system. Note that the connection

  // won't be initialized until it is actually requested.

  require_once DRUPAL_ROOT . '/includes/database/database.inc';

 

  // Register autoload functions so that we can access classes and interfaces.

  // The database autoload routine comes first so that we can load the database

  // system without hitting the database. That is especially important during

  // the install or upgrade process.

  spl_autoload_register('drupal_autoload_class');

  spl_autoload_register('drupal_autoload_interface');

}

 

 这个函数代码里面的第一部分,当数据库为空,当前尚未安装Drupal的时候,此时做了重定向,重定向到了install.php页面。

  if (empty($GLOBALS['databases']) && !drupal_installation_attempted()) {

    include_once DRUPAL_ROOT . '/includes/install.inc';

    install_goto('install.php');

  }

这个函数代码里面的第二部分,表示如果当前为单元测试环境下,要做的工作,我们这里不是单元测试环境,所以不需要考虑这段代码的含义:

  if ($test_prefix = drupal_valid_test_ua()) {

}

再往下,是加载includes/database/database.inc文件,这样就可以建立数据库连接了。最后是自动加载(缓加载)。

  spl_autoload_register('drupal_autoload_class');

  spl_autoload_register('drupal_autoload_interface');

我们这里看到,Drupal7只支持类、接口的缓加载,不支持函数的缓加载。我第一集里面讲过这个问题,今天看到的是对应的代码部分。


Drupal版本:

6 变量阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看阶段3,也就是变量阶段,对应的代码:

        case DRUPAL_BOOTSTRAP_VARIABLES:

          _drupal_bootstrap_variables();

          break;

Drupal将这个阶段要做的工作,委托给了_drupal_bootstrap_variables函数。这个函数就位于bootstrap.inc文件中,我们通过文本查找,很快就找到了这个函数的定义:

/**

 * Loads system variables and all enabled bootstrap modules.

 */

function _drupal_bootstrap_variables() {

  global $conf;

 

  // Initialize the lock system.

  require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc');

  lock_initialize();

 

  // Load variables from the database, but do not overwrite variables set in settings.php.

  $conf = variable_initialize(isset($conf) ? $conf : array());

  // Load bootstrap modules.

  require_once DRUPAL_ROOT . '/includes/module.inc';

  module_load_all(TRUE);

}

首先是加载includes/lock.inc文件,并初始化锁系统。接着是变量的初始化,我们知道,Drupal中很多配置变量都是存放在variable表里面,这里的初始化,将会把这个表中的数据全部加载出来的,是的,整张表的数据。我们平时做开发的时候,不要在变量里面存储大数据,这是我们要注意的。

当我看到lock_initialize的时候,我当时想,我只知道Drupal里面有锁机制,但是从来不知道具体的用处。我今天在写文档的时候,顺着查看了一下API函数,也就是查看了一下variable_initialize函数,一下明白了锁机制的用法,同时也明白了为什么这里需要加载includes/lock.inc文件。

function variable_initialize($conf = array()) {

  // NOTE: caching the variables improves performance by 20% when serving

  // cached pages.

  if ($cached = cache_get('variables', 'cache_bootstrap')) {

    $variables = $cached->data;

  }

  else {

    // Cache miss. Avoid a stampede.

    $name = 'variable_init';

    if (!lock_acquire($name, 1)) {

      // Another request is building the variable cache.

      // Wait, then re-run this function.

      lock_wait($name);

      return variable_initialize($conf);

    }

    else {

      // Proceed with variable rebuild.

      $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed());

      cache_set('variables', $variables, 'cache_bootstrap');

      lock_release($name);

    }

  }

 

  foreach ($conf as $name => $value) {

    $variables[$name] = $value;

  }

 

  return $variables;

}

db_query负责将变量从数据库表中取出,array_map('unserialize',..)负责反序列化。lock_acquire尝试获取锁,获取失败就等待一下lock_wait;获取成功后,就释放锁lock_release。此外,我们看到,所有的变量都存放在一个大的数组里面,名值对。

module_load_all(TRUE),这句代码,负责加载引导指令阶段,需要加载的module文件。我们知道,Drupal启动后,会加载所有的module文件。不过在启动的过程中,也就是在引导指令阶段,就有一部分module文件被优先加载了进来。使用phpmyadmin打开Drupal的数据库,找到system表,打开这个表,我们看到这个表里面包含了bootstrap一列。

1.png 

凡是这里bootstrap的值为1的模块,都会在module_load_all(TRUE)的时候加载进来。哪些模块的bootstrap的值为1呢? 

 

 

2.png 

默认只有这么两个模块。我当时还想着比如systemuser模块会被加载进来,没有想到是这两个模块。


Drupal版本:

7 会话阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看阶段4,也就是会话阶段,对应的代码:

 case DRUPAL_BOOTSTRAP_SESSION:

     require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'includes/session.inc');

     drupal_session_initialize();

     break;

在这里,首先加载includes/session.inc 文件,然后调用drupal_session_initialize函数。drupal_session_initialize函数就位于includes/session.inc文件中,我们通过文本查找,很快就找到了这个函数的定义:

/**

 * Initializes the session handler, starting a session if needed.

 */

function drupal_session_initialize() {

  global $user, $is_https;

 

  session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');

 

  // We use !empty() in the following check to ensure that blank session IDs

  // are not valid.

  if (!empty($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && !empty($_COOKIE[substr(session_name(), 1)]))) {

    // If a session cookie exists, initialize the session. Otherwise the

    // session is only started on demand in drupal_session_commit(), making

    // anonymous users not use a session cookie unless something is stored in

    // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.

    drupal_session_start();

    if (!empty($user->uid) || !empty($_SESSION)) {

      drupal_page_is_cacheable(FALSE);

    }

  }

  else {

    // Set a session identifier for this request. This is necessary because

    // we lazily start sessions at the end of this request, and some

    // processes (like drupal_get_token()) needs to know the future

    // session ID in advance.

    $GLOBALS['lazy_session'] = TRUE;

    $user = drupal_anonymous_user();

    // Less random sessions (which are much faster to generate) are used for

    // anonymous users than are generated in drupal_session_regenerate() when

    // a user becomes authenticated.

    session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE)));

    if ($is_https && variable_get('https', FALSE)) {

      $insecure_session_name = substr(session_name(), 1);

      $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE));

      $_COOKIE[$insecure_session_name] = $session_id;

    }

  }

  date_default_timezone_set(drupal_get_user_timezone());

}

session_set_save_handler用来初始化会话处理器,包括会话的打开、关闭、读取、写入、销毁、垃圾收集。接下来检查会话是否存在,如果会话已经存在,那么启动会话;如果会话不存在,那么生成会话。我们这里看到函数drupal_hash_base64,这是Drupal的加密算法,采用base64的哈希算法。uniqid用来生成一个唯一的ID号。session_id用来设置会话的ID号。


Drupal版本:

8 页面头部阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

 我们来看阶段5,也就是页面头部阶段,对应的代码:

        case DRUPAL_BOOTSTRAP_PAGE_HEADER:

          _drupal_bootstrap_page_header();

          break;

在这里,Drupal将这个阶段的工作委托给了_drupal_bootstrap_page_header函数。我们通过文本查找,很快就找到了这个函数的定义:

/**

 * Invokes hook_boot(), initializes locking system, and sends HTTP headers.

 */

function _drupal_bootstrap_page_header() {

  bootstrap_invoke_all('boot');

 

  if (!drupal_is_cli()) {

    ob_start();

    drupal_page_header();

  }

}

在这个函数里,首先是触发hook_boot;接着使用drupal_is_cli判断一下,当前脚本是否是运行在命令行环境下。我们这里当然不是。如果不是的话,使用ob_start来打开输出缓冲区,这是服务器端和浏览器端交互时,常见的PHP用法,接着使用drupal_page_header输出页面头部信息。什么是页面头部信息,我们来看一下这个帮助函数:

function drupal_page_header() {

  $headers_sent = &drupal_static(__FUNCTION__, FALSE);

  if ($headers_sent) {

    return TRUE;

  }

  $headers_sent = TRUE;

 

  $default_headers = array(

    'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',

    'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),

    'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0',

    'ETag' => '"' . REQUEST_TIME . '"',

  );

  drupal_send_headers($default_headers);

}

    这里面的'Expires''Last-Modified''Cache-Control''ETag'就是页面头部信息,对于登陆用户,Drupal总是将缓存设置为no-cache,这样保证登陆用户每次请求总能看到最新的页面。


Drupal版本:

9 语言阶段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看阶段6,也就是语言阶段,对应的代码:

        case DRUPAL_BOOTSTRAP_LANGUAGE:

          drupal_language_initialize();

          break;

在这里,Drupal将这个阶段的工作委托给了drupal_language_initialize函数。我们通过文本查找,很快就找到了这个函数的定义:

/**

 * Initializes all the defined language types.

 */

function drupal_language_initialize() {

  $types = language_types();

 

  // Ensure the language is correctly returned, even without multilanguage

  // support. Also make sure we have a $language fallback, in case a language

  // negotiation callback needs to do a full bootstrap.

  // Useful for eg. XML/HTML 'lang' attributes.

  $default = language_default();

  foreach ($types as $type) {

    $GLOBALS[$type] = $default;

  }

  if (drupal_multilingual()) {

    include_once DRUPAL_ROOT . '/includes/language.inc';

    foreach ($types as $type) {

      $GLOBALS[$type] = language_initialize($type);

    }

    // Allow modules to react on language system initialization in multilingual

    // environments.

    bootstrap_invoke_all('language_init');

  }

}

在这里,language_types函数用来获取所有的语言类型;language_default函数用来获取默认语言。这里的语言类型包括三类:

/**

 * The type of language used to define the content language.

 */

define('LANGUAGE_TYPE_CONTENT', 'language_content');

 

/**

 * The type of language used to select the user interface.

 */

define('LANGUAGE_TYPE_INTERFACE', 'language');

 

/**

 * The type of language used for URLs.

 */

define('LANGUAGE_TYPE_URL', 'language_url');

我刚开始以为,这里是获取所有的语言呢。后来往下细看了一下,原来的想法不对。注意这里是语言类型,它里面包括三种类型,界面、内容、URLdrupal_multilingual用来判断是否是多语言,判断的标准是启用的语言数量是否大于1,对于中文用户来说,我们通常启用简体中文,以及默认的英文,所以这里的判断应该为真。再往下就是加载includes/language.inc,为每种语言类型初始化。最后触发hook_language_init,给第三方模块一个交互的机会。


Drupal版本:

1路径分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

当我们访问node/1这个路径的时候,会发生什么呢?首先我们看到的路径,通常是这样的形式:

http://www.example.com/node/1

此时,我们是启用了简洁URL的,如果没有启用的话,可以配置,网上有比较多的教程。不启用简洁URL的路径,则是这样的:

http://www.example.com/?q=node/1

是的,与前者相比,多了一个“?q=”。

这是我们看到的路径,也是Apache看到的路径。可能有人会问,难道Drupal看到的路径不是这个样子的?是的,Drupal看到的路径不是这个样子。那么可能还会有人继续问,Drupal看到的路径是什么?Drupal看到的路径是这个样子的:

http://www.example.com/index.php?q=node/1

别名模式下:

http://www.example.com/index.php?q=aboutus

 

我们在本地做一个实验,分别访问路径:

http://localhost/snt6/index.php?q=node/1

http://localhost/snt6/?q=node/1

http://localhost/snt6/node/1

http://localhost/snt6/aboutus

我们看到了什么?我们看到的内容是完全一样的。aboutusnode/1之间是别名关系,可以说是一一对应的关系,Drupal在数据库里面,有张表,用来存储这样的对应关系。当我们访问路径aboutus的时候,它自动的就找到了对应的内部路径node/1。这个我们比较好理解。对于很多人,他刚开始的时候,并不知道Drupal看到的路径是:

http://www.example.com/index.php?q=node/1

很多人可能有个误区,感觉我们看到的路径,就是Drupal看到的路径。这样理解是有问题的。

当我们使用

http://www.example.com/?q=node/1

访问页面时,Apache帮我们做了转换,它将这个路径转为了:

http://www.example.com/index.php?q=node/1

 

在简洁URL下,apache将路径http://www.example.com/node/1最终也会转为:

http://www.example.com/index.php?q=node/1

 

有人可能会问,你是怎么知道这个转换的?在Drupal的根目录下,有这样一个文件.htaccess。我们打开这个文件,里面有这行代码:

  # Pass all requests not referring directly to files in the filesystem to

  # index.php. Clean URLs are handled in drupal_environment_initialize().

  RewriteCond %{REQUEST_FILENAME} !-f

  RewriteCond %{REQUEST_FILENAME} !-d

  RewriteCond %{REQUEST_URI} !=/favicon.ico

  RewriteRule ^ index.php [L]

这段代码就是做这个转换的,这段代码里面的RewriteCond %{REQUEST_FILENAME} !-f是什么意思?这个我也不知道。我们只需要知道,这里做了一个路径转换。仅此而已。


Drupal版本:

1.1 index.php文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们看到的,几乎所有的Drupal页面,入口程序都是index.php。当然也有例外,比如安装Drupal时,会使用install.php,更新Drupal时,会使用update.php。例外的情况,我们这里就不分析了,我们来分析一下正常的情况。

我们打开Drupal根目录下的index.php程序,我们看到了非常简洁的代码:

<?php

 

/**

 * @file

 * The PHP page that serves all page requests on a Drupal installation.

 *

 * The routines here dispatch control to the appropriate handler, which then

 * prints the appropriate page.

 *

 * All Drupal code is released under the GNU General Public License.

 * See COPYRIGHT.txt and LICENSE.txt.

 */

 

/**

 * Root directory of Drupal installation.

 */

define('DRUPAL_ROOT', getcwd());

 

require_once DRUPAL_ROOT . '/includes/bootstrap.inc';

drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

menu_execute_active_handler();

    老外写代码,通常是注释比代码长,这个文件也不例外。上面的注释说,Drupal所有的页面,都走这个入口程序。路由系统,将会把控制权分发给对应的处理器,然后输出对应的页面。我们往下看代码,第一行:

define('DRUPAL_ROOT', getcwd());

这里面定义了一个常量,DRUPAL_ROOTgetcwd,用来获取当前文件所在的目录,比如我们这里就是D:\xampp\htdocs\snt6

第二行代码:

require_once DRUPAL_ROOT . '/includes/bootstrap.inc';

这段代码的作用,是加载bootstrap.inc文件。

第三行代码:

drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

这段代码的作用,是启动Drupal,说的更完整一点就是启用Drupal的完整引导指令。drupal_bootstrap这个函数,位于bootstrap.inc文件中,我们前面加载这个文件,就是为了执行这个函数。这里的引导指令,是每个Drupal页面,都要走过的流程。这就有点类似于,我们打开计算机的时候,点了启动按钮,点过之后,计算机需要一个启动的过程,这个电脑的开机启动过程,就类似于Drupal的引导指令。

第四行代码:

menu_execute_active_handler();

这个函数做了什么?连个参数都没有。前面Drupal已经启动了,跑了起来,这个函数的作用是这样的,根据Drupal看到的路径,找到对应的回调函数,调用对应的回调函数,然后将返回的内容,组装成HTML页面,返回给浏览器。也就是英文注释里面所讲的。

这就是Drupal的组装过程,非常简单。与其它系统不一样,Drupal只有一个入口程序,我见过的一些系统都是这样的:

http://www.example.com/foroum.php

http://www.example.com/login.php

http://www.example.com/user/register.php

只有Drupal,是一个入口程序。

index.php文件的逻辑结构


Drupal版本: