1. Project Description
Client is looking to build a “Shopper Marketing Portal” that will reduce the manual effort involved in managing marketing programs with key retail customers including Walmart, Costco and others. Managing these marketing programs is a complex and involved process that involves several parties: different Marketing Teams – Catapult and Shopper Marketing, Sales Representatives, Retail Representatives, Sales Management staff, and others.
Furthermore, it is a multiple step process that involves review and approval of different kinds of documents: Marketing Programs, Sell Sheets, Order Forms, Confirmation Sheets, Force-Outs and several other such entities
A high level depiction of the project is shown below:

2. Design Approach using Drupal
The project will be implemented using the custom module approach in Drupal.
2.1. Drupal Overview
Although Drupal is often described as a "content management system" (CMS), it is also a "content management framework". In other words, unlike a typical CMS, it is geared more towards configurability and customization. At a high level, the main layers in the Drupal system are shown below:

-
Modules are functional plug-ins that are either part of the Drupal core (they ship with Drupal) or they are contributed items that have been created by members of the Drupal community. Modules provide various functionality to expand a website’s capabilities.
-
Blocks often provide the output from a module, and can be placed in various spots in your template (theme) layout. Blocks can be configured to output in various ways, as well as only showing on certain defined pages, or only for certain defined users.
-
On the surface layer is the site template. This is made up predominately of XHTML and CSS, with some PHP tokens sprinkled throughout to insert content from the system into the correct spots. Also included with each template is a set of functions that can be used to override standard functions in the modules in order to provide complete control over how the modules generate their markup at output time.
2.2. Functionality Mapping
For the Client site, the following application functionality mappings have been identified:
|
Sl No |
Client Site Functionality |
Drupal Feature |
|
1 |
Implementation/Deployment of the solution |
Custom Modules in Drupal |
|
2 |
Custom Schema |
Schema API and Database API |
|
3 |
Forms |
Forms API |
|
4 |
CSS for Forms |
Drupal Themeing |
|
5 |
Form Validation/Security/AJAX kind of functionalities |
Drupal AHAH module |
|
6 |
Form Tables with Update/Delete options for rows |
Views in Drupal EditView Module |
|
7 |
Roles/Permissions – RBAC |
Drupal’s built-in permissions Module |
|
8 |
Role Based Data for form elements |
Profile Module Custom code – read role in form and populate field element with data using Database API |
|
9 |
Session Management |
Session API – to associate data with site visitor |
|
10 |
Development Environment - IDE |
Ecplise PDT/Zend Debugger with xDebug module |
|
11 |
Code Maintenance |
SVN support in Drupal |
|
12 |
Build Management and Deployment |
Drupal modules – deploy and autopilot |
|
13 |
Workflow |
Drupal Workflow Module |
|
14 |
Document Management |
Drupal form file upload |
|
15 |
PDF generation |
Contributed module – print, image module |
2.3. Overview of Implementation
The Client project will be built as a custom module in Drupal. Relevant modules will be integrated with this custom module to provide the functionalities needed for the application
The different forms in the web application will be built using the Forms API and AHAH module. For the look and feel of the site, Drupal themes module will be utilized. Also, the ThemeKey module will be used to have different themes for different sections of the application.
MySQL database will be used for the application. The database schema will be implemented using sql scripts. The constraints in the database will be implemented using foreign keys. Drupal provides a Schema API to create custom database schema during installation – sans constraints. Inkriti will create the schema using scripts – thus bypassing the Schema API.
Any database interactions from the application will be done using the Database API. Stored Procedures and Views will be written in MySQL to minimize the complexity of queries initiated by Drupal. There will also be a significant performance gain by doing so.
Workflows in the application will be implemented using Drupal’s workflow module. Drupal Workflow module is a collection of actions that can be triggered by Drupal’s response to an event. Multiple actions can be configured for a particular event handler by implementing the hook_actions_info and hook_action functions. The interface for assigning actions to events (that is, hooks) is provided by modules/trigger.module. For example, the following workflow shows three states – draft, ready, published – to publish a blog:

Each of these state transitions could be associated with multiple actions using Drupal’s Actions module:

User Roles and Permissions will be handled entirely using the functionality built-into Drupal. The screens provided by Drupal will be used to provide this functionality. The roles and permissions will be read in the forms/pages to provide role-based-functionalities.

3. Scenario Implementation Guide
The scenario implementation guide presented below takes the scenario of a Catapult Marketing user logging into the system and checking his dashboard. The idea is to present a fairly detailed explanation to the implementation of a custom module in Drupal using a sample scenario.

The above functionality can be built by creating a custom module and a custom block and configuring the block to show the dashboard.
Steps to Create Custom Module (for reference implementation, Inkriti used the name demoModule for the module):
-
Create demoModule.info file which holds the information about the module, version of the module, dependent modules and other relevant information.
; $Id$
name = 'Registration'
description = A module with a simple registration form to save the records in the database.
core = 6.x
package = Registration
-
Create demoModule.module file, where the hooks can be implemented for help as well.
function registration_help($path, $arg) {
$output = '';
switch ($path) {
case "admin/help#registration":
$output = '<p>'. t("A simple registration form to save data in database.") .'</p>';
break;
}
return $output;
} // function registration_help
-
Implement hook_access in order to provide access to this node based on the access assigned by admin.
function node_example_access($op, $node, $account) {
if ($op == 'create') {
return user_access('create example content', $account);
}
if ($op == 'update') {
if (user_access('edit any example content', $account) || (user_access('edit own example content', $account) && ($account->uid == $node->uid))) {
return TRUE;
}
}
if ($op == 'delete') {
if (user_access('delete any example content', $account) || (user_access('delete own example content', $account) && ($account->uid == $node->uid))) {
return TRUE;
}
}
}
-
Implement hook_perm to grant permissions for create, view, update, and delete.
function node_example_perm() {
return array(
'create example content',
'delete own example content',
'delete any example content',
'edit own example content',
'edit any example content',
);
}
-
Permissions can be fine-grained by implementing hook_view , hook_insert, hook_update, hook_delete functions. These could be checking the username, role of the user etc.
function node_example_view($node, $teaser = FALSE, $page = FALSE) {
$node = node_prepare($node, $teaser);
$node->content['myfield'] = array(
'#value' => theme('node_example_order_info', $node),
'#weight' => 1,
);
return $node;
}
function node_example_insert($node) {
db_query("INSERT INTO {node_example} (vid, nid, color, quantity) VALUES (%d, %d, '%s', %d)", $node->vid, $node->nid, $node->color, $node->quantity);
}
function node_example_update($node) {
// if this is a new node or we're adding a new revision,
if ($node->revision) {
node_example_insert($node);
}
else {
db_query("UPDATE {node_example} SET color = '%s', quantity = %d WHERE vid = %d", $node->color, $node->quantity, $node->vid);
}
}
function node_example_delete($node) {
// Notice that we're matching all revision, by using the node's nid.
db_query('DELETE FROM {node_example} WHERE nid = %d', $node->nid);
}
-
In order to provide a mechanism to navigate to the page/node, function hook_menu is implemented. A link was added in Primary navigation at the top of header so that just by clicking the link in the header one can navigate to the page/node.
function registration_menu() {
$items = array();
$items['registration'] = array(
'title' => 'Registration',
'description' => 'Regsitration Form settings control',
'page callback' => 'drupal_get_form',
'page arguments' => array('registration_form'),
'access arguments' => array('access administration pages'),
'type' => MENU_NORMAL_ITEM,
);
$items['registration/%user/delete'] = array(
'title' => 'Delete',
'page callback' => 'drupal_get_form',
'page arguments' => array('user_confirm_delete', 1),
'access callback' => 'user_access',
'access arguments' => array('administer users'),
'type' => MENU_CALLBACK,
'file' => 'user.pages.inc',
);
$items['registration/%user/edit'] = array(
'title' => 'Edit',
'page callback' => 'user_edit',
'page arguments' => array(1),
'access callback' => 'user_edit_access',
'access arguments' => array(1),
'type' => MENU_CALLBACK,
'file' => 'user.pages.inc',
);
return $items;
}
-
Drupal uses drupal_get_form(“module_name_form”) which will call module_name_form() function and this function is implemented to create the form. Role based access control can be provided for form fields.
function registration_form($form_state) {
// Full Name:
$form['Name'] = array(
'#type' => 'textfield',
'#title' => t('*Name'),
'#size' => 30,
'#maxlength' => 100,
'#description' => t('Please enter your full name.'),
);
$form['Address'] = array(
'#type' => 'textfield',
'#title' => t('*Address Details'),
'#default_value' => variable_get('Address', ''),
'#size' => 30,
'#maxlength' => 200,
'#description' => t('Your Contact Address.'),
);
$form['Phone'] = array(
'#type' => 'textfield',
'#title' => t('*Phone'),
'#size' => 30,
'#maxlength' => 10,
'#default_value' => variable_get('Phone', ''),
'#description' => t('Your Contact Number.'),
);
$form['Email'] = array(
'#type' => 'textfield',
'#title' => t('*Email ID'),
'#size' => 30,
'#maxlength' => 100,
'#default_value' => variable_get('Email', ''),
'#description' => t('Your Valid Email Address.'),
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
return $form;
}
-
In module_name_form, the form is created and prepopulated if required. AHAH functionality can also be added to dynamically display the form elements or content.
-
Form validation and submit functionalities can be implemented by declaring module_name_form_validate() function and module_name_form _submit() functions.
function registration_form_validate($form, &$form_state) {
/*
Validations based on business logic
*/
if ($form_state['values']['Name'] == '') {
form_set_error('', t('Please enter your name.'));
}
if ($form_state['values']['Address'] == '') {
form_set_error('', t('Please enter your address.'));
}
if ($form_state['values']['Phone'] == '') {
form_set_error('', t('Please enter your Phone number.'));
}
if ($form_state['values']['Email'] == '') {
form_set_error('', t('Please enter your email id.'));
}
}
function registration_form_submit($form, &$form_state) {
/*
Business logic implementation
*/
$result = db_query("INSERT INTO student (name,address,phone,email) VALUES ('%s', '%s', %f, '%s')", $form_state['values']['Name'], $form_state['values']['Address'], $form_state['values']['Phone'], $form_state['values']['Email']);
if ($result == 1) {
drupal_set_message(t($form_state['values']['Name'] . ' details has been saved.'));
}
}
-
The hook_theme function can be used to customize the theme for this module.
function node_example_theme() {
return array(
'node_example_order_info' => array(
'arguments' => array('node'),
),
);
}
-
Similarly custom block is also a custom module, and has module.info and module.module files. These files can be placed at the same location where node module was placed. The module_name_block function also needs to be implemented.
function onthisdate_block($op='list', $delta=0) {
// listing of blocks, such as on the admin/block page
if ($op == "list") {
$block[0]["info"] = t("On This Date");
return $block;
} else if ($op == 'view') {
// our block content
// content variable that will be returned for display
$block_content = '';
// Get today's date
$today = getdate();
// calculate midnight one week ago
$start_time = mktime(0, 0, 0,$today['mon'],
($today['mday'] - 7), $today['year']);
// we want items that occur only on the day in question, so
//calculate 1 day
$end_time = $start_time + 86400;
// 60 * 60 * 24 = 86400 seconds in a day
$query = "SELECT nid, title, created FROM " .
"{node} WHERE created >= '%d' " .
" AND created <= '%d'";
$queryResult = db_query($query, $start_time, $end_time);
while ($links = db_fetch_object($queryResult)) {
$block_content .= l($links->title, 'node/'.$links->nid) . '<br />';
}
// add a more link to our page that displays all the links
$options = array( "attributes" => array("title" => t("More events on this day.") ) );
$link = l( t("more"), "onthisdate", $options );
$block_content .= "<div class=\"more-link\">" . $link . "</div>";
// check to see if there was any content before setting up the block
if ($block_content == '') {
// no content from a week ago, return nothing.
$block['subject'] = 'On This Date';
$block['content'] = 'Sorry No Content';
return;
}
// set up the block
$block['subject'] = 'On This Date';
$block['content'] = $block_content;
return $block;
}
-
After implementing block module, the module needs to be registered and configured so that this block can be displayed in the custom node module.
-
Apart from these files, templates and other include files can be utilized for custom modules.
-
Navigation has been implemented using links and form submission.
-
AHAH functionality was implemented to dynamically populate dropdown based on the selection of data from database.
-
The Views module was utilized to show the grid in the dashboard. This provides a flexible method for Drupal site designers to control how lists of content are presented. The below image shows a data grid implemented using Views module with edit and delete links:

-
Hooks that needs to be implemented by other modules in order to implement the Views API are listed below.
-
Describe table structure to Views using hook_views_data ().
-
/**
* Implementation of hook_views_data()
*/
function registration_views_data() {
// Basic table information.
$data['student']['table']['group'] = t('Student');
$data['student']['table']['base'] = array(
'field' => 'id',
'title' => t('Student'),
'help' => t('Students who have registered under this page.'),
);
// ----------------------------------------------------------------
// Fields
// id
$data['student']['id'] = array(
'title' => t('ID'),
'help' => t('The student ID'), // The help that appears on the UI,
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'name field' => 'name', // display this field in the summary
),
'filter' => array(
'title' => t('Name'),
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
);
// name
$data['student']['name'] = array(
'title' => t('Name'), // The item it appears as on the UI,
'help' => t('The user or author name.'), // The help that appears on the UI,
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// phone
$data['student']['address'] = array(
'title' => t('Address'), // The item it appears as on the UI,
'help' => t('The user address.'), // The help that appears on the UI,
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// phone
$data['student']['phone'] = array(
'title' => t('Phone'), // The item it appears as on the UI,
'help' => t('The user phone number.'), // The help that appears on the UI,
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Note that this field implements field level access control.
$data['student']['email'] = array(
'title' => t('E-mail'), // The item it appears as on the UI,
'help' => t('Email address for a given user.'), // The help that appears on the UI,
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
$data['student']['edit_student'] = array(
'field' => array(
'title' => t('Edit link'),
'help' => t('Provide a simple link to edit the user.'),
'handler' => 'views_handler_field',
),
);
$data['student']['delete_student'] = array(
'field' => array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete the user.'),
'handler' => 'views_handler_field',
),
);
return $data;
}The full documentation for this hook is now in the advanced help using hook_views_plugins ().
-
Register handler, file and parent information so that handlers can be loaded only on request using hook_views_handlers ().
function registration_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'registration'),
),
'handlers' => array(
'views_handler_field_registration_link' => array(
'parent' => 'views_handler_field',
),
'views_handler_field_registration_link_edit' => array(
'parent' => 'views_handler_field_registration_link',
),
'views_handler_field_registration_link_delete' => array(
'parent' => 'views_handler_field_registration_link',
),
),
);
}
-
Register View API information using hook_views_api (). This function needs to be implemented in custom module.
function registration_views_api() {
return array(
'api' => 2.0,
// 'path' => drupal_get_path('module', 'registration'),
);
}
-
Hook hook_views_default_views allows modules to provide their own views which can either be used as-is or as a "starter".
-
There are multiple set of hooks available to provide flexible views. Some of these are hook_views_convert, hook_views_query_substitutions, hook_views_pre_view, pre_execute, hook_views_pre_build, hook_views_pre_render, hook_views_query_alter.
-
The above hooks should be placed in MODULENAME.views.inc and all these will be auto-loaded using hook_views_admin_links_alter.
-
If a custom functionality needs to be implement, it can be handled using custom handlers. For example, in order to implement edit link in view, the sample code is presented below:
class views_handler_field_registration_link_edit extends views_handler_field_registration_link {
// An example of field level access control.
function access() {
return user_access('administer users');
}
function render($values) {
$text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
$id = $values->{$this->aliases['id']};
return l($text, "registration/$id/edit", array('query' => drupal_get_destination()));
}
}
-
The above function should be placed in views_handler_field_registration_link_edit.inc
-
To implement delete link in view, the sample code is presented below:
class views_handler_field_registration_link_delete extends views_handler_field_registration_link {
// An example of field level access control.
function access() {
return user_access('administer users');
}
function render($values) {
$text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
$id = $values->{$this->aliases['id']};
return l($text, "registration/$id/delete", array('query' => drupal_get_destination()));
}
}
-
Place these files in drupal\sites\all\modules folder if using default location.
-
To implement business logic, custom code can be written as functions and invoked from corresponding hook implementation.
-
After implementation of custom module, Admin can register (Enable) custom modules in Site Building section of Site Administration.
-
After enabling the custom module Admin has to assign the permissions to different roles in order to access it.
4. Development Environment
The Windows Operating System will be used for the development environment of the application. The staging environment will replicate the operating system where the application will be deployed.
The following softwares will be used as the base development environment for the project:
- XAMPP will be used to provide the integrated environment for PHP, Apache and MySQL
- Apache will be used as the webserver for development purposes
- The development database will be MySQL
- Drupal version 6.0 will be installed on Apache and will use MySQL as the database
- Eclipse will be used as the IDE of the project.
- The xDebug plugin of Eclipse, along with Drupal’s devel module will be used for debugging purposes
- SVN will be used for source code management. The SVN plugin for Eclipse will be used integrate code management with the IDE. SVN repository at Inkriti is incrementally backed-up on a daily basis.
Drupal provided modules – namely deploy and autopilot - will be used to automate deployment and move codebase from dev-to-staging-to-production environment.
5. Performance Tuning
When building the application, the following performance tuning areas would be considered:
- Reducing total page execution time by disabling known filters like video filter module
- Cross-platform Static Cache
- Optimizing MySQL Cache tables
- Memcache, Cache Router, Booster modules/options for Drupal
- BlockCache module for cached version of Block
- LiteNode module mentioned here
- Convert all tables except menu_router and search_* tables to INNODB
- CDN patch FROM http://tag1consulting.com/patches/cdn for Akamai
- drupal_lookup_path patch from tag1consulting.com
6. Database Schema Design
Inkriti has developed the below shown initial schema to support the online system Client:

With the technical approach mentioned above, Drupal’s built-in roles and permissions scheme will be utilized for the project – and as such, the roles/permissions specific tables in the schema diagram above become redundant.
7. References
1. Form API: http://drupal.org/node/204270
2. Drupal Permissions: http://www.daleeman.com/?p=28
a. Organic Groups: http://drupal.org/project/og
b. OG User Roles: http://drupal.org/project/og_user_roles
c. Taxonomy Access Control: http://drupal.org/project/taxonomy_access
d. Simple Access: http://drupal.org/project/simple_access
e. Full list of user access/authentication modules: http://drupal.org/project/Modules/category/74
3. Overview of Forms Management: http://drupal.org/node/354297
4. High Performance : http://groups.drupal.org/high-performance


