Last updated May 1, 2013. Created by zoo33 on September 30, 2010.
Edited by slucero, smoovb, smokris, univate. Log in to edit this page.

Many modules have a concept of configuration presets – collections of configuration or other types of objects that administrators set up that can then be used in different contexts on a website. A well known example is ImageCache which has its image manipulation presets that allow you to set up groups of image operations that can be applied to images on different parts of the site. In the Views module, administrators define views – objects that are containers for some very complex configuration. These configuration objects are all stored in the database as they are created, but they can also be exported into code, and this is the foundation of the concept of exportables.

With exportables, the exported code of a configuration object can be used in different ways, where the simplest option usually is to paste it into the module's import page to restore a previously exported object back into the database. However, a more powerful way to use exportables is to use the exported code as the actual storage for the configuration, rather than just a source for importing it into the database. This is done by creating a custom, usually site specific module, which implements certain hooks that expose the exported configuration objects to the running Drupal site.

Benefits of using exportables

The advantage of not having the configuration defined in the database is that it's easier to migrate a piece of configuration between, say, a staging and a production site. The configuration can also be kept in version control which makes development and deployment a lot more manageable.

Once an exported configuration object is defined in code, administrators can still make changes to it, which makes them become overridden. This means there is a newer version of the configuration in the database which will take precedence over the one stored in code. There are usually options to revert the configuration to the exported version, or to re-export the overridden version. The Features module is a very convenient tool for managing this process.

Implementing exportable configuration

These are the basics of exportables. Now, let's take a look at how a module can make its configuration presets exportable with minimal effort using CTools' Export and Export-UI APIs. This page is largely based on this blog post by zoo33 which contains a little more detail. The main references for this subject are:

Declaring the CTools dependency

Declare that your module depends on CTools by adding the following line you the .info file:

dependencies[] = ctools

Defining the configuration data

Define the table structure for the configuration objects using hook_schema(). CTools requires that the exportables live in their own database table and have at least a machine readable name field, and a database-only numeric field which is also the primary key. In the example code we'll be using the module name "Mymodule" which will obviously have to be replaced with the name of your module. Here is the example mymodule.install file:

<?php
/**
* Implementation of hook_schema().
*/
function mymodule_schema() {
 
$schema['mymodule_preset'] = array(
   
'description' => t('Table storing preset definitions.'),
   
'export' => array(
     
'key' => 'name',
     
'key name' => 'Name',
     
'primary key' => 'pid',
     
'identifier' => 'preset', // Exports will be defined as $preset
     
'default hook' => 'default_mymodule_preset'// Function hook name.
     
'api' => array(
       
'owner' => 'mymodule',
       
'api' => 'default_mymodule_presets'// Base name for api include files.
       
'minimum_version' => 1,
       
'current_version' => 1,
      ),
    ),
   
'fields' => array(
     
'name' => array(
       
'type' => 'varchar',
       
'length' => '255',
       
'description' => 'Unique ID for presets. Used to identify them programmatically.',
      ),
     
'pid' => array(
       
'type' => 'serial',
       
'unsigned' => TRUE,
       
'not null' => TRUE,
       
'description' => 'Primary ID field for the table. Not used for anything except internal lookups.',
       
'no export' => TRUE, // Do not export database-only keys.
     
),
     
'description' => array(
       
'type' => 'varchar',
       
'length' => '255',
       
'description' => 'A human readable name of a preset.',
      ),
     
'mydata' => array(
       
'type' => 'text',
       
'size' => 'big',
       
'description' => 'My exportable configuration data.',
      ),
    ),
   
'primary key' => array('pid'),
   
'unique keys' => array(
     
'name' => array('name'),
    ),
  );
  return
$schema;
}
?>

In Drupal 6.x, drupal_install_schema() and drupal_uninstall_schema() must also be called explicitly:

<?php
/**
* Implementation of hook_install().
*/
function mymodule_install() {
 
drupal_install_schema('mymodule');
}
/**
* Implementation of hook_uninstall().
*/
function mymodule_uninstall() {
 
drupal_uninstall_schema('mymodule');
}
?>

(In Drupal 7.x, these functions are automatically called implicitly.)

This is mostly a standard .install file, except for the export part of the schema array. This is specific to Ctool's Export API, and it defines how this table's content will be imported and exported. mydata is the table field where the actual configuration data will be saved. Of course, modules can define as many fields as they want for this purpose.

Defining default presets

In order to provide some default configuration presets for the module, you can implement a couple of hooks that CTools provides. These presets will show up in the admin interface without the user having to do anything other than activating the module. This is also how third party modules (and features) would define their own presets for Mymodule.

<?php
/**
* Implementation of hook_ctools_plugin_api().
*
* Tell CTools that we support the default_mymodule_presets API.
*/
function mymodule_ctools_plugin_api($owner, $api) {
  if (
$owner == 'mymodule' && $api == 'default_mymodule_presets') {
    return array(
'version' => 1);
  }
}
/**
* Implementation of hook_default_mymodule_preset().
*
* Provide a couple of default presets.
*/
function mymodule_default_mymodule_preset() {
 
$export = array();
 
$preset = new stdClass;
 
$preset->api_version = 1;
 
$preset->name = 'my_default_preset';
 
$preset->description = 'Default preset';
 
$preset->mydata = 'x';
 
$export['my_default_preset'] = $preset;
  return
$export;
}
?>

The administration interface

In order for CTools to be able to provide an interface for administering the configuration presets, we have to expose the preset schema to the Export UI. This is done by telling CTools that we want to define an 'export_ui' plugin (in the mymodule.module file):

<?php
/**
* Implementation of hook_ctools_plugin_directory().
*/
function mymodule_ctools_plugin_directory($module, $type) {
 
// Load the export_ui plugin.
 
if ($type =='export_ui') {
    return
'plugins/export_ui';
  }
}
?>

The return value tells CTools to look for the plugin in the directory plugins/export_ui within the module directory, so this directory has to be created. Within this directory, create a file called mymodule_ctools_export_ui.inc. The directory structure should then look like this:

mymodule.install
mymodule.module
plugins/
-  export_ui/
-  -  mymodule_ctools_export_ui.inc

In the new file, add the plugin definition:

<?php
/**
* Define this Export UI plugin.
*/
$plugin = array(
 
'schema' => 'mymodule_preset'// As defined in hook_schema().
 
'access' => 'administer mymodule'// Define a permission users must have to access these pages.
  // Define the menu item.
 
'menu' => array(
   
'menu item' => 'mymodule',
   
'menu title' => 'Mymodule',
   
'menu description' => 'Administer Mymodule presets.',
  ),
 
// Define user interface texts.
 
'title singular' => t('preset'),
 
'title plural' => t('presets'),
 
'title singular proper' => t('Mymodule preset'),
 
'title plural proper' => t('Mymodule presets'),
 
// Define the names of the functions that provide the add/edit forms.
 
'form' => array(
   
'settings' => 'mymodule_ctools_export_ui_form',
   
// 'submit' and 'validate' are also valid callbacks.
 
),
);
?>

The code comments should give an idea about what is going on. Finally, in the same plugin file, add the form function for adding and editing the configuration objects, as well as any submit/validate functions that you've specified in the plugin definition. The form can be as complicated as needed, depending on the complexity of the configuration.

<?php
/**
* Define the preset add/edit form.
*/
function mymodule_ctools_export_ui_form(&$form, &$form_state) {
 
$preset = $form_state['item'];
 
$form['description'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Description'),
   
'#description' => t('The human readable name or description of this preset.'),
   
'#default_value' => $preset->description,
   
'#required' => true,
  );
 
// Add Mymodule's configuration interface.
 
$form['mydata'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Mymodule configuration'),
   
'#description' => t('This is just the simplest possible example of a configuration interface.'),
   
'#default_value' => $preset->mydata,
   
'#required' => true,
  );
}
?>

This is all that is needed to provide a fully functional administration interface and exportability for your configuration presets. There is a lot more you can do to customize how the administration pages work. See the export-ui.html file for more information!

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

Most of this works well on D7, except you need to remove "Implementation of hook_install()." function. D7 is smart enough to install and uninstall schema for you.

Also adding following to schema will give you awesome machine-name javascript action...

'admin_title' => array(
        'type' => 'varchar',
        'length' => '128',
        'description' => 'The administrative title.',
      ),

If you are trying to rely upon the default data saving functionality it is required to include the 'primary key' value of the 'export' array, see export.html and export.inc (around like 180) for details.

--
Damien McKenna | Mediacurrent

I quite like CTools. It's useful, complete, and has many interesting features, with extensive documentation both on drupal.org (though less than expected) and on various other websites due to its popularity as tool suite for module developers. While I haven't used it yet when writing modules and I won't try to pretend that I understand everything, I've installed it and read enough of its code to see its complexity but ease of use, especially through the large amount of examples provided. It covers everything from AJAX to forms to plugins, and is highly extendable.

The only negative attribute I can think of is that it may be harder for first-time module writers to start using compared to other, smaller module tools for developers purely because of the large amount of documentation that needs to be found and read before starting.