Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Nov 07 2011
Nov 07

In the last post I demonstrated creating a very basic install profile in Drupal 7. It was more or less a stripped down version of the standard profile with a few very minor additions.

I've been getting some great comments on my posts and one I wanted to note was from @david regarding the Profiler project. I've not had a chance to use it yet but it looks very promising. The profiler module provides an improved API and tools to vastly simplify what's necessary in writing your install profiles. While I would guess some more complex tasks still require you to use the raw Drupal API, this tool looks like it could give you a huge head start.

So, in the previous post, one of the additions I wanted to make but couldn't, was to create the default client user. The client name and email address will obviously differ on all sites we build. For this we need to add a new step in the install process to allow us to configure the client account prior to it's creation. All of the code from here on out will sit in our .profile file (recall that this is equivalent to a .module file, but for install profiles).

The first thing we need to do is define our profile tasks.

/**
 * Implements hook_install_tasks().
 */
function brochure_install_tasks() {
  $tasks = array(
    'brochure_client_form' => array(
      'display_name' => st('Setup Client'),
      'type' => 'form',
    ),
  );
  return $tasks;
}

hook_install_tasks() allows us to create new steps in the install process. Each task more or less maps to a function in your .profile - in this case brochure_client_form. The display_name is used as the text displayed in the sidebar with the install profile steps as can be seen in the screenshot below.

If you're wondering about the st() function, it's basically equivalent to the t() function but used in a context where the localization system may not yet be available. You should generally use st() in your install profile where t() would normally be used.

Install profile tasks can be far more complex than what I've presented here and I'd strongly recommend reading through official documentation on install profiles or the API docs for hook_install_tasks().

The only other thing left to do here is to define our form API handler. This is done the same as any other Drupal module with _form(), _form_validate(), and _form_submit() functions inside your .profile file. There's nothing specific to install profiles here.

function brochure_client_form() {
  $form = array();
  $form['intro'] = array(
    '#markup' => '

' . st('Setup your default client account below.') . '

', ); $form['client_name'] = array( '#type' => 'textfield', '#title' => st('Client Username'), '#required' => TRUE, ); $form['client_mail'] = array( '#type' => 'textfield', '#title' => st('Client E-mail Address'), '#required' => TRUE, ); $form['client_pass'] = array( '#type' => 'password', '#title' => st('Client Password'), ); $form['submit'] = array( '#type' => 'submit', '#value' => st('Continue'), ); return $form; } function brochure_client_form_validate($form, &$form_state) { if (!valid_email_address($form_state['values']['client_mail'])) { form_set_error('client_mail', st('Please enter a valid email address')); } } function brochure_client_form_submit($form, &$form_state) { $values = $form_state['values']; // Setup the user account array to programatically create a new user. $account = array( 'name' => $values['client_name'], 'pass' => !empty($values['client_pass']) ? $values['client_pass'] : user_password(), 'mail' => $values['client_mail'], 'status' => 1, 'init' => $values['client_mail'], ); $account = user_save(null, $account); // Assign the client to the "administrator" role. $role = user_role_load_by_name('administrator'); db_insert('users_roles') ->fields(array('uid' => $account->uid, 'rid' => $role->rid)) ->execute(); }

By using tasks you have the ability to do some very customized stuff out of the box for your profile. Creating a client user and assigning a role to them (as done in this example) takes maybe 15 seconds to do for each site. If you create say 3 brochure client sites a month that's about 9 minutes of savings per year! That's enough time to take at least 2 showers!

Oct 24 2011
Oct 24

This is the second post in my series on install profiles. It covers the anatomy of an install profile and creating .install and .profile files. We create a simple brochure style install profile which is based on the standard D7 profile with a few customizations of my our own.

If you haven't already, take a look at my last post on install profiles and create the folder structure described there. Don't worry about the files as we'll create them in this post.

The folder structure

For reference, the structure will be as follows (I've added "libraries" since the last post):

profiles/brochure
profiles/brochure/libraries
profiles/brochure/modules
profiles/brochure/modules/contrib
profiles/brochure/modules/custom
profiles/brochure/modules/features
profiles/brochure/themes

I've found splitting the modules up into the folders as shown here to work best while developing drupal sites. The contrib folder will store any contrib modules we use, the custom folder will be all custom modules we write (I also use this for contrib modules that I'm a maintainer of), and features will store any Drupal features that we create.

brochure.info

Just as with any theme or module we'll need to start with a .info file. Basing this off of the minimal.info we might have something such as:

name = Brochure
description = Install a basic brochure style of website
core = 7.x

; Core modules
dependencies[] = block
dependencies[] = dblog
dependencies[] = field_ui 
dependencies[] = file 
dependencies[] = help
dependencies[] = image  
dependencies[] = menu
dependencies[] = number
dependencies[] = options
dependencies[] = path
dependencies[] = taxonomy
dependencies[] = toolbar
dependencies[] = rdf

; Sub core modules
dependencies[] = boxes
dependencies[] = context
dependencies[] = ctools
dependencies[] = features
dependencies[] = libraries
dependencies[] = pathauto
dependencies[] = strongarm
dependencies[] = token
dependencies[] = views

; Development tools
dependencies[] = devel

files[] = brochure.profile

I personally prefer to split up my modules by an arbitrary category. In this case "sub core" modules are all of the modules that I tend to use on almost all of my sites, like views and ctools. I also generally will use certain modules during development that would be disabled when the site is ready to go live, like devel or even dblog.

I'm not sure the files[] = brochure.profile strictly needs to be defined here as that's normally used for defining files that contain classes, but since both minimal and standard do it, it's probably a good idea.

Download all of the contrib modules specified in the .info file above and drop them into profiles/brochure/modules/contrib/.

brochure.install

Again, the easiest thing to do here will be to base your custom
profile off of minimal.install (or standard.install). In fact, we want everything that the minimal.profile has so you can just copy that straight out and change "function minimal_install()" to "function brochure_install()".

The first part of the file defines the blocks to use on the site. For the moment there's no reason we can't just go with the default blocks.

function brochure_install() {
  // Enable some standard blocks.
  $default_theme = variable_get('theme_default', 'bartik');
  $values = array(
    array(
      'module' => 'system',
      'delta' => 'main',
      'theme' => $default_theme,
      'status' => 1,
     ....

The default theme being used is bartik, which again, is fine for the moment. As we're just going to be installing the site by hand (i.e. not using Aegir), we can just change that theme after install since each brochure site will likely have it's own custom theme.

Looking through the minimal install profile we can see that it defines the following blocks for display:

Module Block (delta) Region system main content user login sidebar_first system navigation sidebar_first system management sidebar_first system help help

If you want to remove or add blocks just copy the way they've done it here. Of course, if you're using contexts and features this becomes unnecessary.

The next thing done in the .install is setting the variables and permissions, and that's about it.

The standard.install takes this stuff to the next level. It does the following:

  • Defines filter formats
  • Sets the default theme
  • Sets up the blocks
  • Creates two node types (page and article)
  • Sets up RDF mappings
  • Configures node options, comments, and user profile stuff
  • Creates a taxonomy vocabulary called "tags"
  • Creates and adds a taxonomy reference field to the article type
  • Creates and adds an image field to the article type
  • Sets up user role permissions
  • Adds a main menu to the site
  • Enables and sets a default administrative theme

Looking through the standard.profile the first time was a bit of overwhelming, particularly with the image field aspect of it (more so because at the time I had no experience with entities or fields). If you take some time going over the rest of the code you'll notice it's all actually quite straight forward module development stuff.

Building a complex install profile requires a very strong knowledge of the Drupal API or at least willingness to learn and spend hours in frustration banging your head on the table when things don't work (I have the bruises to show for it). However, if you're not a strong programmer but relatively comfortable working with features, in one of my next posts I'll try and demonstrate how to completely replace the standard.install using features, so you may find that much more enjoyable :).

Now as it turns out, the standard install profile already gives us the basics for a simple brochure style website! Who'd a thunk!? All we need to do is remove a couple things provided in there and add a few tricks of our own.

At this point, all I want is a few pages of text (i.e. a brochure) for the website. No blog necessary. So copy everything into brochure.install with the exception of the article node type and any fields added to it, the taxonomy field, the article specific rdf mapping, and the comment settings. Also remove the comment related permissions since comment isn't being installed in this profile either. If you don't remove the comment related permissions, your install profile will fail.

brochure.profile

For now we can simply copy minimal.profile (which is identical to standard.profile) and rename the function to start with brochure_ instead of profile_. All this is doing is setting the default site name on install form.

Additions to the install profile

There are still a number of customizations I'll want on a basic brochure site including:

  1. More customized initial install form.
  2. A WYSIWYG editor
  3. A customized set of shortcuts for the client
  4. Adding a client user account
  5. Roles for the client to use (content editor, for example)

When building an install profile in practice I will always start with a base site and then configure it as I want it to be. I add every single little configuration step back into the install profile as I go. For many of the configurations I want to do I need to browse through the core code to understand how components are added and updated. That's how I solved #3 below, for example.

1. When installing a new site you're presented with a form that you must fill in the site mail, account name and mail, country, and timezone. 95% of all of the sites I would build with a profile like this would have the exact same options on the initial install page, so why should I manually fill them in each time. The standard and minimal profiles automatically set the site name, but I'll take this one step further. In my brochure.profile I've added the following:

function brochure_form_install_configure_form_alter(&$form, $form_state) {

  // Pre-populate the site name and email address.
  $form['site_information']['site_name']['#default_value'] = $_SERVER['SERVER_NAME'];
  $form['site_information']['site_mail']['#default_value'] = '[email protected]';

  // Account information defaults
  $form['admin_account']['account']['name']['#default_value'] = 'admin';
  $form['admin_account']['account']['mail']['#default_value'] = '[email protected]';

  // Date/time settings
  $form['server_settings']['site_default_country']['#default_value'] = 'CA';
  $form['server_settings']['date_default_timezone']['#default_value'] = 'America/Vancouver';
  // Unset the timezone detect stuff
  unset($form['server_settings']['date_default_timezone']['#attributes']['class']);

  // Only check for updates, no need for email notifications
  $form['update_notifications']['update_status_module']['#default_value'] = array(1);
}

The last item there changes the update settings so that I won't receive an email for security updates but a message will still be displayed on the site.

2. Adding a wysiwyg to the site is fairly simple. Install something like ckeditor to profiles/brochure/modules/contrib and download the ckeditor library to profiles/brochure/libraries. (Yes, we could use the wysiwyg module for this too, and in fact in practice that's probably what you should choose. However, wysiwyg profiles can be tedious to setup in install profiles and work to make them exportable in features is still underway http://drupal.org/node/624018).

dependencies[] = ckeditor

If you install the ckeditor 3rd party code into your libraries folder you'll also need to add the following line to your .install:

variable_set('ckeditor_path', 'profiles/brochure/libraries/ckeditor');

The reason you need to hardcode the ckeditor path is because ckeditor does not properly support librares (see this issue for more details). This means that it doesn't know to find libraries in profiles (and thus will never find your ckeditor 3rd party code). You could simply install the 3rd party code into the ckeditor module folder itself, but the "clean" way is to use libraries.

These kinds of problems are by no means unique to ckeditor and you'll more than likely run into them as you customize your profiles. Many modules will work perfectly fine until you want to use them in an install profile. The best way of getting around it is to find and fix the issue and submit a patch.

3. For my shortcuts I'll simply use the core shortcut module with an additions. I want to add a quicker way to add a new page from the shortcuts bar. I've added the following code to do just that into my .install.

module_load_include('inc', 'shortcut', 'shortcut.admin');
$shortcut_set = shortcut_set_load('shortcut-set-1');
$shortcut_link = array(
  'link_title' => 'Add page',
  'link_path' => 'node/add/page',
  'menu_name' => $shortcut_set->set_name,
);
shortcut_admin_add_link($shortcut_link, $shortcut_set, shortcut_max_slots());
shortcut_set_save($shortcut_set);

Ensure this code goes after the page content type is created and after menu_rebuild() to ensure that the 'node/add/page' path exists.

4. For every brochure site I create I will need a client account, so I'd like to automate the creation of that as well. Unfortunately the client name and email address won't be identical between each site, so we'll need some user input for that. This will be a focus of the next post.

5. Though not strictly necessary for such a simple site, it makes sense to put our client into some sort of a role besides "authenticated user". This can be done in an identical manner to the way the 'administrator' role was created in standard.install.

$client_role = new stdClass();
$client_role->name = 'content editor';
$client_role->weight = 3;
user_role_save($client_role);
user_role_grant_permissions($client_role->rid, array('administer nodes', 'create url aliases', 'customize shortcut links', 'administer site configuration', 'access site in maintenance mode', 'view the administration theme', 'access site reports', 'block IP addresses', 'administer taxonomy', 'access toolbar', 'administer users'));

My goal here isn't to lock out the client account, but instead remove obvious access to parts of the site that they won't need to use.

Conclusion

When it comes down to it creating an install profile can be as simple or complicated as you want to get. The more complicated the more tedious it will become, but also the more you'll be able to do (and learn). Install profile skills translate right back into module development anyway.

Initially it may seem that unless you plan on creating dozens of sites under the same profile the ROI on building an install profile will not be high. However, as I'll get into later you can use the install profiles as a best practice to help with automating updates to your production site such that it will make sense even if you only ever have a single installation on your profile.

In the next post I'll write more about adding additional site customizations, additional steps, and forms directly into your install profile so that you will get a much more streamlined site out of the box.

Oct 20 2011
Oct 20

Yet another key component to the Drupal Platform is the install profile and it's another one that deserves a few posts to cover it adequately. The goal of this series of posts will be to build an install profile capable of creating a basic brochure-style website - more or less what WordPress's core functionality offers (ok, we'll be doing a bit less for this example ;-)).

Drupal 7 install profiles differ quite significantly from Drupal 6 install profiles so a lot of the material covered here won't necessarily apply if you're working on D6 only. There's already a lot of great documentation to help get you started. Drupal.org has an overview of install profiles that's worth taking a read through, for example. The Drupal documentation also covers all of the common hooks that you may want to use in your profile. But the best place, without question, is just reading through the Drupal core profile code (no, I'm not joking). Check out profiles/minimal, and profiles/standard folders that come with Drupal, particularly the .info, .profile, and the .install files.

To be upfront about things here, I've personally never built an install profile in Drupal 7 so this is a bit of a learning experience for me as well. I know that a lot of the limitations that existed in Drupal 6 have been solved so hopefully the process will be smoother. As always, if you notice anything I've done wrong or could do better please leave a comment as I want to promote best practices for building platforms.

There are a few differences between an installation profile that you'll run manually as you install Drupal (an option that would sit next to minimal or standard in the Drupal install process) versus an install profile to be used for automatically creating sites through Aegir. The primary difference is that an install profile to be used in Aegir should have no options that the user needs to fill out or answer. Defaults are important here. Any kind of setup that the user must do will need to be done after the site it created, not before, possibly with some sort of configuration wizard for this which I'll hopefully write more on later.

For this post I'm only going to run through a typical install profile as opposed to an Aegir-specific one which I'll write on later (and conveniently, can be much simpler than a standard profile).

Anatomy of an install profile:

The initial setup for the install profile is quite trivial. All you need to do is create the folder and the files, and then list out the modules you wish to be enabled on site creation. Basically a combination of creating a new site in a multisite setup and creating a module.

Start with this folder structure in your D7 site:

profiles/brochure
profiles/brochure/brochure.info
profiles/brochure/brochure.install
profiles/brochure/brochure.profile 
profiles/brochure/modules
profiles/brochure/modules/contrib
profiles/brochure/modules/custom
profiles/brochure/modules/features
profiles/brochure/themes

All the non-core modules used on your site will now go into the installation profiles' folder as opposed to sites/all. This isn't required, but will make your install profile more complete and is a good way to keep things organized.

.info

Unlike in D6 install profiles, you define the modules you want to install with your profile in the .info file as a dependency, just as you would with a standard drupal module. This makes install profiles much more consistent with how the rest of drupal packages work, and more importantly, makes our life easier.

.profile

The .profile file is equivalent to the .module file in a module or template.php in a theme. This is where you'll define any hooks required for your profile. Unlike Drupal 6, install profiles in Drupal 7 now have the privilege of running under a fully bootstrapped Drupal. This is a huge benefit, and makes the life of an install profile in D7 much more enjoyable than those poor D6 profiles.

.install

The tough part comes in the .install file. Take a look at the profiles/standard/standard.install file. It starts by defining and creating filter formats and then blocks. It also includes the creation of content types, fields, rdf mappings, roles, and menus.

The good news is that a huge percentage of this, the heavy lifting, can be removed by the use of features. For example, we don't need to create content types, fields, roles, menus, etc. by hand as they're all defined and created by the features we use on our site (see A simple feature for reference).

The bad news is that for more complex profiles you will need to get into the nitty gritty of the Drupal API. In some cases the install profile api (http://drupal.org/project/install_profile_api) can simplify things for you (at the time of writing there is not yet a D7 version), but for most things you should look at other install profiles for examples.

In my next post I'll demonstrate creating the skeleton of a basic profile based off of the Drupal minimal and standard profiles as well as a little pinch of our own special sauce.

Oct 04 2011
Oct 04

Over the past couple years Drush has become an essential part of the Drupal site builder's toolkit. I personally use it daily and like a lot of developers now couldn't imagine building a site without it! (Well, I suppose I could imagine it, but I can also imagine building sites in Drupal 4.6, and it's not pretty ;) ).

Drush is one of those areas of Drupal that has been very well documented and I think it would be useful to compile what I feel are some of the more appropriate areas for site builders. There are many awesome webcasts, though I'm personally biased against them due to my forever inconsistent connection speeds while I'm traveling, so don't take it personally if I pass your webcast by :). Most of the existing documentation is written for D6, but fortunately Drush is Drupal version agnostic so all D6 documentation should work almost identically to D7.

Beginning with Drush

Drush.ws

First of all, one of the best, most comprehensive resources I've found is the official Drush website itself, drush.ws. It contains the full API for all versions as well as examples and other documentation. Having said this though, I wouldn't specifically recommend it as a starting point for new users.

Introduction to Drush

One of the best introductory write-ups I've found was on the 2tbsp blog. It covers everything from installing to basic commands and even touches on site aliases. If you haven't used Drush yet, this is a great starting point.

Drupalize.me

I realize I just got over stating how I don't like webcasts, but I'll make an exception for lullabot's :). Drupalize.me has made a series of videos introducing Drush. If you haven't used Drush much, this is another great starting point. The downside is that only the first two videos are free and the rest you'll need to register for. If all you're looking for are Drush tutorials then it may not be worth it.

Drupal.org

Of course this list would not be complete without a link back to the great documentation on this topic on Drupal.org. The d.o Drush documentation covers mainly introductory Drush knowledge along with some more advanced topics such as synchronizing sites using Drush.

Intermediate to advanced

Integrating your modules

This past May Code Karate posted a good article on getting started with integrating your modules with Drush. It covers the important drush hooks as well as some key drush functions needed to get Drush support into your modules.

Drush synchonization with aliases

I've found a couple of posts on using aliases to handle a dev / production workflow but the one from level ten was my favourite. If you're looking for a quick solution to a time old Drupal problem, using aliases with Drush is definitely the way to go. Just to add to this, using SSH keys between your server and localhost for passwordless authentication may make your life easier.

Development Seed

Though no longer a Drupal shop, they've still got a lot of great resources on their site for this kind of stuff. When Drush 3 was released Adrian Rossouw posted an article detailing some of the new functionality of Drush. Even though Drush is officially at V4 now I believe everything in Adrian's post is still quite relevent.

Drush make

The Drush Make plugin for Drush written by Dmitri Gaskin is another key tool in the platform builder's toolkit. I would even go so far as to say an essential tool. Jared Stoneberg wrote a post on his Stoneberg Design blog a while back that I thought looked particularly useful on creating and using Drush make files. As an aside, Jared is also working on his own Drupal platform for restaurant websites called Aioli.

Also, one of the best places to look for beginner to advanced help on Drush topics (and Drush Make) is in the code itself. It contains plenty of great examples for doing almost everything you'll want to do with the tool.

If you know of other Drush resources you've found particularly useful please let me know in the comments.

Sep 30 2011
Sep 30

In the previous two posts (Features Part 1: A Simple Feature and Features Part 2: Managing Your Feature) I demonstrated how to build a simple feature. The result is something that's particularly useful for two things:

  1. A starting point for new site builds
  2. Dev -> Stage -> Prod style of workflow for site building

If all you want to use features for is the second option, then this is perfectly acceptable. But when you start re-using features across multiple sites, you'll end up needing to fork the feature across each new site build. While you'll no longer have the overhead of creating a blog feature when doing a new site build, you may wish to add new components to your blog feature at some point. How to you push the updated feature across all sites if you've already forked the feature to accommodate necessary customizations?

As a trivial example, assume you have two sites using the same feature, one has 10 blog posts listed per page and the other has 15. i.e. the feature has been forked to allow for the two separate configurations that are needed. Later you decide to add an image field to the blog feature so images can more easily be inserted into blog content. Because the features have been forked, you'll need to manually create the image field (and update the feature) on both sites.

After the site build you will have lost all benefits of using features. This is a shame, because with only a little extra work your feature could be completely re-usable even with the need for different settings on different sites. The idea behind a re-usable feature is to give it Good Defaults TM to begin with so that you don't need to modify it much. But we'll always need to modify some of those defaults.

Prerequisites

This is a more advanced tutorial, and you need to be familiar with the Drupal API to do some of the things described here.

  • You should have read through and have no problem understanding part 1 and part 2 on features.
  • You should be able to write a simple module in Drupal from scratch.
  • Being somewhat familiar with the Drupal API as well as popular contrib modules such as Views will definitely help.

Anatomy of a feature

If you take a look in the module folder of the example_blog feature we built in the previous two posts you'll find a standard module structure (i.e. .module and .info files) and a bunch of .inc files.

example_blog.context.inc			example_blog.info
example_blog.features.field.inc			example_blog.module
example_blog.features.inc			example_blog.strongarm.inc
example_blog.features.taxonomy.inc		example_blog.views_default.inc
example_blog.features.user_permission.inc

Each .inc represents a specific export. You can see above that we have context, field, taxonomy, user_permission, views, and strongarm. Open up example_blog.module and you'll find it's nearly empty, all it does is include example_blog.features.inc which in turn sets up any hooks (ctools, views, cck) that the module needs to use.

Features will never overwrite your .module file, which means you can safely put whatever code you need to in there and not worry about it being overwritten next time you update the feature.

Hooking in

A few of the things we may want to configure in our blog module may include:

  • Home page display
  • Number of posts on the blog listing
  • Block order on the blog page

What we need to do is to basically "extract" those settings from the feature and make the feature configurable itself. In other words, we'll add a second layer of settings to the site.

This can be done either directly in the features .module file or in another .inc file you create and include from the .module. I would personally recommend using the second .inc file. For simplicity, we'll simply edit the .module in this example.

Again, to keep things simple (for me ;-) ) I'm only going to demonstrate configurability of the number of blog posts here. It may make sense in a future post to simply have a bunch of examples for overriding different parts of your site. If there's something you'd be interested in (i.e. how to control block display order, minor setting changes to cck types, etc.) just post a comment on this article and I can add it to that post that may or may not exist at some future date :)

Settings, settings, settings

Open up the example_blog.module file and add a hook_menu(). We need to add an admin settings page for our module so that users can easily make configuration changes.

/**
 * Implements hook_menu().
 */
function example_blog_menu() {
  $items = array();
  $items['admin/config/content/example_blog'] = array(
    'title' => 'Example blog settings',
    'description' => 'Configure the example blog',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('example_blog_admin_form'),
    'access arguments' => array('administer site configuration'),
  );
  return $items;
}

Implement the admin settings form.

function example_blog_admin_form($form_state) {
  $form = array();

  $view = views_get_view('blog_listing');
  $view->set_display('default');

  $default_items_per_page = $view->display_handler->get_option('items_per_page');
  $form['example_blog_items_per_page'] = array(
    '#type' => 'textfield',
    '#title' => t('Items per page'),
    '#description' => t('The number of items to display per page. Enter 0 for no limit'),
    '#default_value' => variable_get('example_blog_items_per_page', $default_items_per_page),
  );

  return system_settings_form($form);
}

Note that in the above example we get our default value directly from the view. Defaults should always be pulled from the feature itself.

The only thing left to do now is to hook into views to set the number of items to display appropriately.

/**
 * Implements hook_views_pre_view().
 *
 * Set the configurable options for views.
 */
function example_blog_views_pre_view(&$view, &$display_id, &$args) {
  if ($view->name == 'blog_listing' && $display_id == 'page_1') {
    $default_items_per_page = $view->display_handler->get_option('items_per_page');
    $view->display_handler->default_display->options['pager']['options']['items_per_page'] = variable_get('example_blog_items_per_page', $default_items_per_page);
  }
}

The key line above here is:

$view->display_handler->default_display->options['pager']['options']['items_per_page'] = ...

This took a bit of time and trial and error for me to figure out. Anytime you need to modify these kinds of objects on the fly you'll just need to get a bit down and dirty with it. I'm not sure if this is the "best" way to handle this, but it works. Each setting you want to export will have it's own unique issues that you'll need to figure out.

Flush your caches and head to admin/config/content/example_blog, set the display items to something else. You should see this successfully change the blog and still not cause the feature to become overridden.

Of course, now you have more settings to export though. Every site I build has it's own feature just for that sites settings. I would add this example_blog_items_per_page variable to strongarm for that feature.

Pitfalls

Anytime you want a feature to be truly re-usable you'll need to make some tough decisions. Primarily: "what should be configurable?" This varies greatly from feature to feature, but try and keep it to just simple settings. If you get complicated you may end up losing a lot of the benefits of features and spending hours and hours on each feature. Just make some decisions and stick to them. Only make settings configurable when you find that they absolutely must be.

In the blog example we used the context module to display blocks in a region of the site. What if you're using different themes that don't have matching regions? Well, then this won't work.

On Wedful we have several themes and ultimately plan to have several dozen. Each of these themes are sub-themed off of a single master theme (which is in itself a sub-theme of zen).

What if you want to distribute your features to clients / end users who definitely don't use the same themes and regions? This is a huge problem, and will continue to be for some time. Fortunately a solution for new sites already exists, and it's called Kit. I won't go into kit here, but Shawn Price has written a post on Kit which I recommend anyone interested in this stuff (which should be ALL Drupal site builders) take a read through.

He's also doing a talk at the upcoming Drupal PWN on this stuff. I wish I could be there for it as I'm sure it'll be full of good stuff.

UPDATE:
It's come to my attention that there's another great post out there on this same topic that Roger Lopez posted just over a year ago with a D6 focus. I'd definitely recommend taking a read through his Building reusable features post as well if you're interested in doing this.

Sep 22 2011
Sep 22

In the previous post we built a basic blog feature. This post will cover details on how to manage that feature (i.e. update, revert, etc.).

Prerequisites:

You don't need much to get going with this, but what you will need is:

  • Read through the previous post and can build a feature
  • Basic use of Drush and the command line

I'll cover drush in more detail in a later set of posts, but for now it's important that you install Drush if you haven't already. Features includes a set of Drush commands that are used to update and maintain each feature. These links should help get you started with Drush:

Note that if you're doing this on a D6 site, the Drush 7.x version will work. Drush is not a module in the traditional sense, and it's Drupal version agnostic.

Terminology

If you browse back to the features admin page (admin/structure/features) the example blog feature should be enabled (enable it if it's not). Features will take notice of any changes you make to any of it's components and tell you they've been "overridden". Due to some of the things that one can do with features, the terminology can occasionally get a bit ambiguous, so it's probably important to define a couple of the terms used:

  • Default (state): All settings in your feature identically match their respective settings on the site (this is the state you're after :) ).
  • Overridden (state): The components of the feature used in the site no longer match the state of them in the feature module. For example, if you were to use the Views UI to change the number of blog posts listed on a page from 10 to 15, your feature would now be overridden as your site is displaying 15 posts, but your feature module specifies 10 posts.
  • Needs review (state): This is effectively the same state as overridden, but generally means there are more complicated changes and it's recommended you review the differences prior to reverting the feature.
  • Revert (action): You can revert an overridden feature to make the site settings match those in your feature module. In the above example you set your site to show 15 posts instead of 10 posts in a blog listing. If you decide that you no longer want that simply revert the feature and you'll be back to 10 posts. Revert is probably the most confusing term used with features.
  • Update (action): Updating a feature is the exact opposite of reverting one, instead of reverting your site to match your feature, you'll update your feature to match your site. In other words, if you prefer to have 15 posts lists, update your feature to bring it back in sync. This will change the setting from 10 to 15 posts in your feature and take your feature back to a default state.

Diffing a feature

So let's give the above a try. Change the display settings on your blog listing view to show 15 posts instead of 10 this will mean our blog feature will no longer be in sync with the settings on the live site. Back on the features listing page you should see that your blog feature is now "overridden".

Click the overridden link to bring you to your full feature page.

The above image shows that the overridden component is "views". If you've enabled the Diff module, the word overridden will link to a diff between your site and your feature (the "review overrides" tab will also give you the same thing).

Reverting a feature

After reviewing the changes, if we've decided we don't like the changes (perhaps another site admin made them, after all), we can revert the feature. Simply select the components you wish to revert (in this case only views) and click the "Revert components" button.

Your site will now be reset to match the code in your feature module and display 10 posts again instead of 15. I've occasionally experienced caching issues where the component is still listed as overridden instead of default as it should be. Flushing caches should sort this out.

Updating a feature

Ok, you've changed your mind now and do actually want 15 posts displayed on your blog listing. Head back to views and switch the number of posts to display to 15.

We now want to sync up the features code so that it matches the site. This way when we take the feature to new sites it will also show 15 posts.

On the command line change to your site folder for this site (sites/blog.scotthadfield.ca perhaps). Run the command:

$ drush features-update example_blog

example_blog should be replaced with whatever you named your feature.

Features and Drush

Though updating your feature is the only thing Drush is required for, you can perform any feature management actions directly in the command line. drush help shows the following commands:

All commands in features: (features)
 features-diff (fd)    Show the difference between the default and overridden state of a feature. 
 features-export (fe)  Export a feature from your site into a module.                             
 features-list (fl,    List all the available features for your site.                             
 features)                                                                                        
 features-revert (fr)  Revert a feature module on your site.                                      
 features-revert-all   Revert all enabled feature module on your site.                            
 (fr-all, fra)                                                                                    
 features-update (fu)  Update a feature module on your site.                                      
 features-update-all   Update all feature modules on your site.                                   
 (fu-all, fua)

We've diff'd, exported, listed, reverted, and updated our feature now, which covers all the bases for feature management.

Pitfalls

I can't stress enough the importance of keeping your site in sync with your features. If you're lazy and your features are perpetually listed as overridden you're going to be in for a world of hurt. This is particularly true when using a dev -> stage -> prod workflow with features or working with multiple site builders at one time.

NEVER have two different developers working on the same features components at the same time. For example, if developer A updates a view in the blog feature and then updates their code from developer B who updated a different view in the feature, developer A will need to redo their work after reverting the feature. This is equivalent to two developers working on the same line of code in the same file at the same time. There will be conflicts. With that said, it is possible for Developer A to update the content type while Developer B updates a view.

In a simple workflow you'll update the feature in your local dev environment, then push the changes to stage, revert the feature on stage (this is where the ambiguity of the terminology really comes into play :)), test, push to production, revert the feature on production, test.

Make as few changes as possible to the feature on the live site, i.e., even configuration updates should be done locally first and pushed out. This way you'll be less likely to break the live site and you won't run into nasty conflicts with your features as you develop the site.

What if you want to use the same feature on two sites, one which will display 10 posts in the listing and the other 15? This is where "fully" re-usable features come into play. Instead of forking your feature it is possible to give them settings of their own. I'll be going into more detail on this in my the third post on features.

Sep 19 2011
Sep 19

I first heard about features at DrupalCon DC and while it seemed like a nice idea, I couldn't really see how it could be practical to someone who can already write their own modules. Of course, I thought exactly the same of views when I first learned of it too.

If you build Drupal sites and you've never built a feature before, it's about time you gave it a go! This post will walk you through what a feature is and how to go about building one. In a later post I'll show you how to make your feature re-usable and why it's important.

A "feature" is simply a collection of drupal objects wrapped up into a module. Most of my features contains views, content types, permissions, image styles (imagecache presets in D6), etc. One of the goals of features was to provide a way to move database configuration into code, which also has the side effect of allowing you track any changes through your favorite revision control tool. While technically a feature is pretty much as simple as that, it becomes a very powerful tool when it comes to building Drupal platforms, re-usability between sites, and the dev -> staging -> production workflow. This concept is so important that there's even a major Drupal 8 initiative in the works with heyrocker behind it: http://groups.drupal.org/node/134569.

A common need for most websites these days is a simple blog. I've built a blog for websites at least a dozen times. The concept is simple enough and it only takes about an hour or two to set one up if you've done it before (not necessarily including theming work), but if you have a re-usable blog feature you'd save yourself that hour or two for every new site. If you bill out at $100/h, that can add up to some real money pretty fast.

Step 1: The prerequisites:

Starting with a fresh D7 "minimal" site install is probably best so you don't include settings from other parts of the site you don't intend to. Download and install a few standard modules that you'll want for the feature:

Next you'll need the features "toolkit" that will consist of a number of modules necessary for exporting various parts of our site. Download and install the following modules:

  • Features - This provides the core UI and functionality for creating features.
  • Strongarm - Strongarm, though it has other uses, in this case it will primarily allow us to export settings from the variables table into your features.
  • Context - Contexts is a bit more difficult to explain, but for our purposes, we'll be using it to allow us to export the block layout among other things.
  • Diff - This allows you to easily compare changes in your features as you're working on your site.

As you get into building features, you'll find you need to change the way you deal with some parts of your site build. One of the bigger changes is how you handle blocks. Because the default Drupal block system is not exportable you need to use a different solution. This is where contexts and boxes come in. Contexts is basically an input/output system... as a block replacement tool. the input is the current 'page' (node, views listing etc), and the output is the positioning of the blocks on the page. It should all make sense in a moment :). For more background on this I strongly recommend reading Affinity Bridge's "abridged" series of posts on the topic:

Step 2: Building a blog

This blog feature will consist of the following:
* Blog content type
* Tags vocabulary
* Blog listing view
* Recent comments block
* Recent posts block
* Notify users of new comments (ie comment_notify)

When creating CCK types for use in features, always try to create unique fields. Features need to be somewhat independent from each other and things will be much smoother if you don't try to create generic fields that can be re-used.

Add a vocabulary called "tags" or "blog tags" (again, try not to share vocabularies across features).

Create the blog content type with a body field (called blog_body) and a term reference (called blog_tags) . Note that all settings (comment, display, etc. that you add to the content type will be exported with the feature).

Create the views necessary for the blog (or download the exports attached to this post):

  • A full listing of blog posts containing the most recent posts
    • Page display (path of /blog)
    • Views rss display (path of /blog.xml)
  • Recent blog entries
    • Block containing the 5 most recent entries
  • New blog comments
    • Block containing the 10 newest comments on the blog

Let's put the recent comments and recent posts block next to each other on the /blog listing page. We can't use the block system for this as it's not exportable, so instead we'll use contexts. Create a new context and call it "blog_blocks". Add both a Views and a Node condition and set them up accordingly. Now add a Block reaction and add the two blocks in the proper regions (or download my context export at the end of this post).

Finally, enable and configure the comment_notify module so that readers / posters will be notified when new comments are posted.

Step 3: Creating the feature

If you made it here, then awesome! We're ready to export our blog feature now. This will give us a fully functional Drupal module that we could potentially take to any site.

Browse to admin/structure/features/create to create the new feature. Be careful when naming features as you don't want to conflict with the Drupal core or contrib module namespace (i.e. Calling this feature "blog" would conflict with the blog module). Give it a name like "blog feature" or "my blog feature" or "super awesome blog". You can safely ignore the version and URL fields for now but I'll touch on that later.

Under "Edit components" select everything we just created.

  • Context: blog_blocks
  • Fields: node-blog-*, comment-comment_node_blog-comment_body
  • Content types: Blog
  • Taxonomy: Blog tags
  • Permissions:
    • All comment_notify permissions
    • All "Node: Blog:" permissions
    • All Taxonomy "Blog Tags" permissions
  • Strongarm: comment_notify_*
  • Views: blog_listing, recent_blog_comments

After you've completed this, you should see a listing on the right hand of the page that looks something like this:

You'll notice some items are in blue, and others black. Features automatically identifies dependencies and adds them as the blue items. For example, all of the comment and node settings for the Blog type have been automatically added from the variables table.

Download your feature and untar it to your sites/mysite.com/modules/features/ folder. On the main features page (admin/structure/features) you should see the blog feature you just created listed there. All you have to do now is enable the feature and you're done!

I'll get into managing your feature in the next post where I'll explain the state and actions columns as well as how to update your feature.

UPDATE:
I've added the full feature export to the attachments as well.

I've found a few other links to check out on this topic:

UPDATE 2:
I just noticed that I made a mistake in building the feature here, I forgot to add the views we created to the feature. The article and feature export has been updated to fix that now.

Sep 16 2011
Sep 16

Building the Wedful platform took me about a year of pain, tears, blood, and triumph (not necessarily in that order) and since then I've been contacted by several people going through the same difficulties themselves with putting together their own Drupal platform. Wedful is designed specifically for couples planning their weddings to be able to easily launch a website and manage the details surrounding their weddings online, so we needed to be able to easily manage hundreds (hopefully tens of thousands someday) in a scalable manner. Some of the people I've spoken to have been looking to build niche products for the restaurant industry, hotel industry, and even one with a similar concept to Drupal Gardens.

Over the next several weeks (more likely months, there's a lot to cover ;-)) I'll be writing a series of blog posts on just this topic to help anyone else looking to build a Drupal platform of their own. Something that will hopefully reduce the pain, tears, and blood aspect of the process :). I'll initially cover all of the fundamentals and building blocks of the process, such as best practices, features, drush, make files, etc. and then get into the bigger topics of install profiles, distributions and using Aegir to actually build a vanilla Drupal platform. Following that I'd like to write about some more advanced topics such as passing data from Aegir to your site and not giving up UID 1 when you give away sites to the masses.

This will all be Drupal 7 centric, though I'm certain that much of the information will apply back to Drupal 6 (I'll try and make notes where this isn't the case) and likely forward to Drupal 8. Since I've done only a small amount of work on platforms in D7 this will be a learning process for me too, as I work through building a comprehensive Drupal 7 platform.

Ultimately I plan on focusing on two main use cases. The first would be a wordpress.com style of service for simple brochure websites. The second will be using the same techniques to manage a single site deployment with a scalable dev -> stage-> live workflow. Both use very similar techniques so almost everything I cover will apply to each use case.

I'll start next week with a few posts on building features. If there's anything anyone's interested in learning more about that's inline with this "platform" topic, just post a comment or send me an email and I'll see if I can work it in.

Aug 31 2010
Aug 31

With Drupalcon Copenhagen now behind us and Drupalcon Chicago approaching, I've found myself thinking about what Drupalcon is and how it's changing.

My first Drupalcon was in Barcelona, I was lucky enough to get to tag along with the guys from Bryght. I had an absolutely amazing time and met dozens of people, many of whom are now quite close friends. To top it off I also met my now fiancee and a future boss (no longer my boss, but still a good friend).

Since then, the twice yearly Drupalcons have consistently been highlights in my year. It's often the only time I get to see many of my friends in person.

Drupalcon is not a conference. At least not in the traditional sense. It's a time where some of the smartest people in the community get together, work on code, figure out problems, and teach each other what they know. It differs from a traditional conference in that there are no paid speakers and it doesn't come with a $2000+ price tag. In addition almost everyone attending is also a participant, whether they're there to hack code, present, contribute to BoF's, etc., everyone at Drupalcon makes it what it is.

Or I should say Drupalcon was that.

Since the first Drupalcon in Antwerp (correct me if I'm wrong), the number of people in attendance has almost consistently doubled every time. With 3000+ people at DCSF and a planned 4000+ attending Drupalcon Chicago, maintaining the personal feel that Drupalcons have traditionally had is simply no longer sustainable and I don't believe possible.

A few of the signs that lead me to believe this are:
* One of the stated goals of DC Chicago in the opening keynote at Copenhagen was to make it the "biggest" Drupalcon ever. I recall in Barcelona the goal was "Best Drupalcon Ever!". Biggest is still a great goal, but it doesn't say anything of the quality of the con, nor if people will enjoy it or not.

* At the end of each conference, traditionally the final keynote includes a slideshow of flickr photos from the conference. This to me is a reminder that the conference was about the attendees. It's an important reminder that the conference isn't so much about the sessions and learning, as it is about the experience of having everyone there in one place at one time. This was absent from this years closing keynote. In fact, this years closing keynote seemed more like the season finale of a reality TV show, than the closing keynote of a Drupalcon.

* DC Chicago will select a set of more "well known" speakers prior to opening up the session proposals and voting to the public. While this is actually quite beneficial to people who need to convince their companies to let them attend it is a big change (possibly the biggest in my eyes) to the way Drupalcons are traditionally a bit more open for anyone in the community to present their ideas. I see this ultimately heading down the road of having the conference organizers select all the speakers, and possibly even moving to the paid speaker and expensive conference price tag model. When the vast majority of the attendees shifts from Drupal contributors to people trying to learn what Drupal is and how it can fit into their company, this is really only natural.

* Lastly, Drupalcons are now being planned multiple years in advance. This is quite different from the planning that normally occurs one Drupalcon in advance.

None of these changes are necessarily bad things, they're just a sign that times are changing.

For me personally, I think Drupalcon will soon no longer be something I look forward to and anticipate, but instead something I go to out of obligation for the work I do.

This doesn't mean I'm not still super excited about the community and new things that are happening in Drupal, but instead that it's time to redirect my energy elsewhere. I think the stuff I'm really gonna be excited about in the future will be the local Drupal camps, and things like the upcoming PNW Drupal Summit (which unfortunately I'll be missing :( ). Also, I think there will be some very cool community stuff happening in new areas with Drupalcon like conferences happening in Asia, South America, and Africa.

The most important aspect of Drupal is the community. It's sad to think that Drupalcons are leaving that behind a bit, but I also don't think there's any other way it can go.

With that said, I had an amazing time in Copenhagen. There were a few issues (as there always are) but overall the conference organizers did a great job putting it together and I thought the sessions had a very good balance from intro to advanced. And I'm definitely looking forward to seeing everyone in Chicago :).

Jul 13 2010
Jul 13

One of the biggest tasks when building a Drupal site is selecting, configuring, and integrating contrib modules into your site. For almost everything you need to do "there's a module for that", but whether you should be using that module is an entirely different question.

For every new module I choose for a site, I go through some quick steps and questions (mostly unconsciously now) to determine whether I should risk a module.

1. Most recent release - While this may not be a good guide of whether the module is stable or not, it tends to provide a pretty good guide of how actively maintained it is. The first thing I do, before even reading through the description is check the latest release. If the project doesn't have a release in over a year this usually isn't a good sign.

2. Issue queue - The second thing I do is check the issue queue. I expect the average module to have about half a page to a page of open tickets. I'm mostly concerned here with whether or not a maintainer is actually responding to tickets and active with the project. If simple tickets with patches have been sitting open for several months with no response from a maintainer, this is a very bad sign. For larger modules you can probably expect several pages of issues. In all honesty, my only concern here is that tickets are being addressed by maintainers.

3. Usage stats - Every project page on Drupal has a link to "usage statistics". The more users a module has the better bug tested it is, in general.

4. Documentation - Is the project description useful? How about the README? Whether you read the docs or not, knowing they exist is important. In my experience a project with no readme and poor documentation is generally very lacking in other areas too.

5. Post install troubles - Any trouble figuring out how to use the module after you've enabled it? Even after you read the README? Probably a sign that you may have problems integrating and configuring the module with your site.

6. Code structure - I almost always take a peek at the code of every module I use on a site these days. If you're not a developer yourself this won't mean as much to you. But taking a quick scan through the code can set off alarms bells pretty quickly.

You'll likely run into modules that fail in every one of these areas. It might be a good idea to take a pass on it and try to find similar functionality elsewhere. If it's a module that's important enough to your site ask to become a co-maintainer. Just please don't create a second identical module on drupal.org because you thought you could do a better job.

There are also a few other tools that can aid in selecting modules such as http://drupalmodules.com which lets you rate and comment on modules you've used.

As a developer, for me choosing a module comes down to three main questions:

  1. How responsive is the maintainer / How active is the development?
  2. How important is it for me to have this functionality?
  3. Do I have time to take the module on and clean it up myself?

Less important functionality simply gets axed if the module isn't up to par. But ultimately a lot of the selection process just comes down to experience as well as asking others what modules have worked for them.

For every site I maintain I keep a running list as to which ones are likely to cause problems. This helps to identify problems when "unexplained" things happen and also so you know which areas you should try and clean up / replace when you've got a few spare moments to work on a site.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web