Customization and theming

A customization and theming guide for getting more out of your Drupal site. While you can make a fairly nice site using Drupal core modules, in order to leverage Drupal's built in power you should be somewhat familiar with PHP, MySQL and web design.

Included in this section are PHP and SQL code snippets and examples for use in your sites pages, blocks and themes. There are also a few articles on theming engines, which provide the infrastructure to build and create new themes.

Note: Feel free to add handbook pages that are relevant to this section. They go into the moderation queue for review and approval.

Contributed modules

A large number of community created modules allow you to enhance your Drupal system.

You must install and configure each module individually on your machine. Each module download contains specific install instructions. Further description of specific modules' administration and use is contained within. Use modules that have the same version as your Drupal installation.

The following list is just a sample of some useful modules. More modules are added by various people on a constant basis. You can check the latest modules and download them from the Drupal contributed modules project section.

You can keep track of recently created modules by reading Nodes tagged with module term (RSS feed of same) you can also see modules sorted by last update though that page lacks an RSS feed.

NOTE: Not all contributed modules are listed here. Please help by contributing documentation for those that are not. When possible work with the module contributor to ensure accuracy.

Actions: stored procedures for Drupal

The actions module allows the configuration of Drupal actions. A Drupal action is a specially written PHP function whose parameters are configured through the web. For example, the Send Email action has parameters Recipient, Subject, and Message. You could fill in MrFoo@example.com for the recipient, Hi for the subject, and Hello, Mr. Foo for the message to create an instance of the Send Email action. This action instance could then be fired by a module at appropriate times when you want Mr. Foo to get an email.

Actions are like stored procedures that can be loosely coupled with events in Drupal.

Let's examine a ridiculously simple example. Suppose you want to be draconian with your Drupal site's comment policy. Any user who leaves a comment gets blocked immediately. You can implement this quite easily with the comment hook:

<?php
foo_comment
($a1, $op) {
   if (
$op == 'insert') {
     global
$user;
    
// block user here...
  
}
}
?>

What an action does is wrap up this code into a nice package. That package can then be associated with the nodeapi hook. Or some other hook. Actions takes this function, which we'll call "Block current user", and allows you to assign it to any hook-op combination.

We do that by several coding conventions. Here is the skeleton of a simple action:

<?php
/**
  * Implementation of a Drupal action.
  */
function action_block_user($op, $context = array(), &$object) {
   switch (
$op) {

     case
'do':
       global
$user;
      
// block user here...
      
break;

     case
'metadata':
       return array(
        
'description' => t('Block current user'),
        
'type' => 'User',
        
'batchable' => TRUE,
        
'configurable' => FALSE,
        
'supported hooks' => array(
          
'nodeapi' => array(
            
'delete' => 1,
            
'insert' => 1,
            
'update' => 1,
            
'view' => 1
            
),
          
'comment' => array(
            
'insert' => 1,
            
'update' => 1,
            
'delete' => 1
          
)
         )
       );
   }
}
?>

We've implemented two ops. The 'metadata' op describes the action by declaring some things, including what hook/op combinations it supports. The 'do' op is actually where the code runs. We list the 'do' op first in the switch statement for performance, since Drupal will then encounter it first during execution.

The above is a nonconfigurable action. There's nothing about it you can change. It will block the current user and that's that. It's basically a singleton action.

On the other hand, an action like "Send email" needs to know some things in order to run. It needs a subject, and body, a recipient, and such. Configurable actions are not available for use until they have been configured (obviously). They can be configured from the main actions interface at admin/build/actions.

Administration: Create an experience for your users

The administration module provides an interface for site administrators to create a experience for users. The module allows for a full-page interface of the administration menu and can be configured. The default interface for this module is task based to help administrators create accomplish the most common task identified in usability studies. It also includes statistical overview information about the site activities as well as links to handbook pages to address challenging administration tasks.

You can deactive menus.

You can:

AdSense Injector: automatic, no hassle insertion of AdSense ads in node content

The AdSense Injector module allows administrators to specify automatic insertion of AdSense ads into a node full-page view, or, if desired, after teasers in frontpage or taxonomy list views.

It provides centralized control of:

  • Ad format, channel, and group attributes for all inserted ads
  • Full control over allowed node types (for example, you can choose to never insert ads into image nodes, or forum nodes, etc.)
  • MInimum word count control for full-page node views: if a node doesn't have the minimum word count, ads won't be inserted.

AdSense Injector also respects the AdSense module visibility settings - if you have set visibility options in the AdSense module, AdSense injector will insert ads only into the paths you have chosen to allow.

Rationale

Why is this useful? In my experience, this simplifies certain important aspects of ad insertion and placement.

Traditional approaches:

  • Modify your theme's node.tpl.php or other template file(s) in order to inject ads on every node view.
    What happens if you have multiple sites or use multiple themes, or use custom per-node-type template files (node-book.tpl.php, node-image.tpl.php etc)? Now you have to edit, test, and maintain multiple template files, and, if the theme is updated to fix bugs, you have to merge in your changes.
  • Hand-edit each node content and use inline [adsense:x:y] inline filter tags.
    This gives tremendous flexibility in layout, but creates a maintenance nightmare if you should wish to alter your channel or ad layouts site-wide.
  • Use block insertion into the theme's template regions.
    This is great if your theme's regions provide the flexibility you want - it seems that themes vary somewhat in the regions they provide, and those regions aren't always in the places you want - so once again, you are back to tweaking theme template files if you want to place the ads near or in the content.

Usage

AdSense Injector uses (and requires) installation and proper configuration of the AdSense Module in order to function properly. Please install, configure, and test the AdSense module before you install Adsense Injector.

Examples

See it in use at http://exodusdev.com and http://www.roadcarvin.com

Coming soon:

  • Theming/CSS tricks
  • Examples

Configuration

Note: This page describes Adsense_Injector release 2.5 and later

Configuration is fairly straighforward.

Enable the options that make sense (insert in node body and / or on teaser lists), enable insertion on the desired node types, and configure the insertion templates as needed.

The default templates are fairly simple.

One interesting feature as of the 2.5 releases is the ability to insert before and/or after the node teaser or body, and the ad insertion now uses the adsense module filter tag format - in other words, you insert '[adsense:nnnxnn:1:1]' style tags rather than specify ad format in the configuration options. This vastly simplifies the configuration, and, provides maximum flexibility in setting up ad insertion.

Please see the adsense module help and documentation for more information on the filter tag and ad formats available for use.

Installation

Installation is done in the usual way: just drop the module into your selected modules directory and enable the module.

More info here:

http://drupal.org/node/70151

Stupid Template Tricks

Note: This page describes the 2.5 and later releases

Consider the default node insertion template (formatted for readability):

<div class="ad-auto-inserted" style="float:left; margin: 0 1em .25em 0;">
[adsense:120x240:1:1]
</div>
%body
<br class="clear"/>
[adsense:468x60:1:1]

On full node views, this will insert a 120x240 left-floating ad block before the node body, then, after the body text, a 468x60 ad will be appended, both ads using the adsense module's group 1 and channel 1 settings.

Now, then, what else can we do? How about putting an ad on both the left and right side of the top of the node, as well as at the bottom? We can do that very easily, using inline float styles (for the purposes of this example - it's cleaner to use stylesheet-defined CSS rules).

(Yes, this is probably annoying in-your-face advertising, and I wouldn't recommend it, but it serves to demonstrate capabilities.)

<div class="ad-auto-inserted" style="float:left; margin: 0 1em .25em 0;">
[adsense:120x240:1:1]
</div>
<div class="ad-auto-inserted" style="float:right; margin: 0 0 .25em 1em;">
[adsense:120x240:1:1]
</div>
%body
<br class="clear"/>
[adsense:468x60:1:1]

Give it a try.

Obviously, you can play a lot of tricks with the template strings - for example, you can insert any arbitrary text before or after the node body, including html, javascript, etc. The sky (and your imagination) is the limit, just be careful not to break anything. (At this point, the adsense_injector module can do more than just inject ads using the new template scheme.)

Please see the adsense module help and documentation for more information on the filter tag and ad formats available for use.

Archive: view content by date

NOTE: This module has been removed from core as of 5.0

The archive page allows content to be viewed by selecting the day. It also provides a monthly calendar view that users can use to navigate through content.

To view the archive by date, select the date in the calendar. Administrators can enable the Calendar to browse archives block in block administration to allow users to browse by calendar. Clicking on a date in the monthly calendar view shows the content for that date. Users can navigate to different months using arrows beside the month's name in the calendar display. The current date will be highlighted in the calendar.

You can

  • view your archive by day.
  • enable the Calendar to browse archives block at administer >> block.

View an archive by type or by category

Each module that produces content may have a page that lists content of that type. For example all blog posts can be seen at the blog path. Content that has been categorized using taxonomy terms have a page for viewing that content.

Asset: Unify asset file management in Drupal

Placeholder for the documentation of the asset module.

Audio: Uploading and playback of audio files

The audio module allows users to add audio media content to a site. Audio is an important medium for community communication. The recent rise of the podcast phenomenon is an example of the trend towards audio content. The audio module enables music, spoken word, and voicemail messages to be played on a site, and all audio node content is automatically podcast-enabled.

The audio module allows a user to create a new audio post. An audio post lets you uploadand download audio files, and uses the getID3 library to read and write ID3 tag information from the audio file. The getID# library is required. This module comes with a handy flash player that can be embeded in your site, to allow streaming of the audio without allowing downloads. Audio files can be submitted by browsing users computer for files to upload. Audio posts can also be configured to allow the files to be downloaded. Audio administration allows the path for audio files to be uploaded to be configured. The getID3 library can also be configured in audio administration.

You can:

  • listen to the most recent audio files added in your user profile at my account.
  • add an audio file at create content >> audio.
  • enable the latest audio and random audio blocks at administer >> block.
  • administer audio module at administers >> settings >> audio.
  • download and install the required getID3 library from getID3 sourceforge page.
  • file issues, read about known bugs, and download the latest version on the Audio project page.

Add album images to the teaser

If you've got the audio_image module enabled you can attach images to audio nodes. Currently the images are not displayed as part of the teaser. The following code snippet shows how to override the default teaser theme function on a PHPTemplate theme. Add this to your template.php file.

For Drupal 4.7

<?php
function phptemplate_audio_teaser($node) {
 
// make sure that all the allowed tags are included.
 
foreach (audio_get_tags_allowed() as $tag) {
   
$params['%'. $tag] = isset($node->audio_tags[$tag]) ? $node->audio_tags[$tag] : '';
  }
 
$params['%filelength'] = theme('audio_format_filelength', $node->audio_fileinfo);
 
$params['%fileformat'] = theme('audio_format_fileformat', $node->audio_fileinfo);
 
$params['%player'] = audio_get_player($node);
 
$params['%play_count'] = $node->audio_fileinfo['play_count'];
 
$params['%download_count'] = $node->audio_fileinfo['download_count'];

 
// THE CHANGE FROM THE ORIGNAL FUNCTION IS HERE
  // if there's an image, put it in as an %image parameter.
 
if ($image = audio_images_get($node->audio_images)) {
   
$params['%image'] = "<div class='audio-image'>\n" . theme('audio_image', $image) . "\n</div>\n";
  }
 
// THAT'S ALL

 
$format = variable_get('audio_teaser_format', '%player %filelength');

  return
t($format, $params) . $node->teaser;
}
?>

For Drupal 5

<?php
function phptemplate_audio_teaser($node) {
 
// make sure that all the allowed tags are included.
 
foreach (audio_get_tags_allowed() as $tag) {
   
$params['!'. $tag] = isset($node->audio_tags[$tag]) ? check_plain($node->audio_tags[$tag]) : '';
  }
 
$params['!filelength'] = theme('audio_format_filelength', $node->audio_fileinfo);
 
$params['!fileformat'] = theme('audio_format_fileformat', $node->audio_fileinfo);
 
$params['!player'] = audio_get_player($node);
 
$params['!play_count'] = check_plain($node->audio_fileinfo['play_count']);
 
$params['!download_count'] = check_plain($node->audio_fileinfo['download_count']);

 
// THE CHANGE FROM THE ORIGNAL FUNCTION IS HERE
  // if there's an image, put it in as an !image parameter.
 
if ($image = audio_images_get($node->audio_images)) {
   
$params['!image'] = "<div class='audio-image'>\n" . theme('audio_image', $image) . "\n</div>\n";
  }
 
// THAT'S ALL

 
$format = variable_get('audio_teaser_format', '!player !filelength');

  return
t($format, $params);
}
?>

For more info see #78883, the issue that spawned this.

Audio module FAQ

Q: Why does the player stop at weird parts?
A: I'm not really sure. If you're running into this bug please leave a comment on this issue with more information.

Q: Is there an upgrade script for 4.6 to 4.7?
A: Yes, there is read more about on issue #57769.

Q: I don't want to have to upload the audio to my site and waste bandwidth. Can I use remote URLs?
A: No, but it's a feature I'd like to implement.

Q: Why do I have to click the Flash player twice?
A: You're using IE. See #70241.

Q: I can only upload 2MB files or less.
A: If you notice that you can only attach 2Mb MP3's, you need to up your limits. Assuming your host allows this - you need to add a line like this to your .htaccess file: php_value upload_max_filesize 20M or check the upload limitations of your PHP.ini settings.

Q: I screwed something up. How do I completely re-install the audio module?
A: First, you need to remove the audio module's tables:

DROP TABLE audio;
DROP TABLE audio_file;
DROP TABLE audio_metadata;
Next you'll need to remove the record from Drupal's system table that lists the audio module as an installed module. Deleting it means that the next time you enable the module, the install function will be run:
DELETE FROM system WHERE name = 'audio';
After running that query, re-enable the module and check for any errors. The tables should be created.

Audio module review

This module makes use of the wonderful getID3 PHP library for reading and writing ID3 tags for mp3 and other audio files. Creating a new audio file is as simple as creating a new 'audio' node type. The ID3 tags are extracted from the audio file and displayed in the node listing. The title can optionally be automatically set using the ID3 tag information, by default it is artist - song title. The node body itself contains all of the ID3 information for the audio file, with links for each of the fields. This allows a user, for instance, to find all audio files by artist, title, album, genre, and year, by simply clicking the link. Additionally, the length and format of the file are extracted and displayed too. And the best feature, it includes the XSPF Flash player so you can play the audio file right on the website, without having to download! If a user does want the audio file, there is a download link available as well. Additionally, if the ID3 tags are incomplete upon uploading, anyone with node edit permissions can edit the audio node and update the ID3 tags automatically! This module also keeps track of how many times the audio file has been downloaded and how many times it has been played through the website.

Because this audio file is created as a node, all of the wonderful node properties can be used. The most useful would be setting up a category (or a taxonomy) to label the content in this audio file, which can be setup to use 1 or more terms to "tag" the audio file. Each audio file can also be setup so users can post comments on the same page as the audio file as well.

Also, this module adds a link to each user's profile that links to all of their recent audio files, making it very handy to find all audio files uploaded by a certain person.

This review was adapted from a review written on Lullabot.com

Change audio player in 4.7

If you want to make changes to the flash player you can over-ride the theme function. To understand "theme level" and overriding functions in drupal go here. To change the player type you need to make a template file in your theme directory. I use PHPTemplate so the file is called template.php.

The following (assuming you're using PHPTemplate) makes the flash player red:

<?php
// override the colors on the flash player
function phptemplate_audio_mp3_player($node, $options = array()) {
   
$options['b_fgcolor'] = 'cccccc';
    return
theme_audio_mp3_player($node, $options);
}
?>

There are several option values that can be used to control the colors:
  • Background color. If it's not specified, it'll default to transparent. 'b_bgcolor' => "000000"
  • Foreground (border and icon) color. 'b_fgcolor' => "ffffff"
  • Foreground color by state. The order is: connecting (spinner), ready (arrow), playing (square), and ??? (not sure). The b_fgcolor value will be used for any ommitted values. 'b_colors' => "ff0000,,00000ff,000000"
Override to use the Slim player

In the template.php file I put this code which overrides the theme_audio_mp3_player function to use the 'Slim' player:

<?php
function phptemplate_audio_mp3_player($node, $options = array()) {
  global
$base_url;

 
// flash only supports a limited range of sample rates.
 
switch ($node->audio_fileinfo['sample_rate']) {
    case
'44100': case '22050': case '11025':
      break;
    default:
      return;
  }

 
$options = array_merge((array) $options, array(
   
'song_url' => $node->url_play,
   
'song_title' => $node->audio_tags['title'],
  ));

 
$params = array();
  foreach (
$options as $key => $val) {
   
$params[] = rawurlencode($key) .'='. rawurlencode($val);
  }

 
$url = $base_url .'/'. drupal_get_path('module', 'audio') .'/players/xspf_player_slim.swf';
  if (
$params) {
   
$url .= '?'. implode('&amp;', $params);
  }

 
$output = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="400" height="15" id="xspf_player" align="middle">';
 
$output .= '<param name="allowScriptAccess" value="sameDomain" />';
 
$output .= '<param name="movie" value="'. $url .'" />';
 
$output .= '<param name="quality" value="high" />';
 
$output .= '<param name="bgcolor" value="#e6e6e6" />';
 
$output .= '<embed src="'. $url .'" quality="high" bgcolor="#e6e6e6" width="400" height="15" name="xspf_player" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />';
 
$output .= '</object>';

  return
$output;
}
?>

Next you'll need to download the 'Slim' player from the website here. Transfer these files to the /modules/audio/players/ directory on your server and ensure that the shockwave file is named exactly as at the end of the $url line.

Now refresh your page (force reload by adding a '?' to the end of your url) and now everytime that Drupal tries to use theme_audio_mp3_player, which uses the standard 'button' player it will be overridden with the above code and instead use the 'Slim' player.

If you would instead like to use the 'Full' player then substitute the code after $output with the required code from the XSPF Player website.

Avatar Selection: allows users to pick an avatar from a list

Description

When a user edits their accounts details they can choose to upload an image or photo of themselves, also known as an avatar. The Avatar Selection module allows the user to pick an avatar image from a list already loaded by an administrative user. It is also possible to disable the uploading of pictures by users and only allow them to select an avatar icon from this list.

No images are supplied with this module. It is recommended that all images you use are roughly of the same size.

Configuration

Once the module is activated, go to Administer >> Site configuration >> Avatar Selection (admin/settings/avatar_selection). Here you'll find a form that will allow you to upload or delete images. Users with the 'administer avatar selection' access permission will be able to upload and delete the images.

Current maintainer

Stella Power (http://drupal.org/user/66894)

Avatar Selection - FAQ

Commonly asked questions and problems
My images aren't appearing
If your transfer method is set to "private" on Administer >> Site Configuration >> File System, then you may have problems viewing the avatar selection images. Please ensure that the file system path set is an absolute path and if you've changed your transfer method from "public" to "private" since uploading the images, you may need to re-upload them again. See http://drupal.org/node/92376 for more info.

Avatar Selection - Installation

Install the files

Download the latest version of the Avatar Selection module from the project page. Untar/unzip the files and place the entire avatar_selection directory in an appropriate module directory like sites/all/modules.

Enable the module
  • Go to Administer » Site Building » Modules
  • Make sure "Avatar Selection" is enabled (listed under "Other").
  • Click Save configuration at the bottom of this form.

Avatar Selection - Settings

The Avatar Selection module can be configured at Administer >> Site Configuration >> Avatar Selection. Users will need the 'administer avatar selection' permission to alter the settings and to upload or delete images.

  • Disable users uploading pictures to profile - This allows users to pick their avatar image from the selection provided by this module but prevents them from uploading new avatars when editing their account.
  • Force users to select an avatar image - This only comes into effect on the user registration screen or when image uploads are disabled.
  • Upload image - This adds a new avatar image to the selection. Maximum dimensions are 85x85 and the maximum size is 30 kB. Images must have one of the following extensions (case sensitive): png, jpg, jpeg, gif, PNG, JPG, JPEG, GIF.

To delete an image from the selection, go to admin/settings/avatar_selection/delete and check the box for each image you wish to remove. Then just click on the "Delete" button to finish. There is no delete confirmation message, so please ensure you have the correct boxes checked before clicking on this button.

Avatar Selection - User Permissions

There are just two permissions you can assign a user with the Avatar Selection module.

  • access avatars - users with this permission will be able to view the avatar selection list, and select an avatar from it, when they edit their account details.
  • administer avatar selection - this permission enables users to upload and delete avatar images and alter the configuration settings.

Backup: create an archive of your Drupal database and files

The backup module performs a backup of your entire Drupal website. An archive file, or tarball, of your Drupal database and all your files is created on the webserver. You can then download this file with your web browser.

You can:

  • create a backup tarball at administer >> content >> backup.
  • file issues, read about known bugs, and download the latest version on the Backup project page.

Restoring from a backup

The backup module doesn't have the capability to restore from a backup, as this would involve Drupal having its own files and database pulled from under it as it runs.

To restore a backup archive from a unix / linux command line:

  1. Untar the tarball into your document_root directory. This will recreate the file structure.
  2. Unzip the database file. You'll need to use the gzip program for that.
  3. Re-create the database by typing "mysql < database-dump-filename". Depending on your configuration, you may need to supply credentials via parameters to MySQL as necessary.

Banner: management and display of rotating image banners

The banner module provides a way to manage banners on your site. You can upload images, allow users to upload their own banners, choose to approve banners that are uploaded, choose what URL to link banners to, and track statistics around the display and click-through rates of banners.

Banners are meant to be added either in a custom block or directly to your theme. See the Adding banners to your site section that follows for instructions on how to accomplish this.

Note: this is a work in progress. Please feel free to edit and extend this page.

You can:

  • get info about your banners under the my banners link, as well as add new banners if you have permission.
  • administer all banner settings at administer >> banners, including viewing all banners, editing settings such enabling or disabling individual banners, and adding new banners as an admin user
  • set banner module sitewide preferences at administer >> settings >> banner.
  • file issues, read about known bugs, and download the latest version on the Banner project page.

Adding banners to your site

Starting with version 4.7 of the module, Banners have their very own node type. To add a graphic banner, you need to have the Upload module enabled (graphics are attached to the banner nodes). Also, you will need to add them to a taxonomy term. Click administer » categories and either create a new vocabulary with the "banner" node type or add the "banner" node type to an existing vocabulary. Once you're done, click create content » banner to create a banner.

As listed on the Banner module info page, the banners module provides an interface to manage banners. To actually display the banners on your site, you need to add a small snippet of PHP code in either a block or directly to your theme template.

If you are not comfortable with editing your theme, adding a custom PHP block is the easiest method, but you will only be able to display banners in the sidebar. To add, for example, a banner to your header, you will need to edit your theme.

The banner_display(42,1) function is what is needed to display banners.

The number in parentheses refers to the taxonomy term ID from which you would like to show banners. Banners belong to taxonomy terms, so you will need to know the term ID for which they belong. You can usually find this out by going to administer » categories, then click the "list terms" link in your banner vocabulary to find the IDs for the category you use.

Displaying banners in a custom block

  1. Click administer » blocks then the add tab.
  2. Type in a title. This will be shown to the user as the heading for the block.
  3. Choose "PHP code" as the input filter
  4. Paste in the following code: <?php return banner_display(42, 1); ?>
    • Note: replace 42 with the taxonomy term ID from the category you assigned the block(s) you want to rotate through in this block
  5. Type in a block description. Only administrators will see this.
  6. Click the Save block button

See the Blocks section for more information on working with the placement and setup of blocks.

Displaying banners directly as part of your PHPTemplate-based theme

  1. In your theme's page.tpl.php file where you want the banner to appear, paste in the following code:
    <?php print banner_display(42,1); ?>

As noted above, the number indicates from which taxonomy term ID to display banners. You may add this code in multiple places in your theme, with either the same or different banner group numbers.

Important note: the code is slightly different than that for a block, in that it uses 'print' instead of 'return'.

BBCode: Format text using BBCode

The Drupal bbcode.module adds a BBCode filter to Drupal. This allows you to use HTML-like tags as an alternative to HTML itself for adding markup to your posts. BBCode is easier to use than HTML and helps to prevent malicious users from disrupting your site's formatting.

See the help screen of the module (or the code) for information on which tags and variants are supported. This implementation is not necessarily the same as the original BBCode implementation.

Note that this filter also recognizes and converts URLs and email addresses to links automatically

You can file issues, read about known bugs, and download the latest version on the BBCode project page.

Biblio: manage lists of scholarly publications

The biblio module allows you to create and maintain bibliographic lists of publications. The full (HTML) text of the publication can be included is so desired. Other files such as PDF or Word documents can be attached using the upload module.

Bibliographic entries can be imported from EndNote (versions 7,8,9,10 in XML or tagged format) and BibTex. The bibliographic lists can be formated in a number of styles including: American Psychological Association (APA), Council of Science Editors (CSE) and Institute of Electrical and Electronics Engineers (IEEE).

1) Installation and updating

4.7 and greater
Unpack the module ".tar" file in the Drupal module directory. (Note: It is often advisable to create a "contrib" directory under the main module directory in order to make house keeping and upgrades easier).
Now visit the admin/modules page and enable the biblio module. This will install the required database tables and setup a number of pre-defined
publication types. These types can be changed or deleted on the
admin/settings/biblio/types page.

Updating: Any time you install an new version of the module you should run the update.php script in case there are any database modifications included with the new version.

4.6
Unpack the module ".tar" file in the Drupal module directory, then create the database tables using following command:

mysql -u {userid} -p {drupaldatabase} < biblio.mysql

You will also have to enable the module on the admin/modules page.

2) Configuration

Base URL
This sets the base URL used to access the biblio module (e.g. /biblio ).
Number of results per page.:
This sets the number of results that will be displayed per page.
Restrict users such that they can only view their own biblio entries
This option restricts the users capability to view biblio entries. They will only be able to see the entries which they have created and own.
Footnotes
You can integrate with the footnotes module here.
Integration with the footnotes module
Depends on: Footnotes (enabled)
This will convert tags into tags. This will cause intermingled and tags to be sequentially numbered. For this to work, you must put the filter ahead of the filter in the filter chain. If this option is not set, and tags will be handled separately.
OpenURL
You can set an openurl link here
OpenURL Base URL:
This sets your institution's base OpenURL gateway, which is used to generate OpenURL links. To implement a "Universal" OpenURL system, try using OCLC's OpenURL Resolver Registry gateway: http://worldcatlibraries.org/registry/gateway
OpenURL Image:
Enter a path to your image here, this image will be used as button which when clicked will find the entry via the OpenURL link
Sorting
You can set the default sorting and ordering for the /biblio page here.
Sort by:
Author, Title, Type, Year
Order:
Descending, Ascending
Styling
You can set the default style for the /biblio page here.
Normalize author names when displaying biblio records
Tries (doesn't always work) to reformat author names so that they are displayed in the format "Lastname, Initials" e.g. Smith, J.S. (Note: This setting does not modify the entry in the database, it only reformats it's presentation. This option can be turned off at any time to display the original format.)
Links open in new browser
This causes the Author and Title links to open in a new browser window
Node Layout:
Original
Only Fulltext if available
This alters the layout of the "node" (full) view.
Style: (This alters the layout of the "list" view)
Council of Science Editors (CSE)
American Psychological Association (APA)
Institute of Electrical and Electronics Engineers (IEEE)
Classic - This is the original biblio style
Syndication
You can set the RSS defaults here.
Allow RSS feeds of new biblio entries
This will create an rss feed of the most recent biblio entries. It will be available at /biblio/rss.xml
Number of items in the RSS feed.:
Limits the number of items in the /biblio/rss.xml feed to this number.
Taxonomy
You can set the Taxonomy defaults here.
Use keywords from biblio entries as taxonomy "free tags"
This option allows user to add keywords (free tags) to describe their documents. These keywords will be registered as taxonomy.
Vocabulary:
Select vocabulary (category) to use for free tags.

3) Basic usage

visit biblio/

(more to follow)

4) Advanced usage

(content to follow)

5) Customization

(content to follow)

6) Uninstalling

Drupal 5.x
Disable the module on the admin/modules page, then click on the uninstall tab and select the module for removal. This will remove all of the biblio entries in the node table as well as the biblio tables them selves. NOTE: You will loose any/all bibliographic entries that you have made and this action cannot be undone.
So don't say you weren't warned!
Drupal 4.6/4.7
This is a bit trickier since you must delete all "biblio" type entries out of the node table (and in 4.7 the node_revisions table), then you can drop the biblio tables.

BitTorrent: transfer large files efficiently with peers

The BitTorrent module allows the distribution of large files via the BitTorrent network technology. By sharing the cost of downloading as well as uploading with a network of other users no one site has to carry the burden of every download. This is very important for a network of community sites that want to share media such as podcasts, video blogs, and participatory culture media. BitTorrent should not be used to distribute copyrighted material. It is an open system and law enforcement officials can easily track and prosecute users who attempt to pirate content. This module provides a BitTorrent tracker which coordinates the sharing of content via a map of the content pieces in a .torrent file.

There is a list of torrents that are being tracked. You can set the explanation of BitTorrent for users of your site in general settings. You can also make a list of torrent announce URLs that your tracker will use. This module is not currently being distributed. It was developed by Digital Bicycle and will be distributed by CivicSpace Labs in a manner to avoid its use as a piracy tool.

You can:

Torrent tracker module and torrent node type

Blog Information: User Blog Titles and Descriptions

The Blog Information module gives each user, with access permission, the ability to have their own blog title and description. This is similar to sites like blogger.com blogs.

Once enabled, and the appropriate groups have permission, the user on their edit account page as a new section call Blog Information asking for a users blog title and description. This information is displayed via a block on www.example.com/?q=blog/[user] pages and on blog nodes.

Drupal 5 Version 2.x

In the 2.x version of the Blog Information module there are 2 new features.

  1. Input Formats - The blog description now utilizes input formats.
  2. Customizing the Content - The content section of the block displaying the blog information description can now be themed. This content section of the block is now themed by the theme_bloginfo_block function.

Remove Page Title on pages with Blog Information block

On a blog created by the blog module the page title for the blog reads like "example's blog" where the name (example in this case) is the blog authors username. On these pages you may wish to wish to remove the page title and use the Blog Information block instead.

To do this, in phptemplate, you need to modify your page.tpl.php file. Change:

<?php
print $title;
?>

To:

<?php
if (arg(0) != 'blog' && $node->type != 'blog') { print $title; }
?>

This will not display the $title tag on pages that are /blog or have a node type of 'blog'. On all other pages it will be displayed.

Buddylist: list your social network

Buddy list enables users to add buddies in their social network to their user account. Users can maintain a list of their buddies and keep track of what their buddies are posting to the site. They can also track their buddy's buddies and thereby explore a social network.

You can add buddies via user profiles. The administrator must enable viewing users profiles to be able to use the buddy list option. On the View tab of the user profile, there is a Buddy list section. One of the buddy list actions is Add Buddy. Select this action to add a user to your buddy list. Administrators can also enable the Buddylist block, or Recent Buddy Content. This block allows you to see a list of your buddies and content from your buddies respectively. An administrator can also enable the Friends Of A Friend (FOAF) module to allow for sharing of buddy lists between different social networking applications.

You can

  • add a buddy by looking at user profiles.
  • allow users to view profiles in administer >> access control >> permisions.
  • enable the buddy list blocsk at administer >> block.
  • administer the buddy list block at administer >> settings >> buddylist.
  • file issues, read about known bugs, and download the latest version on the Buddylist project page.

Calendar: Any Views Date in a Calendar

Also see Date documentation.

See the Calendar Project Page to download and install this module. Please report problems on the Issue queue rather than as comments on this page, since that makes the handbook quite messy and hard to read. Plus, many of the problems reported here are already reported or fixed, which you can see in the issue queue. I'm going to start removing comments that are bug reports or support requests from this page and asking you to report them on the issue queue instead. Comments that help clarify the handbook or point out ways that it could be improved are perfectly OK.

Requires Views and the Date API. This module will display any Views date field in calendar formats. Works with CCK date fields, Event module dates, node created and updated dates, and any other Views date field. The module does not create dates, it only displays dates that have already been created elsewhere, like CCK date fields.

Multiple calendars can be created for different purposes, i.e. one that displays node created dates as an archive calendar and another to display CCK dates for a custom content type, just give each a unique url.

You can use the supplied default calendar view as a starting point, or create one or more calendar views from scratch:

Setup

  • Select the Calendar display as the page type for your view. If you want to have a mini calendar available in a block, select the Calendar display as the block type as well as the page type.
  • Add any fields you want to display on the calendar as fields to the view. Be sure to include fields that contain the dates that need to be shown on the calendar. Date fields can include CCK date fields, event start and end dates, or dates like the node created or updated dates. If you use CCK date fields, set them to 'Do not group multiple values' so each value will display in its own date box.
  • Add three arguments to the view: Calendar Year, Calendar Month, and Calendar Day, in that order. Set each of them to 'Display All Values'.
  • Add sorts to the view for each date field so the dates are sorted correctly.
  • In most cases, add a Node: Type filter to filter the view by the content types that contain your selected date fields. The default view has a Node: Type filter that selects all node types. Change it to select only the node types you want in your calendar.

Updating From Earlier Versions

The Calendar module now uses the Date API from the Date module. The Date API was pulled out of the Date module so you can use the API with no dependence on CCK. For anyone updating a previous version of the Calendar module may run into problems on the update since the Calendar module will be installed but the Date API will not. The easiest way to make the transition is to uninstall the Calendar module (and Date module if you are using it) before uploading the latest code to your site, then go to the modules page and enable both Date API and the Calendar module. If you don't uninstall the Calendar module first, you will see some errors when you upload the new code, but it will automatically uninstall the Calendar module until you go to the modules page and install the Calendar module and the Date API module.

Other Features

Switch between year, month, week, and day views with back and next navigation.

A mini calendar is available using the block view if the block view is also set to the Calendar type.

Two other blocks are available. A legend block will display only on calendar pages and will show a legend of the color coding for field names and content types.

A Switch Calendar block will display only on calendar pages and allows the user to switch between calendar, list, table, teaser, and full node views for whichever time period is being viewed. The back/next navigation will remain at the top of the traditional views displays so you can move from one month to the next, for instance.

iCal Import and Export

To use iCal import or export, enable the new Calendar iCal module.

To export the calendar as an iCal feed, add a fourth Calendar argument to your view, the Calendar: iCal Feed argument.

To import iCal feeds from other locations into your calendar, choose the 'iCal' tab you see when looking at your calendar and fill out information about the feeds you want to insert into the calendar.

Combining Calendar and Traditional Views

There was lots of good discussion in the DrupalCon presentation on Date + Calendar. Based on Earl's suggestion to make sure the Calendar arguments will work with any view and requests from the audience for the ability to show a list in the full page and a calendar in the block, or other combinations of calendar and traditional views in the page and block, I've made reworked the argument handling to make the Calendar arguments work independently of the plugin theme.

In the lastest commit to the 5.x and HEAD versions, you can now add calendar arguments to any view with a date field (list, table, teasers, etc) and get date-based back/next navigation just like the calendar view has; you can have a mini calendar in the block view and a list in the page, or any other combination you like. Just be sure the date field you want to use as the filter is listed in the Fields list.

Themeing the Results

Calendar module results can be customized by changing the included css and/or themes. The key theme is located at the top of the calendar.theme file and looks like:

<?php
/**
*  Themeable node display
*
*  appends the field name to the title
*  constructs a teaser out of any non-date fields in the view
*/
function theme_calendar_calendar_node($node, $type) {
  
// Display the regular teaser view for local events.
  
if (!$node->remote && $type == 'calendar_node_day') {
    
$node = node_load($node->nid);
    
$node->teaser = node_view($node, TRUE, FALSE);
    
$node->title = '';
   }
  
// For other views, construct a teaser out of the provided fields.
  
else {
     if (
$node->label && !strstr($node->title, $node->label)) {
      
$node->title .= ' ('. $node->label .')';
     }
     if (
$node->fields) {
       foreach (
$node->fields as $field) {
        
$node->teaser .= '<div>'. $field .'</div>';
       }
     }
     if (!
$node->url && !$node->remote) {
      
$node->url = "node/$node->nid";
     }
   }
  
// Remote nodes may come in with lengthy descriptions that won't fit
   // in small boxes of year, month, and week calendars.
  
if ($type != 'calendar_node_day' && $node->remote) {
    
$node->teaser = '';
   }
   return
theme($type, $node);
}
?>

This default theme will display the node title with the field label appended to it, the date, and any other fields that were added to the view. This 'teaser' will be sent to a theme from calendar_api.inc based on the $type chosen. The types are 'calendar_node_month', 'calendar_node_day', 'calendar_node_week', and 'calendar_node_year'. The theme can be overridden in the same way other themes are, by copying that function to template.php and changing 'theme' to the theme name, then making any appropriate changes.

For instance, if you want different results when looking at the 'day' view, you could add a switch to the above theme based on the type, then if it is a 'day' type, do a node_load() to pick up more node information and print it out, or node_view() to display the complete node.

Debugging

  • If you see things consistently one day off or somtimes missing from a time period they should appear in, it is most likely a timezone conversion issue, so double check the timezone configuration for that item.
  • If you use CCK dates and have them set up with no hours granularity, you must also set the field up to use no timezone conversion. The current version of the Date module requires this, but date fields created with earlier versions may be set up incorrectly. Dates with no hours get the hour set by default to midnight, so any negative timezone conversion will move it back to the previous day.

Step by Step Setup of Calendar View

Why not use the Event module?

Using the Event module is perfectly acceptable, quicker/simpler way to get a basic calendar running on your site. If you need something more flexible/extensible, here's how to create a Calendar view. If you've got other differentiating factors, list them in the comments, and I'll incorporate them here.

How to implement a Calendar View

Using

Install the Modules

Download and extract the modules in your /sites/all/modules directory.
Go to Administer › Site building > Modules and enable:

  • Content
  • Date (under CCK)
  • Date API (under Other)
  • Calendar
  • Views
  • Views UI
Make a custom Content Type to go on you calendar

Go to Administer › Content management > Add Content Type

  • Name=Performance (or "event" or "meeting" or whatever you want to call it)
  • Type=performance
  • Description = Sing, Sing a song...
  • Whatever you prefer for the rest of the form
  • Save the Content Type

Edit the content type again and then select Add field

  • Name = Time
  • Field Type = Date / Selected list (or Date / Textfield with javascript pop-up calendar - if you have jstools enabled)
  • Click "Create Field" and you'll be taken to second form
  • Widget = Selected List (or Textfield with javascript pop-up calendar - if you have jstools enabled)(not sure why you have to select this twice)
  • Label = Time
  • Help Text = whatever
  • Under Data settings:
    Required: checked
    Multiple Values: not checked
  • Input Options
    Granularity: Year,Month,Day,Hour, Minute (but not second) selected
    To Date: Optional
  • Click Save Field Settings
  • Make a Performance so you'll see something on the calendar once it's working

    Create content > Performance

    • Title = Check your calendar
    • From Date = This Friday at 3:00pm
    • To Date = blank
    • Body = Hope I'm doing something fun this weekend
    • Click Submit
    Modify the default calendar view

    Go to Administer › Site building > Views and you should have a Calendar View
    Click Add..

    • The info under Basic Information, Page, and Block are all fine.
    • Under Fields delete the Node: Updated Time
    • Then under Add Field, select Date:Time and click Add Field
    • Next to Time Field, Label can be blank, Handler needs to be Do Not Group Multiple Values
    • Arguments can all be left alone
    • Under Filters ... Add Filter, select Node: Type and click Add Filter
    • Operator = Is one of. Value = Performance
    • Exposed Filters can be left alone
    • Under Sort Criteria, delete Node: Updated Time, and Date: Time (field_time). Leave the Order Ascending

    Click Save and you're back at the Administer › Site building > Views. You should see the default calendar view with an Overridden status. And at the top will be your new calendar view that uses your CCK node called Performance

    Click on the link under URL and you should see your performance on the correct day. If everything looks OK, go back and edit the view again and in the Page > Menu section, check Provide Menu. By default, the menu link will appear in the Navigation menu, but you can move it wherever by going to Adminsiter > Site Building > Menus.

Captcha: spam control

A CAPTCHA is a type of challenge-response test used in computing to determine whether the user is human. This is used in Drupal to prevent spam posts and bot activity.

Installation

  1. Enable the module
  2. Go to admin/settings/captcha to enable captchas for various actions

Related Topics

TextImage
Enable image-based Captchas.
reCAPTCHA
Use the reCAPTCHA web service for Captchas.
Form Store
Allows Captchas to be provided on additional forms.

reCAPTCHA

The reCAPTCHA module uses the reCAPTCHA web service to improve the CAPTCHA system and protect email addresses. For more information on what reCAPTCHA is, please visit the official website.

Installation
  1. Extract the reCAPTCHA module to your local favourite modules directory (sites/all/modules)
  2. Download the reCAPTCHA PHP Library
  3. Extract the files to modules/recaptcha/recaptcha so that recaptchalib.php is available at modules/recaptcha/recaptcha/recaptchalib.php
Configuration
  1. Enable both the reCAPTCHA and CAPTCHA modules in admin/build/modules. Note that the reCAPTCHA module requires Captcha version 2.1 or up
  2. You'll now find a reCAPTCHA tab in the CAPTCHA administration page available at admin/settings/captcha/recaptcha
  3. Register for a public and private reCAPTCHA key
  4. Input the keys into the reCAPTCHA settings. The rest of the settings should be fine as their defaults
  5. Visit the Captcha administration page (admin/settings/captcha) and set where you want the reCAPTCHA form to be presented
Mailhide Input Format

The reCAPTCHA module also comes with an input format to protect email addresses. This, of course, is optional to use and is only there if you want it. For a demonstration of how it works, please see the official website. The following is how you use that input filter:

  1. Head over to your input format settings (admin/settings/filters)
  2. Edit your default input format and add the reCAPTCHA Mailhide filter
  3. Click on the Configure tab and put in your public and private Mailhide keys
  4. Use the Rearrange tab to change the weight of the filter depending on what filters already exist
Resources

Category: structure and classify content

The category module allows you to structure your site into a tree-like hierarchy of pages, and to classify your dynamic content, all within one seamless interface. Gone are the days when these two tasks were carried out using separate and incompatible tools: now it's all one and the same. Built upon the solid foundations of the book and taxonomy modules, the category module overcomes the weaknesses of both these tools, to give you more power than ever before in customizing the navigational experience of your Drupal site.

Documentation, announcements, a live demo, and more can be found on the category module web site. If you're interested, also see why the category module documentation is not here on drupal.org.

You can:

  • create and maintain container and category nodes at administer >> categories.
  • administer general category options at administer >> settings >> category.
  • assign content to your categories by configuring containers to allow certain node types, and then selecting categories for your new nodes on the 'create content' form.
  • transform existing nodes of other types into containers or categories, using the tabs displayed on node pages.
  • file issues, read about known bugs, and download the latest version on the Category project page.

CiviCRM: manage community contacts, relationships, and activities

The CiviCRM module stores information on the universe of people associated with a community and on their interactions such as emails, donations, petitions, events, etc. It can act as a stand alone contact management system or it can be integrated with mass mailer, volunteer management, petition, and event finding. CiviCRM enables organizations to maintain all these activities in a single database, creating efficiencies and new opportunities for communities to better communicate and benefit from relationships with their community members.

The CiviCRM module allows you to create contacts, or import them from other sources. You can record relationships between contacts, such as indicating they live in the same household. There are two types of groups of contacts. You can create static groups which have a set list of contacts. You can also create dynamic (smart) groups based on characteristics that contacts have in common. For example, you could create a group of all contacts who live in California AND who have volunteered for your organization within the past year. The CiviCRM module also allows for tagging for less formal categorization of contacts or groups. You can easily extend CiviCRM to record community member information which is specific to your community or organization using custom fields. For example, you can create a set of fields to track volunteer skills and preferences. CiviCRM profile gives you a way to allow community members ('users') to update their own information, as well as share some of that information with others. Finally, you can configure custom activity types such as volunteering or attending events.

You can:

CiviContribute

CiviContribute allows you to accept donations online via Paypal or Moneris. Additional payment processors can be supported by extending the modular code. Read the CiviContribute Guide for more information.

You can:

  • Track and store any type of contributions (time, money, in-kind, volunteer hours, etc.)
  • Associate custom data with any contribution type.
  • Associate contributions with a central data store.
  • Create an online donation page.
  • Configure possible donation amounts including user entered donation amounts.
  • Customize the text of the donation page
  • Add custom fields stored in CiviCRM to handle data required for political, nonprofit or other specialized donations.

Installation instructions for CiviCRM

CiviCRM installation instructions for installing CiviCRM.

CiviNode and CiviNode CCK: Tools For Integrating CiviCRM Contacts Into Drupal Content

CiviNode is a module and API that exposes CiviCRM contacts, groups, and other CiviCRM object types to other Drupal modules. It's designed to make CiviCRM easier to use and integrate with all of the Drupal tools and techniques you already use to create websites.

Using CiviNode with CCK you can:

  • Add CiviCRM contacts or groups as fields in a Content Construction Kit (CCK) data type. Currently, you can add pop-up menus for both groups and contacts. CiviNode CCK also makes use of CiviCRM's native Dojo widget set to give AJAX-style access to contacts.
  • Restrict a CCK field to contacts from a single group, or pull contacts from you entire CiviCRM data store.
  • Display a contact as a link, a CiviCRM profile (a collection of contact fields), or as a link into CiviCRM.
  • Display CiviCRM-based CCK fields using the Views module.

CiviNode does this via its own API to handle most of the lower level interaction with CiviCRM's native API. It implements hooks for CiviCRM into Drupal's form system and APIs, and implements widgets that developers can use in their own modules. If you are a module developer, this wrapper API for handling common programming tasks can save you time and effort when accessing CiviCRM content in your modules. CiviNode also adds hooks for theming CiviCRM content and generating links from Drupal pages back into CiviCRM contact view pages.

Work on this project is being done by Rob Thorne of Torenware Networks, and been sponsored by the generous support of The Collective Heritage Foundation/Bioneers, the Green Party of Canada, and Openflows Community Technology Lab.

Adding a CiviCRM-based Field to a CCK Data Type

To actually see CiviCRM data inside of Drupal, you need to add a CiviNode widget into one of your CCK data types. Here are some quick steps to get you started:

  1. Go to Admin / Content / Content Types, and either create a new data type, or select one you'd like to add a CiviCRM field to. See the CCK Handbook for more information.
  2. Choose "Add Field". If you've activated CiviNode and the CiviCRM CCK Widgets module, you'll see a couple of new widget types in the "Field Types" section: the CiviCRM Contact and CiviCRM Group widgets. The contact widget comes in two basic "flavors": a "Contact Selector", which is a simple pop-up, and the "Contact Autocompletor", which uses AJAX (i.e., JavaScript) to search for contacts. The Contact Selector is a good choice if you have a small group of contacts you want to assign to a field (say, for a "Group Leaders" or "Staff Assigned" field). Use the Autocompleter if you need to search for one contact out of a large group (say, one voter out of a hundred thousand in a voter information database). If you have ever used the Node Reference or User Reference widgets from CCK, you'll recognize how the Autocompleter works. Hit "Create Field" to continue creating your field.
  3. On the next page see, you configure the field. At the bottom of the page, there are a couple of new options to configure in the "Data settings" section: Default CiviCRM Group, and Default CiviCRM Profile. The default group setting lets you restrict the contacts that a field will display to only those contacts belonging to a CiviCRM group. You will almost always want to choose this option, especially if you use a Contact Selector. The default profile setting lets you control what contact fields from CiviCRM you want displayed. This allows you to control who sees what fields, and helps keep your private data private.
  4. Once you've configured your field, you will probably also want to set up the way the field will display in node pages. Go to the Display Fields section of the Manage Fields page, and choose a formatting option. You can choose to display only a single field of your contact (the default is the "display_name" field), or display the contact as a link into CiviCRM's contact viewing and editing page (you'll need to grant the web user the rights to do see this, of course), or as a Profile. You can set different settings for the Teaser display than for a full page display.

There are a lot more features to CiviNode, and the module is under active development. But hopefully, this will get you started.

Installing and Configuring CiviNode and Related Modules

To install CiviNode into Drupal and create and use CiviCRM content in your pages, you first need to set up CiviCRM and CCK, and you should look at the documentation for those modules elsewhere. If you want to take advantage of CiviNode's support of the Views module, you should install Views as well.

Once you've done that, there are a couple of things you will need to do in order to get started:

  • Download the CiviNode tar ball from our project page, and "untar" it as unusual into your sites/all/modules directory.
  • Activate the module in the Admin / Site Building / Modules. Note that under Drupal 5.x, you can't activate CiviNode unless CiviCRM is installed and active. You should also turn on the Content module for CCK, and the CiviCRM CCK Widgets module (note: the CiviCRM Data module is now obsolete, and is only included for those folks who have data in that format.).
  • You need to have at least one CiviCRM profile defined in CiviCRM. A profile is just a grouping of contact fields you want to display together. You define a profile by going to the CiviCRM / Administer CiviCRM page (follow the link in your Navigation menu) and use the "CiviCRM Profile" tool in the Config section. You can define as many profiles as you like, and use different profiles for displaying various kinds of content on your site.
  • Once you've defined some profiles, you need to tell CiviNode which profile to use as its default. Go to Admin / Settings / CiviNode, and you'll see that your profiles now appear in the setttings page. Select the one you want, and click on Save.

CiviNode is now configured, and we're ready to create CCK fields using your contacts.

Classified Ads: a simple path to self-expiring classified ad content

The Drupal Classified Ads module provides a quick and easy way to host textual classified ads on your Drupal 4.7 or Drupal 5.x sites. If you run multiple sites, or don't want to be bothered with installing flexinode/CCK or other modules to piece together a classified ads system, this may be just the ticket.

This module creates a drop-in, plug-and-play textual classified ad node type (ed_classified) with no need to use CCK or flexinode. This module has been in use on several production sites for a while now. It provides the following features:

  • cron-based automatic expiration (on expiration, the classified ad node is moved to unpublished state, but is not yet deleted/purged so it can be 'renewed') and after an admin-defined 'grace period', previously expired ads are purged (deleted).
  • Expired ad renewal: Users with proper permissions may 'renew' their expired but not yet purged ads during the 'grace period' by checking the 'renew ad' checkbox in the edit form.
  • Simple taxonomy-based browsing similar to that provided by image.module galleries. (I lifted some of the code from image.module, and modified it for use here.)
  • Several useful blocks (latest ads, most popular ads, and stats including total number of unexpired ads and number of new ads in last 24 hours).
  • Per-user classified ads lists (under each user's profile, visible to all users with 'access user profiles' permissions).
  • Administration list of all classified ads, sortable by expiration date, with direct edit link to the ad.
  • Length-limited body text with live, javascript-based counter showing characters used and remaining while user enters the body text - no need to hit submit and wait for an error message.
  • New Feb. 25 2007 Version 5.x-1.5 and 4.7.x-1.5 - Ad expiration varies by taxonomy term. Now you can control ad duration depending on category selection

Overview and rationale at http://exodusdev.com/drupal/modules/ed_classified.module

Project page and official releases: http://drupal.org/project/ed_classified

Live demo site: http://gigs.exodusdev.com - sign in with your drupal user id and start creating ads (and if you are a drupal developer, you can tell the world about your services.)

Paid classified ads may be implemented by installing the lm_paypal module and limiting ed_classified node creation to paying users. Other schemes may work as well, but the ed_classified module does not handle paid ad creation for you - this is beyond the scope of the module at present, although lm_paypal integration is planned for a future release.

Photo ads may be implemented using image.module, upload_image, and other image modules. I use upload.module, image.module and upload_image combined on my sites, and that combination works pretty well. If you have upload.module, the ed_classified module will allow you to override the attachment form description text on classified nodes, supplying administrator-supplied text - this helps users understand what to do in order to create a photo ad.

Coming soon:

  • Installation and configuration
  • Setting new ad durations based on selected taxonomy terms (categories) - as of version 1.5, you can set new ad durations based on the taxonomy term(s) chosen for the new ad.
  • Basic and advanced taxonomy for classified ads
  • Implementing Image/Photo ads - third-party modules
  • Paid ads - using lm_paypal
  • Taking advantage of Views integration
    ads expiring soon, sortable lists, etc.
  • Helping visitors contact advertisers: contact forms, privatemsg module, and contact_default module
    (and http://drupal.org/node/122428, http://drupal.org/node/66648#comment-130975

Location, location, location - using the location.module to provide location-based ads.

You can use the location module to add location-based attributes to ads created using the Classified Ads module

You can see an example of the location module in use on http://gigs.exodusdev.com .

One advantage of this technique is that you can also enable google, yahoo, and other map support available in the location module.

Using location.module with classified ads.
  • Install and configure location.module
  • Enable locative information for ed_classified nodes
    Note: The location module requires that a zip/postal code be entered in order to auto-generate latitude and longitude information, which is required if you want to use mapping functionality - nodes with no zip/postal code entered will require manual editing of latitude/longitude information if you wish to use mapping functions. This implies that if you want ads to show google maps functionality, you should require zip/postal codes be provided for all classified ads. Check location.module access control settings to ensure that users who can create new ads are also permitted to submit latitude/longitude information, as well.

Code Review Module - Coder

Coder is the code review module. The name coder is a double entendre, "coder" being someone who writes code, and "code-r" short for "code review".

User Reference

To be filled in...

Review and Rule Developer References

Reviews

A review is a set of rules designed to accomplish a task. There is a code standards review, a review for upgrading from 4.7.x->5.x, another review for upgrading from 5.x->6.x, there's the beginnings of a security review and a performance review. Each review has an array of rules. The reviews are shown on the UI pages and can be found in the includes directory.

Options

  • #title
  • #link - link to drupal.org page about the review
  • #rules - rule definition (see below)
  • #severity - default severity (minor, normal, critical) for all rules in the review. severity controls the color and icons displayed. this value can be over-riden by individual rules.

Rules

A rule is a single specific pattern to find. Rules can use the following #type:

  • regex - look for a particular pattern
  • grep - looks for a particular string, meant to be faster when regex is not needed
  • grep_invert - look for a particular string that does not exist
  • callback - do anything you want

More about regex rules

Thus, to find all _submit functions you could use

    array(
      '#type' => 'regex',
      '#value' => '_submit\s*\(',
    ),

This example regex looks for _submit followed by optional spaces, and a required left parenthesis. If you wanted to be more specific, since the _submit handler has very specific arguments that are changing, you could look for:

      '#value' => '_submit\s*\(\$form_id,',

Remember, coder is just a helpful tool. It's not going to catch every use-case (people who use different variable names), but a simple rule like this will find all 5.x submit handlers and warn you to upgrade it. If you wanted to be more generic, you could write a rule that looked for two arguments instead of 3, but the regex for that is a little harder.

You can also restrict your regex searches within a function. So, if you wanted to warn about the recent menu changes:

    array(
      '#type' => 'regex',
      '#function' => '_menu$',
      '#value' => '\$items\[\]\s*=|if\s*\(\$may_cache\)',
    ),

This rule looks for $items[] = or if ($maycache).

Quoted strings are replaced with '' in the php source (the default, but see below). Thus if you wanted to make sure that the above had a string array index (such as $items['something'] =, you could have written (BTW, this is a bogus example just to show quoted strings):

    array(
      '#type' => 'regex',
      '#value' => '\$items\[\'\'\]\s*=',
    ),

Rule warnings

Finding code patterns is only half the problem. You also need to write a warning. There are two warning options,

  1. #warning
  2. #warning_callback

So, using the first example above, we should add a warning

      '#value' => '_submit\s*\(\$form_id,',
      '#warning' => t('hook_submit() parameters have changed'),

Notice that this warning uses t for text processing. Since this function takes processing time, and since the rules files are read a lot, there also exists a warning_callback that should be used in most cases. So, the above really should be:

      '#value' => '_submit\s*\(\$form_id,',
      '#warning_callback' => '_coder_6x_fapi_submit_warning',
...
  functions _coder_6x_fapi_submit_warning() {
    return t('!hook_submit() parameters have changed',
      array(
        '!hook_submit' => theme('drupalapi', 'hook_submit'),
      )
    );
  }

Warning Callbacks

Warning callbacks support either a text string or an array. If an array is returned, it should contain:

  • #warning - this is the same as the simple text string (or #warning in the rule)
  • #description - a more detailed description. if supplied, the user can click on "..." and receive more information about this warning

Available source options

Usually, you're just looking for something in the php. But coder supports these #source options:

  • php - this is the default
  • html - anything not inside <?php ... ?>, but also looks for html tags inside quotes.
  • all - any line in the source. this is sometimes needed to override the default quote and/or comment handling.
  • quote - anything inside a single (') or double-quote (")
  • doublequote - anything inside a double-quote (")
  • comment - anything inside a comment, including the leading and trailing comment

So, for example, if you want to look for the string "hello world" (inside a doublequote), you can use the following:

    array(
      '#type' => 'regex',
      '#value' => 'hello world',
      '#source' => 'doublequote',
    ),

It should be noted that there isn't a 'comment' option and there probably should be. The only way to search for comments is to use '#source' option 'all'. Otherwise, all comments are stripped.

Other rules options

  • #case-sensitive - pattern matching ignores case by default. Use this to for case sensitive matching. This is needed for camelCase and capitalization rules.
  • #not - to make pattern matching rules simpler, use this to exclude matched values
  • #severity - severity (minor, normal, critical) controls the color and icons displayed. values defined here over-ride values set in the review

Comment mover: thread management for forums

The comment mover module allows you to move comments in a thread, move a comment to another thread and move comments to a new thread and vica versa. It can be used should a user post a comment in a wrong topic or posts a comment that should be a new topic.

Comment mover is designed to be integrated into the OG2List mailing list manager, but also works on its own for any Drupal site which has forums and comments. Redirectors to the new positions of the comment or node will be added as appropriate.

You can:

  • move a comment to another thread below the same post by clicking on the prune link below the comment to go to move comment page.
  • move a comment to another post on the prune comment page.
  • Make a forum topic from a comment on the prune comment page.
  • Merge a post into a thread below another post by going to the graft tab of the post.

Contact List

Overview

This module expands the features of the site wide contact form. It eliminates the need for the drop down category menu by generating a form, and a unique path, for each of the contact form categories.

The path 'contact/list' generates a page with a list of links to each category's contact page.

The path 'contact/{category}' generates a contact form for that category with a title = 'contact {category}'.

Requirements

contact.module must be enabled.

Installation

  1. Copy contact_list folder to modules/.
  2. Enable contact_list module
  3. Check contact.module is enabled

Tips

  1. You can change the message 'You can send %category a message using the contact form below.' on the contact form settings page.
  2. CSS to hide the bullets on the contact/list page
  3. #contact-list li {list-style: none ;}

Content Construction Kit (CCK)

The Content Construction Kit allows you add custom fields to custom content types using a web interface. In Drupal 4.7 the Content Construction Kit creates both custom content types and custom fields. In Drupal 5.x, custom content types can be created in Drupal core, and the Content Construction Kit allows you to add custom fields to any content type.

Documentation and instructions are available in the separate CCK Handbook.

Summary - Why would I need CCK?

Drupal comes with core content types like story and blog. For each content type, I can go to 'create content' and submit a new story, blog entry, etc. That's great if I can tailor my content needs to fit those models of pure chunks of text with or without attachments.

But say I want my users to be able to submit reviews of their favorite aardvark. That's a content type that's sort of a story, but I want to be able to display other stuff as well as a chunk of text for every node of that type, and I don't want it to be just attached file links.

My options: I could hack the heck out of the story.module, OR I could see if someone generous has already created an aardvark.module for the exact type of content I want, OR using CCK I could create a new content type called Aardvark Review.

Using CCK, I can create a content type that has exactly the fields I need, no more or less. My Aardvark Review for instance might have:
text field (comments on the aardvark)
dropdown menu (aardvark color)
audio file (recording of my aardvark's cheerful grunting)
image (photo of aardvark)

The CCK admin interface for creating these new content types is nice and easy: you basically just create your new type and then click through, adding the type of fields you want it to have and what their parameters will be (how much text for the review? which dropdown colors in the menu?). There are many add-ons available in the downloads section under CCK that add new kinds of fields to your options (video field! audio field! calculated values! and vastly more complicated ones).

From the users' perspective, they'll just click on create content>>aardvark review and get a form that asks them to submit their review, the aardvark's color, a recording, and picture. Voila!

Glossary

Content Type
Content Type is a collection of fields that defines the define the structure of a piece of content. Content type is equivalent to node type in everyway. A content type maybe an "event", a "blog", a "story", ...etc.
Field Type
Field
Field Instant
Widget

Content Recommendation Engine: Recommending Content to your users

Content Recommendation Engine (CRE for short) is a set of modules designed to allow administrators to recommend any personalized pieces of content (whether it be nodes, comments, book pages, or even other users) based solely on voting patterns throughout the site. It does this through a Collaborative Filtering Algorithm called Slope One.

CRE is designed as a out of box solution for end users and also as a developers API. It allows for developers to design plug-ins so that any piece of content on the site can have personalized recommendations.

Because CRE is based on voting patterns it requires VotingAPI module to be installed on the Drupal installation. This allows for a standardized voting system from which CRE draws its information. For more information on VotingAPI please read its documentation.

CRE also requires a voting module that uses VotingAPI based code. There are several different ones available and finding them requires reading their individual documentation. Many of them are listed here .

For an interesting discussion on voting modules read this Lullabot article

What the download can do for site administrators

  1. Recommend nodes
  2. Recommend certain types of nodes (i.e recommend only stories, or even a CCK node type)
  3. Recommend comments when viewing a node
  4. Create a list of recommended users

All of this will be explained in further detail in the following pages.

Recommend Nodes

CRE is able to present a list of recommended nodes in various different ways through the use of node_recommendation.module. This module presents lists of recommended nodes in various ways and allows for some variations on how and what type of node can be presented.

Node_recommendation module provides a page for which the current logged in user will receive their recommendations. This page is located at ?q=recommendations or /recommendations depending upon if your site is using clean url's or not. You can change the title, maximum number of recommendations to display, the maximum age of the node and specify the proper votingapi tag (see VotingAPI documentation for further details). All these settings can be accessed at admin/cre/node for those using 5.x or at admin/settings/node_recommendation for those using 4.7.

Secondly, node_recommendation provides three different blocks:

  1. General Recommendation block
  2. Similarly Rated block
  3. Recommend nodes of a particular type

The general block lists, as one would expect, of any type.

Similarly Rated Nodes block will display nodes that have been voted on in a similar pattern as the currently viewed node. Essentially, those that liked this node also liked these nodes.

Recommend nodes of a particular type will list nodes that are only of one type. For instance, you can set this block to only recommend story nodes, or page nodes, or blog nodes, or a user created node.


Important information

If any of the three conditions are true, /recommendations will display the top rated nodes and not recommended nodes.
  1. User has not voted yet
  2. User authored all nodes that have been voted on
  3. User has voted on every node in votingapi_vote table

This is a design decision and each one has important merits for various reasons, some obvious some not so. If you wish to remove one of these conditions, please contact me

Currency Conversion: do currency conversions from your web site

The Currency Conversion module allows visitors to do currency conversions between different currencies from your web site. It also provides an API that can be used by other modules. It depends on Yahoo! Finance for its information.

Users with the appropriate access level (use currency) will be able to use this feature by entering an amount in the text field, choose the source and target currencies and then perform the conversion to the target currency.

You can

  • convert currencies.
  • change default currencies to convert from and to, at administer >> settings >> currency
  • file issues, read about known bugs, and download the latest version on the Currency Conversion project page

Database Administrator: Manage, backup, and load your database

The Database Administrator module allows adminstrators to manage, backup, and load a database. The ability to manage tables inside the site reduces the needs for learning third party database tools and reduces time to accomplish database administration tasks.

The module allows you to set the backup name for multiple tables and repair a MySQL database. Administrators also have the ability to perform many operations on a single table including: view, describe, check, backup, empty, and drop. The administrator can check, backup, empty, and drop a selection of multiple tables.

You can:

  • administer tables at adminster >> database.
  • run scripts on the database at administer >> database >> run script.
  • set the backup file name and the MySQL table check and repair settings at administer >> settings >> database.
  • file issues, read about known bugs, and download the latest version on the Database Administrator project page

Date: Add date/time fields to Content Construction Kit

The Date module adds a date/time field and date API to the Content Construction Kit (CCK) module. This allows the addition of user defined date/time fields in a node created by CCK. These fields can also be referenced by other modules, such as Views.

Date format is selectable as a ISO date (YYYY-MM-DDTHH:MM:SS) or a unix timestamp. There are a variety of date entry and presentation options available.

For more information on the Date module please visit the Date project page.

Directory: A taxonomy-based site directory

The directory module provides a page with a simple taxonomy-based view of your Drupal site. It provides a hierarchical navigation page for content assigned to selected taxonomies, allowing the site users to 'drill down' on categories of interest.

Requires: Taxonomy module.

To my knowledge, has not been tested with CCK, Flexinode, or Category modules. If you have tested this and have any info regarding these modules, please contact me and I'll update this page.

Site administrators can:

  • Define one or more taxonomy terms ('vocabularies') to be included in the directory. The directory module includes content assigned to these terms in the directory.
  • Select the node types to be excluded from the directory. All other node types will be included.
  • Define a 'resource count threshold to display subcategories
  • Define the number of items to display for each visible subcategory
  • Define a text description to be displayed on the directory page

Note: This module is in active development, and I plan on adding new features. Please check out the project page, and try the CVS head version if you want to see the newest features. Latest features:

  • Display enhancements: Child node counts (optional), improved layout
  • Configuration: Enable child node counts, suppress empty terms.

You can download the latest version, and view and submit issues on the directory module project page.

Demo

Live demo at http://www.roadcarvin.com/directory

Basic configuration

  1. Enable the directory module.
  2. Configure the Directory module by visiting your site's admin>>settings>>directory page.

At a minimum, you must configure the following items correctly in order to use the directory module on your site:

  1. First, check off any node types that you want to exclude from the directory listings - by default, all node types are candidates for inclusion in the directory listings, and you must choose the ones that you wish to exclude. This is somewhat confusing and can be a source of frustration for directory module users - be sure to check off only those node types that you wish to exclude from the directory listings!
  2. Next, select the vocabularies (taxonomy terms) you wish to expose in the directory listing index. These selections will determine which taxonomy terms appear in the top-level (and upper-level) branches of the directory listings.

The remaining settings control display behavior - set them according to your needs. Note: Items marked with an asterisk ('*') are not working correctly in the 4.7 version.

  • Resource count threshold to display subcategories*
  • Number of resources to display for each visible subcategory*
  • Explanation guidelines - a block of text to be displayed at the top of every directory page.

Dript: A scripting language for Drupal

Dript is a scripting facility designed specifically for Drupal. Non-programmers may add formulas or behaviors to their Drupal installation without having to go down to PHP. Dript is simpler and safer. The word 'Dript' is derived from three words 'Drupal', 'Lisp' and 'script' blended together. Dript is pronounced like 'script'. Dript may also means dripped as defined in Simplified Spelling Board's 300 Spellings. In other word Dript is also relative to the meaning of Drupal.

Dript is a dialect of Lisp. It is natural for Lisp to have dialects. Why Lisp? Isn't Lisp a very old language, just second older than Fortran? Actually Lisp never age. It only gets wiser over time. Read how Lisp is Beating C in Scientific Computing Applications. That can be mind boggling. However, Dript takes a simpler approach and down to the earth with Drupal.

Dript is written 100% in PHP so that you don't have to install anything else on the hosting server. Dript is also extensible where new functions can be written with PHP and simply dropped into the module. For non-PHP programmers you can use Dript itself to define new functions and upload your scripts to the Drupal's file folder.

You may find tonnes of literatures on Lisp on the web. If you are new to Lisp, take a free on-line interactive Lisp course at Episodic Learner Model - The Adaptive Remote Tutor. However, Dript have slightly different approach to the behaviors and functions to the general Lisp. Different Lisp dialects such as Common Lisp and Scheme are having their own individual aproaches as well. However. this handbook will not discuss the different approaches of the various Lisp dialects.

Note: This handbook is on going, as the Dript itself is on going. Dript has the tendency to grow big (subject to contributors participations). New functions and approaches will be added from time to time.

01. Getting Started

Make your Drupal installation ready with Dript:

  1. Download the latest copy of Dript module from the Dript project page.
  2. Extract the compressed module into a folder.
  3. Upload the files via FTP to a modules folder in your Drupal installation - this could be the modules/ folder, but it is recommended to create and use the folder sites/all/modules/, making it easier to update Drupal later on. Once uploaded you should have folder modules/dript.
  4. Activate your Dript module. Click Administer » Site building » Modules. You need to check the box next to the Dript module, and click on 'Save configuration' at the end.

Test your Dript installation:

  1. Create a page content.
  2. Set the title "Dript Test".
  3. Paste the following code in the body of the content:
    [driptn (print "Hello world")/]
  4. Submit the content.

You should get a new page with the title "Dript Test" and the content "Hello World".

02. Why Lisp?

Why not vbscript instead? Okay, we're not going to start any debate here (a forum should be a good place to exchange ideas). We choose Lisp because the language seems not to let us take side. Most great programmers of other languages will simply praise Lisp, even if they haven't code a single pair of lisp parentheses. We have seen how AutoLisp is very successful with AutoCAD. And not to miss mentioning Emacs.

Lisp is not a proprietary language. And it is natural for Lisp to have dialects. If we were to invent a new language then we can easily create controversy. Lisp is been supported strongly by academic institutions all over the worlds. We can easily find documentation, reference and tutorials about Lisp on the Internet. Most importantly Lisp was created by a non-programmer and has been nurtured by non-programmers as well. The term non-programmer is referring to a person who doesn't use a main stream programming language such as C/C++, C#, vb.net, Java, PHP, Perl, Ruby, and Python to program, as a profession, an application for others to use.

Lisp is an easy language for starters. You don't have to read a whole lot of it before you can get started. In fact you don't have to think like a programmer to start coding. The essence of Lisp is function, like the mathematical functions we used to tweak at high schools. We just need to think what function our code is supposed to do and type it down in Lisp expression. For instance, if we want to get the average of five numbers we will then write (avg 6.2 4.1 5.7 8.3 2.9). We don't have to worry about what module to include. And we don't have to compile the code! The basic idea for non-programmer is to write small code for the job. Once it is done the code will be forgotten. Here is where Lisp excels. Lisp allows us to express our code in unlimited manners where we have ample room to be creative or expressive. However, our code may become oatmeal with fingernail clippings mixed in as Larry Wall of Perl used to say. But then again the code is supposed to be forgotten!

Those who quest for intelligent will find Lisp interesting. John McCarthy the creator of Lisp maintains the artificial intelligent as his main research where he started long ago. And Lisp is being used widely in the subject. I wish one day somebody will maintain a nice web site for him.

Read Richard Stallman's My Lisp Experiences and the Development of GNU Emacs.

03. Inspecting Dript Functions

The number of Dript functions will grow in numerous ways. We will easily get lost with what function exist in Dript. Fortunately all Dript functions are self describing.

Use the following functions to get to know all Dript functions:

  1. To list all the functions in Dript, evaluate (funs).
  2. To get the syntax of a function, evaluate (syntax 'function-name).
  3. To get the description of a function, evaluate (desc 'function-name).

You may need to create a scratch page to play with those functions. Make sure you don't check the published box for your scratch page. Otherwise your web visitors will be wondering what you are doing.

Note: Hyphen '-' and underscore '_' in function names are treated the same. This is so because the underlaying PHP code cannot take hyphen in function or class name. While it is common to use hyphens in Lisp function's names and variables yet we have to convert them to underscores in PHP. Just treat this as a feature in Dript.

04. Dript Behaviors

Symbolic
Dript inherits fundamental Lisp behaviors. However, Dript takes symbolic expression very seriously where Dript discarded many earlier Lisp concepts that wanted to make lisp nearer to machine such as memory pointers, car-cdr link list and dotted pairs. Lisp should keep itself as far as possible from the machine. Dript also does not extend lisp to the complexity of Common Lisp or Scheme. The only data structure in Dript is the list.

Object Orientation
Dript or Lisp does not oppose OO. As Lisp, Dript is a meta language where it can be used to create dialects or sub-languages which may include OO languages. OO is actually a type of namespace. So instead of implementing OO it is better for Dript to go to the root and implement namespace. For a reference, local scoping such as the body of a function is also a namespace. A data structure is also a namespace.

Variable
A true functional language will not implement variable because variable introduces side effect. However, in real world coding, it is very tough to code without escaping to variables. Thus, Dript permits variable but does not make it mandatory. Hence there is no global variable defined by Dript. One significant different between Lisp and Dript is that all variables defined in a body of a function including the parameters have local scope in Dript. The early Lisp violates the principle of functional programming first by allowing variables and second by letting all variables global by default. For the sake of comprehension, all variables can be replaced by functions.

Parentheses
Generally every Lisp dialect will retain the parentheses which are significant in handling complex symbolic expression for both code and data. With parentheses a Lisp code can also be a data or vice versa. Hence it is a feature in Lisp where a code may not only consume data but also another code. The outcome can be very complex, indeed. This is one of the feature that is attractive to artificial intelligent research.

nil
Not in list or nil is a very fundamental concept in Lisp. There is either something in a list or nil. The concept of nil is similar to zero in digital number where even nothing need to have a representation. We may say a function will return something or nil. Hence nil is a very basic logical expression. With nil there is no need for another true or false expression.

Quote
Quote ['] is a marker that tell the evaluator not to evaluate an element that precedes with one. The evaluator is designed to evaluate every element in the list to reduce the list. An evaluation of a function will reduce the function into its return values. An evaluation of a variable will reduce the variable into its values. A quote will give a function a chance to evaluate an element itself. And a quote will let an element to be retained as data. In Dript an element can have multiple quotes where the evaluator will only deduce one of them before passing the element to a function or to the next evaluation.

Function
Dript itself has no build-in function. All functions are define externally. Even arithmetics and logical operators are implemented as external functions. There are two ways a function can be defined in Dript. Firstly, define a function using PHP which requires some understanding of Dript architecture. Secondly, define a function using (defun) function which has been defined earlier using the first approach. A function is always handled using prefix notation where the function name is the first in the list. A function call is alway enclosed in parentheses.

E-Commerce: sell products on your site

The E-Commerce package is a large and growing collection of modules used to drive a full-fledged ecommerce system from within Drupal. The design for this project was to create an extensible framework such that the payment methods, shipping methods, and product types are built upon a pluggable architecture.

If you sell shippable items, three shipping calculation methods come with the package. Furthermore, you can enable inventory management for the products whose stock you wish to track.

The store can be configured so that users can make a one-time transaction, or they can create an user profile and have their shipping and billing information retained from order to order, speeding up the checkout process next time they return.

Finally, a workflow system has been built for transactions and payment status, allowing you to easily send shipping notifications, print invoices and update or manually create new orders.

You can:

  • administer search, advances search, transaction, update transaction, create new transaction, delete at administer >> store.
  • configure customer accounts for purchases and payment notices at administer >> store >> settings.
  • configure ecommerce modules at administer >> store >> settings.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page
.

E-Commerce Installation

Important: Please back up your site and database first. Then proceed as follows:

To install Drupal E-Commerce, it is first assumed that you have Drupal up and running. If you are having problems, please review the handbook, especially Installing Drupal modules.

Place the files

Download E-Commerce (v3) from the project page. The current recommended download is 5.x-3.x-dev. (Note that the "3" part is what people are referring to when they mention "v3" . Untar/unzip the files and place the entire ecommerce directory in an appropriate module directory like /sites/default/modules.

Download the latest Drupal 5 release of Token module as well and place this alongside ecommerce.

Enable the core modules.

While continued testing has shown things to be more stable, the installation process and E-Commerce core has changed a lot for Drupal 5. I recommend that you install your modules in a number of simple steps.

Navigate to the Modules page via Site building » Modules. Then install these modules in 3 stages. Note: you can do steps 1-3 all at once and it should be ok - however you may prefer to take extra care and avoid any disturbing warnings.

  1. There is a single non-EC module to install.
    • Token
  2. It is recommended at this stage to install the following E-Commerce core modules before anything else:
    • Mail Subsystem
    • Product API
    • Anonymous purchasing
  3. And then these core modules:
    • Store
    • Cart
    • Payment API
Additional modules

The rest of the modules can be considered optional. E-Commerce modules are grouped into 4 major categories: core, payment, product and uncategorized.There is information next to the module defining dependencies with some modules (ec_media) requiring you to install modules from outside E-Commerce.

Standard settings

Go to E-Commerce configuration » Mail and here you can set up the emails templates that correspond to various situations. You can use special placeholders, like %user that will be replaced with the actual data.

Go to E-Commerce configuration » Store and make some simple decisions about your store:

  • Can the customer purchase anonymously?
  • Do you want transaction notices sent periodically (requires cron)?
  • Are the E-Commerce core modules all installed?
  • What text would you like to display on the user's order history page?
  • Set emails for invoice, payment error, questions, cancellations
  • Should "add to cart" be a link? Or allow entering a quantity?

Go to E-Commerce configuration » Payment and check that your price formatting is to taste. Also on this page is a payment testing option that you should be aware of, because this setting is usually in addition to settings used by the various payment modules.

Set the order of pages in the checkout process by going to
E-Commerce configuration » Payment. Adjust the order as required and click Update screen order.

Standard settings

Optionally, you can enable the cart block via:
Site building » Blocks

Of course, grant the proper access to user accounts under:
User management » Access control. The main access control provided by E-Commerce core is "administer store" in the Store section. Remember, each time you enable a module, you should check this page for new permissions that may need to be assigned.

HOWTO: Quickly configure ecommerce core modules

See this table in full page view.

Ecommerce core module Dependency Admin Settings Store_Settings Access Control
address cart, and store
Default country
cart product, payment, store update screen order Shopping cart access content

Checkout access content

Please review and submit your order access content

checkout administer store
file product and store Ecommerce file

download settings
access content

administer store
generic product and store create non-shippable products

edit own non-shippable products
item quantity
parcel product, and payment create collections of products

edit own collections of products
payment cart Price Formatting

Recurring Payments

Payment Testing
paypal payment paypal settings
product store administer products

create, update, delete products
product_transform
shipping product, and cart
store product, payment, and cart search

advances search

transaction

update transaction

create new transaction

delete

confirm delete
customer accounts for purchases

payment notices
subproducts payment, cart, product, store list variation, add variation, add attribute, 
edit variation
delete child products

aggregation parameters
administer subproducts

administer own subproducts
tangible shipping, and product create, update, delete
tax cart, address(bug), payment(but function is commented) location value

product type

tax rate
taxes, add tax, edit tax, delete tax, tax autocomplete
HOWTO: Quickly configure ecommerce contributed modules

View a full page version of this chart.

Ecommerce contributed module dependency admin settings store settings access control
apparel product, tangible, subproduct create apparel products

edit own apparel products
assist payment Assist Shop ID

Successful payment URL

Cancel payment URL

Language

Currency code

Allowed payment methods

Debug Assist

Assist Debug Result

Minimum purchase amount
auction product auction days access content

place bids
authorize_net Payment, PHP Curl support Explanation or submission guidelines

Login ID

Transaction key

Authorize.net processing URL

Successful payment URL

Authorize.net test mode

Email Authorize.net Reciept
ccard payment nad PHP CURL support Ccard Client Id

Credit Card Payment Page

Thank you Page
cod Payment Title to use COD

Page to go to after payment

Mark transaction as paid?

Mark transaction workflow as completed?
coupon product, cart Coupon e-mail
custom custom: product, tangible, subproduct, and
optionaly other product types including apparel i think(doesn't
understand custom)
Customizable product types create custom products

edit own custom products
donate product make donations

edit own donations
ec_devel cart
ec_media
ecivicrm Record activity types

Countries which use Abbreviations for States

Require Billing location to have Email for Anonymous Users
ecommailer
eway E-Way Client Id

Client Id that was issued by E Way

Credit Card Payment Page

Thank you Page
exact E-Xact payment processing

Explanation or submission guidelines

Gateway terminal credentials

ExactID

Password

WSDL URL

Successful payment URL

SOAP include path
hosting create hosting packages

edit own hosting packages
linkpoint_api Explanation or submission guidelines

Store ID

SSL Certificate

Linkpoint processing URL

Successful payment URL

Linkpoint test mode

Email Linkpoint Reciept
payflow_link
paypalpro paypalpro express Use secure SSL connection

Express Checkout URL

API Server URL

PayPal Web Services API Account Name

PayPal Web Services API Account Password

PayPal Digital Certificate

Successful payment URL

Explanation or submission guidelines
recommend
role_discount Set discount per role
service create service products

edit own service products
shipcalc
stores
submanage
userpoints
worldpay Installation ID (instId)

Testing Mode (testMode)

Currency code (currency)

WorldPay Minimum Amount

Worldpay Submission Form (top)

Make Payment Button help text

Make Payment button title

Worldpay Submission Form (bottom)

WorldPay processing URL

Thank you page/WorldPay callback URL
E-Commerce Installation 4.7 v2

To install Drupal E-Commerce, it is first assumed that you have Drupal up and running. If you are having problems, please review the handbook, especially Installing Drupal modules.

Important: Please back up your site and database first. Then proceed as follows:

  1. Download E-Commerce (4.7.x-2.1) from the downloads section. Untar/unzip the files and place the entire ecommerce directory in /sites/default/modules which is generally a recommended place ot put any contrib modules.
  2. As of 4.7, you do not need to install the database tables manually.
  3. Navigate to administer » modules. The E-Commerce modules are signified by EC: before each of the E-Commerce module descriptions. Install the modules below in three steps. These are considered the standard core modules required by a typical E-Commerce installation. Note: you can do steps 1-3 can all at once and it should be ok - however you may prefer to take extra care and avoid any disturbing warnings.

    1. There is a single non-EC module to install.
      • Token
    2. Next install these three modules:
      • Mail Subsystem
      • Product API
      • Anonymous purchasing
    3. And finally:
      • Store
      • Cart
      • Payment API
  4. Enable a further choice of modules as required, or follow the rest of the E-Commerce handbook section for further instruction.
  5. Go to administer » store » settings and set the following:
    • Can the customer purchase anonymously?
    • Do you want transaction notices sent periodically (requires cron)?
    • Are the E-Commerce core modules all installed?
    • What text would you like to display on the user's order history page?
    • Set emails for invoice, payment error, questions, cancellations
    • Should "add to cart" be a link? Or allow entering a quantity?
  6. Optionally, you can enable the cart block via:
    administer » blocks :: Shopping Cart
  7. Set the order of pages in the checkout process by going to
    administer » store » settings » checkout. Adjust the order as required and click Update screen order
  8. Set the "shipped" confirmation email at administer » settings » shipping.
  9. Grant the proper access to user accounts under:
    administer » access control. The main access control provided by E-Commerce core is "administer store" in the Store section.

E-Commerce upgrade notes

Token module

Before upgrading your site to v3, please make sure that Token module is installed. A fresh install will force you to install token module, but upgrading is a less controlled situation.

Flexicharge - adding custom charges

Flexicharge is a module which integrates into the shopping cart, and which allows you to add a range of custom charges to the cart that meet your specific business needs. To add these charges, go to E-Commerce configuration @raquo; Flexicharge.

Base features

Every Flexicharge has a number of behaviors that you can turn on and off. Some of these behaviors are not always available, if they are not relevant to the charge type. All of these are available in a simple "Site-wide charge":

Property Description
Display label Define your own custom description for this charge.
Operator Simple +/- or a percentage
Charge rate A simple number, if the operator is +/- then it will be added (so use a negative number if you want a deduction. If the operator is a percentage then "-10" will mean that 10% will be removed.
Order The weight of the charge is mostly for the bill display. Charge or can dictate the amount of a charge (eg. % of subtotal depends on what comes before). It is also possible that a custom charge can remove a previous charge (eg. explicitly state "surcharge waived") and order would be important there too.
Subtotal before/after For display purposes, this inserts a subtotal for the customer. In the case of many charges, the cart is smart enough to suppress sequential subtotals.
Display 'as included' Some charges you might like to simply state "as included" because they are not clearly calculated anywhere on the bill. In some cases this might be a simple substitute for tax declaration (eg. "VAT included") where you don't want to then add that amount.
Suppress if 0 Allows you to hide the row if the charge is $0.
Product types You can configure a charge to apply only if certain product types appear in the cart. Basically if any product in the cart is a match then the charge will apply. (Each charge still only applies for the whole cart).
Roles Similar to the Product types setting, but in this case it looks at the roles that the customer belongs to.
Site-wide charge

This is a stock standard charge with only the complexity that Flexicharge adds to it. Flexicharge has been designed to take as much complexity from the charge providing files as possible. So if a simple "site-wide" charge doesn't fit the bill, it will be easy to develop your own or to hire a developer to do so.

Shipping charge

This is designed to be an alternative to Shipping and Shipcalc. This allows you to apply a charge based on the weight of the products multiplied by a rate. Here are some instructions to set up a simple shipping charge.

  1. Install the E-Commerce Address module so that you can capture shipping details in the checkout.
  2. Got to E-Commerce configuration » Location and set your default country and the default measures for your site. Currently Flexicharge shipping only requires weight.
  3. Install the E-Commerce Regions module. This will allow you to combine locations together under a "region" and then configure shipping charges based on the region of the buyer combined weight of the product. Follow the instructions for groups your locations into shipping regions. The regions you will create might be called "Local", "Interstate" and "O/S".
  4. Once you have your regions configured, go to E-Commerce configuration » Flexicharge and add a new "Simple shipping" charge. The first section is for your standard Flexicharge configuration settings, and beyond that is a section for each region so that you can allocation a base rate plus cost per kg/lb.
Creating a custom flexicharge

Flexicharge does not try to cover every use-case, because experience has shown that this is basically impossible. However, the .inc files should hopefully have as little logic as necessary to work, so that they are as easy as possible to modify.

First you need a template to work from.

  1. Look in your ecommerce module files. If you keep your modules in /sites/default/modules you should go to: /sites/default/modules/ecommerce/contrib/flexicharge/providers/
  2. (Copy and) rename the file custom.inc.example as custom.inc. When you go to the flexicharge page, you should now see a new charge type called "My custom charge". This charge will behave the same as a Site-wide charge.

In the following pages of this handbook section, you'll find some snippets that show you have to make various modifications to your custom charge.

HOWTO: Simple quantity discounts per item

If your business requirements include quantity discounts on some of your items but not others, you can create quantity discounts on individual items by using a custom Flexicharge coupled with a new product type (that you create yourself). At checkout time, the Flexicharge you create will check the quantity of the item type you specify and return the appropriate discount.

The following steps assume that you already have ecommerce and Flexicharge installed and working with the generic product type. In this example, you will create a new product to sell technical support hours, and offer quantity discounts if the customer orders any number of hours above 1.

Important note: This code has not been tested and may do something wrong. It's just an example. Also, since I'm new to Drupal e-commerce there might be a better way to accomplish the same goal. This is simply the way I figured out how to do it based on my requirements. If you have many different types of items that will each receive different sizes and/or types of discounts this method is probably not appropriate, and instead you should look into a more custom solution.

STEP 1: Create a custom product
The ecommerce module installs several optional product types such as Tangible, Generic, File, and Parcel. First, decide which type of product type you will be using for your discountable product. The example below uses a Generic product type, which means it does not implement inventory management or availablility. If your product requires those features you should consider starting from the Tangible product instead.

NOTE: Starting from a product type such as Tangible that implements inventory management will require additional changes for SQL statements and table names. See /ecommerce/tangible/tangible.install for an example.

Create a directory under /ecommerce/ called "support". Copy the contents of the /ecommerce/generic/ folder into the /ecommerce/support/ directory. Now you will modify the generic module to identify it as your new "support" product.

Rename the files in /ecommerce/support/. The generic.module will be renamed support.module, and the generic.info will be renamed support.info.

Edit each of the two files you just renamed. In both files, replace all occurrences of "generic" with "support". In support.module, you should also replace the text "non-shippable" with "support" (this is text that is displayed in the administration screen only).

Save your work and (if you are working with a local copy of these files) upload the new folder to your Drupal server. Enable the Support product type under Administer -> Site building -> Modules.

STEP 2: Create a custom Flexicharge
First, follow the instructions to create a custom charge the template at http://drupal.org/node/123026. The example below assumes you have named your new Flexicharge provider foo.inc.

Next, modify your new Flexicharge to detect quantities of your support product that are greater than 1, and return the appropriate discount. If you want to deduct a percentage you will add code to one of the percentage sections of the _flexicharge_calculate function in foo.inc. The example below will apply a flat-rate discount to any hours over 1.

        case FLEXICHARGE_CHARGE:
                $supportquantity = 0;

                foreach ((array)$txn->items as $item) {
                if (product_has_quantity($item)) {
                // check to see if the item is of type "support"
                if ($item->ptype == 'support') {
                        $supportquantity += $item->qty;
                        }
                }
        }

        // Check to see if the total number of support items is greater than 1. If not, set the quantity to zero.
        // For more complicated tiers of discounts (x off for 2, y off for 3, etc)
        // you could use a case construct here, and return a calculated
        // number instead of $supportquantity * misc->rate.
        if ($supportquantity == 1) {
                $supportquantity = 0;
        }

        // If $supportquantity is zero, the next line will return zero. If it is greater than zero,
        // it will multiply the number of support hours times the discount rate.
        return ($supportquantity * $misc->rate);

When you are finished, save your work and upload foo.inc to /ecommerce/contrib/flexicharge/providers.

STEP 3: Adding products and implementing the Flexicharge
Next, create a new product in your store. Select Create content -> Product -> Support. Fill in the product details as appropriate.

Finally, add a new Flexicharge in Administer -> E-Commerce configuration -> Flexicharge. Select the "Simple +/-" operator. If you want to subtract $10 for each hour of support (when the buyer orders 2 or more hours) set the Charge rate to -10.00. Under product types, select Support product. Save your changes.

Now when you add quantity 1 of your new item to the shopping cart there should be no discount. Adding 2 or more of your item should deduct the Charge rate from each item ordered.

NOTE: As of this writing there appears to be a minor bug in both Drupal 4.7 and 5.1 when adding new Flexicharges (see http://drupal.org/node/134468). If your Flexicharge doesn't appear to work initially, go back to edit the Flexicharge and simply save it a second time.

The above example allows you to create broad categories of items (such as "books" or "technical support hours") that receive the same quantity discount. For example, if you created a quantity discount on books, you might create a new product type based on the "tangible" product called "books", and then add many books to your online store each with product type "books". Then a single custom Flexicharge for books would apply to all combinations and/or quantities of books purchased by the customer.

Snippet: Total items excludes product type

First, follow the instructions to create a custom charge the template.

Warning! We don't take any responsibility for use of custom charges in live E-Commerce websites.

Warning! This snippet is untested.

In this example we are going to have a charge that ignores the value of a specific product type when it calculates a "percentage of the item total".

Look for this part in custom.inc. We are cycling through the items on the bill and adding them up, then finally we apply a percentage on top.

<?php
case FLEXICHARGE_CHARGE_PCT_ITEMTOTAL:
 
$value = 0;
  foreach ((array)
$txn->items as $item) {
    if (
product_has_quantity($item)) {
     
$value += ($item->price * $item->qty);
    }
    else {
     
$value += $item->price;
    }
  }
  return (
$value * $pct * $factor);
?>

By adding a conditional statement, we can avoid adding the a specific product type. Let's use donation products as an example.

<?php
case FLEXICHARGE_CHARGE_PCT_ITEMTOTAL:
 
$value = 0;
  foreach ((array)
$txn->items as $item) {
    if (
$item->ptype != 'donate') {     // add this check
     
if (product_has_quantity($item)) {
       
$value += ($item->price * $item->qty);
      }
      else {
       
$value += $item->price;
      }
    }   
// add this closing bracket
 
}
  return (
$value * $pct * $factor);
?>

Payment: use payment gateways

The payment module allows for various payment gateways to be used such as different regional credit card gateways. The payment module is useful for enabling stores to support a choice in currency and payment gateway.

You can:

  • enable dependecies like the payment module at administer >> modules.
  • configure price formatting, recurring payments, and payment testing at administer >> store >> settings >> payment.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.
New gateways

Because of the current development environment, we are unable to commit most new payment gateway modules that people submit to us. The is because payment gateway code is a potential risk to other users, and therefore we require at least 2 active E-Commerce contributors to provide ongoing testing and bug-fixing.

We hope that this arrangement will be more flexible in the future, but in the meantime all the payment gateway module issues that are not committed (as well as requests) are kept postponed in the queue.

The one exception to this is our desire to support Google checkout. Please register your interest (as a developer or a user/tester) on this Google checkout ticket

COD: Cash on Delivery

The ecommerce cash on delivery module is a basic payment type module. It is useful for stores that want to handle payment off-line.

You can:

  • enable the payment module which is a dependency at administer >> modules and check the payment module.
  • configure the title to use COD, page to go to after payment, mark transaction as paid, mark transaction workflow as completed at administration >> store >> settings >> cod.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.
Gateways: Supported payment gateways

E-Commerce provides support for a growing list of payment gateways. For detailed information about each gateway, please visit the listed website or consult the README.txt for the corresponding module. Vist the E-Commerce Issues Queue queue for information and advice about the status of each module.

To enable a payment gateway in a Drupal E-commerce installation, the administrator needs to visit the administer > modules page and select the payment module as well as one or more of the following:

Assist
Gateway, Russian: http://www.assist.ru (and in English)
This module is not release with 4.7 at this stage.
Authorize.Net
Gateway: http://www.authorize.net
ccard
Gateway, Australian: http://www.ccard.com.au
eWAY
Gateway, Australian: http://eway.com.au
E-xact
Gateway: http://e-xact.com. This module is currently broken.
iTransact
Gateway: http://itransact.com
LinkPoint
Gateway: http://linkpoint.com. Not yet release with 4.7.
ProtX
Gateway, UK: http://www.protx.com
Worldpay
Gateway: http://worldpay.com

The settings page for these modules can usually be found under

administer >
store > settings > the_module
. The general settings for the payment.module can be found at
administer >
store > settings > payment
.
Beginners FAQs on Authorize.Net

1) Authorize.net requires SSL
2) Credit card details are collected after the review page. (payment is done after reviewing the customer's details. )
3) Drupal payment modules accept the credit card info and transmit it but does not store it. The modules store an authorization code
4)Billing/ shipping address form fields doesnt have any validations (like - city should contain only alphabets etc etc)

PayPal: Process payments using PayPal

The PayPal module allows your site to support payments for products using the PayPal service, including direct bank payment, PayPal credit, and credit card payments.

In order to use this module, you need to create an account with PayPal. Also, you can setup Instant Payment Notification (IPN) to track PayPal payments from your own website with the path paypal/ipn.

You can:

  • enable the payment module dependency at administer >> modules.
  • configure paypal receiver email, PayPal URL, return URL, cancel URL, and PayPal IPN "request back" validation URL at administer >> settings >> paypal.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.

Product: Create products for ecommerce store

The product module allows you to sell a product which is a good or service that you wish to sell through your web site store. This is useful for generating revenue to help fund a site.

You can:

  • enable the store module dependency at administer >> modules.
  • configure permissions to administer, create, update, and delete products at administer >> access control.
  • create products at create content >> products and then select product types.
  • use image_attach module to add images to content types and then product enable content types to display products with images.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.
Auction: sell to the highest bidder

The E-Commerce auction module allows you to create a simple product that your customers can bid on for a prescribed time. The auction process is quite similar to other well-known auction websites. When the auction is over, the customer with the highest bid is then able to add the item to her cart and purchase it.

Please do not use price adjustment modules in conjunction with this module.

You can:

  • create a new product at create content >> auction item.
  • set the auction expiry, when are creating a new auction.
  • set the default auction expiry (in days, eg. all auctions expire after 3 days) in auction settings administer >> store >> settings >> auction. You can over-ride this when you create each auction.
  • enable the tangible and other ecommerce modules at administer >> modules.
  • configure the ecommerce file and down load settings at administer >> store >> settings >> payment .
  • file issues, read about known bugs, and download the latest version via the ecommerce project page.
Donate: a reason to give

The E-Commerce donate module allows you to receive money for specified charities, activities and causes. Each "donate" product that you create will have a name like: "Kids for Squids". When your website visitor chooses to donate, they enter how much they would like to donate, and that amount is added to their shopping cart.

You can:

  • create a new "donation" product at create content >> donation.
  • enable donate and other ecommerce modules at administer >> modules.
  • configure the ecommerce file and down load settings at administer >> store >> settings >> payment .
  • file issues, read about known bugs, and download the latest version via the ecommerce project page.
File: paid access to files

The ecommerce file module allows the customer to access the files they paid for by clicking on the 'my files' link in their navigation block after they login.

Customers are able to download songs after the payment status has been marked 'completed' for their transaction. Typically this happens immediately after payment, but in cases of e-checks and other payment types, this could be a couple of days. If you are using cron on your site, transactions that consist only of non-shippable items, such as file downloads, will have their workflow be moved from 'pending' to 'completed'. This saves you the time of doing this manually.

You can:

  • You can add instructions to the e-mail users receive after making a purchase at administer >> store >> settings >> payment .
  • enable the file and the dependent product and store modules at administer >> modules.
  • configure the ecommerce file and down load settings at administer >> store >> settings >> payment .
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.
Generic: a non-shippable ecommerce product

The generic module is used to sell non-shippable products in a store. It's the simplest product and is used frequently for selling services or event registrations.

The generic module does not have inventory management.

You can:

  • enable product and store module depedencies at administer >> modules
  • configure permissions for creating and managing non shippable products at administer >> access control.
  • create non-shippable products and edit own non-shippable products at administer >> store.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.
Generic: hooked on E-Commerce

The E-Commerce generic product module allows you to create a non-specific product which has no special features except a title, price and description and all the standard functions like being cart-addable. This product lends itself to valid-adding modules like ec_role which allows role selling but does not need any special product oriented features like shipping or stock levels.

You can:

  • enable the generic and other ecommerce modules at administer » modules.
  • create a new product at create content » non-shippable product.
Parcel: Create packages of ecommerce items

The parcel module provides a way for you to create a package of products. It is useful for sites that want to sell packages of products at a discount or offer a suite of products that together are a valuable group such as welcome basket or a package of products for an event.

You can:

  • Create the products and then create a product package and add or edit those items.These actions can be done using any of the options under create content.
  • enable dependencies like product, and payment modules at administer >> modules.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.
Tangible: a physical item to ship

The E-Commerce tangible module allows you to create a shippable product. This tends to be a favorite for those who have very standard physical products that each stand-alone and have little variation.Optional features includes inventory control and availability estimates.

You can:

  • create a new product at create content >> shippable product.
  • set an inventory level for each item, when you create or edit it, which will decrease as your product is sold.
  • set an availability estimate for each product when you create or edit it.
  • enable the tangible and other ecommerce modules at administer >> modules.
  • configure the ecommerce file and down load settings at administer >> store >> settings >> payment .
  • file issues, read about known bugs, and download the latest version via the ecommerce project page.
Subproducts and variations

With 4.7, E-Commerce includes functionality for creating and working with parent and child products through the Subproducts module, produced by CivicSpace Labs and sponsored by GoodStorm.

Subproducts are a feature of certain product-type modules. At present, subproducts are implemented by the Apparel and Custom modules, both found in the ecommerce/contrib directory.

Two different types of subproducts are implemented in the Subproducts module. In 'variation'-type subproducts, a parent product's children have a combination of different attributes. For example, a "Unisex T-shirt" might come in both grey and green and both small and large. Subproducts of the parent shirt might then include (a) grey small, (b) grey large, (c) green small, and (d) green large. Any product that comes in different qualities, sizes, colors, compositions, etc. can be handled as variation-type subproducts.

For the other type of subproducts, called a 'base'-type, each subproduct is based on an existing product. A practical example would be a design that's to be printed on a shirt. In this case, the design (say, a logo) is associated with a parent product. A child product might be the logo printed on a large, green unisex t-shirt. Base-type subproducts don't themselves have variations (size, color, etc.), but the products they're based on do. (Does that sound complicated? Don't worry, there are some simple instructions below.)

Working with variation-type subproducts

When you enable or update product.module, it should create a ec_product table with the correct 'pparent' field to the ec_product table (this has been added to the product.install file).

The following steps should get you started.

  • Enable the apparel and subproducts modules.
  • You need to create some variations (e.g., 'size', color') and attributes (e.g., for size, 'small', 'medium', 'large'). Do this at administer > product variations. (The interface is like what you'd see for forum module's 'containers' and 'forums').
  • Create a new apparel product; this will be your 'parent' product.
  • When the product has been created, click the 'subproducts' tab. Here's a sample of what you'll see:
    Parameter Surcharge # in stock
    color
    green 0.00
    pink 0.00
    size
    large 0.00
    small 0.00

    Choose which attributes you want subproducts of, optionally set their stock amounts, and click the "review options" button.

  • On the next page, you'll get a list of subproducts to generate. Example:
    color size Price Stock
    green large 12
    green small 12

    pink large 12
    pink small 12

    Uncheck any products you don't wish to create, optionally adjust their stock amounts, then click the "generate" button. You should now see a "list" option under the subproducts tab, which lists the newly-created subproducts.

Working with base-type subproducts

The following steps should get you started.

  • Enable the apparel, custom, and subproducts modules, and follow the instructions above for creating an apparel node with subproducts.
  • As with variation-type products, create a custom product (this will be your parent product) and click the "subproducts" tab to generate subproducts.
  • Follow the instructions, selecting the apparel products to use as bases and your desired variations.

Recurring products, role assignments and user account creation

From 4.7, subscriptions and memberships has been rewritten for Drupal E-Commerce. In E-Commerce 4.6, there was some support for recurring payments, however this did not cover many user requirements and including integration with memberships. The new code is split into the following modules:

Module Function Description
ec_recurring Recurring products Manages product expiry schedules.
ec_roles Roles / memberships Performs role changes that at product purchase and expiry.
ec_useracc Account generation Facilitates creation of user accounts once a user buys a product that is creational.

All of these modules can be installed by downloading and installing E-Commerce, and activating the modules above via administer » modules

Developers can monitor the expiration schedules via administer » store » settings » show schedule list which becomes available when the ec_devel module is installed.

EC Subscriptions: Configuring emails

The ec_mail module allows you to define multiple emails that can then be used by other modules. This module is important for many subscription systems where there may be, for example, three different emails leading up to the expiration of the product or membership.

Here are step by step instructions for setting up emails.

  1. Navigate to the mail settings via administer » store » settings » mail
  2. You will see a list of configured emails. By default there are two emails, a default email confirmation and a default welcome email. Remember that these emails will not go anywhere on their own, they are simply saved templates.
  3. Click on add mail to start creating a new mail template. For this example, select the email type "Reminder" and give it the name "First Notice".
  4. Type in the subject and body of the email. Specify any required variables using the notation %variables - click on the "See available variables" link for a list of variables that can be used with each mail type.
  5. Once you've created your email, you will see this in your email list. You can then edit or view any email in the list by clicking the relevant links.

Please read further in this section to find out how to use the emails you have created.

EC Subscriptions: Expiry schedules

An expiry schedule defines how long before a product expires and how many times a product can be re-purchased. Adding schedules is a simply process:

  1. Navigate to the E-Commerce schedule settings via administer » store » settings » schedules
  2. Type a name for the schedule, eg. "Yearly"
  3. Select the number of units and the unit of measure (based on our title we choose "1" and "Year(s)")
  4. Select the number of renewals that will occur and then click "Add schedule"

Once you've added the schedule, you will see the information you've just added plus a link to "Add a reminder":

  1. Click on "Add reminder". Select reminder email which you defined by following the email configuration instructions
  2. Select the unit number and type (eg. you might like a reminder to occur 1 week before expiry).
  3. Click "add reminder"

You have now configured a reminder schedule, along with the corresponding reminder emails.

EC Subscriptions: Role products

The primary way to sell a membership through your Drupal site is to sell a Drupal role. Once the customer has purchased a role, all of the benefits of that role will become available. So, the first thing you should do is create a role that you can then sell, although you can leave access permissions until later. Please refer to the handbook for more information about managing users and setting up roles.

You will also need to enable a product module. Probably the best starting point is the generic product which is quite feature-less product on it's own.

Having enabled an appropriate product module, and configured your new role (or if you are happy selling the default "authenticated user"), follow these steps to set up a role product:

  1. Navigate to the E-Commerce role settings via administer » store » setting » ec_roles
  2. Check/select the role(s) that will not be sold, and click "Save Configuration"
  3. Go to create content » non-shippable (or whatever) » and enter the standard information such as title, price and description.
  4. Under "Recurring products" select a schedule which you have already defined
  5. At this point, save your product and then edit it again so that some of the other checkboxes are active.
  6. Under the "Role Assignments" section, check which roles will be come active or will be removed after purchase and expiry.

Note that if you only have ec_roles active, and not ec_recurring as well, you will only be able to select a role to assign on purchase.

EC Subscriptions: Anonymous users

Anonymous purchases present difficulty in tracking and transaction history, so much of the ec_anon goodness is under the hood. Closely associated with this module is ec_useracc which helps you control what happens when a user purchases a product without being registered.

Here are instructions for configuring anonymous purchases:

  1. If you proceed to the settings via administer » store » settings » ec_anon you can set the default product setting. The options are:
    • Disabled: Anonymous purchases are disabled. All customers must register or login before they can checkout.
    • Flexible: Customers can choose to register, login or checkout anonymously.
    • Anonymous Only: Only anonymous purchases are allowed. Customers are never given the option to register during checkout. Customers will be given the option of logging in or purchasing anonymously.
  2. Once you have configured the default setting, you can also over-ride this for each product.

And to set up automatic account creation:

  1. Go to administer » store » settings » ec_useracc
  2. Define the welcome and confirmation emails.
  3. The days to confirmation setting will indicate how long a confirmation email can resonded to.
  4. When you are editing the product you can configure some "User account provision" settings:
    • Create an account for the user when this product is purchased (if they don't already have one)
    • Block the user's account when this product expires (if no other unexpired products exist)

Shipping charges

E-Commerce provides you with the ability to automatically charge shipping fees during the checkout. Essentially you assign weights to your products, and then the shipping fee is calculated locally through charges you assign, or remotely via live shipping APIs (currently UPS, USPS and Canada Post are supported).

Note: For simple shipping, without the need to a shipping API like USP, please refer to Flexicharge.

After you've installed the core modules, the relevant modules you can activate for shipping in administer » modules are:

Module Description
shipping This provides the core shipping functionality (API).
shipcalc Provides hooks for "partner" files which each interface with an external shipping API. It is observed that most of shipcalc's features can be incorporated into the shipping module and the two will be merged eventually.
address Manages the user shipping and billing addresses.
Address: Enable address books for store

The address book for e-commerce allows addresses to be kept for store customers.

You can:

  • select the default country for the address book at administer >> store >> settings >> address.
  • Choose a shipping and billing address or just a shipping address during check-out.
  • file issues, read about known bugs, and download the latest version on the Address project page.
Shipcalc: Real time UPS and USPS price quotes

In version 4.7, the contributed Shipcalc module has been rewritten to support multiple shipping services. Initial support is for UPS and United States Postal Service (USPS).

Follow these steps to enable real-time shipping quotes for UPS. (The steps are directly parellel for USPS.)

Requirements

You will need the following:

  • shipping.module
  • shipcalc.module
  • a shippable product type module, e.g., tangible
  • the various other ecommerce dependencies, e.g., product.module,
    cart.module
Configuration
  1. Enable shipping, shipcalc, and a shippable product type, e.g., tangible.
  2. Add a fulfillment center
    • Navigate to administer >> store >> settings >> shipping
    • Create and submit a fulfillment centre. This is the address from which items will be shipped, and is used in shipcalc as the 'origination' or from address.
  3. Configure shipcalc

    Before setup, you need to apply for and receive a UPS access key.
    Steps:

    Once you have your key:

    • Navigate to administer >> store >> settings >> shipcalc
    • Click on the 'configure' link for 'ups'
    • Fill in and submit the UPS settings form
    • Test your setup.
      • For USPS: When the form reloads, expand the 'testing' fieldset, ensure the testing URL is correct, and click the 'testing' button. You should get message returned with the shipping options and prices of a sample item. If no such messages are returned, or if
      • For UPS, testing requires live data, so you must first complete both of the following steps: configure your product type, and create at least one sample product. Then return to the UPS configuration page and try testing as per USPS.
  4. Configure your product type

    We set a set of available shipping methods for each shippable product type.

    • Navigate to administer >> settings >> content types >> product
    • From the 'configure product types' fieldset (likely at the top), select a shippable product type, e.g., tangible
    • For the particular product type, select which shipping methods you wish to enable. Note that, although USPS options appear here, they are only functional if you are using the CVS version.
  5. Set shipping methods and associated data for each product

    For each product, we select from the shipping methods available
    for its product type.

    • Navigate to e.g. create content >> product >> shippable product
    • Complete required fields
    • Under the 'shipping methods' fieldset, select which methods to make available for this product. The options are those registered to the shipping type.
    • Also under 'shipping methods', enter required product attribute information. For UPS, this is the product weight.

Now, when you add an item with shipping to your cart, you should get a checkout screen with dynamically-fetched UPS shipping price information.

Store: configure your ecommerce site

The store module allows you to configure your ecommerce site. You can see transactions, configure store searching, and see store overiews.

You can:

  • enable product, payment, and cart module dependencies at administer >> modules
  • configure search, advanced search, transaction, update transaction, create new transaction, delete, and confirm delete at administration >> store.
  • configure customer accounts for purchases and payment notices at administer >> strong.
  • review the store transactions, store overview, and create new transactions.
  • file issues, read about known bugs, and download the latest version on the ecommerce project page.

Product types

E-Commerce features several different product types, each implemented through a separate module and each having its own features.

To enable a new product type:

  • Ensure you have the product.module enabled
  • For contributed product types, install database tables if needed. All tables needed by core product type modules are included with the core install, so no additional database installation is needed.
  • Enable the desired product type module (see their module names in the following lists).
  • Under administer > permissions, set desired permissions for the product type.
Core product types
  • File
    Implemented through the file.module, the file product type allows you to sell access to downloadable files. The customer will access the files they paid for by clicking on the 'my files' link in their navigation block after they log in.
  • Non-Shippable
    The generic.module creates non-shippable items with no inventory management.
  • Collections of Products
    The parcel.module allows you to create groups of ecommerce items. Specifically, you can create packages: groups of items that are sold as a whole.
  • Shippable
    The tangible.module creates shippable products for ecommerce. Optional features include inventory control and availability estimates.
Contrib product types

As well as the above product types, a number of contributed types ship with the E-Commerce package distribution. Some of these require special installation, e.g., creating database tables.

  • Auction
    The auction.module creates a very simple product that can be bid on by users.
  • Donation
    The donate.module enables financial contributions.
  • Hosting
    The hosting.module is used to sell web hosting services.
  • Service
    The service.module module allows selling a variable rate service.

There is no price set for the service, so the user has to enter the dollar amount for the service, as per
agreement with the site owner.

This is useful for getting paid via Paypal for things like design and programming project.

When creating donation product types, choose 0 for the price and select
the 'hide' option for the 'add to cart' link.

Media File

This is a stub

Managing your store

This section is open to any tutorials, tips and suggestions that help people manage their E-Commerce installation.

Searching for sales

While there is a transaction history at administer » store » transactions, you may want to do a deep search of your transactions. This can be done on the search page: administer » store » search.

Basic search

To perform a simple search, you can type some text into the first text box and click Search. The search examines all of your product titles and descriptions, and then returns all transactions that contain these products.

Advanced search

Below the basic search are your advanced search options (you may click on Advanced search to reveal it). Please note that the Basic search textbox is a part of the Advanced search. Here is a description of the other search options.

Label Description
Transaction ID This is the unique ID assigned to every sale of 1 or more products.
Product expiration For products which have recurring scedules, enter a date of expiry (or a range like 1/1/2006-31/1/2006).
Username or ID This is the user name or user ID of the purchasing customer's Drupal account.
Date created When the transaction was created. enter a date of expiry (or a range like 1/1/2006-31/1/2006).
Product ID This is the unique ID assigned to every product. If the following node was a product, www.example.com/node/99, it's product ID would be 99.
Total cost This is the gross cost of the invoice which includes shipping. You can enter a range, eg: 10-100 would be all transactions between $10 and $100.
Payment status Search on payment status. Limit to one or more of: pending, payment received, completed, failed, denied, refunded, canceled.
Workflow Search on payment status. Limit to one or more of: transaction received, invoiced, shipped, awaiting customer response, canceled, completed
Payment method Search based on the payment method. The options will be based on those payment methods enabled on your site.
Product type Search based on the product type. The options will be based on those product types enabled on your site.
Recurring status This is a checkbox that allows you to limit to products which are recurring.

Once you submit your search, the search terms that you've used should display on the results page.

Developer documentation

E-Commerce comes with a developed API to facilitate extension of the suite of modules. Here is a quick introduction to the API designed to get you started developing E-Commerce solutions.

The product API

Most E-Commerce hooks involve the product API, implemented through hook_productapi().

Product API hooks options include many of the actions of the nodeapi, enabling the creation of specialized product types, as well as several custom options for accomplishing actions like adjusting prices and managing inventory.

Product API options include:

op description return value
'wizard_select' Define the name of a product type. an array for the product type with a machine-readable key (typically, the name of the module) and value of a human-readable title
'in_stock' If stock is managed for an item, determine if there is stock available. boolean
'is_shippable' Is the product shippable? boolean
'on payment completion' Define action to be taken once payment has been completed. none
'adjust price' Modify the price of a product. new price

Product API also implements versions of the following hook_nodeapi() options: fields, validate, form, load, insert, update, delete.

Creating a new product type module

While there are several product types already implemented by core and contributed E-Commerce modules, there will always be new needs not already covered off. One of the easiest ways to use the E-Commerce API is to write a module implementing a new product type.

At a minimum, a product type module has four functions:

hook_help() Provides a module description and contextual description of the product type.
hook_perm() Defines permissions for the product type--typically, 'create' and 'edit own'.
hook_access() Determines the access permissions of a particular user.
hook_productapi() Uses the product API to define characteristics and behaviours of the product type.

If you want to store information on your product beyond that stored in existing tables, you'll need to include a .install file to create the appropriate table and fields.

To get started, you'll probably want simply to choose one of the existing product type modules and modify it to fit. For a useful model, see the core E-Commerce tangible.module, which defines the 'shippable product' product type.

If you're unfamiliar with Drupal methods for writing node modules, start by studying the example module documentation. There you'll find details on how to implement load, insert, update, delete, and other core methods for working with node types. If you are adding fields in your product type module (e.g,. a "manufacturer" field), you will use use the same general approach, except that instead of implementing each operation in a separate function, e.g., example_load(), you'll do it all as options within your module's _productapi() function.

Quick tips:

  • Create a new directory for your module and copy tangible.module and tangible.install there. Rename them to your product type, e.g., 'potato.module'.
  • Choose a machine-readable and a human-readable name for your product type. Ideally they'd be similar. :) The machine readable name is equivalent to 'tangible', while the human readable one is eqivalent to 'shippable product'.
  • Replace all occurrences of 'tangible' with your machine-readable name (the name of your module), and all occurrances of 'shippable product' and 'shippable products' with the equivalents for your product.
  • If you need to store extra information about your products, design a table and adjust the insert, update, delete, load, and fields productapi options to reference your table's fields. Adjust the .install file to create your table.
  • Look over the rest of the .module file and fix up references. E.g., provide appropriate help text in the hook_help() function describing your product.
  • Delete the last two functions; you won't need them.

See the tips, tricks, and code snippets pages for additional ideas, e.g., how to make your module inherit the tangible functionality.

Tips, tricks, and code snippets

Please add your E-Commerce tips, tricks, and code snippets as comments to this page.

Create a product type that inherits the tangible/shippable features

If you want your product type to be shippable you could just repeat some of the code that's in function tangible_productapi(), but then you'd be stuck updating your module every time tangible.module changes.

Handy shortcut to save you that work: instead of repeating the code from tangible.module, invoke it. See apparel.module for an example. apparel_productapi() reads in part:

<?php
   
case 'fields':
    case
'validate':
    case
'in_stock':
    case
'is_shippable':
    case
'on payment completion':
    case
'form':
    case
'load':
    case
'insert':
    case
'update':
    case
'delete':
    default:
      return
module_invoke('tangible', 'productapi', $node, $op, $a3, $a4);
?>

In other words, use tangible's methods here. (Of course, tangible.module has to be enabled for this to work.)

You could leave out all those case statements and just put default. The point of keeping them is just to remind us of what we're using tangible for. Note: if you have your own use of any of these op values in your module, you'll need to first do your stuff and then invoke tangible.module. E.g.:

<?php
   
case 'fields':
      return
array_merge(array('myfield'), module_invoke('tangible', 'productapi', $node, $op, $a3, $a4));
?>

Event: set times for content

Note: These instructions are for the 5.x-2.x branch of the Event module, which is still in active development. Since most of the work in this new branch is "under the hood", these instructions will work for the 5.x-1.x branch as well.

The major visible difference is that the 5.x-1.x branch includes an extra module called "basicevent". Basicevent provides a ready made content type for your events. This module is automatically turned on when you enable the Event module, so it doesn't matter if you explicitly enable it. Basicevent is no longer used in the 5.x-2.x version, which uses the new ability in Drupal 5.x to make new content types, but your existing content types will be automatically converted so there is no issue in using it in 5.x-1.x.

While the event content type created by basicevent cannot be deleted through the UI, you can go back to the module screen and disable the basicevent module, which will get rid of it. Make sure you do this before creating any content with it as that content will become unusable. Once basicevent is disabled, you will need to create your own event content type to use. Note that if you disable basicevent, it will re-enable itself if you ever run the event module install again.

Overview

The event module allows for any type of content to be event enabled, which means content can have a start and end time and appear in calendars. The ability to event enable any content type combined with the ability to create new types of content make it possible to create unlimited types of calendars.

Using Event

Step 1 - Install and configure

Download, install and enable the latest version of Event from the Event project page See Installing contributed modules for help.

Along with the Event module, there are two other modules in the package: "Event all day" and "Event views". "Event all day" adds a checkbox to events for events that run all day (that is, no set start/end time). "Event views" exposes your events to the views module and allows you advanced control over displaying your events. You can enable these on the modules screen as well.

You can configure the event module at administer >> settings >> events. There are two sections in the configuration.

  • Event overview: This has general settings for events about how they are viewed and filtered.
  • Timezone handling: This is where you set how time zones are handled as well as whether you want times displayed with a 12 hour or 24 hour clock.
  • You also need to make sure you have your site's timezones set up at admin/settings/date-time. If you have no timezone set, you will get a warning when you try to add an event.

Step 2 - Set up content type(s)

The event module comes with a pre-set content type called "Event". This content type is already event enabled and ready to be used. If you only need one event content type and don't need any extra fields, you can skip to step 3.

If you would like more event content types, you create them in the normal way at administer >> content management >> content types >> add content type.

You'll notice that there is now a new option labeled "Show in event calendar: All views, Only in views for this type, never". To make this an event enabled content type, choose one of the first two options. You can also event enable existing content types in this way. As of Drupal 5.x, this is part of core and does not require any extra modules.

If you have the CCK module installed, you have the option of adding extra fields to your events. You can add them to the pre-made Event type or to any that you create. This is useful if you want to add a field for, say, event cost. You can find out more about adding fields in the CCK handbook pages.

If you have the location module installed, you can enable it on your event types to allow people to enter the event's location. Adding Gmap lets you map your events as well.

Step 3 - Creating events
To create a new event, select your event type from the create content menu. In addition to the normal title and body and any fields you may have added, you'll see a place to enter the start and end dates. If you enabled all day events, you'll see a checkbox for that as well. Clicking the "All day" checkbox will turn off the boxes for entering times and set the times to 00:00 - 23:59 in the database. This is the way the event module recognizes that an event is all day.

Note: If you have the jscalendar module from jstools enabled, you will see a different view for the start and end dates. Instead of the separate textboxes, there is one textbox for each with a button for the popup calendar.

Some examples:

  • To enter an event that is all day on May 25, 2007: Check the all day box and enter May 25, 2007 into both the start and end dates.
  • To enter an event that is all day from May 25, 2007 to May 28, 2007: Check the all day box and enter May 25, 2007 into the start date and May 28, 2007 into the end date.
  • To enter an event that is from 9 to 5 on May 25, 2007: Do not check the all day box. Enter May 25, 2007 09:00am into the start date and May 25, 2007 05:00pm into the end date. (If using 24 hour time, use 17:00 instead)
  • To enter an event that is from 9 to 5 every day from May 25, 2007 to May 28, 2007: Do not check the all day box. Enter May 25, 2007 09:00am into the start date and May 28, 2007 05:00pm into the end date. (If using 24 hour time, use 17:00 instead)
  • Multiple date events with multiple times are not possible with just the Event module. See the Event Repeat module for options.

Step 4 - Displaying events
There are various ways to view your events:

  • The calendar page is automatically linked to at the bottom of every event enabled node. The URL for is is ?q=event and it takes parameters in the URL such as "?q=event/2007/05/27/month/". This page gives the options of showing events by month, week, day, and also in a table or list view.
  • In administer >> build >> blocks you can enable the calendar block and the upcoming events block
  • If you have the "Event Views" module enabled, you will see three default views - event_date, event_js, and event_select. These offer a filtered list of of your events with the filters exposed for the user to choose. You can also make your own views based on these or completely custom to be able to show your events however you want.

Related modules

  • Event repeat: allows you to create recurring events.
  • RSVP: users can create and manage RSVPs associated with events
  • Signup: allows you to signup enable

Event mangement module comparison

There are many modules that support event organizing. This chart provides an overview and a comparison of the existing modules functionality. We need to add a pre-event plannig column to this chart. There is a full page version here.

Module Name Invite Register Search Attend Follow-up
Basic event
CiviCRM mailing address, email registration profile, group, activity history activity history activity history
Event Calendar by content type, taxonomy, day, week, month. sets time for content
Event all day Allows for events without times. belongs in core event
IMO.
Event views Customize event listings  
Eventrepeat Allows for scheduling repeat events
EventFinder Register after search only, send HTML mail, sends mail
for each registration, adds attendees to CiviCRM group
Search by event taxonomy, event type, location
proximity, state , metropolitan areas
Gsearch(GoJoinGo) searching for specific node types such as events and
users by location
gjg_event/venue (GoJoinGo) search for "my" events, create venues when posting events Tracks event attendnance
Invite API
invite_api establishes<br />a framework for sending<br />invitations to users.<br />
   
Invite The 4-7 branch of the invite module exposes hook_invite that allows any module to react to the invite lifecycle.  
jscalendar Produces DHTML popup<br />calendars using the<br /> jscalendar library
Location location of users, CiviCRM contacts Location of events can be used in event finder search. Location of event. Location of events
og_calendar     Displays a calendar of events showing only those belonging to an organic group
RSVP Allows attendess to invite others,

allows response to all invitees,

hides attendee list, your invites
Allows yes, no, maybe, none responses from invite link,
allows link to invite, allows user to invite others, allows response
message, your RSVPs(registers)
Signup Allow users with signup permission to see attendees,
send email for each signup, send confirmation, send reminder
Close registration a

certain time before
User Invite user_invite sends <br>invitations from user<br>to user for friend<br>and buddy requests.
Volunteer volunteer link on events,

volunteer for events by default
volunteer overview, default number of volunteers for
events,
volunteer message before registration, volunteer message after
registration, default message for approval or denial or waitinglist or
reminder, volunteer registration for event, select volunteer
coordinator, volunteers wanted, volunteers found, volunteer status:
approve or deny or waitlist or cancel,
Volunteer overview: name, event, date, status default message for follow-up, volunteer rating with
comments, volunteer overview

If you're using Firefox WIN, and you cannot see the entire table's width due to the CSS float bug, try using the "print" view to see the entire table. Note that you must be logged in to access the "print" view.

This page was written by Kieran Lal from CivicSpace. If you are interested in collaborating on event management or improving this page please contact Kieran.

Making sure you got the Events entered correctly

As a recent convert to the Event family, and hitting a small bug (that has been fixed now), I found it messy to make sure I had the events entered without weird starting/ending times or the right taxonomy terms or the right durations. So I decided to check up on myself and created this little snippet to show all my events in a little more concise way. This is just a simple table that summarizes all those fields. Feel free to use it.

Note: This snippet is for 5.x-1.x of Event.

<?php
  $header
= array('Title', 'Type', 'Term', 'Start', 'End', 'Length');
 
$rows = array();
 
$results = db_query("SELECT n.nid, n.title, n.type, td.name AS term, e.event_start AS start, e.event_end AS end FROM {node} n INNER JOIN {event} e ON e.nid = n.nid LEFT JOIN {term_node} tn ON tn.nid = n.nid LEFT JOIN {term_data} td ON td.tid = tn.tid ORDER BY start");
  while (
$node = db_fetch_object($results)) {
    if (
$node->end > $node->start) {
     
$duration = $node->end - $node->start;
      if (
$duration == 86340) { $duration = 'all day';}
      else {
$duration = format_interval($node->end - $node->start, 2); }
     }
    else {
$duration = NULL; }
   
$rows[] = array(l($node->title, 'node/'. $node->nid .'/edit'),
                             
$node->type,
                             
$node->term,
                             
format_date($node->start),
                             
$node->end == $node->start ? NULL : format_date($node->end),
                             
$duration,
                       );
   }
/* end while */
 
return theme_table($header, $rows);
?>

Settings Permissions for Event Mangement

There are several permissions that relate to event management that are documented below.

anonymous users: the most basic issue with permissions as it relates to anonymous users is the ability for them to view events (note that this also applies to all types of nodes, not just events, i.e. - pages, books, etc...). Simply visit admin >> access and set the node module permissions as appropriate. node module => check access content for the anonymous user role.

administering events: for permissions relating to the ability to actually administer events, look for admin >> access >> flexinode module and set role permissions that allow user roles to

  • administer content types - this is required if you want the user role to be able to specify the default options - i.e. promotion, moderation, promote to front page, etc... and if you want the user role to be able to specify the default view, edit and permissions for permissions for users. To be even more specific, this permission controls all permissions which are set here - admin >> node >> configure >> types
  • create event content
  • edit own event content

EventFinder: access event information

EventFinder is a module for accessing event information on your site. It is very useful for communities that have many events. The ability to find events in many ways facilitates bringing community members together in person which makes communities stronger.

This module allows the user to seach for events based on event type, category, geographic location and proximity to major metropolitan areas. A user can also register for events, and it is helpful for event hosts to retrieve lists of contacts for events they have created on the site. Users can access lists of events which they have created and registered for. Event allows users to create saved searches, which monitor the system for new events matching criteria they have specified.

You can:

  • enable required civicrm, event, and location modules at administer >> modules.
  • configure a required default registration page through the CiviCRM Administration profile forms by selecting the Display in Registration Form? checkbox next to fields you are going to collect.
  • configure a required CiviCRM email profile field to select Key to Match Contacts?.
  • set permission for event searches, saved searches, event hosting, and administration at administer >> access control.
  • view eventfinder prerequisites, module configuration, and contact repository settings at administer >> eventfinder.
  • clear registration information or submit feedback about event finder at administer >> eventfinder.
  • configure search settings for event taxonomy, event type, location proximity, state , metropolitan areas at administer >> settings >> eventfinder.
  • configure display, registration, mailer options, and saved search options at administer >> settings >> eventfinder.
  • configure display block to appear above, below, or in a seperate block at administer >> settings >> eventfinder display settings.
  • file issues, read about known bugs, and download the latest version on the EventFinder project page.

CiviCRM integration with EventFinder

EventFinder is integrated with CiviCRM in the latest release. The level of integration currently available is:

  • event registrants are recorded as individuals in the database
  • registrants are tagged as event participants
  • an action is recorded on an individual's record saying he or she registered for a certain event, including the title of the event

There is also a lot of experimental functionality developed using CiviCRM betas that extends this a great deal further. When I get the change to port it, you will get the following as well:

  • users with permission to create events will be able to make lists of people to invite and send trackable email invitations (all data stored in CiviCRM)
  • suggested events (recommends other events based on common registrations between CiviCRM individuals)

Eye-drop Editor

Note: these docs are a work in progress.

Eye-drop Editor is a plugin to jQuery that provides an image editing interface.

One example of integration ships with this tool: Eye-drop Field, which is a CCK field with image_attach dependency. On the node that you've attached an image too, these images will be displayed. The image can be clicked and a new crop area chosen. Save the image updates the database and refreshes the image in the page.

Eye-drop was written by Simon Hobbs and sponsored by Urbits.

Editor Installation

Eye-drop Editor is basically an editor written in JavaScript, with module that provides an API to interact with the editor. The JavaScript ships with the editor so the only thing you need to do is install Eye-drop.

  1. Go to Administer » Site building » Modules.
  2. Tick "Eye-drop Editor" and Submit the form

However this will have no effect, since Eye-drop Editor is a tool that other modules must implement. Read further for a description of how to install and configure Eye-drop Field, which is essentially a CCK field that integrates with the editor.

Eye-drop Field - a CCK field for editable images.

Eye-drop Field is a CCK field that will ship with the first version of Eye-drop Editor (EE). This module is a functional example of integration with EE, and it was written due to the need for a practical solution in a new website.

Download
Goal

This module was written specifically for the following requirements:

  • The client runs a fancy dress shop, her products are often parts of other things. She uses images from her customers - say a customer has a party and sends a picture of 4 people (ie. four costumes). She wants to upload the image once and use parts of the image for different products.
  • The site theming requires consistency in image size. Some have a specific height and width (thumbnails). Some just need to be a certain width.
  • Images need to be applied to multiple products.
  • There is no time or resources for separate image editing software.
Solution
Image Image module is used for the image management. We'll be able to manage media in an independent and proven way. Also, the image module will manage the "variations" or "derivatives" like thumbnail, preview and whatever.
Image Attach Image Attach (which ships with Image) gives us the ability to attach one image to each node. We can change the image, and we can assign each image to multiple nodes. Once again, a tried an tested solution to minimize risk.
Eye-drop Field Eye-drop Field (ships with Eye-drop) is a CCK field. It allows us to associate an defined Image module derivative (eg "thumbnail" or "preview") to a field. The Image module derivative provides the height and width information for the destination image. The Eye-drop Field itself stores the information about how to crop the original.
Content (CCK) Obviously a requirement for a CCK field. The reason we are using CCK field is that each
Image Configuration
  • Go to Administer » Site configuration » Image and configure the image sizes that you want to have in the site. For example, make 'thumbnail' 100 x 100 and 'preview' 500 x BLANK.
  • Go to Administer » Site configuration » Image and allow images to attached to multiple nodes (not actually compulsory, just keeping in line with the goal).
New Content Type
  • Go to Administer » Content management » Add content type and create a new content type, say, called "Costume".
  • Click on your new content type and click Add field. Use a label that is the same as the image derivative name (eg. "thumbnail") and select "Image Selection" as the field type.
  • Next you edit the field itself, and there is only one setting you need to worry about - Link this field to which derivative size configured through the Image module? Here we select the "thumbnail", which effective links the label "thumbnail" and the target width and height (100x100) that we have assigned to the thumbnail derivative in the Image module settings.
Making it work

OK, some of this stuff is a bit hacky.

  • Whatever your normal files directory is, create a directory inside called eyedrop_field
  • Other stuff, yada yada yada. Yes, still very much in development.

Roadmap

This is a brief outline of the next steps (bug fixes not included):

  • Pretty-up the UI
  • Restructure the javascript so that the main routine does not contain any editing code, just managing buttons, parameters, canvas size, layers and callbacks.
  • Make the cropping script more generic so that it can be a) reusable in layers and b) reusable in different editing actions (crop, select, draw, copy/paste).
  • Add new actions: rotate, effects, watermark, outline, etc. This functionality will be couple to transformer. Make these actions tied to buttons.
  • Establish layers, where representations of each step can be viewed and specific actions altered.

FAQ: Frequently Asked Questions

Description

The Frequently Asked Questions (faq) module allows users with the 'administer faq' permission to create question and answer pairs which they want displayed on the 'faq' page. The 'faq' page is automatically generated from the FAQ nodes configured and the layout of this page can be modified on the settings page. Users will need the 'view faq' permission to view the 'faq' page.

There are 2 blocks included in this module, one shows a list of FAQ categories while the other can show a configurable number of recent FAQs added.

Note the function theme_faq_highlights(), which shows the last X recently created FAQs, used by one of the blocks, can also be called in a php-filtered node if desired.

Configuration

Once the module is activated, you can create your question and answer pairs by creating FAQ nodes (Create content >> FAQ). This allows you to edit the question and answer text. In addition, if the 'Taxonomy' module is enabled and there are some terms configured for the FAQ node type, it will also be possible to put the questions into different categories when editing.

The ability to configure the layout of the FAQ page is only possible in the Drupal 5.x version of the module. There are four question and answer layouts to choose from. The 'administer faq' permission is needed for configuring the 'faq' page layout and editing of FAQ nodes.

Current maintainer

Stella Power (http://drupal.org/user/66894)

FAQ - Installation

Install the files

Download the latest version of the FAQ module from the project page. Untar/unzip the files and place the entire faq directory in an appropriate module directory like sites/all/modules.

Enable the module
  • Go to Administer » Site Building » Modules
  • Make sure "Frequently Asked Questions" is enabled (listed under "Other").
  • Click Save configuration at the bottom of this form.

FAQ - Settings

The layout of the FAQ page can be modified on the settings pages (Administer >> Site Configuration >> Frequently Asked Questions). There are a number of question and answer layouts to choose from. In addition, if the 'Taxonomy' module is enabled, it is possible to put the questions into different categories when editing. Users will need the 'administer faq' permission to configure the layout, etc.

You can view screenshots of the settings pages here.

FAQ - Settings: Categories

If you have a large number of questions and answers, it's often useful to be able to put them into different categories. This can be achieved by enabling the "Taxonomy" module and checking the "Categorize questions" checkbox on the FAQ categories settings page (admin/settings/faq/categories).

To start using categories, it is necessary to enable the "Taxonomy" module at Administer >> Site Building >> Modules. You will then need to set up a vocabulary and enable it for the FAQ node type (admin/content/taxonomy/add/vocabulary). Once you have your vocabulary created, you will need to add your terms/categories to it. Finally, when creating or editing your FAQ nodes, you will need to place each node in one or more categories. Use of sub-categories is only recommended for very large lists of questions.

The category name will be displayed above its associated questions and any description you add for a category will be displayed underneath the category name on the 'faq' page. In addition to the 'faq' page, you will now also have one page for each of the categories configured in the FAQ vocabulary. These will have a path like 'faq/tid' where 'tid' is the id of your term/category, e.g. 'faq/123'.

Category layout

This controls now the categories are displayed on the page and what happens when someone clicks on the category. There are three layouts to choose from.

  • Categories inline - Each category name and description is printed with its questions printed directly underneath. Clicking on the category name will take the user to the individual category page, i.e. 'faq/tid'.
  • Clicking on category opens/hides questions and answers under category - A list of all the categories, and their descriptions, which have FAQ nodes associated with them are displayed. Clicking on any of the categories will cause its questions to be displayed underneath. When the category name is clicked on again, the questions are once again hidden.
  • Clicking on category opens the questions/answers in a new page - A list of all the categories, and their descriptions, which have FAQ nodes associated with them are displayed. Clicking on any of the categories opens up a new faq page for just that category, i.e. 'faq/tid'.
Miscellaneous layout settings
  • Categories listing style - This allows to select how the categories listing is presented, wherever it applies. An ordered listing would number the categories, whereas an unordered list will have a bullet to the left of each category.
  • Show FAQ count - This displays the number of questions in a category after the category name.
  • Display category name for answers - This allows the user to toggle the visibility of the category name above each answer section for the 'Clicking on question takes user to answer further down the page' question/answer display.
  • Group questions and answers for 'Categories inline' - This controls how categories are implemented with the 'Clicking on question takes user to answer further down the page' question/answer display.
  • Only show sub-categories when parent category is selected - This allows the user more control over how and when sub-categories are displayed. It does not affect the 'Categories inline' display.
  • Show sub-categories on FAQ category pages - Sub-categories with 'faq' nodes will be displayed on the per category FAQ page. This will also happen if 'Only show sub-categories when parent category is selected' is set.
FAQ - Settings: General

The "general" settings page (admin/settings/faq) for the FAQ module allows the user to configure the title that will appear on the automatically generated 'faq' page. By default, it is set to 'Frequently Asked Questions'. It is also possible to enter a brief description and this will appear at the top of the 'faq' page above the questions and answers. This description can contain any information you require. For example, it could inform the user what to do if they can't find an answer to their question.

FAQ - Settings: Questions

This is probably the most important FAQ settings page. It allows the user to control the layout of the questions and answers on the automatically generated 'faq' page.

Page layout

The user can choose from four different question and answer layouts.

  • Questions inline - Each question is listed with its answer displayed directly underneath. Clicking on the question itself will take the user to the individual FAQ node page.
  • Clicking on question takes user to answer further down the page - All the questions are listed at the top of the page. Clicking on any of the questions will take the user to the answer which appears further down the page. Each question is printed again, just above the answer, which when clicked on will take the user to the individual FAQ node page. With each answer, there is also a link back to the top of the page.
  • Clicking on question opens/hides answer under question - All the questions are displayed in a list. When a user clicks on a question, its answer appears directly underneath. Clicking on the question again causes the answer to be hidden once more. This layout requires javascript to be enabled in the user's browser.
  • Clicking on question opens the answer in a new page - All the questions are displayed in a list. When a user clicks on any of the questions they are taken directly to the FAQ node page.
Miscellaneous layout settings
  • Questions listing style - This allows to select how the questions listing is presented, wherever it applies. An ordered listing would number the questions, whereas an unordered list will have a bullet to the left of each question.
  • Use answer teaser - This enables the display of the answer teaser text instead of the full answer when using the 'Questions inline' or 'Clicking on question takes user to answer further down the page' display options. This is useful when you have long descriptive text. The user can see the full answer by clicking on the question.
  • "Back to Top" link text - This allows the user to change the text displayed for the links which return the user to the top of the page on certain page layouts. Defaults to "Back to Top". Leave blank to have no link.
  • ">> more" link text - This allows the user to change the text displayed for the links to the full answer text when teasers are used. Leave blank to have no link.
FAQ - Settings: Weight

This settings page allows the user to adjust the order in which questions are listed on the 'faq' page. If categories are enabled, then the weighting is done on a per-category basis as a FAQ node can appear in more than one category.

To order the questions, the user must select one or more questions in the select box. Multiple questions can be selected by holding down the Ctrl key while clicking on the questions. Once the user has their selection made, they can use the up and down arrows to change their position in the list. It is also possible to order the list by the question creation date descending or ascending using the links provided.

It is also possible to weight FAQ nodes using the weight module. However, any weight settings made on the FAQ weights configuration page will override those set by the "weight" module.

FAQ - User Permissions

There are a number of different permissions you can assign a user with the FAQ module.

  • view faq - users with this permission will be able to view the FAQ nodes and the automatically generated 'faq' page.
  • administer faq - this permission enables users to add, update and delete FAQ nodes, regardless of who the original author was. In Drupal 5.x and later versions of the module, this permission is required for editing the configuration on the settings pages (Administer >> Site Configuration >> Frequently Asked Questions).
  • create faq - allows users to add new FAQ nodes. (Drupal 5.x and later only)
  • edit faq - allows users to edit FAQ nodes, even if they were not the original author. (Drupal 5.x and later only)
  • edit own faq - allows users to edit FAQ nodes that they have created. They will not be able to edit FAQ nodes created by other users. (Drupal 5.x and later only)

File: post an uploaded file

The file module is a simple post module that is used to allow files to be uploaded. This module is used with Organic Groups to allow groups to upload files. Groups frequently require collaborative work products and this allows them to store the file online rather than share it via email.

The file module is a simple module equivalent to the story or page module and is used with the upload module to allow files to be uploaded to a group.

You can:

  • Create a new file post at create content >> file.
  • read about known bugs, and download the latest version on the File CVS page

Flexinode: new content types

The flexinode module allows administrators to create simple new content types. Administrators find it very useful to create new types of content without having to program a new content module, easily combining fields like dates, links, images, etc. to create their custom content types.

When creating a new flexinode, administrators are presented with a flexinode form to create their new content type. Once administrators have created their flexinode they can accept the format or choose to theme the content type to change it's presentation. For users, creating content that is a flexinode is just like adding other content. The flexinode content type will show up alongside all normal content.

A flexinode is built with fields. Fields are separate (meta-) data entities, stored in the database and loaded into the flexinode when presented to the user.
In a theme you can move around these pieces of data, or alter the look of them.
Flexinode comes with a list of fields by default, but it has many more contributed fields in a directory called 'contrib'. In order to activate them, you must copy them (e.g. with FTP) into the root directory of your flexinode module.

You can

  • create a flexinode content type at administer >> content >> content types and select add content type.
  • create content >> add type.
  • administer flexinode at administer >> settings >> flexinode.
  • file issues, read about known bugs, and download the latest version on the Flexinode project page

Generating a list or table of flexinode output

Flexinode can automatically generate a teaser-style list or table of all nodes of a particular flexinode type:

yoursite/flexinode/table/1 (for Table of Flexinode-1)
yoursite/flexinode/search/1 (for Search of Flexinode-1)
yoursite/flexinode/list/1 (for Listings of Flexinode-1 nodes)

FOAF: friends of a friend

FOAF, or Friend of a Friend, refers to a set of social networking standards and tools for online sharing and searching of user profile information. Users will find this useful if they want to make a list of their friends and be able to export or import their list among social networking applications.

The FOAF module allows users to export FOAF documents based on their profile information. The FOAF module can additionally export your buddy list if the buddylist module is enabled. You can download FOAF files in the view tab of a user profile if FOAF is enabled.

The FOAF module will also auto sync profile information between Drupal sites using the distributed authentication of the drupal module -- this also requires the profile module to be enabled and mapping of profile fields to FOAF fields in the admin section.

You can

  • read the FOAF wikipedia explanation.
  • read the FOAFNet specificiation.
  • view user profiles and select a user to see their FOAF file.
  • map user profile fields to the FOAF standard at administer >> settings >> FOAF
  • enable the buddy list module at administrator >> modules.
  • file issues, read about known bugs, and download the latest version on the FOAF project page

Fontsize: make text larger

The font size module allows users to change the font size of the site they are viewing. This is useful for creating 508 accessible websites.

The font size module adds a block which has two buttons, one for a larger text size and one for a smaller or normal text size. Javascript is used to load generic stylesheets that work with most themes and templates.

You can

Form builder: User interface for building forms

The form builder module allows forms to be built in Drupal using a web interface. This makes it possible to build advanced applications without having to be a programmer.

To be completed

Forms: forms for modules

The forms module is a behind the scenes helper module. It is intended as a generic form building module, allowing module developers to include forms to be created by administrators. Modules which depend on it will instruct you to install and enable it. There is no administrative interface for the forms module.

You can:

  • enable the forms module using administer >> modules
  • file issues, read about known bugs, and download the latest version on the Forms project page.

Front: Show group membership and events

The front module displays a customized front page for a user showing their group membership and events. This is useful for social networking and online group site that wants to help users participate in events and engage in group activities. If users are not logged in then a custom text message can be configured in a text field.

The front module requires the event, gjg_event, and organic groups module. You can enable the module to display users groups and events. A message for anonymous users that do not have any events or groups can set.

You can:

  • Set a message for anonymous to see on their front page at administer >> settings >> front.
  • read about known bugs, and download the latest version on the Front CVS page.

Front_page: A different first page for your site

Front_page is a module that allows you to create a static HTML or PHP page to display as an alternative default front page. It provides options to make a different front pages for anonymous users than for authenticated users. Several options are also provided to allow you to theme the page with your currently installed theme, or as a full page, or to redirect to a new page.

The available choices for front pages are as follows

  • themed - uses your current site's theme to dsplay your text/code
  • full - bypasses your sites theme, uses only your specified text/code
  • redirect - redirects you to another page/story/image/etc.

You can

Front_page vs. Static Pages

Static pages can offer you a lot of the features that the front_page module offers, but, it has a LOT more administration to do. For example, to implement these features with static pages, you'd have to:

  1. Check whether the person is logged in.
  2. All the blocks must be set not to display on that particular page.
  3. The user must know some PHP to do #1 and also to do redirects as logic can't be used in HTML.
  4. No way to disable the logo section of the theme for the page (e.g. a user might just want an enlarged version of his logo with a few bullets of text on the main page - no other themable objects), without using another module.

Front_page module makes creation of a different front page for your site very simple, and offers more choice than static pages. Static pages for customized front pages is too involved for new Drupal users (and of course lazy administrators...) and front_page module provides a completely functional alternative.

Gallery: Embed Gallery2 photo album organizer

The gallery module allows the photo album organizer Gallery2 to be seamlessly embedded into your Drupal website. With it you can place photos, videos and any other Gallery2 content seamlessly into a drupal block (in the sidebar for example) and/or node content.

The gallery module performs the following:

  • Gallery 2 Images, Videos, Albums (displays the Album Highlight Image) can be displayed in drupal content (node, story, page, ...)
  • Random, Recent, Daily, Weekly, Popular Images, Albums can be displayed as image blocks in a drupal block (eg sidebar)
  • Management of Gallery2 users, including automatic synchronization of Gallery2 and drupal users and roles.
  • Automatic logging in of drupal Users into Gallery2
  • Support for g2image, g2filter, grid block, and more ...

Detailed documentation can be found on the Gallery module wiki.

File issues, read about known bugs, and download the latest version on the Gallery project page.

Gallery:Embed Gallery 2 in your Drupal site

The gallery module in and of itself does not create a gallery. Rather it is a bridge between Drupal and Gallery 2. You need both of these programs installed in order to use the module. Because Gallery 2 and Drupal are continuing to be updated, it is important that you get the version of the module that matches both Drupal and Gallery:

Link to 4.6/2.1
Link to 4.6/2.0
Link to 4.7/2.0
Link to 4.7/2.1

To use, first install Drupal and Gallery 2. Make sure you do not install G2 into /gallery as that will conflict with the module. Once you have Drupal and Gallery 2 working on their own, install the module following the instructions in the install.txt file that comes with it.

Since Gallery 2 is a seperate product from Drupal, there are a variety of places to get help.

* For problems with the module itself, check out the gallery module issue queue. It's better to put support requests there than in the Drupal forums if you want people using the module to see it.

* For problems with Gallery 2, use Gallery's forums

* Integration help can also be found at the Gallery Embedded wiki

GMap: Add Google Maps to your site

The GMap module is both an API and a filter to easily embed Google Maps into your site.

Features overview

Drupal 4.7

gmap.module

  • Macro builder
  • Map generation and display API
  • Macro Filter
  • GeoRSS feed display
  • Various utility routines

gmap_location.module

  • Store a user's location and display it on a map
  • Store a location for each node and display it on a map
  • Display a map of users
  • Display a map of nodes
  • Display a block with a map of a node's location
  • Display a block with a map of a node author's location (incomplete)

Drupal 5

gmap.module

  • Map generation and display API
  • Macro filter
  • GeoRSS feed display
  • Various utility routines

gmap_location.module

  • Store a user's location (SCHEDULED FOR REMOVAL -- will move to location.module)
  • Display a map of users
  • Display a map of nodes
  • Display a block with a map of a node's location
  • Display a block with a map of a node author's location

gmap_macro_builder.module

  • Macro builder

gmap_views.module

  • Views display plugin to render a view as a map

GMap 5.x API

The GMap 5.x API is significantly changed from previous releases. Maps are now proper form elements (though they can still be rendered individually) and the entire system is extensible using a system of "handlers" and "events."

Many of the features that previously required custom javascript were integrated into the core system, and can be activated by switching on "behavior flags," another new 5.x feature. Behavior flags can be toggled in code or by a macro, and the system will use the default value as set in the GMap settings page for anything not directly specified in the map code / macro.

Behavior flags

Behavior flags can be used to change a map's behavior in predefined ways.

locpick

Enable location chooser functionality on a map.

Implementor
locpick.js
Default
OFF
nodrag

Inhibit dragging the map.
Having nodrag will force nokeyboard ON.

Implementor
gmap.js
Default
OFF
nokeyboard

Disable keyboard control of the map.
As keyboard control can only be enabled on at most one map per page, it must be explicitly turned on.

Implementor
gmap.js
Default
ON

(TODO: Document all flags in GMap core)

Event list
Core events

"Core events" are always loaded when the GMap system is loaded.

init

System is initializing. Plugins that need to execute initialization code can bind this event.

Bind
Execute initialization code.
Change
Forbidden.
Event owner
gmap.js
zoom

Map zooming.

Bind
Listen for map zoom. New zoom is in vars.zoom.
Change
Zoom map. Set vars.zoom to new value.
Event owner
gmap.js

(TODO)
gmap.js: move, maptypechange, widthchange, heightchange, controltypechange
marker: addmarker, delmarker, clearmarkers, clickmarker, (mouseovermarker, mouseoutmarker, dblclickmarker)
shape: addshape, delshape, clearshapes
macro.js: buildmacro
icon.js: iconsready

Map settings

The map settings are stored in the #settings key of the map element. The entire settings array is passed to the javascript side and is available as this.vars inside of a handler.

  • width
    CSS width for the map. Use % or px.
  • height
    CSS height for the map. Use % or px.
  • zoom
    Map zoom level. Canonicalized to integer.
  • controltype
    Type of control to put in the top left of the map.
    Possible values: Small, Large, None
  • align
    Alignment of the map.
    Possible values: Left, Right, Center
  • latlong
    Backwards compatiblity for latlon.
  • latlon
    Latitude and longitude combined. Will be converted to latitude / longitude if nonexistent.
  • latitude
    Canonical form of latitude.
  • longitude
    Canonical form of longitude
  • maptype
    Which type to draw.
    Possible values:
    Map: G_NORMAL_MAP
    Hybrid: G_HYBRID_MAP
    Satellite: G_SATELLITE_MAP
  • line_colors
    Colors to draw lines.
    Deprecated -- Will be removed shortly
  • behavior
    Array of behavior flags.

(todo: search for other things honored by GMap core and document them)

Google Analytics: free advanced website statistics

Google Analytics is the free statistics package based on the excellent Urchin system.

For webmasters to make use of Google Analytics you need to insert some Javascript containing the unique websites user account ID in to the header of every page.

This could be achieved by editing the Drupal template page.tpl.php. However, this would then mean you cannot run multiple sites from the same base template.

This module allows you to enter your Google Analytics user account ID in to the module settings page located at ?q=admin/settings/googleanalytics. It will then automatically add the required Javascript to the header of every page generated by Drupal.

The Drupal Google Analytics module is currently available from the module downloads.

Development sponsored by Ixis IT.

HTMLarea

Allows users to write custom html and formating of text using a graphical user interface.

It has the following tabs: htmlarea, toolbar, font names, font size, format block, plugins, custom js.

You can

Image Assist

This is a module that assists the user in the act of placing inline images in their posts. It adds an insert picture button to the interface for each text field for convenience. When a user clicks on this button, they are presented with the option to use a previously uploaded image imported by Image Assist or to upload a new one. When they choose a previously imported image, the module presents several fields giving one size options of a thumbnail or a preview, alignment options of left, right, center, or none, whether the image should be a link and what it should link to, an insert mode, and the optional Title and Description fields. Here the user can choose to insert the image, cancel the action or just start over as if they had just pushed the button again. If the user elects to upload a new image, the usual image add interface is presented.

Note that this is not the only means of inserting messages into your posts. It is just a means of making it easier by inserting the code for you after a simple interface gathers the necessary information.

Image: galleries of images

The image module is used to create and administer images for your site. Each image is stored as a node, with thumbnails of the original generated automatically. There are two default thumbnail sizes, thumbnail and preview. The thumbnail size is shown as the preview for image posts and when browsing image galleries. The preview is the default size when first displaying an image node.

Image administration allows the image directory and the image sizes to be set.

Two modules packaged with the image module let you do more with your images:

The Image gallery module lets you organize and display images in galleries. The list tab allows users to edit existing image gallery names, descriptions, parents and relative position, known as a weight. The add galleries tab allows you to create a new image gallery defining name, description, parent and weight.

The Image attach module allows an image to be attached to other nodes: pages, stories, or custom content types. A thumbnail of the image is displayed at the top of the node in both teaser and full view, and is linked to the node for the actual image.

You can

  • configure image sizes and file directories at administer >> settings >> image.
  • use the image assist module to upload and insert images into posts.
  • file issues, read about known bugs, and download the latest version on the Image project page.
  • Using IMG tags with image module

    A common question is how to display images. In addition to other modules such as img_assist, you can also use img tag.

    With clean urls

    <img src="http://www.example.org/image/view/##/preview" />
    <img src="http://www.example.org/image/view/##/thumbnail" />

    Without clean urls

    <img src="http://www.example.org/?q=image/view/##/preview" />
    <img src="http://www.example.org/?q=image/view/##/thumbnail" />

    If you go to admin >> settings >> image and look at the sizes, you will see a default name, preview and thumbnail. You can add and label other sizes as you choose.

    If you want to link to your original image, the syntaxt is the same with the tag of _orginal.
    With clean urls'

    <img src="http://www.example.org/image/view/##/_original" />

    without
    <img src="http://www.example.org/?q=image/view/##/_original" />

    So, to break it down, sitename / image / view / image ID# / label will get you to an image suitable for use in an <img src=""> tag. If you add additional sizes to the your image module settings, you can link to them by their label names as well.

    More information on how to use img src can be found on the web.

IMCE: Image uploader and browser

IMCE is an image/file uploader and browser that supports personal directories and quota. It works with Drupal 4.7 or above versions.

IMCE, initially, was implemented for easy uploading images and adding them to TinyMCE. In the development process many features have been added.

It now supports the other most used wysiwyg editor FCKeditor. In addition to these two editors, with the aid of newly implemented Javacript API, IMCE has also support for inline image/file insertion into plain textareas. Working examples can be seen at the external demo page.

Here is the list of features:

4.7 features

  • no module dependency.
  • uploading .jpg, .png, and .gif images and previewing.
  • option to allow uploading non-image file types.
  • support for private downloads.
  • limits for file size per upload, total directory size(quota), and image dimensions.
  • option to use personal folders or a shared folder for users.
  • file sorting according to file name, file size or date.
  • highlighting of active files.
  • built-in support for TinyMCE

New features in 5.0

  • automatic thumbnail creation.
  • custom resizing.
  • role based settings.
  • custom settings for user #1.
  • file filtering according to type and extension.
  • keyboard shurtcuts (UP, DOWN, DELETE, INSERT).
  • javascript API that allows custom usage of the browser, which makes IMCE suitable for any wysiwyg editor.
  • built-in support for FCKeditor.
  • built-in support for inline image/file insertion into plain textareas.
  • administrator ability to switch to any user's settings.
  • administration of user files in user/x/imce pages.
  • sub-directory navigation that allows management of multiple folders.
  • absolute and relative path switching.

Downloading and Installation

IMCE can be obtained from the project page. You should choose the appropriate release for your drupal installation.

After downloading the package you should extract it to the modules directory. Like any other module, IMCE can be enabled/disabled in modules page.

After enabling the module you can assign access permissions to roles in administer > access control page. The next step is to configure how IMCE will function in your site by changing the settings at administer > settings > imce page.

If IMCE is configured properly;

  • tinyMCE users will be able to open IMCE by clicking the browse button in image or link dialog.
  • FCKeditor users will be able to open IMCE by clicking the Browse Server button in image or link dialog.
  • Plain textarea users will be able to open imce by clicking the appropriate text link under the textareas that are specified in settings.
  • Images and other files can be sent to related applications from the IMCE interface by just clicking the add link or the file itself while previewing.
Troubleshooting

Browse button is missing or not functioning properly

  • Since IMCE inserts the required javascript file into the closure of your theme, you must be sure that your theme supports closure and displays it correctly.
    In phptemplate themes like bluemarine or garland the closure variable is $closure that is usually printed at the end of the page(page.tpl.php). In case of a missing closure try printing theme('closure') or $closure just before the closing </body> tag.
  • There may be a conflict with another javascript code. Check if the browser throws any javascript errors. Report this to forums or issue queue.

Directory related errors

  • IMCE directory paths must be relative to drupal's file system path that is usually files. Therefore, to use the folder files/foo just specify foo.
  • If you are using a manually(ftp etc.) created folder, PHP may not have read/write access to that folder. Try changing the folder permissions to chmod 0777 to allow PHP has full access to it.

Images disappear (on preview or after node submission)

  • This is probably because of your default input format not allowing <img> tag. Select Full HTML as default or add <img> tag to allowed html tags for your default input format.

If you have another issue with IMCE, first of all, you should seek for a solution in the forum page and issue page. In case you can't find any answer and think it is a new issue, post it to issue queue providing detailed information.

Javascript API for custom usage of IMCE

IMCE introduces two javascript hooks for custom usage. One of them is hookImceFinish function and the other is global hookImceUrl variable. In order to use these hooks one must open IMCE browser by specifying a proper window name with window.open method. That window name plus hook names will be the custom function and the custom URL.

Here is a sample code for opening IMCE with window.open:

window.open('imce/browse', 'windowName', 'width=640, height=480');

For this instance of imce there should be a previously declared function named as windowNameImceFinish and optionally a variable named as windowNameImceUrl. If the function exists, IMCE will call it when the user clicks "add" link. windowNameImceUrl will be used for highlighting if it matches any url in the file list.

hookImceFinish

This function is called with five parameters:

  1. file path - URL of the selected file in the file list
  2. image width - width of the selected image. (0 if it's not an image)
  3. image height - height of the selected image.
  4. file size - formatted file size (ex: 35.2 KB, 45 bytes, etc).
  5. IMCE window - reference to the currently active IMCE window. it can be used for closing the pop-up.

so, one can implement a function like this:

function windowNameImceFinish(path, w, h, s, imceWin) {
  imceWin.close();
  alert('selected file path='+ path +', width='+ w +', height='+ h +', filesize='+s);
}

hookImceUrl

This optional global variable is used for highlighting of the matching url in the file list. In other words, if it's value matches any of the paths in the list, that file is highlighted.

One may change the value of this variable just before opening the IMCE pop-up.

windowNameImceUrl = '/files/images/img.gif';

ImportExportAPI

The importexportapi documentation is included in the module download. Look in the docs/developer/topics directory within the module download for instructions on how to use it.

Or you can find the documentation in CVS.

Insert view: embed a view into a node

The Insert view module allow you to embed the result of a particular view into a node by the mean of a tag.

In order to use it, you have to :

  • use the Views module,
  • enable 'insert view filter' in an input filter and use this input filter anytime you need the Insert view functionnalities (you may have more than one input filter using 'insert view filter').

The syntax can have 3 forms :

  1. [view:myview]
  2. [view:myview=maxitem]
  3. [view:myview=maxitem=firstargument]

The first form ([view:myview]) will insert the result of the view myview in place of the tag. Be aware that it always insert the page view even if the view does not provide a page view.

The second form ([view:myview=maxitem]) will do the same except that the results won't never exceed maxitem records. It's only a limiter, it does not provide a pager. If there are more elements to show, users won't be able to see them.

The third form ([view:myview=maxitem=firstargument]) will do what the first two do but you can restrict results using firstargument.

At the time of writing, these tags are not expanded when you use the printable version provided by the book module.

Using an argument with Insert view

The [view:myview=maxitem=firstargument] form can be very useful. For example, if you have a site with the following structure (every node except Home is a book page) :

  • Home
    • Transport
      • Cars
      • Taxi
      • Train
      • Airplane
      • Web sites
    • Culture
      • Museum
      • Library
      • Theatre
      • Web sites

Web sites nodes may seem redundant but they only show web sites about transport or culture. If you had no argument, you should create a view for each one (for example view_culture_sites and view_transport_sites).

One might say you wouldn't need the Insert view module as the Views module already allows you to create a page view with argument. That's true, but you won't be able to attach it to the book structure.

In the example, you need a node that can store URLs (either a new CCK node or a Jan's node, or whatever you want) and a taxonomy attached to it.

You then create your view with the Views module and define an argument on Taxonomy Term ID (or Taxonomy Term name).

In every Web sites node you insert the following tags :

  • [view:websites=100=transport] for the transport web sites page,
  • [view:websites=100=culture] for the culture web sites page.

Internationalization: Building multilingual sites

This is a complete manual and step-by-step guide to building multilingual websites with Drupal and the internationalization package (i18n).

The internationalization package is a collection of modules that adds, besides the basic interface translation provided by the locale module, several features that allow for the creation of comprehensive multilingual sites with content and categories in multiple languages.

Basic site set up

Follow the steps below to set up a basic multilingual site with the most common options selected.

Installing and Enabling the i18n Package
  • Download and install the package
  • Enable the following modules: locale, internationalization, and translation
  • Set up your basic modules
  • Define your languages

These are the basic modules you need to enable to have a multilingual site:

Modules
Figure 1: Modules in i18n package

Once the modules are enabled, visit the module settings page and go to Administer>Site Configuration>Multilingual system

Multilingual settings
Figure 2: Basic multilingual settings

When the internationalization module is enabled, there are two additional options in the language management page: a text field to name the native language name and a check box to mark the language as LTR (left to right). This latter option has no further effect unless it is handled properly by the theme.

Extended language options
Figure 3: Language set up

Enabling Multilingual Content
  • Enable the language block
  • Enable multilingual support for specific content types

Now you have to enable the content types you want to be in multiple languages. This can be done on Administer>Content>Content types for each content type. A new option to enable/disable multilingual support will appear at the bottom of the settings page.

Content type settings
Figure 4: Enable content types to be multilingual

New! There's a new option for content types called extended language support. It will allow you to set all defined languages - not only enabled ones - for selected content types. With this feature enabled, it is possible to have a site in a few languages, which will be the ones enabled, showing in the languages block and used for localization, while allowing a larger number of languages for some content types.

Experimental modules and features

There are a number of new modules, grouped as i18n - Experimental, providing some new features. These are new developments with some of them proof of concept that will be extended in the future and some of them only needing some further testing before being considered production quality.

i18n Taxonomy

This allows for the translation of taxonomy terms using the localization system for chosen vocabularies. This is a temporary solution for term translation. Currently it supports node edit forms, node links, and views filters and fields. Other types of taxonomy terms cannot be translated.

Synchronization

This keeps taxonomy term translations synchronized for all nodes in a translation set. For chosen vocabularies, it automatically updates all nodes that are part of the translation of a node that has been edited or created. This module is the first step of what may be an additional synchronization layer for node translations.

Strings

This is a proof of concept implementation of 'configurable string translation' as outlined here: http://groups.drupal.org/node/1880
It provides a basic interface for string translation and a 'tt' function intended to be used by other modules to have web configurable strings tracked and translated on the fly.

i18n - Content Types

This provides translation for content types like names, descriptions, and help texts using the Strings module.

Translatable Text

This is a proof of concept implementation of a translatable text field for CCK. It requires a small one line patch in content.module to disable caching.

Feature overview

The internationalization package is a set of modules that add multilingual content capabilities to Drupal. In the collection, each module addresses a specific functionality. The main features provided by these modules are the following:

General Features
  • Language selection block to switch the language
  • Browser language detection
  • Multilingual variables
Multilingual Content
  • A new language field can be added to chosen content types (nodes).
  • Only content in the selected language is displayed for each page view.
  • Translations can be defined between nodes, which allows for linking a node to translations in different languages.
  • Several selection modes are available for content, meaning you can have node lists for only the current language, for current and default languages, all languages, etc.
  • Basic work flow management for translations.
  • New! Views support.
Multilingual Taxonomy
  • Language can be set up for vocabularies or terms
  • Both multilingual vocabularies with terms in multiple languages and and single language vocabularies can be created
  • Translations can be defined between terms in different languages
  • New! Taxonomy term synchronization can be defined per vocabulary for translations
  • New! Taxonomy term translation can be done using localization for selected vocabularies
Translatable Profile Fields
  • Configurable profile field created with the profile module can have different names and descriptions in every language
  • Options for selection lists can have a different display name in every language

Internationalization 4.7: Multilingual content

This is a collection of modules to add multi-lingual capabilities to Drupal sites.

Provides content translation -nodes and taxonomy-, interface translation for anonymous users -with the locale module- and browser language detection. Includes a block for language selection and manages translation relationships for nodes and taxonomy terms.

http://drupal.org/project/i18n

Getting the whole thing to work

It took me some time to figure it all out, but I have everything working now:
- installed drupal 4.7.3
- installed i18n, 4.7
- installed nice_menus

Activate locale, path, page and all i18n modules. Import languages through administer -> localisation (these are translations of the menu's).
First you have to define, in the administer > settings > content-types pages, which content types you want to have in multiple languages. Then, a 'language' field and a 'translations' tab - provided you have also enabled the 'translation' module, included in the same package - will show up when creating or editing that node types.

Use the translation block, not the languate switcher block (found in administer -> blocks).

MAKE SURE YOU SET A LANGUAGE FOR EVERY PAGE/ARTICLE YOU CREATE!

Ok, I'll explain the rest with an example:
Let's say you're creating a page "hello" and set the language to English. Go to URL path settings (on the page where you edit "hello") and set the alternate path to "en/hello" (without the quotes, obviously).
Click translation and translate. In this example, we are translating to Dutch (nl). LEAVE THE MENU SETTINGS ALONE. Again, go to URL path settings and enter "nl/hello".

Ok, that being done, we now edit the primary links. So click "edit primary links" (top right if you're still working in the default skin).
Go to the menu item "hello". Click edit. Set the Path to "hello" (NOT "en/hello" and NOT "nl/hello", just "hello": the language things in the background will add the en/ or nl/ in front of the hello depending on which language you've selected in the language switcher block).
Now, depending on which language you have selected, you should be lead to the correct page.

However, you still see "hello" in your navigation bar, regardless if you're working in english or dutch. Of course we want there to be "hallo" (dutch translation) in the menu if we're working in dutch. This you do in the administer->localization menu. There you go to the tab "manage strings", enter hello as the string to search for and search. You will then get one or a few items, pick the one saying just "hello". If nl is in strikethrough, then it hasn't been translated yet. Click edit and translate.
Now, when Dutch is selected, the menu-item "hello" will be shown translated as "hallo".

We're almost there. Everything should be working fine, except if you have expandable items: they don't expand any more. Very annoying. This is why we have the "nice_menus" module: go to administer->settings->nice_menus and make sure the number of nice_menus is set to at least 1. Then go to administer->blocks, where you will find a nice_menu. Click configure, give it a nice name i.e. "navigator", set source menu tree to "Navigation". Pick a menu style. Save. Enable it somewhere and disable your old "Navigation" menu.
Your nice_menu navigator WILL expand menu-items where the standard navigation menu did not. Plus nice_menus just looks a lot better (imo).

Now, if you have the page "hello" in English but not yet translated to Dutch, clicking "hello" when in Dutch will lead to nl/hello, which isn't configured yet. So you'll get a 404 page not found.
To show the English instead of nothing when you haven't got the translation yet, go to administer->url aliases. You will there see all the aliases you've already set. Look for the one "en/hello" and note to which node it goes. Then click "add alias" and add as existing system path the same node that "en/hello" refers to. Fill in "nl/hello" in the second field, as the alternate path.
You'll have to undo this before creating the translation, because you will then let "nl/hello" refer to the node with the Dutch text (as explained above, by just setting nl/hello in URL path settings on the Dutch page).

OK, this should be it (it worked for me).

PS: if you're working with sub-pages/sub-stories (whatever, if it's sub), there is no problem using a path like i.e. en/hello/world and another one like en/hello/universe when "world" and "universe" are your two sub-things from "hello".

PPS: I've only been using Drupal for a couple of days, just trying it out really, and I must say it looks great. BUT translations are somewhat a pain in the ass to do all the settings and I think many more sites could have an advantage in using Drupal if only it supported multiple languages better. I would even go as far as to say this should be core functionality (imo).

PPPS: in my explanation, I've used (and copied) some text found spread over the Drupal website. I didn't take the time to mention the names of the people who's contributions I've used to figure it out (because there were plenty), but thanks guys, I'd still be looking if you hadn't given me the pieces to put together.

I18n how to : common questions, tasks and solutions
About block visibility

With this module enabled, the paths for block visibility are a bit different. You have to use language prefixes for them.

I.e.

<mypath>

will be now:

*/<mypath> -- for all languages
en/<mypath> -- only for page in English
es/<mypath> -- only for page in Spanish
....

Note: <front> doesn't work with '*' so you'll need to define it for each language and the actual front page
en/<node>
es/<node>
...

Custom home page for each language

Set up your home page in administer>settings, i.e. 'home'
Define a path alias for each language page. I.e. 'es/home', 'en/home'....

Different primary/secondary links for each language
  1. Enable language dependent variables. The one needed for this is 'menu_primary_menu', and 'menu_secondary_menu' for secondary links.
  2. Create as many menus as languages in administer>menus.
  3. Set up the menu to be used for each language in administer>settings>menus. You will need to switch languages while on this page.
i18n traslations bar in 4.7 - only flags

In 4.7 version of i18n translation block is not an easy way to setup only flag without anything else for switching lagnuages on page
Well I have solved this thing for my purose and after that I found that there was discusion but is too long and not clear so this is how to make it in easy way (I hope):

In the file of page tepmlate (page.tpl.php) U have to add to the right place [where it should be shown] snippet:
<div id="menu"><?php echo theme('item_list', i18n_get_links($_GET['q']));?> </div>

To the file of theme template (template.php) override function theme_i18n_link, which is responsible on layout of links printed by translations block, beside 'theme' in name of function U have to write name of your theme (name of directory where all files are stored) then if this name is mytheme the function will be like this:

function mytheme_i18n_link($text, $target, $lang, $separator='&nbsp;'){
  $output = '<span class="i18n-link">';
  $attributes = ($lang == i18n_get_lang()) ? array('class' => 'active') : NULL;
  $output .= l(theme('i18n_language_icon', $lang), $target, $attributes, NULL, NULL, FALSE, TRUE);
  $output .= '</span>';
  return $output;
}

In style file [normaly style.css included in headerplaced in the same directory as other files] or directly in header of page template file is styled look of the block:

#menu {
  /* float: right; */
  margin-top: 0.9em;
}
#menu li{
  list-style: none inside none;
  display:inline;
  margin-left: 0.5em;
}

where settings of #menu moves with all the block and #menu li changes settings of flag itself (same for all flags) [eg removing line display:inline; will make every flag in next line]

Slovenská verzia príspevku

Installation

For a fresh install

  1. Create folder 'modules/i18n', and copy all the modules files, keeping directory structure, to this folder.
  2. Just enable the module and the installation system will take care of everything

To update from 4.6 version

  1. Same as above
  2. Run Drupal upgrade script 'update.php'.

Note: no need to apply patches anymore

More language icons

A small collection of language icons is provided with the module. There are some sites from where you can download more language icons (flags)

Just remember to rename file names to match language codes and to set the right settings in administer>settings>i18n (Language icons settings)

Language dependent variables

There are a number of variables through all Drupal that hold texts which need to be translated and also many other things -like logos, primary links, etc..- that you may want to have different for every language.

This module supports language dependent variables, which are normal variables that will be set up to have a different vale for each language.

To enable this feature, the list of variables to be made language dependent must be defined in the configuration file settings.php. You need to open a text editor and add something like this to that file:


$conf['i18n_variables'] = array(
    // Site configuration
    'site_name',
    'site_slogan',
    'site_mission',
    'site_footer',
    'anonymous',
     // Node help
    'blog_help',
    'story_help',
    // User configuration
    'user_registration_help',
    'user_mail_welcome_subject',
    'user_mail_welcome_body',
    'user_mail_approval_subject',
    'user_mail_approval_body',
    'user_mail_pass_subject',
    'user_mail_pass_body',
    // Theme settings: you may want to use different logos for each language
    'theme_settings',
    // These are for primary and secondary links
    'menu_primary_menu',
    'menu_secondary_menu',  
);

These are only the suggested and more common ones, but you can add as many Drupal variables as you want to the array. If you dont know the variable name, you'll need to look at the code or at the 'variable' table in the database and guess which one it is, then add it to the list in your settings file.

You need to redefine these variables for the first time for every language, as previous values are lost and they return to defaults -they will be back if you disable i18n-. To define this values, you have to switch to every language when you are in the administration page where you set that variable.

Note about theme settings: If you want to have language-specific settings for an individual theme, add to your array something like:

'theme_themename_settings'

(Obviously replacing themename with the name of the theme you're working with)
Module blocks: Language switcher and Translations

There are two main blocks providing links to other languages. They can be enabled in the administer > blocks page.
Both are named 'Languages' and look the same, displaying a collection of flags and language names, but the behaviour is quite different.

Language switcher

This one is provided by i18n.module. It is a simple language switcher and knows nothing about translations.

Translations

This one is provided by translation.module. It is a language switcher that also knows about translation relationships, providing the right link to the translation page when available.

You have to enable one of them depending on the behaviour you need. Having both of them enabled may be confusing so better choose only one.

Multilingual categories -taxonomy-

A 'language' field will show up when editing vocabularies and terms. You can create vocabularies and terms with or without language.

The behaviour is like this:

  • If you set language for a vocabulary/term, that term will just show up for pages in that language
  • If you set language for a vocabulary, all the terms in that vocabulary will be assigned that language and you wont be allowed to change it for single terms.
  • When editing nodes, if you change the language for a node, you have to click on 'Preview' to have the right vocabularies/terms for that language. Otherwise, the language/taxonomy data for that node could be inconsistent.

With the translation.module enabled, you can also define translation relationships for vocabulary terms. Click on the 'translation' tab on categories administration pages.

Note: Before assigning translations, the terms *must* have a language assigned to them. Otherwise the behaviour will be undefined.

Multilingual content -nodes-

Note that there is a much more detailed page about this at Getting the whole thing to work.

Enable the Translation module, then administer > access control and enable the "translate nodes" permission. Next define, in the administer > settings > content-types pages, which content types you want to have in multiple languages. This will now give a 'language' field and a 'translations' tab when creating or editing the node type(s).

When you navigate the site using multiple languages, the pages will just show the content for the chosen language plus the ones that haven't a defined language. When editing a node, you must click on 'Preview' after changing language for the right vocabularies and terms to be shown.

The multi language support is expected to work for all node types, and node listings. So far, I have not found incompatibilities with any other content type module. Please, file an issue against the project page is you find any. And yes, flexinode works with multiple languages.

Managing multilingual content

Once you have set up the content types you want to have in multiple languages, a new language field will be added to each one. Then content will appear in the language you are browsing in. Additionally, the translation module allows for translations between nodes.

Translations are created as additional nodes related to the original node. A collection of nodes that are translations of the same content are grouped together as a translation set. When displaying content in a given language, links to different translations will be provided by the languages block and optionally as node links below the content.

A new language field is available below the content title. It will default to the current language, but another can be selected.

Submit content form
Figure 1 : Creating content with language.

Once you have created an original story in English, a new Translation tab becomes available. Once you've clicked on this tab, you'll find options to create a translation in any of your active languages or select an existing node that can be set as this one's translation.
Node translation tab
Figure 2 : Translation tab for content.

Now click on Create translation for Spanish and a new node submission form with some populated fields will be displayed. Note that the taxonomy terms for the original content will be automatically translated if such a translation is available. Regarding term translation, see Multilingual categories.
Creating a translation
Figure 2 : Creating a translation.

Multilingual categories

With the internationalization module, it will be possible to set a language for vocabularies or terms. In addition, when the translation module is enabled, translations can be created for taxonomy terms.

When a language is set for a vocabulary or term, only ones that match the the current language will appear in taxonomy listings and when creating and editing nodes. If the language is set for a vocabulary, all the terms in that vocabulary will be automatically set to that language.

Creating terms with language is simple with the internationalization package. For example, we created the vocabulary 'colours,' which has no language assigned to it, thus allowing terms in multiple languages within the same vocabulary.
Creating multilingual terms
Figure 1: Creating a taxonomy term with English language.

Then we created the terms 'red', 'green', and 'blue' in English and 'rojo', 'verde', and 'azul' in Spanish. If we go to the 'translation' tab and click on add translation, we can choose a term for each language that will be part of this translation.
Translating terms
Figure 2: Creating term translations.

If we repeat this process for the three terms, we can see all the translations in the Translation tab
Term translation tab
Figure 3: Term translations.

Once we have terms and translations, only the correct terms for each language will show up while browsing the site. When we are editing a node, the terms for that node's language will be available.

Multilingual variables

Some text and settings are stored in Drupal as variables, and some site wide ones like 'site name' and 'site slogan,' along with module specific ones, can be edited through the administration pages. The Internationalization module makes it so these variables can be translatable.

To enable these variables to be translatable we need to identify the low level names Drupal uses for them. This can be done looking at the 'variable' table in the database or searching through the code. However, we'll show you how to translate the more common setting below.

Once you have identified the variables you want to be translated, they need to be added in the settings file as follows:

/**
 * Multilingual settings
 * 
 * This is a collection of variables that can be set up for each language when i18n enabled.
 * These are the basic ones for Drupal core, but you can add your own here.
 */
$conf['i18n_variables'] = array(
  'site_name',
  'site_slogan',
  'site_mission',
  'site_footer',
  'anonymous',
  'menu_primary_menu',
  'menu_secondary_menu',
);

The configuration file is usually under /sites/default/settings.php or another subfolder depending on the settings of your site. Also there may be more than one if you are using a multi-site set up.

Once you have the correct settings added to your configuration file, they'll be marked as 'multilingual variable' when you go to the corresponding administration pages. You must switch the site language while in the administration pages to set the value for each language

Multilingual variablesFigure 1 - Multilingual settings

Paths and block visibility

Once the internationalization module is enabled, a language prefix is added to all paths and links. This path defines the interface language - localization - and the default language for content.

The language prefix is removed at the beginning of the request and is added again for internal links to be displayed. Thus, all paths will work the same as before for all the modules enabled.

Path Aliasing

Path aliases can be created as usual without using path prefixes and the system will work just fine. The only limitation is that path aliases must be different for each language. It is also possible to define path aliases using language prefixes. The path system will still work, but this can cause some issues with path handling.

en/mypage --> en/node/1
es/mypage --> es/node/2

Note that language prefix must be in both source and destination. Otherwise the reverse alias, linking a Drupal path with its alias before displaying it, won't work; the behaviour will be undefined. There are some known issues using this method, particularly with menu visibility and active links that may not be marked as such. Thus it is advised to use only paths without language prefixes and to use different path alias for different languages.
Block Visibility

When using paths for block visibility, language prefix may be used to restrict visibility to specific languages. For example,
en/* will mean all English pages
en/node/* means all node pages when browsing the site in English
Note: <front> can not be combined with languages to specify path, thus the path for the front page will depend on the actual site front page setting (i.e. 'en/node' or 'es/node' if the front page is 'node').

Site Front Page

There are two methods to specify different front pages per language:

  • The first one is to add 'site_frontpage' to language dependent variables, and then define a different path for each language.
  • The other option is to use the same path for all languages, but use different aliases like
en/home --> en/node/1
es/home --> es/node/2

* Note than when using a node listing page as the front page - like the default 'node' - this is not necessary, as the system will select only the right nodes for each language.

Some sample sites

This list doesn't intend to be a full listing of Drupal sites using the i18n module, but rather a collection of some sample sites.

Fight Hunger, an initiative of the UN World Food Programme. Includes some bi-directional languages and theme.
Canadian Treatment Action Council
Arco Latino, an organization reaching across several European cities
Industrial Workers of the World, a union's site with 16 languages
DTRAC, the Disaster Tracking Recovery Assistance Center, in English and Thai
Khmer Software Initiative in English and Khmer
Nonviolent Radical Party Transnational and Transparty
THREES ANNA, a writer and theater directors' website
Reyero.net, the website of the module's author

If you have some other good examples just post a comment here and I'll add it to the list.

Technical overview

This is a technical overview of how the i18n module works, how it integrates with Drupal, and some tips to take advantage of the module's API for advanced customizations.

Data Model

This module adds a minimum amount of data to Drupal's database, linked to objects that may be translatable, like nodes and taxonomy terms.
The two pieces of information we need for the system to work are

  • Language property: This is the 'language' text field that keeps language code for some objects, including nodes, taxonomy vocabularies, taxonomy terms, and menu items.
  • Translation ID: This is the 'trid' field that provides the relationship between multiple objects that are translations of each other.

[Table explanation and link to data model picture]

Drupal Integration

As the module has evolved from a set of developer's tools to an end user's pluggable module, it has avoided as much as possible to patch Drupal core, relying instead on several powerful mechanisms built into Drupal.
Path rewriting
...
Query rewriting
...
Node system and taxonomy hooks
....
Forms API
...

i18n API

Some API functions used by the modules internally can also be used to take advantage of some of the features or to build custom pages, blocks, or theme snippets.

Views support

The internationalization package has added some support for the views module with the new i18n - views module. It adds the following fields and filters:

Fields
  • Language: This displays node language.
Filters
  • Language: A simple language filter that allows you to select one or more enabled languages.
  • Language (extended): A language filter that allows you to select from all defined languages.
  • Selection: This filter allows you to set a language selection mode for views results. It allows you to define which kind of language conditions will be applied (current language, all languages, etc...).

Integration with other contributed modules

Here are some notes about integration with other contributed modules. This about configuration and integration with other modules to use i18n features, but not about compatibility problems. If you have any issue about compatibility with other modules, please file a feature request on the issue tracker.

Pathauto

AFAIK Pathauto module plays nice with i18n module. In addition to that, there are some features added into i18n module to provide different path templates for nodes depending on language.

Note: this won't work for bulk pathauto operations

  1. Add pathauto variables to language dependent variables in settings file, like:
  2. $conf['i18n_variables'] = array(
    ..............
    'pathauto_node_pattern', // General pattern for nodes
    'pathauto_node_blog_pattern', // Pattern for blog nodes
    'pathauto_node_story_pattern' // Pattern for story nodes
    ............
    );
  3. Set up different patterns for each node type/language on pathauto settings page (Like other variables, switch interface to set values for each language...)
  4. Play!
TinyMCE

it works fine, but when setting up the paths for the visual editor to show up, the language prefix needs to be taken into account

Use
*/node/*
instead of /node/* that won't work

Event, Image, CCK...

Images, event dates and CCK fields are copied over when creating a translation of a node. It will also work for most of other content types.
For the case of images, you'll need to re-create all the translations if you update the original image file.

Theming features. RTL language support.

One of the new features of this module is some support for Right-To-Left languages like Arabic or Hebrew that needs also to be implemented on the theme side.

In the languages administration tab, there's now a new checkbox to mark languages as 'RTL' -Right to Left-. However it is at the theme stage when this should be taken into account to switch the text direction and maybe also the page layout.

Figure 3: Language set up
To make a theme i18n compatible for RTL support, one possibility is to simply switch stylesheets depending on the language settings. Here's some code example that may be added to your theme to add the right stylesheet.

if (module_invoke('i18n', 'language_rtl') { 
  drupal_add_css(path_to_theme() .'/my-rtl-stylesheet.css', 'theme');
} else {
  drupal_add_css(path_to_theme() .'/my-ltr-stylesheet.css', 'theme');
}

Note: There are a few themes that support bidirectional text using a hardcoded list of languages. Though this will work for some predefined language list that depends on the theme, i18n language settings will be ignored.

Interwiki: wiki syntax for linking

The interwiki module is the way to create links to the many wiki webs on the world wide web. Users avoid pasting in entire URLs, as they would for regular web pages, and instead use a shorthand similar to links within the same wiki. The types of interwiki links allowed in a wiki are defined by an InterMap.

The interwiki module allows you to use interwiki links that point to wikis such as Wikipedia.org, SourceWatch.org and dKosopedia.com. It can also be used to link easily to Google and eBay searches and other online reference sources such as the Merriam-Webster dictionary. It uses a table interwiki which is the same table used by MediaWiki. MediaWiki users should be able to use an interwiki table interchangeably. The URL filter module must be enabled to use interwiki.

You can

Invite API:send invitations to users

The invite API module allows invitation modules to be developed to create invitations. Invitations are important to create network effects and exponential growth of a community of interest. This module is focused on sending invitations to specific users.

The invite API module can be used with the user invite module to invite users who are friends or buddies. This module does not have an administration interface.

You can:

  • enable the invite API module at administration >> modules.
  • file issues, read about known bugs, and download the latest version on the Invite project page.

Janode: How to use and tips and tricks

Introduction

This is a node type that allows you to create a library of http:// links to other resources on the Internet. Each node has a title, the matching url and a description. The node can be allocated to a taxonomy in the normal way. You can use Janode to create your own webdirectory. Janode also periodically checks each http:// link for 404 errors. Any Janodes with a 404 error are put into a moderation queue for investigation.

Installation

Unpack the module into your sites/all folder or site specific folder sites/example.com/modules (4.7 and eariler should use the modules/ rather than sites/all).

Then simply go to "admin >> modules" and enable. The Drupal 4.7 (and above) automatically creates the database table required for this module.

Don't forget to go to "admin >> access control" to setup which roles can create/modify own janodes.

Limitation: currently only "http://" links are permitted.

Cron is used to attempt an "HTTP GET" of your link. If an invalid http header (say 404) is detected, the janode is placed back into the moderation queue and unpublished. The site admin should decide what to do with it.

Note, in order for cron to check a link, your server must be able to make HTTP (TCP Port 80) outgoing requests. Firewalls may block this thus sending all ur links back into the moderation queue. If this is the case and you are unable to chamge any external firewall rule sets but wish to continue using Janodes then you can disable the automatic link check facility as show below.

Settings

Display the link on teasers

Does what it says on the tin

Display the link on main node body

Does what it says on the tin

Use "inward" redirection to external link

Setting this checkbox will make the link first visit a local "redirect" page before sending the browser on it's way to the link. This allows for a) recording the user click in the watchdog log b) bumping the stats "node counter displayed" value if the module stats module is "active" and c) bump the local janode internal click through counter;

Open new browser window

The site admin can force all janode links to open a new browser window if they desire. Just check this box if you want the feature.

Content type display name

This allows you to change the "content type" from "Jan's node" to something a site admin might prefer. It was a feature request that seemed reasonable.

How many janodes to 404 test

Does what it says on the tin. Don't set this too high. The module batch tests the links.

Enable link checking by cron

You can disable the link checking feature by unchecking this checkbox and saving.

The class name applied to the link's HREF

If you want a specific style class applying to the link enter the class name here. A blank input here means the HREF link will not have a class applied

Link prefix and Link suffix

Again, you may want to "wrap" the link in say a <div> You can do that here or leave blank for no wrapping.

How to create a list of links

Introduction

This module came about as a need for a solution "at the time" and was shared on Drupal.org sometime later. One of the requirements was a "list of links" on a page. This page describes how that was done.

First of all, having installed the Janode module create a vocabulary that will be dedicated to the Janodes you create (they can of course live in multiple vocabularies but in order to get a list we need a vocabulary dedicated to Janodes).

Once you have a vocabulary in place make a note of it's vid (vocabulary identification number). The easiest way to do that is, in admin > categories visit the "edit vocabulary" then look at the URL. The number at the end of the URL is your vid (write it down, you will need it later).

For the purposes of the following example this is my link:-

admin/taxonomy/edit/vocabulary/19 so my vid is 19. Replace 19 in the following section with the vid of your vocabulary.

Create the listings page

Now that you have you vid and have created some janodes and applied them to categories from you new vocabulary we'll create a listings page.

From Create content select page and start a new page. In order to create the page you will need to set the "input format" to PHP code as we are about to write a snippet.

Enter the following code into the body of your page. Remember to change the vid from 19 to what ever vid you wrote down ealier.

<?php
  $my_vid
= 19;

 
$sql = "SELECT vid, name FROM {vocabulary} WHERE vid = %d ORDER BY name";
 
$vocabularies = db_query($sql, $my_vid);
 
$output = "";
  while (
$avoc = db_fetch_object($vocabularies))
  {
     
$output .= "<li><strong>". check_plain($avoc->name) ."</strong></li>\n"
             
getChildTerms(0, $avoc->vid);
  }
  print
"<div class='taxonomy_tree'><p><ul>\n" . $output . "</ul></p></div>\n";

  function
getNodeCount($tid)
  {
     
$sql = "SELECT COUNT(1) as num "
          
. "FROM {term_node} "
          
. "WHERE tid = $tid";
      return (
$acount = db_fetch_object(db_query($sql)))? $acount->num : 0;
  }

  function
getChildTerms($parent, $vid)
  {
     
$sql = "SELECT td.tid, td.vid, td.name "
          
. "FROM {term_data} td "
          
. " JOIN {term_hierarchy} th on th.tid = td.tid "
          
. "WHERE th.parent = $parent "
          
. " AND td.vid = $vid "
          
. "ORDER BY td.weight, td.name ";
   
$terms = db_query($sql);
   
$output = "";
    while (
$aterm = db_fetch_object($terms))
    {
       
$output .= "<li>"
               
"<a href='/taxonomy/term/$aterm->tid'>$aterm->name</a> ("
               
getNodeCount($aterm->tid).")</li>\n"
               
getChildTerms($aterm->tid, $vid);
    }
    return (
$output != "")? "<ul>\n" . check_markup($output) ."</ul>\n" : "";
  }
?>

Note, this is your starting point. If you want to alter the appearence you will need to play with the code. Also, remember, if you get broken PHP code in the snippet then, at best, the page will not display, at worst you'll break the site (white screen of death!). So be careful.

This page was written because it's a FAQ in the Janode issue queue.

<credit> This snippet was based on this original snippet </credit>. Comments from ajayg rolled into article

LDAP Addressbook

//@TODO: write documentation

LDAP: Integration with enterprise authentication systems

This LDAP integration module allows users to authenticate against a configurable Lightweight Directory Access Protocol directory. This is useful for organizations which have an existing organizational directory with usernames and passwords.

Users can read and modify their LDAP entries, and the administrator is able to limit configuration.

LDAP Integration Module in reality, is a set of three modules:

  1. ldapauth
    Required module. It implements basic LDAP integration and sets up the environment for the other optional modules.
  2. ldapgroups
    Optional. Extends the basic functionality and integrates LDAP Groups into Drupal Roles
  3. ldapdata
    Option. Extends the basic functionality and allows for the management of LDAP Attributes from within Drupal.
    Requires profile module to be installed

Installation and Upgrades

Fresh Install
LDAP Integration module does not have any specific install instructions. Simply install the module following the Drupal contributed module installation guide.

Upgrades
Between 4.7 and 5.x versions of the module, the module's backend was overhauled and it also was being transferred to new maintainers. As such, upgrading the module from previous versions to 5.x will include a complete uninstallation of the old version and re-installation of the new version. Also, ldap configuration information will have to be re-entered.

  1. uninstall the old module
  2. remove the module entry from the system table
    • This sql query:
      SELECT name FROM system

should show you all of the modules that were in installed (active or inactive). You should see entries for ldapauth, ldapgroups or ldap_integration.

  • Delete those entries. For e.g.
    DELETE FROM system WHERE name = 'ldapauth'
  • Install the new version module following the drupal 5.x module installation guide. It will create the table and the new module.

Configuration of ldapauth module

Once the LDAP Integration module (also referred to as ldapauth module) is installed, it's time to enable the module and configure it.

Enabled the module
  • Proceed to Administer >> Site building >> Modules
  • In the fieldset titled "Administration", enable the ldapauth module
  • Configure the module
  • Proceed to Administer >> Site Configuration >> LDAP Integration
  • Click Configure LDAP Server
  • Server Settings
  • Name: Name of the LDAP Configuration. It must be unique
  • LDAP Server: Hostname of the LDAP Server. For e.g. ldap.example.com For Active Directory, this would be the hostname of the AD domain controller. If you have multiple domain controllers, then common practice is to create a DNS Round Robin entry for all of the domain controllers and use that entry (dc.example.com).
  • LDAP Port: Standard LDAP ports are 389 and 636. 389 is standard non-secure port where communications occur in cleartext (analogous to HTTP Port 80). 636 is the standard encrypted LDAP port (analogous to HTTP Port 443.
    If you are using Active Directory, then you must select the encrypted port, 636.
  • Use TLS Encryption: Required for Active Directory. For encrypted communications, select this option. (TLS is the new name for SSL, so if your LDAP server requires "SSL", then you must check this box )
  • Store passwords in encrypted form: This option is used by the optional ldapdata module, which allows for changing of passwords using Drupal. Using this option will cause the LDAP data module to perform MD5 encryption of the passwords before they are sent to LDAP. If your LDAP server natively performs encryption, then it could cause problems.
  • Login Procedure
  • Do not store users' passwords during sessions: If you are going to use the ldapdata module and allow users to modify their LDAP entries, this module will need to store the user password during the session, so that it can have write access to the LDAP directory.

    Physically, these passwords are stored in the Drupal's session table in clear text. If the database is well protected, this should not be a problem, but some admins may feel uneasy about this.

    If you are not going to use the ldapdata module, or you are, but only for read-only access, you can safely check this box and get extra security for your system.

  • When logging in, Drupal will look up for the user in:
    • Drupal's own database. If it fails, look in LDAP: Self-explanatory
    • LDAP Directory only: Selecting this option will cause ONLY LDAP accounts to be authenticated and registered, except for the initial admin user (user with uid=1). Drupal's core administration relies on this user, so this user is NEVER authenticated with any database other than the local drupal database
  • Base DNs: In the text area below, enter the base dn to search against when authenticating LDAP users. You can enter multiple DNs, one per line. PHP LDAP performs SUB scope searches by design. So if all of your users are organized under several sub-containers under say for e.g., cn=Users,dc=example,dc=org, then you only need to enter 1 base dn, cn=Users, dc=example, dc=com
    For OpenLDAP etc, an example would be ou=People,dc=example,dc=com
    For Active Directory, an example would be cn=Users,dc=example,dc=com
  • Username Attribute: The attribute in the user's object representing the username.
    For Active Directory, it is sAMAccountName and for most Unix LDAP environments, it is uid
  • Advanced Configuration
    The process of authentication starts by establishing an anonymous connection to the LDAP directory and looking up for the user on it. Once this user is found, LDAP authentication is performed on them.
  • However, some LDAP configurations (specially common in Active Directory setups) restrict anonymous searches.

    If your LDAP setup does not allow anonymous searches, or these are restricted in such a way that login names for users cannot be retrieved as a result of them, then you have to specify here a DN//password pair that will be used for these searches.

    For security reasons, this pair should belong to an LDAP account with stripped down permissions.Most LDAP and Active Directories do not allow anonymous binds

  • DN for non-anonymous searches: Enter the BINDDN of the account used to bind to the LDAP directory. For e.g. cn=drupalread,ou=Service Accounts,ou=People,dc=example,dc=com
  • Password for non-anonymous searches: Enter the BIND password. Note: This password is stored in cleartext in the drupal database so you must take steps to protect the database
  • Save the Configuration
    Save the configuration and you will be re-directed to the LDAP Integration configuration list page. You can have multiple LDAP servers configured and individually active and de-activate them. Deactivating a config will prevent it from being used by any of the LDAP modules.

    NOTE: The LDAP Servers will be consulted in the order they are listed. They are listed in the order they are created. Future releases will include an option to re-order them for authentication.

Configuration of ldapgroups module

ldapgroups module integrates LDAP Groups with Drupal roles.

Configuration

  • Goto Administer >> Site configuration >> ldapgroups
  • The active LDAP configuration are listed on the page. Select edit to configure Groups to Roles mapping
  • There are multiple ways to configure LDAP groups to Drupal roles mappings:
    • Group is specified in user's DN:
      In some LDAP installations, users are arranged in OUs that represent their departments etc.
      For e.g.
      uid=jdoe,ou=IT,dc=example,dc=com, represents a user in the IT department
      uid=jdoe2,ou=Accounting,dc=example,dc=com, representing a user in the Accounting Department

    Checking this option will enable the mapping of these departments to Drupal roles. From the above example, this would result in the following roles being created: IT, Accounting

    Check the box and enter the attribute name in the text area. The attribute name is the attribute in the user's DN that represents the group name. In the above example, it would be ou

  • Groups are specified by LDAP attributes:

    Use this option if the user object contains an attribute that represents the group the user belongs to. This is most commonly applicable to Active Directory environment. The attribute of the user object that holds the group DN is memberOf.
  • Groups exist as LDAP entries where a multivalued attribute contains the members' CNs
    This scenario is most applicable to UNIX LDAP environments. In this scenario, the LDAP groups are stored as objects with its members represented by the attribute memberUid
  • Click Save configuration to save this configuration.

  • All active ldap configurations can be configured to map groups into roles. The same configuration that was used to authenticate the user into Drupal will be used to perform groups to roles mapping as well.

Configuration of ldapdata module

This module allows mapping of LDAP user attributes to Drupal fields. It uses the profile module.

Configuration

  • Goto Administer >> Site configuration >> ldapdata
  • The active LDAP configuration are listed on the page. Select edit to configure LDAP attribute mapping
  • Drupal-LDAP fields mapping: Currently, the following attributes can be mapped between Drupal and LDAP
    • mail (E-mail address)
    • password (User Password)
    • signature (Signature line)

    The Drupal to LDAP mapping for these can be done in multiple ways

    1. Changes in account fields will be mapped to LDAP attributes and back: Checking this box will allow the Drupal users to modify the above attributes in Drupal and have it automatically updated in LDAP. This option requires a user account with read/write privileges to LDAP.
    2. Same, but read-only mode: Check this box if the users can view the LDAP information in the Drupal profile but not have the ability to change them
    3. No mapping: This option will clear any existing mappings that are present for this configuration

    Enter the attribute names that map to the specified drupal fields in the text boxes. Consult the LDAP Documentation and/or administrator for details on the attribute names

  • Drupal-LDAP fields mapping: In this section, configure which attributes are visible to in the My account section of the user and which attributes are modifiable by the user. In order to extend the attribute list that is appearing here, the configuration file modules/ldap_integration/ldapdata.conf.php can be modified.
  • Advanced configuration: In this section, enter the bind dn and bind password of an LDAP account that has read/write abilities to the user and group objects.
  • Click Save configuration to save this configuration.

All active ldap configurations can be configured to map ldap attributes to drupal fields The same configuration that was used to authenticate the user into Drupal will be used to perform attribute mapping.

Advanced configuration

ldapgroups
Advanced configuration of the ldapgroups module can be performed by editing the modules/ldap_integration/ldap_integration/ldapgroups.conf.php file.

By default, when a user logs in, all of the groups are converted into roles. This may not be desirable in specific instances and the admin may want to restrict what LDAP groups are actually converted into Drupal roles. In order to achieve this,

  • Edit the modules/ldap_integration/ldapgroups.conf.php file and specify your groups and their role names:
    $GLOBALS['ldap_group_role_mappings'] = array(
      // LDAP group => Drupal role
      'cn=users,ou=Group,dc=example,dc=com' => 'Users',
      'cn=IT,ou=Group,dc=example,dc=com' => 'SiteAdmins'
    );

Add the specific groups that need to be recognized as Drupal roles and their role names. Note: Make sure that the last group-role mapping does not have a trailing comma ,

  • Uncomment the function ldapgroups_roles_filter. Note: Uncommenting this function will cause the groups to be filtered through this function. The result is that only the groups specified in the global variable $GLOBALS['ldap_group_role_mappings'] will be parsed, ignoring the rest.
  • ldapdata

    Advanced configuration of the ldapdata module can be performed by editing the modules/ldap_integration/ldap_integration/ldapgroups.conf.php file.

    By default, the ldapdata module presents the following attributes for read/write access to the users.

    • givenName (First Name)
    • sn (Last Name)
    • cn (First Name)
    • mail (E-Mail address)

    Access to additional attributes can be given to the users by adding to this list. Consult the configuration file modules/ldap_integration/ldap_integration/ldapgroups.conf.php for examples

Lightbox V2

Description

The Lightbox2 module is a simple, unobtrusive script used to overlay images on the current page. It's a snap to set-up and works on most modern browsers. The module places images above your current page, not within. This frees you from the constraints of the layout, particularly column widths. It keeps users on the same page. Clicking to view an image and then having to click the back button to return to your site is bad for continuity (and no fun!). The module inserts the required JavaScript and CSS directly into your page with no need to edit the theme.

The version 2 module has several benefits over the plain Lightbox module. The module allows you to have images in groups and navigate through them - ideal for your image galleries. It also adds a fancy pre-loader and transition when you click on the image. The 4.7 and 5.x versions also format image node thumbnails automatically.

Permission has been granted by Lokesh Dhakar to distribute the lightbox.js file via Drupal.org under this license scheme, as allowed by the Creative Commons License.

Pre-requisites

Due to variation in licensing, you will need to download the Scriptaculous/Prototype libraries separately. For Drupal 4.7, please download the latest versions of the libraries from http://script.aculo.us/download. For Drupal 5 please visit:
http://www.stellapowerdesign.net/scriptaculous-drupal.zip. See the README.txt file for more details.

Current Maintainers

Lightbox V2 - Howto add a lightbox to your images

Adding a basic lightbox

Adding a lightbox to your images couldn't be easier. Just add rel="lightbox" attribute to any link tag to activate the lightbox. For example:

<a href="images/image-1.jpg" rel="lightbox" title="my caption">image #1</a>

The title attribute in the link tag is optional. The addition of this attribute enables the display of a caption with the image displayed in the lightbox.

Grouping images

If you have a set of related images that you would like to group, then you will need to include a group name between square brackets in the rel attribute. For example:

<a href="images/image-1.jpg" rel="lightbox[roadtrip]">image #1</a>
<a href="images/image-2.jpg" rel="lightbox[roadtrip]">image #2</a>
<a href="images/image-3.jpg" rel="lightbox[roadtrip]">image #3</a>

There are no limits to the number of image sets per page or how many images are allowed in each set.

Turning the caption into a link

If you wish to turn the caption into a link, format your caption in the following way:

<a href="images/image-1.jpg" rel="lightbox" title='<a href="http://www.yourlink.com">View Image Details</a>' >image #1</a>

Lightbox V2 - Installation

Install the module

Download the latest version of the Lightbox V2 module from the project page. Untar/unzip the files and place the entire lightbox2 directory in an appropriate module directory like sites/all/modules.

Install the Scriptaculous/Prototype libraries

Due to variation in licensing, you will need to download the Scriptaculous/Prototype libraries seperately. For Drupal 4.7, please download the latest versions of the libraries from http://script.aculo.us/download. For Drupal 5 please visit:
http://www.stellapowerdesign.net/scriptaculous-drupal.zip.

  • Download the Scriptaculous/Prototype libraries using the appropriate link as mentioned above.
  • Copy the files located in the 'lib' and 'src' directories of this download into the 'lightbox/js/' directory of the LightboxV2 module.

The required files are:

  • lib/prototype.js
  • src/builder.js
  • src/dragdrop.js
  • src/effects.js
  • src/scriptaculous.js
  • src/slider.js
  • src/unittest.js
Enable the module
  • Go to Administer » Site Building » Modules
  • Make sure "Lightbox2" is enabled (listed under "Other").
  • Click Save configuration at the bottom of this form.

Lightbox V2 - Settings

The Lightbox V2 module can be configured at Administer >> Site Configuration >> Lightbox2. Users will need the 'administer lightbox2' permission to alter the settings.

  • Use Lightbox2 Plus - Un-checking this box will enable Lightbox2 Lite. The Lightbox2 Lite option does not use the Scriptaculous/Prototype libraries and is therefore less likely to conflict with other modules.
  • Enable for Image Nodes - Checking this box will enable automatic URL formatting for image nodes, for example those in image galleries and in the "random image" block.
  • Enable Grouping - Checking this box will enable automatic grouping of image nodes on a page. This is quite useful for image galleries and image sets.
  • Disable Lightbox for Gallery Lists - Checking this box will disable the lightbox for images in gallery lists, but will still use the lightbox when viewing gallery images. This is useful when you want to be able to click on an image to enter the gallery but you want to have the lightbox appear when you click on a image when viewing the contents of a gallery.
  • Enable Gallery 2 Filter - Checking this box will enable the Gallery 2 filter.

Lightbox V2 - User Permissions

There's just one permission you can assign a user using the Lightbox V2 module, "administer lightbox2". This permission enables users to modify the configuration settings on Administer >> Site Configuration >> Lightbox2.

Liquid Wiki Engine Project: providing wiki functionality to Drupal

THIS IS A TEMPORARY OUTLINE FOR THE LIQUID WIKI ENGINE MODULE PAGE. I AM CREATING IT FOR USE WHILE I AM WORKING ON THIS DOCUMENTATION. PLEASE ASSUME THIS DOCUMENT IS INCOMPLETE UNTIL THIS TAG HAS BEEN REMOVED.

Here are the sections that will be outlined in this document:

1) Front Page -
a - Intro
b - Rationale (Why use it, feature comparison to other Drupal functions)
c - Prerequisites
d - Coming Soon section
2) Child - Installation
3) Child - Admin pages explained
4) Child - Special permissions explained
5) Child - Listing of installed modules and submodules and functions
6) Child - Table structure
7) Child - FAQ
8) Child - REAL EXAMPLES

Intro

The purpose of the Liquid Wiki Engine Project is to provide a framework for implementing a wiki on a drupal site. The intention is that Liquid should provide the Wiki infrastructure to Drupal rather than specific markups. While MediaWiki format is currently the standard markup for Liquid, the actually focus of the module is implementing features such as freelinking, wiki heirarchy, redirects, and other common wiki features. There are many changes from the first hack. Some of the more important ones are listed here.

Modularization

The main problem with the old module was that any extension had to be made by patching. Therefore, the most important change has been to define a hook point interface and split out all but the base functionality into separate modules. Today liquid consists of four modules:

* Liquid Base - The base system which provides the infrastructure for wiki name-binding and access permissions.
* Liquid Access - Provides five more permission controls and three access levels that can be defined for each node in the wiki.
* Liquid MoveRef - Provides redirects and references to moved wiki pages (similar to those in media wiki)
* Liquid Filter - Provides the Media Wiki Markup Language filter.

The extension interface provided by Liquid Base consists of three parts

* Wiki API - Allows modules to respond to wiki events (insert, move, remove, update, validate).
* Page Handles - Allows modules to provide page handles for specific wiki id's. This makes it possible for other modules to provide special pages and similar functionality
* Access Controll - A similar interface to the one provided by the node module for the operations "wiki_move" and "wiki_manage".

Update to Drupal 5.1

The code has been upgraded to work with Drupal 5.1. The older version of Liquid has been deprecated and, unless someone is VERY persuasive, no further development will be done on that branch.

Internationalization

This has not been tested thoroughly, but Liquid should now support wiki id's in native languages. If you experience problems with this functionality, turn off "use path module" feature.

Hierarchical Wiki Ids

Liquid now supports hierarchical wiki ids. You should turn on "set breadcrumbs" in order for this to be of any significant use.

Rationale

It has been suggested in various places on Drupal.org that Drupal already has the ability to imitate a wiki with it's core features. ADD LINKS FOR THE FOLLOWING LINES Typically this involves a cobbling together of things such as the Book, Freelinking, Pear Wiki Filter, and various other modules. Liquid has everything you need for a standard wiki interface all in one place. Any kind of node can be inserted into the wiki. It does not rely on any specific wiki markup language. It also offers access control above and beyond Drupal's standard access control. Liquid is the closest thing that Drupal has had to date that is a one stop, one click solution for sites needing wiki's.

Installing Liquid Wiki Engine Project

Important: Please download the current development release (HEAD). Some important changes have been applied.

Note: The 4.7 branch is no longer being developed. It contains heaps of bugs and should not be used.

  1. From a command line, download the *.tar.gz file to your "/sites/all/modules/" directory. (wget http://ftp.osuosl.org/pub/drupal/files/projects/%filename%)
  2. Expand the archive file using the tar command. (tar -xzvf %filename%)
  3. Navigate to your site's module administration page. (Typically http://www.mysite.com/?q=admin/build/modules)
  4. Enable each of the four Liquid modules according to your needs. If uncertain, enable them all.
    • Liquid Base - Allows wiki name-binding for nodes
    • Liquid Access - Allows more detailed access control
    • Liquid Wiki-Filter Framework - Will become a wiki-filter framework. However, at present it just delivers Mediawiki markup...
    • Liquid MoveRef - Move reference add-on for Liquid. Keeps track of what pages have been moved and redirect/refer to the new locations.
  5. You're now ready to begin configuring Liquid by navigating to your site's admin by module page. (Typically http://www.mysite.com/?q=admin/by-module)

Upgrading: Liquid 0.2 (5.x-0.x) is not backward compatible with Liquid 0.1 (the heavily patched 4.7.x-0.x version). Thus, upgrading can cause a nightmare of problems. One thing to note is that the wikipage module has been removed. You must therefore add a new content type (Administer -> Content management -> Content types-> Add content type) with id "wikipage" in order for your site to work. There are also other problems since the database isn't backward compatible. Be sure to uninstall the old version of liquid before upgrading to the new version. You might want to check that the install scripts doesn't remove the wiki_name and wiki_moveref tables before uninstalling. However, make sure that the wiki_access table is dropped ( if not, do it manually before reinstalling). If you have set the access level of some of your pages to "protected" or "none" these settings will have to be reset after the upgrade.

A note on upgrading: Basically, upgrading isn't supported. If you attempt to upgrade you are pretty much on your own.

Listhandler: connect mailing lists to forums

The listhandler module allows you to connect mailing lists to forums and vice versa. It works in conjunction with the mailhandler module. Mailhandler receives an email and then asks listhandler if the received email is part of a list. If the email is from a mailing list associated with a forum on your site, then listhandler adds the recieved email to the forum.

Listhandler administration allows you to set the email address of the list administrator. The email address is used as the From: field to check the address of messages sent by anonymous users. You can also enable and disable the listhandler for a list.

You can:

HOWTO: Integrate mailing lists with forums using listhandler and mailhandler

(To allow users to post to, and receive email from Drupal forums)



  1. Things to do outside of Drupal


    1. Go to your web hosting account's administrative page and create an email account (mailbox) that will be dedicated to receiving posts to your forum. Give it a non-obvious name, to prevent spammers from sending posts to your forum by addressing this mailbox directly. (Your users will not have to know, nor should they know, the name of this mailbox.)

    2. While still in your hosting account, create a mailing list that will be dedicated to your forum. This is typically done through Mailman or whatever mailing list manager comes with your account. Give this mailing list an easy-to-remember name, since this is the address to which your users will email their forum posts.

    3. Go to the admin page for the list you created in step 1.B, and subscribe the email address of the mailbox you set up in step 1.A to your list. Make this subscription non-public so no one will know it's there but you (in Mailman this is done using the "hide" option for that subscription). Also subscribe your own email address to the list (public or private, depending on whether you are going to allow people to see who is subscribed to your list). You should also add a "subject prefix" (e.g. "[CS Community]") to your list, so that your users will know the origin of the emails they receive. You may also wish to add any initial regular member email addresses to the list at this point, since subscription to the list will not be done automatically when a user is added in Drupal (this is one of the big headaches/maintenance chores of this approach, but I am told that the EZMLM module provides this capability if you use it instead of Mailman or another list manager). Also see the note in step 2.C.3, below.

  2. Things to do in Drupal
    1. If you have not already done so, create your forum using administer|forums. Go to "forums" on the main sidebar menu, click on the name of the forum, and note the "tid" number at the end of the contents of your browser's address bar (e.g. http://www.mysite.com/myforum/6 – "6" is this forum's tid).
    2. Go to administer|mailhandler|add mailbox, and complete the fields as follows:
    1. E-mail address and Second E-mail address: enter the address of the list you set up in step 1.B (not the address of the mailbox you set up in step 1.A).

    2. Complete either the Folder field or the mailbox fields to point Mailhandler to the mailbox you set up in step 1.A.
    3. Mime preference: as desired

    4. Security: disabled (typically)

    5. Send error replies: disabled

    6. From header: leave blank

    7. Default commands: "tid: n", where "n" is the tid of the forum you set up in step 2.A

    8. Signature separator: as desired (I leave mine blank)
    9. Delete messages after they are processed?: selected

    10. Cron processing: disabled while setting up, enabled thereafter (step 3.H)

    11. Input format: as desired

    1. Go to administer|listhandler and complete the fields as follows:
    1. Admin address: enter a unique email address, e.g. mailto:list-admin@mysite.com

    2. Strip title: anything you don't want to see added to the title or subject of your posts. Usually includes the "subject prefix" you entered in step 1.C.

    3. Account status: "blocked" if you don't want anonymous posters to your list (or to the mailbox you set up in step 1.A if someone figures out what it is) to be granted any privileges on your site. "active" if you run a wide-open site to which anyone can subscribe and post. (Note: Listhandler will automatically add an anonymous poster to your site as a user. If you don't want to allow anonymous users to post to your list, thereby preventing them from automatically being made members of your site, be sure to disallow anonymous posts to your list in step 1.C.)

    4. Mailing List: You will see the mailhandler you set up in step 2.B in this list. Enter the same "subject prefix" you entered in step 1.C.


    1. Testing and completing your setup
    1. Open your email client and send a message to the address of the list you set up in step 1.B (not the mailbox you set up in step 1.A).

    2. The list manager you set up in step 1.B should send a copy of this message to the mailbox you set up in step 1.A, and also a copy to you, assuming you have subscribed both the mailbox (required) and yourself (optional, but recommended) to the list in step 1.C.
    3. In CS/Drupal, go to administer|mailhandler, and click "retrieve" for the mailhandler mailbox you set up in step 2.B. You should see a message saying "n" messages have been retrieved for the mailbox.

    4. Go to the forum that corresponds to this mailing list. You should see your post there.

    5. Go back to the inbox of your email client and reply to the copy of your own post that the list manager has sent you. Wait for your reply to appear in your inbox, indicating your list manager has processed it and sent it to the mailbox you set up in step 1.A.

    6. Repeat step 3.C-D, and verify that your reply has been properly "threaded" (attached as a comment to the original post).
    7. Go to your site, click "forums", and use "post new forum topic" to post a message to the forum at the site (i.e. not via email). Repeat step 3.C. Your post should be sent to your list, and you should receive a copy of it in your email client's inbox.

    8. Once you have verified that things are working properly, go to administer|mailhandler, click "edit" for the mailbox, and set Cron processing to "enabled". Mailhandler and Listhandler will now process incoming and outgoing posts every time cron.php is run.


    Congratulations! You have now linked a mailing list with your forum using Mailhandler and Listhander. If you have multiple forums, repeat the above steps using a separate mailbox (step 1.A) and mailing list (step 1.B) for each additional forum. (This will add to your maintenance tasks, though, so keep your site to as few forums as possible.)

    </</p>

Localizer

NOTE1: The HOWTO documentation has been updated to reflect Localizer version 1.9 with Drupal 5.1.
NOTE2: The 2.x versions of Localizer had some contributed code that has been problematic. It is strongly recommended that users stick with 1.x versions for now.

The Localizer module extends Drupal's ability to handle human languages. It provides control over a site's user interface language, the ability to enter and control content in multiple languages, and the ability to display the correct language content needed. The module has been designed so that only the features needed for a particular site need be implemented. Some of the more significant features are as follows:
Language selection

  • A language switch block is provided (optional) so visitors can click to change desired language
  • Automatic language selection is provided (4 variations, all optional and configurable):
    1. via hostname : it.example.com, en.example.com
    2. directly from the viewed node's locale
    3. through the locale prefix that can be added in the path alias
    4. through a locale parameter, in the form of: locale=en
  • A Multi-language selection block is provided (optional) for a visitor to be able to select multiple languages so that content in all languages selected will be displayed

Multi-language content handling

  1. Provides repositories (nodes) where content can be translated into other languages, and keeps track of the relationship between the content of all languages
  2. Provides the ability to see which translations have been created for a specific node
  3. Allows user menus to be translated
  4. Allows blocks to be translated
  5. Allows user variables to be translated
  6. Multi-language taxonomy is supported both for terms and dictionaries

Preference handling

  1. The method for choosing the default language of the front page, the default display language for content, the ability to separate user interface language display from content language display can all be configured for the site
  2. Registered visitors can set their own preferences (in My account) for both user interface language and for language content to be displayed
  3. Registered visitors can set their own preferences (in My account) to view content in one or more of any languages available

Accessibility and extendability

  1. The localization engine has an API that can be used to work with other modules
  2. The Views module is supported

Miscellaneous information
Localizer uses this order to determine which language to display

  1. from the Select language block (if present)
  2. from the user preferences (if the visitor is logged in)
  3. from the site's global preference settings
  4. from the preferred locale set in the visitor's browser preferences

00. HOWTO: Creating a multi-lingual website using Localizer

The purpose of this guide is to provide an introduction to setting up and configuring a Drupal site that has a switchable, multi-lingual user interface, as well as to show how content can be entered and managed in more than one language. This document assumes that the target site is running Drupal 5.x.

Please note that the process for installation of the Localizer module itself is not covered in this document. Please refer to the README.txt file in the downloaded archive for installation instructions. There is also a separate HOWTO for detailed installation procedures.

01. Preparation
  1. Make sure you have a working Drupal site. If your installation is new, create some test content just to verify that everything is working properly. (Do not install Localizer at this point.) You may want to install some of the more basic modules that are compatible with Localizer, such as Views, at this point as well. (Note that Localizer is NOT compatible with the contributed module Internationalization, also known as “i18n”, so this module should be deactivated if it has been installed previously.)
  2. Decide what your taxonomy will look like. This is not mandatory but it maybe good to have it ready in advance as taxonomy can also be in multiple languages.
  3. Download the user interface files for the languages that you will be using on your site. These are known as .po files and can typically be found at www.drupal.org/project/Translations . However, often there are “language-local” sites (EX: www.drupal.jp for Japanese, www.drupalitalia.org for Italian, etc.) that have later versions of .po and other useful files, both for Drupal core and contributed modules, so it is a good idea to do some searching beforehand.
  4. Download the latest version of Localizer. (Do not install Localizer at this point.)
02. Enable necessary core modules
  1. Log in to your site as the site administrator.
  2. Under Administer > Site building > Modules, enable the following Core – optional modules by checking the box beside them and clicking the Save configuration button: Locale, Menu, Taxonomy
  3. If you installed other compatible contributed modules (such as Views), you may enable them as well if they are not enabled already.
03. Adding user interface languages
  1. Under Administer > Site configuration, choose Localization. The List screen will be shown, and for a new installation only English will be listed. Choose Add language.
  2. From the pull down menu, choose the language name for the language you want to add and click Add language. Languages are added one at a time, so repeat this step for each language you want to add. This process creates containers for the actual user interface translations.
  3. When you have setup all language containers, click the Import tab. Browse to a Drupal core .po file that you downloaded earlier. Then assign the correct language to it. (Note that language names for languages you have already added are shown at the top of the list in the pull down menu.) For Mode, select Strings in the uploaded file replace existing ones, new ones are added and click Import.
  4. It is probably best at this point to make sure the process is working correctly. Click on List under the Localization main menu. You should now see the newly added language along with English. Make sure the new language is enabled, and temporarily set it to the default language. When you click Save configuration, the user interface for the site should be shown in the new language.
  5. Repeat these steps for each desired language. Note that if you import .po files for modules other than Drupal core, these should be done after the Drupal core .po files have been added. Also, when adding such non-core .po files, you should select Existing strings are kept, only new strings are added under Mode.
04. Configuring Localizer
  1. Now it is time to install Localizer. It is recommended that you use Localizer 1.9 (or later if available) for both full functionality and for ease of installation. (For Localizer versions earlier than 1.9, patching of some Drupal core files is required. For these earlier versions, carefully follow the instructions in the README.txt file found in the Localizer module download package to install Localizer.)
  2. Localizer installs like any other contributed module, with the exception that there must be a special instruction added to the end of your sites/default/settings.php file to finalize the process. That instruction directs Drupal to use some Localizer-specific caching logic and is as follows: (Note that the path to cache.inc must be correct for your own setup. There is a HOWTO for detailed installation procedures.

    $conf= array (                                                                                                                                         
        'cache_inc' => 'sites/all/modules/localizer/system/includes/cache.inc',                                                                                          
    );
  3. Once Localizer is installed, under Administer > Site building > Modules, enable all the Localizer-related modules that you intend to use. This is done by expanding the Localizer section and checking the boxes. (As of this writing, the modules that can be activated are: Localizer, Localizer block, Localizer menu, Localizer node, Localizer taxonomy, Localizer user, Localizer variable, and Localizer views.)
  4. Also, be sure to give necessary Localizer privileges to appropriate roles under Administer > User management.
  5. Then, under Administer > Site configuration > Localizer, configure Localizer settings according to the preferences for your site. If you are not sure what your settings should be at this point, you may read the details in the child page of this document, or do the following as a good starting point:

    Under Language switching block, leave the defaults as they are (i.e. Hide the current locale, Show language name, and Show flags enabled (see detailed installation HOWTO for instructions on installing flags); Flag and language separator blank, Flag icons path as shown (sites/all/modules/localizer/flags/*.png), and Flag icons size at 16 x 11). By way of example, in a site with English, Italian, and Japanese locales enabled, when you are viewing the site in English, the Italian and Japanese flags will appear with “Italian” and “Japanese” next to them in the Select language block. When you are viewing the site in Italian, the British flag and Japanese flags will appear with “English” and “Japanese” next to them, and so on.

    Under Language initial detection options, enable Detect through browser's locale. What this means is that if the preferred language set in a visitor's browser is the same as a language available on the site, the site will automatically change to that language. This is mostly for anonymous users, as user-specific preferences can be set by registered users in their own account settings that will over-ride this. Note that anonymous users can still click on a language in the Select Language block to view the site in a different language.

    Under Language switching options, for a good starting point enable Redirect front page to the localized version, Switch by locale parameter, Switch by node's locale. (It is doubtful that you will need Switch by url's locale prefix, but this can be enabled also without issue.) It is very important to be sure that Switch by hostname is left disabled unless you know what you are doing. (Using this feature requires setting up Drupal for multi-sites, and setting DNS entries accordingly, so only use this if you are willing to configure Drupal and hosting accordingly first. Also note that if you do use Switch by hostname, you should disable all other selections.)

    Leave Multilingual content support disabled for now as well. What this does is allow someone viewing the site to have content in more than one language viewable at the same time. For sites that will have matching content in all languages, it is probably better to disable the multi-lingual content block anyway, as this can be confusing to many first-time visitors. However, for sites where there is different content in different languages, it can be useful for people who speak more than one of the languages available on the site to see all available content. This preference is also available to a registered user through their My account settings.

    Under Support for different contents types, check the box for each content type you want to have the ability to translate. (Note: While you may be tempted to enable everything, you might want to think out your selections carefully. For types with constant user contributed content, such as forums, keeping up with translation might actually be too difficult.)

    Views support options can be enabled without issue regardless of whether you have installed Views or not.

Details for setting options

The following explains what effect enabling/disabling various Localizer options will have.

Language switching block

Hide the current language-When checked, the current language being displayed will not be shown in the list of languages shown in the Select language block. If you want to show someone all the languages available, leave this unchecked. However, if it is unchecked, and a person is viewing in English, they can also select "English", but of course nothing will change, which may be confusing for some.

Show the language name-If you want the language name (as specified under Localization (locale in 4.7.x) to be displayed in the Select language block, check this box. If you want to save space, you may want to uncheck this and just show flags.

Show flags-When checked, the flag assigned to that language will be displayed in the Select language block. If both the flag and language name are enabled, both will be displayed. (Note that due to drupal.org distribution rules, the flags files are not included in the Localizer module download package. See the detailed HOWTO.)

Flag and language separator-If you would like a character to be placed between the flag and language name in the Select language block, type that character here.

Flag icons path-Enter the relative path from the Drupal root to the flags. This is typically automatically detected and for a normal installation for Drupal 5.x is sites/all/modules/localizer/flags/*.png.

Flag icons size-This controls the display size of the flags. The original size of the flags that are recommend by the Localizer author is 16 x 11, so this will produce the best quality display if you are using those flags. However, the display size can be adjusted here as needed.

Note that other flags can be substituted for the language of your choice--just swap out the files, leaving the filenames the same.

Language initial detection options

Detect through browser's locale-When checked, if the preferred language set in a visitor's browser is the same as a language available on the site, the site will automatically change to that language. This is mostly for anonymous users, as user-specific preferences can be set by registered users in their own account settings that will over-ride this. Note that anonymous users can still click on a language in the Select Language to view the site in a different language. If you always want anonymous visitors to view the site in a specific language, leave this unchecked.

Language switching options
Redirect front page to the localized version-The optional Drupal "front page" can also be translated into available languages. When this setting is checked, the appropriate language front page will be displayed.

Switch by locale parameter-When this is checked, if a locale parameter is added to the URL in the form of locale=en, then the language displayed will change to that locale.

Switch by node's locale-When this is checked, then the locale that was assigned to the node when it was created (or edited) will be used to change the language.

Switch by url's locale prefix-When this is checked, if a language prefix is present in the path (example format: 'it/contact' or 'en/contact') it will be used to change the language. This is especially useful for non-node contents, such as views.

Switch by hostname-When enabled, this feature allows changing locale via a language-specific site URL. For example, for English, en.mysite.com, and for Italian, it.mysite.com. Using this feature requires setting up Drupal for multi-sites, and setting DNS entries accordingly, so only use this if you are willing to configure Drupal and hosting accordingly first. Also note that if you do use Switch by hostname, you must disable all other switch options.

Multilingual content support
When enabled this allows someone viewing the site to see content in more than one language at the same time. This can be useful for multilingual visitors, especially if contributed content maybe different for each language. This allows the visitor to see as much information as available. However, for sites that will have matching content in all languages, it is probably better to disable the Multilingual content block (Select content languages) anyway, as this can be confusing to many first-time visitors, and it is probably not helpful to have all the same content shown in all languages. This preference is also available to a registered user through their My account settings.
Multilingual content display options can be set independently for Authenticated and Anonymous users, including which languages to allow for display, and whether to have the user interface linked to the locale being displayed or not.

Support for different contents types
When enabled, that content type will be integrated by Localizer. So, when a "new" node is created, there is a choice of language available, and once submitted, translations can be done. For any content types created after Localizer is installed, return to this area and enable/disable Localizer integration as required.

Support for external modules
A list of external modules that are specifically supported by Localizer are presented. (As of this writing, only the Views module is supported.) When Views global support is enabled, this provides Localizer integration with the Views module. This can be enabled without issue regardless of whether you have installed Views or not.

05. Configuring language selection blocks

Once the Localizer settings are complete, go to Administer > Blocks and enable the Language block (known as Select language prior to Localizer version 1.9) so that it will be visible to visitors. Configure the block as needed.

If you later decide that you want to allow anonymous visitors to enable display of multiple language content, and accordingly activate the Multilingual content support in the Localizer settings, then the Content languages block feature of Localizer can be configured here as well.

06. Adding and viewing content in multiple languages
  1. With setup complete, the site is ready to accept content in multiple languages. To test this, choose Create content > Page. Note that there is a Language pull down menu on the input page. Add some content for that locale and click Submit. (For the sake of testing, you may want publish this to the front page for now so it is easier to view what happens with language switching later.)
  2. Notice on the acceptance screen that there is a Translations menu item. Select this. A list of the other languages will appear. Choose create translation for another language. This will bring up the same content, but with the locale changed to the new language. Translate the content here, and Submit. (Again, for the sake of testing, you may want publish this to the front page for now so it is easier to view what happens with language switching.)
  3. With the content safely in two languages, toggle between the two using the language selection block. (Note: Do not select any languages in the Content languages block if you activated it.) If the content changes appropriately, everything is working correctly.
  4. Assuming the content was published to the front page, if it was enabled during the setup process, the Content languages block can be tested as well. Just click Home, enable all the languages that you have created content for, and click Change. You should see all the content available, regardless of language. Try disabling one of the languages, and the content for that language should go away.

So there you have it--a multi-language site just waiting to be filled with content!
Bye! Ciao! Sayonara!

01. HOWTO: Installing Localizer (New install)

Note: This document is being updated to reflect changes in Localizer version 1.9, and should not be used for older versions of Localizer.

The purpose of this document is to provide a more detailed version of the installation instructions found in the README.txt file that comes with Localizer. It is geared more for beginners with shared hosting accounts than any other audience, but may be useful to others as well. If you are already a proficient Linux user, this is probably too basic for you--head on to the shorter instructions in the README.txt file. For the rest of us, hopefully this will be of use. Note that this document was based on Localizer version 1.9 when being installed on Drupal 5.1. Things may change faster than this documentation, so keep one eye on the README.txt file in any case.

First some background. Drupal uses Unicode (specifically UTF-8) encoding, so most of the languages of the world can be displayed using it--even side-by-side if that is what is necessary. While there are high hopes for true multilingual capabilities in Drupal 6, as of version 5.1, Drupal core does not provide for a true "multilingual" environment, meaning that it has very limited language switching capabilities, and no way to enter or keep track of content in multiple languages. This is done by Localizer.

As of Localizer version 1.9, brute-force patching of Drupal core is no longer necessary for installing Localizer. However there is one small modification to the settings.php file involved, and due to distribution rules on drupal.org, portions of Localizer (flag graphics and modified drupal core files) must be downloaded and installed separately. So, installation of Localizer is slightly more involved than other modules you may have dealt with. Give yourself a little extra time for the installation.

01. Preparation

Make sure you have a working Drupal site. If you just installed it, create some content to be sure that it is working correctly. After creating content, also check Administer > Logs > Recent log entries to make sure errors are not being generated. If errors are found, fix those first.

Backup your site. We will be making changes to settings.php, and like many other modules Localizer will modify your database, so please heed this warning unless you don't mind starting your site from scratch if it gets messed up.

If you have ever installed the Internationalization (or "i18n") module, you will need to remove it and whatever patches you may have done for it. There will most likely be conflicts if you do not.

This document assumes you are using Drupal 5.x on your site, so create a directory called modules under Drupal's /sites/all/ directory if it does not already exist. This is where we will upload Localizer a bit later. (Technically the localizer folder can also just go into the modules directory directly under the Drupal home directory, but Drupal 5.x best practices say to put contributed modules under /sites/all/modules, so that is what this documentation will be based on.)

While it is not necessary to do it first, it is probably most logical to install the user interface languages you will want to use before you install Localizer. For instructions on doing this, see this HOWTO.

Download the following files:

  1. Download the latest Localizer module (this document assumes 1.9.x) from
    http://drupal.org/project/localizer.
  2. Download the flag icons from
    http://www.speedtech.it/files/localizer-flags.tgz
  3. Download the pre-patched Drupal core files from http://www.speedtech.it/files/localizer-sites-all-5.1-1.9.tgz
    (Note that the download link may change as the version is updated.)
02. Uploading the Localizer module, flag icons and reference files

The first thing to do is to get the main Localizer module (i.e. the localizer folder) complete with all sub-folders intact, into Drupal's /sites/all/modules folder on your server.

If you are using Cpanel on your host or a similar user interface, you can simply navigate to /sites/all/modules using File manager and click "upload files". Then, upload the Localizer module archive (as of this writing the filename is localizer-5.x-1.9.tar.gz).

Once it is uploaded, use File manager to extract the contents, and the localizer directory (i.e. /sites/all/modules/localizer) with all contents intact will be created.

You should now have the localizer folder in Drupal's /sites/all/modules directory. Check to make sure the contents appear to be there.

You may now delete the archive. (NOT the new folder created.)

Next, upload the flags archive into /sites/all/modules/localizer by doing basically the same thing. Namely, navigate to /sites/all/modules/localizer using File manager and click "upload files". Then, upload the flags archive (as of this writing the filename is localizer-flags.tgz).

Extract the files from this archive.

You should now have the flags folder in Drupal's /sites/all/modules/localizer directory. Check to make sure the contents appear to be there.

You may now delete the archive. (NOT the new folder created.)

Now for the final upload, but first a bit of explanation. As of Localizer version 1.9, there is no longer a need for you to patch Drupal core files, nor to "swap out" Drupal core files. Localizer now does its magic by referencing some "pre-patched" Drupal core files that are added under the localizer directory. (See the Disclaimer about pre-patched files.)

So, upload the pre-patched files archive into /sites/all by doing the same process stated above. Namely, navigate to /sites/all using File manager and click "upload files". Then, upload the pre-patched files archive (as of this writing the filename is localizer-sites-all-5.1-1.9.tgz). Note that as of this writing, this archive contains the /modules/localizer directory structure, so when placed under /sites/all and extracted the contents will be placed under /sites/all/modules/localizer.

Extract the files from this archive.

You should now have three new directories under /sites/all/modules/localizer, block, menu, and taxonomy. Check to make sure the contents appear to be there.

You may now delete the archive. (NOT the new folder created.)

This completes the uploading process.

DISCLAIMER about pre-patched files

As of Localizer version 1.9, there is no longer a need for the user to patch Drupal core files, or replace any existing Drupal files.

However, the "extra download" (as of this writing the archive name is localizer-sites-all-5.1-1.9.tgz) that has to be installed for Localizer to function properly actually contains pre-patched Drupal core files that Localizer references.

So, as one might guess, these files are associated with a specific version of Drupal (as of this writing, the version provided is Drupal 5.1). Be sure that the "pre-patched" files are for the version of Drupal that you are running. Also, if for some reason you have hacked these same files, you will probably want to apply the patches yourself. (If you are good enough to have hacked them in the first place, you can figure out how to do this right?!)

03. Modifying settings.php

In order for Localizer to work its magic with those pre-patched Drupal core files we put in the Localizer module directory, we have to let Drupal know that they are there. This is done by adding some settings to the settings.php file.

There are various methods for editing this file. Many people will download this file from their server, edit it locally, and then upload it again. This method is fine. As I have focused on a user with Cpanel, I will explain that method.

Using Cpanel's File manager, navigate to /sites/default. You should see the settings.php file there.

Click on the file, and then from the menu that appears on the right, click Edit file. Scroll down to the end of the file, and add the following code, making sure that the path to cache.inc is set correctly for your setup. (Copy and paste should work fine if you have put localizer under sites/all/modules/ as is standard practice for Drupal 5.1.)

$conf= array (                                                                                                                                        
    'cache_inc' => 'sites/all/modules/localizer/system/includes/cache.inc',                                                                                         
);

Save the file. Then, in File manager, click on settings.php again but this time click on Show file. Scroll down to the bottom and check to make sure that the additions are there.

This is it for the "backend" portion of the Localizer installation. Now you are ready to enable Localizer from within your Drupal site.

04. Enabling Localizer


Now that the necessary files and patches have been uploaded, it is time to enable Localizer.

Log into Drupal as the site administrator (user ID 1).

Under Administer > Site building , choose Modules . Localizer should be in its own collapsible section at the bottom of the page.

Check all Localizer-related modules and click Save configuration .

See if your site seems to be working properly. Add some test content just to see if it is still accepting things OK. Under Administer > Logs , select Recent log entries and make sure that there are no errors being generated.

Remember to take your site out of maintenance mode by changing the status under Administer > Site configuration > Site maintenance.

That's it! For information on how to set Localizer preferences and actually start to use it, see the appropriate section under this HOWTO.

05. If something goes wrong


If something "bad" happens, it is probably best to return things to their original state and start over. As you may want to try again another day, the way to put your site back to its original shape is as follows:

  1. Log into your Drupal site as administrator, and go to Administer > Site building > modules and turn off all Localizer-related modules.
  2. Using Cpanel's file manager, delete the localizer folder and its contents from Drupal's sites/all/modules directory.
  3. Using Cpanel's file manager, delete all the modified settings.php file and replace it with your backup. (Or, just take out those last few lines again.)

Remember to take your site out of maintenance mode.

If your site still doesn't work--well, you have that backup mentioned earlier right? :-)

02. HOWTO: Upgrade to Localizer 1.9.x

This document may get expanded in the future to look more like the other HOWTO documents, but here is a short version that I hope will help people out a bit.

Upgrade instructions (Upgrade from older versions of Localizer to Localizer 1.9 on Drupal 5.1)

  1. Download the latest Localizer module from http://drupal.org/project/localizer.
  2. Download the flags icons from http://www.speedtech.it/files/localizer-flags.tgz
  3. Download pre-patched core files from
    http://www.speedtech.it/files/localizer-sites-all-5.1-1.9.tgz
  4. Login to your site as administrator and under Administer > Site building > modules, disable all the Localizer-related modules
  5. Delete the old module/localizer directory (could be sites/all/modules/localizer)
  6. Return the Drupal 5.1 modules that you previously patched for Localizer versions prior to 1.9 to their original state. (In other words, download Drupal 5.1 and extract the the following files from the tarball: block.module, menu.module, taxonomy.module, bootstrap.inc, and common.inc. Upload these to your site, overwriting the existing modules.)
  7. Extract localizer-5.x-1.9.tgz archive under sites/all/modules (create the modules directory if needed) This will create sites/all/modules/localizer that contains the Localizer-related module code.
  8. Extract localizer-flags.tgz under sites/all/modules/localizer. This will create sites/all/modules/localizer/flags with the flag files in it.
  9. Extract localizer-sites-all-5.1-1.9.tgz under sites/all (it already has the modules and localizer directory, so the contents will go into sites/all/modules/localizer.
  10. To the end of your sites/default/settings.php file, append the following code (making sure that the path is correct for your site) and save (overwrite the file):
    $conf = array(
        'cache_inc' => 'sites/all/modules/localizer/system/includes/cache.inc',
    );
  11. Login to your site as administrator (UID=1)
  12. Under Administer > Site building > modules, enable all the Localizer-related modules you need. Click Save configuration.
  13. Visit www.yoursite.com/update.php and run the update script.
  14. Under Administer > Site configuration > Localizer, configure options as appropriate.
  15. Enjoy!

FAQ

Here there is the list of the FAQs

Customizing the ui locale switching block

In a block with input format PHP add this code :

<?php
$languages
=localizer_block_switchuilocale_links(variable_get('localizer_switchblock_showflags', TRUE), variable_get('localizer_switchblock_showlangname',TRUE), variable_get('localizer_switchblock_flagseparator', ' ', FALSE));

echo
'<ul>';
foreach(
$languages as $i=>$link) {
  echo
'<li>' . $link . '</li>';
}
echo
'</ul>';
?>

The syntax of localizer_block_switchuilocale_links function is :

localizer_block_switchuilocale_links($flags=TRUE, $names=TRUE, $separator = ' ', $setcurrentactive)

$flags : show/hide the flags
$names : show /hide the languages names
$separator : separation text between flags and names
$setcurrentactive : highlights the current language
How to get switch by hostname working

You have to set the correct options under admin/settings,
activating the hostname switch and inserting the hostnames
for each language that you want to use.

Then you have to register in your DNS server the correct entries
and then set up a multisite installation of you Drupal.
This involves an webserver configuration through virtual hosts,
but also a configuration under the sites directory of Drupal.

Switch by hostname switch and login for all languages

Add this line in all your settings.php files :

ini_set('session.cookie_domain', '.domain.com');
Upgrade from version 1 to version 2

The database structure is changed from version 1 to version 2 of
Localizer for 4.7.x branch.
You must run manually these SQL instructions on your database :

RENAME TABLE localizer TO localizernode;

INSERT INTO localizertranslation
(object_key, object_name, object_field, translation, locale)
SELECT m.mid, IF(m.type=115, 'menu', 'menu_item'), 'title', substring( t.translation, 1, 255 ) , t.locale
FROM locales_source s
INNER JOIN locales_target t ON s.lid = t.lid
INNER JOIN menu m ON substring( s.source, 1, 255 ) = m.title
WHERE m.type IN (115,118) AND substring( t.translation, 1, 255 )<>'';

INSERT INTO localizertranslation
(object_key, object_name, object_field, translation, locale)
SELECT td.tid, 'taxonomy_term', 'name', substring( t.translation, 1, 255 ) , t.locale
FROM locales_source s
INNER JOIN locales_target t ON s.lid = t.lid
INNER JOIN term_data td ON substring( s.source, 1, 255 ) = td.name
WHERE substring( t.translation, 1, 255 )<>'';

INSERT INTO localizertranslation
(object_key, object_name, object_field, translation, locale)
SELECT v.vid, 'taxonomy_vocabulary', 'name', substring( t.translation, 1, 255 ) , t.locale
FROM locales_source s
INNER JOIN locales_target t ON s.lid = t.lid
INNER JOIN vocabulary v ON substring( s.source, 1, 255 ) = v.name
WHERE substring( t.translation, 1, 255 )<>'';

Websites using localizer

Location: associate geographic location

The location module allows you to associate a geographic location with content and users. Users can do proximity searches by postal code. This is useful for organizing communities that have a geographic presence.

To administer locative information for content, use the content type administration page. To support most location enabled features, you will need to install the country specific include file. To support postal code proximity searches for a particular country, you will need a database dump of postal code data for that country. As of June 2005 only U.S. postal codes are supported.

You can

  • administer locative information at administer >> content types to configure a type and see the locative information.
  • administer location at administer >> settings >> location.
  • use a database dump for a U.S. postal codes table that can be found at zipcode database.
  • enable a CiviCRM profile with street address, supplemental address 1, city, postal code, state, and country that will be synchronized between CiviCRM and the location module.
  • file issues, read about known bugs, and download the latest version on the Location project page.

CiviCRM integration with Location

CiviCRM is integrated with the location module through location fields in CiviCRM profiles. If any of the following following CiviCRM profile fields are populated in a users account profile fields they will be synchronized with the location modules.

  • Street Address
  • Supplemental Address 1
  • City
  • State
  • Postal Code
  • Country

Creating KML feeds of location-enabled information

The KML module allows location-enabled information from your site to be viewed in Google Earth. It requires the Location module to associate geographic location with a node.

You can:

  • View the location of a single node by clicking a 'KML' link on any location-enabled node.
  • View a collection of nodes either as a normal KML file or as a Network Link which will regularly refresh the data from your site in Google Earth. Collections of nodes include those assigned a certain taxonomy term, results of a search or those in a group.

Mailalias: alias e-mails to user

The mail alias module associates additional e-mail aliases to user accounts. The mailhandler module uses these email aliases to authenticate incoming e-mail with your account. This is useful because many users have multiple email accounts and administrators want to aggregate their content submitted by e-mail to the single user account.

The module is enabled on the users account settings page as a text field. e-mail aliases: where users can enter multiple e-mail addresses separated by a comma.

You can

Mailcommand: manage mailing lists by email

The mail command module can be used to execute Drupal commands through email. This basic functionality can be extended by other modules. The og2list module uses this to allow users to subscribe to mailing lists. This is useful when group managers do not have access to web based connections.

Mail command allows you to perform the following command much like mailing list managers. Mail command is current limited to handle only 5 commands per hour and user to avoid a denial of service attack.

Available commands:

  • help This command allows you to receive information on how to use the mailcommand capabilities here on your OG2List site. It takes no arguments.
  • confirm This command allows you to confirm commands given to the mail processor at OG2List site. It takes one argument, an auto-generated token.
  • discard This command allows you to discard commands given to the mail processor at OG2List site. It takes one argument, an auto-generated token.
  • tokentest This command allows you to test token processing done by the mail processor at OG2List site. It takes one mandatory and two optional arguments. The first argument can be any PHP function and the others will be passed as arguments. This action requires the permission 'test tokens'

Og2list extends mailcommand by

  • Enabling mailcommand to recieve through the og2list perl scripts which makes mailhandler unnessary.
  • enabling a single administration address for all og2list related activities
  • implementing the "subscribe" command. This command requires three arguments: The lists mail address (localpart only), the mail address to subscribe, and the name of the subscriber (optional and only used when a new account is created).
  • implementing the "unsubscribe" command. This command requires two arguments, the list address, and the mailing list of the subscriber.

You can:

  • create a mailhandler mailbox with "mailcommand: 1" in the "Default commands:" textbox to enable mailcommand on that mailbox at adminster > > mailhandler.
  • Send a message to a properly configured mailhandler mailbox with the text "help" in the subject or body to recieve commands.
  • Send commands to a properly configured mailhandler mailbox with the command in the subject or body of the email.

Mailhandler: content via mail

The mailhandler module allows registered users to create or edit nodes and comments via e-mail. Users may post taxonomy terms, teasers, and other post attributes using the mail commands capability. This module is useful because e-mail is the preferred method of communication by community members.

The mailhandler module requires the use of a custom mailbox. Administrators can add mailboxes that should be customized to meet the needs of a mailing list. This mailbox will then be checked on every cron job. Administrators may also initiate a manual retrieval of messages.

This is particularly useful when you want multiple sets of default commands. For example , if you want to authenticate based on a non-standard mail header like Sender: which is useful for accepting submissions from a listserv. Authentication is usually based on the From: e-mail address. Administrators can edit the individual mailboxes when they administer mailhandler.

You can

  • run the cron job at cron.php.
  • add a mailbox at administer >> mailhandler >> add a mailbox.
  • administer mailhandler at administer >> mailhandler.
  • set default commands, (password, type, taxonomy, promote, status), for how to work with incoming mail at admin >> mailhandler select edit for the email address being handled. Set commands in the default command field.
  • post email, such as from a mailing list, to a forum by adding the term id (number found in the URL) to the default commands using tid: #.
  • file issues, read about known bugs, and download the latest version on the Mailhandler project page.

HOWTO: Submit Blog post by email

How to submit Blog post by Email
Note, my experiences are for simple email processing. I haven't done
anything like tying an email list to a forum.

Step 1: Turn on the mailhandler module
Admin -> Modules ... Click on the module

Step 2: Set up an email account to which you will send email. This depends
on what you are using for a mail hosting service.
I use cpanel, click on Mail , click on Manage/Add/Remove accounts.. Click on
Add accounts and set up the email address and the password. For my system,
the email can be retrieved as POP3. Again, this depends on the mail hosting
service you are using.

Step 3: Configure the mailhandler
Admin -> Mailhandler
Click on Add Mailbox, fill in the appropriate fields.

For email address, use the email address you set up in step 2
e.g. incoming@example.com

(I have not used the Second email address for my purposes)

In most cases, using INBOX and POP3 for the folder and Mailbox settings
should work, again, it depends on your mail service.

Mailbox domain
Should be the domain that you are using. e.g. example.com

Mailbox port
Again, the default of 110 should work, depending on your hosting service.

Mailbox username and password: Use the userid and password from Step 2, or
whatever has been assigned for the email address.

Mime preference: I always simply use HTML. You're choice. Not a biggie.

Security: I leave this disabled. I believe there is enough security with
the mail handler as it is. If you do enable it be sure not to use the HTML
mime preference.

Send error replies: I like to enable this and leave it enabled, unless I am
tying somehow to a mailing list. Even if I am doing that, I would initially
have send error replies on until I am sure things are doing what I want.

From header: Again, for personal mail handling, I leave this blank. As
noted, you may want to use Sender for mailing lists.

Default commands:
This is where I end up doing most of my tweaking.
Currently, as an example, I use
type: page
taxonomy: [mail]
promote: 1
format: 3

type: page causes the posts to be page entries. You can also use blog,
story, forum, etc.
taxonomy: [mail] I have a taxonomy term called mail for all posts coming
in via email. This can be omitted if you don't want to place the entries
into a taxonomy category
promote: 1 For the site I'm using, I want email entries to be promoted to
the front page. If you don't want them promoted to the front page, omit
this line
format: 3 This is my latest tweak to get it so the posts are processed as
Full HTML instead of filtered HTML. If you are going to receive HTML email,
you really should have this on.

Signature Separator: I leave this blank. This is probably most useful for
mail coming from a mailing list that appends some signature stuff, or if you
have your own signature file.

Delete Messages after they are processed: Yup. I do that. I don't want
the mail box filling up with messages that have already been processed.

cron processing: I generally leave this on.

Once you have set up an account, send an email to the account.

Step 4:
Once you have the account set up, send an email to the account. Be sure to
send it from an email address that has a userid on your site. Otherwise you
will get a message something like:

The e-mail address 'tester@example.com' may not create page items.

You sent:

From: tester@example.com
...

Step 5:
Testing the mail retrieval
When the mail has been sent, go to
Admin -> Mailhandler

You should see the email address under the list. Click on 'retrieve' next
to the email address. If everything is working properly, you should get a
message something like:
Mailhandler retrieve successful: 1 messages for incoming@example.com

You should then be able to go to
Admin -> Content
and find the message that you submitted.

Assuming this is running properly, all you need to do is make sure that cron
is running regularly and emails will be processed as they are received.

Useful command for mailhandler default commands

The "commands" referred to below get put into the Admin->mailhandler->list->Default commands field. A sample of commands that can be used to post incoming emails to a forum would be (including the square brackets):

type: forum
taxonomy: [My Forum]
promote: 0
status: 1

Where [My Forum] is the "term" in the "forums" vocabulary that was automatically created when a forum named "My Forum" was created (see Admin->Categories).

These can be put either on the configuration page for the mailbox, if you want them to be defaults, or at the beginning of the email:

pass: password
(If you required one on the configuration page.)

type: blog
(or story, or page. use type=comment to force a comment. can be used with nid= command)

taxonomy: [cat1,cat2]
(or whatever your categories are. The square brackets are needed.]

promote: 1
(0 if you don't want it promoted)

status: 1
(0 if you don't want it published)

comment: 2
(0 if no comments, 1 if read-only comments)

Also, in the headers of the email, if you put

In-Reply-To: nid=5

then the email becomes the new version of node 5. I'm told that there's a way to take it down to the level of comments, presumably by using In-Reply-To and the comment id#, but I haven't checked that.

format: #
This is set the format for your submission to be the input format # for you site.

Mapbuilder

The Mapbuilder module integrates the Community Mapbuilder web mapping client with Drupal.

It adds the Mapbuilder context and configuration as content types and can produce the corresponding xml output to be used as input to a Mapbuilder client.
A filter is added for showing Mapbuilder clients inline.

Further developments are being done on the 5.x version of this module. Differences in functionality between the releases:
* support for xml schema for mapbuilder context: ViewContext or OWSContext

A simple check to see if the mapbuilder installation is functioning can be done by creating a 'Map configuration' (?q=node/add/mapconfig) node with the following values:

  • Model Url = 'sites/all/modules/mapbuilder/demo/demisWorldMap.xml' if the mapbuilder module is installed under 'sites/all/' of your drupal installation
  • Title = config with demo context

Note that the Mapbuilder module for Drupal requires the Community Mapbuilder library to be installed, and needs the PHP curl extension to be enabled.

At present, PHP 5 is required for this module. It will not run under PHP4 due to the differences in xml functions.

Massmailer: manage mailing lists

This documentation referers to the mass mailer in CVS head that has support for CiviCRM integrated. It requires PHP client version to run on your server.

The massmailer module allows you to create newsletter style mailing lists that visitors to your website can subscribe to. It provides an interface to then email your subscribers in bulk, manage your subscription lists, and create and delete lists. The mass mailer module is built in such a way that it is possible to swap out the mass mailing back-end or engines. Currently the only usable engine is phplist. However, this module has aspirations for great mailing engines. Mass mailer should be used for mailings to large numbers of users where the mailings must be queued up on the server.

Mass mailer requires a mailing engine such as the phplist module to be enabled and selected in the mass mailing settings. Configuring phplist can be difficult and requires settings to be set up correctly on your webserver.

You can

Check your environment to see if you can run Mass Mailer

Test for Mass Mailer environment

These tests are now done automatically by the mass mailer module.

  1. Use the contact feature on your profile to send yourself an email
  2. Have you enabled PHPList, Mass Mailer, and configured it according to the administration help?
  3. Is the command line version of PHP installed in the correct version (PHP 4.3.3 and greater)?
  4. Can bash scripts be run? Is bash installed in /bin?
  5. What are the permissions for modules/massmailer/engines/phplist/bin/phplist? This file must be executable. If you have command line access chmod o+x. Otherwise 757 should work.

CiviCRM integration with MassMailer module

CiviCRM module is integrated with Mass Mailer to allow mailings to be sent to CiviCRM groups. It also allows for contact management through mass mailer.

  • MassMailer has a default mailing list from CiviCRM called all contacts.
  • Mailings can be sent to CiviCRM groups, and contacts added to mass mailer mailing lists will be added to CiviCRM.
  • Mass Mailer respects CiviCRM's do not contact properties for groups
  • Mass Mailer allows for users to unsubscribe from a mailing list through mass mailer and prevent mailings while still maintaining membership in a CiviCRM group.

Manage lists

This is the documentation for managing lists. Please comment below.

Manage lists screenshot

Manage subscribers

Manage subscribers documentation.
Manage subscriber screenshot

Manage templates

Documentation needed for the mass mailer template module.
Manage templates screenshot

Resolve PHP Errors (Send Message Failures)

If you receive the following error message when sending e-mails from mailing lists, your PHP interface is not properly configured and massmailer will not work properly.

You do not have the php command line executable in your system path and it cannot be located in /usr/bin or /usr/local/bin.
Please edit the modules/massmailer/engines/phplist/bin/phplist file to correct this.

Possible causes of this error:

  • You have the PHP CGI interface installed and configured for your site instead of the PHP Command Line interface
  • When the PHP Command Line interface cannot be found.

To solve the problem:

  • You need to contact your host to have the PHP Command Line Interface installed or configured for phplist.

For more information on installing PHP CLI (Command Line Interface or Interpreter) see PHP CLI and Cron or Using PHP from the command line.

Send messages

Documentation for the send messages page goes here.
Send messages screenshot

Subscriber info

Subscriber information documentation.
Subscriber info screenshot

Membership management

TODO.

Place holder in the handbook so that I can get a handbook URL to reference back to.

Multiforms: Multi-Page Collection of Data from Users

The Multiforms module is designed for the collection of data from users. It can be used to easily author multi-page forms which are published as nodes. The data collected by the forms can then be exported in tab-delimited format.

Its features include:

  • Multi-page form support
  • Ability to save/load form "snippets" (groups of fields)
  • Duplicate submission prevention (based on specification of which fields should collectively be unique)
  • Ability to purge submitted data
  • Closing date and message
  • Integrated email referrals
  • Completion threshold
  • Creation of draw entries upon successful completion
  • Creation of extra draw entries for each email referral

Installation

  1. Visit http://drupal.org/project/multiforms and download and decompress the appropriate archive.
  2. Place the resulting "multiforms" folder in your Drupal installation's "modules" directory.
  3. Enable the three component modules via the admin/modules administration page.

Configuration

Defaults can be set at the multiforms settings page (admin/settings/multiforms).

Administration and content access can be set on the access control administration page (admin/user/access).

Creating a Multiform

These are the steps for creating a multiform:

  1. Visit the create content page (node/add) and click "Multiform".
  2. Fill in the appropriate fields, as explained below:
    • The Title and Description fields are normal node title and body fields.
    • Footer Text is content that will be shown below the form.
    • Completion Text is shown after the user completes the entire form.
    • The Closing Date section allows an optional closing date to be specified.
    • The Completion Criteria section allows completion to be restricted.
    • The Draw Entries section determines whether or not draw entries will be created (for use in contests).

    Once these fields are complete, click the Add button.

Defining Submission Form Fields

The submission form is the first form shown to the user. This form is often used to ask for contact information. Clicking Add New Field will allow you to add a field.

Each field has a name and type and, optionally, a description and validation type.

The Forms Tab

Sections are form forms shown after the submission form. They are used to create groups of related questions. Sections are created and listed on the Forms page which can be accessed by clicking the Forms tab.

Once on the forms page, click Add New Section to begin creation of a new section. You'll then be shown a form that will allow you to describe the section. After filling in these fields, click Add to complete creation. You will then see that your section has been added and you may click Edit to edit it in the same way that you edited the submission form.

Managing Submissions

Multiform submissions can be managed via the Submissions tab. A list of submissions in reverse chronological order will be shown. Submissions can be viewed, purged, exported, and randomly drawn.

MySite: custom user homepages

This documentation covers the 5.x.2 series of MySite. For information regarding MySite for Drupal 4.7, see the documentation provided in the download.

Overview

MySite pages are designed to let users create a personalized summary of the
site and their favorite places on the Web. As such, the MySite module
duplicates the functionality of tools like MyYahoo! and Google's personalized
homepage.

The module allows registered site users to create a MySite page that contains
content from throughout the site. For sites that use the Aggregator module,
users may also add feeds from external web sites to their MySite pages.

Content collections are specific to each user, and users can choose:

  • A custom page layout
  • A personal stylesheet
  • The content format for each page element
  • A personal theme for their page

MySite was written using an API/Plugin model that allows the core module to be
extended to handle additional content types. The following content concepts are
supported by MySite:

  • "Droplets" of content (HTML, PHP, or JavaScript code)
  • Blocks generated by other Drupal modules
  • Content generated by the Views module
  • Individual RSS feeds handled by Aggregator
  • RSS feed categories handled by Aggregator
  • Blog posts by individual site users
  • Forum topics and content
  • Content assigned to taxonomy terms
  • Pages added to individual books
  • User profile data for each MySite user
  • Content posts of a specific node or CCK type
  • All posts by a specific user

The intent is for the core MySite module to handle core Drupal publishing
frameworks.

In order for MySite to function correctly, you must first enable and configure
the content types that your site will use.

Suggested Usage

MySite gives your users a personal homepage for your web site. The recommended usage is to configure the third-level domain http://my.example.com to send visitors directly to http://example.com/mysite.

Under this configuration, set the MySite settings for 'MySite behavior' to 'Go to User MySite.' This is the default setting and will automatically direct users from http://example.com/mysite to http://example.com/mysite/UID/view.

If MySite is configured in this manner, you can market and promote the URL http://my.example.com as you see fit.

Giving instructions for configuring subdomains on your server is beyond the scope of this file. For more information, start with http://en.wikipedia.org/wiki/Subdomains.

Community Contributions

Since MySite uses an API, users are welcome to create their own plugins. You may read a complete plugin development tutorial at http://drupal.org/node/119583.

The following plugins have been developed and a released in the /contrib/ folder of the 5.x.2.0 tarball.

MySite terminology

The following terms are used throughout this documentation and in elements of
the MySite administrative and user interfaces.

  • User -- the person looking at a MySite collection.
  • Owner -- the person to whom a MySite collection belongs. (In cases where the
    user is the owner, the term User is generally applied.)
  • Admin -- the person(s) who may administer MySite settigns and collections.
  • Personal Page -- the default MySite page for a user. Also called page.
  • Settings -- the display options available to a MySite user.
  • Content -- the content options available to a MySite user.
  • Collection -- a MySite user's curent content selections.
  • Item -- a group of content displayed on a MySite page.
  • Element -- the number of headlines or stories within a content item on a
    MySite page.
  • Plugin -- a code file used to define and extend MySite options and features.
  • Content Types -- a MySite plugin that defines and controls how content items
    are generated.
  • Icon -- a graphical element used by MySite.
  • Action Buttons -- display elements that trigger changes in a user's MySite
    page.

If you need help with common Drupal terminology, see: http://drupal.org/node/21951.

MySite: configuration settings

MySite Settings

The following module settings are available.

There are additional configuration options available for some MySite plugins. These are shown as submenu tabs.

  1. Display Settings

    Options that affect user interaction with MySite.

    • MySite Behavior

      This option tells MySite how to behave when a vistor goes to http://example.com/mysite. Most installation will use the default setting 'Go to User MySite,' which directs the user to http://example.com/mysite/UID/view. The additional option mirrors the menu callback for http://example.com/mysite/all and is mainly used for debugging.

    • MySite Privacy

      This option sets the default visibility of the owner's MySIte page with regard to other users. By default, all users with the 'view all mysites' privilege can see all other user personal pages. Individual users can turn this setting to 'private.'

    • Page Setup

      The administrator now has the option to turn off all block regions for MySite pages. This allows the user to have complete control over the content presented by MySite. If this feature is turned on, the 'footer' region of your site will still be printed on all MySite pages. Note: The full screen display may have unespected consequences for your theme, since it eliminates the default sidebars and most page regions. Test before you deploy.

  2. Content Settings

    The Content Settings option highlights the Content Types panel, which lets you configure which content options will be available to your users. These settings control the options that MySite users will be presented.

    • Content Types

      On the MySite settings page (http://example.com/admin/settings/mysite), you have the option of enabling all content types that are available. However, you are not required to offer all content to users. Use these settings to determine which content types can be added to a MySite page.
      Note: when a new Type plug-in is added to your module, a new option should appear in this list.
      Note: Some type files have configuation settings -- these are visible as menu tabs. Be sure to configure each active content type as needed

    • Disabled Content Types

      You may also see a section labelled Disabled Content Types on the settings page. The MySite module checks to see if various options and permissions have been defined before allowing you to activate a content type. The module will present you with a link to correct the configuration error. After you make the suggested change, the content type should be available for activation.

  3. Content Browser

    The Content Browser settings affect how users will be presented the content options for adding elements to their MySite page.

    • Content Browsing

      This option will only be available if you have activated the MySite Icons module. Versions prior to 5.x.2 presented users with a simple table of choices. In newer versions, MySite presents options for displaying content choices to users. The default setting is to show a plain-text table of items, with 'Add' links. The optional behavior is to show a table of icons, with each icon representing a content option.

    • Content Layout

      This options will only be available if you have activated the MySite Icons module. If you select the 'Display row of icons' option for content browsing, this option is used to set the number of items to show per row. You should test this settings to optimize the presentation for your site theme(s).

    • Items per Page

      This option sets the pagination limit for the content browser. In either table or panel mode, this setting will control the number of items to show per page.

  4. User Limits

    These options determine how much content a user can add to their MySite page.

    • Item Count

      Since MySite collects content from a variety of Drupal content, it can be resource intensive. This setting puts a cap on the number of content groups a user can add to a MySite page. The default is 10.

    • Element Count

      MySite is meant to give a quick overview, not to substitute for reading the original content in context. This setting limits the number of items (links) presented under each content group. The default is 5.

  5. Action Buttons

    MySite ships with a small set of GPL icons, created by agentrickard expressly for MySite, which can be used to present the action buttons to users. Select the option that you prefer. Default is 'Icons.' The icons are previewed on the settings page.

  6. Cache Settings

    For high-traffic sites, MySite might cause a high volume of database requests (especially if the Item and Element Count settings are very high). If you have performance issues, you can cache the results of a MySite page view for a set amount of time.

  7. Default Page Settings

    By setting s default user UID, the module can establish a default page for users to view, set at http://example.com/mysite/default. This page may be used as an optional homepage. Note: This menu item is a callback, so no link to it will be generated in your menu. You must create a link manually.

  8. Default Settings

    As of MySite 5.2, the administrator can now setup the user default settings for MySite. Clicking on the 'Default Settings' tab will take you to the MySite configuration page for user 0 (zero). The settings and content that are established for user zero will be used for all anonymous users who visit http://example.com/mysite and for all users who have not yet created a MySite page. Further, when a user creates a MySite page, the user's settings will initially match the Default Settings.

  9. Default Content

    As of MySite 5.2, the administrator can now select a default set of content to show to all anonymous users and to authenticated users who have not yet created a MySite page. When a user creates a new MySite page, the default content set will automatically be saved in their collection. Once saved, the user can change his or her individual content settings to remove the default content. To define the default content set, click on the 'Default Content' tab of MySite's administration page.

Initial plugin configuration

By design, the MySite module is very flexible. Administrators may turn off unwanted features. There are numerous settings options that will affect the behavior of the module. Please read this section carefully and refer to the README.txt for additional details.

The MySite module is dependent on other modules, since all MySite does is present content in a new format. To use MySite correctly, at least one of the following modules needs to be active:

  • Aggregator

    If you wish to allow users to add new RSS feeds, set the appropriate Aggregator settings for MySite at http://example.com/admin/settings/mysite (the Feeds tab).

    Note that there are two types of feed handling in MySite. 'Web Feeds' refer to individual RSS feeds. 'Web Headlines' refer to Aggregator categories containing multiple RSS feeds.

  • Blog

    If the Blog module is active, MySite will allow users to add any active blogger to their MySite page.

  • Book

    If there are any Books present on your site, they can be added to a MySite page. MySite will show the newest additions to any book selected by a uesr.

  • Forum

    If the Forum module is active, MySite will allow users to add Forum topics to their MySite page. For this feature to work, at least one forum container and one forum topic must be present.

  • Path

    If the Path module is active and you turn on Path Aliasing, each MySite will be aliased to the path 'mysite/USERNAME' when the MySite is created or updated.

  • Taxonomy

    If the taxonomy module is active, MySite will allow users to add tagged content to their MySite page. For this feature to work, you must have at least one Vocabulary and one Term created at http://example.com/admin/content/taxonomy. Administrators can limit the categories available to users by confuring the module at http://example.com/admin/settings/mysite/term. Note that Forum categories are not included in MySite's taxonomy-based lists.

  • Theme

    MySite supports user-based theme settings. When enabled, MySite users may choose from any active site theme. Anyone viewing a MySite page will view the page in the user-selected theme.

Special Cases
  • Droplets, Blocks and Views

    'Droplets' are the Drupal equivalent of Googel Gadgets: they enable the site administrators to create small widgets that add new features to the site. Droplets can be created from HTML, JavaScript, or PHP code. They can also be generated from Blocks or from Views.

    MySite does not expose Blocks or Views directly to end users; the administrator must select which elements to expose via the Dropler creation system.

  • Node and CCK (Content)

    The node module is a required core module. MySite can handle requests for all posts of a specific node type. If you use the CCK (Content) module, CCK node types are also supported.

  • User

    The user module is a required core module. MySite can track posts based on individual users.

MySite: plugin settings
Content specific settings

Some of the content plugins for MySite have their own configuration options. you may not be allowed to enable the plugin until you have configured it. Below are the configuration options for the default plugin set.

  1. Droplet Settings

    Droplets are content elements similar to Drupal blocks, Google Gadgets and Yahoo! Widgets. The Droplet system allows the site administrator to define custom content objects that users may place on a MySite page. The Droplet system enables the creation of Droplets from code that you supply, from existing Drupal Blocks, or from existing Views (if the Views module is installed and active). No Droplets are installed by default. The following Droplet creation options exist:

    • Custom Droplets

      Custom droplets allow you to place whatever code you wish into a Droplet item. In this sense, Droplets are very similar to Blocks. We do not use the Drupal Block system because it doesn't make sense to require administrators to create new Blocks every time they wish to add a new Droplet. Custom Droplets can contain any code that you desire, but are subject to Drupal's normal input filter system. If, for example, you wish to add the JavaScript to use a Google Gadget, you must input that Droplet as 'Full HTML.'

    • Block Droplets

      MySite will show a selection form that will allow you to convert any existing Block into a MySite Droplet. Simply select the Block to convert and submit the form. You will then have the option to preview and configure the Droplet. Note: Not all Blocks make good Droplets. Also note that Block access rules do not apply to Droplets. Any user can access any Droplet.

    • Views Droplets

      If you have installed and activiated the Views module, you can create Droplets based on existing Views. (You cannot, however, create Droplets based on default Views until you have activated them locally.) To create a Droplet based on a View, simply select the View from the select list and submit the form. You will then have the option to preview and configure the Droplet. Note: Not all Views make good Droplets. In particular, Views that require arguments cannot be handled by the current Droplet system. Also note that Views access rules do not apply to Droplets. Any user can access any Droplet.

  2. Aggregator Settings

    If the Aggregator module is enabled and you have given users the permission to 'add mysite feeds,' you will need to configure the default settings for handling new Aggregator feeds. These settings help prevent users from overloading the Aggregator be setting throttles on update frequency.

    • Aggregator Feeds

      Only if you 'Allow users to add new feeds' will the access control settings appear. Then you can give specific roles the ability to add new feeds. Recommended use is to allow all registered users to add new feeds.

    • Allowed Feed Categories

      When a user adds a new feed through MySite, they will only be given the catagory options that you allow. If only one option is selected, there is no choice allowed and all user feeds will be filed under the default category. Note: In MySite 5.x.2 Feed Categories are no longer required.

    • Default Feed Category

      Establishes the default feed cateogry for user-submitted feeds.

    • Update Interval

      Sets the update interval for all user-submitted feeds.

  3. Node Settings

    The Node module is a core Drupal module. This MySite feature also works with CCK (Content) module to allow users to select content based on content type. On the Nodes settings page, you should see a list of checkboxes for all content types used by your site. Simply select which elements you want users to be able to access through MySite. The MySite module will display the X most recent posts by type, where X is set by the default Element Count setting. Note: MySite does not use PHPTemplate for node formatting. Custom node theming do at the template layer will not appear to MySite users. See the API documentation for information about changing node templates in MySite.

  4. Profile Settings

    The Profiles handler is new in MySite 5.x.2 and is a little tricky. This plugin works with the User module -- through hook_user() -- and the Profile module to define the available profile elements that MySite users may display on their personal page. The problem is that Profile elements may be different for users of different roles or permissions, or for users who have done certain actions (like blogging). The settings on this page establish the default elements for each user's MySite profile item. Users may change these settings after they activate their profile item. Note: MySite users can only add their profile to their personal page. By design, only publicly-viewable profile fields are allowed. The link to a user's MySite page provided by mysite_user() will not be shown, since it is redundant.

  5. Taxonomy Settings

    If the Taxonomy module is enabled, you must configure which taxonomy categories (Drupal vocabularies) that users may add to MySite pages. At least one vocabulary must be selected, and that vocabulary must have active terms.

  6. Theme Settings

    On Mysite's Theme settings page, you will find a list of all active themes for your web site. Check the themes that users are allowed to apply to their MySite page and save. When users edit their MySite page, the available theme list will be presented.

  7. User Settings

    The Users plugin is new in MySite 5.x.2 and allows MySite users to track posts by individual site users. The settings for this plugin are based on the user Roles active for your site. Only selected roles can be tracked in MySite. This feature let's the site administrator have some selectivity over which users can be tracked. By default, all 'authenticated users' are available to all MySite users. If 'authenticated users' are allowed, then _all_ roles will be allowed by MySite except for 'anonymous user'. Note: The 'anonymous user' role is included in the list in cases where anonymous users are allowed to post site content.

MySite: plugins and user options
Plugins and User Options

Part of the design of MySite is to reduce code overhead through the use of
includes (called Plugins). For MySite to function, certain plugins are
required, but others are optional and can be removed.

In the case of Format, Layout, and Style plugins, the "default" file is always
required. If this file is the only plugin present, users will not be allowed
to change their content settings.

For Type plugins, only the plugins you wish to use need to be present. Since
the administrator can deactivate Content Types, there is no reason to remove
these files.

For additional detail, including how to change the default files, see the
README.txt files in each plguin directory.

Required Plugin Files

If you remove any of these files from your modules directory, MySite will not
work correctly.

mysite/plugins/formats/default.theme
mysite/plugins/layouts/default.php
mysite/plugins/styles/default.css

MySite: user-submitted feeds
User-Submitted Content

A note here on how MySite interacts with Aggregator. The key feature of sites like MyYahoo! and Google's personal homepage is that they allow users to import content from across the web to a single page.

Normally, the ability to add an Aggregator feed to a Drupal site is restricted to trusted users (the site administrator, and perhaps a specific role). In few cases are 'authenticated users' allowed to add their own RSS feeds using Aggregator.

That is because misuse of Aggregator can have some adverse effects, particularly if feeds are updated too often, or if too many feeds are updated at the same time.

Futher, Aggregator's method for selecting Categories for RSS feeds is rarely opened to authenticated users.

MySite handles these issues in two ways.

1) It is possible to simply turn off the ability for users to add their own RSS feeds to their MySite page. Deselect the 'add mysite feeds' permission in your site's access control settings.

2) The site administrator can configure the default settings for handling user-submitted RSS feeds. Those controls are at http://example.com/administer/settings/mysite/type/feed, the 'Feed' tab.

If you grant users the ability to add RSS feeds to their MySite, they only have limited permissions, described below.

- Users can only enter the Name and URL of the feed source. Other settings (update frequency, categories) are set by the administrator.

- When a user enters a new feed, both the title and the URL are checked against existing feeds. If a match is found, that existing feed is used and the new feed is ignored.

- Users can only assign a feed to a category if the administrator enables more than one feed category under the module settings. In practice, we recommend that you establish a User Feeds category in Aggregator and force all user submitted feeds into this category. Doing so has the effect of separating user-submitted feeds from those entered by site administrators. Some, but not all, sites will desire this type of separation to indicate which feeds are 'endorsed' by the site and which are the responsibility of site users.

- If only one category is available for users to add feeds, that category will be used for all user-submitted feeds. If more than one is available, users may choose, though the administrator can set a default category.

Further separation of user feeds from 'site administrator' feeds is not possible without changes to the Aggregator module. Such changes are also, in most cases, not necessary and are considered out of scope for the MySite project.

MySite: writing new plugins

The MySite module is written with a plug-in format and an API that allows core features to be extended. For detailed information, see the README.txt in the file download.

This tutorial will cover creating a new "type" plugin for MySite 5.2.x.

For an updated MySite 5.x.2 API, see http://therickards.com/api.

MySite Plugins

MySite has five plugin types that can be used to control content display.

Type plugins

'Types' are content definitions. They control the content selection options available to the user and generate the data that is presented on a MySite page view.

They are named {type}.inc and stored in the /mysite/plugins/types/ folder.

Layout plugins

These plug-ins handle the page architecture of the MySite page. They are named {layout}.php and stored in the /mysite/plugins/layouts folder.

Layouts have two parts: a {layout}.php file that defines the layout theme and a {layout}.png image that is used in the layout selection form.

To create a new {layout}.png, use the provided default.png as a blank.

Format plugins

Handles the presentation theming for individual content elements on a MySite page. They are named {format}.theme and stored in the /mysite/plugins/formats/ folder.

Style Plugins

Handles stylesheets for user MySite pages. Users may select the style that they prefer. They are named {style}.css and stored in the /mysite/plugins/styles/ folder.

All stylesheets should include the core styles defined in default.css, rewritten as needed. Only the selected stylesheet is loaded on page views.

Additional Documentation

For an updated MySite 5.x.2 API, see http://therickards.com/api.

MySite: writing a content type plugin

This page is a step-by-step tutorial for extending the MySite module by writing a new type.inc file. The code in this tutorial is part of the MySite release and can be found in the download at mysite/plugins/types/book.inc.

For an updated API for MySite 5.x.2, see http://therickards.com/api

Writing book.inc for MySite

Before you start making a type include for MySite, please read:

Planning the code

First, plan out what use cases apply to this content type. In the case of books, we don't necessarily want to show the book in its published order. It makes more sense to show recent changes to a specific book.

There can also be more than one book on a Drupal site, so the book's main node id will be the organizing identifier for the book type.

It is important to review the data structure for how Drupal retrieves a content type. In the case of books, there is a 'book' table in the database. The book table contains 4 data fields:

  1. vid -- the active revision number of the book node.
  2. nid -- the unique node id of the book node.
  3. parent -- the parent node id of this book page.
  4. weight -- the weight of this book page relative to its peers

For our use case, the important item to know is:

  • Is the book node a parent (if so, parent == 0)

This tells us which nodes are books and which are pages belonging to books.

Writing the code
Part One: Required Features
1. Set up the include file like a normal Drupal file.
<?php
// $Id$

Save this as 'book.inc' inside of 'mysite/plugins/types/' and you're ready to start.
2. Define the content type

This function registers your plugin with the MySite type system.

Arguments: $get_options = TRUE | FALSE.
The $get_options argument tells the function whether to return content options as part of the return value.

Returns: A $type array containing information about this content type.

/**
* Implements mysite_type_hook().
*
* Book module must be enabled for this plugin to register.
*
*/
function mysite_type_book($get_options = TRUE) {
  if (module_exists('book')) {
    $type = array(
      'name' => t('Books'),
      'description' => t('<b>Books</b>: Pages from a book collection.'),
      'include' => 'book',
      'prefix' => t(''),
      'suffix' => t('book pages'),
      'category' => t('Content'),
      'weight' => 0,
      'form' => FALSE,
      'label' => t('Add Book Updates'),
      'help' => t('You can track new pages for individual books. Type a book name in the search box, or choose from the list provided.'),
      'search' => TRUE
    );
    if ($get_options) {
      $type['options'] = mysite_type_book_options();
    }
    return $type;
  }
}

If you go to example.com/admin/settings/mysite, you will see the book type under the User Settings group, under 'Content Types'.

3. Define the activation rules for this type.

New in MySite 5.x.2 is the function mysite_type_hook_active($type). This hook is used to alert site administrators when a plugin cannot be activated. The function should check its conditions for activation and return an error message with a link to the page where the problem can be corrected.

Arguments: $type == the content type (here 'book').

Returns: An array in the format array($type => TRUE|FALSE, 'message' => l(t('Error message'), 'path/to/correct/error)).

/**
* Implements mysite_type_hook_active().
*/
function mysite_type_book_active($type) {
  // some users must be able to create or edit books
  $result = db_query("SELECT perm FROM {permission}");
  $check = '';
  while ($perms = db_fetch_object($result)) {
    $check .= $perms->perm;
  }
  if (stristr($check, 'create new books') || stristr($check, 'edit book pages') || stristr($check, 'creat book pages')) {
    return array($type => TRUE);
  }
  else {
    return array($type => FALSE, 'message' => l(t('There are no users with create or edit book permissions.'), 'admin/user/access'));
  }
}
4. Define the content options for this type.

Here we write the logic for presenting Book selection options to the user on the MySite 'add content' page.

Arguments: none.

Returns: An $options array containing one entry for each content group available to add to a MySite page.

/**
* Implements mysite_type_hook_options().
*/
function mysite_type_book_options() {
  $options = array();
  // we are dealing with nodes, so node_access requires db_rewrite_sql here.  See http://drupal.org/node/135378.
  $sql = db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON b.nid = n.nid WHERE b.parent =0 AND n.status = 1 AND n.type = 'book' ORDER BY n.title");
  $result = db_query($sql);
  $books = array();
  while ($item = db_fetch_object($result)) {
    $books[] = $item;
  }
  foreach ($books as $key => $value) {
    $options['name'][] = mysite_type_book_title($value->nid, $value->title);
    $options['type_id'][] = $value->nid;
    $options['type'][] = 'book';
    $options['icon'][] = mysite_get_icon('book', $value->nid);
  }
  return $options;
}

The key here is understanding the structure of the book node type. (See 'planning the code' above.) The code here was copied from the blog.inc type include. The only changes are:

  • Replace all references to 'blog' with 'book' (case-sensitive)
  • Change the $sql statement to return a list of books.
  • Change the $options['name'][] var to $value->title (blog.inc uses $value->name).
  • Change the $options['type_id][] var to $value->nid (blog.inc uss $value->uid.

Now you can enable the Book type under MySite's module settings.

5. Define the content title for this type.

In some cases we access MySite without knowing the title of the content (especially during the save and block routines). The mysite_type_book_title() function tells MySite how to print a title for a specific content group.

Arguments: $type_id = the unqiue ID key for a content group (such as a $term TID or a $user uid).

$title = A string to be used as the title, after attaching the prefeix and suffix.

Returns: A $title string used to label the content group.

/**
* Implements mysite_type_hook_title().
*/
function mysite_type_book_title($type_id = NULL, $title = NULL) {
  if (!empty($type_id)) {
    if (is_null($title)) {
      $book = node_load($type_id);
      $title = $book->title;
    }
    $type = mysite_type_book(FALSE);
    $title = $type['prefix'] .' '. $title .' '. $type['suffix'];
    $title = trim(rtrim($title));
    return $title;
  }
  drupal_set_message(t('Could not find title'), 'error');
  return;
}

WARNING: When fetching $type = mysite_type_book(FALSE);, make sure you set the parameter to FALSE. Failing to do so may trigger an endless logic loop.

This function tells other MySite function how to format and display a title element for a content group. If necessary, it will load the data it needs (here using node_load() and add the prefix and suffix strings.

At this point, it is safe to add book content to an individual MySite page. But notice that if you do, no content will be displayed by mysite/UID/view.

6. Define the content view

To show content for this type, implement mysite_type_book_data().

Arguments: $type_id = a numeric identifier for this group group.

Returns: A positional $items array containing data for each content item within a content group.

/**
* Implements mysite_type_hook_data().
*/
function mysite_type_book_data($type_id = NULL, $settings = NULL) {
  if (!empty($type_id)) {
    $sql = db_rewrite_sql("SELECT n.nid, n.changed FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = %d OR n.nid = %d AND n.type = 'book' AND n.status = 1 ORDER BY n.changed DESC, b.weight");
    $result = db_query_range($sql, $type_id, $type_id, 0, variable_get('mysite_elements', 5));
    $data = array(
      'base' => 'book/'. $type_id,
      'xml' => 'book/'. $type_id .'/feed',
      );
    $items = array();
    $i = 0;
    while ($nid = db_fetch_object($result)) {
      $node = node_load($nid->nid);
      $items[$i]['type'] = $node->type;
      $items[$i]['link'] = l($node->title, 'node/'. $nid->nid);
      $items[$i]['title'] = check_plain($node->title);
      $items[$i]['subtitle'] = NULL;
      $items[$i]['date'] = $node->changed;
      $items[$i]['uid'] = $node->uid;
      $items[$i]['author'] = check_plain($node->name);
      $items[$i]['teaser'] = mysite_teaser($node);
      $items[$i]['nid'] = $node->nid;
      $i++;
    }
    $data['items'] = $items;
    return $data;
  }
  drupal_set_message(t('Could not find data'), 'error');
  return;
}

Again, we just borrow from blog.inc, since we are showing node content. All that we need to edit is the $sql statement. The new $sql statement is designed to return all child pages of the selected book, ordered by last update and then by their order in the book.

When dealing with content other than nodes, it will be necessary to use a different function from node_load() or a db_query to return the data that you need. In either case, the data should be formatted into a positional $items array.

When MySite outputs the content, the module iterates through the $items array and displays the results to the user.

How that content is displayed is handled by Format and Layout includes. To add a content type, you don't need to edit those.

7. Updating user data via cron

This feature is optional, but highly recommended.

There are cases in which content in a user's MySite may no longer be available. For example:

  • An RSS feed is deleted by the site administrator.
  • A blogger's account is deleted, or blog permissions removed.
  • A forum or taxonomy term is deleted.

In these cases, users with that content type in their MySite will be left with empty content. They may wonder what happened. MySite's cron hook is designed to update users in the event of such changes.

MySite's cron invokes the mysite_type_book_clear() function in order to check user data for validity.

Arguments: $type = a string that identifies the content type.

Returns: A $data array containing MySite data to be deleted from a user's MySite record.

/**
* Implements mysite_type_hook_clear().
*/
function mysite_type_book_clear($type) {
  // fetch all the active records of this type and see if they really exist in the proper table
  $sql = "SELECT mid, uid, type_id, title FROM {mysite_data} WHERE type = '%s'";
  $result = db_query($sql, $type);
  $data = array();
  while ($item = db_fetch_array($result)) {
    $sql = "SELECT b.nid FROM {book} b WHERE b.parent = 0 AND b.nid = %d";
    $check = db_fetch_object(db_query($sql, $item['type_id']));
    if (empty($check->nid)) {
      $data[$item['mid']]  = $item;
    }
  }
  return $data;
}

Here, we want the $check->nid value to return a value. If it does, then the content still exists. If it does not, we pass that to the cron function, which handles deletion and sets a message for the user.

Note: The MySite cron function fires once per day. If cron is not enabled on your site, the cron function will also fire when you visit the MySite settings page.

Note: We don't wrap this query in db_rewrite_sql(), because we are just checking for the availability of the data, not access to the data. This point is debatable.

End of part one

That's it. The include works. (Test it to see.) But there's more functionality that we could add.

Part Two: Advanced Features

Advanced features add some extra UI elements to make MySite easier to use. These include search and block links, plus some AJAX features.

8. Adding a link to MySite's block

This feature is optional but recommended.

When users are navigating a site, MySite's block can alert them to content that can be added and removed from their MySite page. This feature is handled by two functions.

  1. mysite_type_{name}_block()
  2. mysite_type_{name}_block_node()

Normally, the first function is used to check the menu callback of the page, and the second function is used if the user is on a node page.

In the case of books, however, the path will always start with node, so a single check works in all cases.

We need to derive the parent_id for the book, regardless of what page we are on. That is done as follows.

Arguments: $nid = a node nid, determined by the mysite_block function.

Returns: $content as specified by hook_block. [This is slightly misleading, the passing of the $data array to the block handler is crucial.]

/**
* Implements mysite_type_hook_block_node().
*/
function mysite_type_book_block_node($nid = NULL) {
  if (!is_null($nid)) {
    global $user;
    $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, n.type FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $nid);
    $book = db_fetch_object($result);
    // if no parent, this is the parent
    if ($book->parent == 0) {
      $book->parent = $book->nid;
    }
    $data = array();
    $data['uid'] = $user->uid;
    $data['type'] = 'book';
    $data['type_id'] = $book->parent;
    $data['title'] = mysite_type_book_title($book->parent, $book->name);
    $content = mysite_block_handler($data);
    return $content;
  }
}

This case turns out to be quite simple. Access control checks are handled in the mysite_block function.

9. Searching for content to add to MySite

On the 'add content' page, MySite provides a table of the top X content groups in each category as set by the site administrator.

Content can also be searched, so that a use can find content from a wider selection. Each type include defines its own search rules.

To make search work, we must include three functions: A search form, a form submit handler, and an AJAX autocomplete.

The search hook uses Drupal's FormsAPI to define how to search our book content.

Arguments: $uid = the user ID of the MySite page that content is being added to.

Return: A Drupal $form array.

/**
* Implements mysite_type_hook_search().
*
* @ingroup forms
*/
function mysite_type_book_search($uid = NULL) {
  if (!is_null($uid)) {
    $output .= drupal_get_form('mysite_type_book_search_form', $uid);
    return $output;
  }
}
9a. Search form handler.

In FormsAPI for Drupal 5, each form expects a builder function. We always pass the $uid variable to this function.

/**
* FormsAPI for mysite_type_book_search
*
* @ingroup forms
*/
function mysite_type_book_search_form($uid) {
  $form['add_book']['book_title'] = array('#type' => 'textfield',
    '#title' => t('Book title'),
    '#maxlength' => 64,
    '#size' => 40,
    '#description' => t('The name of the book you wish to add.'),
    '#required' => TRUE,
    '#autocomplete_path' => 'autocomplete/mysite/book'
  );
  $form['add_book']['uid'] = array('#type' => 'hidden', '#value' => $uid);
  $form['add_book']['type'] = array('#type' => 'hidden', '#value' => 'book');
  $form['add_book']['submit'] = array('#type' => 'submit', '#value' => t('Add Book'));
  return $form;
}
9b. Search submit handler.

Following the FormsAPI, define a function that process the submission of the content form.

Arguments: $form_id and $form_values, as defined by FormsAPI.

Returns: No return value. The results of the search are passed to MySite's search_handler. The $data array are the matches returned by the search.

Notice the use of LIKE and its rationale.

/**
* Implements mysite_type_hook_search_form_submit().
*
* @ingroup forms
*/
function mysite_type_book_search_form_submit($form_id, $form_values) {
  // we use LIKE here in case JavaScript autocomplete support doesn't work.
  // or in case the user doesn't autocomplete the form
  $sql = db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON b.nid = n.nid WHERE LOWER(n.title) LIKE LOWER('%s%%') AND b.parent = 0");
  $result = db_query($sql, $form_values['book_title'], $book);
  $count = 0;
  while ($book = db_fetch_object($result)) {
    $data[$count]['uid'] = $form_values['uid'];
    $data[$count]['type'] = $form_values['type'];
    $data[$count]['type_id'] = $book->nid;
    $data[$count]['title'] = mysite_type_book_title($book->nid, $book->title);
    $data[$count]['description'] = $book->title;
    $count++;
  }
  // pass the $data to the universal handler
  mysite_search_handler($data, 'book');
  return;
}
9c. AJAX Autocomplete for search

The autocomplete function follows the guidelines from the Drupal handbook. However, most of the heavy logic is handled by the MySite core module.

The only item that the function needs to perform is returning matches.

Arguments: $string = the text string that a user has searched for.

Returns: An array of $matches, where key == value.

/**
* Implements mysite_type_hook_autocomplete().
*
* @ingroup forms
*/
function mysite_type_book_autocomplete($string) {
  $matches = array();
  $sql = db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON b.nid = n.nid WHERE LOWER(n.title) LIKE LOWER('%s%%') AND b.parent = 0");
  $result = db_query_range($sql, $string, 0, 10);
  while ($book = db_fetch_object($result)) {
    $matches[$book->title] = check_plain($book->title);
  }
  return $matches;
}

Node import: get CSV content

The node import module enables importing of nodes of any type into your site using comma separated values format (CSV). One possible use is with contact manager to import lists of contacts. Users want to be able to import content from other systems into their site.

Node import accepts a CSV file as input. CSV files can be generated using spreadsheet programs. Your CSV file must contain field names in its first row. These field names can be anything; except when using raw data import type. Modules, such as contact_manager, will add additional import types.

You can

Raw data import type

The raw data import type allows control over what the content of each node object but requires specially formatted CSV files.

Your CSV file must contain field names in its first row. The following fields are required:

  • 'title' the title of the post.
  • 'uid' (user ID) or 'name' (user name).
  • 'type' the type of the node.

UID or name are ignored if current user does not have administer nodes permission. If the user does have this permission, name is used if available, otherwise UID, otherwise anonymous user is assumed. Your CSV file should use quotes around strings that contain a comma. Parsing is handled by the php fgetcsv function. Field names should match those usually entered into your database.

Node privacy by role: node view and edit permissions

The node privacy by role module allows users, when creating or editing a post, to select which roles of users on a site will have view permissions for the node and which users on a site will have edit permissions. Community leaders frequently want to give permissions to roles to create and manage content for a site. The ability to publish information, that would traditionally be hoarded, allows communities to educate each other while still preserving the value of knowledge.

The node privacy by role module must be explicitly enabled in its administration interface. The node privacy by-role permissions are set by users for their nodes. If the node privacy by role module is disabled, the default permissions scheme will be in effect again, in which all users have view permissions for all nodes. However, if the module is re-enabled, the node-by-node permissions that were in place during the previous period in which the module was enabled will take effect again. Roles given edit permissions are automatically given view permissions even if the user tries to give edit permissions to a particular role, but not view permissions.

You must explicitly enable or disable the module at administer >> settings >> node_privacy_byrole.

You can

Node Profile Package

Welcome to the handbook section which covers the Node Profile package of modules.

Node Profile
This module builds user profiles as nodes. It lets you mark content-types as a nodeprofile. The nodeprofile module depends on the nodefamily module.
Apart from defining one content type as nodeprofile, it allows you
  • to build nodeprofiles consisting of several content types,
  • to build different nodeprofiles for different roles or
  • to build multiple (different kind of) nodeprofiles for the same users.
Node Family (required)
The nodefamily module is used by the nodeprofile module. It lets you define relations between content types. That's nothing you have to care about for a simple nodeprofile. However it enables one to build nodeprofiles consisting of several different content types!
Pageroute (optional)
This is a flexible module that provides a userfriendly wizard for creating and editing several nodes. So it suits very well for providing a way for your users to fill out their nodeprofiles.
Usernode (optional)
This module tries to make users nodes. It cares for automatically creation and deletion of a node for each user, the so called usernode. That's useful if you want to use tools for nodes for users, e.g. for listing users with views. It's also often used for presenting the users nodeprofiles information to the public.

Note: This section is new, and people are welcome to add case-studies and comments to be incorporated into the pages.

Getting Started: A Step by Step Tutorial

This tutorial shows how one can use these module for building profiles. It's written for the 5.x compatible nodeprofile 1.x releases. For drupal 4.7.x things are basically the same, however some urls pointing to some settings in the administration pages have changed. So you can apply this tutorial to 4.7.x too, but you have to find the mentioned administration pages on your own.

We 'll start with the basic and necessary steps. Then I'll describe some further possible steps, which are not necessary but useful to adapt it according to your preferences.

Step 1: Create your content type and set it as nodeprofile
  • First of all you have to install at least two modules, nodeprofile and nodefamily.
  • Then go to admin/content/types and create a new content type, which will be used for your nodeprofile. Before that check the checkbox "Use this content type as a nodeprofile for users". Now the nodeprofile module uses the nodefamily module to restrict the "Maximum number of nodes of this content type per user." to 1.
    Nodefamily calls this setting "maximum node population". It also refers to content types with a maximum population of 1 as lonely nodes.
  • Now we already have a simple nodeprofile, consisting of one content-type. Go to the access control settings (/admin/user/access) to restrict the use to the desired user roles.
  • Now you may wonder, how should your users easily input their profile data? For this task there are several solutions available. First of all it's suggested to remove the default "create content" link for your nodeprofile content type by using the menu module. Your users could use it only once, then it wouldn't work any more, so better disable it.
    In the next steps, I'll describe the different possibilities for providing a user friendly way for inputting profile data.
Step 2a: use nodefamilies' url handler

If the population of a content type is restricted to 1, as it is the case for nodeprofiles,
nodefamily provides a unique URL for adding/editing nodes of this type:

One can go
to 'nodefamily/CONTENT_TYPE' for adding/editing your node of the content type or administrators can go to
to 'nodefamily/CONTENT_TYPE/USER_ID' to edit nodes of this type from other users.

Replace CONTENT_TYPE with the machine readable name of your content type. You can find the machine readable name "admin/content/types" in the column "type".
Administrators have to replace USER_ID by the user id of the user, whose profile should be edited.

Use the menu module to create a menu item for this URL. There your users can create and update their profiles. The first time they fill out the form, a new node will be created, then the next times the created node will be updated.

The described way is only applicable to simple nodeprofiles, which consist of one type. If you want to build a more complex profile, try the solution 2b, which makes use of the pageroute module.

Step 2b: use Pageroute

The Pageroute module can be used to provide a user friendly wizard for creating and editing several nodes. So you can use the module to create a "route" which leads users through multiple pages, which suits very well for building a user friendly way for filling out a nodeprofile.

A pageroute consists of several pages, which have different page types. The nodefamily module comes with two page types, which are in particular useful in conjunction with nodeprofiles.

It offers two page types:

* Lonely node management page
* Lonely node display

Both page types may be used only with content types which are restricted to a maximum population of one - as it's the case for nodeprofiles. You can use the lonely node management page for providing the input/update form for the nodeprofile. The lonely node display page just shows a lonely node, so it can be used for showing the created profile node to the user.

We'll use this to create a simple pageroute, which first shows the form for inputting the data and then show the generated node.

  • First create a new route at pageroute's admin center located at admin/build/pageroute. (To have the admin center available, you need to have the pageroute_ui module enabled!). Choose a new path for it, e.g. "/nodeprofile" and proper access permissions.
  • Then create a new page of the type "Lonely node management". Give it the name "input" and set it to your nodeprofile content type. You can just leave the other settings or adapt them to fit your needs.
  • Create another page of the type "Lonely node display" and call it "view" and again set it to your nodeprofile content type. Increase the weight to a higher value, e.g. 5.
  • So now your route is built and you can test it. If you have proper access permissions there should be a new menu item for your route, which is labeled equal to the path of our route - in our example "nodeprofile". You can rename and move it by using drupal's menu module (/admin/build/menu)
  • Click on the menu item and test the route. If you prefer to have tabs on top of each page, you can enable "tab-like submit buttons" in the route's advanced settings. These buttons can be themed to look fine; unfortunately the default look isn't that nice. But having buttons on top instead of usual drupal tabs has the big advantage, if users modify their profile data and click on a tab, then their changes will be saved (instead of getting lost)!
  • Pageroute also allows the administrator to edit the profile of other users. Pageroute 5.x users go to PAGEROUTE_PATH/0/USER_ID, where PAGEROUTE_PATH has to be replaced by the proper path, in our example "nodeprofile" and USER_ID by the desired user's id.
    If you are using Pageroute 4.7.x you have to use PAGEROUTE_PATH/USER_ID.

By using pageroute and nodeprofile you can build extended nodeprofiles which exists of several content types. If you want to do that, proceed with step 3.

Step 3: extend the nodeprofile with further content types (optional)

If you want to build a nodeprofile, which consists of multiple content types, you should use pageroute to provide a user friendly way for inputting the profile data. So be sure to use step 2b instead of step 2a, before you proceed.

You can extend your nodeprofile with multiple content types. You can restrict the maximum number of nodes per user of the further content types to any value or also to 1 as it is the case for the nodeprofile content type.

  • To extend your nodeprofile with another content type first create your new content type and set the maximum population in your content type settings located at "admin/content/types".
  • Then go to the nodefamily admin page located at "admin/content/nodefamily". Let's assume your nodeprofile's content type is called "profile" and your new one is called "personal data". Then create a new relation with parent type "profile" and child type "personal data". All possible further content types which extend this nodeprofile should all have the nodeprofile's content type "profile" as parent type.
  • Then we have to adapt our pageroute. Edit your nodeprofile's pageroute and add a new page. If you have restricted your new content type to a maximum node population of 1, use the lonely node management page type. Otherwise use the node management page type, which lets your users create as much nodes of this type as you have configured as maximum population. The node management page will provide a list of already created pages and if users have appropriate permissions, it will also allow them to edit or delete their nodes from inside the pageroute - without falling out of the route.
  • Nodefamily will create relations between all the nodes, which belong to the same nodeprofile of one user. So one could say these nodes build a "nodefamily". Per default this relation information won't be used anywhere, however the nodefamily README describes some ways, how one could use them to show data of the child nodes at the parent nodes.
  • Here example, which adapts the nodeprofile display to include the node view of all children nodes:
    Create a new theme-template for your nodeprofile's content type, in our example for the content type "profile". More help on this. Then put this snippet inside your theme to just show all nodes of children types:
    <?php
    $children
    = nodefamily_relation_load($nid);
    foreach (
    $children as $childnode) {
      print
    node_view($childnode);
    }
    ?>

    Another possibility is to use nodefamiliy's views integration to embed a view, that lists all child types of a special type. Just do it the way you prefer.
  • Another possibility to use the nodefamily relation data is to make some views with it. For this you need the views fusion module. Proceed with step 4 if you are interested in this.
Step 4: Build a view that lists your profiles (optional)

This page shows you how you can create a view with the popular views module for your profiles.

If you have a simple nodeprofile, which consists only of one type, just create a view as usual and list the nodes of your type. If you have a more complex nodeprofile, consisting of several content types, you'll need to use the views fusion module. This page describes how that can be done.
If you want to use views fusion with CCK nodereference fields, have a look at this handbook page.

Views Fusion allows one to fuse multiple views into one. So you can build fused views that display information that is stored in multiple nodes - useful for tabular views. It uses node relations for joining the appropriate nodes together.
If you want to build a view, which lists node teasers or full nodes, then you need views_fusion only, if you want to filter on data/fields, which belong to a child node. Otherwise you are fine with a view that just lists your nodeprofiles like every other node.

  • Otherwise you need views fusion. So install it and make sure that you are running a compatible views module.
  • Then create a view for each of your content types that shall be included. E.g. if we have a nodeprofile with the content type "profile" and a child content type "personal data", create two views, where the first lists nodes of the type profile, and the second nodes of the type "personal data". Set both views to "tabular" output mode and include at least one field, e.g. the node title of each type.
  • Now you can fuse your views. Decide which view should be the "primary view", this is the one your users will use to see the results. Let's assume we use our "profile" view for this, so we configure this view to provide a "page" for us. Then go to to the fusion tab located at admin/build/views/fusion and create a new fusion. Set the view for the profile content type as "primary view" to be fused with the other view for the "personal data" and use the parent-child nodefamily relationship.
    If you are using the "personal data" view as primary view, you have to use the child-parent relationship according to the existing nodefamily relation settings.
  • That's it, just go to the page provided by your primary view and there should be the fused view, which contains the fields of both views and also respects filters of both views. Now go and adapt your views so that it fits to your needs.
    Don't worry, views fusion can also fuse views, which are already fused. It's also working well for external filters. But unfortunately there is no easy way for reordering the fields of the resulting view, so if you want to mix up the fields of fused views in the resulting view you have to do it in a theme or wait for views 2.0 :)
Step 5: Use usernode for presenting multiple node profiles or user searches (optional)

The usernode module tries to make users nodes. It cares for automatic creation and deletion of a node for each user, the so called usernode.

If the usernode module is installed, the nodeprofile module sets a nodefamily relation between the content type of usernodes and all nodeprofile content types so that it is possible to easily get all node profiles and display them at the usernode. Per default there is a link to each nodeprofile of a user on its usernode. You can theme the usernode to even show the full profile(s).
So the usernode is in particular useful in conjunction with nodeprofiles if you need a page for each user, where you want to present the available data. Usernode comes with a template.php, which adapts user links to point to the usernodes instead of user/USER_ID. If your theme has already a template.php file, just put the functions of usernode's template.php into yours.

Another use case of usernode is building user listings or user searches with views. Due to the automatically created nodefamily relation between your usernode content type and your nodeprofiles, it's even possible to include profile data into these user listings or searches. To do this, you need to use the views fusion module. Have a look at Step 4, which explains how the views fusion module can be used. The only difference is that one of your fused views has to list the usernode content type.
So e.g. if you have a view "profile" that lists your nodeprofiles, create another view "usernodes" which lists nodes of the usernode content type. Then create a fusion, e.g. use the usernode view as primary view fused with the profile view using the nodefamily parent-child relation.

More about: Node Profile and Node Family

This section will begin with useful links to help you find the right documentation. At some stage, this page should probably be split into two but at the moment it seems that the available case-studies are discussing about using these modules together.

More about: Usernode

The usernode module tries to make users nodes. It cares for automatically creation and deletion of a node for each user, the so called usernode.

It's not needed for building nodeprofiles, however it's still useful. Read Step 5 from the Getting Started tutorial, for an introduction, how usernodes play together with nodeprofiles.

As usernode creates a content page for each user you can use it for creating full featured user accounts and user profiles. Community members often use full featured user accounts and profiles as a way to express themselves within a community which is a major factor in their participation within an online community.

The usernode can take advantage of the full flexibility of the modular content system such that usernode can be integrated with comments, taxonomy, and other extensible modules. Usernode can also be used in user listings with the views module.

You can:

  • set the edit own username permission at administer >> access control.
  • visit your usernode through the My usernode menu item link in the navigation menu.
  • create a list of usernodes using the views module.
  • use it for presenting multiple nodeprofiles to the public
  • file issues, read about known bugs, and download the latest version on the usernode project page.

If you want to use it be sure to read the README.

Using usernode instead of nodeprofile

Important note:
This page is about using usernode instead of nodeprofile for building profiles as nodes. It is not about using usernode together with nodeprofile for building profiles as nodes.

With CCK 5.x you are able to add further fields to every content type, so also to the usernode content type. So some users like to use this instead of a simple nodeprofile. However, it can be only used if you want to build a simple profile consisting of one content type - the usernode.

The major difference of this approach vs using the nodeprofile module is that there will be usernode created as soon as the user is created. So you can't determine easily which users have filled out their profile and which have not.

Another problem is with required fields. If the usernode has required fields added to it, they will not be filled in when the node is automatically created. If an administrator needs to edit a person's usernode, they will be forced to fill in all required fields for the user in order to save the node.

So if this approach suffices for you, then there is no cause why you shouldn't use it. But if you use the nodeprofile module you'll earn much more flexibility:

  • different profiles for different roles
  • multiple profiles per user
  • more extensible profiles by having the possibility to add further content types
  • support for newly added features (e.g. workflow-ng integration)

So I prefer to use the nodeprofile module, but decide on your own what is fitting best to your needs.

User submitted tutorials

This is a collection of useful tutorials related to the Node Profile family of modules. Thanks to all who have contributed.

Tutorial: Creating a simple one user = one profile page custom profile

UPDATE JUNE 6: I had forgotten about this handbook page. My original plan was to redo this page to take into account the changes in nodeprofile's latest version. Instead, I ended up writing a tutorial on a pretty feature filled user profile which you can see on my site.

As for this page, it's still applicable for versions of nodeprofile before April 29. As of the April 29 release, there are new features that make it easy to add the profile to the user view page and also to the registration screen. It's worth just reading the module docs and trying it if all you want is a super simple profile.

*** END UPDATE NOTE ***

This tutorial will walk you through step by step in creating a customizable user profile using the NodeProfile family of modules. The resulting profile is one node and does not make use of the more advanced features such as multi page profiles using pageroute.

There are two node types involved in this tutorial:

Usernode - This node type is created by the Usernode module. The module takes care of automatically generating one usernode per user. This node contains no content and only serves to link a user to a node. You can find out more about this in the Usernode readme.txt file.

User Profile - This node type is created by us using CCK. When we create this node type, we tell the Nodeprofile module that this is a nodeprofile node. These nodes are not automatically created and must be created for each user. In the tutorial, we will take this node and embed it inside of the usernode.

Gather the required modules

Download and install these modules: usernode, nodeprofile, nodefamily, cck. Read the readme.txt files of the first 3 carefully as they will help you understand what each piece does.

Make your user profile content type

Create a new CCK content type. Name should be "User Profile" and Type should be "uprofile". (You can use different names, but you'll need to make changes throughout the tutorial.) Delete the text from "body" unless you want to use that field. Check the box labeled "Use this content type as a nodeprofile for users" and leave the "Maximum population" at 1.

This is the node that will become the profile. You can add any fields to it that you want, such as fields for stats (age, gender, etc), fields for interests (books, tv, movies, etc), and more. Exactly how you customize this node is up to you.

Bind the usernode and the User Profile

The usernode module automatically creates a usernode for every user, but it does not automatically create a User Profile (nodeprofile). Once the User Profile is created (more on that later), we need to bind the two nodes together to create the effect of having an automatic profile for every user. The checkbox you did in the last step associates your User Profile node type with the usernode. Next, we need to force the automatically created usernode to display the User Profile (if one exists). To do this, create a new text file named "node-usernode.tpl.php" in your theme dir. Paste into this text file the following code:

<?php
 
// Redirect the /usernode link to the actual usernode so we get the tabs on top
 
if (arg(0) == 'usernode') {
   
drupal_goto('node/'.$nid);
  }
 
 
// Load and display the User Profile, if there is one, otherwise display a message
 
$children = nodefamily_relation_load($nid);
  if (empty(
$children)){
    print
"This user has not yet created a profile.";
  } else {
    print
node_view($children[0]);
  }
?>

Once this code is in place, you can go to www.example.com/usernode and it will load up the usernode for the currently logged in user. Inside of the usernode is your custom User Profile, if there is one. If the user hasn't created a profile, the message "This user has not yet created a profile." will be displayed instead.

Make the edit tab work

If a user looks at his own usernode, there will be a "view" and an "edit" tab at the top. At this point, the "edit" tab tries to edit the usernode and not the User Profile that we stuck inside it. There are various ways of fixing this, including getting rid of the edit tab and putting the link on the menu instead, but I used the following hack for to make the edit tab work:

Open up your page.tpl.php in your theme and find where it prints out the $tabs. Themes vary, but it should look something like this (but all on one line):

<?php
if ($tabs):
?>
<?php
print $tabs
?>
<?php
endif;
?>

Immediately above that line, put in this code:

<?php

// This bit fixes the edit tab so it goes to the nodeprofile instead of trying to edit the usernode
if ($node->type == 'usernode') {
 
$tabs = preg_replace('/\/node\/[0-9]+\/edit/','/nodefamily/uprofile',$tabs) ;
 
$tabs = str_replace("Edit","Create or Edit Profile",$tabs);
}
?>

What this does is take the "/node/##/edit" path in the tabs and replace it with "/nodefamily/uprofile". This is a special URL that takes you to the edit page of the User Profile of the logged in user. The handy thing is that this link will also take you to the create page if your profile doesn't exist. So you have one tab that does both, which makes it easier for the user. To make this clearer, the above code also changes the name of the link from just "Edit" to "Create or Edit Profile".

Note: If you have edit rights on nodes created by other users, this link will be wrong for everyone but you as it goes to the logged in user, not the user that goes with the node. For users who can only edit their own nodes, this isn't an issue, and so this hack works as long as any of your super users are aware of it. If you want to edit someone else's profile, you need to go to "/nodefamily/uprofile/#UID" where #UID is the ID of the user you want to edit the profile for.

Fix the "name" link

When a user makes a post, their name on the post is automatically linked to their account info. If you would rather have that link take you to their user profile, you can use some code that comes with the usernode module to fix it. Look in the module directory for the file template.php. If your theme doesn't already have one, you can simply copy the file. If you already have one, you'll need to copy the contents of the file into your existing one. You should just be able to put it at the end as long as your existing template.php doesn't use the same functions. Once you've done this, go back and click on the person's name and their usernode (with the embedded User Profile) will come up.

Further thoughts

You now have an automatically created usernode that will show the person's profile once it's created. You can enhance this with normal CCK node theming methods. Because of usernode, you can also use views to search profiles. This tutorial only covered one simple case. Check out the rest of the docs for advanced usage.

Node Vote: A node voting system

The Node Vote module rovides the ability for users to vote on various node, assigning a score to each one. The average overall score and number of votes are displayed below each node.

The module is useful in many situations, for example, to rate articles, forum posts, stories.

Several blocks are provided to display top voted for nodes, top scored nodes, and top voting users.

Permissions are used to determine which roles can vote, as well as which roles can see the results.

Voting can be limited to one or more node type (e.g. image, page, ...etc).

You can:

Notify: email notification of new site content

The notification module allows users to subscribe to periodic emails which include all new or revised content and/or comments much like the daily news letters sent by some websites. Even if this feature is not configured for normal site users, it can be a useful feature for an administrator of a site to monitor content submissions and comment posts.

The administrator sets the frequency of the emails in the notify administration interface. They can also set how many email failures should occur before notify stops sending notifications. Note that cron must be enabled for notifications to be sent out.

You can

Organic Group Block Visibility: Blocks for specific groups

This module allows you to specify that a block should be visible only within a selected group. This can be used for many purposes, from providing each organic group with its own navigation menu, to allowing a group to highlight its own featured content, etc.

This module requires the organic groups module and requires block administration privileges. This module updates a block's PHP visibility settings. It is _not_ compatible with any other type of block view restriction. Blocks will show up publicly for open or moderated groups, and be
visible to members only for invite-only or closed groups.

You can:

  • read the organic groups module handbook page.
  • enable the module from administer >> modules.
  • configure block visibiliity for a group at administer >> blocks.

Organic Group Block: Block for recent comments, event, and weblink

The organic group block allows groups to have three types of block. Block for recent comments, events, and weblinks.

The event block can be a list of events or a calendar block.

You can:

Organic Group Forum: Private forum for a group

The organic group forum module creates a forum container for that group, with a single sub-forum. The forum can be restricted to be only seen by members of that group. Forums for group members are very useful to maintain privacy of a groups discussions as it begins to organize and accomplish the groups mission.

A category term for the group is created to tie the organic group with it's forum container.

You can:

Organic group mandatory group: a group for new users

The OG mandatory group module requires the Organic Groups module and adds on additional features. This module makes one group mandatory for all new users. This is useful if you want all users to feel like they are part of a community and should have a group to start with. This is also useful if you are using the access control features of the Organic Groups module and you wish to restirct the content associated with the mandatory group to only registered site memebers.

Choose one group that all new users will be put into. You can chose either an open group where users can unsubscribe. You can also choose a closed group where users can not leave. The user will be auto-approved as member of the group.

As a separate feature (as of October 7, 2006), this module will allow you to require new users to select at least one of the organic groups listed in the registration form. This feature is independent of, and in addition too, any mandatory group.

To use this module you should first have the Organic Groups module installed and be familiar with its use and features. Read the organic group module handbook page.

After installing and activatinng this module, select an open group or select a closed group at administer >> settings >> og_mandatory_group as your mandatory group.

To download this module, submit a bug report, or for more information visit the OG Mandatory Group project page.

Organic group menu: add subscriptions to menu

The organic group menu module adds subscription links to the organic group menu. The menu includes user insert, user update, and user deletes.

Organic Group Promote: assign roles by group

The Organic Group Promote module promote users that join certain groups to a special role. This is useful for managing organizational roles on a website such as web editors who have special roles and can collaborate using an organic groups.

To use this module you should choose one role that all users who join the groups you select will be promoted to. Should a user leave that group, she will be demoted again.

You can:

Organic Group Stores: A store for every group

The Organic Groups Stores module allows organic groups to each have a store. This is very useful for groups that produce products that are branded for each group or that are work products of groups.

This module creates a store and puts that in the moderation queue. When the store is moderated, it creates a term out of its title. We call this step "the mall owner approves the store". When a product is created and it belongs to an organic group, it is tagged by the relevant store tag. This way, you can simply click on "Store for foo group" and see the products for that group.

You can:

  • create, update, and delete a store for an organic group.
  • click on the store link in the organic group menu block
  • complete the store creation form

Organic Groups Book: A book for every group

The organic group book module provides a book for each group. This provides structure content for groups to get groups.

You can:

Organic Groups CiviCRM: Integrate groups with CiviCRM

The OG_CiviCRM module integrates organic groups with CiviCRM groups. This is useful for groups that form organically on the web but need to be tracked for an organization. Once the group of web users are in CiviCRM they can used to do mailings, track address information, apply tags, and track activities of the members.

The module creates a new CiviCRM group for each new OG and use the module to keeps track. The groups are kept synchronized. If a user is subscribed to a group via Drupal's UI, then the user has already been subscribed on the Drupal side, and we will need to subscribe the user on the CRM side here as well. If an organic group is deleted the CiviCRM group is deleted. This module works with CiviCRM 1.4.

You can:

Organic Groups List Manager: An integrated mailing list for organic groups

The organic groups list manager (OG2List) module allows groups of users to have a mailing list managed as part of their organic group, a collection of users with common interests and shared content. Mail on the mailing list is archived in a forum and groups members can respond by email or they can respond in forums directly. Mailing list are one of the most effective and popular methods for group collaboration using the Internet.

OG2List uses the organic groups and forum module for content and member management. Forums are connected with the OG_forum module and comment and comment mover module are used for managing comments as responses to the mailing list. Access control is managed through the use of categories, or taxonomy to restrict content to group members only. The mailing list require a functioning cron to periodically send email.

You can:

  • Reserve mailing list addresses, such as root, postmaster, admin, webmaster, abuse, which users cannot request at administer > > settings > > og2list.
  • Specify the list address for a group at create content > > group.
  • Turn mailing lists on/off for each group under the "list" menu for any given group
  • Create comments to to any mailing list enabled group and have a copy of the comment sent to the group members email addresses.
  • Send an email to the list address for any given list to have the mail made into a group forum post.
  • Reply to an email from the list to create a comment to the original node in the group.
  • Configure moderation for posts and digests for users.

Site walkthough for a OG2List site.

Required Modules

Core modules to be enabled

  • taxonomy
  • forum
  • comment

Contributed modules to be installed and enabled

  • OG
  • OG2List
    • gjg_group_tabs
    • og_forum
  • MailCommand
  • event
    • basicevent
  • comment_mover
  • AJAX

Demo Script:


  1. Create a group
  1. Visit the demo site and log in as the user provided to you or create an
    account by the usual means.

  2. Click the "Create a Group" link in the header.
  3. Enter a group name and email address, and fill out the rest of the form
    with pertinent values.
  4. Click on "View Groups"
  5. Click on your group
  6. Click on "members", "messages" tabs to see the function-ability.
  • Adding people
    1. Click the "invite friends" link on the "home" tab to invite users to the
      group via email.
    2. You can also subscribe users to a group by sending "subscribe <group
      name> <friends email>" in the subject or body of an email to
      administration@<testsite>.com
  • Making a post
    1. Click on the "messages" tab.
    2. Click on "create new forum topic"
    3. Enter title and body for topic and select the "General Discussion" forum
      in the drop-down.

    4. Press submit.
  • Responding via email
    1. Go to the GMail account (the user provided to you) and open the message
      that was just created.

    2. Reply to the message, and press Send.
    3. Note the message appears as a comment to the original post within 2
      minutes.
  • Email Commands

    1. Send a message to administration@demo-site.com with help in the subject.
    2. MailCommand will email back with a list of options within 2min.
    3. --expand--
  • Moving comments (as administrator)
    1. Go back to the Community Messages tab and click on the node with the
      comment you just created
    2. Click on the "move" link below the comment.
    3. Click the "Create forum post" checkbox and Submit.
    4. Note the comment is moved to a new node.
    5. In this new node click the "move" tab above the node title.
    6. In the "Move below other post:" box, enter the name of the original
      post, noting that AJAX will auto-fill the data for you.

    7. Submit and note that the node has been returned to the original thread.

Organic groups moderate: moderate group posts

The organic group moderate module allows for public posts from the organic groups module. Moderated posts will be hidden from anonymous visitors. Moderation needs to be done by node administrators or another module.

Organic Groups Views: create views of organic group data

The Organic Groups views module provides integration between organic groups and views module. It is capable of custom versions of group directory and group home page.

The Organic Groups views module allows you make views tables of: group description, group website, subscriber count, whether to list it in a directory of groups, posts in user subbed groups.

Organic Groups: Enable users to create collaborative groups

Organic groups enables users with permissions to create and manage their own groups. Community members often want to self organize or spontaneously organize around a topic of interest. Allowing communities to organize naturally is important part of a healthy community.

An organic group is created by a single group owner, who has special permissions including the ability to delete the group the owner created. Group admistrators also have special permissions but can not delete the group unless they are assigned to be the group owner. Group subscribers communicate amongst themselves using the group home page as a focal point. They do so by posting the usual content types: blog, story, page, etc. A block is shown on the group home page that links to these group specific posts and actions. The block also provides summary information about the group.

Groups may be selective or not. Selective groups require approval by the group administrator in order to become a member. Organic groups also support private groups which will not be displayed in a list of organic groups. You cannot use this module with other node access modules.

You can:

Integration with CiviCRM(Community Relationship Managment)

This section describes issues involved in integrating the Organic Groups module with CiviCRM.

Organic Groups contributed modules comparison

Module name Content Action Permissions Navigation
Organic
Groups
Node types Restricts to members only,

create and delete permissions for admins
Organic
Groups Block Visibility
Block visibility per group Blocks per group
Organic
Groups Blocks
Event, Recent Posts,
Organic
Groups Book
Book
Organic
Group CiviCRM
Mirrors CiviCRM group contacts with OG members Create, Synchronize, Delete

 CiviCRM group
Organic
Group Forum
Forum container, forum Forum
Organic Group Gmap Map
Organic
Group Mandatory Group
Join new user group
Organic
Group Menu
Subscribe, Number of users,

 other links to OG Block
Organic
Group Moderate
Moderate
Organic
Group Promote
Promote
Organic
Group Store
Store Create, Update, Delete Store adds store link to OG block
Organic
Group Views
Create views of Organic Group content
Organic
Group to List
email, mailing list, forums, forum posts
Organic
Group Roles
Content types and

theming restricted to

certain OG roles
Group admins have ability to assign configurable site
wide roles to group members
Provide UI for site admins so

group admins can assign roles to special group types
Organic
Group Taxonomy
Primary taxonomy term  per group with secondary

taxonomy terms
Organic Group Tabs

( GJG_Group_Tabs)
Adds tabs to OG interface <br>for each content type.
CiviNode Provides API for CiviCRM smart groups for partial
integration with OG groups
Allow for auto subscription between groups without
mandatory Drupal user and CiviCRM contacts
GoJoinGo
- group events service
Adds file upload for groups, group forum, group events
and venues
Group, events Search events by location, Group tabs
Group
Block
Configurable .inc file Group name, group Description, Group image, Name of
organizer, Group location(City, State), # of members
Node
Access Arbiter
Not supported. Use Drupal 5 instead.
Image_attach Allows headers per group

Organic Groups recipe for http://groups.drupal.org

Drupal core, views, viewfeed, gmap (and maybe location) - future, og / og_block, event, casetracker (maybe for future, maybe for DEPs), cck (not currently used, maybe for custom groups), OG2List is desirable.

This page was created by Kieran Lal of CivicSpace, if you are interested in developing more detailed documentation for organic groups and documenting groups use cases please contact me.

Useful posts for configuring OG

this is another interesting discussion "Relocalization Network beta launch, fork of organic groups"
http://drupal.org/node/22902

also some work towords "Show all content for a particular group (Organic Groups)?"
http://drupal.org/node/26559

this might be of some use "OG - Tweaking the UI for very large numbers of groups"
http://drupal.org/node/28224

sure wish we could sort search results on drupal.org *sigh*

Travis

Pathauto: generate URL path aliases automatically

The Pathauto module creates automatic path aliases for nodes, users, and category terms, eliminating the need to create them manually. This way, your site is more user and search engine friendly, and more descriptive about its content with less work from your side.

The aliases are generated when you create an object in your site and are based upon the pathauto patterns (placeholders) you specify at Administer > Settings > Pathauto(4.7) or use Administer > Site Configuration > Pathauto(5.x). Pathauto comes with several default patterns that provide automatic aliases for users, user blogs, vocabularies, taxonomy terms, menus and core content types such as story, page, book etc.

You can also:

Pathauto Pattern Recipes

There are frequently requests for how to make certain patterns of URLs for pathauto.

Basics of Patterns

In general the patterns are a form of variable substitution. So, when you see the default node pattern like [title] that will be replaced with the title of the node. This system should hopefully be intuitive to most users. A blog post with the title "my blog post" would be created with the alias www.example.com/my-blog-post

Filtering and Transliteration

When a pattern is replaced with the actual text there is some filtering and transliteration that may occur. In Pathauto 5.x transliteration is controlled by the existence of the i18n-ascii.txt file. An example file is provided with the download. You can customize this file for your site and then upgrade easily - the version of the file in Pathatuto has .example. in the title so it will not over-write your local changes. If you do not transliterate them to western-ascii then Pathauto currently replaces them with the separator value though in the near future that should be an option.

Static text

You can get a little bit more advanced and use static text in your pattern. For example, you could enter a pattern for all blog posts like blogs/[user]/[title]. This would automatically create all "blog" posts with a prefix of "blogs" followed by the username of the user who posted the item and then the title of the post. Under this pattern my example blog post would get the alias www.example.com/blogs/greggles/my-blog-post

Advanced - Integration with Views

As you can see from the static text example, there is now a concept of directories in my blogs/greggles/my-blog-post alias. Pathauto provides an index alias feature but this is deprecated and will likely be removed in future versions. Instead you can use views to provide the same functionality. For example, if you create a view that has the page url "blogs" and that shows the teasers of all blog posts your site will now handle the www.example.com/blogs. If I then add an argument to that view for "Username: user is author" and select "Display all values" as the default I have created "index aliases" for my pathauto patterns. A site visitor who goes to www.example.com/blogs/greggles/ would now see a list of teasers that were created by greggles. You can take this example even further to support the [yyyy] pattern to filter to a specific time period.

Dangerous patterns

It should also be noted that there are "Dangerous" patterns that can be used in Pathauto. There is a handbook page which provides more details these Dangerous Pathauto Patterns. The short version is this: you should generally put a prefix on your paths that has no ability to conflict with Drupal reserved paths/callbacks/directories.

Share your pattern recipes

Feel free to add your own patterns by creating a child page. List your patterns and describe the motivation for doing them and/or how you feel they benefit your site.

Pathauto Tips and Hints

Before using pathauto, pay some attention to the fields under 'General settings' at ..admin/settings/pathauto. Configuring those settings can make life a little easier when pathauto starts generating aliases based on the node types or nodes you create.

Here are a couple of tips to begin with:

  • Verbose option
    Turn this on to have pathauto print out messages to the screen each time an alias is generated. This will help you keep track of the aliases being generated and alert you to potential bugs and errors in the way pathauto is functioning.
  • Update action
    Turn on the 'Create a new alias, replacing the old one' radio button instead of the default 'Do nothing, leaving the old alias intact'. This can prevent a lot of confusion and save you additional work in many circumstances.

For example, let's say you are creating a vocabulary with a hierarchical taxonomy structure of one or many parent term(s) and many child term(s) under each parent. It is all too common to forget to choose the right parent for the child term(s) you are creating. If the 'Update action' setting is set to the 'Do nothing...' setting, pathauto will NOT generate a new alias even after you edit the child term(s) to have the right parent.

Preventing a Content Type from Getting Aliased

If you want to keep a certain content type from having aliases, you should remove the Default Path Pattern in the Node Path Settings section, and then implicitly specify something for all the content types except the one you wish to ignore. In other words, clear the default field and your problems will go away.

Starting Over - Clearing your Aliases Completely Or Selectively

In the 4.7 and 5.x-1 versions of Pathauto it is possible to regenerate aliases for a certain object type using the "bulk update" feature and setting your update action to "create new alias replacing the existing one". That would allow you to recreate all aliases on the site. This was somewhat problematic in 4.7 and 5.x-1 becuase the bulk update function might not finish running on a large site.

As of Pathauto 5.x-2 you have two new options and one new behavior.

Deleting all aliases or a subset of aliases

You can now delete all of the aliases in your system by visiting Admin >> Site Building >> URL Aliases and then selecting the "Delete all aliases" tab. After confirmation, this will delete all aliases in your database.

Additionally you can selectively delete items by running a "delete" query against your database.

For example, a query like the following will delete all node objects:

DELETE FROM url_alias WHERE src LIKE 'node/%';

Using more advanced query syntax you can selectively delete nodes of a certain type, or by a certain user, etc. This system can be helpful to regenerate all of the aliases after a user's name has changed or some other pattern has changed rendering their aliases no longer valid. While it could be useful to provide this feature in the admin page, the number of possible delete queries that might be useful would quickly grow to being arbitrarily large and irreducibly complex (i.e. too hard).

Bulk Update only non-aliased objects

The new behavior is that Pathauto will only attempt to generate aliases for objects which have no alias. If a node is aliased, Pathauto will no longer try to alias it again. This can lead to problems if you add feed aliases or tracker aliases after an object has already been aliased. In that case you should delete the objects alias and bulk update them.

Performing bulk updates in configurable chunks

You can now specify the number of objects to attempt to alias in each bulk update. This, combined with the new behavior of only updating non-aliased objects, means that a aliases can be regenerated for all objects on a site even if you have millions of objects. Note that if you have a limit of 50 objects per bulk update but also have feed aliases enabled that you should get 100 new aliases generated per bulk update even though only 50 objects were aliased (i.e. 2 aliases per object).

By deleting aliases and then regenerating them with a configurably sized bulk update you can reliably fix a variety of problems that may exist on your site.

Pathauto Patterns that can be Dangerous

Certain patterns can be harmful or dangerous for your site.

Problematic index aliases

For example, certain situations where you create a pattern like forum/[title] and you have "Create Index Aliases" turned on pathauto will then create a page at example.com/forum/ which over-rides the default example.com/forum page. In order to fix this problem you can clear out that entry from the url aliases either via the GUI at example.com/admin/path or via the database table. A better path to create would be forums/ or discussions/ so that the names don't collide.

Problems due to transliteration

Another problem occurred when a user created an alias pattern with user/[user] for his users. Then a user registered with a name that contained characters that were not in the translation table so Pathauto created an alias at user which broke the "My Account" page.

Problems due to a lack of a prefix

You can also run into problems using an alias that may not get any prefix. So, for example, if you leave the default node alias of [title] and then a user creates a node with the title "admin" you will get an alias at www.example.com/admin/ which points to the node instead of the administration panel. Similar problems could occur with [cat]/[title] aliases and a post in the "admin" category titled "settings". The opportunities for problems are enormous.

Recommendation to avoide dangerous paths:

To avoid these problems you should create aliases that have start with static text that is not a reserved drupal path/callback/directory. For example: user aliases should probably start with users/ so that they cannot conflict with the user/ set of paths.

Create Alias Anyway After the message "Ignoring alias {alias_name}"

Pathauto is starting to prevent users from creating aliases that conflict with existing system aliases (callbacks). When it does this Pathauto will output a message: "Ignoring alias {alias_name} due to existing path conflict"

There are times, however, when you may want to create the alias anyway. In order to do so you will have to create the alias manually using the path module.

The standard way to do this is the same way that you would do it for all other paths: in administer > url aliases.

For nodes, this can be achieved in a second way: in the node iteself using the URL Path setting of the node.

For more information on manually creating aliases read the Path Module Documentation.

Pathauto causing Fatal error: Call to undefined function: ctype_alnum()

On certain systems the ctype functions are not installed and therefore the use of Pathauto can result in the following error:

Fatal error: Call to undefined function: ctype_alnum() in /htdocs/www/modules/pathauto.module on line 250

The line number may be different depending on your version of pathauto but the basic error is the same.

The solution to this problem depends on your platform, but the basic resolution is to install the ctype functions.

FreeBSD Solution:

As root (or sudo) from the command line you need to install the php5-ctype port.

portinstall -R php5-ctype
SuSE

The package is also named php5-ctype, and can be installed using YAST.

Other Platforms

Advice for other platforms is welcomed to be added in comments, though most platforms have this installed by default.

You can see this issue from the Pathauto queue for more information and othe rpossible solutions.

PayPal Subscription: Grant roles for users who pay

The PayPal Subscription module automatically grants roles to users who pay through PayPal.

You can:

PHPList: develop and maintain an audience

The PHPList module allows for advanced mass mailing. You can create an audience, capture an audience, and maintain an audience. You should use a mass mailer like PHPList when you have to send a lot of emails that can not be sent by your mail client and must be queued up for your server.

PHPList allows for open tracking, click tracking, bounce handling, HTML mailing, subscription management, preference management.

You can:

  • enable the mass mailer module to use the PHPList engine
  • include images using the format of single quotes and no http protocol <img src='www.civicspacelabs.org/home/themes/internal/images/get_civicspace.png'>
  • read about known bugs, and download the latest version on the PHPList CVS page.

Playlist: group and order media files into a list

This module is actually two projects, a general playlist relationship API, and an audio_playlist.module which makes use of the API.

Audio_playlist.module

This module makes use of the playlist.module API. It is a simple node module that defines a new playlist node type called "audio playlist". This name can be changed, however, to podcast, album, or similar on the module's settings page.
Podcast (RSS), XSPF, PLS, and M3U feed generation is taken care by this module.

Features:
1) iTunes podcast/xspf/m3u/pls feeds generated on the fly, with full metadata support
2) album artwork can now be integrated (through URL)
3) xspf flash players for each feed, including popup players for each
4) audio browser, similar to iTunes, used to search audio tags and find the audio to add to playlists
5) upload new audio files to playlists on the fly with an inline uploader
6) listening station: an xspf flash player block that has a dropdown select to listen to different playlists on the site
7) Cut n' paste HTML and javascript includes for integrating the flash player on other sites
8) lots of customization options

Playlist.module

Seeking to generalize the functionality of all playlists, a common toolkit will hopefully make it easy to build new playlist modules by re-using common code.

The playlist.module provides:

  • database schema that can be used by multiple playlist types.
  • API for getting in and out of the database
  • theme_sort() that provides a drag/drop interface for managing the order of files. If JS is not enabled, up/down arrows will appear.

This module doesn't do anything by itself. For an example of how to use it, see the audio_playlist.module.

Postcard: send an e-card by email

Postcard lets you select a photo from your site, add a message and then send an email to the recipient to view their card. Postcard relies on Image module for all of the image handling and organization.

This module requires the Image module to be installed and both image and image_gallery to be enabled.

You can

  • select a gallery or galleries to use for postcards and set the default letter text at administer >> settings >> postcard.
  • access the postcard main page at http://example.com/?q=postcard.
  • read about the required Image module
  • file issues, read about known bugs, and download the latest version on the Postcard project page.

Privatemsg: an internal messaging system

The private messaging module allows users to send messages to each other without having to share email addresses. An inbox link will appear in the navigation menu. The "write to author" links are included in posts, allowing users to write a private message instead of commenting openly. Allowing users to communicate directly is an important part of building the strength of the community.

Users can also select whether to receive email notices of new messages by editing their user profile. The contacts list contains only users that you have previously messaged. To contact users not in your list, you need to know their local user name. Administrators can set messaging options such as frequency of emails, message status display, and number of messages to display per page. They can also configure 'Write to Author' options.

You can

How to notify online users of a new private message

[Updated Sep 14 2006]

The module will send an email when you have a new private message, but it's nice to have an on screen notification as well. The following snippit can be placed in your theme. Mailbox icons can be found on the web or you can delete that portion of the snippit.

*WARNING* If you decide to disable the private message module, be sure to take this out of your theme as it will not find the function and will error.

<?php
 
global $user;
  if (
$user->uid) {
   
$numnew = _privatemsg_get_new_messages($user->uid);
    if (
$numnew > 0) {
    print
l(theme('image', path_to_theme().'/newmail.gif'),'privatemsg',
        array(
'title' =>'You have ' . $numnew . ' new message(s)'),NULL, NULL, NULL, TRUE);
    }
  }
?>

(I think this worked as is in 4.6 as well but no longer have that installed to confirm)

Block method (thanks Steel Rat):
Create a new block with your message and use the PHP visibility option with this code:

<?php
 
global $user;
 
$newmess = FALSE;
  if (
$user->uid) {
   
$newmess = _privatemsg_get_new_messages($user->uid) > 0;
  }

  return
$newmess;
?>

How to send a notification email when a PM is sent.

Note that this functionality is now built in to the module (as of Jan. 26, 2007). Users can now go in their preferences and choose whether to receive no email, a daily email or an email for every privatemsg.

For this I had to edit the PM module file. I did not like the way that it sent messages via cron only once a day.

I added the following to the top of the module file.

function sendnotify($recipient) {

$from = variable_get('site_mail', ini_get('sendmail_from'));
$subject = t('New private messages at %site.', array('%site' => variable_get('site_name', 'drupal')));
$message = t('Hi %name,
This is an automatic reminder from the site %site. You have %new unread private messages.

To read your messages, follow this link:
%link1

', array('%name' => $user->name, '%site' => variable_get('site_name', 'drupal'), '%new' => $alert->c, '%link1' => url('user/login', 'destination=privatemsg', NULL, 1), '%link2' => url('user/'. $user->uid .'/edit', NULL, NULL, 1)));

user_mail($recipient->mail, $subject, $message, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from");
}

and then I added the function call sendnotify($recipient) in the function called function _privatemsg_edit($edit) I added this in the else block of the privatemsg_edit function the code for the entire function is below.

function _privatemsg_edit($edit) {
  global $user;

  if ($edit['recipient'] == '') {
    form_set_error('recipient', t('The <em>Recipient</em> field is required.'));
    return _privatemsg_form((object)$edit);
  }
  else {
    $recipient = user_load(array('name' => $edit['recipient']));
  }
 
  if (!$recipient->uid) {
    form_set_error('recipient', t('The <em>Recipient</em> does not exist.'));
    return _privatemsg_form((object)$edit);
  }
  else if (!(isset($recipient->privatemsg_allow) ? $recipient->privatemsg_allow : 1)) {
    form_set_error('recipient', t('%name does not accept private messages.', array('%name' => $recipient->name)));
    return _privatemsg_form((object)$edit);
  }
  else if (trim($edit['subject']) == '') {
    form_set_error('subject', t('The <em>Subject</em> field is required.'));
    return _privatemsg_form((object)$edit);
  }
  if (trim($edit['privatemsgbody']) == '') {
    form_set_error('privatemsgbody', t('The <em>Message</em> field is required.'));
    return _privatemsg_form((object)$edit);
  }
  else if (array_key_exists('format', $edit) && !filter_access($edit['format'])) {
    form_set_error('format', t('The supplied input format is invalid.'));
    return _privatemsg_form((object)$edit);
  }
  else {
    $result = db_query("INSERT INTO {privatemsg} (author, recipient, subject, message, timestamp, newmsg, hostname, format) VALUES ('%d', '%d', '%s', '%s', '%d', '%d', '%s', '%d')", $user->uid, $recipient->uid, $edit['subject'], $edit['privatemsgbody'], time(), 1, getenv("REMOTE_ADDR"), $edit['format']);
    drupal_set_message(t('Message sent.'));
sendnotify($recipient);
    drupal_goto($user->uid ? 'privatemsg' : '');
  }
}

This code has not been thoroughly tested I have tested it a couple times and it works and really is kind of simple to the point where I dont see why it wouldnt work or that it would break. You should also get rid of the email notification in the cron function since it would no longer be needed. Let me know how this works, this is my first module hack.

-Kyle

Project: a rich set of tools for (software) project management

The Project package is a set of modules that together provide a rich range of functionality for organizing and carrying out collaborative projects, with a focus on software development.

The Project module delivers a content type, the "project". Projects can have releases (downloadable files) associated with them, and can be categorized and browsed in various ways (by name, date, category). This is the functionality behind the downloads pages for drupal.org.

Projects can have issue tracking associated with them by enabling the project_issue module http://drupal.org/project/project_issue), and can be integrated with a versioning system by enabling the cvslog
(http://drupal.org/project/cvslog) or subversion (http://drupal.org/project/subversion) modules.

The project module is currently being maintained by Derek Wright.

Projects

The project module is the central piece of the project package. It defines the project content type and provides custom ways of browsing projects.

Installation and setup

Begin by installing the module as you would any other--put the files in place, go to the Administer > Site building > Modules page, and select the Project module. Note that Project creates its own section on the Modules page, since there is a collection of related Project modules.

Permissions

The following set of permissions controls access to projects and project releases.

access own projects
For users who don't have the general access projects permission, this permission lets them access their own projects and project releases (ones they created).
access projects
Access projects, e.g., browse listings and bring up project pages and project release information.
administer projects
An overall permission that allows users to set project and project release configuration as well as create and edit all projects.
maintain projects
Allows users to create and edit their own projects and project releases
Setting up project categories

If you're wanting to use categories with your projects, here are the steps.

First, enable the core Taxonomy module.

The Project module automatically creates a "vocabulary" called Projects. To use it, though, you need to add categories (or "terms").

Start at Administer > Content management > Categories and click the "add terms" link for the Projects vocabulary.

The first level of terms you create here will become your project types. For example, on drupal.org, these terms are Modules, Themes, and so on.

By creating a second level of terms, you enable categorization within that project type. So if you want to allow users to categorize modules as either "mail" or "utility", and themes as either "blue" or "green", wanted a third type, "widgets", that didn't have subcategories, you could create a set of terms in the Projects vocabulary as follows:

  • Modules
    • mail
    • utility
  • Themes
    • blue
    • green
  • Widgets
Configuring project settings

Access the project settings page at Administer > Project administration > Project settings.

If you have the forum module enabled, you'll see an option to select a forum for discussion of projects. If you select one, project listings will get a link to a support forum.

Projects can be browsed and sorted three ways: by category, by name, and by date. These sort options will appear to users as tabs that can be clicked to bring up a listing of projects.

The "Default sort option" setting controls which tab is the default one when browsing projects. So, if you wish browsing to default to categories, select categories here.

For each project type, you can select which of these browsing options to enable using the "Enabled sorting options" settings.

The category setting is relevant only if you've created second-level terms for a particular project type. Using our sample project vocabulary, above, the categories for browsing Modules would be "mail" and "utility". You might choose to enable browsing by category for Modules and Themes, but not for Widgets, since they don't have categories defined.

When browsing projects, some project listings (category, date) are paged, meaning that only a set number of projects are displayed per page, with links to see the rest. You can control the length of these lists with the "Number of projects to list in paged browsing" setting.

Project releases

Projects can have releases associated with them through the Project releases module, which ships with the Project download.

Project releases are identified by a version number and can have downloadable files associated with them.

Permissions

Project releases don't have their own set of permissions but rather depend on the permissions defined in the Project module. Users with "access projects" permission can access project releases. Users with "maintain projects" permission can create and edit their own projects. Users with "administer projects" permission can administer project settings.

Installation and setup

Access the Project release settings at Administer > Project administration > Project release settings.

Version numbers

Version numbers are composed of a set of "elements". What these are will differ from site to site depending on the approach being taken. However, the drupal.org system may serve as a useful model for understanding how version numbering can be implemented. For details, see http://drupal.org/handbook/version-info#contrib.

Creating project releases

Although they are their own content type, project releases aren't created in the usual way (via the Create content menu item). Instead, to create a release you first bring up the project's page. From there you can click the "Create new release" link.

Project issues

Project issues module provides an issue tracker for projects, including email subscriptions, follow-up comments, and customizable status options.

Enabling issues for projects

Enable releases for a project in two steps. First, create the project. Then bring it up again in edit view. You'll see a tab for "issues". Click this to bring up the issue configuration page for this project. Here you can select whether to enable issues, provide a list of "components" that issues can relate to, provide a message for users submitting issues, and also set a number of options about how issue posts and follow-ups are mailed out.

Queue: moderation, collaborative rating

The queue module allows community leaders to moderate content to meet the challenges of information overload. This assists users in identifying the most interesting, worthwhile, valuable or entertaining items.

Administrators can set the number of responses required before a post is promoted to the front page. They can also set whether comments are published. Administers can also set conditions for posts to be unpublished.

You can

Moderation queue

Anyone who visits and has some news or some thoughts they'd like to share, can submit new content for consideration. After someone has submitted something, their node is added to a queue. All registered users can access this list of pending nodes, that is, nodes that have been submitted, but do not yet appear on the public front page. Those registered users can vote whether they think the node should be posted or not. When enough people vote to post a node, the node is pushed over the threshold and up it goes on the public page. On the other hand, when too many people voted to drop a node, the node will get trashed.

Moderation depends upon activation of the queue module. Users need the "access submission queue" before they are able to access the submission queue.

Comment rating

Anyone with a user account will be able to moderate comments. This lets people assign a score to a comment on how good they think the comment is or how visible they think it should be.

When more than one person rates a comment, the overall rating is just a simple average of all ratings. Comments with high ratings are more visible than comments with a lower rating. That way, comments that gain the approval of participants will gradually move up through statistical effects and pointless comments will sink into oblivion.

Hence, the purpose of comment moderation is two-fold:

  • To bring the really good comments to everyone's attention.
  • To hide or get get rid of spam, flamebait and trolls.
In the latter, comment moderation provides a technical solution to a social problem.

RelatedContent: assemble teasers at the end of a node

RelatedContent is a Drupal module that allows privileged users to assemble teasers at the end of a node. The teasers are selected from a list of nodes. The list is provided by a view from the Views module. The number of nodes and the view to get them from are configurable for each node type.

Background

It is very common for websites to plug for material on the site by presenting abstracts, which in Drupal idiom is called "teasers". There are many great solutions for doing this in Drupal: taxonomy, views and queues, to mention a few. In spite of these possibilities, there is still need for an alternative way of compiling teasers to be shown. In this section, a quick review of some possibilities are given, and the need for an alternative is explained in the context of an actual use case.

Special pages with teasers are provided out of the box for nodes that are categorized by the core Taxonomy module. If your needs are more elaborative, you can compile a such page (or block) yourself with the wonderful Views module. In both cases, you cannot directly select which teasers to show. Instead you are reduced to setup conditions for the viewed teasers to meet.

To directly select teasers to show, you can resort to the Node Queue module. With the Node Queue module, you can create a "queue", which is a named set of references to nodes. You add references by visiting the nodes one by one or en masse by using the Node Queue Builder module. Once created, you can embed a PHP snippet into a node or a block to view teasers of the nodes referenced by a particular queue.

Although powerful, the Node Queue approach is not always suitable. To understand why, consider the cause for writing the RelatedContent module in the first place:

The Simplenews module can be used to provide newsletters for visitors to subscribe to. The module provides a particular content type called "Newsletter issue". When editing a newsletter issue, the editor has a single text-area for the content. It is suitable for self-contained newsletters. However, it cannot easily be used to accomplish newsletters made up of an introductory text followed by teasers to articles already published on the website. RelatedContent was developed to extend Simplenews (and other content types) with this possibility.

So why is Node Queue less appropriate for this particular purpose? There are at least two reasons:

First, it would be necessary to write PHP code in order to pull out teasers from the nodes referenced by a queue. Probably the editor of a newsletter issue is not a programmer, and hence not comfortable with (or versed in) writing PHP code. It is also a security risk since the editor must have a role with the privilege to use the PHP code input format.

Second, and a little more subtle, but nevertheless a problem, is the fact that changes in a queue is propagated to wherever the queue is used. For a newsletter to not change on the web after it has been published, the queue must not be altered. The implication is that it is necessary to setup a unique queue for each issue, which is not feasible in the long run.

Both these problems are overcome by using the RelatedContent module.

Requirements

Installation

Install RelatedContent as follows:

  1. Download, install and configure the Views module, following the instructions for that module.
  2. Download the archive file with the latest stable version from RelatedContent project page
  3. Unpack the downloaded archive into your Drupal's modules directory.
  4. Verify that the modules directory contains a relatedcontent directory with relatedcontent.module and other files.
  5. Go to admin/build/modules and enable the module.

Configuration

RelatedContent is configured for each content type individually. By default, it is turned off. To enable RelatedContent for a particular content type, do as follows:

  1. If not already existing, go to admin/build/views and add at least one view that filter nodes to be used by the RelatedContent.
  2. Go to admin/content/types/<type>, where <type> is the name of the content type, e.g. page or simplenews, and locate the RelatedContent settings.
  3. In the pull-down menu named RelatedContent view, select the view that that filter nodes to be used by the RelatedContent. Leave empty to disable RelatedContent.
  4. In the pull-down menu named Max number of nodes, select the maximum number of nodes to be available for the RelatedContent.

Usage

When creating or editing a node of a content type with RelatedContent enabled, select nodes to be shown as follows:

  1. Locate and open the collapsible section called Related content.
  2. Check the checkboxes to the left of the nodes whose teasers are going to be shown.

Theming

The RelatedContent module provides following themable functions:

  • theme_relatedcontent($body, $nodes), where
    • $body is a string with the already themed body of the node itself, and
    • $nodes is an array with nodes whose teasers are to be themed and appended to the body.

    The default implementation returns $body concatenated with a sequence of

    <div class="relatedcontent-nodes $type">
      <h3>$type</h3>
      $nodes
    </div>

    where $type is the name of a content type, and $nodes is all nodes of the named content type. Each node is styled by node_view().
  • theme_relatedcontent_form_alter_node($form), where
    • $form['nodes'][$n] is the form element for the checkbox,
    • $form['title'][$n] is the form element for the title,
    • $form['name'][$n] is the form element for the type,
    • $form['created'][$n] is the form element for the create time,
    • $form['username'][$n] is the form element for the author, and
    • $n is the nid of the node in question.

    The default implementation returns the themed table used to select nodes whose teasers are going to be shown.

See also RelatedContent project page.

Rep[lacement]Tags

The Rep[lacement]Tags module allows you to replace tags (like $MYTAG$ or {DATE}) with user-defined content. It implements two different mechanisms to perform replacements:

1. User-/sidewide tags are managed from the reptag admin pages. Users with appropriate permission can easily add/delete/modify these tags online.

2. Module tags are hardcoded in extension modules. They offer an easy but powerful method for more sophisticated site-specific tags. RepTag comes with several .tags modules for general use cases.

RepTag also provides role-based rights management, online help, support for CCK textfields, integration with other modules and a basic API.

Read more on the project page (and in the documentation).

RoleAssign: delegate assignment of selected roles

RoleAssign specifically allows site administrators to further delegate the task of managing user's roles.

RoleAssign introduces a new permission called assign roles. Users with this permission are able to assign selected roles to still other users. Only users with the administer access control permission may select which roles are available for assignment through this module.

Background

It is possible for site administrators to delegate the user administration through the administer users permission. But that doesn't include the right to assign roles to users. That is necessary if the delegatee should be able to administrate user accounts without intervention from a site administrator.

To delegate the assignment of roles, site administrators have had until now no other choice than also grant the administer access control permission. But that is not advisable, since it gives right to access all roles, and worse, to grant any rights to any role. That can be abused by the delegatee, who can assign himself all rights and thereby take control over the site.

This module solves this dilemma by introducing the assign roles permission. While editing a user's account information, a user with this permission will be able to select roles for the user from a set of available roles. Roles available are configured by users with the administer access control permission.

Install

  1. Copy the entire roleassign directory, containing the roleassign.module and other files, to your Drupal modules directory.
  2. Log in as site administrator.
  3. Go to the administration page for modules and enable the module.

Configuration

  1. Log in as site administrator.
  2. Go to the administration page for access control and grant assign roles permission to those roles that should be able to assign roles to other users. Notice that besides the assign roles permission, these roles also must have the administer users permission.
  3. Go to the administration page for role assign and select those roles that should be available for assignment by users with assign roles permission.
  4. For each user that should be able to assign roles, go to the user's account and select a role with both the assign roles and the administer users permissions.

Usage

  1. Log in as a user with both the assign roles and the administer users permissions.
  2. To change the roles of a user, go to the user's account and review the assignable roles and change them as necessary.

See also RoleAssign project page.

RSVP: invite people

The RSVP module lets users invite people by email to events and track a list of people who will be attending. The RSVP module requires the event module because it is necessary to have an event to invite people to first.

The RSVP page shows a your invites and a your RSVPs tab. There are confirmation screens for creating and editing RSVPs. Email addresses which are input for RSVP have input validation. RSVP also creates an invitation url by hash value access so that users can click a URL and be taken directly to their invitation. For each RSVP there are view, edit, manage, and send tabs. Users can manage attendees through the manage attendees tab. Users can also send attendees a message through the send message tab.

You can

  • enable the RSVP module at administer >> modules.
  • not administer the RSVP module.
  • create an RSVP for an event by clicking the create RSVP on the bottom left of the event.
  • view your invites at your invites tab.
  • view your RSVPs at your RSVPs tab.
  • view, edit, and invite more attendees for each RSVP
.

To file issues, read about known bugs, and download the latest version on the RSVP project page.

Scheduler

Scheduler module

Indtroduction

This module allows nodes to be published and unpublished on specified dates.

If JSCalendar is enabled (part of the JSTools module ), a popup Javascript calendar is used to select the date and time for (un)publishing of nodes, otherwise it defaults to text input.

Installation

  1. Copy the scheduler.module to your modules directory.
  2. Enable module, database schemas should be setup automatically.
  3. Grant users the permission "schedule (un)publishing of nodes" so they can set when the nodes they create are to be (un)published.
  4. Visit admin » settings » content-types and click on any node type and check the box "enable scheduled (un)publishing" for this node type.
  5. Repeat for all node types that you want scheduled publishing for.

Usage

For node types that have been enabled for using Scheduler the node edit page has an extra callapsable fieldset. In this you will find:

  • Publish on
  • Unpublish on
  • Timezone

Publish on: Enter here the date that you would like the posting to be published. Leave this blank to disable automatic scheduled publishing. This is useful if you would like to make a posting published immediately but then unplublished at a later date.

Unpublish on: Enter here the date that you would like the posting to be unpublished. Leave this blank if you do not want the posting to be automatically unpublished.

Timezone: Set this to the timezone that you would like the above dates to apply relative to.

Notes

  1. If you have JSCalender (part of the JSTools module) installed and enabled, then the publish and unpublish dates can be entered with the JSCalender pop-up
  2. Scheduled automated publish and unpublish functions occur via the cron system so the actual, real world, publishcations times will be aligned to when your cron system runs. For example, if you enter a publsihed time of 3:45pm but cron only runs once an hour on the hour then the real publish time will be 4:00pm and not 3:45pm

Themeing tips and tricks

Altering a Drupal core form theme function to add additional information

This handbook page was inspired by this feature request to the Scheduler module:

Under Administer > Content Management> Content

Please provide a listing under the "STATUS" column which would indicate "Not Published/Scheduled" instead of just "Not published".

It would be even better it said "Scheduled to publish on {date and time}"

The problem here is that the form (inside a table) is generated by Core (specifically node_admin_nodes() and it's associated theme function theme_node_admin_nodes($form) in modules/node/node.module). Although hook_form_alter() would allow a module to alter a form, this page describes how it can be done within the theme layer which is more appropiate to the requested change).

The key to solving this problem in the theme layer is in theme_node_admin_nodes($form). This tells us the table form is themeable (in fact, all forms are themeable, you just need to find the form_id string) and this is where we can get at the status text to amend.

This first snippet (which you would place inside your theme's template.php file) is a good start and shows how you can override a form theme function:

<?php
function phptemplate_node_admin_nodes($form) {

  if (isset(
$form['title']) && is_array($form['title'])) {
   
$nids = element_children($form['title']);
   
$sql = 'SELECT nid FROM {scheduler} WHERE publish_on > %d AND nid IN (%s)';
   
$r = db_query($sql, time(), implode(',', $nids));
    while (
$row = db_fetch_array($r)) {
     
$scheduled_nids[] = $row['nid'];
    }
    foreach (
element_children($form['title']) as $key) {
      if (
in_array($key, $scheduled_nids)) {
       
$form['status'][$key]['#value'] .= '/'. t('scheduled');
      }
    }
  }

 
// Chain to Drupal's core theme function
 
return theme_node_admin_nodes($form);
}
?>

If you wanted to go that extra step and add Scheduled to publish on {date and time} then this would be what you need:-

<?php
function phptemplate_node_admin_nodes($form) {

  if (isset(
$form['title']) && is_array($form['title'])) {
   
$nids = element_children($form['title']);
   
$sql = 'SELECT nid, publish_on FROM {scheduler} WHERE publish_on > %d AND nid IN (%s)';
   
$r = db_query($sql, time(), implode(',', $nids));
    while (
$row = db_fetch_array($r)) {
     
$scheduled_nids[$row['nid']] = $row['publish_on'];
    }
    foreach (
element_children($form['title']) as $key) {
      if (
array_key_exists($key, $scheduled_nids)) {
       
$form['status'][$key]['#value'] .= '/'.
         
t('scheduled to publish on ') .
         
date(variable_get('date_format_short', 'm/d/Y - H:i'), $scheduled_nids[$key]);
      }
    }
  }

 
// Chain to Drupal's core theme function
 
return theme_node_admin_nodes($form);
}
?>

I have tested both these theme override functions and they work fine. However, a minor issue/problem with these, especially the second one, is that the Status column only has so much width. That second option widens the column a lot. If you can live with it, fine. Otherwise, have a play and develop your own shorthand.

Notes

Both these theme override functions conatin db_query(). What you have to ask yourself here is "I am prepared to live with database functions inside my theme override functions?" It's normal not to do this and there is an alternative. If we did away with the SQL/database part of this snippet we could just use node_load(array('nid' => $key)) in the foreach() loop. This would make the ->publish_on variable available and we could use that instead. However, consider a content table with 30 nodes. That would mean 30 additional SQL queries to the database. The method used above uses one SQL query and makes use of the SQL IN operator. It's faster and more efficient, but to use it, you have to live with DB calls in your theme function (which isn't the end of the World, it does work just fine).

(The snippets above aren't perfect Drupal coding standards as I made sure the line lengths were not too long so they display on Drupal.org ok).

Sections: assign themes to sections of your site

This module allows you to create sections. Each section has an installed template, theme or style attached to it. Each section also contains a path setting similar to the new blocks admin. You can then assign themes to a list (regexped) paths.

For example, if you want another style for your site admin, all you have to do, is create a section with:
name: Adminstration Section
path: admin*
and select (a custom made) theme "admintheme" to that section.

You can:

admin section for an edited site

A lot of sites have only one or two editors, and let users only comment or not give any feedback or input at all. This simple set of regular expressions allows you to use an 'admin' theme for all the administrative tasks.

admin*
user*
node/add*
node/*/edit
node/*/track

Services: An API for remote applications

Services is a standardized api for Drupal that allows you to create "services", or a collection of methods, intended for consumption by remote applications. It works similar to the existing XMLRPC capabilities of Drupal, but provides additional functionality like:

  • pluggable "server" modules allowing for other protocols (like SOAP, REST, AMF)
  • pluggable "service" modules allowing developers to add additional remote services
  • a service browser and method tester
  • api key security
  • remote session handling
  • a number of included service modules which interact with existing Drupal modules like node, taxonomy, user, views, and system

Who might be interested in Services?

  • Drupal developers wanting to add a Flash or Flex app to their site, and are looking for a way to get data from Drupal.
  • Flash or Flex developers looking for the best backend CMS system.
  • Anyone else looking to integrate external applications with Drupal.

Services API Documentation

This section is for developers interested in creating service or server modules to extend the capabilities of Services.

Overview of Services
Overview

Services is comprised of 2 components:

  • services: Modules which contain functions intended to be invoked from remote.
  • servers: Modules which provide a connection protocol, such as XMLRPC.
Hooks

services

  • hook_service(): This is where you map your services and methods to functions. You can provide help text and argument details for each method, to be used in the service browser.

servers

  • hook_server_info(): Specify information about your server.
  • hook_server(): Converts an incoming request into a Drupal function call and then converts the result back into the appropriate return format.
Server Module Example

Here is a commented example of a server module which uses hook_server() to become a server.

<?php
/**
* Implementation of hook_server_info()
* required to let services know that this is a server.
* returns a hashed array
*/
function xmlrpc_server_server_info() {
  return array(
   
// #name - display name used in the Services admin pages
   
'#name' => 'XMLRPC'
   
   
// #path - the path (under '/services/') where the server will
    // handle calls.  This one will be '/services/xmlrpc'
   
'#path' => 'xmlrpc'
 
);
}

/**
* Implementation of hook_server()
* The callback function to handle all requests to the path defined above.
* This is required
*/
function xmlrpc_server_server() {
 
// load in any additional libraries needed
 
require_once './includes/xmlrpc.inc';
  require_once
'./includes/xmlrpcs.inc';
 
 
// handle the request, and return the result of the request
 
return xmlrpc_server(xmlrpc_server_xmlrpc());
}

/**
* Implmentation of hook_xmlrpc
* Specific to this xmlrpc_server module
* this is how we map an xmlrpc request to the Services module
*/
function xmlrpc_server_xmlrpc() {
 
$callbacks = array();
 
 
// we find all the services available
 
foreach (services_get_all() as $method) {
   
$args = array();
   
   
// convert the args to be compatible with the Drupal xmlrpc server
   
foreach ($method['#args'] as $arg) {
      if (!
is_array($arg)) {
       
$args[] = $arg;
      }
      else {
       
$args[] = $arg['#type'];
      }
    }
   
   
// and map each method's callback to the xmlrpc_server_call_wrapper()
    // function. This is explained below.
   
$callbacks[] = array(
     
$method['#method'],
     
'xmlrpc_server_call_wrapper',
     
array_merge(array($method['#return']), $args),
     
$method['#help']);
  }
  return
array_merge($defaults, $callbacks);
}

/**
* XMLRPC callback for each service method
* Specific to this xmlrpc_server module
*/
function xmlrpc_server_call_wrapper() {
 
// we get the server object
 
$xmlrpc_server = xmlrpc_server_get();
 
 
// and the method name
 
$method_name = $xmlrpc_server->message->methodname;
 
 
// and the args
 
$args = func_get_args();
 
 
// and pass this to the services_method_call() function returning the result
  // services_method_call() wraps all service method calls.  Its provides the
  // additional handling of things like api keys and session IDs.  It take a
  // method_name, like "recipe.all" as its first argument, and the method args
  // as its second argument.
 
return services_method_call($method_name, $args);
}


?>
Service Module Example

Here is a commented example of a custom service module that uses hook_service() to become a service.

<?php
/*
* Implementation of hook_service()
* Required by all server modules
* Returns array defining all the methods available in the service
*/
function recipe_service_service() {
  return array(
   
   
/**
     * recipe.all
     * We define methods in hashed arrays
     */
   
array(
     
     
/**
       * #method - defines the namespace and method name
       * the namespace is everything before the last period, so you can do
       * methods like 'recipe.lunch.all' where 'recipe.lunch' is the namespace,
       * or service, and 'all' is the method
       */
     
'#method' => 'recipe.all',
     
     
// #callback - the php function to map the method call to
     
'#callback' => 'recipe_service_all',
     
     
/**
       * #args - a list of method arguments
       * These may be in lazy form - array('string','array') with only an array
       * of datatypes.
       * Or, they may be in in the form of an array of hashed arrays like shown
       * below:
       */
     
'#args' => array(
        array(
         
         
// #name - the name of the argument
         
'#name' => 'fields',
         
         
// #type - the datatype of the argument
         
'#type' => 'array',
         
         
/**
           * #optional - the argument is optional, true or false
           * Because php functions cannot have a required argument after an
           * optional argument, arguments after an optional argument are set
           * to optional regardless of the value of the #optional hash
           */
         
'#optional' => true,
         
         
// #description - Used in the service browser
         
'#description' => 'A list of fields to filter.'
       
)
      ),
     
// #return - The return data type, may be used by certain server modules
     
'#return' => 'array',
     
     
// #help - Used in the service browser
     
'#help' => 'Returns a list of recipes'
   
)
   
  );
}

/**
* Callback for "recipe.all"
* We need to include the fields argument and set a defauld value because
* it is optional,
* We do not need to include an API key or SESSID field if these are enabled
* for Services.  These arguments are handled by Services tranparently and
* stripped before we reach this callback.
*/
function recipe_service_all($fields = array()) {
 
$result = db_query("SELECT nid FROM {node} WHERE type='recipe'");
 
 
$nodes = array();
  while (
$node = db_fetch_object($result)) {
   
// services_node_load filters a node and returns only the requested fields.
   
$nodes[] = services_node_load(node_load($node), $fields);
  }
 
 
// return the array result
 
return $nodes;
}
?>
Service module example: Echo service

This service can be useful as an example, and as a test to make sure the services are functioning. This module simply replies with the message it is sent.

<?php
/**
* Implementation of hook_help().
*/
function echo_service_help($section) {
  switch (
$section) {
    case
'admin/help#services_node':
      return
t('<p>Provides echo methods to services applications. Requires services.module.</p>');
    case
'admin/modules#description':
      return
t('Provides echo methods to services applications. Requires services.module.');
  }
}

/**
* Implementation of hook_service()
*/
function echo_service_service() {
  return array(
   
// echo.echo
   
array(
     
'#method'   => 'echo.echo',
     
'#callback' => 'echo_service_echo',
     
//'#auth'     => false,
     
'#return'   => 'struct',
     
'#args'     => array(
        array(
         
'#name'         => 'message',
         
'#type'         => 'string',
         
'#description'  => t('The message to return.'),
        )),
     
'#help'     => t('Returns an object containing a sessid and user.'))
     
  );
}

function
echo_service_echo($message) {
 
 
$return = new stdClass();
 
$return->sessid = session_id();
 
$return->message = $message;
 
  return
$return;
}
?>

To install, in your modules directory make a directory named echo_service and install the above file as echo_service.module. Then put the following in that directory as echo_service.info. Then go to your Administration >> Modules and enable the Echo Service that shows up.

; $Id$
name = Echo Service
description = Provides echo services, such as if you want a ping ability to make sure the service is alive.
package = Services - services
dependencies = services
;version = "$Name: DRUPAL-5 $"
; Information added by drupal.org packaging script on 2007-02-18
;version = "5.x-1.x-dev"
project = "services"

Services Screencasts

Because its so much more fun to watch than to read!

Screencast 1 - Overview and Creating a Custom Service

This screencast gives you an overview of the Services module, and then goes on to code a custom service module.

Watch it now!

Screencast 2 - Flex Recipe Application

This screencast gives you a little taste of just how powerful Drupal and Services can be as a backend to Flex applications.

Watch it now!

Services Tutorials and Examples

The following tutorials and examples will help get started with Services.

Example: Accessing a service from Flash 8

Accessing exposed services from Flash8 Professional is easy, but some basic setup is required. Below the setup is sample code scraped right out of a thread in the Services group with one minor modification: I added a bit more to getData_Result to dump the retrieved node.title and node.body as well as peppered with comments for the less-adventurous.

  1. Install and configure Services in Drupal. Note: I found that I had to enable Access Services to anonymous users in Admin>>UserMgmt>>Access Control. In Services config, I also turned off API key and Session Key.
  2. Download and install the required Flash Remoting components:
    http://www.adobe.com/products/flashremoting/downloads/components/
  3. Launch Flash 8, create a new movie, open the newly installed libraries: Click Window>>CommonLibraries>>Remoting. Drag both components onto the stage (which adds them to the movie's library) and delete from the stage.
  4. Put the following actionscript code in frame 1 of your flash movie:
    // Picking up a Drupal node with flash remoting
    // Drupal must be set up with Services module enabled
    import mx.remoting.Service;
    import mx.remoting.PendingCall;
    import mx.rpc.RelayResponder;
    import mx.rpc.FaultEvent;
    import mx.rpc.ResultEvent;
    import mx.remoting.debug.NetDebug;

    // expose debugging info to clientside "NetConnection debugger" utility
    mx.remoting.debug.NetDebug.initialize();

    // create, position, and set params on three dynamic text fields: status, node-title, and node-body
    var tf:TextField = this.createTextField("status", 10, 0, 0, 200, 200);
    var ntitle:TextField = this.createTextField("nodetitle", 11, 0, 50, 200, 200);
    var nbody:TextField = this.createTextField("nodebody", 12, 0, 100, 200, 400);
    ntitle.html = true;
    ntitle.multiline = true;
    nbody.html = true;
    nbody.multiline = true;
    nbody.wordWrap = true;

    // establish connection with remote service gateway in Drupal and specify service
    // - /services/amfphp is AMFPHP in Services gateway
    // - node is one of the two default, exposed services in Services module.  The other is view.
    var node:Service = new Service("http://sitedomain/services/amfphp", new Log(), "node", null, null);
    // call service method
    // - load is one of three methods on the default node service
    // - pass in which node to load (hard coded "1" here for node 1)
    // - optional additonal param to node.load is what fields to return (like in a view?).
    // -- array of field names: node.load(1, ['title', 'body']);
    // -- default is to send the entire node.  This example does that.
    var pc:PendingCall = node.load(1);
    // set up response handler functions
    pc.responder = new RelayResponder(this, "getData_Result", "getData_Fault");

    // set default status text
    tf.text = "no response from server yet.";

    // success result handler
    function getData_Result( re:ResultEvent ):Void {
    // re is the object containing the response
    // two properties you can count on being populated for any node are re.result.title and re.result.body
    tf.text = "response:";
    ntitle.htmlText = "<b>Node Title:</b><br/>" + re.result.title;
    nbody.htmlText = "<b>Node Body:</b><br/>" + re.result.body;
    }

    // failure result handler
    function getData_Fault( fe:FaultEvent ):Void {
    tf.text = "error";
    }
  5. CTRL+ENTER to test the movie

The above sample calls the "load" method on the service "node" with a hard-coded parameter of "1" for "node/1". The node service and its methods (load, save, delete) are exposed by the installation of the Services module. Any other methods you wish to call need to be written a Services hook (see the Services handbook).

Example: Accessing a service from Groovy or Java

The Groovy language bundles XMLRPC service in the download. That, and the fact Groovy is a dynamic scripting language, makes it an interesting language to use for XMLRPC applications.

This example uses the echo service elsewhere in this handbook.

On the serverProxy line enter the correct URL.

Go into the Administration >> Services section and click on the Keys tab. Click on Create Key, enter anything for the Application Title and leave the Domain blank. Then paste the value you're given into the value for apiKey.

Then for userName and password enter appropriate values.

In the line saying serverProxy.echo.echo enter any message you want to have printed.

Run the script using the command line: groovy echo.groovy

import groovy.net.xmlrpc.*

def serverProxy = new XMLRPCServerProxy("http://...your server URL .../services/xmlrpc")

def apiKey = ..apiKey..
def userName = ..user name..
def password = ..password..

def res = serverProxy.system.connect(apiKey)

def sessid = res.sessid

serverProxy.user.login(apiKey, sessid, userName, password) {
    serverProxy.echo.echo(apiKey, sessid, "Hello, world").each() { println it }
}
Example: Flex Recipe App MXML

This file shows you how you can connect to a Drupal service to get a list of recipes. It assumes a few things:

First that you have Drupal and all of the required modules and libraries installed:

Drupal is setup in this way:

  • Clean urls are enabled
  • AMFPHP 1.9 Beta is extracted and placed inside of the AMFPHP module, in a folder called "amfphp"
  • There is a content type called recipes, this needs to be created
  • There is a view called "recipes_all" which shows a list of recipes.

With the following modules enabled:

  • Services
  • AMFPHP
  • Views Service
  • Node Service
  • Views and Views UI

Services is setup with:

  • API keys off
  • Session IDs off

And your Flex project is setup:

  • as a basic project
  • with an the additional compiler argument: -services "services-config.xml"
  • and a file called "services-config.xml" in your project root, copied from amfphp.org and with the endpoint uri changed to "/services/amfphp"

Other mentions

  • You must be logged in as the administrator in the same browser you are testing Flex in to be able to save and add new posts

Here's the MXML:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
  <mx:Script>
    import mx.controls.*;
    import mx.rpc.events.*;
    import mx.utils.ArrayUtil;

    [Bindable]
    public var recipes:Array;

    public function init():void
    {
      getRecipes();
    }

    public function onFault(event:FaultEvent):void
    {
      Alert.show(event.fault.faultString, "Error");
    }

    public function onViewsResult(event:ResultEvent):void
    {
      recipes = ArrayUtil.toArray(event.result);
    }

    public function getRecipes():void
    {
      views.getView("recipes_all", ['nid','title','body','changed']);
    }

    public function saveRecipe():void
    {
      var edit:Object;
      if (recipes_select.selectedItem) {
        edit = recipes_select.selectedItem;
      }
      else {
        edit = new Object;
      }

      edit.type = "recipe";

      edit.title = dish.text;
      edit.body = recipe.text;

      if (edit.title == "" || edit.body == "") {
        Alert.show("Enter some content", "Error");
      }

      node.save(edit);
      getRecipes();
    }

    public function onSaved(event:ResultEvent):void
    {
      Alert.show("Recipe was saved", "Saved");
    }

    public function newRecipe():void
    {
      recipes_select.selectedItem = undefined;

      dish.text = "";
      recipe.text = "";
    }
  </mx:Script>

  <mx:RemoteObject showBusyCursor="true" destination="amfphp" source="views" id="views">
    <mx:method name="getView" result="onViewsResult(event)" fault="onFault(event)" />
  </mx:RemoteObject>

  <mx:RemoteObject showBusyCursor="true" destination="amfphp" source="node" id="node">
    <mx:method name="save" result="onSaved(event)" fault="onFault(event)" />
  </mx:RemoteObject>

  <mx:Panel width="500" height="400" layout="absolute" title="Recipes" horizontalCenter="0" verticalCenter="0">
    <mx:DataGrid x="10" y="10" width="460" id="recipes_select" dataProvider="{recipes}" >
      <mx:columns>
        <mx:DataGridColumn headerText="NID" dataField="nid" width="40"/>
        <mx:DataGridColumn headerText="Dish" dataField="title"/>
      </mx:columns>
    </mx:DataGrid>

    <mx:Label x="10" y="160" text="Dish"/>

    <mx:TextInput x="10" y="186" width="460" id="dish" text="{recipes_select.selectedItem.title}"/>

    <mx:Label x="10" y="216" text="Recipe"/>

    <mx:TextArea x="10" y="242" width="460" height="75" id="recipe" text="{recipes_select.selectedItem.body}"/>

    <mx:Button x="416" y="328" label="Save" click="saveRecipe()"/>

    <mx:Button x="420" y="158" label="New" click="newRecipe()"/>

  </mx:Panel>
</mx:Application>

Signup: manage users event registration

The Signup module allows users to sign up for events and for the signups to events to be managed. This is useful for organizing members in a community and keeping them notified of their event status. It also helps event managers by closing attendance to events, and restricting attendance to certain roles.

The Signup module allows users to sign up (or register, as in register for a class) for posts of any type. Includes options for sending a notification email to a selected email address upon a new user signup (good for notifying event coordinators, etc.) and a confirmation email to users who sign up--these options are per post. When used on event nodes (with event.module installed and regular cron runs), it can also send out reminder emails to all signups X days before the start of the event (per node setting) and auto-close event signups 1 hour before their start (general setting). Settings exist for resticting signups to selected roles and content types, and any single node can be enabled for signup 'on the fly'. Administrators can look at both a signup detail for one post, and a signup summary for all nodes.

You can:

Simplenews Template: themable newsletters

Simplenews Template is a Drupal module that extends the Simplenews module by providing a themable template with configurable header, footer and style. Header, footer and style are configurable for each newsletter independently.

Simplenews Template can with advantage be used in conjunction with RelatedContent.

Requirements

Installation

Install Simplenews Template as follows:

  1. Download, install and configure the Mime Mail module, following the instructions for that module.
  2. Download, install and configure the Simplenews module, following the instructions for that module.
  3. Download the archive file with the latest stable version from Simplenews Template.
  4. Unpack the downloaded archive into your Drupal's modules directory.
  5. Verify that the modules directory contains a simplenews_template directory with simplenews_template.module and other files.
  6. Go to admin/build/modules and enable the module.

Configuration

There is no configuration for the Simplenews Template module itself.

Usage

Header, footer and style is setup for each newsletter individually as follows:

  1. If not already existing, go to admin/content/newsletters/types and add at least one newsletter.
  2. Go to admin/content/newsletters/settings, and select the newsletter to be configured.
  3. Locate and open the collapsible section called Header.
  4. Fill out the text area with the content to be shown at the beginning of each issue of the newsletter. Leave blank to not include a header. Do not forget to choose appropriate input format.
  5. Locate and open the collapsible section called Footer.
  6. Fill out the text area with the content to be shown at the end of each issue of the newsletter. Leave blank to not include a footer. Do not forget to choose appropriate input format.
  7. Locate and open the collapsible section called Style.
  8. Fill out the text field called Body background color with any valid HTML color value, e.g. #ff00ff and fuchsia.
  9. Fill out the text area called CSS with style sheet rules valid within the HTML tags <style type="text/css">...</style>, e.g. div.message { color: red }.

See also Simplenews Template project page.

Site Documentation

This all started with a post on the DO forums asking ``I was wondering if there are any modules that output the entire Drupal setup and document the modules and configurations.'' That got me started thinking... Very dangerous for a blonde!


The Site Documentation Module

Why Do I Need It?

Unfortunately Drupal does not include the powers of immortality or invincibility. You may someday get hit by a truck, or even just decide that you no longer want to maintain the site. So someone else may have to take over.

Where do they start? I know you documented the site really well, but the next person doesn't want to read those 14 binders of documentation you left behind. They can always go through all the administration screens and look for the information, but that can take a lot of time and even those pages don't tell you everything. Some things you're only going to get by looking directly into the database.

This is what the Site Documentation module is designed to do. In addition, it will detect some problems that may exist in your installation, and optionally correct some of them.

What Does It Do?

To simplify it a bit, the Site Documentation module picks up information from various places within the Drupal environment. Some of the information comes from internal arrays, some is derived from system calls, and some comes directly from the database tables. As one might imagine, this gathering does not come without a price; you can expect a spike in database and CPU activity while this module runs. Therefore, one should be careful how often and when it is run.

The Site Documentation module presents the following summarized information:

  • Basic Drupal summary - lists some basic information about your Drupal installation.
  • Database summary - list all tables and pertinent information.
  • Sequences Summary - displays the last id of various table entries.
  • Node Summary - lists how many of all content types are in the database, along with status information.
  • Node Access Summary - checks the node_access table to see if all nodes are represented, and includes the ability to see which nodes are in which realms. (Unlike Devel Nodes Access, this does not require any other modules like Views.)
  • Content types - detailed list of content type settings.
  • Vocabularies - shows all the defined vocabularies (taxonomies) and all the terms within them, along with usage counts and other settings.
  • Modules - lists all modules that are known to the Drupal system, even if not enabled. This list is much like that of the Modules Admin page (I borrowed some of that code), and includes dependencies.
  • Themes - lists all the available themes, whether enabled or not.
  • Theme_Engine - shows which theme engines are available.
  • System Variables - lists all variables defined by all modules, along with their content.
  • Blocks - shows a complete list of the blocks that are defined, whether enabled or not. It will also check to see if the theme that owns them exists any longer; if not, a warning message may be issued.
  • Boxes - this shows additional information for manually created blocks.
  • Roles and Permissions - lists all user roles along with their access permissions, blocks that are restricted to the role, and may include all users assigned to the role.
  • Contacts - displays all email contacts for the site.
  • Profile Fields - shows custom user profile fields used on this site.
  • URL Aliases - shows URL Aliases that are defined along with broken and duplicate links. The alias numbers and node IDs are hyper linked to ease corrections.

I had discovered some time ago, that the ``blocks'' table can have rows defined for themes that were tested and discarded. This module allows you to delete them if you would like. If any are found, a warning message will be issued, and if you don't choose to delete them, there will be a SQL statement at the end that you can copy and paste into phpMyAdmin.

In my testing, I discovered that I had rows in my ``term-nodes'' table that belonged to nodes that no longer existed. So I added a check and a delete option that you may choose to run. If you check and don't delete, there will be a SQL statement at the end that you can copy and paste into phpMyAdmin.

Another problem that cropped up in my creating this module was a long-running / never-ending Cron job. This module will now highlight that and gives you an option to fix it.

Installation

The Site Documentation module installs in the standard manner. I put it in the ``sites/all/modules'' directory so that it's available to all of my sites.

The module provides a menu entry in the Administration >> Site building section to execute it according to the current settings. The settings menu entry is in the Administration >> Site Information section; the settings are saved in a system variable as an array (a model I hope other module developers will copy).

The install routine merely logs a message that the module was installed. The uninstall function deletes the system variable, clears the cache, and logs a message.

Module Settings

Unfortunately, one size does not fit all, so I've allowed you some options to control the output of this module.

  • Include Basic Drupal information?
    • Delete Cron variables after - specifies how long Cron is allowed to run before the module deletes the variable ("cron_semaphore") that indicates Cron is running.
  • Include Table Summary?
    • Release overhead? - allows you to specify whether or not to issue an Optimize query on the table if there is overhead.
  • Include Sequences Summary?
  • Include Node Summary? - (SQL intensive)
    • Show nodes exceeding: - allows you to specify that nodes in excess of this many KB (kilobytes) should be listed.
    • Note: if you don't want this function, set the number very high (e.g. 999999).

    • Warn if nodes exceed: - gives you a warning line in the report if nodes exceed this many KB (kilobytes).
    • Include Node Access? - (SQL intensive)
    • Include comment count on nodes? - includes the total comment count in the Node Summary.
  • Include System Variables? - (CPU intensive)
  • Include Module summary?
  • Include Content Type summary?
  • Include Vocabulary summary? - (may be CPU and SQL intensive)
    • Check for orphan Term_nodes? - (may be SQL intensive)
    • Delete orphan term nodes? - If you use the ``Check for orphan Term_nodes'' section, do you want any that are found deleted.
  • Include Themes and Theme Engines?
  • Include Blocks and Boxes?
    • Warn on missing theme for blocks? - This allows the module to display a warning message if a block exists for a missing theme.
    • Delete the orphan blocks? - Delete the blocks with a missing theme.
  • Include Roles and Permissions?
    • Show role permissions as a list? - If not chosen, the roles will be shown as a stream.
    • Show list of users for each role? - Displays the list of users assigned the role.
  • Include Contacts?
  • Include Profile Fields?
  • Include URL Aliases? - (SQL intensive)

Archive Options - The Site Documentation module may be run via Cron and the output HTML file archived to disk for future review. The options chosen above will govern the data collected and reported.

  • Archive frequency - The module will run at the next scheduled Cron run after this period has elapsed. A zero value indicates that running will be done manually only.
  • Archive directory - This is where the module will place the archive file. The current date will be appended to the file name. If it runs more than once a day, a counter will be added to produce a unique name.

In the settings form, you may leave the indented items checked even if you choose not to run their parent sections.

Styling (CSS)

The distributed CSS includes style for the settings page. The produce a more compact page. Feel free to change them to fit your preferences.

All the sections are wrapped in a <div class="sitedoc_xxxxxx">. These are not included in the delivered CSS, but you may add them if you feel the need to override the styling produced by your theme.

How Can I Use It?

Well, obviously, the menu access will be the easiest way to use it. A clever administrator might even work up a Cron job to print the information every night in order to document changes to the system. [Oh, wait a minute, I already did that.]

But there is another way to use this module. On one of my sites, I did actually document how to take over the site at such a time as I am no longer able, or willing, to do so. Before I wrote this module there was hard-coded information, for example, the modules in use, which was sadly out-of-date. With this module, I can insert a php snippet to invoke a section of the Site Documentation module to be included directly into the page.

<a name="extensions"></a>
<h2>Extensions to Drupal</h2>
<p>&quot;Out of the box,&quot; Drupal is pretty powerful, but there were things we wanted to do with it that are not core functions. Drupal has a number of extensions (known as &quot;modules&quot;) that we added to improve the site.</p>
<p>Additionally, we wanted to use the WebCalendar function to show our meeting dates and events. The Swap Shop section uses Noah's Classifieds. Both functions are also installed with Fantastico.</p>
<a name="modstatus"></a>
<?php
 
print sitedoc_get_modules();
?>

It ends up looking like this (with my style sheets):



Calling the Functions

Function Name Parameters Returns
sitedoc_drupal Interval, in seconds, before deleting ``cron_semaphore''; default is zero (0) to indicate no deletion. HTML string
sitedoc_database_overview
  • TRUE - release overhead from a table that has it.
  • FALSE (default) - don't release overhead.
HTML string
sitedoc_get_modules None HTML string
sitedoc_get_system
  • 'theme' - get Theme list
  • 'theme_engine' - get Theme Engine list
HTML string
sitedoc_get_blocks None HTML string
sitedoc_get_boxes None HTML string
sitedoc_content_types None HTML string
sitedoc_get_vocabularies None HTML string
sitedoc_check_orphan_term_node Should only be used within the module, but is separately callable.
  • TRUE - delete the orphan term-node rows
  • FALSE - do not delete the orphan term-node rows (default)
HTML string
sitedoc_sequences None HTML string
sitedoc_node_summary
    • TRUE - include comments count
    • FALSE - do not include comments count (default)
  1. size (KB) to show nodes exceeding
  2. size (KB) to warn if nodes exceed
HTML string
sitedoc_node_access None HTML string
sitedoc_get_roles
    • TRUE - permissions listed as unordered list
    • FALSE - permissions listed as stream (default)
    • TRUE - include users in roles (default)
    • FALSE - do not include users
HTML string
sitedoc_get_variables None HTML string
sitedoc_get_contacts None HTML string
sitedoc_profile_fields string - limits the list to only this category HTML string
sitedoc_url_alias None HTML string

What About Other Modules?

First let me point out that this module does not support the Categories module because I don't use it and have no idea how to make it work.

This module does support "sticky-encoded" node weighting, such as provided by the Weight module and my own generic content type module.

I tried to use t('xxx') where translations might make sense, but I only speak English, so I do not know much about the i18n support. I did use FROM {tablename} x in my queries because I had an i18n collision on a previous module, so hopefully this is avoided.

One of the things that was suggested on the groups about this kind of function was to include a hook so that modules could provide module-specific information to include in the listing. I'm not sure if I satisfied that part, but here's what I came up with:

Hook_sitedoc

Any module may include this hook. If it is enabled in the module, the Site Documentation module will invoke it at run time.

I'm still designing this part. Any input you might offer will be welcome. What would a module developer want to highlight? In what format should it be returned?

How Can I Get It?

This module has been accepted as a contributed module. It is available at http://drupal.org/project/sitedoc.

Site Menu: Site map and sidebar menu

The Site Menu module that provides a site menu based on the site's category hierarchy. Node titles under each category are listed, with options to display the author and number of comments if needed. The module as has a sidebar menu in a block for all categories of the site.

Certain categories can be excluded from the list.

You can:

SiteNotes: Adding Hidden Design or How To notes in Your Database

Ever design a site and happen on a nifty trick that you may want to use again? You wrote it down and lost the paper, didn't you.

You designed a great site for someone else, and the first thing they do is call you up and say "How do I do...?"

You can't remember all the modules, themes, and CSS changes you made and it's time to upgrade...

You'd love to put all that stuff on the web site, some place easy to find, but safe from casual hackers...

Well, here you go... Introducing SiteNotes. A simple module lots of uses.

SiteNotes introduces a new content type, called, coincidentally, "sitenotes." But wait... there's more... It also creates a menu item in the Admin, Site building menu, where it's available only to privileged users. It's even protected by Access Control.

So now all those little Post-Its™ and other scraps of paper can go right into your database where you can find them again.

Create "How To" notes for your users, theme changes, CSS modifications, special code notes, and keep them safe AND available.

This offer is not available in stores. Right now, it is only available from the Site Notes project page.

Spam Module

Important note: this documentation is for the Spam module at kerneltrap.org, not the module hosted at Drupal.org.

Set the permissions for administering spam. It's best to let trusted administrators with this, since it potentially involves the automatic deletion of comment and/or Trackbacks.

Configure the Spam Module

  1. click administer » settings » spam
  2. select which content to filter. The list you will depend on what modules are enabled, but will include all content types that are enabled and, if the respective modules are enabled, comments and Trackbacks.
  3. select whether to flag content and comments as spam if URLs that appear in the URL filters setting (see the "Create automatic spam filters based on URLs" section for how to do this)
  4. choose settings for the limits, such as maximum number of URLs, repeat URLs, duplicate content, and how many spam postings before banning the IP.
  5. choose settings for Actions:
    • Unpublish spam means that the "Published" setting for the content is unset automatically. To review spam, click administer » spam » content tab.
    • select whether the user gets notified that their content has been flagged as spam. This help reduce confusion from the user's point of view (i.e. if their comment is not immediately published they will get a reason why).
    • select whether the administrator gets notified of spam content. This setting may cause the administrator to receive many notifications depending on the amount of spam content.
    • choose a setting for the length of time after which the content is "expired" i.e. deleted. There is no way to recover content older than the setting for this, so make sure you review content and comments within that time frame to catch any potential false positives.
  6. choose the level of log details you wish to keep. The "everything" setting will slow down your site, but will give you more information if you wish to investigate what's going on. The "major events" combined with a short length of time after which logs are discarded (see next item) are recommended.
  7. choose the amount of time after which the logs will get deleted. This will depend on how often you think you'll be checking the logs as well as how far back you want to have information for. The higher the setting the slower your site performance may be.
  8. choose whether to display the probability of whether the item (content or comment is spam). Enable this if you want more information in deciding what probability level to choose for the "Match effect" (see step #5 in the "Create Automatic Spam Filters Based on Keywords" section)

Marking Individual Comments as Spam

When reading comments on a post, you may come across something that is clearly spam. If you have the "administer spam" permission, you should see two links beneath each comment: "mark as spam" and "mark as not spam":

  • If you come across one that is definitely a spam, click the "mark as spam" link underneath the comment. That will set the comment to unpublished and will help the spam module "learn" what should be flagged as spam in the future.
  • If something you know is definitely not spam, you can click the "mark as not spam" link. Even thought it's already published, it will help the module correctly determine which comments should not be flagged as spam.

Bulk Removal of Spam Using Custom Filters

If you have hundreds or thousands of comments or posts that are clearly spam, use the custom spam filters to quickly flag them all—or almost all of them, as you may need to create some additional filters (see next section)—as spam.

  1. click administer » spam » custom filters tab
  2. click the "scan" link next to each filter.
  3. either click "select all" (which will check every box you see) or individually select which comment you wish to make a change to
  4. choose one of:
    • "Mark the selected comments as spam"
    • "Mark the selected comments as not spam"
    • "Unpublish the selected comments"
    • "Publish the selected comments": for comments that are definitely not spam and that you wish to make public.
    • "Delete the selected comments (no confirmation)": by selecting this option you will not be asked to confirm the deletion. Use with care since there is no 'undo' for this action.
  5. click "Update scanned comments"
  6. repeat steps 3-5 for each page of comments.

Create Automatic Spam Filters Based on Keywords

If spammers start using words or phrases that the module is not picking up as spam (or if you want to make sure certain words or phrases make content or comments flagged as not spam), you can add custom spam filters to the list of existing ones.

  1. click administer » spam » custom filters tab.
  2. decide whether you're going to use plain text or a regular expression.
    • Plain text is useful when it's just one word or phrase that you want to have flagged as spam.
    • Regular expressions are a little more advanced, and have a different syntax than entering just one word or phrase, but allow you to include checks for the start of words or multiple words.
  3. in the "Custom filter", type in a word if you're using 'plain text', or a regular expression if you choose that option.
  4. choose whether to filter by header (which means either the title content or the subject of a comment) or by the body are scanned for spam. Choosing "everything" means both the header and body get scanned.
  5. select the "Match effect". You can select "always spam" or "never spam", with the settings in between meaning that the chances that they are either are increased but not necessarily absolute.
  6. select whether you wish to disable email notification when the filter matches. This overrides the email setting under administer » settings » spam.
  7. select whether spam automatically deletes as the result of the filter. This is generally not recommended since it's best to periodically review whether the spam module is working correctly.
  8. create automatic spam filters based on URLs
  9. click administer » spam » url filters tab
  10. type in a domain for the URLs that you notice are commonly used in spam. For example, if the URLs you're noticing are http://example.com/pills-for-sale then type in example.com.
  11. click "Add URL filter".

SPAM: Stopping unwelcome posts on your site

The spam module is a powerful collection of tools designed to help website administrators to automatically deal with spam. Spam is any content that is posted to a website that is unrelated to the subject at hand, usually in the form of advertising and links back to the spammer's own website. This module can automatically detect spam, instantly unpublish it, and send notification to the site administrator.

  • Automatically detects and unpublishes spam comments and other spam content.
  • Automatically learns to detect spam in any language using Bayesian logic.
  • Automatically learns and blocks spammer URLs.
  • Automatically blacklists IPs of learned spammers, preventing them from
    posting additional spam and wasting database resources.
  • Detects repeated postings of the same identical content.
  • Detects content containing too many links, or the same link over and over.
  • Supports the creation of custom filters using powerful regular expressions.
  • Can notify the user that his or her content was determined to be spam,
    preventing confusion over why their content doesn't show up.
  • Can notify the site administrator in an email when spam is detected.
  • Provides simple administrative interfaces for reviewing spam content.
  • Provides comprehensive logging to offer an understanding as to how and why
    content is determined to be or not to be spam.

You can:

Station: Radio station schedule, programs, playlists and webstream archive

Station is a group of modules designed to run a radio station's website. It was originally designed for KPSU, a college radio station in Portland, Oregon.

The module is broken into three parts:

  • station.module - provides functions shared by both the schedule and the archive.
  • station_schedule.module - allows a weekly schedule of programs and program playlists. Requires the views module.
  • station_archive.module - provides a script to rip hour long mp3s and then imports the files as audio nodes. Requires the audio module, views module, and StreamRipper executable.

Station Schedule Module

The station schedule module provides:

  • A weekly schedule of blocks for a radio station. Blocks can be 1 hour, 30 minutes, or 15 minutes.
  • A Program node that is placed into one or more of the schedule blocks.
  • A "DJ" relation between Drupal users and the Program nodes.
  • A Program Playlist node that allows DJs to create play lists detailing the music they played on a given date.
  • An XML-RPC interface for remotely retrieving program information.
  • Basic views support for the Program and Program Playlist nodes.

Station Archive Module

The station archive module provides a framework for keeping an archive of the station's webstream. It uses StreamRipper to connect to the webstream and save an hour-long MP3 of each show. The MP3s are then added to Drupal using the audio module. The archives audio nodes are managed using a taxonomy.

The modules are loosely coupled and designed to run on different machines i.e. schedule runs on your ISPs machine and archive runs on a network where bandwidth is cheap.

Once everything is installed you can:

  • View the list of programs at station >> programs.
  • View the list of playlists at station >> playlists.
  • View the schedule at station >> schedule.
  • Modify the schedule at admin >> schedule.
  • Change the modules' settings at admin >> settings >> station.
  • Submit issues, read about known bugs, and download the latest version on the Station project page.

Installing Station Archive

You'll need to complete the following steps to setup the station archive:

  • Enable the taxonomy module. The archive uses a vocabulary to assign days and times to the audio files.
  • Download and enable the audio modules. You'll want to follow the instructions to setup the audio_getid3 sub-module, it is strongly recommended.
  • Enable the station and station_archive modules.
  • Configure the station module at admin >> settings >> station. If the schedule module is running on another machine, you'll want to set the XMLRPC URL.
  • Download and install the StreamRipper executable.
  • Edit the station/archive/scripts/ripper.ini file. Set your web stream's URL, the full path of the import directory, and the full path to the stream ripper executable.
  • Test running the ripper script on the command line. You'll need to determine the full path to the file and pass that to PHP as the first parameter. So, if you've installed the module into /usr/local/www/drupal/modules then you'd run it by typing: php /usr/local/www/drupal/modules/station/archive/scripts/ripper.php
    If you see any error messages you'll need to correct those. If not, it'll grind away ripping the stream for up-to an hour. Let it run for a few minutes then stop it by pressing CTRL+C. Look in the import directory and make sure you've got an .MP3 file. If there's a .CUE file with the name, delete it.
  • Setup Drupal's cron job. The audio files are automatically imported as part of a cron task.
  • Run Drupal's cron script to import this test file then check that an audio node was created by visiting Home > > audio.
  • Add two cron jobs to run the ripper script. One for even, and one for odd hours. Here's a sample that rips every hour from 9am through 2am the next day:
    # note that times start one minute before the hour
    # run the even hourly stream rip
    59 9,11,13,15,17,19,21,23 * * * /usr/local/bin/php /usr/local/www/drupal/modules/station/archive/scripts/ripper.php
    # run the odd hourly stream rip
    59 0,8,10,12,14,16,18,20,22 * * * /usr/local/bin/php /usr/local/www/drupal/modules/station/archive/scripts/ripper.php

    You might be able to do it with a single cronjob I've gotten mixed results using a single one.

Stock: Provide Stock Quotes on your site

The Stock modules allows visitors to a site to get quotes from major international stock exchanges. Registered users can also save a portfolio that can be displayed in a block on the sidebar. The data is from Yahoo! Finance.

You can:

  • file issues, read about known bugs, and download the latest version on the Stock project page.

Subform Element: Easily reuse existing forms

This module just provides a new form element, that can be used by other modules.
It is an API module and this page is intended for developers!

This module provides a new form element, that can be used by other modules. This form element allows you to reuse existing forms inside your form!
So you can build forms that reuse existing forms while you extend them with further form items. Note that form reusing means not only reusing the visual representation, but also the validation and submit logic.

One important source for developers is the API documentation!

But let's show an example. We combine two forms into one - one node creation form for stories and one for books. All we need are two simple subforms, one for each node creation form. We set the correct form_id for each subform and pass the $node argument to it, which will be passed to node_form().

So here's the code:

<?php

/*
* Implementation of hook_menu()
*/
function subform_example_menu($may_cache) {
  if (
$may_cache) {
   
$items[] = array(
   
'path' => 'example',
   
'title' => t('Subform Example'),
   
'callback' => 'drupal_get_form',
   
'callback arguments' => array('subform_example_add_nodes'),
   
'access' => TRUE,
   
'type' => MENU_NORMAL_ITEM,
    );
    return
$items;
  }
}

function
subform_example_add_nodes() {
  global
$user;

 
$form = array();
  foreach (array(
'story', 'book') as $type) {
    if (
node_access('create', $type)) {
     
// Initialize new node:
     
$node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type);
     
//create the subform element
     
$form[$type.'_form'] = array(
     
'#type' => 'subform',
     
'#id' => $type .'_node_form',
     
'#arguments' => array($node),
      );
    }
  }
 
//this sets the subform submit handler, which submits the subforms for you
 
$form['#submit']['subform_element_submit'] = array();
  return
$form;
}

?>

That's it!
If you want to run and try these code, put it into subform_example.module and also create a subform_example.info file containing this two lines:

name = Subform Element Example
dependencies = subform_element

Then you can activate the example module. But note that this code requires at least version 1.1 of the subform_element module.
The example will present you both fully working node creation forms. You need to fill all required fields of both forms, only then the form validation succeeds. So the whole form will be submitted and your nodes are created!

Survey: community questions

The survey module allows users to create surveys to be completed by site visitors. Survey submissions are stored in the database, can be downloaded to Excel, and can generate an e-mail notification of each survey submission for the survey administrator. Surveys are valuable for gathering information about a community effectively.

Creating a survey is a two-step process. First create a new survey. A new survey requires title, optional thank you page, survey notification e-mail, and intro text. Once the survey is created, there are several tabs just for managing the survey: form and responses.

From the form tab, select add survey question to add questions. The main view will display the intro text with the questions you have selected below it. Finally, when users have responded to your survey, responses can be seen on the responses tab as well as downloaded in Excel format.

This module requires the forms module to be installed and enabled in order to create survey forms.

You can

  • create a survey at create content >> survey.
  • administer surveys at administer >> content types >> survey.
  • read about the required forms module
  • file issues, read about known bugs, and download the latest version on the Survey project page.

SWF Tools: All things Flash

SWF Tools is a comprehensive API for common Flash related tasks like:

  • Playing a movie (FLV) or audio (MP3)
  • Playing a media playlist, using an xml file or generating dynamically.
  • Embedding flash in the page using common JavaScript scripts.
  • Quickly generating playlists in common situations (eg. convert file attachments to a playlist).

The API is made as simple as possible, while remaining completely flexible, so that you can get the job done regardless of your own requirements. Filters also allow you to avoid handing out access to php, without limiting the user's ability to modify the look and feel of the flash players they want to use.

A not-so-secret goal of SWF Tools is to reduce flash embedding redundancy in contrib. To a lesser extent, this also helps the community keep track of CVS commits of non-GPL flash players and scripts, by directing new code contributors to this module. Also, this does not mean that SWF Tools is trying to be Video module, for many modules embedding is only a small part. See discussion about flash embedding modules

SWF Tools is written by SImon Hobbs (with some code ported from Flash Gallery and Flash Filter) and sponsored by emspace. There will be demonstration examples at emspace.com.au soon.

Creating playlists (eg. xml files) on the fly

What is a playlist?

A "playlist" is a general term for set of files which are displayed together or in sequence by a Flash player. It could be a list of videos, MP3s, images, or a mix of these. Within SWF Tools they take the form of a general structured list of data that will eventually find it's way into an XML file.

Displaying a list of files as a playlist

SWF Tools provides a useful function for generating playlist data: swftools_prepare_playlist_data(). This is really useful for quick results where you have a list of files and you just want to push them out. Once you have your playlist data, you can then pass it to a variation of the swf() function called swf_playlist().

<?php
// Generate some playlist data which is compatible with SWF Tools.
$playlist swftools_prepare_playlist_data(array('image1.jpg', 'image2.jpg'));

// Pass the playlist data to the playlist display function.
print swf_playlist($playlist);

// REMEMBER, that swf_playlist bahaves like swf(), so you can
// do all the same tricks with flashvars and params.
print swf_playlist($playlist, '400x300');
?>

Developer documentation

SWF Tools contains, at it's core, an API for integrating Flash components into your site. You'll find here some documentation to help you decide to whether SWF Tools is right for you, and then to help you write your own integration module.

To integrate or not to integrate

An existing or new Flash module? Here is a check list to help you make a decision.

Why is SWF Tools so large?

To use SWF Tools for your own Drupal project, you really only need one or two modules: swftools.module and perhaps something like swfobject.module to optionally enable flash replacement.

The plan is to ship a cut-down API-only version of SWF Tools containing only swftools and swfobject - this will demonstrate SWF Tools modularity and allow site administrators to keep a clean modules directory.

Is the full suite worthwhile?

There are a lot of nice-to-have features in SWF Tools which may become available to your users without any extra work on your behalf. Filters, alternative flash players, and alternative flash replacement techniques.

Why not code my own Flash embedding?

Mainly to avoid redundancy. There is a lot of code in Drupal contrib that replicates the basic process of embedding a flash file.

SWF Tools is also trying to focus community knowledge in one place, this should benefit all co-operating projects because the code is more stable, more flexible and feature rich. Perhaps there are skills and techniques you can add?

I want to use SWF Tools API for a new .swf media player, should I start my own project?

This is entirely up to you. You can start your own project, and add SWF Tools as a dependency. If you'd like to put your module within SWF Tools project, please add your code to the issues queue and it will be appreciated and considered for inclusion.

Data services with Flex and/or AMFPHP?

You should stop in at the Services module project page, as SWF Tools does not plan to extend into this area.

HOWTO: Write a plugin for your Flash Player

The goal of SWF Tools is to allow you to integrate any required Flash Player without worrying about the details of JavaScript Flash-replacement and other such details.

In the SWF Tools download, there are two very plain Flash Players, one for playing MP3 music files, and one for playing FLV format movies. The plug-in module that allows SWF Tools to use these players is in the file genericplayers.module.

This Generic Players module is structured like a stand-alone module, all it needs is a genericplayers.info file. So in the steps below, you would replace all occurances of "genericplayers" with your own module name.

Inform SWF Tools of your plugin

To begin with, you need some code to tell SWF Tools about your plugin. This enables SWF Tools to offer your Flash player as an optional default in the File Handling page.

<?php
/**
* Implementation of swftools_methods hook
* Report methods back to SWF Tools
*/

function genericplayers_swftools_methods() {

 
$methods = array();
 
$mp3_player = array (
   
'name'        => 'generic_mp3', // This is a unique name of your choosing
   
'module'      => 'genericplayers', // Simple the name of your module
   
'shared_file' => 'generic/generic_mp3.swf' // Tell SWF Tools where to find your player
   
'title'       => t('Generic MP3 Player'), // Title for the admin pages.
 
);
 
// SWFTOOLS_MP3_DISPLAY is one of the "display methods"
  // constants defined at the top of swftools.module.
 
$methods[SWFTOOLS_MP3_DISPLAY]['generic_mp3'] = $mp3_player;

 
$flv_player = array (
   
'name'        => 'generic_flv',
   
'module'      => 'genericplayers',
   
'shared_file' => 'generic/generic_flv.swf',
   
'title'       => t('Generic FLV Player'),
  );
 
// SWFTOOLS_FLV_DISPLAY is one of the "display methods"
  // constants defined at the top of swftools.module.
 
$methods[SWFTOOLS_FLV_DISPLAY]['generic_mp3'] = $flv_player;
  return
$methods;
}
?>
Request a configuration page

Using the normal hook_menu, you can slot your page under the SWF Tools configuration area. Access control is set on the /swf part of the menu, so you don't need to define access control if you don't want to. You also don't need to define a callback, as SWF Tools manages this.

<?php
/**
* Implementation of hook_menu(). Items such as access control is set by swftools automatically
*/
function genericplayers_menu($may_cache) {
 
$items = array();
  if (
$may_cache) {
   
$items[] = array('path' => 'admin/swf/generic',
     
'title' => t('Generic Players'),
     
'description' => t('Basic Flash players that ship with SWF Tools'),
      );
  }
  return
$items;
}
?>
Define your configuration settings

Flash Players usually have options that you can pass into them. These are called "FlashVars". These FlashVars then become variables within the ActionScript of the Flash Player. On the settings page we can allow the site admin to define the default settings. In this case "autostart" is used by the Flash Player to determine whether the music/movie/animation should start automatically.

<?php
/**
* Implementation of swftools_admin_hook_form. Return the system settings page.
*/
function swftools_admin_generic_form() {

 
$form = array();
 
$methods = swftools_methods_available(SWFTOOLS_EMBED_METHOD);
 
$form['generic_mp3_autostart'] = array(
   
'#type' => 'checkbox',
   
'#default_value' => variable_get('generic_mp3_autostart', FALSE),
   
'#title' => t('Autostart MP3'),
   
'#description' => t('Automatically start playing the MP3 file.'),
  );
 
$form['generic_flv_autostart'] = array(
   
'#type' => 'checkbox',
   
'#default_value' => variable_get('generic_flv_autostart', FALSE),
   
'#title' => t('Autostart FLV'),
   
'#description' => t('Automatically start playing the FLV file.'),
  );
  return
system_settings_form($form);
}
?>
Overriding FlashVars in a unique way

In this part of the code, SWF Tools has been asked to display some Flash. It has processed the general defaults, and overridden these defaults with settings passed into the swf() call.

At this point, your plugin module has the option of massaging the settings to suit your Flash Player. In this case, as an example, SWF Tools didn't know about the autostart default settings. So the plugin gets the last chance to modify the FlashVars and apply these defaults. You can do a lot of different things with the FlashVars in this hook, so this is just a simple example. Sometimes you may not need to use this hook.

<?php
/**
* Implementation of swftools_flashvars hook.
*/
function genericplayers_swftools_flashvars($action, &$methods, &$vars) {
  switch (
$action) {
    case
SWFTOOLS_MP3_DISPLAY:
     
$vars->flashvars['autostart'] = variable_get('generic_mp3_autostart', FALSE) ? 'true' : 'false';
      break;
    case
SWFTOOLS_FLV_DISPLAY:
     
$vars->flashvars['autostart'] = variable_get('generic_flv_autostart', FALSE) ? 'true' : 'false';
      break;
  }
 
// Must return the flashvars component.
 
return $vars->flashvars;
}
?>

Now, you just need to add a .info file and your module is ready for action.

What we haven't covered here

There is also the ability to output a playlist in XML. This feature is not covered here (yet). Please see wijering.module for a working example if you are keen.

HOWTO: Write code to integrate Drupal modules

This is a stub

Prepared "playlist" data structure ready for xml

There is a special data structure that can be passed to the second parameter of swf_playlist(). You should follow this naming format, because each Flash Player controls it's own XML structure and needs to know what data to expect.

An example of a call to swf_playlist() like this can be found in swftools_integrate_upload_render() in the swftools_integrate.module.

The data is broken into two sections. The 'header' contains information that will likely appear at the top of the xml file.

The 'playlist' section defines each file. The core structure of this data matches the nomenclature of $node->files (as set by the upload.module).

<?php
$playlist_data
= array (
 
'header' => array (
   
'title' => 'My playlist',
   
'name' => 'Author name',
  ),
 
'playlist' => array(
   
0 => array( 
     
'filename' => 'file1.jpg',
     
'filepath' => 'files/file1.jpg'
      'description'
=> 'My cool pic',
     
'fileurl' => 'http://example.com/files/file1.jpg',
    ),
   
1 => array( 
      ... 
// and so on.
   
),
  ),
);
?>

Swfobject: Use Flash movies in Drupal Blocks

This module uses swfobject.js (http://blog.deconcept.com/swfobject/) to insert any number of flash movies as drupal blocks. Most of the parameters that can be passed to a flash movie are configurable through the block configuration, as are flash variables. You will need to download the js and put it in the appropriate folder since it is not available under the GPL.

System Info: Information of the drupal install and system environment

The System Info module displays information of the drupal install and system environment.

Drupal 4.7
You can view the information at administer >> system info.

Drupal 5
You can view the information at Administer >> Logs >> System info.

Tagadelic: weighted tags in a tag cloud

The tagadelic module generates a page with weighted tags, indicating how many times a category or tag has been used to categorize content on the site. The cool thing is that by merely altering font sizes, these lists suddenly gain a dimension: the more often a tag is used, the larger its font size. Tagadelic offers various ways to add terms and vocabularies in one tag cloud. By using the urls, you can create your own clouds pages. It also offers a sideblock for each taxonomy tree. Tagadelic offers dynamic URLs.

This module will work with any vocabulary, be it marked for free tagging or not. If you are not happy with the tags from non-free tagging vocabularies showing up, then use the URL tricks below.

You can:

  • visit tagadelic/list/2,1,5 to get the vocabularies 2,1 and 5 listed as tag groups.
  • visit tagadelic/chunk/2,1,5 to get a tag cloud of the tags in the vocabularies 2,1 and 5.
  • view an example PHP snippet on how to integrate a tag cloud into arbitrary pages at the Bryght wiki
  • file issues, read about known bugs, and download the latest version on the Tagadelic project page.

Taming the "Read More" link - the painless way.

The ed_readmore module provides a quick and easy way to control the placement of your 'read more' links on teasers and in rss feeds.

Some people have observed that the current placement of the read more link is somewhat obscure, leading to confusion among users who aren't particularly experienced with Drupal sites and the layout/placement of the links block containing the original read more link - leading to confusion in some cases. Another problem is that rss feeds that include only the teaser don't provide any hints to readers that there is more to the article. This module can help with these concerns.

It's a pretty simple module - you have a few administrator options that enable/disable the feature, and allow you to control whether or not the 'read more' link will be placed after the last word of the teaser, or, on the line below.

Thanks to Merlinofchaos for the original code that went into this module.

Taxonomy Browser: build a custom category view

http://drupal.org/project/taxonomy_browser
Think of this as a 'build your own category view' page.

A single page with checkboxes for each term (organized nicely by vocabulary). User checks off the terms which he wants to see, and then this module contructs the right URL (e.g. taxonomy/view/and/3,4,5) and then displays matching nodes ot the user.

Taxonomy image; Associate images with taxonomy terms

The taxonomy_image module allows site administrators to associate images with taxonomy terms. With the association created, an admin can then make a call to 'taxonomy_image_display()' from their theme or other PHP code to display the appropriate image.

The module allows the admin to create a one-to-one term-to-image relationship. With image display recursion, it is also possible to create a many-to-one relationship. This is useful if creating taxonomy hierarchies in which an entire tree of terms will use the same image. With recursion enabled, you only need to associate an image with the tree parent, and all children will automatically inherit the same image.

Features

  • Image configuration happens within existing admin/taxonomy menu structure
  • Admins can add custom tags to markup when displaying images
  • Allows one-to-one term-to-image relationships
  • Allows many-to-one term-to-image relationships
  • Admin-permitted users can disable the images
  • Administrators can force images to be a standard size
  • Utilizes core file.inc api

Taxonomy menu: navigation for terms

Taxonomy terms allow classification of content into categories and subcategories. The taxonomy menu module adds links to the navigation menu for taxonomy terms. This is useful when the community is focused on creating content that is organized in a taxonomy.

The taxonomy menu administration interface allows taxonomy terms to be enabled to show in the navigation menu. You can also select whether a term's descendents subterms are displayed.

You can

Taxonomy Sifter

Taxonomy Sifter is a Drupal module that provides a block with terms which any or all selected must apply to a node for it to be listed when viewing a taxonomy term page, e.g. taxonomy/term/... The vocabularies used in the block is configurable.

Background

Given the path taxonomy/term/..., where ... is the identity number of a taxonomy term, Drupal responds by dynamically building a page with teasers of nodes with the given term applied to them. Often, more than just one term is applied to a node. By combining several terms with either the OR operator (+) or the AND operator (,) the returned teasers will correspond to nodes with any or all, respectively, of the terms applied. For further explanation, see the handbook.

There are several modules that leverage this feature by providing a block with a user friendly interface for combining the terms. Some examples are Refine by taxonomy, Taxonomy Filter and uBrowser. These modules works as filters which can be refined step-by-step. But, once the taxonomy term page is left, for instance by viewing a node sorted out by the filter, the filter is reseted. That is often the desired behaviour. But if the purpose of the filter is to provide a short-list of nodes to be reviewed, it can be very frustrating to reapply the refinement over and over again.

To this end Taxonomy Sifter provides a block where the user can select terms that automatically will be combined with the terms in every request of the form taxonomy/term/... The selected terms are combined with the same operator already used in the request. If no operator is used, that is a single term is given, the sifter use the operator given in the module's settings. The vocabularies of the terms presented in the sifter block is also configurable.

Requirements

To install Taxonomy Sifter you need:

Installation

Install Taxonomy Sifter as follows:

  1. Download the archive file with the latest stable version from Taxonomy Sifter project page.
  2. Unpack the downloaded archive into your Drupal's modules directory.
  3. Verify that the modules directory contains a taxonomy_sifter directory with taxonomy_sifter.module.
  4. Go to admin/modules and enable the module.

Configuration

Configure Taxonomy Sifter as follows:

  1. Go to admin/settings/taxonomy_sifter. Select vocabularies that should be available in the Taxonomy Sifter block. Select the default term filter. When a taxonomy term page or feed is requested, the given terms are combined with those selected in the Taxonomy Sifter block. If more than one term is given in the request, the combination is done with the operator used in the request. If only one term is given, the operator selected here is used.
  2. Go to admin/access and select the roles that will be allowed to view the sifter block, and also the roles that will allowed to administer the settings.
  3. Go to admin/block and enable the sifter block.

Usage

To combine the terms in a request for taxonomy/term/... with terms in sifter block, select the terms in question in the sift block, and press the Sift button at the end of the block.

To select a single term, click on it with the mouse pointer. To select all terms between the last selected term and a second term, click on the second term with the mouse pointer while pressing the shift key. To add more terms, repeat the previous step while also pressing the ctrl key. To deselect terms, select them again while also pressing the ctrl key.

To restore the default behaviour of taxonomy/term/..., press the Reset button at the end of the block.

See also Taxonomy Sifter project page.

Taxonomy_access: Restrict user roles to access specific categories only

Allows the user administrator to control access to nodes indirectly, by controlling which roles can access which categories.
http://drupal.org/project/taxonomy_access

Textile: simple text syntax

The textile module allows users to enter content using Textile, a simple, plain text syntax that is filtered into valid XHTML. Textile enables users to learn to format content quickly without having to worry about more complex syntax of html or xhtml. The filter tips page provides syntax descriptions and examples. Users can use syntax to create:

  • headings
  • paragraphs
  • block quotes
  • ordered lists
  • unordered lists
  • footnotes
  • tables

Textile editing is an Input Format option on content creation and editing forms. It allows for blockquote, br, and html tags. It also automatically converts e-mail and web addresses to active links.

You can

Theme editor: store, configure, create

The theme editor module enables the administrator to edit the basic themes for the web site, delete a theme from the theme storage directory, rename a theme, and re-configure the storage directory.

Theme editor has a list, new, and configure tab. The list tab allows for editing, deletion, and renaming. The new tab allows for a theme to be copied to the theme storage directory for editing. The configure tab creates a sub-directory where theme files will be stored.

You can

TinyMCE: a WYSIWYG editor

The TinyMCE module adds a what-you-see-is-what-you-get (WYSIWYG) html editor to normal textareas. This enables users to create rich text in the manner of Open Office or Microsoft Word without having to know HTML markup.

The Editor itself is maintained by Moxiecode Systems AB and is used by many different content management systems. A browser compatibility chart is available at the Moxiecode website.

Installing TinyMCE

  1. Download the drupal TinyMCE Module.
  2. Download the TinyMCE files from the Moxiecode Systems AB web site. It is this module that enables the TinyMCE editor to be used within Drupal.
  3. Untar or unzip the Moxiecode file and copy the resulting directory called tinymce into the drupal TinyMCE module's directory. After you have finished copying the Moxiecode TinyMCE directory into your drupal code base, the path to it should look like: ..public_html/sites/all/modules/tinymce/tinymce. The exact path may vary depending on where you have drupal running, but the point to remember is that the Moxiecode tinymce folder(directory) which contains the TinyMCE editor's engine must go inside the drupal tinymce module's directory. (TinyMCE 5.2 users: copy the TinyMCE files into the module's includes folder).
  4. Enable the module as usual at Administer>>Site Building>>Modules (../admin/build/modules).

TinyMCE Profiles

TinyMCE uses profiles to define which user(s) receive Editor capability, and on which pages the Editor is displayed. Editor buttons and themes are enabled on a per-profile basis as well. The default profile setting uses the "simple" TinyMCE theme which shows the minimal button set (bold, italic, underline, etc).

Per-profile options also exist to turn on or off the Editor for those users, or even to allow/disallow users the ability to toggle TinyMCE on the fly while editing a text node.

Create, modify, and delete your TinyMCE profiles at Administer > Site Configuration > TinyMCE (5.x)

User Roles and Access Control

TinyMCE profiles are attached to users via the Access Control settings at Administer > User Management > Access Control (5.x) Remember that if a user is a member of Roles attached to multiple TinyMCE profiles, they will receive the profile with the lowest Role ID they belong to.

Administer per-user roles at Administer > Roles (5.x)

Change user's TinyMCE preferences via their individual accounts at Administer > User > Edit (5.x)

Please file issues, read about known bugs, and download the latest version on the TinyMCE project page.

Adding the TinyMCE header plugin

Formating controls for headings (h1, h2, h3...) are not included in the base TinyMCE package. However there is a 3rd Party TinyMCE plugin that provides this.

  1. Download the plugin from SourceForge.
  2. Unzip the files and place header directory in your drupal/modules/tinymce/tinymce/jscripts/tiny_mce/plugins folder.
  3. The TinyMCE Drupal module handles plugins differently than stock TinyMCE, hence the standard plugin installation instructions do not apply. Instead edit the drupal/modules/tinymce/plugin_reg.php file and add the following lines immediately before return $plugins;
    $plugins['heading'] = array();
    $plugins['heading']['theme_advanced_buttons1'] = array('h1,h2,h3,h4,h5,h6,separator');
    $plugins['heading']['heading_clear_tag'] = array('p');
  4. on the TinyMCE settings page you should now see a button entry for the headings: "h1,h2,h3,h4,h5,h6,separator – heading". Enable the button.

That's it, you should now see buttons for headings in your editor.

Speed up loading TinyMCE by turning on disk caching

A common observation with the TinyMCE editor is that your site, under certain TinyMCE settings and conditions, can slow down quite dramatically. This is because TinyMCE will be pushed to your browser every time it is called. Apparently, TinyMCE will be pushed to the browser even if there is no text area that it could use, resulting in some speed problem with your site.

A very easy fix is the installation of the TinyMCE compressor AND the editing of one particular configuration switch inside tiny_mce_gzip.php:

Change

$diskCache = false; // If you enable this option gzip files will be cached on disk.

to this:
$diskCache = true; // If you enable this option gzip files will be cached on disk..

This makes TinyMCE be

  1. compressed and
  2. be held in your browser's local cache for a definable periode of time (which can be set in the same file)

Now you will only experience the transfer and loading of TinyMCE the first time until it resides in your browser's cache and from then it is instantly there when needed, no preloading of the editor any more, boosting the speed of page rendering in your browser tremendously!

Adapted from panatlantica's blog entry.

Tinymce gzip compressor and Drupal 5

To enable gzip compression for Tinymce and Drupal:

1. Download the tinymce compressor, upload to your server, unzip, and place tiny_mce_gzip.js and tiny_mce_gzip.php, which are both included with the compressor in the same folder as tiny_mce.js in the /tinymce/includes/jscripts/tiny_mce directory (you know you have the right place if you see tiny_mce.js)

2. Open the tinymce module in a text editor. Change the line:
drupal_add_js(drupal_get_path('module', 'tinymce') . '/includes/jscripts/tiny_mce/tiny_mce_gzip.js');

to

drupal_add_js(drupal_get_path('module', 'tinymce') . '/includes/jscripts/tiny_mce/tiny_mce_gzip.php');

Save your changes, and you're done.

Versions used for these instructions:
tinymce compressor v 1.1, tinymce 2.0.9, Drupal 5.1, and tinymce module v1.90.4.12

Trackback: post remotely

The trackback module allows users to give a blog post a contextual link to another. A context is made because the trackbacking poster is, in theory, writing about something mentioned on another blogger's trackbacked post. Using the trackback URL accompanying each post, another website can send a ping to your website. Once received, the trackback will show up on the trackback page accompanying the post. It also includes auto-discovery, spam moderation queues, and the ability to manually ping another site.

If trackback autodisovery is enabled on your website, someone need only visit your post via a link from another website post to have trackback discover the linking site and create the trackback. Trackback auto-discovery also works internally within a website, automatically creating connections between pages which link to each other. To manually send a ping to another site, edit your post and use the trackback URL field at the bottom of the edit page to submit the trackback URL for the post on the other site. Once you enter submit, your website will ping the other site for you. With trackback autodiscovery enabled, your site will attempt to do this automatically without your intervention.

To enable the moderation queue, go to the administer trackbacks page and select the configure tab. To view, approve, or delete trackbacks awaiting moderation, go to the administer trackbacks page and select the approval queue. To administer the trackback settings for a particular content type go to that content types administration page.

You can

URL filter: turn URLs and e-mail addresses into live links

The URLfilter module automatically converts text web addresses (URLs, e-mail addresses, ftp links, etc.) into hyperlinks. This is useful for users if content authors do not explicitly create a hyperlink for the URL.

This functionality was added into 5.0 core as a filter so the module will no longer be needed for future versions

Use Input Formats to enable the URL filter.

  1. Select an existing input format or add a new one
  2. Configure the input format
  3. Enable URL filter and Save configuration
  4. Rearrange the weight of the URL filter depending on what filters exist in the format

You can

User Import: Create user accounts by importing data

The User Import module can be used to import users into Drupal from a coma separated (CSV) file.

Create An Import

Click the 'Import' tab to create a new import.

CSV File

Choose a file to import

Correct File?

Look at the 'csv column', data is displayed from the first column of the selected CSV file.

If this is the wrong file click 'Remove file' to go back to the file selection screen.

Match Data to Drupal Fields

Match data rows from the csv file to information fields of your website.

Import Options

Select options for the import.

Role(s)

Select what role imported users are assigned.

Welcome Email

If a welcome email is going to be sent to each imported user (set under 'Options'), a custom welcome email can be sent, if these fields are not set Drupal will use the default welcome email.

Save Settings

The settings for this import can be saved and used for future imports of other csv files which have the same data columns.

Test or Import

Click 'Test' to test how well the data will import.

Click 'Import' to import the users.

Import Results

The 'List Imports' page shows the results of a test or import.


Errors

If there are any errors they will show under the 'errors' column, and can be clicked on to see details of the error.


User Invite:Invite friends to join the site

The user invite module is used to invite people to register and create a user account on your site. This is useful for social networking and online groups where the value of the site increases with the more users that join.

The user invite module is dependent on the Invite API module.

You can:

  • enable the user invite module at administer >> modules.
  • download the latest version on the User Invite CVS page.

User Points: Users gain points as they do certain actions

The User Points module provides the ability for users to gain points when they do certain actions. This module is useful in providing an incentive for users to participate in the site, and be more active.

.Upon deleting a node or a comment the number of points is subtracted. The number of points for each of the above actions is configurable by the site adminsitrator. A block displays the number of points the user gained. Another block displays the top 5 users who earned points.

Actions that generate points include:

  • posting a node. Each node type can be given a different number of points, for example an image is of more value to photography site, while a page is more value to a literature site.
  • posting a comment.
  • voting on a node (requires the nodevote module).
  • Inviting a user (requires the invite module).
  • Invited user registers (requires the invite module)
  • .

You can:

UserTag:Tag users with taxonomy terms

The UserTag module allows users to apply tags to their user profile. If the tag exists as a taxonomy term then a link showing users with that tag is available. This allows users to find other users with common interests and create social networks within the community site.

The UserTag module requires the taxonomy module with free tagging modifications.

You can:

  • Create a comma separated list of tags for your user profile.
  • Identify users with the same tags as you.
  • download the latest version on the UserTag CVS page.

Video: Allows uploading and playback of video

The video module(4.7 or with backport patch to 4.6) allows users to post video content to their site. The emergence of portable phones with video capture capabilities has made video capture ubiquitos. Video logging, or vlogging as medium for personal video broadcasting has proven to be popular and is following the blogging, and podcasting phenomenas. Videos are useful for creative collaboration among community members. If community members can not meet in person videos of meetings are valuable for enhancing the interaction between community members.

The video module can be administered to flash player settings. There are a number of page and menu links which can be added to play and download video content on the site. Other configurable options include counts of plays and downloads. Multi-file downloads can also be configured to play. You can add file extensions that the flash video player should handle. There are also up to six custom fields and a group name which can be added.

You can:

Views: Customized Node Lists

The Views module provides a flexible method for Drupal administrators to control how lists of posts are retrieved and presented. Traditionally, Drupal has hard-coded most of this, particularly in how taxonomy and tracker lists are formatted.

Using Views, an administrator can create pages and blocks that list new posts of a particular type (such as forum or blog posts), create alphabetical lists by taxonomy, create weekly, monthly or yearly archive pages, create a table of posts that are flagged as unread, and much much more!

You can:

  • administer your Views at administer >> views.

Please also see the Views Snippets page for user-contributed views!

Please see the Views Documentation Page for information on how to use Views in your site and with your modules.

Advanced use: reset Views module completely

WARNING: This will result in the loss of any existing views

Drops all views tables and removes the system entry so it doesn't think views was ever installed

DROP TABLE view_view;
DROP TABLE view_argument;
DROP TABLE view_filter;
DROP TABLE view_sort;
DROP TABLE view_tablefield;
DELETE FROM system WHERE name LIKE 'views%';

Then go to administer >> modules and enable views and views_ui

WARNING: This will result in the loss of any existing views

Volunteer: organize people to help

Volunteer module helps you organize people for events. There is a managed email correspondence with the volunteer, as well as the ability to rate their volunteer performance at the end. It allows admins to further designate users as volunteer coordinators who can be designated as the contact person for volunteers for an event.

This module now relies on the CiviCRM module to handle contact information for volunteers. The volunteer module is frequently used with the RSVP module to invite community members. The module allows for default settings, contact settings, message settings, and email settings.

You can

  • give at least one user role, volunteer coordinate and volunteer maintain permissions at administer >> access control.
  • administer default, contact, message and email settings at administer >> settings >> volunteer
  • create an event at create content >> event
  • volunteer for an event by clicking on the volunteer link below an event.
  • enable the calendar block for browsing events at administer >> block >> calendar for browing events.
  • see events that may allow volunteering at events.
  • rate volunteers for an event at events >> select an event >> volunteer admin >>
    Volunteer Status >> select a volunteer >> Avg. rating.
  • create a CiviCRM group for volunteers to be added to (when volunteering for an event) at CiviCRM >> Manage Groups >> New Group.
  • create a CiviCRM profile for volunteers to register to (when volunteering for an event) at CiviCRM >> Administer CiviCRM >> CiviCRM Profile >> New CiviCRM Profile.
  • select a CiviCRM group for volunteers to be added to (when volunteering for an event) at administer >> settings >> volunteer >> Contact Settings.
  • select a CiviCRM profile form for volunteers to register to (when volunteering for an event) at administer >> settings >> volunteer >> Contact Settings.
  • see a list of all contacts who have volunteered for events at CiviCRM >> Manage Groups >> select a group of volunteers >> Show Group Members.
  • file issues, read about known bugs, and download the latest version on the Volunteer project page.

CiviCRM integration with Volunteer

The CiviCRM module is integrated with CiviCRM by having a default group that volunteers are added to when they volunteer. There is also a selectable CiviCRM profile that volunteers must complete when they volunteer for an event. This volunteer profile is configurable to have required, and searchable fields.

Creating a group of volunteers

In order to create and populate a group of all users who have volunteered for events:

1. First create a CiviCRM group

~> CiviCRM >> Manage Groups >> New Group

2. Then select the group from "Add/Update contacts to CiviCRM group when volunteering:"

~> administer >> settings >> volunteer >> Contact Settings

3. As registered users volunteer for events, they will be required to fill out a CiviCRM Profile
form and that information along with their user name will be saved to the volunteer group.

VotingAPI: A framework for voting and rating modules

VotingAPI is a flexible, easy-to-use framework for rating, voting, moderation, and consensus-gathering modules in Drupal. It allows module developers to focus on their ideas (say, providing a 'rate this thread' widget at the bottom of each forum post) without worrying about the grunt work of storing votes, preventing ballot-stuffing, calculating the results, and so on.

VotingAPI does four key jobs for module developers:

  1. CRUD: Create/Retrieve/Update/Delete operations for voting data. The simplest modules only need to call two functions -- votingapi_set_vote() and votingapi_get_voting_results() -- to use the API. Others can use finer-grain functions for complete control.
  2. Calculation: Every time a user casts a vote, VotingAPI calculates the results and caches them for you. You can use the default calculations (like average, total, etc) or implement your own custom tallying functions.
  3. Workflow: By integrating with Actions.module, VotingAPI can trigger workflow steps (like promoting a node to the front page, hiding a comment that's been flagged as spam, or emailing an administrator) when votes are cast and results are tallied. Modules can expose their own 'default' action sets and filter criteria to provide moderation and promotion systems.
  4. Display: VotingAPI integrates with Views.module, allowing you to slice and dice your site's content based on user consensus. It also provides convenience functions to format vote data for display to users.

If you're a Drupal user looking for a voting or rating module (rather than just a framework), or you're a developer looking for examples of VotingAPI based code, check out some of the following projects:

VotingAPI: Data storage overview

This information is intended for developers creating modules that use VotingAPI.

VotingAPI acts as a traffic cop for a large pool of voting data: modules throw votes at it and ask for results back. It assumes nothing about the kinds of content being voted on. Instead, it lets modules mark each vote with a 'content type' and 'content id', so that the same APIs can be used to rate nodes, comments, users, aggregator items, or even other votes (in a Slashdot-esque meta-moderation system).

Two kinds of records are stored: individual votes by each user on each piece of content, and cached 'result' records that aggregate calculated values like the average vote for a piece of content, how many people voted on it, etc. Each time a user votes, the cached result records are automatically recalculated. This means that no 'on-the-fly' calculations have to be done when displaying content ratings.

Each individual vote has the following properties:

content_type
This usually corresponds to a type of Drupal content, like 'node' or 'comment' or 'user'.
content_id
The key ID of the content being rated.
value
This is the actual value of the vote that was cast by the user.
value_type
This determines how vote results are totaled. VotingAPI supports three value types by default: 'percent' votes are averaged, 'points' votes are summed up, and 'option' votes get a count of votes cast for each specific option. Modules can use other value_types, but must implement their own calculation functions to generate vote results -- more on that later.
tag
Modules can use different tags to store votes on specific aspects of a piece of content, like 'accuracy' and 'timeliness' of a news story. If you don't need to vote on multiple criteria, you should use the default value of 'vote'. If you use multiple tags, it is STRONGLY recommended that you provide an average or 'overall' value filed under the default 'vote' tag. This gives other modules that display voting data a single value to key on for sorting, etc.
uid
The user ID of the person who voted.
timestamp
The time the vote was cast.
hostname
The IP address of the host the vote was cast from.

Each cached result record has the following properties:

content_type
The same meaning as an individual vote object.
content_id
The same meaning as an individual vote object.
value_type
The same meaning as an individual vote object.
tag
The same meaning as an individual vote object.
function
The aggregate function that's been calculated -- for example, 'average', 'sum', and so on.
value
The value of the aggregate function.
timestamp
The time the results were calculated.

VotingAPI: Full control for complex voting modules

(stub article)

Documentation for the fine-grain control functions that VotingAPI uses.

votingapi_add_vote($content_type, $content_id, $value, $value_type = VOTINGAPI_VALUE_DEFAULT_TYPE, $tag = VOTINGAPI_VALUE_DEFAULT_TAG, $uid = NULL)

votingapi_change_vote($vobj, $value)

votingapi_delete_vote($vobj)

votingapi_delete_votes($votes = array())

votingapi_recalculate_results($content_type, $content_id)

votingapi_get_user_votes($content_type, $content_id, $uid = NULL)

votingapi_get_content_votes($content_type, $content_id)

VotingAPI: Hooks for greater control

This information is intended for developers creating modules that use VotingAPI.

VotingAPI provides a number of hooks that can be used by modules to alter the normal voting or tallying process, or to receive notification of voting-related events.

Create/Read/Update/Delete hooks
hook_votingapi_load(&$vote)
AFTER an individual vote object is loaded, this hook is triggered. You'll probably never need this, but it can be used to alter the incoming vote object before the calling module receives it.
hook_votingapi_insert($vote)
AFTER a new vote has been inserted into the database, this hook is triggered.
hook_votingapi_update($vote, $new_value)
AFTER an existing vote object has been altered, this function is triggered. The $vote object contains the original value of the vote, while $new_value is, predictably, its new value.
hook_votingapi_delete($vote)
BEFORE a vote object is deleted, this hook is triggered.
Calculation hooks
hook_votingapi_calculate(&$results, $votes, $content_type, $content_id)
Whenever a vote is cast, VotingAPI gathers all the votes for that piece of content and calculates aggregate results. It calculates several default results (average vote, total number of voters, etc.), then triggers this hook.

Modules that implement it can directly alter the $results array, inserting their own records or replacing others. $votes is an array of all the votes for the current piece of content, while $results is a series of nested arrays in the following format:
$results[$tag][$value_type][$aggregate_function] = $value

hook_votingapi_results($results, $votes, $content_type, $content_id)
After all the results have been calculated and saved, this hook is triggered for modules that wish to respond to vote results, but not alter them. $results is a flat array of all the cached vote result objects that were saved, while $votes is an array of the individual vote objects.
Other
hook_votingapi_format($vote, $field)
VotingAPI provides several helper functions to format vote and result data. Whenever they are called, this hook is triggered, giving modules a chance to format or theme any custom voting types they have provided. The full vote object is provided, in addition to the field to be formatted ('tag', 'value_type', 'value', or 'function'). All modules have a chance to respond, but only the first respondant's fomatted result will be used. If you need to place nicely with other voting modules, be sure to only return a value if you know the vote is one your module 'understands.'
hook_votingapi_action_sets()
When used in conjunction with Actions.module, VotingAPI allows modules to expose sets of criteria ('average vote is higher than 75,' 'node type is forum', etc) and actions to be executed if the criteria are met ('promote the node to the front page,' 'email the author,' etc). This hook allows modules to return a structured array containing one or more 'action' sets to be evaluated whenever vote results are tallied.

VotingAPI: The basics for developers

(stub article in progress)

1. Introduction to VotingAPI.

votingapi_set_vote($content_type, $content_id, $vote, $uid = NULL)

votingapi_unset_vote($content_type, $content_id, $uid = NULL)

votingapi_get_voting_results($content_type, $content_id)

2. An example workflow (SimpleVote's processing)

WebFM: Hierarchical filesystem manager

WebFM (Web File Manager) implements a hierarchical filesystem unlike the traditional flat filesystem used to date by Drupal (a single directory indexed by a database). WebFM allows administrators to arrange files on the server in the same way they do on their local storage drives which greatly enhances the managability of large collections of documents.

WebFM uses ajax and javascript to provide application intensive functionality such as drag-and-drop and context sensitive menuing. Javascript must be enabled for WebFM to function - functionality will not gracefully degrade for clients that have javascript disabled.

Go to the project page at http://drupal.org/project/webfm for releases, notes, issues and demo site links.

Installation & Configuration

Drupal 5.x:

  • Download the official release or dev snaphot from the project page.
  • Unzip the archive and copy the 'webfm' directory to your modules directory (ie:/sites/all/modules). Alternatively copy the tarball to the module directory if you can unzip it on the server.
  • Enable the module on Drupal's admin/build/modules page. An install file
    updates the database with the necessary table additions.
  • Configure the module. Note: The configuration assumes that the 'File system path' is set in the usual way at admin/settings/file-system. All WebFM directories are relative to this path.
    • Manually create the WebFM root sub-directory inside the directory set at admin/settings/file-system. Optionally create a sub-directory for ftp use. Set the directory permissions to 775 if the server is linux/bsd.
    • In admin/settings/webfm set the root path(s) (must be prefaced with a '/').
    • The icon path allows the user to substitute their own gifs. File names are hardcoded in the javascript so the icons will have to have identical names.
    • The WebFM cron is used for database cleanup of file records that are deleted outside of the WebFM interface (ie: OS shell, ftp). Do NOT enable unless you know exactly what you are doing!
    • The 'Webfm javascript debug' checkbox is only useful for users interested in looking under the covers or who want to develop the module.
    • The 'Forbidden' text box is actually not currently used by the module.
  • Set rights in admin/user/access per role. Roles that are granted the 'access webfm' permission will receive additional configuration fields at the bottom of the admin/settings/webfm page. These additional fields are a file extension whitelist, max upload file size and max total upload size. ACCESS CONTROL NOTE: Webfm is an admin module however 'access_webfm' can be selected for the anonymous user. This is NOT recommended since any user that is a member of a role with this right has the ability to make changes to the contents of the filesystem on the server.
  • Roles granted the 'attach WebFM files' permission must also enable attachments in admin/settings/content-types/*type* for each content type that will accept attachments (default is disabled).
  • Roles granted the 'see webfm_attachments' permission will be able to see and download attached files. Optionally a .htaccess file (apache servers) can be placed in the WebFM root directory to secure file access via the 'see webfm_attachments' permission.
  • Updating the menu cache by navigating to admin/build/menu may be necessary if upgrading from an earlier version of the module with different internal paths.

Drupal 4.7.x:

  • Download the official release from the project page.
  • Unzip the archive and copy the 'webfm' directory to your Drupal modules directory. Alternatively copy the tarball to the module directory if you can unzip it on the server.
  • Enable the module(s) on Drupal's admin/modules page. Attachment is a separate module in 4.7.x and it has its own settings page. An install file updates the database with the necessary table additions.
  • Configure the module. Note: The configuration assumes that the 'File system path' is set in the usual way at admin/settings/file-system. All WebFM directories are relative to this path.
    • Manually create the WebFM root and ftp sub-directories inside the directory set at admin/settings/file-system. Set the directory permissions to 775 if the server is linux/bsd.
    • In admin/settings/webfm set the root paths (must be prefaced with a '/').
    • The icon path allows the user to substitute their own gifs. File names are hardcoded in the javascript so the icons will have to have identical names.
    • The 'Webfm javascript debug' checkbox is only useful for users interested in looking under the covers or who want to develop the module.
    • The 'Forbidden' text box is actually not currently used by the module.
  • Set rights in admin/user/access per role. Roles that are granted the 'access webfm' permission will receive additional configuration fields at the bottom of the admin/settings/webfm page. These additional fields are a file extension whitelist, max upload file size and max total upload size. ACCESS CONTROL NOTE: Webfm is an admin module however 'access_webfm' can be selected for the anonymous user. This is NOT recommended since any user that is a member of a role with this right has the ability to make changes to the contents of the filesystem on the server.
  • Roles granted the 'attach WebFM files' permission must also enable attachments in /admin/content/types/*type* for each content type that will accept attachments (default is disabled).
  • Roles granted the 'see webfm_attachments permission' will be able to see and download attached files. Optionally a .htaccess file (apache servers) can be placed in the WebFM root directory to secure file access via the 'see webfm_attachments' permission.
  • Updating the menu cache by navigating to admin/menu may be necessary if upgrading from an earlier version of the module with different internal paths.

Onunload event handling in template

To explicitly destroy the event closures to minimize browser memory leaks an unload event needs to fire. Unfortunately the control of the body tag happens in the template. I modified my 5.x garland theme accordingly:

Add this function to template.php:

function phptemplate_unload() {
  if(module_exists('webfm')) {
    $path = $_SERVER['REQUEST_URI'];
    if((strstr($path, 'admin/webfm')) ||
       (strstr($path, 'node') && strstr($path, 'edit')))
      print '  onunload="Webfm.unregisterAllEvents();"';
    }
  }
}

Add the following inside the php tag of the body tag of page.tpl.php:

print phptemplate_unload();

I wish Drupal offered a more elegant way of doing this. My first attempt was to put the 'onunload' event into the javascript but this hung IE.

I'm not the only one to struggle with this. See http://drupal.org/node/57635

Uninstall

To uninstall WebFM completely:

Drupal 5.x
Disable the module on the /admin/build/modules page, then click on the uninstall tab and select the module for removal. This will automatically drop the webfm_file and webfm_attach tables as well as all configuration variables.
Drupal 4.7
Disable the module on the /admin/modules page. Tables and variables must be removed manually.

NOTE: This action will permanently discard all attachment and metedata information and cannot be undone. Execute the first step only if you wish to restore WebFM later without loss of data.

wgHTML: Import static HTML pages

The wgHTML module imports and incorporates static HTML pages into Drupal.

You can:

  • file issues, read about known bugs, and download the latest version on the wgHTML project page.

Wireframe: Information architecture designs for user testing

The Wireframe module allows designers to create information architecture designs for user testing. Web page wireframes are an information architecture deliverable that convey the information, the structure and relationships between information, and the flow and navigation of information on a web page. The wireframe module allows designers to share their wireframes with remote users, programmers, and visual designers to improve the development process. The wireframe module allows designers to add, re-use, and manage interface objects in their wireframes.

Designers can create new wireframe components for re-use in the design of their wireframes. They can change the interface objects and they will change in all wireframes in which they appear. Each wireframe page has a list of interface objects that it contains and interface objects can be removed from wireframe pages.

To create a wireframe, simple click on create content, choose page and then select 'PHP code' input format. You do not need to know PHP, just use HTML code. But you can reuse a 'wireframe component' you added before: type <?php print _wireframe_view(184) ?> to insert the contents of node/184, where node/184 is a 'wireframe component'. That's all PHP you'll need. A 'Wireframe component' can be any input format, typically these are simple HTML snippets but they also can be simple PHP nodes to display a list of users etc.

You can:

  • create an interface object at create content >> wireframe component (node/add/wireframe).
  • view a list of wireframe components at wireframe components(node/add/wireframe).
  • manage all wireframe components at manage wireframe components (wireframe/manage).
  • change and rename the wireframe component to a different wireframe component manage wireframe components >> component name (wireframe/manage/1234).
  • use with the CivicSpace theme wireframe theme skeleton, to ensure the visual design does not effect user testing.
  • file issues, read about known bugs, and download the latest version on the Wireframe project page.

Workspace: manage your own content

The workspace module allows you to view a summary of all the content you have posted on a Drupal site.

See screenshot.

XML Sitemap: make it easy to notify search engines about site updates

The XML Sitemap module (previously called Google Sitemap) automatically creates an sitemap.xml file that conforms to the Sitemap specification. This allows search engines that support the Sitemap standard (including Google, Yahoo!, and Microsoft) to build a better index of your site.

The module has several configuration options which allow the site administrator to customize the sitemap output, submit the sitemap to Google automatically, and log downloads of the sitemap.

You can:

CSS Tips, Tricks, and Techniques

In this section of the handbook you can show off your favorite tips, tricks, and techniques to make your site pretty with nothing but CSS. If your tip is theme-specific, be sure to specify to which theme it applies.

These are to be CSS-only tips, please do not include anything that also requires any coding in a page or in a module. There is a modules snippets section for those tips.

Feel free to add new tips or to enhance ones that are already posted. In fact, please try to reduce duplication as much as possible. We'd rather improve an existing page, then add another one.

PLEASE NOTE! The following tips are user-submitted. Apply them at your own risk! In general, CSS will not "break" your site, but it can cause unexpected formatting or lack of user control.

If you are developing a brand new theme, see the Theme developer's guide.

Javascript snippets can be found in the Javascript section.

Block quotes

If you would like to display multi-paragraph quotes with quotation marks as characters, rather than using images, you could use this;-

blockquote p::before {
  content: open-quote;
}

blockquote p::after {
  content: close-quote;
  visibility: hidden;
}

blockquote p:last-child::after {
  visibility: visible;
}

Of course, you can add indenting etc. as required.

Markup is simply;–

<blockquote>
  <p>paragraph one</p>
  <p>paragraph two</p>
  <p>paragraph three</p>
  <p>paragraph four</p>
</blockquote>

NB Only tested on Firefox.

Happy quoting.

Highlight the expandible section of a menu

This tip changes the background of an expanded menu section, highlighting that you are in it. It works really well with my tip on highlighting the actual item.

Another thing I do here is "shrink" the menu item font size, but slightly increase the size of the parent item when it is expanded. I also set the background color of the whole menu<.p>

.menu .leaf {font-size:0.9em; background:#FFDAB9;}
.menu .leaf .active {border:1px solid #D2691E;}
.menu .collapsed {font-size:0.9em; background:#FFDAB9;}
.menu .expanded {font-size:1.0em; background:#FFEBCD;}
#sidebar-left, #sidebar-right {background-color: #FFDAB9;}

Iconification : adding UI icons using (mostly) CSS

It is currently fairly easy to insert icons into the interface of your Drupal5 site using only CSS. However, there are one or two pitfalls and limitations along the way, which I hope to try and document. As ever, this is a work in progress, so please do share your thoughts and ideas by adding comments, or even new pages!

Important note for iconification of Garland themes: If you have color.module enabled, do not add your iconification CSS code to style.css! Otherwise, when you resubmit your color.module settings, color.module redirects all the icon file URL paths off into to the files/color/#colorschemecode/icons folder :-P If your icon images are very theme-specific (ie they only work within a given colour-scheme) this might be an advantage, but my approach is to try and use more generic icons in order to save lots of work :-) So ...

Groundwork: We need to do some filesystem groundwork before we get going - it's very easy, and the advantage of creating new (custom) files and folders is that they will not get overwritten when you update your Drupal installation files.

In your theme folder (eg Drupal5/Themes/Garland/icons.css):

1. create a new file called icons.css:. this keeps (almost) all our iconification code in one place.

2. next, edit the page.tpl.php file to append the following line of code to the style header calls:

<style type="text/css" media="all">@import "<?php print base_path() . path_to_theme() ?>/icons.css";</style>

3. create a new folder called 'icons' inside your theme folder (eg Drupal5/Themes/Garland/icons) to keep all your icon images together, and your filepaths simple.

adding icons to module operator links

Prepending icons to module function labels is very easy to do with simple CSS. Because the CSS hooks are created by module functions they are relatively theme-safe. But this only works if the module is generating proper CSS hooks. If a particular module is not doing this properly, the best thing to do is raise it in that module's issue queue.

For this example, the comment.module generates a link to 'Add new comment'. the HTML looks like this:

<div class="links">
  <ul class="links inline">
    <li class="first last comment_add">
      <a href="/test_site/comment/reply/62#comment-form" title="Share your thoughts and opinions related to this posting." class="comment_add">Add new comment</a>
    </li>
  </ul>
</div>

All we need is the comment_add class attribute (which happens to be attributed to a list item). So all we do is add the following code to our icons.css file:

/** prefix 'Add new comment' link with icon **/
.comment_add a {
    padding-left: 20px;
    padding-bottom: 2px;
    background:url(icons/comment.png)  no-repeat;
}

If you want to show the icon but hide the 'Add new comment' text, you can use this code instead, but beware that a CSS block gets an automatic linebreak before and after the element (so it doesn't work well on $links ... )

/** replace 'Add new comment' link with icon **/
.comment_add a {
    display: block;
    text-indent: -10000px;
    background:url(icons/comment.png)  no-repeat;
}

This works for most of the iconficiation you are likely to need, as long as you can find the right CSS tag (be it a class or and id attribute) for the label, you can re-use the code snippets editing only the .comment_add a and comment.png references to suite.

prefixing Block Titles with an icon

Unfortunately there's not currently a simple <div class="block_x_title"> tag, but we can target the block title by interpreting contextual tags. Currently Drupal is quite strict about generating consistent tags for blocks so this method should be fairly reliable!

All blocks have a class attribute denoting the module that generated the block, so specifying the class will apply the same icon to all the blocks generated by that module. For example, both Login and Navigation blocks come from the (core) user.module :

/** iconify Navigation & Log-in block titles **/
.block-user h2 {
    padding-left: 20px;
    background:url(icons/user.png)  no-repeat;
    background-position: center left;
}

NB: in order to work background-position: ... ; must follow no-repeat;.

Some modules (eg Views!) create more than one block, so the Block id attribute includes a $delta (eg id="block-user-0") which will specify different icons for individual blocks :

/** iconify Log-in block title only **/
#block-user-0 h2 {
    padding-left: 20px;
    background:url(icons/login.png)  no-repeat;
    background-position: center left;
}

/** iconify Navigation block title only **/
#block-user-1 h2 {
    padding-left: 20px;
    background:url(icons/user.png)  no-repeat;
    background-position: center left;
}

Note on using the <h2> tag:To identify the block title we target the <h2> tag which is set in the theme's block.tpl.php. If your theme changes this (eg to <h3>) it breaches coding standards for structural mark-up; <h2> defines a second-level heading. If you want to change the size of an <h2> element, you should do this with CSS: eg

h2 {
  font-size: 160%;
  line-height: 130%;
}

prefixing the Current Node Title with an icon

Unfortunately the node title does not carry a handy CSS tag, and trying to select it by context is not very reliable either because it doesn't (eg Garland) have a parent <div> container that distinguishes it from other <h2> elements in page content (eg the comments header).

The quick solution is to add a tag using #yourtheme's page.tpl.php; eg Garland:
(note we use an id tag because there should only ever be one instance of this title on a given page.)

- <?php if ($title): print '<h2'. ($tabs ? ' class="with-tabs"' : '') .'>'. $title .'</h2>'; endif; ?>
+ <?php if ($title): print '<span id="node_title"><h2'. ($tabs ? ' class="with-tabs"' : '') .'>'. $title .'</h2></span>'; endif; ?>

and then in icons.css add:
/** iconify node-title only **/
#node_title h2 {
    padding-left: 20px;
    background:url(icons/user.png)  no-repeat;
    background-position: center left;
}

IE Conditional Comments

Browser detection using JavaScript is a touchy subject these days.

Setting IE's font size to match the rest of the world's browsers is no easy task. Or is it? Many themers feel a need to adjust the naturally gargantuan tendency of IE's fonts, or use a CSS hack to force IE to comply with a proper box model. Unfortunately for Palm Pilot users, Blackberries, screen readers, and even older versions of Opera, turning to JavaScript browser detection is a failed proposition. There are so many different browsers and screen sizes out there that it becomes impossible to feed each one it's own unique style sheet.

The savvy themer will no doubt be asking, "Why not use one style sheet for all browsers, and then use JavaScript to feed IE it's own settings?" Good point, but there's a better way than scripting to get this done.

Microsoft has enabled the Drupal themer to send Internet Explorer it's very own set of CSS rules using a modified HTML comment tag. This comment is not a valid tag according to HTML standards, it's just a comment. All browsers should simply ignore it and move on. IE, however, will read this unique tag and follow every instruction inside it. The Conditional Comment is wrapped around a <link> tag that contains IE's very own stylesheet. Here's how it looks:

<!--[if IE]>
    <link href="screenStyle4IE.css" rel="stylesheet" type="text/css" media="screen" />
<![endif]-->

Place this tag only within the <head> of the page. Once the <link> tag is parsed by IE, any CSS in the screenStyle4IE stylesheet will take over. Remember that CSS is a cascading ruleset, so any overrides to previous rules must necessarily come after the normal CSS include link. Add a Conditional Comment to Garland's page.tpl.php file like this:

<head>
  <title>php print $head_title</title>
  php print $head
  php print $styles
  php print $scripts
  <style type="text/css" media="print">@import "php print base_path() . path_to_theme() /print.css";</style>
   style type="text/css" media="screen">@import "php print base_path() . path_to_theme() /screen.css";</style>
   style type="text/css" media="handheld">@import "php print base_path() . path_to_theme() /handheld.css";</style>
  <!--[if lt IE 7]>
    <style type="text/css" media="all">@import "php print base_path() . path_to_theme() /fix-ie-layout.css";</style>
  <![endif]-->
  <!--[if IE ]>
  <style type="text/css" media="all">@import "php print base_path() . path_to_theme() /fix-ie-font-sizes.css";</style>
  <![endif]-->
</head>

This flexibility is astounding! It is now possible to feed a class for layout divs to all browsers, and then override the size, z-index, float, or margins of that div specifically for IE.

Font sizes, you say? But of course! IE's fonts are always one size larger than other browsers. Tame them by specifying style rules in fix-ie-font-sizes.css that are one size smaller that the corresponding rule in your regular stylesheet. For instance, if p{font-size:normal}, then fix-ie-font-sizes would spec p{font-size:small}. This keeps all browsers in somewhat of a uniformity without having to rely on font percentage hacks.

Oh, and if you want to hone in on specific versions of IE, refer to the previous code block for an example. <!--[if lt IE 7]> means "All instances of IE that are less than IE7". For more info on "lt IE6", "lte IE7" and so on, refer to Microsoft's Conditional Comments workshop

http://drupal.org/node/16173
http://drupal.org/node/509

Making your content look "fixed width" yet be fluid

Some people don't want their site to use the full width of the screen, but this can cause problem for users who aren't using a full sized window. This tip "shrinks" the content area, yet remains fluid, so it works better with smaller windows. It does not change the header and footer size, so they still extend across the full width.

#content {margin-left:10%; margin-right:10%;}

Note, this also moves the side bars in. You will have "white space" (actually, your body background color) on both sides.

Showing which Menu item you're on

This tips allows you to highlight, or show the focus on, the menu item your user is currently on. By default, Drupal just turns the item black. This tip places a box around the item. One nice "extra" to it, is that it also works on the book navigation block as well.

.menu .leaf .active {border:1px solid #D2691E;}

I know this tip works in Bluemarine and Pushbutton, but should work in all themes.

Use a color that contrasts with, but compliments your menu color scheme.

Styling more usable form buttons

To try and prevent clicking on destructive buttons, it might be a good practice to make the Delete button less important, yet still available. To do that, you can simply style each of the button types Drupal puts out—edit, submit and delete. Here's an example making the submit button boldest, and removing the button style of delete.

#edit-preview {
  border: 2px solid #CACC00;
  color: #A6A800;
}
#edit-submit {
  border: 2px solid #769405;
  color: #668004;
}
#edit-delete {
  background: none;
  border: none;
  color: #999;
}

My favorite module or theme is outdated. What next?

So, a contributed module you like has not been updated to the latest version of Drupal. What now?

The best place to start is the project page for the module to see if there is a more current version packaged and ready to go.

You can also check the pending patches list for your module. Look to see if there are any patches that are labeled for the version of Drupal you are using. If there are:

If no patch exists, check the Issues queue for the module.

A consideration is also that the features in that module have been rolled into another module. As Drupal core advances things become possible that may not have been in the past. Contributed module developers may have joined forces to collaborate on a better module api that helps reduce the work on one person. If this happens it is usually noted inthe modules project page description.

On a last note, if you are currently using Drupal, you may consider staying with the version of Drupal you are on. As long as there are security releases for it and it is supported by the community. The downside to this is that upgrading multiple versions may be more difficult, but that is something that needs to be evaluated on a site by site basis.

Finding broken links (and other things Drupal doesn't do)

If Drupal doesn't have a module that does what you want, you may find a piece of software elsewhere that will help you with your Drupal site. And you may find it by searching in the Drupal forums.

For example:

In a Drupal forum, whatistocome asked,

Are there any modules that will check a site for broken links? (Search didn't turn up anything.) If not, do you know of any free third-party solutions that are "Drupal friendly"?

Several other admins chimed in, expressing a similar need.

And lccweb answered:

If you are running Windows on your workstation try this program: "xenu link sleuth" (freeware). I downloaded my copy off of Snapfiles.com, but this is the link that the "about" brings up: http://home.snafu.de/tilman/xenulink.html.

I used it to track down all the pages that linked to a node that we had given a new alias. It found them plus some we weren't aware of.

It can be set to ignore external links and internal ones you might not want to report. Once the run is complete, a list of broken links is listed, along with some stats. Pick one from the list and right click show the property and you can see what links to that entry.

If you have the same need to find broken links, perhaps Xenu will be your solution. And if you have other needs for your site that Drupal modules don't meet, perhaps in the Drupal forums you will find a way to meet them.

PHPTemplate Theme Snippets

This section of our handbook is a collection of snippets for use with PHPTemplate based themes. (The PHPTemplate theme engine is the default theme engine with Drupal 4.7)

Please Note! Snippets are user submitted. It's impossible to test them all so please use at your own risk! Don't just copy and paste them without checking first on a test site.

For users who have setup Drupal using an alternate database to the default (MySQL), please note that the snippets may contain some database queries specific to MySQL.

I'm in the process of building and organizing this section at the moment. Feel free to add a new child page to submit snippets and drop me an email if you have any questions/queries/suggestions. Thanks - Dublin Drupaller (June 8th 2007)

Before submitting your own snippets, please read the guide to adding your own snippets so we can maintain consistency and uniformity to this section of the handbook.

A guide to adding your own snippets

guidelines

  1. please keep your handbook snippet page as clear and as brief as possible
  2. ensure you test the snippet and tag your page with the appropriate version(s) of Drupal
  3. please categorise your snippets. For example, put blog related snippets as a CHILD PAGE to the Customising the blogs layout page instead of having all the snippets on the same page which will make it more difficult as more snippets are added
  4. copy the template snippet below into your clipboard
  5. Go to the main PHPTEMPLATE THEME SNIPPETS PAGE
  6. Scroll down to the bottom and click on ADD CHILD PAGE
  7. Contact Dublin Drupaller if you have any questions or have submitted a page and would like it approved.

description

Type a very brief description of your snippet here.

Step 1 of 2

Outline what this step is about here.

<h2>description</h2>
Type a very brief description of your snippet here.

<h3>Step 1 of 2</h3>

Outline what this step is about here.
<?php
/**
* insert your snippet here
  */
?>

Step 2 of 2

Insert an introduction to a second step if required.

Outline what this step is about here.

<h3>Step 2 of 2</h3>

Insert an introduction to a second step if required.

Outline what this step is about here.
<?php
/**
* insert your snippet here
  */
?>

Notes

  • insert notes to help and aid users using the snippet
  • link to a forum thread where a discussion related to the snippet is ongoing.
  • include any dependencies, such as a specific module that needs to be enabled first.
<h3>Notes</h3>

<ul><li>insert notes to help and aid users using the snippet</li>
<li>link to a forum thread where a discussion related to the snippet is ongoing.</li>
<li>include any dependencies, such as a specific module that needs to be enabled first.</li>
</ul>

Customising full page layouts and sections

description

This section outlines and provides snippets for customising your full page layout for certain page types. Useful if you want to have different variations of your theme for different sections of your site.

Full page layouts are controlled by the page.tpl.php layout file when using the PHPTEMPLATE ENGINE.

usage

Scroll down for some example snippets, that you can copy-and-paste and use in your custom page-type.tpl.php layout files.

It is recommended that you use a text editor like notepad.exe, or equivalent, when editing your layout files.

For PC users, a useful tool for editing a mix of HTML & PHP is Crimson Editor (Freeware), for Mac users there is the free TextWrangler editor.

available variables

The following is a list of variables available to your custom page-type.tpl.php layout files.

  • head_title: The text to be displayed in the page title.
  • language: The language the site is being displayed in.
  • site: The name of the site, always filled in.
  • head: HTML as generated by drupal_get_html_head() (needed to dynamically add scripts to pages)
  • onload_attributes: Onload tags to be added to the head tag, to allow for autoexecution of attached scripts.
  • directory: The directory the theme is located in , ie: themes/box_grey or themes/box_grey/box_cleanslate
  • logo: The path to the logo image, as defined in theme configuration.
  • site_name: The site name of the site, to be used in the header, empty when display has been disabled.
  • site_slogan: The slogan of the site, empty when display has been disabled.
  • search_box: True(1) if the search box has been enabled.
  • search_url: URL the search form is submitted to.
  • search_button_text: Translated text on the search button.
  • search_description: Translated description for the search button.
  • title: Title, different from head_title, as this is just the node title most of the time.
  • primary_links (array): An array containing the links as they have been defined in the phptemplate specific configuration block.
  • secondary_links (array): An array containing the links as they have been defined in the phptemplate specific configuration block.
  • breadcrumb: HTML for displaying the breadcrumbs at the top of the page.
  • tabs: HTML for displaying tabs at the top of the page.
  • messages: HTML for status and error messages, to be displayed at the top of the page.
  • layout: This setting allows you to style different types of layout ('none', 'left', 'right' or 'both') differently, depending on how many sidebars are enabled.
  • help: Dynamic help text, mostly for admin pages.
  • styles: Required for stylesheet switching to work. This prints out the style tags required.
  • mission: The text of the site mission.
  • is_front: True if the front page is currently being displayed. Used to toggle the mission.
  • sidebar_left: The HTML for the left sidebar.
  • content: The HTML content generated by Drupal to be displayed.
  • sidebar_right: The HTML for the right sidebar.
  • footer_message: The footer message as defined in the admin settings.
  • closure: Needs to be displayed at the bottom of the page, for any dynamic javascript that needs to be called once the page has already been displayed.

Customising the full page layout and sections based on node type

description

This explains how to customise the entire page layout depending on the node type. An example application is if you wanted your entire blog pages to look different to your book pages.

usage

As an illustrative example, the following step-by-step approach shows you how to setup different (full) page layouts for your blogs, books and the front page to your site.

Step 1 of 2
  1. make a copy of your page.tpl.php file and rename it to be page-default.tpl.php.
  2. make further copies your page.tpl.php file it and rename them to be page-front.tpl.php, page-blog.tpl.php and page-book.tpl.php etcetera..
  3. using a text editor like notepad.exe or equivalent, modify the layout of each tpl.php file to suit your desires
  4. upload your new page-type.tpl.php layout files to your active theme folder
Step 2 of 2
  1. Using a text editor like notepad.exe or equivalent, replace the contents of your page.tpl.php file with the snippet below
  2. Ensure that you have a page-default.tpl.php file as part of your collection of layouts.
  3. Upload your new page.tpl.php file to your active theme folder and your new layouts will take effect automatically
<?php

/**
* This snippet loads up different page-type.tpl.php layout
* files automatically. For use in a page.tpl.php file.
*
* This works with Drupal 4.5,  Drupal 4.6 and Drupal 4.7
*/

if ($is_front) {/* check if it's the front page */
   
include 'page-front.tpl.php'; /*load a custom front-page.tpl.php */
   
return; }

if (
$node->type == 'book') {/* check if it's a book page */
   
include 'page-book.tpl.php'; /*load a page-book.tpl.php */
   
return; }

if (
$node->type == 'blog') {/* check if it's a blog node */
   
include 'page-blog.tpl.php'; /*load  page-blog.tpl.php */
   
return; }

if (
$node->type == 'image') {/* check if it's an image node */
   
include 'page-image.tpl.php'; /*load page-image.tpl.php */
   
return; }

if (
$node->type == 'forum') {/* check if it's a forum node */
   
include 'page-forum.tpl.php'; /*load page-forum.tpl.php */
   
return; }

include
'page-default.tpl.php'; /*if none of the above applies, load the page-default.tpl.php */
   
return;

?>

Customising the full page layout and sections using CSS and unique BODY Class & IDs

description

This explains how to control the entire page layout depending on the node type using CSS styles.

Thanks to Zach Harkey for submitting the original snippet.

This snippet works with Drupal 4.5, 4.6 and 4.7.

usage

This snippet will create a body class and id for every page on your site. An example application might be for adjusting a fixed width layout when you use the administrative options.

Using this snippet, when you go to to admin/themes, for example, you may use the following style hooks in your style sheet to control how the full page looks.

<body class="section-admin" id="page-admin-themes">

In your style sheet (the CSS file in your active theme folder), you simply add another line to change the layout when you click on the ADMINISTER menu:

.content { width="744px" }
.section-admin .content { width="100%" }
Step 1 of 2
  1. make a backup copy of your page.tpl.php file.
  2. using a text editor like notepad.exe or equivalent, replace the <body <?php print theme("onload_attribute"); ?>> line with the snippet below in your page.tpl.php file
  3. upload your edited page.tpl.php layout file to your active theme folder
<?php
/**
*  This snippet creates a <body> class and id for each page.
*
* - Class names are general, applying to a whole section of documents (e.g. admin or ).
* - Id names are unique, applying to a single page.
*/
// Remove any leading and trailing slashes.
$uri_path = trim($_SERVER['REQUEST_URI'], '/');
// Split up the remaining URI into an array, using '/' as delimiter.
$uri_parts = explode('/', $uri_path);

// If the first part is empty, label both id and class 'main'.
if ($uri_parts[0] == '') {
   
$body_id = 'main';
   
$body_class = 'main';
}
else {
   
// Construct the id name from the full URI, replacing slashes with dashes.
   
$body_id = str_replace('/','-', $uri_path);
   
// Construct the class name from the first part of the URI only.
   
$body_class = $uri_parts[0];
}
/**
* Add prefixes to create a kind of protective namepace to prevent possible
* conflict with other css selectors.
*
* - Prefix body ids with "page-"(since we use them to target a specific page).
* - Prefix body classes with "section-"(since we use them to target a whole sections).
*/
$body_id = 'page-'.$body_id;
$body_class = 'section-'.$body_class;
print
"<body class=\"$body_class\" id=\"$body_id\"";
print
theme('onload_attribute');
print
">";
?>
Step 2 of 2
  1. Every page will now have a unique BODY class and BODY ID. e.g. <body class="section-admin" id="page-admin">
  2. Edit your style.css file in your active theme folder to style each section.
notes
  • The snippet uses the link to determine what the class and ID are called, so please note that if you have Drupal in a subdirectory, your BODY class and BODY ID titles will be something like this: <body class="section-private" id="page-private-profile"> . Where the Drupal site is in the www.example.com/private subfolder and the user is viewing the PROFILE (User list) page.
  • Thanks to Zach Harkey for posting the original page. Click through for more details.

Customising the full page layout and sections based on path

description

This explains how to customise the entire page layout depending on the PATH and/or taxonomy terms.

usage

A simple example would be using the following path to tell Drupal to load a custom page-admin.tpl.php layout file.

e.g. If the current path is www.example.com/admin/* The arg(n) variable is therefore "admin" and we can use that to tell Drupal to load a page-admin.tpl.php.

Step 1 of 2
  1. make a copy of your page.tpl.php file and rename it to be page-default.tpl.php.
  2. make further copies your page.tpl.php file it and rename them to be page-front.tpl.php, page-blog.tpl.php and page-book.tpl.php etcetera..
  3. using a text editor like notepad.exe or equivalent, modify the layout of each tpl.php file to suit your desires
  4. upload your new page-type.tpl.php layout files to your active theme folder
Step 2 of 2
  1. Using a text editor like notepad.exe or equivalent, replace the contents of your page.tpl.php file with the snippet below
  2. Ensure that you have a page-default.tpl.php file as part of your collection of layouts.
  3. Upload your new page.tpl.php file to your active theme folder and your new layouts will take effect automatically
<?php

/**
* This snippet loads up different page-type.tpl.php layout
* files automatically. For use in a page.tpl.php file.
*
* This works with Drupal 4.5,  Drupal 4.6 and Drupal 4.7
*/

if (arg(0)=="admin") {/* check if the path is example.com/admin */
   
include 'page-admin.tpl.php'; /*load a custom page-admin.tpl.php */
   
return; }

if (
$node->type == 'blog') {/* check if the path is example.com/blog  */
   
include 'page-blog.tpl.php'; /*load page-blog.tpl.php */
   
return; }

/*insert more layout calls here before the call to page-default.tpl.php */

include 'page-default.tpl.php'; /*if none of the above applies, load the page-default.tpl.php */
   
return; }
?>
adding more layouts

The example snippet above should be self explanatory and includes snippets to load a custom page-admin.tpl.php and page-blog.tpl.php.

If you want to add more layout calls to your page.tpl.php file, copy this snippet and edit using the notes below.

if ($node->type == 'blog') {/* check if the path is example.com/blog  */
    include 'page-blog.tpl.php'; /*load page-blog.tpl.php */
    return; }
tips and more examples

Here are more examples of using the path that you can use to extend out your layouts even further.

arg(0)=="admin"// is /admin

arg(0) =="node"// is /node

arg(0)=="user" // is /user

arg(0)=="node" && arg(1)=="add" // is /node/add

arg(0)=="node" && arg(arg(2)=="edit" // is /node/###/edit

arg(0)=="user" && arg(1)=="add" // is /user/add

arg(0)=="admin" && arg(1)="user" && arg(2)=="create" // is /admin/user/create

arg(0)=="alias" && arg(1)=="alias1" is /alias/alias1

arg(0)=="taxonomy" && arg(1)=="term" && arg(2)=="term#" // is /taxonomy/term/term#

//arg(1)=="comment"

//arg(2)=="reply"

front page layout switcher for your page.tpl.php file

description

This snippet automatically switches your theme to a custom page-front.tpl.php for the front page of your Drupal site.

It's an alternative method of customising your front page to using the front_page.module.

usage
  1. Make a copy of your page.tpl.php file and rename it to page-front.tpl.php.
  2. Using a text editor like notepad.exe or equivalent, copy the snippet and place it at the very top of your page.tpl.php file.
  3. Edit your page-front.tpl.php layout file
  4. Upload your new page-front.tpl.php layout file and your edited page.tpl.php file to your active theme folder
<?php

/**
* This snippet tells Drupal to load up a different page-front.tpl.php layout
* file automatically. For use in a page.tpl.php file.
*
* This works with Drupal 4.5,  Drupal 4.6 and Drupal 4.7
*/

if ($is_front) {
  include(
'page-front.tpl.php');
  return;
}

/**
* This snippet MUST go at the very top of your page.tpl.php file.
* the rest of your page.tpl.php in entirety should follow this snippet.
*/
?>
notes

Admin theme for node (add, edit) and user (edit)

If you want to use your selected admin theme when creating a node or editing an user, you can do this easily with a very small custom module. In your custom module's hook_menu(), add this (outside of the $may_cache block):

<?php
 
if ((arg(0) == 'node' && arg(1) == 'add') ||  (arg(0) == 'node' && arg(2) == 'edit') ||  (arg(0) == 'user' && arg(2) == 'edit') ) {
      global
$custom_theme;
     
$custom_theme = variable_get('admin_theme', '0');
     
drupal_add_css(drupal_get_path('module', 'system') .'/admin.css', 'module');
  }
?>

Or whatever other set of path rules you want.

See also:

Customising page layout using taxonomy terms

description

This explains how to customise the entire page layout depending on taxonomy terms.

Step 1 of 2
  1. Make 2 copies of your page.tpl.php file and rename them page-termname.tpl.php and page-default.tpl.php
  2. using a text editor like notepad.exe or equivalent, edit your page.tpl.php and add the snippet below or variations of the same snippet.
  3. Upload your new page.tpl.php and custom page-temname.tpl.php layout files to your active theme folder and your new layout will take effect automatically
<?php

/**
* This snippet loads up a different page-termname.tpl.php layout
* file based on the taxonomy term of the node.
*
* For use in a page.tpl.php file.
*
* This works with Drupal 4.7
*/

/* load up the taxonomy terms for this node and page */

$terms = taxonomy_node_get_terms($node->nid);
rsort($terms);

/**
* Check to see if the taxonomy term matches your query and load
* a custom page-termname.tpl.php layout file
*/

if ($terms[0]->name == 'brochure') {include 'page-brochure.tpl.php';
    return; }

/*If the taxonomy term name doesn't match, load the page-default.tpl.php */

else {include 'page-default.tpl.php';
   return;}
?>
adding more layouts/using variations of the same snippet

The example snippet above should be self explanatory and loads a custom page-brochure.tpl.php layout file when the taxonomy term name is 'brochure' .

If you prefer to refer to the taxonomy term ID, rather than the taxonomy term name, you can use the following snippet which looks for the $terms[0]->tid instead of the taxonomy term name.

<?php
if ($terms[0]->tid == '3') {include 'page-brochure.tpl.php'; /*load a custom page-brochure.tpl.php if the taxonomy term of the current node matches */
   
return; }
?>

display/hide content only on the front page

description

This snippet allows you to hide/display content on the front pageof a Drupal site.

usage
  1. Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the snippet into your page.tpl.php or your custom page-type.tpl.php file.
  2. Change "false" to "true" to use the same snippet for displaying content ONLY on the front page.
  3. Tested and works with Drupal 4.5, 4.6 and Drupal 4.7
  4. Change the div class names or the link prefix text to suit.

The following example snippet will insert an image called blocks-top.jpg on all pages EXCEPT the front page.

<?php if ($is_front == "false"): ?>
  <div id="snapshot">
    <img src="http://www.example.com/images/blocks-top.jpg" /><br />
  </div>
<?php endif; ?>

How to split the results into separate pages if the snippet returns lots of results

Creates a paginated list of node titles of a specific type, within x days of today with a link to each node.

<?php
/**
* Creates a paginated list of node titles of a specific type, within x days of today
* with a link to each node.
*
* To change which type is listed, simply edit the $node_type string.
* To change the amount of results per page, edit the $list_length variable.
*
* This works with both Drupal 4.6 and Drupal 4.7
*/
$node_type = "image";
$time_lapse = strtotime("-50 days");
$list_length = 25;
$start_stamp = date("Y-m-d", $time_lapse);
$end_stamp = date("Y-m-d");
$start_stamp = strtotime($start_stamp);
$end_stamp = strtotime($end_stamp);
$sql = db_rewrite_sql("SELECT n.title, n.nid FROM {node} n WHERE n.created < %d AND n.created > %d AND n.type = '%s'");
$result = pager_query(sprintf($sql, $end_stamp, $start_stamp, $node_type), $list_length);
while (
$anode = db_fetch_object($result)) {
$output []= l($anode->title, "node/$anode->nid");
}
print
theme('item_list', $output);
print
theme('pager', NULL, $list_length);
?>

Same as before, if you wish to give the list a title then change print theme('item_list', $output); into print theme('item_list', $output, 'Node Titles');

i18n custom frontpage

Hi Folks

I was trying custom my frontpage with i18n. (with tpl.php files) I read a lot of things in special http://drupal.org/node/134003

But unfortunately didnt work for me.

So I created this snippet. I am not sure will work in production site because my website isnt finished.

I will appreciate comments and know if is working well

<?php
if (arg(0)=="admin" && i18n_get_lang() == 'en') {
  include(
'page-default.tpl.php');
  return;
}
elseif (
arg(0)=="admin" && i18n_get_lang() == 'it') {
  include(
'page-default.tpl.php');
  return;
}
/* repeat to all languages */
elseif (arg(0)=="node" && arg(1)=="add" && arg(2)=="mycck" && i18n_get_lang() == 'en') {
  include(
'page-mycck.tpl.php');
  return;
}
elseif (
arg(0)=="node" && arg(1)=="add" && arg(2)=="mycck" && i18n_get_lang() == 'pt') {
  include(
'page-mycck.tpl.php');
  return;
}
elseif (
arg(0)=="node" && arg(1)=="add" && arg(2)=="mycck" && i18n_get_lang() == 'it') {
  include(
'page-mycck.tpl.php');
  return;
}
/* repeat to all languages, and insert all paths of drupal */

elseif (i18n_get_lang() == 'en') {include('front-en.tpl.php');return;}
elseif (
i18n_get_lang() == 'it') {include('front-it.tpl.php');return;}
elseif (
i18n_get_lang() == 'pt') {include('front-pt.tpl.php');return;}
elseif (
i18n_get_lang() == 'fr') {include('front-fr.tpl.php');return;}
else
include
'page-default.tpl.php'; /*if none of the above applies, load the page-default.tpl.php by macm */
   
return;
?>

If isnt working properly fell free to remove it with our webmaster.

Regards

macm
Mario

Optional CSS configurable by your visitors

Description

This snippet shows how to offer a link, effectively a switch, to allow users to turn-on additional styles via an additional stylesheet. In this example the additional sheet is called "High_Contrast.css" which brightens up fonts and darkens the backgrounds (effectively overriding certain styles in style.css).

It works for anonymous users (via a cookie) and logged-in users (via a profile setting). My reason for doing this was because I wanted a dark design for it's visual effect, but it presented a problem for some users (one of the users being the site owner!).

The limitation of this script is that it is not able to replace style.css, nor is it set up to allow more than one alternative.

Step 1 of 3 - Preparation
  1. Activate the profile module, and go to settings->profile and set up a new "list selection" field. Create two list options: "Default" and "High Contrast".
  2. Copy the existing style.css and delete everything that is not relevant, then replace the colors (and anything else you want to override).
  3. Save your new stylesheet as High_Contrast.css and put it in your theme directory - the filename must match the corresponding profile option.
  4. Save the new stylesheet in your theme folder.
Step 2 of 3 - template.php

Create a file called template.php and add to it the code below. Save the file to your theme directory. Replace "MYTHEME" with the name of your theme.

<?php

/*
* Determine whether the 'high contrast' stylesheet should be added.
*/
function  MYTHEME_page(&$content) {

  if (isset(
$_COOKIE['theme_profile_changesheet'])) {
   
$style_sheet = drupal_get_path('theme','MYTHEME') .'/'.
   
$style_sheet .= _switcher_set_style($_COOKIE['theme_profile_changesheet']) .'.css';
   
setcookie('theme_profile_changesheet',FALSE);
  }
  else {
   
$style_sheet = drupal_get_path('theme','MYTHEME') .'/'. _switcher_get_style() .'.css';
  }

  if (
file_exists($style_sheet)) {
   
theme_add_style($style_sheet);
  }
 
//having overridden phptemplate_page(), we need to call it now.
 
return phptemplate_page($content);
}

/*
* Helper to SAVE the current selection to profile or cookie. */
*/
function
_switcher_set_style($change_style) {

  global
$user;

//  var_dump($user); die();
 
if (!$user->uid) {
   
setcookie('theme_profile_stylesheet', $change_style);
   
$_COOKIE['theme_profile_stylesheet']=$change_style;
  }
  else {
   
//hacked from user.module
   
$data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $user->uid)));
   
$data['profile_stylesheet'] = $change_style;
   
$query .= "data = '%s' ";
   
$v[] = serialize($data);
   
db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($user->uid)));
   
$user->profile_stylesheet = $change_style;
  }

  return
$change_style;
}

/*
* Helper to LOAD the current selection from profile or cookie. */
*/
function
_switcher_get_style() {

  global
$user;

  if (!
$user->uid) {
   
//check cookie for anonymous user
   
if (isset ($_COOKIE['theme_profile_stylesheet'])) {
     
$style_sheet = $_COOKIE['theme_profile_stylesheet'];
    }
  }
  elseif (isset(
$user->profile_stylesheet)) {
   
//check profile setting for logged-in user
   
$style_sheet = str_replace(' ','_',$user->profile_stylesheet);
  }
  else {
   
//otherwise assume default (which will do nothing)
   
$style_sheet = 'Default';
  }
  return
$style_sheet;
}

/*
* Create three variables for use in the template.
*/
function  _phptemplate_variables($hook, $vars) {

 
$style_sheet = drupal_get_path('theme','MYTHEME') .'/'. _switcher_get_style() .'.css';

  if (
file_exists($style_sheet)) {
   
$change_sheet = 'Default';
   
$change_text = 'Normal'; //the 'turn-off' link will say "Normal"
 
}
  else {
   
$change_sheet = 'High_Contrast';
   
$change_text = 'Bright'; //the 'turn-on' link will say "Bright"
 
}

 
//use javascript of the switch link that will set a change flag before the page is refreshed
 
$vars['style_switcher']['javascript'] = " onClick=\"document.cookie='theme_profile_changesheet=$change_sheet';\"";
 
$vars['style_switcher']['text'] = $change_text; //what the link will say
 
$vars['style_switcher']['self'] = $_SERVER['REQUEST_URI']; //link back to the current page.

 
return $vars;
}
?>
Step 3 of 3 - add bits to page.tpl.php

This piece of code is placed in your page.tpl.php, or equivalent file. The style of the <div> simply positions it in the top-right corner, but by stripping off the div tags, the output is just a link which can be placed anywhere.

<div style="position:absolute;top:0;right:0;z-index:10;"><a href="<?php print $style_switcher['self'] .'" '. $style_switcher['javascript'] .'>'. $style_switcher['text']; ?></a></div>
To conclude

On updating your files, you should be able to switch on your additional stylesheet by clicking the link. For an anonymous user, the setting persists while the browser is open. For a logged in user, the switch works the same, but the setting is recorded in their profile field and can be accessed via Account->Edit, and is therefore more persistent.

admin theme switcher snippet for your page.tpl.php file

description

This snippet automatically switches your theme to a custom page-admin.tpl.php layout file so a custom administrators theme is activated when needed.

Useful if you're using a fixed-width site design and your administration pages breaks the layout.

usage
  1. Make a copy of your page.tpl.php file and rename it to page-admin.tpl.php.
  2. Using a text editor like notepad.exe or equivalent, copy the preferred snippet and place it at the very top of your page.tpl.php file.
  3. Edit your page-admin.tpl.php layout file
  4. Upload your new page-admin.tpl.php layout file and your edited page.tpl.php file to your active theme folder
<?php

/**
* This snippet tells Drupal to load up a different page-admin.tpl.php layout
* files automatically. For use in a page.tpl.php file.
*
* This works with Drupal 4.5,  Drupal 4.6 and Drupal 4.7
*/

if  (arg(0) == 'admin' ||
(
arg(0) == 'node' && arg(1) == 'add') ||
(
arg(0) == 'node' && arg(2) == 'edit') ||
(
arg(0) == 'user' && arg(2) == 'edit')) {
    include
'page-admin.tpl.php'; /*load a custom page-admin.tpl.php */
   
return; }
?>

Customising node layouts

description

This section outlines and provides snippets for customising the node layout for certain node types.

(If you want to customise the layout of your FULL page, click through to the Customising full page layouts and sections collection of handbook pages and snippets).

usage

Scroll down for some example snippets, that you can copy-and-paste and use in your custom node-type.tpl.php layout files.

It is recommended that you use a text editor like notepad.exe, or equivalent, when editing your layout files.

For PC users, a useful tool for editing a mix of HTML & PHP is Crimson Editor (Freeware).

Available variables

  • $title : Title of node.
  • $node_url : Link to node.
  • $terms : HTML for taxonomy terms.
  • $name : Formatted name of author.
  • $date : Formatted creation date.
  • $sticky : True if the node is sticky on the front page.
  • $picture : HTML for user picture, if enabled.
  • $content : Node content, teaser if it is a summary.
  • $links : Node links.
  • $taxonomy (array) : array of HTML links for taxonomy terms.
  • $node (object) : The node object.
  • $main : True if the node is appearing in a context, like the front page, where only the teaser should be shown.
  • $page : True if the node is being displayed by itself as a page.
  • $submitted : Translated text, if the node info display is enabled for this node type.

Customising image gallery layouts

description

This describes how to override the default layout for Image Gallery nodes (when using the image.module).

benefits

I wanted to make minor changes and ammendments to how the galleries were displayed in Drupal using the image.module but I didn't want to hack the module.

By doing it this way means that you don't run the risk of breaking your Drupal installation by messing with the module files and it also means that upgrading is made easier. The override is contained in your /theme/theme_name/ folder and is simple to install and remove.

step 1 of 2

Step one is to catch the default gallery pages output from the image.module and point Drupal to your override template.

Copy the following code into a template.php file and upload it to your /THEMES/THEME_NAME/ folder. If you already have a template.php file in that folder, simply add the code to the bottom of the existing template file.

<?php
/**
* Catch the theme_image_gallery function, redirect through the template api
* and point Drupal to your image_gallery.tpl.php file to determine the layout
* of your image gallery pages.
*/
function phptemplate_image_gallery($galleries, $images) {
 
// Pass to phptemplate, including translating the parameters to an associative array. The element names are the names that the variables
  // will be assigned within your template.
  /* potential need for other code to extract field info */
return _phptemplate_callback('image_gallery', array('galleries' => $galleries, 'images' => $images));
  }
?>
step 2 of 2

Step 2 is to upload the default image_gallery.tpl.php file to the same THEMES/THEME_NAME folder.

Copy and paste the following code into a text editor like crimson editor (open source PHP editor) or NOTEPAD.EXE etc. save it with the filename: image_gallery.tpl.php and upload it into your THEMES/THEME_NAME/ folder. the same folder you uploaded the template.php file in step 1.

<?php
$size
= _image_get_dimensions('thumbnail');
 
$width = $size['width'];
 
$height = $size['height'];

 
$content = '';
  if (
count($galleries)) {
   
$content.= '<ul class="galleries">';
    foreach (
$galleries as $gallery) {
     
$content .= '<li>';
      if (
$gallery->count)
       
$content.= l(image_display($gallery->latest, 'thumbnail'), 'image/tid/'.$gallery->tid, array(), NULL, NULL, FALSE, TRUE);
     
$content.= "<h3>".l($gallery->name, 'image/tid/'.$gallery->tid) . "</h3>\n";
     
$content.= '<div class="description">'. check_output($gallery->description) ."</div>\n";
     
$content.= '<p class="count">' . format_plural($gallery->count, 'There is %count image in this gallery', 'There are %count images in this gallery') . "</p>\n";
      if (
$gallery->latest->changed) {
       
$content.= '<p class="last">'. t('Last updated: %date', array('%date' => format_date($gallery->latest->changed))) . "</p>\n";
      }
     
$content.= "</li>\n";
    }
   
$content.= "</ul>\n";
  }

  if (
count($images)) {
   
$height += 5;
   
$content = 'this is the list of gallries<br><br>';
   
$content.= '<ul class="images">';
    foreach (
$images as $image) {
     
$content .= '<li';
      if (
$image->sticky) {
       
$content .= ' class="sticky"';
      }
     
$content .= ' style="height : '.$height .'px; width : '.$width.'px;"';
     
$content .= ">\n";
     
$content .= l(image_display($image, 'thumbnail'), 'node/'.$image->nid, array(), NULL, NULL, FALSE, TRUE);
   
$content .= '<h3>'.l($image->title, 'node/'.$image->nid)."</h3>";
    if (
theme_get_setting('toggle_node_info_' . $image->type)) {
       
$content .= '<div class="author">'. t('Posted by: %name', array('%name' => format_name($image))) . "</div>\n";
       
$content .= '<div class="date">'.format_date($image->created)."</div>\n";
      }
     
$content .= "</li>\n";
    }
   
$content.= "</ul>\n";
  }

  if (
$pager = theme('pager', NULL, variable_get('image_images_per_page', 6), 0)) {
   
$content.= $pager;
  }

  If (
count($images) + count($galleries) == 0) {
     
$content.= '<p class="count">' . format_plural(0, 'There is %count image in this gallery', 'There are %count images in this gallery') . "</p>\n";
  }

  print
$content;
?>

Step 3: is to start making your changes. I'll post examples of the changes I made to the default layout to illustrate how to do that.

For discussion purposes and to avoid cluttering up the handbook, I have started a specific forum topic where you can post questions/snippets/tips and tricks etc.

Dub

Customising blog layouts

Customising how your blogs are displayed is extremely easy in Drupal using a phptemplate based theme. All you need to do is:

  1. Make a copy of your node.tpl.php file
  2. Rename it to node-blog.tpl.php
  3. in a text editor like notepad.exe or equivalent, edit the layout to suit your needs and upload it to your active theme folder
  4. Drupal will automatically pick up your new node-blog.tpl.php layout file and apply it
Available variables
  • $title : Title of node.
  • $node_url : Link to node.
  • $terms : HTML for taxonomy terms.
  • $name : Formatted name of author.
  • $date : Formatted creation date.
  • $sticky : True if the node is sticky on the front page.
  • $picture : HTML for user picture, if enabled.
  • $content : Node content, teaser if it is a summary.
  • $links : Node links.
  • $taxonomy (array) : array of HTML links for taxonomy terms.
  • $node (object) : The node object.
  • $main : True if the node is appearing in a context, like the front page, where only the teaser should be shown.
  • $page : True if the node is being displayed by itself as a page.
  • $submitted : Translated text, if the node info display is enabled for this node type.
Default template
<div class="node<?php print ($sticky) ? " sticky" : ""; ?>">
  <?php if ($page == 0): ?>
    <h2><a href="/<?php print $node_url ?>" title="<?php print $title ?>"><?php print $title ?></a></h2>
  <?php endif; ?>
  <?php print $picture ?>

  <div class="info"><?php print $submitted ?><span class="terms"><?php print $terms ?></span></div>
  <div class="content">
    <?php print $content ?>
  </div>
<?php if ($links): ?>
   
    <?php if ($picture): ?>
      <br class='clear' />
    <?php endif; ?>
    <div class="links"><?php print $links ?></div>
<?php endif; ?>
</div>
Splitting your main blog content into (x) multiple columns
Description

Intended for use as a custom node-blog.tpl.php layout file, this snippet splits the main body of a blog into multiple columns, like a news paper article.

Usage

The snippet is a complete node-blog.tpl.php file. Copy and paste it using a text editor like notepad.exe and upload it to your active theme folder.

You can specify how many columns by editing the $columns value and the spacing in betwen columns by increasing or decreasing the $column_spacing value.

Notes:
  1. Teasers are treated normally. The multiple columns are only displayed when the user is viweing the full blog page.
  2. Recommended for use with blogs using text only in the main content. The automated column-balance doesn't work sometimes if there are images within the text
  3. The Snippet uses a HTML Table to output the columns, if you know how to achieve the same using just DIVs please post it
  4. To discuss this snippet, please post on the multiple column snippet discussion thread in the forum
<?php  $columns = 3; // number of columns ?>
<?php  $column_spacing = 8; //spacing between columns in pixels ?>

<div class="node<?php print ($sticky) ? " sticky" : ""; ?>">
  <?php if ($page == 0): ?>
    <h2><a href="/<?php print $node_url ?>" title="<?php print $title ?>"><?php print $title ?></a></h2>
  <?php endif; ?>
  <?php print $picture ?>
  <div class="info"><?php print $submitted ?></div>
  <div class="content">
    <?php if (!$page == 0): ?>
<?php
print "<table border=\"0\" cellpadding=\"$column_spacing\"><tr>";
 
$bodytext = array("$content");
 
$text = implode(",", $bodytext); //prepare bodytext
 
$length = strlen($text); //determine the length of the text
 
$length = ceil($length/$columns); //divide length by number of columns
 
$words = explode(" ",$text); // prepare text for word count and split the body into columns
 
$c = count($words);
 
$l = 0;
  for(
$i=1;$i<=$columns;$i++) {
   
$new_string = "";
    print
"<td style=\"text-align:justify\" valign=\"top\">";
  for(
$g=$l;$g<=$c;$g++) {
    if(
strlen($new_string) <= $length || $i == $columns)
   
$new_string.=$words[$g]." ";
    else {
     
$l = $g;
    break;
      }
     }
    print
$new_string;
    print
"</td>";
  }
  print
"</tr></table>"; // complete the table
?>

<?php endif; ?>
<?php if ($page == 0): ?>
  <?php print $content ?>
  <?php endif; ?>
  </div>
<?php if ($links): ?>
    <?php if ($picture): ?>
      <br class='clear' />
    <?php endif; ?>
  <div class="links"><?php print $links ?></div>
<?php endif; ?>
<div class="terms">( categories: <?php print $terms ?> )</div>
</div>

Add header above comments (below content)

The goal: add an extra header below the node content and above the comments to more clearly delineate the comments from the content.

Look at http://api.drupal.org/api/4.7/function/node_show

The comments are rendered after the entire node body is emitted. Thus, you could make your node.tpl.php look something like (adpating from bluemarine):

<div class="node<?php if ($sticky) { print " sticky"; } ?>
<?php if ($picture) {  print $picture;  }?>
<?php if ($page == 0){ ?>
<h2 class="title"><a href="<?php print $node_url?>"><?php print $title?></a></h2>
<?php }; ?>
<span class="submitted"><?php print $submitted?></span>
<span class="taxonomy"><?php print $terms?></span>
<div class="content"><?php print $content?></div>
<?php if ($links) { ?><div class="links">&raquo; <?php print $links?></div><?php }; ?>
<?php if ($page && $node->comment && $node->comment_count){ //<-This is the important part ?>
<div><h2>Comments on this post:</h2></div>
<?php }; ?>
</div>

The three AND conditions in the last if statement are key:
$page != 0 if the node is being shown as a full page.
$node->comment > 0 if comments are enabled for this page.
$node->comment_count > 0 if the number of comments is greater than zero.

Formatting the way the date/time is displayed

You can control way the date is displayed [admin/settings/date settings], or if it is displayed at all [admin/themes/configure/display post information on], in the main Drupal settings, but only to a certain extent.

If you want complete control over how the date is displayed, use PHP at the Theme level. This is from the disussions and is courtesy of Laura S.

At the top of your node.tpl.php file, add:

<?php
$formatted_date
= format_date($node->created, 'custom', 'D j M Y');
?>

Change the "D j M Y" part to what you require (see below for possible variables).

Then print or echo $formatted_date instead of $date. Look in your template for the string $date and replace it with $formatted_date.

To change the date/time display to your taste, arrange the following variables in the manner you require:

a - "am" or "pm"
A - "AM" or "PM"
d - day of the month, 2 digits with leading zeros; i.e. "01" to "31"
D - day of the week, textual, 3 letters; i.e. "Fri"
F - month, textual, long; i.e. "January"
h - hour, 12-hour format; i.e. "01" to "12"
H - hour, 24-hour format; i.e. "00" to "23"
g - hour, 12-hour format without leading zeros; i.e. "1" to "12"
G - hour, 24-hour format without leading zeros; i.e. "0" to "23"
i - minutes; i.e. "00" to "59"
j - day of the month without leading zeros; i.e. "1" to "31"
l (lowercase 'L') - day of the week, textual, long; i.e. "Friday"
L - boolean for whether it is a leap year; i.e. "0" or "1"
m - month; i.e. "01" to "12"
n - month without leading zeros; i.e. "1" to "12"
M - month, textual, 3 letters; i.e. "Jan"
s - seconds; i.e. "00" to "59"
S - English ordinal suffix, textual, 2 characters; i.e. "th", "nd"
t - number of days in the given month; i.e. "28" to "31"
U - seconds since the epoch
w - day of the week, numeric, i.e. "0" (Sunday) to "6" (Saturday)
Y - year, 4 digits; i.e. "1999"
y - year, 2 digits; i.e. "99"
z - day of the year; i.e. "0" to "365"
Z - timezone offset in seconds (i.e. "-43200" to "43200")

Quick 'edit' link of the nodes

Just to give a 'quick edit' link to each and current node on pages with excerpt
edit link next to the '$links' read more' as alternative to the going to the actual node to get edit tab.. saves editors 2 links&load

on note.tpl.php replace or add

<div class="links">
  <?php print $links; ?>
  </div>

with

  <div class="links">
  <?php print $links; ?>
          <li><a href="<?php print '?q=node/'.$node->nid ?>/edit" title="<?php print t('Edit') ?>">edit</a></li>
  </div>

Teaser Small image & body medium image link to full size with CCK Image and Imagecache

Image Cache and CCK Image have been discussed in detail many times, however I haven't seen this anywhere and found trying to get it working quite frustrating. I thought I would pass it on for anyone else that needs it. It is a small bit of code that simply displays 2 different imagecache profile images which link to displaying the full size image on a new page. If you need something more sophisticated it is a good basis for a starting point.

This guide assumes that either you have or you know how to set up and use node.tpl.php which is the file this code should be added to.

Step One

Set up 2 imagecache presets, one called normal and one called large. Add approprioate filters to the sizes you require.

Step Two

Edit your node.tpl.php file from your template and add the following code where you want the image to display:

<?php
if ($page && $field_image[0]['filepath']){
    print
"<a title='View Full Size Image' href='/".$field_image[0]['filepath']."'><img src='/files/imagecache/medium/".$field_image[0]['filepath']."'></a></div>";
} elseif (
$field_image[0]['filepath']){
    print
"<a title='View Full Size Image' href='/".$field_image[0]['filepath']."'><img src='/files/imagecache/normal/".$field_image[0]['filepath']."'></a></div>";
}
?>

That's it, you may need to change the '$field_image' to whatever your CCK image field is called (You can see this by viewing the content type and clicking manage fields).

One more thing about imagecache which I think is the source of many troubles is to understand that the images do not exist in the imagecache directory until the first time they are used by a displayed node. So if you are having problems with the directories not been created etc... make sure that the /files/imagecache/profile_name/ is correct as this is probably the cause. You can also put:

<?php
print_r
($field_image);
?>

temporarily into your node.tpl.php to display the contents of the array.

Inserting Taxonomy Images into nodes

description

These snippets allow you to insert Taxonomy images into your nodes, when using the taxonomy_image.module.

usage
  1. Requires the taxonomy_image.module to be installed and enabled
  2. Use a text editor like notepad.exe or equivalent to copy and paste the snippets below into your template.php file and node.tpl.php (or node-type.tpl.php) files respectively
  3. Upload your edited your template.php file and node.tpl.php (or node-type.tpl.php) files to your active theme folder.
step 1 of 2

To override the default taxonomy_image layout copy the following snippet, using an editor like notepad.exe, name it TEMPLATE.PHP and upload it to your active theme folder.

If you already have a TEMPLATE.PHP file in your active theme folder, simply add the above to the existing TEMPLATE.PHP file and upload it.

NOTE: This is not to be confused with the template.php file in the /THEMES/ENGINES/PHPTEMPLATE folder. themes/engines/phpTemplate.

<?php
function _phptemplate_variables($hook, $vars) {
  if (
$hook == 'node') {
    if (
module_exist("taxonomy_image")) {
       foreach (
taxonomy_node_get_terms($vars['node']->nid) as $term) {
       
$vars['taxonomy_images'][] = taxonomy_image_display($term->tid, "alt='$term->name'");
       }
    }
  }
return
$vars;
}
?>
step 2 of 2

Copy the following snippet into your your node.tpl.php or node-type

.tpl.php file, where you want the taxonomy images to display. Edit the class names and adjust the layout to suit.
<div class="taximage">
<?php print $taxonomy_images[0] ?>
</div>
Can't decide between $taxonomy_images[0] or [1]?

I got up to $taxonomy_images[3]!

I’ve been having fun with Taxonomy Image and, building on all the documentation out there, I think I’ve found a way that works consistently in all the test cases I’ve thrown at it.

You should be able to;–

  • Associate multiple images with a node, using multiple terms
  • Style images in teasers differently to those in a full node

There are a few important differences, so I thought I should document them…

Of course, you will need Taxonomy and Taxonomy Image, installed, enabled and accessible (documented elsewhere).

There are two files that we are interested in; these can be in themes/engines/phptemplate/ or over-ridden by versions in your theme.

template.php

You’ll probably need to create this file in your theme.

<?php
function _phptemplate_variables($hook, $vars) {
  if (
$hook == 'node' && module_exists("taxonomy_image")) {
    foreach (
taxonomy_node_get_terms($vars['node']->nid) as $term) {
      if (
$img = taxonomy_image_display($term->tid, "title='$term->description'")) {
       
$vars['taxonomy_images'][] = "<a href='files/category_pictures/$term->name.jpg'>" . $img . '</a>';
      }
    }
  }
  return
$vars;
}
?>

This will produce an <img> tag inside an anchor pointing at the source.  You could point it elsewhere, include a paragraph containing the description (I’ve put it in the title) or calm down, and continue with the rest of this article.

node.tpl.php

You’ll probably need to copy this file to your theme and edit it there.

<?php
...
  <
div class="taxonomy_image">
    <?
php if ($taxonomy_images) foreach ($taxonomy_images as $image) print $image ? >
  </
div>
...
?>

This will place the tags where you want them in the node.

Finally, you can style with…

style.css
.taxonomy_image{
  float: right;
  margin: 10px;
}
/* images in teaser */
.taxonomy_image img{
   width: 100px;
  height: 100px;
}
/* images in full node */
.breadcrumb~.node .taxonomy_image img{
   width: 180px;
  height: 180px;
}
/* If anyone can preserve aspect ratio, please let me know. */

I’ve been using Drupal for just a few weeks, so if I’ve made any incorrect assumptions, do let me know.

Extract the 'read more' link and display it separately somewhere else in your nodes

description

To take the 'read more' link out of the links below a node and place it somewhere a little more prominent, open up node.tpl.php and insert the following code at the very top.

<?php
// Extract "read more" link from $links so we can display it separately.
if (preg_match('!<a[^>]+>'.t('read more').'</a>!', $links, $match)) {
 
$links = preg_replace('!\| <a[^>]+>'.t('read more').'</a>!', '', $links);
 
$more = '<span class="readmore">'. $match[0] . '</span>';
}
else {
 
$more = '<span class="readmore-fill"></span>';
}
?>

Then, where you want to display the 'read more' link, place the following line:

<?php
if ($more): print $more; endif;
?>

Customising flexinode layouts

description

This explains how to customise how your flexinodes are displayed when using the flexinode.module.

step 1 of 2

Gather the necessary flexinode field IDs for use in your flexinode layout file.

  • go to ADMINISTER -> CONTENT -> CONTENT TYPES so you see the page that lists all your flexinodes and flexinode fields with administration options
  • Make a note of the Content Type flexinode ID you want to customise the layout for. (Tip: ou can do that by hovering your mouse over the EDIT FIELD link for a specific field. You should see a link like this http://www.example.com/admin/node/types/edit_type/1. The number 1 at the end of the link is the ID for that flexinode.)
  • Make a note of your individual flexinode field IDs. (Tip: you can do that by hovering your mouse over the EDIT FIELD link for a specific field. You should see a link like this http://www.example.com/admin/node/types/edit_field/1. The number 1 at the end of the link is the flexinode ID for that field.).
  • step 2 of 2

    Creating your custom flexinode layout file.

    1. In a text editor like notepad.exe or equivalent, create a node-flexinode-n.tpl.php file where n is the flexinode ID
    2. Insert individual fields by using the following format: $node->flexinode_n where n is the flexinode field ID for the field you would like to display
    3. An example node-flexinode-n.tpl.php file is pasted below for reference
    4. Once you're happy with the layout, upload the completed node-flexinode-n.tpl.php file to your active theme folder
    5. Your custom layout will automatically take effect

    example node-flexinode-n.tpl.php file

    Below is an example flexinode-n.tpl.php file for reference purposes. Note that the check_plain() function is used for security.

    <div id="special_content">
    <div id="flexifield_one"> <?php print check_plain($node->flexinode_9) ?> </div>
    <div id="flexifield_two"> <?php print check_plain($node->flexinode_10) ?> </div>
    <div id="flexifield_three"> <?php print check_plain($node->flexinode_11) ?> </div>
    <div id="flexifield_four"> <?php print check_plain($node->flexinode_12) ?> </div>
    <div id="flexifield_weblink"><a href="http:// <?php print check_plain($node->flexinode_13) ?> " target="_blank">Official Website</a></div>
    </div>

    customising flexinode teasers and full view layout

    description

    The following explains using an illustrative example how to handle and customise the layout of teasers and full view nodes using node-flexinode-n.tpl.php files.

    usage

    Simply copy and paste the snippet into your node-flexinode-n.tpl.php file and edit the flexinode IDs to suit.

    The example snippet uses the following flexinode_n variables that you need to edit.

    a) The teaser view should display...
    1. flexinode_11
    2. links

    b) The full node view should display...

    1. flexinode_11
    2. flexinode_12
    3. links
    4. terms

    <?php if ($page == 0): /* teaser view */ ?>
            <div class="summary">
                <div class="content">
                    <?php if ($node->flexinode_11): ?>
                        <div>
                            <?php print $node->flexinode_11 ?>
                        </div>
                    <?php endif;?>
                </div>
                <?php if ($links): ?>
                    <div class="links"><?php print $links ?></div>
                <?php endif; ?>
            </div>

        <?php else:  /* full node view */ ?>
            <div class="extended">
                <div class="content">
                    <?php if ($node->flexinode_11): ?>
                        <div>
                            <?php print $node->flexinode_11 ?>
                        </div>
                    <?php endif; ?>

                    <?php if ($node->flexinode_12): ?>
                        <div>
                            <?php print $node->flexinode_12 ?>
                        </div>
                    <?php endif; ?>
                </div>
               
                <?php if ($links): ?>
                    <div class="links"><?php print $links ?></div>
                <?php endif; ?>

                <?php if ($terms): ?>
                    <div class="terms">( filed under: <?php print $terms ?> )</div>
                <?php endif; ?>
            </div>

    <?php endif; ?>

    Displaying related nodes from relativity module

    I've been searcing for various days among drupal site¡s contens and at least I've found how to show the related nodes provided for "node relativity modules".

    Insert at the end of your node-flexinode-1.tpl this ...

    <?php
        $node
    ->body = "";
       
    $node->teaser = "";

         
    node_invoke_nodeapi($node, 'view', FALSE, FALSE);

        print
    $node->body;
    ?>

    I've found this provisional solution in http://drupal.org/node/25567 , posted by bomarmonk on July 6, 2005.

    Anyone know how to resolved the problem with the extra content not in the flexinode fields?

    handling address fields in node-flexinode-n.tpl files

    If your flexinode_n field is an address field, it is an associative array with the following keys:
    address_1
    address_2
    address_3
    city
    state
    postal_code
    country

    I could have some better logic around the "city, state" part, but i'm cutting and pasting from a template file i already made and i'm too lazy to rewrite it...

    <?php


       
    if ($node->flexinode_n) {
       
        print
    "<p>\n";
       
        if (
    $node->flexinode_n[address_1]) {
        print
    check_plain($node->flexinode_n[address_1]) . "<br />\n";
        }
       
        if (
    $node->flexinode_n[address_2]) {
        print
    check_plain($node->flexinode_n[address_2]) . "<br />\n";
        }

        if (
    $node->flexinode_n[address_3]) {
        print
    check_plain($node->flexinode_n[address_3]) . "<br />\n";
        }

        if (
    $node->flexinode_n[city]) {
        print
    check_plain($node->flexinode_n[city]) . ",";
        }
       
        if (
    $node->flexinode_n[state]) {
        print
    check_plain($node->flexinode_n[state]) . " ";
        }
       
        if (
    $node->flexinode_n[postal_code]) {
            print
    check_plain($node->flexinode_n[postal_code]) . "<br />\n";
        }
       
        if (
    $node->flexinode_n[country]) {
            print
    check_plain($node->flexinode_n[country]) .  "<br />\n";
        }

    }
    ?>

    handling image fields in your node-flexinode-n.tpl.php files

    description

    Use the following snippet to insert a flexinode image field in your node-flexinode-n.tpl.php file.

    Thanks to Brianic for improving this snippet.

    usage

    Note: You must have the image.module enabled and working for this snippet to work. (thanks Elv for the tip)

    Simply copy and paste the snippets into your custom node-flexinode-n.tpl.php file.

    simple image handling

    Edit the $node->flexinode_n->filepath variable where n is the flexinode field ID for that field*

    <img src="<? print file_create_url($node->flexinode_n->thumbpath) ?>" />
    advanced image handling

    If you want to specify the specific height and width for an image, use the following snippet.

    Edit the $node->flexinode_n->filepath variable where n is the flexinode field ID for that field*

    <?php list($width, $height, $type, $attr) = getimagesize($node->flexinode_n->filepath);?>
    <img width="<?php print $width ?>" height="<?php print $height ?>" src="/<? print file_create_url($node->flexinode_n->filepath) ?>" />
    notes

    *Tip: A simple way of working out what the flexinode field ID is, is to go to ADMINISTER -> CONTENT -> CONTENT TYPES and hover your mouse over the EDIT FIELD link for the specific field. You should see a link like this http://www.example.com/admin/node/types/edit_field/1 in your status bar. The number 1 at the end of the link is the flexinode ID for that field.

    handling textarea fields and checking input formats

    description

    Snippets for handling textarea fields and checking input formats, including PHP, Full HTML or FILTERED-HTML.

    Thanks to Fago for helping improve this snippet!

    usage

    Simply copy and paste the snippet into your node-flexinode-n.tpl.php file and edit the flexinode field IDs to suit*.

    simple textarea snippet

    For use with simple textareas, with no formatting.

    <?php if($node->flexinode_n): ?>
    <div ="contentclass">
    <P>notes: <?php print $node->flexinode_n ?> </p>
    </div>
    <?php endif; ?>
    advanced textarea snippet

    this runs a check on the flexinode field to see what INPUT FORMAT has been chosen and filters the output accordingly. i.e. Filtered HTML, Full HTML or PHP Code.

    <?php if($node->flexinode_n): ?>
    <div ="contentclass">
    <P>HTML notes: <?php print check_output($node->flexinode_n, $node->format); ?> </p>
    </div>
    <?php endif; ?>
    notes

    *Tip: A simple way of working out what the flexinode field ID is, is to go to ADMINISTER -> CONTENT -> CONTENT TYPES and hover your mouse over the EDIT FIELD link for the specific field. You should see a link like this http://www.example.com/admin/node/types/edit_field/1 in your status bar. The number 1 at the end of the link is the flexinode ID for that field.

    handling URL fields in your node-flexinode-n.tpl.php files

    description

    Use the following snippet to insert a flexinode URL field in your node-flexinode-n.tpl.php file. This snippet will strip any bad/harmful protocols that may have been entered and then format the url into a clickable link.

    usage

    Simply copy and paste the snippets into your custom node-flexinode-n.tpl.php file.

    Edit the $node->flexinode_n variable where n is the flexinode field ID for the url field.

    <a href="http://<?php print check_url($node->flexinode_n) ?>"><?php print $node->flexinode_n ?></a>

    ignoring empty fields

    description

    This snippet illustrates one method of ignoring fields that are empty.

    The IF...END IF statement simply checks to see if the field has been set before displaying any content. If the field is empty e.g. if there is no text entered or image uploaded it won't display anything.

    usage

    Simply copy and paste the snippet into your node-flexinode-n.tpl.php file and edit the $node->flexinode_n variable where n is the flexinode field ID for that field*

    Simple text field example
    <?php if($node->flexinode_n): ?>
    <P>Field name: <?php print $node->flexinode_n ?></p>
    <?php endif; ?>
    Simple image field example
    <?php if($node->flexinode_n): ?>
    <img src="/ <?php print $node->flexinode_n->filepath ?> ">
    <?php endif; ?>
    notes

    *Tip: A simple way of working out what the flexinode field ID is to go to ADMINISTER -> CONTENT -> CONTENT TYPES and hover your mouse over the EDIT FIELD link for the specific field. You should see a link like this http://www.example.com/admin/node/types/edit_field/1 in your status bar. The number 1 at the end of the link is the flexinode ID for that field.

    Inserting "Submitted by", "time & date" and "number of comments" in your node-flexinode-n.tpl.php file

    description

    The following snippet inserts "submitted by", "date" and "number of comments" in a node-flexinode-n.tpl.php file.

    e.g. article - someuser - December 1, 2005 - 0 comments

    usage

    Simply copy and paste the snippet into your node-flexinode-n.tpl.php file and edit to suit.

    <div class="submitted">
    <?php print 'article - ' .l("$node->name","user/$node->uid"). ' - ' .format_date($node->created, "custom", "F j, Y"). ' - ' .$node->comment_count. ' comments';?> </div>

    Node Vote in Flexinode

    This will get node vote appearing in flexinode custom templates:

    global $user;

    $results=_nodevote_get_vote_data($node->nid);
    print theme_nodevote_display_vote($results['score'], $results['votes'], 2);
    if($page!=0 && (bool) $user->uid){
    print theme_nodevote_do_vote($node);
    }

    The 2 in theme_nodevote_display_vote prints out the stars and numeric vote counts and displays the voting box only on the full page and if the user is logged in - the teaser just gets the vote count box.

    This can be replaced with $results['vote_display'] to get your configured setting. Not sure why, but the stars part of the display seems to come out backwards, but am working on that :)

    Probably better ways to do it, but I needed this myself, so hacked it together this morning after going through the code in nodevote.module

    Overriding flexinode 'date/time' (timestamp) field

    Since it took me a while to make sense of this I thought I would post an example to help others along the way. It's actually quite simple, just not very intuitive.

    This example is for changing the way that the flexinode 'date/time' field will display on a page. (I only wanted month and year to show). But could very easily be adapted to other things.

    My theme is called 'licc' - it is a phptemplate theme.

    The Quick Version

    In the directory for your phptemplate theme (this could be an existing or custom theme), create the following 2 files. For me the files were put in 'themes/licc'.

    Create template.php

    Something like this:

    <?php
    /**
    * Override theme_flexinode_timestamp() from modules/flexinode/field_timestamp.inc
    */
    function phptemplate_flexinode_timestamp($field_id, $label, $value, $formatted_value) {
     
    // nothing happens here.
     
    return _phptemplate_callback('flexinode_timestamp', array('field_id' => $field_id, 'label' => $label, 'value' => $value, 'formatted_value' => $formatted_value));
    }
    ?>
    Create flexinode_timestamp.tpl.php

    Something like this:

    <?php
    $formatted_value
    = strftime ("%B %Y", $value); // format as Month and Year, eg. 'July 2004'
    ?>

    <div class="flexinode-timestamp-<?php print $field_id; ?>">
    <strong><?php print $label; ?>: </strong><br />
    <?php print $formatted_value; ?>
    </div>

    That's it! Just modify the second file so that the field is displayed the way you would like.

    Note: that you will need to visit administer > themes for PHPTemplate to refresh its cache and recognize any new .tpl.php files.

    Below is the long-winded version, read on if you are interested...

    The Long Version
    1. find the theme function for the flexinode field

    Found in modules/flexinode/field_timestamp.inc

    <?php
    function theme_flexinode_timestamp($field_id, $label, $value, $formatted_value) {
     
    $output = theme('form_element', $label, $formatted_value);
     
    $output = '<div class="flexinode-timestamp-'. $field_id .'">'. $output .'</div>';
      return
    $output;
    }
    ?>

    This is just for reference, you could just as easily look in the API documentation. Core documentation is here:
    http://drupaldocs.org/api/head/group/themeable
    (only core modules seem to be online at the moment, so you will need to search through the code for any add-on modules like flexinode)

    If you wanted to theme flexinode 'image' fields, you would need to look for the theme function in modules/flexinode/field_image.inc

    2. Create template.php and add override function

    For my theme I created themes/licc/template.php and then copied the function declaration from above replacing the word 'theme' with 'phptemplate'.

    <?php
    function phptemplate_flexinode_timestamp($field_id, $label, $value, $formatted_value) {
    }
    ?>

    add in the phptemplate callback:

    <?php
    return _phptemplate_callback('flexinode_timestamp', array('field_id' => $field_id, 'label' =>
    $label, 'value' => $value, 'formatted_value' => $formatted_value));
    ?>

    This function doesn't really _do_ anything except give phptemplate control over the display of this field, the next step looks after the actual formatting. Note how the variables are passed on to the _phptemplate_callback() in an associative array.

    Note: do not use the key 'file' in the callback array, as it causes problems for phptemplate. This is a value used for the image field in particular. This is what I did (for the image field) to get around this problem (see 'imgfile' used instead of 'file')

    <?php
    /**
    * Override theme_flexinode_image() from modules/flexinode/field_image.inc
    */
    function phptemplate_flexinode_image($field_id, $label, $file, $formatted_value) {
     
    // empty 'stub' function
     
    return _phptemplate_callback('flexinode_image', array('field_id' => $field_id, 'label' => $la
    bel
    , 'imgfile' => $file, 'formatted_value' => $formatted_value));
    }
    ?>
    3. Create flexinode_timestamp.tpl.php to do formatting

    This goes in your theme directory (for me themes/licc/flexinode_timestamp.tpl.php). As you can see the name matches the bit after 'phptemplate_' in the theme override function, and the first argument of the _phptemplate_callback().

    Put the HTML/PHP that you want in this file for the display of all date/time (timestamp) fields in all flexinode pages.

    Something like this:

    <div class="flexinode-timestamp-<?php print $field_id; ?>">
    <strong><?php print $label; ?>: </strong><br />
    <?php print $formatted_value; ?>
    </div>
    Example Files
    template.php
    <?php
    /***
    * template.php
    *
    * This file contains functions for over-riding the default theme functions
    * in Drupal core and modules (look at the API documentation for more info).
    * The functions don't actually _do_ anything, except pass the variables
    * available to phptemplate for use in the *.tpl.php files.
    *
    * Add similar 'stub' functions to override other default theme functions.
    */

    /**
    * Override theme_flexinode_timestamp() from modules/flexinode/field_timestamp.inc
    */
    function phptemplate_flexinode_timestamp($field_id, $label, $value, $formatted_value) {
     
    // like I said, nothing happens here.

     
    return _phptemplate_callback('flexinode_timestamp', array('field_id' => $field_id, 'label' => $label, 'value' => $value, 'formatted_value' => $formatted_value));
    }
    ?>
    flexinode_timestamp.tpl.php
    <?php
    /***
    * Customised formatting of flexinode timestamp data in nodes.
    * These fields are available:
    * $field_id, $label, $value, $formatted_value
    **/
    // Change the default $formatted_value so that it suits me (no time or day)
    $formatted_value = strftime ("%B %Y", $value); // format as Month and Year, eg. 'July 2004'
    ?>

    <div class="flexinode-timestamp-<?php print $field_id; ?>">
    <strong><?php print $label; ?>: </strong><br />
    <?php print $formatted_value; ?>
    </div>

Customising the user list layout

description

This describes how to override the default user list page layout when using phptemplate based themes and the profile.module.

The user list page is displayed when you click on www.example.com/profile. For an example, click through to the Drupal.org user list page.

Using this override you can control precisely which profile fields are displayed and their positioning/layout.

Please note that this snippet will work with sites using Drupal 4.5
or Drupal 4.6. Scroll down for an <a href="http://drupal.org/node/46156">updated version for Drupal 4.7</a>

Step 1 of 2

In a text editor like notepad.exe, create a file called template.php using the the following snippet. If you already have a template.php file, simply add it to your existing one.

<?php

/**
* This snippet works with Drupal 4.5 and Drupal 4.6
* and will NOT work with Drupal 4.7
*/

function phptemplate_profile_profile($user, $fields = array()) {
/**
* This snippet catches the default user list pages layout
* and looks for a profile_profile.tpl.php file in the same folder
* which has the new layout.
*/
return _phptemplate_callback('profile_profile', array('user' => $user, 'fields' => $fields));
  }
?>

Upload your template.php file to your active theme folder.

Step 2 of 2

The template.php snippet catches the default user list page layout before it's displayed and looks in the same folder for a profile_profile.tpl.php file which determines the new layout.

A very simple/shortened example of a profile_profile.tpl.php file maybe illustrated as follows....(e.g. I have setup custom extended user profile fields called profile_city, profile_country, profile_postcode)....

<?php if($user->picture): ?>
<div class="picture">
<img src="/<?php print $user->picture ?>">
</div>
<?php endif; ?>
<div class="custom_profiles">
<div class="fields">Name: <?php print $user->name ?></div>
<div class="fields">City: <?php print $user->profile_city ?></div>
<div class="fields">Country: <?php print $user->profile_country ?></div>
<div class="fields">Postcode: <?php print $user->profile_postcode ?></div>
</div>

Upload your profile_profile.tpl.php file to your active theme folder and go to your user list page at www.example.com/profile to see the new layout.

Notes

  • Name the classes or change the layout to whatever you want.
  • Edit your style.css file or whatever your main stylesheet is called to control the the class styles.
  • You can include the user profile page snippets if you like
  • Please post tips/tricks discuss this handbook page at this thread.

Customising the user list layout (Update for Drupal 4.7)

description

This describes how to override the default user list page layout when using phptemplate based themes and the profile.module.

The user list page is displayed when you click on www.example.com/profile. For an example, click through to the Drupal.org user list page.

Using this override you can control precisely which profile fields are displayed and their positioning/layout.

Step 1 of 2

In a text editor like notepad.exe, create a file called template.php using the the following snippet. If you already have a template.php file, simply add it to your existing one.

<?php

/**
* This snippet works with Drupal 4.7
* and will NOT work with Drupal 4.5 or Drupal 4.6
*/

function phptemplate_profile_listing($user, $fields = array()) {
/**
* This snippet catches the default user list pages layout
* and looks for a profile_listing.tpl.php file in the same folder
* which has the new layout.
*/
return _phptemplate_callback('profile_listing', array('user' => $user, 'fields' => $fields));
  }
?>

Upload your template.php file to your active theme folder.

Step 2 of 2

The template.php snippet catches the default user list page layout before it's displayed and looks in the same folder for a profile_listing.tpl.php file which determines the new layout.

A very simple/shortened example of a profile_listing.tpl.php file maybe illustrated as follows....(e.g. I have setup custom extended user profile fields called profile_city, profile_country, profile_postcode)....

<?php if($user->picture): ?>
<div class="picture">
<img src="/<?php print $user->picture ?>">
</div>
<?php endif; ?>
<?php profile_load_profile($user->uid) ?>
<div class="custom_profiles">
<div class="fields">Name: <?php print $user->name ?></div>
<div class="fields">City: <?php print $user->profile_city ?></div>
<div class="fields">Country: <?php print $user->profile_country ?></div>
<div class="fields">Postcode: <?php print $user->profile_postcode ?></div>
</div>

Upload your profile_listing.tpl.php file to your active theme folder and go to your user list page at www.example.com/profile to see the new layout.

For Drupal 5.x
It works the same way for Drupal 5, except for the token names used in profile_listing.tpl.php
Here is an example, with custom profile fields :

<table width = "100%" border="0" cellspacing="2" cellpadding="0" align="left">
<tr><td></td><td rowspan=5 valign= "top" align="right"><?php if($user->picture) {print '<br />'.theme('user_picture', $user);} ?></td></tr>

<tr><td>Name : <?php print check_plain($user->profile_Name) ?></td></tr>
<tr><td>Home phone number: <?php print check_plain($user->profile_home_phone) ?></td></tr>
<? if ($user->profile_prefered){ print '<tr><td> Email Address : '.$user->profile_home_email.'</td></tr>'; } ?>
<? if($user->profile_home_address){ print '<tr><td>Home address: '.check_plain($user->profile_home_address).'</td></tr>';} ?>
</table>

If also fixes the problem of the link to the user profile, it's on the picture.

You can have a look at this page : http://drupal.org/node/35728
It has been written for Drupal 5 too, it is similar to this one, and can be useful

Notes
  • Name the classes or change the layout to whatever you want.
  • Edit your style.css file or whatever your main stylesheet is called to control the the class styles.
  • You can include the user profile page snippets if you like
  • Please post tips/tricks discuss this handbook page at this thread.

hide a user profile depending on role or a custom field

description

This collection of snippets allow you to hide/show users based on user roles or a custom profile field.

Useful if you want the user list pages to ignore the SITE ADMINISTRATOR (UID 1) profile or if you want to hide certain users from being listed.

Usage
  • For use in your profile_profile.tpl.php page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the snippet into your profile_profile.tpl.php file
  • Tested and works with Drupal 4.5 and 4.6
Step 1 of 2

Put this at the very top of your profile_profile.tpl.php file: (This example uses a custom checkbox profile field called profile_hidden to determine if the profile should be displayed or hidden)

<?php if((!$user->profile_hidden) == '1'): /* check to see if the profile_hidden field is selected */?>
Alternatively

Alternatively use this snippet if you want to hide users with a specific role type (the example below hides users with the role type Site Admin from the user list)

<?php if (!in_array('Site Admin', $user->roles)): /*only display users who do not have the user role type of Site Admin */ ?>
Step 2 of 2

Put this at the very bottom of your profile_profile.tpl.php file

<?php endif; ?>

HOWTO: Make the user list page compact (two or three columns, by css only)

Hi there,

If you just want to change the layout to two/three columns, you no need to care about the template.php or profile_listing.tpl.php files. Just change the css file - drupal/modules/profile/profile.css (Drupal 5.x only. For 4.7.x, please see the "Note" bellow):

#profile .profile {
  width: 45%;
  margin-right: 10px;
  float: left;
  clear: none;
  margin-bottom: 0px;
  _word-wrap: break-word;
  border: 1px solid #7F98A7;
  margin-bottom: 1em;
  _position: relative; /* avoid IE peekaboo bug */
}

Note:
  • The "width: 45%", "clear: none" and "float: left" settings make it from one column to two
  • Set width to 30% to get three columns
  • For Drupal 4.7, you can add the above lines to your drupal/themes/your_theme_folder/style.css
  • Don't forget to remove the conflict profile css settings from the drupal/themes/your_theme_folder/style.css file first, especially the "clear: both" setting

Customising the user profile layout

The PHP Snippets below are intended for use within a customised USER PROFILE page that simply enables you to "pull" specific content from your drupal database specific to a particular user and display it in the way you want.

They are intended for use with a phptemplate based theme and for Drupal site developers who do not have php programming knowledge but want to push out the boundaries of user profile pages and control precisely how they look.

Simple step-by-step instructions are provided.

The concept

Drupal is an extremely powerful tool for building online communities, in particular, allowing users to submit their own content to a community hub. A good illustration of this working well online might be the World famous myspace.com site, where bands/artists are able to submit content into their own page.

Drupal has all the tools available to create your own myspace.com style community hub.

These snippets are intended as a mini-repository and as an aid for site designers without php programming skills to create sophisticated User Profile Pages for members of their community.

Customised User Profile Pages maybe applied to many applications. myspace.com is primarily a site for artists & bands, but, similar techniques could be used for other applications such as a rzye.com (Drupal powered community hub) style professional networking hub or terminus1525 (Drupal powered community hub) for studios.

Getting Started

Step 1 is to override the default User Profile page layout by uploading the special TEMPLATE.PHP file to your active theme folder.

<?php
/**
* Catch the theme_profile_profile function, and redirect through the template api
*/
function phptemplate_user_profile($user, $fields = array()) {
 
// Pass to phptemplate, including translating the parameters to an associative array. The element names are the names that the variables
  // will be assigned within your template.
  /* potential need for other code to extract field info */
return _phptemplate_callback('user_profile', array('user' => $user, 'fields' => $fields));
  }
?>

If you already have a TEMPLATE.PHP file in your active theme folder, simply add the above to the existing TEMPLATE.PHP file and upload it.

Step 2 is to create your customised user_profile.tpl.php file and upload that to your active theme folder.

If you're starting from scratch, simply open NOTEPAD.EXE or a similar text editor and paste in the snippets linked below to build your custom user profile page. Save it with the user_profile.tpl.php filename and upload it to your theme folder along with the template.php file.

Once you have got started with your first user_profile.tpl.php file, you can experiment with adding in more snippets or including HTML layout controls to get a feel for the flexibility this allows.

How to use these snippets

Simply copy and paste these snippets into your user_profile.tpl.php file and upload it to your active theme folder.

It's recommended that you test your customised user_profile.tpl.php file on a test installation before adding to a live site and you can disable them at any time by removing both files i.e. the template.php file AND the user_profile.tpl.php file. The PHP TEMPLATE engine needs both of these files for the overrides to work.

Adding new snippets

Simply click on the ADD NEW CHILD PAGE link below and create a new handbook page. Include any dependencies, such as which version of Drupal you have tested the snippet with or extra modules that need to be enabled for the snippet to work.

PLEASE NOTE! The following snippets are user submitted. Use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

A basic User Profile Page to help get you started

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays is a basic user_profile.tpl.php file to help get you started.

Dependencies: Requires the profile.module to be enabled and city, country custom single-line profile fields added.

Usage
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the custom profile field names to match what you have setup. (Tip: go to administer -->> settings -->> profile and in the second column it will give you the name of each field.)
  • Tested and works with Drupal 4.5, 4.6, 4.75 and Drupal 5 (Dublin Drupaller Jan 19th)
  • Change the div class names or the prefix text to suit.
<div class="custom_profiles">
<div class="fields"><?php print $user->name ?></div>
<div class="fields">City: <?php print $user->profile_city ?></div>
<div class="fields">Country: <?php print $user->profile_country ?></div>
<div class="fields">Postcode: <?php print $user->profile_postcode ?></div>
</div>

Customising the user profile pages (a "before" and "after" example with screenshots)

This illustrative example shows how easy it is to override theme functions using the User_profile pages as an example.

Before

This is how the out-of-the-box user profile looks like, with extra profile fields, such as City, Country, Postcode, Position etc. added in. (please note that i couldn't fit the whole page into the one screenshot..there is an extra "background/more info." field that doesn't show in the BEFORE screen shot.

click to view the BEFORE screenshot in a new window

After

This is how the exact same user profile looks after overriding the theme and applying a simple user_profile.tpl.php file in my theme directory.

click to view the AFTER screenshot in a new window

How I did it

To override just the layout of the User Profile page..I created a template.php file with this in it:

<?php
/**
* Catch the theme_profile_profile function, and redirect through the template api
*/

function phptemplate_user_profile($user, $fields = array()) {
 
// Pass to phptemplate, including translating the parameters to an associative array. The element names are the names that the variables
  // will be assigned within your template.
  /* potential need for other code to extract field info */
return _phptemplate_callback('user_profile', array('user' => $user, 'fields' => $fields));
  }

?>

I uploaded that into my active theme directory and then created and uploaded, to the same directory, the override layout file which is called user_profile.tpl.php.

A very simple/shortened example of how my user_profile.tpl.php works maybe illustrated as follows....(e.g. I have setup custom extended user profile fields called profile_city, profile_country, profile_postcode)....

<?php if($user->picture): ?>
<div class="picture">
<img src="/<?php print $user->picture ?>">
</div>
<?php endif; ?>
/** If you are using this snippet with Drupal version 4.7.x or 5.x use the
* following line to display a user picture instead
* <?php  if($user->picture) {print theme('user_picture', $user);}?>
*/

<div class="custom_profiles">
<div class="fields">City: <?php print check_plain($user->profile_city); ?></div>
<div class="fields">Country: <?php print check_plain($user->profile_country) ;?></div>
<div class="fields">Postcode: <?php print check_plain($user->profile_postcode); ?></div>
</div>

If you don't want to show empty fields you can use an if check on the field like so:

<?php if($user->profile_postcode) { ?>
<div class="fields">Postcode: <?php print check_plain($user->profile_postcode) ?></div>
<?php }?>
Notes:

Edit your style.css to format the classes.

Security note: I have updated this snippet to include a security check on the content before outputting it, i.e. check_plain(). It's important to remember to check output properly when overriding theme functions in Drupal. (Please consult the How to handle text in a secure fashion for more information).

More details & in depth examples/discussion on this is in the original forum post.

User Profile avatar/picture Snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet checks to see if the user has a picture/avatar uploaded and displays it if they have.

Dependencies: No extra modules required but picture support must be enabled to allow users to upload their avatars/pictures:

1) admin/themes/settings/*yourtheme*
Enable 'User pictures in posts' and 'User pictures in comments'

2) admin/settings/user
'Enable' Picture support. You can also set size of avatar.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
<?php  if($user->picture) {print theme('user_picture', $user);}?>

NOTES: Thanks to leafish_paul for improving this snippet.

Handling single-line profile fields

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays single-line textfields. If the user has not specified anything, it displays nothing

Dependencies: profile.module

Usage
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the custom profile field name from profile_city used in the example below. (Tip: go to administer -->> settings -->> profile and in the second column it will give you the field name.)
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
<?php if($user->profile_city): ?>
<div class="fields">City: <?php print $user->profile_city ?></div>
<?php endif ?>

Handling multi-line profile fields

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays multi-line textfields. If the user has not specified anything, it displays nothing

Dependencies: profile.module

Usage
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the custom profile field name from profile_biography used in the example below. (Tip: In Drupal 4.x go to administer -->> settings -->> profile and in the second column it will give you the field name. In Drupal 5.x the profiles settings page can be found at administer -->> user management -->> profiles)
  • Tested and works with Drupal 4.5, 4.6, 4.7.x and 5.x
  • Make sure you us the snippet variation of the snippet that matches the version of Drupal you are using.
  • Change the div class names or the prefix text to suit.
works with Drupal 4.5.x and Drupal 4.6.x
<?php if($user->profile_biography): ?>
<div class="fields">Biography: <?php print check_output($user->profile_biography) ?></div>
<?php endif ?>
works with Drupal 4.7.x & Drupal 5.x
<?php if($user->profile_biography): ?>
<div class="fields">Biography: <?php print check_markup($user->profile_biography) ?></div>
<?php endif ?>

Handling checkbox profile fields

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet illustrates how to use custom User Profile checkbox Fields.

Dependencies: profile.module

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • In the example snippet we used a custom checkbox for "Click if you're a MAC user" and called the form name profile_mac
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
<div class="fields">
<?php if ($user->profile_mac == '1') {print "I use a mac" ;}; ?>
<?php if ($user->profile_mac == '0') {print "I do not use a mac" ;}; ?>
</div>

Handling freeform profile fields

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays custom User Profile Freeform Fields. If the user has not specified anything, it displays nothing.

Dependencies: profile.module

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the custom profile freeform field name by changing the $fieldname value in the first line of the snippet to suit. (Tip: go to administer -->> settings -->> profile and in the second column it will give you the field name.)
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
  • If you are using the same snippet more than once in the same user_profile.tpl.php file add and increment a number to the end of the of the $fieldname tag e.g. $fieldname2, $fieldname3 etc. each time you copy and use the snippet.
<?php $fieldname = 'profile_keywords'; ?>
<?php if($user->$fieldname != ""): ?>
<div class="fields">
<p><b>Keywords:</b></p>
<ul>
<?php
$temp_array
= split("[\n,]", $user->$fieldname);
$count_total = count($temp_array);
for(
$counter=0; $counter<$count_total; $counter++):?>

<?php $link = each($temp_array) ?>
<li><a href="/profile/<?php print $fieldname; ?>/<?php print preg_replace('[\s]', '+', trim($link[value])) ?>" title="Users who share this  keyword"><?php print $link[value] ?></a></li>
<?php endfor ?>
</ul>
</div>
<?php endif ?>

Handling URL profile fields

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays custom User Profile URL fields. If the user has not specified any link, it displays nothing.

Dependencies: profile.module

Usage
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the Custom profile URL field name. In the example that is currently $urlfieldname='profile_weburl' (Tip: go to administer -->> settings -->> profile and in the second column it will give you the name of the URL field.)
  • If you are using this snippet more than once in the same user_profile.tpl.php file add a number to the end of the $urlfieldname each time you copy the snippet. e.g. $fieldname1, $fieldname2, $fieldname3 etc.
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the link prefix text to suit.
<?php $urlfieldname='profile_weburl' ;?>
<div class="fields">
<?php if($user->$urlfieldname): ?>
Website URL: <a href="/<?php print $user->$urlfieldname ?>"><?php print $user->$urlfieldname ?></a>
<?php endif; ?>

Recent weblog entries (titles) snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays a list of the (x) most recent weblog titles and links to the full blogs

Dependencies: blog.module must be enabled.

Thanks to Thinkinkless and Incidental for help with improving this snippet!

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • To increase/decrease the number of weblog titles listed change the $nlimit value in the first line of the snippet to suit.
  • Change the div class names or the prefix text to suit.
<?php
$nlimit
= 10;
$userid=$user->uid;
$query= "SELECT n.created, n.title, n.nid, n.changed FROM node n WHERE n.uid = $userid AND n.status = 1 ORDER BY n.changed DESC";
$result = db_query_range($query,0,$nlimit);
$output .= "<div class=\"item-list\"><ul>\n";
$output .= node_title_list($result);
$output .= "</ul></div>";
print
$output; ?>
Only show if there are blog entries

This is for user_profile.tpl.php:

<?php

$nlimit
= 10;
$userid=$user->uid;
$query= "SELECT n.created, n.title, n.nid, n.changed FROM node n WHERE n.uid = $userid AND n.status = 1 ORDER BY n.changed DESC";
$result = db_query_range($query,0,$nlimit);
$output .= "<div class=\"item-list\"><ul>\n";
$list = node_title_list($result);
$output .= strip_tags($list) ? $list : 'No Blog Postings available';
$output .= "</ul></div>";
print
$output;
?>

You can put this in a regular page set to php input filter:

<?php
global $user;
$nlimit = 10;
$query= "SELECT n.created, n.title, n.nid, n.changed FROM node n WHERE n.uid = $userid AND n.status = 1 ORDER BY n.changed DESC";
$result = db_query_range($query,0,$nlimit);
$list = node_title_list($result);
$output = '<h2>Latest Posts:</h2>';
$output .= strip_tags($list) ? $list : 'No Blog Postings available';
return
$output;
?>

Recent weblog entries (titles & teasers) snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays a list of the (x) most recent weblog titles & teasers with "read more" links to the full blogs.

Dependencies: blog.module must be enabled.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • To increase/decrease the number of weblog titles listed change the $nlimit value in the first line of the snippet to suit.
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
<?php $nlimit = 10; ?>
<?php $userid=$user->uid; ?>
<?php $result1 = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 AND n.uid = $userid ORDER BY n.created ASC"), variable_get('default_nodes_main', $nlimit)); ?>
<?php while ($node = db_fetch_object($result1)) {$output2 .= node_view(node_load(array('nid' => $node->nid)), 1);}; ?>
<?php print $output2; ?>

Add/delete to/from buddylist snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet inserts the ADD TO BUDDYLIST link

Dependencies: buddylist.module must be installed and enabled. The snippet checks to see if the user viewing the profile page has the access permission to maintain buddy list before displaying the link

Thanks to thinkinkless for finally sorting this snippet!

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Tested and works with 4.6
  • Change the div class name or the link text to suit.
<?php

if (@in_array($user->uid, array_keys(buddylist_get_buddies($account->uid))) && user_access('maintain buddy list')) {
  print
"<a href=\"buddy/delete/".$user->uid."\">remove " .$user->name. " from your buddylist</a>";
  }
else {
  if (
$user->uid != $account->uid && user_access('maintain buddy list')) {
  print
"<a href=\"buddy/add/".$user->uid."\">add " .$user->name. " to your buddylist</a>";
  }
}
?>

Display a list of buddies snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays a list of buddies and links to their profiles to users who have View Buddy List permissions.

Dependencies: profile.module. Buddylist.module

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
<?php
if ( user_access('view buddy lists') || user_access('administer users') ) {
   
// if thisuser has friends, show friends
   
$output = '';
   
$i = 0;
   
$cnt = variable_get('buddylist_prof_buddies', 5);
    if (
$buddies = buddylist_get_buddies($user->uid)) {
      foreach(
array_keys($buddies) as $buddy) {
       
$account = user_load(array('uid' => $buddy));
               
$listbuddies[] = $account;
       
$i++;
        if (
$i > $cnt) {
          break;
        }
      }
     
$output .= t('Buddies');
     
$output .= theme('user_list', $listbuddies);
     
$output .= '<br />';
    if (
count($buddies) > variable_get('buddylist_prof_buddies', 5)) {
             
$output .= '<div class="more-link">' . l(t('more'), 'buddylist', array('title' => t('View more.'))) . '</div>';
            }
    
$sql = 'SELECT b.uid, u.name FROM {buddylist} b INNER JOIN {users} u ON b.uid = u.uid WHERE b.buddy = %d ORDER BY u.access DESC';
   
$result = db_query_range($sql, $user->uid, 0, $cnt);
    while (
$row = db_fetch_object($result)) {
     
$listbuddiesof[$row->uid] = $row;
    }
   
$output .= t('Buddies of');
    if (
$listbuddiesof) {
     
$output .= theme('user_list', $listbuddiesof);
    }
if (
count($sql) > variable_get('buddylist_prof_buddies', 5)) {
             
$output .= '<div class="more-link">'. l(t('more'), 'buddylist/'. $user->uid .'/buddiesof', array('title' => t('View more.'))) .'</div>';
            }
      print
$output;
    }
}
?>

Alternate with images

<div class="friends">
<div id="profileBuddylist">
<div class="picContainer">
<?php
if ( user_access('view buddy lists') || user_access('administer users') ) {
   
// if thisuser has friends, show friends
   
$output = '';
   
$i = 0;
   
$cnt = variable_get('buddylist_prof_buddies', 5);
    if (
$buddies = buddylist_get_buddies($user->uid)) {
      foreach(
array_keys($buddies) as $buddy) {
       
$account = user_load(array('uid' => $buddy));
       
$output .=theme('user_picture', $account);
               
$listbuddies[] = $account;
       
$i++;
        if (
$i > $cnt) {
          break;
        }
      }
     
$output .= '</div>'. theme('user_list', $listbuddies);
    if (
count($buddies) > variable_get('buddylist_prof_buddies', 5)) {
             
$output .= '<div class="more-link">' . l(t('more'), 'buddylist', array('title' => t('View more.'))) . '</div>';
            }
  
      print
$output;
    }
}
?>

Then there is the css to make it look nice.

#profileBuddylist img{float:left; margin-right:10px;}
.picContainer{width:745px; height:87px;}
#profileBuddylist li{float:left; width:100px;margin-right:10px;text-align: center;}
Drupal 4.6
<?php
if (user_access('view buddy lists')) {
   
// if thisuser has friends, show friends
   
$cnt = variable_get('buddylist_prof_buddies', 5);
    if (
$buddies = buddylist_get_buddies($user->uid)) {
      foreach(
$buddies as $buddy) {
       
$listbuddies[] = format_name($buddy);
       
$i++;
        if (
$i > $cnt) {
          break;
        }
      }
     
$output .= form_item(t('Buddies'), theme('item_list', $listbuddies));
      print
$output;
      }
   }
?>

Display list of (x) recent posts (titles) snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays a list of the (x) most recent posts by the user and a links to the full nodes.

Dependencies: none.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • To increase/decrease the number of posts listed change the $nlimit value to suit. The default setting is 5.
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
<div class="fields">
<p>Recent Posts</p>
<?php
  $uid
= arg(1);
 
$nlimit = 5;
 
$result = db_query("SELECT n.created, n.title, n.nid, n.changed
  FROM node n
  WHERE n.uid = %d
  AND n.status = 1
  ORDER BY n.changed
  DESC LIMIT $nlimit"
, $uid);
 
$output3 .= "<div class=\"item-list\"><ul>\n";
 
$output3 .= node_title_list($result);
 
$output3 .= "</ul></div>";
  print
$output3;
 
?>

</div>

User "history" or "member for: (time)" snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays the length of time the user has been registered. The out-of-the-box Drupal prefix is "HISTORY" but you can change that to "MEMBER FOR:" or whatever you choose,

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
<div class="fields">
<p>Member for:<?php print (format_interval(time() - $user->created));?></p>
</div>

allow users to choose whether certain profile fields are visible or hidden

description

This handbook page explains how to allow your users to specify on their own account pages, which profile fields are visible or hidden when someone else is viewing their profile page.

example application

As an example, it might be useful if you are running a membership site where anonymous users can view profiles and you would like to offer the choice to members (registered users) whether their mobile phone number is visible to anonymous users or not.

usage
Step 1 of 2

Using the example of "Do you want to hide your mobile phone number from non-members?" follow these steps.

  1. Click through to ADMINISTER -> SETTINGS -> PROFILES
  2. Below the ADD NEW FIELD option, click on CHECKBOX
  3. Make the Form Name profile_hide_mobile
  4. Fill out the other fields to suit. (Tip: adjust the weight of this custom field so the checkbox appears just below the mobile phone number field on the user EDIT ACCOUNT page.)
  5. Save the field
Step 2 of 2

Use this snippet to show/hide the mobile phone number (where the fieldname for the mobile phone number is profile_mobile).

<?php if ($user->profile_mobile): /*check to see if the user has entered a mobile field. displays nothing if it is blank */ ?>
<?php if (($GLOBALS['user']->uid)  ||  (!$user->profile_hide_mobile) == '1')) : /* check to see if the user viewing the page is logged in OR if the hide mobile field is selected */ ?>
<div class= "fields">
<P>Mobile phone number: <?php print ($user->profile_mobile) ;  /* display this users mobile phone number */?></p>
</div>
<?php endif ; /*end of show or hide check */?>
<?php endif ; /*end of snippet */?>
concise version of the same snippet without comments
<?php if (($user->profile_mobile) && (($GLOBALS['user']->uid)  ||  (!$user->profile_hide_mobile) == '1'))) : ?>
<div class= "fields">
<P>Mobile phone number: <?php print ($user->profile_mobile) ;?></p>
</div>
<?php endif ; ?>
Notes

Allowing users to customize their profile

Allow users to safely post CSS code while editing their profile to customize their profile (like MySpace).

Create a text-field profile category profile_css
Add to the top of your user_profile.tpl.php file:

<style type="text/css"><?php print $user->profile_css ?></style>

You can even create your own wizard for users to use to customize their profile based on your stylesheets.

Alphabetizing Based on a Profile Field

After searching the forums, snippets and Googling (with no success), I've finally managed to crunch my way to a solution to what seems a common problem...

How can put my user profiles into a list with alphabetical order based on a profile field?

Now, someone more Drupal/PHP/mySQL savvy than me can probably find a more elegant way of doing this, but I offer my solution below.

Please note that this is a 'hack' of the profile.module.

In profile.module you will find function profile_browse() around line 398. Then (at around line 477) you find:

// Extract the affected users:
   *$result = pager_query("SELECT uid, access FROM {users} WHERE uid > 0 AND status != 0 ORDER BY access DESC", 20, 0, NULL);

You need to change this to:

// Extract the affected users:
$result = pager_query('SELECT u.uid, u.access FROM {users} u INNER JOIN {profile_values} v ON u.uid = v.uid WHERE v.fid = 19 ORDER BY v.value ASC', 50, 0, NULL);

You will need to change the v.fid value (shown as 19 above). To find this, go to Administer->Settings->Profiles and hover over the 'edit' label associated with the field that you want to use to alphabetize by.

You can also change the Limit value (shown as 50) to determine how many items to include in the list.

Enjoy!

Custom User Blocks and User Tables PHP Snippets

Custom User Blocks and User Tables PHP Snippets

Tested in Drupal 4.7.4 and 4.7.5

These are generic scripts that should work in other Drupal versions too.

The following are some PHP Snippets for Custom User Blocks and User Tables (Member Lists) extracted various forums within the Drupal.org site.

I modified some of the Snippets, as indicated in the sections below, to also include and output user personal information profile fields and their corresponding profile photos.

I am posting them here on one page so that other users may utilize them.

Sam Raheb (Sam308)

------------------------------------------------------------------------------
(1) Custom Who's New Block
Outputs User's Name [all roles]
------------------------------------------------------------------------------

<?php
$result
= db_query_range('SELECT uid FROM {users} WHERE status != 0 ORDER BY uid DESC', 0, 8);
$items = array();
while (
$row = db_fetch_object($result)) {
 
$account = user_load(array('uid' => $row->uid));
 
$items[] = check_plain($account->name);
}
return
theme('item_list', $items);
?>

The above PHP Snippet code yields the following results:

• fourmu
• dicema
• Debbie
• Sammy
• TestUser
• Jimmy
• admin

where the Usernames are not hyperlinks

------------------------------------------------------------------------------
(2) Custom Who's New Block
Outputs User's Real Name [all roles]
(see Home » administer » settings » profiles)
------------------------------------------------------------------------------

<?php
$result
= db_query_range('SELECT uid FROM {users} WHERE status != 0 ORDER BY uid DESC', 0, 8);
$items = array();
while (
$row = db_fetch_object($result)) {
 
$account = user_load(array('uid' => $row->uid));
 
$items[] = check_plain($account->profile_real_name);
}
return
theme('item_list', $items);
?>

The above PHP Snippet code yields the following results:

• Barbara Miller
• Michelle
• Debbie Walker
• Sam Raheb
• Test User
• James Thompson
• Sam Raheb

where the Real Names are not hyperlinks

------------------------------------------------------------------------------
(3) List a User's single personal profile field for a Single Role
------------------------------------------------------------------------------

<?php
global $user;
$sql = "SELECT value FROM profile_values WHERE (uid = $user->uid) AND (fid = 2)"; //'2' is MY location field... it may not be the same as yours.'
$result = db_query($sql);
$location = db_fetch_object($result);
print
"<br> $location->value";
?>

The above PHP Snippet code yields the following results:

Business Owner

------------------------------------------------------------------------------
(4) List Users From a Single Role in a Block
Outputs User's Name (http://drupal.org/node/82002)
------------------------------------------------------------------------------

<?php
$rid
= 3;
$result = db_query("SELECT u.uid, u.name, u.status FROM {users} u
INNER JOIN {users_roles} ur ON u.uid=ur.uid WHERE ur.rid = %d
AND u.status = 1 ORDER BY u.name ASC"
, $rid);
while (
$u = db_fetch_object($result)) {
 
$items[] = l($u->name, "user/" . $u->uid);
}
return
theme('item_list', $items);
?>

The above PHP Snippet code yields the following results:

• admin
• Debbie
• dicema
• Jimmy
• Sammy

where the usernames are hyperlinks to the user's profile.

------------------------------------------------------------------------------
(5) List Users From a Single Role
Output is the typical Profile Listing Style - not a table
------------------------------------------------------------------------------

<?php
//choose the role to list by value. 
// Note ID 1 = anonymous, ID 2 = authenticated user
// so valid values here are > 2.
$rid = 3;
?>

<h2>A list of all users with Role ID <?php print $rid?></h2>
<br/>
<?php
    $fields
= array();
   
$result = db_query('SELECT name, title, type, weight FROM {profile_fields} WHERE visibility = %d ORDER BY category DESC, weight', PROFILE_PUBLIC_LISTINGS);
    while (
$record = db_fetch_object($result)) {
     
$fields[] = $record;
    }
$result = pager_query("SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u INNER JOIN {users_roles} ur ON u.uid=ur.uid WHERE ur.rid = $rid ORDER BY access DESC", 20, 0, NULL);
$status = array(t('blocked'), t('active'));
$output = '<div id="profile">';
while (
$account = db_fetch_object($result)) {
 
$account = user_load(array('uid' => $account->uid));
 
$profile = _profile_update_user_fields($fields, $account);
 
$output .= theme('profile_listing', $account, $profile);
}
$output .= '</div>';
$output .= theme('pager', NULL, 20);
print (
$output);
?>

------------------------------------------------------------------------------
(6) List Users From a Single Role and "Includes Edit field"
Outputs User Table (http://drupal.org/node/63422)
------------------------------------------------------------------------------

<?php
//choose the role to list by value. 
// Note ID 1 = anonymous, ID 2 = authenticated user
// so valid values here are > 2.
$rid = 3;
?>


<h2>A list of all users with Role ID <?php print $rid?></h2>
<br/>

<?php
$header
= array(
  array(
'data' => t('Username'), 'field' => 'u.name'),
  array(
'data' => t('Status'), 'field' => 'u.status'),
  array(
'data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc'),
  array(
'data' => t('Last access'), 'field' => 'u.access'),
 
t('Operations')
);
$sql = "SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u INNER JOIN {users_roles} ur ON u.uid=ur.uid WHERE ur.rid = $rid";
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
 
$rows[] = array(theme('username', $account),
         
$status[$account->status],
         
format_interval(time() - $account->created),
         
$account->access ? t('%time ago', array('%time' => format_interval(time() - $account->access))) : t('never'),
         
l(t('edit'), "user/$account->uid/edit", array()));
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
?>

The above PHP Snippet code yields the following results:

Username      Status       Member for            Last access          Operations
--------------------------------------------------------------------------------
admin         active       1 week 19 hours       4 sec ago             edit
Debbie        active       11 hours 19 min       never                 edit
Jimmy         active       5 days 23 hours       9 hours 41 min ago    edit
Sammy         active       4 days 8 hours        32 min 35 sec ago     edit

------------------------------------------------------------------------------
(7) List Users From a Single Role - "No Edit field"
Outputs User Table (http://drupal.org/node/63422)
------------------------------------------------------------------------------

<?php
//choose the role to list by value. 
// Note ID 1 = anonymous, ID 2 = authenticated user
// so valid values here are > 2.

$rid = 3;
?>

<h2>A list of all users with Role ID <?php print $rid?></h2>
<br/>
<?php

$header
= array(
  array(
'data' => t('Username'), 'field' => 'u.name', 'sort' => 'asc'),
  array(
'data' => t('Status'), 'field' => 'u.status'),
  array(
'data' => t('Member for'), 'field' => 'u.created'),
  array(
'data' => t('Last access'), 'field' => 'u.access')
);
$sql = "SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u INNER JOIN {users_roles} ur ON u.uid=ur.uid WHERE ur.rid = $rid";
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
 
$rows[] = array(theme('username', $account),
         
$status[$account->status],
         
format_interval(time() - $account->created),
         
$account->access ? t('%time ago', array('%time' => format_interval(time() - $account->access))) : t('never'));
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
?>

The above PHP Snippet code yields the following results:

Username      Status       Member for            Last access
--------------------------------------------------------------------
Debbie        active       11 hours 19 min       never
Sammy         active       4 days 8 hours        32 min 35 sec ago
Jimmy         active       5 days 23 hours       9 hours 41 min ago
admin         active       1 week 19 hours       4 sec ago

------------------------------------------------------------------------------
(8) List Users from a Single Role and includes a single personal profile field augmented to the Username
Outputs User Table (http://drupal.org/node/63422)
------------------------------------------------------------------------------

<?php
//choose the role to list by value. 
// Note ID 1 = anonymous, ID 2 = authenticated user
// so valid values here are > 2.

$rid = 3;
?>

<h2>A list of all users with Role ID <?php print $rid?></h2>
<br/>
<?php

$header
= array(
  array(
'data' => t('Username'), 'field' => 'u.name', 'sort' => 'asc'),
  array(
'data' => t('Status'), 'field' => 'u.status'),
  array(
'data' => t('Member for'), 'field' => 'u.created'),
  array(
'data' => t('Last access'), 'field' => 'u.access')
);
$sql = "SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u INNER JOIN {users_roles} ur ON u.uid=ur.uid WHERE ur.rid = $rid";
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
$account = user_load(array('uid' => $account->uid));
$account->name .= ' (' . $account->profile_profession . ')';
$rows[] = array(theme('username', $account),
         
$status[$account->status],
         
format_interval(time() - $account->created),
         
$account->access ? t('%time ago', array('%time' => format_interval(time() - $account->access))) : t('never'));
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
?>

The above PHP Snippet code yields the following results:

Username                Status      Member for            Last access
-----------------------------------------------------------------------------
admin (Business...      active      1 week 19 hours       4 sec ago
Debbie  (Execut...      active      11 hours 19 min       never
Jimmy (Business O.      active      5 days 23 hours       9 hours 41 min ago
Sammy (Business...      active      4 days 8 hours        32 min 35 sec ago

------------------------------------------------------------------------------
(9) List Users from a "Single Role" and includes multiple personal profile data - I modified the above script (8) to also output personal information profile filelds.
Outputs User Table (http://drupal.org/node/103497)
------------------------------------------------------------------------------

Where the following are Personal Profile Fields:
------------------------------------------------
Profession = profile_profession
Membership = profile_membership
Gender = profile_gender
Age = profile_age
Zip code = profile_zipcode

<?php
//choose the role to list by value. 
// Note ID 1 = anonymous, ID 2 = authenticated user,  ID 3 = member,  ID 4 = admin-level1
// so valid values here are > 2.

$rid = 3;
?>

<br/>
Click on the Field name to sort the list.<br/><br>
Currently the fields: Profession, Membership, Gender, Age, Hobbies & Interest, and Zip code, are not sortable.
<?php

$header
= array(
  array(
'data' => t('Username'), 'field' => 'u.name', 'sort' => 'asc'),
  array(
'data' => t('User'), 'field' => 'u.uid'),
  array(
'data' => t('Profession'), 'field' => 'u.profession'),
  array(
'data' => t('Membership'), 'field' => 'u.membership'),
  array(
'data' => t('Gender'), 'field' => 'u.gender'),
  array(
'data' => t('Age'), 'field' => 'u.age'),
  array(
'data' => t('Zip code'), 'field' => 'u.zipcode'),
  array(
'data' => t('Status'), 'field' => 'u.status'),
  array(
'data' => t('Member for'), 'field' => 'u.created'),
  array(
'data' => t('Last access'), 'field' => 'u.access')
);
$sql = "SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u INNER JOIN {users_roles} ur ON u.uid=ur.uid WHERE ur.rid = $rid";
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
      
$account = user_load(array('uid' => $account->uid));

$rows[] = array(theme('username', $account),
         
$account->uid,
         
$account->profile_profession,
         
$account->profile_membership,
         
$account->profile_gender,
         
$account->profile_age,
         
$account->profile_zipcode,
         
$status[$account->status],
         
format_interval(time() - $account->created),
         
$account->access ? t('%time ago', array('%time' => format_interval(time() - $account->access))) : t('never'));
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
?>

The above PHP Snippet code yields the following results:

Note: Only the fields: Ussername, User, Status, Member for, and Last access, are sortable.

This table may wrap and not appear correctly on this drupal.org handbook page, but will display correctly on your website. You can also copy and paste this table into an ASCII document to view it correctly.

Username   User   Profession          Membership    Gender    Age   Zip code   Status   Member for        Last access
----------------------------------------------------------------------------------------------------------------------------
admin      1      Business Owner      A-Member      Male      41    91324      active   1 week 19 hours   4 sec ago
Debbie     6      Executive           A-Member      Female    38    91377      active   11 hours 19 min   never
Jimmy      2      Business Owner      B-Member      Male      24    91405      active   5 days 23 hours   9 hours 41 min ago
Sammy      4      Business Owner      Non-member    Male      37    97651      active   4 days 8 hours    32 min 35 sec ago

------------------------------------------------------------------------------
(10) List Users from a "ALL Roles" and includes multiple personal profile data - I modified the above script (9) to also output both the personal information profile fields and the profile images.
Outputs User Table (http://drupal.org/node/103497)
------------------------------------------------------------------------------

This Members list displays a table of "all users" from "all roles" including their profile images which are links.

Where the following are Personal Profile Fields:
------------------------------------------------
Membership = profile_membership
Zip code = profile_zipcode
Age = profile_age
Gender = profile_gender
Profession = profile_profession

Note:
Remember to replace the default image and path below (../assets/images/default-user-sm.gif) with your default image and path.

<?php

// Displays a list of all users including their profile images (all roles)

$header = array(
  array(
'data' => t('Username'), 'field' => 'u.name', 'sort' => 'asc'),
  array(
'data' => t('Photo'), 'field' => 'u.photo'),
  array(
'data' => t('Membership'), 'field' => 'u.membership'),
  array(
'data' => t('Zip code'), 'field' => 'u.zipcode'),
  array(
'data' => t('Age'), 'field' => 'u.age'),
  array(
'data' => t('Gender'), 'field' => 'u.gender'),
  array(
'data' => t('Profession'), 'field' => 'u.profession'),
  array(
'data' => t('Last access'), 'field' => 'u.access')
);
$sql = 'SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u WHERE uid != 0';
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
      
$account = user_load(array('uid' => $account->uid));
       if(
$account->picture){$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="'.$account->picture.'" height="25" width="25" border="1" alt=""></a>';}
         else{
$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="../assets/images/default-user-sm.gif" height="25" width="25" border="1" alt=""></a>';}

$rows[] = array(theme('username', $account),
         
$account->picture,
         
$account->profile_membership,
         
$account->profile_zipcode,
         
$account->profile_age,
         
$account->profile_gender,
         
$account->profile_profession,
         
$account->access ? t('%time ago', array('%time' => format_interval(time() - $account->access))) : t('never'));
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
?>

The above PHP Snippet code yields the following results:

See an actual screen shot at: http://xlecom.com/assets/images/example-member-list.gif

Note: Only the fields: Ussername and Last access are sortable.

This table may wrap and not appear correctly on this drupal.org handbook page, but will display correctly on your website. You can also copy and paste this table into an ASCII document to view it correctly.

Username    Photo     Membership  Zip code  Age    Gender   Profession            Last access
-----------------------------------------------------------------------------------------------------
admin      (photo)    Member      95421     47     Male     Business Owner        6 min 51 sec ago
bachel     (photo)    Member      95488     46     Male     Warehouse Supervisor  12 hours 21 min ago
cristo     (photo)    Member      90095     43     Male     Career Counselor      5 days 9 hours ago
Debbie     (photo)    Member      95481     42     Female   Executive             never
dicema     (photo)    Member      95450     40s    Female   Investigations        2 weeks 2 days ago
fourmu     (photo)    Non-Member  95421     44     Female   Self Employed         1 week 2 days ago
Jimmy      (photo)    Member      95450     28     Male     Business Owner        1 day 14 hours ago
michael    (photo)    Non-Member  95481     53     Male     Public Affairs        1 day 14 hours ago
Sammy      (photo)    Member      95421     47     Male     Business Owner        8 hours 40 min ago
soulhea    (photo)    Member      95420     42     Female   Business owner        never
TestUs     (photo)    Member      95421     33     Male     Self Employeed        11 hours 5 min ago

------------------------------------------------------------------------------
(11) List Users from a "ALL Roles" and includes their "ROLES", personal profile data, profile images, and Edit field.

This Table is meant to be used only by Site Administrators because of the inclusion of the Roles and the Edit fields.

This is a modification of script (10)

Outputs User Table
------------------------------------------------------------------------------

This Members list displays a table of "all users" from "all roles" including their "ROLES", personal profile data, profile images, and Edit field. The thumbnail profile images are also links to the user's profile page.

The following are the Personal Profile Fields:
----------------------------------------------
Membership = profile_membership
Zip code = profile_zipcode
Age = profile_age
Gender = profile_gender
Profession = profile_profession

Note:
Remember to replace the default image and path below (../assets/images/default-user-sm.gif) with your default image and path.

<?php

// Displays a list of all users including their Roles, personal profile data, profile images, and Edit field. (all roles)

$header = array(
 
t('Edit'),
  array(
'data' => t('Username'), 'field' => 'u.name', 'sort' => 'asc'),
  array(
'data' => t('User ID'), 'field' => 'u.uid'),
  array(
'data' => t('Photo')),
  array(
'data' => t('Roles')),
  array(
'data' => t('Membership')),
  array(
'data' => t('Zip code')),
  array(
'data' => t('Age')),
  array(
'data' => t('Gender')),
  array(
'data' => t('Profession')),
  array(
'data' => t('Member for'), 'field' => 'u.created'),
  array(
'data' => t('Last access'), 'field' => 'u.access')
  );

$sql = 'SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u WHERE uid != 0';
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
$destination = drupal_get_destination();

while (
$account = db_fetch_object($result)) {
   
$rolesresult = db_query('SELECT r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $account->uid);
   
$roles = array();

   
$account = user_load(array('uid' => $account->uid));
    if(
$account->picture){$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="'.$account->picture.'" height="25" width="25" border="1" alt=""></a>';}
       else{
$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="../assets/images/default-user-sm.gif" height="25" width="25" border="1" alt=""></a>';}

    while (
$role = db_fetch_object($rolesresult)) {
      
$roles[] = $role->name;
    }

$rows[] = array(
         
l(t('edit'), "user/$account->uid/edit", array()),
         
theme('username', $account),
         
$account->uid,
         
$account->picture,
         
implode(',<br />', $roles),
         
$account->profile_membership,
         
$account->profile_zipcode,
         
$account->profile_age,
         
$account->profile_gender,
         
$account->profile_profession,
         
format_interval(time() - $account->created),
         
$account->access ? t('%time ago', array('%time' => format_interval(time() - $account->access))) : t('never')
          );
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
?>

The above PHP Snippet code yields the following results:

Note: Only the fields: "Ussername", "User ID", "Member for", and "Last access" are sortable.

This table may wrap and not appear correctly on this drupal.org handbook page, but will display correctly on your website. You can also copy and paste this table into an ASCII document to view it correctly.

Edit   Username  User ID  Photo    Roles      Membership  Zip code  Age    Gender   Profession            Member for          Last access
-------------------------------------------------------------------------------------------------------------------------------------------------
edit   admin     1       (photo)   Employee   Member      95421     47     Male     Business Owner        10 weeks 16 hours   6 min 51 sec ago
edit   bachel    4       (photo)   Employee   Member      95488     46     Male     Warehouse Supervisor  7 weeks 6 days      12 hours 21 min ago
edit   cristo    6       (photo)   Employee   Member      90095     43     Male     Career Counselor      5 days 11 hours     5 days 9 hours ago
edit   Debbie    11      (photo)   Manager    Member      95481     42     Female   Executive             1 weeks 4 days      never
edit   dicema    5       (photo)   Manager    Member      95450     40s    Female   Investigations        6 weeks 18 hours    2 weeks 2 days ago
edit   fourmu    10      (photo)   Employee   Non-Member  95421     44     Female   Self Employed         1 weeks 4 days      1 week 2 days ago
edit   Jimmy     8       (photo)   Manager    Member      95450     28     Male     Business Owner        3 weeks 4 days      1 day 14 hours ago
edit   michael   9       (photo)   Employee   Non-Member  95481     53     Male     Public Affairs        2 weeks 4 days      1 day 14 hours ago
edit   Sammy     2       (photo)   Manager    Member      95421     47     Male     Business Owner        9 weeks 4 days      8 hours 40 min ago
edit   soulhea   7       (photo)   Manager    Member      95420     42     Female   Business owner        4 weeks 4 days      never
edit   TestUs    3       (photo)   Employee   Member      95421     33     Male     Self Employeed        8 weeks 4 days      11 hours 5 min ago

------------------------------------------------------------------------------
(12) An IF-THEN version of script for approved user roles. List Users from a "ALL Roles" and includes multiple personal profile data.
Outputs a User Table similar to example (10)
------------------------------------------------------------------------------

Where the following are Personal Profile Fields:
------------------------------------------------
Zip code = profile_zipcode
Age = profile_age
Gender = profile_gender
Profession = profile_profession
Location = profile_location

Where the Approved Roles are:
-----------------------------
admin-level1
authenticated user
member
pre-authorized

Note:
Remember to replace the default image and path below (../assets/images/default-user-sm.gif) with your default image and path.

Below is the basic IF-THEN script for approved user roles. This is the same as the script below, but without the inserted Member's List PHP code for the TRUE and FALSE conditions.

This version uses an IF-THEN routine to determine the user's role. If the user is a site member (approved roles), then the script will execute the TRUE condition. If not a site member (Anonymous user), then the script will will execute the FALSE condition.

<?php
global $user;
$output ='';
$approved_roles = array('admin-level1', 'authenticated user', 'member', 'pre-authorized');
if (
is_array($user->roles)) {
  if (
count(array_intersect($user->roles, $approved_roles)) > 0)

     {
place your PHP code here for a TRUE condition - for the approved roles}

     else

     {
place your PHP code here for a FALSE condition - for none of the approved roles}

}
?>

A Working Example:

This version uses an IF-THEN routine to determine the user's role. If the user is a site member (approved roles), then the page will display a Member's List with valid usernames and thumbnail photos. If not a site member (Anonymous user), then the page will display the same Member's List using fake usernames and no real photos.

Below is the IF-THEN version which first checks the role of the site visitor and displays the appropriate Member's List:

In the sample code below, I removed some of the "print" HTML statements and two user fields that I am actually using in order to display a shorter version of the script.

<?php
global $user;
$approved_roles = array('admin-level1', 'authenticated user', 'member', 'pre-authorized');
if (
is_array($user->roles)) {
  if (
count(array_intersect($user->roles, $approved_roles)) > 0)
      {

// Actual Site Members

print("<P style=\"TEXT-ALIGN: justify\"><B>Site Members</b><br>The following is a list of all site members.</p>");

print(
"<P style=\"TEXT-ALIGN: justify\"><i>Currently, only the <span style=\"color: rgb(153,0,0)\">Ussername</span> field is sortable.</i></p>");

// Displays a list of all Actual Site Members (all roles)

$header = array(
  array(
'data' => t('Username'), 'field' => 'u.name', 'sort' => 'asc'),
  array(
'data' => t('Photo')),
  array(
'data' => t('Zip code')),
  array(
'data' => t('Age')),
  array(
'data' => t('Gender')),
  array(
'data' => t('Profession')),
  array(
'data' => t('Location'))
  );

$sql = 'SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u WHERE uid != 0';
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
      
$account = user_load(array('uid' => $account->uid));
       if(
$account->picture){$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="'.$account->picture.'" height="25" width="25" border="1" alt=""></a>';}
         else{
$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="../assets/images/default-user-sm.gif" height="25" width="25" border="1" alt=""></a>';}

$rows[] = array(theme('username', $account),
         
$account->picture,
         
$account->profile_zipcode,
         
$account->profile_age,
         
$account->profile_gender,
         
$account->profile_profession,
         
$account->profile_location
         
);
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
      }

  else{

// Sample List of Site Members

print("<P style=\"TEXT-ALIGN: justify\"><B>Sample List of Site Members</b><br>The following is an actual sample list of our site members. Their \"real\" usernames and photos are <u>only available to registered site members</u>. After you log in, this list will include both their \"real\" usernames and photos. You will also have the option to <span style=\"color: rgb(153,0,0)\">sort</span> the list</p>");

// Displays a Sample List of Site Members (all roles)

$header = array(
  array(
'data' => t('Username')),
  array(
'data' => t('Photo')),
  array(
'data' => t('Zip code')),
  array(
'data' => t('Age')),
  array(
'data' => t('Gender')),
  array(
'data' => t('Profession')),
  array(
'data' => t('Location'))
  );

$sql = 'SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u WHERE uid != 0';
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
      
$account = user_load(array('uid' => $account->uid));
      
$account->picture = '<img src="../assets/images/default-user-sm.gif" height="25" width="25" border="1" alt=""></a>';
      
srand((double)microtime()*1000000); 
      
$randnum = rand(0,1000);
      
$fakeuser = "User-" . $randnum;

$rows[] = array($fakeuser,
         
$account->picture,
         
$account->profile_zipcode,
         
$account->profile_age,
         
$account->profile_gender,
         
$account->profile_profession,
         
$account->profile_location
         
);
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
      }
}
?>

------------------------------------------------------------------------------
(13) An IF-THEN version of script for approved user roles. List Users from a "ALL Roles" and includes multiple personal profile data.
Outputs a User Table similar to example (10).
This version outputs a "sortable list by all fields" by utilizing the Members module.
------------------------------------------------------------------------------

Members module: http://drupal.org/project/members (version tested: members-4.7.x-1.x-dev.tar.gz)

This example is essentially the same are example (12), but uses the Members module to create a "sortable list by all fields". Approved Role members can sort the list by field title in ascending or descending order and view the profiles of all site members who share a common response in a field category by clicking on the links within the table itself.

Where the following are Personal Profile Fields:
------------------------------------------------
Zip code = profile_zipcode
Age = profile_age
Gender = profile_gender
Profession = profile_profession
Location = profile_location

Where the Approved Roles are:
-----------------------------
admin-level1
authenticated user
member
pre-authorized

A Working Example:

This version uses an IF-THEN routine to determine the user's role. If the user is a site member (approved roles), then the page will display a Member's List with valid usernames and thumbnail photos. If not a site member (Anonymous user), then the page will display the same Member's List using fake usernames and no real photos.

The following PHP code is used for the TRUE condition. It calls the Members list module to display the members list:

$q = $_GET['q'];
$_GET['q'] = 'members';
print menu_execute_active_handler();
$_GET['q'] = $q;

By the way, this piece of code is useful for retrieving any page or URL in the Drupal system.

To see a working version, go to: http://singleinscv.com/ and click on the "Site Members" link.

Below is the IF-THEN version which first checks the role of the site visitor and displays the appropriate Member's List:

In the sample code below, I removed some of the "print" HTML statements and two user fields that I am actually using in order to display a shorter version of the script.

<?php
global $user;
$approved_roles = array('admin-level1', 'authenticated user', 'member', 'pre-authorized');
if (
is_array($user->roles)) {
  if (
count(array_intersect($user->roles, $approved_roles)) > 0)
      {

// Actual Site Members

print("<P style=\"TEXT-ALIGN: justify\"><B>Site Members</b><br>The following is a list of all site members.</p>");

print(
"<P style=\"TEXT-ALIGN: justify\"><i>Currently, only the <span style=\"color: rgb(153,0,0)\">Ussername</span> field is sortable.</i></p>");

// Displays a list of all Actual Site Members (all roles)

$q = $_GET['q'];
$_GET['q'] = 'members';
print
menu_execute_active_handler();
$_GET['q'] = $q;
      }

  else{

// Sample List of Site Members

print("<P style=\"TEXT-ALIGN: justify\"><B>Sample List of Site Members</b><br>The following is an actual sample list of our site members. Their \"real\" usernames and photos are <u>only available to registered site members</u>. After you log in, this list will include both their \"real\" usernames and photos. You will also have the option to <span style=\"color: rgb(153,0,0)\">sort</span> the list</p>");

// Displays a Sample List of Site Members (all roles)

$header = array(
  array(
'data' => t('Username')),
  array(
'data' => t('Photo')),
  array(
'data' => t('Zip code')),
  array(
'data' => t('Age')),
  array(
'data' => t('Gender')),
  array(
'data' => t('Profession')),
  array(
'data' => t('Location'))
  );

$sql = 'SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u WHERE uid != 0';
$sql .= tablesort_sql($header);
$result = pager_query($sql, 50);

$status = array(t('blocked'), t('active'));
while (
$account = db_fetch_object($result)) {
      
$account = user_load(array('uid' => $account->uid));
      
$account->picture = '<img src="../assets/images/default-user-sm.gif" height="25" width="25" border="1" alt=""></a>';
      
srand((double)microtime()*1000000); 
      
$randnum = rand(0,1000);
      
$fakeuser = "User-" . $randnum;

$rows[] = array($fakeuser,
         
$account->picture,
         
$account->profile_zipcode,
         
$account->profile_age,
         
$account->profile_gender,
         
$account->profile_profession,
         
$account->profile_location
         
);
}

$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
print (
$output);
      }
}
?>

Notes:

The following modifications to the Members module were made for visual enhancement only. This is not a required step.

The default Members module does not allow you to rearrange the order of the displayed field columns and also displays the user's profile pictures in full original size.. To make the both the "Actual Site Members List" and "Sample List of Site Members" appear identical to each other, the following changes were made to the Members module to make the field order the same and the user's profile pictures appear as small thumbnail images.

Modification (1):
This change was made to make all the user's profile pictures appear as small thumbnail images instead a the default large images.

Original Code:

$data = implode (', ', $data);
          }
          else if ($field == 'picture') {

$data = theme_user_picture($account);
          }
          else if ($field == 'access') {

Modified Code:

$data = implode (', ', $data);
          }
          else if ($field == 'picture') {

if($account->picture){$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="'.$account->picture.'" height="25" width="25" border="1" alt=""></a>';}
   else{$account->picture = '<a href="?q=user/'.$account->uid.'"><img src="../assets/images/default-user-sm.gif" height="25" width="25" border="1" alt=""></a>';}

$data = $account->picture;
          }
          else if ($field == 'access') {

Modification (2):
This change was made to make both the user's profile picture and email address fields appear in the first few columns instead of at the end of the table.

Original Code:

function _member_fields() {
  $output = array();

  $output['rid'] = t('Roles');
  $output['name'] = t('Username');

  // profile fields
  if (module_exist('profile')) {
    $result = db_query('SELECT name, title FROM {profile_fields} WHERE visibility = %d ORDER BY weight ASC', PROFILE_PUBLIC_LISTINGS);
    while ($row = db_fetch_object($result)) {
      $output["profile.$row->name"] = $row->title;
    }
  }

  $output['mail'] = t('Email');
  $output['access'] = t('Last Seen');
  $output['picture'] = t('Picture');

  return $output;

Modified Code:

function _member_fields() {
  $output = array();

  $output['rid'] = t('Roles');
  $output['name'] = t('Username');
  $output['picture'] = t('Picture');
  $output['mail'] = t('Email');

  // profile fields
  if (module_exist('profile')) {
    $result = db_query('SELECT name, title FROM {profile_fields} WHERE visibility = %d ORDER BY weight ASC', PROFILE_PUBLIC_LISTINGS);
    while ($row = db_fetch_object($result)) {
      $output["profile.$row->name"] = $row->title;
    }
  }

  $output['access'] = t('Last Seen');

  return $output;

------------------------------------------------------------------------------

Note: If you do not want any of the fields sortable, then make the changes to the script as follows:

Sortable by Username and Last Access:

$header = array(
  array('data' => t('Username'), 'field' => 'u.name', 'sort' => 'asc'),
  array('data' => t('Photo')),
  array('data' => t('Membership')),
  array('data' => t('Zip code')),
  array('data' => t('Age')),
  array('data' => t('Gender')),
  array('data' => t('Profession')),
  array('data' => t('Last access'), 'field' => 'u.access')
);

Non-sortable at all:

$header = array(
  array('data' => t('Username')),
  array('data' => t('Photo')),
  array('data' => t('Membership')),
  array('data' => t('Zip code')),
  array('data' => t('Age')),
  array('data' => t('Gender')),
  array('data' => t('Profession')),
  array('data' => t('Last access'))
);

===========================================================

Question:

Does anyone know how to make the tables shown in Examples (9) thur (12) sortable on all the "personal profile" fields?

If you do, please post your answer on the following topic page:

"User List Sorting Problem" http://drupal.org/node/103497

Thanks,

Sam Raheb (Sam308)

===========================================================

Drupal Members List PHP Script Generator Version 1.0 (January 2007)
© 2007 Developed by Sam Raheb
Released: January 21, 2007
Designed for all version of Drupal

System Requirements
Microsoft Excel 2000, 2002, 2003, or 2007 with Visual Basic for Applications (VBA) support

Download: http://xlecom.com/downloads/Drupal_Members_List_PHP_Code_Generator_Versi...

Instructions
This utility program will create a Members List PHP snippet that you can cut and paste into a node page or block. You only need to enter data into the table on this spreadsheet. The other two sheets are used for support references. To change the drop-down Cell List Box Selection values, edit the values on the "Selections" sheet.

The program only utilizes the data located in the Title and Display columns (red colored fonts). The rest of the data in the table is only for management purposes.

(1) Enter your profile fields in the "Title" column.
(2) Select the fields you want to display in the "Display" column.
(3) Press the "Copy Script to Memory" button and paste the code into your node or block.

Spreadsheet Cell Reference Colors

  • Red font colored cell are user input cells. Enter your data into these cells.
  • Brown font colored cells are users selection cells.
  • Blue colored cells contain formulas. Editing of these cells should be performed by developers who need to modify the code.
  • Black and other font colored cells are code and description labels.

I hope you enjoy this utility. If you do, I would like to know.

Thanks,
Sam Raheb (sam308)

Display link to user's embedded gallery (or not if it does not exist)

This requires (at the time of writing) the CVS version of the Gallery2 module it includes a patch that makes this feature work. You can display a link to a member's Gallerry2 gallery, or not if it does not exist, using this code:

<?
$galleryprofile=gallery_view_user($user);
print $galleryprofile['Gallery2']['gallery_view_user_album']['value'];
?>

Display a customisable "this user is [online/offline]" status message in the User Profile page

Note from the moderator: Thank you for sharing the snippet with the Drupal community. In its current state the snippet might present security risks. See Writing secure code for a background on the most common problems.

Specifically, the second snippet

  • allows SQL injection because it uses user supplied data directly inside an SQL query without further checks. Proper use of Drupals database abstraction layer can prevent these attacks.

Example of db_query usage:

<?php
  db_query
("SELECT * FROM {users} WHERE uid = $uid"); // INSECURE
 
db_query("SELECT * FROM {users} WHERE uid = %d", $uid);
?>

--

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet adds a This user is [online/Offline] status message in the user profile page.

Dependencies: profile.module must be installed and enabled.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Select appropriate snippet for the versionof Drupal you are using
  • Change the div class name or the message text to suit.
Snippet for Drupal 4.5 and 4.6
<?php
print "<div class=\"fields\">";
print
"Online/Offline Status";
$time_period = variable_get('user_block_seconds_online', 2700);
$uid = arg(1); // get the current userid that is being viewed.
$users = db_query('SELECT DISTINCT(uid), MAX(timestamp) AS max_timestamp FROM {sessions} WHERE timestamp >= %d AND uid = %u GROUP BY uid ORDER BY max_timestamp DESC', time() - $time_period, $uid);
         
$total_users = db_num_rows($users);

          if (
$total_users == 1) {
           
$output = t('This user is currently online');
          }
          else {
           
$output = t('This user is currently offline');        
          }

              print
$output;
                       print
"</div>";
?>
Snippet for Drupal 4.7

Thanks to rocketman528 for this snippet.

<?php
print "<div class=\"fields\">";
print
"Online/Offline Status";
$time_period = variable_get('user_block_seconds_online', 2700);
$uid = arg(1); // get the current userid that is being viewed.
$users = db_query("SELECT uid, name, access FROM {users} WHERE access >= %d AND uid = $uid", time() - $time_period);
         
$total_users = db_num_rows($users);

          if (
$total_users == 1) {
           
$output = t('This user is currently online');
          }
          else {
           
$output = t('This user is currently offline');     
          }

              print
$output;
                      print
"</div>";
?>

Display a user friendly text message when profile fields are left blank

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays a user friendly "this user has not provided their [profile field]" text when a profile field is not filled out. An alternative to simply ignoring the field. I find it useful for mobile phone numebrs or personal contact details, e.g. "please contact this user by private message for their [profile field]".

The default settings with Drupal is to hide or ignore empty fields, so Users sometimes don't know they can add a field unless they look at the various EDIT MY ACCOUNT options.

Dependencies: profile.module

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the $pfieldname value in the first line to match the profile field name name you are referecing.
  • If you are using this snippet more than once in the same user_profile.tpl.php file add a number to the end of the $pfieldname titles each time you copy and use the snippet. e.g. $pfieldname1, $pfieldname2, $pfieldname3 etc.
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the link prefix text to suit.
<?php $pfieldname = "profile_mobile_no"; ?>
<?php if(($user->$pfieldname) == ""): ?>
<div class="fields">
Please contact me by private message from my mobile phone number.
</div>
<?php endif; ?>
<?php print $user->$pfieldname ; ?>
<div class="fields">
</div>

Display a user's points value from the userpoints.module

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays the number of points for the user using the userpoints.module.

Usage
  • For use in your user profile page override (or anywhere else you want to display user points)
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Tested and works with Drupal 4.7 and 5.x
  • Change the div class names or the prefix text to suit.

You can directly query the database:

<div class="fields">
<p><?php  print t('Points:').db_result(db_query('SELECT points FROM {userpoints} WHERE uid = %d', $user->uid));?></p>
</div>

Or use this function call which does the query for you:

<?php
print userpoints_get_current_points($user->uid);
?>

Remember if you use this snippit in other places that you need to be sure the $user object is set to who you want first so you're passing in the correct ID.

Display recent nodes (titles & teasers) snippet

Description

This php snippet displays the 10 most recent nodes submitted by the user, restricting certain node types. In this example, pages, stories, and forum posts are excluded from the list.

Usage

• For use in a user profile page override
• Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
• To increase/decrease the number of posts listed change the $nlimit value to suit. The default setting is 10.
• Tested and works with Drupal 4.6
• Change the div class names or the prefix text to suit.

<?php $nlimit = 10; ?>
<?php $userid=$user->uid; ?>
<?php $result1 = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.status = 1 AND n.uid = $userid AND n.type != 'page' AND n.type != 'poll' AND n.type != 'forum' AND n.type != 'story' ORDER BY n.created DESC"), variable_get('default_nodes_main', $nlimit)); ?>
<?php while ($node = db_fetch_object($result1)) {$output2 .= node_view(node_load(array('nid' => $node->nid)), 1);}; ?>
<?php print $output2; ?>

Display user submitted images in their profile page

This snippet will display the thumbnail images of the most N most recently submitted images by the user.
You can also optionally limit the images selected based on taxonomy term id's (see snippet for details

<?php
// Display N most recent thumbnails of images submitted by the user
//  Each thumbnail is linked back to it's image node
// Can optional limit the photos shown by specififying one or taxonomy term id's

// The number of thumbnail images to show
$nlimit = 3;
$taxo_id = array();
// Add one line for each taxonomy term id you want to limit the thumbnails to
// As an example, the following two lines would select images associated with taxonomy terms 36 OR 37
// $taxo_id[] = 36;
// $taxo_id[] = 37;
// Note, if not taxonomy term ids are specified, the selection is from all the user submitted images

$userid=$user->uid;

if (
count($taxo_id) > 0 ) {
   
// Limit images based on taxonomy term id's
   
$taxo_str = implode(',', $taxo_id);
   
$sql = "SELECT n.created, n.title, n.nid, n.changed FROM node n INNER JOIN term_node ON n.nid = term_node.nid AND term_node.tid IN ($taxo_str) WHERE n.type = 'image' AND n.uid = $userid AND n.status = 1 ORDER BY n.changed DESC";
}
else {
   
$sql = "SELECT n.created, n.title, n.nid, n.changed FROM node n WHERE n.type = 'image' AND n.uid = $userid AND n.status = 1 ORDER BY n.changed DESC";
}

$result = db_query_range($sql, 0, $nlimit);
$output = '';
while (
$info = db_fetch_object($result) ) {
 
$node = node_load(array('nid' => $info->nid));
 
$img_tag = image_display($node, 'thumbnail');
 
$link = 'node/' . $node->nid;
 
$output .= l($img_tag, $link, array(), NULL, NULL, FALSE, TRUE);
}
print
$output;
?>

Drupal V5.x warning: Cannot add header information

A very common error while trying to modify the user profile in Drupal 5.x is:

warning: Cannot add header information - headers already sent

This is usually an issue associated with line breaks or text file encoding.

Confirmed Solution for Drupal 5.1
  • Stop using MS Notepad, Download Context ( http://www.context.cx/ )
  • Make sure all your files are in DOS (ANSI) not Unicode. (Context -> Convert Test To -> Dos)
  • Usually the files that are in Unicode are the ones that display in the error message.
  • /includes/theme.inc
  • /includes/common.inc
  • theme-dir/template.php
  • theme-dir/user_profile.tpl.php
  • Eliminate all but 1 line break from the end of all of the php (template and user_profile) and inc (/includes/*) files.

    Please only post specific solutions to your problem as comments.

Handling date profile fields

Adding dates to your custom profile.

This has only been tested with 4.7.0

This module adds a birthday into the custom profile, but you can easily customize it to add any date, such as date of death for a obituary (for a famous actor's profile maybe). You must have made a template.php already. This code should be added to your user_profile.tlp.php file.

<div class="fields"><b>Birthday:</b> <?php print $user->profile_birthday['month']."/".$user->profile_birthday['day']."/".$user->profile_birthday['year'];?>

Note that you will have to customize the profile_birthday to whatever your settings are in admin/settings/profile. To change the date layout, just move the month, day, and year around, like so:

<div class="fields"><b>Birthday:</b> <?php print $user->profile_birthday['year']."/".$user->profile_birthday['month']."/".$user->profile_birthday['day'];?>

Howto: Make the user profile layout compact (with css only)

Screen Shots:
Before: http://img205.imageshack.us/img205/899/beforedw4.png
After: http://img164.imageshack.us/img164/2630/aftergv7.png

To make the view of the 'user profile' page compact, add the following CSS to the end of your theme's .css file. This is generally better than modifying the profile.css or user.css (or drupal.css in 4.7) directly.

.profile h2.title {
  margin-top:13px;
  border-bottom-width: 1px;
  border-bottom-style: solid;
}

.profile dd {
  min-height:10px;
  margin:5px;
  margin-top:13px;
  margin-left:180px;
  margin-bottom:0px;
  padding:0px;
  width:200px;
  height:auto;
  position: relative;
}

.profile dt {
  width:180px;
  background-color: #DEE;
  float:none;
  margin-bottom: -27px;
}

Alternate version using a floating dt

.profile h2.title {
  margin-top: 15px;
  border-bottom: 1px solid #777777;
}
.profile dt {
  margin: 0;
  padding: 2px 3px;
  width: 120px;
  background-color: #dee;
  border-bottom: 1px solid white; 
  float:left;
}
.profile dd {
  margin: 0 0 0 130px;
  padding: 2px 3px;
  border-bottom: 1px dotted #d9d9ff;
}

Insert Subscribed Organic Groups List

You can add a list of groups that the user has subscribed to with this code:

<b>Groups</b>
<?php $userid=$user->uid;
$result = db_query("SELECT n.created, n.title, n.nid, o.nid, n.changed FROM node n, og_uid o WHERE o.uid = $userid AND n.nid = o.nid AND n.type = 'gruppe' AND n.status = 1 ORDER BY n.changed DESC");
$output3 .= "<div class=\"item-list\"><ul>\n";
$list = node_title_list($result);
$output3 .= strip_tags($list) ? $list : '<li>No Groups</li>';
print
$output3; ?>

</ul></div>

Messages for unpublished profiles

These snippets, added to my user_profile.tpl.php file, are for Drupal 4.6. The profile.module must be enabled.

In my profile, I have a check box that asks: Do you want your profile to be listed on the site? If the person checks the box, then the profile will be visible to all registered users on the site. The profile field associated with the check box is "profile_list."

These snippets respond to the case when a user has not yet published his profile to other users on the site.

My goal was to distinguish between the user who might be looking at his own profile and a user looking at someone else's.

If a user clicks on his own unpublished profile, the following snippet informs him, "You have not yet entered in your user profile" and provides a link to his profile page.

<?php if ((!$user->profile_list) and ($user ==  $GLOBALS["user"])) {print "You have not yet entered in your  <a href='user/".$user->uid."/edit/Profile'>user\ profile</a>!";} ?>

If a user comes across someone else's unpublished profile, the following snippet explains that she has not yet entered in a profile.

<?php if((!$user->profile_list) AND  ($user !=  $GLOBALS["user"]))
{print
$user->name." has not yet entered a profile.";} ?>

Not all that important, but it looks better than a generic message for all users.
Please check over this code as I am not yet very experienced. So far it works for me and I thought it might be useful.

Outputting full civiCRM profile to custom profile

Assuming you have already made the template.php and user_profile.tpl.php changes recommended on the parent page of this book, the following snippet allows you to output a whole "profile" from civiCRM into a custom user profile. (The civiCRM profile elements will still obey all the visibility settings you give them...) The theming of this output is still controlled by the civiCRM stylesheets, however, so the customization it actually offers is rather minimal.

I'm a bit of a hack, so please offer suggestions if you have them!

<?php
if (module_exist('civicrm')) {
   
civicrm_initialize(TRUE);
$array = civicrm_view_data($user);
//replace Profile Name with the name you gave the profile in civiCRM
print_r ($array['Profile Name'][0]['value']);
}
?>

Outputting individual CiviCRM profile fields into custom Drupal profile

Assuming you've already modified your template.php file and created a user_profile.tpl.php file as per the suggestions on parent pages of this book, I've coded several ways to pull civiCRM fields into the custom user profile. I'm a bit of an amateur, so please comment if aspects of my coding could be enhanced and I'll happily make revisions:

Code snippet 1: This initializes the civicrm module and defines two handy functions that allow us to retrieve user and profile objects from the civiCRM database using the Drupal user id....I've really only used the first function here (which I found in the civiCRM documentation), but calling the second function during development allows you to print out all of the array keys in the user object if you need to do some digging around.

<?php
if (module_exist('civicrm')) {
   
civicrm_initialize(TRUE);

function
get_user_object($uid) {
 
civicrm_initialize(true);
 
$userID = crm_uf_get_match_id($uid);
  return
crm_get_contact(array('contact_id' => $userID));
}
function
Get_Array_Keys_UL($array=array()) {
$recursion=__FUNCTION__;
if (empty(
$array)) return '';
$out='<ul>'."\n";
foreach (
$array as $key => $elem)
  
$out .= '<li>'.$key.$recursion($elem).'</li>'."\n";
$out .= '</ul>'."\n"
return
$out;
}
$userobject = get_user_object($user->uid);
$profileobject = ($userobject->contact_type_object);
}

?>

Code snippet 2: This uses $profileobject object to return standard civiCRM field types such as first_name, last_name, and job_title

<?php print ($profileobject->first_name).' '.($profileobject->last_name).'<br>'; ?>
<?php print ($profileobject->job_title); ?>

Code snippet 3: I was having a dickens of a time getting at the custom fields I created as a part of the civiCRM user profile, since they appear to be keyed differently for each user, and they are sometimes serial lists separated by the mysterious . character .
They do each have a field_id associated with them, and you can find the field_id of each custom field you defined by poking around your CRM tables. This code iterates through all custom data values an outputs only those associated with field_id == 9. You can change this and add additional

<?php $custom_fields = $userobject->custom_values ?>
<?php
$i
= 0;
foreach (
$custom_fields as $custom_field){
    
$field_id = $custom_fields[$i]['custom_field_id'];
    
$fieldvalues = split('', $custom_fields[$i]['char_data']);
    
$i++;
     foreach (
$fieldvalues as $fieldvalue){
      
//you will need to change the field_id values below to reflect the field_ids you want
      
if ($field_id == 9){
         
//you will need to theme the fields yourself
         
print '<div class="fieldset">'.$fieldvalue."</div>";
       }
        if (
$field_id == 5){
         
//you will need to theme the fields yourself
         
print '<div class="fieldset">'.$fieldvalue."</div>";
       }
        
//place additional if clauses here for additional fields
   
}
}
//Uncommenting the line below during development will allow you to see all the arrays and objects inside the custom_values object.
//print_r ($custom_fields); 
?>

My guess this that list bit probably could be done much more elegantly. Please offer suggestions...

Insert user friendly "click here to add your.." links when user profile fields are left blank

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays a user friendly "click here to add your [profile field]" link when they are looking at their own profile page and have forgotten or not yet filled out some details.

The default settings with Drupal is to hide empty fields, so Users sometimes don't know they can add a field unless they look at the various EDIT MY ACCOUNT options.

The snippet checks to see if the person looking at the profile page is the same person or someone with ADMINISTER USERS permissions. It then checks to see if a field is not yet filled in and displays the "click here to add.." link if that's the case.

Dependencies: profile.module

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the $profilecat value in the first line to match the profile category name you are using. In the example snippet we are using "Business Info."
  • Change the $profilefieldname value in the second line to match the profile field name you are using. In the example snippet we are using "profile_work_telephone"
  • Change the $linktext value in the second line to match the profile field name you are using. In the example snippet we are using "click to add your work phone number"
  • If you are using this snippet more than once in the same user_profile.tpl.php file add a number to the end of the $profilecat, $profilefieldname and $linktext titles each time you copy and use the snippet. e.g. $profilefieldname1, $linktext1, $profilefieldname2, $linktext2, $profilefieldname3, $linktext3 etc.
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the link prefix text to suit.
<?php $profilecat = "Business Info."; ?>
<?php $profilefieldname = "profile_work_telephone"; ?>
<?php $linktext = "click to add your phone number"; ?>
<div class="fields">
<strong>Work Telephone Number:</strong>
<?php if ((user_access('administer users') || $GLOBALS['user']->uid == $user->uid) && (($user->$profilefieldname) == "")): ?>
<a href="/user/<?php print $user->uid ?>/edit/<?php print $profilecat ?>">
<?php print $linktext ?>
</a>
<?php endif; ?>
<?php print $user->$profilefieldname ?>
</div>

Display a date related notice or countdown snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays a special message, based on a date set in the user profile. It only displays for users with ADMINISTER USERS permissions or when a user is looking at his/her own user profile page.

The example below is a warning to Members that there are (x) days left before their membership expires. The same snippet maybe used for many applications.

Dependencies: profile.module must be enabled and a DATE field must be set in the user profile.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Change the name of the profile date field you are using. (Tip: go to administer -->> settings -->> profile and in the second column it will give you the field name.)
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class names or the prefix text to suit.
<?php $date_key = 'profile_expiry_date'; /* change the name to the profile date field you are using */ ?>
<?php if ((user_access('administer users')) || ($GLOBALS['user']->uid == $user->uid)): ?>
<div class="warning">
<?php $keyMonth = $user->$date_key{month}; ?>
<?php $keyDay = $user->$date_key{day}; ?>
<?php $keyYear = $user->$date_key{year}; ?>
<?php $month = date(F); $mon = date(n); $day = date(j); $year = date(Y); ?>
<?php $hours_left = (mktime(0,0,0,$keyMonth,$keyDay,$keyYear) - time())/3600; ?>
<?php $daysLeft = ceil($hours_left/24); ?>
<?php $z = (string)$daysLeft; ?>
<?php if ($z > 1): ?>
<P>There are <?php print $z ;?> days left until your membership expires with this site</p>
</div>
<?php endif; ?>
<?php endif; ?>

Send private message snippet

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet inserts the SEND A PRIVATE MESSAGE link

Dependencies: privatemsg.module must be installed and enabled.

Usage
  • For use in your user profile page override
  • Copy and paste the code into your user_profile.tpl.php file
  • Change the div class name or the link text to suit.
<?php if (user_access('access private messages') && (isset($user->privatemsg_allow) ? $user->privatemsg_allow : 1)) { ?>
<?php $frommetoprofileuser = arg(1); ?>
<div class="fields">
<?php print l(t('Send a private message to ').$user->name, 'privatemsg/msgto/'. $frommetoprofileuser); ?>
</div>
<?php } ?>

Hide link when on your own page

<?php
if (arg(0) == 'user' && is_numeric(arg(1))) {
   
$node = node_load(arg(1));
    if (
$node->uid == $user->uid) {
      echo ;
    }

    else {
  print
l('Send a message to '.$user->name, 'privatemsg/msgto/'. $user->uid);
 
   }
  }
?>

Display a Skype "Call me" or "leave a voicemail" button depending on Users' online/offline status

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Note: There is also a Skype Support module.

Description

This php snippet adds a Skype: Call me! button to their user profile page if they are online or a Leave a voicemail! message if the user is offline.

If the user has not specified their skype username, it displays nothing.

Dependencies: profile.module must be installed and enabled.

An optional extra little piece of javascript checks to see if the user viewing the profile page has skype installed and displays a friendly link explaining what it is if they don't. Click through to skype.com for what the alternate message on the skype.com site looks like and says.

Usage
  • For use in your user profile page override (user_profile.tpl.php)
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • You need to change the custom profile field name from profile_skypename used in the first 2 lines of the snippet if you call the profile field something else. (Tip: go to administer -->> settings -->> profile and in the second column it will give you the field name)
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class name or the link text to suit.
  • Click through to the custom skype buttons page at skype.com to choose alternate button images, or alternatively create your own and edit the IMG links in the snippet. (before editing, make a note of the syntax and in particular how you have to comment out quotation marks using the back slash symbol.)
  • Click for a more advanced snippet (.rar) or zip version that includes a small javascript check to see if the user has skype installed or not and displays an appropriate message if they don't. (I can't upload the advanced snippet here because of the javascript).
Notes

This is not intended as an official endorsement of the Skype webphone. There are many other webphones out there and I plan to update the snippets in future as the VOIP (Voice over IP) technology evolves and improves. Giving users more options and alternate webphone snippets.

<?php
if($user->profile_skypename) {
$time_period = variable_get('user_block_seconds_online', 2700);
$uid = arg(1); // get the current userid that is being viewed.
$users = db_query('SELECT DISTINCT(uid), MAX(timestamp) AS max_timestamp FROM {sessions} WHERE timestamp >= %d AND uid = %d GROUP BY uid ORDER BY max_timestamp DESC', time() - $time_period, $uid);
         
$total_users = db_num_rows($users);

          if (
$total_users == 1) {
          print
"<a href=\"skype:$skypename?call\"><img src=\"http://download.skype.com/share/skypebuttons/buttons/call_blue_white_124x52.png\" style=\"border: none;\" width=\"124\" height=\"52\" alt=\"Call me!\" /></a>";          
          }
          else {
           print
"<a href=\"skype:$skypename?voicemail\"><img src=\"http://download.skype.com/share/skypebuttons/buttons/voicemail_blue_white_213x52.png\" style=\"border: none;\" width=\"213\" height=\"52\" alt=\"Leave me voicemail\" /></a>";
          
          }
      }
?>
Drual 4.7
<?php if($user->profile_skypename):?>
<script type="text/javascript" src"http://download.skype.com/share/skypebuttons/js/skypeCheck.js"></script>
<a href="skype:<?php print $user->profile_skypename ?>?call"><img src="http://download.skype.com/share/skypebuttons/buttons/call_blue_white_124x52.png" alt="My status" title="Call <?php print $user->name ?> using Skype"/></a>
<?php endif ?>

Display users age based on a date-of-birth field

PLEASE NOTE! These snippets are user submitted. It is impossible to check them all, so please use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.

Description

This php snippet displays the age of the user, based on a date of birth field.

Dependencies: profile.module must be installed & enabled and a custom profile date field called profile_dob must be setup. This is the DATE OF BIRTH field that is used to dynamically determine the users age.

Tip: If you haven't setup your date-of-birth field yet, go to ADMINISTER --> SETTINGS --> PROFILES and ADD a new DATE field. Give it the name profile_dob and fill out the rest of the options as suits.

Thanks to Pepe for improving the snippet.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the snippet into your user_profile.tpl.php file
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class name or the message text to suit.
<div class="fields">
<?php
    $year_diff
= date("Y") - ($user->profile_dob{year});
   
$month_diff = date("m") - ($user->profile_dob{month});
   
$day_diff = date("d") - ($user->profile_dob{day});
    if (
$day_diff < 0 && $month_diff < 0)
     
$year_diff--;?>

<p>AGE: <?php print $year_diff; ?> </p>
</div>

insert a "view your order history" and "view [username]s store" link

description

Inserts a simple "view your order history" and "View [username]s store" link in the user profile page. Requires the store.module to be enabled and setup. The store.module is part of the ecommerce.module suite of modules.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the code into your user_profile.tpl.php file
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class name or the link text to suit.
View order history link
<?php if ($GLOBALS['user']->uid == $user->uid): /* check to see if it is the owner of the profile before displaying this link */ ?>
<div class="orders">
<?php print l(t('View your order history'), "store/history/$user->uid")
</
div>
<?
php endif; /* end of snippet */ ?>
View [username]s store link
<div class="storelink">
<?php print l('view '. $user->name. 's store', 'store/'. $user->uid); ?>
</div>

Show/hide certain profile fields depending on user role or user permissions

description

This collection of snippets allows you to show or hide specific profile fields, depending on user permissions or on user roles.

Usage
  • For use in your user profile page override
  • Using a text editor like NOTEPAD.EXE or an equivalent, copy and paste the snippet into your user_profile.tpl.php file
  • Tested and works with Drupal 4.5 and 4.6
  • Change the div class name or the link text to suit.
show content based on user permissions

User permissions are specified on the ADMINISTER --> ACCESS CONTROL page.

<?php if (user_access('administer users')): ?>
<div class="fields">CONTENT FOR ADMIN ONLY GOES HERE</div>
<?php endif; ?>
show content based on user role.

In this example snippet, the content will only be displayed to users with the super admin role type. Change the role type, DIV etc. to suit your needs.

<?php if (in_array('super admin',$GLOBALS['user']->roles)): ?>
<div class="fields">Supper Administraator</div>
<?php endif; ?>

A very different pager

While I really like the basic look and design of Drupal, one thing I never liked was the look of the pager.

The code, In My Not So Humble Opinion, is brilliant, and works extremely well. But the look? Well, it leaves a bit to be desired. And is very hard to theme. But it is doable.

Here is an alternative. (Click Here For An Example)

One note: Due to Internet Explorer's incorrect handling of padding, you may have to manually adjust the IE Hacks section for each div a pager appears in that has a different padding value. In most sites, the padding values don't change, so I don't see this as a big problem.

Note: The following code is for phptemplate themes. For themes that aren't phptemplate, you may have to figure out how to change this on your own; however, for themes that have a THEMENAME.theme, you can put this in that file and do a search and replace to change phptemplate_ to THEMENAME_. I'm not sure how to do it with xtemplate themes, offhand.

The following goes in template.php

<?php
// This is the main theme override.
// one downside is that we ignore the $tags() because they don't fit
// the way we want to display. I don't think most theme('pager')s use them.
function phptemplate_pager($tags = array(), $page_size = 10, $element = 0, $attributes = array()) {
  global
$pager_from_array, $pager_total;
 
$output = '';

 
// It's easier to calculate, to me at least, using page numbers
 
if (($page_num = (ceil(($pager_from_array[$element] + 1) / $page_size))) < 1) {
   
$page_num = 1;
  }

 
// and number of pages
 
if (($num_pages = (ceil(($pager_total[$element] + 1) / $page_size))) < 1) {
   
$num_pages = 1;
  }

 
// Display the pager.
 
if ($pager_total[$element] > $page_size) {
   
$output .= '<div class="pager-top">';
   
$output .= _pager_prev($page_size, $element, $attributes, $page_num, $num_pages);
   
$output .= _pager_next($page_size, $element, $attributes, $page_num, $num_pages);
   
$output .= _pager_page_num($page_size, $element, $attributes, $page_num, $num_pages);
   
$output .= '</div>';

    return
$output;
  }
}

// This function creates a proper pager url.
function _pager_url($page_num, $text, $element, $attributes, $page_size) {
  global
$pager_from_array;
 
$from = ($page_num - 1) * $page_size;

 
$from_new = pager_load_array($from, $element, $pager_from_array);
 
  return
'<a href="/'. pager_link($from_new, $element, $attributes) . "\">$text</a>";
}

function
_pager_page_num($page_size, $element, $attributes, $page_num, $num_pages) {
  return
"<div class='pager-middle'>" . _pager_little_prev_button($page_size, $element, $attributes, $page_num, $num_pages)
    .
"<span class='page-num'>Page $page_num</span>"
   
. _pager_little_next_button($page_size, $element, $attributes, $page_num, $num_pages)
    .
"</div>\n";

}

function
_pager_little_prev_button($page_size, $element, $attributes, $page_num, $num_pages)
{
   
// Is there a previous button to even print?
    // page < 1 == page 1.
   
if ($page_num <= 1)
        return
"";

 
$prev = $page_num - 1;
    return
_pager_url($prev, "&lt;&lt;", $element, $attributes, $page_size);
}

function
_pager_little_next_button($page_size, $element, $attributes, $page_num, $num_pages)
{
   
// Is there a previous button to even print?
    // page < 1 == page 1.
   
if ($page_num >= $num_pages)
        return
"";

 
$next = $page_num + 1;
    return
_pager_url($next, "&gt;&gt;", $element, $attributes, $page_size);
}

// function _pager_prevButton($url, $page, $page_size, $defaultPageSize, $count)
function _pager_prev($page_size, $element, $attributes, $page, $num_pages)
{
   
// Is there a previous button to even print?
    // page < 1 == page 1.
   
if ($page <= 1)
        return
"";

   
// First, let's just do the previous page.
   
$prev = $page - 1;
   
$string = _pager_url($prev, $prev, $element, $attributes, $page_size);

   
// Let's do five pages, plus page #1, and a ... if there's a blank spot.
   
for ($i = $page - 2; $i > max(1, ($page - 5)); $i--)
       
$string = _pager_url($i, $i, $element, $attributes, $page_size) . " $string";

    if (
$i != 1 && $i != 0) // if we stopped at something other than 1 there was > 5
       
$string = "... " . $string;

 
// Now see if we need to do powers of 10.
  // This is probably more complicated than it needs to be, and someone
  // clever could rewrite this -- but I got tired of fiddling with it.
   
for ($counter = 1; $counter < 5; $counter++) {
       
$pow = pow(10, $counter);
        if (
$page > 16 * pow(10, $counter - 1))
            for (
$i = floor((($page)/$pow) - 1) * $pow, $j = 0; $i > 1 && $j < 2; $i -= $pow, $j++)
               
$string = _pager_url($i, $i, $element, $attributes, $page_size) . " $string";
    }

 
// And finally, the very first page always shows up, unless we've already hit it.
   
if ($prev != 1)
       
$string = _pager_url(1, 1, $element, $attributes, $page_size) . " $string";

    return
"<div class='pager-prev'>$string</div>";
}

function
_pager_next($page_size, $element, $attributes, $page, $num_pages)
{
   
// Is there a next button to even print?
   
if ($page >= $num_pages)
        return
"";

   
// First, let's just do the previous page.
   
$next = $page + 1;
   
$string = _pager_url($next, $next, $element, $attributes, $page_size);
   
// Let's do five pages, plus page #1, and a ... if there's a blank spot.

   
for ($i = $page + 2; $i < min($num_pages, ($page + 5)); $i++)
       
$string .= " " . _pager_url($i, $i, $element, $attributes, $page_size);
    if (
$i != $num_pages && $i != $num_pages + 1) // if we stopped at something other than 1 there was > 5
       
$string .= " ...";

    for (
$counter = 1; $counter < 5; $counter++) {
       
$pow = pow(10, $counter);
        if ((
$num_pages - $page) > 11 * pow(10, $counter - 1))
            for (
$i = ceil(($page + 5)/$pow) * $pow, $j = 0; $i < $num_pages && $j < 2; $i += $pow, $j++)
               
$string .= " " . _pager_url($i, $i, $element, $attributes, $page_size);
    }
    if (
$next != $num_pages)
       
$string .= " " . _pager_url($num_pages, $num_pages, $element, $attributes, $page_size);
    return
"<div class='pager-next'>$string</div>";

}
?>

And this goes in style.css.

.pager-top {
  margin-top: 1em;
  margin-bottom: 1em;
  position: relative;
}

.pager-middle {
  text-align: center;
  vertical-align: top;
  clear: none;
  position: relative;
  width: 100%;
  font-size: .8em;
  z-index: 0;
  top: 0;
}

.pager-prev {
  text-align: left;
  position: absolute;
  top: 0;
  left: 0;
  font-size: .8em;
  z-index: 2;
}

.pager-next {
  text-align: right;
  position: absolute;
  right: 0;
  float: right;
  font-size: .8em;
  z-index: 1;
}

/* IE hacks */
* html .main-content .pager-next {
  \margin-right: 5px;
  /* You may have to adjust this manually!!! */
}

* html .main-content td .pager-next {
  \margin-right: 0px;
}


.page-num {
  margin-left: .5em;
  margin-right: .5em;
}

Add a << first < previous next > last >> Pager to Image Nodes Within a Gallery.

I spent days figuring out the code in "Adding next & previous links for node types within taxonomy terms (e.g. Image Galleries)" posting http://drupal.org/node/45050, a very useful enhancement for the Image module. In the process I learned a lot about the great Drupal features like database abstraction layer, pager, taxonomy, and functions for almost anything you can think of.

Here are my two versions of the “pager”.

=================================================

1. << first < previous [13 of 21] next > last >>

I prefer this version. It's simpler and offers more flexibility. It can be easily modified to generate a Drupal pager look-alike:

<< first < previous ... 2 3 4 5 6 7 8 9 10 ... next > last >>

I have added the following to node-image.tpl.php (a copy of node.tpl.php in your theme folder). The $page != 0 and $terms conditions ensure that the pager is printed only when the image is displayed as a standalone page and it belongs to a Gallery (has a term). The $class variable is optional, use it for CSS styling.

  <div class="pager">
    <?php if ($page != 0 && $terms) { print custom_pager($node->nid); } ?>
  </div>

I have created a custom.module for all my custom functions (PHP snippets), including the following custom_pager:

function custom_pager($current_nid, $class = NULL) {
  $tid = reset(array_keys(taxonomy_node_get_terms($current_nid)));
  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid = %s AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC, n.nid DESC'), $tid);
  while ($node = db_fetch_object($result)) {
    $nodes[++$i] = $node;
    if ($node->nid == $current_nid) $x = $i;
  }
  if($x > 1) {
    $output .= l('&laquo; first', 'node/'. $nodes[1]->nid, array('title' => check_plain($nodes[1]->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
    $output .= l('&lsaquo; previous', 'node/'. $nodes[$x-1]->nid, array('title' => check_plain($nodes[$x-1]->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
  }
  $output .= $x .' of '. $i;
  if($x < $i) {
    $output .= l('next &rsaquo;', 'node/'. $nodes[$x+1]->nid, array('title' => check_plain($nodes[$x+1]->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
    $output .= l('last &raquo;', 'node/'. $nodes[$i]->nid, array('title' => check_plain($nodes[$i]->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
  }
  return $output;
}

========================================================

2. << first < previous [Back to Gallery] next > last >>

Add this to node-image.tpl.php (note that the node is passed to the function instead of the node ID):

  <div class="pager">
    <?php if ($page != 0 && $terms) { print custom_pager($node); } ?>
  </div>

And the function:

function custom_pager($node, $class = NULL) {
  if (!isset($node->taxonomy)) {
    $tid = reset(array_keys($node->taxonomy));
  }
  else {
    $tid = reset(array_keys(taxonomy_node_get_terms($node->nid)));
  }
  $prev = db_fetch_object(db_query_range(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid = %d AND (n.sticky > %d OR (n.sticky = %d AND (n.created > %d OR (n.created = %d AND n.nid > %d)))) AND n.nid != %d AND n.status = 1 ORDER BY n.sticky ASC, n.created ASC, n.nid ASC'), $tid, $node->sticky, $node->sticky, $node->created, $node->created, $node->nid, $node->nid, 0, 1));
  $next = db_fetch_object(db_query_range(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid = %d AND (n.sticky < %d OR (n.sticky = %d AND (n.created < %d OR (n.created = %d AND n.nid < %d)))) AND n.nid != %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC, n.nid DESC'), $tid, $node->sticky, $node->sticky, $node->created, $node->created, $node->nid, $node->nid, 0, 1));
  if($prev) {
    $first = db_fetch_object(db_query_range(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid = %s AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC, n.nid DESC'), $tid, 0, 1));
    $output .= l('&laquo; first', 'node/'. $first->nid, array('title' => check_plain($first->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
    $output .= l('&lsaquo; previous', 'node/'. $prev->nid, array('title' => check_plain($prev->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
  }
  if($prev || $next) {
    $gallery = taxonomy_get_term($tid);
    $count = taxonomy_term_count_nodes($tid);
    $output .=  l(check_plain($gallery->name) .' ('. $count .')', 'image/tid/'. $tid, array('title' => check_plain($gallery->name), 'class' => $class));
  }
  if($next) {
    $last = db_fetch_object(db_query_range(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid = %s AND n.status = 1 ORDER BY n.sticky ASC, n.created ASC, n.nid ASC'), $tid, 0, 1));
    $output .= l('next &rsaquo;', 'node/'. $next->nid, array('title' => check_plain($next->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
    $output .= l('last &raquo;', 'node/'. $last->nid, array('title' => check_plain($last->title), 'class' => $class), NULL, NULL, FALSE, TRUE);
  }
  return $output;
}

http://osherl.com

Add an icon to menu links

Place this in template.php, and it'll assign a unique id to each of your menu items, allowing you to attach css (and thus icons, jquery, rollovers, etc.) to individual menu items. Very handy!

<?php
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE) {
 
$link = menu_item_link($mid);
 
$css_id = strtolower(str_replace(' ', '_', strip_tags($link)));
  return
'<li id="' . $css_id . '" class="' . ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. $link . $children ."</li>\n";
}
?>

Then attach the usual css such as

li#my_account a{
background-image:url(../mytotallysweeticons/my_account.png);
}

Thanks goes to Nick Lewis for writing the original script - this is a slightly simpler version of that.

Adding an embedded player for audio in 5.0

Noticed in drupal 5, that the correct enclosures for media files are added taking care of podcasts for RSS. By adding an embedded player in a blog, don't need any extra modules for support (like audio module). Need the Upload core module enabled and active for whatever node types will have audio.

Override theme_upload_attachments, checked the mime type for mp3, and then loaded a flash player. Could use any, odeo, XSPF - or in our case used one from google.

The override for template.php

function phptemplate_upload_attachments($files) {
  return _phptemplate_callback('upload_attachments', array('files' => $files));
}

Created a new template called upload_attachments.tpl.php with the following - embedding the google player.

<?php

  $header
= array(t('Attachment'), t('Size'));
 
$rows = array();
  foreach (
$files as $file) {
    if (
$file->list) {
     
$href = $file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()));
     
$text = $file->description ? $file->description : $file->filename;
      if (
$file->filemime == "audio/mpeg") { ?>

        <p><b>Podcast Audio: <?php print substr($text,0,-4) ?> </b></p>
<object style="border: 1px solid rgb(170, 170, 170); width: 500px; height: 25px;" type="application/x-shockwave-flash"
data="http://mail.google.com/mail/html/audio.swf?audioUrl=<?php print $href; ?>">
<param name="movie" value="http://mail.google.com/mail/html/audio.swf?audioUrl=<?php print $href; ?>" />
</object>

<?php } else {
      $rows[] = array(l($text, $href), format_size($file->filesize));
      }
}
  }
  if (count($rows)) {
    print theme('table', $header, $rows, array('id' => 'attachments'));
  }

Adding next & previous links for node types within taxonomy terms (e.g. Image Galleries)

description

The following adds a custom next_prev function to your theme template.php file that allows you to insert NEXT | PREVIOUS type links in your layout.tpl.php files. Where layout might be node.tpl.php, node-image.tpl.php etc.

In the illustrative example snippet below, a simple NEXT | PREVIOUS link is inserted into a custom node-image.tpl.php file linking to the next and previous image within that gallery (taxonomy term).

step 1 of 2

Add the custom next_prev function to your template.php file.

  1. In a text editor like notepad.exe or equivalent, create/edit a template.php file and paste the following snippet
  2. Save your new/edited template.php file and upload it to your active theme folder
<?php
function next_prev($current_nid, $type, $button_type, $label, $class) {
  
$tid = db_result(db_query(db_rewrite_sql("SELECT tid FROM {term_node} WHERE nid = $current_nid;")));
   if (empty(
$tid)){ //validate that the image 'does' have a tid
   
return '';
   }

   switch (
$button_type) {
     case
'next':
      
$sort= 'DESC';
      
$case = '< ';
       break;
     case
'prev':
      
$sort = 'ASC';
      
$case = '> ';
       break;
     case
'parent':
      
$name = db_result(db_query(db_rewrite_sql("SELECT name FROM {term_data} WHERE tid = $tid;")));
       return
l($label.$name, "$type/tid/$tid", array('title' => $name, 'class' => $class));
       break;
     case
'last':
      
$sql  = "SELECT n.nid, n.title FROM node n INNER JOIN term_node t ON n.nid = t.nid";
      
$sql .= " INNER JOIN term_data r ON t.tid = r.tid WHERE n.type = '". $type ."'";
      
$sql .= " AND r.tid = $tid AND n.status = 1 ORDER BY nid ASC LIMIT 0 , 1";
      
$result = db_fetch_array(db_query(db_rewrite_sql($sql)));
       return
l($label, 'node/'. $result['nid'], array('title' => $result['title'], 'class' => $class));
       break;
     case
'first':
      
$sql  = "SELECT n.nid, n.title FROM node n INNER JOIN term_node t ON n.nid = t.nid";
      
$sql .= " INNER JOIN term_data r ON t.tid = r.tid WHERE n.type = '" .$type. "'";
      
$sql .= " AND r.tid = $tid AND n.status = 1 ORDER BY nid DESC LIMIT 0 , 1";
      
$result = db_fetch_array(db_query(db_rewrite_sql($sql)));
       return
l($label, 'node/'. $result['nid'], array('title' => $result['title'], 'class' => $class));
       break;
     default:
       return
NULL;
       break;
   }
  
$sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid ";
  
$sql .= "INNER JOIN {term_data} r ON t.tid = r.tid WHERE n.type = '". $type ."' AND n.nid ". $case;
  
$sql .= $current_nid ." AND r.tid = ". $tid ." AND n.status = 1 ORDER BY nid ". $sort;
  
$result = db_fetch_array(db_query(db_rewrite_sql($sql)));
   if (!
$result) {
     return
NULL;
     return
l($label.$name, "$type/tid/$tid", array('title' => $name, 'class' => $class));
   } else {
     return
l($label, 'node/'. $result['nid'], array('title' => $result['title'], 'class' => $class));
   }
}
?>

step 2 of 2

Insert this snippet in your custom layout.tpl.php file and edit the $node->type to suit. This example snippet is intended for a custom node-image.tpl.php file. It inserts a link to the NEXT and PREVIOUS image within that image gallery (taxonomy term).

<?php
 
if ($terms && arg(0) == 'node' && is_null(arg(2))) {
     
$next = next_prev($node->nid, 'image', 'next', 'next>', 'link'); // replace 'test' with a valid class for the link
     
$previous = next_prev($node->nid, 'image', 'prev', '<previous', 'link');
     
$last = next_prev($node->nid, 'image', 'last', 'last>>', 'link');
     
$first = next_prev($node->nid, 'image', 'first', '<<first', 'link');
     
$gallery = next_prev($node->nid, 'image', 'parent', '', 'link');
      print
'<p><center>';
      if (
$previous){ print $first .'&nbsp;&nbsp;'. $previous;}
      print
'&nbsp;&nbsp;['. $gallery .']&nbsp;&nbsp;';
      if (
$next){ print $next .'&nbsp;&nbsp;' .$last;}
      print
'</center></p>';
    }
?>

notes

  • once added to your template.php file, the next_prev function maybe called from any custom layout.tpl.php file.
  • Please add a comment/child page with examples of using the custom next_prev function in your layout.tpl.php files.

For Acidfree images

Here is the code to add next, previous buttons to acidfree images. The images are ordered according to weightings. Place the code in template.php in your theme directory. (I know, I know, i'm supposed to use joins...). It works for both images and videos in acid free.

<?php
function next_prev_acidfree($current_nid, $label, $class) {
   
# get its parent
   
$query = db_query("SELECT parent FROM `acidfree_hierarchy` WHERE child = $current_nid;");
   
$parent = db_result($query);
   
     
# root gallery
   
if ($parent == -1) {
         
$arr ['parent'] = l("Photo Gallery", 'acidfree', array("title" => $label, "class" => $class));
      }else {
         
# Get the parent
         
$query = db_query("SELECT title FROM `node` WHERE nid = $parent;");
         
$parent_title = db_result($query);
         
$arr ['parent'] = l($parent_title, 'node/'.$parent, array("title" => $label, "class" => $class));
      }
           
   
# Get all the other photos videos that have the same parent
   
      //$sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid ";
     
$sql = "SELECT a.aid, n.title, nid FROM {node} n, {acidfree} a, {acidfree_hierarchy} ah ".
             
" WHERE n.type='acidfree' AND n.nid = a.aid AND (a.class='photo' || a.class='video')".
             
" AND ah.parent = $parent AND ah.child=a.aid ".
             
" ORDER BY a.weight ASC, n.nid DESC";
     
     
     
$result = db_query($sql);

      if (!
db_num_rows($result)) {
       
$arr ['next'] = null;
       
$arr ['previous'] = null;
      } else {
          while (
$row = db_fetch_array($result)) {
              if (
$row['nid'] == $current_nid && $before) {
                 
$arr ['previous'] = l("< < Previous", "node/".$before['nid'], array("title" => $label, "class" => $class));
              }
              if (
$before['nid'] == $current_nid) {
                 
$arr ['next'] = l("Next > >", "node/".$row['nid'], array("title" => $label, "class" => $class));
              }
             
             
$before = $row;
          }
      }
      return
$arr;
}

function
phptemplate_display_next_previous($node) {
   
$arr = next_prev_acidfree($node->nid, 'Navigation', 'test');
     
     
//echo $previous . " | " . $next ."";
   
$output .= "<table class='acidfree-next-previous'>";
   
$output .= "<tr>";
   
$output .= "<td class='acidfree-previous'>".$arr['previous']."</td>";
   
$output .= "<td class='acidfree-parent'>".$arr['parent']."</td>";
   
$output .= "<td class='acidfree-next'>".$arr['next']."</td>";
   
$output . "</tr>";
   
$output .= "</table>";
    return
$output;
}

function
phptemplate_acidfree_print_full_photo(&$node) {
    if (!
$node->small)
        return
'';
   
$output .= acidfree_pager_creator($node);

   
$output .= phptemplate_display_next_previous($node);
   
$output .= "<div class='acidfree-full'>";
   
$largeurl = _acidfree_get_large_url($node);
    if (!
$largeurl) {
       
$output .= theme('image', _acidfree_get_small_url($node),
               
$node->title, $node->title, NULL, false);
    } else {
       
$output .= "<a href='"._acidfree_get_large_url($node)."'>".
           
theme('image', _acidfree_get_small_url($node),
                   
$node->title, $node->title, NULL, false).
           
"</a>";
    }
   
$output .= "</div><div class='acidfree-body'>{$node->body}</div>";
    if (
variable_get('acidfree_show_exif_data', false)) {
       
$output .= theme('acidfree_exif_data', $node);
    }

    return
$output;
}

function
phptemplate_acidfree_print_full_video(&$node) {
    if (!
$node->large)
        return
'';

   
// get the pager
   
$output = acidfree_pager_creator($node);
   
$output .= phptemplate_display_next_previous($node);

   
$id = $node->nid;
   
$name = $node->title;

   
// icon link to the full size video
   
$full_video = _acidfree_get_large_url($node);
   
$output .= "<a href='$full_video'>".t('download video').'</a><br/>';

   
$ext = pathinfo(_acidfree_get_large_path($node), PATHINFO_EXTENSION);
    switch (
$ext) {
    case
'rm':
       
$type = 'realmedia';
        break;
    case
'mp4':
    case
'mov':
       
$type = 'quicktime';
        break;
    default:
       
$type = 'windowsmedia';
        break;
    }
   
$finfo = image_get_info(_acidfree_get_small_path($node, true));
   
$node->videoy = $finfo['height'];
   
$node->videox = $finfo['width'];
   
$output .= acidfree_call("theme_video_{$type}_control",Array(&$node));
   
$output .= "<div class='acidfree-body'>{$node->body}</div>\n";
    return
$output;
}
?>

Next and Previous link ordered desc creation time

See my approach: I wanted to order nodes like taxonomy does (descending creation time):

<?php
 
function next_prev($current_nid, $created, $type, $btn_type, $label, $class) {
    switch (
$btn_type) {
      case
'next':
       
$sort = 'DESC';
       
$sign = '<';
        break;
      case
'prev':
       
$sort = 'ASC';
       
$sign .= '>';
        break;
      default:
        return
NULL;
        break;
    }
      
   
$sql = '
      SELECT
        tid
      FROM
        {term_node}
      WHERE
        nid = %d'
;
       
   
$tid = db_result(db_query($sql, $current_nid));
 
   
$sql = "
      SELECT
        n.nid,
        n.title
      FROM
        {node} n
        INNER JOIN {term_node} t
        ON n.nid = t.nid
        INNER JOIN {term_data} r
        ON t.tid = r.tid
      WHERE
        n.type = '%s'
        AND ((n.created $sign %d) or (n.created = %d and n.nid $sign %d))
        AND r.tid = %d
        AND n.status = 1
      ORDER BY
        n.created $sort,
        n.nid $sort"
;
       
   
$result = db_fetch_array(db_query($sql, $type, $created, $created, $current_nid, $tid));
    if (!
$result) {
     
$sql = '
        SELECT
          name
        FROM
          {term_data}
        WHERE
          tid = %d'
;
       
     
$name = db_result(db_query($sql, $tid));
     
      return
l(sprintf(t('Back to %s'), $name), "taxonomy/term/$tid", array('title' => $name, 'class' => $class));
    }
    else {
      return
l($label, 'node/' . $result['nid'], array('title' => $label, 'class' => $class));
    }
  }
?>

and:

<?php
  $types
= array('story'); 
  if(
$page != 0 && in_array($node->type, $types)) {
   
$next = next_prev($node->nid, $node->created, $node->type, 'next', t('Next in this category'), 'node');
   
$previous = next_prev($node->nid, $node->created, $node->type, 'prev', t('Previous in this category'), 'node');
    echo
"<div class=\"center\">$previous | $next</div>";
  }
?>

Next/previous ordered by title

I myself order my pictures by title. And added next and prev links. Also, when I get to the last picture, I made some modification so that next will point to the first picture in the album.

What I did is change the template.php and node.tpl.php files in my template folder. My version is similar to that of some earlier posts in this thread, but nevertheless I copy it here for you :

in template.php you need to have the following function :

function next_prev($current_nid, $type, $btn_type, $label, $class) {
  $query = db_query("SELECT tid FROM {term_node} WHERE nid = $current_nid;");
  $tid = db_result($query);
  $query = db_query("SELECT title FROM {node} WHERE nid = $current_nid");
  $current_title = db_result($query);
  $sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid ";
  $sql .= "INNER JOIN {term_data} r ON t.tid = r.tid WHERE n.type = '".$type."' AND n.title ";
  switch ($btn_type) {
        case "next":
                $sql .= "> ";
                $sort = "ASC";
                break;
        case "prev":
                $sql .= "< ";
                $sort = "DESC";
                break;
        case "parent":
$query = db_query("SELECT name FROM {term_data} WHERE tid = $tid;");
        $name = db_result($query);
    return l($label.$name, "$type/tid/$tid", array("title" => $name, "class" => $class));
    default:
                return NULL;
                break;
   }
// the trick here is to sort by title (ORDER BY n.title)
  $sql .= "'".$current_title ."' AND r.tid = ". $tid ." AND n.status = 1 ORDER BY n.title $sort;";
  $query = db_query($sql);
  $result = db_fetch_array($query);
if (!$result)
{
  // here I changed so that instead of "back to album" link for first
// and last pictures I link to the last and first pictures respectively
     $sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid ";
        $sql.="INNER JOIN {term_data} r ON t.tid = r.tid WHERE n.type = '" . $type;
      $sql.="' AND r.tid = " . $tid . " AND n.status = 1 ORDER BY n.title $sort;";
  $query = db_query($sql);
  $result = db_fetch_array($query);
return l($label, "node/".$result['nid'], array("title" => $label, "class" => $class));
}
else {
    return l($label, "node/".$result['nid'], array("title" => $label, "class" => $class));
  }
}

in node.tpl.php : it took me quite a while to fix the first and next links to have them look nicer, but the way to do it for your theme might be different. My theme is Slash green :

so, here is my node.tpl.php file :

<!-- start node -->
<div class="node<?php print ($sticky) ? ' sticky' : ''; ?>">
<div class="title1"><div class="title2">
<?php if ($page == 0): ?>
<h1 class="title"><a href="<?php print $node_url ?>"><?php print $title ?></a></h1>
<?php else: ?>
<h1 class="title"><?php print $title ?></h1>
<?php endif; ?>
</div></div>

<?php if ($terms): ?>
<div class="terms">
<?php
 
print $terms ?>
</div>
<?php endif; ?>
<div class="info"><?php print $submitted ?></div>
<?php print $picture ?>
<div class="content">
<?php echo $content; ?>
</div>
  <?php if ($type == 'image'): ?>
   <div class="more">
  <?php
$next
= next_prev($node->nid, 'image', 'next', 'Sekva>>', 'test');
 
$previous = next_prev($node->nid, 'image', 'prev', '<<Malsekva', 'test');
 
$last = next_prev($node->nid, 'image', 'Lasta', '>|', 'test');
 
$first = next_prev($node->nid, 'image', 'Unua', '|<', 'test');
 
$gallery = next_prev($node->nid, 'image', 'parent', '', 'test');
  echo
"<center><b>";
  echo
"(" . $first."&nbsp;&nbsp;";
  if (
$previous){ echo $previous."  ";}
  echo
"&nbsp;&nbsp;[ ".$gallery." ]&nbsp;&nbsp;";
  if (
$next){ echo "  ".$next;}
  echo
"&nbsp;&nbsp;".$last.")</b><br></<center>";
?>

<?php if ($links): ?>
<?php print $links ?>
<?php endif; ?>
</div><?php endif; ?>
<?php if ($links and ($type!='image')): ?>
<div class="more"><?php print $links ?></div>
<?php endif; ?>
</div>
</div>

of course change "sekva", "malsekva", etc by the translations for your language. My site is in Esperanto.

Next/Previous with images by date

When you display all the images in a gallery they are showed sorted by date, the newest is first. This is also how the prev/next buttons should take you.

In most cases, for new galleries, sorting by date or nid probably will give the same result since you get increasing dates and increasing node ids as you keep adding images.

If you edit an image and if you change the date (Authored on) the you will start running into problems. Here is the code I used to properly navigate by date (and this code also fixes the DESC/ASC sort order):

<?php
function next_prev($current_nid, $type, $btn_type, $label, $class) {
  
$query = db_query("SELECT tid FROM {term_node} WHERE nid = $current_nid;");
  
$tid = db_result($query);
  
$query = db_query("SELECT created FROM {node} WHERE nid = $current_nid");
  
$current_created = db_result($query);
  
$sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid ";
  
$sql .= "INNER JOIN {term_data} r ON t.tid = r.tid WHERE n.type = '".$type."' AND n.created ";
   switch (
$btn_type) {
        case
"next":
               
$sql .= "< ";
               
$sort = "DESC";
                break;
        case
"prev":
               
$sql .= "> ";
               
$sort = "ASC";
                break;
        default:
                return
NULL;
                break;
   }
  
$sql .= $current_created ." AND r.tid = ". $tid ." AND n.status = 1 ORDER BY n.created $sort;";
  
$query = db_query($sql);
  
$result = db_fetch_array($query);
   if (!
$result) {
       
$query = db_query("SELECT name FROM {term_data} WHERE tid = $tid;");
       
$name = db_result($query);
     return
l("Back To $name", "$type/tid/$tid", array("title" => $name, "class" => $class));
   } else {
     return
l($label, "node/".$result['nid'], array("title" => $label, "class" => $class));
   }
}
?>

Simplified for just next/prev

I only needed next | prev so I simplified the code a bit and added append prepend text support. This goes to template.php. I also had do fix the first SQL query.

<?php
 
function next_prev($current_nid, $type, $button_type, $label, $prepend_text=NULL, $append_text=NULL) {
  
$tid = db_result(db_query(db_rewrite_sql("SELECT tid FROM {term_node} tn INNER JOIN {node} n ON tn.nid = n.nid WHERE tn.nid = $current_nid")));
   switch (
$button_type) {
     case
'next':
      
$sort= 'DESC';
      
$case = '< ';
       break;
     case
'prev':
      
$sort = 'ASC';
      
$case = '> ';
       break;
     default:
       return
NULL;
       break;
   }
  
$sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid ";
  
$sql .= "INNER JOIN {term_data} r ON t.tid = r.tid WHERE n.type = '". $type ."' AND n.nid ". $case;
  
$sql .= $current_nid ." AND r.tid = ". $tid ." AND n.status = 1 ORDER BY nid ". $sort;
  
$result = db_fetch_array(db_query(db_rewrite_sql($sql)));
   if (!
$result) {
     return
NULL;
   } else {
     return
$prepend_text.l($label, 'node/'. $result['nid'], array('title' => $result['title'])).$append_text;
   }
}
?>

In node.tpl.php I put this
<?php
if($page!=0&& $node->type=='image')
    {
       
$previous_node_link = next_prev($node->nid, $node->type, 'prev', t('previous'), '&lt;&lt; ', NULL);
       
$next_node_link = next_prev($node->nid, $node->type, 'next', t('next'), NULL, ' &gt;&gt;');

        print
'<div class="previous-next-links">';
        if(
$previous_node_link && $next_node_link)
        {
            print
$previous_node_link.' | '.$next_node_link;
        }
        else if(
$previous_node_link)
        {
            print
$previous_node_link;
        }
        else if(
$next_node_link)
        {
            print
$next_node_link;
        }
        print
'</div>';
    }
?>

Adding next & previous links for same node type, translation-aware

description

This snippet adds a custom next_prev_same_type function to your theme template.php.
This allows you to insert Next and Previous links in your *.tpl.php file.

This function selects previous/next items depending on the time the node was created with a single query.
Only published nodes of the same node type like the current node are selected.

If the next/previous node id retrieved from the database is one of the current nodes' translations,
next_prev_same_type runs again to skip that node. In this case, there will be a new sql query.

Step 1 of 2

Insert this code into your template.php

<?php
function next_prev_same_type($nid=null, $path=null, $direction=null)
{
  if(
$nid){
   
// gather some node information
   
$node = node_load($nid);
   
$type = $node->type;
   
$created = $node->created;
   
   
// retrieve all node ids of this nodes' translations
   
$translation_ids = array();
    foreach(
$node->translation as $translation) {
      if(
$translation->status == 1){
       
$translation_ids[$key] = $translation->nid;
      }
    }
   
   
// switch some vars depending on direction
   
switch ($direction){
      case
'next':
       
$modifier = ">";
       
$order = "ASC";
       
$link_str = "Next project ›";
        break;
      case
'prev':
      default:
       
$modifier = "<";
       
$order = "DESC";
       
$link_str = "‹ Previous project";
    }
   
   
// get next / prev element from database
   
$query = db_query('select nid from {node} where type=\'%s\' AND status=1 AND nid!=%d AND created'.$modifier.'%d ORDER BY created %s, nid %s Limit 1',$type,$nid,$created,$order,$order);
    while (
$item = db_fetch_object($query)){
      if(
$item->nid!=$nid && !in_array($item->nid,$translation_ids)){
       
$nav_nid = $item->nid;
      }else{
       
// skip element, if it is a translation of the current node
       
return next_prev_same_type($item->nid,$path,$direction);
      }
    }
    if(
$nav_nid){
      return
l(t($link_str),$path."/".$nav_nid);;
    }
  }
}
?>

Step 2 of 2

Insert the code below somewhere in your *.tpl.php file (node.tpl.php, view_xyz.tpl.php, ...)

<?php
  $next
= next_prev_same_type($node->nid, "your/path/here", "next");
 
$prev = next_prev_same_type($node->nid, "yout/path/here", "prev");
  if(
$prev!= FALSE){
   
$links[] = $prev;
  }
  if(
$next!= FALSE){
   
$links[] = $next;
  }
  print
theme_links($links, $delimiter = ' ')
?>

OR

<?php
 
print next_prev_same_type($node->nid, "your/path/here", "next");
  print
next_prev_same_type($node->nid, "yout/path/here", "prev");
?>

Format as desired.

function variables

next_prev_same_type($nid=null, $path=null, $direction=null)
$nid is the current node
$path is the path to be used in the link
$direction is either 'next' or 'prev'

Notes

  • Once the function is placed in your template.php, it can be run on any page, template, block, ...
  • Add your comments below. Any ideas or modifications are welcome.

Adding stylesheets for alternate media

If you want to add media-specific stylesheets (e.g. any of those listed at http://www.w3.org/TR/REC-CSS2/media.html), use the following snippet in your page.tpl.php file to properly add them using Drupal theme functions.

<head>
  <title><?php print $head_title ?></title>

  <?php print $head ?>

  <?php
   
print theme('stylesheet_import', base_path() . path_to_theme() . '/css/screen.css', 'screen'); // FOR COMPUTER SCREENS
   
print theme('stylesheet_import', base_path() . path_to_theme() . '/css/print.css', 'print'); // FOR PRINTING
   
print theme('stylesheet_import', base_path() . path_to_theme() . '/css/handheld.css', 'handheld'); // FOR HANDHELD DEVICES
 
?>

 
  <?php print $styles ?>
 
</head>

Block Visibility And Parent Term CSS Class Function

Description

This function was designed to accomplish two purposes. 1) to allow block visibility only on nodes who have a root term that matches function input. 2) to allow for css class output to be used to theme different portions of the site. This function has many uses beyond those two but those were my original intent.

Step 1 of 2

copy the below code into your template.php. You should be using this only on phptemplate themes. If you don't know if you have a phptemplate theme look inside your current themese directory. If any of the files end with .tpl.php you are using a phptemplate theme.
You can optionally change
<?php function phptemplate_show_menu($match) ?>
to
<?php function <yourtemplatesname>_show_menu($match) ?>
if you want this function to only work with your current theme.

<?php

/**
* 02/18/07
* Created by: Matthew Pare - Pare Technologies
* <a href="http://www.paretech.com
" title="http://www.paretech.com
" rel="nofollow">http://www.paretech.com
</a> *
* This function was designed to accomplish two purposes. 1) to allow block
* visibility only on nodes who have a root term that matches function input.
* 2) to allow for css class output to be used to theme different portions of
* the site. This function has many uses beyond those two but those were my
* original intent.
*
* You may freely use, distripute and edit as desired
*
* Originally posted at <a href="http://drupal.org/node/120419
" title="http://drupal.org/node/120419
" rel="nofollow">http://drupal.org/node/120419
</a> */
function phptemplate_show_menu($match) {
 
// Checks to make sure we are viewing a node
 
if ( arg(0) == 'node' && is_numeric(arg(1)) && !$in_preview ) {
   
$node = node_load(arg(1)); // is cached
    // fetches the taxonomy array from the node object
   
foreach ($node->taxonomy as $tax) {
     
// retrieves all the term id's from the taxonomy array and gets the parent
     
foreach (taxonomy_get_parents_all($tax->tid) as $parent) {
       
//stores the term names only as an array
       
$vocabs[] = check_plain($parent->name);
      }
   
// checks to see if node is assigned to the desired term and returns true
   
if ($match && in_array($match, $vocabs)) {
      return
TRUE;
    } elseif (!
$match) {
       
// if you choose not to provide an input a css class attribute is the output
       
$cssclass = strtolower(implode(" ", (str_replace(' ', '', $vocabs))));
        return
$cssclass;
      }
    }
  }
}

?>

Usage 1 of 2

If you are wanting this snippet to control block visibility, customize your block. Go down to "Page specific visibility settings" and select "Show if the following PHP code returns TRUE (PHP-mode, experts only)."
Then, in the "pages:" text field enter
<?php phptemplate_show_menu(<yourdesiredtermtomatch>) ?>
or
<?php <yourtemplatesname>_show_menu(<yourdesiredtermtomatch>) ?>
if you chose to go that route.
Make sure you change , without < >, to be the name of your term you wish the block to appear on.

Usage 2 of 2

You can alternatively put the function directly in your theme. When you do this enter
<?php phptemplate_show_menu() ?>
or
<?php <yourtemplatesname>_show_menu() ?>

When nothing is in () the function will then pull all the terms in your heirarchy, seperated by a space.
As an example, say that the node you are viewing is assigned to term2, term2 is a child term of term1. Using the function with nothing in the () would return term2 term1.
This then allows you to hook on css with .term2.term1 {} to apply your styling.

Notes

  • This snippet was built and tested on Drupal 5.x, though it may work in previous versions. Please comment it if does
  • This function is not limited to the above tasks, many other options and usage exist
  • If you find errors please let me know and I will correct

Breadcrumbs including title

Perhaps your theme requires the breadcrumb div to always exist, even if it is empty.

<?php if ($breadcrumb && $breadcrumb != '<div class="breadcrumb"></div>'): ?>
  <?php print $breadcrumb; ?>
<?php else: ?>
  <div class="breadcrumb">&nbsp;</div>
<?php endif; ?>

Or, you may want to print the current title of the page after the breadcrumb links.

In template.php:

function mytheme_breadcrumb($breadcrumb) {
  $sep = ' &gt; ';
  if (count($breadcrumb) > 0) {
    return implode($breadcrumb, $sep) . $sep;
  }
  else {
    return t("Home");
  }
}

In page.tpl.php:

<div class="breadcrumb"><?php print $breadcrumb . $title; ?></div>

Change submit buttons to images for a given form

This was originally posted in a support thread by Borek, but I believe it needs to be posted here as well. This works perfectly in Drupal 5.x.

I have a form function in my module that looks like this:

<?php
/**
* Contribution Form
*/
function ngplinks_form_contribute() {
 
$form = array();

 
$form['amount'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Amount'),
   
'#default_value' => '',
   
'#size' => 7,
   
'#maxlength' => 256,
   
'#description' => NULL,
   
'#attributes' => NULL,
   
'#required' => TRUE,
  );
  if (
variable_get('ngplinks_subscribe_inline', 1)) {
   
$form['amount']['#title'] = '';
   
$form['amount']['#default_value'] = t('Amount');
   
$form['amount']['#attributes'] = array('onclick' => "if(this.value=='". t('Amount') ."'){this.value='';}");
  }
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Contribute'),
  );

  return
$form;
}
?>

Pretty simple form with a single field and a submit button. Now to change the submit button to an image, I created the following function in my template.php file (should be in your theme's folder, create it if you don't have one):

<?php
function phptemplate_ngplinks_form_contribute($form) {
 
$form['submit']['#theme'] = 'button';
 
$form['submit']['#button_type'] = 'image';
 
$form['submit']['#attributes'] = array(
   
'src' => base_path() . path_to_theme() . '/images/joanfitzgerald_actionbar_contribute.gif',
   
'alt' => t(Search)
  );
  return
drupal_render($form);
}
?>

You should have one of those for each of the forms you wish to alter. Them make sure this generic theme function is also added to template.php to handle the theming of the buttons:

<?php
function phptemplate_button($element) {
 
// following lines are copied directly from form.inc core file:
  // Make sure not to overwrite classes
 
if (isset($element['#attributes']['class'])) {
   
$element['#attributes']['class'] = 'form-'. $element['#button_type'] .' '. $element['#attributes']['class'];
  }
  else {
   
$element['#attributes']['class'] = 'form-'. $element['#button_type'];
  }
 
// My change is type="' . (($element['#button_type'] == "image") ? 'image' : 'submit' ) . '"
 
return '<input type="' . (($element['#button_type'] == "image") ? 'image' : 'submit' ) . '" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ')  .'id="'. $element['#id'].'" value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
}
?>

That's all there is to it. Your form will now show your images instead of the default buttons and they will work as expected. Thanks to Borek, kingandy, and simmerz for this incredibly useful tip.

Changing theme based on http headers

A simple way to alter your theme based on HTTP headers can be accomplished using the following code:

<?php
  $headers
= drupal_set_header();
  if (
strstr($headers, 'HTTP/1.0 404 Not Found')) {
   
// do something, output something, change template file, etc...
 
}
?>

Works great and works with all other headers. Great for having a completely different layout/theme for 404 pages.

Create and display a friendly alias instead of users username.

Note from the moderator: Thank you for the changes. You probably forgot the output in the else if ($object->name) clause; it still lacks output checks. Best use l() for the link to the homepage and check_plain for the $object->name. Thank you for your work.

This code was taken from the forum which I believe was also taken from somewhere else. I have made some fixes that allow it to work with comments and forums. Please claim responsibility if the original code was yours.

This snippet allows you to display user friendly alias names on posts and comments etc. This comes in useful if your using the Webserver_auth module and don't want to see webserver usernames on your site. It allows the users to set an alias in their profile which is then displayed on the site. If the profile field isn't set then it will use the users username.

You will need to enable the profile module and add a single line text field to your users profiles. You can call it anything you want but you will need to alter the code as appropriate. For this example I called my field 'profile_fullname'.

In your template.php file you will need

<?php
function phptemplate_username($object) {
  if (
$object->in_preview) {
      return
theme_username($object);
  }
  return
_phptemplate_callback('username', array('object' => $object));
}
?>

Create a file called 'username.tpl.php' with these contents

<?php
if ($object->uid && $object->name) {
 
// If the user has a full name defined, use that
 
$account = user_load(array(uid => $object->uid));
 
$profilename = $object->name;
  if (!empty(
$account->profile_fullname)) {
   
$profilename = $account->profile_fullname;
  }
 
// Shorten the name when it is too long or it will break many tables.
 
if (drupal_strlen($profilename) > 20) {
   
$name = drupal_substr($profilename, 0, 15) .'...';
  }
  else {
   
$name = $profilename;
  }
  if (
user_access('access user profiles')) {
   
$output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.')));
  }
  else {
   
$output = check_plain($name);
  }
}
else if (
$object->name) {
 
// Sometimes modules display content composed by people who are
  // not registered members of the site (e.g. mailing list or news
  // aggregator modules). This clause enables modules to display
  // the true author of the content.
 
if ($object->homepage) {
   
$output = l($object->name, $object->homepage, array('title' => t('View link')));
  }
  else {
   
$output = check_plain($object->name);
  }
 
$output .= ' ('. t('not verified') .')';
}
else {
 
$output = variable_get('anonymous', 'Anonymous');
}
print
$output;
?>

Create more readable (and portable) block id attributes based on the

Going crazy with a style sheet full of impossible to remember #ids? How about trying to share or reuse styles between sites where the id's aren't the same? We can use the "Block title" field to create friendlier
id names like #address instead of #block-block-24.

block.tpl.php

<?php
// Create css id attribute based on the 'Block title' field if available.
if (!empty($block->subject)) {
 
// Make lowercase.
 
$blockid = strtolower($block->subject);
 
// Convert special characters to dashes.
 
$blockid = preg_replace("/([^a-z0-9])/", "-", $blockid);
}
else  {
   
// If no "Block title", create css id attribute the robotic way.
   
$blockid = "block-$block->module-$block->delta";
}
?>

<div class="block<?php print $block->module ? ' block-'.$block->module : '' ?>" id="<?php print $blockid ?>">
  <h2><?php print $block->subject ?></h2>
  <div class="content"><?php print $block->content ?></div>
</div>

The regex and string replacements do a basic job of enforcing legal CSS names. If you're disciplined, you remove them altogether and save some overhead, but this would really limit your block titles assuming you ever display them.

One small thing to should remember when naming your blocks is that in CSS ids can't start with a number — something this particular snippet doesn't account for.

Custom login

This creates a little custom login area. If you are logged in, it displays your username and a link to your profile, otherwise it includes a Register and minimal Login area. It was originally developed for OurMedia.

Put this code into the page.tpl.php file:

Drupal 5

Thanks, BooDy.

<?php  global $user; ?>
<?php if ($user->uid) : ?>
  <span class="login_text"><b>Logged in as: </span> <?php print l($user->name,'user/'.$user->uid); ?> |
  <?php print l("logout","logout"); ?>
    <?php else : ?>
<form action="user/login" method="post" id="user-login">
      <span class="login_text">Username:</span><span class="form-required" title="This field is required.">*</span>
      <input type="text" maxlength="60" name="name" id="edit-name"  size="5" value="" tabindex="1" class="form-text required" /></label>
      <label for="edit-pass"><span class="login_text">Password:</span><span class="form-required" title="This field is required.">*</span>
      <input type="password" maxlength="" name="pass" id="edit-pass"  size="5"  tabindex="2" class="form-text required" /></label>
      <input type="hidden" name="form_id" id="edit-user-login" value="user_login"  />
     <input type="submit" name="op" value="Log in"  tabindex="3" id="edit-submit" class="form-submit" /></form>
      <?php endif; ?>

Drupal 4.7

<?php  global $user; ?>
<?php if ($user->uid) : ?>
  <span class="login_text"><br><br><b>Logged in as: </span> <?php print l($user->name,'user/'.$user->uid); ?> |
<?php print l("logout","logout"); ?>
  <?php else : ?>
  <span class="login_text"><b>Login:</b> </span><form
action="user/login" method="post">
<div><div class="form-item">
<label for="edit-name"><span class="login_text">Username:</span><span class="form-required" title="This field is required.">*</span>
<input type="text" maxlength="60" name="edit[name]" id="edit-name"  size="10" value="" tabindex="1" class="form-text required" /></label>
<label for="edit-pass"><span class="login_text">Password:</span><span class="form-required" title="This field is required.">*</span>
<input type="password" maxlength="" name="edit[pass]" id="edit-pass"  size="10"  tabindex="2" class="form-text required" /></label>
<input type="hidden" name="edit[form_id]" id="edit-form_id" value="user_login"  />
<a href="/community/user/register">Register</a>&nbsp;&nbsp;<input type="submit" name="op" value="Log in"  tabindex="3" class="form-submit" /></form>
<?php endif; ?>
</div>

A variation that goes to the page the user is currently viewing.

  <div id="header-login">
    <?php  global $user; ?>
    <?php if ($user->uid) : ?>
    <?php print(t('Logged in as:'))?> <?php print l($user->name,'user/'.$user->uid); ?> |
    <?php print l((t('log out')),"logout"); ?>
      <?php else : ?>
    <form action="<?php print "{$base_path}user/login/?".drupal_get_destination();?>" method="post"><input type="hidden" name="edit[destination]" value="user" />
      <?php print l((t('Register')),"user/register"); ?> | <?php print(t('Login:'))?>
    <input type="text" maxlength="64" class="form-text" name="edit[name]" id="edit-name" size="15" value="" />
    <input type="password" class="form-password" maxlength="64" name="edit[pass]" id="edit-pass" size="15" value=""/>
    <input type="hidden" name="edit[form_id]" id="edit-form_id" value="user_login"  />
    <input type="submit" name="op" class="form-submit" value="<?php print(t('Login'))?>" />
    </form>
    <?php endif; ?>
  </div>

Drupal 4.6

<?php  global $user; ?>
<?php if ($user->uid) : ?>
  Logged in as: <?php print l($user->name,'user/'.$user->uid); ?> |
<?php print l("logout","logout"); ?>
  <?php else : ?>
  <?php print l("Register","user/register"); ?> | Login: <form
action="user/login" method="post"><input type="hidden"
name="edit[destination]" value="user" /><input type="text"
maxlength="64" class="form-text" name="edit[name]" id="edit-name"
size="15" value="" /><input type="password" class="form-password"
maxlength="64" name="edit[pass]" id="edit-pass" size="15" value=""
/><input type="submit"  name="op" value="Log in"  /></form>
  <?php endif; ?>

Custom login script


<?php  global $user; ?>
<?php if ($user->uid) : ?>
  <span class="login_text"><br><br><b>Logged in as: </span> <?php print l($user->name,'user/'.$user->uid); ?> |
<?php print l("logout","logout"); ?>
  <?php else : ?>
  <span class="login_text"><b>Login:</b> </span><form
action="user/login" method="post">
<div><div class="form-item">
<label for="edit-name"><span class="login_text">Username:</span><span class="form-required" title="This field is required.">*</span>
<input type="text" maxlength="60" name="edit[name]" id="edit-name"  size="10" value="" tabindex="1" class="form-text required" /></label>
<label for="edit-pass"><span class="login_text">Password:</span><span class="form-required" title="This field is required.">*</span>
<input type="password" maxlength="" name="edit[pass]" id="edit-pass"  size="10"  tabindex="2" class="form-text required" /></label>
<input type="hidden" name="edit[form_id]" id="edit-form_id" value="user_login"  />
<a href="/community/user/register">Register</a>&nbsp;&nbsp;<input type="submit" name="op" value="Log in"  tabindex="3" class="form-submit" /></form>
<?php endif; ?>
</div>

Custom search box for a specific content type

I wanted to make a custom search box on my site that would only search a single content type, with results identical to if the user had done an advanced search and selected one content type only. The code below works perfectly for me. Replace "your_content_type" with the content type to which you'd like to restrict the search.

<form action="/search/node" method="post" id="search-form" class="search-form">
<div class="form-item">
<input type="text" class="input-text" value="" size="25" name="keys" />
<input type="submit" value="Search" name="op" title="Search" alt="Search" />
<input type="hidden" value="<?php print drupal_get_token('search_form'); ?>" name="form_token" />
<input type="hidden" value="search_form" id="edit-search-form" name="form_id" />
<input type="hidden" name="type[your_content_type]" id="edit-type-your_content_type" value="your_content_type" />
</div>
</form>

Good luck. I hope someone finds this useful.

Customising Contributions

These pages contain theme functions specific for contributed modules

Shazamgallery snippets

Shazamgallery is very well themeable. It uses views module, so all the views theme function apply too.

The main aim of shazamgallery is not to provide an end-user gallery, but to provide a lot of tools and APIs for building your own gallery based websites. Hence theme functions are very important.

The following pages contain theme functions specific for shazamgallery.

Large image on top

This theme function, takes the default image-only teaser list from Shazamgallery and makes that into a one-large-rest-teaser view.
For an example, please look at this news photo site.

Add this to your template.php file

<?php
/**
* Theme the frontpage
*/
function YOURTHEME_views_view_shazam_frontpage($view, $type, $nodes) {
  if (
$type == 'page') {
   
// Done before theming so theme can change it if it wants.
   
drupal_set_title(views_get_title($view));

    if (
$view->header) {
     
$header = check_markup($view->header, $view->header_format, false);
     
$output = "<div class='view-header' id='view-header-$view->name'>$header</div>\n";
    }
   
$top_node = array_shift($nodes);
   
$output = node_view(node_load($top_node), FALSE, FALSE, TRUE);
    foreach (
$nodes as $n) {
     
$node = node_load($n->nid);
     
$output .= node_view($node, TRUE, FALSE, TRUE);
    }
    return
$output;
  }
}
?>

Customize a block title

This snippet is aimed at somone who wants to change the titles of just one or two core or module blocks using Drupal 4.7. If there are a large number of block titles you want to change, a better approach is to use the locale module to substitute your preferred titles for the default titles.

Note that this functionality is provided through the Admin area in Drupal 5 and later.

To change a specific block title, you can add a conditional statement to your block template file. Here's an example for Drupal 4.7. This is a modification of the standard block.tpl.php that comes with blumarine or other templates for the phptemplate engine. It changes the name of the "Navigation" block to "My Tools" and the "Who's online" block to "Who's Online".

<?php
if ($block->module == 'user' && $block->delta == 1) {
       
$block->subject = t('My Tools');
}
else if (
$block->module == 'user' && $block->delta == 3) {
       
$block->subject = t('Who\'s Online');
}
?>

<div class="block block-<?php print $block->module ?>" id="block-<?php print $block->module ?>-<?php print $block->delta ?>">
        <h2 class="title"><?php print $block->subject; ?></h2>
  <div class="content"><?php print $block->content; ?></div>
</div>

Note that some themes, like the box_grey theme, don't include their own block.tpl.php. There is a default file that's in the /engines/phptemplate folder. You can copy this to your theme folder and change it. You can also use what I have posted above. The only difference is that the default phptemplate version doesn't have the <div> shown as <div class="title">.

More helpful info on blocks is found here: http://drupal.org/node/11813

Customizing the "user account' title for register/login pages

Did you ever want to customize the "user account" title that appears at the top of the user/register, user/password, and user/login pages, to better describe the task the user is currently performing?

In page.tpl.php, in place of:
<h1 class="title"><?php print $title ?></h1>

Insert:

<h1 class="title">
          <?php if (arg(0) == 'user' && arg(1) == 'register') : ?>
            Create an Account
          <?php elseif (arg(0) == 'user' && arg(1) == 'password') : ?>
            Retrieve lost password
          <?php elseif (arg(0) == 'user' && arg(1) == 'login') : ?>
            User Login
          <?php elseif (arg(0) == 'user') : ?>
            User Account
          <?php else : ?>
            <?php print $title ?>
          <?php endif ; ?>
</h1>

And replace the text with whatever you wish. I'm sure there's a better way to write this code ;) but this works.

For full customization of these pages with a page-login.tpl.php file, see Lullabot's article:
http://www.lullabot.com/articles/hacking_phptemplate

Different Header Images for Different Nodes

Drupal provides same header for the whole site and if we want to theme all the nodes individually then it's a bit complicated process. While looking for alternatives to this, the following snippet work fine, this has been implemented in www.vinrcorp.com you can see the example before using it. The usage of the snippet is also quit simple just put this code into the header part or where ever you want, of page.tpl.php file of your theme.

<table>
<tr>
<td><img src="[path to image folder]/images/<?php
  
if ($node->nid == 1) {
   print
'image-1.jpg" />';
   }elseif (
$node->nid == 2){
   print
'image-2.jpg" />';
   }elseif (
$node->nid == 3){
   print
'image-3.jpg" />';
   }elseif (
$node->nid == 4){
   print
'image-4.jpg" />';
   }elseif (
$node->nid == 5){
   print
'image-5.jpg" />';
   }elseif (
$node->nid == 6){
   print
'image-6.jpg" />';
   }elseif (
$node->nid == 7){
   print
'image-7.jpg" />';
   }elseif (
$node->nid == 8) {
   print
'image-8.jpg" />';
   }elseif ((
$node->nid == 9) | ($node->nid == 10)){
   print
'image-9.jpg" />';
   }elseif (
$node->nid == 11) {
   print
'image-11.jpg" />';
   }else {
   print
'default-image.jpg" />';
   }
?>

</td>
</tr>
</table>

To use it just put images in the image folder and mention the path in the code and also replace the names of images as per the node ids. This will help you to display different banner images for different nodes.
I think you can use it and this works for you too. You can reply to this post for further help.

Vivek Dubey

Display full Breadcrumb in node

If you have a "listing page" of nodes organized by taxonomy, for example, a page called /taxonomy/term/4, the list itself returns the correct breadcrumb but when you click on one of the node titles or "read more" links, the individual node opens with the breadcrumb apparently "broken"; all that shows up is "Home".

The "correct" behavior would be to show the full breadcrumb (everything up to the listing page title AND the category title) in the node's breadcrumb.

This snippet set customizes the node template (usually node.tpl.php) in your theme to do just. (This works with themes using phptemplate in 4.6 and 4.7)

Step 1

Make a copy of node.tpl.php and...

In your original node.tpl.php, put the following code at the very top:

<?php

$listing
= false;
if(
arg(0) == 'taxonomy')
{
$listing = true;
}
if(
$listing)
{
$bc = drupal_get_breadcrumb();
$title = drupal_get_title();
$title = html_entity_decode($title,ENT_QUOTES);
$link = l($title,$_GET['q']);
$bc[] = $link;

$bc_safe = urlencode(serialize($bc));
?>

Step 2

In the original node display code that is right after what you've just pasted in, look for the line that displays the title. Usually it's something like:

<a href="<?php print $node_url ?>" title="<?php print $title ?>"><?php print $title ?></a>

Replace that line with:

<?php print l($node->title, "node/$node->nid",array(),"bc=$bc_safe"); ?>

Step 3

Now look in the original code for the line that prints "$links" ($links includes the "read more" link - thank you Zach!) and replace THAT line with

print l("read More", "node/$node->nid",array('class' => 'read-more'),"bc=$bc_safe");

Step 4

At the end of the node display code insert:

<?php
}
else {

if(isset(
$_GET['bc']))
{
   
$bc = unserialize($_GET['bc']);
}

drupal_set_breadcrumb($bc);

?>

Step 5

Copy the original node display code from your old node.tpl.php copy and paste it under your the above statement. You don't have to change anything in it this time.

Step 6 -- At the very bottom of that pasted code, put in:

<?php
}
?>

Save it and your node page will now display a Drupal-consistent breadcrumb.

--------

To help contextualize this better, here's a sample node.tpl.php with these snippets applied:

<?php

$listing
= false;
if(
arg(0) == 'taxonomy')
{
   
$listing = true;
}
if(
$listing)
{
$bc = drupal_get_breadcrumb();
$title = drupal_get_title();
$title = html_entity_decode($title,ENT_QUOTES);
$link = l($title,$_GET['q']);
$bc[] = $link;

$bc_safe = urlencode(serialize($bc));
?>


<div class="node<?php print ($sticky) ? " sticky" : ""; ?>">
  <?php if ($page == 0): ?>
    <h2><?php print l($node->title, "node/$node->nid",array(),"bc=$bc_safe"); ?>
</h2>
  <?php endif; ?>
  <?php print $picture ?>
  <div class="info"><?php print $submitted ?></div>
  <div class="content">
  <?php print $content ?>
  </div>
<?php if ($links): ?>
    <?php if ($picture): ?>
      <br class='clear' />
    <?php endif; ?>
  <div class="links"><?php
print l("read More", "node/$node->nid",array('class' => 'read-more'),"bc=$bc_safe");
?>
</div>
<?php endif; ?>
<div class="terms"> <?php print $terms ?> </div>
</div>
<?php
}
else {
?>

<?php


if(isset($_GET['bc']))
{
   
$bc = unserialize($_GET['bc']);
}

drupal_set_breadcrumb($bc);

?>

<div class="node<?php print ($sticky) ? " sticky" : ""; ?>">
  <?php if ($page == 0): ?>
    <h2><?php print l($node->title, "node/$node->nid"); ?>
</h2>
  <?php endif; ?>
  <?php print $picture ?>
  <div class="info"><?php print $submitted ?></div>
  <div class="content">
  <?php print $content ?>
  </div>
<?php if ($links): ?>
    <?php if ($picture): ?>
      <br class='clear' />
    <?php endif; ?>
  <div class="links"><?php print $links ?></div>
<?php endif; ?>
<div class="terms"> <?php print $terms ?> </div>
</div>
<?php
}
?>

Display images that have been created between any two story entries in the node-story.tpl.php

I have a blog that basicly lets me publish stories and images for friends and family to see. The frontpage primarily shows the stories, while the images are handled through the Views module. Still I thought it would be nice if the tiny versions of the images added since the last story would show up on top of that story. Therefore I wrote this snippet. Its crude and uses two sql queries per story, but does its job. You might have to tweek it to fit your needs and I guess it could be improved a lot. You could probably limit it to one database query through subqueries), but I still think it is a neat feature.

In the admin/settings/image-page I added an image label called 'micro'. Change this to whatever you like.

Add this in the beginning of your node-story.tpl.php file

<div class="node_images">

<?php
$since
= $node->created;
$image_label = 'micro';
$header = "<div><h2>Images added since last story</h></div>";

$result = db_query("SELECT node.type, node.nid, node.created FROM node WHERE (node.created > $since) AND (node.type = 'story') ORDER by node.created LIMIT 0, 1");
while (
$node = db_fetch_object($result)) {
   
$until = $node->created;
}

/* Check if the current story  is the newest */
if ($until) {
   
$result2 = db_query("SELECT node.type, node.nid, node.created FROM node WHERE (node.created > $since) AND (node.created < $until) AND (node.type = 'image') ORDER by node.created  DESC");
} else {
   
$result2 = db_query("SELECT node.type, node.nid, node.created FROM node WHERE (node.created > $since) AND (node.type = 'image') ORDER by node.created  DESC");
};

while (
$nid = db_fetch_object($result2)) {
   
$output .= l(image_display(node_load(array('nid' => $nid->nid)), $image_label),'node/'.$nid->nid, array(), null, null, FALSE, TRUE);}
    print
$header;
    print
$output;
?>


</div>

Display only tags (and not other category types) below nodes

In node.tpl.php, the following code shows links to all categories to a node, regardless of what kind of categories they are.

<?php
print terms;
?>

If all you want are to list the tags, use the following PHP snippet in place of the code that displays categories. You will need to know which vocabulary you've set as "free tagging", that is, which vocabulary ID. Change the first line to reflect your settings. If you don't yet have a vocabulary set to free-tagging, click administer » categories » add vocabulary tab and check the box next to "Free tagging" after you've filled in the other required fields.

Note: for 4.6 sites, you will need to apply the free-tagging backport patch to your code. The patch requires a fairly advanced knowledge of modifying your Drupal install, so back up everything before attempting the patch. For sites based on the 4.7 Drupal code, this snippet works without any modification necessary to the software..

So replace the code in node.tpl.php above with the code below, and it will show a comma-separated list of tags linked to their individual taxonomy term pages.

<?php
  $vid
= 1;
 
$result = db_query("SELECT t.tid, t.name FROM {term_data} t, {term_node} r WHERE r.tid = t.tid AND r.nid = %d AND t.vid = %d ORDER BY weight, name", array($node->nid, $vid));
  while (
$term = db_fetch_object($result)) {
   
$tags[] = l($term->name, 'taxonomy/term/' . $term->tid);
  }
  if (
$tags) {
    print
t("Tags: ") . implode(', ', $tags);
  }
?>

Display public/non-public node status

Description

If you use some kind of Access Control like I do to make certain nodes only viewable by registered users, you may want to display some text or icon along with a non-public node. This way users can be easily made aware that they are looking at some non-public content. Using Drupal 5 and phptemplate this can easily be done using your template.php

Step 1 of 3

Make sure you have the

<?php
function _phptemplate_variables($hook, $vars) {
/* ... */
}
?>

in your template.php in your theme directory. If you do, build upon the code already in there, if not use the code from step 2 as a template.

Step 2 of 3

<?php
function _phptemplate_variables($hook, $vars) {
 
/* this is needed if you want to display the nonpublic-information in the page template */
 
static $public_node;
  switch (
$hook) {
    case
'page':
     
/* add public_node info to vars so it can be used in page.tpl.php */
     
$vars['public_node'] = $public_node;
      return
$vars;

    case
'node':
     
/* load current user object */
     
global $user;
     
/* backup current user object */
     
$my_account = $user;
     
/* set user object to anonymous user so that node_access() thinks we're anonymous */
     
$user = drupal_anonymous_user();
     
/* get info, if anonymous can view this node. */
      /* using node_access(), this takes into account any access control modules */
     
$public_node = node_access('view', $vars['node']);
     
/* add public_node info to vars so it can be used in node.tpl.php */
     
$vars['public_node'] = $public_node;
     
/* restore current user account */
     
$user = $my_account;
      return
$vars;
  }
  return array();
?>

Step 3 of 3

In your page.tpl.php and/or node.tpl.php use $public_node variable to display a info text. I display a little icon of a lock next to the title to show members of our foundation website (which are always members of the foundation itself) that the content they read is not publicly available.

Display taxonomy terms broken out by vocabulary

By default, taxonomy terms are displayed in the node by the function

<?php
print $terms
?>

This results in one big array of all taxonomy terms associated with that node, no matter what vocabulary. Sometimes it makes sense to break these up, so that the terms are displayed by vocabulary. For example:

  • Topic: foo, bar
  • Tags: bat

To do this, do the following:

Step one

Add the following snippet to your template.php file:

<?php
// split out taxonomy terms by vocabulary
function yourthemename_print_terms($nid) {
    
$vocabularies = taxonomy_get_vocabularies();
    
$output = '<ul>';
     foreach(
$vocabularies as $vocabulary) {
       if (
$vocabularies) {
        
$terms = taxonomy_node_get_terms_by_vocabulary($nid, $vocabulary->vid);
         if (
$terms) {
          
$links = array();
          
$output .= '<li>' . $vocabulary->name . ': ';
           foreach (
$terms as $term) {
            
$links[] = l($term->name, taxonomy_term_path($term), array('rel' => 'tag', 'title' => strip_tags($term->description)));
           }
          
$output .= implode(', ', $links);
          
$output .= '</li>';
         }
       }
     }
    
$output .= '</ul>';
     return
$output;
}
?>

Note: Be sure to replace 'yourthemename' with the actual name of your theme, or this will not work. [Remember, don't include the php tags if you already have them in your file.]

Step Two

Now add the following to your node.tpl.php file:

<?php
print yourthemename_print_terms($node->nid)
?>

Again, be sure to replace 'yourthemename' with the actual name of your theme, or this will not work. You might want to enclose this snippet in your node template within a div with the appropriate class for your stylesheet.

[Hat tip: styro]

Expanding a menu tree based on node types

If you run a Drupal site with various sections based on node types this snippet can be useful to enhance the navigational experience of your visitors.

Drupals theme_menu_tree() function offers the classes you need to highlight active menu items. But this does not work for nodes that have no menu item assigned.

To give a real world example. I run a site with a video section. In my primary links menu I have a menu item called Videos. There are several sub menu items such as most viewed, recently added, etc. When a visitor clicks on Videos the menu is expanded. Then the visitor clicks on recently added, the menu is still expanded, that is what I want. Now when the visitor actually clicks on one if the videos listed under Videos -> recently added he or she is directed to the node the video is contained in. This node has no menu item assigned and so the menu is not expanded any more. This is not what I want.

The solution for this problem is quite easy thanks to Drupals flexibility. Here we go:

Step 1

In your template.php file add the following function:

<?php
// override theme_menu_item function
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE) {
  return
_phptemplate_callback('menu_item',
  array(
'mid' => $mid, 'children' => $children, 'leaf' => $leaf));
}
?>

Step 2

Create a new file in your template directory called menu_item.tpl.php. This file contains the following lines of code:

<?php
$link
= menu_item_link($mid);
if (
arg(0) == 'node' && is_numeric(arg(1))) {
 
$node = node_load(arg(1));
}

if (
$node->type == 'video' && $mid == 158) {
 
$tree = menu_tree(158);
 
$children = "\n".'<ul class="menu">'. $tree ."\n".'</ul>';
 
// add active class to link
 
$link = str_replace('<a ', '<a class="active" ', $link);
}

$output = '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'.
 
$link . $children ."</li>\n";
print
$output;
?>

How does this work?

Step 1 tells Drupal to override the theme_menu_item() function.

In Step 2 first we load the current menu item link and assign it to the variable $link. To get information on the node type we retrieve the current node object by passing the node id (arg(1)) to the node_load() function. Then we check whether this node is of the type video. If this returns true and the current menu id equals the id of the menu item called Videos (in my case 158) we assign the $children variable the sub menu tree of the current menu item add the active class to our link and print out the modified list item.

Now when a visitor watches a video the navigation menu is expanded and gives a clear indication that the visitor is in the video section of the web site.

Focus caret automatically at www.example.com/?q=user

This php snippet will automatically give focus to the username field when anonymous users visit www.example.com/?q=user.

The snippet should be inserted immediately before the <?php print $closure;?> tag in your page.tpl.php.

<?php

// If user is not logged in and visits user page,
// focus caret on username text input.
if ((arg(0) == "user") && (!$user->uid)) {
print
"[script type=\"text/javascript\"]\n";
print
"document.getElementById('edit-name').focus()\n";
print
"[/script]\n";
}
?>

P.S. Hoping somebody with the correct permissions can correct the opening/closing script tags in the snippet....

Garland Simple modification Tutorial

An example of what will be achieved by the end of this tutorial can be seen at www.venturacottage.com

Fresh install of Drupal5 > Using [Windows explorer], Go to themes folder > make copy

of Garland folder and rename custom > open custom folder > > open style.css in

notepad and alter the lines

background: #f7f1de url(bg-navigation.png) repeat-x 50% 100%;
to
background: #f7f1de url(bg-navigation.png) repeat-x 0% 100%;
and
background: #fbf9f2 url(body.png) repeat-x 50% 0;
to
background: #fbf9f2 url(body.png) repeat-x 0% -37px;
and
background: #ffffff url(bg-content.png) repeat-x 50% 0;
to
background: #ffffff url(bg-content.png) repeat-x -10px 0;
and
max-width: 1270px;
to
max-width: 1920px;

- save and close

Using your browser go to your website > admin/build/themes > enable the custom theme

and set to default > [Save Configeration] Now > [configure] and choose the color set

that you want to base your custom theme on (in my case Belgium Chocolate). Decide if

you wish to use the [Logo] and [Site name] options, or if you are incorporating them

directly into your background graphic and tick/untick accordingly. I DO use them, but

have my own home_logo.gif that works well and is available within the download >

[Save configuration]

Using [Windows explorer], Go to files > color > new folder something like

[custom-1e27bf52] view this folder as thumbnails.

Download drupalcustom.pspimage, which is a PaintShopPro v8 file, a trial version of

which is available at

http://www.hensa.ac.uk/sites/ftp.simtel.net/pub/simtelnet/win95/grafedit...

Open file - drupalcustom.pspimage, on RLS of window you will see the layer box > in

the group called GarlandColour most are inactive > turn off pinkplastic (by clicking

on the eye) and turn on say belgiumchocolate > you will see that the background has

changed. Now within [Group - main] > choose [mainimage] > Edit > Cut and the car should have gone. File > open > bring in an image of your own (anything will do for now) > edit > copy > back to drupalcustom and paste > as new selection > position top left to suit your wishes > selections >select none. To recap, we can change the base colour to whatever we want and brining an image in automatically blends it with the background. Now [File] > [Revert] > and change colour the belgiumchocolate as above > and save as drupalcustom.png > close this file

and open up drupalcustom.png again (this ensures it

is now a single layer flat image). Save it in the [custom-1e27bf52] folder as

body.png overwriting existing one > check home page in browser to view change - if

all ok continue

On RHS of PainShopPro window in Overview section click Info tab (if not already showing) > this will show your cursor position

zoom to say 300% > use the selection tool to select 0,0 to 1920x37 copy/ paste as new image > save in the [custom-1e27bf52] folder as bg-navigation.png

overwriting existing one > check home page in browser to view change

go to position 220,117 and select to the bottom right corner > copy and paste to a

new image > save overwriting current bg-content.png.

Now select from top,left > 50 wide x352 deep > copy and paste as new image and save

overwriting current bg-content-left.png. > check home page in browser to view change

(on bg-content.png) go to position 491,0 and select to depth 352 and a width of 50 > copy and paste to a new image > save overwriting current bg-content-right.png.

Now select from top,left > 50 wide x352 deep > copy and paste as new image and save

overwriting current bg-content-left.png.

back to bg-content.png > go to position 488,0 now select 50 wide x352 deep > copy and

paste as new image and save overwriting current bg-content-right.png.

back to the new bg-content.png and crop to 200 deep. Then go from 0,481 total depth and width 10px >edit > copy. Then select from 0,491 to bottom right corner > Edit > paste > into selection > file > save > check home page in browser.

That is pretty much it. Execept to say that you should backup your [custom-1e27bf52] folder, because if you go into the theme settings for whatever reason it helfully overwrites all your custom graphics with new ones!

I hope this is useful to those like me who struggled initially to adapt themes. Let us hope others with more knowledge take this further.

A version of this tutorial with extra screenshots is also at www.ventuacottage.com/garlandmod2.htm

Generalized list function

While there is already a theme_item_list function in Drupal, it always wraps the list in a div with class="item-list" which may not always be desirable. This more general theme function creates ordered, unordered, and definition lists without any extra markup around them, and you can pass in attributes for the main list element as well.

/* one place to print out a list. type = (unordered, ordered, definition) */
function theme_list($items = array(), $type = 'unordered', $attributes = array()) {
  $output = '';
  $tag = '';
  $attr = '';

  if (!is_array($items) || count($items) == 0) {
    return '';
  }

  switch ($type) {
    case 'definition':
      $tag = 'dl';
    case 'ordered':
      $tag = 'ol';
    case 'unordered':
    default:
      $tag = 'ul';
  }

  if (count($attributes) > 0) {
    foreach ($attributes as $key => $val) {
      $attr .= ' ' . $key . '="' . $val . '"';
    }
  }

  $output .= '<' . $tag . $attr . '>';

  foreach ($items as $item) {
    if ($type != 'definition') {
      $output .= '<li>' . $item . '</li>';
    }
    else {
      if (is_array($item)) {
        $dt = key($item);
        $dd = $item[$dt];
        $output .= '<dt>' . $dt . '</dt>';
        $output .= '<dd>' . $dd. '</dd>';
      }
      else {
        $output .= '<dd>' . $dd . '</dd>';
      }
    }
  }

  $output .= '</' . $tag . '>';

  return $output;
}

get a contextual array for your node-links

Often I am very annoyed by the fact the links under the nodes are only availble as plain text strings. For example when I only want the "read more" link, or only the "comments" links. (example of usage)

This PHPtemplate-specific snipped creates an additional variable: an array with much nicer link data.

function _phptemplate_variables($hook, $vars) {
  switch ($hook) {
    case 'node':
      foreach ($vars[node]->links as $link) {
        preg_match("/<a\s*.*?href\s*=\s*['\"]([^\"'>]*).*?>(.*?)<\/a>/i",$link, $matches);
        $vars[nodelinks][]['url'] = $matches[1];
        $vars[nodelinks][]['text'] = $matches[2];
      }
    break;
  }
  return $vars;
}

Please add improved regular expressions for the decomposition of anchors in this post directly, or in a comment.

Get nodes as an array for greater flexibility

By default, in your page.tpl.php you have $content which contains all the nodes and other things like pagination..etc. The snippet below tries to give more flexibility by breaking down $content, and breaking down nodes themselves into an array of nodes. Precisely, what happens is:

  • In page.tpl.php: You get a new array (YES!) called $nodes, contains the XHTML for each node.
  • In page.tpl.php: $content now does NOT contain the nodes. It contains some other things like pagination..etc

Be aware that this snippet doesn't really give you any difference if you didn't adjust your page.tpl.php to exploit the extra flexibility of having nodes as an array. Some examples of doing neat things with $nodes array can be found after the snippet below.

Here comes the PHP snippet, place this in your template.php:

<?php
  
function yourtheme_node($node, $teaser = 0, $page = 0) {
 
set_themed_node(phptemplate_node($node, $teaser, $page));
}  
 
function
set_themed_node($node = NULL) {
  static
$themed_nodes;
  if (
$node) {
   
$themed_nodes[] = $node;
  }
  else {
    return
$themed_nodes;
  }
}    
     
function
_phptemplate_variables($hook, $vars) {
 
$myvars = array();
  switch (
$hook) {
    case
'page':
     
$myvars['nodes'] = set_themed_node();
      break;
  }
  return
$myvars;
}
?>

Then in page.tpl.php, add

<?php
print implode($nodes);
?>
just before
<?php
print $contents;
?>

Just to be sure, here is how page.tpl.php should look...
      <snip>...

      <div id="main">
        <?php print $breadcrumb ?>
        <h1 class="title"><?php print $title ?></h1>
        <div class="tabs"><?php print $tabs ?></div>
        <?php print $help ?>
        <?php print $messages ?>
        <?php print implode($nodes);  // <<< this is our new line ?>
        <?php print $content; ?>
      </div>

      <snip>...

So now the result is not different from before. But it's different in your page.tpl.php, as you have the new $nodes array. If you are reading this, it's likely you know what you want to do with this already :-)

However, here are some example(s). All examples assumes you have added the snippet above to your template.php of course. And oh, If you did something useful with this snippet, post it in comments and it will be added as in example below.

Example: two-column node listing

Let's try to do a two-column listing of nodes. We have $nodes as an array, so our approach will be to divide this into two halfs, and print each in a column. We will use <div class="nodes-left">..</div> to contain our left column nodes. And <div class="nodes-right">..</div> to contain right column nodes. A few lines of CSS will be added, to actually style these divs right and left.

Here is how we do this, in page.tpl.php, remove the line <?php print implode($nodes); ?> and add the following instead:

<?php
$nodes_left
= '';
$nodes_right = '';
foreach (
$nodes as $key => $mynode) {
  if (
$key % 2) {
   
$nodes_left .= $mynode;
  }
  else {
   
$nodes_right .= $mynode;
  }
}
?>


<div class="nodes-left"><?php print $nodes_left; ?></div>
<div class="nodes-right"><?php print $nodes_right; ?></div>

That's it, now we just need the CSS to actually align each div on both sides. So add this to your style.css:

div.nodes-left, div-nodes-right {
  float: left;
  width: 50%;
}

have a primary and secondary front page - useful for flash or splash graphic front pages

description

This describes how to have a flash or splash graphic front page to your site and a "regular" front page.

dependancies

This is intended for use with the front_page.module

Step 1 of 1

  1. Go to ADMINISTER -> SETTINGS -> FRONT_PAGE
  2. In the text area for your front page for ANONYMOUS USERS, paste in the following snippet
  3. Select the ALLOW PHP IN PAGE option and set to FULL PAGE so your splash flash fills the screen
  4. Edit the snippet (yoursite.com) to match your site
  5. Paste in your Splash/Flash HTML code in the place provided
  6. Save settings.
<?php

/**
* Change the $yoursite value to your website address
*/

$yoursite = "yoursite.com";

/**
* paste your flashsplash HTML content in between the quote marks in the next line.
* It can run as many lines as you want as long as it begins and ends
* with the quotemarks.
*/
$flashsplashcode = 'paste your flashsplash HTML code here';

/**
* check to see where the visitor is coming from
*
*/

$ref = $_SERVER["HTTP_REFERER"];

    if  (!
stristr($ref, "$yoursite"))  {

   
//Offsite referrer detected so display the full splash/flash page.
   
print $flashsplashcode;
    }

  else {
/**
* This php snippet displays the 10 most recent weblog entries with
* teaser & info.
*/
 
$listlength=10; //determines the amount of weblog entries displayed. increase or decrease to suit.
 
$result1 = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.created DESC"), $listlength);
  while (
$node = db_fetch_object($result1)) {
   
$output .= node_view(node_load(array('nid' => $node->nid)), 1);
  }
print
theme('page', $output); // displays the output as a themed page to include the mission.
}
?>

Notes

  • It's generally considered to be bad design to have flash/splash page for your site. But I can see where some sites, e.g. adult sites, might want to display a strong warning.
  • Please post tips/tricks discuss this handbook page at this thread.
  • There are a lot of other useful content snippets in the HANDBOOK -> PHP SNIPPETS -> PHP PAGE SNIPPETS if you want to display something other than the 10 most recent weblog entries

How to add "submitted date" to only 'child pages' of a 'book'

Here's a way to add "submitted date" to only child pages but not in the main book page!

<?php if ($submitted) { ?>
<span class="submitted">
<?php  if ($node->type == 'book') { 
    if (
$node->parent != 0) {
print 
format_date($node->created, 'custom', "F jS, Y") ;}
}
?>

</span>
<?php } ?>

Add this code in the node.tpl.php file

How to display mission on every page?

This seems an easy one, but I can't figure out how to display the site mission on every page, it only shows in the home page...

I am using a self-made php template, and I have the following line in page.tpl.php:

<?php if ($mission != ""): ?><div id="mission"><?php print $mission; ?></div><?php endif; ?>

Hope you can help me, TIA!

How to make the 'sidebars' visible even when there's no visible block!

How to make the 'sidebars' visible even when there's no visible block!

open your page.tpl.php file :

<?php if ($sidebar_right) { ?><td id="sidebar-right">
      <?php print $sidebar_right ?>
    </td><?php } ?>

Change it to :

<td id="sidebar-right">
      <?php print $sidebar_right ?>
    </td><?php ?>

Follow similarly for other sidebar too!

How to remove "blogs » user's blog" from breadcrumbs

I'm the only one blogging at my website, so at my site the "blogs » user's blog" part of the breadcrumb is redundant (and a bit confusing).

To remove it, I've added this to template.php:

<?php
/**
* Remove links to individual blog from breadcrumbs.
*
* (This is an uggly solution, prone to break since it identifies blog nodes by matching a string in
* the breadcrumb. Better would be to check if node type is 'blog' But I don't know how to get that info here.
*
* Also note that it does not change the menu, so "my blog" is still present there. (I suspect that 'menu_set_location'
* (as used in blog.module blog_view) may be the function to use to change this.))
*/
function phptemplate_breadcrumb() {
   
$breadcrumb = drupal_get_breadcrumb();
    if (
$breadcrumb[1] == l('blogs',t('blog'))){         // For blog nodes...
       
unset($breadcrumb[2]);                     // ...remove "user's blog"...                            
       
unset($breadcrumb[1]);                  // ...and "blogs".
   
}
   
// Now call the original theme with this modified breadcrumb array, to get formatting.
  
return (theme_breadcrumb($breadcrumb));
}
?>

HOW TO: add a first, previous, next, last to any page, for any node type

Note from the moderator. This page has been updated with code from the comments that is written more to the "Drupal way" than the original code and removes some misinformation (notably the original page stated that db_fetch_array($query) is for postgresql only and that is patently incorrect). The following changes are in the newer code:

1. loops once, not once for every link; I mean, call the function one time and make a single database call, so we can loop through the (possibly quite large) result set only one time ( not do all that for each link ).
2. use l(t()) calls for outputting the links
3. use drupal_sql_query code instead of mysql unique code
4. combined the three loops into one easy, single loop
5. modify the first 6 lines or the function arguments to use your node types
6. If you are using the Category module ( I can't speak for Taxonomy users) the next/previous/oldest links all reference the next/previous/oldest within that category, out of the box.
7. renamed the FIRST link to OLDEST and the LAST to LATEST.
8. This orders nodes by their created times, not by their edited times, and not by simply node number.

You need to edit your template.php file (located at: /themes/your_drupal_theme/template.php) and add the following code:

<?php
function get_nav_node_path($current_nid) {
   
// first need to check if we are looking at a category node and not a real node
   
$result = db_query("select type from node where nid = $current_nid");
   
$line = db_fetch_array($result);
    if (
$line[type] != 'page') return '' // do not show these links for category nodes, only for page nodes
    // Performing SQL query
   
$dbuid = get_session('dbuid');  // drupal browse user_id; I save this in my sessions to limit to users
   
$query = "select nid from node where type='page' AND user_id = $dbuid order by created desc";
   
$result = db_query($query) ;
   
//setup variables
   
$counter = 0;
   
$ary = array();
   
$node_location = 0;
   
$first =''; $previous=''; $next = ''; $last = ''; $next_nid = '';


   
//parse database and put into PHP array
   
while ($line = db_fetch_array($result)) {
          
$ary[$counter] = $line[nid];
           if(
$line[nid] == $current_nid) {
              
$node_location = $counter;
           }
          
$counter ++;
    }


   
$return = '<div id="nav-links">';
   
//tack on relative path of the first node
   
$print_nid = $ary[$counter - 1];
    if (
$print_nid != $current_nid ) {
       
$first = drupal_get_path_alias("node/$print_nid");
       
$return .=  l(t('Oldest'), $first);
    }


   
//tack on relative path of the previous node
   
if($node_location >= 0 && $node_location < $counter - 1) {
       
$print_nid = $ary[$node_location + 1];
       
$previous = drupal_get_path_alias("node/$print_nid");
        if (
$first != '') { $return .= ' | '; }
       
$return .=  l(t('Previous'), $previous);
    }



   
//tack on relative path of the next node
   
if($node_location > 0 && $node_location < $counter) {
       
$next_nid = $ary[$node_location - 1];
       
$next = drupal_get_path_alias("node/$next_nid");
        if (
$previous != '' || ($first !='' && $previous == '') ) { $return .= ' | '; }
       
$return .=  l(t('Next'), $next);
    }

    
//tack on relative path of the last node
   
if ($next_nid != $ary[0]) { // i.e. if next_nid is not the last one
       
$print_nid = $ary[0];
        if (
$print_nid != $current_nid ) {
           
$last drupal_get_path_alias("node/$print_nid");
            if (
$next != '' ) $return .= ' | '. l(t('Latest'), $last);
        }
    }

   
$return .= '</div>';
    return
$return;
}
?>

Now you just need to insert the following code into your node.tpl.php or whatever custom tpl file you like:

  <div class="node<?php if ($sticky) { print " sticky"; } ?><?php if (!$status) { print " node-unpublished"; } ?>">
    <?php if ($picture) {
      print
$picture;
    }
?>

    <?php if ($page == 0) { ?><h2 class="title"><a href="<?php print $node_url?>"><?php print $title?></a></h2><?php }; ?>
    <span class="submitted"><?php print $submitted?></span>
    <span class="taxonomy"><?php print $terms?></span>
<?
global $q;
if ($q != 'node') {   // my default page lists 5~10 entries and
// we don't need the next-prev-last-oldest links when we are simply browsing them.
// So check and only call the function when viewing an individual node
    echo get_nav_node_path($node->nid);
}
?>
    <div class="content"><?php print $content?></div>
    <?php if ($links) { ?><div class="links">&raquo; <?php print $links?></div><?php }; ?>
  </div>

HOW TO: Show block depending on node type and node id

The following snippet allows you to configure certain blocks to show only when the user is at a specific node type and with a specific node id. You can modify the arrays in the snippet to match your needs. Just modify and paste this snippet on the block

How to use:
1. Go to your blocks administer section "Home » Administer » Site building".
2. Add a new block "Add block". (If not just "configure" the one you already have.).
3. Go to "Page specific visibility settings".
4. Select "Show if the following PHP code returns TRUE (PHP-mode, experts only).".
5. Paste the snippet in the text area just below. (NOTE: Remember to keep the <?php> tags.)

Modifying Arrays:
Node Type Array
$types = array('page' => 1);

Node ID Array
$nodes = array(1, 2);

The following will show the configured block in:
Example: http://www.yoursite.com/node/1
Example: http://www.yoursite.com/node/2

<?php
// Only show block from types array and nodes array
$match = FALSE;
// Which node types
$types = array('page' => 1);
// Which nodes (by nid)
$nodes = array(1, 2);
if (
arg(0) == 'node' && is_numeric(arg(1))) {
 
$nid = arg(1);
 
$node = node_load(array('nid' => $nid));
 
$type = $node->type;
  if (isset(
$types[$type]) && in_array($nid, $nodes)) {
       
$match = TRUE;
  }
}
return
$match;
?>

HOWTO: Display some arbitrary HTML on a specific page based on the URL you are on

This snippet shows you how to insert simple "if" statements in your theme to display html or any other block of code based on the URL you are on. In the example it shows you how to display a block of text before printing out a taxonomy node listing.

1) open up page.tpl.php in your phptemplate theme.
2) Right above where it starts printing out your main content in your theme (usually starts with printing
menu tabs) you can insert some logic.
3) you can use some logic to figure out what page you are on. For instance for the URL:

http://www.example.com/taxonomy/term/10

arg(0) returns 'taxonomy'
arg(1) returns 'term'
arg(2) returns '10'

4) If you want to display some html if you are on that
page, you put in some logic in your theme file (page.tpl.php).
<?php if(arg(0)=='taxonomy' & arg(1)=='term' & arg(2)== '10'): ?>
  This is where you put the html you want to print out
  on this page! It won't print this if we aren't on this
  page.

<?php endif; ?>

MAIN CONTENT

That's all the php you have to type. The logic says, "If we are on the taxonomy/term/10 page, let's print
this html code. Otherwise let's skip it and continue to the main content." You can see that this is
powerful because it lets you display a block of HTML or code depending on what page you are on, which is useful in many situations. You can put this snippet anywhere in the theme file, and use it to load different pictures or text anywhere on the page. The arg() function is useful for returning the path of whatever page you are on. Of course, since it is a simple "if" statement you can use this example as a general purpose logic tool. For instance, let's say you want to only print out a block of code for the 100th user (user ID = 100) when they are logged in:

<?php global $user; if($user->uid == 100): ?>
Welcome, 100th user!!! This text will only display when you are logged in!!
<?php endif; ?>

Hope that helps a bit.
Good luck!
zirafa

Image rotator snippet for phpTemplate

This is the implementation of a random image rotator of automaticlab's rotate.php on your Drupal theme's header

While the rotator script can be used as an image reference replacement within a style.css (insead of image.png you refer the directory and rotate.php) the snippet i use below implements the rotator script to a Drupal page.tpl.php with the all php theme ($logo) and ('Home') functions intact

info
http://automaticlabs.com/products/rotator
the script
http://alistapart.com/d/randomizer/rotate.txt

into to Drupal...
- download the script and name it to rotate.php
- create a folder and name it 'header'
- put the rotate.php and all the images you want to be rotated on your theme in the folder
- upload it to your theme's directory
so te resulting directory will be

-your_theme/
header/
image01.png
image02.png
image03.png
/

copy&paste the snippet below to your theme's page.tpl.php in place of the 'header'

<?php /* THE HEADER */ ?>
<div id="header">
      <?php if ($logo) { ?><a href="<?php print $base_path ?>" title="<?php print t('Home') ?>"><img src="<?php print base_path() . path_to_theme() ?>/header/rotate.php" alt="<?php print t('Home') ?>" /></a><?php } ?>

now each image you have in the folder 'header' should rotate on each page refresh

Insert an image instead of text in a menu item.

description

To insert an image instead of text in a menu item you may rewrite theme_menu_item_link().

Step 1 of 2

Put this code into your template.php inside your themes directory:

<?php
function phptemplate_menu_item_link($item, $link_item) {
   
// Allow HTML if the menu text is an image tag: call l() with 7th argument set to TRUE
    // See <a href="http://api.drupal.org/api/4.7/function/l
" title="http://api.drupal.org/api/4.7/function/l
" rel="nofollow">http://api.drupal.org/api/4.7/function/l
</a>    if( strpos($item['title'], '<img') === 0) {
      return l($item['title'], $link_item['path'], !empty($item['description']) ? array('title' => $item['description']) : array(), NULL, NULL, FALSE, TRUE);
    }
   
  return l($item['title'], $link_item['path'], !empty($item['description']) ? array('title' => $item['description']) : array());
}
?>

Here we just look for the menu title starting with the HTML tag <img and if true call l() with the 7th argument set to TRUE. This will allow HTML inside the menu text. See http://api.drupal.org/api/4.7/function/l.

Step 2 of 2

Now just enter an image tag into the "Title" field inside the "edit menu item" form (administer > menus > (edit or add item)) like <img src="/sites/default/menu_item.gif" />

Notes

  • This code should work in Drupal 5.0 too. At least the l() function did not change from 4.7 to 5.0

Inserting HTML into node titles

Drupal strips all HTML from node titles when they're displayed to prevent XSS exploits. However, there are some cases where inline HTML elements are needed, such as when titles contain scientific names.

To work around this, we will use pseudotags like [i] and [/i] which Drupal will safely translate into real HTML tags when the page is displayed.

The PHPTemplate file page.tpl.php will need to be edited in order to make this trick work. Specifically, we need to add two new functions.

<?php
function bb2html($text) {
 
$bbcode = array(
                 
"[strong]", "[/strong]",
                 
"[b]" "[/b]",
                 
"[u]" "[/u]",
                 
"[i]" "[/i]",
                 
"[em]", "[/em]"
               
);
 
$htmlcode = array(
               
"<strong>", "</strong>",
               
"<strong>", "</strong>",
               
"<u>", "</u>",
               
"<em>", "</em>",
               
"<em>", "</em>"
             
);
  return
str_replace($bbcode, $htmlcode, $text);
}

function
bb_strip($text) {
 
$bbcode = array(
                 
"[strong]", "[/strong]",
                 
"[b]" "[/b]",
                 
"[u]" "[/u]",
                 
"[i]" "[/i]",
                 
"[em]", "[/em]"
               
);
  return
str_replace($bbcode, '', $text);
}
?>

This is how you make a phrase in the title italicized:

My title [em]rocks[/em]!

One problem with this approach is that node title also shows up in the page titles, and page titles are not allowed to have any markup. That's where the above bb_strip function comes in. Between the HTML title tags in page.tpl.php, add the following:

<?php print bb_strip($head_title); ?>

And whenever you reference the node title in your template use this:

<?php print bb2html($title); ?>

NOTE: If you use books, this mechanism has a problem unless you are willing to patch book.module. book.module, in the function book_tree_recurse, creates links to nodes by their title, and of course doesn't know to use your code

Menu items that are not links

Problem

The Menu system in Drupal 5 lets admins define an arbitrary number of menu items in a hierarchy, each of which is a link to somewhere in the site (or on another site). That's great, unless you want to have a menu tree that has items in it which are not, in fact, links. For that, the following code lets us designate "menu links" that will not actually link anywhere.

Step 1

We will define the magic value <none> to mean "a path to nowhere", just like <front> means "the home page". The crucial part here is the theme_menu_item_link() function, which we will override. Place the following function in your template.php file.

<?php
function phptemplate_menu_item_link($item, $link_item) {
  if (
$item['path'] == '<none>') {
   
$attributes['title'] = $link['description'];
    return
'<span'. drupal_attributes($attributes) .'>'. $item['title'] .'</span>';
  }
  else {
    return
l($item['title'], $link_item['path'], !empty($item['description']) ? array('title' => $item['description']) : array(), isset($item['query']) ? $item['query'] : NULL);
  }
}
?>

Any menu item whose path is <none> will now become a span rather than a link. Note that because it's not a link you can't click on it, so you can't access sub-menu items unless the no-link menu item has the "Expand" checkbox checked so that its children are always visible.

This method can also be used as a separator. Simply have a menu item with text "------" or similar, and set its path to <none>. It will then show as text with no link, providing a nice separation of the menu.

Step 2

As a nice extra, we can also add additional help text to the menu item edit page. For that you will need to implement a very small module that implements form_alter. The module itself doesn't need anything more than the following code snippet:

<?php
function example_form_alter($form_id, &$form) {
  if (
'menu_edit_item_form' == $form_id) {
   
$form['path']['#description'] .= ' ' . t('Enter %none to have a menu item that generates no link.', array('%none' => '<none>'));
  }
}
?>

That will modify just the menu item edit form and add additional text to the description (the text that appears below the textfield) describing how to use the <none>.

Move the help/description text

Problem: Users often don't read the help text (found between div class="description" tags) for a form field because it is printed below the field, rather than between the label and the field.

Solution:

1. Add the following lines to your template.php

function phptemplate_form_element($title, $value, $description = NULL, $id = NULL, $required = FALSE, $error = FALSE) {
return _phptemplate_callback('form_element', array('value' => $value, 'title' => $title, 'description' => $description,
'id' => $id, 'required' => $required, 'error' => $error));
}

2. Create a form_element.tpl.php file something like:

<?
/**
* Return a themed form element.
*
* @param $title the form element's title
* @param $value the form element's data
* @param $description the form element's description or explanation
* @param $id the form element's ID used by the &lt;label&gt; tag
* @param $required a boolean to indicate whether this is a required field or not
* @param $error a string with an error message filed against this form element
*
* @return a string representing the form element
*/

?>
<div class="form-item">
  <?php
  $required
= $required ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';

  if (
$title) {
    if (
$id) {
      print
' <label for="'. form_clean_id($id) .'">'. t('%title: %required', array('%title' => $title, '%required' =>
       
$required)) . "</label>\n";
    } else {
      print
' <label>'. t('%title: %required', array('%title' => $title, '%required' => $required)) . "</label>\n";
    }
  }

  if (
$description) {
    print
' <div class="description"> '. $description ."</div>\n";
  }

  print
" $value\n";

?>

</div>

Nicely Formatted Calendar Dates:

Required: Event Module

Haven being thoroughly impressed by the calendar output designed by Lullabot at www.fearlessliving.org, I set about creating this myself.

When I had worked it out, I asked jjeff for permission to use this. He went one better, he send me the entire code used on lullabot.com here.
Here follows his code.
Thanks Jeff!

Stick this into template.php:
-----------------------------------------------

<?php
/**
* theme_event_nodeapi()
*/

function phptemplate_event_nodeapi($node) {
$zones = event_zonelist();
$zone = $zones[$node->timezone];

$show_times = !(format_date($node->event_start, 'custom', 'H:i',$node->start_offset) == "00:00");
$show_times = $show_times && !(format_date($node->event_end,'custom', 'H:i', $node->end_offset) == "23:59");

$same_day = (format_date($node->event_start, 'custom', 'mdY',$node->start_offset) == format_date($node->event_end, 'custom','mdY', $node->end_offset));

$output .= '<div class="event-box">';

// start date
$output .=
'<div class="event-nodeapi">
<span class="event-calbox">
<span class="event-month">'
.format_date($node->event_start,'custom', 'M', $node->start_offset).'</span>
<span class="event-hide"> </span>
<span class="event-date">'
.format_date($node->event_start,'custom', 'j', $node->start_offset).'</span>
<span class="event-hide">, </span>
<span class="event-year">'
.format_date($node->event_start,'custom', 'Y', $node->start_offset).'</span>
</span>'
;
if (
$show_times){

$output .= '<span class="event-hide"> - </span><span class="event-time">'.format_date($node->event_start, 'custom', 'g:i a', $node->start_offset).'</span>';
}
$output .= '</div>';

// check that end date is not the same day
if (!$same_day) { // remove this line and the end brace (marked below) to always show end time
$output .= '<div class="event-to">to</div>';

$output .=
'<div class="event-nodeapi">
<span class="event-calbox">
<span class="event-month">'
.format_date($node->event_end,'custom', 'M', $node->end_offset).'</span>
<span class="event-hide"> </span>
<span class="event-date">'
.format_date($node->event_end,'custom', 'j', $node->end_offset).'</span><span class="event-hide">,</span>
<span class="event-year">'
.format_date($node->event_end,'custom', 'Y', $node->end_offset).'</span>
</span>'
;
if (
$show_times){
$output .= '<span class="event-hide"> - </span><span class="event-time">'.format_date($node->event_end,'custom', 'g:i a',$node->end_offset).'</span>';
}

$output .= '</div>';
}
// remove this line and the beginning if statement (marked above) to always show end time

if ($show_times){
$output .= '<div class="event-zone">'. $zones[$node->timezone] .'</div>';
}

$output .= '<div class="clear"> </div>
</div>'
;

return
$output;
}
?>

and this into style.css:
--------------------------------------------------

/* Event Date Display (desk calendar page) */

.event-box {
   width: 160px;
}

.event-nodeapi {
   width: 5em;
   margin: .4em .3em .2em 0;
   text-align: center;
   float: left;
}
.event-nodeapi span {
   display: block;
   text-align: center;
}
.event-nodeapi .event-hide{
   display: none;
}

.event-calbox {
   border: 1px solid #999;
}


.event-month {
   color: #FFF;
   background-color: #666;
   font-weight: bold;
   text-transform: uppercase;
   font-size: 1.2em;
   line-height: 1.2em;
}

.event-date {
        color: #000;
        background-color: #FFF;
        font-weight: bold;
        font-size: 3em;
         line-height: .9em;
         padding: .05em 0 0;
        text-transform: inherit;
}

.event-year {
   color: #666;
   font-size: .9em;
   line-height: 1.1em;
   background-color: #FFF;
}

.event-time {
   color: #333;
   font-size: .9em;
}
div.event-to {
   margin-right: .3em;
   text-align: center;
   float: left;
   color: #999;
   font-weight: bold;
   font-size: 1.5em;
   padding-top: 1.5em;
   width: 1.5em;
}
br.event-clear {
   clear: both;
}
.event-nodeapi-tz label{
   font-weight: bold;
}

node taxonomy into classes (full page & teaser)

The snippet shown here will convert node categories to CSS classes. It will do this for each node when viewed as a teaser or when a node is rendered as a full page.

It's up to your own imagination to style it from the 'style.css' file in your theme folder.

Example: node with taxonomy terms (categories) of 'apples', 'oranges' and 'apples & oranges' will put out tax-apples, tax-oranges and tax-apples-oranges for individual nodes.

b-tax-apples, b-tax-oranges and b-tax-apples-oranges will be used for the page body when an individual node is a full page..

Any non-alphanumeric characters and spaces will be stripped and replaced with a '-' so we don't inadvertently have illegal class characters.

Place the code below into your template.php file in your themes folder to pass along the new variable. Create one if it doesn't exist.

<?php
function _phptemplate_variables($hook, $vars) {
  if (
$hook == 'page') {
   
$prefix = ' b-tax-';
  }
  if (
$hook == 'node') {
   
$prefix = ' tax-';
  }
  if (
module_exist('taxonomy') && $vars['node']->nid) {
    foreach (
taxonomy_node_get_terms($vars['node']->nid) as $term) {
     
$vars['node_terms'] = $vars['node_terms'] . $prefix . eregi_replace('[^a-z0-9]', '-', $term->name);
    }
  }
  return
$vars;
}
?>

Inside your page.tpl.php file add this to the <body> class. Use the same $node_terms variable in your node.tpl.php file. Preferably inside a containing div block. The same variable will print out different classes for the two templates.

class="<?php print $node_terms; ?>"

With this in place you can now make custom styles, colors , layouts or anything else with CSS according to the categories set for you nodes. The possibilities with this are endless from this simple enhancement.

  • Updated to work with the body class
  • I'm in the beginning stages of learning this stuff so any improvements are very welcome. The main php snippet came from Dash with some modifications.
  • This idea came out of the forum discussion: taxonomy as CSS class?.

obfuscate (hide) all emails from spammers

Here's a way to obfuscate emails from spam harvesters using just your theme.

Setting aside the question of whether it really works and how long it might work, on this particular site we wanted the site-wide but weak protection that this provides. Most of the existing obfuscation techniques are parts of modules, but here the more reliable (and potentially efficient?) mechanism is to do all your obfuscation at once at the theme level. In this case, the emails were being exposed by the civicrm module in the profiles.

The actual obfuscation is done by encoding the address in javascript so that most users won't actually notice, with the usual obfuscation as a fallback if the user has no javascript.

So here it is in two parts:

1. In your template.php file, add these two functions:

function _phptemplate_encode_mailto($mail) {
  $link = 'document.write(\'<a href="mailto:' . $mail . '">' . $mail . '</a>\');';
  $js_encode = '';  for ($x = 0; $x < strlen($link); $x++) {
    $js_encode .= '%' . bin2hex($link{$x});
  }

  $link = '<script type="text/javascript" language="javascript">eval(unescape(\''.$js_encode.'\'))</script>';
  $link .= '<noscript>'.str_replace(array('@','.'),array(' at ',' dot '),$mail). '</noscript>';
  return $link;
}

function phptemplate_safemail($text) {
  if (strpos($text, '@') === FALSE)
    return $text;
  // Split at <a> and </a> so that we can avoid encoding addresses in link text.
  $t = preg_replace(":(</?a):i", "\001\\1", strtr($text, "\r\n", "\002\003"));
  $a = explode("\001", $t);
  $n = count($a);

  for ($i = 0; $i < $n; ++$i) {
    if (preg_match('/^(<a[^>]*)mailto:([^@]+@[-.a-z0-9]+)(.*)/i', $a[$i], $m)) {
      $a[$i] = _coopzone_encode_mailto($m[2]);
      $a[1+$i] = substr($a[1+$i],4);
    }
    else {
      while (preg_match('/(.*)[^-.a-z0-9]([-.a-z0-9]+)(@[-.a-z0-9]+)(.*)$/i', $a[$i], $m)) {
        $a[$i] = $m[1] . _coopzone_encode_mailto($m[2].$m[3]) .  $m[4];
      }
    }
  }
  return strtr(implode('', $a), "\002\003", "\r\n");
}

and then add this fragment inside php tags in your page.tpl.php page (before the $content gets printed of course..).

It won't obfuscate emails in the /admin section or on edit forms. There may be some other pages that you don't want obfuscated as well.

if (function_exists('phptemplate_safemail') && ('admin' != arg(0)))  {
    $larg = arg(-1 + count(explode('/', $_GET['q'])));
    if ('edit' != $larg) {
      $content = phptemlate_safemail($content);
    }
  }
}

With thanks to:

and apologies if this looks like a repeat of a forum article I posted, it is (someone suggested this is a better spot for it).

overridding the the default sidebar layout

To override the the default sidebar layout, put the following in template.php

<?php
function phptemplate_blocks($region) {
 
$output = '';

  if (
$list = module_invoke('block', 'list', $region)) {
    foreach (
$list as $key => $block) {
     
// $key == <i>module</i>_<i>delta</i>
     
$output .= theme('block', $block);
    }
  }

 
// Add any content assigned to this region through drupal_set_content() calls.
 
$output .= drupal_get_content($region);

  return
$output;
}
?>

Override the display of attachment /Add icons

Overriding the display of attachments -> Adding icons
Tested on Drupal 4.7

This override will add a column of icons corresponding to the the file attached. Hence, the file types (i.e. image, pdf) are more visible to the viewer.

First:

  • Create an 'icons' directory in default 'files' directory.
  • All icons should be of the same type, i.e. (png, gif)
  • Name the icons according to MIME types & sub-types as follows.
    • For image as a type, name the icon -> 'image'.[png|gif]
    • For text as a type, name the icon-> text.[png|gif]
    • For application as a type, name the icon -> sub-type.[png|gif]
    • Example of icons directory listing
      default.gif
      image.gif
      msword.gif
      pdf.gif
      rtf.gif
      text.gif
      vnd.ms-excel.gif
      vnd.ms-powerpoint.gif
      zip.gif

Note mime types look like this [image/jpeg] -> [type/sub-type]

Secondly:

Create template.php in desired theme directory with following code or if one exist append the following code

<?php
/**
* Overide the default theming for attachments
*/
function phptemplate_upload_attachments($files) {
 
$header = array('',t('Attachment'), t('Size'));
 
$rows = array();
  foreach (
$files as $file) {
    if (
$file->list) {
     
$href = check_url(($file->fid ? file_create_url($file->filepath) : url(file_create_filename($file->filename, file_create_path()))));
     
$text = check_plain($file->description ? $file->description : $file->filename);
     
$rows[] = array(theme('file_icon', $file) ,l($text, $href), format_size($file->filesize));
    }
  }
  if (
count($rows)) {
    return
theme('table', $header, $rows, array('id' => 'attachments'));
  }
}


/**
* Theme file icon according to mime type
* an 'icons' directory should be placed inside your default files direcotry
* containing the file icons. Icon names should follow their Mime sub-type.
* Apart from the two generic types 'image' & 'text'
*
* @param file drupal file object
* @return HTML themed icon
*/
function phptemplate_file_icon($file) {
 
//Check if directory icons in current theme exists otherwise, no point, right
 
$icon_ext = '.gif'; //Change your supported format png for example
 
$path     = file_directory_path().'/icons';

  if( !
is_dir($path) ) {
    return;
  }
 
//Get filemime type act accordingly
 
$mime       = explode('/', $file->filemime);
 
$type       = $mime[0];
 
$sub_type   = $mime[1];
 
$output     = '';
 
$defult_icn = $path.'/default'.$icon_ext;
  switch(
$type) {
    case
'image':
     
$img_path = $path.'/image'.$icon_ext;
      if(
is_file($img_path) ){
       
$output .= theme_image($img_path, $file->filename, $file->description);
      }
      else if(
is_file($defult_icn) ) {
       
$output .= theme_image($defult_icn, $file->filename, $file->description);
      }
      break;
    case
'text':
     
$txt_path = $path.'/text'.$icon_ext;
      if(
is_file($txt_path) ){
       
$output .= theme_image($txt_path, $file->filename, $file->description);
      }
      else if(
is_file($defult_icn) ) {
       
$output .= theme_image($defult_icn, $file->filename, $file->description);
      }
      break;
    case
'application':
     
$app_path = $path.'/'.$sub_type.$icon_ext;
      if(
is_file($app_path) ){
       
$output .= theme_image($app_path, $file->filename, $file->description);
      }
      else if(
is_file($defult_icn) ) {
       
$output .= theme_image($defult_icn, $file->filename, $file->description);
      }
      break;
    default:
      if(
is_file($defult_icn) ) {
       
$output .= theme_image($defult_icn, $file->filename, $file->description);
      }
      break;
  }
  return
$output;
}
?>

Change the $icon_ext variable to desired extension

Finally:

Feedback welcome, its my first and hopefully not last humble contribution to the very giving community of drupal.

Overriding drupal.css; two approaches

There are two methods to removing drupal.css from your theme in phptemplate.

The first cuts out the link from the $head variable. In page.tpl.php, replace your

<?php
print $head
?>

with (remove the space in 'style'):

<?php
print str_replace('<st yle type="text/css" media="all">@import "misc/drupal.css";</style>', '', $head);
?>

The other method requires overriding the stylesheet import themable function. Simply add this to your theme's template.php file:

<?php
function phptemplate_stylesheet_import($path, $media = 'all') {
  if (
$path != base_path() .'misc/drupal.css') {
    return
'<style type="text/css" media="'. $media .'">@import "'. $path .'";</style>';
  }
}
?>

Replace the leading 'phptemplate' of above function with your theme's name if you are using this code in a plain PHP theme.

Pager at top and bottom of comments

Originally posted as http://drupal.org/node/79352#comment-223909

This snippet places a pager at the top of the comments on a node, in addition to the default pager at the bottom. It is especially useful in forums. For an example theme called 'mytemplate,' add the following to template.php:

<?php
/**
* Add a pager at the top of a list of comments.
*/
function mytemplate_comment_wrapper($content) {
 
$comments_per_page = _comment_get_display_setting('comments_per_page');
 
$content = theme('pager', NULL, $comments_per_page, 0) . $content;
  return
theme_comment_wrapper($content);
}
?>

Details

The function comment_render() in comment.module calls theme('comment_wrapper', $output). This theming call will match any hook *_comment_wrapper(), or give up and fall back to theme_comment_wrapper() (which is also in comment.module). This is a great place to jump in and add an extra pager, because the default function only wraps the comments in a <div>.

Line-by-line, the above snippet does the following:

  1. Retrieve the number of comments per page, which is needed for the pager. This line is copied directly from near the top of comment_render().
  2. Prepend a pager to the content (which has already been given a pager at the end). This line is also found in comment.module and allows other theming applied to the pager to function properly.
  3. Use the default theming function from comment.module to wrap the whole set of pager + comments + pager in a <div>.

Placing ads in your theme

This script can be pasted into any .tpl file. See demo at thinktechno.com.

From http://drupal.org/node/48908

Ad File Format

Place code for each ad in a text file. Write "~" between ads.

~
ad code
~
ad code
~

Template Script

Place this script in your .tpl files where you want ads to appear. Specify the ad file you want the script to use, for example files/ads.txt.

<?php
$fcontent
= join('', file('files/ads.txt'));
$s_con = split('~', $fcontent);

$banner_no = rand(0, count($s_con) - 1);
echo
$s_con[$banner_no];
?>

Notes

  • The script will randomly display ads from the ad file.
  • Any type of ad is supported: Adsense, flash, banner, etc.
  • If you use a 468x60 banner as a header ad, all ads in that ads.txt file should 468x60. Other dimensions, like 120x600, will get corrupt your page layout.
  • You can use different ads in different parts of your page. To do so, create multiple ad files and specify the path to the one you want to use in the script for that part of the page.

Alternative for Multiple Ads

This code will let you have multiple ads randomly, as well as prevents duplication of items. Note that you MUST have at least the same number of ad entries in your ads.txt file that you define as being output (this example is for 8 ads, just add/delete the echo statements to change the number you would like output):

<div align="center">
<?php
srand
((float) microtime() * 10000000);
$fcontents = join ('', file ('files/theme_editor/gunmetal/ads2.txt'));
$input = split("~",$fcontents);
$rand_keys = array_rand($input, 8);
echo
$input[$rand_keys[0]] . "\n";
echo
$input[$rand_keys[1]] . "\n";
echo
$input[$rand_keys[2]] . "\n";
echo
$input[$rand_keys[3]] . "\n";
echo
$input[$rand_keys[4]] . "\n";
echo
$input[$rand_keys[5]] . "\n";
echo
$input[$rand_keys[6]] . "\n";
echo
$input[$rand_keys[7]] . "\n";
?>

</div>

Primary Links: "active" class for ancestors of current page and unique ID for each menu item

If you use menu.module to create your primary/secondary links, drop this php snippet into your template.php file to set an "active" class on the anchor elements for the current page and direct ancestors.

The code also gives each link a unique ID (based upon link title) that might be helpful if you are doing some CSS image replacement to spiff up your primary links.

<?php
/**
* Add frontpage 'active' state handling to menu links
* and section 'active' state handling when child is active
* and menu-[title] as css id
*
* modified from code originally posted at
* rogerlopez.net/handbook/primary_menu_tweaks
*
*/
function phptemplate_menu_item_link($item, $link_item) {
  static
$menu;
  if (!isset(
$menu)) {
   
$menu = menu_get_menu();
  }
 
$mid = $menu['path index'][$link_item['path']];
 
$front = variable_get('site_frontpage','node');
 
$attribs = isset($item['description']) ? array('title' => $item['description']) : array();
 
$attribs['id'] = 'menu-'. str_replace(' ', '-', strtolower($item['title']));
  if(((
$link_item['path']=='<front>') && ($front==$_GET['q'])) ||
    (
menu_in_active_trail($mid))){
 
$attribs['class'] = 'active';
  }
  return
l($item['title'], $link_item['path'], $attribs);
}
?>

Select the li element rather than a

<?php
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE) {
 
$active_class = in_array($mid, _menu_get_active_trail()) ? ' active-trail' : '';
  return
'<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) . $active_class .'">'. menu_item_link($mid) . $children ."</li>\n";
}
?>

Drupal 5

Note that it uses the path instead of menu item ID to determine which menu item to add the active class to.

/**
* Implementation of theme_menu_item().
*
* Add active class to current menu item links.
*/
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE) {
  $item = menu_get_item($mid); // get current menu item

  // decide whether to add the active class to this menu item
  if ((drupal_get_normal_path($item['path']) == $_GET['q']) // if menu item path...
  || (drupal_is_front_page() && $item['path'] == '<front>')) { // or front page...
    $active_class = ' active'; // set active class
  } else { // otherwise...
    $active_class = ''; // do nothing
  }

  return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) . $active_class .'">'. menu_item_link($mid) . $children ."</li>\n";
}

Use in Smarty

Just a note for users of the Smarty theme engine: this elegant and clean solution to the 'maintain active state for primary links'-problem also works for Smarty. Just put the above code in YOURTHEME\smartytemplate.php and change "phptemplate" to "smarty" (i.e. phptemplate_menu_item_link becomes smarty_menu_item_link).

Remove unwanted tabs from pages

Description

Some modules add tabs to pages that are not needed for general users, or not needed at all. You may wish to link to the page in a different way, as some users don't understand that they can click on the tab above a node.

There is currently no way to alter the hook_menu() generated tabs from code, so this function will find and strip out a tab based on its name.

Step 1 of 2

Locate your theme's template.php file. If one doesn't exist create an empty one. This is where you can place customisation PHP code.

Step 2 of 2

Custom functions placed in the themes template.php file should begin with the theme name. In the code snippet below replace "yourthemename" with the actual name of your theme, such as "bluemarine".

You may already have a '_phptemplate_variables' function defined depending on what theme you are using, if so do not include the function again from the snippet below.

<?php
function _phptemplate_variables($hook, $vars = array()) {

  if(
$hook == 'page') {
   
yourthemename_removetab('address book', $vars);
   
// add additional lines here to remove other tabs
 
}

  return
$vars;
}

function
yourthemename_removetab($label, &$vars) {
 
$tabs = explode("\n", $vars['tabs']);
 
$vars['tabs'] = '';

  foreach(
$tabs as $tab) {
    if(
strpos($tab, '>' . $label . '<') === FALSE) {
     
$vars['tabs'] .= $tab . "\n";
    }
  }
}
?>

The tab removal work is done in the yourthemename_removetab() function, pass in a plain text tab label, along with the PHPTemplate variables, and the function will remove the tab.

In the above example snippet the 'address book' tab added by the eCOmmerce package is removed from the users profile page.

Notes

  • Call yourthemename_removetab('tab name', $vars); for each tab you wish to remove.
  • No other modules need to be installed to use this.

Replacing certain taxonomy terms with icons

Description

This describes how to replace the display of certain taxonomy terms in the taxonomy terms list with image icons.

Step 1 of 2

In the phptemplate.php file of your theme directory, add the following function.

<?php
/**
* Replace display of certain taxonomy terms in taxonomy term list
* with icons.
*/
function phptemplate_links($links, $attributes = array('class' => 'links')) {
 
// configuration for this function:
 
$replacements = array();
 
 
// create a new element in array $replacements for each taxonomy term that will be replaced by
  // an image (only when it's in a class containing 'inline' and 'links', such as the $terms list.
  // In the replacements array, add any attributes you want in the <img> tag, such as 'src' and 'alt'
 
  // Mac-Intel
 
$replacements['taxonomy_term_2'] = array(
   
'src' =>  '/files/images/icons/mactel_icon.png',
   
'alt' =>  'Macintosh-Intel',
   
'id'  =>  'mac-intel',
   
'width' =>  '25',
   
'height'  =>  '25',
   
'class' =>  'taxonomy_image',
    );
 
 
// Mac-PPC
 
$replacements['taxonomy_term_13'] = array(
   
'src' =>  '/files/images/icons/macppc_icon.png',
   
'alt' =>  'Macintosh-PPC',
   
'id'  =>  'mac-ppc',
   
'width' =>  '25',
   
'height'  =>  '25',
   
'class' =>  'taxonomy_image',
    );

 
// Windows
 
$replacements['taxonomy_term_3'] = array(
   
'src' =>  '/files/images/icons/win_icon.png',
   
'alt' =>  'Windows',
   
'id'  =>  'windows',
   
'width' =>  '25',
   
'height'  =>  '25',
   
'class' =>  'taxonomy_image',
    );
   
 
// END OF CONFIGURATION
 
  // modify the links if class contains both 'links' and 'inline'
  // because these might be linked taxonomy terms
 
if (strstr($attributes['class'], 'links') && strstr($attributes['class'], 'inline')) {
    foreach (
$links as $key => $link) {
      if (
array_key_exists($key, $replacements)) {
       
// create image html code and then pass that as the content instead
        // of the title so that the image will be a link       
       
$img_tag = '<img ';
        foreach (
$replacements[$key] as $img_key => $img_attr_value) {
         
$img_tag .= $img_key. '= "' .$img_attr_value. '" ';
        }
       
$img_tag .= '/>';
       
$links[$key]['title'] = $img_tag;
       
$links[$key]['html'] = TRUE;
      }
    }
  }
  return
theme_links($links, $attributes); 
}
?>

Step 2 of 2

Now you need specify which taxonomy terms should be replaced, and what the img tag attributes should be for that replacement. You do this by adding or removing elements of the $replacements array, as seen in the snippet above. The example code here replaces taxonomy terms for operating system compatibility with an icon.

Taxonomy terms that do not match terms in the $replacements array will be left alone.

Notes

  • In the snippet above, this line:
    <?php
    if (strstr($attributes['class'], 'links') && strstr($attributes['class'], 'inline')) {
    ?>

    restricts replacement of taxonomy terms to ONLY the list of terms printed on the actual node. This is the code produced by the line
    <div class="terms"><?php print $terms ?></div>

    in the node.tpl.php file.

Simpler author / date / comment links

This is a very simple modification to the code you use in node.tpl.php so you can display your author/date/comments metadata a minimally.

If you want this:

By jibbajabba on 21 Feb | 1 comment

Rather than something like this:

February 21, 2007 - 10:06am
jibbajabba's blog | 1 comment

You can use this snippet in your node.tpl.php file:

By <?php print $name; ?> on <?php print format_date($node->created, 'custom', 'd M'); ?> |
<?php
 
print $node->comment_count . ' comment';
  if (
$node->comment_count != 1) { print 's'; }
?>

sort taxonony links ($terms) by vocabulary ($vid)

[Note: An alternative method using template.php can be found here: http://drupal.org/node/133223 ]

I figured out a way to sort term links by vocab - this actually works (!) in node.tpl.php 465 (that's only for phphtemplate themes). It does look a bit clumsy - If anyone can tidy it up a bit I'd be very grateful. It probably belongs in the template.php really ... but I'm happy to just have it working at all!

What it does is create a new set of links for each vocab group of terms rather than just having all terms in a single row. eg the code below would produce something like:

# Submitted by: JohnG on 08 Mar 2006
# Forums: newbies - hacking - drupal
# Articles: contributed - in good faith - GPL

Please note, that the code below only processes terms for a single vocab each time - in the example (vid) 27 and 28. You need to replace all the occurrances of 27 with the vid of your favourite vocab.

You can copy&paste these code-chunks as often as you like to cover all your vocabs if you like, as long as you remember to edit all the '27's correctly it will work.

One more thing, I put all instances of this vocab filter script between the <?php if ($terms): ?> ...  <?php endif; ?> tags and it works happily.

so it goes something like:


<?php if ($terms): ?>

<?php /* sort taxonomy links by vocabulary 27 */
$terms27 = taxonomy_node_get_terms_by_vocabulary($node->nid, 27);
if (
$terms27) {
  print
'<div class="terms">Forums: ';
     foreach (
$terms27 as $key => $term27) {
    
$lterm27 = l($term27->name, 'taxonomy/term/'.$term27->tid);
  print
$lterm27.' - ';
     }
  print
'</div>';
}
?>
<?php /* sort taxonomy links by vocabulary 28 */
$terms28 = taxonomy_node_get_terms_by_vocabulary($node->nid, 28);
if (
$terms28) {
  print
'<div class="terms">Articles: ';
     foreach (
$terms28 as $key => $term28) {
    
$lterm28 = l($term28->name, 'taxonomy/term/'.$term28->tid);
  print
$lterm28.' - ';
     }
  print
'</div>';
}
?>

repeat for each vocab vid as needed and then close the
<?php
if ($terms):
?>
thingamy:
<?php endif; ?>

enjoy!

Switching HTML based on active i18n language

This snippet works for multi-lingual sites that use the Internationalization module (aka i18n) . Depending on the current active language setting this snippet will render different HTML. E.g. if you want a different image, javascript or unique layout for French than for English.

<?php 
   
global $i18n_langpath;
    if (
$i18n_langpath == fr) {
     print
"HTML français ici";
     } else {
     print
"English HTML here";
     }
?>

For other languages use their appropriate two letter language code, e.g. "es" for Spanish, "de" for German.

You can add more 'if else ()' for sites with more than two languages.

Note:

This should be used as a last resort. Best practice is to use i18n native menu translation function and t() function to translate any text, e.g.
<?php print t("Sentance to be translated here."); ?>

Hope you find this helpful!

:-)

Transparent PNG in IE5 & IE6

In a recent project, we really needed to use transparent images for the logo and some background images. We wanted to avoid javascript solutions (in case the user had disabled it) and CSS hacks. We began searching for a better solution and located a php solution at http://koivi.com/ie-png-transparency. Some more searching on Drupal located a post, www.drupal.org/node/45585, and we contacted mfredrickson about collaborating on a module to provide this functionality. After a few emails it was obvious that the time required to build a module was not worth the short life to IE7. We started looking for a simpler way to implement this solution into Drupal.

Some quick modifications and tests provided good results for a quick and dirty implementation of the Koivi solution, but only for foreground transparent png's. Background png's in the CSS were not being corrected for IE5 or IE6. We are still working on the background image solution and will update this book page when we have it working. But as good luck would have it, for this project, the site appearance was acceptable to the client in IE5 & IE6 and pleasing in more standards compliant browsers. You can view the live png transparencies at www.qualstaffrm.com. The logo is a foreground image and the header and footer contain background transparent png's. Seeing the displayed results in Firefox and IE are interesting.

Here's how we did it in Drupal 4.7.4 using the Bluemarine theme as a base:

  1. copy the contents of the bluemarine theme directory to a new directory of your choice (mytheme)
  2. download the Full Source zip file from Koivi.com
  3. unzip the file and copy the 'replacePngTags.php' and 'spacer.png' files into your themes directory (mytheme)
  4. modify the page.tpl.php file in your 'mytheme' directory:
    add this one line to top of the file, before the DOCTYPE statement
          <?php ob_start(); ?>
          <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
       

    and add these four lines to the end of the file, after the closing html tag
          </html>
          <?php
           
    include_once 'replacePngTags.php';
            echo
    replacePngTags(ob_get_clean());
         
    ?>

       
  5. modify the style.css file in your 'mytheme' diredory:
    locate the '#logo img' style
          #logo img {
            float: left;
            padding: 0em 1.0em 0em 1em;
            border: 0;
          }
       

    modify the padding specification to zero and add the margin specification to desired results
          #logo img {
            float: left;
            margin: 0 1em;
            padding: 0;
            border: 0;
          }
       

we discovered that the image replacement process included any padding values in the size of the final image, possibly displaying a distorted image. declaring padding as zero and using margin to provide spacing around the image prevents any image distortion and provides the desired appearance.

A big 'Thank You' and kudos to Justin at http://koivi.com. More details and updates can be found on that site.

Two columns of teasers

These snippets allow you to show nodes in two columns on any teaser listing page. It works for PHPtemplate

in template.php add:

function _exampletheme_nodebreak($node) {
  static $count;
  if ($node->sticky) {
   return TRUE;
  }
  else {
    $count = is_int($count) ? $count : 1;
    $return = ($count % 2) ? FALSE : TRUE;
    $count++;
    //dprint_r('WOOT');
    return $return;
  }
}

in node.tpl.php add to the end (after all the node content)

<?php if (($page == 0) && _exampletheme_nodebreak($node)): ?>
<br class="clear" />
<?php endif; ?>

And add to your style.css, a something like:

#contentcenter .node.teaser {
  float: left;
  width: 223px; //for fluid layout use 50%
  margin-left: 20px; //for fluid layout set to 0;
  padding: 0;
}

Note that you can play aroun with the values and make it a three column mayout without much hassle too.

Zebra to taxonomy list

Do you like theme your taxonomy list with zebra?

You need change theme.inc

function theme_links($links, $attributes = array('class' => 'links')) {
  $count = 1;
  $output = '';

  if (count($links) > 0) {
    $output = '<ul'. drupal_attributes($attributes) .'>';

    $num_links = count($links);
    $i = 1;

    foreach ($links as $key => $link) {
      $class = '';

      // Automatically add a class to each link and also to each LI
      if (isset($link['attributes']) && isset($link['attributes']['class'])) {
        $link['attributes']['class'] .= ' ' . $key;
        $class = $key;
      }
      else {
        $link['attributes']['class'] = $key;
        $class = $key;
      }

  if ($count++ % 2) {
        $output .= '<li class="'. 'odd">';
        } else {
        $output .= '<li class="'. 'even">';}

      // Is the title HTML?
      $html = isset($link['html']) && $link['html'];

      // Initialize fragment and query variables.
      $link['query'] = isset($link['query']) ? $link['query'] : NULL;
      $link['fragment'] = isset($link['fragment']) ? $link['fragment'] : NULL;

      if (isset($link['href'])) {
        $output .= l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment'], FALSE, $html);
      }
      else if ($link['title']) {
        //Some links are actually not links, but we wrap these in <span> for adding title and class attributes
        if (!$html) {
          $link['title'] = check_plain($link['title']);
        }
        $output .= '<span'. drupal_attributes($link['attributes']) .'>'. $link['title'] .'</span>';
      }

      $i++;
      $output .= "</li>\n";
    }

    $output .= '</ul>';
  }

  return $output;
}

Original Code have extra_class to Add first and last classes. I think it is not very useful so just remove it or comment with // (but it is educational and should be there)

Hope be Useful.

Customising how your page headers are displayed for search engines

Background

The default way your page title headers appear in browser window's title bar and subsequently in search engine results like google.com is as follows:

Drupals default page header format
**********************************

(Site front page) SITE NAME | SITE SLOGAN

(All other pages) NODE TITLE | SITE NAME

This is widely regarded as the best approach, as people reading English read from left-to-right and are usually using a search engine to search for a topic, rather than a specific site. Therefore, it's arguably more ergonomic to leave the Drupal default.

Changing how your page titles are constructed

Drupal's flexibility allows you to change the way your page titles are constructed to whatever you want and dynamically, based on what page or section your content is in.

Step 1 of 1

The following only applies to Drupal sites using phptemplate based themes.

  1. Open your page.tpl.php file in a text editor like notepad.exe or equivalent.
  2. At the very top of the file, you should see the following line
    <title>
    <?php print $head_title; ?>
    </title>
  3. Change it to this:
    <title>
    <?php
    print "$node->title | $site_name | $site_slogan";
    ?>

    </title>
  4. As the variable names suggests, that re-orders your page tite to read: NODE TITLE | SITE NAME | SITE SLOGAN
  5. Upload your edited page.tpl.php file to your active theme folder for the changes to take effect.

Available variables

The following is a list of relevant variable names that are available to the page.tpl.php file by default.

For a full list of all the variables available, visit the page.tpl.php overview page in the phptemplate section of the handbook.

  • site: The name of the site, always filled in.
  • site_name: The site name of the site, to be used in the header, empty when display has been disabled.
  • site_slogan: The slogan of the site, empty when display has been disabled.
  • title: Title, different from head_title, as this is just the node title most of the time.
  • breadcrumb: HTML for displaying the breadcrumbs at the top of the page.
  • mission: The text of the site mission.
  • is_front: True if the front page is currently being displayed. Used to toggle the mission.
  • footer_message: The footer message as defined in the admin settings.

How other famous sites format their page title tags

If you're thinking of moving away from the Drupal default, here's a quick guideline to how other sites format their headers.

Drupals default page header format
**********************************

(Site front page) SITE NAME | SITE SLOGAN

(All other pages) NODE TITLE | SITE NAME
Headline only
Site name, headline
  • abcnews.com
    ABC News: Capsule Carrying Comet Dust Lands in Utah
  • slashdot.org
    Slashdot | NewtonOS Running on Linux PDA
  • chicagotribune.com
    Chicago Tribune | `A Million Little Pieces' shatters trust
  • npr.org
    NPR : Iran to Press Ahead on Nuclear Technology
  • usatoday.com
    USATODAY.com - New Orleans mayor invokes King legacy
Site name, section, headline
  • villagevoice.com
    village voice > news > Bloomberg Boots Yankee Stats by Neil deMause
  • canoe.ca/CNEWS
    CANOE -- CNEWS - World: 3 soldiers wounded in attack on Cdn convoy
  • bostonherald.com
    BostonHerald.com - Local / Regional News: MFA’S MLK Day surprise: Museum unveils new, youthful attitude
  • guardian.co.uk
    The Observer | Politics | Kelly told of schools sex crisis last year
  • news.bbc.co.uk
    BBC NEWS | Science/Nature | Stardust capsule returns to Earth
Headline, site name (Drupals default TITLE tag format)
  • Aftenposten.no
    Getting Vurderer tilsynssak mot kreftforsker - Aftenposten.no
  • wordpress.org
    Getting Started with WordPress « WordPress Codex
  • theregister.com
    Penguin steals HP, Compaq, DEC, Tandem vet | The Register
  • theonion.com
    Clinton Escapes Through Air Vent | The Onion - America's Finest News Source
  • wikipedia.org
    Search engine - Wikipedia, the free encyclopedia
  • boston.com/globe
    Patriots' title dream ends, 27-13 - The Boston Globe
  • nytimes.com
    Airstrike by U.S. Draws Protests From Pakistanis - New York Times
Site name, headline, date
  • cnn.com
    CNN.com - Stardust capsule lands in Utah - Jan 15, 2006
Site name, date, headline
site name only

Make images square

Often images are not square -landscape or portrait- which causes rendering problems or just ugly pages. Tables might have different sized rows or columns, inline images look different in each post, or rows of images appear horribly cluttered and inconsistent.
This function will add padding to your images, to make them appear in a square. But remember that this will override any padding applied to img in your stylesheets. This will also only work in image modules images, because you lmight notice that the PHP checks for sizes that are similar to the ones set for image module. That way we can be quite sure the image we display is generated by image module. "Quite", so not very sure!

This is the function you put in tempate.php, when using a phptemplate template.

<?php
/**
* Make images square.
*/
function newsphoto_image($path, $alt = '', $title = '', $attributes = '', $getsize = true) {
 
//always do getimagesize
 
if ($path && (list($width, $height, $type, $image_attributes) = @getimagesize($path))) {
   
//$sizes = _image_get_sizes(); /* To get the below stated IF dynamicly filled */
   
foreach (_image_get_sizes() as $size) {
      if(
in_array($height, $size) ||
        
in_array($width, $size) ||
        
in_array($height + 1, $size) || //rounding can cause the displayed to be one pix bigger or smaller then the size in image_get_sizes.
        
in_array($width + 1, $size) ||
        
in_array($height - 1, $size) ||
        
in_array($width - 1, $size)) {
       
//the difference between the real height and teh pico height, devided by two and a bordr of 2 pixels
       
$height = round(($size['height'] - $height)/2)+2;
       
$width = round(($size['width'] - $width)/2)+4;
       
$attributes['style'] .= 'padding:'. $height .'px '. $width .'px;';
        break;
      }
    }
  }
  return
'<img src="/'. check_url($path) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $image_attributes . drupal_attributes($attributes) .'/>';
}
?>

Customising how links ($links) are displayed in your pages

Note that thispage is for Drupal 4.6 and 4.7. The API for links has changed with Drupal 5 (http://api.drupal.org/api/HEAD/function/theme_links).

This describes how to override the default layout for links when using phptemplate based themes.

Step 1 of 2

In a text editor like notepad.exe, create a file called template.php using the the following snippet. If you already have a template.php file, simply add this snippet to your existing one.

<?php
function phptemplate_links($links = array(), $delimiter = ' | ') {
 
/**
* catches the theme_links function and calls back a link.tpl.php file to determine the layout
*/
 
return _phptemplate_callback('links', array('links' => $links, 'delimiter' => $delimiter));
}
?>

Step 2 of 2

The template.php snippet catches the default theme_links layout before it's displayed and looks in the same folder for a links.tpl.php file which determines the new layout.

A very simple/shortened example of a links.tpl.php file maybe illustrated as follows....

Using a text editor like notepad.exe or other, paste the following snippet and save as links.tpl.php. Upload your new links.tpl.php file to your active theme folder.

<?php

 
/**
* to change the delimiter, just modify the $delimiter value
* Add in other fixed links to the link array by adding something like
* $links[] = l('link text', 'link path');
*
*/

$delimiter = "|";
// Display the left cap of the 'button bar'
print "[";
$link_count = count($links);
$current = 1;
foreach (
$links as $lnk ) {
       
// Print the link
   
print $lnk;
       
// Only print the delimiter if not the last link
   
if ( $current < $link_count ) {
        print
$delimiter;
    }
   
$current++;
}
// Display the right cap of the 'button bar'
print "]";
?>

Overriding the delimiter (the character that seperates links) in primary/secondary links

Description

This snippet is useful if you just want to override the default delimiter character on primary/secondary links. The delimiter is the character that seperates links.

For example: HOME ¦ NEWS ¦ SEARCH ¦ CONTACT.
Where delimiter = ¦

Notes
<div id="navigation">
<?php if ($links['primary']) : ?>
<ul id="main-nav">
<?php
$link_delimiter
= '|' // Change this to what you want the links seperated by
$delimiter = '</li>' . $link_delimiter . '<li>';
print
'</li>' . implode($delimiter, $links['primary']) . '</li>';
?>

</ul>
<?php endif; ?>
</div>

A way to add styling to individual parts of $links

I believe I have figured out a way to customize the individual components of the $links array, by adding css wrappers to them. Using the code from the main Customizing links page in your template.php file, create the links.tpl.php file and put this code into it:

<?php
/**
* to change the delimiter, just modify the $delimiter value
* Add in other fixed links to the link array by adding something like
* $links[] = l('link text', 'link path');
*
*/

$delimiter = "|";
// Display the left cap of the 'button bar'
print ""; // put whatever you want here - using nothing in this case

$link_count = count($links);
$current = 1;

foreach (
$links as $lnk ) {
   
$key = strstr($lnk, 'thumbnail');  // does a search in for thumbnail in the link
   
if ($key !== FALSE) {                 // if it doesn't find the string it will return FALSE, so this checks that its NOT false
     
print "<span class=\"link-thumbnail\">".$lnk."</span>";    // adds a span around link; change the class to whatever you want
   
}   
    else {
    print
"<span class=\"link-$current\">".$lnk."</span>" // for all other links it will create a class=link-2 etc.
   
}
  
// Only print the delimiter if not the last link
   
if ( $current < $link_count ) {
        print
' '.$delimiter.' ';
    }
   
$current++;
}
// Display the right cap of the 'button bar'
print ""; // put whatever you want here
?>

So far this works for me pretty well. You just have to change the search string to whatever you want. In theory (I haven't tried it yet), you can add elseif statements to test for more strings eg. post comment, login, register, etc. and change the class for each of the $links components. I am using span instead of div as I want the links to remain inline. When I used div wrappers, the links appeared on separate lines. I could have corrected it by CSS, but using spans is easier and more correct I think.

There is also another solution in the Drupal forums: http://drupal.org/node/49393#comment-92915. This works very nicely too.

Display only some of the links

The default template shows "read more | add new comment" but I want only the "add new comment" link.

You can add the above function to your template.php file and then simply create your links.tpl.php file with the following lines:

<?php
$link_count
= count($links);
print
$links[1];
?>

In your node.tpl.php template keep:

<?php
print $links
?>

wherever you want the links to appear.

That's it. It worked for me. Maybe, in your case, you have to change number '1' (the $links array index) to some other number (try 0 or 2, maybe). If you want a different $links template on a taxonomy or node-type basis, just have to add some conditionals to the links.tpl.php file, what it's well documented within this very site.

Customise "submitted by" information based on node type

description

You can display different author information and still respect the global "display post information" settings.

In this example I wanted flexinode-2 items to be displayed with the usual submission info, but have every other type respect the "display post information" setting.

Note that the content type I created, "news", goes by its flexinode name, and not by what I named it. Here is an example snippet for a node.tpl.php:

<?php   if (theme_get_setting('toggle_node_info_' . $node->type)) : ?>
<?php   if ($node->type == 'flexinode-2'):?>
<div  class="info"><?php print $submitted; ?></div>
<?php elseif($node->type): ?>
<div  class="info"> by <?php print $name; ?></div>
<?php endif; ?>
<?php endif; ?>

For my settings this displays:

NEWS NODE TITLE HERE
submitted by me on Tue, 04/26/2005 - 10:55am.

STORY NODE TITLE HERE
by me

PAGE NODE TITLE HERE

because I have set news and story nodes to display the submitted info while page is set to display no submission info at all.

Customising how the primary or secondary links are displayed

Description

These examples illustrate how you customise the default layout of primary or secondary links.

Notes

Drupal 4.6 / 4.7

Primary links snippet:

<div id="navigation">
<?php if (is_array($primary_links)) : ?>
<ul id="main-nav">
<?php foreach ($primary_links as $link): ?>
<li><?php print $link?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>

Secondary links snippet:

<div id="navigation">
<?php if (is_array($secondary_links)) : ?>
<ul id="main-nav">
<?php foreach ($secondary_links as $link): ?>
<li><?php print $link?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>

Drupal 5.0

In 5.0, $primary_links is no longer an array of HTML formatted anchors. Instead each primary link is an array of link attributes. To make it more confusing, one of these attributes, href, can have non-uri values, like <front>. You can see how I handled that exception below, but there may be others.

This is only one way to do this. There are probably better ways using theme engines.

Primary links snippet:

<div id="navigation">
<?php if (is_array($primary_links)) : ?>
<ul id="main-nav">
<?php foreach ($primary_links as $link): ?>
<li><?php

$href
= $link['href'] == "<front>" ? base_path() : base_path() . $link['href'];
print
"<a href='" . $href . "'>" . $link['title'] . "</a>";

?>
</li>

<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>

Secondary links snippet:

<div id="navigation">
<?php if (is_array($secondary_links)) : ?>
<ul id="main-nav">
<?php foreach ($secondary_links as $link): ?>
<li><?php

$href
= $link['href'] == "<front>" ? base_path() : base_path() . $link['href'];
print
"<a href='" . $href . "'>" . $link['title'] . "</a>";

?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>

Changing Primary Links to Images

On my site, I wanted to use images instead of text for the primary tabs, so I came up with a method to do so. You'll want to look for where the primary links are generated in page.tpl.php. The default is this:

  <?php if (is_array($primary_links)) : ?>
    <ul id="primary">
    <?php foreach ($primary_links as $link): ?>
      <li><?php print $link?></li>
    <?php endforeach; ?>
    </ul>
  <?php endif; ?>

And you'll want to replace it with this snippet:

<?php if(is_array($primary_links)) : ?>
  <ul id="primary">
    <?php
      $l2i
['pattern'] = '$(<a.*>)(.*)(</a>)$ie';
     
$l2i['themepath'] = url($directory);
     
$l2i['replacement'] = "\"\\1\".'<img src=\"$l2i[themepath]/icon_'.str_replace(' ','_',strtolower('\\2')).'.png\" alt=\"\\2\" />\\3'";
      foreach (
$primary_links as $link) {
        print
"<li>".preg_replace($l2i['pattern'],$l2i['replacement'],$link)."</li>\n";
      }
      unset(
$l2i);
   
?>

  </ul>
<?php endif; ?>

The images should be stored in your themes directory. You should make your icon names be in the format of "icon_menu_item.png", where "Menu Item" is the title of the Link. Here's some more examples:

Pictures - "icon_pictures.png"
Graphic Design - "icon_graphic_design.png"

Basically, you'll want to make the title lowercase, change the spaces to underscores, and wrap it with "icon_" and ".png".

Changing the delimiter on primary/secondary links

Description

This sinppet allows you to specify a delimiter for your primary or secondary links.

Notes

Primary links snippet:

<div id="navigation">
<?php if (is_array($primary_links)) : ?>
<ul id="main-nav">
<?php
$primary_link_delimiter
= '|' // Change this to what you want the links seperated by
$primary_delimiter = '</li>' . $primary_link_delimiter . '<li>';
print
'</li>' . implode($primary_delimiter, $primary_links) . '</li>';
?>

</ul>
<?php endif; ?>
</div>

Secondary links snippet:

<div id="navigation">
<?php if (is_array($secondary_links)) : ?>
<ul id="main-nav">
<?php
$secondary_link_delimiter
= '|' // Change this to what you want the links seperated by
$secondary_delimiter = '</li>' . $secondary_link_delimiter . '<li>';
print
'</li>' . implode($secondary_delimiter, $secondary_links) . '</li>';
?>

</ul>
<?php endif; ?>
</div>
Tips/tricks

Below is an example of the syntax used to change the delimiter to an image, instead of a character (thanks to pwolanin for this tip):

$primary_link_delimiter = '<img src="/path_to_image/myimage.gif">';

Display children of the actual menu node (Not all the menu tree)

In our web (http://www.fundacionalzheimur.org/formacion) we use the structure of the menu primary links, but we haven't found an automatic way of present, as in a book, the child nodes when displaying a menú node.

Our solution is including this php snippet in the node.tpl template, just before the content, in a div floating right:

.... <div class="itemtext">
<!-- Expanded Menu for navigation -->
<?php
$mid
= menu_get_active_nontask_item();
 
$active = menu_get_item($mid);
 
// this is a top-level link in your primary links
 
if($active['pid'] == variable_get('menu_primary_menu', 0)){
   
$output.= theme('menu_tree',$mid);
        print(
'<div id="menubox"><h2>navigate</h2>');
    print
$output;
    print(
'</div>');
  }

?>

<!-- end of menu  -->
    <?php print $content?>
  </div> .....

The CSS would be:
#menubox {
  display: block;
  float:right;
  width: 35%;
  margin-left: 18px;
  margin-bottom: 18px;
  padding-bottom: 5px;
  background: #fff url(images/Fondo_Logo_menu.jpg) no-repeat bottom right;
}
#menubox h2 {
background: #fff  url(images/block-quote_left.gif) no-repeat top left;
color: #6A0D22;
padding: 7px 7px 7px 37px;
margin-bottom: 2px;
line-height: 0.6em;
}

Showing the child items of primary links / Turning primary links into "primary menu"

I am very new to drupal. This works like i wanted it to, but i don't know if this is the best way to do it.

Showing the child items of primary links / Turning primary links into "primary menu"

This shows the whole menu tree of your primary links menu in your theme.

Edit your page.tpl.php

Edit the section where the primary_links are printed to show the whole primary links menu instead of only showing the first level links of your primary links menu.

<?php if (isset($primary_links)) { ?>
<div id="primary_menu">
<?php print theme('menu_tree',variable_get('menu_primary_menu',0)); ?>
</div>
<?php } ?>
Notes
  • You can define, what menu the primary links are from at "admin/settings/menu".
  • Another way to just show the second level of your primary links is to setup that the secondary links show the second level of your primary links (also at "admin/settings/menu")
  • The benefit of the snippet above is, that you can display N-levels of your primary links menu and that it is printed as an unordered list with css classes "active", "expanded" and "leaf" like by default. Just like the menu items in a block menu.

Customising the search theme form

This describes how to override the default SEARCH THEME FORM layout when using phptemplate based themes and the search.module with Drupal 4.7. You may find it easier to just edit the page.tpl.php file with your customised theme search form layout, but, here is an alternate method if you prefer to keep it seperate.

In the example shown below, the SEARCH THEME FORM that appears in your page header is the search form that is being overriden. Refer to the NOTES below for overriding the block search form and main page search form

Step 1 of 2

In a text editor like notepad.exe, create a file called template.php using the the following snippet. If you already have a template.php file, simply add it to your existing one.

<?php
function phptemplate_search_theme_form($form) {
 
/**
   * This snippet catches the default searchbox and looks for
   * search-theme-form.tpl.php file in the same folder
   * which has the new layout.
   */
 
return _phptemplate_callback('search-theme-form', array('form' => $form));
}
?>

Upload your new/edited template.php file to your active theme folder.

Step 2 of 2

The template.php snippet catches the default search box layout before it's displayed and looks in the same folder for a search-theme-form.tpl.php file which determines the new layout.

A very simple example of a search-theme-form.tpl.php file maybe illustrated as follows....

for use with Drupal 4.7.x

<label for="edit[search_theme_form_keys]">Search</label>
<input type="text" maxlength="128" name="edit[search_theme_form_keys]" id="edit-search_theme_form_keys"  size="25" value="" title="Enter the terms you wish to search for." class="form-text" />
<input type="submit" name="op" value="Search"  />
<input type="hidden" name="edit[form_id]" id="edit-search-theme-form" value="search_theme_form" />
<input type="hidden" name="edit[form_token]" id="a-unique-id" value="<?php print drupal_get_token('search_theme_form'); ?>" />

Snippet updated and tested with Drupal 4.7.x Feb 24th 2007

for use with Drupal 5.x

<label for="search_theme_form_keys">Custom Search</label>
<input type="text" maxlength="128" name="search_theme_form_keys" id="edit-search_theme_form_keys"  size="25" value="" title="Enter the terms you wish to search for." class="form-text" />
<input type="submit" name="op" value="Search"  />
<input type="hidden" name="form_id" id="edit-search-theme-form" value="search_theme_form" />
<input type="hidden" name="form_token" id="a-unique-id" value="<?php print drupal_get_token('search_theme_form'); ?>" />

Snippet updated and tested with Drupal 5.x Feb 24th 2007

Note: You don't need the opening and closing form action tags in your search-theme-form.tpl.php file. The <?php print $search_box;?> instruction in your page.tpl.php file does that for you.

Upload your search-theme-form.tpl.php file to your active theme folder.

example: changing the Search button text

Simply change the following line in the search-theme-form.tpl.php snippet from this:

<input type="submit" name="op" value="Search"  />

to this:

<input type="submit" src="images/go-button.gif"  name="op" value="GO!"  />

Which changes the button text from "SEARCH" to "GO!". You can obviously edit the Value="GO!" field to whatever text string you want.

example: changing the Search button to an image

This can be done using CSS, but, if you want to change the button to an image using your search-theme-form.tpl.php, you can simply change the following line:

from this:
<input type="submit" name="op" value="Search"  />

to this:
<input type="image" src="images/go-button.gif"  name="op" value="Search"  />

This replaces the SUBMIT button with an image called go-button.gif. Edit the path and filename to suit).

Notes

  • Thanks to Steve Temple, Jeff H and Philk for helping out with the finalised snippets.
  • Name the classes or change the layout to whatever you want.
  • To override the block search form, you need to change the first line in your template.php file to:
    function phptemplate_search_block_form($form) {
  • Please post tips/tricks discuss this handbook page at this thread.
  • Updated Feb 24th 2007 by Dublin Drupaller - the snippets above have been tested with Drupal 4.7.6 and Drupal 5.1

Primary links "active" or "selected" highlight snippet

description

This snippet allows you to highlight your primary links as being active when a node related to the primary link is being viewed.

usage

For use in your page.tpl.php or custom page-type.tpl.php file. The snippet automatically tags an "active" primary link as <li class="selected"> so you can change your style.css class for your primary links to display an alternate colour/image etc. for active primary links.

step 1 of 2

Using a text editor like notepad.exe, replace the existing primary links snippet in your page.tpl.php file by copying and pasting the following snippet.

<?php if (count($primary_links)) : ?>
    <ul class="primary">
         <?php foreach ($primary_links as $link): ?>
             <?php preg_match("/<a\s*.*?href\s*=\s*['\"]([^\"'>]*).*?> (.*?)<\/a>/i", $link, $matches); ?>
             <?php if (('/'.drupal_get_path_alias('node/'.$node->nid))==$matches[1]): /* if $node exists */ ?>
                <li class="selected"> <?php print $link?> </li>
             <?php elseif ('/'.arg(0)==$matches[1]): /* else try to use arg */ ?>
                <li class="selected"> <?php print $link?> </li>
             <?php elseif ((drupal_get_path_alias('node/'.$node->nid)=='node/') && (arg(0)=='node') && ($matches[1]=='/')): /* else if home */ ?>
                <li class="selected"> <?php print $link?> </li>
             <?php else: ?>
                <li> <?php print $link?> </li>
             <?php endif; ?>
         <?php endforeach; ?>
    </ul>
<?php endif; ?>

notes

  • This code might not do what you expect for psuedo nodes that are part of a module as it currently only uses arg(0).

Split a text field into multiple columns, like a newspaper/magazine article.

description

This recyclable snippet allows you to automatically convert a textfield in your custom-layout.tpl.php files to newspaper style columns.

This has been tested and works with Drupal 4.5.x, Drupal 4.6.x and Drupal 4.7.

usage

The code (called the drupalicious_convert2columns function) sits in your template.php file so you can call it from various tpl.php layout files, such as node.tpl.php, node-image.tpl.php, flexinode.tpl.php etc. using just one simple line of code.

The drupalicious_convert2columns function uses a simple table to convert your text field to columns. (A DIV version of the same will follow.)

Step 1 of 2

In a text editor like notepad.exe, create a file called template.php using the following snippet. If you already have a template.php file, simply add it to your existing one.

<?php
function drupalicious_convert2columns ($colcontent, $columns, $column_spacing) {
 
$coloutput "<table border=\"0\" cellpadding=\"$column_spacing\"><tr>";
 
$bodytext = array("$colcontent");
 
$text = implode(",", $bodytext); //prepare bodytext
 
$length = strlen($text); //determine the length of the text
 
$length = ceil($length/$columns); //divide length by number of columns
 
$words = explode(" ",$text); // prepare text for word count and split the body into columns
 
$c = count($words);
 
$l = 0;
  for(
$i=1;$i<=$columns;$i++) {
   
$new_string = "";
   
$coloutput .= "<td style=\"text-align:justify\" valign=\"top\">";
  for(
$g=$l;$g<=$c;$g++) {
    if(
strlen($new_string) <= $length || $i == $columns)
   
$new_string.=$words[$g]." ";
    else {
     
$l = $g;
    break;
      }
     }
   
$coloutput .= $new_string;
   
$coloutput .= "</td>";
  }
 
$coloutput .= "</tr></table>";
  return
$coloutput;
}
?>

Upload your new or edited template.php file to your active theme folder.

Step 2 of 2

Insert the following snippet in your custom-layout.tpl.php file.

<?php
$columns
= 3;
$column_spacing = 8;
print
drupalicious_convert2columns($textfieldname, $columns, $column_spacing);
?>

$textfieldname = the name of the field you want trimmed.
$columns = the number of columns, increase or decrease to suit.
$column_spacing = the "gutters" or column spacing in pixels, increase or decrease to suit.

notes

  • Because the drupalicious_convert2columns function is sitting in your template.php file, this snippet is recyclable so you can use the short step 2 of 2 snippet above as many times as you like.

Split theme('page') $content into multiple sections

This snippet is based on PHPTemplate. Other engines will work, but you'll have to put the helper function somewhere else.

If you need to break up the a list of nodes on a page, this simple solution should work for you.

  1. At the end of your node.tpl.php file, add an HTML comment to serve as a delimiter. I use <!-- NODESPLITDELIMITER -->.
  2. In your template.php file add the following function to split up content based on a delimiter:
    <?php
    function _contentsplitter($incontent, $delimiter, $headlength)
    {
     
    $outcontent = array();
     
     
    $tmp = explode($delimiter, $incontent, $headlength + 1);
     
      for (
    $i = 0; $i < $headlength; $i++) {
       
    $outcontent[0] .= $tmp[$i];
      }
     
     
    $outcontent[1] = $tmp[$headlength];
     
      return
    $outcontent;
    }
    ?>

    If you need more than 2 sections, you can modify this accordingly.
  3. If you have not already created a function _phptemplate_variables($hook, $vars) in your template.php file, create one like this:
    <?php
    function _phptemplate_variables($hook, $vars) {
       switch(
    $hook) {
         case
    'page' :
           
    $content_sections = _contentsplitter($vars['content'], '<!-- NODESPLITDELIMITER -->', 2);
           
    $vars['content_head'] = $content_sections[0];
           
    $vars['content_tail'] = $content_sections[1];
           
            break;
       }
       return
    $vars;
    }
    ?>

    Again, if you need more than 2 sections, modify accordingly. You could also just pass the sections array to your page.tpl.php file and then iterate over it.
  4. In page.tpl.php you now have access to two segments of the content. You could use it like this:
    <?php


    print $content_head;
    print
    $sidebar_left;
    print
    $content_tail;
    print
    $sidebar_right;
    ?>

And style the side bars according.

Good luck!

Trim a text field to a certain word length

description

This recyclable snippet allows you to automatically trim or summarise a textfield in your custom-layout.tpl.php files.

This has been tested and works with Drupal 4.6.x and Drupal 4.7.

usage

The code (called the drupalicious_summarise function) sits in your template.php file so you can call it from various tpl.php layout files, such as node.tpl.php, node-image.tpl.php, flexinode.tpl.php etc. using just one simple line of code.

The drupalicious_summarise function will trim the text field to the closest sentence, so it doesn't cut your text off mid-sentence.

Step 1 of 2

In a text editor like notepad.exe, create a file called template.php using the the following snippet. If you already have a template.php file, simply add it to your existing one.

<?php
   
function drupalicious_summarise($paragraph, $limit)
{
      
$textfield = strtok($paragraph, " ");
       while(
$textfield)
       {
          
$text .= " $textfield";
          
$words++;
           if((
$words >= $limit) && ((substr($textfield, -1) == "!")||(substr($textfield, -1) == ".")))
               break;
          
$textfield = strtok(" ");
       }
       return
ltrim($text);
    }
?>

Step 2 of 2

Insert the following snippet in your custom-layout.tpl.php file.

<?php print drupalicious_summarise($textfieldname,20);?>

$textfieldname = the name of the field you want trimmed.
20 = the number of words, to the nearest sentance, your text will be trimmed to.

notes

  • Because the drupalicious_summarise function is sitting in your template.php file, this snippet is recyclable. All you need to do is use the <?php print drupalicious_summarise($textfieldname,20);?> line each time you want to use it.

Request for increased functionality: drupalicious_summarise

The drupalicious_summarise function works great. Thank you very much for the contribution.

I've searched and found a method to supply a "...continue reading..." string by performing a test on str_word_count but I was hoping someone could suggest the same functionality directly within the original drupalicious_summarise function. I've tried many times and failed. The output of the drupalicious_summarise routine always results in a sentence that ends with a period. Surely there must be a way to append a second string (perhaps a variable) so that after the period would appear something like this, "......this is the end of the sentence. [..continued..]" directly within the drupalicious_summarise function.

Thank you in advance,
MOTE

EDIT:
I FINALLY figured it out! Here's the mod:

function drupalicious_summarise($paragraph, $limit)
{
$extra = "  [..continued..]";
$textfield = strtok($paragraph, " ");
while($textfield)
      {
           $text .= " $textfield";
           $words++;
           if(($words >= $limit) && ((substr($textfield, -1) == "!")||(substr($textfield, -1) == ".")))
               break;
           $textfield = strtok(" ");
      }
          if (str_word_count($text) >= $limit) $text = $text . $extra;
       return ltrim($text);
    }

Thanks for making me think! :-)

MOTE

Site recipes

This section will house various recipes for different types of Drupal sites. A recipe is a set of instructions that show how to prepare or make something.

As with cooking, there are many ways to assemble a Drupal site. You can arrive at widely different destinations with the same or similar modules. By examining these guides, you will be able to decide for yourself what is appropriate for your site. More information on basic site configrations can be found here.

A thread on various methods to configure a corporate brochure type site can be found here until it is compiled and added to the handbook.

Note: site recipes are opinions of the original author and represent a way to accomplish a goal and not necessarily the only way. Please contribute your methods to help those that come after you learn.

Adding 'Print-friendly' support for a calendar

Required
Print Module > http://drupal.org/project/print
Event Module > http://drupal.org/project/event

The excellent Print Friendly Pages module
http://drupal.org/project/print
does an excellent job on producing nicely formatted output suitable for printing.

However, as the 'Month-view' calendar produced by the events module
http://drupal.org/project/event
isn't actually a node, no 'printer friendly version' link appears.

Here's how to achieve this:

First thing, create a page using this code

<?php

print event_page($year = NULL, $month = NULL, $day = NULL, $view = NULL, $types = NULL, $tids = NULL, $duration = NULL)

?>

Make sure to use PHP as the input type.

This should output the calendar within a node, with the requisite 'printer friendly version' link appearing at the bottom.

Advanced Usage

Create a new menu item, that links directly to the 'print-friendly version'. EG http://example.com/node/123/print.
Create a new menu, and place this menu item as the sole item within in.
Go to 'administer > blocks'.
Enable the block for the new menu.
Configure the 'block visibilty', and set it only to appear on the Event page.

Slightly More Advanced Usage
Place the block in the 'content' region [This does not exist in all themes!]
The new block will appear below the calendar, more or less the same as a standard 'printer friendly version' link!

For Bonus Points:

Ammend the Print module to include event.css file.
By default, the print module will not include the CSS files that make the event calendar 'pretty'.

Open the print.node.tpl.php file within the print module folder.

Add this line

  <style type="text/css">
    @import url(<?php print  $base_path . drupal_get_path('module', 'event') .'/event.css';?>);
    </style>

below the

<style type="text/css">
      @import url(<?php print $node->printcss; ?>);
    </style>

This will add the event.css file to the print-friendly version pages.

Adding BBClone

If you want to add BBClone (excellent statistics/ web counter program) to your Drupal site, or want to continue using it from a previous site, here is one way of achieving it:

1) If you already have BBClone in the root directory of your server, leave it there.
2) If you haven't already, install Drupal and get it all set up.
3) Install or upgrade to the latest version of BBClone if you don't already have it. (Upgrade usually involves uploading all new files except the 'var' folder, and making sure you update the BBClone config.php file).

4) Put the following code into index.php in your Drupal root directory:

define("_BBC_PAGE_NAME", (drupal_get_title()?drupal_get_title():"YOUR SITE NAME"));
define("_BBCLONE_DIR", "bbclone/");
define("COUNTER", _BBCLONE_DIR."mark_page.php");
if (is_readable(COUNTER)) include_once(COUNTER);

Obviously, replace 'YOUR SITE NAME' with the name of your site. (The above tip and more help can be found at http://ichris.ws/little-tweaks).

I found that this code needed to go between:

include_once 'includes/bootstrap.inc';
drupal_page_header();
include_once 'includes/common.inc';

and
fix_gpc_magic();

BUT you may need to play around, depending on your own settings.

Now, BBClone should work OK. You may find some problems with the page titles not displaying properly in the BBClone 'Detailed Stats'. I am trying to work out how to fix this. Once I work it out, I'll post an update here (unless someone beats me to it).

If you are having problems, check the BBClone documentation; subdirectories often cause problems - double check the BBClone config.php file.

Otherwise, inserting the above code into the correct place on Drupal's index.php is all you should need to do as far as the Drupal end goes. I found that with the previous version of BBClone, the code worked better just above drupal_page_footer();

Although it seems logical, inserting the BBClone code into one of the template or putting in a block or the footer (all things I had tried) does not work. It will only work in index.php.

Basic private intranet web site

http://drupal.org/node/63456/

-->

This page relates to forum topic 62582, Access Control for Intranet/Private site. Please visit for more background information.

Description:
Very basic intranet site,suitable for small groups or small business. Geared towards documentation/knowledge management type of functions. More functionality and larger companies should probably look at other modules especially organic groups.

Purpose:
Provide a place for employees to contribute, maintain, and share knowledge/information as well as posting company policy/procedures. Some information is sensitive so it requires permissions to be restricted to a few appropriate roles. Of course, the entire site is protected from public view. Note: This site is separate from the company's public website; a different setup would be necessary to integrate the two. The basic access control requirements are:

  • All pages are protected from public view by default (but some information, like the front page, can be made public)
  • All pages have view and edit privileges by default to all users (wiki style if you will, and revisions should be maintained and accessible)
  • Should be able to restrict edit privileges to certain roles/users for specific pages or groups of pages (only view access)
  • Should be able to restrict view (and edit) privileges to certain roles/users for specific pages or groups of pages (no access)

Core Modules Used:

  • Book
  • Comments
  • Help
  • Menu
  • Path
  • poll
  • Search
  • Taxonomy
  • Tracker
  • Upload

Contributed modules:

  • Freelinking
  • Front_Page
  • Glossary
  • Node_privacy_byrole
  • Print
  • Statistics
  • Taxonomy_menu
  • URLFilter

What I did:

Access Control: I setup a few different roles, like SysAdmin, Management, Financial. Then anonymous users have no privileges at all and authenticated users have most privileges. Other roles have almost no privileges either, except for a few administrative privileges for SysAdmins. Then I used node_privacy_byrole to setup default privileges for each content type. Note that authenticated users have "permissions for permissions" by default as well, and by default all roles have both read and write permissions (except anonymous users).

I used the front_page module to provide a welcome screen to any random person that comes across the site and provided links to the corporate site. Then, (as part of the front_page.module), I set authenticated users to redirect to the "node" page - this allows "post to front page" to actually work. Note -I was glad to find I didn't need to enable anonymous users any permission to "access content" and front_page still worked - however, it does restrict the site just a little and makes it impossible to make any content inside the system viewable by the public. At the same point, this is how the site is supposed to work so I decided not to worry about it plus any information that should be available to the public should be published on the public website (yes that does include minor duplication of effort but worth it).

I setup the glossary.module and enabled the input filter in the default filter. I also used the urlfilter as well. I included the freelinking.module but it caused a few issues which I seemed to have resolved in an "ok" manner, by putting the filters at different weights. It goes html filter -> line break ->freelink->urlfilter->glossary. I'm still not 100% happy with the freelinking module and I might remove if i get any complaints from users. I mainly wanted it so they could easily link to other internal pages without needing html experience.

Used the print module and enabled it for authenticated users - also uploaded a different logo to use for the printed pages. Nothing important here.

I used the statistics module to give a rudimentary "tracking" of different users actions. Semi interesting to look at now as I can see what people are doing upon their first login and how they are exploring bits. Will probably not have much interest after the initial period unless I need to track "problems" - like an angry employee or something of that nature. There might be some better modules for this but since its supposed to be a fairly open system by nature its hard to "lock down".

I used Books for the more formal and static "policy/procedure" type of things and then used taxonomy and taxonomy menu for classifying and navigating the non-book pages. I created a hierarchical structure for classifications and made it a required field (no free tagging). I also created another vocabulary "Other" and enabled free tagging to allow people to add new terms and tags. I disabled this vocabulary in taxonomy_menu though since there will not be much structure to it. I also included a "project name" vocabulary that is also free tagging but is still part of the navigation thru taxonomy_menu. These three classifications should provide enough flexibility for my users while still providing some structure.

As part of the glossary, I also had to create a taxonomy vocabulary for glossary. (Note glossary.module creates its own menu entry, so I disabled the taxonomy_menu for the glossary vocab) Interestingly enough, when i created it I was required to tie it to a content type, but I edited to remove the content type tie and it bypassed that problem. (I didn't want "glossary" to be an option in the create content page). I also found out that "administer glossary" has no functional purpose so you can enable it or disable it with no change. I however wanted users to be able to add/edit glossary terms. To do this, you have to enable "administer taxonomy" which I didn't really like. The work around that I came up with is to add a menu item as a child to the glossary menu item and use the direct link to adding terms to the glossary taxonomy. Then I disabled the "categories" menu item under administration so that users wouldn't see it. Then I created a block with a link to the taxonomy administration page "/admin/taxonomy" and I left the title blank. Then I used the "PHP mode" to only show that block to the admin user (UID #1) but I could've easily modified that to allow any SysAdmins to see that block too. Here is the code in the for the block. (I modified this from the handbook entries on PHP scripts.)

<?php
global $user;
  if (
$user->uid == 1) {
    return
TRUE // block will be shown
 
}
  return
FALSE;
?>

Other Misc:
I re-arranged the navigation menu to have all the navigation elements near the top of the list (better usability i thought). I disabled the freelinking menu item. Used bluemarine theme because it was semi-close to company colors and didn't want to do a custom them. Used the options to include logo and favicon.ico but disabled the search bar in the theme and chose to enable the search block on the left hand side. Also, setup site so only admins could create users (no registrations) and modified the email sent out upon creation. I removed the listing of the password initially created so as to make it more likely they will change their passwords upon login. I enabled clean urls, mainly to make it easier for internal communication of links not search engines. i also setup a cron run to make the search index work. Disabled cache (not needed) and changed files to be governed by drupal (small company so little overhead). Modified the search ranking a little.

Future Work:
In the near future I might add in the notify or subscription modules to allow for email notifications. I might also look at including the tinymce module but I wanted to see how the users responded to this first before potentially complicating things. I have also read there can be a few issues with Tinymce and the different filters and I didn't want to spend time debugging it just yet. I also might have to change my PHP memory if my users start wanting to upload large files, but I haven't modified it for now. In the longer term, I might consider adding other functionality like liquid wiki, organic groups, calendars, CiviCRM, or other features but only as site grows or people request them. The only other consideration would be if the company wanted to allow clients to have access to any of this information like project status or allowing clients to submit issues. This would require reworking the permissions a bit - I think it would be fairly simple to define a new role a "general employee" and move all the default permissions to that role. Then disable all access to "authenticated user" and define a separate role for "client" and provide whatever privileges are there (mainly just view permissions) and update the node_privacy_byrole information accordingly.

Wiki Possibility:
This site was also a prime candidate for a private wiki. I decided to use drupal for a few reasons. 1) drupal provides the ability to add other functionality down the like, like a company calendar or integrating CiviCRM to be used internally. 2) The wiki's I looked at all seemed to have one problem or missing feature that didn't quite fit for me (as a newbie to those systems). I narrowed my choice in wiki's to TWiki (which I liked a lot but seemed more than what I needed and seemed to have difficult setup), MediaWiki (lots of reports of it being difficult to secure since its primary audience is public wikis) and a few others like docuwiki, pmwiki, wikkawiki,wackowiki, and moinmoin. 3) I was more familiar with Drupal.

Choices in Navigation - An Overview

The Drupal framework provides a number of options for implementing navigation. This overview provides a brief description of the navigation choices with associated pros and cons. It is aimed at new Drupal implementors.

The following five navigation methods are reviewed:

  • Primary/Secondary Navigation
  • The Menu Module (part of Drupal base installation, called core)
  • The Book Module
  • Taxonomy
  • Hard-Coded HTML Block

Arguably there is a sixth method: The Blended Approach. It isn't uncommon to combine two or more of these methods. For example, the Drupal.org sites uses a combination of the Primary/Secondary navigation with the Book Module.

Selecting a navigation system does not lock you into that system. Drupal allows multiple, simultaneous navigation systems which can be selectively presented to different users. If you find your site changes over time you can adapt your navigation accordingly.

The Role of Themes

Drupal separates presentation from data. The navigation methods provide the data while the presentation is specified within the theme template. For example, CSS styling for mouse roll-over effects is a template issue. JavaScript usually falls into this category too, though in some cases it may be appropriate to enter it via the navigation module's administration menu. This separation allows the navigation menu look to change with the theme.

Conventions

When a module is said to be part of core, it is included in the base Drupal installation and need only be enabled from the modules menu in administer. When a module is said to be a contributed module it must be downloaded, installed and enabled after the base Drupal installation.

A Drupal Block is a specific feature and not a general term. Information on the Drupal Block can be found in Block: controlling content in the sidebars.

Primary/Secondary Navigation

Primary/Secondary navigation is a site navigation feature Drupal makes available to template designers. It is administered in the administer >> themes menu. By convention it's placed at the top of the page to provide a location for horizontal navigation elements. In reality it could be placed anywhere the theme designer wishes. It is the only way to add horizontal navigation without directly embedding it in the template.

The method for entering menu links is not consistent among templates or Drupal versions. In some cases a text area is provided in the template to enter HTML. In other cases fields are given for menu text and link targets. Some templates provide the CSS to create a tabbed look if you use <ul>/<li> markup to create your navigation structure.

Breadcrumbs are an issue with this module. When you click on a link and are taken to a content page the breadcrumb will revert to simply "Home".

More Information:

Pros

  • Provides horizontal navigation without custom coding the template
  • Depending on template, allows the insertion of JavaScript

Cons

  • Depending on the template, may have to hand code the HTML
  • Breadcrumbs don't work

Menu Module

Menu is a core module for creating and managing menus in Drupal Blocks. A full description can be found in the handbook module description

Menu Blocks can created as required. The Menu Blocks can be displayed based on page name and access descriptors. Multi-level menus that stay expanded or expand when accessed are possible.

Menus were originally created to reference Drupal pages only. Some versions of Drupal allow references to external websites, but you must be careful if you use this. It isn't supported in all versions and an upgrade could break it. It also means you can't make a "Home" reference using "/" as a link. There is no option to create a divider or textual labels to allow link grouping or titles.

In Drupal 4.6 a companion module named Menu on-the-fly can be used to simplify work flow. The ability to add a menu entry for a content node is placed on the content edit/submit form. This feature is built into Drupal 4.7.

Breadcrumbs do work down to the content level. They will properly reflect the menu hierarchy, but when the final content page is selected the breadcrumb will revert to "Home".

More Information:

Pros:

  • Easy updating via administer >> menus, no HTML coding required
  • Can specify which pages the menus appear on
  • Simplified work flow with Menu on-the-fly

Cons:

  • Hit and miss support for referencing external websites or "/"
  • Can't create labels or dividers
  • Breadcrumbs don't work to content level

Book Module

The Book Module is part of core and is slightly misnamed. It's really a hierarchical organization module, making it an option for site navigation. The Book module handbook page describes the module in the context of a book, but think site navigation when you read it!

When Book pages are created they are placed in a page hierarchy and receive automatic menu entries as well as other navigation elements. The page placement options are integrated into the content creation screen simplifying work flow. Book pages are standard Drupal nodes and can contain PHP.

The Book module has specific behaviours. Child pages are given navigation links from the parent page and other navigation elements such as next page and previous page are added. These behaviours either save a lot of work or render this option useless.

The page module is one of the few choices where breadcrumbs work correctly to the lowest page level.

More Information:

Pros:

  • Best option for providing working bookmarks
  • Navigation and content creation work hand-in-hand
  • Automatically creates next, previous, and up links

Cons:

  • Everything must fall within the book hierarchy or breadcrumbs break
  • Has specific behaviours which are not trivial to change

Taxonomy

The Taxonomy module doesn't do navigation, it allows you categorize your content so it can then be referenced by a navigation system. It is part of the Drupal core. In combination with other modules or PHP code you can create a menu system and automatic page placement simply by maintaining the content taxonomy.

The simplest method to provide navigation based on taxonomy is the Taxonomy Menu module. Taxonomy Menu is a contributed module and must be installed. It allows you to tap into the taxonomy structure for navigation. There are a plethora of other modules that leverage taxonomy, making it a very powerful way to organize your site.

If you're considering taxonomy based navigation system because of the suite of available support modules you should plan time to learn how these modules interact with each other. Taxonomy is indispensable for particular site architectures but overkill for smaller sites.

More Information:

Pros:

  • Particularly useful for sites with a large amount of content
  • Simplifies content management with automatic page placement.
  • Breadcrumbs work correctly with some implementations

Cons:

  • Much of the benefit of taxonomy comes with the other support modules, adding complexity
  • Overkill for small sites or relatively static sites

Hard Coded Block

If none of the previous navigation options meet your requirements you can create your own menu. For some types of JavaScript menu systems this is your only option. It is also your only option if your menu includes unlinked labels, dividers, or some types of HTML elements. Creating your own menu involves creating a Block and inserting the navigation HTML into the block.

You may be able to leverage some of the other menu types by calling the menu creation code with a PHP code snippet. This blended approach allows the leveraging of other Drupal modules.

Breadcrumbs will not work with this type of menu unless you are building on a module that manages breadcrumbs.

More Information:

Pros:

  • Extremely flexible
  • Can leverage other menu options with PHP
  • Only way to use unlinked labels and HTML elements in your menu

Cons:

  • HTML coding required to update the menu (highly manual)
  • Breadcrumbs don't work
  • Sophisticated navigation systems could require extensive coding or large amounts of manual labor.

Configuring Drupal for Media: a step-by-step recipe

As I've been working on the artist configuration for CivicSpace, I decided to document the process of configuring a site, step-by-step. What is included here is a fairly detailed account of the process of configuring a site and additional modules. If it seems like a lot of steps, it is! Hopefully this will help some newbies get an idea of the basic steps involved with configuring a site, as well as to highlight how an installer/configurer could minimize the time spent configuring a site. Included is the final configured site, for educational purposes only!
Note: This was originally posted on DrupalART.org but I decided to repost it here for more visibility/feedback.

-zirafa

Drupal Artist/Media Configuration
-------------------------------------------------------------------------------------
You can either:
1) Follow the instructions below to configure Drupal manually
2) Install Civicspace using the Artist Profile which will render the same end result
-------------------------------------------------------------------------------------
General Steps:

  • Install Drupal
  • Create the first user, roles
  • Install additional (contributed) modules
  • Install phpTemplate theme (and theme engine)
  • Configure the theme (change logo, links)
  • Configure Blocks
  • Configure General Settings
  • Configure Modules

-------------------------------------------------------------------------------------
Install Drupal 4.6

Downloaded Drupal 4.6 from Drupal.org and followed the installation instructions written here.

Note: I used Site5's (site5.com) web control panel which has phpMyAdmin and other useful features.

First Steps

First Steps

1) Navigate to your site front page and follow the online instructions to create the first account. I often choose the name "admin" for the first account. Make sure you remember the username and password for this first account (User #1) as it has special privileges. It will ALWAYS bypass access control or permission checks, and you will need it to upgrade to later versions of Drupal.

2) Create some user roles.
NAVIGATION: administer » access control » "roles" tab
-anonymous (visiting users who aren't logged in)
-authenticated user (users who are logged in but are still strangers)
-web maintainer (for those users who help add content to the site)
-web admin (for those users who can change the site configuration, lots of permissions)

I setup the roles as above because it allows for a few basic levels of permission control. You can specify exactly what permissions each role has by going to
NAVIGATION: administer » access control

and you can change a user's role (by default it will be 'authenticated user') by going to
NAVIGATION: administer » user
and clicking the "edit" link next to a user. You may want to create a new user with your real name (i.e., Bob) and change its role to 'web admin', so later you can just login as "Bob".

Install and Turn on Modules

Install Additional (Contributed) Modules
Download the contributed modules below from here. Make sure you match the module version with your Drupal version! For instance, you can only install Drupal 4.6 with modules that are also tagged as Drupal 4.6.

Installing contributed modules involves uploading the files to your 'modules/' directory and following each module's INSTALL.txt. Sometimes you will have to run a .sql script in order to create new tables in your database.

Contributed Modules

  • audio
  • event
  • flexinode
  • front_page
  • image
  • img_assist
  • location
  • playlist
  • tinymce
  • video

Turn on all necessary modules:
NAVIGATION: administer » modules

  • aggregator
  • audio
  • blog
  • comment
  • contact
  • event
  • flexinode
  • forum
  • front_page
  • help
  • image
  • img_assist
  • location
  • node
  • page
  • path
  • playlist
  • profile
  • story
  • taxonomy
  • tinymce
  • tracker
  • upload
  • video

Putting the theme together

Install Your Theme

  1. Download the PHPTemplate Engine and put the files in the 'themes/engines/' folder.
  2. Download the CivicSpace Theme and put the files in the 'themes/' folder.

Turn on the CivicSpace Theme:
NAVIGATION: administer » themes
click the checkbox next to "civicspace" for 'Enabled' and 'Default'.

-------------------------------------------------------------------------------------
Adding Primary Links
NAVIGATION: administer » themes » civicspace » "configure" link

scroll down to primary links and fill in the appropriate information:

[primary link name] [URL]
home front_page
audio audio
video video
images image
playlists playlist
events event
forum forum
aggregator aggregator

Block Configuration

Block Configuration
NAVIGATION: administer » blocks

Blocks enabled:

Block Name Page restrictions
Random Image (restricted to image and image/* pages, left column)
Random Audio (restricted to audio and audio/* pages, left column)
Latest Videos (restricted to video and video/* pages, left column)
Calendar to browse events (restricted to event and event/* pages, left column)
Navigation (restricted to admin and admin/* pages, left column)
Who's Online (restricted to front_page, left column)

General Settings

General Settings Configuration
NAVIGATION: administer » settings

Name of site:[replace with your site's name]
Email Address: [replace with your email address]
Default Front Page:node (later changed to front_page by the front page module)
Clean URLS:enabled
Default Time Zone:[replace with your time zone]

You can optionally change any other settings, but I usually don't.

Module Configuration

Modules Configuration
Now that the basic site is setup, you will need to setup each module.

Event, Location, and Flexinode Configuration

Event, Location, and Flexinode Configuration

In order to use the event and location module, we need to create a new flexinode. A flexinode is basically a flexible way to create a new content type in the database.
Conceptually it is a bit unintuitive, but the basic steps are:
1) create a new flexinode called "event" (creates a new place in the database for events)
2) "event-enable" the flexinode (attaches event information to the flexinode event)
3) "location-enable" the flexinode (attaches location information to the flexinode event)

STEP 1 ::: Create a new flexinode type called, "event"

NAVIGATION >> administer >> content >> content types >> "add content type" tab

Enter the following for the content fields:
Content type name: event
Description: a way to upload events to the website
Help text: Enter the necessary information about your event in the appropriate fields.

click submit to finish creating the flexinode.

STEP 2 ::: Add some fields to your flexinode
NAVIGATION >> administer >> content >> content types >> "list"

A title field is automatically generated. Let's add a description field, too.
Click "add text area" under operations for the event flexinode.

Field Label: event
Description: add a new event
Default Value: [leave blank usually]
Lines: 5
Required field: no
Show in teaser: yes
show in table view: no
Weight: 0

click submit to finish configuring fields for your flexinode.

STEP 3 ::: "Event-enable" and "Location-enable" your flexinode
NAVIGATION >> administer >> content >> "configure" tab >> "content types" sub-menu item

click the "configure" link next to the event content type

-Show in event calendar: all views
-Scroll down to Locative Information and check the box, "Enable for locations"
configure the sub-options for what type of location information to collect.
-Attachments: enabled (for attaching flyers or pdfs about the event)

Click submit to finish configuring settings for flexinode.

STEP 4 ::: Test it to see if it works and optionally configure the location and event modules

For 4.6
NAVIGATION >> create content >> event
You should see fields for title, date & time, location, description, and attachments for event.

Optional configurations for event, such as the calendar view:
NAVIGATION >> administer >> event

Optional configurations and settings for location:
NAVIGATION >> administer >> location

For 4.7
NAVIGATION >> create content >> settings >> event
You should see fields for title, date & time, location, description, and attachments for event.

Optional configurations for event, such as the calendar view:
NAVIGATION >> administer >> settings >> event

Optional configurations and settings for location:
NAVIGATION >> administer >> settings >> location

Aggregator Configuration

Aggregator Configuration
NAVIGATION: administer >> aggregator >> "add category" tab

I created three aggregator categories:
podcasts
video
images

The idea is that we can subscribe to other people's media feeds so that we can import and share it on our site.

Go ahead and add a new podcast feed by going to
NAVIGATION: administer >> aggregator >> "add feed" tab

Something like the a BBC podcast should work...make sure you categorize it as audio!
You can add more if you wish.

Audio Configuration

Audio Configuration
NAVIGATION: administer >> settings >> audio

You need to visit the settings page at least once to initialize some things. Also make sure your getID3 path is correct, and that you downloaded and installed the getID3 libary properly (instructions are in the audio/ directory)

It is also sometimes confusing if the upload module is enabled, so you should probably turn it off:
NAVIGATION >> administer >> content >> "configure" tab >> "content types" sub-menu item
click the configure link next to audio, then scroll down and set
Attachments: Disabled

Blog, Comment, Contact, Forum Configuration

Blog Configuration
If the module is turned on, you should see "personal blog entry" under NAVIGATION >> create content

-------------------------------------------------------------------------------------
Comment configuration
NAVIGATION: settings >> administer >> comments >> "configure" tab
For optional changes.
-------------------------------------------------------------------------------------
Contact Configuration
NAVIGATION >> my account
Turn on your personal contact form so that other registered users can email you. If you will be the only person with a registered account, turn this module off.

-------------------------------------------------------------------------------------
Forum Configuration

To view the forums, go to www.yoursite.com/forum
NAVIGATION >> administer >> forums

If you wish to have a discussion forum, you can configure that here.

click "add forum" tab.
Forum name: General Discussion
Description: General Discussion Forum
Parent:
Weight: -3

clicks submit to finish adding a forum.

Image Configuration

Image Configuration:
NAVIGATION a>> administer >> settings >> image
You need to visit the image settings page at least once to initialize the module. You can also change the number of pictures displayed per page and thumbnail size.

Create new image galleries:
NAVIGATION >> administer >> image galleries >> "add gallery" tab
Gallery Name: My Pictures
Description: A gallery for my pictures.
Parent:
Weight: 0
click submit to finish creating a new image gallery.

Configure settings for the image content type:
NAVIGATION >> administer >> content >> "configure" tab >> "content types" sub-menu item
Click "configure" for the image content type.
Scroll down to bottom and set
Attachments: disabled

Add a new image to your site:
NAVIGATION >> create content >> image

View image galleries:
www.yoursite.com/image

Path Configuration

Path Configuration
NAVIGATION >> create content >> any content type
Path Alias: [insert alternative URL]

When you create a new page, story, event, or other content type, it is automatically generated a link URL.
Usually this URL looks something like www.yoursite.com/node/244. But often times you would like to create an alternative URL that is easy to remember. By typing in a path name when you create a new piece of content, you can have a cleaner looking URL.

Example:
NAVIGATION >> create content >> any content type
Title: My review of a new book
Path Alias: book-review
Description: the text for my review
click submit to finish.

To visit your newly created page, you can go to either:
Automatically created URL: www.yoursite.com/node/244
Using a path alias: www.yoursite.com/book-review

Playlist Configuration

Playlist Configuration

This module works hand-in-hand with the audio module. It will allow a user to make audio playlists from audio files that are uploaded with the audio.module. It only has one configuration option:
NAVIGATION >> administer >> content >> "configure" tab >> "content types" sub-menu item

click the configure link next to audio, then scroll down and set

Allow node to be added to audio album: enabled
Attachments: disabled

Tracker, TinyMCE, Img_Assist Configurations

Tracker Configuration

There are no configuration operations for this module, it simply adds a "track" tab to a user's profile, allowing you to see the latest comments and posts by a user. It's kinda pointless if you aren't running a multi-user site though.
-------------------------------------------------------------------------------------
TinyMCE Configuration

TinyMCE adds a formatting toolbar to text areas, similar to popular word processing programs.

I set TinyMCE to be off by default:
NAVIGATION >> administer >> settings >> tinymce

However you can change it to be on by default, and even create different tinymce profiles for different roles.
-------------------------------------------------------------------------------------
Img_assist

Img_assist works with the image module and creates an icon on the lower left of text areas. By clicking on the icon, it will allow you to insert images that are in the image galleries. You can change some settings so that it can only appear on certain pages, but I just left all the defaults in.
NAVIGATION >> administer >> settings >> img_assist

Upload, Frontpage, Video Configurations

Upload Module
This module allows you to specify which pieces of content can have file attachments or not. This is useful if you are posting something like a blog entry, but wish to attach a PDF file, similar to an email attachment.
This is sometimes confusing if another module has an upload feature, such as image or audio. To disable attachments for certain content types go to:

NAVIGATION >> administer >> content >> "configure" tab >> "content types" sub-menu item
click the configure link next to audio, then scroll down and set
Attachments: disabled

-------------------------------------------------------------------------------------
Front_page Configuration

This module lets you have a customizable front page for your site.

To edit your front page:
NAVIGATION >> administer >> settings >> front_page

-Anonymous users (not logged in)
Front page HTML or TEXT for users not logged in: [your front page text here]
Select whether you want this page to be a THEMED or FULL front_page: themed
Allow embedded PHP code in front page for anonymous users: yes
-Authenticated users (logged in)
Select whether you want this page to be a THEMED or FULL front_page: same

click save configuration to finish.

Don't forget to set your default front page to front_page:
NAVIGATION >> administer >> settings
Default Front Page: front_page

Other popular default front pages: blog, node
-------------------------------------------------------------------------------------
Video Configuration
NAVIGATION >> administer >> settings >> video

I didn't change any settings for videos.

Access Control and Permissions

Access Control Permissions
NAVIGATION >> administer >> access control

You can see the settings I set for each user. I gave web admin access to everything, including administration pages. The web maintainer has similar permissions as the web admin except that maintainers can't access administration. I didn't give authenticated user much access. You should change these permissions to suit your site.

Corporate Website - Basic Brochure

There are a lot of different ways to make a corporate website. The general basics of a corporate or brochure website are that it is typically not terribly community based and tends to have the following components:

* An About Page
* A Contact Us Form
* A Products (or services page)
* A simple front page with two paragraphs about your company and contact information plus a sidebar with links on the right or left which link to headlines from your five latest news articles
* A news page with news articles about press releases, events and news.

This configuration was discussed in the forums http://drupal.org/node/31896

Create a Druplicon cake

See Dries' post on this.

Creating a hierarchical website with taxonomy

Note: site recipes are opinions of the original author. Book module is a way to create hierarchical content out of the box.

This question is asked almost daily in the forums on drupal.org but there seem to be only partial answers. At best Drupal is not configured for a hierarchical site out of the box. At worst Drupal is not currently a good solution for a hierarchical site. The Taxonomy module helps but doesn't always work as I would like it to (an presumably other too). Yet, I need something like Drupal for its flexibility and extensibility. No CMS commercial or open source does everything you want out of the box. I prefer a CMS that is extensible and flexible even if I have to get my hands dirty to make it work the way I need it to.

So far I have a mostly complete solution that meets many of the requirements yet is not quite perfect either. In fact as I concocted this I kept thinking, there has to be a better way to do this but this is the best I have come up with. I post this in hopes others can suggest ways to improve this or

Assumptions

  1. A hierarchical site should start with one major category (home) and have sub categories and sub sub categories as deeply as needed.
  2. Each major category (and potential sub categories) should be editable by a different user or group (some users may need to edit in more than one category).
  3. Each category should have an "index.html" type page with an
    overview of the category.
  4. Navigation menus should conform to the hierarchy as well
  5. Nodes in the menu should be able to be reordered in custom orders
  6. Breadcrumbs should reflect the hierarchy
  7. Does not require extensive php coding but at least a decent understanding of html, css, basic php variables, and php include statements

Requirements

  • Drupal 4.6 or higher
  • Clean Urls enabled
  • Php template engine
  • Taxonomy module
  • Taxonomy Context or Taxonomy Associations Module

Making it work

  1. Install php template (directions here
    http://drupal.org/node/11810)
    and here
    http://support.bryght.com/articles/converting-css-html-design-phptemplat...,
    turn clean urls on, install the taxonomy context and or taxonomy associations modules.
  2. I am using the Box_Grey theme for this example. Download it here:
    http://drupal.org/project/box_grey or use your own theme.
  3. Set up a vocabulary and some terms in Administer-> Categories . I this example I set up a vocabulary called "Sections" and three terms:
    home, section 1 and section 2. Give each term a description. When setting up the vocaublary be sure to select the types of nodes you want to associate with this vocabulary (I choose book pages, pages, and images)
  4. Create a few new page nodes and assign them to home, section 1 or section 2.
  5. Create a new block in Administer -> Blocks -> Add Block. Choose Input Format "Php Code" and paste in the following code:
    // Not you may need to change the $vocabulary_id variable to the number of you master vocabulary or category ID. If you
    // don't know you can find out by going to administer -> categories -> edit Vocabulary. The url will be something like:
    // http://localhost/admin/taxonomy/edit/vocabulary/1 - the number one is the Vocabulary ID in this case
    <?php
    $vocabulary_id
    = 1; // vocabulary id for the "master" category
    $result = db_query("SELECT d.tid, d.name, d.weight FROM {term_data} d INNER JOIN {term_node} USING (tid) INNER JOIN {node} n USING (nid) WHERE d.vid = $vocabulary_id AND n.status = 1 GROUP BY d.tid, d.name ORDER BY d.weight");
    $items = array();
    $sub_items = array();
    while (
    $category = db_fetch_object($result)) {
    $output .= "<ul>"; // for term list
    $output .= "<li>" . l($category->name, 'taxonomy/term/'. $category->tid) . "</li>";
    $output .= "<ul>"; // for nodes list
    // call function to find all nodes in a term
    $sub_items = get_nodes_in_term($category->tid);
    // Add nodes to items array
    foreach($sub_items as $val) {
    $output .= $val;
    }
    $output .= "</ul></li></ul>";
    }
    print
    $output;
    function
    get_nodes_in_term($tid) {
    $sub_items = array();
    $sql = "SELECT node.title, node.nid FROM node INNER JOIN term_node ON node.nid = term_node.nid WHERE term_node.tid = $tid ";
    $result = db_query($sql);
    while (
    $anode = db_fetch_object($result)) {
    $sub_items[] = "<li>" . l($anode->title, "node/$anode->nid") . "</li>";
    }
    return
    $sub_items;
    }
    ?>

    This will give you a hierarchical menu. You may have to adjust your theme's stylesheet to get it to look the way you want it to.

  6. Turn on Taxonomy Context and make sure your top-level category has a description (if not add one under administer->categories-> edit term).

At this point we have a hierarchically organized site with a hierarchically organized navigation menu. Now we need to tweak a few things to get an "index.html" style page for each taxonomy term and sub-term. If I click on the navigation link for the term "home" as things stand now I will see first the taxonomy description, followed by the Section 1 and Section 2 subcategory headers (which are also links), followed by the teaser for the first node created under the "home" section. If you are like me - you just want a description of the category to server as the "index.html" page. To do this we need to change the template and modify the template for the theme.

Tweaking the theme to show an index page for each category

  1. First go to administer->settings-> Taxonomy Context and set "Show Subterm info" to disabled. This will get rid of the subcategory listings in our index page but not the node teasers.
  2. Now we have to chop up the template file and make it show the category term description if we are on a taxonomy term page and the normal node content if we are on a node. How? Here is the original page.tpl.php file for the box grey theme. :
           
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language ?>" xml:lang="<?php print $language ?>">
    <head>
    <title><?php print $head_title ?></title>
    <meta http-equiv="Content-Style-Type" content="text/css" />
    <?php print $head ?>
    <?php print $styles ?>
    </head>
    <body <?php print theme("onload_attribute"); ?>>
    <div id="header">
    <?php if ($search_box): ?>
    <form action="<?php print $search_url ?>" method="post">
    <div id="search">
    <input class="form-text" type="text" size="15" value="" name="edit[keys]" /><input class="form-submit" type="submit" value="<?php print $search_button_text ?>" />
    </div>
    </form>
    <?php endif; ?>
    <?php if ($logo) : ?>
    <a href="<?php print url() ?>" title="Index Page"><img src="<?php print($logo) ?>" alt="Logo" /></a>
    <?php endif; ?>
    <?php if ($site_name) : ?>
    <h1 id="site-name"><a href="<?php print url() ?>" title="Index Page"><?php print($site_name) ?></a></h1>
    <?php endif;?>
    <?php if ($site_slogan) : ?>
    <span id="site-slogan"><?php print($site_slogan) ?></span>
    <?php endif;?>
    <br class="clear" />
    </div>
    <div id="top-nav">
    <?php if (count($secondary_links)) : ?>
    <ul id="secondary">
    <?php foreach ($secondary_links as $link): ?>
    <li><?php print $link?></li>
    <?php endforeach; ?>
    </ul>
    <?php endif; ?>
           
    <?php if (count($primary_links)) : ?>
    <ul id="primary">
    <?php foreach ($primary_links as $link): ?>
    <li><?php print $link?></li>
    <?php endforeach; ?>
    </ul>
    <?php endif; ?>
    </div>
    <table id="content">
    <tr>
    <?php if ($sidebar_left != ""): ?>
    <td class="sidebar" id="sidebar-left">
    <?php print $sidebar_left ?>
    </td>
    <?php endif; ?>
    <td class="main-content" id="content-<?php print $layout ?>">
    <?php if ($title != ""): ?>
    <h2 class="content-title"><?php print $title ?></h2>
    <?php endif; ?>
    <?php if ($tabs != ""): ?>
    <?php print $tabs ?>
    <?php endif; ?>
           
    <?php if ($mission != ""): ?>
    <div id="mission"><?php print $mission ?></div>
    <?php endif; ?>
           
    <?php if ($help != ""): ?>
    <p id="help"><?php print $help ?></p>
    <?php endif; ?>
           
    <?php if ($messages != ""): ?>
    <div id="message"><?php print $messages ?></div>
    <?php endif; ?>
           
    <!-- start main content -->
    <?php print($content) ?>
    <!-- end main content -->
    </td><!-- mainContent -->
    <?php if ($sidebar_right != ""): ?>
    <td class="sidebar" id="sidebar-right">
    <?php print $sidebar_right ?>
    </td>
    <?php endif; ?>
    </tr>
    </table>
    <?php print $breadcrumb ?>
    <div id="footer">
    <?php if ($footer_message) : ?>
    <p><?php print $footer_message;?></p>
    <?php endif; ?>
    Validate <a href="http://validator.w3.org/check/referer">XHTML</a> or <a href="http://jigsaw.w3.org/css-validator/check/referer">CSS</a>.
    </div><!-- footer -->
    <?php print $closure;?>
    </body>
    </html>

    We want take everything up to the point where it says:
    <?php
    endif;
    ?>
    on line 53 and put it in its own file called header.tpl.php. Then we want to take everything from
    <?php
    if ($sidebar_right != ""):
    ?>
    to the end of the file and put this in a file called footer.tpl.php. In te node.tpl.php replace the existing html and php code with this:
      
    <td class="main-content" id="content">
    <!-- start main content -->
    <?php print($content) ?>
    <!-- end main content -->
    </td>

    Put this in a file called taxonomy.tpl.php:
    <td class="main-content" id="content-<?php print $layout ?>">
    <?php if ($title != ""): ?>
    <h2 class="content-title"><?php print $title ?></h2>
    <?php endif; ?>
    <?php if ($tabs != ""): ?>
    <?php print $tabs ?>
    <?php endif; ?>
           
    <?php if ($mission != ""): ?>
    <div id="mission"><?php print $mission ?></div>
    <?php endif; ?>
           
    <?php if ($help != ""): ?>
    <p id="help"><?php print $help ?></p>
    <?php endif; ?>
           
    <?php if ($messages != ""): ?>
    <div id="message"><?php print $messages ?></div>
    <?php endif; ?>

           
           
    </td><!-- mainContent -->
           
            Erase anything left in the page.tpl.php file and add this:
           
    <?php
     
    require('header.tpl.php');
      if (
    arg(0)=="taxonomy") {
      include(
    'taxonomy.tpl.php');
      return;
      }
      else {
      include(
    'node.tpl.php');
      }
      require(
    'footer.tpl.php');
     
    ?>

       </li>
       <li>What's going on here? Well the magic is in the if statement in the page.tpl.php file:
         if (arg(0)=="taxonomy") {}

    Drupal looks at the current url to see if it is a taxonomy page. So a url like this: http://localhost/taxonomy/term/1 would evaluate to true. If it is a taxonomy node it includes the taxonomy.tpl.php file which does not include the normal node content.

    If it is false it includes node.tpl.php which has the normal node content but nothing else.

Drawbacks

  • Main drawback is the term description is not a node so you can't do versioning and all the other good treatments that regular nodes use instead. One way around this is to use Taxonomy Associations which lets you displayed a specified node when a taxonomy term page is displayed.
  • Title of the node prints twice for some reason I have not figured out.

I'll add more as I discover additional methods (hopefully with the help of fellow Drupal users)

Creating context sensitive primary/secondary menus

Description
A new feature created by Richard Archer for 4.7 allows you to link the primary and secondary menus in a way that makes them context sensitive. A working example of context sensitive menus is available at http://www.massaflcio.org.

The links on the left are actually the primary menu. When you click the link at the top, "Massachusetts AFL-CIO" notice a second menu is show across the top of the page. This top menu is the secondary menu. Notice that the "Massachusetts AFL-CIO" button is highlighted, and the "Who we are" item in the secondary menu is also highlighted. If you click on the various secondary menu items, you'll notice the primary button remains lit. Also notice that if you click on "Staff" and then view a staff member by clicking on their title, both the primary and secondary menu items remain highlighted.

How to pull this off
While changes to the code were taken in 4.7 to make this feature possible, it still takes quite a bit of work to get working properly. Hopefully, the necessary changes to the Drupal core will be made in the near future to make this feature much easier to implement.

Step 1: Change menu settings
Go to the administer -> settings -> menus page. Set "menu containing primary links" to "Primary links". Also set "Menu containing secondary links to "Primary Links". Save the configuration.

Step 2: Create your navigation hiearchy
Go to the administer -> menus page. Create some menu items in the primary menu. For each of those menu items, create some children. The children menu items will act as you secondary menu. The parent menu items are your primary items.

THIS POST IS IN PROGRESS. DON'T DELETE.

Display the real login page for anonymous on 403

  1. Make 'denied' the 403 page.
  2. While you could pull the login form as the login block does and 4.7 and onwards it automatically submits -- but the registration tab would not appear. Solving this is very difficult, see http://drupal.org/node/88707 for further details (D6 solved this by rehauling the menu system from ground up). Add this to any hook_menu implementation in the $may_cache part. This is extremely important, this part must be cached.
    <?php
        $items
    [] = array(
         
    'access' => !$user->uid,
         
    'path' => 'denied',
         
    'callback' => 'mymodule_denied',
         
    'type' => MENU_CALLBACK,
        );
    ?>
  3. To your module, add:
    <?php
    function mymodule_denied() {
      global
    $user;
      if (
    $user->uid) {
        return
    t('Access denied');
      }
     
    drupal_goto($_GET['q'], 'login=1');
    }
    ?>
  4. To settings.php add
    <?php
    function custom_url_rewrite($op, $result, $path) {
      if (
    $op == 'source' && !empty($_GET['login'])) {
        return
    'user/login';
      }
      return
    $result;
    }
    ?>

Drupal 4.7 configuration all around short tips

Hallo, I'm a Drupal 4.7 user.

I will write this page and add any short configuration steps on a very Beginner's base.

Of course I have very restricted knowledge about PHP, MySQL and content managements matters, so please consider this pages as my intent to support the expansion of Drupal Users and for their needs to start using it with very simple and short defined tipps.

As I installed DRUPAL for the first time on my isp (internet service provider) web space, I strictly followed the suggestion from the DRUPAL Staff to install Drupal first on a virtual server at home. In fact I did that and for about a month I really tryed to let my DRUPAL (v.4.7) work smoothly in two environments.

First on a MS Windows 2000pro Workstation, second on a REDHAT(linux) Workstation.

... to be continued.

P.S.
Of course this place may be not the right one to publish this, then I will ask the DRUPAL's webmaster to help me to post it in the right place. Thank's

____________________________________________

My intent is to build an index of overall short tipps written as simple as possible for beginners who really wants to use this really fantastic open source Content Management Software, but have no the time to spend months in trying to get their TASK for a DRUPAL Site like I had and are even of OLDER age but want partecipate with their EXPERIENCES and KNOWLEDGE througout a wonderfull "tool" like DRUPAL is.

Theme Layout Steps

Here I'm back. The first Target for any Beginners using Drupal, as from my personal experience, ist that You feel free to test what ever you may want to change in a Test Environment on a Virtual Server.

I will mention here some of the action I did so to achieve as many intensive as possible an allround impression of the Drupal Structure.

Of course You may say that following the good Drupal Manual and Documentation will be the most rational and efficient method to learn all the Module fuctions and the implementation of Drupal.

Yes, but I write here as a Beginners and for Beginners who want and try something as fast as possible and find out that Drupal is really great even for people who have no Knowledge about PHP and CSS.

Theme Layout Steps:

1. After installation of a standard Drupal 4.7 I tryed to change a little bit the Theme. Bluemarine is the default Theme Drupal showup at the beginning. You will find all Theme in the root folder of Drupal in the "Theme" Folder. Before you start to experiment with the Theme of your choise I suggest you to make a copy of the Bluemarine" Theme in the theme folder and nameit "Bluemarine2".

Now you may notice that changing your default theme Bluemarine to the theme Bluemarine2 will result in exactly the same theme but give you now the possibility to start to experiment yourself changing the layout of the theme.

This you can do editing the styles file you will find in the theme folder
under your new copy of Bluemarine wich we called Bluemarine2.

Editing with a text editor the file styles.css you will see the complete list of style structure for the entire theme.

to be continued

Some More about THEME modification- CSS, SPREADFIREFOX

Hi, FOLKs

have again some time to share my last experience in setting THEMES in Drupal 4.7.3.

Ok first i want to say that if you may have any question relative to my (not mother-language) english writing, feel free to contact me.

OK I may continue some particolar statements about changing an existing THEME of Drupals THEMES
playing around with CSS files.

My experiences reffer to my favorite THEME SPREADFIREFOX, http://drupal.org/project/spreadfirefox what i also used for my two testing Drupal Site 1. ADACCS.AT http://www.adaccs.at/drupal/ and the 2. GARSCHAGEN Famlly Site http://www.adaccs.at/gdrupal/ .

Here is also a list of Modules I installed in both sites:

blockregion-4.7.0 used for handling when multiple themes are present
feedback-4.7.0 for create a feedback-form in an simple way
image-4.7.0 manage gallery of pictures and upload image
img_assist-4.7.0 support for image module
jstools-4.7.0 handling different jscript operation, i used it for collapsing Menu in Blocks
tinymce-4.7.0 expand editing functionality
webform-4.7.0 create different type of webforms
sitemenu-4.7.0 have a simple site-map
poormanscron-4.7.0 automativ cron jobs for user that do not have access to cron jobs to their isp
notify-4.7.0 module for supply user with mail-info about change of comments and content

You may see that both site are using the same THEME and you may see that I did some works (not yet perfectly implemented) to change the appareances of the THEMES.

Of course I used TWO very important tools to find out where I want to change parameters.
The first one is with FIREFOX 1.5.0.7 with Expansion MODULE WEB DEVELOPER TOOL BAR.
The secondone with IExplorer IE7 RC3 with Interenet Explorer Developer Toolbar vers 1.00.1517.0

In this way i could identify more easily the CLASS, DIV, TABLE entry in the 4 CSS files that define more specifically the THEME Layout of SPREADFIREFOX Theme.

So my tipp is that you do not resign if sometime you do not find the right entry that allow you to change somehow the color or font or dimension for some Layouts.

Cheer and best Regards

to be continued .....

HOW TO: Add the Drupal Expandable / Collapsible Fieldset Feature to your Node Pages

HOW TO: Add the Drupal Expandable / Collapsible Fieldset Feature to your Node Pages

For Drupal version 4.7.x
Note: This method has not been tested in Drupal version 5.x, but will most likely work.

The Drupal Expandable / Collapsible Fieldset Feature is the drop down area you see underneath a titled section. For example, on the Settings page (Administer » Settings) you see the titled sections such as:

General settings
Error handling
Cache settings
etc...

When you mouse click on any of these titles, the area expands below displaying the information corresponding to the section.

I found this feature useful to incorporate on node pages that are long. Instead of using a page break, a teaser, or read more option, you can use the Expandable / Collapsible Fieldset Feature. This method also works well when you want to initially display half of a table.

View a demonstration:

This example uses the Multiple Expandable / Collapsible Fieldsets as explained in the second section below.

Download some HTML example files:

You can download some example HTML files using the Expandable / Collapsible Fieldset feature and also the collapse.css file using the link below. This way you can experiment with the code offline.

Keep in mind that these example HTML files use local paths to the support files so that they will run from you local computer... so don't just copy and paste the example script and stylesheet code references to your custom code that will eventually be placed on your website.

http://singleinscv.com/help/collapsible/collapsible.zip (12 KB)

To use the Expandable / Collapsible Fieldset Feature, do the following:

(1) Copy the following CSS code to a new file and save it as collapse.css. This code was extracted from the original drupal.css file version 4.7.5 and should work with later drupal versions. It was modified to not display the fieldset border by adding the "Removes Fieldset Border" CSS parameters shown in the first lines of code.

collapse.css File

/* $Id: from the drupal.css file,v 1.147.2.8 2006/12/14 20:20:21 killes Exp $ */
/*
** Collapsing fieldsets
*/

/* Removes Fieldset Border */
html.js fieldset {
border: 0; padding: 0;
}

html.js fieldset.collapsed {
  border-bottom-width: 0;
  border-left-width: 0;
  border-right-width: 0;
  margin-bottom: 0;
}

html.js fieldset.collapsed * {
  display: none;
}

html.js fieldset.collapsed table *,
html.js fieldset.collapsed legend,
html.js fieldset.collapsed legend * {
  display: inline;
}

html.js fieldset.collapsible legend a {
  padding-left: 15px;
  background: url(menu-expanded.png) 5px 50% no-repeat;
}

html.js fieldset.collapsed legend a {
  background-image: url(menu-collapsed.png);
}

/* Note: IE-only fix due to '* html' (breaks Konqueror otherwise). */
* html.js fieldset.collapsible legend a {
}

(2) Upload the collapse.css file to your Site's /misc directory. (This is the same directory that contains the drupal.css file.)
Note: the collapse.js file is a core file and is already in your /misc directory.

(3) Copy the following code to your node page.

Single Expandable / Collapsible Fieldset:

<link rel=stylesheet type="text/css" href="/misc/collapse.css">
<script type="text/javascript" src="/misc/collapse.js"></script>

******* Enter the first part of your text here. *******

<form>
<fieldset class="collapsible collapsed">
<legend>More Information</legend>
<div>

******* Enter the last part of your text your text here. *******

</div>
</fieldset>
</form>

(4) Replace the:

******* Enter the first part of your text here. *******

with the first half of your text.

Next, replace the:

******* Enter the last part of your text your text here. *******

with the remaining last half of the text that will be initially hidden. This is the text section that is expandable and collapsible.

To use multiple Expandable / Collapsible Fieldset, use the following code:

Multiple Expandable / Collapsible Fieldsets:

<link rel=stylesheet type="text/css" href="/misc/collapse.css">
<script type="text/javascript" src="/misc/collapse.js"></script>

******* Enter the first part of your text here. *******

<!-- *** Begin collapsible 1 *** -->
<form>
<fieldset class="collapsible collapsed">
<legend>Read More &gt;&gt; Section 1</legend>
<div>

******* Enter the second part of your text your text here. *******

</div>
</fieldset>
</form>
<!-- *** End collapsible 1 *** -->

<!-- *** Begin collapsible 2 *** -->
<form>
<fieldset class="collapsible collapsed">
<legend>Read More &gt;&gt; Section 2</legend>
<div>

******* Enter the third (last) part of your text your text here. *******

</div>
</fieldset>
</form>
<!-- *** End collapsible 2 *** -->

That's it.

Sam Raheb (sam308)

HOW TO: Create an image gallery using CCK and Views (not Image Module)

Since Drupal is headed toward doing as much as possible with CCK and Views, I wanted to try to create a photo gallery using CCK and Views, rather than Image Module (with the included Image Gallery). I also wanted to use Imagecache, which allows for automatically re-sizing (and cropping) of images previously uploaded (Image Module does not do that).

[For those who don't know, Imagecache stores the original image file you upload, then creates and stores re-sized images based on your Imagecache presets. Down the road you can change those presets and regenerate all of your re-sized images, instead of having to re-upload all of them again. Very cool.]

This is what I used:

• Drupal 5.1
• CCK 5.x-1.3 (the module is called 'Content', not CCK)
• Imagefield 5.x-1.x-dev (2007-Feb-23)
• Imagecache 5.x-1.1
• Views 5.x-1.5
• Views Bonus Pack 5.x-1.0
• Custom Pagers 5.x-1.7
• Thickbox 5.x-1.x-dev (2007-May-05)

[Note: I did NOT have Image module installed. That may be important – not sure as I have not tried this with Image module installed.]

STEPS:

1. Install and enable all of the above modules.

2. Go into Imagecache administration (admin/settings/imagecache) and create your image presets. I created these two:

a. 'Square Thumbnail' – scale to 150 x 150 then crop to 75 x 75
b. 'Display' – scale to 650 x 650

On both of the above I chose 'Inside dimensions' for the 'Scale to fit' option in the scale section.

The 'Square Thumbnail' will be used in the gallery and the 'Display' will be used when viewing the full photos.

3. Go to administer content types (admin/content/types) and create a CCK content type for images. I called mine 'Image' (not to be confused with the Image content type that Image module creates) and used the lowercase 'image' for 'Type' (the machine readable name).

[Note: I don't think it matters what you call this CCK content type. However, I have not tried using anything other than 'image' for the 'Type' (machine readable name). If other modules used here look for a content type called 'image' (I don't know if they do) then possibly this whole thing won't work if you don't call it 'image'. Just guessing this could be a problem.]

4. Add an image field to your 'Image' content type. I called that field 'Image' also. [It definitely does not matter what you call this field.] When you create this field you can choose the Imagecache preset to use for teaser and body of nodes. Visit the 'Display fields' tab and change the label to be hidden, so as to not show the label 'Image:' on every picture.

5. Now create a View (admin/build/views). I called mine 'Image Gallery'. You just need a Page, not a Block. Choose 'gallery' for the Page URL (or whatever you want).

a. In the Page settings, choose the 'Bonus: Grid View' for the View Type. I used 24 nodes per page – shooting for a 6 wide by 4 tall grid (max size).

b. In the fields section add the fields 'Title' and 'Image' (your CCK image field created in 2 above).

c. On the image field, choose 'Do not group multiple values' for 'Handler' and chose the Imagecache preset size that you want to use for your gallery images in the 'Option' section. If you installed the Thickbox module, you should see an option called 'Thickbox: Square_Thumbnail' (where 'Square_Thumbnail' is the name of an Imagecache preset you created in 2 above). Choose that one.

d. In the Filters section choose

Node Published = Yes
Node Type 'is one of' Image (where 'Image' is what you called your CCK image content type).

e. The 'Bonus: Grid View' defaults to 4 columns wide. If you want to change that, find this code in the 'views_bonus' module:

function theme_views_bonus_view_grid($view, $nodes, $type) {
drupal_add_css(drupal_get_path('module', 'views_bonus') .'/views_bonus.css');
$fields = _views_get_fields();
$content = '<table class="view-grid view-grid-' . $view->name . '">';
$count = 0;

….. more code ……

}
$content .= '</table>';
if ($content) {
return $content;
  }

}

Cut an paste that entire function into your theme's template.php file and rename it from 'theme_views_bonus_view_grid' to 'yourthemename_views_bonus_view_grid'. I was using the Zen theme so I called the function 'zen_views_bonus_view_grid'.

You will see this code in two places

$count % 4 == 0

Just change the 4 (in both places you see it) to whatever number of columns you want. I changed mine to 6 to make a 6 column wide grid (table).

So at this point, if you have created some CCK image nodes, you should see a gallery of them here [yourdomain] /?q=gallery (if you used 'gallery' for your gallery View page URL).

There should be a table that shows each image with title underneath. Clicking on the image fires up Thickbox (if JavaScript is enabled). Clicking the title gets you to the image node itself.

Having the title there with a link to the image node is optional (don't add it in step 5b above if you don't want it). I thought it would be good to include because if JavaScript is not enabled, Thickbox just sends you to the image itself, which is not so great for an image gallery (it would be cool if somehow Thickbox could send you to the image node instead of just to the image file – any ideas?).

Now, it would be nice to have a pager on the image node page so that you can page through each image in the gallery when looking at image nodes.

6. Go into administer 'Custom pagers' (admin/build/custom_pagers). Click 'Add a new custom pager'. Most of the settings will be obvious, but these are two key ones:

Node Type = Image (or again whatever you called you CCK image content type)

In the "Use a view" section, pick the name of your gallery view (created in 5 above).

Now go back to your gallery [yourdomain] /?q=gallery and click one of the title links on the gallery images to go to an image node and you should see pagers on top or bottom (or both) of your image, depending on the settings you chose in your custom pager.

One final thing: to create multiple galleries, you can use taxonomy (admin/content/taxonomy). I created a taxonomy vocabulary called 'Image Gallery' and added terms to that vocabulary (e.g. Gallery 1, Gallery 2, etc.). When creating each Image you can pick a gallery (term) to put it into. You have to create a view for each gallery, and when you do, add 'Taxonomy' to the 'Filters' section of the view, picking what term you want the gallery to use. OR you can create one gallery view and 'expose' the taxonomy filter so people can choose what gallery to look at.

OK that's it. Hope this is helpful.

Many, many thanks to the folks who worked on these modules. It just plain worked and I didn't have to mess around at all. I was particularly amazed when I enabled Thickbox and the option to show the Thickbox version of my CCK image field just showed up in my gallery View options!

-------------

Addition May 21, 2007:
See Elliott Rothman's excellent screencast on this here:
http://www.theartlab.net/files/drupalschool009.m4v
(per his comment below)

-------------

Adding Taxonomy

Someone asked about how to create multiple galleries with taxonomy.

Steps:

1. Go to Administer/Categories (admin/content/taxonomy)

2. Click the 'Add Vocabulary' tab

3. Call the vocabulary 'Image Gallery'

4. In the 'Types' section, check the checkbox next to the CCK content type you created for images (mine is called 'Image')

5. Once the vocabulary is created, add terms to it:

'Gallery 1'
'Gallery 2'
'Gallery 3'
etc… or whatever you want to name your galleries

Now when you go to create or edit an image content type, you will see a drop down list for 'Image Gallery' so you can choose what gallery to put each image into.

6. Now go back to the View you created before for your gallery (admin/build/views), and you have two choices:

a. If you want just one gallery page, with a drop down choice to pick what gallery to view, you will edit your View. Go to the 'Filters' section of the your View and add 'Taxonomy: Terms for Image Gallery'. Then make that filter 'exposed'. This will expose a drop down choice in the page of the View so people can choose a gallery.

b. If you want to create a separate page for each gallery, then do the same as above, but do not expose the 'Taxonomy: Terms for Image Gallery' filter and instead, choose one of the terms (galleries) to show in that View. Save that View, then clone the View and repeat for as many pages (galleries) as you have.

That's it.

HOW TO: Drupal as database: A one to many scenario for displaying the data

The Scenario:

This scenario works from a point of view that a drupal node-type is equivalent to a database table. This scenario uses CCK, Views and Contemplate and optionally prepoulute to achieve its results. There are other scenarios and other modules that can relate and group nodes and node-types, which are not addressed within.

There exists a node-type: "type_a" which acts as the ONE table. This "type_a" node-type has other node-types related to it: "type_x", "type_y", "type_z" which act as the MANY tables (this scenario can be used with as many related node-types as needed). It is assumed that you understand that "type_a", "type_x", "type_y", "type_z" are generic names that you would replace with your own node-type names.

On a node-type "type_a" page, I would like to display a simple list (a View) of the related node-types to the node in question. Before this list (View) is displayed, I need to display several fields from node-type "node_a". I want tabbed navigation to appear such that there is an "All" tab, a "Type X" tab, a "Type Y" tab, and a "Type Z" tab, and depending on the users choice, the list (View) of related node-types displays only those node-types. In addition I need a simple "title" search mechanism for the related-nodes. And (optionally) I would like to have an "Add New" link for authorized users to create new a new node ("type_x", "type_y" or "type_z") with "type-a" already filled in.

Sorry for the long description of the scenario, but if one or all of these things is something you might need, then read on.

To implement this scenario, will require some HTML and PHP knowledge, and Clean URLs need to be turned on.

The Set-Up:

The Node Types:

The node-types (tables) are related via CCK's node-reference field type. This is important. You must first create the parent node-type "type_a" and after that create the child node-types "type_x","type_y", and "type_z". These three will have a node-reference field type, limited to "type_a" nodes. CCK is rather complex, and allows inserting existing fields into node-types. This example requires that the 3 related node-types use the same node-reference field. Of the 3 related node-types, create "type_x" first, add a new field - go to the "Create new field" section choose node-reference field type, you can name it what you like, but for examples sake, name it "parent_type_a", and fill in all the other options as required. When creating node-types "type_y" and "type_z" add a new field, go to the "Add existing field" section and choose the field "parent_type_a" that you created with node-type "type_x". Now you should have "type_x", "type_y", and "type_z" all with a common node-reference type field "parent_type_a". To verify, go to the main field list at admin/content/types/fields in the "Field name" column you should see "parent_type_a", in the "Field type" column you should see "nodereference" and in the "Used in" column you should see "type_x, type_y, type_z"

Assuming you have your node-types set-up, you should add some data into your system before moving on too far. Note down the node's nid value of a few of your node-type "type_a" nodes as they will be needed below for some testing.

The View:

Create a new View. You can name the view what ever you like in this example it will be referred to as "view_type_a_children"

Page:

  • Provide Page View: checked
  • URL: for testing purposes, use the same value used for name: "view_type_a_children"
  • Type: Table View

Table:

  • Your view needs to provide common information about each of the related node-types (x, y and z). Common meaning that all the related node-types can display the information - such as "Title", "Author", "Created" or "Updated", any field that starts with "Node: " should be fine.
  • I have only done this with a view type of "Table View", but the other view types should work also.

Arguments:

  • Add "Node Reference: Type A (parent_type_a)", set "Default" to "Return Page Not Found"
  • Add "Node Type", set "Default" to "Display All Values"

Filters:

  • Add "Node: Published", set "Operator" to "Equals", "Value" to "Yes"
  • Add "Node: Title", set "Operator" to "Contains", "Value" to (leave blank)

Save your view, of course, and do a little testing. You should be able to look at your view via it's URL "view_type_a_children" The page should say "Page Not Found". In the URL after "view_type_a_children" type in "/" and then one of your "type_a" node nids (example: view_type_a_children/14). The page should now display a list of all related nodes that are related to the nid you used. In the URL after "view_type_a_children" type in "/" node nid "/" and "type_x" (example: view_type_a_children/14/type_x). The page should now display a list of only "type_x" nodes that are related to the nid you used.

Contemplate:

Assuming the above tasks are set up and working, now we move into adjusting the node page output. This example uses the contemplate module, but in theory this could be addressed in your themes template files. This is now where you need some PHP and HTML knowledge

A word of caution regarding Contemplate. Sometimes some bad PHP code we may use (we all mistakes) will disallow you from getting into the edit template form. If that happens, the only way I've discovered to fix the problem is to go into the database and remove the data. Contemplate stores the template infomation in a table named "contemplate" - one row for each node-type that contemplate has been used with. You will need to delete the row (or fix it) for your node-type "type_a" If you are not confortable with that, I don't know that I would recommend going any further.

Another word of advice, don't write your code in the form's text-area box. Write your code in your favorite PHP or text editor and then copy and paste the code into the form.

While I am no expert, it appears that when using contemplate it overrides most of the output that maybe set up elsewhere, so you need to define the full output you want, so while this gives you a lot of control, it can sometimes take a while to get set up.

For each of your CCK fields the "standard" format follows (where {YOUR_FIELD_NAME} is your actual field's name)

<div class="field field-type-text field-field-{YOUR_FIELD_NAME}">
  <h3 class="field-label">{YOUR LABEL}</h3>
  <div class="field-items">
    <?php foreach ((array)$field_{YOUR_FIELD_NAME} as $item) { ?>
      <div class="field-item"><?php print $item['view'] ?></div>
    <?php } ?>
  </div>
</div>

You can arrange your fields in whatever order you want. You can use whatever HTML you like. You can define your own classes that can then be referrenced in your style sheet. It gives you almost complete control over what is output. If you get confused on field names you can use the "Variables" list. And if you don't like the results or are having trouble, you can always delete the template.

The Template - The Implementation

In this example, I will only be talking about the node-type template for the Body (not the Teaser nor the RSS), and I will just indicate "template" to mean that.

Step 1:

The scenario says "Before the list (View) is displayed, I need to display several fields from node-type 'node_a'."

In my template I include code as noted above in the CCK Overview, arranging those fields accordingly.

Test this, make sure your node page looks how you want before moving on
(go to node/x : x = nid of a "type_a" node).

Step 2:

The scenario says: "I would like to display a simple list (a View) of the related node-types to the node in question"

This requires inserting a view into the page. This topic is disucussed in detail in the Views Documentation section at Inserting Views

What we need

  • name of the view: "view_type_a_children"
  • the value of the "type_a" node's nid: stored in the variable $node->nid

The code (this comes after the code you created in Step 1, and should be enclosed in the PHP tags (<?php and ?>)

<?php

// load view
// using the views_get_view() function we can "load" a view into a local variable: $view
$view = views_get_view('view_type_a_children');

// once loaded we can adjust some of the parameters of the view
// one of the views arguments, if you remember, is "Node Reference: Type A (parent_type_a)"
// we can pass a specific value to the view, for this we want to pass the value of the "type_a" node's nid ($node->nid)

// argument values are passed via a PHP array so we need
$view_args[] = $node->nid;

// we are now ready to "build" and print out the view
// for this we use the views_build_view() function
// the last 2 values: "TRUE" and "20" allow us to have the list paged at 20 rows
print views_build_view('embed', $view, $view_args, TRUE, 20);
?>

Test this, you should see the node and a list of all related nodes beneath it. Make sure this is working before moving on
(go to node/x : x = nid of a "type_a" node).

Step 3:

The scenario says: "I want tabbed navigation to appear such that there is an "All" tab, a "Type X" tab, a "Type Y" tab, and a "Type Z" tab, and depending on the users choice, the list (View) of related node-types displays only those node-types."

Yikes, so demanding...

Let's break this down into
A: create ability to display nodes of all types or nodes of a specific type
B: create links for the options (links will be formatted as tabs)

Part 3-A:

The ability to display related nodes of all types or related nodes of a specific type, already exists, remember from our view, the second argument was "Node Type", we need to pass this "second" argument value to the $view_arg array.

from the code above, (this code gives us the "All" option) we used

<?php
// once loaded we can adjust some of the parameters of the view
// one of the views arguments, if you remember, is "Node Reference: Type A (parent_type_a)"
// we can pass a specific value to the view, for this we want to pass the value of the "type_a" node's nid ($node->nid)

// argument values are passed via a PHP array so we need
$view_args[] = $node->nid;
?>

We just need to add one line of code, at the bottom, to put the node-type into the arguments

<?php
$view_args
[] = {NODE-TYPE};
?>

We have 3 different node types so, if your following...

<?php
// for node-type "type_x" use
$view_args[] = 'type_x';

// for node-type "type_y" use
$view_args[] = 'type_y';

// for node-type "type_z" use
$view_args[] = 'type_z';
?>

How do we know which type the user wants to see?

First, we need to talk a little about Drupal paths (who knew you had to know so much...)

Hopefully by now you understand that in your URL "node/x" (where x = the nid of a node) displays the node page. You can mask it using the Path module, but even so "node/x" is still used by drupal, in the background. Maybe you have noticed that if you go to edit a node the path is "node/x/edit". If you have the book module enabled and you go to outline a node, the path is "node/x/outline". If you have statistics module enabled and you go to track, the path is "node/x/track"

Drupal divides the path into multiple values using the "/" character as the separator. Drupal interprets this path and then looks for instructions to know what to display. If Drupal looks and doesn't find instuctions for what to do, then it lops off the last value and looks again for instructions.

If you go to "node/x/hello" Drupal looks for "node/x/hello" instuctions, does not find any, then looks for "node/x" instructions which informs drupal to display the node page. If you go to "node/x/hello/my/name/is/john" you make Drupal look 6 times for something to do until it finally does.

I noted that Drupal divides the path into multiple values using the "/" character as the separator. Drupal allows you to extract those values by using the "arg() function. Starting with 0, you can find the value of each of the "args"

With "node/15"
- arg(0) = node
- arg(1) = 15
With "node/15/edit"
- arg(0) = node
- arg(1) = 15
- arg(2) = edit
With "node/15/hello"
- arg(0) = node
- arg(1) = 15
- arg(2) = hello

So now that we understand a little about Drupal paths, we can use this fuctionality to our benefit.

In our example with "node/x"
- arg(0) = node
- arg(1) = x
Our page is output, and currently our Views code with "view_type_a_children" produces all node-types

We could pass a node type through arg(2) to indicate the node type

So with "node/15/type_x"
- arg(0) = node
- arg(1) = 15
- arg(2) = type_x
Drupal would check for instructions on how to handle "node/15/type_x", not find any, would look for "node/15" instuctions which would show the node page

So if there is an arg(2) that will be our signal that the we should display a particular node-type. If arg(2) is blank that will be our signal to show all node-types.

So to this section of code...

<?php
// once loaded we can adjust some of the parameters of the view
// one of the views arguments, if you remember, is "Node Reference: Type A (parent_type_a)"
// we can pass a specific value to the view, for this we want to pass the value of the "type_a" node's nid ($node->nid)

// argument values are passed via a PHP array so we need
$view_args[] = $node->nid;
?>

we add a simple if/then (I've also changed the comments for clarity)

<?php
// once loaded we can adjust some of the parameters of the view
// one of the views arguments, if you remember, is "Node Reference: Type A (parent_type_a)"

// argument values are passed via a PHP array so we need
// the first arg we need to pass is the value of the "type_a" node's nid ($node->nid)
$view_args[] = $node->nid;
// the second arg we need to pass is the node type indicated by arg(2)
// if there is no arg(2) then "Show All" is assumed
if (arg(2)) {
 
$view_args[] = arg(2);
}
?>

We're not quite finished with this step - we still need to create the links, but you should be able to test this
With "node/x" you should see all related nodes
With "node/x/type_x" you should only see related nodes that are node-type "type_x"
With "node/x/hello" you should see no nodes at all, unless you have a related node-type named "hello"

Part B: create links for the options (links will be formatted as tabs)

Links are actually easy enough to create just with simple HTML, we just need to know the node's nid which we've already used, it's $node->nid. The href value is "http://www.example.com/node/{nid}/{node-type}"

As a first step we will put them in a simple unordered list, you should of course change www.example.com/ to your domain. Note this is HTML with PHP used just to print the $node->nid, so this section should not be enclosed it PHP tags. This section should come just before the Views code we did above.

<ul>
<li><a href="http://www.example.com/node/<?php print $node->nid ?>">Show All</a></li>
<li><a href="http://www.example.com/node/<?php print $node->nid ?>/type_x">Show Type X</a></li>
<li><a href="http://www.example.com/node/<?php print $node->nid ?>/type_y">Show Type Y</a></li>
<li><a href="http://www.example.com/node/<?php print $node->nid ?>/type_z">Show Type Z</a></li>
</ul>

Now we need to format the links as tabs.

I haven't discovered a function to just wrap my links as tabs - the various theme funtions make calls to the menu system, and since these aren't real menu items, that's not going to work...

So the HTML you need should copy your themes method of outputting tabs. You could also use your own classes and create some CSS to make them look however you want. In this example I'll just use the markup provided by the default garland theme.

One of the tabs needs to be set as "active" so we have to if/then each of the tabs
Again HTML code, so this section should not be enclosed it PHP tags

<div id="tabs-wrapper" class="clear-block">
<ul class="tabs primary">
<li<?php print (arg(2) ? '' : ' class="active">'?>><a href="http://www.example.com/node/<?php print $node->nid ?>">Show All</a></li>
<li<?php print (arg(2) == 'type_x' ? ' class="active" : ''>'?>><a href="http://www.example.com/node/<?php print $node->nid ?>/type_x">Show Type X</a></li>
<li<?php print (arg(2) == 'type_y' ? ' class="active" : ''>'?>><a href="http://www.example.com/node/<?php print $node->nid ?>/type_y">Show Type Y</a></li>
<li<?php print (arg(2) == 'type_z' ? ' class="active" : ''>'?>><a href="http://www.example.com/node/<?php print $node->nid ?>/type_z">Show Type Z</a></li>
</ul>
</div>

Here's the same output written all in PHP, and should be enclosed it PHP tags
Not sure which one is easier to to read.

<?php
// print view navigation tabs
print '<div id="tabs-wrapper" class="clear-block">';
print
'<ul class="tabs primary">';
print (
arg(2) ? '' : '<li class="active">') .'<a href="http://www.example.com/node/'. $node->nid .'">Show All</a></li>';
print (
arg(2) == 'type_x'  ? '<li class="active">' : '<li>') .'<a href="http://www.example.com/node/'. $node->nid .'/type_x">Show Type X</a></li>';
print (
arg(2) == 'type_y'  ? '<li class="active">' : '<li>') .'<a href="http://www.example.com/node/'. $node->nid .'/type_x">Show Type Y</a></li>';
print (
arg(2) == 'type_z'  ? '<li class="active">' : '<li>') .'<a href="http://www.example.com/node/'. $node->nid .'/type_x">Show Type Z</a></li>';
print
'</ul>';
print
'</div>';
?>

Give this a test, it should all be working the way you think.
With "node/x" you should see all related nodes, the "Show All" tab should be active
With "node/x/type_x" you should only see related nodes that are node-type "type_x" the "Show Type X" tab should be active
With "node/x/hello" you should see no nodes at all, unless you have a related node-type named "hello", no tabs should be active

Step 4:
The scenario says: "In addition I need a simple "title" search mechanism for the related-nodes."

If you are familiar with Views you can do an exposed filter for this. If we had done that with our "view_type_a_children" we would see a simple text box and a submit button. The underlying form though has an action="http://www.example.com/view_type_a_childern/x" Because of the action, if we had the exposed filter, then the user would be redirected to the views URL which is not what we want.

<form action="http://www.example.com/view_type_a_childern/x"  method="get" id="views-filters">
<div>
  <table>
    <thead>
      <tr>
        <th>Title</th><th></th>
      </tr>
    </thead>
    <tbody>
      <tr class="odd">
        <td>
          <div class="form-item">
            <input type="text" maxlength="255" name="filter0" id="edit-filter0"  size="10" value="" class="form-text" />
          </div>
        </td>
        <td>
          <input type="submit" id="edit-submit" value="Submit"  class="form-submit" />
        </td>
      </tr>
    </tbody>
  </table>
</div>
</form>

I have not seen a place to over-ride that, so this is what I chose to do. I copied the code output by the Views Filter and put it into my template, I, of course, removed the action="-blah" part so the first line becomes

<form method="get" id="views-filters">

But, there is a little more that needs to be changed, and this was the part that frustrated me the most when I was trying to figure it out.

First I wanted to get the value submitted by the user to appear in the text box.
That part is easy enough, change the input box HTML to

<input type="text" maxlength="255" name="filter0" id="edit-filter0"  size="10" value="<?php print $_GET['filter0'] ?>" class="form-text" />

Then the Views code needs to be adjusted to take into account the filter value (if any)

This you'll remember from Section 2 is the last part of code used to print out the view

<?php
// we are now ready to "build" and print out the view
// for this we use the views_build_view() function
// the last 2 values: "TRUE" and "20" allow us to have the list paged at 20 rows
print views_build_view('embed', $view, $view_args, TRUE, 20);
?>

before that section we need to add

<?php
// if user submitted a filter value, apply it to the view
if ($_GET['filter0'] != '') {
   
$view->filter[1][value] = $_GET['filter0'];
}
?>

The part that confused me for the longest time was the number associated with the filter
If you look in the HTML code used with the form its filter 0
If you look at the PHP that applies the filter to the view the filter is 1

What's going on....

Well, the numbering I wrongly assumed was somehow related. The HTML form, starting with 0 assigns a value to each exposed filter. If only the Node Title filter was exposed, then that is filter 0. The PHP code to assign the value to the actual view filter numbers it as 1. If you remember, when we set up the view our 1st filter (filter 0) was "Node: Published equals Yes" and the 2nd filter (filter 1) was "Node: Title contains (blank)"

So when using PHP to apply the filter, make sure you are applying a value to the right filter. I ended up being so confused that I changed the name of the input box to match the actual views filter.

So I really rendered the input box with the followiwng HTML to and changed the variable from $filter to $filter1
HTML changed to:

<input type="text" maxlength="255" name="filter1" id="edit-filter1"  size="10" value="<?php print $_GET['filter1'] ?>" class="form-text" />

PHP Code
<?php
// if user submitted a filter value, apply it to the view
if ($_GET['filter1'] != '') {
   
$view->filter[1][value] = $_GET['filter1'];
}
?>

Section 5 (optionally):

The scenario says: "And (optionally) I would like to have an 'Add New' link for authorized users to create new a new node ("type_x", "type_y" or "type_z") with "type-a" already filled in."

This section can be achieved by the prepopulate module, but it can be frustrating to figure out.

The first thing to do is figure our the querystring you need to make prepopulate work to insert the "type_a" nid into a new "type_x"

There are various combinations you should try are

Select List Option:
x = nid, example: "10"
- node/add/type_x?edit[field_type_a_parent][nid]=x
- node/add/type_x?edit[field_type_a_parent][nids]=x
w/ field in a group ({GROUP-NAME} = the group name)
- node/add/type_x?edit[group_{GROUP-NAME}][field_type_a_parent][nid]=x
- node/add/type_x?edit[group_{GROUP-NAME}][field_type_a_parent][nids]=x

Autocomplete Option:
x = Title [nid:nid], example: "Drupal Rocks! [nid:10]"
- node/add/type_x?edit[field_type_a_parent][0][node_name]=x
w/ field in a group ({GROUP-NAME} = the group name)
- node/add/type_x?edit[group_{GROUP-NAME}][field_type_a_parent][0][node_name]=x

That's the most frustrating part, but once you figure it out, you're good to go

The link will only appear when the user is looking at a specific type, and not on the "Show All" tab, and only if the user has permission to create such node types. You will need to go to your admin/user/access page and note down the name of the create permission for each your related node types. If you created them using Drupals interface, chances are the format is "create {node-type} content"

This code is PHP and should appear in PHP tags. While it can be placed where ever you like, for examples sake I suggest between the your links and your view.

<?php
// perform switch statement on arg(2) to see if user has selected a type
// if so check for "create" permission of user
// if user has permission, write link...
switch (arg(2)) {
  case
'type_x':
      if (
user_access('create type_x content')) {
       
// this example shows the link with a simple nid value
       
print '<a href="http://www.example.com/node/add/type-x?edit[field_type_a_parent][nids]='. $node->nid .'>Add New Type X</a>';
      }
    break;
  case
'type_y':
      if (
user_access('create type_y content')) {
       
// this example shows the link with an autocomplete value
       
print '<a href="http://www.example.com/node/add/type-y?edit[field_type_a_parent][0][node_name]='. $node->title .' [nid:' .$node->nid .']>Add New Type Y</a>';
      }
    break;
  case
'type_z':
      if (
user_access('create type_z content')) {
       
// this example is the same as type_x
       
print '<a href="http://www.example.com/node/add/type-z?edit[field_type_a_parent][nids]='. $node->nid .'>Add New Type Z</a>';
      }   
    break;
}
?>

You should now test this to see if the "add new" links are working properly.

Well that's it. Hope you found it useful in some way.

The Review

One last thing, this code can be condensed, and you'll know what I mean if you are a PHP coder. I kept each of the distinct tasks modularized so that people could leave out certain sections without having to make a bunch of adjustments elsewhere. Or just use the section they needed.

The Code

Here though is all the code for the entire template in a more natural order

<!-- Step 1:  putting fields in the top of your node page -->
<!-- you can arrange your fields however you like, this is just the default coding used -->
<div class="field field-type-text field-field-{YOUR_FIELD_NAME}">
  <h3 class="field-label">{YOUR LABEL}</h3>
  <div class="field-items">
    <?php foreach ((array)$field_{YOUR_FIELD_NAME} as $item) { ?>
      <div class="field-item"><?php print $item['view'] ?></div>
    <?php } ?>
  </div>
</div>

<!-- Step 3:  tabbed navigation to allow users to select node-type to view -->
<?php
// print view navigation tabs
print '<div id="tabs-wrapper" class="clear-block">';
print
'<ul class="tabs primary">';
print (
arg(2) ? '' : '<li class="active">') .'<a href="http://www.example.com/node/'. $node->nid .'">Show All</a></li>';
print (
arg(2) == 'type_x'  ? '<li class="active">' : '<li>') .'<a href="http://www.example.com/node/'. $node->nid .'/type_x">Show Type X</a></li>';
print (
arg(2) == 'type_y'  ? '<li class="active">' : '<li>') .'<a href="http://www.example.com/node/'. $node->nid .'/type_x">Show Type Y</a></li>';
print (
arg(2) == 'type_z'  ? '<li class="active">' : '<li>') .'<a href="http://www.example.com/node/'. $node->nid .'/type_x">Show Type Z</a></li>';
print
'</ul>';
print
'</div>';
?>


<!-- Step 4:  from to allowing filtering by node title -->
<form method="get" id="views-filters">
<div><table>
<thead><tr><th>Title</th><th></th></tr></thead>
<tbody><tr class="odd">
<td><div class="form-item">
<input type="text" maxlength="255" name="filter1" id="edit-filter1"  size="10" value="<?php print $_GET['filter1'] ?>" class="form-text" />
</div></td>
<td><input type="submit" id="edit-submit" value="Submit"  class="form-submit" /></td>
</tr></tbody>
</table></div>
</form>

<!-- Step 5:  "add new" links -->
<?php
// perform switch statement on arg(2) to see if user has selected a type
// if so check for "create" permission of user
// if user has permission, write link...
switch (arg(2)) {
  case
'type_x':
      if (
user_access('create type_x content')) {
       
// this example shows the link with a simple nid value
       
print '<a href="http://www.example.com/node/add/type-x?edit[field_type_a_parent][nids]='. $node->nid .'>Add New Type X</a>';
      }
    break;
  case
'type_y':
      if (
user_access('create type_y content')) {
       
// this example shows the link with an autocomplete value
       
print '<a href="http://www.example.com/node/add/type-y?edit[field_type_a_parent][0][node_name]='. $node->title .' [nid:' .$node->nid .']>Add New Type Y</a>';
      }
    break;
  case
'type_z':
      if (
user_access('create type_z content')) {
       
// this example is the same as type_x
       
print '<a href="http://www.example.com/node/add/type-z?edit[field_type_a_parent][nids]='. $node->nid .'>Add New Type Z</a>';
      }   
    break;
}
?>



<!-- Step 2:  inserting the view into the page -->
<!-- incorporating Step 3 dealing with node-type and Step 4 dealing with Title filter -->
<?php
// load view
// using the views_get_view() function we can "load" a view into a local variable: $view
$view = views_get_view('view_type_a_children');

// once loaded we can adjust some of the parameters of the view
// one of the views arguments, if you remember, is "Node Reference: Type A (parent_type_a)"
// we can pass a specific value to the view, for this we want to pass the value of the "type_a" node's nid ($node->nid)

// argument values are passed via a PHP array so we need
// the first arg we need to pass is the value of the "type_a" node's nid ($node->nid)
$view_args[] = $node->nid;
// the second arg we need to pass is the node type indicated by arg(2)
// if there is no arg(2) then "Show All" is assumed
if (arg(2)) {
 
$view_args[] = arg(2);
}

// if user submitted a filter value, apply it to the view
if ($_GET['filter1'] != '') {
   
$view->filter[1][value] = $_GET['filter1'];
}

// we are now ready to "build" and print out the view
// for this we use the views_build_view() function
// the last 2 values: "TRUE" and "20" allow us to have the list paged at 20 rows
print views_build_view('embed', $view, $view_args, TRUE, 20);
?>

The End

In terms of comments...
If there is an error in the code, or a section is completely unclear, please comment on that and it can be fixed in the post. I have no doubt that I've dropped several periods and missed some parentheses (and I appologize now for that).

If you want to ask a related how-to, I think that would best be done in the forums, this post could get cluttered pretty quickly. Questions like, how do I use a different view for "type_y" nodes, or how do I get a vocabulary->term selector, can a calendar view be used, will probably get answered more quickly in the forums. Once you get the answer you are looking for then see the note below!

If you do something similar, please share, think about adding a book page to this post and outline what you were looking to do and how you accomplished it.

HOW TO: setup CCK text field editing with TinyMCE text editor

"Crawl before you may walk, walk before you may run"

How to enable CCK text fields to use the TinyMCE text editor, in 10 easy steps (in an environment with "Categories" module installed, but also valid for any other environment).

This is a basic, off-the-shelf, step-by-step roadmap of how to set up a text editor that will function in your CCK text fields. It refers to a Drupal 5 installation with CCK and TinyMCE (tinymce-5.x-1.x-dev) installed. It provides a basic configuration to start you off with using CCK with TinyMCE. From that point on you may play around and determine what configuration suits you best. But, at least you will have a starting point. This is not a sophisticated recipe, just something that will set you off. What is most important, it works.

1. Check that CCK fields within your Content Type function properly, even if this means that you get a continuous long line of text without any formatting (i.e. without breaks, paragraphs or anything else you would normally expect even of Notepad). Pick a content type for experimentation, say YourContentType, at:
Administer > Content management > Content types > YourContentType.

2. Go to: YourContentType > Manage Fields and pick YourTextFileld > Configure. Check that field Rows has a value greater than 1, so that you may really have a meaningful text area, say 20. Your new text editor will only apply to text fields with over 1 row, so that all your single-row text fields remain unaffected: no text editor there. Data Settings: Required: leave this unchecked for the moment, for testing purposes. Text Processing: check Filtered text (user selects input format), so that your text editor may take over. Now Save Field settings, and go to YourContentType » Display Fields » YourTextFileld and set Teaser and Full at Default - submit to save configuration.

3. Install TinyMCE module carefully so that you may do it correctly. Actually, after you unzip the TinyMCE module in the modules folder go immediately and read the very detailed Install.txt file that is provided in the tinymce folder - and follow all instructions carefully. Make sure you also download and unzip the original TinyMCE code in a folder named tinymce within the folder of module tinymce. Detailed instructions on all necessary steps are included in the Install.txt file. In this file, find the section titled Caveats. Read it carefully. Now, read it one more time. Hope you get the point - but, not to worry, we shall do that step by step.

4. Go to: Administer > Site configuration > Input Formats > Add Input Formats.
Name: YourInputFormat, Roles: check both anonymous user and authenticated user, Filters: check only Line break converter. Submit (save) your settings. See, YourInputFormat is there in the input formats table, last in line. Now, before we forget, check YourInputFormat *and* submit (Set Default format). Now, click Configure and make certain that what you see is what you entered before - if not, edit. So far, so good.

5. Small break for a cup of coffee, check if the plants in the garden are doing fine, relax, etc.

6. Back to the task again, in fine spirits.
Go to: Administer > Site configuration > TinyMCE Settings. Read the instructions carefully. In the last line there is a link on the word "permissions" - click on it by opening a new window and you will land in Access Control. Go down the list to section "tinymce module" and check "access tinymce" both for "anonymous user" and "authenticated user" - check also "administer tinymce" for "authenticated user". Save Permissions. Close window.

7. Back now to Administer > Site configuration > TinyMCE settings > Create new profile:

* Pick Basic setup: Profile name: YourProfileName, Roles allowed to use this profile: check both, Default state: True, Allow users to choose default: False, Show disable/enable rich text editor toggle: False, Language: en, Safari browser warning: False.

* Pick Visibility. Show tinymce on specific pages: Show on only the listed pages, Pages: leave as is.

* Pick Buttons & Plugins. Check only for an absolutely basic configuration, for the moment - later you may do more: bold, italics, underline, justify left, bullets, cut, copy, paste.

* Pick Editor Appearance. Check: Toolbar location: top, Toolbar alignment: left, Path location: none, Enable resizing button: false, Block formats: leave as is.

* Pick Cleanup and Output. Verify HTML: true, Preformatted: true, Convert tags to styles: true.

* Pick CSS. Editor CSS: use theme css. Leave the rest blank.
* Click: Update Profile

8. Go to Administer > Content management » Categories > Containers > YourContainer > Edit > Input Format. Hello, there, last in line is YourInputFormat smiling at you. Success. Check it. Submit.

9. Go to your category tree (menubar) and pick YourCategory > Edit > Input Format : check YourInputFormat. Submit. Do the same down the category tree.

10. And finally: Go to: Create Content > YourContentType =>> Hello, all your (large) text fields are now TinyMCE editor enabled! Congratulations, you have followed instructions to the letter! And here is now your reward: copy / paste some formatted text from some browser window, select a category that has been enabled for YourInputFormat (as described above) - Submit. Wow! see, a nicely formatted text. You have arrived.

Now, wasn't that easy!

"With patience and calm,
the donkey may climb the palm"

HOWTO turn Image Galleries into a simple shopping cart

This will cover a lot of things and as such is certainly not limited to a shopping cart. It took me forever to figure out all the bits and pieces to do all I this, so I thought some of this would be beneficial for others as well in a variety of tasks. There's likely a better way to do some of this, but after a month of working on it and trying everything else, this is what I found that worked the best for me.

Modules: image.module, taxonomy_browser
Drupal Version 4.6.4
PayPal or some other kind of simple shopping cart setup.

First of all, if you're running this version of Drupal, you're going to want to patch the common.inc, otherwise your next and previous links won't work. More info here. If you don't know how to patch a file and are on a Windows OS, go here. Drupal 4.6.5 already has this patch included.

If you don't already have the image.module installed, go here to get it and follow its instructions for installation. Make sure you have permission to administer and create images by going to administer/access control.

Go to settings/image and adjust image sizes, path and how many images you think you might want on a page. You can always come back to this later if need be.

Start by figuring out what categories you're going to need. For instance, for a Prints category, you might have black and white and colour. You might have a books category. Whatever. Once you get that sorted out, go into administer/categories. Click “edit vocabulary”. In the Hierarchy, select multiple if you think you'll need it and then multiple select if you might have a product covering more than one category.

  1. Create your categories and sub categories, selecting which parent they belong to (if any) and a description if you want one.
  2. Now you should be able to see the list of categories within your Category Browser (provided you installed the taxonomy_browser module). I wouldn't worry too much about the way things look, it's not going to be available to the public anyway. Disable the “category browser” menu item.
  3. Create your shopping cart menu in Blocks. Don't enable it yet.
  4. If you already have little promotional things you want in the sidebars (or anywhere else) go ahead and create Blocks for those as well.
  5. Upload your images by going to create content/image.
  6. Since the title will be linked to the main preview page, I named all my images “View More” with the url alias as the item name/number.
  7. There should be a box with Image Galleries that has your list of categories. Select which one (or more) you want it in.
  8. Pick the image to upload
  9. In your description is where you'll put the add to cart button and anything else you might have. For instance, the price, description of the
    item and the button to add it to their cart. I found it easier to make a little template in html and copy/paste each one, changing the item number as I went along due to lack of space in the description box.

Example:

<div align="center">$126.00<br/>Collection of four 8x12 prints
<table align="center" cellspacing="0" cellpadding="0" border="0"><tr> <td>
<f orm method="post" action="http://www.ccnow.com/cgi-local/cart.cgi?account_name_item_name">
<input type="submit" s tyle="background-color:#333333; border-style:solid; border-color:#000000; font-size: 10; color:#cccccc; font-family: verdana, helvetica, arial;" type="submit" name="Submit" value="Add to Cart"></form>
</td></tr></table></div>

If you're using something like PayPal, you should already have the buttons made for you. Just copy and paste that.

Now when you go to http://yourdomain.com/image or http://yourdomain.com/?q=image you'll see your list of images there but not very impressive to look at. So this is where we get to make things pretty.

Follow the instructions from here in creating your template.php and image_gallery.tpl.php . This will be your base. From here on out you're only going to be editing your main css file and the image_gallery.tpl.php file.

In order to further prettify things, I added a table (you can use divs if you're more comfortable with that) in the image_gallery.tpl.php. Two columns, one for the block we're going to be adding and one for the rest of it. I also had to add more tables within the code to keep it blocked into my existing style, you may not have to.

Below is what my code looks like in image_gallery.tpl.php:

<table width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
    <td valign="top" style:"width:150px;"></td>
    <td valign="top">
<? php
$size = _image_get_dimensions('thumbnail');
  $width = $size['width'];
  $height = $size['height'];
  $content = '';
  if (count($galleries)) {
    $content.= '<table align="center" cellspacing="5" cellpadding="5" border="0" s tyle="height:500px;"><tr><td valign="top">< u l class="galleries">';
    foreach ($galleries as $gallery) {
      $content .= '<li>';
  $content .= "\n";
      if ($gallery->count)
        $content.= l(image_display($gallery->latest, 'thumbnail'), 'image/tid/'.$gallery->tid, array(), NULL, NULL, FALSE, TRUE);
      $content.= "<h3>".l($gallery->name, 'image/tid/'.$gallery->tid) . "</h3>\n";
      $content.= '<div class="price">'. check_output($gallery->description) ."</div>\n";
      $content.= '<p class="count">' . format_plural($gallery->count, 'There is %count print in this section', 'There are %count prints in this section') . "</p>\n";
      $content.= "</li>\n";
    }
    $content.= "</ul></td></tr></table>\n";
  }
  if (count($images)) {
    $height += 5;
    $content = '<table align="center" cellspacing="5" cellpadding="5" border="0" width="100%" s tyle="height:500px;"><tr><td valign="top">';
    $content.= '<ul class="images">';
    foreach ($images as $image) {
      $content .= '<li';
      if ($image->sticky) {
        $content .= ' class="sticky"';
      }
      $content .= ' s tyle="height : 250px; width : 150px; border-style:solid; border-width:1px; border-color:black; text-align:center;"';
      $content .= ">\n";
      $content .= l(image_display($image, 'thumbnail'), 'node/'.$image->nid, array(), NULL, NULL, FALSE, TRUE);
       $content .= '<h3>'.l($image->title, 'node/'.$image->nid)."</h3>\n";
   $content.= '<div class="price">'. check_output($image->body) ."</div>\n";
   if (theme_get_setting('toggle_node_info_' . $image->type)) {
        $content .= '<div class="author">'. t('Posted by: %name', array('%name' => format_name($image))) . "</div>\n";
        $content .= '<div class="date">'.format_date($image->created)."</div></span>\n";
}
      $content .= "</li>\n";
    }
    $content.= "</ul></td></tr></table>\n";
  }
   if ($pager = theme('pager', NULL, variable_get('image_images_per_page', 6), 0)) {
    $content.= $pager;
  }
  If (count($images) + count($galleries) == 0) {
      $content.= '<p class="count">' . format_plural(0, 'There is %count print in this category', 'There are %count prints in this category') . "</p>\n";
  }
  print $content;
? >
</td>
</tr>
</table>

Here are some things to note:

  • If you want the description to show up with your thumbnails, add this line:
    $content.= '<div class="price">'. check_output($image->body) ."</div>\n"; below this one, $content .= '<h3>'.l($image->title, 'node/'.$image->nid)."</h3>\n";
  • Add some style to the <li> tag to create little boxes around your product images, like this:
    $content .= ' s tyle="height : 250px; width : 150px; border-style:solid; border-width:1px; border-color:black; text-align:center;"';
  • The first column in the table is going to be for your shop navigation, so keep it small.
  • Note the styles used here. Be sure you style them in your stylesheet.
  • You can add blocks to any part of your site by slipping in this little bit of code:

    <?php
    $block
    = module_invoke('block', 'block', 'view', 6);
      if (
    $block['content']) {
       
    $output = "<div class=\"front-page-block\">\n";
       
    $output .= "<div id=\"front-block-title\"><h2>".$block['subject']."</h2></div>\n";
       
    $output .= "<div class=\"content\">".$block['content']."</div>\n";
       
    $output .= "</div>\n";
        print
    $output;
      }
    ?>

    (note the styles used here. Be sure you style them in your stylesheet.)

    If you're adding your own block, leave the first ‘block' alone on this line:
    $block = module_invoke('block', 'block', 'view', 2);
    The number will change based on your block's number. To figure out what number it is, go to your administer/blocks, click on “ configure” for one that you've made. In your address bar in the browser, you'll see something like this: http://www.yourdomain.com/?q=admin/block/configure/block/2
    That “2” at the end is the number you use.

    You can do the same thing with other blocks by changing the first ‘block' into the name of the module's block. Which might be useful for adding recent images, random images, etc. More information on this can be found here.

    Now you wanna create a node-image.tpl.php to make the main image pages look nice.

    This is the code I used: (more info found here)

    <?php //$Id: node-image.tpl.php,v 1.3 2006/03/05 11:22:27 weitzman Exp $
    ?>

    <table width="100%" cellspacing="0" cellpadding="0" border="0">
    <tr>
        <td valign="top" s tyle:"width:150px;">
    <?php
    $block
    = module_invoke('block', 'block', 'view', 6);
      if (
    $block['content']) {
       
    $output = "<div class=\"front-page-block\">\n";
       
    $output .= "<div id=\"front-block-title\"><h2>".$block['subject']."</h2></div>\n";
       
    $output .= "<div class=\"content\">".$block['content']."</div>\n";
       
    $output .= "</div>\n";
        print
    $output;
      }
    ?>

    </td>
        <td valign="top"><div align="center">
    <div class="node <?php print ($sticky) ? " sticky" : ""; ?> ">
       <?php if ($page == 0): ?>
        <h2><a href="/ <?php print $node_url ?> " title=" <?php print $title ?> "> <?php print $title ?> </a></h2>
       <?php endif; ?>
      <div class="content">
         <?php print $content ?>
      </div>
    </div></div></td>
    </tr>
    </table>

    Again, I split it up into two columns with a table and added the navigation block in the left side.
    Now, on to the style sheet…
    Aside from the little bits of style used in all the above code, you're gonna need to add:

    /*Image Galleries*/
    ul.galleries {
       margin : 0;
      padding : 5px;
       list-style:none;
    }

    /*this is going to set the style for the “album” view*/
    ul.galleries li {
      list-style:none;
      float : left;
      text-align:center;
      margin : 15px 15px 75px 15px;
      padding:5px;
      list-style:none;
      background:#444444;
      height:150px;
      width:230px;
      border-style:solid; border-width:1px; border-color:black;
    }

    ul.galleries li img {
      float : left;
      text-align:center;
      padding : 3px;
      margin-right : 0px;
      list-style-position: inside;
      list-style:none;
    }

    ul.galleries li div.count {
      clear : both;
      list-style:none;
      }

    ul.galleries h3 {
      margin : 0;
      padding : 0;
      list-style:none;
      }

    ul.images {
    list-style:none;
      margin : 0;
      padding : 0px;
       }

    /*this is going to set the style for the “album” view*/
    ul.images li {
      float : left;
      text-align:center;
      margin : 15px 15px 75px 15px;
      padding:5px;
      list-style:none;
       background:#444444;
    }

    I also added my own class for the description:

    .price {
    color: #cccccc;
    font-size: 11px;
    font-family: Arial, "Lucida Sans", Tahoma, Sans-Serif;
    background:#444444;
    text-align:center;
    }

    Upload your image-node.tpl.php, image_gallery.tpl.php, template.php and stylesheet to your themes directory.

    Make your link to the shop go to ?q=image or image, if your using clean urls.

    If you made any blocks for promotional items, you can now set them to show on whatever pages you want.

    Here is a working example.

HOWTO: Create a classified ad section

This is a recipe on how to get Classifieds in Drupal 4.7.

Demo of classifieds using these instructions.

Overview: This recipe uses these (up to date) items:

  • Drupal 4.7
  • Views.module
  • flexinode.module
  • style.css (for your theme)

1) The first thing you need to do is create a custom flexinode. Create the fields that you want people to be able to enter. Some recommendations are: Title, Description, Name, Email, Phone Number, Price, and Pictures. Do not create fields that you would want people to be able to filter the classifieds by (state, category etc).

2) Create Categories (taxonomy) with the terms you want people to choose from and assign your flexinode as "type". I would suggest having at least:

  • Regions (state, country etc)
  • Category (Real estate, clothes, cars, trailers etc...)
  • Type of ad (for sale, wanted, rent, etc)

3) Create a custom template.php file in order to use a custom node-flexinode-n.tpl.php file.

4) Customize your node-flexinode-n.tpl.php file where n is your flexinode number. Be sure to add your taxonomy "categories" to the node in a way that will make sense to the end user.

(If you need help with steps 3 and 4, refer to the handbook pages on Customising flexinode layouts.

5) Create a custom "view" using views.module. Make it a "Page" and "table view." Next, choose the "fields" you want shown on the classified table view. Next, choose the filters you want the type to be able to be filtered by. Choose which filters you want to "expose" to the end user. Next modify all the permission and misc view settings to get it to appear the way you want.

6) Next, edit your style.css file to get the classifieds to look the way you want them to. You can adjust the tabs, colors, padding, etc...

7) Visit "access control" and make sure you set role permission for creating your classified ads.

8) That's about it!

This is my first attempt at creating a "recipe" for drupal. If you have any questions or if I'm not clear enough on a point, please let me know.

HOWTO: Create a manageable blogroll with Views

Objective: Create a Blogroll (list of links and descriptions). We want a block to show a set number of the links, in a random order. We also want a page to show all of the links and descriptions.

Modules needed:

(optional)

Difficulty: Easy. Don't go by the length of this article. :)

Step 1: Create a content type called "blogroll_item".

Navigate to /admin/content/types/add/. I used the following settings:
Name: Blogroll Item
Type: blogroll_item
Title field: Title
Body field: blank (this makes the content type have no Body field)
Default Options: Published (none of the others -- you don't want these showing up on the front page, most likely)
Comments: Disabled
Attachments: Disabled

Save the content type, then click its "Edit" link. Choose "Add Field" from the top. Here are the parameters:
Type: Link (save)
Name: URL
Required: Yes
Link Title: Required
NoFollow Value: Yes

Save this, and add another field for the description. Here are its parameters:
Type: Text field (save)
Name: Description
Rows: 2 (it is up to you)
Required: No
Multiple Values: No
Text Processing: Filtered HTML

Save this, and click Manage Fields. Give the URL field a weight of -1, because you want it to be above the Description. Save.

Click Display Fields. Make sure the Teaser setting for URL is "Default as link." Save this.

Call the Body field "Description" or "Notes" or something that indicates that it will be information about the link.

Step 2: Create the Page view

Navigate to /admin/build/views/add. Here are the parameters:
Name: blogroll_page
Provide Page View: yes
URL: blogroll
View Type: Table View (or List View, you can try both and see which you like better)
Title: Blogroll
Pager: Numeric
Nodes Per Page: 10 (you decide)
Fields:

  • Name: Link (URL)
  • Handler: Group Multiple Values
  • Option: Default As Link
  • Name: Text: Description
  • Handler: Group Multiple Values
  • Option: Default
  • Sortable: no
  • Name: Node: Edit Link
  • Name: Node: Delete Link

Filters:

  • Field Node Value is one of blogroll_item
  • Node Published is True

Sort: Node Title (ascending)

Save this and test it by creating a few Blogroll Item nodes, and going to http://www.yoursite.com/blogroll/. You should have clickable links for the URLs, and descriptions if you added them. They should be in alphabetical order by the Node Title. I made the Node Title and Link Title the same thing for all of mine, as it seemed to make the most sense.

Step 3: Create the Block view

You need a separate view for the block, because you probably don't want the descriptions to show up in the block. You probably also don't want to list every link in your blogroll, but an arbitrary number, probably sorted in a random order.

So create a new View, called "blogroll_block." Here are the parameters:

Name: blogroll_block
Provide Block: Yes
View Type: List View
Title: Blogroll
Nodes Per Block: 10 (you decide)
(More) Link: No
Footer: <a href="http://www.yoursite.com/blogroll/">(More)</a> (a fake More link which goes to the Page view you created in step 2)
Fields: Link: URL, Option Default as Link
Filters: Same as the page view
Sort: Random

Step 5: Enable the block.

In /admin/build/block you now have a block called "blogroll_block". Enable it and put it wherever you want it on your page.

Step 6: Make it quicker to add Blogroll Items by using the Node Go To module.

This module lets you specify a page to go to after inserting, updating, or deleting specific node types. Go to the Administer By Module page, and find the Node Go To section. Under Insert, find Blogroll Item, and paste the link to /node/add/blogroll-item/.

Now after you create a blogroll item, you will be presented a blank form to create another (and another and another).

Step 6: Go crazy adding links to your blogroll!

HOWTO: Create multiple Views on one node with paging

I really wanted to get multiple Views onto my home page to display new/popular Groups and content. I am not a PHP coder, and the only help I found to do this was at http://drupal.org/node/42599. That post got me started and I took it a bit further. Following the instructions below you can add as many Views as you want to a node and use paging with each. This method has not been tested in various situations and I do not know its limits. Like I said, I'm not a coder. Use at your own risk :-)

Obviously you'll need the Views.module. Enter Administer > Views and create all the Views you will want for this one node. Now, all my Views provide a "Page", the View Types are "Table View", I use the "Pager", and they all have similar "Fields", "Filters", and "Sort Criteria". (Not sure if this all matters but just so you know.) Also make sure you enter the node URL for each View in the View settings. I didn't do this at first and it was strangely screwing up my 404 page.

You need to designate one of these Views as a primary, and the rest as subordinates. By this I mean just use descriptive View names and descriptions, for clarity. The View you designate as the primary will appear at the bottom of the node. The subordinates will appear in sequence from the top of the node down. The primary will also contain the code which will make all this happen in its Page > Header.

Enter the following code into the Page > Header of the View you have designated as the primary. Be sure to use the PHP input filter:

<!-- Put any text here that you want at the top of the page.  Minus the title, enter the title within the primary View's settings. -->

<?php
// This calls the primary View.
global $current_view;

// This calls our first secondary View.
// Change the View name within the parenthesis.
$view = views_get_view('home_page_new_groups');

// This is  HTML I needed for my theme/layout
// and a sub-heading for the View.
print t("<br />");
print
t("<h2>Newest Groups</h2>");

// This loads the View's arguments?
// I changed the last two things in the parenthesis. 
// It was "false, false" but then paging didn't work.
// "true, 10" enables paging and pages after 10 nodes are listed -
// change this number to whatever you need.
print views_build_view('embed', $view, $current_view->args, true, 10)
?>

<?php
// It was necessary to separate each View with its own PHP tags. 
$view = views_get_view('home_page_popular_groups');
print
t("<p>&nbsp;</p>");
print
t("<h2>Most Popular Groups</h2>");
print
views_build_view('embed', $view, $current_view->args, true, 10)
?>

<?php
$view
= views_get_view('home_page_new_content');
print
t("<p>&nbsp;</p>");
print
t("<h2>Newest Content</h2>");
print
views_build_view('embed', $view, $current_view->args, true, 10)
?>

<!-- I added this HTML for my theme/layout and a sub-heading for the primary View.  Since the primary View comes last, I provide a sub-heading for it here at the end. -->
<p>&nbsp;</p>
<h2>Most Popular Content</h2>

You should now have multiple Views on one node (considering that content is being forwarded to this node by default).

A bit of a hack I guess, but hey, it works! Hope this helps :-)

Have each view page individually

Each views_build_view statement needs to have a separate pager id:

views_build_view('embed', $view, $current_view->args, 1, 5);
views_build_view('embed', $view, $current_view->args, 2, 5);
views_build_view('embed', $view, $current_view->args, 3, 5);

In the views.module, the views_build_view function, the use_pager item is defined:
* @param $use_pager
*   If set, use a pager. Set this to the pager id you want it
*   to use if you plan on using multiple pagers on a page. To go with the
*   default setting, set to $view->use_pager. Note that the pager element
*   id will be decremented in order to have the IDs start at 0.

Each view is paged separately on the page. Cool!

HOWTO: Customize Drupal text and terminology

Depending on your particular application, you may find that the default text or terminology used in the Drupal core or a contributed module doesn't suit your style or need. While it is tempting to edit a core or module file when you want to customize a particular piece of text, this should be avoided. By changing the module or core code, you'll make your life much more difficult when the time comes to upgrade your site to a new release. Instead, do this in a way that is more easily portable.

One approach is to use the theme files. Changing a single block title or other such element can be done in the corresponding template file.

If you want to change several pieces of text or terminology, Drupal already has a well-developed system for handling this situation (as has recently been highlighted). This system is designed for making translations, but will also efficiently substitute your prefered terminology for the default Drupal terminology. You will find detailed instructions in the handbook for the locale module. With this system you can make change to most or all of the default text, button names, input field names, etc. For example, you might wish that the things described by Drupal as "forums" were instead called "bulletin boards". The locale module will let you substitute "bulletin board" in every string where the word "forum" is used.

HOWTO: Display your node's "last edited by" information.

Let's say that you are the type of Drupal website where many different users change your nodes around, and you want to display the most recent author and date of that node's most recent revision.

I have done this by editing my node.tpl.php file in my theme's directory as thus:

<?php
   
   
//Display a more Wiki like information blurb
    //about who last edited and when for this node.
   
   
$nodeid = $node->nid;
    if (isset(
$nodeid)) {
   
$result = db_query("SELECT pv.value AS last_editor,
                        u.uid AS the_uid
            FROM node_revisions nr, users u, profile_values pv
                            WHERE    nr.uid = u.uid
                            AND    pv.fid = 1
                            AND    nr.uid = pv.uid
                            AND    nr.nid = "
.$nodeid. "
                            ORDER BY timestamp DESC
                            LIMIT 1"
);
   
$resultset = db_fetch_object($result);
    print
'Last edited by <a href="user/' .$resultset->the_uid. '">'
   
.$resultset->last_editor.'</a> on '
       
.format_date($changed);
    }
?>

In my Drupal installation, we have a custom profile value for "Full Name". In the database, this profile_value has an fid = 1, hence the SQL statement joining on this information.

If you do not have this profile value for full names, you can simply display the username. The code would look like this:

<?php
   
   
//Display a more Wiki like information blurb
    //about who last edited and when for this node.
   
   
$nodeid = $node->nid;
    if (isset(
$nodeid)) {
   
$result = db_query("SELECT u.name AS last_editor, u.uid AS the_uid
                          FROM node_revisions nr, users u
                            WHERE    nr.uid = u.uid
                            AND    nr.nid = "
.$nodeid. "
                            ORDER BY timestamp DESC
                            LIMIT 1"
);
   
$resultset = db_fetch_object($result);
    print
'Last edited by <a href="user/' .$resultset->the_uid. '">'
           
.$resultset->last_editor.'</a> on '.format_date($changed);
    }
?>

HOWTO: enable thumbnail on teaser, larger image on page using cck imagefield, imagecache and upload

I decided to write this after having read an awful lot of posts about the struggle to make sense of images in drupal and still finding it relatively difficult to achieve this simple requirement myself. I know I am by no means the first to post about this issue and I certainly do not claim to be any kind of expert (at the time of writing I have been looking at drupal for about a week). However, this guide might help anyone with a need to have thumbnail images on teasers and larger images on pages.

Stage 1 - drupal version and required modules

If you are still reading then you need to be using drupal 4.7.x, enable the upload core module and install and enable these contributed modules:

cck.module (contributed module - http://drupal.org/project/cck)
imagefield.module (contributed module - http://drupal.org/project/imagefield)
imagecache.module (contributed module - http://drupal.org/project/imagecache)

Stage 2 - Configure the two required imagecache "presets"

Q: What is an imagecache "preset"?
A: An action that will automatically be performed on the uploaded inages.

Q: How many presets can I use?
A: Many presets can be used but only two will be required for our needs.

Preset 1 - needed for the main image that will display on the node's page

namespace: yourcontentnode_mainimage
action: scale
width: 400
height: 400
scale to fit: inside dimensions
weight: 1

Preset 2 - needed for the thumbnail image that will display on the node's teaser

namespace: yourcontentnode_thumbnail
action: scale
width: 100
height: 100
scale to fit: inside dimensions
weight: 0

Please note that I have set the dimensions of the main image to be 400x400 pixels and the thumbnail to be 100x100 pixels. Obviously you will need to set the size of the images to suit your needs.

Also note that preset 1 is weighted "1" and preset 2 is weighted "0". This ensures that when the image is uploaded the first action is to create the main image from the original (which may be larger than 400x400 pixels) and the second is to create the thumbnail from the 400x400 pixel image (and not from the original I think).

Stage 3 - define the node content imagefield

Q: What is an imagefield?
A: It is a CCK field type that we are using to hold the uploaded image. Do not confuse this with an image node that would be created by the image.module or with a flexinode.

I am presuming that at this stage you already have the content type defined. If not create one and return here.

1. Administer your content type and create a field of type "image" (this makes it an "imagefield")
2. Give the image "label" meaningful to users e.g. "image" (we will assume "image" has been used in the rest of these instructions).
3. You do not need to set a "Maximum resolution" unless you are allowing the original image to be stored on your server (see 5.)
4. Set an "image path" if necessary to allow the original image to be stored but know that the path will also be used in the imagecache directory strucure
5. "Enable custom alternate text" if you need to.
6. "Enable custom title text" if you need to.

Imagecache Options

7. "Teaser preset" - set this to the imagecache preset (created in Stage 2) that defines your thumbnails e.g. yourcontentnode_thumbnail
8. "Body preset" - set to the imagecache preset (created in Stage 2) that defines your main image to be displayed in the node page body e.g. yourcontentnode_mainimage

Data settings

9. "Required" - set if you require that every content entry of this type must have an image
10. "Multiple values" - tick if you will allow users to upload and display more than one image for each content entry. Note that both the multiple thumbnails and main images will be displayed.
11. Save the settings.

Stage 4 - edit the node content image field to upload an image

Find the "image" section (remember this name was given in Stage 3 above)

1. click "browse" and select the image file to be uploaded.
2. click "upload" to upload the file which will then be automatically imagecached to two new files with "mainimage" and "thumbnail" settings.
3. click "submit" to save your changes to the content node.

Stage 5 - verify the result by viewing the content node

1. Navigate to the page view of your content node to check that the "mainimage" format of your image is displayed.
2. Navigate to a teaser view to check that the "thumbnail" format of your image is displayed.

Stage 6 - review the imagecache files that were created

if we assume that you did not set an [image path] in section 3) above then you should see the following directories on your web server

yoursitedirectory/files/imagecache/yourcontentnode_image/files
yoursitedirectory/files/imagecache/yourcontentnode_thumbnail/files

the first contains the imagecache preset "mainimage" files e.g. the 400x400 pixel images
the second contains the imagecache preset "thumbnail" files e.g. the 100x100 pixel images

End of guide

P.S. Please note that this is my first attempt at creating a useful guide and excuse me if I have made some errors, used an inappropriate format or posted in the wrong part of the forum :)

HOWTO: Node Listings with Thumbnails using Drupal 5.1, CCK and ImageCache

I needed to apply the concepts outlined at http://drupal.org/node/101748 to create a listing of node teasers that contained thumbnail images. That article was written for 4.7 and was useful, but not completely helpful for Drupal 5.1 which I am running. Here's a way to create a teaser list with thumbnails ... keep in mind I am also not a Drupal expert, but a Drupal learner. This is just what worked for me.

Note: Currently (4/17/2007), the ImageCache module only works when the "Download method" setting in Drupal's site configuration is set to "Public."

Install the Modules

You will need to install and enable the following modules:

Configure Imagecache

The imagecache settings are at http://www.example.com/admin/settings/imagecache (replace example.com with your domain). We are going to define a generic thumbnail namespace that can be reused for different purposes. This namespace will exist to scale the uploaded images to a size of 100x100 pixels for display in the listings.

  1. Enter a name in the Preset Namespace field and click the Create Preset button. I used the name thumbnail for this example.
  2. In the Add a New Action dropdown select the value scale and click the Update Preset button.
  3. Enter the Width and Height you want your thumbnail to be. When you are finished press the Update Preset button. For this example, I entered 100 in both fields.

Modify the Node Type with CCK

This example is modifying the Blog node so navigate to http://example.com/admin/content/types/blog. We will be adding an image field to the existing node type.

  1. Click the Add field tab.
  2. Enter a machine readable name into the Name field. For this example, I entered thumbnail.
  3. Toggle on the Image radio button.
  4. Press the Create field button to submit the form.
  5. Optionally enter a directory to store the images in the Image path field. For this example, I entered blogs
  6. Optionally check the Enable custom alternate text checkbox to allow for a clear description of the image.
  7. Press the Save field settings button to submit the form.

Create Content and Upload an Image

Now create a new instance of the modified node.

  1. Navigate to the create page of the node type you are working with. For this example, I used http://example.com/node/add/blog
  2. Upload an image. If you are following this tutorial, you will have a new upload entitled thumbnail. If not, the fieldset will be named after the data you entered in step 2 of Modify the Node Type with CCK
  3. Submit your content.

Create a Module

I use a custom module for each site that I do that contains functions specific to that site I would like to keep removed from the templates. Not sure if it is the correct Drupal way of doing things, but it works for me. This is a simple module with 2 files, example.info and example.module The module itself will contain a simple function that can be called from any template to generate a teaser list of that node type complete with images.

Create the following two files and upload them to an example folder in your modules directory. Don't forget to activate the module before proceeding.

Create example.info

name = "Teaser List"
description = "Example Teaser listing of a node complte with thumbnail images."
version = "1.0"

Create example.module

function example_getBlogTeasers() {
  $results = db_query("SELECT nid FROM {node} WHERE type = '%s'", 'blog');
  $output = '';
  while ( $data = db_fetch_object($results) ) {
    $node = node_load($data->nid);
    $output .= l($node->title, 'node/'.$node->nid);
    $output .= theme('imagecache', 'thumbnail', $node->field_thumbnail[0]['filepath']);
    $output .= check_markup($node->teaser, $node->format, FALSE);
    $output .= '<hr />';
  }
  return $output;
}

This is not ideal code ... but it is functional code. There are many ways to tweak the output of this listing. In a production environment you may want to enclose the content in divs or as a list that can be styled and displayed to your liking. You may need to alter the query to select your specific node type. For the purpose of this tutorial we will keep it simple and move on.

Modify Your Theme

THis is the easy part. Simply add the following code to a template page you would like to display your teaser list on:

<?php echo example_getBlogTeasers(); ?>

Conclusion

It took me a while to figure this out so I do hope it helps someone. This is the first content I've contributed to Drupal, so by all means let me know if there is an easier or more correct way to accomplish any of the above.

Here's a list of articles that I used to figure this out:

HOWTO: PayPal Donation using CCK

Several postings have asked for a fast way to take a donation from a user and process it via PayPal. The E-Commerce module has a facility to do this, but it involves a shopping cart and several additional steps for the user, thus driving down the likelihood of completing the donation process.

This example features a custom CCK donation form that dynamically generates the PayPal button once the donation parameters are submitted. The user then simply clicks the button and is off to PayPal to pay for the donation. It is assumed that the reader is familiar with the PayPal developer's requirements to process payments. If not, please go to http://developer.paypal.com. It is also assumed that the reader is familiar with CCK and computed fields. This example does provide some instruction on implementing html via php inside of a computed field. I do not profess to be proficient or professional at coding either html or php, so please accept my code with a grain of salt. It does work for me.

I have not been able to get PayPal's dynamic encryption working yet with this and will return to this post if I ever get it up and running.

Building the CCK Node:

The following fields are defined in this CCK node:

Note: all computed fields are stored.

Donation Date
field_donation_date
Datestamp

Organization
This is a hardcoded code for the organization to which the donation is to be made. This is optional in most installations, but necessary in this instance as we service multiple organizations.
field_organization
Computed field
$node_field[0]['value'] = "WFF";

Organization Name
field_organization_name
Computed field
$node_field[0]['value'] = "Wilson Freedom Federation";

User Name
field_user_name
Computed field

global $user;
$node_field[0]['value'] = $user->name;

Tax Status
In this instance, the user can choose between making a tax deductible contribution to our 501(c)(3) side of the house or a non-deductible donation to our political action wing.
field_tax_status
Text - Check Boxes/Radio Buttons

TD|Tax Deductible - Supports our educational and operational mission
ND|Non Deductible - Supports our political and legislative action organization

Donation Frequency
User can select a one time donation or a recurring donation. Note that PayPal has different calls depending on whether the payment is a one time payment or recurring (subscription) payment. It is all factored into this process.
field_donation_frequency
Text - Check Boxes/Radio Buttons

OT|One Time Donation
AN|Annual Recurring Donation
SA|Semi-Annual Recurring Donation
QT|Quarterly Recurring Donation
MO|Monthly Recurring Donation

Donation Amount
field_donation__amount (note the two underscores between donation and amount)
Text - Select List

10|$10
25|$25
50|$50
75|$75
100|$100
150|$150
250|$250

Item Name
field_item_name
Computed field
$node_field[0]['value'] = $node->field_tax_status_0[0]['value'] . " Contribution to " . $node->field_organization_name_0[0]['value'] . " | " . $node->field_donation_frequency[0]['value'];

Item Number
This is a SKU/product number to identify the donation.
field_item_number
Computed field

$node_field[0]['value'] = $node->field_organization_0[0]['value'] . "|" . $node->field_tax_status_0[0]['value'] . "|" . $node->field_donation_frequency[0]['value'] . "|" .
$node->field_donation__amount[0]['value'];

Submit to PayPal
This generates the PayPal button. Note that the field name is encrypted_paypal (from an earlier version) but it is not encrypted.
field_encrypted_paypal
Computed field

/****************************************
*    
* Drupal Computed Field for Paypal Donation (Unencrypted)
* @created 3/22/07 for Drupal CCK Computed Fields
* @author Jim Rea <jarea at teleweb-pub dot com>
* @version 1.0
*
*
*
****************************************/
//
// Note: though the field name is 'field_encrypted_paypal' this field is not encrypted
//
$item_number = $node->field_item_number[0]["value"];
$item_name = $node->field_item_name[0]["value"];
$amount = $node->field_donation__amount[0]["value"];
$business = "aoetest2@djdjdjdj.com";
$frequency = $node->field_donation_frequency[0]['value'];

$button_data = "<form action='https://www.sandbox.paypal.com/cgi-bin/webscr' method='post'>";
$button_data .= "<input type='image' src='https://www.sandbox.paypal.com/en_US/i/btn/x-click-but02.gif' border='0' name='submit' alt='Make payments with PayPal - it is fast, free and secure!'>";

switch ($frequency) {

case "OT";
$button_data .= "<input type='hidden' name='cmd' value='_xclick'>";
$button_data .= "<input type='hidden' name='business' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='receiver_email' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='item_number' value='" . $item_number . "'>";
$button_data .= "<input type='hidden' name='item_name' value='" . $item_name . "'>";
$button_data .= "<input type='hidden' name='amount' value='" . $amount . "'>";
$button_data .= "<input type='hidden' name='currency_code' value='USD'>";
$button_data .= "<input type='hidden' name='no_shipping' value='1'>";
break;

case "AN";

$button_data .= "<input type='hidden' name='cmd' value='_xclick-subscriptions'>";
$button_data .= "<input type='hidden' name='business' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='receiver_email' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='item_number' value='" . $item_number . "'>";
$button_data .= "<input type='hidden' name='item_name' value='" . $item_name . "'>";
$button_data .= "<input type='hidden' name='currency_code' value='USD'>";
$button_data .= "<input type='hidden' name='no_shipping' value='1'>";
$button_data .= "<input type='hidden' name='a3' value='" . $amount . "'>";
$button_data .= "<input type='hidden' name='p3' value='1'>";
$button_data .= "<input type='hidden' name='t3' value='Y'>";
$button_data .= "<input type='hidden' name='src' value='1'>";
$button_data .= "<input type='hidden' name='sra' value='1'>";
$button_data .= "<input type='hidden' name='no_note' value='1'>";
break;

case "SA";

$button_data .= "<input type='hidden' name='cmd' value='_xclick-subscriptions'>";
$button_data .= "<input type='hidden' name='business' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='receiver_email' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='item_number' value='" . $item_number . "'>";
$button_data .= "<input type='hidden' name='item_name' value='" . $item_name . "'>";
$button_data .= "<input type='hidden' name='currency_code' value='USD'>";
$button_data .= "<input type='hidden' name='no_shipping' value='1'>";
$button_data .= "<input type='hidden' name='a3' value='" . $amount . "'>";
$button_data .= "<input type='hidden' name='p3' value='6'>";
$button_data .= "<input type='hidden' name='t3' value='M'>";
$button_data .= "<input type='hidden' name='src' value='1'>";
$button_data .= "<input type='hidden' name='sra' value='1'>";
$button_data .= "<input type='hidden' name='no_note' value='1'>";
break;

case "QT";

$button_data .= "<input type='hidden' name='cmd' value='_xclick-subscriptions'>";
$button_data .= "<input type='hidden' name='business' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='receiver_email' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='item_number' value='" . $item_number . "'>";
$button_data .= "<input type='hidden' name='item_name' value='" . $item_name . "'>";
$button_data .= "<input type='hidden' name='currency_code' value='USD'>";
$button_data .= "<input type='hidden' name='no_shipping' value='1'>";
$button_data .= "<input type='hidden' name='a3' value='" . $amount . "'>";
$button_data .= "<input type='hidden' name='p3' value='3'>";
$button_data .= "<input type='hidden' name='t3' value='M'>";
$button_data .= "<input type='hidden' name='src' value='1'>";
$button_data .= "<input type='hidden' name='sra' value='1'>";
$button_data .= "<input type='hidden' name='no_note' value='1'>";
break;

case "MO";

$button_data .= "<input type='hidden' name='cmd' value='_xclick-subscriptions'>";
$button_data .= "<input type='hidden' name='business' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='receiver_email' value='" . $business . "'>";
$button_data .= "<input type='hidden' name='item_number' value='" . $item_number . "'>";
$button_data .= "<input type='hidden' name='item_name' value='" . $item_name . "'>";
$button_data .= "<input type='hidden' name='currency_code' value='USD'>";
$button_data .= "<input type='hidden' name='no_shipping' value='1'>";
$button_data .= "<input type='hidden' name='a3' value='" . $amount . "'>";
$button_data .= "<input type='hidden' name='p3' value='1'>";
$button_data .= "<input type='hidden' name='t3' value='M'>";
$button_data .= "<input type='hidden' name='src' value='1'>";
$button_data .= "<input type='hidden' name='sra' value='1'>";
$button_data .= "<input type='hidden' name='no_note' value='1'>";
break;

}

$button_data .= "</form>";

$node_field[0]['value'] = $button_data;

That is it. Point the user's browser to create content/donation form and (hopefully) begin taking donations.

Good luck.

HowTo: tab blocks with jQuery

The following will allow your site to generate a tab for every block assigned to a region. It requires jQuery and the tabs plugin from stilbuero.

stilbuero's demo page

Each block from Drupal will have its' own tab for a designated region. It's an efficient way to save space and clear out the clutter from your sidebars or wherever else you have more than one block. -look below for live example in a drupal site.

The main problem is the way blocks are structured out of Drupal. The subjects need to be grouped together in an unordered list with a link pointing to a block it's related to. Drupal outputs the subject right above the block content which is difficult to override server side.

Drupal's output:

<code>
<div id="block-1" class="block">
  <h2 class="title">subject 1</h2>
  <div class="content">
    ...
  </div>
</div>

<div id="block-2" class="block">
  <h2 class="title">subject 2</h2>
  <div class="content">
    ...
  </div>
</div>

<div id="block-3" class="block">
  <h2 class="title">subject 3</h2>
  <div class="content">
    ...
  </div>
</div>

desired results:

<code>
<div id="tab-container">

  <ul class="tab-title-group">
    <li class="tab-title"><a href="#block-1">subject 1</a></li>
    <li class="tab-title"><a href="#block-2">subject 1</a></li>
    <li class="tab-title"><a href="#block-3">subject 1</a></li>
  </ul>

  <div id="block-1" class="block tab-block">
    <div class="content">
      ...
    </div>
  </div>

  <div id="block-2" class="block tab-block">
    <div class="content">
      ...
    </div>
  </div>

  <div id="block-3" class="block tab-block">
    <div class="content">
      ...
    </div>
  </div>

</div>

One way to achieve this is to use jQuery's DOM manipulation. A big bonus of this is that the tabs will not break the site if someone is browsing without javascript. It will just be another block to them. Your block.tpl.php does have to output a unique id, a class of "block" and the subject must have a class of "title". -look above for the assumed structure.

First prepare a new region in your template or dedicate an existing one for the tabs. Inside your page.tpl.php file, look for a region and surround it with a unique div. Lets say for example that you have a region named "tab-region". The id of "tab-container" is what will be called to trigger the tab plugin.

<code>
<?php if ($tab-region) { ?>
<div id="tab-container">
  <?php print $tab-region ?>
</div>
<?php } ?>

Now you must include the jQuery library. If your using version 4.7, a patch is available. Be aware that it can cause complication down the line since it is not supported! It's part of Drupal core for version 5 though.

An easy alternative is to link to jQuery directly in the page.tpl.php. The biggest side effect is that it can conflict with the functions inside autocomplete.js and upload.js files. This is a known problem so you can try and modify core files or use the code below which checks for the files and disables itself if it is present. It's a cheap solution until you decide to patch 4.7 or upgrade to 5. Considering that your site shouldn't break when it's disabled, it's not an unreasonable tradeoff.

<code>
<!-- check for autocomplete.js -->
<?php if (!preg_match("/autocomplete.js/",$head) || !preg_match("/upload.js/",$head)) { ?>

<link rel="stylesheet" href="<?php echo url($directory.'/tabs.css') ?>" type="text/css" media="print, projection, screen" />

<!--[if lte IE 7]>
<link rel="stylesheet" href="<?php echo url($directory.'/tabs-ie.css') ?>" type="text/css" media="projection, screen" />
<![endif]-->


<script type="text/javascript" src="<?php echo url($directory.'/scripts/jQuery.js') ?>"></script>

<script type="text/javascript" src="<?php echo url($directory.'/scripts/jQuery.tabs.js') ?>"></script>


<script type="text/javascript">

  $(document).ready(function(){

    // new class is added for separate optional theming.
    $("#tab-container > .block").addClass("tab-block");
   
    // iterate through each block chaining functions.
    $("#tab-container > .tab-block").each(

      function() {

        $(".title",this)
          // wrap .title in li.
          .wrap('<li class="tab-title"></li>')
          // make link fragment from block id. Must be unique! $(this) = .tab-block or .block
          .wrap('<a href="#' + $(this).id() + '"></a>');

        // copy inner text of .title into the link.
        $("li.tab-title > a",this).append($(".title",this).text());

        // delete old .title since markup associated with it doesn't make sense here.
        $("li.tab-title .title",this).remove();

      }

    );

    // add an unordered list container to the top of #tab-container.
    $("#tab-container").prepend('<ul class="tab-title-group"></ul>');

    // move all list items to unordered list container.
    $("#tab-container > .tab-title-group").prepend($("#tab-container .tab-title"));

    // trigger tabs with optional effects. More options available. Look at the documentation.
    $("#tab-container").tabs({fxAutoheight: false, fxSlide: false, fxFade: true, fxSpeed: 180});

  });

</script>


<?php
}?>

Place all the files into your theme folder including the css files included with the plugin. From there it should function but you must style the css files to match your theme. Use the new classes to style so the blocks don't look out of place when the script is disabled or when a visitor has javascript turned off.

Generated classes:

<code>
.tab-title-group {
  ...
}
.tab-title {
  ...
}
.tab-block {
  ...
}

Any blocks added to your "administration > blocks" page will have its' own tab now. You can reorder them, hide/show them as usual.

Take a look at stilbuero's demo page for usage information.

notes:

  • this came out of a forum question http://drupal.org/node/88558.
  • working example in a drupal site. -scroll down Theme isn't finished so it will break in Internet Explorer.
  • If you know of a way to make it more efficient then please post. The author of this post is by no means an expert.

Mod of Garland background

Quick tutorial based on what I did at www.shelfanger.com

First as with all learning – before you start make a backup of your site and database in case of any problems

Now go to Admin > Themes > Garland > Configure. Chose the standard colourset that most closely matches your desired end result. In this case I am choosing Greenbean. Turn off [Logo], [Site name], [Site Slogan] and [Mission statement] and click [Save configuration].

What you would (usually) now do is browse to your drupal site folder > files. > color > you will now find a new folder in color called [something like] garland-b3aeefcb > open this and find body.png.

If your site is online use ftp-filezilla (or whatever you use) to download this file to your hard disk. To save repeating myself at each stage when we work on this file locally IF your drupal site is online you will need to upload the new version via ftp to then browse the result. If working with a local server simply save and check in browser.

OK – open body.png in image editing program change its size from 1px by 280px to 1280px.by 280px or download and use the versions I have already created at http://82.69.23.179/shelfanger/files/color.zip

Save this elongated version overwriting the original (tip I always make extra copies as I go in case I want to go back a stage). If you go back to your site in a browser and refresh you should see no difference because all we have done, to date, is replace a background.png that is one pixel wide (but that repeats across the background) with a similar one that is 1028 pixels wide. Now go back to your image program > turn body.png negative > save > and check in browser and the background image should now be negative. If so we know we are working with the correct background image.

Back to the image editing program and reverse the last step, turning it back to a positive image and save. The best way forward is to create a new layer and then bring in your new photos, graphics, fancy text onto this new layer. As a test just bring in a very small photo Say 150pixels wide aand put it in the top left corner. Save and publish. (but keep it open if your editing prigramme so you can continue working. When you check again in your browser you should see the new graphic.

Waita minute... it appears to have mostly disappeared into the left hand margin!. Now some code adapting I'm afraid.

Go to files. > color > garland-b3aeefcb > open style.css in notepad and alter the line

background: #fbfaf7 url(body.png) repeat-x 50% 0;

to

background: #fbfaf7 url(body.png) repeat-x 0% 0;

Save file and and recheck in browser. However you have adapted the background in your editing programe should now be reflected in your site. If the new background elements crash into any site text or existing elements simply go back to the editing prog and resize or move them.

That should be enough for part one of this tutorial. - do give me your feedback

Garland Simple mod Tutorial - part 2

Garland Simple mod Tutorial - part 2 http://drupal.org/node/128395#comment-224690

Private forums and member-only sites


Version: This topic is for Drupal versions 4.6, 4.7, and 5.x, although some menu paths will be slightly different for pre-5.x versions.

The purpose of this page is to:

A private forum is one which is only available to registered members, or to only a certain class of users (or 'members'). A private-side web site is when a section of a web site is only accessible to a certain class of users.

New users to Drupal often look for the functionality to create private forums in the forum module. Why isn't it there?

The root of it, in my opinion, is that Drupal divides its modules by functionality rather than by specific use. The functionality is access control; the use is for private forums. The reason Drupal does this, is so that the functionality can be applied not just for one use, the forums, but for any use across the web site, including books, pages, stories, and whole private-side web sites.

Options for forum access control are:

Other access control modules, which may or may not be able to readily accomplish private forums, and how I think they compare:

  • Front Page (only if entire site is private, only allows one public page)
  • node privacy byrole (by role; by action; by node, not by taxonomy; 4.7)
  • nodeaccess (by role or user; by action; by node, not by taxonomy; 4.7 beta)
  • Nodeperm_role (by role; by action incl edit own; not by taxonomy; 4.7 alpha or beta)
  • simple access (by role; by action; by node, not by taxonomy; defaults to all viewable; 4.7)
  • Taxonomy Access Control Lite (by role or user; ? lacks by action for each taxonomy term ?; by taxonomy)

These are all non-core contributed modules. Taxonomy Access Control will be described here as the method; it completely and efficiently provides selected private-side access. Like many things in Drupal, there is more than one way to accomplish private forums and member-only web sites; this describes just one.

This page will use an example with the goal to set up a site with:

  • One public forum board, which anyone can view.
  • Log-in required for posting in forum boards.
  • Special premium (or 'members-only') forum board.
  • Special moderator-only forum board.
  • Special 'moderator' role to run the forum boards.
  • Stories and blogs 'writers' create and 'editors' edit.
  • Web pages that are created and edited by 'maintainers.'

Taxonomy Access Control works with the core (built-in) Taxonomy module, which is used by the forum module. In fact, the forums have taxonomy terms built in. This page also describes how to set up private-side web sites, since it is done in essentially the same manner. A private-side web site might be useful if you want marketing information pages public, but maintain other content for subscribers or members.

The built-in 'access control' that comes standard is by module, such as access to the forum module, the book module, or the taxonomy module. It doesn't give permissions by forum board, or provide access limitations for different actions. Taxonomy Access Control, on the other hand, sets access by taxonomy term, as well as by two other criteria; it sets access by:

  • Each taxonomy term (rather than by module).
  • By role (like regular Access control).
  • By action: view, update, delete, create, list.

Comparison of the built-in 'Access Control' to 'Taxonomy Access Control' to keep them straight:

Access Control Taxonomy Access Control
core module (built in) contributed module (plug in)
Path: (administer > content management > categories) Path: (administer > user management > taxonomy access permissions)
Sets access to modules Sets access to taxonomy terms
Sets access by role Sets access by role
(some modules have action control) Sets access for each action (view, update, delete, create, list)

Private forum step-by-step directions:

Set up roles: Go to (administer > user management > roles), and create roles for the different classes of users. 'anonymous user' and 'authenticated user' are automatic. This example also uses 'moderator,' 'writer,' 'editor,' and 'maintainer.'

Down load and install the taxonomy_access module, and enable it at (administer > site building > modules). It works with the Taxonomy module. Taxonomy is built in to Drupal. The 'Taxonomy' module and the 'Forum' module should be enabled if not already.

Set up your forums at (administer > content management > forums). Forums have taxonomy terms built-in: the vocabulary is titled "Forum" and the different forum boards are each a term in the vocabulary. This example will use three boards: 'open,' 'premium,' and 'removed threads,' the latter for moderator use.

Set up other taxonomy vocabularies: Just to make things confusing, taxonomies are called 'categories' in the menu system. Go to (administer > content management > categories). Set up a 'vocabulary' for each different type of content. For this example, create 'web pages' vocabulary for node types 'Book pages' and 'Pages.' Create 'news' vocabulary for node types 'Blog entries' and 'Stories.'

Then assign terms to each of these vocabularies. Keeping the terms of each 'vocabulary' unique from terms in other vocabularies will help reduce confusion. The example 'web pages' vocabulary will have terms: contact, marketing, information, links, and miscellaneous. The example 'news' vocabulary will have terms: hot, current, and archive.

Set permissions: (administer > user management > access control) for the built-in access. Make sure both "authenticated users" and "anonymous users" have "node module > access content" privileges. They should also have access to the forum module, the story module, the page module, the book module, etc.: any node type for which there is a public version that they should be able to view. In other words, in the 'basic' access permissions, fairly open access is granted so that even anonymous users will be able to view stories, pages, and forums. That way, they'll be able to see the public content. The private content will be controlled by 'Taxonomy access control.'
      Note that some modules provide finer control to actions (such as the book module: create pages, edit own pages, edit all pages) which are similar to 'Taxonomy access control.' But Taxonomy access control will allow some pages to be public and others not, with the vocabulary terms that were defined earlier.
      Just to keep things confusing, note that the function of editing comments written by others (including replies to forum topics) is part of the comment module. Thus, the forum moderator function is assigned to the moderator role under basic access at "comment module > administer comments."

Forum access by role is at (administer > user management > taxonomy access permissions). Here you specify actions each role has permission to do (view, update, delete, create, list) for each taxonomy term! Note that the taxonomy vocabularies (and therefore the terms) can span across more than one node type, but you can grant access by terms, which are (to the extent you set them up) independent of node type.

For the "anonymous user" role, you can allow them to view which nodes (by vocabulary term) they can view. For the "forum" vocabulary, the "open" board.

For the "authenticated user" role, allow posting (not just viewing) in the "open" forum board.

For the "full member" role, add permissions to view and post to the premium board.

For the "moderator" role, you can let them view the "removed threads" forum board by checking the 'List' box in 'Taxonomy Access permissions,' and marking 'View' and 'Update' for this board as 'Allowed.' The permission to maintain the comments on the boards was done earlier by setting the basic access at "comment module > administer comments."
      For editing comments, an 'edit' and 'delete' link will be placed next to the 'reply' link at the bottom of every comment. For editing the original forum post, the 'edit' tab at the top of the main content area will be added.

Private-side web site setup is similar. It uses the taxonomies that you manually defined earlier. With 'Taxonomy' turned on, and the 'news' vocabulary set up for blog and story nodes, there will be a taxonomy section for each blog and story node created. Assign which term(s) apply to each page, story, or other node type. Then assign permissions in (administer > user management > taxonomy access permissions), where you setup who can view, update, delete, create, and list all the content on your site.

For the "anonymous user" role, allow viewing of "web pages" with terms "contact info," and "marketing." For the "news" vocabulary, say, only "hot" news.

For the "authenticated user" role, allow access to "current" news in addition to "hot."

For the "full member" role, add all the private-side content, including "web page" nodes that are information, links, and miscellaneous (not just 'contact info' and 'marketing') and access to the "news" 'archive' stories and blog posts.

For the "maintainer" role, you can give permission to edit pages and create new ones (but not edit member posts in the forums).

For the "reporter" role, you can give permission to create "story" nodes and "blog" nodes, and edit their own.

For the "editor" role, you can give permission to update (edit all) "story" nodes, and adjust them from "hot," "current," or "archive."

Overview of all these privileges crammed into one table:

Vocabulary: Forum Web Pages News
Node types: forum book, page story, blog
Terms: open premium removed threads contact, marketing information, links, miscellaneous hot current archive
Anonymous users: V N N V N V N N
Authenticated users: V, C, E N N V N V V N
Full Members: V, C, E V, C, E N V V V V V
Moderators: V, C, E, U, D V, C, E, U, D V, C, E, U V V V V V
Writers: V, C, E V, C, E N V V V, C, E V, C, E V, C, E
Editors: V, C, E V, C, E N V V V, C, E, U, D V, C, E, U, D V, C, E, U, D
Maintainers: V, C, E V, C, E N V, C, E, U, D V, C, E, U, D V V V
Privilege abbreviations:   N - None; V - View; C - Create; E - Edit (own); U - Update (all); D - Delete

How do I view a list of forum moderators? The answer to this question parallels the answer about setting up private forums: it is not part of the forum module. That would be the use. The function that you used to create the forum moderators was the assignment to a role that was then granted access to edit the forums. So the more general form of the question is, "How do I view a list of people in the role I set up to moderate forums?" The answer is go to (administer > user management > users) and use the 'filter' feature, which is inside the box 'Show only users where.' Select the 'role' radio button, and from the drop-down the role you set up to moderate the forum, and click the 'filter' button.

Performance of the web site will be affected by using 'Taxonomy access control,' but I don't have any hard information.

References:

  • Lullabot.com article on Forum Access vs. Taxonomy Access vs. Taxonomy Access Control Lite by Angie Byron webchick; excellent article complete with screen shots.

Summary: Hopefully that gives the idea. You could also set up your site to give your customers access to the private side of your company web site while maintaining the marketing pages open to everyone.

There is probably a way to use the workflow module to automatically assign users to roles, thereby granting them permissions through 'Taxonomy Access Control.' Likewise, 'Organic groups' sets up private taxonomy by group. If groups are distinct (rather than being super-sets and sub-sets of each other), this may be a better tool.

 

Single user blog - basic

Note that despite its name the blog module is not necessary for a single-user blog. Blog module is used to create blogs for multiple users.

The purpose of this recipe is to have you end up with a basic single user blog using primarily core modules to start you off with a solid foundation. When complete, you will have a place for blog posts with integrated rss feeds, an image gallery and be more familier with the basic configuration settings of Drupal. Using this as a starting point, you will be able to customize your site by adding additional modules as you see fit.

If you haven't already, please take a moment to review the best practices guide, paticularly the importance of backups and test sites.

Download and install Drupal on your server.
Click the create new user account to create your first user account.
Call it siteadmin (or whatever), enter an email address then click the login button and set your password. Make sure to set your timezone as well now.

Modules

?q=admin/modules
Click on administrator, then modules
From core turn on modules so you have these checked.

Note that in 5.x page and story are no longer modules but are default content types that can be configured at ?q=admin/content/types.

and from contributed modules download and install the following

settings

Click on administrator, then settings
There are some fields that you need to change, so put your site name and slogan. In the email address, put a sending email address (no-reply@example.com) and if you have a slogan put it here.
Enter something for the Mission statement.
Set your file system path and temporary directory for your site
Save your settings.

access control

?q=admin/access
Click on administrator, then access control
Click on the roles tab.
Create a role, we'll call it blogger. The rights you give it depend on how you use your site. For now, we'll select a few options.
administer and create images
administer menu
access content and administer nodes
create pages and edit own page
create url aliases
access statistics
create and edit own stories
access administration pages
adminster taxonomy

access control

?q=admin/users
Click on administrator, then users
click the configure tab and choose Only site administrators can create new user accounts. (single user blog remember?)
Click the add user tab and create a user account you will blog with. Assign it the role you created earlier.

themes

?q=admin/themes
Click on administrator, then themes
Select the configure tab.
Scroll down to Display post information on and uncheck the box next to Page.
In Toggle display and make sure that Site name, slogan and mission statement are checked. The nice thing about Drupal is that you can change the look and feel of your site without changing the links to the content at any time.

workflow

?q=admin/node
Click on administrator, then content
click the configure tab and then content types.
click on page
In the Workflow remove the check next to Promoted to front page. Save and do the same for Image as well.
This will set only story type content to be automatically promoted to the front page.

content

Now we can add content. Log out of your siteadmin account and log on with the account you created (we'll call ours joeuser). Go ahead and create a story page, something simple like first blog post. If you go to your home page, you will see that the default Drupal new site message is now gone and your post is in place.

For content types, it is suggested that you use Page type for more permanent type content (such as an About Me page) and create a new menu to link to it.

Terms Of Service - Terms of Agreement on Registration Page

This is a quick and easy way to have users agree to a terms of service during registration using the profile.module. Insure that the profile.module (part of core) is enabled, before begining this recipe.

Using Drupal 5.x:

goto administer -> profiles
Add a "checkbox" field, by clicking on the checkbox link under Add new field.

Now you are presented with a form, for the new profile field.
Fill out the form as follows (verbage can be changed to suit your own use case)

Category: Terms Of Service (This will be the category under which the terms of service will be shown on the registration page)

Title: I Agree (Title of the checkbox itself)

Form Name: profile_tos (The field the checkbox will occupy in the database)

Explaination:
Here you can list your Terms of Service that should be read over, before checkmarking the checkbox. or you could create a node for the Terms of Sevice and link to that node in this field.

Weight: Set as you wish

Visibility: Private field, content only available to privileged users.

Page title: Leave Blank

Now all you have to do is check mark the checkboxes at the bottom of page titled:
The user must enter a value & Visible in user registration form.

-----Submit Form-----

Now visit the new registration form to see whats been created! : )





Using Drupal 4.7:

goto administer -> settings -> profiles
Add a "checkbox" field, by clicking on the checkbox link under Add new field.

Now you are presented with a form, for the new profile field.
Fill out the form as follows (verbage can be changed to suit your own use case)

Category: Terms Of Service (This will be the category under which the terms of service will be shown on the registration page)

Title: I Agree (Title of the checkbox itself)

Form Name: profile_tos (The field the checkbox will occupy in the database)

Explaination:
Here you can list your Terms of Service that should be read over, before checkmarking the checkbox. or you could create a node for the Terms of Sevice and link to that node in this field.

Weight: Set as you wish

Visibility: Private field, content only available to privileged users.

Page title: Leave Blank

Now all you have to do is check mark the checkboxes at the bottom of page titled:
The user must enter a value & Visible in user registration form.

-----Submit Form-----

Now visit the new registration form to see whats been created! : )






Special Thanks: to Drupal User Florian for bringing this method to the attention of Community Members by way of the forums.

Thanks: to Drupal User Chill35 for proofreading and double checking steps involved to insure that steps are correct.

Use CCK to make a unique front page

The Lullabots suggested this one: If you want a custom front page, one way to do it is to use the Content Creation Kit. Basically, you make a custom content type which you will use only for your front page.

  • Make a new content type called "home page."
  • Define a field for each area of your front page: "tip of the day", "about the site", whatever. Probably all text fields.
  • Create a "node-content_home_page.tpl.php" file. Remember, when Drupal is showing CCK nodes, it automatically looks for a node-content_NAMEOFTYPE.tpl.php file. So you don't need to change template.php.
  • Format each field that you defined. If you created a field called "tip of day", you would do this...
  • <div id="custom_html_div">
    <h2>Tip of the Day<h2>
    <?php print $node->field_tip_of_day[0]['value']; ?>
    </div>

    You can get a list of what your fields are named by going to the "manage fields" tab under administer->content->content types.

  • Then create one node of this type and make it your front page. Like if it's node 34 you go to Administer->General Settings>Default Front Page and set that to "node/34" You could theoretically make other nodes of this type but the idea is that you're only going to need one node.

Now when authorized users/authors come to the site, they should see an "edit" tab right on the front page, letting them change each section easily. And you have a separate .tpl.php file for your front page. Handy!

You can still mix in blocks. Print an existing region like this:

<?php print theme('blocks', 'left_sidebar'); ?>

Or a custom region:

<?php print theme('blocks', 'home_page_region'); ?>

Creating a static archive of a Drupal site

Over time, some Drupal sites may no longer have any user interactivity or require any updating of content. They have essentially become static. Because these sites still require security administration, an administrator has to continue to upgrade the site with patches or consider removing the site all together. Alternatively, you may want to produce an offline copy for archiving or convient reference when you don't have access to the Internet.

Before simply removing the site, consider a third alternative: produce a static HTML mirror archive of all public pages on the site and replace the Drupal installation:

  1. Create a custom block and/or post a node to the front page that notes that the site has been archived from Drupal to static HTML. Be sure to include the date of the archiving.
  2. Disable interactive elements which will be nonfunctional in the static HTML version. For instance, make sure the following are disabled:
    • login block
    • who's online block
    • registration
    • anonymous commenting
    • links to the search module and/or any search boxes in the header
    • comment controls which allow the user to select comment display format
  3. Update all nodes by setting their comments to read only. This will eliminate the login or register to post comments link that would otherwise accompany each of your posts. You can do this through phpMyAdmin by running the following SQL command from the node table:
    update node set comment = '1';
  4. It can also be a good idea to disable any third party dynamically generated blocks; once the site is archived, it would be difficult to remove these blocks if the 3rd party services are no longer available.
  5. Use one of the following applications to produce the mirror:
    • HTTrack. The Windows GUI client version will produce the mirror with almost no configuration on your part.
    • Site Sucker. This is a Mac GUI option for downloading a site.
    • Wget is generally available on almost any 'nix machine and can produce the mirror from the command line. However, wget seems to have problems converting the relative style sheet URL's properly with many Drupal site pages. Modify your theme template to produce hardcoded absolute links to the stylesheets and try the following command:
      wget -q --mirror -p --html-extension --base=./ -k -P ./ http://example.com
  6. Verify that the offline version of your site works in your browser. Test to make sure that you properly turned off any interactive elements in Drupal that will now confuse site users.
  7. Remove your Drupal site (except for the style sheets if they were hardcoded in step 4) and replace with your archives.

Theme developer's guide

This section of our handbook documents aspects of our theme system that will be of interest to theme developers.

An important note- when developing a theme using any of the methods described here, you must be sure that the name of the theme is not the same as the name of any module being used on the site because the function names may collide and your site may no longer function correctly.

Javascript snippets (for theming, etc.) can be found in the Javascript section.

Theming overview

Note: this page describes the theme system from a themer's perspective. If you are a module coder looking to make your module themable, you should read this page.

As of version 4.5, Drupal's theme system is very flexible. The new structure makes it easy to plug components together to form your theme: templating engines, templates, stylesheets and PHP.

The chart is for Drupal 4.5 and 4.6 but is the structure is still valid. In 4.7 the default theme engine that is included with Drupal will be PHPTemplate. Both the default Blue Marine and Pushbutton themes become PHPTemplate based and the XTemplate engine becomes a contributed theme engine.

Here's how some existing themes are built:

Theme Engine (PHP) Template (XHTML) Style (CSS)
Garland PHPTemplate .xtmpl .css
Pushbutton XTemplate .xtmpl .css
Box Grey PHPTemplate .tpl.php .css
Box Cleanslate .css
Bluebeach .tpl.php .css
Chameleon Chameleon.theme .css
Marvin .css

A 'theme' is now an abstract thing, which can be formed in several ways:

  • PHP .theme file containing overrides for theme_functions: e.g. Chameleon
  • Template file (.xtmpl, .tpl.php) for a templating engine (XTemplate, PHPTemplate, ...): e.g. Pushbutton, Bluebeach
  • Style sheet for an existing template or theme: e.g. Marvin, Box Cleanslate

The directory structure for the example above looks like this:

themes/engines/xtemplate/xtemplate.engine
themes/engines/phptemplate/phptemplate.engine
themes/pushbutton/xtemplate.tmpl
themes/pushbutton/style.css
themes/box_grey/page.tpl.php
themes/box_grey/style.css
themes/box_grey/box_cleanslate/style.css
themes/bluebeach/page.tpl.php
themes/bluebeach/style.css
themes/chameleon/chameleon.theme
themes/chameleon/style.css
themes/chameleon/marvin/style.css

Themes and templates are placed in their own subdirectory in the themes directory. The theme engines will scan every subdirectory for template files (.xtmpl, .tpl.php, ...). If a style.css file is present, it will also be used.

You can also make CSS-only themes by making a subdirectory in any theme directory and placing a new style.css file in it. Drupal will combine the new stylesheet with the template it belongs in, and make it available as a new theme. This is how the Marvin and Box Cleanslate themes work.

Finally, if there is a screenshot.png file in the theme directory, Drupal will show it in the theme administration screen.

Creating custom themes

If you want to create a custom theme, you can either customize an existing theme or start from scratch.

To customize an existing theme, just copy it to a new directory in themes, and give it a unique name. Themes should not have a name that is the same as any of the default modules in Drupal or any custom modules you might have enabled or configured. Then modify the copy as much as you want. Depending on whether the theme is template or .theme-file based, you can use PHP or XHTML/CSS to modify it. As explained above, if you only want to alter the CSS of a theme, then just place a new style.css file in a subdirectory of the theme: it will appear as a new theme in Drupal.

If you want to start from scratch, there are several ways to go. If you're not a programmer, then the easiest solution is to use one of the template engines. By default, Drupal 4.6 and previous versions comes with the XTemplate theme engine, which requires you to create an (X)HTML skeleton with special markers. See the XTemplate documentation for more info. As of Drupal 4.7 PHPTemplate changes to the default theme engine and XTemplate becomes a contributed theme engine.

Drupal themes used to be coded directly in PHP. This method is still available, but is harder to use and maintain than template-based themes.

Regions in themes

As of version 4.7, Drupal theme authors can define and implement any number of 'regions' for content to be rendered into. This regioning system replaces the previous 'left' and 'right' sidebar regions and should enable much more flexible layout and design.

For themes not based on theme engines, available regions are defined in a themename_regions() function in the .theme file. Engine-based themes have their engine's regions (defined in a .engine file) and may also implement their own regions.

Content is assigned to regions through the block system and also through drupal_set_content() calls. For example, drupal_set_content('left', 'Hello there.') would assign the text 'Hello there.' to the left region (usually implemented as a left sidebar). Any regions defined in the current theme or its theme engine are available on the block configuration page.

The first region defined in a theme's .theme file (or a theme's engine's .engine file) becomes that theme's 'default' region, used for example as the default option for block placement in the block configuration pages.

See the page on upgrading 4.6 themes for minimal upgrade requirements; see the plain PHP theme and PHPTemplate pages for details on how to implement regions.

PHPTemplate theme engine

PHPTemplate is a theme engine written by Adrian Rossouw (who is also behind the theme reforms in Drupal 4.5).

It uses individual something.tpl.php files to theme Drupal's theme_something() functions. Drupal's themeable functions are documented on the Development Plumbing site. Every file contains an HTML skeleton with some simple PHP statements for the dynamic data. Thus, PHPTemplate is an excellent choice for theming if you know a bit of PHP: with some basic PHP snippets, you can create advanced themes easily.

If you don't know PHP, then PHPTemplate can still be a good choice because only small bits of code are involved. They can just be copy/pasted into your template.

An extended Forum discussion provides some of the reasoning behind the creation of PHPTemplate.

Installing PHPTemplate in 4.6

The engine that runs PHPTemplate is included in 4.7 Drupal but not in 4.6 . For 4.6, the themes that use PHPTemplate (e.g. Box_grey, Kubrick, Persian) require that the PHPTemplate engine be installed. To set this up:

  1. Download the latest release of the PHPTemplate Engine
  2. Upload this folder into the drupal_base/themes/engines directory on your site.

You will now be able to use and configure PHPTemplate themes.

Creating a new PHPTemplate

To create a new PHPTemplate, create a new directory under your themes directory, for example themes/mytheme. Then, you need to create a file called page.tpl.php in that directory. Or you may copy an existing theme and just rename the directory. For Drupal 5, consider basing your theme on Zen or Blue breeze.

This is the only file which is absolutely required. It overrides the theme('page') function, which outputs the final page contents, along with all the extra decorations like a header, tabs, breadcrumbs, sidebars and a footer.

You can create files to override the following functions:

The PHPTemplate package contains example template files for most of these, see box_grey for an example of page.tpl.php. Simply copy them into your theme/mytheme directory and edit them. Note that you will need to visit administer > themes for PHPTemplate to refresh its cache and recognize any new .tpl.php files.

If you want to theme a function other than the defaults listed here, you need to provide an override yourself.

Customizing themes for node types, terms, sections, paths, and front page

Note: for Drupal 5, you probably want to see http://drupal.org/node/104316 instead

You can customize your theme for individual posts, URL paths, taxonomy terms, sections, and your front page.

A more comprehensive collection of examples for theming nodes, pages and other can be found in the PHP Template Snippets >> Customising full page layouts and sections section of the handbook.

One way to customize your site is to use the sections module to apply different themes to different parts of your site. However, if you use a custom theme or modules that have their own theme template like the event module this requires a lot of duplicate CSS and image resources.

The key to having multiple themes on one site is determining from the theme engine what part of the site you are in. Here is a list of the conditions we can check for from the template engine.

$ls_front phptemplate variable can be used to check if this is the front page.

<?php
if ($is_front) {
  include(
'front.tpl.php');
  return;
}
?>

The drupal arg() function is used to get the arguments in a path.

arg(0)=="admin"// is /admin
arg(0) =="node"// is /node
arg(0)=="user" // is /user
arg(0)=="node"&&arg(1)=="add" // is /node/add
arg(0)=="node"&& arg(2)=="edit" // is /node/###/edit
arg(0)=="user"&&arg(1)=="add" // is /user/add
arg(0)=="admin"&&arg(1)="user"&&arg(2)=="create" // is /admin/user/create
arg(0)=="alias"&&arg(1)=="alias1" is /alias/alias1
arg(0)=="taxonomy"&&arg(1)=="term"&&arg(2)=="term#" // is /taxonomy/term/term#

//arg(1)=="comment"
//arg(2)=="reply"

You might be interested in Customizing your node template by node type

Once we know the front_page value or path we can use different theme templates or apply CSS files or modify the CSS classes of the xHTML tags. This section will be broken into three subpages shortly.

  1. Apply a specific template such as front.tpl.php, admin.tpl.php, term#.tpl.php.
    <?php
    if ($is_front) {
        include 'front.tpl.php';
        return;
    }
    elseif (arg(0)=="taxonomy"&&arg(1)=="term"&&arg(2)=="term#") { // add argument of choice
        include 'term#.tpl.php';// add template of choice
        return;
    }
  2. Alternately we can use different CSS for each section of our site. In the example below we determine the section based on path and then apply a different CSS. In the example below we determine which CSS to add. Further explanation pending.
  3. <?php
    if ($is_front) {
        include
    'page_front.tpl.php';
        return;
    }
    if (
    $node->type == 'nodetype') { // all pages on my site are 'nodetype'
       
    $my_path = explode("/", drupal_get_path_alias('node/'.$node->nid)); // all pages have path like 'section/page'
       
    $my_path = $my_path[0];
    } else {
       
    $my_path = arg(0);
    }
    switch (
    $my_path) {
        case
    'using':
           
    $section = 1;
            break;
        case
    'education':
           
    $section = 2;
            break;
        case
    'company':
           
    $section = 3;
            break;
        case
    'image':
           
    $section = 4;
            break;
        case
    'forum':
           
    $section = 5;
            break;
        default:
           
    $section = 0;
    }
    $css_section = 'section'.$section;
    ?>
  4. We can also apply CSS classes directly to the xHTML that is to be themed
  5. PHPTemplate theme:
    on your body tag (from bluemarine) in page.tpl.php :
    <body <?php print $onload_attributes ?> >

    Would become:
    <body class=" <?php print arg(0); ?> " <?php print $onload_attributes ?> >

    You can then use .admin as a parent css selector for admin sections.

    For Smarty
    on your body tag (from bluemarine_smarty) in page.tpl :
    <body{$onload_attributes}>

    Would become:
    <body class="{php} echo arg(0); {/php}"{$onload_attributes}>

    When viewing the root node this may produce empty class attribute values -- including a constant prefix to the class would prevent this.

How to generate <body> class/id attributes for each page

This code will create a body class and id for every page on your site.

In your page template(page.tpl.php), replace your existing body tag…

<body <?php print theme("onload_attribute"); ?>>

with the following code:

<?php
/**
* Create a <body> class and id for each page.
*
* - Class names are general, applying to a whole section of documents (e.g. admin or ).
* - Id names are unique, applying to a single page.
*/
// Remove any leading and trailing slashes.
$uri_path = trim($_SERVER['REQUEST_URI'], '/');
// Split up the remaining URI into an array, using '/' as delimiter.
$uri_parts = explode('/', $uri_path);
/**
* If the gallery module is installed, translate URI's like
* "gallery?g2_view=core:ShowItem&g2_itemId=420" into a string like...
* "gallery-item-420".
*/
if (module_exist('gallery') && arg(0) == 'gallery') {
   
$body_id = str_replace('gallery?g2_view=core:ShowItem&g2_itemId=', 'gallery-item-', $uri_path);
   
$body_class = 'gallery';
}
// If the first part is empty, label both id and class 'main'.
elseif ($uri_parts[0] == '') {
   
$body_id = 'main';
   
$body_class = 'main';
}
else {
   
// Construct the id name from the full URI, replacing slashes with dashes.
   
$body_id = str_replace('/','-', $uri_path);
   
// Construct the class name from the first part of the URI only.
   
$body_class = $uri_parts[0];
}
/**
* Add prefixes to create a kind of protective namepace to prevent possible
* conflict with other css selectors.
*
* - Prefix body ids with "page-"(since we use them to target a specific page).
* - Prefix body classes with "section-"(since we use them to target a whole sections).
*/
$body_id = 'page-'.$body_id;
$body_class = 'section-'.$body_class;
?>

(immediately followed by...)

<body class="<?php print $body_class ?>" id="<?php print $body_id ?>" <?php print theme("onload_attribute"); ?>>

Note: Class and id names are built from a combination of internal drupal designations and your current URI as seen in the address bar. So to use this properly you really need to have clean urls turned on.

Example of use:
Since these style hooks are even applied to the admin pages so they are especially good for overridding the layout of either the entire admin section, or just some specific troublesome admin pages that tend to break layouts.

For instance, I use a fixed width layout and my main content area has a width of 744px. When I go to an admin page with an unweildy table, like “themes”, my sidebar crashes into the tables and I can't click certain links - it's a pain, but I'm not going to design my public site just to accomodate some administrative table. Using this snippet, when I go to admin/themes, I will get the following style hooks:

<body class="section-admin" id="page-admin-themes">

So now all I have to do is create another rule that defines an alternative width for my entire admin section:

.content { width="744px" }
.section-admin .content { width="100%" }

So now when I go to /admin/themes (or admin/*) my layout pops open to accomodate the wider content.

Hint: If you enable the path alias module, and take the time to create a heirarchical path for each of your pages, you can create easily create site sections and apply a completely different layout (background image, colors, widths, etc.)

Dev ticker

A cool trick that really makes theme development and styling easier, is to display the current page's body class and id where you can see them. The following instructions will create a strip across the top of your page that displays the style hooks for the current page. The cool thing is that it will only be vsible to users with role ‘dev'.

Here is an example of what it looks like on my site

. *Notice the red bar across the top of the page.
  1. Create a `dev' role and add yourself to it.
  2. Place this code as the first line after your opening body tag.
  3. Add some rules to your theme's stylesheet(style.css):
    #ticker {
    padding-left: 10px;
    position: absolute; top: 0; left: 0;
    color: #FFFC7E;
    text-align: left;
    font-size: 9px;
    width: 100%;
    background-color: #B1240E;
    }
Node matches primary menu link

Here's a code snippet to find if the current node matches a primary menu link. This code assumes the primary menu links are of the form '/somepage'.

<?php if (count($primary_links)) : ?>
    <ul class="primary">
        <?php foreach ($primary_links as $link): ?>
            <?php preg_match("/<a\s*.*?href\s*=\s*['\"]([^\"'>]*).*?>(.*?)<\/a>/i", $link, $matches); ?>
            <?php if (('/'.drupal_get_path_alias('node/'.$node->nid))==$matches[1]): /* if $node exists */ ?>
                <li class="selected"><?php print $link?></li>
            <?php elseif ('/'.arg(0)==$matches[1]): /* else try to use arg */ ?>
                <li class="selected"><?php print $link?></li>
            <?php elseif ((drupal_get_path_alias('node/'.$node->nid)=='node/') && (arg(0)=='node') && ($matches[1]=='/')): /* else if home */ ?>
                <li class="selected"><?php print $link?></li>
            <?php else: ?>
                <li><?php print $link?></li>
            <?php endif; ?>
        <?php endforeach; ?>
    </ul>
<?php endif; ?>

This code might not do what you expect for psuedo nodes that are part of a module as it currently only uses arg(0).

Block.tpl.php

Lays out content for blocks (left and/or right side of page). This template is optional, and can be overridden by copying the default template and modifying it.

Available variables
$block (object)
includes:
$block->module
The name of the module that generated the block.
$block->delta
The number of the block, in the module.
$block->subject
The block title.
$block->content
The html content for the block.
$block->status
Status of block (0, or 1).
$block->path
The path that matches whether or not a block is displayed.
$block->region
Region name, by default the available regions are 'left', 'right', 'header' and 'footer'.
$block->throttle: Throttle setting.
$id
The sequential id of the block displayed, ie: The first block is 1, the second block is 2 etc.
$block_id
The same as $id, but is reset for the left and right sidebars.
$zebra
Wether or not the block is odd , or even. This is useful for creating 'zebra stripes' with your css. This value will be either 'odd', or 'even'.
$block_zebra
The same as $zebra, but is reset for the left and right sidebars.
Default template

The default block.tpl.php, which can be found at themes/engines/phptemplate/block.tpl.php.

<div id="block-<?php print $block->module .'-'. $block->delta; ?>" class="block block-<?php print $block->module ?>">

<?php if ($block->subject): ?>
  <h2><?php print $block->subject ?></h2>
<?php endif;?>

  <div class="content"><?php print $block->content ?></div>
</div>
4.6 vs. 4.7 and higher

In 4.6 these variables are different:

  • $block->region only allows for Left (0), or Right(1) column.
  • Use $seqid instead of $id.
  • Use $block_seqid instead of $block_id.

In 4.7 and higher versions it is also possible to define custom regions in addition to the default 'left', 'right', 'header' and 'footer'.

Suggested reading:

Add an "Edit this Block" link

I am making a few sites that I won't ultimately maintain and I find people get confused about nodes and books and pages and blocks, so I added an "edit this block" link to the bottom of all custom blocks, visible to users with block admin privs (I stuck pretty much the same thing in my page.tpl.php right after the footer to point users with admin privs to the page where the footer is set.

<div class="<?php print "block block-$block->module" ?>" id="<?php print "block-$block->module-$block->
delta"
; ?>
">
<?php print $block->subject ?>
  <div class="content"><?php print $block->content ?>
  <?php  if ($block->module == "block"):?>
      <?php  if (user_access('administer blocks')) :?>
      <br /><center><a href='/admin/block/edit/<?php print $block->delta;?>'>(edit this block)</a></center>
      <?php endif; ?>
  <?php endif; ?>
  </div>
</div>

Make sure the edit link uses the correct path for your version of Drupal.

  • /admin/build/block/configure for 5.x
  • /admin/block/configure for 4.7
  • /admin/block/edit/ for 4.6

Box.tpl.php

Prints a simple html box around a page element. For instance: The comment view options are surrounded by box.tpl.php.

Available variables
$title
The title of the box.
$content
The content of the box.
$region
Region. main, left or right.
Default template
<div class="box">
  <h2><?php print $title ?></h2>
  <div class="content"><?php print $content ?></div>
</div>

Comment.tpl.php

Define the HTML for a comment block. This doesn't have anything to do with comment threading, just the actual comment.

Available variables
$new
Translated text for 'new', if the comment is infact new.
$comment(object)
Comment object as passed to the theme_comment function.
$submitted
Translated post information string.
$title
Link to the comment title.
$picture
User picture HTML (include <a> tag.) , if display is enabled and picture is set.
$links
Contextual links below comment.
$content
Content of link.
$author
Link to author profile.
$date
Formatted date for post.
Default template
<div class="comment <?php print ($comment->new) ? 'comment-new' : '' ?>">
<?php if ($comment->new) : ?>
  <a id="new"></a>
  <span class="new"><?php print $new ?></span>
<?php endif; ?>

<div class="title"><?php print $title ?></div>
  <?php print $picture ?>
  <div class="author"><?php print $submitted ?></div>
  <div class="content"><?php print $content ?></div>
  <?php if ($picture) : ?>
    <br class="clear" />
  <?php endif; ?>
  <div class="links"><?php print $links ?></div>
</div>

Node.tpl.php

This template controls the display of a node, and a node summary.

Available variables
$title
Title of node.
$node_url
Link to node.
$terms
HTML for taxonomy terms.
$name
Formatted name of author.
$date
Formatted creation date.
$sticky
True if the node is sticky on the front page.
$picture
HTML for user picture, if enabled.
$content
Node content, teaser if it is a summary.
$links
Node links.
$taxonomy (array)
array of HTML links for taxonomy terms.
$node (object)
The node object. To view all of the properties of a current $node object, put the following line of code in your $node.tpl.php: <pre><?php print_r($node); ?></pre>
$main (4.6)
True if the node is appearing in a context, like the front page, where only the teaser should be shown. No longer used in 4.7 and later.
$page
True if the node is being displayed by itself as a page.
$submitted
Author and create date information, if the node info display is enabled for this node type.
Default template for 5.x
<div id="node-<?php print $node->nid; ?>" class="node<?php if ($sticky) { print ' sticky'; } ?><?php if (!$status) { print ' node-unpublished'; } ?> clear-block">

<?php print $picture ?>

<?php if ($page == 0): ?>
  <h2><a href="<?php print $node_url ?>" title="<?php print $title ?>"><?php print $title ?></a></h2>
<?php endif; ?>

  <div class="meta">
  <?php if ($submitted): ?>
    <span class="submitted"><?php print $submitted ?></span>
  <?php endif; ?>

  <?php if ($terms): ?>
    <span class="terms"><?php print $terms ?></span>
  <?php endif;?>
  </div>

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

<?php
 
if ($links) {
    print
$links;
  }
?>


</div>
Default template for 4.7
<div class="node<?php if ($sticky) { print " sticky"; } ?><?php if (!$status) { print " node-unpublished"; } ?>">
  <?php if ($page == 0): ?>
    <h2><a href="<?php print $node_url ?>" title="<?php print $title ?>"><?php print $title ?></a></h2>
  <?php endif; ?>
  <?php print $picture ?>

  <div class="info"><?php print $submitted ?><span class="terms"><?php print $terms ?></span></div>
  <div class="content">
    <?php print $content ?>
  </div>
<?php if ($links): ?>

    <?php if ($picture): ?>
      <br class='clear' />
    <?php endif; ?>
    <div class="links"><?php print $links ?></div>
<?php endif; ?>
</div>
Separate theme for a specific node

If you want to use a specific node.tpl.php for a node, paste or merge this code into your template.php:

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'node':
     
$vars['template_files'] = array('node-'. $vars['nid']);
      break;
  }
  return
$vars;
}
?>

You can now for example create files named node-34.tpl.php to create a custom theme for just the node 34.

Theme teaser to look different than full node

You can give a teaser its own unique look by using the following condition on any node.tpl.php file:

<?php
if ($page == 0): //if node is being displayed as a teaser
//Anything here will show up when the teaser of the post is viewed in your taxonomies or front page
endif;
if (
$page == 1): //if node is being displayed as a full node
//Anything here will show up when viewing only your post
endif;
?>
Theming specific node (content) types

The file node.tpl.php is used to theme individual nodes. This one file generically covers all content types and you can edit it to make changes across the board as detailed on http://drupal.org/node/11816 . To theme individual content types in different ways, you need to create a file node-$type.tpl.php, where $type is the name of the content type, for each type you wish to theme. Some examples:

node-story.tpl.php would theme only story nodes.
node-page.tpl.php would theme only page nodes. (Note that this is different from page.tpl.php which controls the layout of the entire page including header, sidebars, etc)
node-forum.tpl.php would theme only forum nodes.
node-book.tpl.php would theme only book nodes.

In general, you can replace the $type with any content type name. One exception is flexinode, which uses numbers internally to name the types. Theming flexinodes is covered in http://drupal.org/node/31646 . Also, you cannot use node-admin.tpl.php to theme just the admin pages.

Once you have this new file, copy in the contents of node.tpl.php and modify it however you wish. All the variables listed on http://drupal.org/node/11816 can be used. When Drupal displays a node that is the type you put in the filename, it will use this file instead of node.tpl.php.

Create a theme for a specific flexinode type

From the comment by jmengle at http://drupal.org/node/17565#comment-47183

If you are using this feature with a node type created by flexinode, the name you give the node type (and therefore the name that is displayed) is not the name of the node type that the system uses. The node types created by flexinode are actually named "flexinode-1", "flexinode-2", etc. One way to find this name is to look at the database table "node" in the name column.

For example, if you used flexinode to create a node type called "info", then if you looked at the database, you would instead see a node named "flexinode-1". So, the template file you would create for "info" would need to be named "node-flexinode-1.tpl.php".

You can also find the number of the flexinode by going to admin>>content>>content types and click on the choosen type. The number will be in the url (i.e. node/types/edit_type/1 would be flexinode-1).

In your node template (ie node-flexinode-1.tpl.php) you will reference the different fields of your flexinode with

<?php print $node->flexinode_1 ?>

You can then find the different fields in a similar way from the admin>>content>>content types page, by clicking on edit for the choosen field and looking at the cooresponding url (i.e. node/type/edit_field/1 would be $node->flexinode_1).

This works well for fields of text area, text field, and date/time. For image you would use
<img src="/<?php print $node->flexinode_1->filepath ?>" />.

If the print call results in "Object" then you need to get into the code some. Substitute the print command with

<?php print serialize($node->flexinode_1)?>

It will look strange, but if you look it is a series of variables and their content. Find the one that meets your needs. If you figure out how a particular field type works, you might post the code here.

Page.tpl.php

This template defines the main skeleton for the page.

Available variables (Alphabetical)
$breadcrumb
HTML for displaying the breadcrumbs at the top of the page.
$closure
Needs to be displayed at the bottom of the page, for any dynamic javascript that needs to be called once the page has already been displayed.
$content
The HTML content generated by Drupal to be displayed.
$directory
The directory the theme is located in , e.g.
themes/box_grey or themes/box_grey/box_cleanslate.
$footer_message
The footer message as defined in the admin settings.
$head
HTML as generated by drupal_get_html_head().
$head_title
The text to be displayed in the page title.
$help
Dynamic help text, mostly for admin pages.
$is_front
True if the front page is currently being displayed. Used to toggle the mission.
$language
The language the site is being displayed in.
$layout
This setting allows you to style different types of layout ('none', 'left', 'right' or 'both') differently, depending on how many sidebars are enabled.
$logo
The path to the logo image, as defined in theme configuration.
$messages
HTML for status and error messages, to be displayed at the top of the page.
$mission
The text of the site mission.
$node
(5.x and after only)If you are in page.tpl.php displaying a node in full page view then $node is available to your template.
$onload_attribute
(4.7 and older only) Onload tags to be added to the head tag, to allow for autoexecution of attached scripts.
$primary_links (array)
An array containing the links as they have been defined in the phptemplate specific configuration block.
$scripts
(5.x and after only) HTML to load the JavaScript files and make the JS settings available. Previously, javascript files are hardcoded into the page.tpl.php
$search_box
True(1) if the search box has been enabled.
$search_button_text
(4.7 and older only)Translated text on the search button.
$search_description
(4.7 and older only)Translated description for the search button.
$search_url
(4.7 and older only)URL the search form is submitted to.
$secondary_links (array)
An array containing the links as they have been defined in the phptemplate specific configuration block.
$sidebar_left
The HTML for the left sidebar.
$sidebar_right
The HTML for the right sidebar.
$site
The name of the site, always filled in.
$site_name
The site name of the site, to be used in the header, empty when display has been disabled.
$site_slogan
The slogan of the site, empty when display has been disabled.
$styles
Required for stylesheet switching to work. This prints out the style tags required.
$tabs
HTML for displaying tabs at the top of the page.
$title
Title, different from head_title, as this is just the node title most of the time.
Default template

Here is the contents of the bluemarine template's page.tpl.php for Drupal 5, to give you an idea of the layout of the file.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language ?>" xml:lang="<?php print $language ?>">

<head>
  <title><?php print $head_title ?></title>
  <?php print $head ?>
  <?php print $styles ?>
  <?php print $scripts ?>
  <script type="text/javascript"><?php /* Needed to avoid Flash of Unstyle Content in IE */ ?> </script>
</head>

<body>

<table border="0" cellpadding="0" cellspacing="0" id="header">
  <tr>
    <td id="logo">
      <?php if ($logo) { ?><a href="<?php print $base_path ?>" title="<?php print t('Home') ?>"><img src="<?php print $logo ?>" alt="<?php print t('Home') ?>" /></a><?php } ?>
      <?php if ($site_name) { ?><h1 class='site-name'><a href="<?php print $base_path ?>" title="<?php print t('Home') ?>"><?php print $site_name ?></a></h1><?php } ?>
      <?php if ($site_slogan) { ?><div class='site-slogan'><?php print $site_slogan ?></div><?php } ?>
    </td>
    <td id="menu">
      <?php if (isset($secondary_links)) { ?><?php print theme('links', $secondary_links, array('class' =>'links', 'id' => 'subnavlist')) ?><?php } ?>
      <?php if (isset($primary_links)) { ?><?php print theme('links', $primary_links, array('class' =>'links', 'id' => 'navlist')) ?><?php } ?>
      <?php print $search_box ?>
    </td>
  </tr>
  <tr>
    <td colspan="2"><div><?php print $header ?></div></td>
  </tr>
</table>

<table border="0" cellpadding="0" cellspacing="0" id="content">
  <tr>
    <?php if ($sidebar_left) { ?><td id="sidebar-left">
      <?php print $sidebar_left ?>
    </td><?php } ?>
    <td valign="top">
      <?php if ($mission) { ?><div id="mission"><?php print $mission ?></div><?php } ?>
      <div id="main">
        <?php print $breadcrumb ?>
        <h1 class="title"><?php print $title ?></h1>
        <div class="tabs"><?php print $tabs ?></div>
        <?php print $help ?>
        <?php print $messages ?>
        <?php print $content; ?>
        <?php print $feed_icons; ?>
      </div>
    </td>
    <?php if ($sidebar_right) { ?><td id="sidebar-right">
      <?php print $sidebar_right ?>
    </td><?php } ?>
  </tr>
</table>

<div id="footer">
  <?php print $footer_message ?>
</div>
<?php print $closure ?>
</body>
</html>
$page==0 or $is_front

If you plan to use $is_front for distinguishing between the front page and non-front pages better read this. Especially read the comments...
http://drupal.org/node/29132

Simply put $is_front is set ONLY when you are on the front page. If you click on a taxonomy term and get a listing of nodes classified under that taxonomy term then $is_front is NOT set for that page.

$page is equal to 0 for all front-like pages (including nodes classified under a specific taxonomy term etc).

How to Call Additional CSS Files
Drupal 5.x

Additional styles sheets can be added to the array by using the drupal_add_css() function:

<?php
$styles
= drupal_get_css(drupal_add_css(path_to_theme() . '/example.css', 'theme', 'all', TRUE));
?>

In this example, example.css is retrieved from the theme folder. Alternatively, style sheets can be placed in the files directory and accessed by leaving off the path_to_theme() function and using the 'files/subdirectory/example.css' notation.

The drupal_add_css() function can take the following optional parameters:

  1. Path to the style sheet file (Relative to the base_path())
  2. Type of style sheet being added (module or theme)
    • Style sheets are called in order starting with the system styles sheets, then the module style sheets, and then the theme style sheets. If a style sheet was created to override the CSS that came before it, using the theme option will append it to end of the array and will be called last.
  3. The Media that the style sheet should be applied to (all, screen, print, handheld, etc.)
  4. The Preprocess option will aggregate and compress the style sheet if this feature has been turned on under the performance section (TRUE, FALSE)
For Drupal 4.7.x
<?php
print $head;
print
theme('stylesheet_import', base_path() . path_to_theme() . '/your_extra.css');
print
$styles;
?>

So this is the order we end up with:

  1. drupal.css
  2. module CSS (for example, event.css from event.module)
  3. theme CSS (for example, your_extra.css)
  4. style CSS (style.css files from any theme)

Style sheets can override rules applied in any CSS which precedes them. (i.e. style CSS can override everything else, except for user CSS)

Creating a separate admin theme

NOTE: This article was originally posted here.

Note2: Drupal5 has an administration theme selector built in. No need for these shenanigans.

Want to create a front page that's styled differently from the rest of your site? Perhaps you need a separate admin theme? Or how about a login page which only shows the login block and nothing else? With a little PHP knowledge these problems are easy to solve.

Note: You must be using the PHPTemplate theme engine for your theme. An easy way to determine this is by looking for files ending in .tpl.php within your site's theme folder. PHPTemplate is compatible with Drupal 4.6 and up. As a matter of fact, it's the default theme engine for Drupal 4.7 since it combines the best of both worlds for designers and programmers. Designers get an easy way to manipulate HTML lightly sprinkled with PHP variables for dynamic content, and developers get a fast rendering template system that's a snap to extend. Here's what a template looks like using PHPTemplate.

<div class="node">
  <h2 class="title"><a href="/<?php print $node_url?>"><?php print $title?></a></h2>
  <span class="submitted"><?php print $submitted?></span>
  <span class="taxonomy"><?php print $terms?></span>
  <div class="content"><?php print $content?></div>
  <div class="links">&raquo; <?php print $links?></div>
</div>

Developers can override any of the above PHP variables or even add new ones to pass to the template for the designer to use. What most folks don't know is that for every template section, PHPTemplate looks for a special variable named template_file which stores the name of the file to execute. This is your key to conditionally loading different theme files.

Working with template_file

Intercepting PHPTemplates' default variables is accomplished through creating a new function named _phptemplate_variables() within your theme. Navigate to your site's current theme folder and look for a file called template.php. If that file doesn't exist, create it. Now add the following function:

<?php
/**
* Intercept template variables
*
* @param $hook
*   The name of the theme function being executed
* @param $vars
*   A sequential array of variables passed to the theme function.
*/
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
   
// more code here...
 
}

  return
$vars;
}
?>

This function is called by Drupal just after a template engine call is made. Examples of this include: loading the node template, the block template or what we want, the page template. The $hook parameter above is the name of the template section the system is calling (node, block, page, etc).

First the system assigns a bunch of default variables. In the case of page hook: $sidebar_left, $sidebar_right, $footer_message, $search_box, $title, $content, etc. If _phptemplate_variables() exists, any values set there will override the defaults, including your opportunity to change the name of the template file the system should be looking for. Let's take a look at our first example.

Separate Administration Theme

The admin area is known for its wide tables which can be difficult to style when they're found nowhere else on your main site. Many folks find themselves wishing for a separate theme. Here's how to do it.

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'page':
      if ((
arg(0) == 'admin')) {
       
$vars['template_file'] = 'page-admin';
      }
      break;
  }

  return
$vars;
}
?>

Here we're changing value of $vars['template_file'] from page.tpl.php to page-admin.tpl.php.

Let's build our new admin theme. You probably want to use the Blue Marine layout as the admin theme. If your already using Blue Marine for the rest of your site that's okay as you'll get that basic idea. Grab a copy of page.tpl.php from the Blue Marine theme and paste it into your theme folder and rename the file to page-admin.tpl.php. Next, we want this file to use a separate stylesheet, so copy the Blue Marine style.css file and rename it to admin-style.css. Finally edit page-admin.tpl.php so it knows about the new CSS file.

<?php //print $styles ?>
<style type="text/css" media="all">@import "<?php print base_path(). $directory; ?>/admin-style.css";</style>

Now navigate to your admin section and behold the marvels of PHPTemplate.

Custom Front Page

Here's how we do the special front page on Lullabot.com.

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'page':
      global
$user;
      if (
$vars['is_front']) {
       
$vars['template_file'] = 'page-index';
      }
      break;
  }

  return
$vars;
}
?>

Create a file called page-index.tpl.php and start styling away.

Stand-alone Login Page

Sometimes you want the login/register page to stand alone, with no other action for a user to take.

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'page':
      global
$user;
      if (
arg(0) == 'user'){
        if (
$user->uid == 0) {
         
$vars['template_file'] = 'page-login';
        }
        elseif (
arg(1) == 'login' || arg(1) == 'register' || arg(1) == 'password' ) {
         
$vars['template_file'] = 'page-login';
        }
      }
      break;
  }

  return
$vars;
}
?>

Create a page-login.tpl.php such as the following

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language ?>" xml:lang="<?php print $language ?>">

<head>
  <title><?php print $head_title ?></title>
  <?php print $head ?>
<?php print $styles ?>
</head>

<body<?php print $onload_attributes  ?>>
  <table border="0" cellpadding="0" cellspacing="0" id="header">
    <tr>
      <td id="logo">
        <?php if ($logo) { ?><a href="/<?php print $base_path ?>" title="<?php print t('Home') ?>"><img src="/<?php print $logo ?>" alt="<?php print t('Home') ?>" /></a><?php } ?>
        <?php if ($site_name) { ?><h1 class='site-name'><a href="/<?php print $base_path ?>" title="<?php print t('Home') ?>"><?php print $site_name ?></a></h1><?php } ?>
        <?php if ($site_slogan) { ?><div class='site-slogan'><?php print $site_slogan ?></div><?php } ?>
      </td>
      <td id="menu"></td>
    </tr>
    <tr>
      <td colspan="2"><div><?php print $header ?></div></td>
    </tr>
  </table>

<table border="0" cellpadding="0" cellspacing="0" id="content">
  <tr>
    <td valign="top">
      <div id="main">
        <?php //print $breadcrumb ?>
        <h1 class="title"><?php print $title ?></h1>
       <div class="tabs"><?php print $tabs ?></div>
        <div id="help"><?php print $help ?></div>
        <?php print $messages ?>

        <?php print $content; ?>
      </div>
    </td>
  </tr>
</table>

<?php print $closure ?>

</body>
</html>

Making additional variables available to your templates

Examples from this forum discussion. The $hook refers to the area the variable is to be used in (e.g. for comment.tpl.php, it would be "comment").

This function needs to be defined in a template.php file, which is placed inside the template directory (for instance : themes/box_cleanslate/template.php)

<?php
function  _phptemplate_variables($hook, $vars) {
   switch(
$hook) {
     case
'comment' :
       
$vars['newvar'] = 'new variable';
       
$vars['title'] = 'new title';
        break;
   }
   return
$vars;
}
?>

The output of this function is merged with the variables returned from phptemplate_comment, so you can easily adjust whichever variables you feel necessary.

Your comment.tpl.php file will now have a new variable available in it
called $newvar. Similarly the $title variable will be overridden with the value specified in
the function.

A neat trick is to count how many times each of the hooks is called, so you can pass an extra variable. re :

<?php
function _phptemplate_variables($hook, $vars) {
    static
$count;
   
$count = is_array($count) ? $count : array();
   
$count[$hook] = is_int($count[$hook]) ? $count[$hook] : 1;
   
$vars['zebra'] = ($count[$hook] % 2) ?'odd' : 'even';
   
$vars['seqid'] = $count[$hook]++;
    return
$vars;
  }
?>

That is 'even' if it is an even number, and 'odd' if it is odd. This means you do zebra striping (ie: alternating colors) for each of your nodes / blocks / comments / whatever.

Then you can set up a some styles for class='$zebra' , which handle the alternating colors.

Another example is a flag to show us if we are looking at a node. Might be handy for rendering items different, when someone is looking at an article.

<?php
function _phptemplate_variables($hook, $vars) {
  switch (
$hook) {
    case
'page':
      if (
arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == '') {
       
$vars['content_is_node'] = TRUE;
      }
    break;
  }
  return
$vars;
}
?>

Note that the switch is kind of obsolete here, but i leave it here, because you might want to add more variables. In that case you need them.

The args() checks will see if you have an url like /node/NID/ and not like /node/NID/edit or /node. If that is found, we set the flag TRUE.

PHPTemplate Raw Layouts

A series of raw layouts to help get new theme creators on their way.

Currently, the raw templates are available here. These will be added to the handbook section with additional code information and layout instruction.

PHPTemplate snippets and example layout files you can use with your themes

Customising specific page layouts with PHPTemplate based themes is made possible by simply editing existing node-$type.tpl.php files in your /themes/themename/ folder.

The default PHPTemplate layout files are:

As a quick example, let's say you wanted to remove author and date/time stamps on your static 'about' or 'contact' pages and just display the main body:

  1. Copy node.tpl.php in your /themes/themename/ folder and rename it to node-page.tpl.php
  2. Edit node-page.tpl.php and remove the following:
    <?php print $name ?> @ <?php print $date ?>
  3. Upload the new edited node-page.tpl.php file into your /themes/themename/ folder

The snippets or example node-$type.tpl.php files listed below are user submitted modifications to the default you can use in your own PHPTEMPLATE based themes.

PLEASE NOTE!The following snippets and files are user submitted. Use at your own risk! For users who have setup drupal using an alternate database to the default (MYSQL), please note that the snippets may contain some database queries specific to MYSQL.
Replace the XML (RSS) icon with a custom image

The easiest way to do this is to replace the /misc/xml.png file with a custom graphic. If that's not possible, you can override which image displays as the XML icon with PHP.

With a PHPTemplate-compatible theme you can replace the icon with one you've made yourself (and not just on the sidebar block but when it appears at the bottom of a page as well) by following the instructions below. It is based on the instructions on how to override a theme function.

  1. Either create a new theme or modify an existing theme.
  2. Create a file in that theme's directory called template.php or, if it already exists, edit the existing file.
  3. Insert the following code (replacing an entire phptemplate_xml_icon() function if it already exists), between the <?php ?> tags:
    function phptemplate_xml_icon ($url) {<br /> $icon_url = 'path/to/new/icon';<br /> if ($image = '<img src="'. $icon_url . '" alt="'. t('XML feed') . '" />') {<br />    return '<span class="xml-icon"><a href="'. check_url($url) .'">'. $image. '</a></span>';<br />  } <br />}
  4. Visit administer » themes to register the new icon function.
  5. Enable the theme if necessary.

Regions in PHPTemplate

Note: don't use a dash in the name of a region. underscore is OK ... also, your theme name is your base theme name not the name of your style.css directory (if you are using one). for example, if your theme is box_cleanslate, you should name your function box_grey_regions. yes, this is lame.

As of Drupal 4.7, themes can implement any number of regions for rendering blocks and other content into, and these regions can be assigned to either the main page or any other themed elements, including inline in nodes.

The PHPTemplate engine defines five regions: left, right, content, header, and footer. To implement these regions, themes need only include $sidebar_left, $sidebar_right, $content, $header, and $footer_message variables in their page.tpl.php files, e.g., <?php print $header; ?>.

How to define custom regions

If you want to have your own set of regions for a theme, you do this by defining a _regions() hook. In this case, your custom regions will override the ones defined by default in PHPTemplate.

If the theme doesn't have a template.php file in its directory, create one; otherwise use the existing template.php file. In either case, add a mytheme_regions() function to define your regions. Each region needs to have a name (a string with no spaces) and a description (the text that will show up in, for example, the block configuration pages). The following would define all the standard PHPTemplate regions except the left sidebar and, in addition, two new regions for a theme called "mytheme".

<?php
function mytheme_regions() {
  return array(
   
'right' => t('right sidebar'),
   
'content' => t('content'),
   
'header' => t('header'),
   
'footer' => t('footer'),
   
'floater' => t('floater'),
   
'inline1' => t('inline 1')
  );
}
?>

NOTICE! use the name of your theme instead of mytheme_regions(), eg: bluemarine_regions()

How to write the theme's region variables to the page.

If you're assigning your region to page.tpl.php, you don't need to worry about creating a variable for your region and assigning content to it; PHPTemplate handles this automatically. All you need to do is write the variables to the page, by editing your theme's page.tpl.php file. For each new region you've defined, include in page.tpl.php a print call. For the 'floater' region defined above, this would look like:
<?php  print $floater;?>. Of course, you'll probably want to use HTML, CSS, and possibly PHP (e.g., if tests) to get your new regions looking how you want them.

Inline regions: how to assign regions to nodes, comments, etc.

By default, all defined regions are passed to page.tpl.php. But with an extra step you can also choose to make specific regions available to any other template file: node.tpl.php, comment.tpl.php, etc. Here's how.

In the same template.php file as you defined the regions in, define a function _phptemplate_variables() (or use an existing one if there's one already defined). What you'll do here is assign region content to a specific theme call. When _phptemplate_variables() is called, it receives the theme argument as the $hook variable, for instance 'node'. So we can assign new regions to the node template like this:

<?php
function _phptemplate_variables($hook, $variables) {
 
// Load the node region only if we're not in a teaser view.
 
if ($hook == 'node' && !$vars['teaser']) {
   
// Load region content assigned via blocks.
   
foreach (array('inline1') as $region) {
     
$variables[$region] = theme('blocks', $region);
    }
  }
  return
$variables;
}
?>

Note that we're testing to make sure we aren't in a 'teaser' view, so that the region will be loaded only in a full node view.

Now, in your node template, you can include the region variable in your output. Here's the standard phptemplate node.tpl.php with an 'inline1' region added:

  <div class="node<?php if ($sticky) { print " sticky"; } ?><?php if (!$status) { print " node-unpublished"; } ?>">
    <?php if ($picture) {
      print
$picture;
    }
?>

    <?php if ($page == 0) { ?><h2 class="title"><a href="<?php print $node_url?>"><?php print $title?></a></h2><?php }; ?>
    <span class="submitted"><?php print $submitted?></span>
    <span class="taxonomy"><?php print $terms?></span>
    <div class="content"><div class="floatleft"><?php print $inline1?></div><?php print $content?></div>
    <?php if ($links) { ?><div class="links">&raquo; <?php print $links?></div><?php }; ?>
  </div>

Likely you'll want to add style declarations to the region, which you do as usual via the theme's style.css file. Here, for example, we could make the inline region float left:

div.floatleft {
  float: left;
}

Because a node's variables are passed to other node templates, you can also do your customized display in node-type templates.

And this same basic approach can be applied to any other themed area. This example assigns specific regions to nodes and comments:

<?php
function _phptemplate_variables($hook, $variables) {
 
// Load region content assigned via blocks.
  // Load the node region only if we're not in a teaser view.
 
if ($hook == 'node' && !$vars['teaser']) {
    foreach (array(
'node1', 'node2') as $region) {
     
$variables[$region] = theme('blocks', $region);
    }
  }
  else if (
$hook == 'commment') {
    foreach (array(
'comment1', 'comment2') as $region) {
     
$variables[$region] = theme('blocks', $region);
    }
  }
  return
$variables;
}
?>
Regions without blocks

If you want to assign content to regions but not have it output via blocks, do so with the drupal_set_content() funciton. This allows you to bypass the regular block system. Try something like this:

  1. Set content to regions
    In your module code, set content to the regions. You can use region names that aren't listed in your themename_regions() array. That way, the regions won't be available for blocks, and so won't end up with block content in them. Say your regions are called 'region1' and 'region2'. In your module code, do this:
<?php
$output
= 'whatever';
drupal_set_content('region1', $output);
?>
  • In a template.php file, set a variable and assign the region content to it.
    <?php
    function _phptemplate_variables($hook, $variables) {
     
    // Load region content assigned via drupal_set_content().
     
    if ($hook == 'page') {
        foreach (array(
    'region1', 'region2') as $region) {
         
    $variables[$region] = drupal_get_content($region);
        }
      }
      return
    $variables;
    }
    ?>
  • Output your content
    In your page.tpl.php file, output the regions where you want them:
  • <?php
    print $region1;
    ?>

template.php: Overriding other theme functions

If you want to override a theme function not included in the basic list (block, box, comment, node, page), you need to tell PHPTemplate about it.

To do this, you need to create a template.php file in your theme's directory. This file should contain the required <?php ?> tags, along with stubs for the theme overrides. These stubs instruct the engine what template file to use and which variables to pass to it.

First, you need to locate the appropriate theme function to override. You can find a list of these in the API documentation. We will use theme_item_list() as an example.

The function definition for theme_item_list() looks like this:

<?php
function theme_item_list($items = array(), $title = NULL) { 
?>

Now you need to place a stub in your theme's template.php, like this:

<?php
/**
* Catch the theme_item_list function, and redirect through the template api
*/
function phptemplate_item_list($items = array(), $title = NULL) {
 
// Pass to phptemplate, including translating the parameters to an associative array.
  // The element names are the names that the variables
  // will be assigned within your template.
 
return _phptemplate_callback('item_list', array('items' => $items, 'title' => $title));
}
?>

We replaced the word theme in the function name with phptemplate and used a call to _phptemplate_callback() to pass the parameters ($items and $title) to PHPTemplate.

Now, you can create a item_list.tpl.php file in your theme's directory, which will be used to theme item lists. This function should follow the same logic as the original theme_item_list().

Note that you will need to visit admininster > themes for PHPTemplate to refresh its cache and recognize the new file. Beginning with version 4.6, this is not necessary anymore.

Note that the switch is kind of obsolete here, but i leave it here, because you might want to add more variables. In that case you need them.

In addition to Drupal's standard theme functions (as shown in the API) you can also override any Drupal form using a theme function. For example, given a Core form that is created from a form builder function, the form_id can be used to override. A worked example makes this clearer.

An example - Theming flexinode

Since it took me a while to make sense of this I thought I would post an example to help others along the way. It's actually quite simple, just not very intuitive.

This example is for changing the way that the flexinode 'date/time' field will display on a page. (I only wanted month and year to show). But could very easily be adapted to other things.

My theme is called 'licc' - it is a phptemplate theme.

The process goes like this:

1. find the theme function for the flexinode field

Found in modules/flexinode/field_timestamp.inc

<?php
function theme_flexinode_timestamp($field_id, $label, $value, $formatted_value) {
 
$output = theme('form_element', $label, $formatted_value);
 
$output = '<div class="flexinode-timestamp-'. $field_id .'">'. $output .'</div>';
  return
$output;
}
?>

This is just for reference, you could just as easily look in the API documentation. Core documentation is here:
http://drupaldocs.org/api/head/group/themeable
(only core modules seem to be online at the moment, so you will need to search through the code for any add-on modules like flexinode)
2. Create template.php and add override function

For my theme I created themes/licc/template.php and then copied the function declaration from above replacing the word 'theme' with 'phptemplate'.

<?php
function phptemplate_flexinode_timestamp($field_id, $label, $value, $formatted_value) {
}
?>

add in the phptemplate callback:
<?php
return _phptemplate_callback('flexinode_timestamp', array('field_id' => $field_id, 'label' =>
$label, 'value' => $value, 'formatted_value' => $formatted_value));
?>

This function doesn't really _do_ anything except give phptemplate control, the next step looks after the actual formatting. Note how the variables are passed on to the _phptemplate_callback() in an associative array.
3. Create flexinode_timestamp.tpl.php to do formatting

This goes in your theme directory (for me themes/licc/flexinode_timestamp.tpl.php). As you can see the name matches the bit after 'phptemplate_' in the theme override function, and the first argument of the _phptemplate_callback().

Put the HTML/PHP that you want in this file for the display of all date/time (timestamp) fields in all flexinode pages.

Something like this:

<div class="flexinode-timestamp-<?php print $field_id; ?>">
<strong><?php print $label; ?>: </strong><br />
<?php print $formatted_value; ?>
</div>
Example Files
template.php
<?php
/***
* template.php
*
* This file contains functions for over-riding the default theme functions
* in Drupal core and modules (look at the API documentation for more info).
* The functions don't actually _do_ anything, except pass the variables
* available to phptemplate for use in the *.tpl.php files.
*
* Add similar 'stub' functions to override other default theme functions.
*/


/**
* Override theme_flexinode_timestamp() from modules/flexinode/field_timestamp.inc
*/
function phptemplate_flexinode_timestamp($field_id, $label, $value, $formatted_value) {
 
// like I said, nothing happens here.
 
return _phptemplate_callback('flexinode_timestamp', array('field_id' => $field_id, 'label' => $label, 'value' => $value, 'formatted_value' => $formatted_value));
}
?>
flexinode_timestamp.tpl.php
<?php
/***
* Customised formatting of flexinode timestamp data in nodes.
* These fields are available:
* $field_id, $label, $value, $formatted_value
**/

// Change the default $formatted_value so that it suits me (no time or day)
$formatted_value = strftime ("%B %Y", $value); // format as Month and Year, eg. 'July 2004'

?>

<div class="flexinode-timestamp-<?php print $field_id; ?>">
<strong><?php print $label; ?>: </strong><br />
<?php print $formatted_value; ?>
</div>
Not including drupal.css

The theme_stylesheet_import function can be overwritten to omit drupal.css. Simply add the following snippet to the theme's template.php file:

<?php
/*
  Do not include drupal's default style sheet in this theme !
*/
function phptemplate_stylesheet_import($stylesheet, $media = 'all') {
  if (
strpos($stylesheet, 'misc/drupal.css') == 0) {
    return
theme_stylesheet_import($stylesheet, $media);
  }
}
?>

Replacing drupal.css with a custom .css file:

<?php
function phptemplate_stylesheet_import($stylesheet, $media = 'all') {
  if (
strpos($stylesheet, 'misc/drupal.css') != 0) {
   
$stylesheet = str_replace('misc/drupal.css', 'misc/mysite.css', $stylesheet);
  }
  if (
strpos($stylesheet, 'misc/drupal.css') == 0) {
    return
theme_stylesheet_import($stylesheet, $media);
  }
}
?>

Placement of this snippet into the theme's template.php file will switch out the drupal.css file with a custom .css file. Replacing drupal.css can be useful on sites that do not want to be identified as using Drupal on the back end. This example copied the original drupal.css file, renamed it to mysite.css, and was saved in a directory named misc.

Note on using strpos from PHP.net:

Warning -- This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates to FALSE, such as 0 or "". Please read the section on Booleans for more information. Use the === operator for testing the return value of this function.

Using different block templates for different blocks, regions, etc.

In Drupal 5.0, designers can create multiple tpl.php files for blocks based on the specific block, the module that created the block, or the region that the block appears in.

Template files are searched in the following order:

block-[module]-[delta].tpl.php
block-[module].tpl.php
block-[region].tpl.php
block.tpl.php

For example, the user login block has a delta of '0'. If you put it in the left sidebar, when it's rendered, PHPTemplate will search for the following templates in descending order:

block-user-0.tpl.php
block-user.tpl.php
block-left.tpl.php
block.tpl.php

Alternative templates for different block types (4.7)

Note: Drupal 5 makes this even cleaner. Just create a properly named template file

Here's a little trick that will allow you to use a different block.tpl.php for a specific block. You can control by Block name or Block ID. Here' a modified block.tpl.php with a conditional at the top. All you need to do is edit the module == '[modulename here]' and delta == '[block name or id here]' part.

Below is my current block.tpl.php, and the block information in the conditional is for my site. You will need to change it as described above for it to work on your site. This is for loading a custom block.tpl.php for two blocks on my site, but you could use it for one, six or however many you need. You will need a conditional (the "if " part) for each custom template.

<?php
if ( ( $block->module == 'views' && $block->delta == 'Cool Block' ) || ( $block->module == 'node' && $block->delta == '0' ) ) {
    include
'block-custom.tpl.php'; /*load a custom template for those two block IDs */
   
return; }
?>

<div class="<?php print "block block-$block->module" ?>" id="<?php print "block-$block->module-$block->delta"; ?>">
  <div class="title"><h3><?php print $block->subject ?></h3></div>
  <div class="content"><?php print $block->content ?></div>
</div>

You need to create another block-custom.tpl.php (or block-whateveryoulikehere.tpl.php) and you can have as many of these as you like. To test this on your site, just duplicate the standard block template, name it block-custom.tpl.php, and edit it, putting the title last instead of first and you'll see the effect. The rest is up to you. You could put whatever you want in the template.

This means all blocks are NOT created alike, which is something that drives many people crazy. Add this to the new regions functionality and you have 100% customizable blocks.

Credit to iDonny, nevets and Heine

Using different page templates depending on the current path

In Drupal 5.0, PHPTemplate supports the use of multiple page templates for a single theme. Depending on the current url path (node/1, taxonomy/term/2, or user/1, for example), PHPTemplate will search for multiple templates before falling back on the default page.tpl.php file.

For example, if you were to visit http://www.example.com/node/1/edit, PHPtemplate would look for the following templates, in descending order:

page-node-edit.tpl.php
page-node-1.tpl.php
page-node.tpl.php
page.tpl.php

If you were to visit http://www.example.com/tracker, PHPTemplate would look for the following templates:

page-tracker.tpl.php
page.tpl.php

Remember that these template suggestions are based on the default drupal path for a particular page. If you've used the path or pathauto module to hide them with url aliases, these templates will still be searched based on the original paths.

If you need to switch page template files based on some other rule (the role of the logged in user, for example), implement the phptemplate_variables() function in your theme's template.php file. The $vars['template_files'] variable should store an array of possible tpl.php files, with the first one to check listed last.

Different node templates depending on URL aliases

Drupal 5 includes useful mechanism for providing different templates for different pages, blocks, nodes etc. For example, you can style nodes of type 'blog' simply by adding node-blog.tpl.php which will be preferred over standard node.tpl.php template. For page templates, there is even more flexibility, so you can for example create specific templates for specific nodes, see Using different page templates depending on the current path.

The list of possible "template suggestions" is also editable via the _phptemplate_variables() function. For instance, the code snippet below will create additional "suggestions" based on the the URL alias. That's much more powerful than the default method of using the internal Drupal path.

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'page':
   
     
// Add node template suggestions based on the aliased path.
      // For instance, if the current page has an alias of about/history/early,
      // we'll have templates of:
      // node_about_history_early.tpl.php
      // node_about_history.tpl.php
      // node_about.tpl.php
      // Whichever is found first is the one that will be used.
     
if (module_exists('path')) {
       
$alias = drupal_get_path_alias($_GET['q']);
        if (
$alias != $_GET['q']) {
         
$suggestions = array();
         
$template_filename = 'node';
          foreach (
explode('/', $alias) as $path_part) {
           
$template_filename = $template_filename . '_' . $path_part;
           
$suggestions[] = $template_filename;
          }
        }
       
$vars['template_files'] = $suggestions;
      }
      break;
  }
 
  return
$vars;
}
?>
Different page templates depending on URL aliases

The list of "template suggestions" for a template can be modified in your template.php file. The following snippet will add page template suggestions for each level of URL alias for the current page. That allows you to for example define page-music.tpl.php template which would apply to every page under 'music' path / logical directory.

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'page':
   
     
// Add page template suggestions based on the aliased path.
      // For instance, if the current page has an alias of about/history/early,
      // we'll have templates of:
      // page_about_history_early.tpl.php
      // page_about_history.tpl.php
      // page_about.tpl.php
      // Whichever is found first is the one that will be used.
     
if (module_exists('path')) {
       
$alias = drupal_get_path_alias($_GET['q']);
        if (
$alias != $_GET['q']) {
         
$suggestions = array();
         
$template_filename = 'page';
          foreach (
explode('/', $alias) as $path_part) {
           
$template_filename = $template_filename . '_' . $path_part;
           
$suggestions[] = $template_filename;
          }
        }
       
$vars['template_files'] = $suggestions;
      }
      break;
  }
 
  return
$vars;
}
?>

XTemplate to PHPTemplate conversion

Firstly rename the xtemplate.xtmpl file to original.xtmpl so that the theme is no longer a xtemplate theme.

Creating page.tpl.php
  1. copy original.xtmpl to page.tpl.php
  2. Remove the node, comment, box, and block sections. In the place of these sections add the following code
    <?php echo $content ?>
  3. Change all the "{" characters to "<?php print $"
  4. Change all the "}" characters to "; ?>"
  5. Change the "$footer" to "$closure"
  6. Change "$message" to "$messages"
  7. As the primary and secondary links in phptemplate are arrays you will need to change them from "echo $primary_links;" to "echo theme('links', $primary_links);". Also the same needs to be done for secondary links.
  8. In the blocks section we need to change the $block to either $sidebar_left or $sidebar_right depending on which side of the content it is on.
Creating node.tpl.php
  1. Copy the node section from the original.xtmpl to a new file node.tpl.php
  2. As before change all the "{" and "}" characters to "<?php print $" and "; ?>" respectively.
  3. change $link to $node_url.
  4. Change "$taxonomy" to "$terms"
  5. Change "print $sticky;" to "if ($sticky) { print " sticky"; }"
  6. Change "print $picture;" to "if ($picture) { print $picture; }"
  7. As both the page.tpl.php and node.tpl.php have the displaying of the title twice. To get around this you need to only display the header when $main = 1. You will need to add the following around the $title line. "<?php if ($main) { ?>...<?php } ?>"
Creating comment.tpl.php
  1. Copy the comment section from the original.xtmpl to a new file comment.tpl.php
  2. As before change all the "{" and "}" characters to "<?php print $" and "; ?>" respectively.
  3. Change "print $picture;" to "if ($picture) { print $picture; }"

Also just to be clean, you may want to change the displaying of the new so it will only show when the $new != ''

Create block.tpl.php
  1. Copy the block section from the original.xtmpl to a new file block.tpl.php
  2. As before change all the "{" and "}" characters to "<?php print $block->" and "; ?>" respectively.
  3. Then change the $block->title to $block->subject.
Create box.tpl.php
  1. Copy the box section from the original.xtmpl to a new file box.tpl.php
  2. As before change all the "{" and "}" characters to "<?php print $" and "; ?>" respectively.

XTemplate theme engine

The XTemplate theme system uses templates to layout and style Web pages. It separates logic (PHP), structure (XHTML/HTML), and style (CSS), making it easy for designers to create or modify templates by working on XHTML/HTML and CSS without having to worry about any PHP coding.

XTemplate templates are directories, which contain all the XHTML/HTML, CSS, image and JavaScript files that a template uses. Templates are located in the themes directory of a Drupal installation:

/themes/

Once a template exists in the themes directory, XTemplate auto-detects it, and makes it available for selection to administrators:

administer -> themes

Drupal is distributed with two XTemplate templates included - Bluemarine and Pushbutton.

Although XTemplate is still supported as part of the core, it may not be in the future, for several reasons. This will not necessarily mean the end of XTemplate since it may be maintained as an alternative contributed engine like PHPTemplate.

Creating a new XTemplate

To make a new XTemplate template, create a directory in your Drupal installation at this location:

/themes/

Whatever you name the new directory will be used as the name of your new template, for instance:

/themes/rembrant

Once you create a template in this directory, it will appear on the theme selection page as the "rembrant" template.

The easiest way to create a new template is to make a copy of an existing template, such as Default or Pushbutton, and start making changes to the files.

The only file required in a template directory is xtemplate.xtmpl, which is a regular HTML or XHTML file containing some XTemplate tags that Drupal substitutes with content when a page is served. The xtemplate.xtmpl file can be edited in DreamWeaver, GoLive, BBEdit or any other application you use to work on HTML/XHTML.

All other files in the template are optional, and are linked to from the xtemplate.xtmpl file. These can include CSS, image or JavaScript files, and should all be included in the template directory to make the template easy to maintain and portable between Drupal installations.

Note that if you name your stylesheet style.css, it will automatically be picked up by Drupal, and you will not need to add an explicit @import or <link /> for it. If you make a subdirectory within your template, containing another style.css file, then the subdirectory becomes a new theme, using the XHTML from the first template, but with a different stylesheet.

Template basics

xTemplate creates Web pages by substituting place holder tags in a template, the xtemplate.xtmpl file, with content from the database.

There are two kinds of template place holder tags, section tags and item tags.

Section Tags

Section tags deal with the structure of a Web page, marking areas of the page, and are XHTML/HTML comment tags which look like this:

<!-- BEGIN: title -->

<!-- END: title -->

Some section tags mark areas were the content, and it's structure, will be repeated. For instance the comment section may be repeated more than once depending on how many comments are on a page:

<!-- BEGIN: comment -->
<!-- END: comment -->

Section tags can be nested, so that one set of section tags can be contained by another:

<!-- BEGIN: node -->
  <!-- BEGIN: title -->
  <!-- END: title -->
<!-- END: node -->

Item Tags

Item tags are place holders for content items, such as the title of a page, who the page was submitted by, or the main content of a page. Item tags look like this:

{title}
{submitted}
{content}

Item tags are associated with the section tag that surrounds them, for instance:

<!-- BEGIN: node -->
{title}
<!-- END: node -->

The {title} tag above is the main title of a page, while the {title} tag below is the title for the comments on a page.

<!-- BEGIN: comment -->
{title}
<!-- END: comment -->

Header section

The Section

The xTemplate Header section starts and ends with these tags

<!-- BEGIN: header -->
<!-- END: header -->

Don't confuse the Header section with the XHTML/HTML <head> element. Although the <head> element is included in the Header section, it also holds the top part of the Web page - the area designers usualy refer to as the "Header", which usually consists of a horizontal bar with the site's logo and some navigation links.

Prolog

The WC3 recommends that all XHTML documents should start with an XML prolog specifying the encoding of the document, for instance:

<?xml version="1.0" encoding="utf-8"?>

Unfortunately there are many browsers that handle the XML prolog badly, and either crash, fail to display the page, or display it incorrectly. It is therefore recommended to leave out the XML prolog, and specify encoding in a Content-Type element in the <head> of your template (which Drupal does automatically).

DOCTYPE

The DOCTYPE element tells a browser two things, which XML language the document is using, and where the DTD (Document Type Declaration) of that language is located.

This is an example of a DOCTYPE element:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

There should be absolutely nothing in your document before the DOCTYPE or XML prolog. The xTemplate tag <!-- BEGIN: header --> is OK, as it will be removed by Drupal before sending the page to the browser, but make sure to remove spaces or line breaks between this and the DOCTYPE or XML prolog elements, or you may get unexpected results in some browsers.

To learn more about the DOCTYPE element, and which version would suit your needs best, read:

Fix Your Site With the Right DOCTYPE!

by Jeffrey Zeldman

{head_title}

Content of the <title> element. Used as the window title by browsers, and as the page title in search engine listings.

{head}

Filled in with the following:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<base href="http://yoursite.com/" />
<style type="text/css" media="all">
@import url(misc/drupal.css);
</style>
{styles}

Declarations for the current style:

<style type="text/css" media="all">@import "themes/bluemarine/style.css";</style>

Add this tag to allow your template to take advantage of the Drupal theme system's style-switching ability. Note that, if you have a default stylesheet, it should be named style.css and be located in the same directory as your xtemplate.xtmpl file.

{onload_attributes}

The page attributes for the <body> tag.

{logo}

The logo section begins and ends with these tags:

<!-- BEGIN: logo -->
<!-- END: logo -->

The filename for the site logo, configurable by the Administrator in the text box in the Drupal theme administration section. (Display of this item is optional.)

{site_name}

The site name section begins and ends with these tags:

<!-- BEGIN: site_name -->
<!-- END: site_name -->

The current site name, configured by the Administrator in the text box "Name" on Drupal page:

administer->settings

(Display of this item is optional.)

{site_slogan}

The site slogan section begins and ends with these tags:

<!-- BEGIN: site_slogan -->
<!-- END: site_slogan -->

The current site slogan, configured by the Administrator in the text box "Slogan" on Drupal page:

administer->settings

(Display of this item is optional.)

{secondary_links} {primary_links}

These tags hold whatever the Administrator inputs into the text boxes "Secondary links:" and "Primary links" in the Drupal theme administration section. If the Administrator does not specify any "Primary links", Drupal will automatically generate a set of links based on the currently-enabled modules.

The Administrator could use these tags to input links to the main sections of the site, the title of the site, a site message, an image or anything else they require.

Search Box

The Search Box section begins and ends with these tags:

<!-- BEGIN: search_box -->
<!-- END: search_box -->

{search_url}

The form action: "search"

{search_description}

The alt text description of the search text box: "Enter the terms you wish to search for."

{search_button_text}

The value of the search submit button: "Search"

Mission

The Mission section begins and ends with these tags:

<!-- BEGIN: mission -->

<!-- END: mission -->

{mission}

The text of the site mission statement, appears only on the Home Page, and is configured by the Administrator in the text box "Mission" on Drupal page:

administer->settings

Title

The Title section begins and ends with these tags:

<!-- BEGIN: title -->

<!-- END: title -->

{title}

The title of the node

Tabs

The Tabs section begins and ends with these tags:

<!-- BEGIN: tabs -->
<!-- END: tabs -->

{tabs}

Draws the Drupal "local tasks" for the current page.

{breadcrumb}

The breadcrumb trail of the page, the path from Home Page to the current page.

Help

The Help section begins and ends with these tags:

<!-- BEGIN: help -->
<!-- END: help -->

{help}

Contains any help information which exists for a particular page.

Message

The Message section begins and ends with these tags:

<!-- BEGIN: message -->
<!-- END: message -->

Message appears when Drupal confirms the results of an action by the user, for instance after updating or deleting a page.

{message}

The text of the message.

HOWTO: Allow PHP in primary links in XTemplate

NOTE: This works for Drupal 4.6.5; it should work for 4.6.6 as well

One of the most annoying things with XTemplate is the fact that it doesn't handle PHP code when it displays primary links. However, there are two functions found on php.net's user-submitted comments (http://us2.php.net/manual/en/function.eval.php) that can be added to the engine that helps fix this issue:

  1. Copy the following lines and paste at the very bottom of your xtemplate.engine file (located in /themes/engines/xtemplate); right before the PHP closing tag:
    function eval_mixed_helper($arr){
      return ("echo stripslashes(\"".addslashes($arr[1])."\");");
      }
    function eval_mixed($string){
      $string = "<? ?>".$string."<? ?>";
      $string = preg_replace("/<\?=\s+(.*?)\s+\?>/", "<? echo $1; ?>", $string);
      $string = str_replace('?>', '', str_replace( array(' <?php', '<?'), '', preg_replace_callback( "/\?> ((.|\n)*?)<\?(php)?/","eval_mixed_helper",$string) ) );
      return eval($string);
    }
    ?>


  2. XTemplate uses theme_get_setting() to grab primary_links html markup and place it on a page. It's possible to place that html in a variable and run the variable though said functions. To do this, simply modify the following lines (starting from line 28-ish, depending on your version of Drupal):
      $xtemplate->template->assign(array(
        "language" => $GLOBALS['locale'],
        "head_title" => implode(' | ', $head_title),
        "head" => drupal_get_html_head(),
        "styles" => theme_get_styles(),
        "onload_attributes" => theme_onload_attribute(),
        "primary_links" => theme_get_setting('primary_links'),
        "secondary_links" => theme_get_setting('secondary_links')
       ));


    to this...

      // allow for php in the primary links
      ob_start();
      eval_mixed(theme_get_setting('primary_links'));
      $primary_link_eval = ob_get_clean();
      $xtemplate->template->assign(array(
        "language" => $GLOBALS['locale'],
        "head_title" => implode(' | ', $head_title),
        "head" => drupal_get_html_head(),
        "styles" => theme_get_styles(),
        "onload_attributes" => theme_onload_attribute(),
        "primary_links" => $primary_link_eval,
        "secondary_links" => theme_get_setting('secondary_links')
       ));
    ?></li>

    Now any php code you put in your primary links should work.

now a contrib module

XTemplate is no longer part of core in 4.7. Now, it's maintained as a contrib module.

Node section

The Node Section

The node section (xtemplate.xtmpl) contains the main content of the page, and begins and ends with these tags:

<!-- BEGIN: node -->
<!-- END: node -->

{sticky}

Sets the class to "node sticky" if a node is "stickied" at the top of lists. (i.e. if a teaser for the page is always to be displayed on the home page) If the node has not been set to be sticky, the class is set to "node ".

Picture

Picture contains an image representing the user who posted the content of a node, the image is linked to the poster's profile. This is also sometimes called an "avatar". Picture begins and ends with these tags:

<!-- BEGIN: picture -->
<!-- END: picture -->

{picture}

Outputs the following:

<a href="user/1" title="View user profile.">

<img src="http://www.yoursite/files/pictures/picture-1.gif"
alt="Username's picture" /></a>
Title

The title of the main content of the page (node), tags begin and end:

<!-- BEGIN: title -->
<!-- END: title -->

On a node page, the title is output as:

<h1 class="title">Node Title</h1>

On the Home Page, each node title is output as:

<h2 class="title"><a href="node/31"
>Node Title</a></h2>
{link}

Outputs the link to the node , "node/31" in the example above.

{title}

Outputs the text of the node title, "Node Title" in the example above.

{submitted}

The username of the person who submitted the node content, outputs:

Submitted by <a href="user/1" title="View user profile."
>Username</a> on 16 February, 2004 - 23:46.
Taxonomy

A list of links to taxonomies which the node belongs to, tags begin and end:

<!-- BEGIN: taxonomy -->
<!-- END: taxonomy -->

{taxonomy}

Outputs a taxonomy term that the node belonds to:

<a href="taxonomy/term/30">Taxonomy Term</a>

{content}

The main content of the node.

Links

The control options for the node: "printer-friendly version", "add new comment",
and the visitor history of the node. Tags begin and end:

<!-- BEGIN: links -->
<!-- END: links -->

{links}

Outputs the following (depending on the viewer's permisions):

<a href="book/print/8"
title="Show a printer-friendly version of this book page
and its sub-pages.">printer-friendly version</a> |
<a href="comment/reply/8#comment"
title="Share your thoughts and opinions related to this posting."
>add new comment</a> |
<a href="admin/statistics/log/node/8">662 reads</a>

Comment

The Comment Section

The comment section (xtemplate.xtmpl) contains all the comments associated with a node, and begins and ends with these tags:

<!-- BEGIN: comment -->
<!-- END: comment -->

The content of this section creates the code for a single comment, and is automaticaly repeated for as many times are there are comments.

Avatar

Avatar contains an image representing the user who posted the content of a node, the image is linked to the poster's profile. Avatar begins and ends with these tags:

<!-- BEGIN: avatar -->
<!-- END: avatar -->

{avatar}

Outputs the following:

<div class="avatar">
<a href="user/1" title="View user profile.">
<img src="http://www.drupal.site/files/avatars/avatar-1.jpg" alt="username's avatar" />
</a>
</div>
Title

The title of a comment. Tags begin and end:

<!-- BEGIN: title -->
<!-- END: title -->

{link}

If required, changes the comment title into a link to the comment. Used when displaying comments in certain views.

{title}

The text of the comment title.

Submitted
{submitted}

Displays the username of the comment poster, linked to their profile, and the date and time the comment was posted. This is the output:

Submitted by <a href="user/10" title="View user profile.">username</a> on Mon, 04/19/2008 - 11:56.

New

Indicates if a comment is new. Tags begin and end:

<!-- BEGIN: new -->
<!-- END: new -->

{new}

Adds the word "new" to a comment.

Content

Displays the content of a comment.

{content}

The comment text.

Links

Displays control links for comment, such as "reply", "delete", and "edit". Tags begin and end:

<!-- BEGIN: links -->
<!-- END: links -->

{links}

Displays the control links.

Blocks

The Section

The blocks section contains the column of boxes which can be used to display various navigation and feature options, such as Forum Topics, Blogs, Who's Online, and Syndicate. Blocks sections can be configured to appear on the left or right of a page, or on both sides. The section begins and ends with this code:

<!-- BEGIN: blocks -->
<!-- END: blocks -->
{blocks}

This tag is replaced by whatever blocks have been switched on in the Administration page (admin/system/block).

Block

The block section defines the structure of each block, note the 's' in block/blocks.

<!-- BEGIN: block -->
<!-- END: block -->

{module}

The name of the module who's block is being displayed, this is added to a CSS class and ID which can be used customise the look of the block.

{delta}

Adds a number to the ID of a block, so that each block has a unique ID even if a module displays more than one block.

{title}

The title of the block.

{content}

The content of a block.

Footer

The Footer Section

The footer section appears at the very bottom of each page, it's content can be specified by the Administrator (admin/settings). The section begins and ends with this code:

<!-- BEGIN: footer -->
<!-- END: footer -->
Message

This area holds the mark-up around the message posted by the Administrator. The section begins and ends with this code:

<!-- BEGIN: message -->
<!-- END: message -->
{footer_message}

Displays the actual content defined through the field "Footer message" in the "Settings" Administration page (admin/settings).

{footer}

Outputs footer messages generated by Drupal modules. (i.e. performance statistics from devel.module)

Editing with Golive

Set Up

To edit xTemplate template files (xtemplate.xtmpl) in Adobe GoLive, follow these simple steps:

  1. In the GoLive menu select "GoLive" then "Web Settings"
  2. The Web Settings window will appear, click on the "File Mappings" tag.
  3. In the File Mappings window open the "text/" directory
  4. Scroll down until you see "html" in the Suffix column.
  5. Click on "html" to select it, then click on the "+" button to create a duplicate.
  6. Change the suffix of the duplicate html to "xtmpl"
  7. That's it you're done!
Editing

If when opening a template file GoLive asks you which encoding to use, select "UTF-8".

If all you see after opening a template is "body onload-attributes", go into source mode and delete "{onload_attributes}" from:

<body{onload_attributes}>

Remember to add "{onload-attributes}" back once you are finished editing.

In xtemplate.xtmpl, you may wish to add the following line temporarily:

<link type="text/css" rel="stylesheet" href="style.css" />

Remember to remove this line when completing work on the template, however. If you do not, Drupal will not be able to switch between various styles for your theme. Drupal will automatically load your style.css, if one exists, in the {styles} tag.

Plain PHP themes

PHP themes are the most direct way of themeing Drupal. A PHP theme consists of overrides for Drupal's built-in theme functions. You will most likely only override the basic theme hooks (pages, nodes, blocks, ...), but you can theme anything from lists to links if you desire.

To create a PHP theme, create a directory in your themes directory (we will assume themes/mytheme in this document), and inside that directory create a mytheme.theme file. This file is a regular PHP file, so make sure it contains <?php ?> tags.

The default theme functions in Drupal are all named theme_something() or theme_module_something(), thus allowing any module to add themeable parts to the default set provided by Drupal. Some of the basic theme functions include: theme_error() and theme_table() which as their name suggests, return HTML code for an error message and a table respectively. Theme functions defined by modules include theme_forum_display() and theme_node_list().

In your .theme file, you can override any of these functions. To override the function theme_something(), define the function mytheme_something() in your .theme file. This function should have the same definition as the original. It is easiest to start with Drupal's function, and apply your changes there: many theme functions contain code logic within them. To avoid problems when upgrading Drupal in the future, it is best to mark the changes between the original Drupal function and your customized version. That way, you can reapply to your customizations if the original was changed.

Required functions.

Aside from theme functions, there are two functions that you need to include.

The first is mytheme_features(). This function should return an array of strings, marking the features your theme supports (e.g. search box, logo, mission statement, ...). The theme system will provide toggles and settings for these features in the administration section. In your code, you can retrieve the value of these settings though theme_get_setting(). If you are planning on releasing your theme to the public, it is advised to implement all Drupal features, so others can customize your theme.

Available features are:

logo A logo can be used. The theme should check the settings default_logo (boolean) and logo_path (string).
toggle_logo The logo can be turned on/off
toggle_name The site name can be turned on/off
toggle_search The search box can be turned on/off
toggle_slogan The site slogan can be turned on/off
toggle_mission The mission statement can be turned on/off
toggle_node_user_picture The theme can optionally display user pictures next to nodes
toggle_comment_user_picture The theme can optionally display user pictures next to comments

Here's the _features() function from the standard chameleon.theme:

<?php
function chameleon_features() {
  return array(
      
'logo',
      
'toggle_name',
      
'toggle_slogan');
}
?>

The second required function is mytheme_regions(), which defines the available regions in the theme.

Here's the chameleon.theme _regions() function:

<?php
function chameleon_regions() {
  return array(
      
'left' => t('left sidebar'),
      
'right' => t('right sidebar')
  );
}
?>

For each region you define, you'll want to include a code block including the region's content in the page output. Typically, this might look something like:

<?php
 
if ($blocks = theme_blocks('regionname')) {
   
$output .= '[wrapping content]' . $blocks . '[/wrapping content]';
  }
?>

Note that 'regionname' here is the array key, not its text description (that is, 'left' and not 'left sidebar'). By 'wrapping content' we just mean any div, td, text, or other elements you might want to enclose your region's content in.

So, to introduce additional regions beyond just the standard 'left' and 'right' ones, you simply (a) add your region to the list defined in your _regions() function and (b) render the resulting content to the output as shown. So the coding part's quick and easy, don't hesitate to be creative!

Directory names

Note that unlike templates and styles, themes are tied to their directory name. If you want to clone a PHP theme, you need to rename its directory, its .theme file and its functions inside the .theme file.

Theme coding conventions

Theme authors should take care to write clean, well structured code just like a coder for any other project. Doing so makes the code easier to read, understand and maintain. While different organisations have different conventions, it's usually best to follow the Drupal standards as this helps when collaborating or asking for help.

(This theme coding style guide is based on the cvs log message of a developer sick of fixing strange spacing and indentation.)

  • Add 2 spaces for indents; rather than a tabbed indent
  • Match the indentation of (long) opening and closing block HTML tags
  • Distinguish between PHP and HTML indentation. Not:
      ...
      function header($title = "") {
        ?>
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
        <html>
        ...

    but:

      function header($title = "") {
    ?>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    ...

    This not only saves the superfluous leading spaces, but also makes it much easier to find matching opening and closing tags defined in functions with different indentation.

  • Prefer PHP in HTML to HTML in PHP. For example, not:
    <?php
     
    function node($node, $main = 0) {
        print
    "\n<!-- node: \$node->title\ -->\n";
        print
    "<div class=\"nodetitle\">$node->title</div>";
        print
    "<div class=\"nodebody\"><span class=\nodedate\>" . $this->links(
          array(
    format_name($node), format_date($node->created, "small"), "&amp;nbsp;")
        ) .
    "</span>";
    ?>

    but:

      function node($node, $main = 0) {
    ?>
          <!-- node: "<?php print $node->title; ?>" -->;
          <div class="nodetitle"><?php print $node->title; ?></div>
          <div class="nodebody">
          <span class="nodedate"><?php print $this->links( array(format_name($node), format_date($node->created, "small"), "&amp;nbsp;") ); ?></span>

    After all, PHP is a HTML embedded scripting language - and not the other way around.

list of classes

This page lists the classes as used in core of Drupal, by theme_wrapper. Module developers should pick existing classes wherever possible.

2-column
for a two column view
3-column
for a three column view
admin-table
wrapper around tables in tghe admin area (to be able to set overflow: auto)
container-inline
An inline container
message
wrapper around a message. note tat system messages with drupal_set_messages bear the same class
search-results
wrapper around any search result list
wrapper
The default container

Integrating with Color.module

Color.module allows the admin to change the color scheme of a theme completely. By selecting a palette of 5 colors (either from a set or by hand), you can change the colors of an entire theme.

The module can alter the stylesheet and re-render images. However, the theme must provide specific hooks to allow this, and the design must be created specifically to accomodate this.

This document explains the basics of making a colorizable theme.

Design

It is important to realize that due to the way color.module works, not every design can be colorized.

We take a transparent image of the design (the base), which includes everything except the background. We then compose this image on top of a colored background, to get the colored versions. Finally we slice up this composite image into smaller images and save them to separate image files.

We also process the stylesheet and change all the colors based on the ones you defined. The module smoothly changes the colors using the palette as a reference. Colors that don't appear literally in the palette are adjusted relative to the closest matching palette color (whether it is a link, text or background color).

So, the photoshop mockup of the design should consist of a layered file that has one or more colored layers at the bottom of the layer stack, with everything else blended on top. When you save the base image, you have to merge all layers together, while keeping the colored layers invisible. Take a look at Garland's base.png file to see an example (open it in an image editor to see the transparencies).

All the files generated in this process are written to /files/css and used instead of the default images. This means that a colorizable theme should still work out of the box without color.module, in the default color scheme.

In Practice

Let's use Garland as an example. The most important files are in the themes/garland/color subdirectory:

base.png
This contains the base design of the theme, which is composed and sliced into the images.
color.inc
This file contains all the necessary parameters to color the theme. See below.
preview.css
This stylesheet is used to generate the preview on the color changer.
preview.png
This image is used to generate the preview on the color changer.

The presence of color/color.inc makes the color picker appear on the theme's settings. It is a regular PHP file which contains an $info array with the following values:

Schemes

<?php
array('schemes' => array(
   
'#0072b9,#027ac6,#2385c2,#5ab5ee,#494949' => t('Blue Lagoon (Default)'),
   
'#464849,#2f416f,#2a2b2d,#5d6779,#494949' => t('Ash'),
   
'#55c0e2,#000000,#085360,#007e94,#696969' => t('Aquamarine'),
   
'#d5b048,#6c420e,#331900,#971702,#494949' => t('Belgian Chocolate'),
   
'#3f3f3f,#336699,#6598cb,#6598cb,#000000' => t('Bluemarine'),
   
'#d0cb9a,#917803,#efde01,#e6fb2d,#494949' => t('Citrus Blast'),
   
'#0f005c,#434f8c,#4d91ff,#1a1575,#000000' => t('Cold Day'),
   
'#c9c497,#0c7a00,#03961e,#7be000,#494949' => t('Greenbeam'),
   
'#ffe23d,#a9290a,#fc6d1d,#a30f42,#494949' => t('Mediterrano'),
   
'#788597,#3f728d,#a9adbc,#d4d4d4,#707070' => t('Mercury'),
   
'#5b5fa9,#5b5faa,#0a2352,#9fa8d5,#494949' => t('Nocturnal'),
   
'#7db323,#6a9915,#b5d52a,#7db323,#191a19' => t('Olivia'),
   
'#12020b,#1b1a13,#f391c6,#f41063,#898080' => t('Pink Plastic'),
   
'#b7a0ba,#c70000,#a1443a,#f21107,#515d52' => t('Shiny Tomato'),
   
'#18583d,#1b5f42,#34775a,#52bf90,#2d2d2d' => t('Teal Top'),
  ));
?>

This entry contains a straightforward array of pre-defined color schemes. Each entry must have 5 colors, formatted as above, and a title.

The first scheme is used as a reference and must match the colors used in the theme's default images and stylesheet closely. Otherwise, the final colors might not be what the user intended. See the 'stylesheets' section for more information about how the colors are calculated.

Images to copy

<?php
 
array('copy' => array(
   
'images/menu-collapsed.gif',
   
'images/menu-expanded.gif',
   
'images/menu-leaf.gif',
  ));
?>

This array contains a list of images which should not be altered. They are copied to the location of the generated images and stylesheet.

Fill areas and Gradients

To color the image, we create a target image that is the same size as the base image, and draw colored areas and a gradient. For full flexibility, the location of these areas is defined by specifying their coordinates using (x, y, width, height):

<?php
 
array('gradient' => array(0, 37, 760, 121));
?>

You can specify one vertical two-color gradient.

<?php
 
array('fill' => array(
   
'base' => array(0, 0, 760, 568),
   
'link' => array(107, 533, 41, 23),
  ));
?>

You can specify regions for each of the palette colors. The region will be filled in with the selected color. Available colors are 'base', 'link', 'top', 'bottom' and 'text'.

Image slices

Next, you need to define the areas of the base image to slice out for each of the images. Again, you specify coordinates as (x, y, width, height) along with the filename of the image, as used in the stylesheet. The logo and screenshot slices are special and always take the same filename. The screenshot will be resized to 150x90 pixels.

<?php
 
array('slices' => array(
   
'images/body.png'                      => array(0, 37, 1, 280),
   
'images/bg-bar.png'                    => array(202, 530, 76, 14),
   
'images/bg-bar-white.png'              => array(202, 506, 76, 14),
   
'images/bg-tab.png'                    => array(107, 533, 41, 23),
   
'images/bg-navigation.png'             => array(0, 0, 7, 37),
   
'images/bg-content-left.png'           => array(40, 117, 50, 352),
   
'images/bg-content-right.png'          => array(510, 117, 50, 352),
   
'images/bg-content.png'                => array(299, 117, 7, 200),
   
'images/bg-navigation-item.png'        => array(32, 37, 17, 12),
   
'images/bg-navigation-item-hover.png'  => array(54, 37, 17, 12),
   
'images/gradient-inner.png'            => array(646, 307, 112, 42),

   
'logo.png'                             => array(622, 51, 64, 73),
   
'screenshot.png'                       => array(0, 37, 400, 240),
  ));
?>

Files

Finally you need to specify the location of the files for your theme. You need an image and a stylesheet for the preview, as well as the base image:

<?php
array(
 
'preview_image' => 'color/preview.png',
 
'preview_css' => 'color/preview.css',
 
'base_image' => 'color/base.png',
);
?>

Stylesheets

The color.module will read in a theme's style.css file as well as any other styles that are imported with @import statements and create a new style.css file. It will change the colors in the CSS using one of the chosen palette colors as a reference, depending on the context:

  • Links: the 'link' color is used, for rules that apply to a elements.
  • Text: the 'text' color is used, for rules that appear in color: styles.
  • Base: the 'base' color is used for everything else.

However, if a color in the stylesheet matches one of the reference colors exactly, the context will be ignored, and the matching replacement color will be used instead.

For example, suppose your reference color is dark blue by default, but you change it to red. Your default stylesheet contains both light blue and gray purple, both relative to this reference color.

The resulting colors (mauve and brown) are similarly different from red as the original colors were from blue. In technical terms: the relative difference in hue, saturation and luminance is preserved.

If you find color.module is using the wrong reference color, try separating the different pieces into separate CSS rules, each in their own selector { ... } section, so there is no confusion about the context.

Note that if you edit your stylesheet after changing the color scheme, you must resubmit the color scheme to regenerate the color-shifted version.

If wish for certain colors in the stylesheet not to be altered, you should place their CSS below the following marker:

/*******************************************************************
* Color Module: Don't touch                                       *
*******************************************************************/

You can only use this marker once in your style.css file. It applies globally, so if you use it inside an imported stylesheet, all colors below the @import statement will be left alone too.

Making colors match

It is important that the generated images match with the shifted colors in the generated stylesheet. Otherwise, ugly edges might appear.

To make this work, pixels in the base image must all be a simple color in areas where they have to match with CSS-defined colors. Because we don't know where CSS-defined colors appear in the base image, we use a global blending color which must be the same in the whole design. Garland uses white. Note that the Garland base does include e.g. gray and black pixels, but only in areas where only images are used as backgrounds (e.g. the header). Other than white, black or grey are good candidates too.

<?php
 
array('blend_target' => '#ffffff');
?>

Masochists can take a peek at color.module's innards, particularly the _color_shift() function if you're interested in the how and why of this.

PHPTemplate changes

Finally, you need to hook color.module into your theme. We'll use a PHPTemplate theme as an example, but this applies to other engines as well.

In your theme's template.php file, add the following snippet:

<?php
/**
* Override or insert PHPTemplate variables into the templates.
*/
function _phptemplate_variables($hook, $vars) {
  if (
$hook == 'page') {

   
// Hook into color.module
   
if (module_exists('color')) {
     
_color_page_alter($vars);
    }
    return
$vars;
  }
  return array();
}
?>

This will allow the module to override your theme's logo, stylesheet and screenshot. If you perform other changes in _phptemplate_variables, you need to merge in this snippet.

Smarty theme engine

Smarty theme engine is a theme engine maintained by Travis Cline designed to easily use the Smarty Template Engine syntax in drupal themes. It is largely ported from the PHPTemplate theme engine.

As it is a fairly direct port, refering to the phptemplate documenation is valuable in many cases: http://drupal.org/phptemplate

Similar to PHPTemplate, it uses individual something.tpl (note extension is .tpl not .tpl.php) files to theme Drupal's theme_something() functions. Drupal's themeable functions are documented on the Development Plumbing site.

Every file contains an HTML skeleton with Smarty tags for the dynamic data. Smarty tags are more human readable than embedded php code.

These themes have been converted from PHPTemplate format for use with the Smarty theme engine:

'templates_c' Directory Permissions

The templates_c subdirectory in the themes/engines/smarty directory must be both readable and writable by the web server process.

In an *nix environment the solution is such:

If you have chown access:

The directory should be owned by your user and have group ownership including your web server process. i.e. (your user name):apache
The directory should be readable/writable/executable for both owner and group and read/execute for other. i.e. 775
If 'apache' is not the correct web server user/group you can check the output of <?php phpinfo(); ?> to find it.

chmod 775 templates_c
chown (your user name):apache templates_c

More likely - no chown access:

A simple
chmod 777 templates_c
will correct the permissions for the directory.

If chown access is available, use it. Not having global write access is preferable but in many cases it's necessary.

In win32 you must give write permissions for the web server process user to the templates_c subdirectory.

Customizing Smarty Themes

Create a file in your theme directory named: smartytemplate.php.

<?php
 
function _smarty_variables($hook, $vars = array()) {
    switch (
$hook) {
      case
'page':
      if ((
arg(0) == 'admin')) {
       
$vars['template_file'] = 'admin';
      }
      break;
   }
   return
$vars;
  }
?>

References:

Installing Smarty theme engine

To enable use of Smarty-based Drupal themes such as (Box_grey_smarty, Bluemarine_smarty, Leaf_smarty, etc.) you must have the Smarty theme engine installed.

Alternative download - skips step two and three

  1. Download and extract tarball from the project page to the drupal_base/themes/engines directory.
  2. Download and extract a Smarty Template Engine tarball to a temporary location.
  3. Copy the 'libs' subdirectory from the temporary location to drupal_base/themes/engines/smarty/libs.
    Smarty.class.php should be located at 'drupal_base/themes/engines/smarty/libs/Smarty.class.php'
  4. Ensure drupal_base/themes/engines/smarty/templates_c directory is writable by the web server process (see other documentation for details).

You will now be able to use and configure Smarty themes.

Making additional variables available to your templates

As the Smarty Theme Engine is very closely ported from phptemplate most solutions are of the same nature.

For simple introduction of additional variables you implement a _smarty_variables function in the smartytemplate.php file within your theme directory (see http://drupal.org/node/70247 : smartytemplate.php: Your theme's powerhouse)

For example:
If your theme were box_grey_smarty:
Adding a smartytemplate.php file at themes/box_grey_smarty/smartytemplate.php with content of (file needs php tags):

<?php
function  _smarty_variables($hook, $variables) {
  switch(
$hook) {
    case
'comment' :
       
$variables['newvar'] = 'new variable';
       
$variables['title'] = 'new title';
        break;
  }
  return
$variables;
}
?>

Then within your comment.tpl you could then write New Variable: {$newvar} to print 'new variable'.

See the phptemplate page on template variable addition for more examples, just remember to replace _phptemplate_variables with _smarty_variables.

smartytemplate.php: Your theme's powerhouse

To step into more advanced aspects of the Drupal Theme system with a Smarty theme requires the creation of an additional file within your theme's directory.
(e.g. themes/blumarine_smarty/smartytemplate.php)

This file allows for

  • Overriding Themable Functions see howto for phptemplate here: http://drupal.org/node/11811.
  • Introduction of complex logic into your templates.
  • Easily registering custom and existing functions for use within your templates.
  • Defining additional variables for use within your templates.



An example for overriding an existing themable function follows:
(heavily borrowed from the phptemplate howto)


First, you need to locate the appropriate theme function to override. You can find a list of these in the API documentation. We will use theme_item_list() as an example.

If you want to override a theme function not included in the basic list (block, box, comment, node, page), you need to tell Smarty Theme Engine about it.

To do this, you need to create a smartytemplate.php file in your theme's directory. This file should contain the required

<?php

?>
tags, along with stubs for the theme overrides. These stubs instruct the engine what template file to use and which variables to pass to it.

The function definition for theme_item_list() looks like this:

<?php
function theme_item_list($items = array(), $title = NULL, $type = 'ul') {
?>

Now you need to place a stub in your theme's smartytemplate.php, like this:

<?php
/**
* Catch the theme_item_list function, and redirect through the template api
*/
function smarty_item_list($items = array(), $title = NULL, $type = 'ul') {
 
// Pass to Smarty Theme Engine, translating the parameters to an associative array. The element names are the names that the variables
  // will be assigned within your template.
 
return _smarty_callback('item_list', array('items' => $items, 'title' => $title, 'type' => $type));
}
?>

We replaced the word theme in the function name with phptemplate and used a call to _smarty_callback() to pass the parameters ($items, $title and $type) to the Smarty Theme Engine.

Alternatively, you can use the theme's name as opposed to 'smarty' in the function name (e.g., if the theme were bluemarine_smarty: bluemarine_smarty_item_list or smarty_item_list could both be used).

Now, you can create a item_list.tpl file in your theme's directory, which will be used to theme item lists. This function should follow the same logic as the original theme_item_list().

Sometimes it is not appopriate to hand off rendering to a template file. If the overridden theme function makes more sense being written in straight php then you can skip the invokation of smarty with the _smarty_callback() call and return output directly.

Updating your themes

As Drupal develops with each release it becomes necessary to update themes to take advantage of new features and stay functional with Drupal's theme system.

Converting 5.x themes to 6.x

Overview of Drupal Theme changes in 6.x
  1. Themes now have .info files
  2. New $signature variable
  3. $language is now an object
  4. Right to left CSS override files supported
  5. jQuery updated to 1.1.2

Themes now have .info files

In 5.x, Drupal modules saw the introduction of .info files to store metadata about the module (for example, the name, description, version, dependencies, etc). Starting in 6.x, Drupal themes also have a .info file. See the complete guide to writing .info files for themes for more information.

New $signature variable

In Drupal 6, signatures were made dynamic, which means they display when viewing a comment, and are not part of the comment itself. Therefore, a $signature variable needs to be added to comment.tpl.php.

In 5.x:

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

In 6.x:

<div class="content">
  <?php print $content ?>
  <?php if ($signature): ?>
    <div class="user-signature clear-block">
      <?php print $signature ?>
    </div>
  <?php endif; ?>
</div>

Note: you can use the following to prevent double-display of signatures on old posts:

<div class="content">
  <?php print $content ?>
  <?php if ($signature && $comment->cid > 3443): // The highest comment ID before upgrading to Drupal 6 ?>
    <div class="user-signature clear-block">
      <?php print $signature ?>
    </div>
  <?php endif; ?>
</div>

$language is now an object

The $language variable accessible to PHPTemplate themes is now not only a simple string holding the language code of the current page viewed, but an object representing multiple properties of the current language. You can now make your theme right to left compliant, so that your theme can be used by people hosting content in right to left written scripts (like Hebrew, of which http://www.drupal.org.il/ is a good example).

$language has the $language->language property available with the current language code, and $language->direction being an intereger (0 or LANGUAGE_LTR for left to right and 1 or LANGUAGE_RTL for right to left). If you are only interested in updating your themes, just change every instance of $language to $language->language.

Right to left CSS override files supported

Any CSS file added to the page with drupal_add_css() can have a right to left CSS file pair. An example could be style.css, which can have a style-rtl.css file in the same directory. This file can contain overrides for the stlyes in style.css which should be different in a right to left language. The Drupal core system includes such RTL CSS files for built-in modules as well as some themes. By convention, the overriden rules are marked with an /* LTR */ comment in the original CSS file, so maintainers will notice that the RTL CSS might need modification when modifying the original CSS file later. These CSS files are only loaded when an RTL language is used to display the page.

An excerpt from the modules/system/defaults.css file:

th {
  text-align: left; /* LTR */
  padding-right: 1em; /* LTR */
  border-bottom: 3px solid #ccc;
}

An excerpt from the modules/system/defaults-rtl.css file:

th {
  text-align: right;
  padding-right: inherit;
  padding-left: 1em;
}

jQuery updated to 1.1.2

The jQuery JavaScript library included in Drupal was updated to version 1.1.2.

Converting 4.7.x themes to 5.x

Overview of Drupal API changes in 5.x
  1. changed $primary_links and $secondary_links now return structured links
  2. drupal_add_css() - the proper way to add CSS files
  3. theme_get_styles() has been replaced by drupal_get_css()
  4. New $feed_icon
  5. New clearing class
  6. theme_links() now returns list of links
  7. drupal_add_js()
  8. _phptemplate_callback signature change
  9. id='pager' is now class='pager'

4.7.x themes:

<?php
print '<ul>';
foreach (
$primary_links as $link) {
   print
'<li>'. $link .'</li>';
}
print
'</ul>';
?>

5.x themes:

<?php
print theme('links', $primary_links);
?>

theme_links() now returns a list of links.

Additionally, the keys for each link are of the form 'moduleName-linkName', for example: menu-1-3-4 or node-read-more or comment-add-new.

For more info on this change, please see the Converting 4.7.x modules to 5.x docs.

drupal_add_css() - the proper way to add CSS

In 4.7.x, you would use theme('stylesheet_import', base_path() . path_to_theme() . '/extra_stylesheet.css') or theme_add_style($themes[$theme]->filename)

In 5.x, this is now changed to drupal_add_css($path = NULL, $type = 'module', $media = 'all')

This makes it very easy and consistent to add CSS in a proper cascading manner (with core CSS first, then module CSS, then theme CSS).

There is also a new variable available in phpTemplate now, $css, which is an array of all the CSS files for the current page. $styles still holds the actual HTML to load the CSS files, but if at the theme level you want to swap out certain CSS files (like drupal.css for example), this is very easy.

<?php
 
// print $styles;
  // we don't need to print out $styles since we want to take out drupal.css

 
unset($css['core']['misc/drupal.css']);
  print
drupal_get_css($css);
?>

Remember that $css gets it's value before the code in page.tpl.php is run. So trying to call drupal_add_css() from within page.tpl.php doesn't change the value of $css.

Usually, one would only call drupal_add_css() within template.php or within a module.

If you really want to add a CSS file from within page.tpl.php, you will either need to reset the value of $css or don't use it at all:

// page.tpl.php code:
<?php
drupal_add_css
(path_to_theme().'/override-defaults.css', 'core', 'all'); // consider 'theme' instead of 'core'

print drupal_get_css(); // don't add $css to the params

// equivalent, but longer code here:
$css = drupal_add_css();  // reset the value of $css (will now contain "override-defaults.css")
print drupal_get_css($css);  // add $css to the params
?>

References:
- Simplify adding CSS in Drupal (issue)
- How to properly add CSS files (Lullabot) (previous, 4.7.x and lower versions of Drupal)

theme_get_styles() has been replaced by drupal_get_css()

Related to the above there is a new drupal_get_css function which calls drupal_add_css. It loads the CSS in order, with 'core' CSS first, then 'module' CSS, then 'theme' CSS files. This ensures proper cascading of styles for easy overriding in modules and themes.

New $feed_icon

There is a new $feed_icon available to phpTemplate themes. This variable is a string of *all* feed icons for the current page. You can freely put this anywhere you want on your page--no longer are the icons tied to the body of the page.

For more details, read updating your modules.

New clearing class

In previous versions of Drupal, the bundled clearing class was:

<br class="clear" />

However, the problem with this is it that it adds unessecary breaks all over the page, just to clear floats.

This has been changed to now use a proper clearing class, as outlined at Position is Everything.

This clearing class is completely CSS based and requires no additional markup. It does have a 2 small hacks in there, but they are safe hacks, one simply makes it work in IE6 and the other hides it from IE5 on Mac. As browsers change and grow, altering the CSS in one central place will make it easier to maintain this.

To properly use this, you add the clearing class to the *containing* element at top, instead of the bottom:

Old way:

<div>
Some text here.
<br class="clear" />
</div>

New way:

<div class="clear-block">
Some text here.
</div>

theme_links() now correctly returns a list of links. It also adds in useful classes as well, including "first" and "last".

New usage:

<?php
print theme('links', $primary_links);
?>

Will print out a list of the primary links as follows:

<ul class="links">
 
<li class="first menu-1-1-52"><a href="/drupalCVS/?q=test" title="" class="menu-1-1-52">test</a></li>

<li class="menu-1-2-52"><a href="/drupalCVS/?q=test2" title="" class="menu-1-2-52">test2</a></li>

<li class="last menu-1-3-52"><a href="/drupalCVS/?q=test3" title="" class="menu-1-3-52">test3</a></li>

</ul>

Additionally, you can send in parameters to theme_links() to add in classes an IDs as follows:

<?php

print theme('links', $primary_links, array('class' =>'links', 'id' => 'navlist'));
?>

Which creates the same structure but with the class and ID added to the parent UL:

<ul class="links" id="navlist">

<li class="first menu-1-1-52"><a href="/drupalCVS/?q=test" title="" class="menu-1-1-52">test</a></li>

<li class="menu-1-2-52"><a href="/drupalCVS/?q=test2" title="" class="menu-1-2-52">test2</a></li>

<li class="last menu-1-3-52"><a href="/drupalCVS/?q=test3" title="" class="menu-1-3-52">test3</a></li>

</ul>

drupal_add_js()

In 4.7.x, JavaScript files were added to the page header by calling drupal_set_html_head().

In 5.x, JavaScript files and settings are added with drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE).

JavaScript file inclusions, settings and plain code are now inserted by calling drupal_get_js(). The best place to call this function in your theme is right below the drupal_get_css() call in the HTML-head of your theme.

There is also a new variable available in phpTemplate now, $scripts, which holds the actual HTML to load the JavaScript files and make the JS settings available. If you want to swap out certain JavaScript files at the theme level (like autocomplete.js with your custom file for example), this is very easy.

<?php
 
// we don't need to print out $scripts since we want to take out autocomplete.js
  // print $scripts;
 
  // this returns the array of JavaScript files for the header
 
$js = drupal_add_js(NULL, NULL, 'header');
  unset(
$js['module']['misc/autocomplete.js']);
  print
drupal_get_js('header', $js);
?>

References:
- Streamline JavaScript addition and add settings storage (issue)

_phptemplate_callback signature change

The function _phptemplate_callback in the phptemplate theme engine, used to externalize theme functions to *.tpl.php files, has become more flexible in the way it finds and associates *.tpl.php files with theme function invocations. In Drupal 4.7, one had to specify the name of the external file, wheras in 5.0, one can specify a list of suggestions. Phptemplate will then look through all of the suggested filenames and look for each one. In practice, this makes the third parameter to the function an array instead of a string:

Drupal 4.7
<?php
function _phptemplate_callback($hook, $variables = array(), $file = NULL) {
?>
Drupal 5.0
<?php
/**
* Execute a template engine call.
*
* Each call to the template engine has two parts. Namely preparing
* the variables, and then doing something with them.
*
* The first step is done by all template engines / themes, the second
* step is dependent on the engine used.
*
* @param $hook
*   The name of the theme function being executed.
* @param $variables
*   A sequential array of variables passed to the theme function.
* @param $suggestions
*   An array of suggested template files to use. If none of the files are found, the
*   default $hook.tpl.php will be used.
* @return
*  The HTML generated by the template system.
*/
function _phptemplate_callback($hook, $variables = array(), $suggestions = array()) {
?>

id='pager' is now class='pager'

The 'pager' identifier has been changed from an id to a class in the event that there is more than one pager present on a page. So stylesheets need to be changed from #pager to .pager.

References:
- theme_pager : id="pager" to class="pager" (issue)

Converting 4.7.x themes to 4.7.4

Theming images

Changes to filter_xss_bad_protocol cause a cryptic bug in theme_image to surface. If your theme overrides the function theme_image and the override contains theme_image code, be sure to update the way the image url is generated.

See theme_image in includes/theme.inc for example code.

Theming forms

Drupal 4.7.4 saw the addition of a new default form field; form_token, to protect against cross site request forgeries. The token ensures that forms submitted to the site are actually requested first.

There are a few potential issues surrounding the form_token.

Relying on specific, known form fields

If you do not output the form_token, the form will fail validation.

Consider the following example form:

<?php
function your_form() {
 
$form['field'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Example'),
   
'#default_value' => 'text',
  );
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Submit'),
  );
  return
drupal_get_form('your_form_id', $form);
}
?>

Suppose you write a theme function for this form:

<?php
// Will cause form validation to fail.
function theme_your_form_id($form) {
 
// Output fields in a specific order / with markup:
 
$output = form_render($form['field'])
 
$output .= form_render($form['form_id']);
 
$output .= form_render($form['submit']);
  return
$output;
}
?>

As the form_token will not be included in the HTML form the user receives, the token value won't be posted back and the form will fail validation.

The solution is easy; be sure to output the form_token field by adapting your custom theme function.

<?php
function theme_your_form_id($form) {
 
// Output fields in a specific order / with markup:
 
$output = form_render($form['field'])
 
$output .= form_render($form['form_id']);
 
$output .= form_render($form['submit']);

 
// form_token necessary to pass validation
 
$output .= form_render($form['form_token']);
  return
$output;
}

// Or better

function theme_your_form_id($form) {
 
// Output fields in a specific order / with markup:
 
$output = form_render($form['field'])
 
$output .= form_render($form['submit']);
 
 
// Render the remainder of the form, including hidden fields.
 
$output .= form_render($form);
  return
$output;
}
?>

Converting 4.6.x themes to 4.6.10

Drupal 4.6.10 saw the addition of a new form field; token, to protect against cross site request forgeries. The token ensures that forms submitted to the site are actually requested first.

This token will be added to all forms generated via the form function.

There is a potential issue surrounding the form_token for forms that are not defined via the form() function.

Raw HTML forms

If your theme outputs an HTML form, it will (nearly) always fail validation. To solve this you need to add the hidden form_token field to the form.

Example phptemplate:

<!-- Fails validation -->
<form action="<?php print $search_url ?>" method="post">
    <div id="search">
      <input class="form-text" type="text" size="15" value="" name="edit[keys]" /><input class="form-submit" type="submit" value="<?php print $search_button_text ?>" />
    </div>
  </form>

Adding a form token just before the closing form-tag solves the issue.

<!-- Corrected example -->
<form action="<?php print $search_url ?>" method="post">
    <div id="search">
      <input class="form-text" type="text" size="15" value="" name="edit[keys]" /><input class="form-submit" type="submit" value="<?php print $search_button_text ?>" />
    </div>
  <?php print form_token() ?>
  </form>

If you want your theme to keep functioning on earlier Drupal 4.6 versions, check whether form_token() exists with function_exists('form_token'):

<!-- Corrected example -->
<form action="<?php print $search_url ?>" method="post">
    <div id="search">
      <input class="form-text" type="text" size="15" value="" name="edit[keys]" /><input class="form-submit" type="submit" value="<?php print $search_button_text ?>" />
    </div>
  <?php if (function_exists('form_token')) { print form_token(); } ?>
  </form>

Converting 4.6.x themes to 4.7.x

Table row coloring

The class names for alternating table rows have been changed from light and dark to odd and even.

$site removed from page.tpl.php

Any uses of the variable $site should be replaced with $site_name.

$seqid is now $id

If you were using the $seqid counter, please change all occurences to simply $id. As $id is not used by any core modules (and should not be used) this is not likely to cause any problems.

New theme_username()

You can now change the appearance of your user's names by implementing this function. Used to be format_name().

theme_node: $main => $teaser

In the theme_node() function, $main has been renamed to $teaser for consistency. This possibly affects the template engine (phptemplate_node()) as well as the template file: node.tpl.php.

Regions

Drupal theme authors can now define and implement any number of 'regions' for content to be rendered into. This regioning system replaces the previous 'left' and 'right' sidebar regions and should enable much more flexible layout and design, and requires changes to every theme and theme engine.

At a minimum, all plain PHP themes must now include a mytheme_regions() function in their .theme file, defining the available regions. If left and right sidebars were already available and no new regions were introduced, a plain PHP theme could be upgraded simply by adding the following function:

<?php
function mytheme_regions() {
  return array(
      
'left' => t('left sidebar'),
      
'right' => t('right sidebar')
  );
}
?>

Want to do more? Look to the regioning information in the plain PHP theme author's guide.

To upgrade PHPTemplate-based themes, the minimum change needed is to add a "header" region to the page.tpl.php file (a region below the top of page banner). In bluemarine, the added region looked like this:

  <tr>
    <td colspan="2"><div><?php print $header ?></div></td>
  </tr>

See the page on regioning in PHPTemplate themes for tips on how to introduce your own custom regions.

Like plain PHP themes, template engines besides the core PHPTemplate one can be minimally upgraded simply by adding a mythemeengine_regions() function defining the already-available left and right regions. But full support for regioning will require more extensive changes. The phptemplate.engine section of the recently-applied regioning patch should be a partial guide for what's needed.

theme_comment_thread_min() and theme_comment_thread max() renamed

theme_comment_thread_max() has been renamed to theme_comment_thread_expanded(). theme_comment_thread_min() has been renamed to theme_comment_thread_collapsed().

Forum icons now controlled by the theme

_forum_icon() has been replaced by theme_forum_icon(). Themes can now override the display of forum icons which appear next to a topic.

Drupal.css changes

Changes since 4.6 can be viewed from the CVS log

We no longer use the <base> element

In versions of Drupal prior to 4.7, we used the HTML BASE tag to indicate the base to which all relative links should be appended to. In Drupal 4.7, however, the BASE tag has been removed entirely. All Drupal functions that specifically return URLs (such as l() or url() or the various theme_add_style() features) have been updated to now return the base_path() prepended to any URLs. If, however, you are manually creating your own URLs and not using one of Drupal's internal functions, you'll need to do something like:

BEFORE:
print '<link rel="stylesheet" type="text/css" href="themes/chameleon/common.css" />";

AFTER (generic approach):
print '<link rel="stylesheet" type="text/css" href='. base_path() .'"themes/chameleon/common.css" />";

AFTER (strongest specific approach, using Drupal functions):

// this would only apply to styles, naturally
print theme('stylesheet_import', base_path() . path_to_theme() ."/common.css");
theme_onload_attribute has been removed; use JS addLoadEvent() instead

This is covered in more detail in the modules upgrade section.

Converting 4.5.x themes to 4.6.x

Search form

If your theme implements a search form, it needs to be altered. The search box <input> tag should have the name attribute set to edit[keys] rather than keys.

Node links

Node links no longer use the link_node() function, but instead are passed as an array in $node->links. PHP-based themes will need to be updated to pass this array through theme('links'). Template-based themes shouldn't need any changes.

Pages

The function theme_page() no longer takes $title or $breadcrumb arguments. Remove the two arguments and any special handling of them. All page titles and breadcrumbs are now retieved using drupal_get_title() and drupal_get_breadcrumb().

Node and comment markers

Node and comment markers are not restricted anymore to signal that something is new or that a form element is required. The required form element marker was moved to theme_form_element(), while theme_mark() was kept to generate content markers. New constants help in deciding on the marker to display: MARK_NEW signals new content, MARK_UPDATED is for changed or extended content and MARK_READ is for read or too old content. Now it is possible to output markers for read content too, and distinguish between new and updated content.

Pager and menu item themeing

Parts of the pager are now themeable themselfs. The menu theming was also reorganized, to be easier to add wrappers and theme menu links. If you override any of the theme_menu...() functions in your theme or template, compare them to the current versions in theme.inc and menu.inc to update them.

Text validation changes

Due to some changes in plain-text processing, some parameters which were HTML are now plain-text and vice-versa. If you use a theme engine, you shouldn't need any changes, except if you override extra theme_ functions yourself.

If you are seeing problems, the best approach is to compare every theme_ functions that you override with the one from Drupal core. In particular, the menu theme functions (theme_menu_*) require changes in the way l() is used.

You should also try submitting a node and a comment with HTML tags in the subject. The tags should come out escaped, and should be shown on screen rather than interpreted. If this is not the case, you need to check your theme_node() and theme_comment() functions.

Finally, page titles should be run through strip_tags() when put into the html <title> tag in <head>. This should only be a problem if you have a .theme theme, as the theme engines have all be updated to accomodate this change.

Converting 4.4.x themes to 4.5.x

Note: the theme system changed significantly in 4.5. Make sure you read through this entire guide, as an outdated theme will prevent you from accessing vital parts of your site.

Directory structure

Templates are now seen as themes unto themselves, rather than hiding behind their template engine. Template engines now reside in subdirectories of themes/engines, while templates simply are placed in subdirectories of themes. Template engines compatible with Drupal 4.5 will identify templates based on their filename and send the appropriate listings to the theme system.

For xtemplate templates, your template must be named xtemplate.xtmpl, and your default stylesheet must be named style.css (as mentioned below in the "Styles" section).

For example, the old Xtemplate pushbutton template has moved from themes/xtemplate/pushbutton to themes/pushbutton.

Tabs (a.k.a. Local Tasks)

Drupal now separates out menu items that are "local tasks"; functions to be performed on the current location. By default, these are rendered as a set of tabs. Themes are responsible for printing these. A typical location is below the page title, so that

<?php
if ($title = drupal_get_title()) {
 
$output .= theme("breadcrumb", drupal_get_breadcrumb());
 
$output .= "<h2>$title</h2>";
}

if (
$help = menu_get_active_help()) {
 
$output .= "<small>$help</small><hr />";
}
?>

becomes
<?php
if ($title = drupal_get_title()) {
 
$output .= theme("breadcrumb", drupal_get_breadcrumb());
 
$output .= "<h2>$title</h2>";
}

if (
$tabs = theme('menu_local_tasks')) {
 
$output .= $tabs;
}

if (
$help = menu_get_active_help()) {
 
$output .= "<small>$help</small><hr />";
}
?>

For xtemplate templates, Before:
<!-- BEGIN: title -->
{breadcrumb}
<h1 class="title">{title}</h1>
<!-- END: title -->

After:
<!-- BEGIN: title -->
{breadcrumb}
<h1 class="title">{title}</h1>
<!-- BEGIN: tabs -->
<div class="tabs">{tabs}</div>
<!-- END: tabs -->
<!-- END: title -->
Status Messages

The theme_page function is no longer responsible for rendering each status message. Instead, we now use the theme_status_messages() function. Before:

<?php
 
foreach (drupal_get_messages() as $message) {
    list(
$message, $type) = $message;
   
$output .= "<strong>". t("Status") ."</strong>: $message<hr />";
  }
?>

After:
<?php
  $output
.= theme_status_messages();
?>
Static vs. Sticky

In Drupal 4.5, "static" posts have been renamed as "sticky" posts. If your theme uses special styling for this type of post, you'll want to change any references from "static" to "sticky".

Avatar vs. User Picture

In Drupal 4.5, "avatars" have been renamed to "user pictures". Additionally, the method by which themes display avatars has changed. Themes now call theme_user_picture, which returns the appropriate image and link HTML. Before:

<?php
 
if (module_exist("profile") && variable_get("theme_avatar_node", 0)) {
   
$avatar = $node->profile_avatar;
    if (empty(
$avatar) || !file_exists($avatar)) {
     
$avatar = variable_get("theme_avatar_default", "");
    }
    else {
     
$avatar = file_create_url($avatar);
    }
    if (
$avatar) {
     
$avatar = "<img src=\"$avatar\" alt=\"" . t("%user's avatar", array("%user" => $node->name ? $node->name : t(variable_get("anonymous", "Anonymous")))) . "\" />";
      if (
$node->uid) {
       
$avatar = l($avatar, "user/view/$node->uid", array("title" => t("View user profile.")));
      }
     
$output .= $avatar;
    }
  }
?>

After:
<?php
  $output
.= theme('user_picture', $node);
?>

For xtemplate templates, simply replace:
<!-- BEGIN: avatar -->
<div class="avatar">{avatar}</div>
<!-- END: avatar -->

with:
<!-- BEGIN: picture -->
{picture}
<!-- END: picture -->
Theme Screenshots

The new theme selector looks for a screenshot of each theme with the filename screenshot.png in each directory. Screenshots are optional and themes without screenshots will simply display "no screenshot" on theme selection pages. To create a screenshot which matches those in core, follow these instructions:

  1. Log in as administrator user.
  2. Enable the following modules, for some extra menu items:
    aggregator, blog, node, page, story, tracker
  3. Create the following story node:
    • title: Donec felis eros, blandit non.
    • body: Morbi id lacus. Etiam malesuada diam ut libero. Sed blandit, justo nec euismod laoreet, nunc nulla iaculis elit, vitae. Donec dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Vivamus vestibulum felis <a href="#">nec libero. Duis lobortis</a>. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc venenatis pretium magna. Donec dictum ultrices massa. Donec vestibulum porttitor purus. Mauris nibh ligula, porta non, porttitor sed, fermentum id, dolor. Donec eu lectus et elit porttitor rutrum. Aenean justo. Phasellus augue tortor, mattis nonummy, aliquam euismod, cursus eget, ipsum. Sed ultricies bibendum ante. Maecenas rhoncus tincidunt eros.
  4. Look at the node, and make sure the tabs are visible. Take a screenshot.
  5. Cut out a piece about 420x254 resized to 150x90 (35% zoom). Try to show useful page elements (menu, tabs, title, links).
  6. Applied a plain 'sharpen' filter to the thumbnail.
  7. Save as "screenshot.png" in theme (or style) directory.
Centralized Theme Configuration

The theme system now has the ability to store certain common configuration items for each theme. However, some themes may not wish to utilize all of these settings, so a theme_features hook has been introduced. In each theme / theme engine, this function should return an array of settings which the theme supports. To implement each of these functions, themes / theme engines should call the theme_get_setting function, which will return data regarding the administrator's setting for this particluar theme. If there are no settings for the current theme, global values will be returned. Below is a table of values for the _features hook, a description of their function, and a code snippet of the appropriate theme_get_settings call.

_features hook value Description theme_get_settings call
'logo' theme allows customization of site logo
<?php
if ($logo = theme_get_setting('logo')) {
 
$output .= "  <a href=\"./\" title=\"Home\"><img src=\"$logo\" alt=\"Home\" /></a>";
}
?>
'toggle_name' theme allows site name to be switched on/off
<?php
if (theme_get_setting('toggle_name')) {
 
$output .= "  <h1 class=\"site-name title\">". l(variable_get('site_name', 'drupal'), ""). "</h1>";
}
?>
'toggle_search' theme allows search box to be switched on/off
<?php
if (theme_get_setting('toggle_search')) {
 
$output .= search_form();
}
?>
'toggle_slogan' theme allows site slogan to be switched on/off
<?php
if (theme_get_setting('toggle_slogan')) {
 
$output .= "  <div class=\"site-slogan\">". variable_get('site_slogan', '') ."</div>";

}
?>
'toggle_mission' theme allows site mission to be switched on/off
<?php
if ($mission = theme_get_setting('mission')) {
 
$output .= $mission;
}
?>
'toggle_primary_links' theme allows primary links to be customized
<?php
$output
.= theme_get_setting('primary_links');
?>
'toggle_secondary_links' theme allows secondary links to be customized
<?php
$output
.= theme_get_setting('secondary_links');
?>
'toggle_node_user_picture' theme allows node user pictures to be switched on/off
<?php
if (theme_get_setting('toggle_node_user_picture') && $picture = theme('user_picture', $node)) {
 
$output .= $picture;
}
?>
'toggle_comment_user_picture' theme allows comment user pictures to be switched on/off
<?php
if (theme_get_setting('toggle_comment_user_picture') && $picture = theme('user_picture', $comment)) {
 
$output .= $picture;
}
?>
N/A (Global Setting) Allow admin to specify which node types should display "Submitted by..." message
<?php
$output
.= theme_get_setting("toggle_node_info_$node->type") ? t("Submitted by %a on %b.", array("%a" => format_name($node), "%b" => format_date($node->created))) : '';
?>

Note that all of these settings are optional, but recommended.

Theme-specific settings are still possible as well. They are still read from the theme_settings, but are now placed in a group on the appropriate theme's tab, rather than on a separate page.

Styles

The theme system now allows for switching between different "styles" for each theme. Each "style" is defined by a style.css file in a subdirectory of the theme. In order to accomplish this style switching, themes should add a call to theme_get_styles() within their <head> block. For example:

<?php
$output
.= drupal_get_html_head();
$output .= " &lt;link rel=\"stylesheet\" type=\"text/css\" href=\"themes/chameleon/common.css\" /&gt;\n";
$output .= theme_get_styles();
$output .= "&lt;/head&gt;";
?>

Notice how the reference to common.css is listed before theme_get_styles(). This allows individual styles to override your common CSS rules (if you use any).

The "default" style for each theme (the stylesheet in which you define color scheme and other general presentation items) should be renamed to style.css and placed in your theme directory. You should also remove any references to it from your theme or template. Drupal will reference it in theme_get_styles(). (If the default style is selected)

For xtemplate themes, you need to add the {styles} tag add the end of your <head> section.

_help hook

The theme_help hook is no longer used. It can be removed if desired.

References to comment_referer_load() should be switched to comment_node_url()

Converting 4.3.x themes to 4.4.x

For more information on how the interaction between themes and modules has changed, see converting 4.3 modules to 4.4.

  • The theme system is no longer built on PHP's object model. The BaseTheme class is no more and, as such, you no longer have to use a class for your theme. Instead, a theme is a collection of functions. This will make Drupal theme development feel much the same as Drupal module development. Prefix your theme function with your theme's name. Examples:
    mytheme_page(), mytheme_comment(), mytheme_node().
  • mytheme::system() (or in the new parlance, mytheme_system()) is no longer used. The theme description used on the theme administration page should instead be returned by a new function called
    mytheme_help(). This function follows the same semantics as the regular module _help hook:
    <?php
    function mytheme_help($section) {
      switch (
    $section) {
        case
    'admin/system/themes#description':
          return
    t("A description of mytheme");
      }
    }
    ?>
  • All theme functions now return their output instead of printing them to the user. There should be no print or echo statements in your theme.
  • The mytheme_header() and mytheme_footer() functions and no longer used, a mytheme_page() function is introduced instead.
    <?php
    function mytheme_page($content, $title = NULL, $breadcrumb = NULL) {
      if (isset(
    $title)) {
       
    drupal_set_title($title);
      }
      if (isset(
    $breadcrumb)) {
       
    drupal_set_breadcrumb($breadcrumb);
      }
      ...
    }
    ?>

    This function should return the HTML code for the full page, including the header, footer and sidebars (if any). Note that it is important to set the title and the breadcrumbs for Drupal with the setter functions as suggested above, instead of just using the values provided as parameters. This way modules acting on the title or breadcrumb values can use the real value when generating blocks for example.
  • Themes now have the responsibility of placing the title, breadcrumb trail, status messages, and help text for each page. This gives them the flexibility to, for example, place the breadcrumb trail above the title or in the footer. It is now expected that mytheme_page() will return these elements. The page theme function should override the title and breadcrumb trail retrieved from Drupal, in case some explicit value is provided in the function parameters (see above). A theme can obtain the values set before by calling the functions drupal_get_title(), drupal_get_messages(), menu_get_active_help(), and drupal_get_breadcrumb(). The breadcrumb trail is returned from the latter function as an array of links; it can be formatted into a string by using theme("breadcrumb", drupal_get_breadcrumb()). Most themes use the following new code-snippet in their page function:
    <?php
    if ($title = drupal_get_title()) {
     
    $output .= theme("breadcrumb", drupal_get_breadcrumb());
     
    $output .= "<h2>$title</h2>";
    }

    if (
    $help = menu_get_active_help()) {
     
    $output .= "<div class=\"help\">$help</div><hr />";
    }

    foreach (
    drupal_get_messages() as $message) {
      list(
    $message, $type) = $message;
     
    $output .= "<strong>". t("Status") ."</strong>: $message<hr />";
    }
    ?>
  • The _head() hook is eliminated and replaced with the drupal_set_html_head() and drupal_get_html_head() functions, therefore the HTML head part should include the return value of drupal_get_html_head() instead of the return value of theme("head").
  • The theme_node() function takes an extra parameter now, $page, that indicates to the theme whether to display the node as a standalone page or not. If $page is true, then the title of the node should not be printed, as it will already have been printed by theme_page. Also note that the node body will only be filtered with the configured filters if the node page is displayed. Otherwise only the teaser will be filtered for performance reasons. Example:
    <?php
    function mytheme_node($node, $main = 0, $page = 0) {
      if (!
    $page) {
       
    $output = "<h2>" . $node->title . "</h2>";
      }
      if (
    $main && $node->teaser) {
       
    $output .= "<div>". $node->teaser . "</div>";
      }
      else {
       
    $output .= "<div>". $node->body . "</div>";
      }
      return
    $output;
    }
    ?>
  • To improve block themeability, theme_block() has been changed. The old
    function theme_block($subject, $content, $region = "main")

    has become
    function theme_block($block)

    with $block being an object containing $block->subject, $block->content, etc. See the doxygen doc for details and for how you can style blocks with CSS.
  • Also, theme_blocks() has been improved to allow themes to hook into (change) the blocks before outputting them. See this cvs log message for details.

fooo bar

Converting 4.2.x themes to 4.3.x

No changes are required :)

A few more CSS classes are available to you if you wish to use them. A non-exhaustive list is

  • read-more: affects the formatting of the 'read more' link
  • cell-highlight: affects the cell in the table header which is currently the sort key. this cell also has an image which you can override in your theme->image directory (most images are overridable in this way).

Converting 4.1.x themes to 4.2.x

Required changes
Add a theme_onload_attribute() to a <body> tag:
<body <b><?php print theme_onload_attribute(); ?></b> >
Optional changes
Take advantage of settings() hook

Themes can now populate settings to adminstration pages using the function <em>themename</em>_settings(). Example:

function mytheme_settings() {
  $output  = form_select("Sidebar placement", "mytheme_sidebar",
             variable_get("mytheme_sidebar", "right"),
             array(
             "none" => t("No sidebars"),
             "left" => t("Sidebar on the left"),
             "right" => t("Sidebar on the right"));
}
Direct you site logo to index.php

If you theme has the logo and you have made it to link &lt;a href=""&gt; or even &lt;a href="&lt;?php print path_uri();&gt;"&gt; then please replace these instances with a simple &lt;a href="index.php" alt=""&gt;

One additional change may be needed. Using a custom theme adapted from a generic one, the original node function has the following code:

<?php
    $terms
= array();

    if (
function_exists("taxonomy_node_get_terms")) {
       if (
$terms = taxonomy_node_get_terms($node->nid)) {
        
$taxlinks = array();
         foreach (
$terms as $term) {
           
$taxlinks[] = l($term->name, array("or" => $term->tid), "index");
         }
      
$taxo = $this->links($taxlinks);
       }
    }
?>

Which gaves an error on the index page after upgrading from 4.1 to 4.2 and contains invalid URLs. Replacing the above code with this fixes the error.
<?php
$terms
= array();

    if (
module_exist("taxonomy")) {
       
$terms = taxonomy_link("taxonomy terms", $node);
    }
   
$taxo = $this->links($terms);
?>

Converting 4.0.x themes to 4.1.x

Required changes

There is no required changes, all Drupal 4.0 themes should also work in Drupal 4.1

Optional changes
theme_head

Insert a function theme_head() inside your theme, right after the HTML's &lt;head&gt; tag:

<html>
  <head>
  <b><?php print theme_head(); ?></b>
  ...
This change allows modules to incorporate custom markup inside &lt;head&gt; &lt;/head&gt; tags such as Javascript, &lt;meta&gt; tags, CSS and more.

Converting 3.0.x themes to 4.0.x

Required changes
Changes in class definition

Theme class definition uses now a different syntax:
Instead
  class Theme extends BaseTheme {you should use
  class Theme_<i>themename</i> extends BaseTheme {where themename is name of your theme in lowercase.

Changes in function header()
  • Function header() takes now an optional parameter $title.
    Instead
      function header() {you should use
      function header($title = "") {
  • Previously all pages in Drupal site had the fixed page title: sitename - site slogan. Now the page title can be dynamic - for example when displaying single node, the page title can be note title - sitename. So, instead
      print variable_get("site_name", "drupal") ." - ". variable_get("site_slogan", "");you should use a more complex syntax:
      if ($title) {                                                                          
        print $title ." - ". variable_get("site_name", "drupal");                            
      }                                                                                      
      else {                                                                                 
        print variable_get("site_name", "drupal") ." - ". variable_get("site_slogan", "");   
      }
    of if you want to use compact version of the same construction:
      print $title ? $title." - ". variable_get("site_name", "drupal") :         
    variable_get("site_name", "drupal") ."  ". variable_get("site_slogan", "");
    This piece of code checks if $title is present. If yes, it outputs $title and site name, if not, it outputs site name and slogan.
  • If you used theme_account() function (what outputs login/membership box) in header(), please remove it. Login box placement is controlled in Administration > blocks page from now on and theme_account() is no longer used.
Changes in function node()
  • format_name() accepts now parameter $node, not $node->name. Also $node->timestamp is replaced with $node->created. So, instead   print strtr(t("Submitted by %a on %b"), array("%a" => format_name(<b>$node->name</b>), "%b" => format_date(<b>$node->timestamp</b>)));you should use
      print strtr(t("Submitted by %a on %b"), array("%a" => format_name(<b>$node</b>), "%b" => format_date(<b>$node->created</b>)));
  • node_index() is no longer used because Drupal 4.0 has more sophisticated classification system than Drupal 3.0 meta tags. So instead plain simple
      print node_index($node);you have to use
      $terms = array();                                                                           
     
      if (function_exists("taxonomy_node_get_terms")) {                                           
        foreach (taxonomy_node_get_terms($node->nid) as $term) {                                  
          $terms[] = l($term->name, array("or" => $term->tid), "index");                            
        }                                                                                           
      }

      print $this->links($terms);
  • Function link_node() accepts an optional parameter $main. Instead
      if ($main) {
        print $this->links(link_node($node));
      }
    you should use
      if ($links = link_node($node, $main)) {                                                     
        print $this->links($links);
      } </li></ul><h2>Changes in function comment()</h2>
    <ul><li>format_name() accepts now parameter $comment, not $comment->name. Instead
    <code>  print strtr(t("Submitted by %a on %b"), array("%a" => format_name(<b>$comment->name</b>), "%b" => format_date($comment->timestamp)));
    you should use
      print strtr(t("Submitted by %a on %b"), array("%a" => format_name(<b>$comment</b>), "%b" => format_date($comment->timestamp)));
Changes in function footer()
  • If you used theme_account() function (what outputs login/membership box) in footer() function, please remove it. Login box placement is controlled in Administration > blocks page from now on and theme_account() is no longer used.
Optional changes
New function: system()
  • Optionally theme can have a system() function what provides info about theme and its author:
    function system($field) {
    $system["name"] = "theme name";
    $system["author"] = "author name";
    $system["description"] = "description of the theme";
    return $system[$field];
    }

Using Theme Override Functions

Many resources for and about Drupal will tell you that the road to true customization lies through overriding theme functions in order to create exactly the kind of output you desire. Doing so is extremely powerful, though it can require a little PHP knowledge to pull off, since you need to create a function. However, there are many snippets and examples to choose from, and many of these are cut and paste with just a little bit of modification necessary to get them to work.

Which File to Use

In order to use theme overrides, the first thing you need is a place to put them. Exactly where you put them depends upon which theme engine you are using. The default theme engine in Drupal 4.6 was XTemplate; however, XTemplate proved problematic when paired with PHP 5.0, so Drupal 4.7 and higher now use PHPTemplate as the default theming engine.

The file you need will live in themes/MYTHEMENAME.

Plain PHP Theme
If using a plain PHP Theme, such as Marvin or Chameleon, the file you place your overrides in is
THEMENAME.theme
XTemplate
XTemplate does not allow theme function overriding.
PHPTemplate
PHPTemplates keep their theme overrides in template.php.
SmartyTemplates
SmartyTemplates use the file smartytemplate.php.
wgSmarty
wgSmarty uses template.php but as of this writing that code is commented out; it may not be possible to do theme overrides in wgSmarty.
phptal
PHPTAL uses template.php, and is very similar to PHPTemplate.

Most themes do not actually come with this file. You will have to create it!

And you will need to ensure this file contains a <?php statement at the top, or the file will not work! (And might cause your site to crash with a white screen).

How to Name your Override Function

When overriding a theme function, module documentation will usually inform you to use or override something along the lines of theme_SOME_FUNCTION_NAME. In order to override this, you need to name it something slightly different from this, since PHP requires all function names to be unique.

The example function we're going to look at for this is theme_item_list, which is used whenever Drupal wants to display a list of items, and is used almost everywhere you see the <li> tag in Drupal, except in the menu structure.
This function is defined as:

<?php
 
function theme_item_list($items = array(), $title = NULL, $type = 'ul') {
?>

Overrides can be named in two ways, unless you're using a plain PHP theme. First, all theme overrides can be named by replacing the 'theme_' at the beginning of the function with 'MYTHEMENAME_'. So if you're using the chameleon template, in your chamelon.theme file you, to override the function theme_item_list as:

<?php
 
function chameleon_item_list($items = array(), $title = NULL, $type = 'ul') {
?>

This is not actually the preferred method of overriding, however, but it is the one that always works. Most themes, however, are derived from one of the Template Engines. They can override these functions by using the name of the template engine instead of the theme name. This is preferred, because it makes it very easy to share these functions with other users who might be using a different theme. Or even share a single file of functions with several themes on the same site!

Some examples:

<?php
 
function phptemplate_item_list($items = array(), $title = NULL, $type = 'ul')
  function
smarty_item_list($items = array(), $title = NULL, $type = 'ul')
?>

Using Theme Override Functions For Forms

Introduction

It's an often overlooked fact that as well as being able to override the standard theme_ functions, forms generated with Drupal 4.7's Forms API can be themed in a similar manner. This tutorial will work through the process of theming a form.

Sample form and data

The original question was "how to embed checkboxes within a table". To demonstrate this we need some data for the table we are going to create. So we begin this tutorial by getting some data we want to put in a table along with the checkboxes. So, lets start with this:

<?php
  $r
= db_rewrite_sql(db_query('SELECT nid, title, created FROM {node}' LIMIT 20));
  while (
$row = db_fetch_object($r)) {
   
$rows[] = $row;
  }
?>

Note, we put an SQL LIMIT here so that we don't end up with too many rows in our sample table, we'll remove that later when discussing pagination. Also, obviously this isn't the best way to get nodes in Drupal. It's only here to provide us with some data for our form/table

From the supplied data we'll create a table with a checkbox in the left most column (similar to the "mass operations" for comments found in Drupal Core).

The first thing to do is generate the $form array in preperation for the call to drupal_get_form. The following code does that:

<?php
  $r
= db_rewrite_sql(db_query('SELECT nid, title, created FROM {node} LIMIT 20'));
  while (
$row = db_fetch_object($r)) {
   
$rows[] = $row;
  }
 
  foreach (
$rows as $v) {
   
$nids[$v->nid] = '';
   
$form['title'][$v->nid] = array('#type' => 'markup', '#value' => check_plain($v->title));
   
$form['created'][$v->nid] = array('#type' => 'markup', '#value' => date("d/m/Y H:i", $v->created));
  }
 
$form['nids'] = array('#type' => 'checkboxes', '#options' => $nids);
?>

This may look a little unusual. Most $form arrays are static and developers are use to seeing long lists of $form declarations. Well, in this case you can see the form is dynamic. Within the loop, we create the nested array $form['title'][$v->nid] and $form['created'][$v->nid] of type markup. This used as these are display fields within the form. $nids[$v->nid] creates an array suitable to pass to the #options of the checkboxes form element type.

Lastly, every table needs a header. However, we need to pass the header array as part of the form as there's no ther way to pass it. So, we'll add a header array and enter it as type markup and lastly we'll make a call to drupal_get_form() to get the form itself.

<?php
  $r
= db_rewrite_sql(db_query('SELECT nid, title, created FROM {node} LIMIT 20'));
  while (
$row = db_fetch_object($r)) {
   
$rows[] = $row;
  }
 
  foreach (
$rows as $v) {
   
$nids[$v->nid] = '';
   
$form['title'][$v->nid] = array('#type' => 'markup', '#value' => check_plain($v->title));
   
$form['created'][$v->nid] = array('#type' => 'markup', '#value' => date("d/m/Y H:i", $v->created));
  }
 
$form['nids'] = array('#type' => 'checkboxes', '#options' => $nids);
 
$form['submit'] = array('#type' => 'submit', '#value' => 'Update');
 
 
$form['header'] = array(
   
'#type' => 'value',
   
'#value' => array(
      array(
'data' => 'nid'),
      array(
'data' => t('Title')),
      array(
'data' => t('Created')),
    )
  );

  return
drupal_get_form('my_test_form', $form); // 4.7
?>

Now, if we did nothing more the resulting form on the browser would look quite odd indeed (try it on a test site and see). So, what we want to do is place the form inside a table and to do this we use a theme override function.

The name of the this function is derived for the $form_id parameter to drupal_get_form() above. So, in our case, the function name is theme_my_test_form($form). And here it is:

<?php
function theme_my_test_form($form) {
 
  foreach (
element_children($form['title']) as $key) {
   
$row = array();
   
$row['data'][0] = form_render($form['nids'][$key]);
   
$row['data'][1] = form_render($form['title'][$key]);
   
$row['data'][2] = form_render($form['created'][$key]);
   
$rows[] = $row;
  }

  return
form_render(theme('table', $form['header']['#value'], $rows));
}
?>

Drupal 5 users should use drupal_render() rather than form_render()

So, what's going on here. The theme function gets the $form array passed in. Now, the array is full of various things, submit, form_token, even out table header is in there. So, to extract the rows we are interested in the function element_children() is used to loop over one of the child arrays (I choose $form['title']).

As you can see, there are three <td> </td> the first holds the checkbox, the following two conatin the two markup fields. Now, running this you see that each database row has been rendered into an html table row with a checkbox in the left column.

Coming next I'll take this further and describe how to define the table to have sortable headers and how to paginate large datasets.

Thanks go to a79v for updates and corrections which have been incorporated into the main article

Writing .info files for themes (Drupal 6.x)

Starting with Drupal version 6.x, themes now use .info files to specify metadata, just like modules have done since version 5.x. Therefore, themes can now have a friendly name like modules, a description, a visible version number, and other descriptive information.

Furthermore, regions and features have also been moved into .info files, which makes it easier to copy a theme and modify its behavior without having to touch any php code. Drupal core still provides a default set of features and regions, so a theme's .info file only needs to specify either one if the defaults in core won't suffice.

The syntax for .info files in 6.x Drupal (which is the same for both modules and themes) is similar to the ini file format, but with some important enhancements to support nested data. For more details, see the comment at the top of drupal_parse_info_file() in includes/common.inc.

From 6.x onward, all .info files (for modules and themes) must indicate what major version of core they are compatible with. For example, if a theme has been ported to the Drupal core 6.x theme API, it should contain the following in its .info file:

core = 6.x

Please note that the drupal.org packaging script automatically sets this value based on the Drupal core compatibility setting on each release node, so people downloading packaged themes from drupal.org will always get the right thing. However, for sites that deploy Drupal directly from CVS, it helps if you commit this change to the .info file for your theme. This is also a good way to indicate to users of each theme what version of core the HEAD of CVS is compatibile with at any given time.

This page is still under construction...

Theme screenshot guidelines

Every theme for 4.5+ needs a screenshot in the form of a screenshot.png placed in the theme/template/style directory. It is best that screenshots are consistent. The guidelines for core theme screenshots are (starting from a blank Drupal site):

  1. Log in as the first user.
  2. Enable the following modules, for some extra menu items: aggregator, blog, node, page, search, story and tracker.
  3. Turn on the features that the theme supports (logo, site name, slogan, search box). Add some primary and secondary links if needed. We suggest "Link 1" "Link 2" "Link 3", you can link them to e.g. "user/1".
  4. Set the site name to Drupal and slogan to Community Plumbing.
  5. Create the following story node:

    Donec felis eros, blandit non

    Morbi id lacus. Etiam malesuada diam ut libero. Sed blandit, justo nec euismod laoreet, nunc nulla iaculis elit, vitae. Donec dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Vivamus vestibulum felis <a href="#">nec libero. Duis lobortis</a>. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc venenatis pretium magna. Donec dictum ultrices massa. Donec vestibulum porttitor purus. Mauris nibh ligula, porta non, porttitor sed, fermentum id, dolor. Donec eu lectus et elit porttitor rutrum. Aenean justo. Phasellus augue tortor, mattis nonummy, aliquam euismod, cursus eget, ipsum. Sed ultricies bibendum ante. Maecenas rhoncus tincidunt eros.

  6. Look at the node, and make sure the tabs are visible. Take a screenshot.
  7. Cut out a piece about ~420x254 resized to exactly 150x90 (~35% of the original size). Try to show only useful page elements (menu, tabs, title, links). Don't include browser chrome (toolbar, status bar, scrollbar, etc).
  8. Apply a standard 'sharpen' filter to the thumbnail for clarity.
  9. Save as a PNG, in paletted colorspace to cut down on size.

Example:
bluemarine screenshot

For Drupal.org project thumbnails, use the guidelines above except that:

  • You should fill up the site more. For example, add a comment to the story node or add some blocks.
  • The screenshot should show the entire page, though still without browser chrome (toolbar, status bar, scrollbar, etc).
  • The thumbnail should be 320x200 pixels large. It is best to resize to a width of 320 pixels first, then to crop off the bottom to a height of 200 pixels.
  • Try to make your original screenshot have a width of about 1000 pixels, so that the thumbnail is about 30% of the original size.
  • Name the screenshot screenshot-drupal.org.png or screenshot-drupal.org.jpg.
  • Try to keep the image small: save as paletted PNG or as a JPEG with 10-20% compression. This reduces the load time of the theme list on Drupal.org. Aim for 15-20KB.
  • Include the image in your project's description with class picture:
    <img src="http://cvs.drupal.org/......./screenshot-drupal.org.png" class="picture" />

    If you don't have permission to use the <img> tag, ask a site maintainer to add your screenshot. Please note, when a site administrator switches your project descripition to Full HTML you will loose the ability to edit it as you won't have permissions. If you need to edit it you will need to request it be switched back to Filtered HTML by a site admin. This drawback is a known issue we hope to improve on in the future.
Example:

Theme how-to's

This section collects 'How-to' articles on subjects relevant to theme developers. If you are looking for examples of modifications to themes you can read the PHPTemplate theme snippets.

Basic instructions for adding graphic tabs to your Drupal theme

Tested with version 4.6.X, may work with other versions.

Here are some basic instructions for adding graphic tabs to your Drupal theme, this particular example was done using the box_grey theme but it should work with most any theme that includes primary links. This is based on the Sliding Doors Technique discussed here.

http://www.alistapart.com/articles/slidingdoors

Our tabs will consist of 5 graphics, a right tab, a left tab, a right_on (active) tab, a left_on (active) tab and a background graphic for the tabs which contains the bottom border, this allows us to bleed the tabs through so they look right.

The basic technique for creating the graphics goes as follows, you create your tab graphics for the regular tab and the active tab, then split them into two pieces, a thin left edge and the rest of the tab as the right tab.

First thing we need to do is change the code that creates the primary links for your theme to include a tag that we can use in our style to place the left_on tab correctly.

Replace the following code in the page.tpl.php file in your theme's directory......

<?php if (count($primary_links)) : ?>
   <ul id="primary">
       <?php foreach ($primary_links as $link): ?>
          <li><?php print $link?></li>
       <?php endforeach; ?>
   </ul>
<?php endif; ?>

With this code which checks each link for the string "active" which the active tab will contain. It then adds id="current" to the link. This ID will be referenced in the CSS style.

<?php if (count($primary_links)) : ?>
    <ul id="primary">
        <?php foreach ($primary_links as $link): ?>
            <?php $class = ""; ?>

            <?php if ( stristr($link, 'active')  ) :  ?>
                <?php $class = 'id="current"'; ?>
            <?php endif; ?>

            <li <?php print $class?>><?php print $link?></li> 

        <?php endforeach; ?>

    </ul>
<?php endif; ?>

Next locate all the existing #primary lines in your CSS file for the theme that you're modifying. For the box_grey theme, these were scattered in various places in the file, I would suggest moving them all to the same place to make changing things easier.

The #primary tag controls the formatting for the primary links, #secondary controls the formatting for the secondary links (this technique could be used for both, you'd use different ID's in the page.tpl.php file.)

If these entries are there then change them, otherwise just add the following entries to your CSS file. The important parts are the float, background, padding and margin entries. You can, of course, adjust or add other parameters to tweak things to look as you like such as the font or the color of the tab text in it's various states.

I'm not going to explain these entries, a little research on CSS files should make clear what's going on.

These style entries should give you tabs for each of the Primary Links you have defined, all lined up on the right side of your screen. To start from the left just change the float command as indicated by the comment below.

#primary a:hover { color: #666633; }
#primary #current a.active:hover { color: black; }

#primary {
    float: left;
    width: 100%;
    background:#DAE0D2 url("images/bg.gif") repeat-x bottom;
    font-size: 93%;
    line-height: normal;
}

#primary ul {
    margin: 0;
    padding: 10px 10px 0;
    list-style: none;
}

#primary a, #primary strong, #primary span {
  display: block;
  background: url("images/norm_right.gif") no-repeat right top;
  padding: 5px 15px 4px 6px;
  font-weight: bold;
  text-decoration: none;
  color: black;
}

#primary li {
/* Change the following entry to make the tabs line up to the left */
  float: right;
  background: url("images/norm_left.gif") no-repeat left top;
  padding: 0 0 0 9px;
  margin: 0;
  list-style-type: none;
}

#primary #current {
    background-image: url("images/norm_left_on.gif") ;
    border-bottom: none;
}

#primary #current a {
   background-image: url("images/norm_right_on.gif") ;
   font-weight: bold;
   padding-bottom: 5px;  
   border-bottom: none;
   color: #666633;
}

And don't forget to copy the tab graphics to the images directory and make sure their permissions are correct so that they can be displayed.

That's all there is to it.

Displaying random images

This is made possible by a script found at www.alistapart.com called rotate.php.

Procedure:

1. Create a directory under the web server's document root. I placed it under my theme's directory.
2. Populate that directory with images.
3. Download script and put it in a nice resting place.
4. Edit script so it can find your image directory. To avoid editing you can just place the script in the same directory as the images.
5. Under your theme's config, for custom logo, instead of a path to an image use the path to the script.
6. You're done.

HOWTO: build your frontpage with regions

A nice feature in Drupal 4.7 is that you can define regions. A region is normally a global thing, appearing on every page, but with some creativity you can use this technology to build complex (front) pages.

Let us assume we want three rows on the frontpage, frontpage top, frontpage centre and frontpage bottom. And let us assume we have a custom theme called bimbam. In the examples, you should replace bimbam with the name of your theme. If you call your theme 'jungle_fever', then just replace all occurrences of 'bimbam' with 'jungle_fever'.

  1. We add the following to our template.php in themes/bimbam/ (create the this file if you don't have one already). This creates three new regions.
    <?php
    /** Define the regions **/
    function bimbam_regions() {
      return array(
           
    'left' => t('left sidebar'),
           
    'right' => t('right sidebar'),
           
    'content' => t('content'),
           
    'header' => t('header'),
           
    'footer' => t('footer'),
           
    'frontpage_top' => t('frontpage top'),
           
    'frontpage_centre' => t('frontpage centre'),
           
    'frontpage_bottom' => t('frontpage bottom'),
       );
    }
    ?>
  2. Now visit admin >> block, and you will see that for the bimbam theme, the select lists for each block contain three new options, frontpage top, frontpage centre and frontpage bottom.
    ?>
  3. These do not yet print, though, because you still need to print the regions in your pages. To do this, open page.tpl.php and add the following:
        <?php if ($is_front) : ?>
        <div id="frontpage_top" class="frontpage">
          <?php print $frontpage_top ?>
        </div>
        <?php endif; ?>

    The surrounding if() is there to prevent the region from printing anywhere except the frontpage.
    However, this also prevents the region from printing on the block config page. So we must fine tune this a little.
  4. We add more logic to make it show on the blocks administration too:
        <?php if ($is_front || strstr($_GET['q'], 'admin/block')) : ?>
        <div id="frontpage_top" class="frontpage">
          <?php print $frontpage_top ?>
        </div>
        <?php endif; ?>
  5. Repeat this for all your three frontpage regions. If you need nothing in between two regions, put them in one if(). That will gain you speed and performance.
  6. Now with some more HTML (tables) or CSS, you can place these regions anywhere on your frontpage, make them look nicer, etc.
  7. If you want the default content not to print on the frontpage, you should surround the <?php print $content ?> with an if (!$is_front).
  8. Visit the blocks repository for nice blocks, or create them with views so that you can put them on your frontpage.

Enjoy your nice frontpage!

HOWTO: Create a custom user login bar

In this how-to, I will show you how to create a user bar, that:

  • Displays a compact, nice user login form that can fit in one line.
  • Once user is logged in, It will display a welcome message and a couple of helpful links.

This is the final result we will be trying to achieve:

IMAGE 1: the login form

IMAGE 2: the welcome message after a successful login

As you can see, this how-to is written for Garland theme, the default theme for Drupal 5. But I'm pretty sure it can be easily used in any other theme.

Decide on the approach..

Drupal is very powerful, it's so flexible that you have many ways to do things. That's why a 5 minutes of thinking is very recommended before we get our hands dirty.

The solution I thought of will work like this:

  • We will write a separate function, will call it garland_user_bar(). It decide, and return HTML for the login form, or the welcome message. It will contain the necessary code to check which one it should return.
  • In page.tpl.php, we will place a call to this function somewhere where it gets displayed in the top.
  • Back to the function, If it should show a login form, then it will just borrow the default login form, we will not try to create our own. Why? Because likely, the default form, while it looks rectangular in shape and fits in many lines, by checking view-source I could tell that we can style it using CSS just like in the screenshots without changing anything in the form itself.
  • If the user is already logged in, the function will instead return a nice welcome message, and some helpful links.
  • Now we have the logic, after that we will write some CSS to style it, to change how it looks.
Oh, let's place the function in page.tpl.php first

Let's do this first, let's place a call to garland_user_bar() (even if it doesn't exist yet), that's better so while we are working on garland_user_bar() we can hit 'refresh' in our browser to see the result any time we want to.

  1. Open themes/garland/page.tpl.php, at around line 16 you will find:
    <div id="navigation"></div>

    This <div> is the container for the top bar in Garland theme, so let's add our call to garland_user_bar() there..
    <div id="navigation"><?php print garland_user_bar() ?></div>
Write the necessary code, garland_user_bar()

Before giving you the whole piece of code, I will explain how we go for writing this..

  1. Open themes/garland/template.php, at the of the file, let's start an empty function.. we will add code inside it in the next steps..
    function garland_user_bar() {
    }
  2. We will need access to the global $user variable, which Drupal makes available for us, and it's going to tell us whether a user is logged in already or not. So first line we add inside the function is going to be:
    global $user;
  3. I will also define a new variable called $output, so I can keep adding whatever I want to that variable, and at the end, I will return its contents to be placed on where we placed the function itself in page.tpl.php. Alright, So that's extra line..
    $output = '';
  4. I want to check if the user is logged in or not. We do this by checking if $user->uid exists or not, because when Drupal logs a user in, it will automatically change $user->uid to reflect the user id, which is always > 0. That's what we need to add..
    if (!$user->uid) { // if user is NOT logged in..

    Notice the ! here, it translates to NOT, so this code means "if not $user->uid" which means if the user is NOT logged in already...
  5. Alright, if the user is NOT logged in.. do what? Give him/her a login form! Remember what we concluded in the deciding step? We said that the default login form is okay, it just needs a bit of CSS love, no need to write another form. So let's just use that form.. this is how..
    $output .= drupal_get_form('user_login_block');

    drupal_get_form() is a very handy function, you give it the name of a form, and it returns it to you, ready for user consumption.
  6. Okay, now we handles the case when user is not logged in, but what if they are already logged in?? let's continue our code to handle this case.. add:
    }
    else {

    This allows us to handle the other case.
  7. If user is logged in already, we want to display a welcome message, and a few helpful links. Let's see how we display the welcome message first..
    $output .= t('<p class="user-info">Hi !user, welcome back.</p>', array('!user' => theme('username', $user)));

    t() function encapsulates the whole message, so it can be translated. theme('username', $user) is what you should always use for displaying usernames.
  8. Well, now the links.. I want two links, "Your accont" and "Sign out". Drupal has a function called theme_item_list() that you can give a list of items, and it will return a HTML <ul> list out of them. "Your account" should link to user/<uid>, and "Sign out" should link to logout. These are standard URLs in Drupal. Alright, let's do this..
    $output .= theme('item_list', array(
      l(t('Your account'), 'user/'.$user->uid, array('title' => t('Edit your account'))),
      l(t('Sign out'), 'logout')));
    }

    Oh well, new function here, l(), this one is used to generate links. You simply pass it the title and the URL, and it will generate a sound and safe <a href="..." title="..">....</a>.
  9. Now one more last thing before we exit and return the output, I want to encapsulate all this in a <div id="user-bar">...</div>. Why? because it will make it easier for us to write the CSS that will apply to this bar. How I do this? Easy, using the . (dot) operator. Like this:
    $output = '<div id="user-bar">' . $output . '</div>';
  10. Oh, now everything is ready, let's return the HTML we generated..
    return $output;

So what's the final overall function? this is it..

<?php
function garland_user_bar() {
  global
$user;                                                               
 
$output = '';

  if (!
$user->uid) {                                                          
   
$output .= drupal_get_form('user_login_block');                           
  }                                                                           
  else {                                                                      
   
$output .= t('<p class="user-info">Hi !user, welcome back.</p>', array('!user' => theme('username', $user)));
 
   
$output .= theme('item_list', array(
     
l(t('Your account'), 'user/'.$user->uid, array('title' => t('Edit your account'))),
     
l(t('Sign out'), 'logout')));
  }
   
 
$output = '<div id="user-bar">'.$output.'</div>';
     
  return
$output;
}
?>
Styling time!

Now that login form and welcome message are displayed correctly for users, we only need to make them look better. That's where CSS comes in.

  1. First, let's create a new file to place all our user bar CSS into, that way we keep organized. So create a new file themes/garland/user_bar.css.
  2. Now the file exists, but nobody knows about it, we should modify page.tpl.php to include it. How? Open themes/garland/page.tpl.php and AFTER line 11 add this:
    <style type="text/css" media="all">@import "<?php print base_path() . path_to_theme() ?>/user_bar.css";</style>

    That will include our user_bar.css file.
  3. Let's write the magical CSS inside user_bar.css
    Notice that it's not too big really. It's just the comments consuming too much space :p.
    /*
    this will expand the default garland bar, make it bigger so our form and message can fit in.
    */
    #navigation {
      height: 3em;
    }
     
    /*
    by default, the default form adds some surrounding space, this cancels it
    */
    #navigation div.form-item,
    #navigation div.content {
      margin: 0; padding: 0;
    }
     
    /*
    this adds some space in top and bottom, so anything inside can look vertically
    centered
    */
    #user-bar {
      padding: .65em 0;
    }

    /*
    by default, fields labels tries to reserve a whole line for itself, this
    cancels that and and sends it to the left.
    it also adds some space on the right and left of the label to look easy on
    the eye.
    */
    #user-bar label {
      float: left;
      margin-left: 10px;
      margin-right: 2px;
    }

    /*
    inputs too, they try to reserve a whole line for itself, this
    cancels that and sends it to the left
    */
    #user-bar input {
      float: left;
    }

    /*
    I don't like the required * (asterisks), so I hide them.
    */
    #user-bar span.form-required {
      display: none;
    }

    /*
    the form submit button, it's so tight so we expand it a bit, and give it some
    free space around.
    */
    #user-bar input.form-submit {
      margin-top: -1px;
      margin-left: 10px;
      padding: 0 .5em;
    }

    /*
    now this is for the links list, lists by default tries to reserve a whole line
    also they add space surrounding them. we cancel all that and send the list
    to the right
    */
    #user-bar div.item-list ul {
      float: right;
      margin: 0; padding: 0;
      margin-right: 10px;
    }

    /*
    remember, stylign above was for the whole list, now for each item,
    we all know each item in the list by default exists on a separate line, also
    has that bullet on the left. we cancel all that. and makes all items sit beside
    each other
    */
    #user-bar div.item-list ul li {
      float: left;
      background: none;
      margin: 0 5px;
      padding: 0 10px;
      border: 1px solid #b8d3e5;
    }

    /*
    this is the "Hi user, welcome back message".
    by default <p> tries to exist on a separate line, we cancel that.
    also by default <p> has some surrounding space, we cancel that too, and give it
    only space on the left.
    */
    #user-bar p.user-info {
      float: left;
      padding: 0;
      margin: 0 0 0 10px;
    }
The end..

That was it, A solution that works and doesn't override or modify any existing code. And since it doesn't have any custom hardcoded HTML for forms, that means it will rarely need to be maintained after new releases of Drupal.
Post any improvements you can think of in the comments, and I will add them here.

HOWTO: Create an admin theme for 4.6

The following describes the steps to creating an admin them using the Civicspace admin theme. The final outcome of this HOWTO should be a portable admin theme that can be added to any other theme.

Once the Civicspace admin theme has been removed form the Civicspace theme it should easer to customize, or to create your own.

For the purpose of this HOWTO mytheme will refer to the theme that is being created.

Step 1:
Download the 4.6 version of the civicspace theme

Step 2:
Copy the following files out of the Civicspace directory to the mytheme directory.

  • /global/print/print.css >> /admin/print.css
  • /global/styles/utility.css >> /admin/utility.css
  • /admin/visual.css >> /admin/visual.css
  • /admin/layout.css >> /admin/layout.css
  • /admin/admin.css >> /admin/admin.css
  • /scripts/pngfix.js >> /admin/scripts/pngfix.js
  • /scripts/utility.js >> /admin/scripts/utility.js
  • /scripts/toggle.js >> /admin/scripts/toggle.js
  • /scripts/csshover.htc >> /admin/scripts/csshover.htc
  • /page_admin.tpl.php >> /page_admin.tpl.php
  • /images >> /admin/images/ (only the images used in the admin theme)
    • bg_admin.png
    • bg_error.png
    • bg_help.png
    • bg_local_tasks_secondary.png
    • bg_mission.png
    • bg_status.png
    • bg_table_head.png
    • bg_table_head_active.png
    • icon_bullet_gray.png
    • icon_collapsed.png
    • icon_comment.png
    • icon_expanded.png
    • icon_help.png
    • menu_collapsed.png
    • menu_expanded.png
    • menu_leaf.png

After all that you should have the following in the admin directory:

  • admin
    • 2 x dir (images and scripts)
    • 4 x .css
  • admin/images
    • 16 x .png
  • admin/scripts
    • 2 x .htc
    • 3 x .js

Step 3:
Edit the following file paths in mytheme/page_admin.tpl.php, and find and replace all the references to civicspace with mytheme

  • line 27 /global/print/ >> /admin/
  • line 30 /global/styles/ >> /admin/
  • line 56 /scripts/ >> /admin/scripts/
  • line 71 /scripts/pngfix.js >> /admin/scripts/pngfix.js
  • line 72 /scripts/utility.js >> /admin/scripts/utility.js
  • line 73 /scripts/toggle.js >> /admin/scripts/toggle.js
  • line 112 (url) >> (?)
  • line 158 /images/bg_mission.png >> /admin/images/bg_mission.png
  • line 170 /images/icon_collapsed.png >> /admin/images/icon_collapsed.png
  • line 173 /images/icon_expanded.png >> /admin/images/icon_expanded.png
  • line 176 /images/icon_help.png >> /admin/images/icon_help.png

Step 4:
Create the file template.php using the functions from civicspace/template.php and changing all the references to civicspace with mytheme

Needed Functions:

  • civicspace_is_admin
  • _phptemplate_variables
  • civicspace_path_to_style
  • phptemplate_stylesheet_import
  • civicspace_get_body_classes
  • civicspace_word_split
  • _civicspace_word_split

Most of the above functions are specif to the civicspace theme. Only 2 function should be required (civicspace_is_admin and _phptemplate_variables). The civicspace_is_admin
function is used in the _phptemplate_variables function.

The _phptemplate_variables function creates the vars array and sets the template file to be used, but only if the civicspace_is_admin function returns true.

Step 5:
Edit the /admin/visual.css CSS file to point to the correct image paths. Eg. ../global/images/ >> images/

  • lines 13, 264, 289, 437, 440, 444, 659, 778, 793, 829, 874, 913

HOWTO: Have "first" and "last" classes on menu blocks

Often, one wants to style the first and last items in a list differently than the rest. CSS 2 provides a simple selector to do that dynamically but sadly it's not supported by Internet Explorer so most designers add a "first" and "last" class to list items where needed so that they can target them with CSS definitions.

Drupal 5 added automatically-generated "first" and "last" classes to primary and secondary links (yay!), but unfortunately did not do so for menus displayed in blocks (boo!). Fortunately, the theme system allows us to do so ourselves.

The menu is actually generated by the menu_tree() function, which is not overridable by a theme. However, that function is itself called from a theme function, so we can override that. To do so, copy and paste the following code into your template.php file:

<?php
function phptemplate_menu_tree($pid = 1) {
  if (
$tree = phptemplate_menu_tree_improved($pid)) {
    return
"\n<ul class=\"menu\">\n". $tree ."\n</ul>\n";
  }
}

function
phptemplate_menu_tree_improved($pid = 1) {
 
$menu = menu_get_menu();
 
$output = '';

  if (isset(
$menu['visible'][$pid]) && $menu['visible'][$pid]['children']) {
   
$num_children = count($menu['visible'][$pid]['children']);
    for (
$i=0; $i < $num_children; ++$i) {
     
$mid = $menu['visible'][$pid]['children'][$i];
     
$type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL;
     
$children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL;
     
$extraclass = $i == 0 ? 'first' : ($i == $num_children-1 ? 'last' : '');
     
$output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($type & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($children) == 0, $extraclass);     
    }
  }

  return
$output;
}

function
phptemplate_menu_item($mid, $children = '', $leaf = TRUE, $extraclass = '') {
  return
'<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) . ($extraclass ? ' ' . $extraclass : '') . '">'. menu_item_link($mid, TRUE, $extraclass) . $children ."</li>\n";
}
?>

The first function differs from the default only in that it directs the system to the alternate version of menu_tree(), phptemplate_menu_tree_improved(). That function works exactly like its core counterpart except that it passes an extra class to our alternate menu item theme function. (Yes, you can add parameters to a theme override function.) phptemplate_menu_item() simply tacks on the extra class if specified, or does nothing different if it isn't. Voila, first and last classes on any menu block.

Note that this code adds the first/last class to the list item, not to the link itself. That is how Drupal generally handles such classes, as it allows a CSS rule to target either the list item or the link, depending on which is needed, while putting the class on the <a> element itself would only allow a themer to style the link, not the list item.

HOWTO: Hide the node title on a page

You can hide node titles by specific node type by adding some code to your template.php file.

Here's the snippet, in this example it is ignoring the title on any node that is of the type "page" or "story". Note that we're also saving the title to another variable for use in breadcrumbs if desired.

<?php
//titles are now ignored by specific node type when they are anomalous in the design 
$vars['breadcrumb_title'] = $vars['title'];
if (
arg(0) == 'node' && is_numeric(arg(1))) {
 
$node = node_load(arg(1));
  if (
in_array($node->type, array('page', 'story'))) {
   
$vars['title'] = '';
  }
}
?>

Here's an example with the code where it belongs in the _phptemplate_variables() function

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'page':
   
//titles are now ignored by specific node type when they are anomalous in the design
   
$vars['breadcrumb_title'] = $vars['title'];
    if (
arg(0) == 'node' && is_numeric(arg(1))) {
     
$node = node_load(arg(1));
      if (
in_array($node->type, array('page', 'story'))) {
       
$vars['title'] = '';
      }
    }
    break;
  } 
 
  return
$vars;
}
?>

HOWTO: Theme a CCK input form

How to theme a CCK input form. (originally posted here http://drupal.org/node/98253)
The reason I have come up with this step by step overview of themeing input forms was that I was working with CCK created content types myself and was trying to figure out how to change the order of the fields of the input form. What I found in the forums was very technical (pointers to the API in 'developer speak') or incomplete, or simply did not cover input forms. There was a lot of help for people looking to modify/theme output, but nothing clear and concise for theming input forms.

So, as a non-developer, semi-technical, marketing/business type person, I set out to discover how to 'theme' my input forms. (all those developers and others that are better informed than I, please feel free to correct me where I am wrong!) I found there are at least two different ways to theme input forms: 1)using the Form API (creating new .module files programmed in php and using things like 'hook_form_alter'), and 2)by creating tpl.php files. I have found that it is far easier to use the second method. You actually end up having to do a little more than create a custom tpl.php file - you have to modify template.php, which requires a little bit of PHP knowledge as well as work with style sheets (either edit style.css or create a new one and link it to your theme - more on this later)

First off, there are a couple people who are responsible for the bulk of the content in the tutorial and deserve proper credit: Lyal (http://www.harostreetmedia.com) thanks for emailing code snippets in the middle of the night, Dublin Drupaller (http://www.dublindrupaller.com) thanks for the rich depth of knowledge you share with others on the forums from which I have gleaned and repurposed much, jghyde, who has helped out others on this very same quest and finally Nick Lewis (http://www.nicklewis.org) whose website is full of helpful and insightful Drupal information.

Now on with the show.

Assumptions:

  1. you understand a little CSS (how it works, how you use class= and id= in HTML tags to style the output)
  2. you understand a little PHP (nothing serious, at least the ability to understand what a PHP code snippet is doing so you can copy it and modify it a little)
  3. you have CCK installed and know how to create content types with it(although I am referring to CCK input forms in this tutorial, I am pretty s