One of the first questions asked by those new to Drupal development is, “What is a node?” A node is a piece of content.
Drupal assigns each piece of content an ID number called a node ID (abbreviated in the code as $nid). Generally each
node has a title also,to allow an administrator to view a list of nodes by title.
There are many different kinds of nodes, or node types. Some common node types are “blog entry,” “poll,” and “forum.”Often the term content type is used as a synonym for node type, although a node type is really a more abstract concept and can bethought of as a derivation of a base node, as Figure 7-1 represents.
The beauty of all content types being nodes is that they’re based on the same underlying data structure. For developers, this means that for many operations you can treat all content the same programmatically. It’s easy to perform batch operations onnodes, and you also get a lot of functionality for custom content types out of the box. Searching, creating, editing, and
managing content are supported natively by Drupal because of the underlying node data structure and behavior. This uniformity is apparent to end users, too. The forms for creating,editing, and deleting nodes have a similar look and feel, leading to a consistent and thus easier-to-use interface.
Figure 7-1. Node types are derived from a basic node and may add fields.
Node types extend the base node, usually by adding their own data attributes. A node of type poll stores voting options such as the
duration of the poll, whether the poll is currently active, and whether the user is allowed to vote. A node of type forum loads the
taxonomy term for each node so it will know where it fits in the forums defined by the administrator. blog nodes, on the other hand,don’t add any other data. Instead, they just add different views into the data by creating blogs for each user and RSS feeds for
each blog. All nodes have the following attributes stored within the node and node_revisions database table:
• nid: A unique ID for the node.
• vid: A unique revision ID for the node, needed because Drupal can store content revisions for each node. The vid is unique
across all nodes and node revisions.
• type: Every node has a node type—for example, blog, story, article, image, and so on.
• language: The language for the node. Out of the box, this column is empty, indicating language-neutral nodes.
• title: A short 255-character string used as the node’s title, unless the node type declares that it does not have a title, indicated by
a 0 in the has_title field of the node_type table.
• uid: The user ID of the author. By default, nodes have a single author.
• status: A value of 0 means unpublished; that is, content is hidden from those who don’t have the “administer nodes” permission.
A value of 1 means the node is published and the content is visible to those users with the “access content” permission.The display of a published node may be vetoed by Drupal’s node- level access control system (see the “Limiting Access to a Node Type with
hook_access()” and “Restricting Access to Nodes” sections later in this chapter). A published nodewill be indexed by the search module if the search module is enabled.
• created: A Unix timestamp of when the node was created.
• changed: A Unix timestamp of when the node was last modified. If you’re using the node revisions system, the same value is
used for the timestamp field in the node_revisions table.
• comment: An integer field describing the status of the node’s comments, with three possible values:
• 0: Comments have been disabled for the current node. This is the default value for existing nodes when the comment module is
disabled. In the user interface of the node editing form’s “Comment settings” section, this is referred to asDisabled.
• 1: No more comments are allowed for the current node. In the user interface of the node editing form’s “Comment settings”
section, this is referred to as “Read only.”
• 2: Comments can be viewed, and users can create new comments. Controlling who can create comments and how comments appear visually is the responsibility of the comment module. In the user interface of the node editing form’s “Comment settings”
section, this is referred to as Read/Write.
• promote: An integer field to determine whether to show the node on the front page, with two values:
• 1: Promoted to the front page. The node is promoted to the default front page of your site. The node will still appear at its normal page, for example, http://example.com/?q=node/3. It should be noted here that, because you can changewhich page is considered the front page of your site at Configuration -> Site information, “front page” can be a misnomer. It’s actually more accurate to say the http://example.com/?q=node page will contain all nodes whose promotefield is 1. The URL http://example.com/?q=node is the front page by default.
• sticky: When Drupal displays a listing of nodes on a page, the default behavior is to list first those nodes marked as sticky, and then list the remaining unsticky nodes in the list by date created. In other words, sticky nodes stick to the top ofnode listings. A value of 1 means sticky, and a value of 0 means, well, unsticky. You can
have multiple sticky nodes within the same list.
• tnid: When a node serves as the translated version of another node, the nid of the source node being translated is stored here. For example, if node 3 is in English and node 5 is the same content as node 3 but in Swedish, the tnid field of node 5will be 3.
• translate: A value of 1 indicates that the translation needs to be updated; a value of 0 means translation is up to date.
If you’re using the node revisions system, Drupal will create a revision of the content as well as track who made the last edit.
Not Everything Is a Node
Users, blocks, and comments are not nodes. Each of these specialized data structures has its own hook system geared toward its intended purpose. Nodes (usually) have title and body content, and a data structure representing a user doesn’t need that. Rather,users need an e-mail address, a username, and a safe way to store passwords. Blocks are lightweight storage solutions for smaller pieces
of content such as menu navigation, a search box, a list of recent comments, and so on. Comments aren’t nodes either, which keeps
them lightweight as well. It’s quite possible to have 100 or more comments per page, and if each of those comments had to go
through the node hook system when being loaded, that would be a tremendous performance hit.
In the past, there have been great debates about whether users or comments should be nodes, and some contributed modules actually implement this. Be warned that raising this argument is like shouting “Emacs is better!” at a programming convention.
Creating a Node Module
Traditionally, when you wanted to create a new content type in Drupal, you would write a node module that took responsibility for providing the new and interesting things your content type needed. We say “traditionally” because recent advents within theDrupal framework allow you to create content types within the administrative interface and extend their functionality with contributed modules rather than writing a node module from scratch. I’ll cover both solutions within this chapter.
I’ll write a node module that lets users add a job posting to a site. A job posting node will include a title, a body where the details of the job posting will be entered, and a field where the user can enter the name of the company. For the job posting title and a body,I’ll use the built-in node title and body that are standard with all Drupal nodes. I’ll need to add a new custom field for the
company’s name. I’ll start by creating a folder named job_post in your sites/all/modules/custom directory.
Creating the .install File
The install file for the job post module performs all of the set-up operations for things like defining the node type,
creating the fields that make up our new node type, and handling the uninstall process when an administrator uninstalls the module
<?php
/**
* @file
* Install file for Job Post module.
*/
/**
* Implements hook_install().
* - Add the body field.
* - Configure the body field.
* - Create the company name field.
*/
function job_post_install() { node_types_rebuild();
$types = node_type_get_types();
// add the body field to the node type node_add_body_field($types['job_post']);
// Load the instance definition for our content type's body
$body_instance = field_info_instance('node', 'body', 'job_post');
// Configure the body field
$body_instance['type'] = 'text_summary_or_trimmed';
// Save our changes to the body field instance.
field_update_instance($body_instance);
// Create all the fields we are adding to our content type.
foreach (_job_post_installed_fields() as $field) { field_create_field($field);
}
// Create all the instances for our fields.
foreach (_job_post_installed_instances() as $instance) {
$instance['entity_type'] = 'node';
$instance['bundle'] = 'job_post'; field_create_instance($instance);
}}
/**
* Return a structured array defining the fields created by this content type.
* For the job post module there is only one additional field – the company name
* Other fields could be added by defining them in this function as additional elements
* in the array below
*/
function _job_post_installed_fields() {
$t = get_t(); return array('job_post_company' => array( 'field_name' => 'job_post_company','label' => $t('Company posting the job listing'),'type'=> 'text',),);}
/**
* Return a structured array defining the field instances associated with this content type.
*/
function _job_post_installed_instances() {
$t = get_t(); return array('job_post_company' => array('field_name' => 'job_post_company', 'type' => 'text', 'label' => $t('Company posting the job listing'), 'widget'=> array('type'=> 'text_textfield',), 'display' => array( 'example_node_list' => array('label'=> $t('Company posting the job listing'), 'type' => 'text',),),),);}
/**
* Implements hook_uninstall().
*/
function job_post_uninstall() {
// Gather all the example content that might have been created while this
// module was enabled.
$sql = 'SELECT nid FROM {node} n WHERE n.type = :type';
$result = db_query($sql, array(':type' => 'job_post'));
$nids = array();
foreach ($result as $row) {
$nids[] = $row->nid;}
// Delete all the nodes at once node_delete_multiple($nids);
// Loop over each of the fields defined by this module and delete
// all instances of the field, their data, and the field itself.
foreach (array_keys(_job_post_installed_fields()) as $field) { field_delete_field($field);}
// Loop over any remaining field instances attached to the job_post
// content type (such as the body field) and delete them individually.
$instances = field_info_instances('node', 'job_post'); foreach ($instances as $instance_name => $instance) {
field_delete_instance($instance);}
// Delete our content type node_type_delete('job_post');
// Purge all field infromation field_purge_batch(1000);}
Creating the .info File
Let’s also create the job_post.info file and add it to the job post folder.
name = Job Post
description = A job posting content type package = Pro Drupal Development
core = 7.x
files[] = job_post.install files[] = job_post.module
Creating the .module File
Last, you need the module file itself. Create a file named job_post.module, and place it inside sites/all/modules/custom/job_
posting. After you’ve completed the module, you can enable the module on the module listings page (Modules). You begin with
theopening PHP tag and Doxygen comments.
<?php
/**
* @file
* This module provides a node type called job post
*/
Providing Information About Our Node Type
Now you’re ready to add hooks to job_post.module. The first hook you’ll want to implement is hook_node_info(). Drupal
calls this hook when it’s discovering which node types are available. You’ll provide some metadata about your custom node.
/**
* Implements hook_node_info() to provide our job_post type.
*/
function job_post_node_info() { return array('job_post' => array('name' => t('Job Post'), 'base' => 'job_post','description' => t('Use this content type to post a job.'), 'has_title' => TRUE,'title_label' => t('Job Title'), 'help' => t('Enter the
job title, job description, and the name of the company that posted the job'),),);}
A single module can define multiple node types, so the return value should be an array. Here’s the breakdown of metadata values
that may be provided in the node_info() hook:
"name": The human-readable name of the node type. Required.
"base": The base string used to construct callbacks corresponding to this node type (i.e., if base is defined as example_foo, then
example_foo_insert will be called when inserting a node of that type). This string is usually the name ofthe module, but not always.Required.
"description": A brief description of the node type. Required.
"help": Help information shown to the user when creating a node of this type. Optional (defaults to '').
"has_title": Boolean indicating whether this node type has a title field. Optional (defaults to TRUE).
"title_label": The label for the title field of this content type. Optional (defaults to “Title”).
"locked": Boolean indicating whether the administrator can change the machine name of this type. FALSE = changeable (not locked), TRUE = unchangeable (locked). Optional (defaults to TRUE).
Modifying the Menu Callback
Having a link on the “Create content” page isn’t necessary for implementing hook_menu(). Drupal will automatically
discover your new content type and add its entry to the http://example.com/?q=node/add page, as shown in Figure 7-2.
The name and description are taken from the values you defined in job_post_node_info().
If you do not wish to have the direct link added, you could remove it by using hook_menu_alter(). For example, the following
code would remove the page for anyone who does not have “administer nodes” permission.
/**
* Implements hook_menu_alter().
*/
function job_post_menu_alter(&$callbacks) {
// If the user does not have 'administer nodes' permission,
// disable the job_post menu item by setting its access callback to FALSE.
if (!user_access('administer nodes')) {
$callbacks['node/add/job_post']['access callback'] = FALSE;
// Must unset access arguments or Drupal will use user_access()
// as a default access callback.
unset($callbacks['node/add/job_post']['access arguments']);}}
Defining Node-Type–Specific Permissions with hook_permission()
Typically the permissions for module-defined node types include the ability to create a node of that type, edit a node you have
created, and edit any node of that type. These are defined in hook_ permission() as create job_post, edit own job_post, edit any
job_post, and so on. You’ve yet to define these permissions within your module. Let’s create them now using hook_permission():
/**
* Implements hook_permission().
*/
function job_post_permission() { return array('create job post' => array('title' => t('Create a job post'), 'description'
=> t('Create a job post'),),'edit own job post' => array('title' => t('Edit own job post'),'description' => t('Edit your own job posting'),),'edit any job post' => array('title' => t('Edit any job post'), 'description' => t('Edit any job posting'),),'delete own job post' => array('title' => t('Delete own job post'), 'description' => t('Delete own job posting'),),'delete any job post' => array('title' => t('Delete any job post'), 'description' => t('Delete any job posting'),),);}
Now if you navigate over to People and click the Permissions tab, the new permissions you defined are there and ready to be assigned to user roles.
Limiting Access to a Node Type with hook node_access()
You defined permissions in hook_permission(), but how are they enforced? Node modules can limit access to the node types
they define using hook_node_access(). The superuser (user ID 1) will always bypass any access check, so this hook isn’t called
in that case. If this hook isn’t defined for your node type, all access checks will fail, so only the superuser and those with
“administer nodes” permissions will be able to create, edit, or delete content of that type.
/**
* Implements hook_node_access().
*/
function job_node_access($op, $node, $account) {
$is_author = $account->uid == $node->uid; switch ($op) {case 'create':
// Allow if user's role has 'create joke' permission.
if (user_access('create job', $account)) { return NODE_ACCESS_ALLOW;}
case 'update':
// Allow if user's role has 'edit own joke' permission and user is
// the author; or if the user's role has 'edit any joke' permission.
if (user_access('edit own job', $account) && $is_author || user_access('edit any job', $account)) {
return NODE_ACCESS_ALLOW;}
case 'delete':
// Allow if user's role has 'delete own joke' permission and user is
// the author; or if the user's role has 'delete any joke' permission.
if (user_access('delete own job', $account) && $is_author || user_access('delete any job', $account)) {
return NODE_ACCESS_ALLOW;
}}}
The preceding function allows users to create a job post node if their role has the “create job post” permission. They can also
update a job post if their role has the “edit own job post” permission and they’re the node author, or if they have the “editany
job post” permission. Those with “delete own job post” permission can delete their own job post, and those with “delete any jobpost” permission can delete any node of type job post.
One other $op value that’s passed into hook_node_access() is view, allowing you to control who views this node. A word of warning, however: hook_node_access() is called only for single node view pages. hook_node_access() will not preventsomeone from viewing a node when it’s in teaser view, such as a multinode listing page. You could get creative with other hooks and manipulate the value of $node->teaser directly to overcome this, but that’s a little hackish. A better solution is to use
hook_node_grants(), which we’ll discuss shortly.
Customizing the Node Form for Our Node Type
So far, you’ve got the metadata defined for your new node type and the access permissions defined. Next, you need to build the node form so that users can enter a job. You do that by implementing hook_form(). Drupal provides a standard node form thatincludes the title, body, and any optional fields that you have defined. For the job post content type, the standard form is more than adequate, so I’ll use it to render the add/edit form.
/**
* Implement hook_form() with the standard default form.
*/
function job_post_form($node, $form_state) { return node_content_form($node, $form_state);}
As the site administrator, if you’ve enabled your module, you can now navigate to Add content -> Job Post
and view the newly created form (see Figure 7-3).
Figure 7-3. The form for submission of a job post
When you’re working with a node form and not a generic form, the node module handles validating and storing all the default
fields it knows about within the node form (such as the title and body fields) and provides you, the developer, with hooks to
validate and store your custom fields. We’ll cover those next.
Validating Fields with hook_validate()
When a node of your node type is submitted, your module will be called via hook_validate(). Thus, when the user submits the
form to create or edit a job post, the invocation of hook_validate() will look for the function job_post_validate() so that you can
validate the input in your custom field(s). You can make changes to the data after submission—see form_set_value(). Errors should be set with form_set_error(), as follows:
/**
* Implements hook_validate().
*/
function job_post_validate($node) {
// Enforce a minimum character count of 2 on company names.
if (isset($node->job_post_company) && strlen($node->job_post_company['und'][0]['value']) < 2) { form_set_error('job_post_company',t('The company name is too short. It must be atleast 2 characters.'),}}
$limit_validation_errors = NULL);
Notice that you already defined a minimum word count for the body field in hook_node_info(), and Drupal will validate that for you automatically. However, the punchline field is an extra field you added to the node type form, so you are responsible for
validating (and loading and saving) it.
Saving Our Data with hook_insert()
When a new node is saved, hook_insert() is called. This is the place to handle any custom processing of the node’s content before the node is saved. This hook is called only for the module that is defined in the node type metadata. This information is defined in
the base key of hook_node_info() (see the “Providing Information About Our Node Type” section). For example, if the base keyis job_post, then job_post_insert() is called. If you enabled the book module and created a new node of type book,job_post_
insert() would not be called; book_insert() would be called instead because book.module defines its node type with a base key ofbook. Here’s the hook_insert() function for job_post.module. I’ll create a log entry in the watchdog table every time a new job
posting node is created.
/**
* Implements hook_insert().
*/
function job_post_insert($node) {
// log details of the job posting to watchdog watchdog('job post', 'A new job post titled: '.$node->title.' for company: '.$node->job_post_company['und'][0]['value'].' was added by UID: '.$node->uid, $variables = array(), WATCHDOG_NOTICE, $link = 'node/'.$node->nid);}
Keeping Data Current with hook_update()
The update() hook is called when a node has been edited and the core node data has already been written to the database. This is
the place to write database updates for related tables. Like hook_insert(), this hook is called only for the current node type. For
example, if the node type’s module key in hook_node_info() is job_post, then job_post_update() is called.
/**
* Implements hook_update().
*/
function job_post_update($node) {
// log details of the job posting to watchdog
watchdog('job post', 'A job post titled: '.$node->title.' for company: '.
$node->job_post_company['und'][0]['value'].
' was updated by UID: '.$node->uid, $variables = array(), WATCHDOG_NOTICE, $link = 'node/'.$node->nid);}
Cleaning Up with hook_delete()
Just after a node is deleted from the database, Drupal lets modules know what has happened via hook_delete(). This hook is typically used to delete related information from the database. This hook is called only for the current node type being deleted. If the
node type’s base key in hook_node_info() is job_post, then job_post_delete() is called.
/**
* Implements hook_delete().
*/
function job_post_delete($node) {
// log details of the job posting to watchdog
watchdog('job post', 'A job post titled: '.$node->title.' for company: '.
$node->job_post_company['und'][0]['value'].
' was deleted by UID: '.$node->uid, $variables = array(), WATCHDOG_NOTICE, $link = 'node/'.$node->nid);}
Modifying Nodes of Our Type with hook_load()
Another hook you need for your job_post module is the ability to add custom node attributes into the node object as it’s
constructed. We need to inject the job post sponsor into the node loading process so it’s available to other modules and the theme layer.For that you use hook_load(). This hook is called just after the core node object has been built and is called only for the
current node type being loaded. If the node type’s module key in hook_node_info() is job_post, then job_post_load() is called. In
the example, I will insert anode attribute called sponsor and will assign a value that can then be used else where.
/**
* Implements hook_load().
*/
function job_post_load($nodes) {
// Add a new element to the node at load time for storing the
// job posting sponsor information foreach ($nodes as $node) {
$node->sponsor = "ACME Career Services, Your Source for Drupal Jobs";
}
return $node;}
Using hook_view()
Now you have a complete system to enter and edit job posts. However, your sponsors will be frustrated, because although sponsor
information has been added previously through hook_load, you haven’t provided a way for the sponsor information to be displayed when viewing a job post. I’ll do that now with hook_view():
/**
* Implement hook_view().
*/
function job_post_view($node, $view_mode) {
// Add and theme the sponsor so it appears when the job post is displayed if ($view_mode == 'full') {
$node->content['sponsor'] = array(
'#markup' => theme('sponsor', array('sponsor' => $node->sponsor, ‘sponsor_id’ => $node_nid)),'#weight' => 100,);}
return $node;}
I’ve broken the formatting of the sponsor into a separate theme function so that it can be easily overridden. This is a courtesy to the overworked system administrators who will be using your module but who want to customize the look and feel of theoutput. To
enable this capability, I’ll create a hook_theme() function that defines how the module will handle theming the new sponsor field. In
the hook_theme function, I’ll define the variables associated with the sponsor field and the template file that will benused to define
how the sponsor information will be rendered as part of the node.
/**
* Implements hook_theme().
*/
function job_post_theme() {
// define the variables and template associated with the sponsor field
// The sponsor will contain the name of the sponsor and the sponsor_id
// will be used to create a unique CSS ID
return array('sponsor' => array('variables' => array('sponsor' => NULL, 'sponsor_id' => NULL), 'template' =>'sponsor',),);}
The last step in the process is to create the template file for displaying sponsor information. In the hook_theme() function, I assigned the value sponsor to the template file attribute—so I’ll need to create a sponsor.tpl.php file in my module directory.
The contentof that file is as follows:
<?php
/**
* @file
* Default theme implementation for rendering job post sponsor information
*
* Available variables:
* - $sponsor_id: the node ID asociated with the job posting
* - $sponsor: the name of the job post sponsor
*/
?>
<div id="sponsor-<?php print $sponsor_id ?>" class="sponsor">
<div class="sponsor-title">
<h2>Sponsored by</h2>
</div>
<div class="sponsored-by-message">
This job posting was sponsored by: <?php print $sponsor; ?>
</div>
</div>
You will need to clear the cached theme registry so that Drupal will look at your theme hook. You can clear the cache using devel.module or by simply visiting the Modules page. You should now have a fully functioning job post entry and viewingsystem. Go ahead and enter some job posts and try things out. You should see your job post in a plain and simple format, as in
Figures 7-4 and 7-5.
Figure 7-4. Simple theme of job post node
Figure 7-5. The sponsor is not added when the node is shown in teaser view.
Manipulating Nodes That Are Not Our Type with hook_node_xxxxx()
The preceding hooks are invoked only based on the base key of the module’s hook_node_info() implementation. When Drupal
sees a blog node type, blog_load() is called. What if you want to add some information to every node, regardless of its type?The hooks we’ve reviewed so far aren’t going to cut it; for that, we need an exceptionally powerful set of hooks.
The node_xxxx hooks create an opportunity for modules to react to the different operations during the life cycle of any node. The node_xxxx hooks are usually called by node.module just after the node- type–specific callback is invoked. Here’s a list of the
primary node_xxxx hook functions:
hook_node_insert($node): Responds to creation of a new node.
hook_node_load($node, $types): Acts on nodes being loaded from the database. $nodes is a keyed array of nodes being loaded
where the key is the node ID, $types is an array of node types being loaded.
hook_node_update($node): Responds to updates to a node. hook_node_delete($node): Responds to node deletion. hook_node_view$node, $view_mode): Acts on a node that is being rendered where $view_mode defines what mode the node is being displayed in—e.g., full or teaser.
hook_node_prepare($node): Acts on a node that is about to be shown in the add/edit form.
hook_node_presave($node): Acts on a node that is being inserted or updated.
hook_node_access($node, $op, $account): Controls access to a node where $op is the type of operation being performed (e.g.,
insert, update, view, delete) and $account is the user account of the person performing the operation.
hook_node_grants_alter(&$grants, $account, $op): Alters user access grants when trying to view, edit, or delete a node.
The order in which hooks are fired when displaying a node page such as http://example.com/?q=node/3 is shown in Figure 7-6.
Figure 7-6. Path of execution for displaying a node page
How Nodes Are Stored
Nodes live in the database as separate parts. The node table contains most of the metadata describing the node. The node_revisions table contains the node’s body and teaser, along with revision-specific information. And as you’ve seen in the
job_post.module example, other nodes are free to add data to the node at node load time and store whatever data they want in their own tables. A node object containing the most common attributes is pictured in Figure 7-7. Note that the table
created by the field API to store the job post company is separate from the main node table. Depending on which other
modules are enabled, the node objects in your Drupal installation might contain more or fewer properties.
Không có nhận xét nào:
Đăng nhận xét