Lesson #1 Class Notes wiki -- Drupal Basics, FormAPI and CCK

You are viewing a wiki page. You are welcome to join the group and then edit it. Be bold!

Request to the Author!!
Please describe system requirements for this lesson.
What versions of PHP and Drupal are needed to accomplish given task?
I tryed it on Drupal 6.16.
form output does not work!

Complete Class Notes of First Lesson: read on, but also, type on, and execute on, till the end!

[Note: all code examples tested and run as advertised on my own test installation. Everyone should do exactly the same, so we are all on the same page, and can at some point even feel right at home doing patch reviews!!!]

An hour and a half screencap of the 1st lesson is also available (warning 90mb download). NOTE: link currently offline - will update this page when I find a good copy. Full transcript below the jump.

Part 1) Introduction

  • What this group is all about.
    Largely redundant, just read Josh's first posts prior to the first lesson

  • Why we are doing this.
    Josh has been involved with Drupal for more than 3 years now, would like to help more people get into it since it is such a great tool.
    Right now there is a great deal of dispersion and big gaps between the knowledge different groups of users have.
    Users might have a great deal of knowledge on specific areas, but not have a clear understanding of the "Drupal Way", something essential for making the best use out of and the best contributions to the Drupal community, to get the most out of the system.
    This group is dedicated to helping people get the most expertise in the system. This is an in-depth question, so Drupal Dojo is not a support system (problem-solution turnaround community) like #drupal-support is.
    It's more like a "come and hang out and we will help each other with our projects" kind of community, which will also help the Drupal community, with documentation and providing a place for people who want to learn more.

  • Where it's going in the future.
    The hope is that other people besides Josh will step up to lead sessions, there are a lot of people who can make contributions, on in-depth topics such as AJAX, or specific modules and their architecture and use.

  • A minute or two of technical fixes for people with problems getting on IRC or webhuddle
    This was ongoing throughout

Part 2) Drupal Fundamentals

The terms node, taxonomy, hooks and themes/theme engines were explained, by references to the Drupal API and documentation, and using a live site by way of teaching aid.

Going beyond the Drupal Handbook reference: Introduction to Drupal terminology, it was explained that nodes could be created by admins, and that with the module CCK (Content Creation Kit) fields could also be added; and that Nodes have their own API of course. We need to browse over this stuff a lot.

Node

Nodes are the central unit of content in Drupal. There are a set of hooks around Node that make it possible to perform operations on it. It's attribute structure can best be seen on a live site, with the aid of the Devel Module, which when enabled will show a Devel load and a Devel render tab (object structure tab?) alongside the more familiar View and Edit. Hitting those tabs reveals the structure of a node, for example:

nid  (node id)
1
vid  (revision id)
2
type
page
status
1
created
1166258623
changed
1166314457
comment
2
promote    (promoted to [default] front page)
1
sticky         (sticky, post stays on top)
0
revision_timestamp
1166314457
title
Welcome
body
<h1><font color="#339966"><em>Welcome to intra...</em></font></h1>
teaser
<h1><font color="#339966"><em>Welcome to intra...</em></font></h1>
log
format       (input format)
3                 (for example, 3 means "full html")
uid
2
name
victorkane
picture
files/pictures/picture-2.png
data
a:4:{s:18:"admin_compact_mode";b:1;s:7:"contact";i:1;s:14:"picture_delete";s:0:"";s:14:"picture_upload";s:0:"";}
last_comment_timestamp
1166258623
last_comment_name
NULL
comment_count
0
taxonomy

Array
(
)

files

Array
(
)

An interesting thing about node->type is that it determines which hooks will be fired during the rendering process.

moderate is deprecated in Drupal 5.

teaser is generated during creation or editing, and can be fine tuned by the break point (used to be html comment with break in it, now break tag???)

On another note, we saw how the Devel module also lists redirects as they occur, which helps greatly with debugging.

Taxonomy

Taxonomy is the most basic and robust categorization system we have in Drupal.

In the above example there are no taxonomy terms applied to the node, but here is what they would look like in the Devel load object structure:

**Taxonomy**

Array
(
    [4] => stdClass Object
        (
            [tid] => 4
            [vid] => 3
            [name] => good Argentine stuff
            [description] => 
            [weight] => 0
        )

)

If we go look at the database we are using for drupal, we see it is just a two column many to many table linking a node to a category term:

mysql> \u drpl5_01
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> describe term_node;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| nid   | int(10) unsigned | NO   | PRI | 0       |       |
| tid   | int(10) unsigned | NO   | PRI | 0       |       |
+-------+------------------+------+-----+---------+-------+
2 rows in set (0.03 sec)

As drupaleros interested in gaining expertise with Drupal, it behooves us to study the database structure in general. Drupal's core exhibits many best practices in relational database architecture and you can learn a lot in general by looking at how it works.

mysql> describe term_data;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| tid         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| vid         | int(10) unsigned | NO   | MUL | 0       |                |
| name        | varchar(255)     | NO   |     | NULL    |                |
| description | longtext         | YES  |     | NULL    |                |
| weight      | tinyint(4)       | NO   |     | 0       |                |
+-------------+------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

The taxonomy system supports administratively creative top-down hierarchies as well as free-tagging folksonomies, so it's a very powerful system.

One example of a powerful use of taxonomy is to go to the Working Californians site, the three columns are created by the Panels module, very cool for laying out content, and the content that goes into each column is determined not only by content type, but also its taxonomy, the terms applied to the nodes.

Hook

We have to learn our way around the Drupal API site, our "php.net" just for Drupal.

Now, for hooks, we go to http://api.drupal.org/api/HEAD/group/hooks (substitute "4.7" for "HEAD" for version 4.7).

So hooks are ways for modules that you write to be called by Drupal when things happen.

This is similar in operating system programming to registering for events, so that when something happens, a given function gets called.

In this way, as the API reference page shows, hook_access allows you to write code in your module defining what goes on with permissions.

You implement a function called "modulename_hook", so if your module is called "example", you would write a function called "example_access" for Drupal to call.

This module "namespace" is especified in the info file in 5.0, in 4.7 it is determined by the name of the module file. The namespace helps keep your code organized, and prevents function-name collisions.

As a result, obviously, it is better not to put underscores in your module names, since it is a delimiter between module and hook names for its function names. It's better to have a module called, for instance, userreference than user_reference.

So, there are a lot of "hooks" mentioned on the API reference page, and they tend to work through the "module_invoke" and "module_invoke_all" function calls (see bottom of API references page; which you can use to invoke other modules to work with yours).

This kind of architecture is very much a part of the Drupal gestalt, a very modular way of doing things to avoid the core being hacked upon.

Theme

Drupal does a pretty good job of following the Model View Controller MVC design pattern , as long as people code well.

Go to http://api.drupal.org/api/HEAD/function/theme, for example.

(Discussion on object oriented approach in general, comparison with Drupal; Josh speaks of well architectured, more efficient approach; see Drupal programming from an object-oriented perspective, also to be found as part of the API docs).

Josh says: "Ideally, anything that prints out HTML to the browser should be run by a theme function, since theme functions can be overridden without any hacking. So if a module spits out HTML code, and you love what the module does but you hate the way it looks, you should be able to change the way it looks."

Separation of what something is from how it looks.

That's what the theme layer lets you do. To quote from the theme function:

All requests for themed functions must go through this function. It examines the request and routes it to the appropriate theme function. If the current theme does not implement the requested function, then the current theme engine is checked. If neither the engine nor theme implement the requested function, then the base theme function is called.

For example, to retrieve the HTML that is output by theme_page($output), a module should call theme('page', $output).

[8:26 pm] Believe it or not: 25% of lesson!

We checked out the theme_page function.

The idea was mentioned of a mytheme_user function to intercept the way a user's name appears every time it appears (if AOL, show if online, etc.). Check out the theme_username function.

Since theme functions are about overriding, a good first step is to copy the whole theme function you are overriding first, to make sure you don't miss anything, and then alter what you want to override. And the place for that is in the theme, because you don't want to force any HTML on everyone else. The only time your module should not have functions prefixed with modulename_ is when they're prefixed with theme_.

In the same way, the Form API provides us with a way of hooking into the way Drupal creates forms, and node API, although as of Drupal 5.0, it is increasingly unnecessary to create a module in order to create a node type, since that can be created in administer > content types.

[snipping advanced discussion concerning changes in Form API and clean output via simple return of variables with Drupal 5.0 which will be dealt with opportunely]

[snipping comments and discussion about what constitutes a callback function, although see Wikipedia article.

Theme Engine

Not dealt with in this lesson.

Why follow standards? Code style, community norms, best practices.

Not dealt with in this lesson.

Part 3) Coding Kung Fu: The Power of FormAPI

This part to be followed on everyone's own installation of Drupal and development environment.

  • What's it do?
  • Getting (and grokking) a $form array to work with
  • Setting a theme function
  • Altering/adding elements
  • Registering additional callbacks

OK, we are going to make a module of our own. The easiest way is to copy the blog module.
We go, say (there are other alternative), to the ./modules subdirectory and do:

cp -R blog dojo
cd dojo
mv blog.info dojo.info
mv blog.module dojo.module

Then, edit dojo.info so it reads:

name = Dojo
description = Example.
package = Dojo
version = VERSION

Then we just delete all the text of the file dojo.module (we will actually start from scratch).

Then go to administer > modules, and we actually see our Dojo group and module:

Enabled   Name    Version Description
    Dojo    5.0-RC-1    Example.

And we enable it by checking the checkbox.

Now we are going to get into a concrete example of Form API.

First code snippet:

<?php
function dojo_form_alter($form_id, &$form) {
print_r($form);
}
?>

(last line put in for highlighting; we don't actually close out php in module files)

First we invoke the "hook" form_alter, which picks up the id of the form and the form array that gets used later.

BTW, see Josh's post on how to invoke Drupal API from Firefox browser, although if you replace "4.7" with "HEAD", you'll get the 5.0 version.

Then we ridiculously dump all forms via this print_r.

To see the effect of this, edit any node, for example, to get a form.

At the top of "Create content", for example, you might see something like this:

Array
(
    [code] => Array
        (
            [#type] => textarea
            [#title] => PHP code to execute
            [#description] => Enter some code. Do not use <code><?php ?></code> tags.
        )

    [op] => Array
        (
            [#type] => submit
            [#value] => Execute
        )

    [#redirect] => 
    [#parameters] => Array
        (
            [0] => devel_execute_form
        )

    [#type] => form
    [#post] => Array
        (
        )

    [#programmed] => 
    [#token] => devel_execute_form
    [form_token] => Array
        (
            [#id] => edit-devel-execute-form-form-token
            [#type] => token
            [#default_value] => 6a074926cb8636473fc93d26136d197e
        )

    [form_id] => Array
        (
            [#type] => hidden
            [#value] => devel_execute_form
            [#id] => edit-devel-execute-form
        )

    [#id] => devel-execute-form
    [#description] => 
    [#attributes] => Array
        (
        )

    [#required] => 
    [#tree] => 
    [#parents] => Array
        (
        )

    [#method] => post
    [#action] => /drupal/node/add
    [#submit] => Array
        (
            [devel_execute_form_submit] => Array
                (
                )

        )

)

Now, working with a theme is less technical, it will allow us to work on the theme end instead of the module end.
As a first step, without delegating anyting to the template.php file of a theme, but instead delegating to a theme function, we do this:

<?php
function dojo_form_alter($form_id, &$form) {
$form['#theme'] = 'dojo';
}

function
theme_dojo($form) {
print_r($form);
}
?>

Now, instead of just dumping the form, we can output stuff, which will go inside the "frame" of the site:

<?php
function dojo_form_alter($form_id, &$form) {
$form['#theme'] = 'dojo';
}

function
theme_dojo($form) {
 
$output = '<h1>I added this to the top of the form</h1>';
 
// 4.7 code: $output .= form_render($form);
  // 5.0 code:
 
$output .= drupal_render($form);
 
// this will stick it at the top: echo $output;
  // this will do what we want:
 
return $output;
}
?>

(Play with the little pitfalls that were discovered along the way, they are fun).

We can go further and mess with the title (which messed with my fckeditor, but after a few refreshes the beweildered javascript kicked in anyway):

<?php
function dojo_form_alter($form_id, &$form) {
$form['#theme'] = 'dojo';
}

function
theme_dojo($form) {
 
$output = '<h1>I added this to the top of the form</h1>';
  unset(
$form['title']);
 
$output .= drupal_render($form);
  return
$output;
}
?>

As we shall see, this could easily be done at the theme level, but the module level will allow us to conditionally impose these effects, for example, according to content type, or according to access levels, or whatever, in this case, only for page, but not for story:

<?php
function dojo_form_alter($form_id, &$form) {
  if (
$form['type']['#value'] == 'page') {
   
$form['#theme'] = 'dojo';
  }
}

function
theme_dojo($form) {
 
$output = '<h1>I added this to the top of the form</h1>';
  unset(
$form['title']);
 
$output .= drupal_render($form);
  return
$output;
}
?>

This is not magic! Whatever "$form['#theme']" is, gets passed to theme().

We now add a validation callback, where values entered into the form are checked (the other main callback for forms is the submit callback).

If you put 'Foo' in the title field of a page, you will see the error message "Foo not allowed":

<?php
function dojo_form_alter($form_id, &$form) {
  if (
$form_id == 'page_node_form') {
   
$form['#theme'] = 'dojo';
   
$form['#validate']['dojo_validate'] = array($form['title']);
  }
}

function
theme_dojo($form) {
 
$output = '<h1>I added this to the top of the form</h1>';
// unset($form['title']);
 
$output .= drupal_render($form);
  return
$output;
}

function
dojo_validate($form_id, $form_values, $form) {
  if(
$form_values['title'] == 'Foo') {
   
form_set_error('title', t('Foo not allowed!'));
  }
}
?>

Similar callbacks can be registered for form submission, which is a great way to add new functionality to existing working modules.

In answer to the question of how to alter the form at the theme level, say, altering the order of form elements and labels, Drupal is smart, and knows what it has already rendered and what it hasn't, so you can pick out different elements and render them at different times; for example, first the title, then the body (encased in the body_filter element), then a label, then the form as a whole (it doesn't care about the order, it just needs all its elements):

<?php
function dojo_form_alter($form_id, &$form) {
  if (
$form_id == 'page_node_form') {
   
$form['#theme'] = 'dojo';
   
$form['#validate']['dojo_validate'] = array($form['title']);
  }
}

function
theme_dojo($form) {
 
$output .= drupal_render($form['title']);
 
$output .= drupal_render($form['body_filter']);
 
$output .= '<h1>Yo! I added this to the top of the form</h1>';
 
$output .= drupal_render($form);
  return
$output;
}

function
dojo_validate($form_id, $form_values, $form) {
  if(
$form_values['title'] == 'Foo') {
   
form_set_error('title', t('Foo not allowed!'));
  }
}
?>

And the following will stick a nice markup element "Baz!" at the top of your form:

<?php
function dojo_form_alter($form_id, &$form) {
  if (
$form_id == 'page_node_form') {
   
$form['#theme'] = 'dojo';
   
$form['#validate']['dojo_validate'] = array($form['title']);
   
$form['mything'] = array(
     
'#type' => 'markup',
     
'#value' => '<h1>Baz!</h1>',
     
'#weight' => -10,
    );
  }
}

function
theme_dojo($form) {
//  $output .= drupal_render($form['title']);
//  $output .= drupal_render($form['body_filter']);
//  $output .= '<h1>Yo! I added this to the top of the form</h1>';
 
$output .= drupal_render($form);
  return
$output;
}

function
dojo_validate($form_id, $form_values, $form) {
  if(
$form_values['title'] == 'Foo') {
   
form_set_error('title', t('Foo not allowed!'));
  }
}
?>

Part 4) Theming Tai Chi: The Way of node.tpl.php

  • Looking at phptemplate_node
  • Creating a new node-type.tpl.php

For this we have to make sure we have CCK installed, and we are going to go ahead and create the content type "dojo", with CCK.

Note: since we added the custom Dojo module to ./modules, CCK should also be installed there (and not in ./sites/all/modules). If you put it in ./sites/all/modules, you won't see the CCK fields when using print_r($form) to scope out your form, since the custom Dojo module's form_alter hook (which is where you'll put print_r($form), will be called before CCK gets a chance to add your custom fields to the form. So you either need to install CCK to ./modules, or move the Dojo module we created above to ./sites/all/modules as well.

(By this time we should know how to find it on the Drupal site, but since this is the first lesson, we'll just remind ourselves that for that we have to go to http://drupal.org/project/cck and download the appropriate version (we are following the lesson in 5.0 rc1, so we need the latest 5.0 version).

Even though Drupal 5.0 allows administrators to create arbitrary nodes (content types) without writing node modules, you still need CCK in order to be able to add additional fields beyond title and body. See the spanking new Content Construction Kit Handbook for background.

On your lesson development site, go to administer > content management > content types and create the "Dojo" content type, then add a field "Belt Color" of type text, select list, and make it required. The allowed values are going to be white, yellow, orange, green, brown, black.

We create a Dojo entitled "White Belt", with a Belt Color of White.
With the Devel module loaded, when we view the Dojo node as Devel load, we see:

nid
10
vid
10
type
dojo
status
1
created
1168047675
changed
1168047675
comment
2
promote
1
sticky
0
revision_timestamp
1168047675
title
White Belt
body
I have a white belt!
teaser
I have a white belt!
log
format
3
uid
2
name
victorkane
picture
data
a:0:{}
last_comment_timestamp
1168047675
last_comment_name
NULL
comment_count
0
taxonomy

Array
(
)

field_belt_color

Array
(
    [0] => Array
        (
            [value] => white
        )

)

Now, the rendering of the node is horrible in view, so lucky for us there is the Drupal theme system, with a specific template file for each node type.

So we change directory to the current theme dir (probably Garland for all of us 5.0 rc1'ers).

Now, node.tpl.php is the most general one, it gets called every time a node is rendered and passed through theme().

We are going to roll our own for the Dojo content type we have created, and of course we call it node-dojo.tpl.php.

We clone the node.tpl.php file to node-dojo.tpl.php, and edit it.

We comment out all the code with HTML comments and just stick in <h1>FOO!</h1> at the top, and when we "view" the dojo node, we should only see FOO! at the top. Try it in your lesson development environment.

Got that? Right, let's get to theming!

Let's start with something sane, uncomment the code and stick in Baz! above the content (look for the content div in node-dojo.tpl.php):

  <div class="content">
     <h2>Baz</h2>
    <?php print $content ?>
  </div>

Now, what if we want to build our own content theming?

To see the node structure, just pretty print the result of replacing the whole node-dojo.tpl.php file with a print_r statement like this, then pick and choose from the elements we see are there:

node-dojo.tpl.php:

<?php
print_r
($node);
?>

output now while viewing:

stdClass Object
(
    [nid] => 10
    [vid] => 10
    [type] => dojo
    [status] => 1
    [created] => 1168047675
    [changed] => 1168047675
    [comment] => 2
    [promote] => 1
    [sticky] => 0
    [revision_timestamp] => 1168047675
    [title] => White Belt
    [body] => 

Belt Color

white

I have a white belt!

[log] => [format] => 3 [uid] => 2 [name] => victorkane [picture] => [data] => a:0:{} [last_comment_timestamp] => 1168047675 [last_comment_name] => [comment_count] => 0 [taxonomy] => Array ( ) [field_belt_color] => Array ( [0] => Array ( [value] => white [view] => white ) ) [readmore] => [content] => Array ( [field_belt_color] => Array ( [0] => Array ( [#weight] => 0 [#value] =>

Belt Color

white [#printed] => 1 ) [#children] =>

Belt Color

white [#printed] => 1 ) [body] => Array ( [#weight] => 0 [#value] =>

I have a white belt!

[#printed] => 1 ) [#children] =>

Belt Color

white

I have a white belt!

[#printed] => 1 ) [links] => Array ( [comment_add] => Array ( [title] => Add new comment [href] => comment/reply/10 [attributes] => Array ( [title] => Share your thoughts and opinions related to this posting. ) [fragment] => comment_form ) ) )

So to print out just the body (look for it above in the "node dump" to see if you can guess how to do it) we put back our regular node-dojo.tpl.php stuff as it was when we first copied it from node.tpl.php, and we just comment out the printing of the whole $content variable, and change the content div as follows:

  <div class="content">
    <!-- <?php print $content ?>  -->

  <?php
   
echo $node->content['body']['#value'];
 
?>

  </div>

This works, so now we can really go crazy, recognizing our "White Belt" buddies!

  <div class="content">
    <!-- <?php print $content ?>  -->

  <?php
   
echo $node->content['body']['#value'];
    if (
$node->field_belt_color[0]['value'] == 'white') {
      echo
'<h4>White Belt! Welcome to The Dojo!</h4>';
    }
 
?>

  </div>

as well as our Senseis:

  <div class="content">
    <!-- <?php print $content ?>  -->

  <?php
   
echo $node->content['body']['#value'];
    if (
$node->field_belt_color[0]['value'] == 'white') {
      echo
'<h4>White Belt! Welcome to The Dojo!</h4>';
    } elseif (
$node->field_belt_color[0]['value'] == 'black') {
      echo
'<h4>Sensei!</h4>';
    }
 
?>

  </div>

So that's how to use a node type specific template for a given node, or content type, created with CCK.

Part 5) Dojo Challenge!

  • Use CCK + FormAPI + node.tpl.php to create a rich new content type!
  • Create a CCK node type
  • Designate a field as admin-only w/FormAPI
  • Use node-type.tpl.php to theme output correctly

So, putting it all together, to create something really awesome!

To do so, we are going to use both form_alter in our dojo.module, as well as further changes in the node-dojo.tpl.php theme file.

First thing we do is a little debugging.

In dojo.module, we specify our dojo node instead of the page node, and let print_r do its tricks for us once again, so we can see what we have to play with. The first function in dojo.module looks like this:

<?php
function dojo_form_alter($form_id, &$form) {
  if (
$form_id == 'dojo_node_form') {
  
print_r($form);
   
$form['#theme'] = 'dojo';
   
$form['#validate']['dojo_validate'] = array($form['title']);
   
$form['mything'] = array(
     
'#type' => 'markup',
     
'#value' => '<h1>Baz!</h1>',
     
'#weight' => -10,
    );
  }
}
?>

And the $form dump is huge, but take a look at what it gives you to see what fun stuff we could do...

For example (suggestion from IRC) change the drop down list of belt colors into radio buttons (something we could do with CCK configuration, but, for the sake of an example):

<?php
function dojo_form_alter($form_id, &$form) {
  if (
$form_id == 'dojo_node_form') {
 
//print_r($form);
   
$form['#theme'] = 'dojo';
   
$form['#validate']['dojo_validate'] = array();
   
$form['field_belt_color']['key']['#type'] = 'radios';
  }
}
?>

The last 4 minutes or so of the video has no sound. Josh did an example of theming and taxonomy. (Without the audio and since I was not there, I cannot figure out what happened. Anyone who was present, your explanation of the last example about taxonomy would be appreciated.)

Faithfully transcribed (sigh!),
Victor Kane
http://awebfactory.com.ar

(I don't see a way to comment on the class notes, but many many thanks to Victor for this great transcription.)

Drupal Dojo

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: