Using hook_views to programmatically filter a view in Views 3, Drupal 7

Tuesday, December 13, 2011

There is a real dearth of documentation, examples, and tutorials out there surrounding how to use hook_views to do anything useful in general. There is even less out there specific to Views 3 and Drupal 7. I recently found myself in a situation where I needed to programmatically filter a view using hook_views. I needed to filter one content type by a taxonomy term, given only the nid of a node of a different content type which contained in it a field with values related to the taxonomy I needed to filter on. I tried a number of approaches within the Views UI in an attempt to accomplish this, but was thwarted by what I think are a couple of bugs in Views 3 (subject of another post altogether).

When you look at the hook_views documentation, it's quite a challenge to figure out which hook you need. In my case, I wanted to hook into the view as early in the process as possible because I was attempting to add a filter, which I thought would need to modify the database query (in the end, I'm not sure it actually modifies the query, but rather just the display properties). As a result of my assumptions, I figured hook_views_pre_view to be the best choice.

I started tinkering by selecting the view and display I was wanting to modify, and then inspecting the view object for that view/display combination.

  1. function lsadataclassify_views_pre_view(&$view, &$display_id, &$args){
  2.   if ($view->name === 'protection_measures' && $view->current_display === 'page'){
  3.   }
  4. }

Given the massive view object, I didn't know what to do. I tried looking through to find the likely location of the filters, but wasn't sure how to construct a new one. I found multiple locations that seemed to list filters and didn't know which one to edit. The breakthrough happened when I discovered an answer to a stackoverflow question. Translating that answer to my view, it turned out the path in the object to the properties I needed to modify was:

  1. $view->display['page']->handler->options['filters']

I was still confused about how to construct a filter from scratch, but at that point it dawned on me to modify the view in the UI to add the filter I wanted. Doing this allowed me to see it show up in the view object, at which point I could just modify its properties, rather than have to go through and create the filter from scratch. This worked because I essentially just needed a way of dynamically changing the term tids to filter by based on some custom logic related to the nid argument that was passed in via the url. As a result, I performed my custom logic and created an array of tids to replace the view's taxonomy field array of tids. The result looks like this:

  1. function lsadataclassify_views_pre_view(&$view, &$display_id, &$args){
  2.   if ($view->name === 'protection_measures' && $view->current_display === 'page' && isset($args[0])){
  3.     $node = node_load($args[0]);
  4.     $sensitivity = $node->field_research_sensitivity[$node->language][0]['value'];
  5.     $tids = array();
  6.     switch ($sensitivity) {
  7.       case 'high':
  8.         $tids = array(5, 6, 7);
  9.         break;
  10.       case 'moderate':
  11.         $tids = array(6, 7);
  12.         break;
  13.       case 'low':
  14.         $tids = array(7);
  15.         break;
  16.     }
  17.     $view->display['page']->handler->options['filters']['field_pm_sensitivity_termref_tid']['value'] = $tids;
  18.   }
  19. }

Unfortunately, this wasn't the end to my problems. This successfully filtered the view as I needed it, but because this view is a part of a more complex display pattern which involves attaching a node edit form to each view row and allowing all of them to be submitted upon clicking one submit button (subject for yet another post!), this new dynamic filter broke my display. The cause turned out to have to do with the way in which hook_views_pre_view interacts with the view in conjunction with how the function views_get_view_result works.

In order to pull off the complex display, I was calling views_get_views_result to retrieve the result of this view so that I could get access to the node objects of the view (and not just the rendered html output of the rows). As it turns out though, views_get_view_result was not recognizing my hook_views. Instead of getting the view filtered by my dynamic hook_views filter, I was getting the view result with all the possible rows unfiltered. I think views_get_view_result only operates on the view itself irrespective of the display properties. I was clued into this idea by some obscure comment in a Drupal forum which suggested that in order to access the view with its display properties applied, you needed to use views_get_view instead. All my attempts to implement the suggestion there failed for me.

After searching around quite a bit, finding some useful information about views_get_view_result here, but ultimately exhausting all the sources I could think of, I set to examining and testing each of the views api functions that might prove useful for this. After trying out views_get_current_view, I stumbled upon views_get_page_view. While views_get_current_view returned the full view without my hook_views filter applied, views_get_page_view did not! The end result using views_get_page_view is slightly simpler than using views_get_view_result because I don't have to pass in the view name, display, and arguments. It looks something like this, retrieving the view and looking through the result to store the node objects in an array for use later:

  1.   $view = views_get_page_view();
  2.   //drupal_set_message(dprint_r($view2, TRUE));
  3.   $pm_nodes = array();
  4.   // loop through each row of the view which contains a node
  5.   foreach ($view->result as $key => $entity){
  6.     // node of protection measure
  7.     $pm_nodes[$key] = $entity->_field_data['nid']['entity'];
  8.   }

Kevin's Bookmarks