July 16, 2010

Getting started with Drupal install profiles

For every new Drupal project there are many components and setup steps that are the same or similar to other projects.  These steps are repetitive and time consuming.  We needed to be able to standardize and automate the setup process to reduce human error and increase efficiency.

Here is an example of some of the steps that are repeated for every project (doing it the traditional way):

  • Download any required contributed modules from http://drupal.org and libraries such as CKEditor and JQuery
  • Enable the fusebasic custom theme (our own base theme)
  • Enable the rubik administrator theme
  • Enable WYSIWYG editor
  • Enable search engine optimization related modules
  • Enable support for file and image field upload
  • Set default input format to “Full HTML”
  • Create custom user roles and permission

This traditional process takes time.  Thankfully there is a better way!

Solution: Drush Make and Install Profiles!

Drush Make is a command line application.  We created a Drush Make file that tells Drush which Drupal version, modules, themes and libraries to download and install.  This is a real time saver, if your site needs 20 different contrib modules, it is quite time consuming to go to each module’s project page, download and extract them.  Drush Make automates these steps for you.

An Install Profile is a PHP script we create that helps us enable the themes and modules that we need.  In addition, it can also set any modules and Drupal custom configurations.

Here are the steps on how to create one:

Step 1: Create a Drush Make file

Assuming your site’s folder is in /var/www/myproject

Save your make file in /var/www/myproject

You can use the example make file below and add/remove any modules that you want or change to a different theme.

core = 6.x projects[] = "drupal" ; Development related modules. ; projects[devel][subdir] = "contrib" projects[coder][subdir] = "contrib" projects[simpletest][subdir] = "contrib" ; Get our admin area all sorted out and looking nice. ; projects[admin][subdir] = "contrib" projects[admin_menu][subdir] = "contrib" projects[tao][location] = http://code.developmentseed.org/fserver projects[rubik][location] = http://code.developmentseed.org/fserver projects[vertical_tabs][subdir] = "contrib" ; Some basic helper modules and site config modules. ; projects[token][subdir] = "contrib" projects[transliteration][subdir] = "contrib" projects[advanced_help][subdir] = "contrib" projects[ctools][subdir] = "contrib" projects[date][subdir] = "contrib" projects[strongarm][subdir] = "contrib" projects[features][subdir] = "contrib" projects[context][subdir] = "contrib" ; Image handling bits. ; projects[imageapi][subdir] = "contrib" projects[imagecache][subdir] = "contrib" ; Extra contrib modules needed by most sites. ; projects[google_analytics][subdir] = "contrib" projects[custom_breadcrumbs][subdir] = "contrib" projects[better_formats][subdir] = "contrib" projects[gmap][subdir] = "contrib" projects[location][subdir] = "contrib" projects[pngfix][subdir] = "contrib" projects[backup_migrate][subdir] = "contrib" ; CCK related modules. ; projects[cck][subdir] = "contrib" projects[filefield][subdir] = "contrib" projects[imagefield][subdir] = "contrib" ; Views, obviously. ; projects[views][subdir] = "contrib" ; SEO and path/title niceties modules. ; projects[pathauto][subdir] = "contrib" projects[nodewords][subdir] = "contrib" projects[page_title][subdir] = "contrib" projects[xmlsitemap][subdir] = "contrib" ; WYSIWYG and supporting modules and libraries. ; projects[wysiwyg][subdir] = "contrib" projects[imce][subdir] = "contrib" projects[imce_wysiwyg][subdir] = "contrib" libraries[ckeditor][download][type] = "get" libraries[ckeditor][download][url] = "http://download.cksource.com/CKEditor/CKEditor/CKEditor%203.3.1/ckeditor_3.3.1.tar.gz" libraries[ckeditor][destination] = "libraries" ; Jquery UI and Jquery update needed by most sites we build. ; projects[jquery_ui][subdir] = "contrib" libraries[jquery.ui][download][type] = "svn" libraries[jquery.ui][download][url] = "http://jquery-ui.googlecode.com/svn/tags/1.7.3/" libraries[jquery.ui][destination] = "modules/contrib/jquery_ui" projects[jquery_ui_dialog][subdir] = "contrib" ; The fusebasic theme. ; projects[fusebasic][type] = "theme"

For example if you want to add the link module, add the line below to the make file:

projects[link][subdir] = "contrib"

Step 2: Run drush make

This step will download all the modules, themes, libraries etc that you specified to your web folder.

If you don't have Drush and Drush Make set up yet.  Go here for further instructions.

Run this in command line:

cd /var/www/myproject

drush make fusebase.make

Step 3: Create an installation profile file

Create a new folder to store your profile file in /var/www/myproject

In this example we will create a folder call fusebase.  So you will store your fusebase.profile file in /var/www/myproject/profiles/fusebase/fusebase.profile

To help you get started, you can use our example installation profile below.


'Fusebase', 'description' => 'Base install profile for Fuse Interactives Drupal sites.', ); } /** * Return an array of the modules to be enabled when this profile is installed. * * @return * An array of modules to be enabled. */ function fusebase_profile_modules() { return array( // Enable required core modules first. 'block', 'comment', 'dblog', 'filter', 'help', 'menu', 'node', 'path', 'search', 'system', 'taxonomy', 'upload', 'user', // Then, enable any contributed modules here. 'admin', 'admin_menu', 'vertical_tabs', // Helper modules. 'token', 'transliteration', 'advanced_help', 'ctools', 'strongarm', 'features', 'context', 'better_formats', 'backup_migrate', 'pngfix', // Image modules. 'imageapi', 'imagecache', // SEO type modules. 'pathauto', 'nodewords', 'nodewords_basic', 'page_title', 'xmlsitemap', // WYSIWYG and supporting modules and libraries. 'wysiwyg', 'imce', 'imce_wysiwyg', // Jquery UI and Jquery update needed by most sites we build. 'jquery_ui', 'jquery_ui_dialog', // CCK and support for file and image uploads. 'content', 'content_permissions', 'fieldgroup', 'filefield', 'imagefield', 'number', 'optionwidgets', 'text', // Views. 'views', 'views_ui', ); } /** * Reimplementation of system_theme_data(). The core function's static cache * is populated during install prior to active install profile awareness. * This workaround makes enabling themes in profiles/[profile]/themes possible. */ function _fusebase_system_theme_data() { global $profile; $profile = 'fusebase'; $themes = drupal_system_listing('\.info$', 'themes'); $engines = drupal_system_listing('\.engine$', 'themes/engines'); $defaults = system_theme_default(); $sub_themes = array(); foreach ($themes as $key => $theme) { $themes[$key]->info = drupal_parse_info_file($theme->filename) + $defaults; if (!empty($themes[$key]->info['base theme'])) { $sub_themes[] = $key; } $engine = $themes[$key]->info['engine']; if (isset($engines[$engine])) { $themes[$key]->owner = $engines[$engine]->filename; $themes[$key]->prefix = $engines[$engine]->name; $themes[$key]->template = TRUE; } // Give the stylesheets proper path information. $pathed_stylesheets = array(); foreach ($themes[$key]->info['stylesheets'] as $media => $stylesheets) { foreach ($stylesheets as $stylesheet) { $pathed_stylesheets[$media][$stylesheet] = dirname($themes[$key]->filename) .'/'. $stylesheet; } } $themes[$key]->info['stylesheets'] = $pathed_stylesheets; // Give the scripts proper path information. $scripts = array(); foreach ($themes[$key]->info['scripts'] as $script) { $scripts[$script] = dirname($themes[$key]->filename) .'/'. $script; } $themes[$key]->info['scripts'] = $scripts; // Give the screenshot proper path information. if (!empty($themes[$key]->info['screenshot'])) { $themes[$key]->info['screenshot'] = dirname($themes[$key]->filename) .'/'. $themes[$key]->info['screenshot']; } } foreach ($sub_themes as $key) { $themes[$key]->base_themes = system_find_base_themes($themes, $key); // Don't proceed if there was a problem with the root base theme. if (!current($themes[$key]->base_themes)) { continue; } $base_key = key($themes[$key]->base_themes); foreach (array_keys($themes[$key]->base_themes) as $base_theme) { $themes[$base_theme]->sub_themes[$key] = $themes[$key]->info['name']; } // Copy the 'owner' and 'engine' over if the top level theme uses a // theme engine. if (isset($themes[$base_key]->owner)) { if (isset($themes[$base_key]->info['engine'])) { $themes[$key]->info['engine'] = $themes[$base_key]->info['engine']; $themes[$key]->owner = $themes[$base_key]->owner; $themes[$key]->prefix = $themes[$base_key]->prefix; } else { $themes[$key]->prefix = $key; } } } // Extract current files from database. system_get_files_database($themes, 'theme'); db_query("DELETE FROM {system} WHERE type = 'theme'"); foreach ($themes as $theme) { $theme->owner = !isset($theme->owner) ? '' : $theme->owner; db_query("INSERT INTO {system} (name, owner, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d)", $theme->name, $theme->owner, serialize($theme->info), 'theme', $theme->filename, isset($theme->status) ? $theme->status : 0, 0, 0); } } /** * Implementation of hook_profile_tasks(). */ function fusebase_profile_tasks(&$task, $url) { // 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, ), ); 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); // Clear caches. drupal_flush_all_caches(); // Enable the right theme. This must be handled after drupal_flush_all_caches() // which rebuilds the system table based on a stale static cache, // blowing away our changes. _fusebase_system_theme_data(); db_query("UPDATE {system} SET status = 0 WHERE type = 'theme'"); db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name = 'fusebasic'"); db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name = 'rubik'"); db_query("UPDATE {blocks} SET region = '' WHERE theme = 'fusebasic'"); variable_set('theme_default', 'fusebasic'); variable_set('admin_theme', 'rubik'); variable_set('node_admin_theme', 1); // Set default WYSIWYG settings db_query('INSERT INTO {wysiwyg} VALUES (1,\'\',NULL),(2,\'ckeditor\',\'a:20:{s:7:"default";i:1;s:11:"user_choose";i:0;s:11:"show_toggle";i:1;s:5:"theme";s:8:"advanced";s:8:"language";s:2:"en";s:7:"buttons";a:2:{s:7:"default";a:2:{s:4:"Bold";i:1;s:5:"Image";i:1;}s:4:"imce";a:1:{s:4:"imce";i:1;}}s:11:"toolbar_loc";s:3:"top";s:13:"toolbar_align";s:4:"left";s:8:"path_loc";s:6:"bottom";s:8:"resizing";i:1;s:11:"verify_html";i:1;s:12:"preformatted";i:0;s:22:"convert_fonts_to_spans";i:1;s:17:"remove_linebreaks";i:1;s:23:"apply_source_formatting";i:0;s:27:"paste_auto_cleanup_on_paste";i:0;s:13:"block_formats";s:32:"p,address,pre,h2,h3,h4,h5,h6,div";s:11:"css_setting";s:5:"theme";s:8:"css_path";s:0:"";s:11:"css_classes";s:0:"";}\')'); // Set default input format to Full HTML variable_set('filter_default_format', '2'); // Enable vertical tabs on node type settings page variable_set('vertical_tabs_default', 1); variable_set('vertical_tabs_minimum', '1'); variable_set('vertical_tabs_node_type_settings', 1); // Pathauto default path variable_set('pathauto_node_pattern', '[title-raw]'); // Make an 'editor' role db_query("INSERT INTO {role} (rid, name) VALUES (3, 'editor')"); // 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"); // Change authenticated user's permissions - this is UPDATE rather than INSERT db_query("UPDATE {permission} SET perm = CONCAT(perm, ', search content, view uploaded files') WHERE rid = 2"); // Allow editor role to use admin bar + other default editor permissions db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (3, 'use admin toolbar, collapse format fieldset by default, collapsible format selection, show format selection for blocks, show format selection for comments, show format selection for nodes, show format tips, show more format tips link, administer blocks, access comments, administer comments, post comments, post comments without approval, access content, administer nodes, create page content, delete any page content, delete own page content, delete revisions, edit any page content, edit own page content, revert revisions, view revisions, search content, view uploaded files, administer users',0)"); } ?>

Step 4: Customize the installation profile

You can add/remove the modules in the fusebase_profile_modules function.

For example, if you want the installation profile to automatically enable the link module, you can add the line ‘link’, within the fusebase_profile_modules function return array.

If you want to change the site to a different default theme, change any reference in the example fusebase.profile file of “fusebasic” to the theme that you want.

There are tonnes of other settings that you can add specific to your needs, but I won’t go into it in this tutorial.  Just take a look at the fusebase_profile_tasks function.  Most are pretty self explanatory.  There are some good articles out there that already have this covered such as this one here http://www.mc-kenna.com/drupal/2009/06/building-drupal-installation-profiles