Solving Menu Import Issue when Featuring Sample Data in Drupal

[UPDATE] – alias menu import module was rolled into “Sample Data” module that is of newer version. it is listed on Drupal site
Here is post about it – http://margotskapacs.com/2014/07/importexport-sample-data-and-assets-for-kickstart-all-via-features/

Currently, if you would like to export and import the sample data( like kickstart) via features, then the menus for your data breaks because it uses the hard coded path(i.e. node/id) and the new instance has different path for the nodes imported. To my understanding there is a work in place to fix this in future release of features by using the path uuid but of January, 2014 that is not available at least in stable version

The solution presented here utilizes ‘pathauto’ and its generated alias path for each node to do the mapping between the same nodes on two different Drupal instances. In another words, we export existing menu structure with alias path as reference. Once these nodes are imported(before menu import script is run) on the another Drupal instance, it automatically generates the default alias path(for custom alias, please, section ‘If Custom Path Alias…’ below). Since these alias path are the same between the drupal instances, the installation script looks up the mapping when structuring and building menu.

To maintain the existing menu structure, you will have to run a script that will generate the structure used by the menu importer script as showed in this post. This may be improved in future but author didn’t have time at this time and may not be a value since there is path uuid project in progress that will be the better solution in long run

In addition, the path for nodes has to be unique as well.

Requirement:

  1. Pathauto module has to be installed and enabled
  2. Each node have to have path alias or setting ‘Generate automatic URL alias’ selected under ‘Url path settings'(this is default behavior once ‘pathauto’ enabled, so you should need to do anything unless you have nodes present and you just installed/enabled ‘pathauto’. In that case, you have to go into each node and check the setting ‘Generate automatic URL alias’)
  3. The nodes has to be already imported before running the menu_import on the new Drupal instance
  4. (Recommended) When importing Nodes via features, ensure the StrongArm variable or configuration of ‘pathauto_node_pattern’ is also imported. This ensures that for imported nodes the generated alias path is the same as the one in current Drupal instance. This is especially important if the alias pattern for nodes is not the default one

The solution consists of two parts:

Part I: Build Exportable Menu

To build current menu structure for menu of your choice run the following script in “PHP Execute” block on Drupal instance you want to export the menu from(copy/past and then specify menus to export on line 3):

include_once DRUPAL_ROOT . '/includes/utility.inc';
//specify menues to export
$menus = array('menu-bottom-menu', 'main-menu');
$mainMenuArray = array();
//mlid to alias map
$alias_map = array();
$menu_items = array();

$output = "function _menus_altered(){ \n";
$output .= "return array (\n";

foreach ($menus as $menu) {
    $links = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC))
        ->fields('ml')
        ->condition('ml.menu_name', $menu)
        ->condition('ml.hidden', '0')
        ->orderBy('weight')
        ->execute()
        ->fetchAll();

    foreach ($links as $key => $link) {
        $links[$key]['options'] = unserialize($link['options']);
    }
    $mainMenuArray = array_merge($mainMenuArray, $links);

    $mlid_ins = db_select('menu_links', 'ml')
        ->fields('ml', array('mlid', 'link_path'))
        ->condition('ml.menu_name', $menu)
        ->condition('ml.hidden', '0')
        ->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);

    $menu_items = $menu_items + $mlid_ins;

    $menu_ins = menu_load($menu);
    if ($menu_ins) {
        $menu_item = 'array(';
        $menu_item .= "'menu_name' => '" . $menu_ins['menu_name'] . "',";
        $menu_item .= "'title' => '" . str_replace("'", "\'", $menu_ins['title']) . "',";
        $menu_item .= "'description' => '" . str_replace("'", "\'", $menu_ins['description']) . "'";
        $menu_item .= ")";
        $output .= $menu_item . ",\n";
    } else {
        watchdog(WATCHDOG_NOTICE, "menu: $menu not found");
    }
}
$output .= "    );\n";
$output .= "}\n\n\n";

//generate the map between mlid and alias path
foreach ($menu_items as $mlid => $options) {
    $alias_map[$mlid] = drupal_get_path_alias($options['link_path']);
}
$output .= "function _menu_installed_items() {\n";
$output .= "return array (\n";
foreach ($mainMenuArray as $key => $link) {
    $alias_path = drupal_get_path_alias($link['link_path']);

//prepare link to pass in menu_link_save()
    $link_export = $link;
    $link_export['link_path'] = $alias_path;
    unset($link_export['mlid']);
    unset($link_export['router_path']);
    unset($link_export['options']['identifier']);
    $link_export['plid'] = ($link_export['plid']) ? $alias_map[$link_export['plid']] : $link_export['plid'];
    $link_export['p1'] = ($link_export['p1']) ? $alias_map[$link_export['p1']] : $link_export['p1'];
    $link_export['p2'] = ($link_export['p2']) ? $alias_map[$link_export['p2']] : $link_export['p2'];
    $link_export['p3'] = ($link_export['p3']) ? $alias_map[$link_export['p3']] : $link_export['p3'];

    $output .= "'" . $alias_path . "' => " . drupal_var_export($link_export) . ",\n";
}
$output .= ");\n";
$output .= "}\n";

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

First copy and past the above code in to “PHP Execute” block. Second, specify the menus you like to export in the line 3 variable ‘$menus’
This script generates two arrays encapsulated in functions _menu_installed_items() and _menus_altered() that you will have to supply in your custom module installation file in next step

Part – II : Build Custom Module to Import Menus

Create a custom block with the following .install script:

function MODULE_NAME_install(){
    $t = get_t();

   // clear menus and create new ones if doesn't exist
    foreach(_menus_altered() as $menu){
        //remove menu links for clean install
        menu_delete_links($menu);
        drupal_set_message($t('Links deleted from menu - ' . $menu['menu_name']));

       if(!menu_load($menu['menu_name'])){
           //doesn't exist...lets create one
           drupal_set_message($t('Missing menu '.$menu['menu_name'].'...creating'));
           menu_save($menu);
       }
    }

    //map containing the structure of the menu
    $alias_map = array();

    //shuffle so that parents are always above the children menu items
    //this is important so when building the $alias_map child is not being inserted without parent already present
    $all_items = _menu_installed_items();
    $parents_p1 = array();
    $parents_p2 = array();
    $parents_p3 = array();
    $parents_p4 = array();
    $parents_p5 = array();

    foreach($all_items as $key => $item){
        if($key == $item['p1']){
            $parents_p1[$key] = $item;
        }
        if($key == $item['p2']){
            $parents_p2[$key] = $item;
        }
        if($key == $item['p3']){
            $parents_p3[$key] = $item;
        }
        if($key == $item['p4']){
            $parents_p4[$key] = $item;
        }
        if($key == $item['p5']){
            $parents_p5[$key] = $item;
        }
    }

    $sorted_menu_list = array_merge($parents_p1,$parents_p2,$parents_p3,$parents_p4,$parents_p5);

    //add links
    foreach ($sorted_menu_list as $key => $item) {
        $alias_link_path = $item['link_path'];
        //look up node based on the alias path of existing drupal instance
        $item['link_path'] = drupal_get_normal_path($alias_link_path);

        //drupal_set_message('plid: '.$item['plid']. ' alias_map:' . $alias_map[$item['plid']] );
        $item['plid'] = ($item['plid'] !== 0 && !empty($alias_map[$item['plid']])) ? $alias_map[$item['plid']] : $item['plid'] ;
        $item['p1'] = ($item['p1'] !== 0 && !empty($alias_map[$item['p1']])) ? $alias_map[$item['p1']] : $item['p1'] ;
        $item['p2'] = ($item['p2'] !== 0 && !empty($alias_map[$item['p2']]) ) ? $alias_map[$item['p2']] : $item['p2'] ;
        $item['p3'] = ($item['p3'] !== 0 && !empty($alias_map[$item['p3']])) ? $alias_map[$item['p3']] : $item['p3'] ;

        $installed = menu_link_save($item);
        if($installed !== FALSE) {
            drupal_set_message($t('Menu Item : '. $key . ' installed'));
            //drupal_set_message('INSERT: link_path: '.$alias_link_path. ' mlip:' . $installed );
            $alias_map[$alias_link_path] = $installed;
        }else{
            drupal_set_message($t('Menu Item : '. $key . ' was not installed'));
        }
    }
    menu_link_save($item);
    menu_cache_clear_all();
    drupal_set_message($t('Installed Menu'));
}

//ADD THE TWO FUNCTIONS FROM PART I HERE

Make sure you add the two functions generated in Part I (i.e._menus_altered(),_menu_installed_items() ) in the .install file where it says ‘//ADD THE TWO FUNCTIONS FROM PART I HERE’. These are used by the script to build the new menus

Thats all….in future we hope to automize the above process by creating module if the problem is not solved soon by the features,uuid modules.

If Custom Path Alias…

The features module currently does not export/import path_alias for nodes. This is okey if you are using path alias of the node that were generated by default by pathauto module, however. If not, then your custom alias path for nodes are not exported/imported via features and the above menu export/import solution will not work. We have created module to export/import path alias published at https://github.com/kapasoft-config-scripts/designssquare_alias_path. By enabling this module, your custom path alias is going to be exported and then imported for all nodes.

Troubleshooting

All of the menu items does not import

Make sure all the menu items imported are pointing to path alias that are unique. If not unique, then you have to break into more than one menu import to work.

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

How To Implement Custom Field Types In Drupal

In this post, we go over on how to create, handle and display a custom fields in Drupal. In addition, we look on situations when your custom field contains an artifacts that requires an additional care

The hook_field_schema() defines the columns. The table will be only added after an actual field is created(attached to content type). So, if you like to see if the data is being added for the custom field, look into table ‘field_xxx’

The hook_field_shema() needs to be define and declared in the module .install file as following:

function NAME-OF-SCHEMA_field_schema($field) {
    $schema = array();
    $schema['columns']['class'] = array(
        'type' => 'varchar',
        'length' => 50,
        'not null' => FALSE
    );
  
    $schema['columns']['image'] = array(
        'description' => 'The {file_managed}.fid being referenced in this field.',
        'type' => 'int',
        'not null' => FALSE,
        'unsigned' => TRUE,
    );

    $schema['foreign keys'] = array(
        'image_fid' => array(
            'table' => 'file_managed',
            'columns' => array('image' => 'fid'),
        ),
    );

    return $schema;
}

In the above example, a schema of 2 fields are created. One of the fields are a file upload, so there is additional foreign constrain defined.

Test from PHP Execute block

$field = field_info_field('field_designssquare_slider_layer');
module_load_install('field_designssquare_slider_layer');
$schema = (array) module_invoke('field_designssquare_slider_layer', 'field_schema', $field);

Look into Watchdog logs for a message. You should see the message if the install was performed

The columns can also be checked in the table ‘field_NAME_OF_FIELD’ in database

Specify Display

You may like to create a display format for the custom field. The field display gives user ability to select display for the field. Developer can take it and create separate layout via custom theme functions wrapped into template file

To declare display:

/**
 * Implements hook_field_formatter_info().
 */
function videojs_field_formatter_info() {
  return array(
    'videojs' => array(
      'label' => t('Video.js : HTML5 Video Player'),
      'field types' => array('file', 'media', 'link_field'),
      'description' => t('Display a video file as an HTML5-compatible with Flash-fallback video player.'),
      'settings'  => array(
        'width' => NULL,
        'height' => NULL,
        'posterimage_field' => NULL,
        'posterimage_style' => NULL,
      ),
    ),
  );
}

The ‘field types’ specify to which types the display is available. The ‘settings’ is custom parameter passed to the display(will cover later). So, this declares the display available for user to specify from UI. Next, lets implement the display:

/**
 * Implements hook_field_formatter_view().
 */
function videojs_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  if ($display['type'] !== 'videojs') {
    return array();
  }
  if (empty($items)) {
    return array();
  }

  if ($field['type'] == 'link_field') {
    //for field type of 'link_field' do things differently
  }

  $settings = $display['settings'];
  $attributes = array();
  if (!empty($settings['width']) && !empty($settings['height'])) {
    $attributes['width'] = intval($settings['width']);
    $attributes['height'] = intval($settings['height']);
  }


  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  return array(
    array(
      '#theme' => 'videojs',
      '#items' => $items,
      '#player_id' => 'videojs-' . $id . '-' . str_replace('_', '-', $instance['field_name']),
      '#attached' => videojs_add(FALSE),
      '#entity' => $entity,
      '#entity_type' => $entity_type,
      '#attributes' => $attributes,
      '#posterimage_style' => !empty($settings['posterimage_style']) ? $settings['posterimage_style'] : NULL,
    ),
  );
}

The display formatter calls a custom function declared as following:

function videojs_theme() {
  return array(
    'videojs' => array(
      'variables' => array('items' => NULL, 'player_id' => NULL, 'attributes' => NULL, 'entity' => NULL, 'entity_type' => NULL, 'posterimage_style' => NULL),
      'template' => 'theme/videojs',
    ),
}

Here, the theme custom function used for rendering display is declared. This theme custom function uses a template file ‘theme/videojs’ to render output, but before that the hook_preprocess_CUSTOM-THEME-FUNC is called, so the variables can be preprocessed.

Render the Display

To render specified display as following:

$video_render_array = field_view_field(
                        'node',
                        $entity,
                        'field_video_file',
                        array(
                            'type' => 'videojs',
                            'label' => 'hidden',
                            'settings' => array(
                                  'width' => '300',
                                  'height' => '400',
                                  'posterimage_field' => NULL,
                                  'posterimage_style' => NULL,
                            ),
                        )
                    );
 $content = render($video_render_array);

Here the field ‘field_video_file’ uses a display ‘jw_player’ that takes one parameter jwplayer_preset. Afterwards, the renderable array returned from the display is being rendered.

Create Custom Field Type

To declare custom field type use hook_field_info() as following:

function MODULE-NAME_field_info() {
 return array(
        'OUR_CUSTOM_FIELD_NAME' => array(
            'label' => t('Slide Layer'),
            'description' => t('field containing info about one of the layers part of the Rev Slider'),
            'settings' => array('max_length' => 255),
            'instance_settings' => array(
                'text_processing' => 0,
            ),
            'default_widget' => 'OUR_CUSTOM_FIELD_NAME_widget',
            'default_formatter' => 'OUR_CUSTOM_FIELD_NAME_formatter',
        ),
    );
}

Besides name and description, the default widget and formatter is specified. Next, lets declare and implement the widget for this field type :

/**
 * Implements hook_field_widget_info().
 * Expose Field API widget types.
 */
function MODULE-NAME_field_widget_info() {
    return array(
        'OUR_CUSTOM_WIdGET_NAME' => array(
            'label' => t('Revolution Slide Layer'),
            'field types' => array('OUR_CUSTOM_FIELD_NAME'),
        ),
    );
}

The widget name is given and assigned to our custom field type – OUR_CUSTOM_FIELD_NAME via ‘field types’. After declaring the widget, lets specify the form for user to enter values:

/**
 * Implements hook_field_widget_form().
 */
function MODULE-NAME_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
    $element += array(
        '#type' => $instance['widget']['type'],
        '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
    );
    return $element;
}

/**
 * Implements hook_element_info().
 * Declare the field Form API element types and specify their default values.
 */
function MODULE-NAME_element_info() {
    $elements = array();
    $elements['OUR_CUSTOM_WIdGET_NAME'] = array(
        '#input' => TRUE,
        '#process' => array('designssquare_rev_slider_layer_field_process')
    );
    return $elements;
}

In the function hook_field_widget_form(), it specifies the widget type, so we can use hook_element_info for specifying form. Afterwards, the hook_element_info() is used to specify function returning the renderable array with columns of the schema from the .install file

At last, the widget form is specified:

function designssquare_rev_slider_layer_field_process($element, $form_state, $complete_form) {
    $element['layer_header'] = array(
        'layer_header' => array(
            '#type' => 'markup',
            '#weight' => 0,
            '#markup' => '<p>' . t('Slide Layer '.($element['#delta'] + 1)) . '</p>',
        ),
    );
$element['image'] = array(
        '#title' => t('Image'),
        '#type' => 'managed_file',
        '#weight' => 8,
        '#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'),
        '#default_value' => isset($element['#value']['image']) ? $element['#value']['image'] : '',
        '#upload_location' => REV_SLIDER_DEST.'/img',
        '#states' => array(
            'visible' => array(
//                ':input[title="content_choice_'.$element['#delta'].'"]' => array('value' => 'image'),
                ':input[name="content_choice['.$element['#delta'].']"]' => array('value' => 'image'),
            ),
        ),
        '#upload_validators' => array(
            'file_validate_extensions' => array('jpg jpeg png gif'),
            // Pass the maximum file size in bytes
            'file_validate_size' => array(MAX_SIZE_LIMIT_DS*1024*1024),
        ),

    );
    // To prevent an extra required indicator, disable the required flag on the
    // base element since all the sub-fields are already required if desired.
    $element['#required'] = FALSE;

    return $element;
}

We still have to declare and implement hook_field_is_empty($item, $field), so it know when the field is considered to be empty

function MODULE-NAME_field_is_empty($item, $field)
{
    return !isset($item);
}

Here, we tell that empty is when no instance of our custom field is present

Validating Custom Field Values

To validate the custom field, we first register our custom validation function via hook_formt_alter():

function MODULE-NAME_form_alter(&$form, &$form_state, $form_id)
{
    switch ($form_id) {
        case 'OUR-FORM-ID':
            //assign validation
            $form['#validate'][] = 'some_custom_validate';
            //assign submition
            $form['#submit'][] = 'some_custom_submit';
            break;
    }
}

Since this is a general hook, we filter out only the form of our interest. (For finding Form ID, please, see post ‘Forms In Drupal Overview‘). Afterwards, we specify our custom function for validating the form

Next, the validation logic is declared:

function some_custom_validation(){
 if(empty($form_state['values']['some_field']['und'])){
         form_set_error('some_field[und]', t("error message comes here"));
}

On validation, our custom validation function is being called in which we perform validation for our custom field values

Handling Artifacts For Custom Field

If your custom field contains files, then there is extra care of keeping them permanent as well as removed on deletion. In the above section “Validating Custom Field Values”, we registered our submission processor function. By default all artifacts via file_manage is saved temporary. Next, we ensure the artifacts are permanent on creation and deleted on the remove event:

function some_custom_submit($form, &$form_state)
{
  
        if ((isset($form_state['clicked_button']) && $form_state['clicked_button']['#value'] == "Delete")) {
            //custom field needs to be deleted
            //remove artifact image
            $file = file_load(['values']['our_custom_field']['image']);
            if ($file) {
                file_delete($file);
                file_usage_delete($file, 'MODULE-NAME');
            }
        } else {
            //custom field needs to be added
            //make permanent image file
            $file = file_load(['values']['our_custom_field']['image']);
            if ($file) {
                // Change status to permanent.
                $file->status = FILE_STATUS_PERMANENT;
                //all permanent files needs an entry in the 'file_usage' table
                $id = (isset($form_state['node']->nid)) ? $form_state['node']->nid : 0;
                file_usage_add($file, 'MODULE-NAME', $form_state['node']->type, $id);
                // Save.
                file_save($file);
            }
        }
    }
}

First, we check to see if the node is being deleted or created. Afterwards, we load the artifact and make it permanent or remove it if being deleted

See post ‘Handling Assets For Custom Module In Drupal’ for how to automize artifact import/export

Handling Multiple Instances of Custom Field

In case, user chooses your custom field to be more than one time for specific node, then there is module field_remove_item that allows easy for them to remove any of the instance, however. You will have to handle the artifacts again. That easy to do via hook_field_validate()

function MODULE-NAME_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors)
{
    //remove artifacts  on 'remove item' bottom
            foreach ($items as $key => $item) {
               //see if any of the layers is selected to be deleted
                if ($item['field_remove_item']) {
                    //remove image
                    $file = file_load($item['image']);
                    if ($file) {
                        file_delete($file);
                        file_usage_delete($file, 'MODULE-NAME');
                    }
                }
            }
}

Here, we loop through each instance of your custom field since we don’t know exactly which of the one is being removed. The instance with field ‘field_remove_item’ set, is the one we have to remove artifacts, so we have if statement following with standard way of removing asset managed with manage_file

Uninstalling Custom Fields

If you developed a custom field and installed as a separate module than it will create a lock where you will not be able to uninstall the module. There are two solutions that i am aware. The quick and dirty is to update table ‘fields_config’ to remove the dependency. Another solution is to create a separate module for the purpose to run uninstall hook during which it removes the field, thus, removing the lock so it becomes available to be uninstalled. Lets look at the last solution

Here is the hook_uninstall() for uninstalling the custom module:

function MODULE-NAME_uninstall()
{
    //remove fields from content types
    foreach(_get_bundles_for_field_type('CUSTOM-FIELD-TYPE') as $bundle){
        field_attach_delete_bundle('node', $bundle);
        drupal_set_message('uninstalled field for bundle:'.$bundle);
    }

    //remove tables
    drupal_uninstall_schema('CUSTOM-FIELD-SCHEMA');

    // remove variables
    db_query("DELETE FROM {variable} WHERE name LIKE 'CUSTOM-FIELD-SCHEMA_%'");

    // Remove assets.
//    file_unmanaged_delete_recursive(file_default_scheme() . '://slider');

    // Clear the cache.
    field_info_cache_clear();
}

The function ‘_get_bundles_for_field_type’ returns all the bundles referencing the custom field. Afterwards, we use field_attach_delete_bundle API To remove our custom field type as well as all of its instances. Next the schema is removed, the variables deleted and files removed. At last, we clear the cache

Here is the implementation of _get_bundles_for_field_type():

//returns all the bundles of entity instances that references a field instance with a given type
function _get_bundles_for_field_type($field_type)
{
    $field_map = field_info_field_map();
    $bundles = array();
    foreach ($field_map as $field_name => $item) {
        if ($item['type'] == $field_type) {
            foreach ($item['bundles'] as $bundle_list) {
                foreach ($bundle_list as $bundle) {
                    $bundles[] = $bundle;
                }
            }
        }
    }
    return array_unique($bundles);
}

Render The Field

Troubleshooting

1. PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ‘public://some/path/img.jpg’ for key ‘uri’: INSERT INTO {file_managed}

This issue came up when our custom field was a file and by reinstalling the field it created a duplicate entry in the manage_file table. The solution was clean file_manage table when instance with our custom field was delete

//clean file_manage database on field instance delete
function MODULE-NAME_node_delete($node){
    if($node->type == 'CERTAIN-TYPE'){
        //removes images from file_manage/file_usage tables
        $field_instance = field_get_items('node', $node, 'field_FIELED-NAME');
        foreach($field_instance as $key => $instance){
            $file = file_load($instance['image']);
            file_delete($file);
            file_usage_delete($file, 'MODULE-NAME');
  ...
}

The hook-node_delete($node) is called when any node is being deleted. Here, in line 5, we look up our custom fields for the node being deleted and then clean the file entry in file_manage and file_usage tables.

This also removes the images from Drupal public directory(by default sites/default/files). We did made files being copied when module is being enabled:

function MODULE-NAME_uuid_node_features_rebuild_alter(&$node, $module){
...
            $source = _get_source_file($final_file->uri);
            file_unmanaged_copy($source, $final_file->uri);
...
}

function _get_source_file($uri){
    $source = drupal_get_path('module','MODULE-NAME').'/imports/public/'.substr($uri, 9);
    if(!file_exists($source)){
        watchdog(WATCHDOG_NOTICE, 'MODULE-NAME Import: Image does not exists at '.$source);
    }
    return $source;
}

The hook_uuid_node_features_rebuild_alter is covered in the article Make Custom Field With File Exportable In Drupal Features. We just extend it with file transfer when module feature is being enabled. In highlighted line, we copy from predefined location to a Drupal public folder.

See also post “Handling Artifacts for Custom Module In Drupal” for solution on handling assets such as images, files

2. Drupal claim “Field type(s) in use”

This message is displayed when you would like to disable the module but Drupal doesn’t permit. To solve problem, go to ‘field_config’ table and update the ‘type’ and ‘module’ text for your custom field. More info on the issue https://drupal.org/node/1284380

Fields pending deletion

This comes up after the custom field is removed but the chrom job run only once which is not sufficient. It needs to run several times.

Reference:

  • http://clikfocus.com/blog/how-set-custom-field-type-using-drupal-7-fields-api
  • http://drewpull.drupalgardens.com/blog/drupal-7-field-api-sample
  • http://drupaltutorial4u.blogspot.com/2011/04/how-to-create-custom-field-for-drupal-7.html
  • https://drupal.org/project/examples
  • http://drewpull.drupalgardens.com/blog/drupal-7-custom-field-formatter
  • http://drupal.stackexchange.com/questions/49958/hook-field-schema-does-not-work
  • http://nodesforbreakfast.com/article/2012/02/24/create-custom-form-api-elements-hookelementinfo-drupal-7
  • https://api.drupal.org/api/drupal/includes%21database%21schema.inc/group/schemaapi/7
  • http://www.slideshare.net/zugec/fields-in-core-how-to-create-a-custom-field

Make Custom Field with File Exportable in Drupal Features

In this post, we cover a case where we have a custom field that contains a file and we would like to be able export/import it with a Features module in Drupal

Env: Drupal 7, UUID Features 7.x-1.0-alpha3+1(dev), UUID 7.x-1.0-alpha5+1(dev)

Intro

We have content type with a field field_elastic_slide that contains a file among other things(see our other post for “How To Implement Custom Field Types In Drupal”). We are exporting Nodes with this field using features module. Without doing anything, the export will contain FID(File ID) of the file which is not going to be relevant in another Drupal instance since there is completely different FIDs. So, we will alter the export to store all info of a file in the export instead only FID of the file. Afterwards, we will hook into the import process to use this file info to import the file in the new Drupal Instance

Alter Export To Include All File Info

We are going to use hook_uuid_node_features_export_render_alter(&$export, $node, $module){..} to alter the export as following:

function MODULE-NAME_uuid_node_features_export_render_alter(&$export, $node, $module){
        if($module == 'MODULE-NAME-DOING-EXPORT'){
        $field_instance = field_get_items('node', $node, 'field_elastic_slide');
        foreach($field_instance as $key => $instance){
            $file = file_load($instance['image']);
            $export->field_elastic_slide['und'][$key]['image'] = (array)$file;
            ...
        }
    }
}

In line 5, we load all the file by its FID. Afterwards, we alter the export for our custom field to store array of file info instead only FID. The Feature module will take from there and write the file info in the file ending .features.uuid_node.inc part of the export. So far, so good.

Alter Import To Import File

When importing custom fields containing file, we have to save the file in the table file_managed by ourselves. The feature module will do the work. It will only do the work of importing files for the default field types(file, text,etc). So, we will save the file in the table file_managed and that will generate the necessary FID to be set for our custom field.

To alter import, we use hook_uuid_node_features_rebuild_alter as following:

function MODULE-NAME_uuid_node_features_rebuild_alter(&$node, $module){
    if($module == 'MODULE-NAME'){
        $field_instance = field_get_items('node', $node, 'field_elastic_slide');
        uuid_features_load_module_includes();
        foreach($field_instance as $key => $instance){
            $uuid = $instance['image']['uuid'];
            unset($instance['image']['fid']);
            unset($instance['image']['timestamp']);
            $file_to_save = (object)$instance['image'];
            $final_file = file_save($file_to_save);
            $node->field_elastic_slide['und'][$key]['image'] = $final_file->fid;
            
            //transfer file into public dir available 
            $source = _get_source_file($file_to_save->uri);

            file_unmanaged_copy($source, $file_to_save->uri);
...
}

function _get_source_file($uri){
    $source = drupal_get_path('module','MODULE-NAME').'/imports/public/'.substr($uri, 9);
    if(!file_exists($source)){
        watchdog(WATCHDOG_NOTICE, 'MODULE-NAME Import: Image does not exists at '.$source);
    }
    return $source;
}

Here, the export is retrieved and available in the $node variable. So, in line 3, we retrieve our custom field containing file info we saved in the export in the previous step. Afterwards, we remove some info among it the FID that is irrelevant in the new Drupal instance and is not needed. Next, in highlighted line, we use File API to save the file that makes entry in table file_managed. Then, we set the new FID to our custom field leaving rest for Features module to continue with the import process. At last, the file is copied from predefined location to a Drupal public dir.

Clean Afterwards

When an instance with our custom field is deleted, we want to ensure to delete the file from the file_mage and file_usage tables. Otherwise, there is going to be an error – PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 next time re-enabling the module or uploading the file with the same name.

//clean file_manage database on field instance delete
function MODULE-NAME_node_delete($node){
    if($node->type == 'CERTAIN-TYPE'){
        //removes files from file_manage/file_usage tables
        $field_instance = field_get_items('node', $node, 'field_FIELED-NAME');
        foreach($field_instance as $key => $instance){
          if(isset($instance['image']) && $instance['image']){
            $file = file_load($instance['image']);
            file_delete($file);
            file_usage_delete($file, 'MODULE-NAME');
          }
  ...
}

The hook_node_delete($node) is called every time a node is being deleted. So, in line 3, we check that the particular node of our interest is being deleted. If it is, then we retrieve the field containing artifact. At last, in line 7, we load the artifact by its FID before removing from file_managed/file usage table and file system as well

This also removes the files from Drupal public directory(by default sites/default/files) that was copied when module enabled as described above

Summary

In short, for exporting and importing custom fields containing files, there is an extra work of physically saving the file that needs to be done by ourselves and is not done by Features module. To do the task at hand, we altered the import so that, instead only the FID, it stores all the info of the file in the export. Afterwards, we used this info to physically import the file(save in managed_file table) and set the new FID to our custom field containing the file in the New Drupal Instance that import takes place

Troubleshoot

The specified file [..] could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions

This was coming up when trying to copy artifacts via file_unmanaged_copy but the destination directories were missing.

//ensure dir is present
                $directory = drupal_dirname($final_file->uri);
                file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
                
                file_unmanaged_copy($source, $final_file->uri);

By adding the file_prepare_directory() in front of file_unmanaged_copy(), creates a directories if missing. As result, the file_unmanaged_copy() executes without any errors

References:
http://www.phase2technology.com/blog/making-your-module-features-exportable/
https://origin.previousnext.com.au/blog/how-handle-embedded-images-when-migrating-content-plone-drupal

Rendering Page Programmatically To Create Permanent URLs like /shop, /gallery

In this post, we cover how to render a page programmatically in a context of practical need to have a permanent generic URLs for widgets such us ‘Shop'(i.e. /shop), ‘Gallery'(i.e./gallery) etc. One may argue that by creating these permanent generic URLs you are limiting others because those URLs are not available unless the module implementing them is disable, however.

We believe permanent URLs can help people get up Drupal Site for the first time much faster and more convenient. We also believe it can be a nice entry point for your custom plugin or feature. At last, current stable version of feature modules doesn’t support URL import/export for dynamic URLs but works for permanent URLs

1. Create Permanent Generic URL

To create permanent URL, we use hook_menu() as follows:

function MODULE-NAME_theme_menu() {
    $items['basic-page'] = array(
        'title' => 'Basic Page',
        'page callback' => 'display_basic_page',
        'access arguments' => array('access content'),
        'type' => MENU_NORMAL_ITEM,
        'file' => 'inc/basic_page.pages.inc',
    );

    return $items;
}

This creates a url ‘/basic-page’ and once requested the callback function ‘display_basic_page’ is called. This callback function will call our custom theme function to generate the content:

2. Declare Callback and Custom Theme Functions

First, lets declare the callback function in file inc/basic_page.pages.inc file as follows:

function display_basic_page(){
 //query for the first instance for the basic_page
 $nid_basic_page = db_select('node', 'n')
        ->fields('n', array('nid'))
        ->fields('n', array('type'))
        ->condition('n.type', 'basic_page')
        ->range(0,1)
        ->orderBy('nid', 'DESC')
        ->execute()
        ->fetchCol();


    if(isset($nid_basic_page[0]) && is_numeric($nid_basic_page[0])){
        //prepare content for rendering
       @ToDO retrieve all contexts that apply for the current content type and pass it to init_content to activate them
       init_content($nid_basic_page[0], 'some_context_id');
    }

    return array(
            '#theme' => 'render_basic_page',
            '#view_mode' => 'full',
            '#type' => 'page',
        );
}

/***
 * Prepares node content for rendering including set in cache, enable context,etc
 * This is intended for building single point entry for widgets as described in blog post
 * http://margotskapacs.com/2014/02/rendering-page-programmatically-to-create-permanent-urls-like-shop-gallery
 * @param $node_id
 *      the id of node
 * @param string $context_id
 *      the id of context ot activate
 */
function init_content($node_id, $context_id = 'state_wide'){
    //load node
    $node_to_render = node_load($node_id);

    $content = node_view($node_to_render, 'full');

    //this seems to be standard Drupal content setup, so to be consistent
    $nodes = array(
        'nodes' => array(
            $node_to_render->nid => $content,
            '#sorted' => true,
        )
    );

    //set content in cache so its available for others
    drupal_set_page_content($nodes);

    //load context after content cache is set, so hook_context_load_alter receives the content
    if(module_exists('context')){
       //retrieve active contexts
        $all_active_contexts  = context_active_contexts();
        foreach($all_active_contexts as $key => $context){
            context_set('context', $context->name, $context);
        }
//        context_flush_caches();
    }else{
        watchdog(WATCHDOG_NOTICE, 'The module - context is not present. Unable set context with id ' . $context_id);
    }
}

First, the node instance is queried from database. Afterwards, this node is set in content cache. Next, we activate any context for this page. At last, our custom theme function ‘render_basic_page’ is specified to generate content. Next, lets declare our theme function:

 
function MODULE-NAME_theme(){
    $current_module = basename(__FILE__, '.module');
    $current_theme = $GLOBALS['theme'];


    return array(
        'render_basic_page'=> array(
            'template' => 'page',
            'render element' => 'page',
            'path' => drupal_get_path('theme',$current_theme).'/templates',
            'preprocess functions' => build_page_preprocessors('render_contact', $current_module)
        ),
    );
}

/***
 * builds list of default theme and all modules page preprocessors including one custom specific to the theme function
 * This is intended for building single point entry for widgets as described in blog post
 * http://margotskapacs.com/2014/02/rendering-page-programmatically-to-create-permanent-urls-like-shop-gallery
 * @param $theme_func_name
 *      the name of theme function the list of hooks are assigned
 * @param $module_name
 *      the module name implementing theme function
 * @return array
 *      return an array of all theme hooks in an order of execution consistent with Drupal order
 */
function build_page_preprocessors($theme_func_name, $module_name){
    $current_theme = $GLOBALS['theme'];

    $prefix_processors =  array(
        'template_preprocess',
        'template_preprocess_page',
        $module_name . '_preprocess_'.$theme_func_name,
    );
    $post_processors = array(
        $module_name . '_preprocess_page',
        $current_theme . '_preprocess_page',
    );
    $other_module_preprocessors  = module_preprocessors('preprocess_page');
    $combined_processors  = array_merge($prefix_processors,$other_module_preprocessors,$post_processors);

    return $combined_processors;
}

/**
 * retrieves list of functions with hook provided
 * @param $hook
 *   hook name (i.e. preprocess_page, preprocess_block)
 * @return
 *   list of hook functions in format of module_hook
 */
function module_preprocessors($hook){
    $preprocessors = array();

    //retrieve all modules with the hook - preprocess_page
    $modules = module_list();
    foreach($modules as $module){
        if (module_hook($module, 'preprocess_page')) {
            $preprocessors[] = $module.'_preprocess_page';
        }
    }

    return $preprocessors;
}

Here, the custom theme function ‘render_basic_page’ is declared. We also specify the template file and path to it to used for rendering content. In addition, we specify the name of variable(i.e. ‘page’ which is conventional variable name for Drupal) where to put the Drupal generated variables available in preprocessor functions.

At last, we set all page preprocessors. We have externalized into function build_page_preprocessors to build a list of all preprocessors. Besides other module page preprocess, there are 5 total preprocessors. The ‘template_preprocess_page’ & ‘template_preprocess’ are the functions that sets default variables such as ‘classes_array’, ‘site_name’,etc. The ‘MODULE-NAME_preprocess_THEME-FUNC-NAME’ preprocessor function is the default preprocessor function called if no preprocessor functions are set. We use it for logic specific to the theme_function that is mostly used to load the node to render. Afterwards, all other module preprocessors are included. Afterwards, the preprocess function – ‘MODULE-NAME_preporcess_page’ is where the module specific logic is implemented. As the last, we specify themes preprocess function, so the themer can decide last on how the variables are presented and customize as they wish

3. Preprocess Node Instance For Rendering

We have to specify the content to render on this page that we do in the preprocess function. Every custom theme function, by default have a preprocessor function ‘hook_preprocessor_NAME-OF-THEME-FUNC’ that we use in this case to process content:

function MODULE-NAME_preprocess_render_basic_page(&$vars){
    //retrieve current node being loaded(alternative is to get the node from content cache)
    $current_node = array_slice($vars['page']['content']['system_main']['nodes'],0,1);
    
    //set content
    if(isset($current_node) && !empty($current_node)){
        //prepare for theme_preprocess_page function
        $vars['node'] = array_values($current_node)[0]['#node'];
    }else{
          drupal_set_message(t('It appears there is no Basic Page created. Please, go to \'Content\'->\'Add Content\'->\'Basic Page\' or import sample data'));
        $vars['page']['content'] = array(
            '#markup' => "" ,
        );
    }

Here, first we retrieve the current node to be rendered. This can be done from content cache or page structure as in our case. At last, we set the $vars[‘node’] to the node we rendering so the other preprocessor functions have it available. In case, there are no nodes available to render, then we display message.

Templates

With this approach the Page scope template is not set and The Node scope template is the default. In case, you would like to use your own, then one way to configure each would be via preprocess hooks as following:

function MODULE-NAME_preprocess_node(&$vars){
    if(arg(0) == 'basic-page'){
        $vars['theme_hook_suggestions'][] = 'node__basic_page_no_wrapper';
    }
}

function MODULE-NAME_preprocess_page(&$vars){
    if(arg(0) == 'basic-page'){
        $vars['theme_hook_suggestions'][] = 'page__basic_page_widget';
    }
}

In the custom theme function, we specified the preprocessor hooks including -MODULE-NAME_preprocess_node and MODULE-NAME_preprocess_page, so they are called. Here, in the first hook we set the node template to be ‘node–basic-page-no-wrapper.tpl.php’ and then in the second hook the page template is set to be ‘page–basic-page-widget.tpl.php’ based on the path(i.e. arg(0)).

Summary

In this post, we covered the basic page scenario but this can be useful for more complicated cases like a ‘shop’, ‘gallery’, etc for plugins that you like to provide to your users out of box with little or minimal configurations. All they have to do is go to the generic permanent URL.

Troubleshooting

1. Error message $breadcrumb or $message are not set

The variables – $breadcrumb and $message is set in processor function template_process_page. Make sure this function is specified when declaring your custom theme function:

function HOOK_theme($existing, $type, $theme, $path){
  return array( 
        ...     
       'render_portfolio' => array(
            'template' => 'page',
            'render element' => 'page',
            'path' => $current_theme . '/templates',
            'preprocess functions' => build_page_preprocessors('render_portfolio', $current_module),
            'process functions' => array('template_process','template_process_page'),
        ),
);
}

This will set template_process_page function to be executed and, thus, set $message and $breadcrumbs variables

Reference:

http://chicago2011.drupal.org/sessions/render-api-drupal-7

Breadcrumbs in Drupal

In this post, we go over manipulating the structure while altering the presentation of breadcrumbs in Drupal Framework

When the task is to update breadcrumbs in Drupal, we try to separate the task into two sub-tasks:

  • Manipulating Breadcrumbs Structure
  • Altering Breadcrumbs Presentation

Manipulate Breadcrumbs Structure

We have found that the best way to manipulate breadcrumbs structure is in the page preprocessor function(in template.php file). Lets, say we need to insert additional item in the breadcrumbs list as the last element as well as add icon img in front of first item:

function THEMENAME_preprocess_page(&$vars, $hook) {
    $node = $vars['node'];
    if (isset($node)) {
        $breadcrumb = drupal_get_breadcrumb();
        $breadcrumb[0] = '<i class="icon-home home-icon"></i>'.$breadcrumb[0];
        $breadcrumb[] = $node->title;
        drupal_set_breadcrumb($breadcrumb);
    }
}

In the line five, the array of breadcrumbs is retrieved. In the line six, we add an icon in front of the first element of the breadcrumbs. In the line seven, we add additional item that is the title of current page at the end for breadcrumbs

A better way to manipulate Breadcrumb structure might be via hook_menu_breadcrumb_alter(&$active_trail, $item) hook as following:

function HOOK_menu_breadcrumb_alter(&$active_trail, $item) {
    
         $end = end($active_trail);
        if ($item['href'] == $end['href']) {
            //the last element is of the current page
            $last = array_pop($active_trail);//store the last temporarily
            $active_trail[] = array(
                'title' => 'Portfolio',
                'href' => base_path().'path/to/portfolio',
                'localized_options' => array(),
            );
            //put back the last element
            $active_trail[] = $last;
        }else{
            $active_trail[] = array(
                'title' => 'Portfolio',
                'href' => base_path().'path/to/portfolio',
                'localized_options' => array(),
            );
        }
}

Here, we first check to see if the laset element of the current breadcrumb trail is for the current page. If so, we store it before putting our own new breadcrumb element ‘Portfolio’. As you see, there is three elements – title, href and localized_options to create new breadcrumb element and by adding to the active trail array, we accomplish the task

Alter Breadcrumb Presentation

One way to alter presentation is int he hook_breadcrumb function(in tempalate.php file)as following:

function THEME-NAME_breadcrumb($variables) {
    $breadcrumb = $variables['breadcrumb'];

    if (!empty($breadcrumb)) {
        $breadcrumbs = '<ul class="breadcrumb">';

        $count = count($breadcrumb) - 1;
        foreach ($breadcrumb as $key => $value) {
            if ($count != $key) {
                $breadcrumbs .= '<li>' . $value . '</li>';
            }
            else{
                $breadcrumbs .= '<li class="active">' . $value . '</li>';
            }
        }
        $breadcrumbs .= '</ul>';

        return $breadcrumbs;
    }
}

We are overriding the bootstrap implementation and updating with our new theme requirements. In line 6, the appropriate class attribute value is added. In lines 14, the separator(‘active’ class) is inserted per our theme design.

Render Breadcrumbs

To render breadcrumbs:

      <?php if (!empty($breadcrumb)): print $breadcrumb; endif;?>

Issues

function drupal_set_breadcrumb() is not defined
This error appear when parameter of the function drupal_set_breadcrumb() is not in a proper format(needs to be array of links)

Add and Manipulate Pages with Menu System in Drupal

In this post, we cover different ways to add and manipulate pages with Menu System in Drupal.

Every page loaded in Drupal:

domainname.com/path/of/somekind

is transformed as following request:

domainname.com/index.php?q=path/of/somekind

This is what you will see if the ‘clean url’ setting is turned off.
In Drupal, every path is routed through single page – index.php. This is called front controller design pattern seen in many other web frameworks today. Drupal determines where to route the request by looking at the path(everything on the right of domainname.com)

Drupal takes the path and references an index it has that assigns every path to a function call. If it finds that path in the index than it calls that function and expects the function to tell Drupal what to do next. Most of the time it will provide some content to built into the final page, but it may as well redirect to go to another page or present error message like access denied

Every module determines which paths are assigned to which call back functions and all that data is stored in db table ‘menu_router’

Simple Menu Callback

In your module file MODULENAME.module, add the following:

function MODULENAME_menu() {
    $items['pages'] = array(
        'title' => 'Menu system examples',
        'description' => 'Menu system example that returns a string.',
        'page callback' => 'register_member_page',
        'access callback' => TRUE,
    );

    return $items;
}

function register_member_page() {
    $build = array(
        'header_text' => array(
            '#type' => 'markup',
            '#markup' => '<p class="lead">' . t('Membership Registration') . '</p>',
        ),
       'example_form' => drupal_get_form('collect_member_info_form'),
    );
    return $build;
}

This is implementation of hook hook_menu(). The hook_menu() expects array of call back function(i.e. register_member_page) along other info. Here the Url path defined is ‘/pages’. The ‘page callback'(i.e.register_member_page()) is the call back function routed to this url – ‘/pages’ that will tell what to do next. The ‘access callback’ specifies permissions.

We can supply content to page request in 2 ways:
1. Through string containing html and text
2. Through the build array containing content in a structred array that can be later manipulated by other modules before rendered

Next implement the page callback function ‘pages_string’ that supplies the content as simple string(Nr.1)

function pages_string() {
    $output = '
    <p>Pages can be returned as strings.</p>
    <p>Pages can be returned as <em>render arrays</em>.</p>';
    $output .= theme('item_list', array(
            'title' => 'Render arrays are better because...',
            'items' => array(
                'They allow content to be modified as an array.',
                'Arrays are a lot easier to modify than HTML.',
            ))
    );

    return $output;
}

Here we structure the output as a string and use theme(‘item_list’…) function to help construct a list order ed or unordered with heading if you wish.

Next, make sure the module is installed and go to domainname.com/pages to verity it works

How to Use Render Arrays and Tabs

When callback function returns build array instead a string, then other modules can manipulate the content by adding/removing or reorder the elements in content before its rendered

function pages_menu() {
    $items['pages'] = array(
        'title' => 'Menu system examples',
        'description' => 'Menu system example that returns a string.',
        'page callback' => 'pages_string',
        'access callback' => TRUE,
    );
    $items['pages/default'] = array(
        'title' => 'String',
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -10,
    );
    $items['pages/render-array'] = array(
        'title' => 'Render array',
        'description' => 'Menu system example using a render array.',
        'page callback' => 'pages_render_array',
        'access arguments' => array('access content'),
        'weight' => 2,
        'type' => MENU_LOCAL_TASK,
    );

    return $items;
}

Here we taken the pages content page and added two tabs. The page ‘pages/default’ is the default tab by specifying the type to default. Important to note, that the default tab doesn’t have the page callback specified, so it falls back to the parent callback function – ‘pages_string’

Another tab(‘pages/render-array’) is added with new call back function ‘pages_render_array’. This tab has access arguments. Unless boolean is specified, it expects function. If no specified as it is here, then it uses default callback ‘user_access’ and it expect permission to be passed which we do here with ‘content access’

The build render array looks as following:

function pages_render_array() {
    $build = array(
        'string_paragraph' => array(
            '#type' => 'markup',
            '#markup' => '<p>Pages can be returned as strings.</p>',
        ),
        'render_array_paragraph' => array(
            '#type' => 'markup',
            '#markup' => '<p>Pages can be returned as <em>render arrays</em>.</p>',
        ),
        'why_render_arrays' => array(
            '#items' => array('They allow content to be modified as an array.', 'Arrays are a lot easier to modify than HTML.'),
            '#title' => 'Render arrays are better because...',
            '#theme' => 'item_list',
        ),
    );
    return $build;
}

Here we specify to element to be html strings and then the third is unorder list. This is being passed to theme() function ‘item_list’ as specified

Drupal caches menu registry, so you have to tell Drupal to rebuild the registry and you can do by clearing the cache resulting to rebuild

Sub-Tabs

function pages_menu() {
...
    $items['pages/render-array'] = array(
        'title' => 'Render array',
        'description' => 'Menu system example using a render array.',
        'page callback' => 'pages_render_array',
        'access arguments' => array('access content'),
        'weight' => 2,
        'type' => MENU_LOCAL_TASK,
    );
    $items['pages/render-array/tab1'] = array(
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'title' => 'Tab 1',
    );
    $items['pages/render-array/tab2'] = array(
        'title' => 'Tab 2',
        'description' => 'Demonstrating secondary tabs.',
        'page callback' => 'pages_render_array',
        'access callback' => TRUE,
        'type' => MENU_LOCAL_TASK,
    );

    return $items;
}

Here we define two new items – ‘pages/render-array/tab1’ and ‘pages/render-array/tab2’ both of which are subpages of tab ‘pages/render-array’, so its important that the sub-tab path corresponds to the pages path its sub-tab from

How to Add Page Without Menu Item

function pages_menu() {
...
        $items['pages/callback'] = array(
        'title' => 'Example of a callback type',
        'page callback' => 'pages_render_array',
        'access callback' => TRUE,
        'type' => MENU_CALLBACK,
    );
...

Here we define separate page by specifying the type to be ‘MENU_CALLBACK’ instead ‘MENU_LOCAL_CALLBACK’. After clearing cash, the new page is displayed at domain.com/pages/callback end point

How to Pass Variables Through the Path

function pages_menu() {
...
 $items['pages/argument'] = array(
        'title' => 'Argument',
        'description' => 'Menu system example using an argument.',
        'page callback' => 'pages_argument',
        'access arguments' => array('access content'),
    );
}

function pages_argument($arg1) {
    $build['argument_paragraph'] = array(
        '#type' => 'markup',
        '#markup' => '<p>' . t('The argument passed was @arg1.', array('@arg1' => $arg1)) . '</p>',
    );

    return $build;
}

The only difference is to change the callback function to accept arguments. Here the callback function ‘pages_argument’ takes an arguments. So, with path domain.com/pages/argument/hello’, the argument ‘hello’ is going to be passed to the callback function ‘pages_argument’. For more arguments you just add them to the callback function parameter. Also can use the Drupals args() function

How To Use Placeholders to Pass Arguments in Middle of the Path

When you know the beginning and the end of the path but not the middle (i.e. node/*/edit)

function pages_menu() {
...
 $items['pages/%/placeholder'] = array(
        'title' => 'Placeholder',
        'description' => 'Menu system example using a placeholder.',
        'page callback' => 'pages_argument',
        'page arguments' => array(1),
        'access arguments' => array('access content'),
    );
}

There are two places to register the placeholder in hook_menu(). In the path, by specifying ‘%’ and ‘page arguments’ attributed as highlighted above. The number 1 specifies the location(starting from 0) of the path variable of placeholder. Afterwards, this variable is passed to the callback function

How To Create Dynamic Title With Title Callback

function pages_menu() {
...
  $items['pages/title/%'] = array(
        'description' => 'Example of a dynamic title.',
        'page callback' => 'pages_render_array',
        'access callback' => TRUE,
        'title callback' => 'pages_title_callback',
        'title arguments' => array(2),
    );
...
}

function pages_title_callback($arg) {
    return 'There is an argument in this title: ' . $arg;
}

Here, we specify the title callback and pass an argument. Afterward, we declare the callback that returns a string and is rendered as title

How To Modify Page Output with hook_page_alter()

How do adjust menu items that have been defined by other modules
How do we change the value returned by callback function defined by other modules

The idea of hook_page_alter() is that before content is rendered, modules have the last chance to manipulate the rendered page content.

function THEME_page_alter(&$page) {
    $page['content']['system_main']['why_render_arrays']['#weight'] = -10;
}

Here we reorder the array so that element item list named ‘why_render_arrays’ is moved up before other elements

How To Modify Menu Items With hook_menu_alter()

Instead modifying the render array, we modify the callback function all together. We will replace one page already exist with another we defined
Anything that is defined in hook_menu will be passed through the hook_menu_alter where it can be altered one more time

function pages_menu_alter(&$items) {
    $items['admin/content']['title'] = 'The Goods';
    $items['admin/content']['page callback'] = 'pages_render_array';
}

Here we override the admin page with our own defined page as declared in callback ‘pages_render_array’

How Define Path To Be Admin

Perhaps, you would like your new custom path to be part of Admin path whether its because you like to have the page in overlay or take advantage of the admin permissions, then:

function MODULE-NAME_admin_paths() {
    $paths = array(
        'path/*/edit' => TRUE,
    );
    return $paths;
}

here all the path of pattern – “‘path/*/edit” is going to be admin path with all the admin access rights, overlay functionality,etc.

How Use Include File To Improve Performance

On every page load, every .module file got loaded for every module currently installed. If we can make the code shorter in those .module files then we can improve the performance. One way doing it is by separating out the .module code into separate include files and then use special parameter in the hook_menu() item declaration goes in fetches that include file when needed. The idea, all of the code is not needed in the .module file at the same time.

function pages_menu() {
...
$items['pages/external'] = array(
        'title' => 'External file',
        'description' => 'Example of using an include for a page callback.',
        'page callback' => 'pages_external',
        'access callback' => TRUE,
        'file' => 'pages.external.inc',
        'file path' => drupal_get_path('module', 'NAMEOFMODULE'),
    );
...
}

Here, we specify the callback function ‘pages_external’ but the function is not in the .modules file. it is in the file ‘pages.external.inc’ that we specify to load in the highlighted lines. It will only include this file, when requesting ‘pages/external’

Adding Color Wheel to Form Element in Drupal

You may have input for choosing color. While there are many different ways to add color wheel, here we demonstrate one powered by Spectrum.js

1. Download Library and Customize Basic Palette

First download the Spectrum.js library(spectrum.js and spectrum.css) and then create basic color palette. Here is an example:

jQuery(".basic-color-palette").spectrum(
    {
        color: this.value,
        allowEmpty:true,
        showInput: true,
        className: "full-spectrum",
        showInitial: true,
        showPalette: true,
        showSelectionPalette: true,
        maxPaletteSize: 10,
        preferredFormat: "hex",
        localStorageKey: "spectrum.demo",
        move: function (color) {

        },
        show: function () {

        },
        beforeShow: function () {

        },
        hide: function () {

        },
        change: function() {

        },
        palette: [
            ["#000","#444","#666","#999","#ccc","#eee","#f3f3f3","#fff"],
            ["#f00","#f90","#ff0","#0f0","#0ff","#00f","#90f","#f0f"],
            ["#f4cccc","#fce5cd","#fff2cc","#d9ead3","#d0e0e3","#cfe2f3","#d9d2e9","#ead1dc"],
            ["#ea9999","#f9cb9c","#ffe599","#b6d7a8","#a2c4c9","#9fc5e8","#b4a7d6","#d5a6bd"],
            ["#e06666","#f6b26b","#ffd966","#93c47d","#76a5af","#6fa8dc","#8e7cc3","#c27ba0"],
            ["#c00","#e69138","#f1c232","#6aa84f","#45818e","#3d85c6","#674ea7","#a64d79"],
            ["#900","#b45f06","#bf9000","#38761d","#134f5c","#0b5394","#351c75","#741b47"],
            ["#600","#783f04","#7f6000","#274e13","#0c343d","#073763","#20124d","#4c1130"]
        ]
    }
);

So, we have three artifacts – spectrum.js, spectrum.css and basic-color-palette.js the last is the above code

2. Import Artifacts

Next, we attach these artifacts to the form. One way to do is as following:

$form['backgrounds'] = array(
        '#type' => 'fieldset',
        '#title' => t('Background'),
        '#group' => 'builder',
        '#attached' => array(
            'js' => array(
                drupal_get_path('module', 'MODULE-NAME') . '/js/color-picker/spectrum.js' => array(
                    'type' => 'file',
                    'scope' => 'header'
                ),
                drupal_get_path('module', 'MODULE-NAME') . '/js/color-picker/basic-palette.js' => array(
                    'type' => 'file',
                    'scope' => 'footer'
                ),
            ),
            'css' => array(
                drupal_get_path('module', 'MODULE-NAME') . '/js/color-picker/spectrum.css' => array(
                    'type' => 'file',
                    'scope' => 'header'
                ),
            ),
        ),
    );

Here, we have fieldset ‘background’ that will have input text field for the color(see next section). In this fieldset, we attach the three artifacts stored in some module – MODULE-NAME ‘js/color-picker’ directory. This will load the artifacts whenever we load the form for our color wheel

3. Attach the Wheel

At last, we attach the color wheel for particular form input field:

..
$form['backgrounds']['SOME-FORM-INPUT'] = array(
        '#type' => 'textfield',
        '#title' => t('Choose Background Color'),
        '#default_value' => isset($form_state['value']['SOME-FORM-INPUT']) ? $form_state['value']['SOME-FORM-INPUT'] : theme_get_setting('ds_theme_bg_color'),
        '#attributes' => array(
            'class' => array('basic-color-palette'),
        ),
    );

Here, we attach color wheel to the field – SOME-FORM-INPUT that is part of the field set – backgrounds. By adding class ‘basic-color-palette’ to this field, we attach the color wheel. Note, the class ‘basic-color-palette’ is the same element specified in the artifact basic-palette.js

Resources

http://bgrins.github.io/spectrum/

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/

How To Install Drush

Drush is command line utility for installing, maintaining and troubleshooting Drupal platform. This post logs the steps for installing drush on Mac(OS X 10.7.5)/Linux Ubuntu and Windows as well

Linux & Mac

Install Drush

To install the Dev or the most current version for Drush:
1. Clone Drush git repository

sudo git clone https://github.com/drush-ops/drush.git /root/tools/drush

2. Put Drush executable in the search path:

sudo ln -s /root/tools/drush/drush /usr/local/bin/drush

If you don’t know the search locations, then look it up variable $PATH that lists all the search locations for executables:

echo $PATH

Drush is using composer to deploy, so lets install composer as following:

sudo curl -sS https://getcomposer.org/installer | sudo php

this will download the composer. It displays the location that you will need next step

mv dir/downloaded/composer.phar /usr/bin/composer

Here, you move the composer into a path that looks for executables as specified by $PATH, so it can be found. At last, lets install drush

cd /path/to/drush
composer install

DONE! Test it by running ‘drush –v’ which should display current version

Upgrade Drush From Legacy Install

1. Clone Drush git repository

sudo git clone https://github.com/drush-ops/drush.git drush

2. Find where the executable is currently used:

which drush

This will display path of current drush executable. Go to that directory, rename or delete it and create new link to the new version of Drush cloned in Step 1 as following

sudo ln -s /Users/margots/DevTools/drush/drush drush

Here, the path is to the new version of drush cloned in step 1

Done!!


Old Way

Prerequisites

  • wget -or-
  • unzip -or-
  • git -or-

Step-1: Installing Prerequisites.

Verify Unzip installed by running ‘unzip’ from command line. If it isn’t installed then:

sudo apt-get install unzip

Verify wget is installed by running ‘wget’ from command line. If it isn’t installed then:

curl -O http://ftp.gnu.org/gnu/wget/wget-1.14.tar.gz
sudo tar -xzf wget-1.14.tar.gz
cd wget-1.14
sudo ./configure --with-ssl=openssl
sudo make
sudo make install
Stept-2: Installing Drush with PEAR

To verify PEAR(PHP Extension and Application Repo) is installed type ‘pear version’ in command line. If it doesn’t exist then:
For Mac:

cd /usr/local
sudo wget http://pear.php.net/go-pear.phar
sudo php -d detect_unicode=0 go-pear.phar
sudo pear upgrade --force pear
sudo pear upgrade --force Console_Getopt Console_Table
sudo pear upgrade-all

For Ubuntu:

sudo apt-get install php-pear
sudo pear upgrade --force pear
Step-3: Installing Drush

To install drush:

sudo pear channel-discover pear.drush.org
sudo pear install drush/drush
which drush
drush

If you see the following message – ‘Drush needs to download a library from [..]Console_Table-1.x.x.tgz[..]’ with error, then:

sudo rm -Rf ~/.drush
Upgrade drush

To upgrade drush:

sudo pear upgrade drush

Windows

Prerequisites

  • cygen

Installing Drush

Run Windows installer for Drush listed at http://drush.ws/drush_windows_installer

Configure Drush for Cygen

To run drush from Cygen, we mount the path and then create shell alias. To mount path to drush in the Cygen, add the following in the /etc/fstab

C:\ProgramData\Drush\drush.php /cygdrive/c/ProgramData/Drush/drush.php binary,posix=0,user 0 0

Next, we create alias – drush by adding the following in .bashrc in your home dir

alias drush='/cygdrive/c/ProgramData/Drush/drush.php'

Afterwards, reload Cygen shell and run ‘drush –version’. It should display the drush version which is good way to verify if drush is working

Troubleshooting

1. Tip

If you run into any issues while trying to install some package, make sure you run ‘sudo apt-get update’

2. Unable to load autoload.php. Drush now requires Composer in order to install

This happen after upgrading drush. Solution is to install composer as following:

sudo curl -sS https://getcomposer.org/installer | sudo php

this will donwload the composer. It displays the location that you will need next step

mv dir/downloaded/composer.phar /usr/bin/composer

Here, you move the composer into a path that looks for executables as specified by $PATH, so it can be found. At last, lets install drush

cd /path/to/drush
composer install

The This should solve the problem

MS-DOS style path detected: C:\path\drush.php
Preferred POSIX equivalent is: /cygdrive/c/path/drush.php

You have to mount and then refer the drush in the Cygen. See step – Configure Drush for Cygen above

Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.

For details, please, see post – . To fix it, run:

composer update --lock

This will update the lock file to get rid of the error message,so you can proceed with the installation

[RuntimeException] vendor does not exist and could not be created

This message is given when the drush directory(or dir you try to run the composer installer) doesn’t have write permissions. To fix this:

sudo chmod -R 777 DIR
Unable to send e-mail drupal

To turn off drupal sending email add the following line to the php.ini

sendmail_path = /bin/true
[UnexpectedValueException] Could not parse version constraint ^2.6.3: Invalid version string

To solve this error, update the composer currently installed:

composer self-update
the requested PHP extension pcntl is missing from your system drush

The error came when installing drush via composer “composer install”. To solve, I deleted the composer.lock and rerun the installer “composer install –dev”

References

http://duntuk.com/how-install-drush-github-after-drupal-project-removal
https://drupal.org/node/1674222