Drupal专业开发指南 第23章 安装轮廓(profile)

当你安装Drupal时,会有一些模块被启用,一些特定的配置被选择,但是这些默认的设置可能并不是你所需要的。Drupal安装器使用了一个默认的安装过程轮廓,用来决定所有的这些配置。通过创建你自己的安装轮廓,你可以定制Drupal的初始安装,从而使你的站点带有你想要的模块和设置。假定你在为某一高校工作,你想创建一个安装过程profile,从而能够启用一个与学校单点登录系统相绑定的定制模块,能够为站点管理员创建一个新角色,能够在安装完成时向你发送e-mail。Drupal的安装器系统,允许你通过创建一个安装轮廓来定制安装时的各种操作。在本章你将学到如何做到这一点。

Drupal版本:

Drupal专业开发指南 第23章 一个批处理操作回调

导入了用户以后,那么将会调用importusers_optimize()。最后,当该操作也完成后,那么将会调用我们在finished键中指定的回调(importusers_finished())。下面是importusers_import()函数:

 
/**
 * Batch callback operation: Import users.
 *
 * @param $size
 * Number of users to import in each operation.
 * @param $context
 * Batch context containing state information.
 */
function importusers_import($size, &$context) {
    // Initialize sandbox the first time through.
    if (!isset($context['sandbox']['progress'])) {
        $context['sandbox']['progress'] = 0;
        $context['sandbox']['current_user_id'] = 0;
        $context['sandbox']['max'] = db_result(
            db_query('SELECT COUNT(DISTINCT user_id) FROM {old_users}'));
    }
 
    // Retrieve some users from the old_users table.
    $result = db_query_range("SELECT user_id, username AS name, email AS mail,
        pass FROM {old_users} WHERE user_id > %d ORDER BY user_id",
        $context['sandbox']['current_user_id'], 0, $size);
 
    // Transform them into Drupal users.
    while ($account = db_fetch_array($result)) {
        $new_user = user_save(array(), $account);
 
        // Update progress information.
        $context['sandbox']['progress']++;
        $context['sandbox']['current_user_id'] = $account['user_id'];
        $context['message'] = t('Importing user %username', array('%username' =>
            $new_user->name));
 
        // Store usernames in case the the 'finished' callback wants them.
        $context['results'][] = $new_user->name;
    }
 
    // Let the batch engine know how close we are to completion.
    if ($context['sandbox']['progress'] == $context['sandbox']['max']) {
        // Done!
        $context['finished'] = 1;
    }
    else {
        $context['finished'] = $context['sandbox']['progress'] /
        $context['sandbox']['max'];
    }
}
 
/**
 * Batch callback operation: Optimize users.
 * For now, this function does nothing.
 *
 * @param $context
 * Batch context containing state information.
 */
function importusers_optimize(&$context) {
    // Code would go here.
    // Inform the batch engine that we are done.
    $context['finished'] = 1;
}
 
    注意,除了你在批处理集操作数组中指出的参数以外,importusers_import()还接收了另一个名为$context的参数。$context是一个数组,它是通过引用传递的,它包含了来自于批处理引擎的关于当前批处理集的状态信息。$context的内容如下所示:
 
• sandbox:这个区域是供回调函数使用的。你可以在这里存储你需要的任何东西,并且它将会自动持久化。在我们的例子中,我们存储的信息有,要导入的用户数,当前正被导入的用户,等等。在批处理进行处理期间,使用这个来存储信息,而不是使用$_SESSION。如果你使用了$_SESSION,那么当用户打开了一个新的浏览器窗口,就可能出错。
 
• results:这个给finished回调使用的包含结果的数组。例如,如果用户想看到导入的用户名的列表时,那么就可以使用这个。
 
• message:是用来显示在进度页面的消息。
 
• finished:这是一个浮点数字,位于从0到1之间,用来指示处理了多少数据。当所有的数据处理完时,将它设置为1,来指示批处理引擎可以继续下一个操作了。
 
    下面是所有的批处理操作运行完成以后,所要调用的回调:
 
/**
 * Called when all batch operations are complete.
 */
function importusers_finished($success, $results, $operations) {
    if ($success) {
        drupal_set_message(t('User importation complete.'));
    }
    else {
        // A fatal error occurred during batch processing.
        $error_operation = reset($operations);
        $operation = array_shift($error_operation);
        $arguments = array_shift($error_operation);
        $arguments_as_string = implode(', ', $arguments);
        watchdog('importusers', "Error when calling operation '%s'('%s')",
            array($operation, $arguments_as_string));
        drupal_set_message(t('An error occurred and has been recorded
            in the system log.'), 'error');
    }
}

Drupal版本:

Drupal专业开发指南 第23章 使用批处理API

使用批处理API

    有时,你需要运行一系列的任务,这可能需要很长时间----有可能会超过PHP的时间限制。幸运的是,Drupal提供了一个API用来专门处理这种情况。你只需要指定要做什么,接着将它委托给批处理的处理器。这一般在一个表单提交后执行,当然这不是必须的。我们将检查在安装器中是如何使用批处理API的,接着学习一下如何在表单提交中使用批处理API。
 
使用批处理API启用模块
    隐藏在批处理API背后的基本思想是,你定义一组操作,定义用来显示进度消息的一些信息,定义如何运行这些操作,接着将其委托给批处理引擎。引擎将负责执行这些操作,如果需要的话,还会刷新HTTP,更新进度指示器。接着,当所有的操作执行完毕后,它将调用你定义的最终函数。
    下面是安装器使用批处理API启用模块的简化版本:
$operations = array();
foreach ($modules as $module) {
    $operations[] = array(
        '_install_module_batch', // Name of callback.
        array($module, $files[$module]->info['name']), // Array of parameters.
    );
}
$batch = array(
    'operations' => $operations,
    'finished' => '_install_profile_batch_finished', // Call this when done.
    'title' => st('Installing @drupal', array(
        '@drupal' => drupal_install_profile_name())
    ),
    'error_message' => st('The installation has encountered an error.'),
);
batch_set($batch);
batch_process($url, $url);
 
    首先,它创建了一个操作数组。每个操作都包含了,要调用的PHP函数的名字,和传递过来的参数的数组。由于在处理进行时,安装器将在后面调用这个PHP函数,所以它又被称为回调
    接着,定义了一个批处理集。这里面有,包含操作的数组,处理完成后要调用的回调的名字,在处理期间使用的标题,还有当出现错误时使用的错误消息。使用batch_set()来检验批处理集,接着调用batch_process()开始处理。
 
提示在这种情况下,所有的操作都调用了同一个函数,这里仅仅使用了不同的参数。然而,操作可以是你想要调用的任意函数。
 
    面是来自于install.php的_install_module_batch()函数的代码,对于每个操作都会执行这段代码:
 
/**
 * Batch callback for batch installation of modules.
 */
function _install_module_batch($module, $module_name, &$context) {
    _drupal_install_module($module);
    // We enable the installed module right away, so that the module will be
    // loaded by drupal_bootstrap() in subsequent batch requests, and other
    // modules possibly depending on it can safely perform their installation
    // steps.
    module_enable(array($module));
    $context['results'][] = $module;
    $context['message'] = 'Installed '. $module_name .' module.';
}

Drupal版本:

Drupal专业开发指南 第23章 安装轮廓的工作原理

Drupal的安装器启动时,它扫描profiles目录以查看有多少个可用的轮廓。如果它发现有多个轮廓时,它将显示所有的轮廓以供用户选择。例如,在创建了我们的university.profile文件,并向其中添加了university_profile_details()函数以后,访问http://example.com/install.php,将会产生一个如图23-1所示的界面。(当然,安装轮廓现在实际上还不能工作---还有很多工作需要去做。)

 
提示 如果Drupal只找到了一个轮廓,那么它将自动选择该轮廓。因此,如果你想直接运行你自己的轮廓,而不需要显示图23-1所示的界面,那么你只需要删除profiles/default/default.profile就可以了。
 
 
23-1 Drupal显示了一个界面,里面包含了有哪些安装轮廓可以选择
 
Drupal的安装器接下来还会回到安装轮廓上的。返回以后,它将找出安装轮廓想要运行的定制任务(这样,它就可以将它们添加到页面左边栏的步骤列表中了)。它还会找出轮廓想要起用的模块,并将这些模块自动启用。在安装过程的最后,安装器再次将执行委托给安装轮廓,以运行自定义的任务。在后面的这一阶段,会对Drupal进行进一步的定制。流程的概况如图23-2所示。
 
23-2 安装器是如何与安装轮廓交互的

Drupal版本:

Drupal专业开发指南 第23章 定义一个批处理集

我们在前面提到,一组处理又被称为一个批处理集。批处理API可以处理多个批处理集,而无须混合它们。可以按照先后顺序来处理多个批处理集,同时为每个批处理集使用一个新的进度指示器。

    让我们钻研一个例子。这里我们没有把它放在安装轮廓中,而是把它写在了一个单独的模块中了。这样,当你测试、调试、使用它时,就不需要每次都清空数据库和重新安装Drupal了。记住,只要稍作修改,你就可以在安装轮廓中使用这种方式了,比如,在一个自定义的轮廓任务中,可以使用批处理来响应显示给用户的表单。
    对于我们的场景,让我们使用最常见的例子,客户想将一个网站从自定义的内容管理系统迁移到Drupal中来。在原有的数据库中已经有了一个存储用户的数据库表,在我们导出的SQL中,它的样子应该是这样:
 
CREATE TABLE old_users (
    user_id int(32) NOT NULL,
    username varchar(32) NOT NULL,
    email varchar(32) NOT NULL,
    pass varchar(32) NOT NULL
);
INSERT INTO old_users VALUES (3, 'mary', 'mary@example.com', 'foo');
INSERT INTO old_users VALUES (4, 'joe', 'joe@example.com', 'bar');
INSERT INTO old_users VALUES (6, 'fred', 'fred@example.com', 'zou');
INSERT INTO old_users VALUES (7, 'betty', 'betty@example.com', 'baz');
INSERT INTO old_users VALUES (8, 'friedrich', 'freidrich@example.com', 'fre');
INSERT INTO old_users VALUES (9, 'martin', 'martin@example.com', 'aoi');
INSERT INTO old_users VALUES (10, 'fozzie', 'fozzie@example.com', 'lii');
INSERT INTO old_users VALUES (11, 'steve', 'steve@example.com', 'doi');
 
    让我们设立一个批处理,当管理员点击如图23-5所示的表单时,它将把这些用户作为Drupal用户导入进来。
 
23-5.用来选择一次导入多少用户的表单
 
    下面是我们模块的.info文件,我们应该把它放在sites/all/modules/custom/importusers/importusers.info:
 
; $Id$
name = Import Users
description = Imports users from a database using the batch API.
package = Pro Drupal Development
core = 6.x
 
    我们将首先实现菜单钩子,创建表单定义,和为表单定义编写处理器。sites/all/modules/custom/importusers/importusers.module的初始代码如下:
 
<?php
// $Id$
 
/**
 * @file
 * Example of using the batch API.
 */
 
/**
 * Implementation of hook_menu().
 */
function importusers_menu() {
    $items['importusers'] = array(
        'title' => 'Import users',
        'page callback' => 'drupal_get_form',
        'page arguments' => array('importusers_form'),
        'access arguments' => array('administer users'),
    );
    return $items;
}
 
/**
 * Menu callback: define form to begin user importation.
 */
function importusers_form() {
    $form['size'] = array(
        '#type' => 'select',
        '#title' => t('Import how many users per pass?'),
        '#description' => t('Choose a value and click the Begin button.'),
        '#options' => drupal_map_assoc(array(1, 5, 10, 25, 50)),
    );
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Begin'),
    );
    return $form;
}
 
/**
 * Handle form submission by beginning batch operation.
 */
function importusers_form_submit($form_id, &$form_state) {
    $size = $form_state['values']['size'];
    $batch = array(
        'operations' => array(
            array('importusers_import', array($size)),
            array('importusers_optimize', array()),
        ),
        'finished' => 'importusers_finished',
        'title' => t('Importing Users'),
        'init_message' => t('The user import process is beginning.'),
        'progress_message' => t('Imported @current of @total.'),
        'error_message' => t('The importation process encountered an error.'),
    );
    batch_set($batch);
    // batch_process() not needed here because this is a form submit handler;
    // the form API will detect the batch and call batch_process() automatically.
}
 
    对于菜单钩子和表单定义函数,我们应该已经很熟悉了(如果不熟悉的话,那么可以分别参看第4章和第10章)。这里值得注意的地方是importusers_form_submit()函数,我们在它里面定义了我们的批处理集。一个批处理集,在它的关联数组中可以使用以下键。这里只有operations键是必须的。
 
• operations: 这是一个数组的数组。每个数组包含两个成员:回调函数的名字,和执行操作时传递给回调函数的参数。
 
• finished: 当所有的操作都完成时调用的回调函数的名字。这个函数将接收在处理期间所发生的事件信息,这样可以通过drupal_set_message()对它进行分析、总结,或者在其它方面使用这些信息。
 
• title: 这是为用户显示进度信息的页面的标题。如果没有设置title的话,那么将使用t('Processing')。
 
• init_message:当批处理集的处理进行到初始化时,将会显示这个消息。如果没有设置init_message的话,那么将使用t('Initializing')。
 
• progress_message:在批处理集的处理期间,显示的消息。在进度消息中可以使用以下占位符:@current, @remaining, @total, 和@percent。随着批处理集的不断处理,这些值也会随之变化。如果没有设置progress_message的话,那么将使用t('Remaining @remaining of @total.')。
 
• error_message: 在处理期间当发生错误时,显示给用户的消息。如果没有设置error_message的话,那么将使用t('An error has occurred.')。
 
• file:在一个普通的Drupal请求期间,如果operationsfinished的回调函数不在当前范围内,那么必须给出包含这些函数的文件的路径。该路径是相对于Drupal安装的base_path()的,可以使用drupal_get_path()来方便的构建这个路径。如果函数已经位于了范围以内,那么就不需要定义file了。
 
    前面的批处理集是非常简单的,它只包含两个操作.首先,批处理引擎将重复的调用importusers_import($size),直到该函数指出已经导入了所有的用户。记住, $size参数是每次调用所要导入的用户数。$size在这里很重要,这是因为,在批处理API让客户初始化另一个HTTP请求以前,在每个请求周期内,这个变量决定着工作量的大小。例如,如果你有100个用户需要导入,将$size设置为1将会产生100个HTTP请求;而将$size设置为50,那么将会产生2个HTTP请求。在每个请求中,你想要执行的工作量,是由你的服务器的强大程度、服务器的繁忙程度、总工作量的大小共同决定的。

Drupal版本:

Drupal专业开发指南 第23章 定义附加的安装任务

注意图23-1中左边栏中的任务列表(“选择轮廓,”“选择语言,” “验证需求,”等等)。让我们通过在我们的安装轮廓中定义一些任务,来将它们也添加到这个列表中。我们将编写一个函数,它的名字为:我们的轮廓名字+_profile_task_list:

 
/**
 * Return a list of tasks that this profile supports.
 *
 * @return
 * A keyed array of tasks the profile will perform during
 * the final stage. The keys of the array will be used internally,
 * while the values will be displayed to the user in the installer
 * task list.
*/
function university_profile_task_list() {
    return array(
        'dept-info' => st('Departmental Info'),
        'support-message' => st('Support'),
    );
}
 
选择了我们的轮廓以后,我们将看到刚刚定义的任务,如图23-3所示。
23-3.轮廓定义的任务(部门信息和支持)显示在了左边栏
 
    安装器将执行一系列的任务,包括内置任务和你的安装轮廓可能定义的任务。表23-2给出了内置任务列表。自定义任务时,确保在你的任务数组中定义的键的唯一性,也就是与内置任务的任务标识不冲突。
 
23-2.任务的名字和描述,这里按照它们的执行顺序进行排列
任务标识符            描述
profile-select          选择轮廓*
locale-select           选择语言
requirements            验证系统需求
database                设立数据库
profile-install         为模块的安装和启用准备批处理
profile-install-batch  安装轮廓(安装和启用模块)
locale-initial-import  为用于导入的界面翻译准备批处理
locale-initial-batch    通过导入.po文件设立翻译
configure               配置站点(用户填写表单)
profile                 将控制权转交给安装轮廓的_profile_tasks()函数
profile-finished        为用于导入的剩余界面翻译准备批处理
locale-remaining-batch 设立剩余翻译
finished                告诉用户安装已完成
done                    重构表单,注册动作,和显示初始页面
*如果仅有默认轮廓可用,那么在用户界面将不会显示“选择轮廓”任务,而“安装轮廓”任务也被重命名为了“安装站点”。
 
    你定义的任务用来指示安装流程中的步骤;在这里定义任务的目的是,让Drupal将它们包含在用户界面中。如果你想让你的安装轮廓更加模块化,那么你完全可以在university_profile_task_list()中定义更多的任务,但是在定义任务时,一定要避免与已有任务的命名冲突。

Drupal版本:

Drupal专业开发指南 第23章 总结

在本章,你学到了以下几点:

• 什么是安装轮廓
• 安装轮廓的存储位置
• 如何设立一个基本的安装轮廓
• 如何制定安装哪些模块
• 如何指定安装期间应该运行的轮廓任务
• 在安装阶段,当轮廓任务运行时,如何操作Drupal
• 如何在安装器中使用批处理API
• 如何创建你自己的批处理集
 

Drupal版本:

Drupal专业开发指南 第23章 指示需要启用哪些模块

通过添加函数university_profile_modules(),我们告诉Drupal我们的安装轮廓想要启用哪些模块(还有,我们知道这个函数的名称是由我们的轮廓名称加上_profile_modules合成)。这个函数返回一个数组,里面包含了轮廓所要启用的模块名称。对于数组中模块名称出现的顺序,你要小心一点,因为模块之间可能是存在依赖关系的,以需要正确的处理这种可能存在的依赖关系。

 
/**
* Return an array of the modules to be enabled when this profile is installed.
*
* The following required core modules are always enabled:
* 'block', 'filter', 'node', 'system', 'user'.
*
* @return
* An array of modules to be enabled.
*/
function university_profile_modules() {
    return array(
        // Enable optional core modules.
        'dblog', 'color', 'help', 'taxonomy', 'throttle', 'search', 'statistics',
 
        // Enable single signon by enabling a contributed module.
        'pubcookie',
    );
}
 
在启用这些模块以前,安装器会询问每一个模块,以查看Drupal底层的系统是否提供了该模块所有的必要条件。通过为每个模块调用hook_requirements('install')来完成检查。如果要求没有完全被满足,安装器将会失败,并报告缺少了哪些条件。
 
注意 必要条件钩子是一个可选的钩子,它允许模块在进行安装以前,测试是否准备好了所需要的环境。例如,一个模块可能在PHP的最小版本上有限制。必要条件钩子必须放在模块的.install文件中。关于该钩子的更多信息,可参看http://api.drupal.org/api/function/hook_requirements/6
 
    安装器在启用模块以前,首先会确保模块是存在的。它会在多个地方查找,表23-1列出了这些地方。由于我们要启用pubcookie模块(不包含在Drupal核心中的模块),在运行我们的安装轮廓以前,我们需要确保能够在下面所列的目录中找得到它。
 
23-1. Drupal模块可存放的目录
------------------------------------------------------------------------------
目录                     存放的模块
modules                         Drupal 核心模块
sites/all/modules               3方模块(适用于所有站点)
profiles/profilename/modules    位于安装轮廓中的模块
sites/*/modules                 与你的settings.php文件位于同一目录的模块
 
安装器还会在放置你站点settings.php文件的目录下查找模块。如果settings.php位于sites/default,那么Drupal会在sites/default/modules下面找。类似的,如果settings.php位于sites/example.com,那么Drupal将在sites/example.com/modules下面寻找模块。

Drupal版本:

Drupal专业开发指南 第23章 渐进式和激进式(Nonprogressive)批处理集

渐进式批处理集就是一个普通的批处理集,它使用一个进度指示器向用户提供反馈。然而,当时想通过drupal_execute()来使用程序提交表单时,你应该不需要渐进式批处理集。所以,在这种情况下,表单API认出这是由程序提交的表单,并将批处理集设置为激进式。激进式批处理集将在单个请求中执行所有的操作。渐进式和激进式批处理集的设置,如图23-6所示。

 
 
23-6.渐进式和激进式批处理器的起始处理
 
批处理请求周期
    当操作正被执行时,批处理引擎负责刷新进度指示器页面来避免PHP的超时。图23-7给出了周期的图示,通过阅读includes/batch.inc源代码可以深入的研究一下这个周期。
 
23-7.批处理请求周期概览

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第23章 设置Drupal变量

设置Drupal变量

    通过简单的调用variable_set()就可以设置Drupal变量:
 
variable_set('pubcookie_login_dir', 'login');
 
创建初始节点类型
    如果你需要创建节点类型的话,那么可以使用Drupal内置的内容类型支持,创建一个节点类型定义对象,并调用node_type_save()就可以了。在前面的轮廓例子中,我们最后创建了两个节点类型:page,用于普通的web页面(在我们调用default_profile_tasks()时,由默认轮廓创建);news,用于新闻条目。接着,我们使用variable_set()设置了默认的节点选项,这样当发布新闻条目时,就会自动将其推到首页,然而普通的页面则不可以。
    如果你已经启用的模块提供了节点类型,那么通过这些模块中的node_info()钩子,这些节点类型在Drupal中已经可用。
 
将信息保存到数据库中
    安装轮廓可能想调整一些数据库设置。由于数据库连接已经可用,所以可以使用db_query()来修改数据库。在我们的轮廓例子中,我们向Drupal站点添加了一个角色。在你的轮廓中,你可能想添加更多的信息,例如,向permissions表中插入权限。
    为了获取正确的查询语句,这里有一个方便的方式,那就是执行一个普通的Drupal安装,然后对其进行配置,直到得到你想要的。这可能包括创建一些节点作为占位符,添加一些URL别名。对于这个大学部门,它可能需要一个“关于”页面,一个“课程教学”页面,等等。在完成了这些配置以后,你可以使用数据库工具从你站点的数据库中导出SQL脚本。然后,你从SQL脚本中选出你想使用的INSERT SQL命令,然后把这些插入命令包含在你的安装轮廓中。
 
通过程序提交表单
    因为Drupal支持程序式表单提交,所以,当你需要与网站进行交互时,你可以使用drupal_execute()提交表单。在前面的例子中,我们就使用了这种方式向站点添加了分类术语。关于drupal_execute()的更多详细,可参看第10章。
 
在安装期间设置一个主题
    Drupal把默认主题的值存储在了名为theme_default的持久化变量中。因此,通过设置这个变量,你就可以为站点选择初始主题了,在安装完成后就显示出来初始主题。在前面的轮廓例子中,我们选择了一个名为university的自定义主题。
 
// Change to our custom theme.
$themes = system_theme_data();
$theme = 'university';
if (isset($themes[$theme])) {
    system_initialize_theme_blocks($theme);
    db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND
        name = '%s'", $theme);
    variable_set('theme_default', $theme);
    menu_rebuild();
    drupal_rebuild_theme_registry();
}
 
    但是还有一些常规事务需要完成。调用system_theme_data(),并检查是否定义了$themes['university'],来确保Drupal发现了我们的自定义主题。需要做的还有,设立新主题中的区块,启用主题本身,接着重构菜单和主题注册表。
    这里的方式是,根据你感兴趣的流程(在这种情况下就是启用和设置一个默认主题),为其查找对应的函数,接着要么调用该函数,要么将其代码复制到你的安装轮廓任务重。在前面的例子中,代码是从modules/system/system.admin.inc的system_themes_form_submit()函数中提取出来的。

Drupal版本:

Drupal专业开发指南 第23章 资源

编写安装轮廓需要更多的技巧。在我们的例子中,尽管我们没有在university_profile_modules()中包含评论模块,但是我们需要把评论模块包含在范围内,这样就可以使用它的一些常量来设置首选项了。我们在输入分类术语时,使用了程序的方式来提交表单,在这里我们需要定义一个$form_state['clicked_button']条目,尽管看起来它是没用的,但是该表单的提交处理器需要这个条目。在你的安装轮廓中,你也需要花点时间注意一下类似的细节。

    尽管这些事情可能会花费额外的时间,但是你可以通过使用一个安装轮廓生成器,来节省不少时间。更多信息可参看http://drupal.org/node/180078。如果你对改进安装轮廓的当前状态感兴趣的话(没有双关的意思),可加入分发轮廓小组http://groups.drupal.org/distributions

Drupal版本:

Drupal专业开发指南 第23章 轮廓的存放位置

你的Drupal站点已经包含了一个安装轮廓。它是Drupal自带的默认安装轮廓,位于profiles/default/default.profile。我们想创建一个新的名为“university”(大学)的轮廓,所以我们首先需要在profiles/university/university.profile创建一个新文件。现在,我们向这个文件中添加一个单独的函数:

 
<?php
// $Id$
/**
* Return a description of the profile for the initial installation screen.
*
* @return
* An array with keys 'name' and 'description' describing this profile,
* and optional 'language' to override the language selection for
* language-specific profiles, e.g., 'language' => 'fr'.
*/
function university_profile_details() {
    return array(
        'name' => 'Drupal (Customized for Iowa State University)',
        'description' => 'Select this profile to enable settings typical for a
            departmental website.',
    );
}
注意,这里文件的名称与轮廓目录的名称相同,而在文件名的后面则使用了.profile后缀,文件university.profile中的所有函数都以前缀university_开头。
 
由于安装轮廓的选择界面出现在本地化选择界面以前,所以无法翻译这里name和description键对应的字符串。然而,安装轮廓中的其它字符串,都需要使用函数st(),注意在这里不是使用通常的t()函数,这是因为安装器在运行这段代码时,Drupal还没有完成一个完整的引导指令,所以在这里不能使用t()函数。如果有人想为我们的安装轮廓创建一个法语翻译的话,那么翻译需要放在profiles/university/translations/fr.po中(参看第18章)。

Drupal版本:

Drupal专业开发指南 第23章 运行附加的安装任务

Think in Drupal

在安装的profile阶段,将运行我们在university_profile_task_list()中指定的任务。在该阶段期间,安装器将重复的调用university_profile_tasks(),并向其传递参数$task和$url,其中$task变量包含了任务名字,而$url则是一个在表单函数中可能用到的URL。安装器初次调用它时,$task将包含字符串profile

    每个任务完成后,Drupal将使用includes/install.inc中的install_goto()请求浏览器来执行一个HTTP重定向,接着在进入下一个任务以前执行一个完整的引导指定。当所有的任务都完成以后,$task将被设置为profile-finished,安装器将停止对university_profile_tasks()的调用并继续前进。
    下面是university_profile_tasks()的一个骨架:
 
function university_profile_tasks(&$task, $url) {
    if ($task == 'profile') {
        // The value of $task is 'profile' the first time we are called.
        // Set up all the things a default installation profile has.
        require_once 'profiles/default/default.profile';
        default_profile_tasks($task, $url);
        // Then do our custom setup here.
 
        // Set $task to the next task.
        $task = 'dept-info';
        // Display a form requesting some info.
        return drupal_get_form('university_department_info', $url);
    }
    if ($task == 'dept-info') {
        // Send email indicating that a site was set up.
 
        // Set $task to key of next task.
        $task = 'support-message';
        // Build some output.
 
        return $output;
    }
    if ($task == 'support-message') {
        // Return control to the installer.
        $task = 'profile-finished';
    }
}
 
    由于我们想要的大多数设置都与平时的Drupal站点一样,所以我们加载了Drupal的默认轮廓,并简单的调用default_profile_tasks(),而不是在我们的安装轮廓中重复所有的代码。另外的一种方式就是将默认轮廓中的代码复制粘贴到第一个任务中。
 
提示 一个简单的安装轮廓不需要实现多个任务。在它被调用时,它可以忽略传递给它的参数并运行代码。当安装器看见$task变量没有改变的话,它将继续前进,运行后面的安装轮廓步骤。Drupal的默认安装轮廓就是这样的一个轮廓,所以我们可以直接在这里调用default_profile_tasks(),而不用担心它会修改$task的值。
 
    注意前面代码中的结构。它包含了一系列的if语句,每一个语句对应一个任务。在每个任务的结尾处,通过引用传递的$task变量,此时将被修改,并返回任意输出,这样将生成一个附加的界面供用户进行交互。
    由于在自定义安装任务运行以前,已经设立了数据库并建立了连接,所以,安装器可以在每个任务的结尾处调用variable_set('install_task', $task),使用一个持久化变量来追踪当前任务的名字。如果你想从一个任务中向另一个任务传递信息的话,那么也可以使用这一技术。只要记住,在你最后一个任务的结尾处,使用variable_del()来删除你用过的变量,这样就可以了。
    下面是大学安装轮廓的university_profile_tasks()函数的完整版本,让我们仔细的学习一下:
 
/**
 * Perform final installation tasks for this installation profile.
 */
function university_profile_tasks(&$task, $url) {
    if ($task == 'profile') {
        // $task is set to 'profile' the first time this function is called.
        // Set up all the things a default installation profile has.
        require_once 'profiles/default/default.profile';
 
        // Need constants defined by modules/comment/comment.module
        // to be in scope.
        require_once 'modules/comment/comment.module';
 
        default_profile_tasks($task, $url);
        // If the administrator enables the comment module, we want
        // to have comments disabled for pages.
        variable_set('comment_page', COMMENT_NODE_DISABLED);
 
        // Define a News Item node type.
        $node_type = array(
            'type' => 'news',
            'name' => st('News Item'),
            'module' => 'node',
            'description' => st('A news item for the front page.'),
            'custom' => TRUE,
            'modified' => TRUE,
            'locked' => FALSE,
            'has_title' => TRUE,
            'title_label' => st('Title'),
            'has_body' => TRUE,
            'orig_type' => 'news',
            'is_new' => TRUE,
        );
        node_type_save((object)$node_type);
 
        // News items should be published and promoted to front page by default.
        // News items should create new revisions by default.
        variable_set('node_options_news', array('status', 'revision',                   'promote'));   
 
        // If the administrator enables the comment module, we want
        // to have comments enabled for news items.
        variable_set('comment_news', COMMENT_NODE_READ_WRITE);
 
        // Create a News Categories vocabulary so news can be classified.
        $vocabulary = array(
            'name' => st('News Categories'),
            'description' => st('Select the appropriate audience for your news                  item.'),
            'help' => st('You may select multiple audiences.'),
            'nodes' => array('news' => st('News Item')),
            'hierarchy' => 0,
            'relations' => 0,
            'tags' => 0,
            'multiple' => 1,
            'required' => 0,
        );
        taxonomy_save_vocabulary($vocabulary);
 
        // Define some terms to categorize news items.
        $terms = array(
            st('Departmental News'),
            st('Faculty News'),
            st('Staff News'),
            st('Student News'),
        );
 
        // Submit the "Add term" form programmatically for each term.
        $form_id = 'taxonomy_form_term';
        // The taxonomy_form_term form is not in taxonomy.module, so need
        // to bring it into scope by loading taxonomy.admin.inc.
        require_once 'modules/taxonomy/taxonomy.admin.inc';
        foreach ($terms as $name) {
            $form_state['values']['name'] = $name;
            $form_state['clicked_button']['#value'] = st('Save');
            drupal_execute($form_id, $form_state, (object)$vocabulary);
        }
 
        // Add a role.
        db_query("INSERT INTO {role} (name) VALUES ('%s')", 'site administrator');
 
        // Configure the pubcookie module.
        variable_set('pubcookie_login_dir', 'login');
        variable_set('pubcookie_id_is_email', 1);
        // ...other settings go here
 
        // Set $task to next task so the installer UI will be correct.
        $task = 'dept-info';
        drupal_set_title(st('Departmental Information'));
        return drupal_get_form('university_department_info', $url);
    }
    if ($task == 'dept-info') {
        // Report by email that a new Drupal site has been installed.
        $to = 'administrator@example.com';
        $from = ini_get('sendmail_from');
        $subject = st('New Drupal site created!');
        $body = st('A new Drupal site was created: @site', array(
            '@site' => base_path()));
        drupal_mail('university-profile', $to, $subject, $body, $from);
 
        // Set $task to next task so the installer UI will be correct.
        $task = 'support-message';
        drupal_set_title(st('Support'));
        $output = '<p>'. st('For support, please contact the Drupal Support Desk
            at 123-4567.') .'</p>';
        // Build a 'Continue' link that goes to the next task.
        $output .= '<p>'. l(st('Continue'), $url) .'</p>';
        return $output;
    }
    if ($task == 'support-message') {
        // Change to our custom theme.
        $themes = system_theme_data();
        $theme = 'university';
        if (isset($themes[$theme])) {
            system_initialize_theme_blocks($theme);
            db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND
                name = '%s'", $theme);
            variable_set('theme_default', $theme);
            menu_rebuild();
            drupal_rebuild_theme_registry();
        }
 
        // Return control to the installer.
        $task = 'profile-finished';
    }
}
 

Drupal版本:

Drupal专业开发指南 第23章 运行附加的安装任务(1)

Think in Drupal

我们的第一个自定义安装任务为用户显示了一个交互式的表单。现在让我们定义该表单。我们可以使用标准的表单API,但是在这里需要仔细一点,我们把$form['#redirect']设置为FALSE,把表单的动作设为安装器提供的URL。表单的处理由一个提交处理器负责,这和普通的表单一样。下面是表单定义和提交处理器。表单如图23-4所示。

 
/**
 * Define form used by our dept-info installer task.
 *
 * @param $form_state
 * Keyed array containing the state of the form.
 * @param $url
 * URL of current installer page, provided by installer.
 */
function university_department_info($form_state, $url) {
    $form['#action'] = $url;
    $form['#redirect'] = FALSE;
    $form['department_code'] = array(
        '#type' => 'select',
        '#title' => st('Departmental code'),
        '#description' => st('Please select the correct code for your                   department.'),
        '#options' => array('BIOL', 'CHEM', 'COMP', 'DRUP', 'ENGL', 'HIST', 'MATH',
            'LANG', 'PHYS', 'PHIL'),
    );
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => st('Save and Continue'),
    );
    return $form;
}
 
/**
 * Handle form submission for university_department_info form.
 */
function university_department_info_submit($form, &$form_state) {
    // Set a persistent variable.
    variable_set('department_code', $form_state['values']['department_code']);
}
 
注意 在整个安装轮廓中,我们使用了st()来替代t(),这样就可以将整个安装轮廓的翻译保存在一个安装轮廓翻译文件中了。它是一个位于安装轮廓的可选目录translations中的.po文件。关于.po文件的更多详细,可参看第18章。
23-4.我们的自定义任务的截图
 

Drupal版本:

Drupal专业开发指南 第23章 错误处理

错误处理

    让我们修改一下第2个操作,importusers_optimize(),来演示一下出现错误时的样子:
 
/**
 * Batch callback operation. Demonstrate error handling.
 */
function importusers_optimize() {
    // Cause fatal error by calling nonexistent function.
    go_bananas();
}
 
    批处理引擎将实际的捕获错误并将用户重定向到一个错误页面。在前面一节中所给的finished回调负责生成错误页面。
 
重定向
    在批处理处理完,和finished函数运行完以后,将会进行一次最终的重定向。重定向的目的地将是批处理在开始时设置的$destination变量。如果没有设置这个变量,那么将使用表单提交处理器中的$form_state['redirect']的值。如果这个也失败了,那么将使用$batch['redirect']。如果所有的都失败了,那么将使用批处理初始化时用户所在页面的URL。

Drupal版本:

Drupal专业开发指南 第23章 附加中英文对照:

installer:安装器

installation profile:安装轮廓
batch:批处理
    在这里最主要的是profile的翻译,在现有的Drupal中,有两个地方用到了profile:Drupal安装的profile,Drupal用户的profile。这两个地方,在英语中应该是同一个意思。但是在汉语中,在简体中文包中,用户的profile被翻译成了“个人资料”,这个含义比较贴近用户profile,但是也不完全吻合。而安装器中的,installation profile被翻译为了“安装包”,这个应该翻译错了,installation profile和我们通常说的安装包不是一回事。而把profile翻译为“包”,那更不对。Adrupal上也翻译过profile,不过我上网不方便,没有参考到。金山词霸中给出了多个意思:剖面, 侧面, 外形, 轮廓,我觉得都不贴切,也不切当。在drupal5版的翻译中,我好像没有翻译profile。但是为了给每个Drupal中的英语找个对应的汉语意思,我在这里翻译成了轮廓。理由是,与“剖面, 侧面, 外形,”这3个相比,轮廓读起来稍微顺口一点,我个人觉得,尽管不贴切。
    另外,installation profile和user profile中的含义是相同的,轮廓放在安装上,放在个人身上,能勉强的说过去。
    还有就是尽可能的从现有的语言翻译中找对应的意思,这个词是我从金山词霸中挑出来的。另外,四个词中“剖面”也相对较好一点,但是还是选用了轮廓。
    轮廓这个词的翻译就是这样,尽管生硬了一点。它是我在一群瘸子中挑出来的,如果有更好的,大可以换了它。

Drupal版本: