Thứ Năm, 22 tháng 5, 2014

Creating the Files [Writing a Module]

The first thing we are going to do is to choose a name for the module. The name “annotate” seems appropriatit’s short
and  descriptive. Next, I need a plac 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, thbasic  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 an 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 online 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 youll 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
white space in files (see http:// drupal.org/coding-standards#phptags).

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 twmenu 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 chec boxes (see Figure 2- 1). Such a page is an administrative page,  and  the cod 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  actas 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 name(those that will be shown to
the user)  contained in the name attribute of the objectDrupal’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 its 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 value 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 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 title 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 modules 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 clickin 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 functiosystem_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.
That means that when you go to http://example.com/?q=admin/config/annotate/settingsthe call
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),
an 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  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