Slides from Drupalcon Boston 2008
Video of the presentation I gave at Drupalcon Boston 2008, titled Triggers and Actions and Hooks, Oh, My! should be appearing shortly. (In the meantime, here's me babbling about actions.) The slides are downloadable below.
Drupalcon 2008 actions session
The session I proposed, Triggers and Actions and Hooks, Oh My! has been accepted at Drupalcon Boston 2008. I will be presenting the new capabilities of Drupal 6 in this area, from the big ideas to the nitty gritty. The session will be Tuesday afternoon from 5-6 pm, just before the Acquia Conference Social.
We had 26 people at the first Drupal conference in Antwerp (three years ago today, by the way!). There are currently 800 registered attendees for the upcoming Drupalcon, and the conference has reached its maximum capacity.
I'm looking forward to the presentations, the birds-of-a-feather meetings, hanging out with a bunch of geeks, some hacking time, and the business fair.
How to Speed up Drupal Forum Pages on a Busy Site
Drupal 5's forum module is OK. It lets you create multiple forums with taxonomy terms. Forum posts are true nodes so you get all the benefits of nodeness. And Drupal keeps track of which posts you've read. All that is great. But I've had some trouble scaling up a site that relies heavily on the forum module, even when using advcache. Investigating how the database was spending its time, I found lots of queries like this:
SELECT n.nid, n.title, n.sticky, l.comment_count, l.last_comment_timestamp FROM node n INNER JOIN node_comment_statistics l ON n.nid = l.nid INNER JOIN term_node r ON n.nid = r.nid AND r.tid = 123 WHERE n.status = 1 AND n.type = 'forum' ORDER BY n.sticky DESC, l.last_comment_timestamp desc
An EXPLAIN shows how nasty this is:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | n | ref | PRIMARY,node_type,status,node_status_type,nid | node_status_type | 102 | const,const | 30679 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | l | eq_ref | PRIMARY | PRIMARY | 4 | drupal.n.nid | 1 | |
| 1 | SIMPLE | r | eq_ref | PRIMARY,nid,tid | PRIMARY | 8 | const,drupal.l.nid | 1 | Using where; Using index |
Searching Drupal's code turns up this query in theme_forum_topic_navigation(). It turns out the database is smoking, grinding away, eating up cycles generating previous and next links for forum topics. I thought about it for a minute, and realized that I don't think I've ever used those links. Which would you rather have? Blazing speed or a link to the next forum topic? Fortunately, the slow query is in a themable function, which means we can get our performance back with a few lines added to our theme's template.php:
// No previous/next links for forum topics.
function phptemplate_forum_topic_navigation($node) {
return '';
}In my case, server load dropped from 46 to 0.5. Sometimes it's the little things.
Multistep Forms with Lookup Functions that Change Values
A number of people lately have been asking about how to change the values of form fields in multistep forms. So I wrote up an example.

Here we have a simple multistep form that lets you enter the name of a U.S. state, such as Iowa. Note the form has a default value.

Then, upon clicking the Lookup button, the value "Iowa" is replaced with a standardized state abbreviation, IA.

Upon submission both values are available to the submit function.
![]()
This example is not intended to do anything useful. It's just a little example that you can poke around with to learn how the sometimes confusing multistep form submission works in Drupal 5. You can download the module here.
FCKEditor toolbar will not appear - solution
When using FCKEditor as a WYSIWYG editor in Drupal, if you've double-checked all your settings and you can't get the formatting toolbar to appear, check to make sure that your template (e.g. page.tpl.php) is printing out the $closure variable. Thanks, latentdabbler.
Using Word 2007 Blog functionality to post to Drupal via the MetaWeblog API
First, I enabled the Blog API module on my Drupal site.
Then under Administer - Site Configuration - Blog APIs, I enabled the content type to which I want Word 2007 to publish to (just page in my case).
In Word 2007, I clicked on the round Office Button at the upper left of the screen and selected New.
I selected New blog post from the Microsoft Office Online template, then clicked the Create button.
I chose Other when asked about the provider:
.gif)
I chose the MetaWebLog API, which Drupal supports. The Blog Post URL being requested is the URL of your site plus xmlrpc.php. The username and password are the Drupal username and password.

I am not exploring image support at this time, so I clicked on Picture Options and chose None - Don't upload pictures.

I then typed in my blog post and clicked the friendly Publish button in the Word 2007 Blog Toolbar, and...happy happy, my post appeared on my blog.
Apache [client ::1] requests, broken pipes, virtual hosts and Drupal
I was puzzling over entries like this in Apache 2.2.3's error log:
[info] [client ::1] (32)Broken pipe: core_output_filter: writing data to the network
A check for which user agent was causing that revealed:
::1 - - [01/Dec/2007:04:08:53 +0000] "GET / HTTP/1.0" 200 85781 "-" "Apache/2.2.3 (CentOS) (internal dummy connection)"
So it's Apache's internal dummy connection throwing the errors. Oh, but what's this?
::1 - - [01/Dec/2007:04:08:53 +0000] "GET / HTTP/1.0" 200 85781 "-" "Apache/2.2.3 (CentOS) (internal dummy connection)"
Yikes! Here's what is happening.
1. Apache opens an internal dummy connection with a request for /.
2. On this server, / turns out to be a Drupal page.
3. Drupal spends time and effort putting together the page and sends it...
4. ...to the dummy connection that ignores it resulting in the broken pipe.
It turned out that in httpd.conf, an include statement which pulled in a virtual host configuration was being encountered before the regular virtual host configuration...making the included virtual host configuration the default host for all requests, including internal Apache requests.
I solved this by making sure the default page of the first virtual host is not a dynamic page.
Blank white pages when submitting forms in Drupal
This has bitten me enough that I'm going to write a blog post about it so when I search for this issue, I will find my own blog post and say, "Duh!"
Recently we had one site in development that presented a white screen whenever a form was submitted. Other sites right next to it worked fine. The form was being processed. But no errors were appearing either on the screen or in the error log for Apache or PHP -- all we saw was a blank page.
Then I noticed that the output_buffering setting in php.ini was set to Off. I turned it on and it solved the problem. But I wanted to know why it solved the problem. Here's why, and why it doesn't really solve the problem.
When Drupal's form handler completes processing of a form submission, it normally does a 302 redirect. The code responsible for that is in drupal_goto() and looks like this:
header('Location: '. $url, TRUE, $http_response_code);
// The "Location" header sends a redirect status code to the HTTP daemon. In
// some cases this can be wrong, so we make sure none of the code below the
// drupal_goto() call gets executed upon redirection.
exit();
Running the code through a debugger showed that indeed, we were reaching the exit() call.
If any of your code has sent anything back to the browser, PHP has already sent the headers to the browser. If output_buffering is Off, Drupal cannot take that back and say no, really we meant to send you a 302. But if output buffering is on, the headers are not really sent, so calling header() still works. That's why turning on output buffering solves the problem.
But that's just treating the symptom. The real problem is that somewhere in your code, something is being sent back to the browser when it shouldn't be. This is the problem being referred to when the Drupal coding standards say, Note that the final ?> should be omitted from all code files--modules, includes, etc. The closing delimiter is optional, and removing it helps prevent unwanted white space at the end of files which can cause problems elsewhere in the system.
In my case, a designer had put a drupal_add_js(..., 'inline') call directly into template.php. Moving the JavaScript to a .js file solved the real problem.
Installing Drupal on Mac OS X 10.5 Leopard
I thought I'd write up the steps I took to get Drupal running on a stock Leopard installation. You may wish to save some time and install MAMP instead. Especially if you need GD support (i.e., you're going to have Drupal do image resizing). Update 17-Dec-2007: In fact, I recommend using MAMP instead.
Step 1: Enable PHP
Uncomment line 114 in /etc/apache2/httpd.conf to enable Leopard's built-in PHP:
LoadModule php5_module libexec/apache2/libphp5.so
Start Apache 2 by using the Sharing panel in Preferences or at the command line with the following:
sudo apachectl start
(If Apache was already running, use restart instead of start.)
Place a test document into the default htdocs root to see if php is running. I created /Library/WebServer/Documents/phpinfo.php with the following content:
<?php phpinfo(); ?>
Now going to http://localhost/phpinfo.php shows me the info page for PHP 5.2.4. Yay!
Step 2: Friendly Virtual Hosts in Apache
I don't like keeping my websites in /Library/WebServer/Documents. It's a cumbersome place; I'd much rather keep them in /Users/john/Sites. That's right in my home directory and when I copy or sync my home directory I get the sites I'm working on, too. But using Leopard's built-in URL support for my home directory is verbose, too:
http://localhost/~john/sitename
I'd much rather use a nice short URL like http://dev/sitename. So first I assigned the name dev to my computer by adding a line to /etc/hosts:
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 dev
127.0.0.1 localhost
255.255.255.255 broadcasthost
...Since Leopard caches DNS queries, we force it to reread /etc/hosts by using dscacheutil which replaces the lookupd utility that was in OS X 10.4.
dscacheutil -flushcache
I changed /etc/apache2/users/john.conf from
<Directory "/Users/john/Sites/">
Options Indexes MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>to
<Directory "/Users/john/Sites/">
Options Indexes MultiViews FollowSymLinks
# Allow .htaccess files to override httpd.conf.
AllowOverride All
# No access allowed.
Order deny,allow
Deny from all
# Except from this machine.
Allow from 127.0.0.1
</Directory>
# Enable virtual hosts.
NameVirtualHost *:80
# Point virtual host to our directory.
<Virtualhost *:80>
DocumentRoot /Users/john/Sites
Servername dev
</Virtualhost>You can test that everything works and you didn't make any typos by using
sudo apachectl configtest
which should tell you that the syntax of your Apache configuration files is OK (it will point you to the line containing the error otherwise). If all is OK, restart Apache to effect the changes:
sudo apachectl restart
Now you should be able to go to http://dev/ in your browser, and the file at /Users/john/Sites/index.html should be displayed.
Installing MySQL
There was no friendly installer for OS X 10.5 so I used the OS X package for 10.4. It ran fine.
It was unclear from my web searches whether the startup item for MySQL is working on Leopard. So I created the following file at /Library/LaunchDaemons/com.mysql.mysqld.plist to autostart MySQL (thanks Joannou Ng):
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>KeepAlive</key> <true/> <key>Label</key> <string>com.mysql.mysqld</string> <key>Program</key> <string>/usr/local/mysql/bin/mysqld_safe</string> <key>RunAtLoad</key> <true/> </dict> </plist>
Before I restarted, I wanted to make sure that mysql would be in my PATH environment variable when I restart. So I created a file at /etc/paths.d/mysql containing
/usr/local/mysql/binFor more information on this, type man path_helper.
Then I restarted to make sure that MySQL would launch. Yes, it's running:
ps -ax | grep my
40 ?? 0:00.01 /bin/sh /usr/local/mysql/bin/mysqld_safe
88 ?? 0:00.11 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --user=mysql --pid-file=/usr/local/mysql/data/localhost.pidNow to set it up securely.
mysql_secure_installation
This allows you to set a root password, disallow remote root logins, and generally tighten up MySQL security.
It's nice to be able to tweak MySQL parameters, so I created a my.cnf file:
sudo cp /usr/local/mysql/support-files/my-large.cnf /etc/my.cnf
The only thing I tweaked in the my.cnf file was to add one line under the [mysqld] heading to prevent MySQL from listening on port 3306 (I don't like unnecessary open ports):
skip-networking
Now I can create a database for Drupal to use:
mysql -uroot -p
mysql> CREATE DATABASE drupaldb;
Query OK, 1 row affected (0.00 sec)One more thing. PHP and MySQL are confused about which socket to use (/tmp/mysql.sock vs. /var/mysql/mysql.sock). So let's provide a symlink so they can use either one:
sudo mkdir /var/mysql
sudo ln -s /tmp/mysql.sock /var/mysql/mysql.sockInstalling Drupal
Pull down a copy of Drupal 5 from the CVS repository:
cd ~/Sites
cvs -z9 -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal checkout -r DRUPAL-5 drupalChange permissions on the settings file so the installer can modify it:
chmod o+w drupal/sites/default/settings.php
Run the Drupal installer by going to http://dev/drupal.
Remove the permissions from the settings file:
chmod o-w drupal/sites/default/settings.php
And create Drupal's files directory:
mkdir /Users/john/Sites/drupal/files
sudo chown www /Users/john/Sites/drupal/filesRejoicing
Now I've got an easy setup where I can create my Drupal sites under /Users/john/Sites and refer to them with short names like http://dev/foo. MySQL is running nicely. Public access to my dev sites is disabled by Apache. Life is good.
Drupal wallpaper
Surprisingly, a search for Drupal wallpaper turns up nothing.
Update: Vadim Barsukov pointed me to one by Steven. But we need more inspiring wallpaper. Or perhaps live wallpaper that gives graphical hourly updates of patch queues and such. And a pony.



