Community Documentation

ARCHIVE: Writing an installation profile for the Drupal installer

Last updated March 13, 2011. Created by Freso on June 8, 2006.
Edited by linclark, LeeHunter, ax, dmitrig01. Log in to edit this page.

Drupal 7

This documentation has been updated for Drupal 7. If you reached here via a link on Drupal.org, please correct that link to point to the updated page.

The following is based on the initial documentation by CivicSpace Labs on how to create an install profile.

Anatomy of an installation profile

Install profiles are located in the 'profiles' directory of a Drupal installation, and named like example.profile. A typical profile file contains the following functions:

Note: I use profilename here as an example profile name. This is whatever your .profile file is named, some examples include default, my_blog_site, corporate, etc.

1. profilename_profile_modules() - REQUIRED

<?php
/**
* Return an array of the modules to be enabled when this profile is installed.
*
* @return
*  An array of modules to be enabled.
*/
function profilename_profile_modules() {
  return array(
   
// Enable required core modules first.
   
'block', 'filter', 'node', 'system', 'user', 'watchdog',
   
   
// Enable optional core modules next.
   
'blog', 'color', 'comment', 'forum', 'help', 'menu', 'taxonomy',

   
// Then, enable any contributed modules here.
   
'og', 'views', 'views_ui', 'views_rss',
  );
}
?>

Each module specified is enabled in order (important when you want to make database changes which affect other modules), which in turn fires its modulename.install file, if present. Note that you need to enable stuff like 'system', 'blocks', etc. in addition to extra modules like 'cart' and 'product.' Probably the best thing to do is copy/paste from the default.profile file's profile_modules() hook and go from there.

Warning: Not all modules can be enabled here. When this hook is run, none of the modules is actually included, not even the required core ones. Thus any module using a function call within its .install file, will cause this hook to fail. Thus, such modules can only be included within hook_profile_final(). This buggy behaviour will probably endure until Drupal 7.

2. profilename_profile_details() - REQUIRED

<?php
/**
* Return a description of the profile for the initial installation screen.
*
* @return
*   An array with keys 'name' and 'description' describing this profile.
*/
function profilename_profile_details() {
  return array(
   
'name' => 'Example profile',
   
'description' => 'This example profile will install some commonly used contrib modules.',
  );
}
?>

3. profilename_profile_final() - optional

<?php
/**
* Perform any final installation tasks for this profile.
*
* @return
*   An optional HTML string to display to the user on the final installation
*   screen.
*/
function profilename_profile_final() {
 
// Insert default user-defined node types into the database.
 
$types = array(
    array(
     
'type' => 'page',
     
'name' => t('Page'),
     
'module' => 'node',
     
'description' => t('If you want to add a static page, like a contact page or an about page, use a page.'),
     
'custom' => TRUE,
     
'modified' => TRUE,
     
'locked' => FALSE,
    ),
    array(
     
'type' => 'story',
     
'name' => t('Story'),
     
'module' => 'node',
     
'description' => t('Stories are articles in their simplest form: they have a title, a teaser and a body, but can be extended by other modules. The teaser is part of the body too. Stories may be used as a personal blog or for news articles.'),
     
'custom' => TRUE,
     
'modified' => TRUE,
     
'locked' => FALSE,
    ),
  );

  foreach (
$types as $type) {
   
$type = (object) _node_type_set_defaults($type);
   
node_type_save($type);
  }

 
// Default page to not be promoted and have comments disabled.
 
variable_set('node_options_page', array('status'));
 
variable_set('comment_page', COMMENT_NODE_DISABLED);

 
// Don't display date and author information for page nodes by default.
 
$theme_settings = variable_get('theme_settings', array());
 
$theme_settings['toggle_node_info_page'] = FALSE;
 
variable_set('theme_settings', $theme_settings);

 
// The return message is optional, if you omit it the default will be used.
 
return '<p>'. (drupal_set_message() ? t('Please review the messages above before continuing on to <a href="@url">your new Profile Name site</a>.', array('@url' => url(''))) : t('You may now visit <a href="@url">your new Profile Name site</a>.', array('@url' => url('')))) .'</p>';
}
?>

In the profilename_profile_final() implementation, you have the opportunity to do anything extra AFTER the modules specified in profilename_profile_modules() have been installed. In this function you have access to the full Drupal API so you could define any custom content types, create vocabularies and terms, change variable settings to your liking, etc.

General strategy for setting options in your profile's hook_profile_final()

Below I've tried to itemize some of the most common tasks. But here is a general strategy that worked for me when I was developing the GJG install profile:

1. Take a dump of the database, using mysqldump or PHPMyAdmin
2. Change something on a form or whatever
3. Take another database dump
4. Diff the two
5. Take those lines that are different and stick them in db_query(), replacing table_name with {table_name}

Common stuff you need to do: store settings/variables

Anytime you submit a form under administer >> settings, it's put into the variable table. I find it helpful to take a dump of the variable table before and after visiting a settings page and then diff to find the differences. Another approach might be to view source to find out field names, and then check the database to see what value it input.

Variable defaults usually do not exist in the variable table when you first install. A routine way to to capture as many variables as possible is to open every link on the /admin page, (and then look for more links called "settings") and then submit every form, even if you do not want to change the value. This will force every default into the variables table.

A quick way to view all existing variables is to use devel and enable the devel block. This block contains a link to view all variables.

To then override a variable in your profile, we use variable_set. For example, to enable user locations from location module:

<?php
  variable_set
('location_user', 1);
?>

The only place this can get tricky is with checkboxes/mutiselects, because those will get stored in a serialized array, so the db record will look like:

node_options_event:
a:2:{i:0;s:6:"status";i:1;s:7:"promote";}

The "a:2" means it's an array with 2 elements. The way to insert this is like:

<?php
  variable_set
('node_options_event', array('status', 'promote'));
?>

Common tasks you need to do: enable blocks

For this, I just take a dump of the blocks and boxes (for custom blocks) tables and then figure out which ones are custom vs. which ones come with Drupal by default (these will be in 'modules/system/system.install'). Then just insert the SQL directly in db_query statements, like:

<?php
  db_query
("INSERT INTO {blocks} VALUES ('gjg', '0', 1, 0, 1, 0, 0, 1, 'my*', '')");
?>

Common stuff you need to do: configure roles/permissions

Again, for this I take a dump of the role, users_roles, and permissions tables, and just make sure not to include definitions for the built-in roles (basically, if it's in database.mysql, you don't want to take it because it will cause an error because of a duplicate record).

<?php
// Make an 'administrator' role
db_query("INSERT INTO {role} (rid, name) VALUES (3, 'admin user')");

// Add user 1 to the 'admin user' role
db_query("INSERT INTO {users_roles} VALUES (1, 3)");

// Change anonymous user's permissions - this is UPDATE rather than INSERT
db_query("UPDATE {permission} SET perm = 'access comments, can send feedback, access content, search content, view uploaded files' WHERE rid = 1");

// Insert new role's permissions
db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (3, 'administer blocks, edit own blog,....', 0)");
?>

Sample .profile file - gojoingo.profile

<?php
/**
* The modules that are enabled when this profile is installed.
*
* @return
*  An array of modules to be enabled.
*/
function gojoingo_profile_modules() {
 
$core = array('system', 'block', 'blog', 'comment', 'contact', 'filter', 'forum', 'help', 'menu', 'node', 'page', 'path', 'profile', 'search', 'story', 'taxonomy', 'upload', 'user', 'watchdog');
 
$contrib = array('buddylist', 'front', 'content', 'text', 'jstools', 'location', 'location_views', 'event''rsvp', 'signup', 'signup conflicts', 'image', 'image_attach', 'image_gallery', 'invite', 'logintoboggan', 'og', 'og_basic', 'privatemsg', 'urlfilter', 'views', 'views_ui');
 
// TODO: How to deal w/ spam requirement?
  // TODO: How will this deal with image_attach, which is a contrib module inside image?
 
return array_merge($core, $contrib);
}

/**
* Implementation of hook_profile_details().
*
* This contains an array of profile details for display from the main selection screen.
*/
function gojoingo_profile_details() {
  return array(
   
'name' => 'GoJoinGo',
   
'description' => 'A social networking website with groups, events, friends, etc.'
 
);
}

/**
* Implementation of hook_profile_final().
*
* GoJoinGo platform installation.
*/
function gojoingo_profile_final() {

 
// Enable user locations
 
variable_set('location_user', 1);

 
// Turn on LoginToboggan features
 
variable_set('login_with_mail', 1);
 
variable_set('email_reg_confirm', 1);
 
variable_set('reg_passwd_set', 1);
 
variable_set('toboggan_immed_login', 1);
 
variable_set('toboggan_role', 4);
 
variable_set('toboggan_hijack', 1);

 
// Enable Organic Group access control
 
variable_set('og_enabled', 1);
 
db_query("DELETE FROM {node_access}");

 
// Make post visibility selectable by author - default to Public
 
variable_set('og_visibility', 2);

 
// Omit page types from OG
 
variable_set('og_omitted', array('page'));

 
// Enable Urlfilter
 
db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (1, 'urlfilter', 0, 10)");

 
/** CONFIGURATION SETTINGS */

  // Change front page to my/home
 
variable_set('site_frontpage', 'my/home');
 
 
// Turn on user pictures
 
variable_set('user_pictures', 1);

 
// Set default primary links
 
variable_set('phptemplate_secondary_links', array(
   
'text' => array('my home', 'my blog', 'my groups', 'my events', 'my friends'),
   
'link' => array('my/home', 'my/blog', 'my/groups', 'my/events', 'my/friends'),
   
'description' => array('', '', '', '', ''),
  ));

 
// Set welcome message for anonymous users
 
variable_set('front_page', 'Welcome to '. variable_get('site_name', 'GoJoinGo') .'!');

 
// Change welcome email to include validation URL
 
variable_set('user_mail_welcome_body', "

%username,

Thank you for registering at %site.

IMPORTANT:
For full site access, you will need to click on this link or copy and paste it in your browser:

%login_url

This will verify your account and log you into the site. In the future you will be able to log in using the username and password that you created during registration.

Your new %site membership also enables to you to login to other Drupal powered websites (e.g. drupal.org) without registering. Just use the following Drupal ID along with the password you've chosen:

Drupal ID: %username@%uri_brief


--  %site team

"
);

 
// Remove default line break filter for the FULL HTML filter
 
db_query("DELETE FROM {filters} WHERE format = 3");

 
/** BLOCK CONFIGURATION **/
  // Recommendations block - only show on my*
 
db_query("INSERT INTO {blocks} VALUES ('block', '1', 1, 0, 1, 0, 0, 1, 'my*', '')");
 
db_query("INSERT INTO {boxes} VALUES (1, 'Recommendations', '<?php\r\n  /* Edit the following variables if you''d like to change the text for this block */\r\n  \$groups = ''Groups in your Area'';\r\n  \$events = ''Events in your area'';\r\n  \$people = ''People in your area'';\r\n  \$popular = ''Popular Groups'';\r\n  \$new = ''New Groups'';\r\n  \r\n  /* Below is PHP code, only edit if you feel comfortable with PHP and the Drupal API.  */\r\n  global \$user;\r\n    \$items = array(l(t(\$groups), ''gsearch/og''),\r\n                   l(t(\$events), ''gsearch/gjg_event''),\r\n                   l(t(\$people), ''gsearch/user''),\r\n                   l(t(\$popular), ''groups/popular''),\r\n                   l(t(\$new), ''groups/new''));\r\n   \$output = theme(''gjg_menu'', \$items);\r\n   print \$output;', 'Recommendations', 2)");

 
// My friends block - only show on my*
 
db_query("INSERT INTO {blocks} VALUES ('gjg', '0', 1, 0, 1, 0, 0, 1, 'my*', '')");

 
// Recommendations block
 
db_query("INSERT INTO {blocks} VALUES ('gjg', '1', 0, 0, 0, 0, 0, 0, '', '')");

 
// Group profile block - only show on og types
 
db_query("INSERT INTO {blocks} VALUES ('group_block', '0', 1, 0, 0, 0, 0, 0, '', 'og')");

 
// Group actions block - only show on og types
 
db_query("INSERT INTO {blocks} VALUES ('group_block', '1', 1, 1, 0, 0, 0, 0, '', 'og')");

 
// LoginToboggan login block
  // NOTE: The following lines are commented out until I get LT working
  //db_query("INSERT INTO {blocks} VALUES ('logintoboggan', '0', 1, 0, 0, 0, 0, 0, '', '')");

  // Hide normal user login block
  //db_query("UPDATE {blocks} SET status = 0 WHERE module = 'user' AND delta = 0");

  // Move navigation block down
 
db_query("UPDATE {blocks} SET weight = 1 WHERE module = 'user' AND delta = 1");

 
// User actions block - show only on my* and tracker*
 
db_query("INSERT INTO {blocks} VALUES ('user_block', '0', 1, 0, 0, 0, 0, 1, 'my*\r\ntracker*', '')");

 
// User personal actions block - show only on my* and tracker*
 
db_query("INSERT INTO {blocks} VALUES ('user_block', '1', 1, 1, 0, 0, 0, 1, 'my*\r\ntracker*', '')");

 
/** DEFAULT CONTENT TYPE SETTINGS **/

  // Generally, all nodes default to _not_ promoted to front page and
  // attachments disabled
 
foreach(node_list() as $node) {
   
variable_set("node_options_$node", array('status'));
   
variable_set("upload_$node", 0);
  }

 
// File: enable attachments
 
variable_set('upload_file', 1);

 
// GJG Event: enable events
 
variable_set('event_nodeapi_gjg_event', 'all');

 
// OG: turn off comments, enable locations
 
variable_set('comment_og', 0);
 
variable_set('location_og', 1);
 
variable_set('location_name_og', 1);
 
variable_set('location_street_og', 1);
 
variable_set('location_city_og', 1);
 
variable_set('location_province_og', 1);
 
variable_set('location_postal_code_og', 1);
 
variable_set('location_country_og', 2);

 
// Page: turn off comments
 
variable_set('comment_page', 0);

 
// Venue: enable locations
 
variable_set('location_venue', 1);
 
variable_set('location_name_venue', 1);
 
variable_set('location_street_venue', 1);
 
variable_set('location_city_venue', 1);
 
variable_set('location_province_venue', 1);
 
variable_set('location_postal_code_venue', 1);
 
variable_set('location_country_venue', 2);

 
/** ROLES AND PERMISSIONS **/
  // Administrator user
 
db_query("INSERT INTO {role} (rid, name) VALUES (3, 'admin user')");

 
// Pre-authorized user (for LoginToboggan)
 
db_query("INSERT INTO {role} (rid, name) VALUES (4, 'pre-authorized user')");

 
// Add user 1 to authenticated and admin roles
 
db_query("INSERT INTO {users_roles} VALUES (1, 2)");
 
db_query("INSERT INTO {users_roles} VALUES (1, 3)");

 
// Configure default permissions for each role
 
db_query("UPDATE {permission} SET perm = 'access comments, can send feedback, access content, search content, view uploaded files' WHERE rid = 1");
 
db_query("UPDATE {permission} SET perm = 'edit own blog, access comments, post comments, post comments without approval, can send feedback, create files, edit own files, create forum topics, edit own forum topics, create events, edit own events, create images, submit latitude/longitude, view location section, access content, create groups, access private messages, search content, report spam, create stories, edit own stories, upload files, view uploaded files, access user profiles, create venue, edit own venues' WHERE rid = 2");
 
db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (3, 'administer blocks, edit own blog, access comments, administer comments, administer moderation, moderate comments, post comments, post comments without approval, can send feedback, create files, edit own files, administer filters, administer forums, create forum topics, edit own forum topics, create events, edit own events, administer images, create images, submit latitude/longitude, view location section, administer menu, access content, administer nodes, administer organic groups, create groups, create pages, edit own pages, administer url aliases, create url aliases, access private messages, administer search, search content, access spam, administer spam, bypass filter, report spam, create stories, edit own stories, access administration pages, administer site configuration, administer taxonomy, upload files, view uploaded files, access user profiles, administer users, create venue, edit own venues, administer watchdog', 0)");
 
db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (4, 'access comments, post comments, can send feedback, create forum topics, create events, submit latitude/longitude, view location section, access content, search content, create stories, view uploaded files, access user profiles, create venue', 0)");

 
/** THEME SETUP **/
  // Disable bluemarine
  //db_query("UPDATE {system} SET status = 0 WHERE name = 'bluemarine'");

  // Enable PHPTemplate theme engine
 
db_query("INSERT INTO {system} VALUES ('themes/engines/phptemplate/phptemplate.engine', 'phptemplate', 'theme_engine', '', 1, 0, 0)");

 
// Enable default theme
 
drupal_system_enable('theme', 'gojoingo');
 
variable_set('theme_default', 'gojoingo');

 
// Disable default logo and enter new logo path
 
variable_set('theme_gojoingo_settings', array(
   
'default_logo' => 0,
   
'logo_path' => 'themes/gojoingo/images/gojoingo-header.png',
   
'toggle_name' => 1,
   
'toggle_slogan' => 0,
   
'toggle_mission' => 1,
   
'toggle_primary_links' => 1,
   
'toggle_secondary_links' => 1,
   
'toggle_node_user_picture' => 0,
   
'toggle_comment_user_picture' => 0,
   
'toggle_search' => 0,
  ));

}
?>

This initial documentation was presented by CivicSpace Labs.

Comments

Docu needs much work

The complete part after Sample .profile file - gojoingo.profile should be revised. i worked on this and there is nearly in every "second" line a bug. most of this code isn't working at all!

i wonder how the following parts can ever work:

<?php

// 1. Page and Story isn't created

// 2. comment: secondary and primary links are not inside variables table, isn't it? this isn't working at all.
// Set default primary links
 
variable_set('phptemplate_secondary_links', array(
   
'text' => array('my home', 'my blog', 'my groups', 'my events', 'my friends'),
   
'link' => array('my/home', 'my/blog', 'my/groups', 'my/events', 'my/friends'),
   
'description' => array('', '', '', '', ''),
  ));
// Set default primary links
 
variable_set('phptemplate_secondary_links', array(
   
'text' => array('my home', 'my blog', 'my groups', 'my events', 'my friends'),
   
'link' => array('my/home', 'my/blog', 'my/groups', 'my/events', 'my/friends'),
   
'description' => array('', '', '', '', ''),
  ));

// 3. comment: non-existing function
drupal_system_enable('theme', 'gojoingo');

// 4. comment: user1 already have ALL permissions
// Add user 1 to authenticated and admin roles
db_query("INSERT INTO {users_roles} VALUES (1, 2)");
db_query("INSERT INTO {users_roles} VALUES (1, 3)");

// 5. comment: is already inside, noone need this
// Enable PHPTemplate theme engine
db_query("INSERT INTO {system} VALUES ('themes/engines/phptemplate/phptemplate.engine', 'phptemplate', 'theme_engine', '', 1, 0, 0)");

// 6. comment: garland is the default theme!
// Disable bluemarine
//db_query("UPDATE {system} SET status = 0 WHERE name = 'bluemarine'");

// 7. to activate other themes, this one needs an insert like:
db_query("INSERT INTO {system} VALUES ('sites/all/themes/mytheme/page.tpl.php', 'mytheme', 'theme', 'themes/engines/phptemplate/phptemplate.engine', 1, 0, 0, -1, 0)");
?>

Activate a custom theme

When hook_profile_final() is fired only Garland theme is presented in a database. We need to call system_theme_data() to update it with all available themes:

<?php
$themes
= system_theme_data();
$preferred_themes = array('my_theme', 'my_another_theme', 'zen');
// In preference descending order
foreach ($preferred_themes as $theme) {
  if (
array_key_exists($theme, $themes)) {
   
variable_set('theme_default', $theme);
    break;
  }
}
?>

in 5.6

system_theme_data();
system_initialize_theme_blocks('theme_name');
db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' and name = 'theme_name'");
variable_set('theme_default', 'theme_name');

translation import... a hack.

Translation import can by done with

<?php
 
/* Import current install language files
   * TODO This is only a HACK until autolocale module becomes available...
   *
   * copy the small "[isocode]/install.po" into profiles/myprofile/[isocode].po
   * copy the bigger "[isocode]/de.po" into profiles/myprofile/po/[isocode].po
   */
 
global $profile, $install_locale;
  static
$mode = 'overwrite';

 
// Add language, if not yet supported
 
$languages = locale_supported_languages(TRUE, TRUE);
  if (!isset(
$languages['name'][$install_locale])) {
   
$isocodes = _locale_get_iso639_list();
   
_locale_add_language($install_locale, $isocodes[$install_locale][0], FALSE);
  }

 
$filename = './profiles/' . $profile . '/po/' . $install_locale . '.po';
  if (
file_exists($filename)) {
   
$file = (object) array('filepath' => $filename);
    if (
$ret = _locale_import_po($file, $install_locale, $mode) == FALSE) {
     
$message = t('The translation import of %filename failed.', array('%filename' => $file->filename));
    }
    else {
     
// import successfull, enable selected language and make standard
     
db_query("UPDATE {locales_meta} SET isdefault = 0 WHERE locale = '%s'", 'en');
     
db_query("UPDATE {locales_meta} SET enabled = 1, isdefault = 1 WHERE locale = '%s'", $install_locale);
    }
  }
?>

This is nuts...

Why not have a way to copy an existing site as a new profile? Seriously. Creating a profile could be as simple as

cp /var/lib/mysql/drupal_existing $drupal-path/profiles/new-profile.profile

And applying it to a new site would be just as straightforward. Or, since this approach assumes separate databases for each site, you could do the equivalent in SQL backups.

You never REALLY learn to swear until you own a computer.

I agree. Here's help?

I agree that the installation routines are complicated. My workaround was as follows:

1) Set up a drupal site on my local server how I wanted it, noting especially the modules I planned to use. (Note: I learned the hard way to put non-core modules into sites/all/modules rather than in /modules)

2) Used PHPMyAdmin to dump the data for the entire site to an SQL text file, stored in my custom profile folder.

3) Through trial and error, decided which of the tables could be stripped out (the caches, for example) and removed them from the SQL file.

4) Created a new profile based on the Drupal default.

5) Modified the list of modules to include my additions.

6) Replaced the profile_final code with the code I offered in this link: http://drupal.org/node/147720 This code simply populates an empty Drupal database with all the data from the original site--content, settings, accounts, etc.

7) Wrapped everything up.

This will accomplish your goal.

HTH,
David

Script to download modules

See here for a script to include that will automatically download the latest version of any missing modules for you:

http://groups.drupal.org/node/10810

----
Drupal Micro-Blogging at Twitter

variable_set: unserialize

when trying to recreate a serialized variable like this from above:

node_options_event:
a:2:{i:0;s:6:"status";i:1;s:7:"promote";}

Rather than laboring to recreate these as an array I like to just do this:

variable_set('node_options_event', unserialize(a:2:{i:0;s:6:"status";i:1;s:7:"promote";}));

array('status', 'promote')

array('status', 'promote') is shorter, more readable, and less resource intensive than unserialize(a:2:{i:0;s:6:"status";i:1;s:7:"promote";}). I'd understand if you were pulling the a:2:{...} directly from the db, but since you're using it as a default...

--
Frederik 'Freso' S. Olesen

that was an *example*

That was just an exmple, so I used a simple one.

Generally what I'm doing is grabbing 100+ variables from the db (with a bit of help from the vardump module http://drupal.org/project/vardump). I then put it all in a file and do a bit of search/replace and bam, I have a file that can recreate all my configuration settings/variables during an installation.

While some of the variables might be simpler to recreate as an unserialized version, in general i find it much easier to just write a quick function that takes the serialized version, unserializes it, and then calls variable_set, than to do manual unserialization for all 100+ variables, or worse yet, to go through and the file and manually recreate the ones that are easy to recreate.

My code ends up looking like this:

/**
* knows how to unserialize the serialized data and set variable
*
* @param $name
*    name of variable to set
*  @param $serialized_data
*     serialized data to unserialize and pass to variable_set
*/
function profile_variable_set($name, $serialized_data){
  $unserialized_data = unserialize($serialized_data);
  variable_set($name, $unserialized_data);
}


...

profile_variable_set('node_rank_comments', 's:1:"5";');
profile_variable_set('node_rank_recent', 's:1:"5";');
profile_variable_set('node_rank_relevance', 's:1:"5";');
profile_variable_set('overlap_cjk', 'i:1;');
profile_variable_set('panels_node_default', 'a:6:{s:5:"block";s:5:"block";s:5:"other";s:5:"other";s:7:"content";i:0;s:11:"panels_mini";i:0;s:6:"views2";i:0;s:9:"node_form";i:0;}');
profile_variable_set('pathauto_case', 's:1:"1";');
profile_variable_set('pathauto_ignore_words', 's:108:"a,an,as,at,before,but,by,for,from,is,in,into,like,of,off,on,onto,per,since,than,the,this,that,to,up,via,with";');
profile_variable_set('pathauto_indexaliases', 'b:0;');
profile_variable_set('pathauto_indexaliases_bulkupdate', 'b:0;');
profile_variable_set('pathauto_max_bulk_update', 's:2:"50";');
profile_variable_set('pathauto_max_component_length', 's:3:"100";');
profile_variable_set('pathauto_max_length', 's:3:"100";');

...

That's just how I do it. No one is forced to do it that way, and I don't think there is a right or wrong way here. This works for me. The actual point for me isn't for this to be easily readable... heck serialized variables aren't easily readable in the db, or through devel; but rather to quickly be able to dump my variable table into a format that can be used by my install profile to recreate my config settings.

is there a compendium of variable settings?

is there a compendium of variable settings?

seems that getting the right variable settings is one of the bigger challenges (most labor intensive) of the tasks in creating a highly specific installation profile.

if a way to share this type of information doesn't already exist, what would be the best approach to building a well indexed, systematic, easily updated and --eventually-- comprehensive listing? (e.g. is it something like api.drupal.org?)

ownsourcing.com - Drupal training

CCK & Views

I'm messing around with a profile and realized that the export of node types can be simple cut and paste into a separate file (content-type/type.inc) and then pulled into the profile using this code I ripped from phpedu_profile.

function my_profile_create_content_types() {
$path = drupal_get_path('profile', 'my_profile') .'/content_types';
$calendar_options = array();
if ($handle = opendir($path)) {
while ($file = readdir($handle)) {
if (preg_match('@(.*)\.inc$@', $file)) {
$file_path = $path .'/'. $file;
$type_name = str_replace('.inc', '', $file);
$existing_type_name = ($type_name == 'profile') ? $type_name : '';
install_content_copy_import_from_file($file_path, $existing_type_name);
}
}
}
}

I can export views in the same way, but I haven't yet figured out how to import them similarly.

I have this same question...

I have this same question... did you ever find a solution?

Thank you,
April

You can do this with Features

You can do this with Features these days. Features will make your view a module which can include at the beginning of your .profile file, so the view will be activated right after installation.

people will look for the Patterns module

Hi,
someone pointed to me this page as a start for creating modules from a full installation setup.

The

Patterns module

actually does this exactly and

people should be told about the existence and use of that module at the beginning of your article

" Complex websites and web applications can be created by combining configurations of Modules, Content Types (CCK,) Views, Panels, Menus, Blocks, Categories, Roles / Permissions, etc.. This site setup and configuration process is a very time consuming and repetitive bottleneck.

Patterns module is built to bypass this bottleneck by managing and automating site configuration. Site configuration is stored in XML* files called Patterns which are easy to read, modify, manage, & share and can be executed manually or as a part of an automated web site " (taken from the patterns module page)

There are other ways

Patterns is an option, but there are other ways which are gaining popularity. For example drush (command line drupal - not a module) + features (module).

But also for install profiles there is this API helper module:
http://drupal.org/project/install_profile_api

And here is another great page with example profiles:
http://api.drupal.org/api/file/developer/example.profile/6

Sub-Installation Profile

Is there any way to create related installation profiles similar to themes and sub-themes?

Erik

Erik Webb
Technical Consultant, Acquia

Should we want users to assist in making install profiles

We should improve this documentation to bring it up to date with at least Drupal 6. I consider myself a fairly advanced user at this stage, but the install profile documentation is seriously lacking which is disheartening as I would like to create one for myself to ease deployment of "the normal things" i need to do for an early development.

Currently I think an easier option for me would be backup and migrate with a simple search and replace of the domain in a couple places. I noticed there's a install profile for creating a new website from a backup and migrate.

I'm going to give up for now, but I'll be back at this in the future I'm sure. Right now I just don't have the time to spend figuring this all out.

For those who are interested, there's a good install profile for the "Open Atrium" project, which can help direct you as to what needs to get done in a fairly large install profile which is nicely documented.

Blows right by the basics

1) What are "hooks"? There must be some reason why we don't just call them "functions. It's always nice to see fundamental terms defined before they're used in explanations.
2) I see no code that calls the functions so I'm assuming they are called automatically (and this is why they're called "hooks"?), in the order they appear in the text. Making assumptions basically means guessing.
3) Where is the list of all allowed "hooks"? The profile I'm trying to understand contains functions not listed above. Is there any naming convention or are all functions "hooks"?

Good starter on what hooks

Good starter on what hooks are here. Also would recommend Pro Drupal Development book (Ed 2) if you're intending on doing any serious Drupal coding.

http://api.drupal.org/api/group/hooks/6

The above says it's for

The above says it's for Drupal 5.x. Where can we find documentation for Drupal 6?

Update for Drupal 6

I used information found on this page, plus some comparisons from the Drupal 6 default profile to create my first install profile. Much of what you'll find below rolled in some good suggestions for earlier comments, as well.

Notes:

  • The reference about enabling required modules must have been for D5. The default D6 profile does not include them, and mine worked without them. Similarly, I didn't have much trouble starting modules from profilename_profile_modules() hook.
  • As noted in the main article, you have to download all of the contrib modules in order for this to work.
  • I realized that by the time the profilename_profile_tasks() hook executes, Drupal is running. Almost anything Drupal can do can be done here, too. This is probably even more true if it's executed multiple times with different "tasks" set, but I didn't mess with that.
  • To figure out how to manipulate settings, I looked through all of the related _submit() functions on api.drupal.org. An alarming number of those use db_query() to INSERT or UPDATE. I tried to use drupal_write_record() as much as possible, but I was lazy on permissions.
  • To find values, I set my test site the way I wanted, then looked in the database with phpMyAdmin. To test, I blew everything away and re-ran the installation until it worked the way I wanted it to.
  • For @flickerfly's question about CCK above, I'd probably use the Features module. It's my understanding that Installation Profiles can enable Features.

<?php
// $Id$

/**
* @file
*   This file contains an example Drupal 6 Install Profile. It is based on the default installation profile and comments
*   found at drupal.org/node/67921.
*/

/**
* Return an array of the modules to be enabled when this profile is installed.
*
* @return
*   An array of modules to enable.
*/
function myprofile_profile_modules() {
  return array(
   
// Core Modules
   
'comment', 'contact', 'dblog', 'help', 'menu', 'path', 'profile', 'search', 'statistics', 'upload',
   
// Contributed Modules
   
'advanced_help', 'backup_migrate', 'blocks404', 'date_api', 'date_timezone', 'jquery_ui', 'token', 'transliteration', 'vertical_tabs'
 
);
}

/**
* 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.
*/
function myprofile_profile_details() {
  return array(
   
'name' => 'My Installation Profile',
   
'description' => 'Select this profile to create My Custom Website.'
 
);
}

/**
* 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 myprofile_profile_task_list() {
}

/**
* Perform any final installation tasks for this profile.
*
* @param $task
*   The current $task of the install system. When hook_profile_tasks()
*   is first called, this is 'profile'.
* @param $url
*   Complete URL to be used for a link or form action on a custom page,
*   if providing any, to allow the user to proceed with the installation.
*
* @return
*   An optional HTML string to display to the user. Only used if you
*   modify the $task, otherwise discarded.
*/

function myprofile_profile_tasks(&$task, $url) {

 
// Insert default user-defined node types into the database. For a complete
  // list of available node type attributes, refer to the node type API
  // documentation at: api.drupal.org/api/HEAD/function/hook_node_info.
 
$types = array(
    array(
     
'type' => 'page',
     
'name' => st('Page'),
     
'module' => 'node',
     
'description' => st("A <em>page</em> is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments.");
     
'custom' => TRUE,
     
'modified' => TRUE,
     
'locked' => FALSE,
     
'help' => '',
     
'min_word_count' => '',
    ),
  );

  foreach (
$types as $type) {
   
$type = (object) _node_type_set_defaults($type);
   
node_type_save($type);
  }

 
// Default page to not be promoted and have comments disabled.
 
variable_set('node_options_page', array('status', 'revision'));
 
variable_set('comment_page', COMMENT_NODE_DISABLED);

 
// Don't display date and author information for page nodes by default.
 
$theme_settings = variable_get('theme_settings', array());
 
$theme_settings['toggle_node_info_page'] = FALSE;
 
variable_set('theme_settings', $theme_settings);

 
// Set the preferred theme.
 
$themes = system_theme_data();
 
$preferred_themes = array('mytheme1', 'mytheme2', 'garland');
  foreach (
$preferred_themes as $theme) {
    if (
array_key_exists($theme, $themes)) {
     
system_initialize_theme_blocks($theme);
     
db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' and name = ('%s')", $theme);
     
variable_set('theme_default', $theme);
      break;
    }
  }

 
// Default input filter allowed HTML tags.
 
variable_set('allowed_html_1', '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <ins> <del> <p> <br> <h3> <h4>');

 
// Set timezone for date_timezone.module.
 
variable_set('date_default_timezone', -14400);
 
variable_set('date_default_timezone_name', 'America/Detroit');

 
// Set site_footer value.
 
variable_set('site_footer', st('&copy; Copyright 2010 by Nobody Special. All rights reserved.'));

 
// Configure access log for statistics module.
 
variable_set('statistics_count_content_views', '0');
 
variable_set('statistics_enable_access_log', '1');
 
variable_set('statistics_flush_accesslog_timer', '2419200');

 
// Configure user settings. Set user creation to administrator only.
 
variable_set('user_register', '0');

 
// Build Custom menus and set default node menu.

 
$menu = array(
   
'menu_name' => 'menu-site',
   
'title' => 'Site Menu',
   
'description' => 'This is the default menu that users will see.'
 
);
 
drupal_write_record('menu_custom', $menu);

 
$menu = array(
   
'menu_name' => 'menu-footer-links',
   
'title' => 'Footer Links',
   
'description' => 'These links will appear in the site footer. Your theme should display them as an inline list.'
 
);
 
drupal_write_record('menu_custom', $menu);

 
variable_set('menu_default_node_menu', 'menu-site');

 
$link = array('menu_name' => 'menu-footer-links', 'link_path' => 'contact', 'link_title' => 'Contact Us', 'weight' => 5);

 
$link = array('menu_name' => 'menu-site', 'link_path' => '<front>', 'link_title' => 'Home', 'weight' => -50);
 
menu_link_save($link);

 
$link = array('menu_name' => 'menu-site', 'link_path' => 'user/login', 'link_title' => 'Log In', 'weight' => 49);
 
menu_link_save($link);

 
$link = array('menu_name' => 'menu-site', 'link_path' => 'logout', 'link_title' => 'Logout', 'weight' => 50);
 
menu_link_save($link);

 
// Configure visible blocks.
  // Disable all default blocks for current theme and enable the two custom menus created above.

 
db_query("UPDATE {blocks} SET status = 0 where theme = '%s'", $theme);

 
$block = array(
   
'module' => 'menu',
   
'delta' => 'menu-footer-links',
   
'theme' => $theme,
   
'status' => 1,
   
'region' => 'footer',
   
'title' => '<none>'
 
);
 
drupal_write_record('blocks', $block);

 
$block = array(
   
'module' => 'menu',
   
'delta' => 'menu-site',
   
'theme' => $theme,
   
'status' => 1,
   
'region' => 'left',
   
'title' =>  '<none>'
 
);
 
drupal_write_record('blocks', $block);

 
// Create custom roles and set initial permissions.

 
$role = array('name' => 'editor');
 
drupal_write_record('role', $role);
 
$role = array('name' => 'ninja');
 
drupal_write_record('role', $role);

 
db_query("UPDATE {permission} SET perm = 'access content, search content, view uploaded files' WHERE rid = 1");
 
db_query("UPDATE {permission} SET perm = 'view advanced help index, view advanced help popup, view advanced help topic, access content, search content, use advanced search, upload files, view uploaded files, access user profiles' WHERE rid = 2");
 
db_query("INSERT INTO {permission} (rid, perm, tid) VALUES(3, 'upload files', 0)");
 
db_query("INSERT INTO {permission} (rid, perm, tid) VALUES(4, 'perform backup, administer blocks, administer menu, administer nodes, create url aliases, access statistics, access administration pages, access site reports, upload files', 0)");

 
// Create Home Page.

 
$node = new StdClass();
 
$node->type = 'page';
 
$node->status = 1;
 
$node->promote = 0;
 
$node->uid = 1;
 
$node->name = 'admin';
 
$node->path = 'home';
 
$node->title = 'Welcome';
 
$node->body = st('Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt.');
 
node_save($node);

 
variable_set('site_frontpage', 'node/1');

 
// Configure Core Profile Module.

 
$profile = array(
   
'title' => 'Full Name',
   
'name' => 'profile_fullname',
   
'category' => 'Personal Information',
   
'type' => 'textfield',
   
'visibility' => 2
 
);
 
drupal_write_record('profile_fields', $profile);

 
// Update the menu router information.
 
menu_rebuild();

 
// Run cron for the first time.
 
drupal_cron_run();

}

/**
* Implementation of hook_form_alter().
*
* Allows the profile to alter the site-configuration form. This is
* called through custom invocation, so $form_state is not populated.
*/
function myprofile_form_alter(&$form, $form_state, $form_id) {
  if (
$form_id == 'install_configure') {
   
// Set default for site name field.
   
$form['site_information']['site_name']['#default_value'] = $_SERVER['SERVER_NAME'];
   
$form['site_information']['site_mail']['#default_value'] = 'noreply@example.com';
   
$form['admin_account']['account']['name']['#default_value'] = 'admin';
   
$form['admin_account']['account']['mail']['#default_value'] = 'drupal@example.com';
  }
}
?>

TODO: This profile does not configure file upload quotas or contact form information.

Wow! Thanks so much for this

Wow! Thanks so much for this - I'm sure this will be very helpful!

I'm no developer, but your inserted comments are very clear and helpful!

Thanks again!

Thanks from me too. That's

Thanks from me too. That's about as thorough as it could possibly be. I hope it's good for Drupal 6 too.

Permissions api and Roles

For roles and permissions there is the permissions api http://drupal.org/project/permissions_api where you can do permissions_create_role('Editor');

permissons api

And it works like a charm!
I use this instead of database dumps,..and it is really brilliant and easy to change.
Function for inheritance, module granted permissions, easy, quick etc.

If there will be views and panels api,... :-D creating strong installation profile will be really fun (seriously)

Page status

About this page

Drupal version
Drupal 5.x

Archive

Drupal’s online documentation is © 2000-2012 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.