eca_content - "Entity: load via reference" action - tokens on field ref

Created on 14 March 2023, over 1 year ago
Updated 20 March 2023, over 1 year ago

Problem/Motivation

I have a scenario where I have a node with several form modes with entity references in each of them and those entity references have entity reference to a navigation content which I need to use later one to load a given entity reference in the main node. Sounds confusing, I know :). Please see the screenshot attached.

In short I need to use tokens on the The field name of the entity reference.

Proposed resolution

Replace $reference_field_name = $this->configuration['field_name_entity_ref']; with:
$reference_field_name = trim((string) $this->tokenServices->replaceClear($this->configuration['field_name_entity_ref'] ?? ''));
On web/modules/contrib/eca/modules/content/src/Plugin/Action/LoadEntityRef.php

Feature request
Status

Fixed

Version

1.2

Component

Code

Created by

🇬🇧United Kingdom lexsoft London

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

Comments & Activities

  • Issue created by @lexsoft
  • 🇬🇧United Kingdom lexsoft London
  • @lexsoft opened merge request.
  • Status changed to Needs review over 1 year ago
  • 🇬🇧United Kingdom lexsoft London
  • Status changed to Active over 1 year ago
  • 🇩🇪Germany jurgenhaas Gottmadingen

    This looks great, thanks a lot @lexsoft for your contribution.

    Just 2 things I'd like to ask for:

    • The token replacement looks a bit too complicated. In other places we simply have e.g. $name = $this->tokenServices->replace($this->configuration['field_name']); which seems perfectly sufficient. Any reason we could do it that way in this case too?
    • Would you mind providing a simple test to verify that this also works with and without a token?
  • 🇬🇧United Kingdom lexsoft London

    Hi @jurgenhaas,

    1. This should also work $name = $this->tokenServices->replace($this->configuration['field_name']). I've just took a copy from the work was done on same module.

    2. Will provide a simple example with and without token.

  • 🇩🇪Germany mxh Offenburg

    Will provide a simple example with and without token.

    Ideally this may be covered with a Kernel test. There is already Drupal\Tests\eca_content\Kernel\LoadEntityRefTest, which could be extended by your suggested example.

  • 🇬🇧United Kingdom lexsoft London

    Please find attached a simple example of usage using load via ref with tokens and without.
    Modules used:

     "drupal/eca": "^1.1",
     "drupal/bpmn_io": "^1.1",
     "drupal/token": "^1.11"
    

  • 🇬🇧United Kingdom lexsoft London

    @mxh I have tried to extend the test Drupal\Tests\eca_content\Kernel\LoadEntityRefTest but I'm running into:

    Testing Drupal\Tests\eca_content\Kernel\LoadEntityRefTest
    E                                                                   1 / 1 (100%)
    
    Time: 00:01.036, Memory: 4.00 MB
    
    There was 1 error:
    
    1) Drupal\Tests\eca_content\Kernel\LoadEntityRefTest::testLoadEntityRef
    InvalidArgumentException: Field [node:field_node_ref_mn] does not exist for entity type node/article.
    
    /var/www/html/web/modules/contrib/eca/modules/content/src/Plugin/Action/LoadEntityRef.php:61
    /var/www/html/web/modules/contrib/eca/modules/content/src/Plugin/Action/LoadEntity.php:76
    /var/www/html/web/modules/contrib/eca/modules/content/tests/src/Kernel/LoadEntityRefTest.php:220
    /var/www/html/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
    
    ERRORS!
    Tests: 1, Assertions: 12, Errors: 1.

    Not sure what I've done wrong but here is my try:

    public function testLoadEntityRef() {
        // Create the Article content type with revisioning and translation enabled.
        /** @var \Drupal\node\NodeTypeInterface $node_type */
        $node_type = NodeType::create([
          'type' => 'article',
          'name' => 'Article',
          'new_revision' => TRUE,
        ]);
        $node_type->save();
        ContentLanguageSettings::create([
          'id' => 'node.article',
          'target_entity_type_id' => 'node',
          'target_bundle' => 'article',
          'default_langcode' => LanguageInterface::LANGCODE_DEFAULT,
          'language_alterable' => TRUE,
        ])->save();
        // Create a reference field.
        $field_definition = FieldStorageConfig::create([
          'field_name' => 'field_node_ref',
          'type' => 'entity_reference',
          'entity_type' => 'node',
          'settings' => [
            'target_type' => 'node',
          ],
          'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
        ]);
        $field_definition->save();
        $field = FieldConfig::create([
          'field_storage' => $field_definition,
          'label' => 'A node reference.',
          'entity_type' => 'node',
          'bundle' => 'article',
        ]);
        $field->save();
    
        // Create a plaintext field to be used as token.
        FieldStorageConfig::create([
          'field_name' => 'field_node_ref_mn',
          'type' => 'string',
          'entity_type' => 'node',
          'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
        ])->save();
        FieldConfig::create([
          'field_name' => 'field_node_ref_mn',
          'label' => 'The reference field machine name.',
          'entity_type' => 'node',
          'bundle' => 'article',
        ])->save();
    
        // Create a reference field target for token.
        FieldStorageConfig::create([
          'field_name' => 'field_node_ref_target_token',
          'type' => 'entity_reference',
          'entity_type' => 'node',
          'settings' => [
            'target_type' => 'node',
          ],
          'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
        ])->save();
        FieldConfig::create([
          'field_name' => 'field_node_ref_target_token',
          'label' => 'A node reference target token.',
          'entity_type' => 'node',
          'bundle' => 'article',
        ])->save();
    
    
        /** @var \Drupal\Core\Action\ActionManager $action_manager */
        $action_manager = \Drupal::service('plugin.manager.action');
        /** @var \Drupal\eca\Token\TokenInterface $token_services */
        $token_services = \Drupal::service('eca.token_services');
        /** @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
        $account_switcher = \Drupal::service('account_switcher');
    
        $referenced = Node::create([
          'type' => 'article',
          'title' => 'I am a referenced node.',
          'langcode' => 'en',
          'uid' => 1,
          'status' => 0,
        ]);
        $referenced->save();
    
        $referenced_by_token = Node::create([
          'type' => 'article',
          'title' => 'I am a referenced node using tokens.',
          'langcode' => 'en',
          'uid' => 1,
          'status' => 0,
        ]);
        $referenced_by_token->save();
    
        $node = Node::create([
          'type' => 'article',
          'title' => '123',
          'langcode' => 'en',
          'uid' => 1,
          'status' => 0,
        ]);
        $node->save();
        $first_vid = $node->getRevisionId();
        $node->title = '456';
        $node->field_node_ref->target_id = $referenced->id();
        $node->field_node_ref_target_token->target_id = $referenced_by_token->id();
        $node->field_node_ref_mn->value = 'field_node_ref_target_token';
        $node->setNewRevision(TRUE);
        $node->save();
    
        // Create an action that that loads the referenced entity.
        /** @var \Drupal\eca_content\Plugin\Action\SetFieldValue $action */
        $defaults = [
          'token_name' => 'mynode',
          'from' => 'current',
          'entity_type' => '_none',
          'entity_id' => '',
          'revision_id' => '',
          'properties' => '',
          'langcode' => '_interface',
          'latest_revision' => FALSE,
          'unchanged' => FALSE,
          'field_name_entity_ref' => 'field_node_ref',
        ];
        /** @var \Drupal\eca_content\Plugin\Action\LoadEntity $action */
        $action = $action_manager->createInstance('eca_token_load_entity_ref', [] + $defaults);
        $this->assertFalse($action->access($node), 'User without permissions must not have access.');
    
        // Now switch to priviledged user.
        $account_switcher->switchTo(User::load(1));
    
        /** @var \Drupal\eca_content\Plugin\Action\LoadEntity $action */
        $action = $action_manager->createInstance('eca_token_load_entity_ref', [] + $defaults);
        $this->assertTrue($action->access($node), 'User with permissions must have access.');
        $this->assertFalse($token_services->hasTokenData('mynode'), 'Token must not yet be defined.');
        $action->execute($node);
        $this->assertTrue($token_services->hasTokenData('mynode'), 'Token must be defined.');
        $this->assertSame($referenced->id(), $token_services->getTokenData('mynode')->id());
    
        $token_services->addTokenData('node', $node);
        /** @var \Drupal\eca_content\Plugin\Action\LoadEntity $action */
        $action = $action_manager->createInstance('eca_token_load_entity_ref', [
            'field_name_entity_ref' => '[node:field_node_ref_mn]',
          ] + $defaults);
        $action->execute($node);
        $this->assertTrue($token_services->hasTokenData('mynode'), 'Token must be defined.');
        $this->assertSame($referenced_by_token->id(), $token_services->getTokenData('mynode')->id());
    
  • 🇩🇪Germany mxh Offenburg

    @lexsoft That's a very nice test implementation! The token "[node:field_node_ref_mn]" won't get replaced in this test, because the contrib Token module is not installed. Node tokens will only be replaced when contrib Token module is installed. The property protected static $modules in the kernel test could be extended by the token module.

    Alternatively without using contrib Token module, you could just add a token yourself by using $token_services->addTokenData('ref_field_name', 'field_node_ref') and then use that token [ref_field_name] in the action to test.

    If you get stuck again, just let us know here. We're happy to help get this going.

  • 🇬🇧United Kingdom lexsoft London

    @mxh Brilliant! Thanks a lot, it works now.

  • 🇬🇧United Kingdom lexsoft London
  • Status changed to Needs review over 1 year ago
  • 🇬🇧United Kingdom lexsoft London
  • Status changed to RTBC over 1 year ago
  • 🇩🇪Germany mxh Offenburg

    The MR is looking good. This needs to be merged into 1.2.x first, then we may consider backporting this into 1.1.x and 1.0.x (The MR is currently set to be merged into 1.1.x). Backport should be fine IMO. Although it's a new feature rather than a bug fix, this change should not break existing configurations as token syntax wouldn't work on that configuration field until now.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    Amazing work, thank you so much.

    While re-basing this to 1.2.x I realized that we had a test in the 1.1.x branch which was missing in 1.2.x - no idea how that came about. That formerly missing test is now also part of this MR after the rebase, it came from Issue #3344752 by boromino, mxh, jurgenhaas: Config read without overridden always empty

    Another quick question, that confuses me a bit: as we now added token to the list of modules, how is that even possible for a contrib module which is not a dependency?

  • 🇩🇪Germany mxh Offenburg

    Another quick question, that confuses me a bit: as we now added token to the list of modules, how is that even possible for a contrib module which is not a dependency?

    I guess Drupal's test infrastructure automatically installs required dev packages that are defined in composer.json. We're also already using contrib token in one other kernel test.

  • Status changed to Fixed over 1 year ago
  • 🇩🇪Germany jurgenhaas Gottmadingen

    Ah, it's in require-dev and that certainly gets loaded for tests.

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.69.0 2024