Skip to main content

Collecting and writing configuration settings to setting.php from an install profile

We've recently been working with one of Australia's television networks to build an installation profile that will form the basis of migrating many of their existing sites to Drupal. One of the interesting challenges in the project has been handling collecting additional configuration and settings during the installation profile, but before the database is installed.

by lee.rowlands /

Background

SBS is one of Australia's most well-known brands. With a presence in Television, Radio, Print and increasingly online they're immediately recognisable as a household brand.

After an exhaustive review of various CMS's, SBS decided to standardise on Drupal.

One of the more challenging requirements of their plan to standardise on Drupal is the ability to share content between sites. To this effect SBS have built a Symfony2 and MonogDb powered content repository with a PHP api to allow them to syndicate content from their existing in-house CMS applications.

SBS engaged us to provide consulting on how to best integrate Drupal with the content repository. We came up with a unique solution that allows content (nodes, taxonomy terms, vocabularies and files) to be sent to the content repository when it is created or updated in Drupal. Site editors and administrators can then search and browse the repository from Drupal and select content to import to their site.

In addition after importing they can take a number of actions on the imported content, including edit it, but more importantly - decide on what action to take when the Drupal site receives notification of events for that piece of content from the content repository. For example if the original piece of content on Drupal site A is updated and sent to the content repository, Drupal site B, whilst polling the content repository event log via cron, will detect that a piece of content it has created a derived work from has been updated. It can then take a number of actions based on the options selected by the site editor, including auto-updating the content, firing rules - which could do anything possible from the rules interface, eg sending an email to the editor, flag the content as requiring update - so the editor can manually review or any number of actions. These are implemented as ctools plugins, so it can be easily extended.

The challenge

So one of the key pieces of functionality here is the ability for the site-editor to easily search for content to import.

We identified early on that if we could manage to build these search interfaces with Search API and Views then the SBS team would be able to readily create and adapt search interfaces for any sites they created in a flexible fashion.

Given that the content repository was already indexing its content in Solr, this would mean we could potentially utilise Search api solr and Facet API to build some really sweet search interfaces.

To this effect we decided to use Sarnia module, a great module that uses Search API Solr and Search API to allow you to create searches against Solr indexes even when the content in that solr index is not present in Drupal.

Where it got really challenging

Sarnia creates a new entity type for each search api server you define of type sarnia. This is all fine if you're building a site and installing it once. However the requirements in this case are for an install profile, so the intent is to install it over and over again.

The issues that crop up are a result of timing, in some circumstances a contrib module will call entity_get_info() whilst Sarnia is not in a fully installed state, the module is loaded and sarnia_entity_info() runs. But at that particular point in time, for whatever reason, sarnia_schema() has not yet been installed.

We wrote somepatches for Sarnia to make this a little more robust, and we added some install hooks in the module which provided the search api server (via hook_default_search_api_server()) to force the schema to be installed if there was a timing issue. With some deep knowledge of how it hinged together, we managed to get the installation working fine against a fixed environment.

Where it got really, really challenging

So this is where it got interesting. We were using hook_search_api_default_server() to define the connection options for the solr server provided by the content repository.

However the connection settings we used from outside the SBS intranet were different to those setting required inside their network. And these would obviously be different depending on whether the site you were building was connecting to a production or staging environment, or whether the developer was working at the office or remotely.

We had to come up with a solution that would collect the server connnection settings before installation had commenced and make these available as variables so that we could replace the hard-coded values in our hook_default_search_api_server() with calls to variable_get() to retrieve the connection settings from configuration.

The solution

Using hook_install_tasks_alter() we were able to modify the default installation tasks and use our own function to handle the installation step where users are prompted for database connection settings.

/**
 * Implements hook_install_tasks_alter().
 */
function sbsdistribution_install_tasks_alter(&$tasks) {
  // We take over the settings page.
  $tasks['install_settings_form']['function'] = 'sbsdistribution_form_install_settings_form';
}

Then it was a matter of writing our own settings form

/**
 * Form builder for our install settings form.
 */
function sbsdistribution_form_install_settings_form($form, $form_state) {
  global $install_state;
  // Build the default database settings form.
  $form = install_settings_form($form, $form_state, $install_state);
  // Add anything we needed to the form.
  // eg $form['solr_server'] = ....
  
  // Add our own validation handlers
  $form['#validate'] = array(
    'sbsdistribution_validate_settings',
    // Make sure that the default validation runs.
    'install_settings_form_validate'
  );

  // Add our own submission handler (remove the default).
  $form['actions']['save']['#submit'] = array('sbsdistribution_submit_settings');
  return $form;
}

Now the form handling during installation is slightly different, don't expect to see values in $form_state['values'] in your submit hook, instead you need to add these to the $form_state in your validator.

/**
 * Validation handler for settings form.
 */
function sbsdistribution_validate_settings($form, &$form_state) {
  // Validate that we can actually connect to the solr server with the given settings.
  // ..
  // Save the settings for the submit handler.
  $form_state['storage']['sbssettings'] = $form_state['values']['sbssettings'];
}

Then we had to do the actual storing of these variables. Now at this stage the database connection is not setup so you cannot call variable_set(), you have to use another of Drupal's lesser known api functions - drupal_rewrite_settings(). This is the function Drupal uses to write the $databases variable to your settings.php. But with some minor wrangling you can use it to also write $conf variables to settings.php, which are from that point forward available for use via variable_get(). And therefore available for us to use to define our connection settings for the solr server. Because drupal_rewrite_settings should only be called once (to avoid having your settings overridden) we had to duplicate what the standard install does - ie writing the database settings - then we could write our own $conf settings.

/**
 * Submit handler for CR settings.
 */
function sbsdistribution_submit_settings($form, &$form_state) {
  // Duplicate steps in standard install.
  global $install_state;

  // Update global settings array and save.
  $settings['databases'] = array(
    'value' => array('default' => array('default' => $form_state['storage']['database'])),
    'required' => TRUE,
  );
  $settings['drupal_hash_salt'] = array(
    'value' => drupal_hash_base64(drupal_random_bytes(55)),
    'required' => TRUE,
  );

  // Now inject our custom values.
  foreach ($form_state['storage']['sbssettings'] as $key => $value) {
    // Add our conf settings to settings.php.
    $settings['conf[' . $key . ']'] = $value;
  }

  // Do the actual write to settings.php.
  drupal_rewrite_settings($settings);

  // Do remainder of standard settings submit hook.
  // Indicate that the settings file has been verified, and check the database
  // for the last completed task, now that we have a valid connection. This
  // last step is important since we want to trigger an error if the new
  // database already has Drupal installed.
  $install_state['settings_verified'] = TRUE;
  $install_state['completed_task'] = install_verify_completed_task();

  // Clean up our form state.
  unset($form_state['storage']['sbssettings']);
}

Summary

With a bit of wrangling and custom code, Drupal's installer can be refactored to meet a wide variety of custom requirements, it might take some scratching beneath the surface of install.php and install.inc, but as always with Drupal, there is a wide array of customisation options available to the experienced Drupal developer.

Oh, and if the great work that SBS have done here with Symfony2, Solr and Mongo for their Content Repository and API interests you, be sure to get your ticket to Drupalcon Sydney, they're presenting a session on it.