Jump to content

Send email with changes when user profile is changed


Torsten Baldes
 Share

Recommended Posts

Hi,

I'm building a site with user profiles, where the users can change and update their profile.

For different reasons it's important that the person who oversees these users get's an email, when a user updates his profile. At best with all the values that changed and their value before. 

I tried to hook into User::changed and this kind of works, but it get's triggered for every field, that's changed. With each trigger the list of fields also get's longer, so that only the last run contains all the changed fields.

This would mean, i would send multiple emails for one profile change. Is there a way to prevent this and get all changes at once in only one hook run?

This is my code so far (in ready.php):

wire('user')->setTrackChanges(Wire::trackChangesOn | Wire::trackChangesValues);

wire()->addHookAfter("User::changed", null, function($event){
	$user = $event->object;
	$changes = $user->getChanges(true);

	// output changes as log entry
	wire('log')->save('testchangelog', print_r($changes, true) );

	// TODO: send mail
});

 

Thanks!

Link to comment
Share on other sites

Hi @Torsten Baldes, you could try the following:

wire('user')->setTrackChanges(Wire::trackChangesOn | Wire::trackChangesValues);

wire()->addHookAfter('Pages::save', function($event) {  // >>> ON PAGE SAVE
	$page = $event->arguments(0);

	if($page->template->name === 'user') { // >>> APPLY ONLY TO USER TEMPLATE
      $changes = user()->getChanges(true);

      // output changes as log entry
      wire('log')->save('testchangelog', print_r($changes, true) );

      $m = new WireMail();
      $m->from('Your email From');
      $m->to('Your email To');
      $m->subject('Your email subject');
      $m->body('Your email text');
      $m->send();

	}
});

Note: made a couple of corrections and changed to WireMail as per @horst suggestion. Still testing if changes are recorded...

  • Like 3
Link to comment
Share on other sites

1 hour ago, Edison said:

@mail($receipt, $subject, $body, "From:{$sender}");

Hi @Edison, if this shows the old and the new values inlog and mail, this is really good! (Will test this later when back in buero).

Only thing that should be changed is the use of (PHP) @mail function. Instead you should use wireMail(). :)

  • Like 1
Link to comment
Share on other sites

Thanks for your help!

For now I ended up with something like this:

wire('user')->setTrackChanges(Wire::trackChangesOn | Wire::trackChangesValues);

wire()->addHookAfter("Pages::saved", null, function($event){
	$user = $event->arguments(0);

	if($user->template->name == 'user'){

		$userfields = $user->template->fieldgroup;
		$excludeFields = ['pass', 'roles', 'language', 'admin_theme', 'schedulerSkip', 'cpf_added'];
	
		$changes = $event->arguments(1);


		// check if there are any changes, if it's not an InputfieldFileAjax (also triggers the hook), the user changed his own profile, if the changed user is not a superuser (could be any other role to exclude)
		if(count($changes) && !wire('input')->get->InputfieldFileAjax && wire('user')->id == $user->id && !$user->hasRole('superuser')){

			$userfieldsArray = array();
			foreach($userfields as $userfield){
				$userfieldsArray[] = $userfield->name;
			}

			//remove unwanted fields
			$changes = array_diff($changes, $excludeFields);

			// order changed fields like template
			$changesSorted = array();
			foreach ($userfieldsArray as $item) {
				if(in_array($item, $changes)){
					$changesSorted[] = $changes[$item];
				}
			}
			$changes = $changesSorted;
			unset($changesSorted); // probably not necessary
			
			// get the changes in a string
			$changedFields = '';
			foreach($changes as $change){
				$changedFields .= $userfields->get($change)->label . "\r\n";
			}
			
			// compose the mail
			$subject = "Änderung im Nutzerprofil von  »".$user->u_fullname."«";

			$message =  "Hallo,\r\n\r\n das Nutzerprofil von ".$user->u_fullname." wurde aktualisiert.\r\nFolgende Felder haben sich geändert:\r\n\r\n" ;
			$message .= $changedFields;
			$message .= "\r\n\r\n" ;
			$message .= "Klicken Sie hier, um das Profil zu bearbeiten: ".trim(wire('urls')->httpRoot, '/').$user->editUrl ;
			$message .= "\r\n\r\n\r\n";

			$mailmessage = wire('mail')->new();
			$mailmessage->to('mail@domain.tld')->from($user->email);
			$mailmessage->subject($subject)->body($message);
			$sent = $mailmessage->send();

		}
	}
});

I decided that it's not necessary to send the before and after values within the email. This could even be a privacy and/or security issue.

Thanks again for your ideas and help!

Link to comment
Share on other sites

  • 2 years later...

Sorry to reopen this old topic, but I have a question about this exact topic.

How can I restrict the sending of emails even further and ONLY if there has been a change to the template user (see code above) AND ONLY for the roles, for example?

In other words, please send an email if there is a change in a role (or a field xy) for a user and DO NOT send an email for all other changes for a user!

Link to comment
Share on other sites

@androbey

Thanks for the tip, I think that's exactly what I'm looking for. BUT unfortunately I can't get it to work like in the example...

If I use the code from @Edison above, it basically works.

But if I extend the if-line - like this

if($page->template->name === 'user' && $page->isChanged('roles')) { // >>> APPLY ONLY TO USER TEMPLATE

and if I can't address roles directly but have to iterate, I also tried it with a "normal" textfield e.g. like this

if($page->template->name === 'user' && $page->isChanged('surname')) { // >>> APPLY ONLY TO USER TEMPLATE

OR the if-statement also separately after the if-statement of the template "user"...

if($page->template->name === 'user') { // >>> APPLY ONLY TO USER TEMPLATE
	$changes = user()->getChanges(true);

	if($page->isChanged('surname')){
		// LOG & send email from @Edison
	}
}

But with all my attempts I don't get an email anymore 😞

Is this related to the user template or have I simply implemented the code incorrectly?

Link to comment
Share on other sites

@Ralf You're right, my bad. It seems "isChanged()" method does not work on User template. But you still have two options. 

1) Tweek the code from above and check if the field you want to monitor is in changes array. (e.g. in_array('roles', $changes)).

2) Use a slightly different approach with User::changed hook. You have to modify to fit your needs, but here's a very basic example.

wire()->addHookAfter('User::changed', function (HookEvent $event) {
    $user = $event->object;
    $changedField = $event->arguments(0);
	
	//which field you want to monitor
    $fieldToCheck = "roles";

    if ($fieldToCheck == $changedField) {
        //send mail.. or do more checks..
    }

    $return = $event->return;
    $event->return = $return;
});

Edit: the second approach is ONLY sensible if you want to check for a single changed field! Otherwise you would get a mail for each changed field. If you want to check for changes of more than one field use the first approach.

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...