0

Is it possible to append a campaign tracking code to the requested URL basing on a specific field value on the linked node? I'm trying to use a path_processor_outbound service, but it doesn't work quite as expected and it works rather randomly.

This is the code I am using.

function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {if(isset($options['entity']) && $options['entity'] instanceof \Drupal\node\NodeInterface ) {
      $node = $options['entity'];

      if($node->bundle() == 'article') {
        $channel = $node->get('field_channel')->getValue();
        $promotions = array(10);
        $channel_value = reset($channel);
        $channel_id = $channel_value['target_id'];

        if(in_array($channel_id, $promotions)) {
          $options['query']['foo'] = 'bar';
        }
      }
    }
    return $path;
  }

I have a feeling this is correct approach for links to nodes generated on site (teasers on front page), but it doesn't append parameters on page request.

I also considered adding parameters to the path alias, but that doesn't seem to be right and JavaScript code would be added too late.

6
  • "Randomly" sounds like a caching problem. In my case I also had to create a cache context to get the outbound processor working correctly.
    – Hudri
    Feb 27, 2019 at 13:59
  • @Hudri could you please be more particular? Did you make it to append param with page load so if you go to /something it will become /something?foo=bar ?
    – bluedrop
    Feb 27, 2019 at 14:45
  • I see the url with parameter is stored correctly in path variable in templates but that doesn't make it to appear automatically in address bar I guess.
    – bluedrop
    Feb 27, 2019 at 14:48
  • did you add YourModule.path_processor: class: Drupal\YourModule\PathProcessor\D8apiPathProcessor tags: - {name: path_processor_outbound} to the yourModule.services.yml
    – berramou
    Feb 27, 2019 at 15:04
  • @berramou yes, otherwise it wouldn't do anything I guess
    – bluedrop
    Feb 27, 2019 at 16:36

2 Answers 2

0

Don't know if it fits for your use case, but I wanted all outgoing links from a specific node type to have the ID appended, e.g. on every product page all outgoing "Call to action" links should appent something like ?from_product_id=1234. My module name was 'wt_ctalinks`

wt_ctalinks.services.yml

services:
  cache_context.wt_ctalinks:
    class: Drupal\wt_ctalinks\CtaLinksCacheContext
    arguments: ['@request_stack']
    tags:
      - { name: cache.context }
  wt_ctalinks.ctalinks_pathmodifier_service:
    class: Drupal\wt_ctalinks\CtaLinksPathModifierService
    arguments: ['@request_stack']
    tags:
      - { name: path_processor_outbound, priority: 3000 }

src/CtaLinksCacheContext.php

<?php

namespace Drupal\wt_ctalinks;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;

class CtaLinksCacheContext implements CacheContextInterface {


  /**
   * @var Symfony\Component\HttpFoundation\RequestStack;
   */
  protected $requestStack;

  public function __construct(RequestStack $request_stack) {
    $this->requestStack = $request_stack;
  }

  /**
  * {@inheritdoc}
  */
  public static function getLabel() {
    drupal_set_message('Modify CTA links based on current node type');
  }

  /**
  * {@inheritdoc}
  */
  public function getContext() {
    $nid = 0;
    $request = $this->requestStack->getCurrentRequest();
    $node = $request->attributes->get('node');
    if (is_numeric($node)) {
      $node = Node::load($node);
    }
    if ($node instanceof NodeInterface && $node->bundle() === 'article') {
      $nid = $node->id();
    }
    return $nid;
  }

  /**
  * {@inheritdoc}
  */
  public function getCacheableMetadata() {
    return new CacheableMetadata();
  }
}

src/CtaLinksPathModifierService.php

<?php

namespace Drupal\wt_ctalinks;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\config_pages\Entity\ConfigPages;
use Drupal\node\Entity\Node;


/**
 * Class DefaultService.
 */
class CtaLinksPathModifierService implements OutboundPathProcessorInterface {

  /**
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  protected $bookingPath;
  protected $enquirePath;

  /**
   * Constructs a new DefaultService object.
   */
  public function __construct(RequestStack $request_stack) {
    $this->requestStack = $request_stack;
  }

  /**
   * {@inheritdoc}
   */
  public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
    if (empty($request)) {
      $request = $this->requestStack->getCurrentRequest();
    }

    $node = $request->attributes->get('node');

    if (is_numeric($node)) {
      // missing upcasting in revision view, have to load manually there
      $node = Node::load($node);
    }

    if ($node instanceof NodeInterface && $node->getType() === 'article') {
      // your logic here
    }

    if ($bubbleable_metadata) {
      $bubbleable_metadata
        ->addCacheContexts([
          'wt_ctalinks',
        ]);
    }
    return $path;
  }
}
3
  • $request_stack returns current request object though, not the node being processed
    – bluedrop
    Mar 1, 2019 at 15:07
  • @bluedrop Well, yes, that was my use case. I don't understand the reason of appending a value of the target node to a link to the target node. The target node can access those values naturallly, no need for outbound processor in such a case?
    – Hudri
    Mar 1, 2019 at 15:27
  • I also don't find any reason to append a query to the link when the link is for a node and one of its fields has a specific value. I would rather check the field value when somebody access the node.
    – apaderno
    Mar 2, 2019 at 20:54
0

$options['entity'] is set only for links rendered by Views and getting the node id from the route match works only if the node is the main page content. So both solutions are not working for all outbound node links.

Another idea might be to use the route pattern /node/{node} to get the node id from $path, before it is aliased by PathProcessorAlias::processOutbound. Set a priority >300 in the service tag to make sure your code runs first:

  tags:
    - { name: path_processor_outbound, priority: 500 }
4
  • If $options['entity'] is added to links rendered in a view, how can the URL match /node/{node}?
    – apaderno
    Feb 27, 2019 at 18:49
  • @kiamlaluno, my suggested approach, matching the unaliased $path with the route pattern for the canonical node route, should work for all cases, while the other methods as explained only work for specific node links.
    – 4uk4
    Feb 27, 2019 at 19:50
  • You mean getting the node ID from $path. You should edit the answer to make it clearer. get the node ID from the URL is not clear, at least to me.
    – apaderno
    Feb 27, 2019 at 20:24
  • And yes, the information got from $request or from the current_route_match service is about the current request, not the link being output. So, the approach I was suggesting doesn't work.
    – apaderno
    Feb 27, 2019 at 20:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.