Import/Export Sample Data and Assets for Kickstart All via Features

This is an overview of the custom module – Sample Data developed and used at DesignsSquare.com that was published at Drupal.org for anyone that finds it useful. It is also at github – designssquare-lib-sample-data branch ‘7.x-1.x’

Issue: Widget or Theme needs sample data for kickstart. There is no automatic, easy and standard way to package Sample Data with all of its assets(i.e. images, videos,etc) part of single artifact deliverable for easy install in Drupal.

Solution: Use Features module along with Sample Data module for sample data exports/imports in an automotive manner without another dependency

The common practice to package widgets and themes at DesignsSquare.com delivered to client is by separating the deliverable into 3 parts – code, Structures&Configurations and Sample Data for KickStart all part of one deliverable via Features. The Sample Data part contains sample nodes, menu instances and assets referenced from content, fields and variables.

To automate the packaging process for Sample Data part of the deliverable, we have created Sample Data module. The Sample Data module does the following:

      1. Export/Imports nodes with Alias path
      2. Export/Imports menus based on Alias path
      3. Handle the assets – images, videos, etc for sample data. Specifically:

          A) assets referenced from sample content or other modules located in default public directory(i.e. sites/default/file)
          B) assets referenced by fields(both core type or custom type)
          C) assets referenced by variables
      4. Overrides for context and StormArm variables

Note: assets referenced by custom fields and variables need to use file_managed functionality to work

1. Export/Imports nodes with Alias path

In order to have alias path, you will have to install and enable alias path module – https://www.drupal.org/project/pathauto. Once the sample data have alias path, then export it from features(admin/structure/features). The Sample Data module will hook into the export and store alias path for each node later to restore at the time of import

2. Export/Imports Menus based on Alias path

Once you have the pathauto module enabled, each node will have the alias path used by Sample Data to export and import Menus. Go to Features(admin/structure/features) and look for section “MENU ALIAS” with options to select menues you like to import/export. It will store the menus in the export and then import to the new instance based on alias path. Since it uses alias path, each menu has to have unique links(alias path) to work

3A) Assets referenced from sample content or other modules

Your sample content may be referencing assets. It is also possible that other modules like “imce” for editor is storing assets. The Sample Data module lets you export those assets. Go to Features(admin/structure/features) and look for section “Content Assets”. In this section, it will display all assets(files and directories) from the public directory. Select the ones you like to export and it will store them for later import in the new instance. If you select directory, then it will store the whole tree of the assets

3B) Assets Referenced by Fields

Your sample data(nodes) may have fields of type ‘file’ that is going to reference assets. The Sample Data module will automatically store those assets when you are exporting the sample data(nodes). It will automatically transfer the assets at the time of import in the new instance. The Sample Data module does the same for custom fields referencing assets and not only the Drupal default such as ‘file’. There is one requirement. The ‘file_managed’ functionality needs to be handling the asset in order for the import/export to work

3C) Assets Referenced By Variables

You may have chosen to store asset references in form of FID in the Drupal variable. The Sample Data module will export/import this assets as long as the name of the variable is stored part of the entry in the file_usage table for field(column) – ‘type’:

 //we also add variable name as the 'type' parameter, so we can export via Sample Data module
        file_usage_add($file, 'module_name', 'VARIABLE_NAME', '1');
        // Save.
        file_save($file);

As you see, the variable name is saved part of the usage entry. This is done, so Sample Data module exports the asset

Once the variable is holding reference to the asset and the variable name is stored part of entry in the file_usage table, then in section ‘VARIABLE ASSETS’ in the Features page(admin/structure/features) there is listed available references for you to select to export/import those assets.

4. Overrides for context and StormArm variables

The “CONTEXT OVERRIDE” provides a solution to the fact that features doesn’t let you export something that is already part of another feature. This is an issue if you say have a widget with context that needs to be overridden by site specific context. So by select the context from “CONTEXT OVERRIDE”, it will store those context to import in the new instance

The “STRONGARM OVERRIDE”, besides the ability of overriding another conflicting feature, it also provides a solution to be able export home page, error 404, 403 that is impossible otherwise because it uses the hard coded path(node/id) specific to each Drupal instance. The Sample Data utilizes alias path to import/export home page, error 404 and 403 pages

To summarize, the Sample Data module is one full solution for exporting/importing Sample data for Kickstart data. It exports/imports nodes and menus using alias path. It handles all the assets referenced by content, fields or variables. It also provides ability to override contexts as well as export/import home page, error 404, 403 pages. It does all for you, so you can focus on more important aspects than handling sample data/assets when building widgets,themes and other cool things

Troubleshooting

I am unable to export Content(nodes) via futures

Ensure you have the correct versions for features(7.x-2.0 or above), feature_uuid(7.x-1.0-alpha4…the dev version on July 2014) and uuid(7.x-1.0-alpha5…dev version on July, 2014). Afterwards, enable the content to by exportable at admin/config/content/uuid_features

The specified file public://artifact-name.jpg could not be copied, because the destination directory is not properly configured

Ensure the export feature module has write access and recreate the feature.

Module Development in Drupal

In this post, we cover different aspects for developing modules in Drupal including creating modules, setting variables and other

Creating Module

Modules location

  • The /modules folder that is reserved for core is not good location for your custom modules
  • The good location is /sites/all/modules folder to keep the contributed and custom modules(/sites/all/modules/contr or /sites/all/modules/custom)

Modules Naming

  • keep lower case
  • its not good practice to include numbers

->the modules description along the name and core version is stored in .info files as following:

name = Sample Module
description = sample module description
core = 7.x
version = 7.x-0.1-dev ;to show up in the admin ui
package = Block Wrapper
files[] = nameOfModule.module
dependencies[] = module1
configure = admin/config/moduleName ;this adds configuration button next to module name in modules list

In order to show up in core admin UI, need .module file. It can be empty but required. Documentation can be included

/**
*@file
*Custom functionally for your custom module
*/

Try, clear cache and check to install the module from admin/modules interface

Create Module Admin Interface

Once installed, next we create an admin UI interface to be able edit configurations and settings of the custom module. To do so, we are going to use hook_config_menu:

function moduleName_config_menu(){
    $items = array();

    //Admin config group
    $items['admin/config/kapasoft'] = array(
        'title' => 'Kapasoft',
        'description' => 'Configure Interface with Driver',
        'access arguments' => array('administer kapasoft configurations'),
    );

    //admin configuraiton - settings
    $items['admin/config/kapasoft/manage'] = array(
        'title' => 'Kapasoft configuration',
        'description' => 'Manage Kapasoft Interface config settings',
        'access arguments' => array('administer kapasoft configurations'),
        'page callback' => 'drupal_get_form',
        'page arguments' => array('kapasoft_config_form'),
    );

    return $items;
}

Here we create the module’s admin page – ‘manage’ in admin UI section ‘admin/config/kapasoft’. Next, clear the cache and see if it appears in the admin menu

You should see an error, because in the ‘page callback’ we specify function that builds a form but its not yet declared. Lets do it now

Admin Menu Form

Here, we create a form displayed in admin UI for our module to manipulate settings. In last step, we specified the function(i.e kapasoft_config_form) that builds the form. Now, we implement it as follows:

function kapasoft_config_form($nodes, &$form_state){
    $form = array();

    $form['overview'] = array(
        '#markup' => t('This interface allows to manage KapaSoft modules Settings'),
        '#prefix' => '<p>',
        '#suffix' => '</p>',
    );

    $form['interface_config'] = array(
        '#title' => t('Interface Config'),
        '#description' => t('Configurations of Driver'),
        '#type' => 'fieldset',
        '#collapsible' => TRUE,
        '#collapsed' => FALSE,
    );

    $form['interface_config']['driver_url'] = array(
        '#title' => t('Driver Url'),
        '#description' => t('url of the driver'),
        '#type' => 'textfield',
        '#default_value' => 'localhost',
        '#required' => TRUE,
    );

    $form['interface_config']['driver_port'] = array(
        '#title' => t('Driver Port'),
        '#description' => t('port of the driver'),
        '#type' => 'textfield',
        '#default_value' => '3000',
        '#required' => TRUE,
    );

    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Save'),
    );
    return $form;
}

Here we add two fields and set default values. Afterwards, clear the cache and go to the admin UI (admin/config/kapasoft/manage) to see the form with default settings.

Make Data Persistent

One of the ways Drupal provides ability to persist almost any type of data are through the variable_get, variable_set and variable_del API. Good for settings but shouldn’t be used for content

Lets, adjust our default values:

function kapasoft_config_settings_form($nodes, &$form_state){
...
 $form['interface_config']['driver_url'] = array(
        '#title' => t('Driver Url'),
        '#description' => t('url of the driver'),
        '#type' => 'textfield',
        '#default_value' => variable_get('kapasoft_driver_url', 'localhost'),
        '#required' => TRUE,
    );

    $form['interface_config']['driver_port'] = array(
        '#title' => t('Driver Port'),
        '#description' => t('port of the driver'),
        '#type' => 'textfield',
        '#default_value' => variable_get('kapasoft_driver_port', 3000),
        '#required' => TRUE,
    );
}

In the highlighted lines, the default value is retrieved from variable. If the variables hasn’t been set,yet, then it sets to the default values as specified(i.e. ‘locahlost’, 3000))

At submission, lets save the new variables as well:

function kapasoft_config_form_submit($form, &$form_state){
    //rebuild the form
    $form_state['rebuild'] = TRUE;

    //Save kapasoft setting variables
    variable_set('kapasoft_driver_url', $form_state['values']['driver_url']);
    variable_set('kapasoft_driver_port', $form_state['values']['driver_port']);
 
    //notify user
    drupal_set_message(t('kapasoft driver settings are saved'));
}

At form submission, the values from the form is taken and saved using the variable_set API

Variable_get With Defaults

So far as implemented, it only works with the form. If the form is not submitted, variables are not saved.

In addition, defaults are set in dozen places that don’t scale. In another words, we would have to edit code for each instance of variable_get/variable_set if the default value changes

So, we going to use hook_install to set the default variables once at the time module is installed. This hook is placed in moduleName.install file as following:

function kapasoft_config_install(){
    //set default variables
    variable_set('kapasoft_driver_url', 'localhost');
    variable_set('kapasoft_driver_port', '3000');

    //get localization function for installation as t() may not be available
    $t = get_t();

    //give user feedback
    drupal_set_message($t('Kapasoft driver configurations created'));
}

function kapasoft_config_uninstall(){
    //Delete variables
    variable_del('kapasoft_driver_url');
    variable_del('kapasoft_driver_url');
    
    //get localization function for installation as t() may not be available
    $t = get_t();
    
    //give user feedback
    drupal_set_message($t('Kapasoft driver configurations removed'));    
}

Next, remove all default values from moduleName.module file, so there is single point of initialization for defaults in the moduleName.install file(i.e. make variable_get(‘kapasoft_driver_url’, 3000) to variable_get(‘kapasoft_driver_url’))

Adding Configuration Button

To add a configuration button next to module name in the modules list, add the following line in the modulesName.info file:

...
configure = admin/config/kapasoft/manage

Here the path ‘admin/config/kapasoft/manage’ is the path specified in the moduleName_config_menu() function above

Shorten Code

We can shorten code by completely removing submit function(i.e kapasoft_config_settings_form_submit()) and let the Drupal built in mechanism handle our form submission by updating the form build function as follows:

function kapasoft_config_form($nodes, &$form_state){
...
//    $form['submit'] = array(
//        '#type' => 'submit',
//        '#value' => t('Save'),
//    );

    return system_settings_form($form);
}

As you see, we uncomment the submit button and return Drupal function ‘system_settings_form() with an argument that contains our admin form. This will handle the submission, save the variables and display the message to user. Note, make sure the variable names are the same as specified in form.

Load Other Modules

To utilize other modules within your module, all you need is to load it as following:

module_load_include('inc', 'location', 'earth');
module_load_include('inc', 'gmap3_tools');

This loads the ‘location’ and gmap3_tools modules. Afterwards, you are able to use any of their modules API

How To add Content Type

Here is a great article Creating Custom Content Type…

How to add Block

see post Blocks in Drupal

Troubleshooting

1. Form structure and values
When implementing form validation, it may be useful to print out the form structure

function bazar_node_form_validate($form, &$form_state){
    //to see what validated - the structure
    dpm($form_state);
    //purposely set error
    form_set_error('','testing');

The ‘bazar_node_form_validation’ is custom form validation function added to the list of function names that are used to validate the form in hook_form_alter:

/**
 * implements hook_form_alter()
 */
function bazar_form_alter(&$form, &$form_state, $form_id){
    //like to add validation but i don't know the form id
    dpm($form_id);
    //i would like to see the structure of the form
    dpm($form);
    //return to browser, clear craches and go to form

    //Afterwards, we can turn on debuging only for our form of interest. From the form id we can figure out the validation callback function to be bazar_node_form_validate
    switch($form_id){
        case 'bazar_node_form':{
            //dpm($form);
            //look for #validate that takes list of function names that are used to validate the form. I can ad custom funciton to this array
            $form['#validate'][] = 'bazar_node_form_validate';
            break;
        }
    }
}

Here, we are adding custom validation function ‘bazar_node_form_validate’ to the form with id ‘bazar_node_form’

2. Verify variables loaded
Here is an example to find out what variables are loaded for your theme function(the theme hook specifies theme funciotn)

function theme_ThemeName_gmap($variables){
    dpm($variables);
}

This will print out all the variables passed to this theme. The theme funciton ‘theme_ThemeName_gmap’ was specified in the theme_hook

Useful Links

  • https://api.drupal.org/api/drupal/