Support multiple instances of the same token type in token replacement

Created on 18 February 2013, over 11 years ago
Updated 14 March 2024, 4 months ago

Problem/Motivation

Is not possible to use two tokens of same type when replacing tokens in a string. For example:

$string = 'Node [node1:title] points to [node2:title]';
$string = token_replace($string, array('node1' => $node1, 'node2' => $node2));

Proposed resolution

All modules that implements hook_tokens() expect their tokens (declared in hook_token_info()) to have a certain type name ('node', 'user', etc) so we can't change this behavior because all modules implementing this hook should be updated.

Instead, the approach is to add an 'alias' to the type, and internally strip this alias and call token_generate with the clean type name.

For example, the first example using this approach:

$string = 'Node [node{pointer}:title] points to [node{pointed}:title]';
$string = token_replace($string, array('node{pointer}' => $node1, 'node{pointed}' => $node2));

The modified code is token_replace() function. After getting all standard tokens a new loop is added to process the token alias. For each token type a new token_generate call is made, passing the stripped token (without the alias name chunk) and the filtered data array with the object related to this alias.

For the previous example two additional token_generate() calls are made:

  • first call: $type is 'node', $tokens is first token alias tokens stripped ('title' => '[node:title]'), and $data is the filtered data ('node' => $node1).
  • second call: $type is 'node', $tokens is first token alias tokens stripped ('title' => '[node:title]'), and $data is the filtered data ('node' => $node1).

Remaining tasks

Add tests to test functionality in deep to allow start a discussion on this approach. Having test coverage would work very well as examples and basis for such a discussion.

Add documentation for this feature.

User interface changes

No UI changes.

API changes

Added new token_alias_scan() function similar to token_scan but scans for token alias.

Additional notes

โœจ Feature request
Status

Needs work

Version

11.0 ๐Ÿ”ฅ

Component
Tokenย  โ†’

Last updated 5 days ago

No maintainer
Created by

๐Ÿ‡บ๐Ÿ‡ธUnited States Dave Reid Nebraska ๐Ÿ‡บ๐Ÿ‡ธ

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Open thread in the MR.

    Also why in the MR did you add phpstan-baseline update? Don't see that in the patch before.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia Bhanu951

    @smustgrave

    Updated baseline due to this PHPStan error https://www.drupal.org/pift-ci-job/2558692 โ†’

    
    Running PHPStan on *all* files.
     ------ ---------------------------------------------------------------------- 
      Line   core/lib/Drupal/Core/Utility/Token.php                                
     ------ ---------------------------------------------------------------------- 
             Ignored error pattern #^Variable \$aliased_token_name might not be    
             defined\.$# in path                                                   
             /var/www/html/core/lib/Drupal/Core/Utility/Token.php was not matched  
             in reported errors.                                                   
      269    Variable $alias_tokens_mapping might not be defined.                  
     ------ ---------------------------------------------------------------------- 
    
    
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    That seems like a valid warning that may need to be addressed vs ignored.

    Would have to compare to previous patch though and see whatโ€™s different in the MR.

  • ๐Ÿ‡จ๐Ÿ‡ญSwitzerland Berdir Switzerland

    Agreed, new problems should not be ignored but fixed.

  • ๐Ÿ‡ฉ๐Ÿ‡ชGermany ro-no-lo

    Hi, for everyone coming to this issue after 10+ years, I have created a small Token extension module, were aliased tokens can be used in a programmatically way. It is a different approach, in syntax, but if you are desperate you might give it a try.

    https://www.drupal.org/project/token_aliases โ†’

    (You may have to use it from the git directly.)

    Usage:

      $token_service = \Drupal::service('token_aliases');
      $string = 'Hello [user1:username], we have news from [user2:username] see [node:title] and [my_node:title] for more info.';
      // Fully loaded User entities which are keys with the aliases used in the string.
      $data = [
        'user1' => $user1,
        'user2' => $user2,
        'node' => $node,
        'my_node' => $my_node,
      ];
      $result = $token_service->plainReplace($string, $data);
    
  • last update 12 months ago
    Custom Commands Failed
  • last update 12 months ago
    Custom Commands Failed
  • last update 12 months ago
    29,804 pass
  • last update 12 months ago
    29,804 pass
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium Grayle

    There's a bug with the latest MR. The variables should be reset a loop deeper.

    I do not have time to make a patch or MR, but the main block should look like this:

        foreach ($text_token_aliases as $type => $text_token_alias) {
          foreach ($text_token_alias as $alias => $tokens) {
            // Reset the variables in this loop.
            $alias_tokens_mapping = [];
            $data_for_current_token = [];
            $unaliased_tokens = [];
            foreach ($tokens as $token => $unaliased_token_name) {
              $unaliased_tokens[$token] = "[{$unaliased_token_name}]";
              $alias_tokens_mapping["[{$unaliased_token_name}]"] = "{$type}{{$alias}}:$token";
            }
            // @todo Check if need to add else condition ?
            if (isset($data["{$type}{{$alias}}"])) {
              $data_for_current_token[$type] = $data["{$type}{{$alias}}"];
              $replacements_unaliased = $this->generate($type, $unaliased_tokens, $data_for_current_token, $options, $bubbleable_metadata);
    
              foreach ($replacements_unaliased as $unaliased_token_name => $value) {
                $aliased_token_name = $alias_tokens_mapping[$unaliased_token_name];
                $replacements["[{$aliased_token_name}]"] = $value;
              }
            }
          }
        }
    

    Otherwise you get very weird issues. Easy way to test:

    
    function test_token_reps() {
      $data = [
        'user{target}' => User::load(1),
        'user{source}' => User::load(0),
        'user{thirdy}' => User::load(6),
      ];
    
      $text = "
      Target UID: [user{target}:uid]
      Target Name: [user{target}:name]
      Source Name: [user{source}:name]
      Thirdy UID: [user{thirdy}:uid]
      Source Name: [user{source}:name]
      Target Mail: [user{target}:mail]
      Thirdy Mail: [user{thirdy}:mail]
      ";
    
      $text = Drupal::token()->replace($text, $data);
    
      echo $text;
    }
    

    The thing is that you can't use the same fields for all the aliases, gotta have some diffs. Then it goes haywire.

Production build 0.69.0 2024