Handling Assets for Custom Module in Drupal

[UPDATE Aug 22, 2014]There is newer version of this article “Import Export Sample Data And Assets For Kickstart All Via Features” and a Drupal module samle_data at Drupal site. It is also in the git repo DesignsSquare Lib Sample Data at branch ‘7.x-1.x’

You have custom module a plugin that you have made exportable via Features module. This custom module has sample kickstart data that contains assets such as images, video, etc. In this post, we cover how to manage these assets, so when user enables your custom module, those are copied accordingly and automatically. Furthermore, we provide a custom module on github to handle assets for you so you don’t have to ever worry about assets import/export for you sample kickstart data. At last, the assets referenced directly from content is also packaged for auto transfer whenever a custom module is enabled.

Env: DRupal:7.26, Features:7.x-2.0, UUID:7.x-1.0-alpha5+17-dev, Feature_UUID:7.x-1.0-alpha3+15-dev

About uuid_features_file_path

Each artifact exported via Features module receives an attribute – uuid_features_file_path. This attribute specifies where are the artifact located which is used at the time of import(when module is enabled). We are going to set this attribute at the time of export, so it is pointing to our artifact location. Its important to note, we like to keep our assets part of module dir that way all plugin info is in one dir easier to deliver and import for user.

Set uuid_features_file_path At Export

We are going to hook into feature export process via MODULE-NAME_uuid_node_features_export_render_alter(&$export, $node, $module) as following:

function MODULE-NAME_uuid_node_features_export_render_alter(&$export, $node, $module){
    if($module == 'MODULE-NAME'){
        //setting artifact source location so it imports accordingly
        $field_instance = field_get_items('node', $node, 'field_ARTIFACT-FIELD-NAME');
        $export->field_ARTIFACT-FIELD-NAME['und'][0]['uuid_features_file_path'] = drupal_get_path('module','MODULE-NAME').'/imports/'.$field_instance[0]['filename'];
...
}

Here, we first ensure the code is executed for our module by having IF condition. Afterward, we retrieve field containing artifact for particular Node instance. Next, we update the export attribute uuid_features_file_path with a path to the custom module and path to artifact

This will export the custom module sample date into file with uuid_features_file_path specifying assets located in our custom module folder. There is nothing else to do, because at the import, features module will take care of coping the assets from location specified in uuid_features_file_path to the public dir of that Drupal instance in folder assets “uri” is set to

Forget About Assets…

In the above solution, a user is constrained to have the module placed in the exact location and named accordingly as specified in the uuid_features_file_path, otherwise, it will break. How about forgetting about assets of the sample kickstart data, so you can focus building features. Here is a Drupal module that does just that:
https://github.com/kapasoft-config-scripts/designssquare-lib-assets

Once enabled, this module does the following:

  • Handles all assets of the sample data being exported/imported via Features module
  • Besides Drupal core fields, it also works for custom fields referencing assets
  • It stores the assets relative to the module, so user can name the exportable feature or place it as they wish
  • The assets need to be handled by ‘file_managed’ functionality in order for the module to work

With this module enabled, you should never need to worry about assets any more.

How About Assets Referenced From Content …In Sample Data

So far, we have handled asset export/import for cases where the asset is referenced by some field in a node, however. Most of the time, your sample data will have content that will also reference assets(i.e.

<img src="/sites/default/files/img/some.jpg" alt="thumbnail">

). We also like to export/import those assets, so the sample data doesn’t break on clients Drupal instance. Here are steps to accomplish this

1. Make Part of Custom Module

Make sure the assets is part of the custom module directory…let, say the assets are in ‘modules/CUSTOM-MODULE/assets/img’ directory

2. Generate Array of Assets To Import

Run the following script in the ‘PHP Executable’ block to generate an array of assets to export/import:

include_once DRUPAL_ROOT . '/includes/utility.inc';
include_once DRUPAL_ROOT . '/includes/file.inc';

$MODULE_NAME = 'CUSTOM-MODULE-NAME';
$PATH_TO_MODULE = drupal_get_path('module', $MODULE_NAME);
//source dir or location of the files to import relative to the module specified by variable - $MODULE_NAME
$BASE_SOURCE_REL_PATH = '/assets/img';
//the destination folder relative to the public dir of Drupal instance
$DESTINATION_DIR = '/img';

$output = "function _assets_list(){ \n";
$output .= "\$module_dir = drupal_get_path('module', '" . $MODULE_NAME . "');\n";
$output .= "     return array (\n";

$output .= write_file_array('', $MODULE_NAME, $PATH_TO_MODULE, $BASE_SOURCE_REL_PATH, $DESTINATION_DIR);

$output .= "     );\n";
$output .= "}\n";
drupal_set_message("<textarea rows=100 style=\"width: 100%;\">" . $output . '</textarea>');

function write_file_array($rel_path = '', $MODULE_NAME, $PATH_TO_MODULE, $BASE_SOURCE_REL_PATH, $DESTINATION_DIR)
{

    $output = '';
    $full_path = $PATH_TO_MODULE . $BASE_SOURCE_REL_PATH . $rel_path;
    if ($handle = opendir($full_path)) {
        while (false !== ($file = readdir($handle))) {
            if (is_dir($full_path . '/' . $file) && $file != '.' && $file != '..') {
                $output .= write_file_array('/' . $file, $MODULE_NAME, $PATH_TO_MODULE, $BASE_SOURCE_REL_PATH, $DESTINATION_DIR);
            } elseif ($file != '.' && $file != '..') {
                $uri = file_build_uri($DESTINATION_DIR . $rel_path . '/' . $file);
                $output .= "     '" . $uri . "' => array(\n";
                $output .= "     'uri' => '" . $uri . "',\n";
                $output .= "     'filename' => '" . $file . "',\n";
                $output .= "     'source' => \$module_dir.'" . $BASE_SOURCE_REL_PATH . $rel_path . '/' . $file . "',\n";
                $output .= "),\n";
            }
        }
        closedir($handle);
    }
    return $output;
}

In line 4, we specify our custom module that contains the assets to export. In line 7, we specify the exact directory of assets to export relative to our custom module. In line 9, we specify the destination folder to import the assets relative to the public directory of the Drupal instance. In another words, if the sample content references image via tag

<img src="/sites/default/files/img/some.jpg" alt="thumbnail">

then the destination dir is ‘/img’ assuming the Drupal public dir is ‘sites/default/files’. After running this script, it will generate function ‘_assets_list()’ that contains all the assets to export/import and is going to be used part of the installation script as described next step

3. Transfer assets via module installation script

Next, we configure our Custom Module installation script CUSTOM-MODULE-NAME.install to transfer assets whenever the module is enabled

function CUSTOM-MODULE-NAME_install(){
    $t = get_t();

    //looping through the files and coping to the destination
    foreach(_assets_list() as $file){
        $uri = $file['uri'];
        $dir_name = drupal_dirname($uri);
        file_prepare_directory($dir_name,FILE_CREATE_DIRECTORY);
        (!file_exists(drupal_realpath($uri))) ? file_unmanaged_copy($file['source'], $uri) : '';
    }
    drupal_set_message($t('CUSTOME MODULE NAME assets transferred'));
}

/************COPY _assets_list() HERE*******/

NOTE: Please, copy the function _assets_lists() from the step 2 in the same file CUSTOM-MODULE-NAME.install

This script is run when custom module is enabled. It runs through each asset and transfers from the CUSTOM-MODULE/img into the Drupal/public/img directory. Afterwards, those assets are available when referenced from the sample content

4. Clean on Uninstall

You may also like to remove these assets whenever the custom module with sample data is uninstalled, then add the following in CUSTOM-MODULE-NAME_uninstall() in the CUSTOM-MODULE-NAME.install file:

function CUSTOM-MODULE-NAME_uninstall(){
    $t = get_t();

    //looping through the files and removing one by one
    foreach(_assets_list() as $file){
        $uri = $file['uri'];
        $filename = drupal_realpath($uri);
        (file_exists($uri)) ? file_unmanaged_delete($filename) : '';
    }
    drupal_set_message($t('CUSTOM-MODULE-NAME assets removed'));
}

This script is run on module uninstall and it runs through the same list of assets from _assets_list() and removes each asset from the public dir

Summarize

The attribute uuid_features_file_path specifies the location from where to transfer assets at the time of import, so we hooked into the export and updated this attribute to a location of our assets. We keep it in the same custom module dir for easy delivery and import by user. Furthermore, there is a module developed in github that will handle all assets of the sample data kickstart for you. At last, we also go over how to export/import assets referenced by sample data from the content itself

References

http://drupalcontrib.org/api/drupal/contributions!features!features.api.php/7

2 thoughts on “Handling Assets for Custom Module in Drupal

  1. Thanks for publishing these Drupal modules. Its the only solution I have come across that seems to meet my needs. Challenging for me to to install & configure, however the ability build documents with sample content and collect it’s assets off in folder is exactly what I am after.

Leave a Reply

Your email address will not be published. Required fields are marked *