Drupal Tutorials

I am a documentation maintainer for Drupal, the most comprehensive open-source content management available today. I write these guides in my non-existent spare time, so these tutorials are works-in-progress and should be taken as such. All completed tutorials will be marked as such, and will also be published on Drupal.org. Since this site is my own personal sandbox, if you stumble upon a tutorial here while looking for information on Drupal, I'll simply refer you to the best place for knowledge on the subject, the Drupal.org Handbooks

Making contrib modules more admin-friendly

It's a premise of the upcoming Drupal 7 that the UI is king. We see a lot of planning, lots of work, and tons of code going into this upcoming release's User Interface, but that only addresses core's needs. What about the plethora of contrib modules? Can they get help from Drupal 7 in order to become more admin friendly? They could if they were using the new hook_configuration() functionality!

Here's what I envision. If modules had an API that they could use to do things in the background such as creating new content types, creaing or enabling blocks, and even pre-setting some variables, life would be a lot easier for the average contrib module user.

People who are just trying out a module to see how it works would no longer have to manually configure their site for that new module just to make it work. It would work out-of-the-box.

Here's the admin_friendly_modules slides I used in my presentation at Drupalcon Boston.

AttachmentSize
admin_friendly_modules.pdf6.79 MB

Adding tables prefixes to an existing database

The other day, I was about to add a gallery2 installation and a PHPlist to an already-functioning Drupal 5.2 site, and I realized that if I ever wanted to use PHPmyAdmin to do stuff in this database's tables ever again, I needed to add some sort of prefixing to the Drupal tables first. You see, both of those apps use prefixing on their tables, so to keep my Drupal install's tables from being scattered, I had to prefix them as well, here's how I did it...

I made a backup of the Drupal tables, using the MySQLadministrator program. When I was done, I opened the resulting .sql file in my beloved TextMate program, and began changing things:

This is an example line from a MySLadministrator database dump:

CREATE TABLE  `databaseName`.`access` (

needs to become:

CREATE TABLE  `databaseName`.`drupal_access` (

If I'm ever going to get anywhere in an automated fashion, this will have to be written into a module at some point. For now, though, I just ran a find & replace on the text file using the beloved TextMate program.

FIND: CREATE TABLE  `databaseName`.`
REPLACE: CREATE TABLE  `databaseName`.`drupal_

And then also:
FIND: INSERT INTO `databaseName`.`
REPLACE: INSERT INTO `databaseName`.`drupal_

I asked several developers in the IRC channel, and I suspect there might be a way to do this in a Drupal module, so I'll lay out the steps for that if I ever figure them out.

Recyclicon! How To Reuse An Old Site For New Clients

The Recyclicon. Painted by Andrew Stevenson.

After installing my eighth Drupal 5.1 site this afternoon, I came to the sudden realization that I now have older sites with all the configuration and modules exactly as I need them. Why can't I reuse one of those sites' config options and settings for a brand new install? It would save hours of downloading all those modules again, installing them, and configuring all the minuscule settings till they're just right.

Well, it turns out that I can, in fact, do just that. Let me show you how!

Moving The Site

Step One: Prepare The Old Site

  1. Turn off the cache. Get your old site ready for the migration by disabling the site-wide cache at Administer > Site Configuration > Performance
  2. Use the Empty Cache button in the Devel module to empty all your cache tables.
  3. Run cron.php to clean up the logs and other laundry items.

Step Two: Make a Copy of The Old Site

Using MySQLadministrator, make a backup copy of the originating site's tables. I'll show you what settings to use in my video tutorial on drupaldojo.com/lesson/22 if you're interested. PHPmyAdmin or the command line would accomplish the same thing if you'd rather go that route. You want to end up with a text file who's extension is .sql and saved as UTF-8.

Once you've got the old site's database saved onto your computer, open it up with your favorite text editor and run Find & Replace on the name of the old database, changing it to the name of the new database. Here's what my old_drupal_database.sql file looked like:

-- MySQL Administrator dump 1.4
-- ------------------------------------------------------
-- Server version 5.0.30-Debian_3

CREATE DATABASE IF NOT EXISTS myOldSiteDatabaseName;
USE myOldSiteDatabaseName;

CREATE TABLE `myOldSiteDatabaseName`.`drupal_access` (
`aid` int(11) NOT NULL auto_increment,
`mask` varchar(255) NOT NULL default '',
`type` varchar(255) NOT NULL default '',
`status` tinyint(4) NOT NULL default '',
PRIMARY KEY (`aid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Which I then changed into:

CREATE DATABASE IF NOT EXISTS myNEWsiteDatabaseName;
USE myNEWsiteDatabaseName;

CREATE TABLE `myNEWsiteDatabaseName`.`drupal_access` (
`aid` int(11) NOT NULL auto_increment,
`mask` varchar(255) NOT NULL default '',
`type` varchar(255) NOT NULL default '',
`status` tinyint(4) NOT NULL default '',
PRIMARY KEY (`aid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Performing a simple Find & Replace for myOldSiteDatabaseName and changing all instances of that to myNEWsiteDatabaseName will be sufficient to create a new database for your new site. There's no need to do it line-by-line, just click the Replace All button. You'll notice that I use "_drupal" table prefixes in this database, as I do for all my sites. Don't even worry about those prefixes, there's no way for the find/replace to mess anything up. My server automatically creates an identical MySQL user for each new website account that I create. It also creates a database whose name is identical to that account's username. Your mileage may vary.

Save the edited file into the new website's directory somewhere, in preparation for the next steps.

Step Three: Upload an Identical Version of Drupal Codebase

You cannot change Drupal versions while executing this re-use procedure, so don't even attempt it. This tutorial is already a shortcut. DO NOT layer shortcuts in web development. That being said, upload it to the same relative directory of your new site as the old site resided in. You can't change from having Drupal installed in /drupal/ on your old site, to / on your new site. Well, you could, but you'd have to know a lot more about the tables in these databases than I do, and if that's the case, you can stop reading this tutorial right... about... here.

Step Four: Install Your Copied Database

Using MySQLadministrator again, upload the edited copy of your .sql file to the MySQL server. True, there are many ways to go about this, but since I'm working on a remote VPS server for purposes of this tutorial, and I prefer GUI apps, you'll just have to put up with me. You may extrapolate a faster or better method than what I show you here, but that just means you understood what I was trying to say. :=)

A word of encouragement here: You won't overwrite any existing database tables belonging to another app with this method. Remember, we turned off the 'DROP table if EXISTS' feature in MySQLadministrator. We also have table prefixes for everything drupal_ related, so it's not a problem to upload this file into a database that already has some PHPlist, Gallery2, or what-have-you in it. Drupal tables play nicely with the other kids.

Configure Your New Site

Step Five: Change The settings.php File

In your new site, Change the username/password to match your new site's database login settings. Also change the $base_url and any variable override you might have set. Add a variable override to change the theme to Garland. You'll want this, especially if you were using a custom theme before. Don't worry, you can change back later once you know you can login to your new site.

$conf = array(
  'theme_default' => 'garland',
  );

Step Six: Reset The Variables

Now it starts to get tricky. You'll need PHPmyAdmin or a command line, because MySQLadministrator is not yet full-fledged enough to make edits to fields in a table. Maybe someday an aspiring coder will write these commands into a Drupal module and save us all the trouble, but here goes!

Open the database, and move to the drupal_variable table. I've pasted the PHPmyAdmin SELECT commands for you, so you shouldn't have any trouble finding these rows. You're looking to change the following variables:

  • site_name
    SELECT *
    FROM `drupal_variable`
    WHERE `name` LIKE CONVERT( _utf8 'site_name'
    USING utf8 )
  • site_slogan You could change this in the admin interface of the site, but since you're already here...
    SELECT *
    FROM `drupal_variable`
    WHERE `name` LIKE CONVERT( _utf8 'site_slogan'
    USING utf8 )
  • site_mission Same thing with this variable. It could also be changed in the admin section of the site.
    SELECT *
    FROM `drupal_variable`
    WHERE `name` LIKE CONVERT( _utf8 'site_mission'
    USING utf8 )
  • site_mail I list this one because you're likely to forget it.
    SELECT *
    FROM `drupal_variable`
    WHERE `name` LIKE CONVERT( _utf8 'site_mail'
    USING utf8 )
  • theme_default Remember that theme names are always lowercase. I recommend using garland, at least for now.
    SELECT *
    FROM `drupal_variable`
    WHERE `name` LIKE CONVERT( _utf8 'theme_default'
    USING utf8 )

Once you've selected, changed, and saved these sitewide variables, it's time to nuke the old site's content. Be careful though. Hands off that big red button!

Step Six: Nuke The Old Content

All of the content for a site resides in two places. The drupal_node table and the drupal_node_revisions table. (Remember that I'm using table prefixes. Your site might just be node and node_revisions). I use the Truncate command for this procedure because it empties a table of all rows, but leaves the structure of the Drupal table intact so I can refill it with new content later.

  1. Delete all content in the drupal_node table:
    TRUNCATE TABLE `drupal_node`;
  2. Delete all content in the drupal_node_revisions table:
    TRUNCATE TABLE `drupal_node_revisions`;

UPDATE on 07.26.2007: An array that lists all the tables that need to be emptied would be a good thing. Something like:

/* Make an array out of all the table names that need emptying */
$tables-to-empty = array(
  'node',
  'node_revisions',
  'files',
  'file_revisions',
  'comments',
  'node_comment_statistics',
  'flood',
  'search_dataset',
  'sessions',
  'url_aliases'
  'DELETE FROM {users} WHERE uid > 2' => 'users',
);

07.25.2007 -- Steps that still need more explanation

I'll cover the changes to /files dir and the /temp dir when I can. There's a way to set up your site so you don't have to worry about re-creating these two directories each time you do this Recyclicon procedure.

Change your contact form information. If you forget this step, everyone will laugh at you behind your back. Or possibly even in front of your back.

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() /views.css";</style>
   style type="text/css" media="screen">@import "php print base_path() . path_to_theme() /views.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 hacks such as font percentages.

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