Drupal

Better New Relic reporting on sites with Panels

My last week at DoSomething I spent some time working on getting better metrics on which panel pages are slow. One half of that was to use New Relic's PHP API to provide better transaction names that included the node type and panel name:

<?php
/**
* Implements hook_page_alter().
*
* We want to provide more detail to New Relic on the transaction and late in
* the page build seemed like the simplest place.
*/
function example_page_alter(&$page) {
  if (!
extension_loaded('newrelic')) {
    return;
  }

 
$name = NULL;

 
// Look for a panel page...
 
$panel_page = page_manager_get_current_page();
  if (isset(
$panel_page['name'])) {
   
// If it's a node page put the argument's node type into the transaction
    // name.
   
if ($panel_page['name'] == 'node_view') {
      if (isset(
$panel_page['contexts']['argument_entity_id:node_1']->data)) {
       
$node = $panel_page['contexts']['argument_entity_id:node_1']->data;
       
$name = 'page_manager_node_view_page/' . $node->type;
      }
    }
   
// If it's a page_manager page use the panel name.
   
else if ($panel_page['task']['task type'] == 'page') {
     
$name = 'page_manager_page_execute/' . $panel_page['name'];
    }
  }
  else {
   
$menu_item = menu_get_item();
    if (
$menu_item['path'] == 'node/%') {
     
// Looks like panels didn't have a variant and it's falling back to
      // node_page_view.
     
$name = 'node_page_view/' . $menu_item['page_arguments'][0]->type;
    }
  }

  if (
$name) {
   
newrelic_name_transaction($name);
  }
}
?>

So once you know which panels are slowing down your site you can use the new Panels, Why so slow? module to put the blame on the specific panes.

Drupal on Mountain Lion (OS X 10.8)

The instructions still need some work. I'd did some updating but haven't tried using it with a clean install yet. After reading this it sounds like there's some bigger changes. I've also been trying to switch from macports to homebrew so that'll also mean some changes to this.

Install XCode

Install XCode from the App Store. Run Xcode and open its Preferences (⌘+,) select the Downloads tab and then the Components sub-tab. Click the Install button on the Command Line Tools component.

Install MacPorts

Follow the directions to install Mac Ports.

Become root

To follow these instructions you need to be running as the root user using the default sh shell. If you've got administrator permissions you can open up a Terminal window and switch users using the sudo command then provide your password.

amorton@minivac:~% sudo su
Password:
sh-3.2#

Install MySQL

Use port to install MySQL:

/opt/local/bin/port install mysql55-server

You'll need to create the databases:

sudo -u _mysql /opt/local/lib/mysql55/bin/mysql_install_db

Let launchd know it should start MySQL at startup.

/opt/local/bin/port load mysql55-server

Secure the server and set a new admin password:

/opt/local/lib/mysql55/bin/mysql_secure_installation

Create a configuration file:

cp /opt/local/share/mysql55/support-files/my-large.cnf /etc/my.cnf

Edit /etc/my.cnf using your editor of choice and make the following changes to the [mysqld]:

  • Change the maximum packet size to 16M:

    max_allowed_packet = 16M

Drupal on Lion (OS X 10.7)

I was half way done adding some info how to setup pecl/pear to my guide to running Drupal 6 on OS X 10.6 before I realized I'd been running Lion for almost nine months. So it seemed like a good excuse to update it for Lion. These might be a little wonky since I did an upgrade rather than a clean install so if you notice anything please drop me a line.

Note:I'll save you the trouble of commenting, I am familiar with MAMP but would rather punch myself myself in the face than use it. If you'd like to, go right, but I'm going to continue to compile my own so I know where everything ends up.

Drupal for the win

My Uncle Hugh called me up last year asking if I knew anyone who could help him get a website setup—he's very polite like that—for a book he'd written on the housing crisis. Once he said he didn't really care what it looked like—and I knew I wouldn't have to do a bunch of themeing—I knew I'd be able to set him up something simple using Drupal, so of course I offered to help. It was a nice change from the normal insanity of incomplete specs and legacy requirements that the day jobs always entail. I got to just plug together off the shelf modules and enjoy using Drupal for a change.

I haven't been able to read the book yet but he's a fascinating guy so if you're interested in a first hand account of the housing meltdown go checkout The Great American Housing Fiasco by Hugh Morton. I'm going to demand a copy as payment for the website ;)

Drupal JavaScripting

I was trying to find some docs on how to use Drupal's JavaScript behaviors system to send to some people at work and realized that two years after D6 was released it was still poorly documented. The JavaScript and jQuery page had good examples of how to get JavaScript onto the page from a module or theme but didn't really discuss what to do from that point. I spent some time adding some documentation to the page on drupal.org but wanted to put a copy here for Google's benefit.

After announcing the change on twitter Tim Plunkett pointed out that there were already some D7 docs so incorporated those.

Setup Drupal 6

The station's website will be build using Drupal an extremely powerful, open source content managment system written in PHP.

Drupal uses some PHP functions that require the installation of additional ports. You'll need:

  • devel/php5-pcre - Perl regular expressions.
  • textproc/php5-xml - XML parsing.
  • textproc/php5-simplexml - Simple XML.
  • databases/php5-mysqli - MySQL support for PHP.
  • www/php5-session - Session support.
  • ftp/php5-curl - cURL support.
  • graphics/php5-gd - Image handing. Optional, some modules need it.
  • converters/php5-mbstring - Unicode support. Optional, but Drupal prefers that it be installed.

Correctly accessing CCK fields in SQL queries

Update: I've written a tool to help generate this code: http://drewish.com/tools/cck-query

Twice today I've had to deal with writing a SQL query that needed data in a CCK field. The naive approach is to just look at the table and field names and plug them into your query:

<?php
$result
= db_query("SELECT COUNT(*) AS count FROM {node} n
INNER JOIN {term_node} tn ON n.vid = tn.vid
INNER JOIN {content_type_date} ctd ON n.vid = ctd.vid
WHERE tn.tid = 25 AND ctd.field_date_value > NOW() AND n.changed > %d"
, $newtime);
?>

Often this will work just fine but since CCK can dynamically alter the database schema (when you add a field to a second content type or change the number of values) the query may break.

Fortunately CCK provides functions for finding a field's table and column names so it's simple to do it correctly:

<?php
$field
= content_fields('field_date');
$db_info = content_database_info($field);
?>

A var_dump($db_info) gives:

array(2) {
  ["table"]=>
  string(17) "content_type_date"
  ["columns"]=>
  array(2) {
    ["value"]=>
    array(6) {
      ["type"]=>
      string(7) "varchar"
      ["length"]=>
      int(20)
      ["not null"]=>
      bool(false)
      ["sortable"]=>
      bool(true)
      ["views"]=>
      bool(true)
      ["column"]=>
      string(16) "field_date_value"
    }
    ["value2"]=>
    array(6) {
      ["type"]=>
      string(7) "varchar"
      ["length"]=>
      int(20)
      ["not null"]=>
      bool(false)
      ["sortable"]=>
      bool(true)
      ["views"]=>
      bool(false)
      ["column"]=>
      string(17) "field_date_value2"
    }
  }
}

After noting that the field has two columns and making our choice, we've got the pieces to plug into the query:

<?php
$field
= content_fields('field_date');
$db_info = content_database_info($field);
$result = db_query("SELECT COUNT(*) AS count FROM {node} n
INNER JOIN {term_node} tn ON n.vid = tn.vid
INNER JOIN {"
. $db_info['table'] ."} ctd ON n.vid = ctd.vid
WHERE tn.tid = 25 AND ctd."
. $db_info['columns']['value']['column'] . " > NOW() AND n.changed > %d", $newtime);
?>

The query is a bit harder to read, but you've future proofed your code so you won't be back to fix six months from now when you reuse that date field on another node type.

Using logrotate and drush for daily Drupal backups

If you've got Drush installed—and you really should—you can use the following recipe to setup a backup system that will maintain daily backups for the last two weeks. Most of the logrotate configuration is based on a Wikibooks book that I found.

Drupal 6 on OS X 10.6

Running Drupal on OS X 10.5 was a pretty huge pain in the ass. It's much easier in in 10.6 since it includes PHP 5.3 with GD and the PDO out of the box. And Drupal 6.14 resolves the PHP 5.3 incompatibilities.

In this guide I'll walk through the process I used for reinstalling OS X, then installing MacPorts and using it to install MySQL.

Note: I've shortened this up a bunch since it was first posted (originally it was using PHP 5.2 from MacPorts). I also want to make it clear that I am familiar with MAMP but would rather punch myself myself in the face than use it. If you'd like to, go right ahead since it's probably easier—and as evidenced by the commenters below—you're in good company. But I'm going to continue to compile my own so I know where everything ends up.

Comparing a node's values with its previous version on save

There was a great question on Drupal developers mailing list the other day—one to which I've "rediscovered" the solution to a few times—so I wanted to make sure that everyone was aware of it.

The basic question is:

When a node is being saved, how can you see what values have changed?

The short answer is:

Use the 'presave' operation to load a copy of the node before it's saved, stick it back into the node object, and in your 'update' operation code compare the "before" and "after" versions:

<?php
/**
* Implementation of hook_nodeapi().
*/
function example_nodeapi(&$node, $op, $a3, $a4) {
 
// We want to compare nodes with their previous versions. Ignore new
  // nodes with no nid since there's no previous version to load.
 
if ($op == 'presave' && !empty($node->nid)) {
   
// We don't want to collide with values set by other modules so we'll
    // use the module name as a prefix and a long name to be save.
   
$node->example_presave_node = node_load($node->nid);
  }
  elseif (
$op == 'update') {
   
// On update we pull the previous version out of the node and compare
    // it to the newly saved one.
   
$presave = $node->example_presave_node;
   
// Pretend we're comparing a single value CCK number field here.
   
$field_name = 'field_example';
    if (
$node->$field_name != $presave->$field_name) {
     
drupal_set_message(
       
t("The node's value changed from %previous to %current.", array(
         
'%previous' => $presave->$field_name[0]['value'],
         
'%current' => $node->$field_name[0]['value'],
        ))
      );
    }
  }
}
?>

Simple loop to update nodes

For some reason I find myself rewriting this little bit of code every time I need to update a bunch of nodes on a site. Going to post it here to save myself some time. Be aware that this might time out if you've got a large number of nodes, designed for up to a couple hundred nodes:

<?php
// TODO: Set your basic criteria here:
$result = db_query("SELECT n.nid FROM {node} n WHERE n.type = '%s'", array('task'));
while (
$row = db_fetch_array($result)) {
 
$node = node_load($row);
  if (
$node->nid) {
   
$node->date = $node->created;

   
// TODO: Test and set your own value here:
   
if (empty($node->field_task_status[0]['value'])) {
     
$node->field_task_status[0]['value'] = 'active';
     
$node = node_submit($node);
     
node_save($node);
     
drupal_set_message(t('Updated <a href="!url">%title</a>.', array('!url' => url('node/'. $node->nid), '%title' => $node->title)));
    }
  }
}
?>

Partying like it's 1999

I just got around to upgrading my website to Drupal 6. In the process I decided that I was going to redo the theme using 960.gs for a nice grid based design and then I got it into my head that I wanted a nice retro 1990 theme. So here it is, my stab at recreating the Netscape 2 experience. I'm not sure how long I'll be able to stand it but I'm going to enjoy it while it lasts.

Update: couldn't handle it anymore, had to "modernize" it a bit.

Creating a CCK field in hook_install()

My rule of thumb for deciding what to post on this blog has been to document anything I've spent more than an hour trying to figure out. Today I've got a good one for anyone trying to create CCK fields as part of a module's installation process.

Back in Drupal 5 the Station module was made up of lot of custom code to track various values like a playlist's date or program's genre and DJs. During the upgrade to Drupal 6 I migrated that data into locked, CCK fields that were created when the module was installed. As people started to install the 6.x version of module I began getting strange bug reports about the Station Schedule that I couldn't seem to replicate on my machine.

Everything is an update

For some work projects we've started making all the configuration changes via update functions. These get checked into version control and from there deployed to the staging site for testing, and then eventually deployed on the production site. The nice thing about update functions is that you can test it on staging and be sure that exactly the same changes will occur on the production site.

Here's a few examples, I'll continue to update it as I get more good examples.

Installing a module

Simple one liner to enable several modules:

<?php
function foo_update_6000(&$sandbox) {
 
$ret = array();
 
drupal_install_modules(array('devel', 'devel_node_access'));
  return
$ret;
}
?>

Themeing a specific CCK field

I wasted more time that I want to admit do trying to figure this out. I was trying theme a specific CCK field named field_images on all the nodes where it appears. The devel_themer module was listing content-field-field_images.tpl.php as a candidate:

But after copying CCK's content-field.tpl.php into my theme and renaming it I couldn't seem to get the theme to pick it up. Roger López gave me the frustratingly simple answer on irc: "i think you need to have both templates in place"... duh. Copied content-field.tpl.php into my theme and everything worked great.

Syndicate content