I’ve now successfully made the move from WordPress to Drupal. The move was challenging at times, not the least of which I was so comfortable with WordPress and was moving into a completely new environment. However, now that I’m finished and I’ve had a chance to look around, I’m happy I’ve taken the time—both in learning the new tool and in re-organizing my sites.
Drupal doesn’t provide the out-of-the-box ease of WordPress when it comes to weblogging. I wouldn’t recommend Drupal to people who just want to set up a weblog and publish and are, more or less, happy with “standard” weblogging functionality. However, Drupal does provide the fine grained modularity and control that I both want and need. In addition, there’s a different mindset in play with Drupal that appeals to me; including a stronger commitment to standard XHTML and other W3C specifications, as well as support for more formal taxonomies. After years of going back and forth about Semantic Web as compared to semantic web as compared to “Web 3.0”, I’ve found I’m really not a microformats kind of gal. I really do like structure in my web sites.
To me, the extra time necessary to learn how Drupal works was more than justified by what I’ve been able to accomplish. Frankly, I would probably have spent as much time bitching about WordPress as I took to become familiar with Drupal, and had a lot less fun, too.
Being critical of the tools we use is important—that’s how they improve. However, if most of our communications about a tool are negatively critical, than it’s time to move to a different tool. Product “loyalty” isn’t, when you’re forcing the developers of the tool to continuously stop their efforts in order to answer your criticisms. We only have to look at the recent fooflah over Twitter to see the wisdom of knowing when to stay, and when to leave.
Though my experiences may not be similar to others, I thought I would detail the steps and decisions I made when moving from WordPress to Drupal. I’m not an expert at Drupal, so I can’t guarantee the steps I took are the right ones, but they seem to work for me.
Migrating the data
I wrote previously how I made the decision to make static snapshots of my WordPress post pages and install these rather than actually porting the database from WordPress to Drupal. Since I close down comments on posts anyway, there really wasn’t a compelling reason to keep the old pages in “live” versions.
By creating static snapshots, I don’t have to worry about implementing the same URL structure in Drupal that I had in WP. I also didn’t have to worry about any data migration issues, nor did I have to carrying around 20,000+ records when I am, essentially, starting over.
The static pages are installed here in RealTech, and at the now closed down main Burningbird weblog. The old pages feature the old web page design, and are located in the same permalink structure. In addition, there’s a Google search form in RealTech, which allows searching on keywords across old and new pages. This is in addition to my Drupal maintained search capability.
To recap, I made the static pages using the utility wget.
wget --mirror -w 3 -p --html-extension http://realtech.burningbird.net
To ensure that the .html extension pages are served as XHTML (since I used SVG in my site design), I added the following to the .htaccess file:
AddType text/html .xhtml
RewriteBase /
RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml
RewriteCond %{HTTP_ACCEPT} !application/xhtml\+xml\s*;\s*q=0
RewriteCond %{REQUEST_URI} \.(html|xhtml)$
RewriteCond %{THE_REQUEST} HTTP/1\.1
RewriteRule .* - [T=application/xhtml+xml]
There are WordPress to Drupal migration modules, though I haven’t found a clear story on the issue. Most sites that ask about migrating from WordPress to Drupal end up being inundated by WordPress fanboys, asking why would a person want to move from WordPress to Drupal. I imagine the same occurs when a person asks about moving from Drupal to WordPress.
The web site Adding Understanding seems to have the best write up on moving from WP to Drupal.
Installing Drupal
Drupal installation is covered quite nicely in the Drupal documentation, so I won’t be covering the steps in detail. My installation consisted of the following steps:
-
- Download the software to a non-web location.
- Copy the software to each subdirectory where I will be installing Drupal. I decided not to go the “shared” code route, because it doesn’t really gain me that much.
- Created a separate database for each Drupal site. Multiple Drupal installations can run on the same database, and can even share common tables, but I prefer to keep my installations separate. Prevents accidental muck-ups, not to mention frequent occurrences of Oopsie! Shit!.
- I then changed the permission of sites/default using
chmod o+w sites/default
from the command line, though you can set the world write using FTP or whatever file management tool is in use for your site.
- You don’t have to hack the configuration file, Drupal does the edits for you (which is why you need to set the previously mentioned write permissions.)
- Once installed, I then reset the write permissions on my default directory, using
chmod o-w
.
- I’m also supporting clean URLs, so I added the following into each Drupal installation’s .htaccess file:
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
At this point, Drupal is installed, and I’m ready to do the configuration.
Configuring Drupal
Configuring Drupal is straight forward. There’s an option in Site Configuration to turn on clean URLs, as well as set the date and time, if different from the server. There’s also a Site Information page that reflects the information entered when the site was first installed, and includes fields to change the site title, mission statement, logo, and so on.
Another configuration page is Input Formats, where you can set whether you want to use filtered HTML or full HTML by default, as well as establish exactly what is handled by Drupal. I modified the full HTML option to not do element correction or automatically create links from URLs. I run my site as XHTML (which I’ll get into more, later), and I prefer to do my own markup.
There are other configuration options but many of these reflect installed and activated modules.
Installed Modules
Drupal is strongly modularized. What’s standard in other content management tools is encapsulated as modules in Drupal, including search, comments, database logging, and taxonomy support. The advantage to this approach is that you have finer control of what’s implemented or not, without having to programatically alter the application’s default behavior.
Not all have modules have been ported to Drupal 6 yet, but I found all the functionality I wanted. Some of the modules I activated are pre-installed with Drupal; others I found at the Drupal site and downloaded manually. They’re all installed in the /modules subdirectory in my installations, and each can be activated from the Modules page, as well as configured from the module configuration page.
The following are the pre-installed Drupal modules I activated.
- Book
- If there is one aspect of Drupal I like the most, it’s the application’s support for a book—pages grouped under a title and with their own internal navigation. Though longer writings are not fashionable at the moment, multi-page writings are the only way to adequately cover a topic. With the book content type, creating these longer writings is a snap. I’m using the book content type with this writing.
The only quibble I have with the book implementation is that the top-level page for the book must be published before it’s accessible to be parent for the other pages. However, just because the front page is published doesn’t mean it automatically shows on the front page. You have to specifically promote the book to the front page. In addition, you don’t have to publish all the pages to the book at once. You can publish the first page and then add new pages as you write them. I’m primarily using the book content type at my personal site, Just Shelley.
- Contact
- The Contact module enables a contact form per user, as well as a site-wide contact form. The site wide contact form requires specifying a category, as well as a receiving email address before the page is available.
- Forum
- Rather than comments across all my sites, I’ve implemented a forum at RealTech, with forum categories for each site. People can comment on existing forum topics, anonymously or signed in using a registered account or OpenID. I’ve also created a custom “user”, with the ability to create new forum topics. Needless to say, I have to know the person before they get “trusted” access. I’ll have more on this later.
- Menu
- The Menu module allows customization of the menus. This is one area I’m still feeling my way around, with the help of some non-core modules. By default, Drupal provides for multiple menus, which can be located in the sidebar (Navigation), or along the top of the page (Primary and Secondary links). For my multi-column sites, such as RealTech, I implement Navigation and custom menus, but all sites, including Painting the Web and Just Shelley, have Primary links along the top. Where the menus are displayed is based on which block they’re implemented in, which I’ll cover in a later section.
- Path
- The Path module allows users to rename URLs, and is required for the Pathauto module.
- PHP Filter
- The PHP Filter is a handy module. With it activated, I can embed PHP within a post and the code is actually processed, rather than being escaped to uselessness. I can implement PHP code snippets for tutorials or whatever, knowing that the little buggers will work and without me having to sacrifice a chicken to the Web Safe god.
- Profile
- The Profile module allows me to configure the user profile; to add additional fields, such as first and last name.
- Search
- The Search module generates both regular and advanced search pages, both accessible via the default /search URL. Drupal’s search is actually quite sophisticated. You can search on category, as well as story type.
- Taxonomy
- The Taxonomy module is what provides all of the categorization and grouping you see in effect for this writing and others at all of my sites. Before I even started installing Drupal, I mapped out the organization for each of my sites and then implemented them using the Taxonomy module. The only site that doesn’t have a taxonomy is my personal site.
- Tracker
- Tracker provides a list of recent posts of any type, including book pages, stories, blog entries, and forum topics. It doesn’t list recent comments.
- Token
- Token is an underlying core module that manages the strings or aliases used to represent specific data accessible by the Drupal APIs. When constructing URL aliases, you use tokens such as book-raw and vocab-raw in order to specify book name or vocabulary name, respectively. I did have to install the development version of Token because it has a patch which allows me to use the book title as part of the URL for all of the pages. This is a bit of a pain, because the Update module, which I also activated, keeps telling me to upgrade the development version of Token.
There are some other core modules, but the rest are more for reporting and logging. In addition to the core modules—each of which was installed with Drupal, though not activated—I also installed several non-core modules.
- Menu Breadcrumb
- You can see the Menu Breadcrumb module in action along the top of this page. This module provides a breadcrumb showing the menu entry for each individual writing.
- NiceMenus
- Nice Menus provides the drop down menus in use at Painting the Web. The menus are driven by CSS except for some JavaScript support when the pages are accessed by IE6.
- Pathaudo
- Pathauto works with Path to automatically generate URL aliases, such as the ones for these book pages. There are any number of available patterns you can use to generate your paths, and you can also generate alias patterns for taxonomy entries, as well as users. This automatic path generation can be overridden on a post by post basis.
- Search404
- Search404 provides a nice option: if someone tries to access a page that doesn’t exist, the 404 page automatically does a keyword search with the URL terms, as well as providing a search form.
- Vocabulary Index
- The Vocabulary Index module is what creates the nice taxonomy pages you see when you access the main categories from the Categories menu in the left sidebar of RealTech, and in the main topic menu items in the header of Painting the Web.
- Print Friendly
- At the bottom of every page, you’ll see a link to a “printer friendly” version of the page. In addition, for book pages, you’ll also see a link for generating a PDF version of the page. I also include a link to the printer friendly version in the Atom syndication feed, just in case someone has trouble accessing my sites with all the graphics I use. Since I’m using a non-standard layout for Just Shelley, with a fixed left side graphic, I wanted to provide an alternative view of the writing, though the site should be accessible with most browsers, and mobile devices.
Another aspect of the Print Friendly option is that for a book, if you access the link from the top page, all of the pages are concatenated into one document. I’ve been able to convert this printer friendly page into a Kindle ebook for offline reading.
- Atom
- Out of the box, Drupal supports RSS 2.0. The Atom module provides access to an Atom feed, which is the only one I support. The module generates a valid Atom feed, which can be configured to support summary, full content, or both. It also has the option to embed ads. What I’ve done is use the ad override to add the printer friendly option.
XML Sitemap
- The XML Sitemap module generates a dynamic sitemap that can be used with the Google Webmaster tools. I don’t use XML Sitemap with all my sites, but I do with RealTech.
- The RDF and Sparql modules
- The RDF and Sparql modules provide support for RDF data and Sparql querying, respectively. I only have this implemented at RealTech, as the modules are currently in alpha. I’ll have more on the metadata support capabilities of Drupal in a later writing.
- htmLawed
- I just installed this on RealTech, but this module should scrub all input; to ensure that the X/HTML is clean, as well as to do the necessary entity encoding. We’ll see how it does.
- Image.module
- I just installed this module in Just Shelley. This module incorporates ImageMagick into processing uploaded photos and other images. It also adds a new image type, which allows posting of photos with post, in addition to maintaining a gallery.
Many of these modules result in additions to the user interface of the Drupal installation. Where these end up being positioned in the page depends on the site theme.
Structure of a Drupal Theme
Before jumping into Drupal theming, it’s important to understand how the Drupal theming infrastructure works. The Drupal web site has good documentation on Drupal themes but I wanted to cover those aspects that differ from my experiences with WordPress—if for no other reason to document what I’ve learned for when I add new sites.
Like WordPress, dynamic content is embedded as PHP within page templates, and similar to WP there are the main sections that handle the header, footer, sidebars, and main content. However, the similarity between the two begins to end at this point.
A relatively typical Drupal page consists of left and right sidebars, center content, header and footer. I borrowed the following image from the Drupal theme guide, which shows these sections mapped out.
Unlike WordPress, with separate header, footer, and sidebar pages, the page.tpl.php file shown in the diagram pulls together all of the content that is displayed in the page. However, there is very limited processing within this page: typically just a condition to check to see if section data exists and a PHP print statement to print out the section data. The following is the template code that manages the post displays from RealTech’s page.tpl.php file, which is a sub-scheme of the default Drupal Garland theme:
<div class="clear-block">
<?php print $content ?>
</div>
Unlike WordPress, there is no loop to manage the output. Instead, the only code is the statement to print out the global $content variable, which is processed within the PHP templating engine. The same applies for sidebars, headers, and footers, as the following code snippet demonstrates for the left sidebar:
<?php if ($left): ?>
<div id="sidebar-left" class="sidebar">
<?php if ($search_box): ?><div class="block block-theme"><?php print $search_box ?></div><?php endif; ?>
<?php print $left ?>
</div>
<?php endif; ?>
Whether there is any left content or not is dependent on another important Drupal theming concept: blocks.
Drupal blocks
To add new content to the theme template, you can hack the page. A better approach, though, is to use the Drupal Block configuration page, as shown in the following screenshot:
Each activated content block is listed under the section of the page, known in Drupal as a region. I don’t have a right sidebar, so there are no blocks listed under the right sidebar. However, I do have three menus, one standard (Navigation), and two custom listed as blocks under the left sidebar, each given a weight equivalent to where I want the block to display in the bar (starting from negative values to the top, positive values to the bottom).
In addition, I also have a couple of blocks in the footer: the standard “powered by Drupal” button, as well as a custom block named, imaginatively enough, “my additions to the footer”. This block contains the link to the previous weblog entries, as well as the Google search form.
To create a custom block, select “Add block” from the top menu, and in the page that opens, define the block contents, including the HTML to format the data.
Once the custom block is created, you can then assign it to a specific region, such as the footer for my custom block. You never have to crack the template code directly again. Well, until Drupal upgrades to version 7, I imagine.
Of course, not every custom functionality is implemented so easily. In an ideal world there would be three tool users, each exactly identical in nature. Since we thankfully don’t live in an ideal world, we need a way to override some of the default template behavior. Before I get into that, though, I want to return to the concept of creating a custom Drupal theme.
Theming and subtheming
There are two ways you can custom theme your Drupal site. The first is to create an entirely new theme, either from scratch or by using another theme as template. The second is to create a subtheme—a theme that exists as a subdirectory of another theme, making use of much of the previous theme’s settings, while still having a custom look and feel. I used both approaches for my sites. I created a custom theme for Just Shelley and Painting the Web, while this site is a subtheme I’ve named Flame, based on the default Drupal Garland theme.
All themes require an .info file, which identifies the theme and theme components. The following is the .info file for Painting the Web.
name = Painting the Web
description = Single column, fixed width
core = 6.x
engine = phptemplate
Included is the name, the description, the Drupal core, as well as template engine, in this case the default PHPTemplate engine.
The .info file and other theme components are installed in an appropriately named subdirectory under the main /themes subdirectory. Also included in my Painting the Web theme subdirectory is a style sheet, named the default, style.css, in addition to stylesheets specifically for Internet Explorer, a node.tpl.php file containing the design template used for displaying the site’s posts (known as “nodes” in Drupal), and the page.tpl.php file providing the template design for the page. Rather than design from scratch, I adapted the Simplex2 theme to my own, since it’s one of the cleaner designs.
The Flame design used in RealTech is different because I created it as a subtheme of the default Drupal theme, Garland. A Drupal subtheme doesn’t require anything more than an .info file, such as mine for Flame, which is named flame.info.
name = Flame
description = Tableless, recolorable, multi-column, fixed width theme.
version = VERSION
core = 6.x
base theme = garland
stylesheets[all][] = flame.css
Included in the file is identification of the basic theme, Garland, as well as an identification of the subtheme stylesheet, flame.css. The subtheme CSS works with the Garland style.css, so all elements don’t have to be styled: only the ones that differ from the parent theme.
The subtheme files for Flame are included in a subdirectory named “flame”, located in the Garland theme subdirectory. No other files other than the .info file are necessary, though it wouldn’t make sense not to have a subtheme style sheet. The only reason I provided a page.tpl.php file is because of processing I added for XHTML support, which I’ll discuss in a later section. I copied the one from Garland and only added the additional XHTML processing. Anything that is not overridden in the subtheme is inherited from the base theme, and anything not provided in the base theme, is provided either by the template engine, or the Drupal core.
Of course, subthemes aren’t completely without challenges. For instance, Garland is a fairly complex, multiple layered theme, so it can be difficult to figure out which element needs styling, and in what context. I found that the Web Developer Toolkit extension for Firefox, with the View Style Information option, to be absolutely essential in identifying elements and their current CSS settings.
I still use Garland for the Administrative theme because of my XHTML settings in my subtheme. Since I can’t always control the page contents in the administration pages, I don’t want these served up as XHTML. However, I did change the default blue color to the same color scheme I used for this site: Mediterrano. Drupal themes can be designed in such a way that the color schemes can be dynamically changed, without recourse to the style sheet, as well as being able to configure other things, such as location of favicon, logo, whether to include a search form and so on. The configure option is a link next to each theme in the Themes site configuration page, alongside a radio button selecting one theme as the default.
In addition to the stylesheets and page templates, I also provided a template.php file for all of my themes in order to override the default output for one PHP variable, $head.
XHTML and template.php
Since I use inline SVG in all of my sites, I need to serve my pages up as XHTML. I couldn’t find a Drupal module or option that triggers Drupal to serve the pages up as XHTML, so I added the following code at the very top of the page.tpl.php page:
<?php
header("Vary: Accept");
if (stristr($_SERVER["HTTP_ACCEPT"], "application/xhtml+xml"))
header("Content-Type: application/xhtml+xml; charset=utf-8");
else
header("Content-Type: text/html; charset=utf-8");
?>
All this code does is check whether the user agent can support XHTML. If it can, the page is served up as XHTML; otherwise HTML. Using embedded PHP in page.tpl.php for the theme works as desired. However, the content type of the page no longer agrees with the content-type meta element, which is included within the $head variable.
<head>
<title><?php print $head_title ?></title>
<?php print $head ?>
<?php print $styles ?>
<?php print $scripts ?>
<!--[if lt IE 7]>
<?php print phptemplate_get_ie_styles(); ?>
<![endif]-->
</head>
One solution would be to not print out the $head variable and just hard code all of the head entries. However, when you add a new module, such as the RDF module, which adds entries to the $head variable, you either have to hard code these in, also, or you lose functionality.
Instead, I used the template.php file, which is used to override default Drupal behavior. Specifically, I created a variant of _preprocess_page for my subtheme, used this to access $head, and alter it.
function flame_preprocess_page(&$vars) {
$head = $vars['head'];
$head = str_replace("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />",
"<meta http-equiv=\"Content-Type\" content=\"application/xhtml+xml; charset=UTF-8\" />", $head);
$head = str_replace("<link rel=\"alternate\" type=\"application/rss+xml\" title=\"Burningbird's RealTech RSS\" href=\"http://realtech.burningbird.net/rss.xml\" />\n","",$head);
$vars['head'] = $head;
}
I am still getting my head around customization in Drupal, and will have a follow up posting later, after I’ve had more time to work through issues. In the meantime, from my understanding, the _preprocess_page passes in an array of system variables, each of which can be accessed and altered, as I’ve done in the code example. The subtheme naming is essential, otherwise I would overwrite the use of the function within the Garland theme.
There is another theme specific function for overriding variables called _phptemplate_variables, but I gather that was Drupal 5 and the approach I used is Drupal 6.x and forward.
Now, the head entries are as I want them, and modules such as RDF can add their entries without me impacting on their work. More importantly, with the addition of the XHTML support, I can add inline SVG and the SVG will display correctly for those browsers that support inline SVG.
XHTML and the forum
An issue I had with supporting XHTML in my WordPress weblogs is that it can be difficult to control others’ input in comments. I solved the issue with Drupal by not supporting comments directly on posts. Instead, I’ve provided a forum, using the Drupal Forum module, here at RealTech. I’ve only provided the one forum for all of my sites to make it simpler for people to register, as well as follow any active discussions.
To ensure that Forum pages don’t create XHTML errors, I modified the PHP to serve pages as XHTML to exclude URLs that have the word “forum” in them. I realize this will impact on any page with forum in the title, such as this page. However, it’s unlikely that I’ll be using inline SVG and the word “forum” in the title for the same post.
header("Vary: Accept");
if (stristr($_SERVER["HTTP_ACCEPT"], "application/xhtml+xml"))
if (stristr($_SERVER["REQUEST_URI"],"forum"))
header("Content-Type: text/html; charset=utf-8");
else
header("Content-Type: application/xhtml+xml; charset=utf-8");
else
header("Content-Type: text/html; charset=utf-8");
In addition, I turned filtered HTML on for forum entries, as well as installed htmLawed, to ensure that the entries are as clean as possible. Regardless, a problem in the forums won’t take down a post, and that was my main criteria for making this change.
The forums should also provide a much more flexible communication system. You can use your OpenID to register, or just register directly. You can still comment anonymously, though the comments are moderated.
Typically, any person who registered is an authorized user, and could create forum topics. Well, I wasn’t quite ready to make that leap of faith. I created a “trusted” user who can create forum topics, and will reserve this user classification to people I know. I then adjusted the permissions to enable forum topic creation for trusted users and admins, only.
I’ve created main forum categories. Over time, I imagine I’ll need to adjust the forum categories to be general enough to be useful, without being so general that it’s difficult to find discussions of interest.
Menus and vocabulary index pages
The final customization I added for all my sites was the menus. In all three sites, I turned on the Primary Links menu, which displays at the top. I then created an “about” page for each site, as well as added in links for the Contact form, as well as Search for Painting the Web.
For Painting the Web, I added the Vocabulary Index pages as part of the Primary Links menu, since I have no sidebar. In RealTech, I created two custom menus, Explore and Categories, with the Vocabulary Index pages listed as entries in Categories, and Search and Tracker’s Recent Posts in Explore. In addition, I’ve also turned on the default Book Navigation menu in the left sidebar, as well as the Navigation menu for managing content for registered users. Both of these are activated in the Blocks configuration page, though the Navigation menu displays in the Menus page once activated. The Book Navigation menu does not, because it cannot be customized.
I’m using the Nice Menus module in Painting the Web. There is a specialized function to call when using it with the Primary links, which is embedded directly into the page.tpl.php file, replacing the existing primary links print from the Simplex2 theme.
<div id="header">
<?php if (count($primary_links)) : ?>
<?php
print theme('nice_menu_primary_links');
?>
<?php endif; ?>
</div>
With the addition of the menus, the theming and customization sites was finished. At least, for now.
The Way Forward
I’ve barely touched on the customizations you can incorporate into a Drupal site. Drupal has a Content Construction Kit (CCK) that can be used to define new content types, in addition to the Panels, Views, and Fields modules, which can be used to create new views, panel pages, custom fields in addition to the content types created with the CCK. I didn’t incorporate use of these customizations into my existing sites, as the 6.x support is under development, and they weren’t essential to the site designs.
I do, however, plan on incorporating these modules, as well as perhaps creating some of my own modules, in my next phase sites, including my Adding Ajax and Learning JavaScript book support sites, as well as my phase three sites: MissouriGreen and The Secret of Signals. I won’t be able to work on these sites, however, until after I’m finished with the second edition of Learning JavaScript.
Eventually, I’ll have a more indepth coverage of my use of Drupal for my sites, but this is a beginning. Do let me know if you run into any problems, and enjoy Painting the Web, Just Shelley, RealTech, and the Burningbird forums.