help making custom (story based) module - adding new fields
I am basically trying to customized story module with additional fields as a preliminary step towards understanding Drupal modules.
I have been reading through the documentation on drupaldocs.org (really very good stuff, by the way), and while I know little about the stuff, I can understand what I read ;p.
The story module was the simplest, so I changed all the 'story' references to 'setinfo' (appropriately), and if that's all I do, it works fine. However, the point is to add new custom fields to it. When I add something, it shows up in the submit/edit form, but when I preview the item or submit it, the extra info is not there. It doesn't get saved/parsed and I can't figure out why it doesn't.
Here is the code that seems to work:
/**
* Implementation of hook_form().
*/
function setinfo_form(&$node) {
$output = '';
if (function_exists('taxonomy_node_form')) {
$output .= implode('', taxonomy_node_form('setinfo', $node));
}
$output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE);
$output .= filter_form('format', $node->format);
return $output;
}But if I steal the code from the book module (thought it would minimize the chance of my error), the extra 'log' info does not get saved:
/**
* Implementation of hook_form().
*/
function setinfo_form(&$node) {
$output = '';
if (function_exists('taxonomy_node_form')) {
$output .= implode('', taxonomy_node_form('setinfo', $node));
}
$output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE);
$output .= filter_form('format', $node->format);
$output .= form_textarea(t('Log message'), 'log', $node->log, 60, 5, t('An explanation of the additions or updates being made to help other authors understand your motivations.'));
return $output;
}Any help, or direction to what I should read in the docs would be really appreciated, thank you! Even a name for this behavior would be helpful. :)
Oh, yes, and I do know about the flexinode module. Excellent stuff there too. I just thought I would try making my own before messing with flexinode to get the module to look how I want it to look.
Anisa.

Take this module as a sample
this is my resource.module, a story module with additional fields:
<?php
// $Id: resource.module,v 1.00 2004/12/18 19:13:03 matteo Exp $
/**
* @file
* Enables users to submit resources or commercial activities
*/
/**
* Implementation of hook_help().
*/
function resource_help($section) {
switch ($section) {
case 'admin/modules#description':
return t('Enables users to submit resources.');
case 'admin/settings/resource':
return t("resources are like newspaper articles. They tend to follow a publishing flow of <strong>submit -> moderate -> post to the main page -> comments</strong>. Below you may fix a minimum word count for resources and also write some submission or content guidelines for users wanting to post a resource.");
case 'admin/help#resource':
return t("
<p>The resource module lets your users submit resources for consideration by the rest of the community, who can vote on them if moderation is enabled. resources usually follow a publishing flow of <strong>submit -> moderate -> post to the main page -> comments</strong>. Administrators are able to shortcut this flow as desired.</p>
In <a href=\"%resource-config\">administer » settings » resource</a> you can set up an introductory text for resource authors, and a floor on the number of words which may be included in a resource. This is designed to help discourage the submission of trivially short resources.</p>
<h3>User access permissions for resources</h3>
<p><strong>create resources:</strong> Allows a role to create resources. They cannot edit or delete resources, even if they are the authors. You must enable this permission to in order for a role to create a resource.</p>
<p><strong>edit own resources:</strong> Allows a role to add/edit resources if they own the resource. Use this permission if you want users to be able to edit and maintain their own resources.</p>
", array('%resource-config' => url('admin/settings/resource')));
case 'node/add/resource':
return variable_get('resource_help', '');
case 'node/add#resource':
return t("Let the others know about resources or commercial activities.");
}
}
/**
* Implementation of hook_settings().
*/
function resource_settings() {
$output .= form_textarea(t('Explanation or submission guidelines'), 'resource_help', variable_get('resource_help', ''), 70, 5, t('This text will be displayed at the top of the resource submission form. It is useful for helping or instructing your users.'));
$output .= form_select(t('Minimum number of words'), 'minimum_resource_size', variable_get('minimum_resource_size', 0), drupal_map_assoc(array(0, 10, 25, 50, 75, 100, 125, 150, 175, 200)), t('The minimum number of words a resource must be to be considered valid. This can be useful to rule out submissions that do not meet the site\'s standards, such as short test posts.'));
return $output;
}
/**
* Implementation of hook_node_name().
*/
function resource_node_name($node) {
return t('resource');
}
/**
* Implementation of hook_perm().
*/
function resource_perm() {
return array('create resources', 'edit own resources');
}
/**
* Implementation of hook_access().
*/
function resource_access($op, $node) {
global $user;
if ($op == 'create') {
return user_access('create resources');
}
if ($op == 'update' || $op == 'delete') {
if (user_access('edit own resources') && ($user->uid == $node->uid)) {
return TRUE;
}
}
}
/**
* Implementation of hook_link().
*/
function resource_link($type, $node = 0, $main) {
$links = array();
if ($type == 'node' && $node->type == 'resource') {
// Don't display a redundant edit link if they are node administrators.
if (resource_access('update', $node) && !user_access('administer nodes')) {
$links[] = l(t('edit this resource'), "node/$node->nid/edit");
}
}
return $links;
}
/**
* Implementation of hook_menu().
*/
function resource_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'node/add/resource', 'title' => t('resource'),
'access' => resource_access('create', NULL));
}
return $items;
}
/**
* Implementation of hook_validate().
*
* Ensures the resource is of adequate length.
*/
function resource_validate(&$node) {
if (isset($node->body) && count(explode(' ', $node->body)) < variable_get('minimum_resource_size', 0)) {
form_set_error('body', t('The body of your resource is too short. You need at least %words words to submit your resource.', array('%words' => variable_get('minimum_resource_size', 0))));
}
if (isset($node->email) && $node->email=='') {
form_set_error('email', t("Specify an e-mail address."));
}
if (isset($node->email) && $node->email!='' && !valid_email_address($node->email)) {
form_set_error('email', t("Specify a valid e-mail address."));
}
if (isset($node->city) && $node->city=='') {
form_set_error('city', t("Specify city."));
}
}
/**
* Implementation of hook_form().
*/
function resource_form(&$node) {
$output = '';
if (function_exists('taxonomy_node_form')) {
$output .= implode('', taxonomy_node_form('resource', $node));
}
$output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE);
$output_address = form_textfield(t("City"), "city", $node->city, 50, 100, t("Specify city"), NULL, TRUE);
$output_address .= form_textfield(t("Zip"), "zip", $node->zip, 5, 5, t("Specify Zip code"), NULL, FALSE);
$output_address .= form_textfield(t("Address"), "address", $node->address, 50, 100, t("Specify address"), NULL, FALSE);
$output_address .= form_textfield(t("Phone"), "phone", $node->phone, 30, 100, t("Specify phone"), NULL, FALSE);
$output_address .= form_textfield(t("Fax"), "fax", $node->fax, 30, 100, t("Specify fax number"), NULL, FALSE);
$output_address .= form_textfield(t("E-mail"), "email", $node->email, 40, 60, t("Specify e-mail address"), NULL, TRUE);
$output_address .= form_textfield(t("Web Site"), "url", $node->url, 50, 150, t("Example: <a href="http://www.xxx.yyy"" title="http://www.xxx.yyy"" rel="nofollow">http://www.xxx.yyy"</a>), NULL, FALSE);
$output .= form_group(t("Address"), $output_address);
$output .= filter_form('format', $node->format);
return $output;
}
function resource_view(&$node, $main = 0, $page = 0) {
$node = resource_content($node, $page);
if ($page) {
// Breadcrumb navigation
$breadcrumb[] = l(t('Home'), NULL);
$breadcrumb[] = l(t('resources'), 'taxonomy_html/resource');
drupal_set_breadcrumb($breadcrumb);
}
return theme('node', $node, $main, $page);
}
/**
* Implementation of hook_content().
*/
function resource_content(&$node, $page = 0) {
if ($page) {
// Full page
$output .= theme('group_field', t("Address"), $node->address ."<br />". $node->zip." ".$node->city);
if ($node->phone) $output .= theme('resource_field', t("Phone"), $node->phone);
if ($node->fax) $output .= theme('resource_field', t("Fax"), $node->fax);
if ($node->email) $output .= theme('resource_field', t("E-mail"), check_output($node->email,$node->format));
$output .= theme('resource_field', t("WEB Site"), check_output($node->url));
$node->teaser = ($node->teaser ? '<div class="content">'. check_output($node->teaser, $node->format) . $output . '</div></div>' : '</div>');
$node->body = ($node->body ? '<div class="content">'. check_output($node->body, $node->format) . $output . '</div></div>' : '</div>');
}
else {
// Short page
$node->teaser = check_output($node->city,$node->format);
}
$node->readmore = true;
return $node;
}
function resource_load($node) {
$resource = db_fetch_object(db_query("SELECT uid, url, email, address, city, zip, phone, fax FROM resource c, node n WHERE c.nid=n.nid AND n.nid = '%d'", $node->nid));
return $resource;
}
function resource_update($node) {
db_query("UPDATE resource SET url = '%s', email = '%s' , address = '%s', city = '%s', zip = '%s', phone = '%s', fax = '%s' WHERE nid = '%d'", $node->url, $node->email, $node->address, $node->city, $node->zip, $node->phone, $node->fax, $node->nid);
}
function resource_insert($node) {
db_query("INSERT INTO resource (nid, url, email, address, city, zip, phone, fax) VALUES ('%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->url, $node->email, $node->address, $node->city, $node->zip, $node->phone, $node->fax);
}
function resource_delete($node)
{
db_query("DELETE FROM resource where nid = '%s'", $node->nid);
}
function theme_resource_field($label, $formatted_value) {
$output = theme('form_element', $label, $formatted_value);
$output = '<div class="resource">'. $output .'</div>';
return $output;
}
?>
Of course you have to create a SQL table to store data.
CREATE TABLE `resource` (`nid` int(10) unsigned NOT NULL default '0',
`url` varchar(150) NOT NULL default '',
`phone` varchar(30) NOT NULL default '',
`fax` varchar(30) NOT NULL default '',
`email` varchar(100) NOT NULL default '',
`address` varchar(100) default NULL,
`city` varchar(100) default NULL,
`state` char(2) NOT NULL default '',
`zip` varchar(8) NOT NULL default '',
`click` int(11) NOT NULL default '0',
PRIMARY KEY (`nid`),
KEY `nid` (`nid`)
) TYPE=MyISAM ;
hope it helps.
Matteo
Thank you for the code! It
Thank you for the code! It looks interesting. I'll have a more indepth look at it when I get home (at work now).
I do have a question though... In your module, your info is stored in a table. In the original story module, where is the info stored? There aren't any sql calls, and there isn't a story table (when I checked there wasn't, may have missed something). If there were, it would make sense that the additional fields were not being stored because there was no place to store them.
Thanks again for the help!
Anisa.
All data are stored in node table
Normally, all data are stored in node table.
To make modules independent from the base functionalities, all external data are stored in additional tables.
This also to avoid hacking base node table calls, which are in node.module.
I can guarantee you that, even it seems complicated, once you understand the logic, Drupall will be simpler to you !!!
Matteo
^.^
The documentation is so nice, it doesn't seem so bad at all. Themes seem more scary though. Your module is also a tremendous help.
I have a couple of questions, looking at your code (and probably more as I go through it more)... Hope you can bear with me.
case 'node/add/resource':return variable_get('resource_help', '');
case 'node/add#resource':
return t("Let the others know about resources or commercial activities.");
}
Was the point of having node/add/resource retrieve the help instructions so that when a person submits the resource, they see the help instructions on the top of the screen?
Also...
/*** Implementation of hook_content().
*/
function resource_content(&$node, $page = 0) {
if ($page) {
// Full page
$output .= theme('group_field', t("Address"), $node->address ."<br />". $node->zip." ".$node->city);
if ($node->phone) $output .= theme('resource_field', t("Phone"), $node->phone);
if ($node->fax) $output .= theme('resource_field', t("Fax"), $node->fax);
if ($node->email) $output .= theme('resource_field', t("E-mail"), check_output($node->email,$node->format));
$output .= theme('resource_field', t("WEB Site"), check_output($node->url));
$node->teaser = ($node->teaser ? '<div class="content">'. check_output($node->teaser, $node->format) . $output . '</div></div>' : '</div>');
$node->body = ($node->body ? '<div class="content">'. check_output($node->body, $node->format) . $output . '</div></div>' : '</div>');
}
else {
// Short page
$node->teaser = check_output($node->city,$node->format);
}
$node->readmore = true;
return $node;
}
As far as I can tell, this controls the display of the published content, you have it set so you can theme it. But there isn't a 'hook_content' (at least, not one that looks like this), is this something you made especially? How does it know whether or not to display the teaser or the full page? Is how a node displayed left all up to a theme, normally? (must read more about that later).
I also have questions about the db queries, but I will save them. ^.^
Thank you again for all your help. I wouldn't want to do this as a job, but it's fun to learn about.
Anisa.
アニサです。
content gets called from view
You're right, there is no content hook. There is, however, a view hook:
<?phpfunction resource_view(&$node, $main = 0, $page = 0) {
$node = resource_content($node, $page);
...
?>
which also answers your question about the teaser - Drupal decides for you and passes the $page parameter in to the view hook.
I think you are very wise to learn how to write your own modules. When I started writing my own modules is when I started falling in love with Drupal. Up until then it was just something to download so I could have a blog. Now it is the only software I am interested in using for web applications of all natures.
- Robert Douglass
-----
www.robshouse.net
www.webs4.com
Thanks. I think I am
Thanks. I think I am blushing. ^.^
Right now it is spouting errors at me, though. I uninstalled a similarly named flexinode module, and the img assist module, and it started telling me that I was missing argument 2 from my access hook.
/**
* Implementation of hook_access().
*/
function setinfo_access($op, $node) {
global $user;
if ($op == 'create') {
return user_access('submit set info');
}
if ($op == 'update' || $op == 'delete') {
if (user_access('edit submitted set info') && ($user->uid == $node->uid)) {
return TRUE;
}
}
}
Pretty standard hook, I thought, and the 2nd argument is the *node*, so ... not sure where to go from there.
Anisa.
Check out flexinode
Check out the flexinode module. It allows arbitrary node types with admin-defined fields.
May not be the simplest way to learn, but is a powerful feature.
OK, getting access denied...
here is my module as I have it (please ignore all my notes ^.^;;):
<?php
// $Id: setinfo.module,v 1 2005/02/21 19:36:46 rivena Exp $
/**
* SETUP
* Create a form for turning in set information
* format using theme_node
* Right hand side: image, insertable by inline?
* Left hand side
* Series (probably)
* Set Name (probably)
* Publisher (25 char text field), Year (5 char text field, to allow for ?)
* Card Types (text box, wiki filter)
* Notes (text box, any filter)
* Link to ACO (url, can just be text with html filter...)
* Donator? (dunno about this one)
* things to add
*/
/**
* @file
* Enables users to submit anime trading card set information.
*/
/**
* Implementation of hook_help().
*/
function setinfo_help($section) {
switch ($section) {
case 'admin/modules#description':
return t('Enables users to submit anime trading card set information.');
case 'node/add/setinfo':
return variable_get('resource_help', '');
case 'node/add#setinfo':
return t('Designed especially for submitting anime trading card set information.');
}
}
//ANISA NOTE: The middle case above required by hook_settings.
/**
* Implementation of hook_node_name().
*/
function setinfo_node_name($node) {
return t('Set Info');
}
/**
* Implementation of hook_perm().
*/
function setinfo_perm() {
return array('Submit set info', 'Edit submitted set info');
}
//ANISA NOTE: I can add an EDIT THIS PAGE link by using hook_link, see this page <a href="http://drupaldocs.org/api/head/function/hook_link
/**
" title="http://drupaldocs.org/api/head/function/hook_link
/**
" rel="nofollow">http://drupaldocs.org/api/head/function/hook_link
/**
</a> * Implementation of hook_access().
*/
function setinfo_access($op, $node) {
global $user;
if ($op == 'create') {
return user_access('Submit set info');
}
if ($op == 'update' || $op == 'delete') {
if (user_access('edit submitted set info') && ($user->uid == $node->uid)) {
return TRUE;
}
}
}
/**
* Implementation of hook_settings().
*/
function setinfo_settings() {
$output .= form_textarea(t('Submission guidelines'), 'setinfo_help', variable_get('setinfo_help', ''), 70, 5, t('This text will be displayed at the top of the setinfo submission form. It is useful for helping or instructing your users.'));
return $output;
}
//ANISA note: this is stolen from the Resource module. Requires the variable setinfo_help to be defined in hook_help
/**
* Implementation of hook_menu().
*/
function setinfo_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'node/add/setinfo', 'title' => t('Set Info'),
'access' => user_access('Submit set info'));
}
return $items;
}
/**
* Implementation of hook_form().
*/
function setinfo_form(&$node) {
$output = '';
if (function_exists('taxonomy_node_form')) {
$output .= implode('', taxonomy_node_form('setinfo', $node));
}
$output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE);
$output .= filter_form('format', $format=4);
return $output;
}
?>
I am getting access denied to the create new content page for my module. I used to get 'missing argument 2' for the access hook, but that no longer pops up.
I had removed a similarly named flexinode type and the image assist module before I started getting these errors.
Really not sure what is going on... As far as I can tell, the access things are pretty standard. I am logged in as the super user.
Anisa.
help making custom (story based) module - adding new fields
Thanks, for this excellent code!
I would like to add the following fields to code as follows:
field type: checkbox (multiple select)
field type: file
field type: image
field type: select (single select)
field type: textarea (done)
field type: textfield (done)
field type: timestamp
The single select option will be used to select list of countries, states and cities.
The checkbox option will be used to select multiple categories.
All comments greatly appreciated!
Thanks, Darly
Apache is bandwidth limited, PHP is CPU limited, and MySQL is memory limited.