One of the features I'm most excited about in Drupal 7 is the greatly improved flexibility of installation profiles. However, documentation on writing an installation profile for Drupal 7 currently doesn't exist, so I thought I'd change that. Here's what I've discovered by reading through the code.
Installation profiles are like modules
All installation profiles must have profilename.info and profilename.profile files, and they can also have a profilename.install file. The profilename.profile file has access to almost everything a normal Drupal modulename.module file does because Drupal is fully bootstrapped before almost anything in the profile runs. The main exception is that st() should generally be used to translate strings instead of the usual t() because the localization hasn't been set up until the installation process completes.
The profilename.info file should look like this:
name = Profile Name description = Description of what the profile does. core = 7.x dependencies[] = block dependencies[] = color dependencies[] = comment dependencies[] = contextual dependencies[] = dashboard dependencies[] = help dependencies[] = image dependencies[] = list dependencies[] = menu dependencies[] = number dependencies[] = options dependencies[] = path dependencies[] = taxonomy dependencies[] = dblog dependencies[] = search dependencies[] = shortcut dependencies[] = toolbar dependencies[] = overlay dependencies[] = field_ui dependencies[] = file dependencies[] = rdf files[] = profilename.profile
Name, description, and core are all required; dependencies list modules that will be enabled when this profile is installed (the ones above are the defaults from the Standard install profile); and files[] lists files that should be registered with the Drupal dynamic code registry. You can include custom modules with your install profile or have Drupal.org automatically include other projects in your profile's package, and you can list those as dependencies as well.
In most cases, your profilename.install file will look like this:
/** * Implement hook_install(). * * Perform actions to set up the site for this profile. */ function profilename_install() { include_once DRUPAL_ROOT . '/profiles/standard/standard.install'; standard_install(); }
You can use hook_install() to insert content into your database during the installation process. This example uses the defaults given by the Standard install profile in Drupal core.
The rest of this documentation explains what you can do in profilename.profile.
Adding steps to the install process
New steps added to the installation process can either run invisibly in the background (normally to save data or include code needed in later steps) or display something on the screen. New steps are added in hook_install_tasks() by returning an associative array. Here's what each task array looks like (all elements are optional):
$task['machine_name'] = array( 'display_name' => st('Human-readable task name'), 'display' => TRUE, 'type' => 'normal', 'run' => INSTALL_TASK_RUN_IF_REACHED, 'function' => 'function_to_execute', );
Let's break it down.
- machine_name
- The internal name of the task.
- display_name
- The human-readable name of the task.
- display
- Determines whether to show this task in the list of installation tasks on the side of the installation screens. If
display_name
is not given ordisplay
is FALSE, the task does not appear in the list of installation tasks on the side of the installation screens.display
is useful if you want to conditionally display a task; for example, you might want to display a configuration form for a module that the user chose to enable in a previous step, but skip that form if the module was not enabled. - type
- One of "normal," "batch," or "form."
- "Normal" tasks can return HTML that will be displayed as a page in the installer or
NULL
to indicate that the task is completed. This is the default. Remember to provide some way to continue to the next step, such as a "continue" button. - "Batch" tasks return a batch array to be processed by the Batch API, which is useful for example if you want to import something that could take awhile. The task will be considered complete when the batch finishes processing.
- "Form" tasks return a Form API structured array which will be displayed as a form on a page in the installer. The installer will take care of moving the user to the next task when the form is submitted.
You can see more specifically what happens for each type in install_run_task().
- "Normal" tasks can return HTML that will be displayed as a page in the installer or
- run
- One of INSTALL_TASK_RUN_IF_REACHED, INSTALL_TASK_RUN_IF_NOT_COMPLETED, or INSTALL_TASK_SKIP.
- INSTALL_TASK_RUN_IF_REACHED means the task will run at each stage of the installation that reaches it. This is mainly used by core to include important resources on each page of the installer.
- INSTALL_TASK_RUN_IF_NOT_COMPLETED means the task will run once during the installation process. This is the default, and it is useful for things like displaying instructions and configuration forms, or for inserting content into the database.
- INSTALL_TASK_SKIP means the task will not be run. This is usually set conditionally, since you may want to skip tasks depending on the options the user chose in previous steps. It is often the case that a task will be skipped when
display
is set to FALSE, but it is possible to display a task in the list without executing it.
- function
- This is the function that will be run when the task is executed. If not set, this is the same as the machine_name of the task. You may want to set it to something else if you want to use the same function for multiple tasks, or if you want to conditionally choose a function depending on the options the user chose in previous steps.
As an example, let's say we're writing an installation profile called geo for a geographically-focused website, so we want the installer to ask what geographic area the website covers. We'll start by adding a page that asks for the relevant country. If the country is "United States," we'll add another page that asks for the relevant state, or if the country is "Canada," we'll add another page that asks for the relevant province.
/** * Implements hook_install_tasks(). */ function geo_install_tasks($install_state) { $country_is_us = !empty($install_state['parameters']['country']) && $install_state['parameters']['country'] = 'US'; $country_is_ca = !empty($install_state['parameters']['country']) && $install_state['parameters']['country'] = 'CA'; $tasks = array( 'geo_country' => array( 'display_name' => st('Choose a country'), ), 'geo_state' => array( 'display_name' => st('Choose a state or province'), 'display' => $country_is_us || $country_is_ca, 'type' => 'form', 'run' => $country_is_us || $country_is_ca ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, 'function' => $country_is_us ? 'geo_state_form' : 'geo_province_form', ), ); }
For the geo_country
task above, "Choose a country" is displayed in the list of tasks in the installer, and the function geo_country()
is run when the installer reaches that task. Since it has type "normal," it must do its own processing, calling a form using drupal_get_form() and then rendering it by returning the results of drupal_render(). (See install_select_locale() for an example of this.) The form's submit function would set $install_state['parameters']['country']
appropriately. Then, if the country chosen is the U.S. or Canada, "Choose a state or province" is displayed in the installer, and either geo_state_form()
or geo_province_form()
is used to generate the state- or province-selection form.
All tasks added in hook_install_tasks() are run after all dependencies are installed and loaded, so you have access to pretty much everything you would in a normal Drupal module.
Changing and removing steps from the install process
hook_install_tasks_alter() allows changing or removing steps from the install process. For example, if you are building an install profile specifically for sites using a certain language, you might want to remove the localization steps and simply configure the language settings yourself.
hook_install_tasks_alter() takes two parameters: &$tasks and $install_state. The $tasks variable contains a structured array of all installation tasks like the one you returned in hook_install_tasks(). Here are the default tasks, in order, as defined by install_tasks():
- install_select_profile
- A configuration page to select which profile to install. It doesn't make any sense to change this, because by the time your profile gets control, it will have already been chosen.
- install_select_locale
- Allows choosing the language to use in the installer.
- install_load_profile
- Loads the selected profile into memory.
- install_verify_requirements
- Checks whether the current server meets the correct requirements for installing Drupal.
- install_settings_form
- The form used to configure and rewrite settings.php.
- install_system_module
- Installs the system module so that the system can be bootstrapped, and installs the user module so that sessions can be recorded during bootstrap.
- install_bootstrap_full
- Does a full bootstrap of Drupal, loading all core functions and resources.
- install_profile_modules
- Installs and enables all modules on which the profile depends (as defined in the .info file), and then runs profilename_install() if it exists (as defined in the .install file).
- install_import_locales
- Import available languages into the system.
- install_configure_form
- A form to configure site information and settings.
- Profile tasks
- Now any tasks defined by the current installation profile are run. Drupal has already been fully bootstrapped, all required modules are already installed and enabled, and the profile itself has also been "installed," so each task should have access to anything a normal Drupal module would be able to access.
- install_import_locales_remaining
- Imports additional languages into the system if any have not yet been imported.
- install_finished
- Performs final installation tasks (like clearing caches) and informs the user that the installation process is complete.
Altering forms
You can also alter forms displayed by the installer using hook_form_alter() and hook_form_FORM_ID_alter() just like you would alter forms in a normal module. The form IDs are the machine names of the tasks by default. You may want to do this to add additional fields to the install_configure_form step, for example. Most installation profiles will also want to include this snippet, which sets the default site name to the name of the server:
/** * Implements hook_form_FORM_ID_alter(). */ function standard_form_install_configure_form_alter(&$form, $form_state) { // Pre-populate the site name with the server name. $form['site_information']['site_name']['#default_value'] = $_SERVER['SERVER_NAME']; }
I'm not sure why that doesn't happen by default.
Updating installation profiles
Another nice feature of installation profiles is that they can now have hook_update_N() functions in their .install file so that they can be updated to the latest version. For example, if you release version 1.0 of your install profile that installs one content type, and then you release version 2.0 that installs two content types, version 2.0 could have an update function that installed the second content type for sites that had originally used the 1.0 version.
Other useful functions
- variable_set() and variable_get() are particularly useful to remember what your install tasks have already done. Just remember to use variable_del() to delete any temporary variables you've created when you're done.
- If you need more fine-grained control than the dependencies[] in the .info file can give you, you may want to use module_enable() to enable a specific module during the install process. Enabling themes is similar, but setting the default, admin, and maintenance themes requires a little extra work.
- drupal_set_title() allows you to set the title of the page as it appears at the top of the browser window.
- Check the standard_install() function for examples of how to save various kinds of content in the database, including content types, taxonomies, user roles and permissions, input formats, fields, menu links, and RDF mappings.
Limitations
Drupal 7 has removed nearly all limitations in terms of what is technically possible with an installation profile. However, Drupal.org does not allow third-party resources like WYSIWYG editors to be hosted in its projects for legal reasons. Unfortunately, this means that many Drupal distributions will continue to be hosted off of Drupal.org. There is discussion about allowing drupal-org.make files to pull in third-party resources, however, so this situation may change.
Conclusion
Hopefully this can get you started on building an installation profile. Be sure to check out how to create a drupal-org.make file if you want to distribute your profile on Drupal.org. And of course, let me know if I left out anything.
I also put most of this article into the documentation on Drupal.org, so feel free to update it there.