Hello Team,

I am facing an issue when using [current-user:uid] token in the menu for the authenticated user. I have created a menu "My Dashboard" with URL /user/[current-user:uid]/dashboard. The token is getting replaced with the currently authenticated user until at very same time some other authenticated user access pages. Request finishing in last have a wrong replacement for [current-user:uid]

Example:
User 1 : John Smith (UID - 1090)
User 2 : Mark Web (UID - 1406)

I am testing it on the same computer with the different browsers. User 1 login in a browser on a machine. User 2 login on a different browser on the same machine. They both see the same menu in the top (Menu render from the block in navigation region). Now if I refresh both browsers at the same time (before request completes on the first browser), the link is not correct for request finishing later.

Can this be resolved from some settings in menu or configurations?

Comments

manojsaini created an issue. See original summary.

ariane’s picture

I have exactly the same error.

In my case I made a custom link with the path /user/[current-user:uid]/edit in a new block menu.

Sometimes the token replacement changes to user/not-assigned-yet/edit even when the user is logged in.

aperedos’s picture

+1

godotislate’s picture

We ran into the same issue, and the issue is this:

  1. menu_token has an event subscriber that listens for the event that is dispatched when the controller has been found.
  2. At that point, menu_token replaces all the tokenized menu link URL data stored in the menu_tree database table with the token values.
  3. Later in the pipeline, the renderer loads the menu link data from the menu_tree table.
  4. When there are multiple nearly simultaneous requests, between the time the first request has updated the menu_tree table and when it loads from that table to render the menu, the later requests could update the table. So when the first request loads the menu_tree data for rendering, the menu link tokens have been replaced with a different request's token data.

Our less than desirable workaround was to add some code to re-replace the the token data in the links in the menu block render arrays after they have been built.

I think a better solution for the module would be to eschew updating the menu_tree table with replaced token values, and instead replace the tokens after menu tree data is loaded before menu is rendered.

eahonet’s picture

@godotislate can you elaborate on your solution? I'm running into some issues using menu_token. Sometimes it won't output a current-user value after a clear cache. Your comments have me worried once I start having multiple users testing the site.

thank you.

godotislate’s picture

@eahonet, it's been a year or since I worked on the project using menu_token, so my memory is a little vague on how it works. There's probably a better workaround, but basically, I created a your_module.menu_builder service that looks like this:

services:
  your_module.menu_builder:
    class: Drupal\your_module\Render\MenuBuilder
    arguments: ['@plugin.manager.block', '@state', '@token']

The service class implements an interface:

<?php

namespace Drupal\your_module\Render;

/**
 * Defines an interface for building a render array for a menu.
 */
interface MenuBuilderInterface {

  /**
   * Builds a render element for a menu based on configuration.
   *
   * @param string $menu_name
   *   The menu name.
   * @param array $options
   *   The menu configuration options.
   *
   * @return array
   *   The render element for the menu.
   */
  public function buildMenu($menu_name, array $options = []);

}

And the service class:

<?php

namespace Drupal\your_module\Render;

use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Render\BubbleableMetadata;;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Token;

/**
 * Defines an interface for turning a render array into a string.
 */
class MenuBuilder implements MenuBuilderInterface {

  /**
   * The block manager.
   *
   * @var \Drupal\Core\Block\BlockManagerInterface
   */
  protected $blockManager;

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * Renderer constructor.
   *
   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
   *   The block manager.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Drupal\Core\Utility\Token $token
   *   The token service.
   */
  public function __construct(BlockManagerInterface $block_manager, StateInterface $state, Token $token) {
    $this->blockManager = $block_manager;
    $this->state = $state;
    $this->token = $token;
  }

  /**
   * {@inheritdoc}
   */
  public function buildMenu($menu_name, array $config = []) {
    $menu_id = "menu_block:{$menu_name}";
    $config += [
      'expand' => 1,
      'depth' => 0,
      'level' => 1,
      'menu_name' => $menu_name,
    ];

    $menu = $this->blockManager->createInstance($menu_id, $config);
    $build = $menu->build();

    if (!empty($build['#items'])) {
      // There is a race condition introduced by the menu_token module. The way
      // it works is that it has an event subscriber that listens for when
      // the controller has been loaded. At that point menu_token loads the list
      // of links that use tokens in the URL or title, and replaces those
      // tokens with the respective values. Then the link gets saved back to the
      // menu_tree DB table with the token-replaced URL and text. If two
      // requests come in nearly simultaneously, after the first request process
      // has updated the menu_tree table, but before its renderer loads the
      // menu, the second request process can change the menu_tree table, and
      // thus the first renderer loads menu tree links with incorrectly replaced
      // values.  The code below reruns the token replacement on the menu links
      // in the render array so that everything is set straight.
      $contextual_replacement_links = unserialize($this->state->get('menu_token_links_contextual_replacements'));
      $bubbleable_metadata = new BubbleableMetadata();
      foreach ($build['#items'] as $id => &$item) {
        if (isset($contextual_replacement_links[$id])) {
          $link_data = $contextual_replacement_links[$id];
          // Replace the tokens in the URL and account for cache metadata.
          $uri = $this->token->replace($link_data['link']['url'], [], [
            'configuration' => $link_data['config'],
          ], $bubbleable_metadata);
          // Replace the tokens in the title and account for cache metadata.
          $title = $this->token->replace($link_data['link']['title'], [], [
            'configuration' => $link_data['config'],
          ], $bubbleable_metadata);

          // Update the URL and title for the link item in $build render array.
          $item['url'] = Url::fromUri($uri);
          $item['title'] = $title;
        }
      }
      // Apply caching appropriately to the render array.
      $bubbleable_metadata->applyTo($build);
    }

    return $build;
  }

}

Then, when assembling/altering the render array for the page/block/element/what have you, call the service's buildMenu() method and use the returned render array as needed.

vuil’s picture

Category: Bug report » Feature request
Priority: Major » Normal

Update the issue to be "Feature request" because it is.

stijndmd’s picture

I don't see why this would be a feature request, to be fair. The token replacement for [current-user:uid] does not work.
It either straight up doesn't replace the "[current-user:uid]" string to the user id, or it changes the token replacement to "not-assigned-yet".

What was posted is a workaround to achieve the same goal.

lamp5’s picture

Category: Feature request » Bug report
develcuy’s picture

Still awaiting for a patch guys

vuil’s picture

I added my employer only.

kenton.r’s picture

This is because Views converts the link to a route and the when context looks to rebuild the link it is only looking for a url path. The patch #27 in menu_token/issues/2921178 looks for routes and promotes URL to matching routes to maintain route permissions.

develcuy’s picture

Status: Active » Closed (duplicate)