The first thing we are going to do is to choose a name for the module. The name “annotate” seems appropriate it’s short
and descriptive. Next, I need a place to put the module. Contributed and custom modules are stored in the /sites/all/
modules directory, with each module stored in its own directory that uses the same name as the module.
You may wish to create a /sites/all/modules/custom directory to hold any modules that you create from scratch, making it easy for someone looking at your site to understand which modules are contributed modules that were
downloaded from Drupal.organd which modules were custom-coded for this site.
Next I’ll create an annotate directory within the /sites/all/modules/custom directory to hold all of the files associated with
the annotate module.
The first file I will create for the new module is the annotate.info file. Every module in Drupal 7 must have a .info file,
and the name must match the name of the module. For the annotate module, the basic information required for Drupal to
recognize the module is
name = Annotate
description = "Allows users to annotate nodes." package = Pro Drupal Development
core = 7.x
files[] = annotate.module files[] = annotate.install files[] = annotate.admin.inc
configure=admin/config/content/annotate/settings
The structure of the file is standard across all Drupal 7 modules. The name element is used to display the name of the
module on the Modules configuration page. The description element describes the module and is also displayed on the
Modules configurationpage. The package element defines which package or group the module is associated with. On the
Modules configuration page, modules are grouped and displayed by package. The Core field defines the version of Drupal
the module was written for. The phpelement defines what version of PHP is required by the module. And, the files
element is an array of the names of the files that are associated with the module. In the case of the annotation module,
the files associated with this module are the annotate.moduleand annotate.install files.
We could assign optional values in addition to those listed previously. Here’s an example of a module that requires PHP 5.2 and is dependent on the forum and taxonomy modules being installed in order for this module to work.
name = Forum confusion
description = Randomly reassigns replies to different discussion threads.
core = 7.x dependencies[] = forum dependencies[] = taxonomy files[] = forumconfusion.module
files[] = forumconfusion.install package = "Evil Bob's Forum BonusPak" php = 5.2
Now we’re ready to create the actual module. Create a file named annotate.module inside your sites/all/modules/custom/annotate subdirectory. Begin the file with an opening PHP tag and a CVS identification tag, followed by a comment:
<?php
/**
* @file
* Lets users add private annotations to nodes.
*
* Adds a text field when a node is displayed
* so that authenticated users may make notes.
*/
First, note the comment style. We begin with /**, and on each succeeding line, we use a single asterisk indented with one space( *) and */ on a line by itself to end a comment. The @file token denotes that what follows on the next line is adescription of what this file does. This one line description is used so that api.module (see http://drupal.org/project/api), Drupal’s automated
documentation extractor and formatter, can find out what this file does. While you’re on Drupal.org, also visit
http://api.drupal.org. Here you’ll find detailed documentation on every API that Drupal provides. I suggest you take a moment and look around this section of Drupal.org. It’s an invaluable resource for those of us who develop or modify modules.
After a blank line, we add a longer description aimed at programmers who will be examining (and no doubt improving) our code. Note that we intentionally do not use a closing tag (?>); these are optional in PHP and, if included, can cause problems with trailing
Our next order of business is to define some settings so that we can use a web based form to choose which node types to annotate.There are two steps to complete. First, we’ll define a path where we can access our settings. Then, we’ll create the settings form. To make a path, I need to implement a hook, specifically hook_menu.
Implementing a Hook
Drupal is built on a system of hooks, sometimes called callbacks. During the course of execution, Drupal asks modules if
they would like to do something. For example, when a node is being loaded from the database prior to being displayed on a page, Drupal examines all of the enabled modules to see whether they have implemented the hook_node_load() function. If so, Drupal executes that module’s hook prior to rendering the node on the page. We’ll see how this works in the
annotate module.
The first hook that we will implement is the hook_menu() function. We’ll use this function to add two menu items to the administrative menu on our site. We will add a new “annotate” menu item off of the main admin/config menu and a
submenu item under “annotate” named “settings,” which when clicked will launch the annotate configuration settings page.
The values of our menu items are arrays consisting of keys and values describing what Drupal should do when this path is requested. We’ll cover this indetail in Chapter 4, which covers Drupal’s menu/callback system. We name the call to hook_menu “annotate_menu”—replacing “hook” with the name of our module. This is consistent across all hooks— you always replace the word “hook” with the name ofyour module.
Here’s what we’ll add to our module:
/**
* Implementation of hook_menu().
*/
function annotate_menu()
{
$items['admin/config/annotate'] = array( 'title' => 'Node annotation','description' => 'Adjust node annotation options.',
'position' => 'right', 'weight' => -5, 'page callback' => 'system_admin_menu_block_page', 'access arguments' => array
('administer site configuration'), 'file' => 'system.admin.inc', 'file path' => drupal_get_path('module', 'system'),);
$items['admin/config/annotate/settings'] = array( 'title' => 'Annotation settings', 'description' => 'Change how annotationsbehave.', 'page callback' => 'drupal_get_form', 'page arguments' => array('annotate_admin_settings'), 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, 'file' => 'annotate.admin.inc',);
return $items;
}
Don’t worry too much about the details at this point. This code says, “When the user goes to http://example.com/?q=admin/config/annotate/settings, call the function drupal_get_form(), and pass it the form ID annotate_admin_settings.
Look fora function describing this form in the file annotate.admin.inc. Only users with the permission administer site
configuration may view this menu item.” When the time comes to display the form, Drupal will ask us to provide a form
definition (more on thatin a minute). When Drupal is finished asking all the modules for their menu items, it has a menu from which to select the proper function to call for the path being requested.
You should see now why we call it hook_menu() or the menu hook.
Adding Module-Specific Settings
Drupal has various node types (called content types in the user interface), such as articles and basic pages. We will want to restrict the use of annotations to only some node types. To do that, I need to create a page where we can tell our module which content
types we want to annotate. On that page, we will show a set of check boxes, one for each content type that exists. This will let
the end user decide which content types get annotations by checking or unchecking the check boxes (see Figure 2- 1). Such a page is an administrative page, and the code that composes it need only be loaded and parsed when needed. Therefore, we will put the code into a separate file, not in our annotate.module file, which will be loaded and run with each web request. Since we toldDrupal to look for our settings form in the annotate.admin.inc file, I’ll create sites/all/modules /annotate/annotate.admin.inc,
and add the following code to it:
<?php
/**
* @file
* Administration page callbacks for the annotate module.
*/
/**
* Form builder. Configure annotations.
*
* @ingroup forms
* @see system_settings_form().
*/
function annotate_admin_settings()
{
// Get an array of node types with internal names as keys and
// "friendly names" as values. E.g.,
// array('page' => ’Basic Page, 'article' => 'Articles')
$types = node_type_get_types(); foreach($types as $node_type)
{
$options[$node_type->type] = $node_type->name;
}
$form['annotate_node_types'] = array( '#type' => 'checkboxes', '#title' => t('Users may annotate these content types'), '#options' => $options, '#default_value' => variable_get('annotate_node_types', array('page')), '#description' => t
('A text field will be available on these content types to make user-specific notes.'),);
$form['#submit'][] = 'annotate_admin_settings_submit'; return system_settings_form($form);
}
Forms in Drupal are represented as a nested tree structure that is, an array of arrays. This structure describes to Drupal’s form
rendering engine how the form is to be represented. For readability, we place each element of the array on its own line. Each formproperty is denoted with a pound sign (#) and acts as an array key. We start by declaring the type of form element to be
checkboxes, which means that multiple check boxes will be built using a keyed array. We’ve already got that keyed array in the
$options variable. We set the options to the output of the function node_type_get_types(), which returns an array of objects. The output would look something like this:
[article] => stdClass Object ( [type] => article [name] => Article [base] => node_content
[description] => Use articles for time-sensitive content like news, press releases or blog posts. [help] => [has_title] => 1
[title_label] => Title [has_body] => 1 [body_label] => Body [custom] => 1 [modified] => 1 [locked] => 0 [orig_type] => article
)
The keys of the object array are Drupal’s internal names for the node types, with the friendly names (those that will be shown to
the user) contained in the name attribute of the object. Drupal’s form API requires that #options be set as a key => value paired
array so the foreach loop uses the type attribute to create the key and the name attribute to create the value portions of a new
array I named $options. Using the values in the$options array in our web form, Drupal will generate check boxes for the Basic
page and article node types, as well as any other content types you have on your site. We give the form element a title by definin
the value of the #title property.
The next directive, #default_value, will be the default value for this form element. Because checkboxes is a multiple form element (i.e., there is more than one check box) the value for #default_value will be an array. The value of #default_value is worth discussing: variable_get('annotate_node_types', array('page'))
Drupal allows programmers to store and retrieve any value using a special pair of functions: variable_get() and variable_set(). Thevalues are stored to the variables database table and are available anytime while processing a request. Because thesevariables are
retrieved from the database during every request, it’s not a good idea to store huge amounts of data this way. But it’s a very
convenient system for storing values like module configuration settings. Note that what we pass to variable_get() is akey describing our value (so we can get it back) and a default value. In this case, the default value is an array of which node types should allowannotation. We’re going to allow annotation of Basic page content types by default.
We provide a description to tell the site administrator a bit about the information that should go into the field. I’ll cover forms in detail in Chapter 11.
Next I’ll add code to handle adding and removing the annotation field to content types. If a site administrator checks a content type, I’ll add the annotation field to that content type. If a site administrator decides to remove the annotation field from a content type,
I’ll remove the field. I’ll use Drupal’s Field API to define the field and associate the field with a content type. The Field API handles all of the activities associated with setting up a field, including creating a table in the Drupal database to store the values submitted by content authors, creating the form element that will be used to collect the information entered by the author, and associating a fieldwith a content type and having that field displayed on the node edit form and when the node is displayed on a page. I will cover
the Field API in detail in Chapter 8.
The first thing that I will do is to create a form submission routine that will be called when the site administrator submits the form.
In this routine, the module will check to see whether the check box for a content type is checked or unchecked. If it isunchecked, I’ll verify that the content type does not have the annotation field associated with it. If it does, that indicates that the site administrator
wants the field removed from that content type, and removes the existing annotations that are stored in thedatabase. If the check
box is checked, the module checks to see whether the field exists on that content type, and if not, the module adds the annotation
field to that content type.
/**
* Process annotation settings submission.
*/
function annotate_admin_settings_submit($form, $form_state) {
// Loop through each of the content type checkboxes shown on the form.foreach ($form_state['values']['annotate_node_types'] as $key => $value) {
// If the check box for a content type is unchecked, look to see whether
// this content type has the annotation field attached to it using the
// field_info_instance function. If it does then we need to remove the
// annotation field as the administrator has unchecked the box.
if (!$value)
{
$instance = field_info_instance('node', 'annotation', $key); if (!empty($instance))
{
field_delete_instance($instance);
watchdog("Annotation", 'Deleted annotation field from content type: %key', array('%key' => $key));
}} else {
// If the check box for a content type is checked, look to see whether
// the field is associated with that content type. If not then add the
// annotation field to the content type.
$instance = field_info_instance('node', 'annotation', $key); if (empty($instance)) {
$instance = array('field_name' => 'annotation', 'entity_type' => 'node','bundle' => $key,'label' => t('Annotation'), 'widget_type' => 'text_textarea_with_summary', 'settings' => array('display_summary' => TRUE), 'display' => array('default' => array('type' => 'text_default',),'teaser' => array('type' => 'text_summary_or_trimmed',),),);
$instance = field_create_instance($instance);
watchdog('Annotation', 'Added annotation field to content type: %key', array('%key' => $key));
}}} // End foreach loop.}
The next step is to create the .install file for our module. The install file contains one or more functions that are called when the
module is installed or uninstalled. In the case of our module, if it is being installed, we want to create the annotation field so it can
be assigned to content types by site administrators. If the module is being uninstalled, we want to remove the annotation field from all the content types and delete the field and its contents from the Drupal database. To do this, create a new file in yourannotate
module directory named annotate.install.
The first function we will call is hook_install(). We’ll name the function annotate_install() following the standard Drupal conventionof naming hook functions by replacing the word “hook” with the name of the module. In the hook_install function,I’ll check to seeif the field exists using the Field API, and if it doesn’t, I’ll create the annotation field.
<?php
/**
* Implements hook_install()
*/
function annotate_install() {
// Check to see if annotation field exists.
$field = field_info_field('annotation');
// if the annotation field does not exist then create it if (empty($field)) {
$field = array(
'field_name' => 'annotation', 'type' => 'text_with_summary', 'entity_types' => array('node'), 'translatable' => TRUE,
);
$field = field_create_field($field);
}
}
The next step is to create the uninstall function using hook_uninstall. I’ll create a function named annotate_uninstall and will usethe watchdog function to log a message that tells the site administrator that the module was uninstalled. I will then use thenode_get_types() API function to gather a list of all content types that exist on the site and will loop through the list of types, looking to seewhether the annotation field exists on that content type. If so, I’ll remove it. Finally I’ll delete the annotation fielditself.
/**
* Implements hook_uninstall()
*/
function annotate_uninstall() {
watchdog("Annotate Module", "Uninstalling module and deleting fields");
$types = node_type_get_types();
foreach($types as $type) { annotate_delete_annotation($type);
}
$field = field_info_field('annotation'); if ($field) {
field_delete_field('annotation');}
}
function annotate_delete_annotation($type) {
$instance = field_info_instance('node', 'annotation', $type->type); if ($instance) {field_delete_instance($instance);}}
The last step in the process is to update the .module file to include a check to see whether the person viewing a node is the author of that node. If the person is not the author, then we want to hide the annotation from that user. I’ll take a simple approachof using hook_node_load(), the hook that is called when a node is being loaded. In the hook_node_load() function, I’ll check to see
whether the person viewing the node is the author. If the user is not the author, I’ll hide the annotation by unsetting it.
/**
* Implements hook_node_load()
*/
function annotate_node_load($nodes, $types) {
global $user;
// Check to see if the person viewing the node is the author. If not then
// hide the annotation.
foreach ($nodes as $node) {
if ($user->uid != $node->uid) { unset($node->annotation);}}}
Save the files you have created (.info, .install, .admin.inc, .module), and click the Modules link in the administrators menu at the
top of the page. Your module should be listed in a group titled Pro Drupal Development (if not, double-check the syntax in your
annotate.info and annotate.module files; make sure they are in the sites/all/modules/custom directory). Go ahead and enable
your new module.
Now that the annotate module is enabled, navigating to admin/config/annotate/settings should show us the configuration form for annotate.module (see Figure 2-1).
Figure 2-1. The configuration form for annotate.module is generated for us.
In only a few lines of code, we now have a functional configuration form for our module that will automatically save and
remember our settings! This gives you a feeling of the power you can leverage with Drupal.
Let’s test the process by first enabling annotations for all content types. Check all of the boxes on the configuration settings page
and click the “Save configuration” button. Next create a new basic page node, and scroll down until you see the Annotation field (see Figure 2-2).
Figure 2-2. The annotation form as it appears on a Drupal web page
Create a new node by entering values in the title, body, and annotation field. When you’re finished, click the save button, and youshould see results similar to Figure 2-3.
Figure 2-3. A node that has an annotation
Since we didn’t implicitly perform any database operations, you might be wondering where Drupal stored and retrieved the
value for our annotation field. The Field API handles all of the behind the- scenes work of creating the table to holdthe
value, plusstoring and retrieving the value on node save and node load. When you call the Field API’s field_create_field()
function, it handles the creation of a table in the Drupal database using a standard naming convention of field_data
<fieldname>. In the case of our annotations field, the name of the table is field_data_annotations. We’ll cover additional
details about the Field API in Chapter 4.
Defining Your Own Administration Section
Drupal has several categories of administrative settings such as content management and user management that appear on theConfiguration page. If your module needs a category of its own, you can create that category easily. In this example, we
created a newcategory called “Node annotation.” To do so, we used the module’s menu hook to define the new category:
/**
* Implementation of hook_menu().
*/
function annotate_menu() {
$items['admin/config/annotate'] = array( 'title' => 'Node annotation','description' => 'Adjust node annotation options.', 'position'
=> 'right','weight' => -5,'page callback' => 'system_admin_menu_block_page','access arguments' => array('administer site
configuration'), 'file' => 'system.admin.inc','file path' => drupal_get_path('module', 'system'),);
$items['admin/config/annotate/settings'] = array( 'title' => 'Annotation settings','description' => 'Change how annotationsbehave.','page callback' => 'drupal_get_form','page arguments' => array('annotate_admin_settings'),'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM,'file' => 'annotate.admin.inc',);
return $items;
}
The category on the Configuration page with our module’s setting link in it is shown in Figure 2-4.
Figure 2-4. The link to the annotation module settings now appears as a separate category.
If you ever modify code in the menu hook, you’ll need to clear the menu cache. You can do this by truncating the cache_menu table or by clicking the “Rebuild menus” link that the Drupal development module (devel.module) provides or by
using the“Clear cached data” button by visiting the Configuration page and clicking the Performance link.
We were able to establish our category in two steps. First, we added a menu item that describes the category header. This menu item has a unique path (admin/config/annotate). We declare that it should be placed in the right column with a
weight of -5,because this places it just above the “Web Services” category, which is handiest for the screenshot shown in
Figure 2-3.
The second step was to tell Drupal to nest the actual link to annotation settings inside the “Node annotation” category. We did this by setting the path of our original menu item to admin/config/ annotate/settings. When Drupal rebuilds the menu
tree, itlooks at the paths to establish relationships among parent and child items and determines that, because admin/config/annotate/settings is a child of admin/config/annotate, it should be displayed as such.
Drupal loads only the files that are necessary to complete a request. This saves on memory usage. Because our page call back points to a function that is outside the scope of our module (i.e., the function system_admin_menu_block_page() in
system.module), I need to tell Drupal to load the file modules/system/system.admin.inc instead of trying to load sites/all/modules/custom/annotate/ system.admin.inc. We did that by telling Drupal to get the path of the system module and put the result in the file path key of our menu item.
Of course, this is a contrived example, and in real life, you should have a good reason to create a new category to avoid confusing the administrator (often yourself!) with too many categories.
Presenting a Settings Form to the User
In the annotate module, we gave the administrator the ability to choose which node types would support annotation(see Figure 2-1). Let’s delve into how this works.
When a site administrator wants to change the settings for the annotate module, we want to display a form so the
administrator can select from the options we present. In our menu item, we set the page callback to point to the
drupal_get_form()function and set the page arguments to be an array containing annotate_admin_settings.
drupal_get_form('annotate_admin_settings') will be executed, which essentially tells Drupal to build the form
defined by the function annotate_admin_settings().
Let’s take a look at the function defining the form, which defines a check box for node types (see Figure 2- 1),
and add two more options. The function is in sites/all/modules/custom/annotate/ annotate.admin.inc:
/**
* Form builder. Configure annotations.
*
* @ingroup forms
* @see system_settings_form().
*/
function annotate_admin_settings() {
// Get an array of node types with internal names as keys and
// "friendly names" as values. E.g.,
// array('page' => 'Basic Page', 'article' => 'Articles')
$types = node_type_get_types(); foreach($types as $node_type) {
$options[$node_type->type] = $node_type->name;}
$form['annotate_node_types'] = array( '#type' => 'checkboxes','#title' => t('Users may annotate these
content types'), '#options' => $options,'#default_value' => variable_get('annotate_node_types', array
('page')), '#description' => t('A text field will be available on these content types to make user-
specific notes.'),);
$form['annotate_deletion'] = array( '#type' => 'radios','#title' => t('Annotations will be deleted'), '#description'
=> t('Select a method for deleting annotations.'), '#options' => array(t('Never'),t('Randomly'), t('After 30 days')),
'#default_value' => variable_get('annotate_deletion', 0) // Default to Never);
$form['annotate_limit_per_node'] = array( '#type' => 'textfield','#title' => t('Annotations per node'),'#description' => t('Enter the maximum number of annotations allowed per node (0 for no limit).'), '#default_value' => variable_get('annotate_limit_per_node', 1),'#size' => 3);
$form['#submit'][] = 'annotate_admin_settings_submit'; return system_settings_form($form);}
We add a radio button to choose when annotations should be deleted and a text entry field to limit the number of annotations
allowed on a node (implementation of these enhancements in the module is left as an exercise for you). Rather than managing theprocessing of our own form, we call system_settings_form() to let the system module add some buttons to the form and
manage validation and submission of the form. Figure 2-5 shows what the options form looks like now.
Không có nhận xét nào:
Đăng nhận xét