Similar nodes in Drupal 7 using Views

Internal links are important part of Search Engine Optimization. A common use of automatic internal links is "Similar articles" section suggesting pages with the same tags as the current one. For Drupal there's a module that does just that called Similar by Terms. Unfortunately the version for Drupal 7 is not in working state at the moment and I could not manage to utilize it. Instead I chose to use my favorite way - standard and actively supported generic modules namely Views (a must to install module for any Drupal site anyway).

I lack proper skills necessary to work with this module, but Google already serves as substitute for the parts of brain responsible for reinventing the wheel and I quickly found ready-made solutions based on filters and relations. At first it seemed that the problem was solved however upon closer examination the suggested nodes were sorted randomly and not by the number of taxonomy terms they share with the current one. The sorting looks like a five minute job for someone who knows what they are doing, but not for me. I've spent few days trying to figure out how to sort "similar" nodes until I decided to gave up (literally and some of my reputation) and posted a question on StackOverflow. Five minute job indeed:

  1. Create a block view. Change display format to be "of fields";
  2. Add Contextual filter -> Content: Nid -> Provide default value -> Content ID from URL;
  3. Add Relationship -> Content: Taxonomy terms on node -> specify the appropriate vocabulary;
  4. Add Relationship -> Taxonomy term: Content using vocabulary as specified above -> Check "Require this relationship" box;
  5. Turn on Views aggregation (I was missing this step while trying to solve the problem myself)
  6. Click "Show" under Format and select "Fields"
  7. Edit the title field to use the Relationship you set up in #4 above;
  8. Add new sort criteria of Content: Nid. In aggregation settings, select Count. Use relationship from #4 and sort descending. Delete existing sort by post date if present;
  9. Add Contextual filter -> Content: Nid -> Use relationship from #4 -> Provide default value - Content ID from URL -> Scroll down and expand "More" then check "Exclude" (this removes current node from the view)
  10. Click "Content: Published" under Filter Criteria. Select relationship from #4 - this will make the view to only display published similar nodes.

Setting up a view of similar nodes in Drupal 7

Here's full view code for import:

$view = new view();
$view->name = 'similar_nodes';
$view->description = '';
$view->tag = 'default';
$view->base_table = 'node';
$view->human_name = 'Similar nodes';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */

/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['title'] = 'Similar nodes';
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['group_by'] = TRUE;
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '10';
$handler->display->display_options['style_plugin'] = 'default';
$handler->display->display_options['row_plugin'] = 'fields';
$handler->display->display_options['row_options']['inline'] = array(
  'title' => 'title',
);
/* Relationship: Content: Taxonomy terms on node */
$handler->display->display_options['relationships']['term_node_tid']['id'] = 'term_node_tid';
$handler->display->display_options['relationships']['term_node_tid']['table'] = 'node';
$handler->display->display_options['relationships']['term_node_tid']['field'] = 'term_node_tid';
$handler->display->display_options['relationships']['term_node_tid']['vocabularies'] = array(
  'tags' => 'tags',
  'forums' => 0,
);
/* Relationship: Taxonomy term: Content using Tags */
$handler->display->display_options['relationships']['reverse_field_tags_node']['id'] = 'reverse_field_tags_node';
$handler->display->display_options['relationships']['reverse_field_tags_node']['table'] = 'taxonomy_term_data';
$handler->display->display_options['relationships']['reverse_field_tags_node']['field'] = 'reverse_field_tags_node';
$handler->display->display_options['relationships']['reverse_field_tags_node']['relationship'] = 'term_node_tid';
$handler->display->display_options['relationships']['reverse_field_tags_node']['required'] = TRUE;
/* Field: Content: Title */
$handler->display->display_options['fields']['title']['id'] = 'title';
$handler->display->display_options['fields']['title']['table'] = 'node';
$handler->display->display_options['fields']['title']['field'] = 'title';
$handler->display->display_options['fields']['title']['relationship'] = 'reverse_field_tags_node';
$handler->display->display_options['fields']['title']['label'] = '';
$handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
$handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
$handler->display->display_options['fields']['title']['element_label_colon'] = FALSE;
/* Sort criterion: COUNT(Content: Nid) */
$handler->display->display_options['sorts']['nid']['id'] = 'nid';
$handler->display->display_options['sorts']['nid']['table'] = 'node';
$handler->display->display_options['sorts']['nid']['field'] = 'nid';
$handler->display->display_options['sorts']['nid']['relationship'] = 'reverse_field_tags_node';
$handler->display->display_options['sorts']['nid']['group_type'] = 'count';
$handler->display->display_options['sorts']['nid']['order'] = 'DESC';
/* Contextual filter: Content: Nid */
$handler->display->display_options['arguments']['nid']['id'] = 'nid';
$handler->display->display_options['arguments']['nid']['table'] = 'node';
$handler->display->display_options['arguments']['nid']['field'] = 'nid';
$handler->display->display_options['arguments']['nid']['default_action'] = 'default';
$handler->display->display_options['arguments']['nid']['default_argument_type'] = 'node';
$handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0';
$handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
$handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25';
/* Contextual filter: Content: Nid */
$handler->display->display_options['arguments']['nid_1']['id'] = 'nid_1';
$handler->display->display_options['arguments']['nid_1']['table'] = 'node';
$handler->display->display_options['arguments']['nid_1']['field'] = 'nid';
$handler->display->display_options['arguments']['nid_1']['relationship'] = 'reverse_field_tags_node';
$handler->display->display_options['arguments']['nid_1']['default_action'] = 'default';
$handler->display->display_options['arguments']['nid_1']['default_argument_type'] = 'node';
$handler->display->display_options['arguments']['nid_1']['summary']['number_of_records'] = '0';
$handler->display->display_options['arguments']['nid_1']['summary']['format'] = 'default_summary';
$handler->display->display_options['arguments']['nid_1']['summary_options']['items_per_page'] = '25';
$handler->display->display_options['arguments']['nid_1']['not'] = TRUE;
/* Filter criterion: Content: Published */
$handler->display->display_options['filters']['status']['id'] = 'status';
$handler->display->display_options['filters']['status']['table'] = 'node';
$handler->display->display_options['filters']['status']['field'] = 'status';
$handler->display->display_options['filters']['status']['relationship'] = 'reverse_field_tags_node';
$handler->display->display_options['filters']['status']['value'] = '1';
$handler->display->display_options['filters']['status']['group'] = 1;
$handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;

/* Display: Page */
$handler = $view->new_display('page', 'Page', 'page');
$handler->display->display_options['path'] = 'similar-nodes';

/* Display: Block */
$handler = $view->new_display('block', 'Block', 'block');
$handler->display->display_options['defaults']['pager'] = FALSE;
$handler->display->display_options['pager']['type'] = 'some';
$handler->display->display_options['pager']['options']['items_per_page'] = '5';
$handler->display->display_options['defaults']['style_plugin'] = FALSE;
$handler->display->display_options['style_plugin'] = 'default';
$handler->display->display_options['defaults']['style_options'] = FALSE;
$handler->display->display_options['defaults']['row_plugin'] = FALSE;
$handler->display->display_options['row_plugin'] = 'fields';
$handler->display->display_options['defaults']['row_options'] = FALSE;
$translatables['similar_nodes'] = array(
  t('Master'),
  t('Similar nodes'),
  t('more'),
  t('Apply'),
  t('Reset'),
  t('Sort by'),
  t('Asc'),
  t('Desc'),
  t('Items per page'),
  t('- All -'),
  t('Offset'),
  t('« first'),
  t('‹ previous'),
  t('next ›'),
  t('last »'),
  t('term'),
  t('field_tags'),
  t('All'),
  t('Page'),
  t('Block'),
);