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.

  • This might be of help drupal.stackexchange.com/questions/198507/… – GiorgosK Apr 17 '18 at 19:05
  • @Hidri, How you reference the webfrom from the node? using web form reference filed? – Ziftman Apr 18 '18 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 '18 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? – Stefanos Petrakis Apr 18 '18 at 13:02
  • @Stefanos Petrakis: Yes, exactly – Hudri Apr 18 '18 at 13:34
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/

  • 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 '18 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 '18 at 23:12
  • @Hudri happy it worked, thanks for the gist, I updated the answer accordingly, including a mod version of it. – Stefanos Petrakis Apr 20 '18 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 '18 at 6:57
1

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.

  • 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 '18 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 '18 at 13:46
  • @Hudri that what you want? – Ziftman Apr 18 '18 at 16:01
  • Thanks, this is the starting point I needed, first tests look very promising – Hudri Apr 19 '18 at 9:06
  • Hm, seems to work very well with link fields, but not with links inside CKeditor / a textfield. – Hudri Apr 19 '18 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.