Attributing RSS traffic on your Drupal site using UTM

It seems like RSS is not quite as a buzz as it once was, years ago. There are reasons for that, but I partly believe it is because more services mask direct RSS feed subscriptions in larger aggregate tools. This change also makes it more interesting to get analytics about where that traffic is coming from, and what feed. When I migrated my site to Drupal 8, I decided to take an adventure on adding UTM parameters to my RSS feeds.

This was not nearly as easy as I had thought it would be. My RSS feeds are generating using Views. My Views configuration is straightforward, pretty much the out of the box setup. It uses the Feed display plugin and the Content row display. The setup I assume just about everyone has.

Blog RSS feed configuration

.

In order to start attributing my RSS links and understanding my referral traffic, I needed to adjust the links in my RSS feed for the content. So my first step was to review the node_rss plugin. The link is generated from the entity for its canonical version and with an absolute path, which is to be expected, but there is no way to alter this output (unless you possibly want to alter every time a URL is generated.)

// \Drupal\node\Plugin\views\row\Rss::render()

    $node->link = $node->url('canonical', ['absolute' => TRUE]);

// ...

    $item->title = $node->label();
    $item->link = $node->link;
    // Provide a reference so that the render call in
    // template_preprocess_views_view_row_rss() can still access it.
    $item->elements = &$node->rss_elements;
    $item->nid = $node->id();

// If only I could alter $item!

    $build = [
      '#theme' => $this->themeFunctions(),
      '#view' => $this->view,
      '#options' => $this->options,
      '#row' => $item,
    ];

    return $build;

My next thought was to just extend the node_rss plugin and add my link generation logic. But that's messy, too. I would have to override the entire method, not some "get the link" helper, meaning I'd have to maintain more code on my personal site.

So I kept digging. My next step was to visit template_preprocess_views_view_row_rss and see if I can do a good ole preprocess, hack it in, and call it a dirty day. And, guess what? I could. I did. And I feel a little dirty about it, but it gets the job done.

function bootstrap_glamanate_preprocess_views_view_row_rss(&$variables) {
  /** @var \Drupal\views\ViewExecutable $view */
  $view = $variables['view'];
  if ($view->id() == 'taxonomy_term') {
    $term = \Drupal\taxonomy\Entity\Term::load($view->args[0]);
    $label = $term->label();
    if ($label == 'drupal') {
      $source = 'Drupal Planet';
    }
    else {
      $source = 'Term Feed';
    }
    $variables['link'] .= '?utm_source=' . $source . '&utm_medium=feed&utm_campaign=' . $term->label();
  }
}

My taxonomy term feed is what funnels into Drupal Planet, so only posts tagged "Drupal" show up. So in my preprocess I check the vie and decide to run or not. I then attribute the source, medium, and campaign.

Ideally, I should make a UTM friendly row display which extends node_rss and allows Views substitutions to populate the UTM parameters, if available when generating the URL object. I might do that later. But hopefully, this helps anyone looking to do something similar.