Problem/Motivation

When sending out the registration emails, the profile isn't linked to the user yet, so in \profile_tokens when fetching the profile around line 50, there is no profile to be loaded.

Steps to reproduce

Create a profile type, add in on the registration form. Use a token from that profile type in one of the registration mails that are triggered.

Proposed resolution

Have a fallback to fetch the profile because storage hasn't gone through yet at that point.

Remaining tasks

User interface changes

API changes

Data model changes

Comments

daften created an issue. See original summary.

finne’s picture

just sharing a workaround I'm using

/**
 * Implementation of hook_mail_alter().
 */
function custom_module_mail_alter(&$message) {
  switch ($message['id']) {
    case "user_register_admin_created":
    case "user_register_no_approval_required":
    case "user_register_pending_approval":
    case "user_password_reset":
    case "user_status_blocked":
    case "user_status_deleted":
      /** @var \Drupal\user\Entity\User $user */
      $user = $message['params']['account'];
      $profile_ref = $user->user_profiles->first();
      if ($profile_ref) {
        $profile = $profile_ref->entity;
        if ($profile) {
          $token_replacements = [
            '!user_first_name' => $profile->field_first_name->value,
            '!user_last_name' => $profile->field_last_name->value,
          ];
          $message['subject'] = strtr($message['subject'], $token_replacements);
          $message['body'][0] = strtr($message['body'][0], $token_replacements);
        }
      }
      break;
  }
}
chi’s picture

chi’s picture

StatusFileSize
new2.99 KB
mglaman’s picture

Status: Needs review » Needs work
  1. +++ b/profile.tokens.inc
    @@ -46,9 +46,15 @@ function profile_tokens($type, array $tokens, array $data, array $options, Bubbl
    -        /** @var \Drupal\profile\ProfileStorageInterface $storage */
    -        $storage = \Drupal::entityTypeManager()->getStorage('profile');
    -        $profile = $storage->loadByUser($user, $profile_type);
    +        // Fetch the profile from user entity as it might not be available in
    +        // the storage yet during registration process.
    +        $field_name = $profile_type . '_profiles';
    +        if ($user->hasField($field_name)) {
    +          $profile_field = $user->get($field_name);
    +          if (!$profile_field->isEmpty()) {
    +            $profile = $profile_field->entity;
    +          }
    +        }
    

    I'm not sure how this works. While it is nice re-using our computed entity reference field, it's doing the same thing as the computed field.

    https://git.drupalcode.org/project/profile/-/blob/8.x-1.x/src/Plugin/Fie...

    Unless somehow the field item list is prepopulated and we're able to fetch them statically?

  2. +++ b/tests/src/Kernel/ProfileTokenTest.php
    @@ -92,24 +92,28 @@ class ProfileTokenTest extends EntityKernelTestBase {
    +    $user = $this->createUser();
    +
    ...
    -      'uid' => $this->user->id(),
    +      'uid' => $user->id(),
    ...
    +    $user->get($profile->bundle() . '_profiles')->appendItem(['target_id' => $profile->id()]);
    ...
    -    $field_token_output = $token_service->replace($field_token, ['user' => $this->user]);
    +    $field_token_output = $token_service->replace($field_token, ['user' => $user]);
    ...
    -    $entity_token_output = $token_service->replace($entity_token, ['user' => $this->user]);
    +    $entity_token_output = $token_service->replace($entity_token, ['user' => $user]);
    

    I'm not sure what this change accomplishes?

chi’s picture

I'm not sure how this works. While it is nice re-using our computed entity reference field, it's doing the same thing as the computed field.

I was also wondering how computed field fixes the issue. Note that workaround in #2 also relies on it.

I'm not sure what this change accomplishes?

The user needs to be created after configuring profile type. See failed patch in #3.

mglaman’s picture

Status: Needs work » Needs review

The user needs to be created after configuring profile type. See failed patch in #3.

Ah, I see :). That makes sense, because the user object is stale and does not have the computed field reference, yet.

It has to magically work due to the fact the profile form is attached as the field widget. I don't quite understand how, as I read \Drupal\profile\Plugin\Field\FieldWidget\ProfileFormWidget::saveProfiles

  public static function saveProfiles(array $form, FormStateInterface $form_state) {
    /** @var \Drupal\Core\Session\AccountInterface $account */
    $account = $form_state->getFormObject()->getEntity();
    if (!$account) {
      return;
    }
    $profiles = $form_state->get('profiles');
    foreach ($profiles as $profile) {
      assert($profile instanceof ProfileInterface);
      $profile->setOwnerId($account->id());
      $profile->setPublished();
      $profile->save();
    }
  }

But that also shows that the profile has a user ID attached. Which is weird the query call is empty in token.

No idea. It'd be nice to debug \Drupal\profile\Plugin\Field\ProfileEntityFieldItemList::computeValue and get an idea of "how" this works.

Otherwise the fix isn't controversial. We're normalizing how we access profiles for a user by leveraging the computed field.

finne’s picture

I tested this on a D8.9 installation and it works fine. User profile fields are now available in the registration mails.

loze’s picture

StatusFileSize
new3 KB

#4 wouldn't apply to the latest dev. here is a reroll

soulreceiver’s picture

Tested this latest patch on a Drupal 9.5.11 instance running Profile 1.7.0 and it works wonderfully!

longwave’s picture

Status: Needs review » Reviewed & tested by the community

We are using #9 in production to send registration emails containing profile fields and it works great.