<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD Docbook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
<book id="node-601">
  <title>Module developer's guide</title>
  <preface>
    <title>Preface</title>    
      <para>
Developer documentation can be found at <ulink url="http://drupaldocs.org/">http://drupaldocs.org/</ulink> and in the remainder of the
Drupal developer's guide below.
</para>
      <itemizedlist>
        <listitem>
          <para>drupaldocs.org <ulink url="http://drupaldocs.org/api">documents the Drupal
APIs</ulink> and presents <ulink url="http://drupaldocs.org/api/head">an overview of
Drupal's building blocks</ulink> along with <ulink url="http://drupaldocs.org/api/head">handy examples</ulink>.
</para>
        </listitem>
        <listitem>
          <para>The Drupal developer guide provides guidlines as how to upgrade your
modules (API changes) along with development tips/tutorials.
</para>
        </listitem>
      </itemizedlist>    
  </preface>
  <chapter id="node-535">
    <title>Introduction to Drupal modules</title>      
      <para>
When developing Drupal it became clear that we wanted to have a system which is
as modular as possible. A modular design will provide flexibility,
adaptability, and continuity which in turn allows people to customize the site
to their needs and likings.
</para>
      <para>
A Drupal module is simply a file containing a set of routines written in PHP.
When used, the module code executes entirely within the context of the site.
Hence it can use all the functions and access all variables and structures of
the main engine. In fact, a module is not any different from a regular PHP
file: it is more of a notion that automatically leads to good design principles
and a good development model. Modularity better suits the open-source
development model, because otherwise you can't easily have people working in
parallel without risk of interference.
</para>
      <para>
The idea is to be able to run random code at given places in the engine. This
random code should then be able to do whatever needed to enhance the
functionality. The places where code can be executed are called "hooks" and are
defined by a fixed interface.
</para>
      <para>
In places where hooks are made available, the engine calls each module's
exported functions. This is done by iterating through the modules directory
where all modules must reside. Say your module is named foo (i.e.
modules/foo.module) and if there was a hook called bar, the engine will call
foo_bar() if this was exported by your module.
</para>
      <para>
See also <ulink url="http://drupaldocs.org/api/head/group/hooks">the overview of module hooks</ulink>,
which is generated from the Drupal source code.
</para>
  </chapter>
  <chapter id="node-536">
    <title>Drupal's menu building mechanism</title>    
      <para>
(Note: this is an analysis of the menu building mechanism in pre-4.5 CVS as of
August 2004. It does not include menu caching.)
</para>
      <mediaobject>
        <imageobject>
          <imagedata fileref="file:///Users/djun/Projects/Drupal/projects/docbook/drupalmenudiagram.png"/>
        </imageobject>
      </mediaobject>
      <para>
This continues our examination of how Drupal serves pages. We are looking
specifically at how the menu system works and is built, from a technical
perspective. See the excellent overview in <ulink url="http://drupaldocs.org/api/head/group/menu">the menu system documentation</ulink>.
</para>
      <para>
We begin in index.php, where menu_execute_active_handler() has been called.
Diving in from menu_execute_active_handler(), we immediately set the $menu
variable by calling menu_get_menu(). The latter function declares the global
$_menu array (note the underline, it means a 'super global', which is a
<ulink url="%20http://www.php.net/manual/en/language.variables.predefined.php">predefined
array</ulink> in PHP lore) and calls _menu_build() to fill the array, then returns
$_menu. Although menu_get_menu() initializes the $_menu array, the
_menu_build() function actually reinitializes the $_menu array. Then it sets up
two main arrays within $_menu: the items array and the path index array.
</para>
      <para>
The items array is an array keyed to integers. Each entry contains the
following fields:
</para>
      <informaltable>
        <tgroup cols="3">
          <colspec colnum="1" colname="col1"/>
          <colspec colnum="2" colname="col2"/>
          <colspec colnum="3" colname="col3"/>
          <thead>
            <row>
              <entry namest="col1" nameend="col3">
Required fields
</entry>
            </row>
          </thead>
          <tbody>
            <row>
              <entry>path</entry>
              <entry>string</entry>
              <entry>the partial URL to the page for this menu item</entry>
            </row>
            <row>
              <entry>title</entry>
              <entry>string</entry>
              <entry>the title that this menu item will have in the menu</entry>
            </row>
            <row>
              <entry>type</entry>
              <entry>integer</entry>
              <entry>a constant denoting the menu item type (see comments in menu.inc)</entry>
            </row>
            <row>
              <entry namest="col1" nameend="col3">
Optional fields
</entry>
            </row>
            <row>
              <entry>access</entry>
              <entry>boolean</entry>
            </row>
            <row>
              <entry>pid</entry>
              <entry>integer</entry>
            </row>
            <row>
              <entry>weight</entry>
              <entry>integer</entry>
            </row>
            <row>
              <entry>callback</entry>
              <entry>string</entry>
              <entry>name of the function to be called if this menu item is selected</entry>
            </row>
            <row>
              <entry>callback arguments</entry>
              <entry>array</entry>
            </row>
          </tbody>
        </tgroup>
      </informaltable>
      <para>
The $menu_item_list array is normalized by making sure each array entry has a
path, type and weight entry. As each entry is examined, the path index array of
the $_menu array is checked to see if the path of this menu item exists. If an
equivalent path is already there in the path index array, it is blasted away.
The path index of this menu item is then added as a key with the value being
the menu id. In the items array of the $_menu array, the menu id is used as the
key and the entire array entry is the value.
</para>
      <para>
Note: the $temp_mid and $mid variables seem to do the same thing. Why,
syntactically, cannot only one be used?
</para>
      <para>
The path index array contained 76 items when serving out a simple node with
only the default modules enabled.
</para>
      <para>
Next the menu table from the database is fetched and its contents are used to
move the position of existing menu items from their current menu ids to the
menu ids saved in the database. The comments says "reassigning menu IDs as
needed." This is probably to detect if the user has customized the menu entries
using the menu module. The path index array entries generated from the database
can be recognized because their values are strings, whereas up til now the
values in the path index array have been integers.
</para>
      <para>
Now I get sort of lost. It looks like the code is looking at paths to determine
which menu items are children of other menu items. Then
_menu_build_visible_tree is a recursive function that builds a third subarray
inside $_menu, to go along with items and path index. It is called visible and
takes into account the access attribute and whether or not the item is hidden
in order to filter the items array. As an anonymous user, all items but the
Navigation menu item are filtered out. See also the comments in menu.inc for
menu_get_menu(). In fact, read all the comments in menu.inc!
</para>
      <para>
Now the path is parsed out from the q parameter of the URL. Since node/1 is
present in the path index, we successfully found a menu item. It points to menu
item -44 in our case, to be precise, but there must be a bug in the Zend IDE
because it shows item -44 as null. Anyway, the menu item entry is checked for
callback arguments (there are none) and for additional parameters (also none),
and execution is passed off to node_page() through the call_user_func_array
function.
</para>
  </chapter>
  <chapter id="node-538">
    <title>Drupal's node building mechanism</title>
      <para>
(This walkthrough done on pre-4.5 CVS code in August 2004.)
</para>
      <mediaobject>
        <imageobject>
          <imagedata fileref="file:///Users/djun/Projects/Drupal/projects/docbook/nodebuildingdiagram.png"/>
        </imageobject>
      </mediaobject>
      <para>
The node_page controller checks for a $_POST['op'] entry and, failing that,
sets $op to arg(1) which in this case is the '1' in node/1. A numeric $op is
set to arg(2) if arg(2) exists, but in this case it doesn't ('1' is the end of
the URL, remember?) so the $op is hardcoded to 'view'. Thus, we succeed in the
'view' case of the switch statement, and are shunted over to node_load(). The
function node_load() takes two arguments, $conditions (an array with nid set to
desired node id -- other conditions can be defined to further restrict the
upcoming database query) for which we use arg(1), and $revision, for which we
use _GET['revision']. The 'revision' key of the _GET array is unset so we need
to make brief stop at error_handler because of an undefined index error. That
doesn't stop us, though, and we continue pell-mell into node_load using the
default $revision of -1 (that is, the current revision). The actual query that
ends up being run is
</para>
      <para>
SELECT n.*, u.uid, u.name, u.picture, u.data FROM node n INNER JOIN users u on
u.uid WHERE n = '1'
</para>
      <para>
We get back a joined row from the database as an object. The data field from
the users table is serialized, so it must be unserialized. This data field
contains the user's roles. How does this relate to the user_roles table? Note
that the comment "// Unserialize the revisions and user data fields" should be
moved up before the call to drupal_unpack().
</para>
      <para>
We now have a complete node that looks like the following:
</para>
      <informaltable>
        <tgroup cols="2">
          <colspec colnum="1" colname="col1"/>
          <colspec colnum="2" colname="col2"/>
          <thead>
            <row>
              <entry>Attribute</entry>
              <entry>Value</entry>
            </row>
          </thead>
          <tbody>
            <row>
              <entry>body</entry> 
              <entry>This is a test node body</entry>
	    </row>
            <row>
              <entry>changed</entry>
              <entry>1089859653</entry> 
            </row>
            <row>
              <entry>comment</entry>
              <entry>2</entry>
            </row>
            <row>
              <entry>created</entry>
              <entry>1089857673</entry>
            </row>
            <row>
              <entry>data</entry>
              <entry>a:1:{s:5... (serialized data)</entry>
            </row>
            <row>
              <entry>moderate</entry>
              <entry>0</entry>
            </row>
            <row>
              <entry>name</entry>
              <entry>admin</entry>
            </row>
            <row>
              <entry>nid</entry>
              <entry>1</entry>
            </row>
            <row>
              <entry>picture</entry>
              <entry>''</entry>
            </row>
            <row>
              <entry>promote</entry>
              <entry>1</entry>
            </row>
            <row>
              <entry>revisions</entry>
              <entry>''</entry>
            </row>
            <row>
              <entry>roles</entry>
              <entry>array containing one key-value pair, 0 = '2'</entry>
            </row>
            <row>
              <entry>score</entry>
              <entry>0</entry>
            </row>
            <row>
              <entry>status</entry>
              <entry>1</entry>
            </row>
            <row>
              <entry>sticky</entry>
              <entry>0</entry>
            </row>
            <row>
              <entry>teaser</entry>
              <entry>This is a test node body</entry>
            </row>
            <row>
              <entry>title</entry>
              <entry>Test</entry>
            </row>
            <row>
              <entry>type</entry>
              <entry>page</entry>
            </row>
            <row>
              <entry>uid</entry>
              <entry>1</entry>
            </row>
            <row>
              <entry>users</entry>
              <entry>''</entry>
            </row>
            <row>
              <entry>votes</entry>
              <entry>0</entry>
              <entry/>
            </row>
          </tbody>
        </tgroup>
      </informaltable>
      <para>
All of the above are strings except the roles array.
</para>
      <para>
So now we have a node loaded from the database. It's time to notify the
appropriate module that this has happened. We do this via the
node_invoke($node, 'load') call. The module called via this callback may return
an array of key-value pairs, which will be added to the node above.
</para>
      <para>
The node_invoke() function asks node_get_module_name() to determine the name of
the module that corresponds with the node's type. In this case, the node type
is a page, so the page.module is the one we'll call, and the specific name of
the function we'll call is page_load(). If the name of the node type has a
hyphen in it, the left part is used. E.g., if the node type is page-foo, the
page module is used.
</para>
      <para>
The page_load() function turns out to be really simple. It just retrieves the
format, link and description columns from the page table. The 'format' column
specifies whether we're dealing with a HTML or PHP page. The 'link' and
'description' fields are used to generate a link to the newly created page,
however, those will be deprecated with the improved menu system. To that
extend, the core themes no longer use this information (unlike some older
themes in the contributions repository). We return to node_load(), where the
format, link and description key-value pairs are added to the node's
definition.
</para>
      <para>
Now it's time to call the node_invoke_nodeapi() function to allow other modules
to do their thing. We check each module for a function that begins with the
module's name and ends with _nodeapi(). We hit paydirt with the comment module,
which has a function called comment_nodeapi(&amp;$node, $op, arg = 0). Note
that the node is passed in by reference so that any changes made by the module
will be reflected in the actual node object we built. The $op argument is
'load', in this case. However, this doesn't match any of comment_nodeapi()'s
symbols in its controller ('settings', 'fields', 'form admin', 'validate' and
'delete' match). So nothing happens.
</para>
      <para>
Our second hit is node_nodeapi(&amp;$node, $op, $arg = 0) in the node.module
itself. Again, no symbols are matched in the controller so we just return.
</para>
      <para>
We'll try again with taxonomy_nodeapi(&amp;$node, $op, $arg = 0). Again, no
symbols match; the taxonomy module is concerned only with inserts, updates and
deletes, not loads.
</para>
      <para>
Note that any of these modules could have done anything to the node if they had
wished.
</para>
      <para>
Next, the node is replaced with the appropriate revision of the node, if
present as an attribute of $node. It is odd that this occurs here, as all the
work that may have been done by modules is summarily blown away if a revision
other than the default revision is found.
</para>
      <para>
Finally, back in node_page(), we're ready to get down to business and actually
produce some output. This is done with the statement
</para>
      <para>
print theme('page', node_show($node, arg(3)), $node-&gt;title);
</para>
      <para>
And what that statement calls is complex enough to again warrant another
commentary. (Not yet done.)
</para>
    <section id="node-537">
      <title>How Drupal handles access</title>
        <para>
I believe this page should explain how user_access table works.
1.- Drupal checks if the user has access to that module, if he does ...
2.- The he checks the user _access page where gid is the role, view should be 1
and realm should be "all". If there is no access given in that table, he will
not give the access to the user.
</para>
        <para>
I believe there is not enough documentation on how to use node access, and
hopefully this page will have more information as people contribute.
</para>
    </section>
  </chapter>
  <chapter id="node-539">
    <title>Drupal's page serving mechanism</title>
      <para>
This is a commentary on the process <ulink url="http://www.drupal.org/">Drupal</ulink>
goes through when serving a page. For convenience, we will choose the following
URL, which asks Drupal to display the first node for us. (A node is a thing,
usually a web page.)
</para>
      <programlisting>http://127.0.0.1/~vandyk/drupal/?q=node/1</programlisting>
      <para>
A visual companion to this narration can be found <ulink url="http://www.lo.redjupiter.com/gems/iowa/drupalwalk.png">here</ulink>; you may want
to print it out and follow along. Before we start, let's dissect the URL. I'm
running on an OS X machine, so the site I'm serving lives at
/Users/vandyk/Sites/. The drupal directory contains a checkout of the latest
<ulink url="http://drupal.org/book/view/320">Drupal CVS</ulink> tree. It looks like
this:
</para>
      <programlisting>CHANGELOG.txt
cron.php
CVS/
database/
favicon.ico
includes/
index.php
INSTALL.txt
LICENSE.txt
MAINTAINERS.txt
misc/
modules/
phpinfo.php
scripts/
themes/
tiptoe.txt
update.php
xmlrpc.php</programlisting>
      <para>
So the URL above will be be requesting the root directory <literal>/</literal> of the
Drupal site. Apache translates that into <literal>index.php</literal>. One
variable/value pair is passed along with the request: the variable 'q' is set
to the value 'node/1'.
</para>
      <para>
So, let's pick up the show with the execution of <ulink url="http://drupaldocs.org/api/head/file/index.php">index.php</ulink>, which looks very
simple and is only a few lines long.
</para>
      <para>
Let's take a broad look at what happens during the execution of
<literal>index.php</literal>. First, the <literal>includes/bootstrap.inc</literal> file is
included, bringing in all the functions that are necessary to get Drupal's
machinery up and running. There's a call to <literal>drupal_page_header()</literal>,
which starts a timer, sets up caching, and notifies interested modules that the
request is beginning. Next, the <literal>includes/common.inc</literal> file is
included, giving access to a wide variety of utility functions such as path
formatting functions, form generation and validation, etc. The call to
<literal>fix_gpc_magic()</literal> is there to check on the status of PHP "magic
quotes" and to ensure that all escaped quotes enter Drupal's database
consistently. Drupal then builds its navigation menu and sets the variable
<literal>$status</literal> to the result of that operation. In the switch statement,
Drupal checks for cases in which a Not Found or Access Denied message needs to
be generated, and finally a call to <literal>drupal_page_footer()</literal>, which
notifies all interested modules that the request is ending. Drupal closes up
shop and the page is served. Simple, eh?
</para>
      <para>
Let's delve a little more deeply into the process outlined above.
</para>
      <para>
The first line of <literal>index.php</literal> includes the
<literal>includes/bootstrap.inc</literal> file, but it also executes code towards the
end of <literal>bootstrap.inc</literal>. First, it destroys any previous variable
named <literal>$conf</literal>. Next, it calls <literal>conf_init()</literal>. This
function allows Drupal to use site-specific configuration files, if it finds
them. The name of the site-specific configuration file is based on the hostname
of the server, as reported by PHP. <literal>conf_init</literal> returns the name of
the site-specific configuration file; if no site-specific configuration file is
found, sets the variable <literal>$config</literal> equal to the string
<literal>$confdir/default</literal>. Next, it includes the named configuration file.
Thus, in the default case it will include
<literal>sites/default/settings.php</literal>. The code in <literal>conf_init()</literal>
would be easier to understand if the variable <literal>$file</literal> were instead
called <literal>$potential_filename</literal>. Likewise <literal>$conf_filename</literal>
would be a better choice than <literal>$config</literal>.
</para>
      <para>
The selected configuration file (normally
<literal>/sites/default/settings.php</literal>) is now parsed, setting the
<literal>$db_url variable</literal>, the optional <literal>$db_prefix variable</literal>,
the <literal>$base_url</literal> for the website, and the <literal>$languages</literal>
array (default is <literal>"en"=&gt;"english"</literal>).
</para>
      <para>
The <literal>database.inc</literal> file is now parsed, with the primary goal of
initializing a connection to the database. If MySQL is being used, the
<literal>database.mysql.inc</literal> files is brought in. Although the global
variables <literal>$db_prefix</literal>, <literal>$db_type</literal>, and
<literal>$db_url</literal> are set, the most useful result of parsing
<literal>database.inc</literal> is a global variable called <literal>$active_db</literal>
which contains the database connection handle.
</para>
      <para>
Now that the database connection is set up, it's time to start a session by
including the <literal>includes/session.inc</literal> file. Oddly, in this include
file the executable code is located at the top of the file instead of the
bottom. What the code does is to tell PHP to use Drupal's own session storage
functions (located in this file) instead of the default PHP session code. A
call to PHP's <literal>session_start()</literal> function thus calls Drupal's
<literal>sess_open()</literal> and <literal>sess_read()</literal> functions. The
<literal>sess_read()</literal> function creates a global <literal>$user object</literal>
and sets the <literal>$user-&gt;roles</literal> array appropriately. Since I am
running as an anonymous user, the <literal>$user-&gt;roles</literal> array contains
one entry, <literal>1-&gt;"anonymous user"</literal>.
</para>
      <para>
We have a database connection, a session has been set up...now it's time to get
things set up for modules. The <literal>includes/module.inc</literal> file is
included but no actual code is executed.
</para>
      <para>
The last thing <literal>bootstrap.inc</literal> does is to set up the global variable
<literal>$conf</literal>, an array of configuration options. It does this by calling
the <literal>variable_init()</literal> function. If a per-site configuration file
exists and has already populated the <literal>$conf</literal> variable, this
populated array is passed in to <literal>variable_init()</literal>. Otherwise, the
<literal>$conf</literal> variable is null and an empty array is passed in. In both
cases, a populated array of name-value pairs is returned and assigned to the
global <literal>$conf</literal> variable, where it will live for the duration of this
request. It should be noted that name-value pairs in the per-site configuration
file have precedence over name-value pairs retrieved from the "variable" table
by <literal>variable_init()</literal>.
</para>
      <para>
We're done with <literal>bootstrap.inc</literal>! Now it's time to go back to
<literal>index.php</literal> and call <literal>drupal_page_header()</literal>. This
function has two responsibilities. First, it starts a timer if
<literal>$conf['dev_timer']</literal> is set; that is, if you are keeping track of
page execution times. Second, if caching has been enabled it retrieves the
cached page, calls <literal>module_invoke_all()</literal> for the 'init' and 'exit'
hooks, and exits. If caching is not enabled or the page is not being served to
an anonymous user (or several other special cases, like when feedback needs to
be sent to a user), it simply exits and returns control to
<literal>index.php</literal>.
</para>
      <para>
Back at <literal>index.php</literal>, we find an include statement for
<literal>common.inc</literal>. This file is chock-full of miscellaneous utility
goodness, all kept in one file for performance reasons. But in addition to
putting all these utility functions into our namespace, <literal>common.inc</literal>
includes some files on its own. They include <literal>theme.inc</literal>, for theme
support; <literal>pager.inc</literal> for paging through large datasets (it has
nothing to do with calling your pager); and <literal>menu.inc</literal>. In
<literal>menu.inc</literal>, many constants are defined that are used later by the
menu system.
</para>
      <para>
The next inclusion that <literal>common.inc</literal> makes is
<literal>xmlrpc.inc</literal>, with all sorts of functions for dealing with XML-RPC
calls. Although one would expect a quick check of whether or not this request
is actually an XML-RPC call, no such check is done here. Instead, over 30
variable assignments are made, apparently so that if this request turns to
actually be an XML-RPC call, they will be ready. An <literal>xmlrpc_init()</literal>
function instead may help performance here?
</para>
      <para>
A small <literal>tablesort.inc</literal> file is included as well, containing
functions that help behind the scenes with sortable tables. Given the paucity
of code here, a performance boost could be gained by moving these into
<literal>common.inc</literal> itself.
</para>
      <para>
The last include done by <literal>common.inc</literal> is <literal>file.inc</literal>,
which contains common file handling functions. The constants
<literal>FILE_DOWNLOADS_PUBLIC = 1</literal> and <literal>FILE_DOWNLOADS_PRIVATE =
2</literal> are set here, as well as the <literal>FILE_SEPARATOR</literal>, which is
<literal>\\</literal> for Windows machines and <literal>/</literal> for all others.
</para>
      <para>
Finally, with includes finished, common.inc sets PHP's error handler to the
<literal>error_handler()</literal> function in the <literal>common.inc</literal> file. This
error handler creates a watchdog entry to record the error and, if any error
reporting is enabled via the <literal>error_reporting</literal> directive in PHP's
configuration file (<literal>php.ini&lt;code&gt;), it prints the error message to
the screen. Drupal's &lt;code&gt;error_handler()</literal> does not use the last
parameter <literal>$variables</literal>, which is an array that points to the active
symbol table at the point the error occurred. The comment "<literal>// set error
handler:</literal>" at the end of common.inc is redundant, as it is readily
apparent what the function call to <literal>set_error_handler()</literal> does.
</para>
      <para>
The <literal>Content-Type</literal> header is now sent to the browser as a hard coded
string: "<literal>Content-Type: text/html; charset=utf-8</literal>".
</para>
      <para>
If you remember that the URL we are serving ends with
<literal>/~vandyk/drupal/?q=node/1</literal>, you'll note that the variable
<literal>q</literal> has been set. Drupal now parses this out and checks for any path
aliasing for the value of <literal>q</literal>. If the value of <literal>q</literal> is a
path alias, Drupal replaces the value of <literal>q</literal> with the actual path
that the value of <literal>q</literal> is aliased to. This sleight-of-hand happens
before any modules see the value of <literal>q</literal>. Cool.
</para>
      <para>
Module initialization now happens via the <literal>module_init()&lt;code&gt;
function. This function runs &lt;code&gt;require_once()&lt;code&gt; on the
&lt;code&gt;admin</literal>, <literal>filter</literal>, <literal>system</literal>,
<literal>user</literal> and <literal>watchdog</literal> modules. The filter module defines
<literal>FILTER_HTML*</literal> and <literal>FILTER_STYLE*</literal> constants while being
included. Next, other modules are <literal>include_once</literal>'d via
<literal>module_list()</literal>. In order to be loaded, a module must (1) be enabled
(that is, the status column of the "system" database table must be set to 1),
and (2) Drupal's throttle mechanism must determine whether or not the module is
eligible for exclusion when load is high. First, it determines whether the
module is eligible by looking at the throttle column of the "system" database
table; then, if the module is eligible, it looks at
<literal>$conf["throttle_level"]</literal> to see whether the load is high enough to
exclude the module. Once all modules have been <literal>include_once</literal>'d and
their names added to the <literal>$list</literal> local array, the array is sorted by
module name and returned. The returned <literal>$list</literal> is discarded because
the <literal>module_list()</literal> invocation is not part of an assignment (e.g.,
it is simply <literal>module_list()</literal> and not <literal>$module_list =
module_list()</literal>). The strategy here is to keep the module list inside a
static variable called <literal>$list</literal> inside the <literal>module_list()</literal>
function. The next time <literal>module_list()</literal> is called, it will simply
return its static variable <literal>$list</literal> rather than rebuilding the whole
array. We see that as we follow the final objective of
<literal>module_init()</literal>; that is, to send all modules the "init" callback.
</para>
      <para>
To see how the callbacks work let's step through the init callback for the
first module. First <literal>module_invoke_all()</literal> is called and passed the
string enumerating which callback is to be called. This string could be
anything; it is simply a symbol that call modules have agreed to abide by, by
convention. In this case it is the string "init".
</para>
      <para>
The <literal>module_invoke_all()</literal> function now steps through the list of
modules it got from calling <literal>module_list()</literal>. The first one is
"<literal>admin</literal>", so it calls <literal>module_invoke("admin","init")</literal>.
The <literal>module_invoke()</literal> function simply puts the two together to get
the name of the function it will call. In this case the name of the function to
call is "<literal>admin_init()</literal>". If a function by this name exists, the
function is called and the returned result, if any, ends up in an array called
<literal>$return</literal> which is returned after all modules have been invoked. The
lesson learned here is that if you are writing a module and intend to return a
value from a callback, you must return it as an array. [Jonathan Chaffer:
<emphasis>Each "hook" (our word for what you call a callback) defines its own return
type. See <ulink url="http://drupaldocs.org/api/head/group/hooks">the full list of hooks available
to module developers</ulink>, with documentation about what they are expected to
return.</emphasis>]
</para>
      <para>
Back to <literal>common.inc</literal>. There is a check for suspicious input data. To
find out whether or not the user has permission to bypass this check,
<literal>user_access()</literal> is called. This retrieves the user's permissions and
stashes them in a static variable called <literal>$perm</literal>. Whether or not a
user has permission for a given action is determined by a simple substring
search for the name of the permission (e.g., "bypass input data check") within
the <literal>$perm</literal> string. Our <literal>$perm</literal> string, as an anonymous
user, is currently "0access content, ". Why the 0 at the beginning of the
string? Because <literal>$perm</literal> is initialized to 0 by
<literal>user_access()</literal>.
</para>
      <para>
The actual check for suspicious input data is carried out by
<literal>valid_input_data()</literal> which lives in <literal>common.inc</literal>. It
simply goes through an array it's been handed (in this case the
<literal>$_REQUEST</literal> array) and checks all keys and values for the following
"evil" strings: javascript, expression, alert, dynsrc, datasrc, data, lowsrc,
applet, script, object, style, embed, form, blink, meta, html, frame, iframe,
layer, ilayer, head, frameset, xml. If any of these are matched watchdog
records a warning and Drupal dies (in the PHP sense). I wondered why both the
keys and values of the <literal>$_REQUEST</literal> array are examined. This seems
very time-consuming. Also, would it die if my URL ended with
"<literal>/?xml=true</literal>" or "<literal>/?format=xml</literal>"?
</para>
      <para>
The next step in <literal>common.inc</literal>'s executable code is a call to
<literal>locale_init()</literal> to set up locale data. If the user is not an
anonymous user and has a language preference set up, the two-character language
key is returned; otherwise, the key of the single-entry global array
<literal>$language</literal> is returned. In our case, that's "en".
</para>
      <para>
The last gasp of <literal>common.inc</literal> is to call <literal>init_theme()</literal>.
You'd think that for consistency this would be called <literal>theme_init()</literal>
(of course, that would be a namespace clash with a callback of the same name).
This finds out which themes are available, which the user has selected, and
then <literal>include_once</literal>'s the chosen theme. If the user's selected theme
is not available, the value at <literal>$conf["theme_default"]</literal> is used. In
our case, we are an anonymous user with no theme selected, so the default
xtemplate theme is used. Thus, the file
<literal>themes/xtemplate/xtemplate.theme</literal> is <literal>include_once</literal>'d.
The inclusion of <literal>xtemplate.theme</literal> calls
<literal>include_once("themes/xtemplate/xtemplate.inc")</literal>, and creates a new
object called xtemplate as a global variable. Inside this object is an
xtemplate object called "<literal>template</literal>" with lots of attributes. Then
there is a nonfunctional line where <literal>SetNullBlock</literal> is called. A
comment indicates that someone is aware that this doesn't work.
</para>
      <para>
Now we're back to <literal>index.php</literal>! A call to
<literal>fix_gpc_magic()</literal> is in order. The "gpc" stands for Get, Post,
Cookie: the three places that unescaped quotes may be found. If deemed
necessary by the status of the boolean <literal>magic_quotes_gpc</literal> directive
in PHP's configuration file (<literal>php.ini</literal>), slashes will be stripped
from <literal>$_GET</literal>, <literal>$_POST</literal>, <literal>$_COOKIE</literal>, and
<literal>$_REQUEST</literal> arrays. It seems odd that the function is not called
<literal>fix_gpc_magic_quotes</literal>, since it is the "magic quotes" that are
being fixed, not the magic. In my distribution of PHP, the
<literal>magic_quotes_gpc</literal> directive is set to "Off", so slashes do not need
to be stripped.
</para>
      <para>
The next step is to set up menus. This step is crucial. The menu system doesn't
just handle displaying menus to the user, but also determines what function
will be handed the responsibility of displaying the page. The "<literal>q</literal>"
variable (we usually call the Drupal path) is matched against the available
menu items to find the appropriate callback to use. Much more information on
this topic is available in the <ulink url="http://drupaldocs.org/api/head/group/menu">menu system documentation for
developers</ulink>. We jump to <literal>menu_execute_active_handler()</literal> in
<literal>menu.inc</literal>. This sets up a <literal>$_menu</literal> array consisting of
items, local tasks, path index, and visible arrays. Then the system realizes
that we're not going to be building any menus for an anonymous user and bows
out. The real meat of the node creation and formatting happens here, but is
complex enough for a separate commentary. Back in <literal>index.php</literal>, the
switch statement doesn't match either case and we approach the last call in the
file, to <literal>drupal_page_footer</literal> in <literal>common.inc</literal>. This takes
care of caching the page we've built if caching is enabled (it's not) and calls
<literal>module_invoke_all()</literal> with the "exit" callback symbol.
</para>
      <para>
Although you may think we're done, PHP's session handler still needs to tidy
up. It calls <literal>sess_write()</literal> in <literal>session.inc</literal> to update
the session database table, then <literal>sess_close()</literal> which simply returns
1.
</para>
      <para>
We're done.
</para>
      <mediaobject>
        <imageobject>
          <imagedata fileref="file:///Users/djun/Projects/Drupal/projects/docbook/drupalwalk.png"/>
        </imageobject>
      </mediaobject>
  </chapter>
  <chapter id="node-551">
    <title>Creating modules - a tutorial</title>
      <para>
This tutorial describes how to create a module for Drupal 4.5.*. It is an
update to the tutorial for <xref linkend="wb_4721"/>. Please see
comments there, also.
</para>
      <para>
A module is a collection of functions that link into Drupal, providing
additional functionality to your Drupal installation. After reading this
tutorial, you will be able to create a basic block module and use it as a
template for more advanced modules and node modules.
</para>
      <para>
This tutorial will not necessarily prepare you to write modules for release
into the wild. It does not cover caching, nor does it elaborate on permissions
or security issues. Use this tutorial as a starting point, and review other
modules and the <xref linkend="wb_handbooks"/> and <xref linkend="wb_318"/>for more information.
</para>
      <para>
This tutorial assumes the following about you:
</para>
      <itemizedlist>
        <listitem>
          <para>Basic PHP knowledge, including syntax and the concept of PHP objects
</para>
        </listitem>
        <listitem>
          <para>Basic understanding of database tables, fields, records and SQL statements
</para>
        </listitem>
        <listitem>
          <para>A working Drupal installation
</para>
        </listitem>
        <listitem>
          <para>Drupal administration access
</para>
        </listitem>
        <listitem>
          <para>Webserver access
</para>
        </listitem>
      </itemizedlist>
      <para>
This tutorial does not assume you have any knowledge about the inner workings
of a Drupal module. This tutorial will not help you write modules for versions
of Drupal earlier than 4.5.
</para>
    <section id="node-540">
      <title>Getting started</title>
        <para>
To focus this tutorial, we'll start by creating a block module that lists links
to content such as blog entries or forum discussions that were created one week
ago. The full tutorial will teach us how to create block content, write links,
and retrieve information from Drupal nodes.
</para>
        <para>
Start your module by creating a PHP file and save it as 'onthisdate.module' in
the modules directory of your Drupal installation.
</para>
        <programlisting>&lt;?php
?&gt;</programlisting>
        <para>
As per the <xref linkend="wb_318"/>, use the longhand &lt;?php
tag, and not &lt;? to enclose your PHP code.
</para>
        <para>
All functions in your module are named {modulename}_{hook}, where "hook" is a
well defined function name. Drupal will call these functions to get specific
data, so having these well defined names means Drupal knows where to look.
</para>
    </section>
    <section id="node-541">
      <title>Letting Drupal know about the new function</title>
        <para>
As mentioned above, the function we just wrote isn't a 'hook': it's not a
Drupal recognized name. We need to tell Drupal how to access the function when
displaying a page. We do this with the menu() hook. The menu() hook defines the
association between a URL and the function that creates the content for that
url. The hook also does permission checking, if desired.
</para>
        <programlisting>&lt;?phpfunctiononthisdate_menu() {
&#xA0;&#xA0;$items=
array();
&#xA0;&#xA0;$items[]
= array('path'=&gt;'onthisdate',
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
'title'=&gt;t('on this
date'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
'callback'=&gt;'_onthisdate_all',
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
'access'=&gt;user_access('access content'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
'type'=&gt;MENU_CALLBACK);
&#xA0;&#xA0;return$items;
}?&gt;</programlisting>
        <para>
Basically, we're saying if the user goes to "onthisdate" (either via
?q=onthisdate or <ulink url="http://.../onthisdate">http://.../onthisdate</ulink>),
the content generated by onthisdate_all will be displayed. The title of the
page will be "on this date". The type MENU_CALLBACK Drupal to not display the
link in the user's menu, just use this function when the URL is accessed. Use
MENU_LOCAL_TASK if you want the user to see the link in the side navigation
block.
</para>
        <para>
Navigate to /onthisdate (or ?q=onthisdate) and see what you get.
</para>
    </section>
    <section id="node-542">
      <title>Telling Drupal about your module</title>
        <para>
The first function we'll write will tell Drupal information about your module:
its name and description. The hook name for this function is 'help', so start
with the onthisdate_help function:
</para>
        <programlisting>&lt;?phpfunctiononthisdate_help($section='')
{
}?&gt;</programlisting>
        <para>
The $section variable provides context for the help: where in Drupal or the
module are we looking for help. The recommended way to process this variable is
with a switch statement. You'll see this code pattern in other modules.
</para>
        <programlisting>&lt;?php/**
* Display help and module information
* @param section which section of the site we're displaying help
* @return help text for section
*/functiononthisdate_help($section='') {
&#xA0;&#xA0;$output='';
&#xA0;&#xA0;switch ($section) {
&#xA0;&#xA0;&#xA0;&#xA0;case"admin/modules#description":
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output=t("Displays links to nodes created on
this date");
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;}
&#xA0;&#xA0;return$output;
}// function
onthisdate_help?&gt;</programlisting>
        <para>
You will eventually want to add other cases to this switch statement to provide
real help messages to the user. In particular, output for
"admin/help#onthisdate" will display on the main help page accessed by the
admin/help URL for this module (/admin/help or ?q=admin/help).
</para>
    </section>
    <section id="node-543">
      <title>Telling Drupal who can use your module</title>
        <para>
The next function to write is the permissions function. The permissions
function doesn't grant permission, it just specifies what permissions are
available for this module. Access based on these permissions is defined later
in the {module}_access function below. At this point, we'll give permission to
anyone who can access site content or administrate the module:</para>
        <programlisting>&lt;?php/**
* Valid permissions for this module
* @return array An array of valid permissions for the onthisdate module
*/functiononthisdate_perm() {
&#xA0;&#xA0;return array('access
content');
}// function
onthisdate_perm()?&gt;</programlisting>
        <para>
Conversely, if you are going to write a module that needs to have finer control
over the permissions, and you're going to do permission control, you should
expand this permission set. You can do this by adding strings to the array that
is returned. For example:
</para>
        <programlisting>&lt;?phpfunctiononthisdate_perm() {
return array('access content','access onthisdate','administer
onthisdate');
}// function
onthisdate_perm?&gt;</programlisting>
        <para>
For this tutorial, start with the first one. We'll later move to the second
version.
</para>
        <para>
You'll need to adjust who has permission to view your module on the administer
&#xBB; accounts &#xBB; permissions page. We'll use the user_access function to check
access permissions later (whoa, so many "laters!").
</para>
        <para>
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.
</para>
    </section>
    <section id="node-544">
      <title>Announce we have block content</title>
        <para>
There are several types of modules: block modules and node modules are two.
Block modules create abbreviated content that is typically (but not always, and
not required to be) displayed along the left or right side of a page. Node
modules generate full page content (such as blog, forum, or book pages).
</para>
        <para>
We'll create a block content to start, and later discuss node content. A module
can generate content for blocks and also for a full page (the blogs module is a
good example of this). The hook for a block module is appropriately called
"block", so let's start our next function:
</para>
        <programlisting>&lt;?php/**
* Generate HTML for the onthisdate block
* @param op the operation from the URL
* @param delta offset
* @returns block HTML
*/functiononthisdate_block($op='list',$delta=0) {
}// end function
onthisdate_block?&gt;</programlisting>
        <para>
The block function takes two parameters: the operation and the offset, or
delta. We'll just worry about the operation at this point. In particular, we
care about the specific case where the block is being listed in the blocks
page. In all other situations, we'll display the block content.
</para>
        <para>
When the module will be listed on the blocks page, the $op parameter's value
will be 'list':
</para>
        <programlisting>&lt;?php/**
* Generate HTML for the onthisdate block
* @param op the operation from the URL
* @param delta offset
* @returns block HTML
*/functiononthisdate_block($op='list',$delta=0) {
&#xA0;&#xA0;// listing of blocks, such as on the
admin/block page
&#xA0;&#xA0;if ($op=="list") {
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$block[0]["info"] =t('On This
Date');
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;} else {
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;// our block
content
&#xA0;&#xA0;}
}// end onthisdate_block?&gt;</programlisting>
    </section>
    <section id="node-545">
      <title>Generate content for a block</title>
        <para>
Now, we need to generate the 'onthisdate' content for the block. Here we'll
demonstrate a basic way to access the database.
</para>
        <para>
Our goal is to get a list of content (stored as "nodes" in the database)
created a week ago. Specifically, we want the content created between midnight
and 11:59pm on the day one week ago. When a node is first created, the time of
creation is stored in the database. We'll use this database field to find our
data.
</para>
        <para>
First, we need to calculate the time (in seconds since epoch start, see
<ulink url="http://www.php.net/manual/en/function.time.php">http://www.php.net/manual/en/function.time.php</ulink>
for more information on time format) for midnight a week ago, and 11:59pm a
week ago. This part of the code is Drupal independent, see the PHP website
(<ulink url="http://php.net/">http://php.net/</ulink>) for more details.
</para>
        <programlisting>&lt;?php/**
* Generate HTML for the onthisdate block
* @param op the operation from the URL
* @param delta offset
* @returns block HTML
*/functiononthisdate_block($op='list',$delta=0) {
&#xA0;&#xA0;// listing of blocks, such as on the
admin/block page
&#xA0;&#xA0;if ($op=="list") {
&#xA0;&#xA0;&#xA0;&#xA0;$block[0]["info"]
=t('On This Date');
&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;} else {
&#xA0;&#xA0;&#xA0;&#xA0;// our block content
&#xA0;&#xA0;&#xA0;&#xA0;// Get today's date
&#xA0;&#xA0;&#xA0;&#xA0;$today=getdate();
&#xA0;&#xA0;&#xA0;&#xA0;// calculate midnight one
week ago
&#xA0;&#xA0;&#xA0;&#xA0;$start_time=mktime(0,0,0,
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$today['mon'], ($today['mday'] -7),$today['year']);
&#xA0;&#xA0;&#xA0;&#xA0;// we want items that
occur only on the day in question, so
&#xA0;&#xA0;&#xA0;&#xA0;// calculate 1 day
&#xA0;&#xA0;&#xA0;&#xA0;$end_time=$start_time+86400;
&#xA0;&#xA0;&#xA0;&#xA0;// 60 * 60 * 24 = 86400
seconds in a day
&#xA0;&#xA0;&#xA0;&#xA0;...
&#xA0;&#xA0;}
}?&gt;</programlisting>
        <para>
The next step is the SQL statement that will retrieve the content we'd like to
display from the database. We're selecting content from the node table, which
is the central table for Drupal content. We'll get all sorts of content type
with this query: blog entries, forum posts, etc. For this tutorial, this is
okay. For a real module, you would adjust the SQL statement to select specific
types of content (by adding the 'type' column and a WHERE clause checking the
'type' column).
</para>
        <para><emphasis role="bold">Note:</emphasis> the table name is enclosed in curly braces: <literal>{node}</literal>.
This is necessary so that your module will support database table name
prefixes. You can find more information on the Drupal website by reading the
<xref linkend="wb_2622"/> page
in the Drupal handbook.
</para>
        <programlisting>&lt;?php
$query="SELECT nid,
title, created FROM ".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"' AND created &lt;= '".$end_time."'";?&gt;</programlisting>
        <para>
Drupal uses database helper functions to perform database queries. This means
that, for the most part, you can write your database SQL statement and not
worry about the backend connections.
</para>
        <para>
We'll use db_query() to get the records (i.e. the database rows) that match our
SQL query, and db_fetch_object() to look at the individual records:
</para>
        <programlisting>&lt;?php
&#xA0;&#xA0;// get the links
&#xA0;&#xA0;$queryResult=&#xA0;&#xA0;db_query($query);
&#xA0;&#xA0;// content variable that will be
returned for display
&#xA0;&#xA0;$block_content='';
&#xA0;&#xA0;while ($links=db_fetch_object($queryResult)) {
&#xA0;&#xA0;&#xA0;&#xA0;$block_content.='&lt;a href="'.url('node/'.$links-&gt;nid)
.'"&gt;'.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$links-&gt;title.'&lt;/a&gt;&lt;br /&gt;';
&#xA0;&#xA0;}
&#xA0;&#xA0;// check to see if there was any
content before setting up
&#xA0;&#xA0;//&#xA0;&#xA0;the block
&#xA0;&#xA0;if ($block_content=='') {
&#xA0;&#xA0;&#xA0;&#xA0;/* No content from a week
ago.&#xA0;&#xA0;If we return nothing, the block
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;* doesn't show, which is what we want. */
&#xA0;&#xA0;&#xA0;&#xA0;return;
&#xA0;&#xA0;}
&#xA0;&#xA0;// set up the block
&#xA0;&#xA0;$block['subject']
='On This Date';
&#xA0;&#xA0;$block['content']
=$block_content;
&#xA0;&#xA0;return$block;
}?&gt;</programlisting>
        <para>
Notice the actual URL is enclosed in the l() function. <literal>l</literal> generates
&lt;a href="link"&gt; links, adjust the URL to the installation's URL
configuration of either clean URLS: <xref linkend="wb_2"/> or <xref linkend="wb_2"/></para>
        <para>
Also, we return an array that has 'subject' and 'content' elements. This is
what Drupal expects from a block function. If you do not include both of these,
the block will not render properly.
</para>
        <para>
You may also notice the bad coding practice of combining content with layout.
If you are writing a module for others to use, you will want to provide an easy
way for others (in particular, non-programmers) to adjust the content's layout.
An easy way to do this is to include a class attribute in your link, or
surround the HTML with a &lt;div&gt; tag with a module specific CSS class and
not necessarily include the &lt;br /&gt; at the end of the link. Let's ignore
this for now, but be aware of this issue when writing modules that others will
use.
</para>
        <para>
Putting it all together, our block function at this point looks like this:
</para>
        <programlisting>&lt;?phpfunctiononthisdate_block($op='list',$delta=0) {
&#xA0;&#xA0;// listing of blocks, such as on the
admin/block page
&#xA0;&#xA0;if ($op=="list") {
&#xA0;&#xA0;&#xA0;&#xA0;$block[0]["info"]
=t("On This Date");
&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;} else {
&#xA0;&#xA0;// our block content
&#xA0;&#xA0;&#xA0;&#xA0;// content variable that will be returned for
display
&#xA0;&#xA0;&#xA0;&#xA0;$block_content='';
&#xA0;&#xA0;&#xA0;&#xA0;// Get today's date
&#xA0;&#xA0;&#xA0;&#xA0;$today=getdate();
&#xA0;&#xA0;&#xA0;&#xA0;// calculate midnight one
week ago
&#xA0;&#xA0;&#xA0;&#xA0;$start_time=mktime(0,0,0,$today['mon'],
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;(
$today['mday'] -7),$today['year']);
&#xA0;&#xA0;&#xA0;&#xA0;// we want items that
occur only on the day in question, so
&#xA0;&#xA0;&#xA0;&#xA0;//calculate 1 day
&#xA0;&#xA0;&#xA0;&#xA0;$end_time=$start_time+86400;
&#xA0;&#xA0;&#xA0;&#xA0;// 60 * 60 * 24 = 86400
seconds in a day
&#xA0;&#xA0;&#xA0;&#xA0;$query="SELECT nid, title, created FROM
".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"' AND created &lt;= '".$end_time."'";
&#xA0;&#xA0;&#xA0;&#xA0;// get the links
&#xA0;&#xA0;&#xA0;&#xA0;$queryResult=&#xA0;&#xA0;db_query($query);
&#xA0;&#xA0;&#xA0;&#xA0;while ($links=db_fetch_object($queryResult)) {
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$block_content.='&lt;a href="'.url('node/'.$links-&gt;nid).'"&gt;'.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$links-&gt;title.'&lt;/a&gt;&lt;br /&gt;';
&#xA0;&#xA0;&#xA0;&#xA0;}
&#xA0;&#xA0;&#xA0;&#xA0;// check to see if there
was any content before setting up the block
&#xA0;&#xA0;&#xA0;&#xA0;if ($block_content=='') {
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;// no content
from a week ago, return nothing.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return;
&#xA0;&#xA0;&#xA0;&#xA0;}
&#xA0;&#xA0;&#xA0;&#xA0;// set up the block
&#xA0;&#xA0;&#xA0;&#xA0;$block['subject']
='On This Date';
&#xA0;&#xA0;&#xA0;&#xA0;$block['content']
=$block_content;
&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;}
}?&gt;</programlisting>
    </section>
    <section id="node-546">
      <title>Installing, enabling and testing the module</title>
        <para>
At this point, you can install your module and it'll work. Let's do that, and
see where we need to improve the module.
</para>
        <para>
To install the module, you'll need to copy your onthisdate.module file to the
modules directory of your Drupal installation. The file must be installed in
this directory or a subdirectory of the modules directory, and must have the
.module name extension.
</para>
        <para>
Log in as your site administrator, and navigate to the modules administration
page to get an alphabetical list of modules. In the menus: administer &#xBB;
modules, or via URL:
</para>
        <blockquote>
          <para>
            <programlisting>http://.../admin/modules</programlisting>
            <emphasis role="bold">or</emphasis>
            <programlisting>http://.../?q=admin/modules</programlisting>
          </para>
        </blockquote>
        <para>
When you scroll down, you'll see the onthisdate module listed with the
description next to it.
</para>
        <para>
Enable the module by selecting the checkbox and save your configuration.
</para>
        <para>
Because the module is a blocks module, we'll need to also enable it in the
blocks administration menu and specify a location for it to display. Node
modules may or may not need further configuration depending on the module. Any
module can have settings, which affect the functionality/display of a module.
We'll discuss settings later. For now, navigate to the blocks administration
page: <literal>admin/block</literal> or administer &#xBB; blocks in the menus.
</para>
        <para>
Enable the module by selecting the enabled checkbox for the 'On This Date'
block and save your blocks. Be sure to adjust the location (left/right) if you
are using a theme that limits where blocks are displayed.
</para>
        <para>
Now, head to another page, say, select the modules menu. In some themes, the
blocks are displayed after the page has rendered the content, and you won't see
the change until you go to new page.
</para>
        <para>
If you have content that was created a week ago, the block will display with
links to the content. If you don't have content, you'll need to fake some data.
You can do this by creating a blog, forum topic or book page, and adjust the
"Authored on:" date to be a week ago.
</para>
        <para>
Alternately, if your site has been around for a while, you may have a lot of
content created on the day one week ago, and you'll see a large number of links
in the block.
</para>
    </section>
    <section id="node-547">
      <title>Create a module configuration (settings) page</title>
        <para>
Now that we have a working module, we'd like to make it better. If we have a
site that has been around for a while, content from a week ago might not be as
interesting as content from a year ago. Similarly, if we have a busy site, we
might not want to display all the links to content created last week. So, let's
create a configuration page for the administrator to adjust this information.
</para>
        <para>
A module's configuration is set up with the 'settings' hook. We would like only
administrators to be able to access this page, so we'll do our first
permissions check of the module here:
</para>
        <programlisting>&lt;?php/**
* Module configuration settings
* @return settings HTML or deny access
*/functiononthisdate_settings() {
&#xA0;&#xA0;// only administrators can access this
module
&#xA0;&#xA0;if (!user_access("admin onthisdate")) {
&#xA0;&#xA0;&#xA0;&#xA0;returnmessage_access();
&#xA0;&#xA0;}
}?&gt;</programlisting>
        <para>
If you want to tie your modules permissions to the permissions of another
module, you can use that module's permission string. The "access content"
permission is a good one to check if the user can view the content on your
site:
</para>
        <programlisting>&lt;?php
&#xA0;&#xA0;...
&#xA0;&#xA0;// check the user has content
access
&#xA0;&#xA0;if (!user_access("access content")) {
&#xA0;&#xA0;&#xA0;&#xA0;returnmessage_access();
&#xA0;&#xA0;}
&#xA0;&#xA0;...?&gt;</programlisting>
        <para>
We'd like to configure how many links display in the block, so we'll create a
form for the administrator to set the number of links:
</para>
        <programlisting>&lt;?phpfunctiononthisdate_settings()
{
&#xA0;&#xA0;// only administrators can access this
module
&#xA0;&#xA0;if (!user_access("admin onthisdate")) {
&#xA0;&#xA0;&#xA0;&#xA0;returnmessage_access();
&#xA0;&#xA0;}
&#xA0;&#xA0;$output.=form_textfield(t("Maximum number of
links"),"onthisdate_maxdisp",
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
variable_get("onthisdate_maxdisp","3"),2,2,
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
t("The maximum number
of links to display in the block."));&#xA0;&#xA0;return$output;}?&gt;</programlisting>
        <para>
This function uses several powerful Drupal form handling features. We don't
need to worry about creating an HTML text field or the form, as Drupal will do
so for us. We use <literal>variable_get</literal> to retrieve the value of the system
configuration variable "onthisdate_maxdisp", which has a default value of 3. We
use the form_textfield function to create the form and a text box of size 2,
accepting a maximum length of 2 characters. We also use the translate function
of t(). There are other form functions that will automatically create the HTML
form elements for use. For now, we'll just use the form_textfield function.
</para>
        <para>
Of course, we'll need to use the configuration value in our SQL SELECT, so
we'll need to adjust our query statement in the onthisdate_block function:
</para>
        <programlisting>&lt;?php
&#xA0;&#xA0;$limitnum=variable_get("onthisdate_maxdisp",3);
&#xA0;&#xA0;$query="SELECT nid, title, created FROM
".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"' AND created &lt;= '".$end_time."' LIMIT ".$limitnum;?&gt;</programlisting>
        <para>
You can test the settings page by editing the number of links displayed and
noticing the block content adjusts accordingly.
</para>
        <para>
Navigate to the settings page: admin/modules/onthisdate or
administer &#xBB; configuration &#xBB; modules &#xBB; onthisdate. Adjust the number of links
and save the configuration. Notice the number of links in the block adjusts
accordingly.
</para>
        <para><emphasis role="bold">Note:</emphasis>We don't have any validation with this input. If you enter "c" in
the maximum number of links, you'll break the block.
</para>
    </section>
    <section id="node-548">
      <title>Adding menu links and creating page content</title>
        <para>
So far we have our working block and a settings page. The block displays a
maximum number of links. However, there may be more links than the maximum we
show. So, let's create a page that lists all the content that was created a
week ago.
</para>
        <programlisting>&lt;?phpfunctiononthisdate_all()
{}?&gt;</programlisting>
        <para>
We're going to use much of the code from the block function. We'll write this
ExtremeProgramming style, and duplicate the code. If we need to use it in a
third place, we'll refactor it into a separate function. For now, copy the code
to the new function _onthisdate_all(). Contrary to all our other functions,
'all', in this case, is not a Drupal hook. In our code, we can prefix this
function with an underscore to help us remember this isn't a hook call. We'll
discuss below.
</para>
        <programlisting>&lt;?phpfunction_onthisdate_all() {
&#xA0;&#xA0;// content variable that will be
returned for display
&#xA0;&#xA0;$page_content='';
&#xA0;&#xA0;// Get today's date
&#xA0;&#xA0;$today=getdate();
&#xA0;&#xA0;// calculate midnight one week
ago
&#xA0;&#xA0;$start_time=mktime(0,0,0,
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$today['mon'], ($today['mday'] -7),$today['year']);
&#xA0;&#xA0;// we want items that occur only on
the day in question,
&#xA0;&#xA0;// so calculate 1 day
&#xA0;&#xA0;$end_time=$start_time+86400;
&#xA0;&#xA0;// 60 * 60 * 24 = 86400 seconds in a
day
&#xA0;&#xA0;// NOTE!&#xA0;&#xA0;No LIMIT clause here!&#xA0;&#xA0;We want to
show all the code
&#xA0;&#xA0;$query="SELECT nid, title, created FROM
".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"' AND created &lt;= '".$end_time."'";
&#xA0;&#xA0;// get the links
&#xA0;&#xA0;$queryResult=&#xA0;&#xA0;db_query($query);
&#xA0;&#xA0;while ($links=db_fetch_object($queryResult)) {
&#xA0;&#xA0;&#xA0;&#xA0;$page_content.='&lt;a
href="'.url('node/'.$links-&gt;nid).'"&gt;'.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$links-&gt;title.'&lt;/a&gt;&lt;br /&gt;';
&#xA0;&#xA0;}
&#xA0;&#xA0;...
}?&gt;</programlisting>
        <para>
We have the page content at this point, but we want to do a little more with it
than just return it. When creating pages, we need to send the page content to
the theme for proper rendering. We use this with the theme() function. Themes
control the look of a site. As noted above, we're including layout in the code.
This is bad, and should be avoided. It is, however, the topic of another
tutorial, so for now, we'll include the formatting in our content:
</para>
        <programlisting>&lt;?phpprinttheme("page",$content_string);?&gt;</programlisting>
        <para>
The rest of our function checks to see if there is content and lets the user
know. This is preferable to showing an empty or blank page, which may confuse
the user.
</para>
        <para>
Note that we are responsible for outputting the page content with the 'print
theme()' syntax.
</para>
        <programlisting>&lt;?phpfunction_onthisdate_all() {
&#xA0;&#xA0;...
&#xA0;&#xA0;// check to see if there was any
content before
&#xA0;&#xA0;// setting up the block
&#xA0;&#xA0;if ($page_content=='') {
&#xA0;&#xA0;&#xA0;&#xA0;// no content from a week
ago, let the user know
&#xA0;&#xA0;&#xA0;&#xA0;printtheme("page",
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"No events occurred on this site on this date in history.");
&#xA0;&#xA0;&#xA0;&#xA0;return;
&#xA0;&#xA0;}
&#xA0;&#xA0;printtheme("page",$page_content);
}?&gt;</programlisting>
    </section>
    <section id="node-549">
      <title>Adding a 'more' link and showing all entries</title>
        <para>
Because we have our function that creates a page with all the content created a
week ago, we can link to it from the block with a "more" link.
</para>
        <para>
Add these lines just before that $block['subject'] line, adding this to the
$block_content variable before saving it to the $block['content'] variable:
</para>
        <programlisting>&lt;?php// add a more link to our
page that displays all the links
&#xA0;&#xA0;$block_content.=
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"&lt;div class=\"more-link\"&gt;".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;l(t("more"),"onthisdate", array("title"=&gt;t("More
events on this day.")))
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;."&lt;/div&gt;";?&gt;</programlisting>
        <para>
This will add the more link.
</para>
    </section>
    <section id="node-550">
      <title>Conclusion</title>
        <para>
We now have a working module. It created a block and a page. You should now
have enough to get started writing your own modules. We recommend you start
with a block module of your own and move onto a node module. Alternately, you
can write a filter or theme.
</para>
        <para>
As is, this tutorial's module isn't very useful. However, with a few
enhancements, it can be entertaining. Try modifying the select query statement
to select only nodes of type 'blog' and see what you get. Alternately, you
could get only a particular user's content for a specific week. Instead of
using the block function, consider expanding the menu and page functions,
adding menus to specific entries or dates, or using the menu callback arguments
to adjust what year you look at the content from.
</para>
        <para>
If you start writing modules for others to use, you'll want to provide more
details in your code. Comments in the code are incredibly valuable for other
developers and users in understanding what's going on in your module. You'll
also want to expand the help function, providing better help for the user.
Follow the Drupal [Coding standards], especially if you're going to add your
module to the project.
</para>
        <para>
Two topics very important in module development are writing themeable pages and
writing translatable content. Please check the <xref linkend="wb_handbook"/> for more details on these two subjects.
</para>
    </section>
  </chapter>
  <chapter id="node-590">
    <title>Updating your modules</title>
      <para>
As Drupal develops with each release it becomes necessary to update modules to
take advantage of new features and stay functional with Drupal's API.
</para>
    <section id="node-552">
      <title>Converting 3.0 modules to 4.0</title>
        <para>
Converting modules from version 3.0 to version 4.0 standards requires rewriting
the <literal>form()</literal> function, as follows:
</para>
        <para>
          <emphasis role="bold">Drupal 3.0:</emphasis>
        </para>
        <programlisting>
function form($action, <emphasis role="bold">$form</emphasis>, $method = "post", $options = 0)

// Example

global $REQUEST_URI;
$form = form_hidden("nid", $nid);
print form($REQUEST_URI, $form);
</programlisting>
        <para>
          <emphasis role="bold">Drupal 4.0:</emphasis>
        </para>
        <programlisting>
function form(<emphasis role="bold">$form</emphasis>, $method = "post", $action = 0, $options = 0)

// Example

$form = form_hidden("nid", $nid);
print form($form);
</programlisting>
    </section>
    <section id="node-555">
      <title>Converting 4.0 modules to 4.1</title>
        <para>
Drupal 4.1 changed the block hook function and taxonomy API. To convert a
version 4.0 module to 4.1, the following changes must be made. First, the
<literal>*_block()</literal> function must be re-written. Next, calls to
<literal>taxonomy_get_tree()</literal> must be re-written to supply the parameters
required by the new function. Finally, you may wish to take advantage of new
functions added to the taxonomy API.
</para>
      <section id="node-553">
        <title>Required changes</title>
          <variablelist>
            <varlistentry>
              <term>
                <emphasis role="bold">Modified block hook:</emphasis>
              </term>
              <listitem>
		<para>
                <emphasis role="bold">Drupal 4.0:</emphasis>
                <programlisting>
function *_block() {
  $blocks[0]["info"] = "First block info";
  $blocks[0]["subject"] = "First block subject";
  $blocks[0]["content"] = "First block content";

  $blocks[1]["info"] = "Second block info";
  $blocks[1]["subject"] = "Second block subject";
  $blocks[1]["content"] = "Second block content";

  // return array of blocks
  return $blocks;
  }
}
</programlisting>
                  <emphasis role="bold">Drupal 4.1:</emphasis>
                <programlisting>
function *_block($op = "list", $delta = 0) {
  if ($op == "list") {
    $blocks[0]["info"] = "First block info";
    $blocks[1]["info"] = "Second block info";
    return $blocks; // return array of block infos
  }
  else {
    switch($delta) {
      case 0:
        $block["subject"] = "First block subject";
        $block["content"] = "First block content";
        return $block;  
      case 1:
        $block["subject"] = "Second block subject";
        $block["content"] = "Second block content";
        return $block;
    }
  }
}
</programlisting>
	      </para>
              </listitem>
            </varlistentry>
            <varlistentry>
              <term>
                <emphasis role="bold">Modified taxonomy API:</emphasis>
              </term>
              <listitem>
                <para>
Changes: in function taxonomy_get_tree()
</para>
                <itemizedlist>
                  <listitem>
                    <para>there is no longer a "parent" property; rather "parents" is an array
</para>
                  </listitem>
                  <listitem>
                    <para>the result tree is now returned instead of being passed by reference
</para>
                  </listitem>
                </itemizedlist>
                <para>
                  <emphasis role="bold">Drupal 4.0:</emphasis>
                </para>
                <programlisting>
function taxonomy_get_tree($vocabulary_id, &amp;$tree, $parent = 0, $depth = -1, $key = "tid")
</programlisting>
                <para>
                  <emphasis role="bold">Drupal 4.1:</emphasis>
                </para>
                <programlisting><emphasis role="bold">$tree =</emphasis> taxonomy_get_tree($vocabulary_id, <emphasis role="bold">$parents</emphasis> = 0, $depth = -1, $key = "tid")
</programlisting>
              </listitem>
            </varlistentry>
          </variablelist>
      </section>
      <section id="node-554">
        <title>Optional changes</title>
          <itemizedlist>
            <listitem>
              <para>Take advantage of new taxonomy functions
<literal>taxonomy_get_vocabulary_by_name($name)</literal> and
<literal>taxonomy_get_term_by_name($name)</literal></para>
            </listitem>
            <listitem>
              <para>Take advantage of pager functions
</para>
            </listitem>
            <listitem>
              <para>Move hardcoded markup from modules to themes, using <ulink url="http://drupal.org/node/view/608">theme_invoke</ulink></para>
            </listitem>
          </itemizedlist>
      </section>
    </section>
    <section id="node-556">
      <title>Converting 4.1 modules to 4.2</title>
        <para>
Some points <ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-February/022183.html">posted
by Axel</ulink> on <ulink url="http://drupal.org/node.php?id=322">drupal-devel</ulink> on
migrating 4.1.0 modules to CVS [updated and added to by ax]:
</para>
        <itemizedlist>
          <listitem>
            <para>the big "<ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-January/020973.html">clean
URL</ulink>" patch: Over the weekend, [dries] bit the bullet and converted every
single URL in Drupal's code. meaning we'll [can] have clean URLs like <ulink url="http://foo.com/archive/2003/01/06">http://foo.com/archive/2003/01/06</ulink>,
<ulink url="http://foo.com/user/42">http://foo.com/user/42</ulink>, <ulink url="http://foo.com/blog">http://foo.com/blog</ulink>, and so on.. meaning, for the
code:
<itemizedlist><listitem><para><literal>drupal_url(array("mod" =&gt; "search", "op" =&gt; "bla"), "module"[,
$anchor = ""])</literal>
&#xA0;&#xA0;became<literal>url("search/bla")</literal>,
&#xA0;&#xA0;with the first url part being the module, the second (typically)
being the operation ($op); more arguments are handled differently per module
convention.
</para></listitem><listitem><para><literal>l("view node", array("op" =&gt; "view", "id" =&gt; $nid), "node"[,
$anchor = "", $attributes = array()])</literal>
&#xA0;&#xA0;became<literal>l("view node", "node/view/$nid"[,$attributes = array(), $query =
NULL])</literal></para></listitem><listitem><para>&#xA0;&#xA0;similar,<literal>lm()</literal>, which meant "module link" and used to be
<literal>module.php?mod=bla&amp;op=blub...</literal>, is now <literal>l("title",
"bla/blub/..."</literal>); and<literal>la()</literal>, which meant "admin link" and used to be
<literal>admin.php?mod=bla&amp;op=blub...</literal>, is now <literal>l("title",
"admin/bla/blub/..."</literal></para></listitem><listitem><para>After fixing those functions, you'll need to edit your _page() function and
possibly others so that they get their arguments using the arg() function (see
includes/common.inc. These arguments used to be globals called "mod", "op",
"id" etc. now these same arguments must be accessed as arg(1), arg(3), for
example.
</para></listitem></itemizedlist></para>
          </listitem>
          <listitem>
            <para><literal>$theme-&gt;function()</literal> became <literal>theme("function")</literal>. see
<ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-February/021824.html">[drupal-devel]
renaming 2 functions</ulink>, <ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-February/021927.html">[drupal-devel]
theme("function") vs $theme-&gt;function()</ulink> and <ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-February/021936.html">[drupal-devel]
[CVS] theme()</ulink></para>
          </listitem>
          <listitem>
            <para><literal>&amp;lt;module&amp;gt;_conf_options()</literal> became
<literal>&amp;lt;module&amp;gt;_settings()</literal> - see <ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-February/021824.html">[drupal-devel]
renaming 2 functions</ulink>. note that doesn't get an extra menu entry, but
is accessed via "site configuration &gt; modules &gt; modules settings"
</para>
          </listitem>
          <listitem>
            <para>
the administration pages got changed quite a lot to use a "database driven link
system" and become more logical/intuitive - see <ulink url="http://lists.drupal.org/pipermail/drupal-devel/2002-December/020726.html">[drupal-devel]
X-mas commit: administration pages</ulink>. this first try resulted in poor
performance and a not-so-good api, so it got refactored - see <ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-February/022134.html">[PATCH]
menus</ulink>. this, as of time ax is writing this, isn't really satisfying,
neither (you cannot build arbitrary menu-trees, some forms don't work (taxonomy
&gt; add term), ...), so it probably will change again. and i won't write more
about this here.
</para>
            <para>
well, this: you use <literal>menu()</literal> to add entries to the admin menu.
<literal>menu("admin/node/nodes/0", "new or updated posts", "node_admin", "help",
0);</literal> adds a menu entry "new or updated posts" 1 level below "post
overview" (admin/node/nodes) and 2 level below "node management" (admin/node)
(ie. at the 3. level), with a weight of 0 in the 3. level, with a line "help"
below the main heading. for the callback ("node_admin") ... ask dries or zbynek
</para>
            <para>
one more note, though: you do not add
<literal>&amp;lt;module&amp;gt;_settings()</literal> to the menu (they automatically
go to "site configuration &gt; modules &gt; module settings" - you only add
<literal>&amp;lt;module&amp;gt;_admin...()</literal> ... things.
</para>
          </listitem>
          <listitem>
            <para>[from <ulink url="http://drupal.org/node/view/4230#6570">comment_is_new
function lost</ulink>]
<programlisting>
-  comment_is_new($comment)
+  node_is_new($comment-&gt;nid, $comment-&gt;timestamp)
</programlisting></para>
          </listitem>
        </itemizedlist>
        <para>
please add / update / correct!
</para>
    </section>
    <section id="node-570">
      <title>Converting 4.2 modules to 4.3</title>
        <variablelist>
          <varlistentry>
            <term>Database table prefix</term>
            <listitem>
	      <para>
On 2003 Jul 10, Dries <ulink url="http://drupal.org/node/view/805">committed</ulink>
Slavica's table prefix patch which allows for a configurable "prefix to each drupal mysql table to easily
share one database for multiply applications on server with only one database
allowed." This patch requires all table names in SQL-queries to be enclosed
in {curly brackets}, eg.
<programlisting>
-  db_query("DELETE FROM book WHERE nid = %d", $node-&gt;nid);
+  db_query("DELETE FROM {book} WHERE nid = %d", $node-&gt;nid);
</programlisting>
so that the table prefix can be dynamically prepended to the table name. See
the <ulink url="http://drupal.org/node/view/805">original feature request</ulink> and
the <ulink url="http://lists.drupal.org/pipermail/drupal-devel/2003-July/027145.html">corresponding
discussion at the mailing list</ulink> for details.
	      </para>
	    </listitem>
          </varlistentry>
          <varlistentry>
            <term>New help system</term>
            <listitem>

	      <para>From <ulink url="http://lists.drupal.org/archives/drupal-devel/2003-10/msg00519.html">Michael Frankowski</ulink> message:

		<blockquote>
		  <para>There is a block of text placed at the top of each
admin page by the admin_page function. After 4.3.0 is out the door the
function<literal>menu_get_active_help()</literal> should probably be
renamed/moved into the help module and be attached -- somehow -- to
every <literal>_page</literal> hook (probably in the node module) so
that we can use this system through out Drupal but for now, there is a
block of text displayed at the top of every admin page.  This is the
active help block. (context sensitive help?)
		</para>

		<para>If the URL of the admin page matches a URL in
a <literal>_help</literal> hook then the text from
that <literal>_help</literal> hook is displayed on the top of the
admin page. If there is no match, the block it not displayed. Because
Drupal matches URLs in order to stick "other" stuff in
the <literal>_help</literal> hook we have taken to sticking
descriptors after a "#" sign. So far, the following descriptors are
recognised:
		</para>

		<informaltable>
		  <tgroup cols="2">
		    <colspec colnum="1" colname="col1"/>
		    <colspec colnum="2" colname="col2"/>
		    <thead>
		      <row>
			<entry> Descriptor</entry>
			<entry>Function</entry>
		      </row>
		    </thead>
		    <tbody>
		      <row>
			<entry>admin/system/modules#name</entry>
			<entry>The name of a module (unused, but there)</entry>
		      </row>
		      <row>
			<entry>admin/system/modules#description</entry>
			<entry>The description found on the admin/system/modules page.</entry>
		      </row>
		      <row>
			<entry>admin/help#modulename</entry>
			<entry>The module's help text, displayed on the admin/help page and through themodule's individual help link.</entry>
		      </row>
		      <row>
			<entry>user/help#modulename</entry>
			<entry>The help for a distrbuted authorization module</entry>
		      </row>
		    </tbody>
		  </tgroup>
		</informaltable>

<para>
In the future we will probably recognise #block for the text needed
in a block displayed by the help system.
</para>
	      </blockquote>
		</para>
	      </listitem>

          </varlistentry>
        </variablelist>
      <section id="node-566">
        <title>Creating modules for version 4.3.1</title>

          <para>
This tutorial describes how to create a module for Drupal-CVS
(i.e. Drupal version &gt; 4.3.1). A module is a collection of
functions that link into Drupal, providing additional functionality to
your Drupal installation. After reading this tutorial, you will be
able to create a basic block module and use it as a template for more
advanced modules and node modules.
</para>

          <para>
This tutorial will not necessarily prepare you to write modules for
release into the wild. It does not cover caching, nor does it elaborate
on permissions or security issues. Use this tutorial as a starting
point, and review other modules and the [Drupal handbook] and [Coding
standards] for more information.
</para>
          <para>
This tutorial assumes the following about you:
</para>
          <itemizedlist>
            <listitem>
              <para>Basic PHP knowledge, including syntax and the concept of PHP objects
</para>
            </listitem>
            <listitem>
              <para>Basic understanding of database tables, fields, records and SQL statements
</para>
            </listitem>
            <listitem>
              <para>A working Drupal installation
</para>
            </listitem>
            <listitem>
              <para>Drupal administration access
</para>
            </listitem>
            <listitem>
              <para>Webserver access
</para>
            </listitem>
          </itemizedlist>
          <para>
This tutorial does not assume you have any knowledge about the inner
workings of a Drupal module. This tutorial will not help you write
modules for Drupal 4.3.1 or before.
</para>
        <section id="node-557">
          <title>Getting Started</title>
            <para>
To focus this tutorial, we'll start by creating a block module that
lists links to content such as blog entries or forum discussions that
were created one week ago. The full tutorial will teach us how to
create block content, write links, and retrieve information from Drupal
nodes.
</para>
            <para>
Start your module by creating a PHP file and save it as 'onthisdate.module'.
</para>
            <programlisting>&lt;?php
?&gt;</programlisting>
            <para>
As per the [Coding standards], use the longhand &lt;?php tag,
and not &lt;? to enclose your PHP code.
</para>
            <para>
All functions in your module are named {modulename}_{hook}, where
"hook" is a well defined function name. Drupal will call these
functions to get specific data, so having these well defined names means
Drupal knows where to look.
</para>
        </section>
        <section id="node-558">
          <title>Telling Drupal about your module</title>
            <para>
The first function we'll write will tell Drupal information about your
module: its name and description. The hook name for this function is
'help', so start with the onthisdate_help function:
</para>
            <programlisting>&lt;?phpfunctiononthisdate_help($section)
{
}?&gt;</programlisting>
            <para>
The $section variable provides context for the help: where in Drupal or
the module are we looking for help. The recommended way to process this
variable is with a switch statement. You'll see this code pattern in
other modules.
</para>
            <programlisting>&lt;?php/* Commented out until bug
fixed */
/*
function onthisdate_help($section) {
&#xA0;&#xA0;switch($section) {
&#xA0;&#xA0;&#xA0;&#xA0;case "admin/system/modules#name":
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output = "onthisdate";
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;&#xA0;&#xA0;case "admin/system/modules#description":
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output = "Display a list of nodes that
were created a week ago.";
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;&#xA0;&#xA0;default:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output = "onthisdate";
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;}
&#xA0;&#xA0;return $output;
}
*/?&gt;</programlisting>
            <para>
You will eventually want to add other cases to this switch statement to
provide real help messages to the user. In particular, output for
"admin/help#onthisdate" will display on the main help page accessed by
the admin/help URL for this module (/admin/help or ?q=admin/help).
</para>
            <para><emphasis role="bold">Note:</emphasis>This function is commented out in the above code. This is
on purpose, as the current version of Drupal CVS won't display the
module name, and won't enable it properly when installed. Until this
bug is fixed, comment out your help function, or your module may not
work.
</para>
        </section>
        <section id="node-559">
          <title>Telling Drupal who can use your module</title>
            <para>
The next function to write is the permissions function. Here, you can
tell Drupal who can access your module. At this point, give permission
to anyone who can access site content or administrate the module.
</para>
            <programlisting>&lt;?phpfunctiononthisdate_perm() {
&#xA0;&#xA0;return array("administer
onthisdate");
}?&gt;</programlisting>
            <para>
If you are going to write a module that needs to have finer control over
the permissions, and you're going to do permission control, you may want
to define a new permission set. You can do this by adding strings to
the array that is returned:
</para>
            <programlisting>&lt;?phpfunctiononthisdate_perm() {
&#xA0;&#xA0;return array("access
onthisdate","administer onthisdate");
}?&gt;</programlisting>
            <para>
You'll need to adjust who has permission to view your module on the
administer &#xBB; accounts &#xBB; permissions page. We'll use the user_access
function to check access permissions <xref linkend="settings"/>.
</para>
            <para>
Be sure your permission strings must be unique to your module. If they
are not, the permissions page will list the same permission multiple
times.
</para>
        </section>
        <section id="node-560">
          <title>Announce we have block content</title>
            <para>
There are several types of modules: block modules and node modules are
two. Block modules create abbreviated content that is typically (but
not always, and not required to be) displayed along the left or right
side of a page. Node modules generate full page content (such as blog,
forum, or book pages).
</para>
            <para>
We'll create a block content to start, and later discuss node content.
A module can generate content for blocks and also for a full page (the
blogs module is a good example of this). The hook for a block module is
appropriately called "block", so let's start our next function:
</para>
            <programlisting>&lt;?phpfunctiononthisdate_block($op='list',$delta=0) {
}?&gt;</programlisting>
            <para>
The block function takes two parameters: the operation and the offset,
or delta. We'll just worry about the operation at this point. In
particular, we care about the specific case where the block is being
listed in the blocks page. In all other situations, we'll display the
block content.
</para>
            <programlisting>&lt;?phpfunctiononthisdate_block($op='list',$delta=0) {
&#xA0;&#xA0;// listing of blocks, such as on the
admin/system/block page
&#xA0;&#xA0;if ($op=="list") {
&#xA0;&#xA0;&#xA0;&#xA0;$block[0]["info"]
=t("On This Date");
&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;} else {
&#xA0;&#xA0;// our block content
&#xA0;&#xA0;}
}?&gt;</programlisting>
        </section>
        <section id="node-561">
          <title>Generate content for a block</title>
            <para>
Now, we need to generate the 'onthisdate' content for the block. In
here, we'll demonstrate a basic way to access the database.
</para>
            <para>
Our goal is to get a list of content (stored as "nodes" in the database)
created a week ago. Specifically, we want the content created between
midnight and 11:59pm on the day one week ago. When a node is first
created, the time of creation is stored in the database. We'll use this
database field to find our data.
</para>
            <para>
First, we need to calculate the time (in seconds since epoch start, see<ulink url="http://www.php.net/manual/en/function.time.php">http://www.php.net/manual/en/function.time.php</ulink>
for more information on
time format) for midnight a week ago, and 11:59pm a week ago. This part
of the code is Drupal independent, see the PHP website (<ulink url="http://php.net/">http://php.net/</ulink>)
for more details.
</para>
            <programlisting>&lt;?phpfunctiononthisdate_block($op='list',$delta=0) {
&#xA0;&#xA0;// listing of blocks, such as on the
admin/system/block page
&#xA0;&#xA0;if ($op=="list") {
&#xA0;&#xA0;&#xA0;&#xA0;$block[0]["info"]
=t("On This Date");
&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;} else {
&#xA0;&#xA0;// our block content
&#xA0;&#xA0;&#xA0;&#xA0;// Get today's date
&#xA0;&#xA0;&#xA0;&#xA0;$today=getdate();
&#xA0;&#xA0;&#xA0;&#xA0;// calculate midnight one
week ago
&#xA0;&#xA0;&#xA0;&#xA0;$start_time=mktime(0,0,0,
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$today['mon'], ($today['mday'] -7),$today['year']);
&#xA0;&#xA0;&#xA0;&#xA0;// we want items that
occur only on the day in question, so calculate 1 day
&#xA0;&#xA0;&#xA0;&#xA0;$end_time=$start_time+86400;&#xA0;&#xA0;// 60 * 60 * 24 = 86400
seconds in a day
&#xA0;&#xA0;&#xA0;&#xA0;...
&#xA0;&#xA0;}
}?&gt;</programlisting>
            <para>
The next step is the SQL statement that will retrieve the content we'd
like to display from the database. We're selecting content from the
node table, which is the central table for Drupal content. We'll get
all sorts of content type with this query: blog entries, forum posts,
etc. For this tutorial, this is okay. For a real module, you would
adjust the SQL statement to select specific types of content (by adding
the 'type' column and a WHERE clause checking the 'type' column).
</para>
            <para><emphasis role="bold">Note:</emphasis> the table name is enclosed in curly braces:
<literal>{node}</literal>.
This is necessary so that your module will support database table name
prefixes. You can find more information on the Drupal website by
reading the [Table Prefix (and sharing tables across instances)] page in
the Drupal handbook.
</para>
            <programlisting>&lt;?php
&#xA0;&#xA0;$query="SELECT nid, title, created FROM ".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"' AND created &lt;= '".$end_time."'";?&gt;</programlisting>
            <para>
Drupal uses database helper functions to perform database queries. This
means that, for the most part, you can write your database SQL statement
and not worry about the backend connections.
</para>
            <para>
We'll use db_query() to get the records (i.e. the database rows) that
match our SQL query, and db_fetch_object() to look at the individual
records:
</para>
            <programlisting>&lt;?php
&#xA0;&#xA0;// get the links
&#xA0;&#xA0;$queryResult=&#xA0;&#xA0;db_query($query);
&#xA0;&#xA0;// content variable that will be
returned for display
&#xA0;&#xA0;$block_content='';
&#xA0;&#xA0;while ($links=db_fetch_object($queryResult)) {
&#xA0;&#xA0;&#xA0;&#xA0;$block_content.='&lt;a href="'.url('node/view/'.$links-&gt;nid)
.'"&gt;'.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$links-&gt;title.'&lt;/a&gt;&lt;br /&gt;';
&#xA0;&#xA0;}
&#xA0;&#xA0;// check to see if there was any
content before setting up the block
&#xA0;&#xA0;if ($block_content=='') {
&#xA0;&#xA0;&#xA0;&#xA0;/* No content from a week
ago.&#xA0;&#xA0;If we return nothing, the block
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;* doesn't show, which is what we want. */
&#xA0;&#xA0;&#xA0;&#xA0;return;
&#xA0;&#xA0;}
&#xA0;&#xA0;// set up the block
&#xA0;&#xA0;$block['subject']
='On This Date';
&#xA0;&#xA0;$block['content']
=$block_content;
&#xA0;&#xA0;return$block;
}?&gt;</programlisting>
            <para>
Notice the actual URL is enclosed in the url() function. This adjusts
the URL to the installations URL configuration of either clean URLS:<ulink url="http://sitename/node/view/2">http://sitename/node/view/2</ulink> or
<ulink url="http://sitename/?q=node/view/2">http://sitename/?q=node/view/2</ulink></para>
            <para>
Also, we return an array that has 'subject' and 'content' elements.
This is what Drupal expects from a block function. If you do not
include both of these, the block will not render properly.
</para>
            <para>
You may also notice the bad coding practice of combining content with
layout. If you are writing a module for others to use, you will want to
provide an easy way for others (in particular, non-programmers) to
adjust the content's layout. An easy way to do this is to include a
class attribute in your link, and not necessarily include the &lt;br
/&gt; at the end of the link. Let's ignore this for now, but be aware
of this issue when writing modules that others will use.
</para>
            <para>
Putting it all together, our block function looks like this:
</para>
            <programlisting>&lt;?phpfunctiononthisdate_block($op='list',$delta=0) {
&#xA0;&#xA0;// listing of blocks, such as on the
admin/system/block page
&#xA0;&#xA0;if ($op=="list") {
&#xA0;&#xA0;&#xA0;&#xA0;$block[0]["info"]
=t("On This Date");
&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;} else {
&#xA0;&#xA0;// our block content
&#xA0;&#xA0;&#xA0;&#xA0;// content variable that will be returned for
display
&#xA0;&#xA0;&#xA0;&#xA0;$block_content='';
&#xA0;&#xA0;&#xA0;&#xA0;// Get today's date
&#xA0;&#xA0;&#xA0;&#xA0;$today=getdate();
&#xA0;&#xA0;&#xA0;&#xA0;// calculate midnight one
week ago
&#xA0;&#xA0;&#xA0;&#xA0;$start_time=mktime(0,0,0,
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$today['mon'], ($today['mday'] -7),$today['year']);
&#xA0;&#xA0;&#xA0;&#xA0;// we want items that
occur only on the day in question, so calculate 1 day
&#xA0;&#xA0;&#xA0;&#xA0;$end_time=$start_time+86400;&#xA0;&#xA0;// 60 * 60 * 24 = 86400
seconds in a day
&#xA0;&#xA0;&#xA0;&#xA0;$query="SELECT nid, title, created FROM
".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"' AND created &lt;= '".$end_time."'";
&#xA0;&#xA0;&#xA0;&#xA0;// get the links
&#xA0;&#xA0;&#xA0;&#xA0;$queryResult=&#xA0;&#xA0;db_query($query);
&#xA0;&#xA0;&#xA0;&#xA0;while ($links=db_fetch_object($queryResult)) {
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$block_content.='&lt;a href="'.url('node/view/'.$links-&gt;nid).'"&gt;'.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$links-&gt;title.'&lt;/a&gt;&lt;br /&gt;';
&#xA0;&#xA0;&#xA0;&#xA0;}
&#xA0;&#xA0;&#xA0;&#xA0;// check to see if there
was any content before setting up the block
&#xA0;&#xA0;&#xA0;&#xA0;if ($block_content=='') {
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;// no content
from a week ago, return nothing.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return;
&#xA0;&#xA0;&#xA0;&#xA0;}
&#xA0;&#xA0;&#xA0;&#xA0;// set up the block
&#xA0;&#xA0;&#xA0;&#xA0;$block['subject']
='On This Date';
&#xA0;&#xA0;&#xA0;&#xA0;$block['content']
=$block_content;
&#xA0;&#xA0;&#xA0;&#xA0;return$block;
&#xA0;&#xA0;}
}?&gt;</programlisting>
        </section>
        <section id="node-562">
          <title>Installing, enabling and testing the module</title>
            <para>
At this point, you can install your module and it'll work. Let's do
that, and see where we need to improve the module.
</para>
            <para>
To install the module, you'll need to copy your onthisdate.module file
to the modules directory of your Drupal installation. The file must be
installed in this directory or a subdirectory of the modules directory,
and must have the .module name extension.
</para>
            <para>
Log in as your site administrator, and navigate to the modules
administration page to get an alphabetical list of modules. In the
menus: administer &#xBB; configuration &#xBB; modules, or via URL:
</para>
            <blockquote>
              <para>
                <programlisting>http://.../admin/system/modules</programlisting>
                <emphasis role="bold">or</emphasis>
                <programlisting>http://.../?q=admin/system/modules</programlisting>
              </para>
            </blockquote>
            <para><emphasis role="bold">Note:</emphasis> You'll see one of three things for the 'onthisdate' module at this
point:
</para>
            <itemizedlist>
              <listitem>
                <para>You'll see the 'onthisdate' module name and no description
</para>
              </listitem>
              <listitem>
                <para>You'll see no module name, but the 'onthisdate' description
</para>
              </listitem>
              <listitem>
                <para>You'll see both the module name and the description
</para>
              </listitem>
            </itemizedlist>
            <para>
Which of these three choices you see is dependent on the state of the
CVS tree, your installation and the help function in your module. If
you have a description and no module name, and this bothers you, comment
out the help function for the moment. You'll then have the module name,
but no description. For this tutorial, either is okay, as you will just
enable the module, and won't use the help system.
</para>
            <para>
Enable the module by selecting the checkbox and save your configuration.
</para>
            <para>
Because the module is a blocks module, we'll need to also enable it in
the blocks administration menu and specify a location for it to display.
Navigate to the blocks administration page: admin/system/block or
administer &#xBB; configuration &#xBB; blocks in the menus.
</para>
            <para>
Enable the module by selecting the enabled checkbox for the 'On This
Date' block and save your blocks. Be sure to adjust the location
(left/right) if you are using a theme that limits where blocks are
displayed.
</para>
            <para>
Now, head to another page, say select the module. In some themes, the
blocks are displayed after the page has rendered the content, and you
won't see the change until you go to new page.
</para>
            <para>
If you have content that was created a week ago, the block will display
with links to the content. If you don't have content, you'll need to
fake some data. You can do this by creating a blog, forum topic or book
page, and adjust the "Authored on:" date to be a week ago.
</para>
            <para>
Alternately, if your site has been around for a while, you may have a
lot of content created on the day one week ago, and you'll see a large
number of links in the block.
</para>
        <bridgehead>Create a module configuration (settings) page</bridgehead>
            <para>
Now that we have a working module, we'd like to make it better. If we
have a site that has been around for a while, content from a week ago
might not be as interesting as content from a year ago. Similarly, if
we have a busy site, we might not want to display all the links to
content created last week. So, let's create a configuration page for
the administrator to adjust this information.
</para>
            <para>
The configuration page uses the 'settings' hook. We would like only
administrators to be able to access this page, so we'll do our first
permissions check of the module here:
</para>
            <programlisting>&lt;?phpfunctiononthisdate_settings()
{
&#xA0;&#xA0;// only administrators can access this
module
&#xA0;&#xA0;if (!user_access("admin onthisdate")) {
&#xA0;&#xA0;&#xA0;&#xA0;returnmessage_access();
&#xA0;&#xA0;}
}?&gt;</programlisting>
            <para>
If you want to tie your modules permissions to the permissions of
another module, you can use that module's permission string. The
"access content" permission is a good one to check if the user can view
the content on your site:
</para>
            <programlisting>&lt;?php
&#xA0;&#xA0;...
&#xA0;&#xA0;// check the user has content
access
&#xA0;&#xA0;if (!user_access("access content")) {
&#xA0;&#xA0;&#xA0;&#xA0;returnmessage_access();
&#xA0;&#xA0;}
&#xA0;&#xA0;...?&gt;</programlisting>
            <para>
We'd like to configure how many links display in the block, so we'll
create a form for the administrator to set the number of links:
</para>
            <programlisting>&lt;?phpfunctiononthisdate_settings()
{
&#xA0;&#xA0;// only administrators can access this
module
&#xA0;&#xA0;if (!user_access("admin onthisdate")) {
&#xA0;&#xA0;&#xA0;&#xA0;returnmessage_access();
&#xA0;&#xA0;}
&#xA0;&#xA0;$output.=form_textfield(t("Maximum number of
links"),"onthisdate_maxdisp",
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
variable_get("onthisdate_maxdisp","3"),2,2,
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
t("The maximum number
of links to display in the block."));
&#xA0;&#xA0;return$output;
}?&gt;</programlisting>
            <para>
This function uses several powerful Drupal form handling features. We
don't need to worry about creating an HTML text field or the form, as
Drupal will do so for us. We use <literal>variable_get</literal> to retrieve
the value of the system configuration variable "onthisdate_maxdisp",
which has a default value of 3. We use the form_textfield function to
create the form and a text box of size 2, accepting a maximum length of
2 characters. We also use the translate function of t(). There are
other form functions that will automatically create the HTML form
elements for use. For now, we'll just use the form_textfield function.
</para>
            <para>
Of course, we'll need to use the configuration value in our SQL SELECT,
so we'll need to adjust our query statement in the onthisdate_block
function:
</para>
            <programlisting>&lt;?php
&#xA0;&#xA0;$limitnum=variable_get("onthisdate_maxdisp",3);
&#xA0;&#xA0;$query="SELECT nid, title, created FROM
".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"' AND created &lt;= '".$end_time."' LIMIT ".$limitnum;?&gt;</programlisting>
            <para>
You can test the settings page by editing the number of links displayed
and noticing the block content adjusts accordingly.
</para>
            <para>
Navigate to the settings page: admin/system/modules/onthisdate or
administer &#xBB; configuration &#xBB; modules &#xBB; onthisdate. Adjust the number
of links and save the configuration. Notice the number of links in the
block adjusts accordingly.
</para>
            <para><emphasis role="bold">Note:</emphasis>We don't have any validation with this input. If you enter
"c" in the maximum number of links, you'll break the block.
</para>
            <bridgehead>
Adding menu links and creating page content
</bridgehead>
            <para>
So far we have our working block and a settings page. The block
displays a maximum number of links. However, there may be more links
than the maximum we show. So, let's create a page that lists all the
content that was created a week ago.
</para>
            <programlisting>&lt;?phpfunctiononthisdate_all() {
}?&gt;</programlisting>
            <para>
We're going to use much of the code from the block function. We'll
write this ExtremeProgramming style, and duplicate the code. If we need
to use it in a third place, we'll refactor it into a separate function.
For now, copy the code to the new function onthisdate_all(). Contrary
to all our other functions, 'all', in this case, is not a Drupal hook.
We'll discuss below.
</para>
            <programlisting>&lt;?phpfunctiononthisdate_all() {
&#xA0;&#xA0;// content variable that will be
returned for display
&#xA0;&#xA0;$page_content='';
&#xA0;&#xA0;// Get today's date
&#xA0;&#xA0;$today=getdate();
&#xA0;&#xA0;// calculate midnight one week
ago
&#xA0;&#xA0;$start_time=mktime(0,0,0,
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$today['mon'], ($today['mday'] -7),$today['year']);
&#xA0;&#xA0;// we want items that occur only on
the day in question, so calculate 1 day
&#xA0;&#xA0;$end_time=$start_time+86400;&#xA0;&#xA0;// 60 * 60 * 24 = 86400
seconds in a day
&#xA0;&#xA0;// NOTE!&#xA0;&#xA0;No LIMIT clause here!&#xA0;&#xA0;We want to
show all the code
&#xA0;&#xA0;$query="SELECT nid, title, created FROM
".
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"{node} WHERE created &gt;= '".$start_time.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"' AND created &lt;= '".$end_time."'";
&#xA0;&#xA0;// get the links
&#xA0;&#xA0;$queryResult=&#xA0;&#xA0;db_query($query);
&#xA0;&#xA0;while ($links=db_fetch_object($queryResult)) {
&#xA0;&#xA0;&#xA0;&#xA0;$page_content.='&lt;a
href="'.url('node/view/'.$links-&gt;nid).'"&gt;'.
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
$links-&gt;title.'&lt;/a&gt;&lt;br /&gt;';
&#xA0;&#xA0;}
&#xA0;&#xA0;...
}?&gt;</programlisting>
            <para>
We have the page content at this point, but we want to do a little more
with it than just return it. When creating pages, we need to send the
page content to the theme for proper rendering. We use this with the
theme() function. Themes control the look of a site. As noted above,
we're including layout in the code. This is bad, and should be
avoided. It is, however, the topic of another tutorial, so for now,
we'll include the formatting in our content:
</para>
            <programlisting>&lt;?php
&#xA0;&#xA0;&#xA0;&#xA0;printtheme("page",$content_string);?&gt;</programlisting>
            <para>
The rest of our function checks to see if there is content and lets the
user know. This is preferable to showing an empty or blank page, which
may confuse the user.
</para>
            <para>
Note that we are responsible for outputting the page content with the
'print theme()' syntax. This is a change from previous 4.3.x themes.
</para>
            <programlisting>&lt;?phpfunctiononthisdate_all() {
&#xA0;&#xA0;...
&#xA0;&#xA0;// check to see if there was any
content before setting up the block
&#xA0;&#xA0;if ($page_content=='') {
&#xA0;&#xA0;&#xA0;&#xA0;// no content from a week
ago, let the user know
&#xA0;&#xA0;&#xA0;&#xA0;printtheme("page",
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
"No events occurred on this site on this date in history.");
&#xA0;&#xA0;&#xA0;&#xA0;return;
&#xA0;&#xA0;}
&#xA0;&#xA0;printtheme("page",$page_content);
}?&gt;</programlisting>
        </section>
        <section id="node-563">
          <title>Letting Drupal know about the new function</title>
            <para>
As mentioned above, the function we just wrote isn't a 'hook': it's not
a Drupal recognized name. We need to tell Drupal how to access the
function when displaying a page. We do this with the _link hook and
the menu() function:
</para>
            <programlisting>&lt;?phpfunctiononthisdate_link($type,$node=0) {
}?&gt;</programlisting>
            <para>
There are many different types, but we're going to use only 'system' in
this tutorial.
</para>
            <programlisting>&lt;?phpfunctiononthisdate_link($type,$node=0) {
&#xA0;&#xA0;if (($type=="system")) {
&#xA0;&#xA0;&#xA0;&#xA0;// URL, page title, func
called for page content, arg, 1 = don't disp menu
&#xA0;&#xA0;&#xA0;&#xA0;menu("onthisdate",t("On This Date"),"onthisdate_all",1,1);
&#xA0;&#xA0;}
}?&gt;</programlisting>
            <para>
Basically, we're saying if the user goes to "onthisdate" (either via
?q=onthisdate or <ulink url="http://.../onthisdate">http://.../onthisdate</ulink>),
the content generated by
onthisdate_all will be displayed. The title of the page will be "On
This Date". The final "1" in the arguments tells Drupal to not display
the link in the user's menu. Make this "0" if you want the user to see
the link in the side navigation block.
</para>
            <para>
Navigate to /onthisdate (or ?q=onthisdate) and see what you get.
</para>
        </section>
        <section id="node-564">
          <title>Adding a more link and showing all entries</title>
            <para>
Because we have our function that creates a page with all the content
created a week ago, we can link to it from the block with a "more" link.
</para>
            <para>
Add these lines just before that $block['subject'] line, adding this to
the $block_content variable before saving it to the $block['content']
variable:
</para>
            <programlisting>&lt;?php
&#xA0;&#xA0;// add a more link to our page that
displays all the links
&#xA0;&#xA0;&#xA0;$block_content.="&lt;div
class=\"more-link\"&gt;".l(t("more"),"onthisdate", array("title"=&gt;t("More
events on this day."))) ."&lt;/div&gt;";?&gt;</programlisting>
            <para>
This will add the more link.
</para>
        </section>
        <section id="node-565">
          <title>Conclusion</title>
            <para>
We now have a working module. It created a block and a page. You
should now have enough to get started writing your own modules. We
recommend you start with a block module of your own and move onto a node
module. Alternately, you can write a filter or theme.
</para>
            <para>
As is, this tutorial's module isn't very useful. However, with a few
enhancements, it can be entertaining. Try modifying the select query
statement to select only nodes of type 'blog' and see what you get.
Alternately, you could get only a particular user's content for a
specific week. Instead of using the block function, consider expanding
the menu and page functions, adding menus to specific entries or dates,
or using the menu callback arguments to adjust what year you look at the
content from.
</para>
            <para>
If you start writing modules for others to use, you'll want to provide
more details in your code. Comments in the code are incredibly valuable
for other developers and users in understanding what's going on in your
module. You'll also want to expand the help function, providing better
help for the user. Follow the Drupal [Coding standards], especially if
you're going to add your module to the project.
</para>
            <para>
Two topics very important in module development are writing themeable
pages and writing translatable content. Please check the [Drupal
Handbook] for more details on these two subject.
</para>
        </section>
      </section>
      <section id="node-567">
        <title>How to build up a _help hook</title>
          <para>
The following template can be used to build a <literal>_help</literal> hook.
</para>
          <programlisting>&lt;?phpfunction
&lt;modulename&gt;_help($section){
&#xA0;&#xA0;$output="";
&#xA0;&#xA0;switch ($section) {
&#xA0;&#xA0;}
&#xA0;&#xA0;return$output;
}?&gt;</programlisting>
          <para>
In the template replace modulename with the name of your module.
</para>
          <para>
If you want to add help text to the overall administrative section.
(admin/help) stick this inside the switch:
</para>
          <programlisting>&lt;?php
&#xA0;&#xA0;&#xA0;&#xA0;case'admin/help#&lt;modulename&gt;':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output=t('The text you want
displayed');
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;?&gt;</programlisting>
          <para>
If you also want this same text displayed for an individual help link in your
menu area. You have this kind of tree:
</para>
          <programlisting>
  + Administration
  |
  -&gt; Your area
  |  |
  |  -&gt; Your configuration
  |  -&gt; help
  |
  -&gt; Overall admin help.
</programlisting>
          <para>
Change the function line to this:
</para>
          <programlisting>&lt;?phpfunction
&lt;modulename&gt;_help($section='admin/help#&lt;modlename&gt;')
{?&gt;</programlisting>
          <para>
Now that you have the template started place a case statement in for any URL
you want a "context sesitive" help message in the admin section. An example,
you have a page that individually configures your module, it is at
admin/system/modules/, you want to add some text to the top help area.
</para>
          <programlisting>&lt;?phpcase'admin/system/modules/&lt;modulename&gt;':$output=t('Your new help text');
      break;?&gt;</programlisting>
      </section>
      <section id="node-568">
        <title>How to convert a _system hook</title>
          <para>
There are three things that can appear in a _system hook:
</para>
          <informaltable>
            <tgroup cols="2">
              <colspec colnum="1" colname="col1"/>
              <colspec colnum="2" colname="col2"/>
              <thead>
                <row>
                  <entry>Field</entry>
                  <entry>Function</entry>
                </row>
              </thead>
              <tbody>
                <row>
                  <entry>$field == "name"</entry>
                  <entry>The module name</entry>
                </row>
                <row>
                  <entry>$field == "description"</entry>
                  <entry>The description placed in the module list</entry>
                </row>
                <row>
                  <entry>$field == "admin-help"</entry>
                  <entry>The help text placed at the TOP of this module's individual configurationarea.</entry>
                </row>
              </tbody>
            </tgroup>
          </informaltable>
          <para>
Take the text for each one and move it into the _help hook. Replace the
<literal>$system[&lt;name&gt;]</literal> that is normally at the front of each one
with $output, now place a "break;" after the line and a <literal>case
'&lt;name&gt;':</literal> before it where name is one of the following:
</para>
          <itemizedlist>
            <listitem>
              <para>If <literal>$system</literal> is <literal>$system["name"]</literal> then the case is
<literal>case 'admin/system/modules#name'</literal></para>
            </listitem>
            <listitem>
              <para>If $system is $system["description"] then case is <literal>case
'admin/system/modules#description'</literal></para>
            </listitem>
            <listitem>
              <para>If <literal>$system</literal> is <literal>$system["admin-help"]</literal> then the case
is <literal>case 'admin/system/modules/&lt;modulename&gt;'</literal></para>
            </listitem>
          </itemizedlist>
          <para>
Now remove the <literal>_system</literal> function and you are done.
</para>
          <para>
An example:
</para>
          <programlisting>&lt;?phpfunctionexample_system($field){
&#xA0;&#xA0;$system["description"] =t("This is my example _system hook to
convert for
the help system I have spent a lot of time with.");
&#xA0;&#xA0;$system["admin-help"] =t("Can you believe that I would actually
write an
indivdual setup page on an EXAMPLE module??");
&#xA0;&#xA0;return$system[$field];
}?&gt;</programlisting>
          <programlisting>&lt;?phpfunctionexample_help($section)
{
&#xA0;&#xA0;$output="";
&#xA0;&#xA0;switch ($section) {
&#xA0;&#xA0;&#xA0;&#xA0;case'admin/system/modules#example':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output=t("This is my example _system hook to
convert for the help
system I have spent a lot of time with.");
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;&#xA0;&#xA0;case'admin/system/modules/example':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output=t("Can you believe that I would actually
write an indivdual
setup page on an EXAMPLE module??");
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;}
&#xA0;&#xA0;return$output;
}?&gt;</programlisting>
      </section>
      <section id="node-569">
        <title>How to convert an _auth_help hook</title>
          <para>
Okay, you have written your Distributed Authorization module, and given us a
great help text for it and I had to go and ruin it all by changing the help
system. What a terrible thing for me to do. How do you convert it?
</para>
          <para>
It is not that hard. There are two places you have to deal with:
</para>
          <orderedlist>
            <listitem>
              <para>The text inside the _auth_help hook needs to be moved inside the _help hook
under the section <literal>user/help#&lt;modulename&gt;</literal> and
</para>
            </listitem>
            <listitem>
              <para>You have to change the _page hook, which normally displays that help text,
to find your text in a new location by changing the function call
<literal>&lt;modulename&gt;_auth_help()</literal> to
<literal>&lt;modulename&gt;_help("user/help#&lt;modulename&gt;")</literal>.
</para>
            </listitem>
          </orderedlist>
          <para>
See, it is not THAT terrible.
</para>
          <para>
An example:
</para>
          <programlisting>&lt;?phpfunctionexampleda_page() {
&#xA0;&#xA0;theme("header");
&#xA0;&#xA0;theme("box","Example DA",exampleda_auth_help());
&#xA0;&#xA0;theme("footer");
}
functionexampleda_auth_help() {
&#xA0;&#xA0;$site=variable_get("site_name","this web site");
&#xA0;&#xA0;$html_output="
&#xA0;&#xA0;&lt;p&gt;This is my example Distributed Auth help. Using this
example you cannot login to &lt;i&gt;%s&lt;/i&gt; because it has no _auth
hook.&amp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;BUT&lt;/u&gt; you should still use Drupal since it is a
&lt;b&gt;GREAT&lt;/b&gt; CMS and is only getting better.&lt;/p&gt;
&lt;p&gt;To learn about about Drupal you can &lt;a
href=\"www.drupal.org\"&gt;visit the
site&lt;/a&gt;&lt;/p&gt;";
&#xA0;&#xA0;returnsprintf(t($html_output),$site);
}?&gt;</programlisting>
          <programlisting>&lt;?phpfunctionexampleda_page() {
&#xA0;&#xA0;theme("header");
&#xA0;&#xA0;theme("box","Example DA",exampleda_help('user/help#exampleda'));
&#xA0;&#xA0;theme("footer");
}
functionexampleda_help($section)
{
&#xA0;&#xA0;$output="";
&#xA0;&#xA0;switch ($section) {
&#xA0;&#xA0;&#xA0;&#xA0;case'user/help#exampleda':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$site=variable_get("site_name","this web site");
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output.="&lt;p&gt;This is my
example Distributed Auth help. Using this example you cannot login to %site
because it has no _auth hook.&lt;/p&gt;";
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output.="&lt;p&gt;&lt;u&gt;BUT&amp;&lt;/u&gt; you should still use
Drupal
since it is a &lt;b&gt;GREAT&lt;/b&gt; CMS and is only getting
better.&lt;/p&gt;";
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output.="&lt;p&gt;To learn about
about Drupal you can
visit %drupal.&lt;/p&gt;";
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output=t($output,
array("%site"=&gt;"&lt;i&gt;$site&lt;/i&gt;","%drupal"=&gt;"&lt;a
href=\"www.drupal.org\"&gt;visit the site&lt;/a&gt;"));
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;}
&#xA0;&#xA0;return$output}?&gt;</programlisting>
      </section>
    </section>
    <section id="node-578">
      <title>Converting 4.3 modules to 4.4</title>
        <para>
Since Drupal 4.3, major changes have been made to the theme, menu, and node
systems. Most themes and modules will require some changes.
</para>
      <section id="node-571">
        <title>Menu system</title>
          <para>
The Drupal menu system has been extended to drive all pages, not just
administrative pages. This is continuing the work done for Drupal 4.3, which
integrated the administrative menu with the user menu. We now have consistency
between administrative and "normal" pages; when you learn to create one, you
know how to create the other.
</para>
          <para>
The flow of page generation now proceeds as follows:
</para>
          <orderedlist>
            <listitem>
              <para>The <literal>_link</literal> hook in all modules is called, so that modules can
use <literal>menu()</literal> to add items to the menu. For example, a module could
define:<programlisting>&lt;?phpfunctionexample_link($type)
{
&#xA0;&#xA0;if ($type=="system") {
&#xA0;&#xA0;&#xA0;&#xA0;menu("example",t("example"),"example_page");
&#xA0;&#xA0;&#xA0;&#xA0;menu("example/foo",t("foo"),"example_foo");
&#xA0;&#xA0;}
}?&gt;</programlisting></para>
            </listitem>
            <listitem>
              <para>The menu system examines the current URL, and finds the "best fit" for the
URL in the menu. For example, if the current URL is
<literal>example/foo/bar/12</literal>, the above <literal>menu()</literal> calls would
cause <literal>example_foo("bar", 12)</literal> to get invoked.
</para>
            </listitem>
            <listitem>
              <para>The callback may set the title or breadcrumb trail if the defaults are not
satisfactory (more on this later).
</para>
            </listitem>
            <listitem>
              <para>The callback is responsible for printing the requested page. This will
usually involve preparing the content, and then printing the return value of
<literal>theme("page")</literal>. For example:<programlisting>&lt;?phpfunctionexample_foo($theString,$theNumber) {
&#xA0;&#xA0;$output=$theString." - ".$theNumber;
&#xA0;&#xA0;printtheme("page",$output);
}?&gt;</programlisting></para>
            </listitem>
          </orderedlist>
          <para>
The following points should be considered when upgrading modules to use the new
menu system:
</para>
          <itemizedlist>
            <listitem>
              <para>The <literal>_page</literal> hook is obsolete. Pages will not be shown unless
they are declared with a <literal>menu()</literal> call as discussed above. To
convert former <literal>_page</literal> hooks to the new system as simply as
possible, just declare that function as a "catchall" callback:<programlisting>&lt;?php
&#xA0;&#xA0;menu("example",t("example"),"example_page",0,MENU_HIDE);?&gt;</programlisting>
The trailing MENU_HIDE argument in this call makes the menu item hidden, so the
callback functions but the module does not clutter the user menu.
</para>
            </listitem>
            <listitem>
              <para>Old administrative callbacks returned their content. In the new system,
administrative and normal callbacks alike are responsible for printing the
entire page.
</para>
            </listitem>
            <listitem>
              <para>The title of the page is printed by the theme system, so page content does
not need to be wrapped in a <literal>theme("box")</literal> to get a title printed.
If the default title is not satisfactory, it can be changed by calling
<literal>drupal_set_title($title)</literal> before <literal>theme("page")</literal> gets
called, or by passing the title to <literal>theme("page")</literal> as a parameter.
</para>
            </listitem>
            <listitem>
              <para>The breadcrumb trail is also printed by the theme. If the default one needs
to be overridden (to present things like forum hierarchies), this can be done
by calling <literal>drupal_set_breadcrumb($breadcrumb)</literal> before
<literal>theme("page")</literal> gets called, or by passing the breadcrumb to
<literal>theme("page")</literal> as a parameter. <literal>$breadcrumb</literal> should be a
list of links beginning with "Home" and proceeding up to, but not including,
the current page.
</para>
            </listitem>
          </itemizedlist>
      </section>
      <section id="node-572">
        <title>Theme system</title>
          <para>
For full information on theme system changes, see <ulink url="http://drupal.org/node/view/4475">converting 4.3 themes to CVS</ulink>. The
following points are directly relevant to module development:
</para>
          <itemizedlist>
            <listitem>
              <para>All theme functions now return their output instead of printing them to the
user. Old <literal>theme()</literal> usage:<programlisting>&lt;?php
theme("box",$title,$output);?&gt;</programlisting>
New usage:<programlisting>&lt;?phpprinttheme("box",$title,$output);?&gt;</programlisting>
Modules that define their own theme functions should also return their output.
</para>
            </listitem>
            <listitem>
              <para>The naming of theme functions defined by modules has been standardized to
<literal>theme_&amp;lt;module&amp;gt;_&amp;lt;name&amp;gt;</literal>. When using a
theme function there is no need to include the theme_ part, as
<literal>theme()</literal> will do this automatically. Example:<programlisting>&lt;?phpfunctiontheme_example_list($list)
{
&#xA0;&#xA0;returnimplode('&lt;br /&gt;',$list);
}
printtheme('example_list', array(1,2,3));?&gt;</programlisting>
Theme functions must always be called using <literal>theme()</literal> to allow for
the active theme to modify the output if necessary.
</para>
            </listitem>
            <listitem>
              <para>The <literal>theme("header")</literal> and <literal>theme("footer")</literal> functions
are not available anymore. Module developers should use the
<literal>theme("page")</literal> function which wraps the content in the site theme.
The full syntax of this function is<programlisting>&lt;?php
theme("page",$output,$title,$breadcrumb);?&gt;</programlisting>
where <literal>$title</literal> and <literal>$breadcrumb</literal> will override any values
set before for these properties.
</para>
            </listitem>
          </itemizedlist>
      </section>
      <section id="node-573">
        <title>Node system</title>
          <para>
The node system has been upgraded to allow a single module to define more than
one type of node. This will allow some of the more convoluted code in, for
example, project.module to be tidied up.
</para>
          <itemizedlist>
            <listitem>
              <para>The <literal>_node()</literal> hook has been deprecated. In its place, modules
that define nodes should use <literal>_node_name()</literal> and
<literal>_help()</literal>.
</para>
            </listitem>
            <listitem>
              <para>The <literal>_node_name()</literal> function should return a translated string
containing the human-readable name of the node type.
</para>
            </listitem>
            <listitem>
              <para>The <literal>_help()</literal> function, when called with parameter
<literal>"node/add#modulename"</literal>, should return a translated string
containing the description of the node type.
</para>
            </listitem>
            <listitem>
              <para>Modules wishing to use the new ability to define multiple node types should
see the <ulink url="http://drupal.org/doxygen/drupal/">Doxygen documentation</ulink>
for <ulink url="http://drupal.org/doxygen/drupal/group__hooks.html#ga30"><literal>hook_node_name()</literal></ulink>
and <ulink url="http://drupal.org/doxygen/drupal/group__hooks.html#ga31"><literal>hook_node_types()</literal></ulink>.
</para>
            </listitem>
          </itemizedlist>
      </section>
      <section id="node-574">
        <title>Filter system</title>
          <itemizedlist>
            <listitem>
              <para>The various filter hooks ('filter', 'conf_filters') have been merged into
one 'filter' hook. A module that provides filtering functionality should
implement:<programlisting>&lt;?phpfunctionexample_filter($op,$text="")
{
&#xA0;&#xA0;switch ($op) {
&#xA0;&#xA0;&#xA0;&#xA0;case"name":
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;returnt("Name of
the filter");
&#xA0;&#xA0;&#xA0;&#xA0;case"prepare":
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;// Do
preparing on $text
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$text;
&#xA0;&#xA0;&#xA0;&#xA0;case"process":
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;// Do
processing on $text
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$text;
&#xA0;&#xA0;&#xA0;&#xA0;case"settings":
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;// Generate
$output of settings
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$output;
&#xA0;&#xA0;}
}?&gt;</programlisting><itemizedlist><listitem><para>"name" is new, and should return a friendly name for the filter.
</para></listitem><listitem><para>
"prepare" is also new. This is an extra step that is performed before the
default HTML processing, if HTML tags are allowed. It is meant to give filters
the chance to escape HTML-like data before it can get stripped. This means, to
convert meaningful HTML characters like &lt; and &gt; into entities such as
&amp;lt; and &amp;gt;.
</para><para>
Common examples include filtering pieces of PHP code, mathematical formulas,
etc. It is not allowed to do anything other than escaping in the "prepare"
step.
</para><para>
If your filter currently performs such a step in the main "process" step, it
should be moved into "prepare" instead. If you don't need any escaping, your
filter should simply return $text without processing in this case.
</para></listitem><listitem><para>"process" is the equivalent of the old "filter" hook. Normal filtering is
performed here, and the changed $text is returned.
</para></listitem><listitem><para>"settings" is the equivalent of the old "conf_filters" hook. If your filter
provides configurable options, you should return them here (using the standard
form_* functions).
</para></listitem></itemizedlist></para>
            </listitem>
            <listitem>
              <para>The filter handling code has been moved to a new required
<literal>filter.module</literal>, and thus most of the filter function names changed,
although none of those should have been called from modules. The
<literal>check_output()</literal> function is still available with the same
functionality.
</para>
            </listitem>
            <listitem>
              <para>Node filtering is optimized with the <literal>node_prepare()</literal> function
now, which only runs the body through the filters if the node view page is
displayed. Otherwise, only the teaser is filtered.
</para>
            </listitem>
            <listitem>
              <para>The <literal>_compose_tips</literal> hook (defined by the contrib
<literal>compose_tips.module</literal>) is not supported anymore, but more advanced
functionality exists in the core. You can emit extensive compose tips related
to the filter you define via the <literal>_help</literal> hook with the
<literal>'filter#long-tip'</literal> section identifier. The
<literal>compose_tips</literal> URL is thus changed to <literal>filter/tips</literal>. The
<literal>form_allowed_tags_text()</literal> function is replaced with
<literal>filter_tips_short()</literal>, which now supports short tips to be placed
under textareas. Any module can inject short tips about the filter defined via
the <literal>_help</literal> hook, with the <literal>'filter#short-tip'</literal> section
identifier.
</para>
            </listitem>
          </itemizedlist>
      </section>
      <section id="node-575">
        <title>Hook changes</title>
          <para>
Other than those mentioned above, the following hooks have changed:
</para>
          <itemizedlist>
            <listitem>
              <para>The <literal>_view</literal> hook has been changed to return its content rather
than printing it. It also has an extra parameter, <literal>$page</literal>, that
indicates whether the node is being viewed as a standalone page or as part of a
larger context. This is important because nodes may change the breadcrumb trail
if they are being viewed as a page. Old usage:<programlisting>&lt;?phpfunctionexample_view($node,$main=0)
{
&#xA0;&#xA0;if ($main) {
&#xA0;&#xA0;&#xA0;&#xA0;theme("node",$node,$main);
&#xA0;&#xA0;}
&#xA0;&#xA0;else {
&#xA0;&#xA0;&#xA0;&#xA0;$breadcrumb[] =l(t("Home"),"");
&#xA0;&#xA0;&#xA0;&#xA0;$breadcrumb[] =l(t("foo"),"foo");
&#xA0;&#xA0;&#xA0;&#xA0;$node-&gt;body=theme("breadcrumb",$breadcrumb) ."&lt;br /&gt;".$node-&gt;body;
&#xA0;&#xA0;&#xA0;&#xA0;theme("node",$node,$main);
&#xA0;&#xA0;}
}?&gt;</programlisting>
New usage:<programlisting>&lt;?phpfunctionexample_view($node,$main=0,$page=0) {
&#xA0;&#xA0;if ($main) {
&#xA0;&#xA0;&#xA0;&#xA0;returntheme("node",$node,$main,$page);
&#xA0;&#xA0;}
&#xA0;&#xA0;else {
&#xA0;&#xA0;&#xA0;&#xA0;if ($page) {
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$breadcrumb[] =l(t("Home"),"");
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$breadcrumb[] =l(t("foo"),"foo");
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;drupal_set_breadcrumb($breadcrumb);
&#xA0;&#xA0;&#xA0;&#xA0;}
&#xA0;&#xA0;&#xA0;&#xA0;returntheme("node",$node,$main,$page);
&#xA0;&#xA0;}
}?&gt;</programlisting></para>
            </listitem>
            <listitem>
              <para>The <literal>_form</literal> hook used by node modules no longer takes 3
arguments. The second argument <literal>$help</literal>, typically used to print
submission guidelines, has been removed. Instead, the help should be emitted
using the module's <literal>_help</literal> hook. For examples, check the story,
forum or blog module.
</para>
            </listitem>
            <listitem>
              <para>The <literal>_search</literal> hook was changed to not only return the result set
array, but a two element array with the result group title and the result set
array. This provides more precise control over result group titles.
</para>
            </listitem>
            <listitem>
              <para>The <literal>_head</literal> hook is eliminated and replaced with the
<literal>drupal_set_html_head()</literal> and <literal>drupal_get_html_head()</literal>
functions. You can add JavaScript code or CSS to the HTML head part with the
<literal>drupal_set_html_head()</literal> function instead.
</para>
            </listitem>
            <listitem>
              <para>See also the description of the <literal>_compose_tips</literal> hook changes
below.
</para>
            </listitem>
          </itemizedlist>
      </section>
      <section id="node-576">
        <title>Emitting links</title>
          <itemizedlist>
            <listitem>
              <para>The functions <literal>url()</literal> and <literal>l()</literal> take a new
<literal>$fragment</literal> parameter. Calls to <literal>url()</literal> or
<literal>l()</literal> that have '#' in the <literal>$url</literal> parameter need to be
updated. If you don't update such calls, Drupal's path aliasing won't work for
URLs with # in them.
</para>
            </listitem>
            <listitem>
              <para>Drupal now emits relative URLS instead of absolute URLs. Contributed
modules must be updated whenere an absolute url is required. For example:
</para>
            </listitem>
            <listitem>
              <para>
                <itemizedlist>
                  <listitem>
                    <para>Any module that outputs an RSS feed without using <literal>node_feed()</literal>
should be updated. Note: this is discouraged. please use
<literal>node_feed()</literal> instead. Also modules using <literal>node_feed()</literal>
should provide an absolute link in the 'link' key, if any.
</para>
                  </listitem>
                  <listitem>
                    <para>Any module which send email should be updated so that links in the email
have absolute urls instead of relative urls. You do this using a parameter in
your call to <literal>l()</literal> or <literal>url()</literal></para>
                  </listitem>
                </itemizedlist>
              </para>
            </listitem>
          </itemizedlist>
      </section>
      <section id="node-577">
        <title>Status and error messages</title>
          <itemizedlist>
            <listitem>
              <para>Modules that use <literal>theme('error', ...)</literal> to print error messages
should be updated to use <literal>drupal_set_message(..., 'error')</literal> unless
used to print an error message below a form item.<programlisting>&lt;?php
drupal_set_message(t('failed
to update X','error'));&#xA0;&#xA0;// set the second
parameter to 'error'?&gt;</programlisting></para>
            </listitem>
            <listitem>
              <para>Modules that print status messages directly to the screen using
<literal>status()</literal> should be updated to use
<literal>drupal_set_message()</literal>. The <literal>status()</literal> function has been
removed.<programlisting>&lt;?php
drupal_set_message(t('updated
X'));?&gt;</programlisting></para>
            </listitem>
          </itemizedlist>
      </section>
    </section>
    <section id="node-587">
      <title>Converting 4.4 modules to 4.5</title>
      <section id="node-579">
        <title>Menu system</title>
          <para>
The Drupal menu system got a complete rewrite. The new features include:
</para>
          <itemizedlist>
            <listitem>
              <para>The administrator may now customize the menu to reorder, remove, and add
items.
</para>
            </listitem>
            <listitem>
              <para>Menu items may be classified as "local tasks," which will by default be
displayed as tabs on the page content.
</para>
            </listitem>
            <listitem>
              <para>The menu API is much more consistent with the rest of Drupal's API.
</para>
            </listitem>
          </itemizedlist>
          <para>
The menu() function is no more. In its place, we have hook_menu(). The old
hook_link() remains, but will no longer be called with the "system" argument.
The hook reference in the Doxygen documentation details all the <ulink url="http://drupal.org/doxygen/drupal/group__hooks.html#ga15">specifics</ulink> of this
new hook. In short, rather than making many calls to menu() in your hook_link()
implementation, you will implement hook_menu() to return an array of the menu
items you define.
</para>
          <para>
As an example, the old pattern:</para>
          <programlisting>&lt;?phpfunctionblog_link($type,$node=0,$main) {
&#xA0;&#xA0;global$user;
&#xA0;&#xA0;if ($type=='system') {
&#xA0;&#xA0;&#xA0;&#xA0;menu('node/add/blog',t('blog entry'),user_access('maintain personal
blog') ?MENU_FALLTHROUGH:MENU_DENIED,0);
&#xA0;&#xA0;&#xA0;&#xA0;menu('blog',t('blogs'),user_access('access content') ?'blog_page':MENU_DENIED,0,MENU_HIDE);
&#xA0;&#xA0;&#xA0;&#xA0;menu('blog/'.$user-&gt;uid,t('my blog'),MENU_FALLTHROUGH,1,MENU_SHOW,MENU_LOCKED);
&#xA0;&#xA0;&#xA0;&#xA0;menu('blog/feed',t('RSS feed'),user_access('access content') ?'blog_feed':MENU_DENIED,0,MENU_HIDE,MENU_LOCKED);
&#xA0;&#xA0;}
}?&gt;</programlisting>
          <programlisting>&lt;?phpfunctionblog_menu($may_cache) {
&#xA0;&#xA0;global$user;
&#xA0;&#xA0;$items=
array();
&#xA0;&#xA0;if ($may_cache) {
&#xA0;&#xA0;&#xA0;&#xA0;$items[] = array('path'=&gt;'node/add/blog','title'=&gt;t('blog entry'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'access'=&gt;user_access('maintain personal blog'));
&#xA0;&#xA0;&#xA0;&#xA0;$items[] = array('path'=&gt;'blog','title'=&gt;t('blogs'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'callback'=&gt;'blog_page',
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'access'=&gt;user_access('access content'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'type'=&gt;MENU_SUGGESTED_ITEM);
&#xA0;&#xA0;&#xA0;&#xA0;$items[] = array('path'=&gt;'blog/'.$user-&gt;uid,'title'=&gt;t('my blog'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'access'=&gt;user_access('maintain personal blog'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'type'=&gt;MENU_DYNAMIC_ITEM);
&#xA0;&#xA0;&#xA0;&#xA0;$items[] = array('path'=&gt;'blog/feed','title'=&gt;t('RSS feed'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'callback'=&gt;'blog_feed',
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'access'=&gt;user_access('access content'),
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'type'=&gt;MENU_CALLBACK);
&#xA0;&#xA0;}
&#xA0;&#xA0;return$items;
}?&gt;</programlisting>
          <para>
Drupal now distinguishes between 404 (Not Found) pages and 403 (Forbidden)
pages. To accommodate this, modules should abandon the practice of not
declaring menu items when access is denied to them. Instead, they should set
the "access" attribute of their newly-declared menu item to FALSE. This will
have the effect of the menu item being hidden, and also preventing the callback
from being invoked by typing in the URL. Modules may also want to take
advantage of the drupal_access_denied() function, which prints a 403 page (the
analogue of drupal_not_found(), which prints a 404).
</para>
      </section>
      <section id="node-580">
        <title>Path changes</title>
          <para>
Some internal URL paths have changed; check the links printed by your code.
Most significant is that paths of the form "node/view/52" are now "node/52"
instead, while "node/edit/52" becomes "node/52/edit".
</para>
      </section>
      <section id="node-581">
        <title>Node changes</title>
          <itemizedlist>
            <listitem>
              <para>The database field <literal>static</literal> has been renamed to
<literal>sticky</literal>.
</para>
            </listitem>
            <listitem>
              <para>Error handling of forms (such as node editing forms) is now done using
form_set_error(). It simplifies the forms and validation code; however, it does
change the node API slightly:
<itemizedlist><listitem><para>The _validate hook and the _nodeapi('validate') hook of the node API no
longer take an "error" parameter, and should no longer return an error array.
To set an error, call form_set_error().
</para></listitem><listitem><para>Node modules' hook_form() implementations no longer take an "error"
parameter and should not worry about displaying errors. The same applies to
hook_nodeapi('form_post') and hook_nodeapi('form_pre').
</para></listitem><listitem><para>All of the form_ family of functions can take a parameter that marks the
field as required in a standard way. Use this instead of adding that
information to the field description.
</para></listitem></itemizedlist></para>
            </listitem>
            <listitem>
              <para>In order to allow modules such as book.module to inject HTML elements into
the view of nodes safely, hook_nodeapi() was extended to respond to the 'view'
operation. This operation needs to be invoked after the filtering of the node,
so hook_view() was <ulink url="http://drupal.org/doxygen/drupal/group__hooks.html#ga38">changed slightly</ulink>
to no longer require a return value. Instead of calling theme('node', $node)
and returning the result as before, the hook can just modify $node as it sees
fit (including running $node-&gt;body and $node-&gt;teaser through the filters,
as before), and the calling code will take care of sending the result to the
theme. Most modules will just work under the new semantics, as the return value
from the hook is just discarded, but the $node parameter is now required to be
passed by reference (this was common but optional before).
</para>
            </listitem>
            <listitem><para>
We have node-level access control now! This means that node modules need to
make very small changes to their hook_access() implementations. The check for
$node-&gt;status should be removed; the node module takes care of this check. A
value should only be returned from this hook if the node module needs to
override whatever access is granted by the node_access table. See the hook API
for <ulink url="http://drupal.org/doxygen/drupal/group__hooks.html#ga2">details</ulink>.
	      </para>

	      <para>
Node listing queries need to be changed as well, so that they properly
check for whether the user has access to the node before listing
it. Queries of the form

	      <programlisting>
&lt;?php
  db_query('SELECT n.nid, n.title FROM {node} n WHERE n.status = 1 AND foo');
?&gt;
</programlisting>
become
<programlisting>
&lt;?php
  db_query(
     'SELECT n.nid, n.title FROM {node} n '.node_access_join_sql()
     .' WHERE n.status = 1 AND '.node_access_where_sql()
     .' AND foo');
?&gt;
</programlisting>

See <ulink url="http://drupal.org/doxygen/drupal/group__node__access.html">node
access rights</ulink> in the Doxygen reference.
</para>
</listitem>
          </itemizedlist>
      </section>
      <section id="node-582">
        <title>Filtering changes</title>
          <para>
            <emphasis role="bold">This change affects non-filter modules as well! Please read on even if your
module does not filter.</emphasis>
          </para>
          <para>
The filter system was changed to support multiple input formats. Each input
format houses an entire filter configuration: which filters to use, in what
order and with what settings. The filter system now supports multiple filters
per module as well.
</para>
      </section>
      <section id="node-583">
        <title>Check_output() changes</title>
          <para>
Because of the multiple input formats, a module which implements content has to
take care of managing the format with each item. If your module uses the node
system and passes content through <literal>check_output()</literal>, then you need to
do two things:
</para>
          <itemizedlist>
            <listitem>
              <para>Pass $node-&gt;format as the second parameter to
<literal>check_output()</literal> whenever you use it.
</para>
            </listitem>
            <listitem>
              <para>Add a filter format selector to <literal>hook_form</literal> using a snippet
like:<programlisting>&lt;?php
$output.=filter_form('format',$node-&gt;format);?&gt;</programlisting></para>
            </listitem>
          </itemizedlist>
          <para>
The node system will automatically save/load the format value for you.
</para>
          <para>
If your module provides content outside of the node system, you can decide if
you want to support multiple input formats or not. If you don't, the default
format will always be used. However, if your module accepts input through the
browser, it is strongly advised to support input formats!
</para>
          <para>
To do this, you must:
</para>
          <itemizedlist>
            <listitem>
              <para>Provide a selector for input formats on your forms, using <ulink url="http://www.drupaldocs.org/filter_form">filter_form()</ulink>.
</para>
            </listitem>
            <listitem>
              <para>Validate the chosen input format on submission, using <ulink url="http://www.drupaldocs.org/filter_access">filter_access()</ulink>.
</para>
            </listitem>
            <listitem>
              <para>Store the format ID with each content item (the format ID is a number).
</para>
            </listitem>
            <listitem>
              <para>Pass the format ID to check_output().
</para>
            </listitem>
          </itemizedlist>
          <para>
Check the API documentation for these functions for more information on how to
use them.
</para>
      </section>
      <section id="node-584">
        <title>Filter hook</title>
          <para>
The _filter hook was changed significantly. It's best to start with the
following framework:</para>
          <programlisting>&lt;?phpfunctionhook_filter($op,$delta=0,$format=
-1,$text='') {
&#xA0;&#xA0;switch ($op) {
&#xA0;&#xA0;&#xA0;&#xA0;case'list':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return array(0=&gt;t('Filter
name'));
&#xA0;&#xA0;&#xA0;&#xA0;case'description':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;returnt("Short
description of the filter's actions.");/*
&#xA0;&#xA0;&#xA0;&#xA0;case 'no cache':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return true;
*/
&#xA0;&#xA0;&#xA0;&#xA0;case'prepare':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$text= ...
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$text;
&#xA0;&#xA0;&#xA0;&#xA0;case'process':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$text= ...
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$text;
&#xA0;&#xA0;&#xA0;&#xA0;case'settings':
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;$output= ...;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$output;
&#xA0;&#xA0;&#xA0;&#xA0;default:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return$text;
&#xA0;&#xA0;}
}?&gt;</programlisting>
          <para>
However, you should now include the $format parameter in the variable names for
filter settings. If your filter has a setting "myfilter_something", it should
be changed to "myfilter_something_$format". This allows the setting to be set
separately for each input format. To check if it works correctly, add your
filter to two different input formats and give each instance different
settings. Verify that each input format retains its own settings.
</para>
          <para>
Unlike before, the 'settings' operation should only be used to return actually
useful settings, because there is now a separate overview of all enabled
filters. A filter does not need its own on/off toggle. If a filter has no
configurable settings, it should return nothing for the settings, rather than a
message like we did before.
</para>
          <para>
Finally, the filter system now includes caching. If your filter's output is
dynamic and should not be cached, uncomment the 'no cache' snippet. Only do
this when absolutely necessary, because this turns off caching for any input
format your filter is used in. Beware of the filter cache when developing your
module: it is advised to uncomment 'no cache' while developing, but be sure to
remove it again if it's not needed.
</para>
      </section>
      <section id="node-585">
        <title>Filter tips</title>
          <para>
Filter tips are now output through the format selector. Modules no longer need
to call filter_tips_short() to display them.
</para>
          <para>
A module's filter tips are returned through the filter_tips hook:</para>
          <programlisting>&lt;?phpfunctionhook_filter_tips($delta,$format,$long=false)
{
&#xA0;&#xA0;if ($long) {
&#xA0;&#xA0;&#xA0;&#xA0;returnt("Long
tip");
&#xA0;&#xA0;}
&#xA0;&#xA0;else {
&#xA0;&#xA0;&#xA0;&#xA0;returnt("Short
tip");
&#xA0;&#xA0;}
}?&gt;</programlisting>
      </section>
      <section id="node-586">
        <title>Other changes</title>
          <para>
In addition to the above mentioned changes:
</para>
          <itemizedlist>
            <listitem>
              <para>hook_user() was changed to allow multiple pages of user profile
information. The <ulink url="http://drupal.org/doxygen/drupal/core_8php.html#a23">new syntax</ulink> of the
hook is given in the API reference. Pay particular attention to the
"categories", "form", and "view" operations.
</para>
            </listitem>
            <listitem>
              <para>When processing a form submission, you should use drupal_goto() to redirect
to the result if the submission was accepted. This prevents a double post when
people refresh their browser right after submitting. Messages set with
drupal_set_message() will be saved across the redirect. If a submission was
rejected, you should not use drupal_goto(), but simply print out the form along
with error messages.
</para>
            </listitem>
          </itemizedlist>
      </section>
    </section>
    <section id="node-588">
      <title>Converting 4.5 modules to 4.6</title>
        <bridgehead>Block system</bridgehead>
        <para>
Every block now has a configuration page to control block-specific options.
Modules which have configurations for their blocks should move those into
<ulink url="http://drupaldocs.org/hook_block">hook_block()</ulink>.
</para>
        <para>
The only required changes to modules implementing hook_block() is to be careful
about what is returned. Do not return anything if $op is not 'list' or 'view'.
Once this change is made, modules will still be compatible with Drupal 4.5.
</para>
        <para>
If a specific block has configuration options, implement the additional $op
options in your module. The implementation of 'configure' should return a
string containing the configuration form for the block with the appropriate
$delta. 'save' will have an additional $edit argument, which will contain the
submitted form data for saving.
</para>
        <bridgehead>
Search system
</bridgehead>
        <para>
The search system got a significant overhaul.
</para>
        <para>
Node indexing now uses the node's processed and filtered output, which means
that any custom node fields will automatically be included in the index, as
long as they are visible to normal users who view the node. Modules that
implement <literal>hook_search()</literal> and <literal>hook_update_index()</literal> just
to have extra node fields indexed no longer need to do this.
</para>
        <para>
If you wish to have additional information indexed that is not visible
in the node display at node/id, then you can do so using
nodeapi('update index'). If you want to add extra information to the node
results, use nodeapi('search result').
</para>
        <para>
However, the standard search is still limited to a keyword search. Modules that
implement custom, specific search forms (like project.module) can still do so.
Custom search forms that do not use hook_search() should be located/moved to a
local task under the /search page.
</para>
        <para>
If you are unsure of what you need to do, please refer to the <ulink url="http://drupaldocs.org/api/head/group/search">complete search
documentation</ulink>.
</para>
        <bridgehead>
Module paths
</bridgehead>
        <para>
The function <literal>module_get_path</literal> was renamed to
<literal>drupal_get_path</literal> which now returns the path for all themes, theme
engines and modules. Because of this abstraction you must pass an additional
parameter identifying the type of item for which the path is requested. The
following example compares retrieving the path to image module between Drupal
4.5 and 4.6.
</para>
        <programlisting>&lt;?php// Drupal
4.5:$path=module_get_path('image');// Drupal
4.6:$path=drupal_get_path('module','image');?&gt;</programlisting>
        <para>
All instances of <literal>module_get_path</literal> should be renamed to
<literal>drupal_get_path</literal>.
</para>
        <bridgehead>
Database backend
</bridgehead>
        <para>
The function <literal>check_query</literal> was renamed to
<literal>db_escape_string</literal> and now has a database specific implementation.
All instances of <literal>check_query</literal> should be renamed to
<literal>db_escape_string</literal>.
</para>
        <bridgehead>
Theme system
</bridgehead>
        <para>
The function <ulink url="http://drupaldocs.org/theme_page">theme_page()</ulink> no
longer takes <literal>$title</literal> or <literal>$breadcrumb</literal> arguments. Set
page titles using <ulink url="http://drupaldocs.org/hook_menu">hook_menu()</ulink> or,
if the title must be dynamically determined, use drupal_set_title(). Set
breadcrumb trails first using hook_menu(), which can be overridden with
<ulink url="http://drupaldocs.org/menu_set_location">menu_set_location()</ulink> and
<ulink url="http://drupaldocs.org/drupal_set_breadcrumb">drupal_set_breadcrumb()</ulink>.
</para>
        <bridgehead>
Watchdog messages
</bridgehead>
        <para>
The watchdog() function now takes a severity attribute, so
<literal>watchdog($type, $message, $link);</literal> becomes <literal>watchdog($type,
$message, $severity, $link);</literal>. Specify a severity in case you are
reporting a warning or error. Possible severity constants are:
<literal>WATCHDOG_NOTICE</literal>, <literal>WATCHDOG_WARNING</literal> and
<literal>WATCHDOG_ERROR</literal>. Also make sure that you provide the type as a
literal string, so translation extraction can pick it up.
</para>
        <para>
If you are unsure of which severity to use, remember these rules:
</para>
        <itemizedlist>
          <listitem>
            <para>If the problem is caused by a definite fault and should be fixed as soon as
possible, use an error message.
</para>
          </listitem>
          <listitem>
            <para>If the problem could point to a fault, but could also be harmless, use a
warning message. This type should also be used whenever the problem could be
caused by a remote server (example: ping timeout, failed to aggregate a feed,
etc).
</para>
          </listitem>
          <listitem>
            <para>Normal messages should be notices.
</para>
          </listitem>
        </itemizedlist>
        <bridgehead>
Node markers
</bridgehead>
        <para>
If you have a module calling <literal>theme('mark')</literal>, note that it is now
possible to have different markers for different states of a node. The
supported states are <literal>MARK_NEW</literal>, <literal>MARK_UPDATED</literal> and
<literal>MARK_READ</literal>. You can get the marker state from
<literal>node_mark()</literal>, which replaces the <literal>node_new()</literal> function
available in previous Drupal versions.
</para>
        <bridgehead>
Control over destination page after form processing
</bridgehead>
        <para>
Occasionally a module might want to specify where a user should go after he
submits a form. This is now possible by passing a querystring parameter
<literal>&amp;destination=&lt;path&gt;</literal>. For example, editing of nodes and
comments from within the Admin pages now returns the user to those pages after
he is done. For example usage, search drupal_get_destination() which can be
found in path.module, node.module, comment.module, and user.module
</para>
        <bridgehead>
Confirmation messages
</bridgehead>
        <para>
Confirmations for dangerous actions should now be presented with the
<literal>theme('confirm')</literal> function for consistency. Check the <ulink url="http://drupaldocs.org/theme_confirm">function's documentation</ulink> or look at
some of the core modules for examples.
</para>
        <para>
Note that this is a themable function which should be invoked through
<literal>theme('confirm')</literal> and not <literal>theme_confirm()</literal>.
</para>
        <bridgehead>
Inter module calls
</bridgehead>
        <para>
New features are available -- it's not necessary to use them. Now you can
really (and should) use module_invoke to call a function from another module.
For example, <literal>taxonomy_get_tree</literal> should be called by
<literal>module_invoke('taxonomy', 'get_tree')</literal> If you need to loop through
the implementations of a hook, please check the new <ulink url="http://drupaldocs.org/module_implements">module_implements</ulink> function.
</para>
        <bridgehead>
Node queries
</bridgehead>
        <para>
If you have a module which retrieves a list of nodes by issuing its own
database query, then the following applies.
</para>
        <para>
The functions node_access_join_sql() and node_access_where_sql() should not be
used any more but the SELECT-queries should be wrapped in a db_rewrite_sql()
call.
</para>
        <para>
If you have used DISTINCT(nid) -- because of node_access_join_sql() -- you no
longer need it, replace it simply with n.nid. If you have SELECT *, please
replace it with SELECT n.nid, n.* -- and always make sure that n.nid field
comes first in the SELECT statement -- this way the db_rewrite_sql() function
can rewrite the query to use DISTINCT(nid) should there be a need for it. If
the n.nid field is not first, the query will fail when node access modules are
enabled. Also, at the moment db_rewrite_sql can not handle AS -- either leave
it out or lowercase it.
</para>
        <para>
Always use table name before the field names, especially before nid because
other tables may be JOINed during the rewrite process.
</para>
        <para>
Example:
</para>
        <programlisting>&lt;?php// Drupal
4.5:$nodes=db_query_range('SELECT DISTINCT(n.nid) FROM {node} n
'.node_access_join_sql()
.' WHERE '.node_access_where_sql()
.' AND n.promote = 1 AND n.status = 1 ORDER BY
n.created DESC',0,15);// Drupal 4.6:$nodes=db_query_range(db_rewrite_sql('SELECT n.nid FROM {node} n&#xA0;&#xA0;WHERE n.promote = 1 AND
n.status = 1 ORDER BY n.created DESC'),0,15);?&gt;</programlisting>
        <para>
If you are not using the node table, then you shall pass the table name from
which you SELECTing the nodes. For example
</para>
        <programlisting>&lt;?php
$result=db_query(db_rewrite_sql("SELECT f.nid, f.* from {files} f WHERE filepath =
'%s'",'f'),$file);?&gt;</programlisting>
        <para>
note the 'f' parameter of db_rewrite_sql().
</para>
        <para>
Avoid USING because there could be JOINs before it, which will break the USING
clause.
</para>
        <bridgehead>
Text output
</bridgehead>
        <para>
Drupal's text output was audited and several escaping bugs were found. For more
info, see the <ulink url="http://drupal.org/node/18817">check_plain patch</ulink>.
</para>
        <para>
You need to pay attention that all user-submitted plain-text in your module is
escaped using check_plain() when you output it into HTML. No escaping should be
done on data that is going into the database: only escape when outputting to
HTML.
</para>
        <para>
Check_plain() replaces drupal_specialchars() and check_form(), so if you are
using any of those two, you should use check_plain() instead.
</para>
        <para>
You should also wrap user-submitted text in messages with
<literal>theme('placeholder', $text).</literal> For example for "created term %term".
</para>
        <para>
Pay attention in particular to node and comment titles as their behaviour has
been changed. They are now stored as plain-text, like other single-line fields
in Drupal and should be escaped when output. However, the function l() now
takes plain-text by default instead of HTML, which means that whenever
$node-&gt;title is used as the caption for a link, it will automatically be
escaped. When outputting titles literally, you still have to escape them
yourself.
</para>
        <para>
URLs also require attention, as the URL functions (url, request_uri,
referer_uri, etc) were changed to output 'real' URLs rather than HTML-escaped
URLs. When putting any of them inside an HTML tag attribute (e.g. &lt;a
href="..."&gt;), you need to pass it through check_url() first. When putting an
URL into HTML outside of a tag or attribute, you can use check_url() or
check_plain(), it doesn't matter. Don't use check_url() in situations where a
real URL is expected (e.g. the HTTP "Location: ..." header).
</para>
        <para>
The best test is to submit forms with HTML tags in the plain-text/single-line
fields (e.g. "&lt;u&gt;test&lt;/u&gt;"). If the underline tag is not
interpreted, but displayed literally, your module is escaping the text
correctly.
</para>
        <para>
Nothing has changed for filtered/rich text, which still uses check_output()
like before.
</para>
    </section>
    <section id="node-589">
      <title>Converting 4.6 modules to HEAD</title>
        <bridgehead>Taxonomy API change</bridgehead>
        <para>
In order to provide more meaningful messages to the user, you are now required
to provide your own when using the taxonomy APIs to create or modify terms and
vocabularies. This applies to <literal>taxonomy_save_vocabulary()</literal> and
<literal>taxonomy_save_term()</literal>.
</para>
        <para>
A status message is returned, which can be either <literal>SAVED_NEW</literal>,
<literal>SAVED_UPDATED</literal> or <literal>SAVED_DELETED</literal>.
</para>
        <para>
This snippet shows you an example of handling this:</para>
        <programlisting>&lt;?php// Drupal
4.6taxonomy_save_vocabulary($edit);// Drupal
4.7switch (taxonomy_save_vocabulary($edit))
{
&#xA0;&#xA0;caseSAVED_NEW:
&#xA0;&#xA0;&#xA0;&#xA0;drupal_set_message(t('Created
new vocabulary %name.', array('%name'=&gt;theme('placeholder',$edit['name']))));
&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;caseSAVED_UPDATED:
&#xA0;&#xA0;&#xA0;&#xA0;drupal_set_message(t('Updated
vocabulary %name.', array('%name'=&gt;theme('placeholder',$edit['name']))));
&#xA0;&#xA0;&#xA0;&#xA0;break;
&#xA0;&#xA0;caseSAVED_DELETED:
&#xA0;&#xA0;&#xA0;&#xA0;drupal_set_message(t('Deleted
vocabulary %name.', array('%name'=&gt;theme('placeholder',$deleted_name))));
&#xA0;&#xA0;&#xA0;&#xA0;break;
}?&gt;</programlisting>
        <bridgehead>
Table API change
</bridgehead>
        <para>
Themes tables sometimes were called with arguments set to NULL or an empty
string to indicate that there were either no rows or no header. This has now to
be an empty array.
</para>
        <para>
Change</para>
        <programlisting>&lt;?php
theme('table','',$rows);?&gt;</programlisting>
        <programlisting>&lt;?php
theme('table', array(),$rows);?&gt;</programlisting>
        <bridgehead>
Check Output change
</bridgehead>
        <para>
Due to a security vulnerability discovered earlier in the filter system, we
have tightened security around the <literal>check_output()</literal> function. The
format passed to <literal>check_output()</literal> is now checked for access by
default. If you don't want this check, pass FALSE for the third parameter,
<literal>$check</literal>.
</para>
        <programlisting>&lt;?phpfunctioncheck_output($text,$format=FILTER_FORMAT_DEFAULT,$check=TRUE) {?&gt;</programlisting>
        <para>
Note that if you disable the check by passing FALSE, you need to make sure the
<literal>$format</literal> value has been checked by <literal>filter_access()</literal>
before. <literal>filter_access()</literal> checks the permissions of the current
user, so it should be checked on submission, not on output.
</para>
    </section>
  </chapter>
  <chapter id="node-591">
    <title>Join forces</title>
      <para>
Too often new modules are contributed that do nothing new, only do it in a
different way. We are then stuck with two modules that offer nearly similar
functionality, but both do not do it well enough. This leads to confusion,
clutter and a lot of innefficiency.
</para>
      <para>
So please consider the following guidelines or ideas:
</para>
      <itemizedlist>
        <listitem>
          <para>Develop and use one central API. do not introduce any new .incs, .modules
or other files with APIS, if there are modules that have these already.
</para>
        </listitem>
        <listitem>
          <para>Consult other developers of modules in your domain when you plan to add
features, or plan to add a module. try to agree on features, to avoid
overlapping. Nothing is more confusing for a user when he has, for example a
Spam Queue for comments, and a completely different one for trackbacks, which
does not respect the options you set for comments. Even worse, but certainly
not unheard of, is that module Foo breaks module Bar, because they want to do
the same, or want to use the same database tables.
</para>
        </listitem>
        <listitem>
          <para>Do not try to duplicate functionality because "you do not really like how
its done there" That only adds clutter. Rather improve the existing one, then
introduce yet another-half-witted-module.
</para>
        </listitem>
      </itemizedlist>
      <para>
Note that they are not rules or laws. But that respecting them will most often
help you and the community better. For only then will we be able to "stand on
the shoulders of Giants" as they say in Open Source Land. If you keep
reinventing wheels, you will be stuck with lots of incompatible and half
finished wheels, in the end. When you use existing wheels and build a car on
top of them, you will be able to get somewhere, one day.
</para>
  </chapter>
  <chapter id="node-594">
    <title>Reference</title>
      <para>
This section is intended as a handy reference, collecting things which you may
need to look up as you code to Drupal.
</para>
    <section id="node-592">
      <title>'Status' field values for nodes and comments</title>
        <para>
Just documenting the <emphasis role="bold">status</emphasis> field for the following tables
</para>
        <para>
NODES
</para>
        <itemizedlist>
          <listitem>
            <para>0: not published
</para>
          </listitem>
          <listitem>
            <para>1: published
</para>
          </listitem>
        </itemizedlist>
        <para>
COMMENTS
</para>
        <itemizedlist>
          <listitem>
            <para>0: published
</para>
          </listitem>
          <listitem>
            <para>1: not published
</para>
          </listitem>
          <listitem>
            <para>2: deleted (no longer exists in Drupal 4.5 and above)
</para>
          </listitem>
        </itemizedlist>
    </section>
    <section id="node-593">
      <title>Values of 'comment' field in node table</title>
        <para>
Here are the values of the 'comment' field in the node table:
</para>
        <itemizedlist>
          <listitem>
            <para>0 = comments cannot be added to this node and published comments will not
display
</para>
          </listitem>
          <listitem>
            <para>1 = comments cannot be added to this node, but published comments will
display
</para>
          </listitem>
          <listitem>
            <para>2 = new comments can be added and published comments will display
</para>
          </listitem>
        </itemizedlist>
    </section>
  </chapter>
  <chapter id="node-600">
    <title>Module how-to's</title>
      <para>
This section collects various 'How-to' articles of interest to module writers
and hackers.
</para>
    <section id="node-595">
      <title>How to write a node module</title>
        <para>
This information is superseded by the Doxygen documentation. In particular, its
<ulink url="http://drupaldocs.org/api/head/file/contributions/docs/developer/examples/node_example.module">
example node module</ulink> is a good tutorial.
</para>
    </section>
    <section id="node-596">
      <title>How to write database independent code</title>
        <para>
In order to ensure that your module works with all compatible database servers
(currently Postgres and MySQL), you'll need to remember a few points.
</para>
        <itemizedlist>
          <listitem>
            <para>When you need to LIMIT your result set to certain number of records, you
should use the db_query_range() function instead of db_query(). The syntax of
the two functions is the same, with the addition of two required parameters at
the end of db_query_range(). Those parameters are $from and then $count.
Usually, $from is 0 and $count is the maximum number of records you want
returned.
</para>
          </listitem>
          <listitem>
            <para>If possible, provide SQL setup scripts for each supported database
platform. The differences between each platform are slight - we hope
documentation on these differences will be forthcoming.
</para>
          </listitem>
          <listitem>
            <para>You should test any complex queries for ANSI compatibility using <ulink url="http://developer.mimer.se/validator/index.htm">this tool by Mimer</ulink></para>
          </listitem>
          <listitem>
            <para>If you are developing on MySQL, use it's <ulink url="http://www.mysql.com/doc/en/ANSI_mode.html">ANSI compatibility mode</ulink></para>
          </listitem>
          <listitem>
            <para>If you can install all database servers in your environment, it is helpful
to create shell databases in each and then run sample queries in each
platform's query dispatch tool. Once your query succeeds in all tools,
congratulate yourself.
</para>
          </listitem>
          <listitem>
            <para>Don't use <literal>''</literal> when you mean <literal>NULL</literal></para>
          </listitem>
          <listitem>
            <para>Avoid table and field names that might be reserved words on any platform.
</para>
          </listitem>
          <listitem>
            <para>Don't use auto-increment or SERIAL fields. Instead, use an integer field
and leverage Drupal's own sequencing wrapper:
<literal>db_next_id(&lt;tablename_fieldname&gt;)</literal></para>
          </listitem>
        </itemizedlist>
    </section>
    <section id="node-597">
      <title>How to write efficient database JOINs</title>
        <para>
This page is based on an e-mail posted by Craig Courtney on 6/21/2003 to the
drupal-devel mailing list: <ulink url="http://drupal.org/node/view/322">http://drupal.org/node/view/322</ulink>.
</para>
        <para>
There are 3 kinds of join: INNER, LEFT OUTER, and RIGHT OUTER. Each requires an
ON clause to let the RDBMS know what fields to use joining the tables. For each
join there are two tables: the left table and the right table. The syntax is as
follows:
</para>
        <para>
{left table} {INNER | LEFT | RIGHT} JOIN {right table} ON {join criteria}
</para>
        <para>
An INNER JOIN returns only those rows from the left table having a matching row
in the right table based on the join criteria.
</para>
        <para>
A LEFT JOIN returns ALL rows from the left table even if no matching rows where
found in the right table. Any values selected out of the right table will be
null for those rows where no matching row is found in the right table.
</para>
        <para>
A RIGHT JOIN works exactly the same as a left join but reversing the direction.
So it would return all rows in the right table regardless of matching rows in
the left table.
</para>
        <para>
It is recommended that you not use right joins, as a query can
always be rewritten to use left joins which tend to be more portable and easier
to read.
</para>
        <para>
With all of the joins, if there are multiple rows in one table that match one
row in the other table, that row will get returned many times.
</para>
        <para>
For example:
Table A
tid, name
1, 'Linux'
2, 'Debian'
</para>
        <para>
Table B
fid, tid, message
1, 1, 'Very Cool'
2, 1, 'What an example'
</para>
        <para>
Query 1:
SELECT a.name, b.message FROM a INNER JOIN b ON a.tid = b.tid
Result 1:
Linux, Very Cool
Linux, What an example
</para>
        <para>
Query 2:
SELECT a.name, b.message FROM a LEFT JOIN b ON a.tid = b.tid
Result 2:
Linux, Very Cool
Linux, What an example
Debian, &lt;null&gt;
</para>
        <para>
Hope that helps in reading some of the queries.
</para>
    </section>
    <section id="node-598">
      <title>How to connect to multiple databases within Drupal</title>
        <para>
Drupal can connect to different databases with elegance and ease!
</para>
        <para>
First define the database connections Drupal can use by editing the
<literal>$db_url</literal> string in the Drupal configuration file (settings.php for
4.6 and above, otherwise conf.php). By default only a single connection is
defined
</para>
        <programlisting>&lt;?php
$db_url='mysql://drupal:drupal@localhost/drupal';?&gt;</programlisting>
        <para>
To allow multiple database connections, convert <literal>$db_url</literal> to an
array.
</para>
        <programlisting>&lt;?php
$db_url['default'] ='mysql://drupal:drupal@localhost/drupal';$db_url['mydb']
='mysql://user:pwd@localhost/anotherdb';$db_url['db3']
='mysql://user:pwd@localhost/yetanotherdb';?&gt;</programlisting>
        <para>
Note that database storing your Drupal installation should be keyed as the
<literal>default</literal> connection.
</para>
        <para>
To query a different database, simply set it as active by referencing the key
name.
</para>
        <programlisting>&lt;?php
db_set_active('mydb');db_query('SELECT * FROM other_db');//Switch back to the default
connection when finished.db_set_active('default');?&gt;</programlisting>
        <para>
Make sure to always switch back to the default connection so Drupal can cleanly
finish the request lifecycle and write to its system tables.
</para>
    </section>
    <section id="node-599">
      <title>How to write themable modules</title>
        <para>Note: this page describes Drupal's theming from the code side of
things.</para>
        <para>
Drupal's theme system is very powerful. You can accommodate rather major
changes in overall appearance and significant structural changes. Moreover, you
control all aspects of your drupal site in terms of colors, mark-up, layout and
even the position of most blocks (or boxes). You can leave blocks out, move
them from right to left, up and down until it fits your needs.
</para>
        <para>
At the basis of this are Drupal's theme functions. Each theme function takes a
particular piece of data and outputs it as HTML. The default theme functions
are all named <literal>theme_something()</literal> or
<literal>theme_module_something()</literal>, thus allowing any module to add
themeable parts to the default set provided by Drupal. Some of the basic theme
functions include: <literal>theme_error()</literal> and <literal>theme_table()</literal>
which as their name suggest return HTML code for an error message and a table
respectively. Theme functions defined by modules include
<literal>theme_forum_display()</literal> and <literal>theme_node_list()</literal>.
</para>
        <para>
Custom themes can implement their own version of these theme functions by
defining <literal>mytheme_something()</literal> (if the theme is named
<literal>mytheme</literal>). For example, functions named:
<literal>mytheme_error()</literal>, <literal>mytheme_table()</literal>,
<literal>mytheme_forum_display()</literal>, <literal>mytheme_node_list()</literal>, etc.
corresponding to the default theme functions described above.
</para>
        <para>
Drupal invokes these functions indirectly using the <literal>theme()</literal>
function. For example:</para>
        <programlisting>&lt;?php
$node=node_load(array('nid'=&gt;$nid));$output.=theme("node",$node);?&gt;</programlisting>
        <programlisting>theme_node($node)</programlisting>
        <programlisting>mytheme_node()</programlisting>
        <programlisting>mytheme_node($node)</programlisting>
        <para>
This simple and straight-forward approach has proven to be both flexible and
fast.
</para>
        <para>
However, because direct PHP theming is not ideal for everyone, we have
implemented mechanisms on top of this: so-called template engines can act as
intermediaries between Drupal and the template/theme. The template engine will
override the <literal>theme_functions()</literal> and stick the appropriate content
into user defined (X)HTML templates.
This way, no PHP knowledge is required and a lot of the complexity is hidden
away. More information about this can be found in the <ulink url="http://drupal.org/node/509">Theme developer's guide</ulink>, specifically the
<ulink url="http://drupal.org/node/11774">Theming overview</ulink>.
</para>
    </section>
  </chapter>
</book>

