2. What is the API thing?
Features is designed to capture module
“state” information and allows it to be
reproduced on any server.
The features API allows module coders to
add functions that allow this “State” to be
transferred.
Reference: API.txt file in Features module
3. Some Terminology
Feature module – A special module that captures the “state” of
module components needed for the function it defines. E.g.
profiles
Component Type – A grouping of configuration objects
Component – A configuration object that can be exported, e.g. a
view, content type, or CCK field. Components are listed in the
“edit component” field of the create feature page.
Machine Name – An identifier that is unique in and across sites
(as opposed to an DB id field which unique only on a site).
Exportable component – An exportable setting that can exist
only in code. E.g. a view.
Faux-Exportable component – a exportable setting that needs
to be in the db. Settings of this type are used to synchronize
entries in the DB using machine names
4. Feature Component Hashes
The Feature module code maintains various md5 hashes
that are use to compare states. Basically these hashes
are for:
The state defined by the site settings
The state defined by the current feature module code
The last known state set by feature module code
These are used to generate the component status shown
in the Features management page.
5. Feature States
The status of the Feature component as displayed on the
Manage Features pages.
Default – All three component hashes match
Overridden – Site settings do not match
Needs Review – All three hashes are different. E.g.,
local overrides with new feature code.
Rebuildable – Site settings match previous settings but
the feature code is new.
Rebuilding – A rarely “seen” transient state that is set
when a component is being synced with DB.
6. The API Hooks
-`hook_features_api()` defines the component types
- `hook_features_export()` processes a list of components,
detecting any dependencies or further components
- `hook_features_export_options()` provides an array of
components that can be exported for a given type.
- `hook_features_export_render()` renders a set of
components to code as a defaults hook.
- `hook_features_revert()` reverts components of a feature
back to their default (as defined in code) state.
-`hook_features_rebuild()` updates faux-exportable
components back to their default state.
Reference: features.api.php in Features module code
7. A Case Study
Need:
The ldapauth module (part of LDAP Integration suite) has
some complex settings that often need to be duplicate
across a lot of server. Features support is a great way to do
this.
Requirements:
Transfer the module settings that fall into two types:
- Global settings stored as persistent variables
- LDAP server definitions (can be more than one) are stored
in the ldapauth table.
8. A Case Study
Strategy:
Create two Features component types to support the two
kinds of settings.
Utilize Strongarm and Ctools API/Code to simplify coding
Issues:
The LDAP Server settings use DB generated id fields that
do not map across server.
9. Adding Machine Name Support
In order to make the LDAP server configuration entries
exportable, they needed to support a cross site machine
name. This was done by:
• Adding a Machine name field to the ldapauth DB
• Adding code to retrieve/update ldapauth table entries by
machine name ( ldapauth_server_load() )
• Modifying the admin screen to allow for autogeneration /
editing of machine names. ( ldapauth_admin_form() )
• Modifying the initial install process to include this new field.
( ldapauth_schema() )
• Modifying the update process to add field and populate it.
( ldapauth_update_6005() )
10. Define the Component Types
The part of the Features API to implement is hook_features_api(). Here’s
the one for ldapauth:
function ldapauth_features_api() {
$info = array(
'ldap_servers' => array(
'name' => 'LDAP Integration',
'default_hook' => 'default_ldap_servers',
'default_file' => FEATURES_DEFAULTS_INCLUDED_COMMON,
'feature_source' => TRUE,
'file' => drupal_get_path('module‘,'ldapauth‘).'/ldapauth.features.inc',
)
);
if ( module_exists('strongarm') ) {
$info['ldap_settings'] = array(
'name' => 'LDAP Integration',
'default_hook' => 'default_ldap_settings',
'default_file' => FEATURES_DEFAULTS_INCLUDED_COMMON,
'feature_source' => TRUE,
'file' => drupal_get_path('module‘,'ldapauth‘).'/ldapauth.features.inc',
);
}
return $info;
}
11. Component Type Options
For each component type, the <component>_features_export_options()
hook needs to be implements. This tells the Features management GUI
what components to display under each type.
function ldap_servers_features_export_options() {
module_load_include('inc', 'ldapauth', 'includes/ldap.core');
$options = array();
$servers = ldapauth_server_load_all();
foreach ( $servers as $server ) {
$options[$server->machine_name] = $server->name;
}
return $options;
}
function ldap_settings_features_export_options() {
$info = array(
'ldapauth_login_process’=>t('Authentication‘).': ‘.t('Authentication mode'),
'ldapauth_login_conflict’=>t('Authentication‘).': ‘.t('User conflict resolve pr
'ldapauth_forget_passwords’=>t('Authentication‘).': ‘.t('Store user passwords')
'ldapauth_sync_passwords’=>t('Authentication‘).': ‘.t('Sync passwords'),
'ldapauth_create_users’=>t('Authentication‘).': ‘.t('Create new users'),
'ldapauth_alter_username_field’ =>t('Authentication‘).': ‘.t('Alter user name f
'ldapauth_disable_pass_change’=>t('Authentication‘).': ‘.t('Remove password fie
'ldapauth_alter_email_field’=>t('Authentication‘).': ‘.t('Alter email field'),
);
ldapauth_submodules("ldap_settings", $info);
return $info;
}
12. Features Export List
The features_export hook generates the information that goes into the
features module .info file at build time. Each component type needs one.
For the LDAP Global settings, we just define the module dependencies
and them make use of Strongarm’s variable_features_export() function.
function ldap_settings_features_export($data, &$export, $module_name = "") {
$export['dependencies']['ldapauth'] = 'ldapauth';
$submodules = ldapauth_submodules('ldap_settings');
foreach ( $submodules as $submodule ) {
if ( module_exists($submodule ) ) {
$export['dependencies'][$submodule] = $submodule;
}
}
return variable_features_export($data, $export, $module_name);
}
13. Features Export List
For the LDAP Server setting, we define the module dependencies
and return a list of server definition machine names that the user has
selected in the GUI.
function ldap_servers_features_export($data, &$export, $module_name = "") {
module_load_include('inc', 'ldapauth', 'includes/ldap.core');
$export['dependencies']['ldapauth'] = 'ldapauth';
$submodules = ldapauth_submodules('ldap_servers');
foreach ( $submodules as $submodule ) {
if ( module_exists($submodule ) ) {
$export['dependencies'][$submodule] = $submodule;
}
}
foreach ( $data as $component ) {
$export['features']['ldap_servers'][$component] = $component;
}
return array();
}
14. Features Export Render
The features_export_render hook generates the information that goes
into the features module code file(s) at build time. Each component type
needs one.
This feature is also used to generate the md5 hash tag to check for
override and other status.
For the LDAP Global settings, we just make use of Strongarm’s
variable_features_export_render() function.
function ldap_settings_features_export_render($module, $data) {
return variable_features_export_render($module, $data);
}
15. Features Export Render
For the LDAP Server settings, we need to generate the code to create a
servers array with the machine name as a key and it’s settings as a value.
function ldap_servers_features_export_render($module, $data, $export) {
module_load_include('inc', 'ldapauth', 'includes/ldap.core');
$code = array();
$code[] = ' $servers = array();';
if ( is_null($export) ) {// If this is an override check, export all current servers
$servers = ldapauth_server_load_all(TRUE);
foreach ($servers as $server) {
unset($server->sid);
$server = (array) $server;
ksort($server); // Sort because updated tables don't match, new tables
$code[] =" $servers['{$server['machine_name']}'] = “.features_var_export($server, ‘ ‘)."
}
} else {
foreach ($data as $name) {
$server = ldapauth_server_load($name);
unset($server->sid);
$server = (array) $server;
ksort($server);
$code[] = " $servers['{$name}'] = " . features_var_export($server, ' ') . ";";
}
}
$code[] = ' return $servers;';
$code = implode("n", $code);
return array('default_ldap_servers' => $code );
}
The check for import or override check is because if there is a new server entry,
it should be flagged as an override condition.
16. Features Rebuild
The features_rebuild hook is called when a feature is first installed.
Each component type needs one.
For the LDAP Global settings, we just make use of Strongarm’s
variable_features_rebuild () function.
function ldap_settings_features_rebuild($module) {
variable_features_rebuild($module);
}
For the LDAP server settings, we need to get the settings in code and add any
NON-existent server.
function ldap_servers_features_rebuild($module) {
module_load_include('inc', 'ldapauth', 'includes/ldap.core');
$servers = module_invoke($module, 'default_ldap_servers');
foreach ($servers as $server) {
$existing_server = ldapauth_server_load($server['machine_name']);
if ( empty($existing_server) ) {
ldapauth_server_save($server);
}
}
}
17. Features Revert
The features_revert hook is called when a feature is
“reverted” to the settings defined in the feature code
via the admin GUI.
For the LDAP Global settings, we just make use of
Strongarm’s function.
function ldap_settings_features_rebuild($module) {
variable_features_revert($module);
}
18. Features Revert
For the LDAP server settings, any server entry not defined in the code needs
to be deleted. Any existing server entry that is defined in code definition needs
to have it settings update to match the code. Finally, any server entry defined
in the code but not in the DB need to be created.
function ldap_servers_features_revert($module) {
module_load_include('inc', 'ldapauth', 'includes/ldap.core');
$default_servers = module_invoke($module, 'default_ldap_servers');
$servers = ldapauth_server_load_all(TRUE);
$existing_servers = array();
// Update any existing servers in feature and delete those not in the feature.
foreach ( $servers as $server ) {
$machine_name = $server->machine_name;
$existing_servers[$machine_name] = $machine_name;
if ( isset($default_servers[$machine_name]) ) { // Update servers
$default_servers[$machine_name]['sid'] = $server->sid;
ldapauth_server_save($default_servers[$machine_name], TRUE);
} else { // Delete server
ldapauth_server_delete($server, TRUE);
}
}
// Add any servers in the feature that don't exist.
foreach ( $default_servers as $server ) {
if ( ! isset($existing_servers[$server['machine_name']]) ) { // Add in default server not in
unset($server['sid']);
ldapauth_server_save($server, TRUE);
}
}
}