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.

  • "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 '19 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 '19 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 '19 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 '19 at 15:04
  • @berramou yes, otherwise it wouldn't do anything I guess – bluedrop Feb 27 '19 at 16:36
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;
  }
}
  • $request_stack returns current request object though, not the node being processed – bluedrop Mar 1 '19 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 '19 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. – kiamlaluno Mar 2 '19 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 }
  • If $options['entity'] is added to links rendered in a view, how can the URL match /node/{node}? – kiamlaluno Feb 27 '19 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. – 4k4 Feb 27 '19 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. – kiamlaluno Feb 27 '19 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. – kiamlaluno Feb 27 '19 at 20:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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