Drupal: module development for Drupal 7 – Part 1

Drush

$ drush dl drupal [download latest version of drupal]
 
$ drush si –y --db-url=mysql://root:root@localhost/database_name
 
[si = site install]
 
[-y = say automaticly 'yes' to all prompts]

Developer modules
Devel

  • Helper functions for Drupal developers and inquisitive admins
  • Generate content for quickly filling a site with content
  • Performance logging

After installation an extra tab ‘devel’ is added to ‘My account’ for example. Krumo is used to display information in a nice looking format. It also provide some block we can enable by going to StructureBlocks. Enable Development, Execute PHP and Switch user blocks. Best to add them to the right column.

The ‘Devel settings’ page has a nice feature called ‘Query Log’ which logs each query Drupal makes to the database and displays it to the bottom of the page.

Drupal functions

debug($param) [nicely formatted print_r]
 
dsm($param) [Drupal Set Message; nicely formated print_r using Krumo library]
 
dsm($param, 'label1')
 
dsm('Hello world', 'label2')
 
kpr($param) [Krumo Print Recursive; nicely formatted print_r]

Drupal API documentation

Link: http://api.drupal.org

Examples: http://drupal.org/projects/examples

Developer documentation third-party modules: http://api.lullabot.com

Anatomy of a Drupal Module

Download third-party modules in sites/all/modules/contrib

Download own modules in sites/all/custom

Requirements for own module:

  • mymodule.info
  • mymodule.module

Optional files:

  • README.txt
  • HTML templates, CSS
  • mymodule.install [when using a database]

Folder / file names [a.k.a. demo]

  • lowercase
  • underscores

demo.info

name = Demo
 
description = Just a simple demo module.
 
core = 7.x [big release numbers]

demo.module

<?php
// put your functions here
  • Never use a PHP closing tag ?>

Folder structure:

sites/all/themes
sites/all/modules/contrib
sites/all/modules/custom
sites/all/modules/custom/demo
sites/all/modules/custom/demo/demo.info
sites/all/modules/custom/demo/demo.module

Custom modules are listed under Modules – Other tab.

The field sets are packages:

  • Core
  • Development
  • Other [if you don’t specify a package]

Writing module .info files

Link: http://drupal.org/node/542202

Drupal uses .info files to store metadata about themes and modules. This .info file is required for the system to recognize the presence of a module. For modules, the .info file is used for:

  • Rendering information on the Drupal Web GUI administration pages
  • Providing criteria to control module activation and deactivation
  • Notifying Drupal about the existence of a module
  • General administrative purposes in other contexts

The .info file should have the same name as the .module file and reside in the same directory. For example, if your module is named example.module then your .info file should be named example.info.

This file is in standard .ini file format, which defines properties in key/value pairs separated by an equals sign (key = value). You may use quotation marks to wrap the value. Quoted values may contain newlines.

.info files may contain comments. A semi-colon [;] placed at the beginning of a line makes that line a comment, and that line will not be parsed.

Note: Whenever you create or change your .info file, you will need to clear your site’s cache for your changes to take effect.

Properties

The .info file can contain the following properties:

configure (Optional)

As of version 7.x, the path of the module’s (main) configuration page. If a module is enabled, a Configure and Permissions link appear. This will be the path of the Configure link for this particular module on the modules overview page.

configure = admin/config/content/example

A module configuration form

Link: http://drupal.org/node/1111212

First we will set a path that will make the form available from the Configuration page (http://example.com/admin/config). We would like only administrators to be able to access this form, and we’ll check that permission here in hook_menu(). To minimize the number of permissions an administrator has to deal with, we’ll use the core administration permission instead of creating a new custom permission.

We begin the path with admin/config to tell Drupal to include a link to this form on the site administration page. We include the administrator permission under access arguments, and the value of type is the default. This listing will not appear in any menu, only on the Configuration page.

Page callback tells Drupal what to call when the link is requested, in this case, drupal_get_form, the ‘key’ function in the Form API. Page arguments values are passed to that function. By assigning demo_configuration_form, we define that as both the form ID and the name of the function that will create our settings form.

/**
* Implements hook_menu().
*/
function demo_configuration_menu() {
  $items = array();
 
  $items['admin/config/demo'] = array(
    'title' => 'Demo configuration',
    'description' => 'Configuration for the demo module',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('demo_configuration_form'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
  );
 
  return $items;
}

Drupal makes it easy for us to save the form’s data with the function system_settings_form(). By using the function in our code, we tell Drupal to provide a submit button and to save data into persistent variables using variable_set(). It will also provide a green confirmation message when data is successfully saved, and a red error message if something went wrong. If you prefer, you can create a submit function yourself, but for now, we will use this handy shortcut.

/**
* Page callback: demo configuration
*
* @see demo_configuration_menu()
*/
function demo_configuration_form($form, &$form_state) {
  $form['demo_max'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum number of links'),
    '#default_value' => variable_get('demo_max', 10),
    '#size' => 2,
    '#maxlength' => 2,
    '#description' => t('The maximum number of links.'),
    '#required' => TRUE,
  );
 
  return system_settings_form($form);
}

In your demo.info file add the following line to create a linked button to your configuration page in the modules page.

configure = admin/config/demo

The event-driven hook system
Hooks allow modules to modify data, extend functionality and change existing work flows.

<?php
/*
 * Implements hook_permission().
 */
function demo_permission() {
  return array(
    'administer demo module'=>array(
      'title'=>t('Administer demo module'),
      'description'=>t('Perform administration to the demo module'),
      ),
    );
}
  • Replace hook with name of custom module
  • Use Drupal api documentation to look up params for hooks
  • t($string) is used for translations

Writing secure code

Drupal does not sanitize user input when it saves the data. All characters are escaped properly. Data gets ‘washed’ on output. Wash your output:

Link: http://api.drupal.org/api/drupal/includes–common.inc/group/sanitization/7

check_plain

  • When inserting plain text into HTML
  • No HTML is output
  • Uses the encoded special chraters instead

check_markup

  • When inserting richt text into HTML
  • Specify a format to use [a.k.a filtered_html]
  • Only white-listed tags are allowed

check_url

  • Strips dangerous protocols [e.g. javascript] out of url’s

filter_xss

  • Filters a HTML string to prevent cross-site-scripting

drupal_mail

  • Doesn’t allow headers to be injected [e.g. from user forms]

Drupal Forms API (FAPI) protects against XSRF using a token and session system that checks for validity of POST data. Illigal added content to a forms is also checked (e.g. using Firebug).

The query builder with variable replacement uses the database API to safely handle the data (e.g. the db_merge function).

Drupal menu system

After the ‘bootstrap’ Drupal makes a request to all the enabled modules to figure out which url’s it knows howto respond to. This is where hook_menu comes into display. Any enabled module can implement hook_menu. Hook_menu returns an associative array. For example:

<?php
/*
 * Implements hook_menu().
 */
function demo_menu() {
  $items['about'] = array(
    'title’ => 'About us',
    'description’ => 'A description of us.',
    'page_callback’ => 'demo_about',
    'access arguments' => array('access content'),
  );
  return $items;
}

Once Drupal found the module which handles the request and checked the user access it calls the page_callback function. For example:

<?php
/*
 * This function builds the content of the page '/about'
 */
function demo_about() {
  return 'Hello world';
}
  • In Drupal’s menu system, ‘title’ and ‘description’ attributes are automatically translated. Throughout Drupal, you are encouraged to use the t() function on all string literals. This is one place where you must remember not to do that.

Using URL arguments in page callback functions

<?php
/*
 * Implements hook_menu().
 * The % is the first position in the path
 * 0 = about
 */
function demo_menu() {
  $items['about/%'] = array(
    'title' => 'About us',
    'description' => 'A description of us.',
    'page_callback' => 'demo_about',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
    'file'=> 'demo_about.extra.inc',
  );
  return $items;
}

The file property specifies the file were the page_callback function is located.

<?php
/*
 * This function builds the content of the page ‘/about’
 */
function demo_about($argument) {
  return 'Hello world';
}

Add a menu item to local tasks (or tabs)

<?php
/*
 * Implements hook_menu().
 * The % is the first position in the path
 * 0 = about
 */
function demo_menu() {
  $items['user/%/about'] = array(
    'title' => 'About us',
    'description' => 'A description of us.',
    'page_callback' => 'demo_about',
    'page arguments' => array(1),
    'access arguments' => array('administer users'),
    'file'=> 'demo_about.user.inc',
    'type' => MENU_LOCAL_TASK
  );
  return $items;
}

Adding contextual menu links and using menu autoloaders
Not viewed yet

Using the Drupal Render API
Documentation: Drupal.org – Documentation – Developing for Drupal – Working with Drupal API’s – Render Arrays in Drupal 7.

<?php
/*
 * This function builds the content of the page '/about'
 */
function demo_about($argument) {
  $content['raw_markup'] = array(
    '#type'=> 'markup',
    '#markup' => 'Hello world',
    '#prefix' => '<p>',
    '#suffix' => '</p>',
  );
 
  $variables = array(
    'path' => 'http://www.domain.org/image.jpg',
    'alt' => t('Alt text'),
    'title' => t('Title text'),
  );
 
  // Use theme_image($variables) function
  $content['themed_data’] = array(
    '#type'=> 'markup',
    '#markup' => theme('image', $variables),
  );
 
  return $content;  
}

$content is a Render Array. The key raw_markup (without #) is considered as an element. A key with a # is considered as a property. For element types like markup check:

http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7

Drupal provides a number of theme functions that modules can use in order to output content to the page. The reason to use these functions is that a theme can change the theme function and change the output.

<?php
/*
 * This function builds the content of the page '/about'
 */
function demo_about($argument) {
  $content['renderable_element'] = array(
    '#theme’=> 'item_list',
    '#title’ => t('This is the title'),
    '#items’ => array(
      t('Listitem 1'),
      t('Listitem 2'),
      t('Listitem 3'),
    ),
  );
 
  return $content;  
}

The render engine is goint to call the function theme_item_list in order to render this element. The function theme_item_list returns HTML for a list or nested list of items.
Attachments and caching with the Render API

<?php
/*
 * This function builds the content of the page '/about'
 */
function demo_about($argument) {
  $variables = array(
    'path' => 'http://www.domain.org/image.jpg',
    'alt' => t('Alt text'),
    'title' => t('Title text'),
  );
 
  // Use theme_image($variables) function
  $content['themed_data'] = array(
    '#type'=> 'markup',
    '#markup' => theme('image', $variables),
    '#prefix' => '<div class=”about-image”>',
    '#suffix' => '</div>',
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'about') . 'about.css',
      ),
    );
  );
 
  return $content;  
}

Output caching

<?php
/*
 * This function builds the content of the page '/about'
 */
function demo_about($argument) {
  $content[‘renderable_element’] = array(
    '#theme'=> 'item_list',
    '#title' => t('This is the title'),
    '#items' => array(
      t('Listitem 1'),
      t('Listitem 2'),
      t('Listitem 3'),
    ),
    '#cache' => array(
      'keys' => array('about', 'renderable_element'),
      'bin' => 'cache',
      'expire' => time() + 30,
      'granularity' => DRUPAL_CACHE_PER_PAGE,
    ),
  );
 
  return $content;  
}

Altering the page array
Create a new module demo_mangler.

<?php
/*
 * Implements hook_page_alter().
 */
function demo_mangler_page_alter($page) {
  if (arg(0) == 'about' {
    dsm($page);
    $page['content']['system_main']['renderable_element'][#type’] = 'ol';
  }
}

Integrating with the theme system
Not viewed yet


Leave a Reply