Mar
13
2010
Your correspondent has just completed a report that compares video hosting and integration options for Drupal.
We looked at four options — FlashVideo, Blip.tv, Viddler and Kaltura — that cover the range of options from do-it-yourself to enterprise hosting. A detailed comparison chart at the bottom of the article includes a complete feature breakdown summary.
Check out the article at UrbanInsight.com.
Feb
16
2010
(See end of post for updates.)
Your correspondent was fortunate enough to recently partake in a four day, hands-on, in-depth Drupal training (agenda: theming, forms API, menu API, module development, jQuery, best practices) conducted by the firm that literally wrote the book on Drupal: Lullabot.
Our excellent instructors were Nate Haug and Karen Stevenson. The training was tremendously helpful and Nate and Karen really know Drupal through to the core. It was a blast getting a deeper look at Drupal and being able to have all of our questions answered.
These tips were collected during training and every effort was made to capture them accurately, but do point out any errors or feedback in the comments (any errors were mine in capture, not Lullabot’s in training).
- Output the $body_classes variable in the class attribute of your body tag on your page.tpl.php files to have access to handy classes like “front”, “not_front”, “logged-in”, etc.
- The /admin/build/block page is the only admin page that doesn’t use an admin theme so that the block location helper overlays make sense.
- The two most common elements left out of your page.tpl.php files are $closure and $tabs.
- The basic convention for theming is to copy the file you want to create from the code module folder (like node.tpl.php) and then edit it as needed.
- Use placeholder tokens to handle languages that order words differently. Example:
$variables['submitted'] = t('On @date', array('@date'=>format_date($variables['created'],'custom','F jS')));
- Best practice: For major Drupal upgrades the best practice is to remove and recreate any theme functions you overrode, so that you can include any code changes that you haven’t captured.
- If you don’t want to use $content in node.tpl.php files, then don’t. Just output each field individually.
- Performance: Use the row style “Fields” rather than “Nodes” on views. The “Nodes” mode does a node_load() call on each node in your view which costs more than 50 queries per node. “Fields” mode just grabs the data you need.
- The dsm() dpm() function is like the insanely helpful pr() function in CakePHP, offering nice output of complex data for debugging. You need devel installed and enabled to use it.
- Group all custom modules under the same “package” name in the module list to make it clear to future developers where all of the custom modules are.
- Use the coder module to identify Drupal 6 to 7 api changes.
- Best practice/convention: The variable name user means the logged in user to the site at the moment of code execution; the variable name account means information about some user on the site, unrelated to the logged in user.
- Performance: The entire variable table is loaded on every page load, so limit what you store in variables.
- Performance: The variable_get() function is free (no hit to the database) as all variable information is stored in memory.
- Don’t call t() on menu title and descriptions as they are stored in the cache on build, meaning that the language used at the point of cache creation gets set for everyone.
- Use the MENU_LOCAL_TASK in the hook_menu array to add a tab to a page, like node or user.
- Performance: Move large menu item callback functions to inc files, using the ‘file’ attribute of the menu array. This results in more efficient memory management as the entire .module file is loaded on every page load, but inc files are only loaded when specifically needed.
- Using %user and %node in your hook_menu array will cause Drupal to automatically call node_load or user_load on the passed in IDs. Nate called these magic handlers.
- You can use your own magic handler by using “%” in the hook_menu array. Example: %yourown will cause Drupal to run yourown_load(). These functions must reside in main .module file, not in inc files.
- In your module, you can use $GLOBALS['conf']['cache'] = false to turn off caching for a page. (Note: If your page is currently cached you won’t see caching disabled until you clear the cache or it expires.)
- A handy way to determine if a site is Drupal is to look for a page expire date in the headers of 11/19/1978 (the birth date of the creator of Drupal).
- Doing routes in Drupal: custom_url_rewrite_inbound() and custom_url_rewrite_outbound().
- Choosing the right date field type in CCK: Date (iso date), good for historical and imprecise dates , like year only; Datestamp (Unix epoch), same format as Drupal core, better to use Datetime as it offers the same precision; Datetime stores in native format of a date at database tier (you can then do date manipulation functions on it at the database level which is fast).
- Best practice: Split your sites/all/modules folder into contrib and custom.
- Best practice: If you have to modify a contributed module for a project, track the change in a patch file and create a folder in your site to store all of you individual patch files. Each time you update a contributed module with a newer release verify the issue still needs to be patched manually (reapply the patch you have) or that the patch is now part of the official module (delete your patch file). This way your patches can be stored in version control, easily submitted to the module maintainer and reapplied in the future when you apply important Drupal security patches to your site.
- Best practice: hook_menu() should be the first function in your custom module because it serves as an index for the module, describing what it does and where.
- The reason the keys in form arrays start with a pound sign is to allow the nesting of forms in the array.
- The “clicked_button” attribute is added to the form state by Drupal to handle images used as submit buttons because Internet Explorer doesn’t use the submit button’s name like other browsers (and like a regular submit button is handled by all browsers).
- You set the error on a nested field by using the format parent][child. Example: "home][street", where home is the parent form, and street is the field.
- form_error() is cleaner and more logically formatted than form_set_error(), but they do the same thing.
form_set_error('home][street','You must enter the street address.');
form_error($form['home']['city'], ‘You must enter the street address.’);
- If anything is placed in $form_storage Drupal will ignore any redirects specified and rebuild the form on the submit. To avoid this you must unset $form_storage.
- Best practice: You can use any HTML you want in a theme function because a theme is empowered to override the HTML in it's own theme function.
- Drupal will render any remaining parts of $form that you haven't rendered already, so it's not necessary to manually render every part, only the ones you want to handle individually.
- Use db_set_active() to switch between the database connections you have specified in your settings file on-the-fly in a routine to easily go outside of the core Drupal database for external content or data.
- The Table Wizard module lets you expose any database table to Drupal views (you can even define keys to allow for joins).
- If you specify a set value ("#value") on a form element (like hidden), the Drupal handler will set the form element back to that value regardless of what comes in from the user.
- A form type ("#type") of "value" is never sent to the user but kept in the form data so you can access the data in other functions in your module.
- HTML tip: Inline JavaScript stops the browser from loading anything after it (HTML, JavaScript, etc) until the browser finishes loading the code.
- JQuery tip: VisualjQuery.com is a handy visual API reference for JQuery
- Firebug tip: The ">>>" at the bottom of the console tab in Firebug lets you run JavaScript you type in.
- HTML tip: Some browsers will strip out A tags if they don't have an href attribute specified.
- JQuery performance: Specifying a tag name when looking for a class is much faster than just looking for the class as browsers have built-in support for getElementysByTagName().
Fast: $('.content');
Faster: $('div.content');
- JQuery performance: Using $(this) inside of selector functions is faster than using the selector again.
- An easy way to manage your views is to export them and install them in a custom module. This way you can keep the code in version control, protect them from user (you don't need to even install the Views UI) and you can always revert in the Views UI if changes are applied by a user accidentally to the view.
- Managing major patches: Create an empty module and use hook_update() to push major site configuration changes (settings, etc). This way you can update the module code then run update.php and install the patch in Drupal.
- When to sanitize: Generally speaking data at the template layer tends to be safe, anywhere else (or higher) isn't. Use check_plain (no tags) or check_markup (runs through default input filter of site).
- You can create a Drush profile drush_make script that you can feed into a drush command to build a clean install of Drupal with all of the current versions of modules downloaded live from Drupal.org.
- Use cache_get() and cache_set() as much as possible as serving data from the cache does not require a reduces database hits.
Thanks again to Nate and Karen for the great training!
Thanks to the commentators that helped with these refinements:
- Update: Refined #47. Thanks Boris! (see comments)
- Update: Refined #48. Thanks zzolo! (see comments)
- Update: Refined #9. Thanks abaddon! (see comments)
Nov
17
2009
The Wordpress folks have a great article on Hardening WordPress over on the codex. It covers strategies for securing your install in depth, and talks about general security principles.
But it really all waters down to a few steps that are painless to implement and will vastly improve the security of your WordPress install:
- Force SSL for admin and admin login; add this to wp-config.php:
define(‘FORCE_SSL_LOGIN’, true);
define(‘FORCE_SSL_ADMIN’, true);
- Add an Apache username/password prompt to the /wp-admin/ directory
- Change the username for admin (in the database)
- Setup WordPress for automatic updates and patch your core and plug-ins as soon as updates are available
Nov
10
2009
Your correspondent ran into this rather crippling fatal error while working on a CakePHP application:
Fatal error: Class '' not found in path-to-cake\cake\libs\class_registry.php on line 140
Googling, clearing the CakePHP cache and several Apache restarts didn’t offer a solution. The problem turned out to be a stupid mistake in a model file that had a blank hasOne relationship setting:
var $hasOne = '';
Fixing the half-completed model relationship fixed the issue (either remove the line or enter in the name of a model for the relationship).
Nov
10
2009
Your correspondent spent the day at the StackOverflow Dev Day conference in Washington, DC. Overall the conference was great; Joel, Jeff and the Carsonified team should be very proud of all their hard work.
Highlights
- Power strips were all over the place, under the seats, for laptop power. Thank you!
- It was neat getting to chat to members of the FogCreek team face to face
- The jQuery team does a great job of branding jQuery speakers; the slides were fantastic: expertly branded, logically ordered and compellingly supportive of the speaker
- Great keynote theme: “How does your software help me copy my DNA?”
- The between speaker interlude program was great (having a count down timer made it easy to spend the brief downtime efficiently and the twitter feed was compellingly interactive)
- The wifi actually worked
- Lunch was both accessible and yummy (a rarity for conference food)
- The discussion topic by area lunch was a great idea
- The opening [scrums] (sp?) video was funny
- All of the talks were great: jQuery, iPhone development, the problems of backwards compatibility in language design, ASP.NET MVC, Google App Engine and the keynote.
Low lights
- The front door staff actually asked for paper tickets; you can fly on a plane without a paper ticket
- The name badges didn’t have StackOverflow rep printed on them (nor did they have a space to write that in)
- Parking was a pain
- The seats in the venue (balcony at least) had no leg room, even for your vertically challenged correspondent
- It would have been nice if Jeff Atwood was on hand
- Bruce Eckel’s presentation on the problems of backwards compatibility could have used additional visual support and a bit more dialog polish
Oct
19
2009
Your correspondent recently had to deliver a training session on Google Analytics to savvy but non-technical users for a content driven community site. The audience of the training were the people behind the site and hadn’t used Google Analytics before.
This training outline worked really well:
- About
- What Google Analytics tracks
- So much data!
- Logging in
- Core features
- Understanding the layout
- Using date ranges
- Using the help icon
- Using the export, email buttons
- Dashboard
- Main metrics
- Visitors
- Traffic sources
- Content
- Key metrics
- Top content
- Top exit pages
- Search keywords
- Metrics for your site
- Advanced topics
- Goals, not useful for them
- Custom reports
- The Help section
- Questions
Tips for training:
- Occasionally point out how the common page elements appear on various pages (email, export, date range, etc).
- Occasionally point out how to add content to the dashboard, as you show metrics.
- Show how easy it is to use the Google help site by searching for a term, and showing the glossary.
- Just show and briefly explain the advanced topics are, don’t delve into them.
- Spend two minutes thinking specifically about what the two metrics you would look at if you were responsible for the site, in order to be able to act on them.
- This training takes about 45 minutes; expect 30 more minutes of questions.
And don’t forget to send an agenda to the team ahead of time:
- About Google Analytics
- Logging in
- Core features
- Dashboard
- Main metrics
- Key metrics
- Advanced features
- Questions & wrap-up
Jul
14
2009
Here is how you add an RSS feed to a Drupal 6 page that is powered by a view:
- Edit your view
- Add a display called “Feed”
- Specify a path for the RSS, like /rss
Drupal should automatically add the RSS auto-discovery link to your site, but often this doesn’t happen. You can add this manually by editing the page.tpl.php file in your theme and following these directions.
If you want to customize the theme it gets a bit more tricky. You can add new files to your theme folder to customize the RSS feed:
- Edit your view
- Click on the feed display
- Click on the “Theme Information” link
The first file lets you alter the RSS wrapper around your RSS content, and the second file lets you alter the style of each item in the feed. However, you don’t really have any additional information about your node, so you can’t add CCK fields or other information.
Nota bene: If you add either file be sure to click the “Rescan folder” icon, otherwise Drupal won’t see the file.
There is a great post over at TIMtheToon that has a great function you can add to your theme to add the full $node data to your RSS template file.
Nota bene: Don’t forget to reset the theme registry to Drupal detects changes to your theme file.
Jul
14
2009

Adobe licensing error messages, including debug info.
Adobe makes some great software, but the licensing management experience for corporate licenses is an embarrassment.
Your correspondent purchased some additional corporate licenses for Adobe InCopy for two team members. In order to get the serial numbers for the software you need to log into the hapless Adobe Licensing site: http://licensing.adobe.com.
Unfortunately the licenses your correspondent purchased were associated to a new licensing.adobe.com login ID, but needed to be associated to the preexisting ID linked to other Adobe software already purchased.
Your correspondent had two goals: obtain the license keys and merge this login ID to another login ID. Attempting this feat trivial task revealed a variety of problems:
- There is no way to merge accounts in licensing.adobe.com.
- It takes 6+ clicks to get from the contact us link on licensing.adobe.com to a support number (which isn’t the direct support line).
- You must wait on hold and spend several minutes at the main support line until you are transferred to the licensing team, resulting in a lost queue position and additional hold time.
- If you attempt to login with the wrong password several times your account is locked out and you must call into Adobe to get your account reactivated.
- Your correspondent was transferred to a line that rang about 15 times and then soberly declared: “Your party is not answering. Your call will now be disconnected.”
- Login error and alert messages appear in a tiny font and include actual debugging output.
There are a variety of lessons we can take-away from this:
- Queue positions ought to be respected when transferred to different departments. If you wait for 10 minutes for the main team and then are transferred to another team queue you should be placed 10 minutes ahead of everyone else.
- Unless you are creating the login routine for GetNukeLaunchCodes.gov, account lockouts should life after a set time automatically.
- Licensing sites should include tools that actual help the busy IT professional, like an instant combine/merge accounts feature.
- Search for and eliminate dead end phone paths. Phone menu systems, like software, need to fail well. In this case, that means routing a person back to a customer service rep (at the top of the queue) when transfers aren’t picked up properly.
- Check the work of your programmers to ensure that debugging information isn’t revealed to the user on production systems. It’s unprofessional and possibly a security risk.
Jul
09
2009
The pitfalls of using PowerPoint slides for presentations are well known. However, conferences would be much improved if organizers gave presenters a few simple guidelines to follow.
Here are four quick tips:
- Your first/title slide should include the presentation title, your name, your title, your company and some kind of contact (this all lends credibility);
- When showing an image on a slide make the image as large as the slide;
- Limit to 2-3 words per slide; and,
- Do not read slides, have a conversation (each slide should either remind you of key themes to touch on during the chatter for that slide or evoke a reaction — thought, laughter, emotion, etc — in the audience).
May
25
2009
When creating a new site it’s a good idea to standardize on your domain name (www or no www?) and to gracefully handle HTTPS/SSL requests (do you have an SSL site, or should you redirect users off of it?). It’s also a good idea to compress the text files your server returns (like HTML, CSS and JavaScript pages).
You can do all this with an .htaccess file.
The great power of .htaccess files is that they can include rewrite rules via Apache’s mod_rewrite module. There are loads of things rewrite rules can do, so we decided to create a standard file that would handle a few things:
- Redirect all HTTPS/SSL traffic to the same URL but to HTTP;
- Redirect all traffic without a “www” entered to the same URL but with a “www.” added; and,
- Compress all HTML, CSS and JavaScript files (to speed up website browsing).
The goal is that requests to:
https://example.com/some/page
will be gracefully redirected to
http://www.example.com/some/page
A clean, standard URL with no risk of SSL confusion.
The .htaccess file
# Standard .htaccess file
# - Compress text documents for speed
# - Rewrite https to http and no www to www
<IfModule mod_rewrite.c>
RewriteEngine on
# move off of https
RewriteCond %{HTTPS} on
RewriteRule (.*) http://%{HTTP_HOST}%{REQUEST_URI} [R=301]
#move to www if no www is entered
RewriteCond %{HTTP_HOST} !^(www\.).*
RewriteRule (.*) http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
# compress stuff for faster delivery
AddOutputFilterByType DEFLATE text/css text/javascript application/x-javascript text/html
Header append Vary User-Agent