Create new content-type for Drupal 5.x
Introduction
Okay, you've read the tutorial on creating modules and know the basics of hooks, blocks and possibly forms. You'd now like to use more of the various Drupal APIs, and create a new node type.
Before creating a new node type, first decide if you really need to create a new one: if you can use CCK and Views to gather and manipulate your data, you may not need this tutorial. However, if you need to manipulate your data in a different way, or have multiple data dependencies that can't be done with CCK, or if you just want to learn more about Drupal internals, then this tutorial may just be for you.
Note that you can probably still use CCK+Views if the custom parts of your node just includes variations of the built-in commenting system, paths, publishing information and other built-in Drupal goodies.
A simple node-type (or content type) such as "page" or "story" is the most elemental aspect of a Drupal site. It's nothing more than a Title, Body, and an optional Teaser. You can easily build your own node-type programmatically by following these step-by-step directions. Learn this, and you will begin to understand how flexible Drupal can be with it's content.
Why would you need to know this?
Drupal 5.x now allows you to easily create these "simple" node-types using the admin interface, doesn't it? The answer is a resounding YES, and everyone is absolutely thrilled with that. Thanks, Drupal developers!!!
Then CCK came along, and made our lives even easier! It does not teach you the basics of module programming, however, and that's why this tutorial was written.
There is a great example of creating your own node-type in the API at node_example. There is also a great tutorial at Creating Modules 5.x Tutorial.
In previous examples "How to create your own simple node type (from story node) (4.7) and (4.6) the "story.module" file was copied as a starting point, but that module no longer exists because of how Drupal 5.x handles node-types (content types), so this example will require you to start with a new file. This example does use the example in the API node_example as the code reference point (and you should definately read that).
There are no database changes or hacking of code, the result will be a new module that you will need to upload into its own module directory.
What you need
- means to download/upload files to your modules directory
- a text editor
- a little php knowledge is helpful, but not required
- some information about your node type
- a TECHNICAL-NAME for your node type - limit 32 characters - don't use the name of an existing module - no spaces, numbers or punctuation unless you know what you're doing
- a USER-FRIENDLY-NAME for your node type (and a plural version of this name) (this is how your node type will appear in most places on your site) - spaces and numbers are ok but again no punctuation unless you know what you're doing, note: you can use the same name as your TECHNICAL-NAME above
- a MODULE-DESCRIPTION - a short description of your node type that will appear on the admin->modules page - don't use quotes or apostrophes unless you know what you are doing
- a CREATE-CONTENT-DESCRIPTION - a short description of your node type that will appear on the create content page - don't use quotes or apostrophes unless you know what you are doing
- a HELP-DESCRIPTION - a short description of your node type that will appear on the help page - don't use quotes or apostrophes unless you know what you are doing
As an example... say you want a simple press release node type.
- TECHNICAL-NAME = "press_release" (note the underscore)
- USER-FRIENDLY-NAME = "Press Release"
- USER-FRIENDLY-PLURAL = "Press Releases"
- MODULE-DESCRIPTION = "Enables the creation of press releases."
- CREATE-CONTENT-DESCRIPTION = "Create a press release."
- ADMIN-HELP-TEXT = "This module was created by [your name here]."
I specifically use an example that has two words: "Press Release" - this provides a slightly different TECHNICAL-NAME: "press_release" (note the underscore) and USER-FRIENDLY-NAME: "Press Release" (no underscore) so that it is clear in the code changes where the two are used, but many node-types that exist (or that you may want to create) use the same value.
In the following, bolded text is intended to show where the code changes (so PHP formatting is not used). Quotes and apostrophes in the code are important, please place close attention. Where you see ----- this is intended to show where the code starts and ends and is not needed in your code.
There is a huge change that came about with drupal 5.x -- for your module to work you need 2 files. In earlier versions only one file was needed.
The first file is a .info file (say it out loud: "dot info file"). This is the "new" file required by drupal 5.x. This file does not appear to be addressed in the API node_example, in a place I could find. But, you can read more about .info files at Writing .info files (Drupal 5.x) or in the Creating Modules 5.x Tutorial series in section 02. Telling Drupal about your module.
This is not a PHP file so you don't start with the <?php
The general format is:
; $Id$
name = Module Name
description = "A description of what your module does."NOTE: Without this file, your module will not show up in the module listing at admin/build/modules
This breaks down to:
-----
; $Id$
name = USER-FRIENDLY-NAME
description = "MODULE-DESCRIPTION"
-----
In our example:
-----
; $Id$
name = Press Release
description = "Enables the creation of press releases."
-----
Yes, you only need those 3 lines (minimally)
Save this file as "TECHNICAL-NAME.info" (in our example: "press_release.info")
The second file needed is the .module file (say it out loud "dot module file"). This is the file that does all the heavy lifting.
In the node_example there are a number of hooks listed and exampled. The node_example is one where the node-type has its own additional fields. This how-to does not address that need, but is a only a primer to get you started. If you need additional fields, you will want to learn about those hooks and node_example is a great place to start that.
The .module file is where all the "hooks" appear. Hooks are pretty easy to understand.
This note from the Creating Modules 5.x Tutorial series in section 01. Getting started provides a nice way of describing them.
All functions in your module that will be used by Drupal are named {modulename}_{hook}, where "hook" is a pre-defined function name suffix. Drupal will call these functions to get specific data, so having these well-defined names means Drupal knows where to look.
The following hooks are outlined in node_example in the API
- these hooks should be implemented in every node-type module. This example will implement these 4 hooks.
- hook_node_info()
- hook_perm()
- hook_access()
- hook_form()
- these hooks are needed if you have additional fields that your node-type creates (note you can use CCK with your node-type to add additional fields). This example will not implement these hooks.
- hook_insert()
- hook_update()
- hook_delete()
- hook_validate()
- hook_nodeapi()
- hook_view()
- hook_load()
- this hook does not appear in node_example but is a good idea to use. This example will implement this hook.
- hook_help()
Note: it is considered a good practice to include the following comment before each hook
/**
* Implementation of hook_{hook name here}().
*/but it is not included in the following.
So let's get started. This is a PHP file so the very first line should be <?php
Implementation of hook_node_info(). This function replaces hook_node_name() and hook_node_types() from 4.6. Drupal 5 expands this hook significantly.
This is a required hook and can define a lot of things about the node-type, minimally the following is required.
From node_example:
<?php
function node_example_node_info() {
return array(
'node_example' => array(
'name' => t('example node'),
'module' => 'node_example',
'description' => t("This is an example node type with a few fields."),
)
);
}
?>This breaks down to:
-----
function TECHNICAL-NAME_node_info() {
return array(
'TECHNICAL-NAME' => array(
'name' => t('USER-FRIENDLY-NAME'),
'module' => 'TECHNICAL-NAME',
'description' => t("CREATE-CONTENT-DESCRIPTION"),
)
);
}
-----
In our example:
-----
function press_release_node_info() {
return array(
'press_release' => array(
'name' => t('Press Release'),
'module' => 'press_release',
'description' => t("Create a press release."),
)
);
}
-----
Implementation of hook_perm(). Since we are limiting the ability to create new nodes to certain users, we need to define what those permissions are here. We also define a permission to allow users to edit the nodes they created.
The permissions you define will be referenced by the next hook (hook_access) in the example.
For the curious, this is the same as version 4.7
from node_example:
<?php
function node_example_perm() {
return array('create example node', 'edit own example nodes');
}
?>node_example uses USER-FRIENDLY-PLURAL to define its permissions, but this note from the Creating Modules 5.x Tutorial series in section 03. Telling Drupal who can use your module provides other advice.
Your permission strings must be unique within your module. If they are not, the permissions page will list the same permission multiple times. They should also contain your module name, to avoid name space conflicts with other modules.
The current naming convention is an action verb + modulename.
This example will follow that advice (although using USER-FRIENDLY-PLURAL is probably fine.
This hook then breaks down to:
-----
function TECHNICAL-NAME_perm() {
return array('create TECHNICAL-NAME', 'edit own TECHNICAL-NAME');
}
-----
In our example, this becomes:
-----
function press_release_perm() {
return array('create press_release', 'edit own press_release');
}
-----
Implementation of hook_access(). Node modules may implement node_access() to determine the operations users may perform on nodes. This example uses a very common access pattern.
It is important to note that you should use the same permissions you defined in the hook above
For the curious, this is the same as version 4.7
from node_example:
<?php
function node_example_access($op, $node) {
global $user;
if ($op == 'create') {
// Only users with permission to do so may create this node type.
return user_access('create nameofnodetype');
}
// Users who create a node may edit or delete it later, assuming they have the
// necessary permissions.
if ($op == 'update' || $op == 'delete') {
if (user_access('edit own nameofnodetype') && ($user->uid == $node->uid)) {
return TRUE;
}
}
}
?>Again, we are following the advice to use the TECHNICAL-NAME and not the USER-FRIENDLY-PLURAL
This breaks down to:
-----
function TECHNICAL-NAME_access($op, $node) {
global $user;
if ($op == 'create') {
// Only users with permission to do so may create this node type.
return user_access('create TECHNICAL-NAME');
}
// Users who create a node may edit or delete it later, assuming they have the
// necessary permissions.
if ($op == 'update' || $op == 'delete') {
if (user_access('edit own TECHNICAL-NAME') && ($user->uid == $node->uid)) {
return TRUE;
}
}
}
-----
In our example:
-----
function press_release_access($op, $node) {
global $user;
if ($op == 'create') {
// Only users with permission to do so may create this node type.
return user_access('create press_release');
}
// Users who create a node may edit or delete it later, assuming they have the
// necessary permissions.
if ($op == 'update' || $op == 'delete') {
if (user_access('edit own press_release') && ($user->uid == $node->uid)) {
return TRUE;
}
}
}
-----
Implementation of hook_form(). Now it's time to describe the form for collecting the information specific to this node type. This hook requires us to return an array with a sub array containing information for each element in the form.
Forms are in my mind one of the most difficult aspects of drupal to learn. But because of the amazing way drupal implements forms, in this example we have very little code to change.
For the curious, this has been changed somewhat since 4.7
from node_example:
<?php
function node_example_form(&$node) {
$type = node_get_types('type', $node);
// We need to define form elements for the node's title and body.
$form['title'] = array(
'#type' => 'textfield',
'#title' => check_plain($type->title_label),
'#required' => TRUE,
'#default_value' => $node->title,
'#weight' => -5
);
// We want the body and filter elements to be adjacent. We could try doing
// this by setting their weights, but another module might add elements to the
// form with the same weights and end up between ours. By putting them into a
// sub-array together, we're able force them to be rendered together.
$form['body_filter']['body'] = array(
'#type' => 'textarea',
'#title' => check_plain($type->body_label),
'#default_value' => $node->body,
'#required' => FALSE
);
$form['body_filter']['filter'] = filter_form($node->format);
// NOTE in node_example there is some addition code here not needed for this simple node-type
return $form;
}
?>The only change needed for this function is the function name itself - all the code stays the same
This breaks down to:
-----
function TECHNICAL-NAME_form(&$node) {
(all the code stays the same)
}
-----
In our example, it becomes:
-----
function press_release_form(&$node) {
(all the code stays the same)
}
-----
Implementation of hook_help().
This hook is not included in the node_example - not sure why
For the curious, this is the same as version 4.7 (as far as I can tell), though some of its functionalities are now incorporated in hook_node_info()
from the hook_form() example in the api
<?php
function hook_help($section) {
switch ($section) {
case 'admin/help#block':
return t('<p>Blocks are the boxes visible in the sidebar(s)
of your web site. These are usually generated automatically by
modules (e.g. recent forum topics), but you can also create your
own blocks using either static HTML or dynamic PHP content.</p>');
break;
}
}
?>This breaks down to:
-----
function TECHNICAL-NAME_help($section) {
switch ($section) {
case 'admin/help#TECHNICAL-NAME':
return t('ADMIN-HELP-TEXT');
break;
}
}
-----
This breaks down to:
-----
function press_release_help($section) {
switch ($section) {
case 'admin/help#press_release':
return t('This module was created by [your name here].');
break;
}
}
-----
That's it for the code. Hopefully not too difficult.
Save the file as "TECHNICAL-NAME.module", (in our example: "press_release.module") you're now ready to upload.
Next
Create a directory in the modules directory with your TECHNICAL-NAME (in our example: "press_release")
Upload both files (the .module file and the .info file) to your new directory
Go to your site, administer -> modules, (admin/build/modules) you should see your new module listed, enable it as with any other module.
Note: if after you enable the module, you get a white screen, it means you have an error in the php code of your new module, delete the files from your new directory and everything should be fine.
You need to fix your new module, but it is probably easier to start over, re-read the directions and follow them carefully, if it still doesn't work, then consider these directions a bunch of bunk and try something else.
Your new node type should work the same as with page and story nodes, you will need to enable access to them (administer-> users -> access control), configure them (administer->content->content types), allow for categorization (administer->content->categories), etc.
Sorry for the long directions, I hope they are understandable and usable.
Code that you can copy and paste, then find and replace:
for the .info file
; $Id$
name = USER-FRIENDLY-NAME
description = "MODULE-DESCRIPTION"for the .module file
<?php
/**
* Implementation of hook_node_info().
*/
function TECHNICAL-NAME_node_info() {
return array(
'TECHNICAL-NAME' => array(
'name' => t('USER-FRIENDLY-NAME'),
'module' => 'TECHNICAL-NAME',
'description' => t("CREATE-CONTENT-DESCRIPTION"),
)
);
}
/**
* Implementation of hook_perm().
*/
function TECHNICAL-NAME_perm() {
return array('create TECHNICAL-NAME', 'edit own TECHNICAL-NAME');
}
/**
* Implementation of hook_access().
*/
function TECHNICAL-NAME_access($op, $node) {
global $user;
if ($op == 'create') {
// Only users with permission to do so may create this node type.
return user_access('create TECHNICAL-NAME');
}
// Users who create a node may edit or delete it later, assuming they have the
// necessary permissions.
if ($op == 'update' || $op == 'delete') {
if (user_access('edit own TECHNICAL-NAME') && ($user->uid == $node->uid)) {
return TRUE;
}
}
}
/**
* Implementation of hook_form().
*/
function TECHNICAL-NAME_form(&$node) {
$type = node_get_types('type', $node);
// We need to define form elements for the node's title and body.
$form['title'] = array(
'#type' => 'textfield',
'#title' => check_plain($type->title_label),
'#required' => TRUE,
'#default_value' => $node->title,
'#weight' => -5
);
// We want the body and filter elements to be adjacent. We could try doing
// this by setting their weights, but another module might add elements to the
// form with the same weights and end up between ours. By putting them into a
// sub-array together, we're able force them to be rendered together.
$form['body_filter']['body'] = array(
'#type' => 'textarea',
'#title' => check_plain($type->body_label),
'#default_value' => $node->body,
'#required' => FALSE
);
$form['body_filter']['filter'] = filter_form($node->format);
// NOTE in node_example there is some addition code here not needed for this simple node-type
return $form;
}
/**
* Implementation of hook_help().
*/
function TECHNICAL-NAME_help($section) {
switch ($section) {
case 'admin/help#TECHNICAL-NAME':
return t('ADMIN-HELP-TEXT');
break;
}
}
?>
Adding View and Delete Permission
The following changes in hook_perm and hook_access provide a separate View and Delete permissions that one doesn't currently get with drupal. You must also implement hook_db_rewrite_sql() to have the "view" permission apply to things like teaser lists. There is an issue with hook_db_rewrite_sql() because some list modules may not use db_rewrite_sql() properly, but it doesn't appear to happen too often.
<?php
/**
* Implementation of hook_perm().
*/
function TECHNICAL-NAME_perm() {
return array('view TECHNICAL-NAME', 'create TECHNICAL-NAME', 'edit TECHNICAL-NAME', 'edit own TECHNICAL-NAME', 'delete TECHNICAL-NAME');
}
/**
* Implementation of hook_access().
*/
function TECHNICAL-NAME_access($op, $node) {
global $user;
switch ($op) {
case 'view':
// only users with view permission may view.
return user_access('view TECHNICAL-NAME');
break;
case 'create':
// only users with create permission may create.
return user_access('create TECHNICAL-NAME');
break;
case 'update':
if (user_access('edit TECHNICAL-NAME')) {
// users with edit permission may edit all
return TRUE;
} else {
// users with edit own permission may edit if same user
if (user_access('edit own TECHNICAL-NAME') && ($user->uid == $node->uid)) {
return TRUE;
}
}
break;
case 'delete':
// only users with delete permission may delete
return user_access('delete TECHNICAL-NAME');
break;
}
}
/**
* Implementation of hook_db_rewrite_sql().
*/
function TECHNICAL-NAME_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
switch ($primary_field) {
case 'nid':
// this query deals with node objects
$return = array();
if (!user_access('view TECHNICAL-NAME')) {
if ($primary_table != 'n') {
$return['join'] = "LEFT JOIN {node} n ON $primary_table.nid = n.nid";
}
$return['where'] = "n.type <> 'TECHNICAL-NAME'";
return $return;
}
break;
case 'tid':
// this query deals with taxonomy objects
break;
case 'vid':
// this query deals with vocabulary objects
break;
}
}
?>