1

Is there a some sort of global preprocessing hook for any link on a Drupal site?

Use case:

I've got an enquiry webform embedded on a node that presets some inputs by URL parameters. Editors can set links to that enquiry node on multiple different places on the site (in navigation menus, inside text fields using LinkIt, in URL fields,...).

Whenever the currently active context is a certain node type (e.g. on every full view of a product like "/product/product-id"), I want all links that are rendered on the page (e.g. menu links, blocks, entity reference field on the product pointing to the enquiry node, etc) to have the product_id appended.

I know how to get the current node id in preprocessing and I do know hook_link_alter for menus. But I wonder if there is some sort of global hook that captures all links, saving me from creating 453 different hooks doing all the same thing.

5
  • This might be of help drupal.stackexchange.com/questions/198507/…
    – GiorgosK
    Apr 17, 2018 at 19:05
  • @Hidri, How do you reference the webform from the node, using a webform reference field?
    – Ziftman
    Apr 18, 2018 at 10:00
  • @Ziftman Yes, not directly though, the node has an intermediate paragraph field, and the paragraph contains the webform reference field.
    – Hudri
    Apr 18, 2018 at 10:56
  • So, whenever the currently active context is a product (e.g. /product/product-id), you want all links that are rendered on the page (e.g. menu links, blocks, entity reference field on the product pointing to the enquiry node, etc) to have the product_id appended. Is that correct? Apr 18, 2018 at 13:02
  • @Stefanos Petrakis: Yes, exactly
    – Hudri
    Apr 18, 2018 at 13:34

2 Answers 2

5
+50

One way to accomplish this would be by using a service tagged with the path_processor_outbound service tag.
Your service should then implement the processOutbound() function in order to modify all links on a page, if the request delivers a node object of the type you want.

This is an example service definition using the described service tag:

services:
  dummy.link_modifier_service:
    class: Drupal\dummy\DefaultService
    tags:
      - { name: path_processor_outbound }
    arguments: ['@request_stack']

This is an example implementation of such a service:

<?php

namespace Drupal\dummy;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Render\BubbleableMetadata;

/**
 * Class DefaultService.
 */
class DefaultService implements DefaultServiceInterface, OutboundPathProcessorInterface {

  /**
   * Symfony\Component\HttpFoundation\RequestStack definition.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;
  /**
   * 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 ($node && $node->bundle() === 'product') {
      $options['query'][] = [$node->bundle() . '_id' => $node->id()];
    }
    return $path;
  }  

}

This code will now be modifying all Drupal-handled URLs and adding a product_id=XX parameter to them.

Hope this helps, good luck!

@see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21PathProcessor%21OutboundPathProcessorInterface.php/function/OutboundPathProcessorInterface%3A%3AprocessOutbound/

4
  • Thanks, this worked for both, link fields and links inside text fields / CKeditor. @Stefanos, it might be helpful for others to include a services.yml example in your answer, I didn't know first how to use the tags. You can copy it from my Gist
    – Hudri
    Apr 19, 2018 at 10:19
  • This works great. I notice the <link> tags in the head also inherits the changes (i.e. canonical, alternate, shortlink, etc).
    – osman
    Apr 19, 2018 at 23:12
  • @Hudri happy it worked, thanks for the gist, I updated the answer accordingly, including a mod version of it. Apr 20, 2018 at 7:27
  • Addendum: This solution also needs a cache context for links in blocks or menus, otherwise you might get cached links without added parameters.
    – Hudri
    Aug 23, 2018 at 6:57
2

If you want to alter all links then you need to adjust the input for the preRenderLink and add the query to the element before its generated.

Firs you implement this hook_element_info_alter to use your own class which extends the core Link class

/**
 * Implements hook_element_info_alter().
 */
function custom_element_info_alter(array &$info) {
  $info['link']['#pre_render'] = [
    [Drupal\custom\Links\CustomLink::class, 'preRenderLink'],
  ];
}

You extends the Link class and add your check if true then you can inject the parameters to your query

namespace Drupal\custom\Links;
use Drupal\Core\Render\Element\Link;
use Drupal\node\NodeInterface;

class CustomLink extends Link {

  public static function preRenderLink($element) {
    $node = \Drupal::routeMatch()->getParameter('node');
    if ($node instanceof NodeInterface && $node->getType() == 'product') {
      $element['#options']['query']['product_id'] = $node->id();
    }

     return parent::preRenderLink($element);
  }

}

Another solution can be with javascript which not recommended. You can take look how extlink module is changing the links icon based on the link if it's external or not.

8
  • Sorry, but I don't think this is related to my problem. If I understand it correctly, this modifies how the webform field is rendered on the node. But my problem is with links to the node anywhere on the site, e.g. a link to the enquiry node in a menu or a block.
    – Hudri
    Apr 18, 2018 at 13:41
  • Sorry for the unclear question, I've edited it according to @Stefanos Petrakis' comment on the question to make it more clear.
    – Hudri
    Apr 18, 2018 at 13:46
  • @Hudri that what you want?
    – Ziftman
    Apr 18, 2018 at 16:01
  • Thanks, this is the starting point I needed, first tests look very promising
    – Hudri
    Apr 19, 2018 at 9:06
  • Hm, seems to work very well with link fields, but not with links inside CKeditor / a textfield.
    – Hudri
    Apr 19, 2018 at 9:20

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.