Filter Drupal Views results beyond what Views can do
Drupal's Views module gives site builders a great deal of control in presenting lists of content, but sometimes greater control is needed. Fortunately, this greater control is available to a programmer. On a recent project we needed to filter items out of the results in a way the Views user interface could not. Here's how to go behind the wizard's curtain.
The need surfaced while working on the web site for MIT's Global Studies and Languages department, which uses Views to pull in data from a remote service and display it. The Views module provides a flexible method for Drupal site builders to present data. Most of the time you can configure your presentation needs through the UI using Views and Views-related contributed modules. Notwithstanding, sometimes you need to implement a specific requirement which is not available out of the box. Luckily, Views provides hooks to alter its behavior and results. Let’s see how to filter Views results before they are rendered.
Assume we have a website which aggregates book information from different sources. We store the book name, author, year of publication, and ISBN (International Standard Book Number). ISBNs are unique numerical book identifiers which can be 10 or 13 characters long. The last digit in either version is a verification number and the 13 character version has a 3-character prefix. The other numbers are the same. A book can have both versions. For example:
ISBN10: 1849511160
ISBN13: 9781849511162
In our example website, we only use one ISBN. If both versions are available, the 10-character version is discarded. We do this to prevent duplicate book entries which differ only in ISBN as shown in the following picture:
To remove the duplicate entries, follow this simple two step process:
- Find the correct Views hook to implement.
- Add the logic to remove unwanted results.
After reviewing the list of Views hooks, hook_views_pre_render is the one we are going to use to filter results before they are rendered. Now, let’s create a custom module to add the required logic. I have named my module views_alter_results so the hook implementation would look like this:
/**
* Implements hook_views_pre_render().
*/
function views_alter_results_views_pre_render(&$view) {
// Custom code.
}
The ampersand in the function parameter indicates that the View object is passed by reference. Any change we make to the object will be kept. The View object has a results property. Using the devel module, we can use dsm($view->results)
to have a quick look at the results.
Each element in the array is a node that will be displayed in the final output. If we expand one of them, we can see more information about the node. Let’s drill down into one of the results until we get to the ISBN.
The output will vary depending on your configuration. In this example, we have created a Book content type and added an ISBN field. Before adding the logic to filter the unwanted results, we need to make sure that this logic will only be applied for the specific view and display we are targeting. By default, hook_views_pre_render will be executed for every view and display unless otherwise instructed. We can apply this restriction as follows:
/**
* Implements hook_views_pre_render().
*/
function views_alter_results_views_pre_render(&$view) {
if ($view->name == 'books'
&& $view->current_display == 'page_book_list') {
// Custom code.
}
}
Next, the logic to filter results.
/**
* Implements hook_views_pre_render().
*/
function views_alter_results_views_pre_render(&$view) {
if ($view->name == 'books'
&& $view->current_display == 'page_book_list') {
$isbn10_books = array();
$isbn13_books = array();
$remove_books = array();
foreach ($view->result as $index => $value) {
$isbn = $value->field_field_isbn[0]['raw']['value'];
if (strlen($isbn) === 10) {
// [184951116]0.
$isbn10_books[$index] = substr($isbn, 0, 9);
}
elseif (strlen($isbn) === 13) {
// 978[184951116]2.
$isbn13_books[$index] = substr($isbn, 3, 9);
}
}
// Find books that have both ISBN10 and ISBN13 entries.
$remove_books = array_intersect($isbn10_books, $isbn13_books);
// Remove repeated books.
foreach ($remove_books as $index => $value) {
unset($view->result[$index]);
}
}
}
To filter the results we use unset on $view->result. After this process, the result property of the view object will look like this:
And our view will display without duplicates book entries as seen here:
Before wrapping up, I’d like to share two modules that might help you achieve similar results: Views Merge Rows and Views Distinct. Every use case is different, if neither of these modules gets you where you want to be, you can leverage hook_views_pre_render to implement your custom requirements.