Data Syncing Fun

This happens to be about Drupal sites, but isn’t really Drupal-specific. If you must know, the custom classes I refer to later are Migrate module migration classes, with the JSON source plugin.

I’m working on a project (let’s call it Satellite site) that shares data with the Parent site. Content editors on the parent site work on items and those items get synced every few minutes to Satellite site. How they get synced is an integral part of this story. As part of our Drupal 6 to 7 upgrade, we already have some URLs on Parent site that allow us to list which items have changed since a given timestamp, and to get the raw data dump for a given item. We have a nightly build that calls all those URLs and has some custom classes to process the data into the new Drupal 7 format. Satellite extends those classes to do some additional logic. The problem given to me by my PM is that new items would transfer over ok from Parent to Satellite, but updates on Parent wouldn’t come over. I found and fixed the following problems:

On Parent site to save processing during our nightly upgrade test build, each item’s data is written out to a file. When the item’s URL is called, it first looks for a file with the item’s ID and if it’s there returns the JSON data within. If it doesn’t exist, it writes the file and returns the JSON content. Since the upgrade build is under major construction, rather than rebuild each file when the item changes, we just have a batch script that regenerates the files since it’s mostly getting the structure migrated correctly that we care about for now. When we go live, we’ll be locking down content edits and re-running the batch script at that point to get the latest data. But for Satellite’s purposes, it needs the freshest data. I added code on Parent to accept a query string parameter that would force generation of a new file if passed in.

Ok, but the updates were still not coming over. The next problem I found was a truly nasty logic bug and one that didn’t occur to me when I architected this setup, nor did it occur to the engineer who followed my instructions and actually did the work. Data migrations occur in two steps: first, we call a URL saying ‘get me a list of the item IDs of everything that’s changed since X’, where X is a unix timestamp of the last time an update was run; then, we loop through each of those items and call their individual item URLs. After successful completion of a migration cron cycle, we store the current timestamp for the next round. While debugging why the upgrades weren’t happening I looked at the item list URL and compared the timestamp to the last changed date for a test item. And there was the problem… Parent and Satellite are on two completely different servers (and hosts, for that matter). Satellite’s clock happens to be ahead of Parent’s clock, therefore the last migrated timestamp we were storing for the next round on Satellite was actually ahead of the Parent. This meant that no items would have an updated time more recent than the server’s time for quite a while, when Parent’s clock caught up to Satellite’s stored timestamp. So I rewrote the code on Satellite to get the next round of items as all items newer than the most recent updated time of the items from the previous batch.

For example, let’s say a regular sync iteration runs at 1:50; 3 items were in the last batch and their updated times were 1:23, 1:25, 1:22. Under the original code, I’d store 1:50 as the ‘since’ timestamp (i.e. next round would request ‘get me all items that have been updated since 1:50’). As you can see, anything updated between 1:25 and 1:49 would miss out. Under the corrected code, I’d store 1:25.

A couple people now who know this story have asked why we don’t just have the times synced through the standard tools (NTP, for example). We don’t have control over systems level stuff on Parent, for one. Also, I needed a solution now before the content editors staged a witch hunt. And hell, I can do basic sysadmin stuff, but y’know, I’m not a sysadmin; I didn’t even know there were options for syncing servers until said people mentioned it.

And yet that still didn’t solve the lack of updates! WTF! The last problem was relatively straightforward. I first tried pulling the item data URL from Parent in the browser and the latest data was there. Then I remembered that I had a logged-in session on Parent and logged in users bypass Parent’s Varnish cache. So I pulled up an Incognito tab in Chrome, tried the same URL and sure enough, the old data was presented instead. After consulting with a coworker, he reminded me that you can bypass Varnish by putting a unique query string parameter in the URL. I added the current timestamp to the item data URL and was able to get the latest changes.

Long story short, what a pain.

Drupal: Adding JS/CSS to a specific content type page with Panels

We’re using Panels for a project I’m on, and of course we’re using the node_view panel page to lay out the full node page for each content type. Panels is great in that for each variant you create, you can add CSS body classes, a CSS ID, and even style blocks. But what if you need, as I did, to include a separate file–JS in my case–to a page?

My coworker, the esteemed Jeff, sent me a couple links to a possibility using CTools that he hadn’t explored but thought was a likely solution. And he was right! Obviously you can replace the JS addition with whatever logic you want.

/**                                                                                                                                                                                                                                                                                 
 * Implements hook_ctools_render_alter().                                                                                                                                                                                                                                           
 */
function MYMODULE_ctools_render_alter(&$info, &$page, &$context) {

  // If we're looking at a CONTENT TYPE X node page, add the appropriate JS                                                                                                                                                                                                        
  if (isset($context['contexts']['argument_entity_id:node_1'])  // There is a context that is a node                                                                                                                                                                                
    && $context['contexts']['argument_entity_id:node_1']->data->type == 'content_type_x_machine_name' // That node context is a CONTENT TYPE X node                                                                                                                                        
    && $context['handler']->name == 'node_view_panel_context') { // We're viewing the node_view panel page (individual node page)                                                                                                                                                   

    drupal_add_js(drupal_get_path('module', 'MYMODULE') . '/js/supercool_scripts.js');
  }                                                                                                                                                                                                                                                                
}

PHP: Do Objects As Array Values Pass By Ref?

Cliff notes: YES!

$class = new StdClass();
$class->alpha = 'hello';

$foo = array(
  'a' => 1,
  'b' => $class,
);


function foo($foo) {

  $foo['b']->alpha = 'goodbye';

}

print_r($foo['b']->alpha);
foo($foo);
print_r($foo['b']->alpha);
$ php foo.php
hellogoodbye

Migrating Drupal 6 Multigroups to Drupal 7 Field Collections

At work we’ve been on a Drupal 6 to 7 upgrade project. Unlike our Drupal 5 to 6 migration, in which we used custom SQL and PHP scripts to move the data over to the new schema, we’re using the fantastic Migrate module. It takes a little bit of digging in the documentation to get a migration class running, but once you get the hang of it, it’s easy to knock them out quickly. The only downside we found is that there’s no good way to migrate a Drupal 6 CCK multigroup to a Drupal 7 Field Collection.

The problem with Migrate and Field Collections is that it will only import the first multigroup values. See more on this problem here. One of the other engineers found a way to migrate multiple multigroups by using a separate migration for the multigroup, rather than trying to do it in the parent content type, but I discovered an even easier way using hook_user_insert().

For the following code, we’re using an example from a real content type we have (a user’s profile) that has a multigroup containing 2 fields that represent the user’s children. Those two fields are field_child_birthday and field_gender, and there are an unlimited number of possible children (multigroup instances) belonging to a profile node. Luckily we only have to keep the first 6 children in this migration because data mining has shown that any users with more than 6 kids are just test users. I’m already assuming you know how to write migration classes and how Drupal 6 multigroups and Drupal 7 field collections work.

One of the neatest things I discovered is that you can set arbitrary object properties in a migration class that will persist to the relevant hooks. This allowed me to set a flag on the user object in the migration class that indicated this user is being created through migration, not regular registration.

protected function mapFields() {

  // This is in place to change logic in hook_user_insert()                                                                                                     
  $this->addFieldMapping('is_migration')->defaultValue(TRUE);

  // Rest of mapping will go here
}

Then, I set up my hook_user_insert() in a Drupal 7 custom module to listen for new users being added to the system, and then I told it to only do logic when the user is being inserted from migration.

/**                                                                                                                 
 * Implements hook_user_insert().                                                                                   
 */
function MYMODULE_user_insert(&$edit, $account, $category) {

  // If we're not in migration, exit so we don't run any logic here                                                 
  // for regular user registrations                                                                                 
  if (!isset($account->is_migration)) {
    return;
  }

  // Rest of logic will go here
}

Ok, so now my migration class needed to actually get the data from the Drupal 6 user dump and into a flat format so it could be mapped to–virtual, in this case–Drupal 7 fields.

protected function prepareRow($row) {

  // Migrate user's kids                                                                                  
  $max_kids = 6;
  for ($i = 0; $i < $max_kids; $i++) {
    $birthday_field_name = 'migration_kid_birthday_' . $i;
    $gender_field_name   = 'migration_kid_gender_' . $i;
    // Only pass on kids with birthdays                                                                   
    if (isset($source->profile->field_child_birthday[$i]) && $source->profile->field_child_birthday[$i]->value) {
      $row->{$birthday_field_name} = $source->profile->field_child_birthday[$i]->value;
      $row->{$gender_field_name} = isset($source->profile->field_gender[$i]) ? $source->profile->field_gender[$i]->value : NULL;
    }
    else {
      $row->{$birthday_field_name} = NULL;
      $row->{$gender_field_name} = NULL;
    }
  }

}

protected function mapFields() {

  // This is in place to change logic in hook_user_insert()                                                                                                     
  $this->addFieldMapping('is_migration')->defaultValue(TRUE);

  // Rest of mapping will go here

  // Map flattened user's kid multigroup
  for ($i = 0; $i < 6; $i++) {
    $birthday_field_name = 'migration_kid_birthday_' . $i;
    $gender_field_name   = 'migration_kid_gender_' . $i;
    $this->addFieldMapping($birthday_field_name, $birthday_field_name);
    $this->addFieldMapping($gender_field_name, $gender_field_name);
  }

}

What the above code did is to add 12 properties to the Drupal 7 user object, named migration_kid_birthday_(0-5) and migration_kid_gender_(0-5). If you dump out the $account object in your hook_user_insert you’ll see something like the following. In this case, the user has 3 kids so the rest is filled with NULL.

stdClass Object
(
  [is_migration] => 1 // This was a virtual field passed over from earlier
  [is_new] => 1
  [uid] => 1701521
  [name] => TestUser
  ...
  [migration_kid_birthday_0] => 2007-09-01T00:00:00
  [migration_kid_gender_0] => m
  [migration_kid_birthday_1] => 2009-01-01T00:00:00
  [migration_kid_gender_1] => f
  [migration_kid_birthday_2] => 2011-01-01T00:00:00
  [migration_kid_gender_2] => f
  [migration_kid_birthday_3] => 
  [migration_kid_gender_3] => 
  [migration_kid_birthday_4] => 
  [migration_kid_gender_4] => 
  [migration_kid_birthday_5] => 
  [migration_kid_gender_5] => 
)

The last part of this process is to take those virtual fields and turn them in to field collection items attached to the new account object. The Field Collection module provides a handy API for this, as it turns out.

/**                                                                                                                 
 * Implements hook_user_insert().                                                                                   
 */
function MYMODULE_user_insert(&$edit, $account, $category) {

  // If we're not in migration, exit so we don't run any logic here                                                 
  // for regular user registrations                                                                                 
  if (!isset($account->is_migration)) {
    return;
  }

  // Rest of logic will go here

  // Add the user's children as field collections          
  $delta = 0; // This ensures we don't have blank kids in between legit ones
  for ($i = 0; $i < 6; $i++) {
    $birthday_field_name = 'migration_kid_birthday_' . $i;
    $gender_field_name = 'migration_kid_gender_' . $i;
    if ($account->{$birthday_field_name}) {
      $field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_collection_child\
'));
      $field_collection_item->setHostEntity('user', $account);
      $field_collection_item->field_child_birthday[LANGUAGE_NONE][]['value'] = $account->{$birthday_field_name};
      $field_collection_item->field_gender[LANGUAGE_NONE][]['value'] = $account->{$gender_field_name};
      $field_collection_item->save(TRUE); // TRUE prevents saving of the host entity which causes a db error        
      $account->field_collection_child[LANGUAGE_NONE][$delta]['value'] = $field_collection_item->item_id;
      $account->field_collection_child[LANGUAGE_NONE][$delta]['revision_id'] = $field_collection_item->revision_id;
      $delta++;
    }
  }

}

Creating a Custom Entity with One to Many Rows

My team is in the middle of a research sprint for moving us from Drupal 6 to 7. As creator/owner of the API, I’ve been looking into what can be done differently in Drupal 7. The first candidate for major changes is how we store API keys; I decided that they were best suited as entities instead of plain rows in a custom table. Based on how much work I’ve had to do, it really would have been easier to leave it as a standalone custom table with direct SQL queries, but that’s beside the point. I’m assuming at this point that you’re familiar with the various Drupal hooks referenced, or can at least feed the Google.

Table Creation

The main table (csm_api_keys) contains the API keys and some metadata about each key, and has a one to many relationship with the methods permission table (csm_api_keys_methods). That table contains one row for every API method that a given key has access to. It’s all controlled in a UI with checkboxes so business development can manage this as their customers purchase access to more data.

/**                                                                                                                                                                                                                                    
 * Implementation of hook_schema().                                                                                                                                                                                                    
 */
function csm_api_schema() {

  $schema = array();

  // API keys table                                                                                                                                                                                                                    
  $schema['csm_api_keys'] = array(
    'description' => 'Keys for the CSM API.',
    'fields'      => array(

      'api_key' => array(
        'description' => 'API key',
        'type'        => 'char',
        'length'      => 32,

      ),

      'name' => array(
        'type'   => 'varchar',
        'length' => 100,
      ),

      'role' => array(
        'type'   => 'varchar',
        'length' => 32,
      ),

    ),

    'primary key' => array('api_key'),
  );

  // API methods relation table                                                                                                                                                                                                        
  $schema['csm_api_keys_methods'] = array(
    'description' => 'Permissions for API keys per method',
    'fields'      => array(

      'api_key' => array(
        'description' => 'FK to csm_api_keys::api_key',
        'type'        => 'char',
        'length'      => 32,
        'not null'    => TRUE,
      ),

      'method' => array(
        'description' => 'Name of API method',
        'type'        => 'varchar',
        'length'      => 255,
        'not null'    => TRUE,
      ),

    ),
  );

  return $schema;
}

Entity Creation

There are plenty of posts on how to create a custom entity in Drupal 7 using Entity API, which will explain this code snippet.

Two items of note: I didn’t make the entity fieldable, and I’m using a custom controller. Since the entity will not ever need new fields, or if it does it’s because I’m doing some very major structural changes to the API, I decided not to make it fieldable to avoid the FieldAPI overhead. I’m also using a custom controller instead of the default EntityAPIController to allow me to write my own CRUD logic for the entity.

The *_load() and *_load_multiple() functions are based on the design pattern that’s been evolving since Entity API was released; it should be self-explanatory.

/**                                                                                                                                                                                                                                    
 * Implements hook_entity_info().                                                                                                                                                                                                      
 */
function csm_api_entity_info() {
  return array(
    'csm_api_key' => array(
      'label'            => t('API key'),
      'base table'       => 'csm_api_keys',
      'entity keys'      => array(
        'id' => 'api_key',
      ),
      'entity class'           => 'Entity',
      //'controller class'       => 'EntityAPIController',                                                                                                                                                                             
      'controller class'       => 'CsmApiKeyController',
      'views controller class' => 'EntityDefaultViewsController',
      'label callback'         => 'entity_class_label',
      'uri callback'           => 'entity_class_uri',
    ),
  );
}

/**                                                                                                                                                                                                                                    
 * Load a single record.                                                                                                                                                                                                               
 *                                                                                                                                                                                                                                     
 * @param $id                                                                                                                                                                                                                          
 *    The id representing the record we want to load.                                                                                                                                                                                  
 */
function csm_api_key_load($id, $reset = FALSE) {
  return array_shift(csm_api_key_load_multiple(array($id), $reset));
}

/**                                                                                                                                                                                                                                    
 * Load multiple records.                                                                                                                                                                                                              
 */
function csm_api_key_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('csm_api_key', $ids, $conditions, $reset);
}

class CsmApiKeyController extends EntityAPIController {} // Nothing to see here, move along.

This created an object something like the following, which is fantastic except I need to know which methods the key has permissions to. And this is where it gets “fun”.

Entity Object
(
    [api_key] => d41d8cd98f00b204e9800998ecf8427e // This isn't a real key, don't bother trying it
    [name] => My Sample Key
    [role] => testing
)

Custom Entity Controller

Remember earlier when I mentioned I was using a custom controller instead of the default EntityAPIController? Here’s why. Aside from the data in the main table, I need to get the data in the csm_api_keys_methods table as well, which means joining that table to the main one. That happens to be done in the aptly named buildQuery() method.

class CsmApiKeyController extends EntityAPIController {

  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
    $query = parent::buildQuery($ids, $conditions, $revision_id);

    // Specify additional fields from the user and node tables.                                                                                                                                                                        
    $query->innerJoin('csm_api_keys_methods', 'km', 'base.api_key = km.api_key');
    $query->addField('km', 'method', 'method');
    return $query;
  }

}

All done! Nope, just kidding. That got me an object that has one value for the method property, even though there are multiple rows in that table for this key.

Entity Object
(
    [api_key] => d41d8cd98f00b204e9800998ecf8427e // This isn't a real key, don't bother trying it
    [name] => My Sample Key
    [role] => testing
    [method] => reviews.browse
)

Multiple Values, One Property

What I really want is $key->method to be 1) plural ($key->methods) and 2) an array that holds all values. The join syntax is correct to return multiple rows, so what’s happening? Turns out the problem is in EntityAPIController::load(). It loops through each row in the query result set, assumes one row to one entity, and writes that row into the final data set. Of course, when you join in SQL with a one to many relationship, you’ll get one result for each row in the many table, with the values for columns from the one table repeated in each row.

The way to get around this was to override this method in my controller class to handle that logic. I first copied the parent method’s code over to my controller class, then changed what I needed. To make the example code shorter, only the relevant part is displayed. The original line that was replaced is–at the time of this writing–on line 238 of entity/includes/entity.controller.inc.

class CsmApiKeyController extends EntityAPIController {

  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
    $query = parent::buildQuery($ids, $conditions, $revision_id);

    // Specify additional fields from the user and node tables.                                                                                                                                                                        
    $query->innerJoin('csm_api_keys_methods', 'km', 'base.api_key = km.api_key');
    $query->addField('km', 'method', 'method');
    return $query;
  }

  /**                                                                                                                                                                                                                                  
   * Overridden.                                                                                                                                                                                                                       
   * @see EntityAPIController#load($ids, $conditions)                                                                                                                                                                                  
   *                                                                                                                                                                                                                                   
   * In contrast to the parent implementation, multiple rows are                                                                                                                                                                       
   * grouped by field api_key to handle multiple methods for each key.                                                                                                                                                                 
   */
  public function load($ids = array(), $conditions = array()) {
    $entities = array();

    // CUT FOR BREVITY

        //$queried_entities[$record->{$this->idKey}] = $record; // REPLACE THIS WITH THE BELOW
        // THIS IS THE ONLY PART OVERRIDDEN FROM PARENT                                                                                                                                                                                
        if (isset($queried_entities[$record->{$this->idKey}])) {
          $queried_entities[$record->{$this->idKey}]->methods[] = $record->method;
        }
        else {
          $record->methods = array();
          $record->methods[] = $record->method;
          unset($record->method);
          $queried_entities[$record->{$this->idKey}] = $record;
        }
        // END OVERRIDE 

    // CUT FOR BREVITY
   
  }

}

Tada! Now I have an object that looks like this.

Entity Object
(
    [api_key] => d41d8cd98f00b204e9800998ecf8427e // This isn't a real key, don't bother trying it
    [name] => My Sample Key
    [role] => testing
    [methods] => Array
        (
            [0] => reviews.browse
            [1] => reviews.get
            [2] => users.register
            [3] => users.authenticate
            [4] => users.update
        )
)

Drupal: Views 3 Exposed Filters for Multiple Fields

I’m working on a side project with another local dev. This project is in Drupal 7 with Views 3. The purpose of one of the views is to show users stores in a certain city/state. Each store is a node, the address of which is stored in a postal address field from the Address Field module. However, rather than having two exposed filters (one for city, one for state), the client wants a single field. By default, the exposed filter for the city field is a text field, but a better UI would be to have a dropdown. Of course, there could be stores in two cities of the same name in different states–think Portland, OR and Portland, ME–so the options in the dropdown should be labeled appropriately, like Portland, OR.

/**                                                                                                                        * Implements hook_form_FORM_ID_alter() for form views_exposed_form.                                                  
 */
function MY_MODULE_form_views_exposed_form_alter(&$form, $form_state, $form_id) {

  // If we're looking at the store listing view...
  if ($form_state['view']->name == 'store_listing') {

    // Convert the city field to a city/state select

    // Get the unique city/state combos
    $sql = "SELECT DISTINCT field_store_address_locality AS city, field_store_address_administrative_area AS state FROM main_field_data_field_store_address ORDER BY state, city";
    $res = db_query($sql);
    $cities = array();
    foreach ($res as $row) {
      $cities[$row->city . '|' . $row->state] = $row->city . ', ' . $row->state;
    }
    $field_name = 'field_store_address_locality';
    $form[$field_name]['#type'] = 'select'; // Make the text field a select box
    $form[$field_name]['#options'] = $cities; // Populate the options
    $form[$field_name]['#empty_option'] = ' - Select Location - '; // Give a no value option
    unset($form[$field_name]['#size']); // Take this out to avoid it being a multiselect

  }
}

/**
 * Implements hook_views_query_alter().
 *
 * @see http://api.lullabot.com/hook_views_query_alter/7
 */
function MY_MODULE_views_query_alter(&$view, &$query) {

  if ($view->name == 'store_listing') {

    // Loop through the WHERE clauses
    foreach ($query->where as &$where) {
      foreach ($where['conditions'] as $idx => &$condition) { // Who knows why Views dumps this into a sub-key
        // If we're looking at the city|state WHERE clause...
        if ($condition['field'] == 'field_data_field_store_address.field_store_address_locality') {
          list($city, $state) = explode('|', $condition['value']); // Split the city|state value into separate components
          $condition['value'] = $city; // Assign the city value to the city WHERE clause
          $where['conditions'][] = array( // Create a new WHERE clause for the state
            'field'    => 'field_data_field_store_address.field_store_address_administrative_area',
            'value'    => $state,
            'operator' => '=',
          );
          break;
        }
      }
    }
  }

}

Drupal: Fun with Views’ hooks

I’m still working on the site for my artist friend I mentioned in my last post. There are two problems I’m facing, both solved with the creative use of Drupal Views hooks and a patch to Drupal core (yes!).

I’m using a view to display a series of images, one on the homepage, and one on a dedicated Gallery page. Each one is a view: the homepage uses the Promote to frontpage flag to filter content, and the Gallery view filters by content type. Both have to share a common behavior, which is to have the image’s title and artist added as a title attribute to the image tag. For example, Leonardo DaVinci – Mona Lisa.

I could save the title as part of the image, either by allowing the user to set it manually during upload, or using hook_nodeapi() to save the title automatically. The problem with that approach is that it hardcodes the title as part of the node content, which doesn’t allow for situations like, oh, say, my friend decides she wants the image name before the artist name instead of the way it currently is. I also could have overridden the output of the image in the view using a template, but I’m also using the Lightbox2 module with its custom formatter. Doing an override in the template would make me lose the lightbox formatting.

I thought to myself: “there has to be a place where I can access the view data after it’s generated but before it’s rendered.” Sure enough, a google search came up with hook_views_pre_render(). Once I found this function, I added the node title and artist name as fields to the two views. Using the handy dpm() function from the Devel module, I dumped the view object to see where my field values were and added the code to give the image a title. Note the check for existence for the field names. This is because field_image is used in multiple content types, not all of which will have the other fields available to them in a view.

/**
 * Implements hook_view_pre_render().
 */
function MY_MODULE_views_pre_render(&$view) {

  // If there is an image field...                           
  if (isset($view->field['field_image'])) {
    // For each result...                                                                                         
    foreach ($view->result as &$result) {
      foreach ($result->field_field_image as &$image) {

        // Set the image's title attribute as "artist name - image title"
        $title = NULL;
        if (isset($result->field_field_display_name) && $result->field_field_display_name[0]) {
          $title .= $result->field_field_display_name[0]['rendered']['#markup'];
        }
        if (isset($result->node_title)) {
          if ($title) {
            $title .= ' - ';
          }
          $title .= $result->node_title;
        }
        $image['rendered']['#item']['title'] = $title;
      }
    }
  }                                                                                                
}

The next problem I wanted to solve was how to make images sized using image styles (Imagecache in Drupal 6) work with Omega’s resizable grid. Omega is a responsive theme based on the 960 grid system. Depending on the size of the user’s viewport, each column may be a different width. For resizable content like text, this isn’t a big deal because the content will automatically wrap, but for fixed width content like images, it can be a problem. The solution is to create the image style in the largest size the grid allows for the given column size, then give the image a 960 grid class so it will be proportionate to the rest of the page, but resize to the smaller size when the grid columns shrink in a smaller viewport.

For one of the views, there is a 4×4 grid. At 24 columns, this means each image is 6 columns wide. At the largest setting, a column is 30px wide with a 20px gutter. Therefore, the image style for the image field needs to be 280px (6*30 + 5*20) wide. After using CSS to override Views’ default grid styles to remove extra padding and margins, the images displayed perfectly on the widest screen setting. But of course, when the page was shrunk to a smaller size, the images started overlapping and hiding. So! I needed to add the .grid-6 class to the image tag itself so it would resize as the columns did, per the sizes specified by Omega’s CSS.

/* alpha-default-wide-24.css */
.container-24 .grid-6 {
    width: 280px;
}

/* alpha-default-normal-24.css */
.container-24 .grid-6 {
    width: 220px;
}

/* alpha-default-narrow-24.css */
.container-24 .grid-6 {
    width: 160px;
}

Views doesn’t expose any UI settings for adding a class to the image itself, which is where hook_views_pre_render comes in again. Between the two views I had 3 different image styles: homepage (230x230px, grid-5), homepage-featured which is used in an attachment to the homepage view (430x480px, grid-9), and gallery (280x280px, grid-6). Since the two homepage styles are used in the same view, I needed to differentiate not only by view name, but display. I could have also chosen to switch based on the is_attachment property of the view, but wanted to keep it more specific for better understanding of the intent.

For readability, I broke up the switch logic into its own function. If it doesn’t find a match, it just returns NULL.

/**
 * Returns the appropriate grid size class for an image based on the view context
 *
 * @param object $view
 *   The current view an image belongs to 
 *
 * @return string  
 *   The .grid-N class for the image size, no class notation dot included, if one is set. Otherwise, NULL.
 */
function _MY_MODULE_get_image_grid_size($view) {

  switch ($view->name) {

    case 'gallery' :
      return 'grid-6';

    case 'frontpage' :
      switch ($view->current_display) {

        case 'page' :
          return 'grid-5';

        case 'attachment_first_item' :
          return 'grid-9';
      }

  }

}

I called this function in hook_views_pre_render() as well, so now the hook function looked like:

/**
 * Implements hook_view_pre_render().
 */
function MY_MODULE_views_pre_render(&$view) {

  // If there is an image field...                                                                      
  if (isset($view->field['field_image'])) {
    // For each result...                                                                                     
    foreach ($view->result as &$result) {
      foreach ($result->field_field_image as &$image) {

        // Set the image's title attribute as "artist name - image title"                                 
        $title = NULL;
        if (isset($result->field_field_display_name) && $result->field_field_display_name[0]) {
          $title .= $result->field_field_display_name[0]['rendered']['#markup'];
        }
        if (isset($result->node_title)) {
          if ($title) {
            $title .= ' - ';
          }
          $title .= $result->node_title;
        }
        $image['rendered']['#item']['title'] = $title;

        // Add the appropriate grid class based on the view and image style                               
        if ($grid_class = _MY_MODULE_get_image_grid_size($view)) {
          $image['rendered']['#item']['attributes']['class'] = $grid_class;
        }

      }
    }
  }

}

But wait… the class still didn’t show up in the img tag. WTF? After tracing the path of the image data array, I found a bug in Drupal core. Turns out that theme_image_formatter() doesn’t pass through the attributes array to theme_image() or theme_image_style().

I dutifully searched for an existing bug for this issue and when I didn’t find one, submitted a patch, which was promptly rejected as a duplicate. Drupal.org SERIOUSLY needs to fix their search engine if a search for image_formatter doesn’t turn up a result for an item titled theme_image_formatter should pass along attributes. Anyway, there was a patch in the issue comments so I applied that and voila! The classes showed up.

The rest of the work was minor styling in CSS to remove the left/right margins the grid classes added to the <img> elements. I could have used the alpha and omega classes but figuring out which image needed which class would have been too difficult. I also used CSS to keep the proportions of the images the same regardless of which width was used:

body.responsive-layout-normal img.grid-6 {
  height: 220px;
}
body.responsive-layout-narrow img.grid-6 {
  height: 160px;
}

body.responsive-layout-normal img.grid-5 {
  height: 180px;
}
body.responsive-layout-narrow img.grid-5 {
  height: 130px;
}

body.responsive-layout-normal img.grid-9 {
  height: 380px;
}
body.responsive-layout-narrow img.grid-9 {
  height: 280px;
}

Drupal: Omega Theme + Submenus

I’m working on a side project for an artist friend of mine. Their current website is in Drupal 6 and a total mess. Whoever built it left a ton of extra modules lying around, code placed in bad locations, no organization, etc. It’s been a nightmare to work on. After doing some quick fixes for their current site, I’ve started an upgrade to Drupal 7 and a cleaner codebase for them.

I wanted a responsive theme, so I picked Omega, which research showed to be the most lauded and used of my options. Creating a subtheme and the zone/region system is straightforward, but I had a weird problem (maybe not weird to someone who’s been working with this theme for a while) with getting submenus to show up. By default, if you add the Menu region to the Menu zone you’ll get a one level deep version of Drupal’s Main menu. But my Main menu has sub-items and those weren’t added to the markup at all. I also wanted the children to be hidden by default, expanding only on rollover. Here’s how I did it.

First, I installed the Superfish module. This module uses the jQuery Superfish plugin to give you collapsible menus. You’ll also need to install the Libraries module and then download the Superfish Javascript library to the global libraries directory. You can read more about that in the module documentation. Once Superfish was enabled, I went to the module configuration page (/admin/config/user-interface/superfish) and set the number of blocks to 1 since I only needed one for the main menu. Going to the Blocks admin page (/admin/structure/block) showed a new available block labeled Superfish 1 in the Disabled section. To make it appear above all the other content, I set the Region to Menu (provided by Omega). Clicking configure gave me the options I needed to set which menu I wanted the block to display and a whole bunch of CSS-related options. Since I wasn’t doing a very fancy menu, I turned off all the options for extra classes to keep the markup cleaner. I also renamed the block to Main menu from its original Superfish 1 title so its purpose would be clear.

So… now I had two copies of the menu in the Omega-provided Menu zone, one Superfished version with children and one without. The solution to that was to uncheck Main menu from the Toggle Display section in my subtheme settings. This counter-intuitively appears below the vertical tabs, so keep scrolling till you see it. After that, the rest of the work was adding CSS to my subtheme to style the menu.

Drupal: calling a view programmatically

I have a situation where I needed to call a view programmatically to output its results in a way that Views can’t natively. That’s pretty well documented in the wild, but I had a couple special factors that I needed to account for.

Building the View

My view needed to get all published nodes of a certain content type that were updated on or after a given date. I started with a base view and created a new block display, added my fields and the content type and status filters. Now, the updated date is chosen by the user which normally means using arguments, but arguments can only handle equality comparisons. This means I had to use an exposed filter. I saved the view, tested, everything ok.

Calling the View

First, I just retrieved the view to make sure it worked.

$view = views_get_view('foo');
$view->execute();
$response = $view->result;

By default, views_get_view() returns the default display, but in my case, I needed to return the block display that I had just created. Regardless of the title and name you give a display, Views assigns a unique key for each display based on its type and order. In this case, the display I had built was the first block, so its key was block_1.

$display = 'block_1';
$view = views_get_view('foo');
$view->set_display($display);
$view->execute();
$response = $view->result;

Filtering Results

Ok, that’s great, but what about my filter? Adding it was straightforward but it wasn’t filtering rows based on the $delta value. It turns out that when you make a filter based on the updated date, Views prefers the date in a “machine readable format”, according to the UI. This evidently doesn’t include unix timestamps. Once I formatted the timestamp to a string, it worked.

$display = 'block_1';
$filter_name = 'changed';
$view = views_get_view('foo');
$view->set_display($display);
$filter = $view->get_item($view->current_display, 'filter', $filter_name);
$filter['value']['value'] = date('r', $delta);
$view->set_item($view->current_display, 'filter', $filter_name, $filter);
$view->execute();
$response = $view->result;

Getting All Results

For some reason, even though the display I created was set to return unlimited results, verified in the UI, calling the view programmatically returned only 10 rows. I still have no clue why it ignores that setting, but there’s a way around it. Here’s the completed code!

$display = 'block_1';
$filter_name = 'changed';
$view = views_get_view('foo');
$view->set_display($display);
$view->set_items_per_page(0);
$filter = $view->get_item($view->current_display, 'filter', $filter_name);
$filter['value']['value'] = date('r', $delta);
$view->set_item($view->current_display, 'filter', $filter_name, $filter);
$view->execute();
$response = $view->result;

Toilet Seat Logic

Women often complain that men leave the toilet seat up. Men complain that women should just put the seat down when they need it. While I agree with men that women shouldn’t complain about it, here’s my argument for why the seat should always be down.

There are three scenarios in the toilet debate: always up, always down, and switch as needed. I would argue that having the toilet seat in one consistent position is better than switching as needed because if you’re not paying attention or it’s dark, you may mistake the state of toilet and either fall in or dirty the seat, depending on its position and your physiology. Also, having it in one position minimizes how many people have to adjust the seat (one versus two) so it’s more efficient. Ok, so why down instead of up? Men need the down position too, so it’s even more efficient. And in my opinion, a toilet seat with the seat down is more aesthetically pleasing.

I had more arguments for this, but as it all occurred to me while I was dreaming, I don’t remember them. I’m just glad my husband is polite enough to put the seat down for me even without me ever having to ask. And no, I don’t complain when I have to put the seat down should he occasionally forget.