Adapting the query

Main topic described: Database API

The new page function will do almost the same work as current_posts_block_view, which derives its data from our custom function, current_posts_contents. Now the benefit of writing a separate function for our database query becomes apparent. All we need to do is edit it to take an argument and adjust some of the query code, and it will be ready to go for the page.

Here’s the edited code:

<?php
function current_posts_contents($display){   //$display argument is new.
//Get today’s date.
$today = getdate();
//Calculate midnight a week ago.
$start_time = mktime(0, 0, 0,$today[‘mon’],($today[‘mday’] – 7), $today[‘year’]);
//Get all posts from one week ago to the present.
$end_time = time();

$max_num = variable_get(‘current_posts_max’, 3);

//Use Database API to retrieve current posts.
$query = db_select(‘node’, ‘n’)
->fields(‘n’, array(‘nid’, ‘title’, ‘created’))
->condition(‘status’, 1) //Published.
->condition(‘created’, array($start_time, $end_time), ‘BETWEEN’)
->orderBy(‘created’, ‘DESC’); //Most recent first. Query paused here.

if ($display == ‘block’){
// Restrict the range if called with ‘block’ argument.
$query->range(0, $max_num);
} //Now proceeds to execute().
//If called by page, query proceeds directly to execute().

return $query->execute();
}
?>

First of all, we add the $display argument to the function. Then, in the query statements, we pause building the query by adding a semi-colon after the orderBy method. We then get the function’s argument value and decide how to continue. If the argument is from the block, we pick up the query object and restrict the range, then proceed to execute. If it’s from the page, we exclude the range() method and proceed straight to execute, thus including all the available posts.

Editing current_posts_block_view

To make this code work as it did in the current_posts_block_view function before the change, we must add one word to one line of code:

<?php
$result = current_posts_contents(‘block’);
?>

Adding the word ‘block’ as an argument to the function call is all it takes.

Specifying a custom permission for a new page

Specifying a custom permission for a new page

Drupal hooks described: hook_permission(), hook_menu()

So far we have our working block and a settings form. The block displays a maximum number of links. However, there may be more links than the maximum we show. So we’ll create a page that lists all the content that was created in the last week.

Custom permission

First, we’ll create a custom permission, using hook_permission(). The permissions this hook defines can be configured at People > Permissions (tab), or http://example.com/admin/people/permissions. Only user roles with this permission granted will have access to the page we will create.

Add this to your .module file:

<?php
/**
* Implements hook_permission().
*/
function current_posts_permission(){
return array(
‘access current_posts content’ => array(
‘title’ => t(‘Access content for the Current posts module’),

); 
}
?>

This hook follows the typical Drupal pattern of attributes defined in arrays. The main key is the machine readable name of the permission, which we will use in our hook_menu() implementation. ‘title’ provides the human-readable name of the permission, to be shown on the permission administration page. This should be wrapped in the t() function for translation. See hook_permission() for all the available options.

Registering the URL and naming the page function

We’ll need to edit current_posts_menu() to establish a path and name for the new page. A quick note on function naming conventions in Drupal: If you are creating a strictly private function (i.e. no other module should rely on it being a stable function or call it), start the function name with an underscore: “_your_module_name_”. If your function is public (i.e. it would be okay for another module to call it, and you don’t intend to change its argument signature or behavior often), start the function name with “your_module_name_” (no underscore). If you are implementing a Drupal hook, you must always name the function “your_module_name_hookname”. Likewise, if you are not implementing a Drupal hook, check the hooks to be sure you have not accidentally picked a function name matching a hook.

Page callbacks are good candidates for strictly private functions, so we will start with an underscore, then the module name, and finally the word, ‘page’. Add the following code to your current_posts_menu() function as the second item in the $items array. Be sure the code, return $items; is the final line of the function, and remember not to include the php markers.

<?php
$items[‘current_posts’] = array(
‘title’ => ‘Current posts’,
‘page callback’ => ‘_current_posts_page’,
‘access arguments’ => array(‘access current_posts content’),
‘type’ => MENU_NORMAL_ITEM, //Will appear in Navigation menu.
);
?>

This listing mirrors the previous menu item with a couple of exceptions. The first needed a description for the Configuration page. That’s less important here, and we are not including it. Because we are creating a page function and not calling drupal_get_form, we have no page arguments to pass. The new permission becomes the access argument. Since we are not using the admin/config path, our item will appear in the Navigation menu.

Check

Enable your module again and check the Navigation menu. You should see a listing for Current posts. If it’s not there, try clearing the caches. Because we have not yet written the page function, the link leads to a blank page, and you may get an error message. Remember to disable your module before continuing.

See also

how to develop module for registration process drupal 7

Telling Drupal about your module

Main topic described: .info files

All modules must have a ‘modulename.info’ file, which contains meta information about the module.

The general format is:

name = Module name
description = A description of what your module does.
core = 7.x

For our module, we will replace ‘modulename’ in the example above with the name of our module, ‘current_posts’. Without this file, the module will not show up in the module listing. Here are the contents:

name = Current posts
description = A block module that lists links to recent posts.

core = 7.x

Add the source above to a file named current_posts.info and save it into the module’s directory at sites/all/modules/current_posts.

Note: If you copy and paste this code block, ensure that the description data does not contain a line break (turn off word-wrap on your text-editor to be sure). Otherwise, the .info file will not parse correctly.

.Info File Details

name (Required)
The displayed name of your module. It should follow the Drupal capitalization standard: only the first letter of the first word is capitalized (“Example module”, not “example module” or “Example Module”).
name = Current posts
description (Required)
A short, preferably one line description that will tell the administrator what this module does on the module administration page. Overly long descriptions can make this page difficult to work with, so please try to be concise. This field is limited to 255 characters.

description = A block module that lists links to recent posts.

Descriptions can contain links to documentation and sources. The following example shows a link to the author. It could be a link to a documentation node on Drupal.org. This is useful when the online documentation is better than the readme file and when you want to read about a module before switching it on.
description = Domain manager by <a href=”http://petermoulding.com”>Peter Moulding .com</a>.

core (Required)
The version of Drupal that your module is for. For Drupal 7 this would be 7.x, etc. Note that modules cannot specify the minor version of a branch of Drupal. 6.x is correct; 6.2 is not.

core = 7.x

files (Optional)
Drupal now supports a dynamic-loading code registry. To support it, all modules must now declare any code files containing class or interface declarations in the .info file, like so:

name = Example module
description = “Gives an example of a module.”

files[] = example.test

When a module is enabled, Drupal will rescan all declared files and index all the classes and interfaces that it finds. Classes will be loaded automatically by PHP when they are first accessed.

dependencies (Optional)
There are a couple of extra options that may appear in the .info file, one of which are module dependencies. If a module requires another module to be enabled, list each module (filename) required in the following syntax:

dependencies[] = taxonomy
dependencies[] = comment

For the example module, these don’t apply and we will simply omit them. If dependencies are assigned for a module, Drupal will not allow it to be activated until the required dependencies are met.

package (Optional)
If a module has a package string, on the admin/build/modules page it will be listed with other modules with the same category. If a package string is not assigned, it will simply be listed in the ‘Other’ category. Not assigning a package for your module is perfectly ok; in general packages are best used for modules that are distributed together or are meant to be used together. If in doubt, leave this field blank.

package = “Your arbitrary grouping string”

Suggested examples of appropriate items for the package field:

  • Administration
  • Commerce
  • Development
  • Fields
  • Media
  • User interface
  • Views
  • Voting (if it uses/requires VotingAPI)

.info files have several other optional categories that are rarely used or discouraged and are not relevant to this tutorial. See Writing .info files (Drupal 7.x), if you want all the details.

Check

Your module should now appear in the module list in your site. Go to Modules and scroll down to the bottom of the list in the ‘Other’ category. You should see the module ‘Current posts’.

Writing comments and implementing your first hook

Drupal hook described: hook_help()

Comments in Drupal modules

It’s always a good idea to document how your module works in comments. Drupal uses Doxygen to draw documentation from source code, so contrib modules on drupal.org follow strict comment guidelines. See Doxygen and comment formatting conventions for more details. Following these guidelines is beneficial to anyone looking at your code even if it’s not strictly necessary for your situation.

Your first comment:

<?php
/**
* @file
* A block module that displays recent blog and forum posts.
*/
?>

@file signifies that this comment pertains to the entire file. The doc block begins with a slash and two asterisks (/**) and ends with one asterisk and a slash (*/). Following Drupal guidelines, we will introduce each function in the module with such a comment.

Implementing your first hook

Hooks are fundamental to Drupal modules. They allow you to integrate your module into the actions of Drupal core. If you missed it, go back and read about hooks in Introduction to Drupal modules.

Your first hook is hook_help. This hook is recommended for inclusion in all contrib modules. hook_help provides help and additional information about the module to the user. To implement any hook in Drupal, replace “hook” in the hook name with your module’s short name, and create a function in the .module file with that name. So, to implement hook_help() in our example module, we create a function called current_posts_help() in the current_posts.module file:

<?php
function current_posts_help($path, $arg) {

}
?>

The $path parameter provides context for the help: where in Drupal or the module the user is when they are accessing help. The recommended way to process this variable is with a switch statement. This code pattern is common in Drupal modules. Here is an abbreviated implementation of this function for your module, along with its doc block comment:

<?php
/**
* Implements hook_help.
*
* Displays help and module information.
*
* @param path
*   Which path of the site we’re using to display help
* @param arg
*   Array that holds the current path as returned from arg() function
*/
function current_posts_help($path, $arg) {
switch ($path) {
case “admin/help#current_posts”:
return ‘<p>’.  t(“Displays links to nodes created on this date”) .'</p>’;
break;
}
}
?>

(Note the closing ?> should not appear in your code.)

The admin/help#modulename case is used by Drupal core to link from the main help page (/admin/help or ?q=admin/help). You will eventually want to add more text to provide a better help message to the user; see Help text standard for the complete recommendation. The t() function that wraps the text marks it for translation. This function not only allows for translation, it can give your code an added layer of security. See Localization API for more information.

Check

Look again at the module list by clicking Modules on the Toolbar in your Drupal installation. Enable the module and click ‘Save configuration’. Close and reopen the Modules page, find your module in the list, and you should see a link to ‘Help’ on the right. Click it to see the help text returned by current_posts_help. You can also follow the Help link in the toolbar (http://example.com/admin/help) and find your help link listed with the others on that page.

Disable your module and save (‘Save configuration’ button). Important: Be sure you follow that last step and disable your module and save, or new partial code in your module could break your site.

Declaring the block

Drupal hook described: hook_block_info()

Modules are created to do all sorts of things: create blocks (abbreviated content that often appears on the right or left side of multiple pages), create special content types (for full page content – such as the content you are reading right now), track back-end information, and more. You may hear the term ‘block modules’ used to describe modules that primarily create block content (such as the menu module), or ‘node modules’ used to describe modules that primarily generate full page content (such as the blog and forum modules). At this stage, this module is a ‘block module’, because it generates a block.

In Drupal 7, there are at least eight block hooks. For the purposes of this module, we will use two of them. The first is hook_block_info(). As you might suspect, this hook tells Drupal what block(s) your module creates. We will use this hook to define a block that will eventually display the most recent posts. You can use a given hook exactly once in any module, so this hook must declare all blocks the module needs. For this module, a single block is all we need.

To use this hook to define our block, go to your current_posts.module file and create the function current_posts_block_info() as follows:

<?php
/**
* Implements hook_block_info().
*/
function current_posts_block_info() {
$blocks[‘current_posts’] = array(
‘info’ => t(‘Current posts’), //The name that will appear in the block list.
‘cache’ => DRUPAL_CACHE_PER_ROLE, //Default
);
return $blocks;
}
?>

(Remember not to include the closing ?> in your code.)

The doc block simply identifies the hook. For such a straightforward implementation, that is sufficient. Anyone reading the code can easily go to the API and call up the hook for further information.

The return value takes the form of an associative array. Pay special attention to this array structure, as it is Drupal’s preferred programming structure. Arrays in PHP are well supported and very fast, and Drupal makes extensive use of them.

The only required value is ‘info’. hook_block_info can also specify configuration settings. Here we set the cache to the default. See hook_block_info for a complete list of settings available.

Don’t forget the final return statement.

Check

At this point, go to Modules, click the checkbox to enable Current posts, and save your configuration. Next, navigate to Structure > Blocks. Scroll down to the bottom of the list. Among the disabled blocks, you should find the name, ‘Current posts’. Return to Modules, disable your module and save. Important: Be sure you disable your module and save, or new partial code in your module could break your site.


See Also
hook_block_info

Retrieving data

Main topic described: Database API
Main function described: db_select()

Next we will create a custom function to retrieve the most recent posts. When a node is first created, the time of creation is stored in the database. We’ll use this database field to find our data. We could include this code in the hook we will implement in the next tutorial page. Putting this section into a separate function keeps the code cleaner, easier to read and to maintain.

We will call the function current_posts_contents. We continue to follow the naming convention by beginning with the module short name. Then we use a descriptive word that is not a Drupal hook. The function begins with getting the time numbers. Here’s the first part:

<?php
/**
* Custom content function.
*
* Set beginning and end dates, retrieve posts from database
* saved in that time period.
*
* @return
*   A result set of the targeted posts.
*/
function current_posts_contents(){
//Get today’s date.
$today = getdate();
//Calculate the date a week ago.
$start_time = mktime(0, 0, 0,$today[‘mon’],($today[‘mday’] – 7), $today[‘year’]);
//Get all posts from one week ago to the present.
$end_time = time();
?>

This code gets the current time, then calculates the time (in seconds since epoch start, see mktime for more information on time format) for midnight a week ago. These functions are straight PHP; you can look them up on the PHP website (php.net) for more details.

Next we use Drupal’s Database API to retrieve our list of current nodes. This is the second part of the custom function:

<?php
//Use Database API to retrieve current posts.
$query = db_select(‘node’, ‘n’)
->fields(‘n’, array(‘nid’, ‘title’, ‘created’))
->condition(‘status’, 1) //Published.
->condition(‘created’, array($start_time, $end_time), ‘BETWEEN’)
->orderBy(‘created’, ‘DESC’) //Most recent first.
->execute();
return $query; 
}
?>

Built into Drupal is a very robust query builder, using a special object-oriented API. INSERT, UPDATE, and DELETE queries need this special care in order to behave consistently across all different databases. Although our SELECT query doesn’t require this structure, this is a good opportunity to become familiar with it. You will see this structure used throughout Drupal.

  1. We build the query using the db_select method, which takes a table name (‘node’) and alias (‘n’) as arguments.
  2. The fields method uses the table assigned the alias ‘n’ to select the fields listed in the array in the second argument.
  3. The condition method takes three arguments. The first is the field, the second the value, the third the operator. If no operator is specified, as in ‘status’ above, = is assumed.
  4. The orderBy method sorts according to the field in the first argument, in the order specified by the second argument.
  5. The execute method compiles and runs the query and returns a result set/statement object.

Here’s the complete function:

<?php
/**
* Custom content function.
*
* Set beginning and end dates, retrieve posts from database
* saved in that time period.
*
* @return
*   A result set of the targeted posts.
*/
function current_posts_contents(){
//Get today’s date.
$today = getdate();
//Calculate the date a week ago.
$start_time = mktime(0, 0, 0,$today[‘mon’],($today[‘mday’] – 7), $today[‘year’]);
//Get all posts from one week ago to the present.
$end_time = time();

//Use Database API to retrieve current posts.
$query = db_select(‘node’, ‘n’)
->fields(‘n’, array(‘nid’, ‘title’, ‘created’))
->condition(‘status’, 1) //Published.
->condition(‘created’, array($start_time, $end_time), ‘BETWEEN’)
->orderBy(‘created’, ‘DESC’) //Most recent first.
->execute();
return $query; 
}
?>

(Remember not to include the closing ?> in your code.)

Generating block content

Drupal hook described: hook_block_view()
Main functions described: user_access(), l(), theme()

The next step in this tutorial is to take the data we generated in our custom function and turn it into content for the block. We will do this using hook_block_view. This function returns two values, ‘subject’, which is the title of the block, and ‘content’, which is self-documenting. Once again, we will use a switch structure. This structure allows for additional blocks to be added in the future if needed. The $delta parameter tells the function which block is being requested.

Access check

Here’s the first part of the code:

<?php
function current_posts_block_view($delta = ”) {
switch($delta){
case ‘current_posts’:
$block[‘subject’] = t(‘Current posts’);
if(user_access(‘access content’)){
//Retrieve and process data here.
}
?>

Best practice dictates that information displayed to the user be assessed against that user’s permissions. Here we implement the most basic of checks, ‘access content’. You can use any permission here that Drupal supplies, or even create your own. For a list of permission names, go to People > List (tab) (or http://example.com/admin/people). In the permission dropdown there, you will find a list of the machine readable names that you can use in your code. This list will include all permissions active on your site, including those created by contrib modules. The names in the Permissions tab (http://example.com/admin/people/permissions) are not the machine readable names.

Another way to find permissions set in core modules is through the API reference. Enter the module name followed by _permission, for example, node_permission. You’ll get a list of all the permissions that node defines. Knowing which one to use might be a bit tricky. For example, the access content permission we use here is actually defined in the node module, not the block module.

Coding the data as links

Here’s the next bit of code:

<?php
//Use our custom function to retrieve data.
$result = current_posts_contents();
//Array to contain items for the block to render.
$items = array();
//Iterate over the resultset and format as links.
foreach ($result as $node){
$items[] = array(
‘data’ => l($node->title, ‘node/’ . $node->nid),
);
}
?>

First we use our custom function to save the data into the $result variable. The $items variable gives us a place to store the processed data. We then iterate over the resultset, processing each item and formatting it as a link.

Notice the actual link is created by the l() function. (That’s a small case ‘L’.) The first parameter sets the link text, in this case the node’s title. The second parameter is the actual link path. The path to any node is always “node/#”, where # is the ID number of the node. l() uses this path to generate appropriate links, adjusting the URL to the installation’s URL configuration.

Theming the data

Drupal’s presentation layer is a pluggable system known as the theme layer. Each theme can take control of most of Drupal’s output, and has complete control over the CSS. Theming in Drupal is a vast subject with entire books devoted to the subject. Here we will merely touch the surface with a theme function.

Here’s the last section of code for current_posts_block_view:

<?php

if (empty($items)) { //No content in the last week.
$block[‘content’] = t(‘No posts available.’); 
} else {
//Pass data through theme function.
$block[‘content’] = theme(‘item_list’, array(
‘items’ => $items));
}
}
}
return $block;
}
?>

First, we allow for the possibility of no content that fits our criteria. Your module’s block should appear whether or not new content from the last week exists on your site.

If the $items variable contains data, it goes to the theme() function. The first argument of this function is the theme hook. Drupal includes many default theme hooks you can use with this function; see Default theme implementations for the complete list. We have chosen to theme our data as an unordered list, the theme_item_list theme hook. The second argument passes the content to be themed.

You may wonder why the code takes this form if the function is theme_item_list. theme() looks for a theme function called theme_hookname, where ‘hookname’ is the first argument, in this case, item_list. In other words, the second part of the hook name becomes the first argument when the function is called, telling the theme function which of its default versions to use.

The whole function

<?php
/**
* Implements hook_block_view().
*
* Prepares the contents of the block.
*/
function current_posts_block_view($delta = ”) {
switch($delta){
case ‘current_posts’:
$block[‘subject’] = t(‘Current posts’);
if(user_access(‘access content’)){
//Use our custom function to retrieve data.
$result = current_posts_contents();
//Array to contain items for the block to render.
$items = array();
//Iterate over the resultset and format as links.
foreach ($result as $node){
$items[] = array(
‘data’ => l($node->title, ‘node/’ . $node->nid),
);
}

if (empty($items)) { //No content in the last week.
$block[‘content’] = t(‘No posts available.’); 
}
else {
//Pass data through theme function.
$block[‘content’] = theme(‘item_list’, array(
‘items’ => $items));
}
}
}
return $block;
}
?>

(Remember not to include the opening or closing PHP tags when you add this section.)

Testing and troubleshooting the module

It’s time to enable and fully test your module!

Enable the module

Go to Modules, or http://example.com/admin/modules, and scroll down to the bottom of the list in the ‘Other’ category. You should see the module ‘Current posts.’ Click the checkbox to enable Current posts, and save your configuration. Now you should see a link to Help beside the module name. Click it to see the help text you entered in current_posts_help.

Enable the block

Next, navigate to Structure > Blocks, or http://example.com/admin/structure/block. Scroll down to the bottom of the list. Among the disabled blocks, you should find the name, ‘Current posts’. Set its location for one of the page regions and save. Navigate to another page like your homepage to see your block. Congratulations! You have written a working module.

Troubleshooting

If you get a “white screen” or a PHP error when you enable this module, it probably means you have a syntax error in your .module file. Be sure all your punctuation is correct, semi-colons, commas, etc. all in the right places, and that you have all the hook names and module short names spelled correctly. (In the case of a white screen, you may be able to find out what the PHP error was by looking in your Apache error log. Or you can try changing PHP’s error reporting level.)

If you cannot find and fix the syntax error, nothing on your site will display, because Drupal will try to load your module on every page request. The easiest way to get your site working again is to delete the module’s folder or move it out of the site, in which case Drupal will figure out that it shouldn’t load this module after all, and your site should work again.

Clear caches

Drupal caches a lot of data, and if you are not seeing changes appear, that could be why. In this phase of the module, the caches shouldn’t be an issue, but they will be as we proceed. To get all the troubleshooting instructions in one place, we’ll give you the instructions here that you’ll need later.

To clear the caches, go to Configuration > Performance or http://example.com/admin/config/development/performance, and click the Clear all caches button.

Preparing for a module configuration form

Drupal hook described: hook_menu()
Main function described: drupal_get_form()

Now that we have a working module, we’d like to make it more flexible. With a busy site, we might not want to display all the links to content created last week. So we’ll create a configuration form where the administrator can adjust how many links to display.

Registering the URL

We will define our form using hook_menu(). The name of this hook is a bit misleading, as it does far more than simply assign menu items. Using this hook, modules register paths to define how URL requests are handled. Paths may be registered for URL handling only, or as a link to be placed in a menu (usually the Navigation menu). In this case, we will set a path that will make the form available from the Configuration page (http://example.com/admin/config).

hook_menu() implementations return an associative array whose keys define paths and whose values are an associative array of properties for each path. The definition for each path may include a page callback function, which is invoked when the registered path is requested. After we have set up our form in hook_menu, we will create the actual form by writing the page callback function we define here.

Access check

hook_menu() also provides an important part of Drupal’s security, as it performs user access checks. 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.

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.

Here’s the code:

<?php
/**
* Implements hook_menu().
*/
function current_posts_menu() {
$items = array(); 

$items[‘admin/config/content/current_posts’] = array(
‘title’ => ‘Current posts’,
‘description’ => ‘Configuration for Current posts module’,
‘page callback’ => ‘drupal_get_form’,
‘page arguments’ => array(‘current_posts_form’),
‘access arguments’ => array(‘access administration pages’),
‘type’ => MENU_NORMAL_ITEM,
);

return $items;

?>

(Remember not to include the opening or closing PHP tags when you add this section.)

Setting properties

The path set as the array key for $items becomes the path to our form. It must take the form of an internal Drupal path with no leading or trailing slashes. We begin the path with admin/config to tell Drupal to include a link to this form on the site administration page (Configuration or http://example.com/admin/config). content tells Drupal which section of this page the link belongs to, in this case, Content authoring. You can choose any one of the default sections for your form by including its path name here.

Next comes the value, the associative array that lists properties of the item. title and description, as noted previously, are without the t() function. 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.

Declaring the form

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. (Note that the trailing parentheses are not included in the call to drupal_get_form.) page arguments values are passed to that function. By assigning current_posts_form, we define that as both the form ID and the name of the function that will create our settings form. As the name of a function, it must follow PHP variable naming conventions, with no spaces or hyphens. To prevent name collision, always start the function name with the name of your module, followed by an underscore.

We’ll begin to explore the Form API as we build that function in the next part of the tutorial.

See also

Creating the configuration form

Main topic described: Form API
Main functions described: variable_get(), system_settings_form()

Next, we will build the current_posts_form() function. We build the form by adding elements to the $form array. In a pattern similar to the menu $items variable, the name of each element is its key in the array, with that key’s value being a specially formatted associative array. In this value array, we list the attributes of the element, this time preceding each key with a hash mark (#).

Add the following to your current_posts.module file:

<?php
/**
* Form function, called by drupal_get_form()
* in current_posts_menu().
*/
function current_posts_form($form, &$form_state) {
$form[‘current_posts_max’] = array(
‘#type’ => ‘textfield’,
‘#title’ => t(‘Maximum number of posts’),
‘#default_value’ => variable_get(‘current_posts_max’, 3),
‘#size’ => 2,
‘#maxlength’ => 2,
‘#description’ => t(‘The maximum number of links to display in the block.’),
‘#required’ => TRUE,
);

return system_settings_form($form);
}
?>

(Remember not to include the opening or closing PHP tags when you add this section.)

The element attributes

Here we create a text field, with a title of ‘Maximum number of posts’, and a description, which will appear below the field. Note that both of these strings are passed through the t() function for translation. Size is 2, maximum length is 2. The Form API will automatically validate the maxlength attribute when the form is submitted and throw an error if the length exceeds 2. We have also designated the field as required. The form API will mark it with an asterisk and will throw an error if the form is submitted with no value in the field. See the forms_api_reference for the complete list of available elements and properties.

Persistent variables

Certain data, like system settings and user-configurable information, needs to be saved and retrievable for a website to function properly. Drupal accomplishes this through the use of persistent variables. These variables are stored in a database table with keys provided by the implementing module.

The function variable_get() retrieves a persistent variable, and variable_set(), as you might expect, sets one. We use variable_get() above to retrieve the value of our field if it has been set. If not, we specify a default value of 3. variable_get() uses the name of the element, current_posts_max, to find the relevant data.

System settings

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, (see Form API Quickstart Guide) but for now, we will use this handy shortcut.

Editing the query

We must add two lines of code to our query function, current_posts_contents, one using variable_get() to retrieve the data from our settings form, and the other adding the range function to include this limit in the query. Here’s the revised function, with the new lines noted in comments:

<?php
function current_posts_contents() {
//Get today’s date.
$today = getdate();
//Calculate midnight a week ago.
$start_time = mktime(0, 0, 0,$today[‘mon’],($today[‘mday’] – 7), $today[‘year’]);
//Get all posts from one week ago to the present.
$end_time = time();

//NEW LINE
$max_num = variable_get(‘current_posts_max’, 3);

//Use Database API to retrieve current posts.
$query = db_select(‘node’, ‘n’)
->fields(‘n’, array(‘nid’, ‘title’, ‘created’))
->condition(‘status’, 1) //Published.
->condition(‘created’, array($start_time, $end_time), ‘BETWEEN’)
->orderBy(‘created’, ‘DESC’) //Most recent first.
->range(0, $max_num) //NEW LINE
->execute();
return $query;
}
?>

Using variable_get(), we save the configuration setting into the $max_num variable or assign a default of 3. Then we add the range method to our select query. That method’s first argument is set to zero to specify starting at the beginning of the set. The second argument, $max_num, determines how many records to return.

Check

Go ahead and again enable your module. Then you will need to clear the menu cache, so that Drupal will recognize the new URL. (Drupal caches a lot of data, including a list of all the URLs it recognizes.) To clear the cache, go to Configuration > Performance or http://example.com/admin/config/development/performance, and click the Clear all caches button.

Now you can test the settings form. Navigate to it: Configuration > Content authoring > Current posts or http://example.com/admin/config/content/current_posts. Adjust the number of links and save the configuration. The maximum number of links in the block should adjust accordingly. See how system_settings_form() has added a submit button and gives you a confirmation message when you have successfully saved.

Please note: be sure to disable your module before continuing.

See also

Validating the data

Main topic described: Form API
Main functions described: _validate(), form_set_error()

The Form API supplies excellent default validation, but it also allows us to create our own validation function. We want to be sure that the value the user enters is a number greater than 0. The validation function works much like a hook, with the form ID taking the place of the module name. Our validate function will be named current_posts_form_validate($form, &$form_state). Don’t confuse this with hook_validate(), which is part of the main hook system.

Notice the $form_state array variable, an argument here and also for our form function. $form_state is passed by reference along through each stage of form processing to capture information about the form’s workflow and its current state. Incoming $_POST data is first sanitized and checked against the structure of the form before being handed off to validate and submit handlers. The array’s values key is the default key for storing this data. See drupal_build_form() for a list of $form_state keys.

Add this function to your current_posts.module file:

<?php
/**
* Implements validation from the Form API.
*
* @param $form
*   A structured array containing the elements and properties of the form.
* @param $form_state
*   An array that stores information about the form’s current state
*   during processing.
*/
function current_posts_form_validate($form, &$form_state){
$max_num = $form_state[‘values’][‘current_posts_max’];
if (!is_numeric($max_num)){
form_set_error(‘current_posts_max’, t(‘You must enter an integer for the maximum number of posts to display.’));
}
else if ($max_num <= 0){
form_set_error(‘current_posts_max’, t(‘Maximum number of posts to display must be positive.’));      
}
}
?>

(Remember not to include the closing ?> in your code.)

Testing the data

First we retrieve the configuration setting from $form_state and save it into a variable. We then check to be sure it’s a number. If not, form_set_error() goes into action, setting the element in the first argument with an error CSS class (red by default). It also displays a red box with the error message given in the second argument.

If the data passes the first check, we make sure it’s a positive number. If not, we again call form_set_error() Notice that both our string literals for error messages are wrapped in t().

Check

Enable your module again and visit the configuration page. Experiment with entering values to see how the validation works on errors. Be sure to disable your module before you add more code.

See also

Coding standards

Source : http://drupal.org

Note: The Drupal Coding Standards apply to code within Drupal and its contributed modules. This document is loosely based on the PEAR Coding standards. Comments and names should use US English spelling (e.g., “color” not “colour”).

Drupal coding standards are version-independent and “always-current”. All new code should follow the current standards, regardless of (core) version. Existing code in older versions may be updated, but doesn’t necessarily have to be. Especially for larger code-bases (like Drupal core), updating the code of a previous version for the current standards may too huge of a task. However, code in current versions should follow the current standards.

Note: Do not squeeze coding standards updates/clean-ups into otherwise unrelated patches. Only touch code lines that are actually relevant. To update existing code for the current standards, always create separate and dedicated issues and patches.

Also note:

  • the Coder module provides an automated way of checking for code standards compliance and
  • the Grammar Parser module provides an automated way of rewriting code files in compliance with code standards.

Contents of this Page

Indenting and Whitespace

Use an indent of 2 spaces, with no tabs.

Lines should have no trailing whitespace at the end.

Files should be formatted with \n as the line ending (Unix line endings), not \r\n (Windows line endings).

All text files should end in a single newline (\n). This avoids the verbose “\ No newline at end of file” patch warning and makes patches easier to read since it’s clearer what is being changed when lines are added to the end of a file.

Operators

All binary operators (operators that come between two values), such as +, -, =, !=, ==, >, etc. should have a space before and after the operator, for readability. For example, an assignment should be formatted as $foo = $bar; rather than $foo=$bar;. Unary operators (operators that operate on only one value), such as ++, should not have a space between the operator and the variable or number they are operating on.

Casting

Put a space between the (type) and the $variable in a cast: (int) $mynumber.

Control Structures

Control structures include if, for, while, switch, etc. Here is a sample if statement, since it is the most complicated of them:

if (condition1 || condition2) {
action1;
}
elseif (condition3 && condition4) {
action2;
}
else {
defaultaction;
}

Control statements should have one space between the control keyword and opening parenthesis, to distinguish them from function calls.

You are strongly encouraged to always use curly braces even in situations where they are technically optional. Having them increases readability and decreases the likelihood of logic errors being introduced when new lines are added.

For switch statements:

switch (condition) {
case 1:
action1;
break;

case 2:
action2;
break;

default:
defaultaction;
}

For do-while statements:

do {
actions;
} while ($condition);

Line length and wrapping

The following rules apply to code. See Doxygen and comment formatting conventions for rules pertaining to comments.

  • In general, all lines of code should not be longer than 80 chars.
  • Lines containing longer function names, function/class definitions, variable declarations, etc are allowed to exceed 80 chars.
  • Control structure conditions may exceed 80 chars, if they are simple to read and understand:
      if ($something[‘with’][‘something’][‘else’][‘in’][‘here’] == mymodule_check_something($whatever[‘else’])) {

    }
    if (isset($something[‘what’][‘ever’]) && $something[‘what’][‘ever’] > $infinite && user_access(‘galaxy’)) {

    }
    // Non-obvious conditions of low complexity are also acceptable, but should
    // always be documented, explaining WHY a particular check is done.
    if (preg_match(‘@(/|\\)(\.\.|~)@’, $target) && strpos($target_dir, $repository) !== 0) {
    return FALSE;
    }
  • Conditions should not be wrapped into multiple lines.
  • Control structure conditions should also NOT attempt to win the Most Compact Condition In Least Lines Of Code Award™:
      // DON’T DO THIS!
    if ((isset($key) && !empty($user->uid) && $key == $user->uid) || (isset($user->cache) ? $user->cache : ”) == ip_address() || isset($value) && $value >= time())) {

    }

    Instead, it is recommended practice to split out and prepare the conditions separately, which also permits documenting the underlying reasons for the conditions:

      // Key is only valid if it matches the current user’s ID, as otherwise other
    // users could access any user’s things.
    $is_valid_user = (isset($key) && !empty($user->uid) && $key == $user->uid);

    // IP must match the cache to prevent session spoofing.
    $is_valid_query = (isset($user->cache) ? $user->cache == ip_address() : FALSE);

    // Alternatively, if the request query parameter is in the future, then it
    // is always valid, because the galaxy will implode and collapse anyway.
    $is_valid_query = $is_valid_cache || (isset($value) && $value >= time());

    if ($is_valid_user || $is_valid_query) {

    }

    Note: This example is still a bit dense. Always consider and decide on your own whether people unfamiliar with your code will be able to make sense of the logic.

Function Calls

Functions should be called with no spaces between the function name, the opening parenthesis, and the first parameter; spaces between commas and each parameter, and no space between the last parameter, the closing parenthesis, and the semicolon. Here’s an example:

$var = foo($bar, $baz, $quux);

As displayed above, there should be one space on either side of an equals sign used to assign the return value of a function to a variable. In the case of a block of related assignments, more space may be inserted to promote readability:

$short         = foo($bar);
$long_variable = foo($baz);

Function Declarations

function funstuff_system($field) {
$system[“description”] = t(“This module inserts funny text into posts randomly.”);
return $system[$field];
}

Arguments with default values go at the end of the argument list. Always attempt to return a meaningful value from a function if one is appropriate.

Class Constructor Calls

When calling class constructors with no arguments, always include parentheses:

$foo = new MyClassName();

This is to maintain consistency with constructors that have arguments:

$foo = new MyClassName($arg1, $arg2);

Note that if the class name is a variable, the variable will be evaluated first to get the class name, and then the constructor will be called. Use the same syntax:

$bar = ‘MyClassName’;
$foo = new $bar();
$foo = new $bar($arg1, $arg2);

Arrays

Arrays should be formatted with a space separating each element (after the comma), and spaces around the => key association operator, if applicable:

$some_array = array(‘hello’, ‘world’, ‘foo’ => ‘bar’);

Note that if the line declaring an array spans longer than 80 characters (often the case with form and menu declarations), each element should be broken into its own line, and indented one level:

$form[‘title’] = array(
‘#type’ => ‘textfield’,
‘#title’ => t(‘Title’),
‘#size’ => 60,
‘#maxlength’ => 128,
‘#description’ => t(‘The title of your node.’),
);

Note the comma at the end of the last array element; This is not a typo! It helps prevent parsing errors if another element is placed at the end of the list later.

Quotes

Drupal does not have a hard standard for the use of single quotes vs. double quotes. Where possible, keep consistency within each module, and respect the personal style of other developers.

With that caveat in mind: single quote strings are known to be faster because the parser doesn’t have to look for in-line variables. Their use is recommended except in two cases:

  1. In-line variable usage, e.g. “<h2>$header</h2>”.
  2. Translated strings where one can avoid escaping single quotes by enclosing the string in double quotes. One such string would be “He’s a good person.” It would be ‘He\’s a good person.’ with single quotes. Such escaping may not be handled properly by .pot file generators for text translation, and it’s also somewhat awkward to read.

String Concatenations

Always use a space between the dot and the concatenated parts to improve readability.

<?php
$string = ‘Foo’ . $bar;
$string = $bar . ‘foo’;
$string = bar() . ‘foo’;
$string = ‘foo’ . ‘bar’;
?>

When you concatenate simple variables, you can use double quotes and add the variable inside; otherwise, use single quotes.

<?php
$string = “Foo $bar”;
?>

When using the concatenating assignment operator (‘.=’), use a space on each side as with the assignment operator:

<?php
$string .= ‘Foo’;
$string .= $bar;
$string .= baz();
?>

Comments

Comment standards are discussed on the separate Doxygen and comment formatting conventions page.

Including Code

Anywhere you are unconditionally including a class file, use require_once(). Anywhere you are conditionally including a class file (for example, factory methods), use include_once(). Either of these will ensure that class files are included only once. They share the same file list, so you don’t need to worry about mixing them – a file included with require_once() will not be included again by include_once().

Note: include_once() and require_once() are statements, not functions. You don’t need parentheses around the file name to be included.

When including code from the same directory or a sub-directory, start the file path with “.”:
include_once ./includes/mymodule_formatting.inc
In Drupal 7.x and later versions, use DRUPAL_ROOT:
require_once DRUPAL_ROOT . ‘/’ . variable_get(‘cache_inc’, ‘includes/cache.inc’);

PHP Code Tags

Always use <?php ?> to delimit PHP code, not the shorthand, <? ?>. This is required for Drupal compliance and is also the most portable way to include PHP code on differing operating systems and set-ups.

Note that as of Drupal 4.7, the ?> at the end of code files is purposely omitted. This includes for module and include files. The reasons for this can be summarized as:

  • Removing it eliminates the possibility for unwanted whitespace at the end of files which can cause “header already sent” errors, XHTML/XML validation issues, and other problems.
  • The closing delimiter at the end of a file is optional.
  • PHP.net itself removes the closing delimiter from the end of its files (example: prepend.inc), so this can be seen as a “best practice.”

Semicolons

The PHP language requires semicolons at the end of most lines, but allows them to be omitted at the end of code blocks. Drupal coding standards require them, even at the end of code blocks. In particular, for one-line PHP blocks:

<?php print $tax; ?> — YES
<?php print $tax ?> — NO

Example URLs

Use “example.com” for all example URLs, per RFC 2606.

Naming Conventions

Functions and variables

Functions and variables should be named using lowercase, and words should be separated with an underscore. Functions should in addition have the grouping/module name as a prefix, to avoid name collisions between modules.

Persistent Variables

Persistent variables (variables/settings defined using Drupal’s variable_get()/variable_set() functions) should be named using all lowercase letters, and words should be separated with an underscore. They should use the grouping/module name as a prefix, to avoid name collisions between modules.

Constants

  • Constants should always be all-uppercase, with underscores to separate words. (This includes pre-defined PHP constants like TRUE, FALSE, and NULL.)
  • Module-defined constant names should also be prefixed by an uppercase spelling of the module that defines them.
  • In Drupal 8 and later, constants should be defined using the const PHP language keyword (instead of define()), because it is better for performance:
    <?php
    /**
    * Indicates that the item should be removed at the next general cache wipe.
    */
    const CACHE_TEMPORARY = -1;
    ?>

    Note that const does not work with PHP expressions. define() should be used when defining a constant conditionally or with a non-literal value:

    <?php
    if (!defined(‘MAINTENANCE_MODE’)) {
    define(‘MAINTENANCE_MODE’, ‘error’);
    }
    ?>

Global Variables

If you need to define global variables, their name should start with a single underscore followed by the module/theme name and another underscore.

Classes

Classes should be named using “CamelCase.” For example:

<?php
abstract class DatabaseConnection extends PDO {
?>

Class methods and properties should use “lowerCamelCase”:

<?php
public $lastStatement;
?>

The use of private class methods and properties should be avoided — use protected instead, so that another class could extend your class and change the method if necessary. Protected (and public) methods and properties should not use an underscore prefix, as was common in PHP 4-era code.

For more information on class and OO standards, see the more detailed coverage.

File names

All documentation files should have the file name extension “.txt” to make viewing them on Windows systems easier. Also, the file names for such files should be all-caps (e.g. README.txt instead of readme.txt) while the extension itself is all-lowercase (i.e. txt instead of TXT).

Examples: README.txt, INSTALL.txt, TODO.txt, CHANGELOG.txt etc.

Helper Module

There is a contributed module for assisting with code review. To use this module you must complete the following steps:

  • Install the Coder module.
  • Click on the “Code Review” link in your navigation menu.
  • Scroll down to “Select Specific Modules”.
  • Select the module you wish to review, and click the “Submit” button.

As an alternative to starting from the Code Review link in navigation, you can also review a particular module’s code by clicking on the link on the Modules admin screen.

The Coder module also comes with a command-line script called “Coder Format”, which will not only check your files for standards compliance, but fix them. Use with care!

BookMadeSimple – Simplify book use.

Source : http://drupal.org

BookMadeSimple aims to bypass some lacks of the standard Book module and so, simplify book management.

The major features are :

  • Create automatically main book page for any content-type by checking option in content-type settings
  • Add links to book page to add any content-type as child of book. Childs content-type are defined in content-type settings

REMARK : Explinations below are for Drupal 6, but BookMadeSimple behaviours are the sames for Drupal 5

Auto creation of main book page

With standard book module, to create a new book, you need to create a node, go to Book Outline section and choose Create an new book.
With BookMadeSimple, you only need to define in BookMadeSimple settings or content-type settings, content-types to automatically create as book

Add child page list

With standard book module, to add a child page, you need to click to “Add child page link”. So you can only add book page as child. To add another content-type as child, you need to create a new content and select parent book page in book outline section. Not really user friendly, isn’t it ? So, use BookMadeSimple
BookMadeSimple adds a list at bottom of page to directly add a content-type as child of the book. This list can be defined as a global way in BookMadeSimple settings or by content-type in content-type settings.
The list can be :

  • A dropdown list box (default)
  • An unordered list (standard drupal links)
  • Anything you want by themeable function

Permissions

BMS manage two user permissions :

  • show core Outline links : By default, standard outline links (outline tab, fieldset and reorder tab) are hidden. You can show them by role.
  • show book reorder link : You can also show only reorder tab by role.

Tips

  1. You can hide default “Add child page” link by checking “Hide default add child link” checkbox in BookMadeSimple settings.
  2. To place dropdown listbox top of links, add these lines to your template.php file :
    <?php
    function <your template>_links($links, $attributes = array(‘class’ => ‘links’)) {
    if (array_key_exists(“book_made_simple”,$links)) {
    $a = $links[“book_made_simple”];
    unset($links[“book_made_simple”]);
    array_unshift($links,$a);
    }
    return theme_links($links, $attributes = array(‘class’ => ‘links’));
    }
    ?>
  3. To theme the render of the child list, create a function _add_child_book_content_types(…) in your template.php like this :
    <?php
    /*
    Args :
    $allowedTypes : Associative array (content-type => name) of allowed child for the content type. Caution, before 2.2, you must convert underscore to hyphens for links.
    $node : node object.
    Return : HTML code to display
    */
    function <your_template>_add_child_book_content_types($allowedTypes, $node) {
    $html = “”;
    foreach ($allowedTypes as $type => $name ) {
    // str_replace needed for version prior to 2.2
    $html .= l($name, ‘node/add/’ . str_replace(‘_’, ‘-‘,$type));
    }   
    return $html;
    }
    ?>

Book module: Creating structured documents

Source : http://drupal.org

A book is a set of pages tied together in a hierarchical sequence, perhaps with chapters, sections, subsections, and so on. You can use books for manuals, site resource guides, Frequently Asked Questions (FAQs), or whatever you’d like.

Book module is not enabled by default. It must be enabled through Administer >> Site building >> Modules (Drupal 5 and 6) or Administration >> Modules (Drupal 7).

Users who have permission can create a book and write, review, modify, or rearrange the pages. Many users can work together on a book — you can allow or disallow collaboration, to whatever extent you want.

Uses

Creating, modifying, and administering books

On the books administration page administer >> content >> books (Drupal 5 and 6) or Content >> Find content >> Books (Drupal 7), users with proper permission can view a list of all published books on your site. For each book there’s a link to an outline, from which you can edit or delete pages or sections, change their titles, or change their weight (thus putting them in a different order). In some versions of Drupal, you can also check for orphan pages (pages that have become disconnected from the rest of the book); in other versions of Drupal, pages cannot be orphaned.

When a user creates new content of type Book page, they can add their page at the level of their choice in a book, or start a new book if they have permission. This is called defining the “parent” for a book page, and is in the “Book outline” section of the edit screen.

You also can change the position of a page in the book hierarchy later from the page edit screen, by changing the “parent” to which it belongs. Any “child” pages of the page you are editing will automatically be moved too, so if the page you are editing is a section header, this allows you to move an entire section.

On the permissions page administer >> user management >> permissions (Drupal 6) or Dashboard >> People >> Permissions (Drupal 7), you can assign users with various roles the permission to create book pages, to create new books, and to edit their own book pages or the pages of others.

You can also give permission to outline posts in books or add content to books (depending on the version of Drupal you are using). Users with this permission can take any other type of content on your site and add it to a book. When viewing content they’ll see an outline tab, and by clicking it they’ll come to an interface that lets them move the content into a book.

Book navigation and menus

When a visitor to your site is viewing a book page, they will automatically see links at the bottom for navigating to the previous page and the next, and a link labeled up that leads to the level above in the structure. There will also be a link to a printer-friendly version of the page at the bottom, for users with permission to view printer-friendly versions of pages.

The Book module automatically generates a contents page for each book. However, if the books on your site are complex, you may find that you need additional navigational aids beyond the table of contents and the previous/next/up links for users to understand where they are in your book. One navigational aid you can use is the book navigation block, which you can enable on the blocks page administer >> site building >> blocks (Drupal 5 and 6) or Dashboard >> Structure >> Blocks (Drupal 7). Enabling this block will turn on a menu that shows where the user is in your book; the menu is only visible when viewing the book.

Another navigational aid you can add to your site is a books link in any of your menus, which will take users to a list of your books. The books menu item is automatically part of the Navigation menu, and you can enable it from menus page administer >> site building >> menus (Drupal 5 and 6) or Dashboard >> Structure >> Menus (Drupal 7). You can also add this link to any menu you want (click “add menu item,” and enter “book” in the “path” field.)

Note that the “books” link takes users to your books. The “book navigation” block helps users move around inside your books.

Configuration

Here are the common operations with books. You can:

  • create a new book: create a new book page create content >> book page (Drupal 5 and 6) or content >> add content >> book page (Drupal 7) with a title for the new book, then select <create new book> in the Book Outline section, then publish the page.
  • create new book pages: create content >> book page (Drupal 5 and 6) or content >> add content >> book page (Drupal 7).
  • administer individual books (choose a published book from list): administer >> content >> books (Drupal 5 and 6) or Content >> Books (Drupal 7).
  • set workflow and other global book settings at administer >> content >> content types >> book page (Drupal 5 an 6) or Dashboard >> Structure >> Content types >> Book page >> Edit.
  • enable the book navigation block: administer >> site building >> blocks (Drupal 5 and 6) or Dashboard >> Structure >> Blocks (Drupal 7).
  • control who can create, edit, and maintain book pages at administer >> access control or administer >> user management >> permissions (Drupal 5 and 6) or Dashboard >> People >> Permissions (Drupal 7).

Confusing behaviour

If you create a new book and choose to not publish it, the book will not appear in the list of books and you will not have the option to add child pages to your first book page. Effectively you have to publish the first page to start adding child pages. If you want to create a book structure without making it public until it is edited and vetted, use a role based access module or similar to let you publish the book, so you can add child pages, but hide it from the public until ready for publication.

Child pages and child page menu entries are listed alphabetically. If you reorder the child pages using weights, the menu entries do not change to the same order. You have to separately reorder the menu entries.

Create your first simple Drupal 7 module

Source : http://reinholdweber.com

Create your first simple Drupal 7 module

Create your first Drupal 7 module with the following steps.

  1. Create a folder called helloworld in sites/all/modules/custom
  2. Create a helloworld.info file
  3. Create a template file page-helloworld.tpl.php in your theme directory
  4. Enable your module at http://domain.com/admin/build/modules
  5. Visit http://domain.com/helloworld

This belongs into your helloworld.info file

; $Id$
 
name = helloworld

description = Reinholds Hello World module
package = Reinholds modules
core = 7.x

 
files[] = helloworld.module

The helloworld.module file

<?php
	function helloworld_menu(){

	  $items = array();
 
	  $items['helloworld'] = array(

	    'title'            => t('Hello world'),
	    'page callback'    => 'helloworld_output',

	    'access arguments' => array('access content'),
	  );
 
	  return $items;

	}
 
	/*
	* Display output
	*/
	function helloworld_output() {
	  header('Content-type: text/plain; charset=UTF-8');

	  header('Content-Disposition: inline');
	  return 'helloworld';
	}
?>

The theme template file page-helloworld.tpl.php

<?php
print $content;
?>