1

I have a profile form with a select menu for a user to choose their nearest location (a Category group.) A user will select their nearest location, it will add the category to their profile and then add them to the corresponding User group.

I would also like to remove the user from their existing group. For example, if they change from London to New York, they should be removed from the London user group and placed in the New York group.

The complication is that they belong to other user groups, which they need to stay in.

Adding to the new location group is working well, but I'm looking for a hand in removing the exisiting one. If anyone has any thoughts on my code below, I would very much appreciate it!

Event::on(
    User::class, Element::EVENT_AFTER_SAVE, function (ModelEvent $event) {
        if (Craft::$app->getRequest()->getIsConsoleRequest()) {
            return;
        }
    $user = $event->sender;
    $userLocation = Craft::$app->getRequest()->getBodyParam('fields[userLocation][0]');

    if (!$userLocation) {
        return;
    }

    $newLocationCategory = Craft::$app->getCategories()->getCategoryById($userLocation);
    $newLocationGroup = Craft::$app->getUserGroups()->getGroupByHandle($newLocationCategory->title);

    $oldLocationCategory = Craft::$app->getCategories()->getCategoryById($user->userLocation->id[0]);
    $oldLocationGroup = Craft::$app->getUserGroups()->getGroupByHandle($oldLocationCategory->title);

    $currentGroupIds = \array_map(function ($group) {
        return (int)$group->id;
    }, $user->getGroups());


    // Remove the user from their old Location group - this is not working
    if (($key = array_search($oldLocationGroup->id, $currentGroupIds)) !== false) {
        unset($currentGroupIds[$key]);
    }


    $finalGroupIds = array_merge($currentGroupIds, [$newLocationGroup->id]);
    Craft::$app->getUsers()->assignUserToGroups($user->id, $finalGroupIds);
}

);

supazu
  • 576
  • 4
  • 12

2 Answers2

2

Try to use EVENT_BEFORE_SAVE and try to do a query the user again to get the old user data, something like this:

Event::on(
        User::class, 
        Element::EVENT_BEFORE_SAVE, 
              function (ModelEvent $event) {
                if (Craft::$app->getRequest()->getIsConsoleRequest()) {
                    return;
                }
            $user = $event->sender;
            $userLocation = $user->userLocation->one();

            $newLocationCategory = $user->userLocation->one();
            $newLocationGroup = Craft::$app->getUserGroups()->getGroupByHandle($newLocationCategory->title);
            $currentGroupIds = [];
            if (!$event->isNew) {
                // to get all location we need to do a query for user again.
                $oldUserData = User::findOne($user->id);
                $oldUserCategory = $oldUserData->userLocation->one();
                $oldLocationGroup = Craft::$app->getUserGroups()->getGroupByHandle($oldUserCategory->title);

                $currentGroupIds = \array_map(function($group) {
                    return (int)$group->id;
                }, $user->getGroups());


                // Remove the user from their old Location group - this is not working
                if (($key = array_search($oldLocationGroup->id, $currentGroupIds)) !== false) {
                    unset($currentGroupIds[$key]);
                }
            }

            $finalGroupIds = array_merge($currentGroupIds, [$newLocationGroup?->id]);
            Craft::$app->getUsers()->assignUserToGroups($user->id, $finalGroupIds);
        }
);

PS: I didn't test the code.

aodihis
  • 801
  • 5
  • 10
  • Nice! Re-querying for the user seems to be the trick: $oldUserData = User::findOne($user->id);. I'm not totally sure why that wasn't available just from $user but I get because it's coming from $event->sender?

    Thanks for your help!

    – supazu Nov 06 '22 at 09:23
  • Yes, because it's coming from $event->sender, and that user value you get from that has the updated one value. – aodihis Nov 07 '22 at 01:53
  • Makes sense. However, it's introduced a new complication: this works for the profile edit form, but not for creating a new user. Using EVENT_BEFORE_SAVE means $oldUserData = User::findOne($user->id); returns null. Any thoughts on how I could get around that? – supazu Nov 07 '22 at 08:28
  • @brynli64 you can check if the events is new or not, if not new then run get the old category, I updated the answer for new user. – aodihis Nov 08 '22 at 01:56
  • Thank you, I appreciate your help on this but unfortunately the same thing exists. Because it's EVENT_BEFORE_SAVE, $user->id returns null. Changing to EVENT_AFTER_SAVE solves that, but then the group isn't removed properly (presumably because the old data is now the new data). I think I might need to think about how to set this up to handle both scenarios. – supazu Nov 08 '22 at 02:58
  • Yes, you correct, how about to separate the process for the new user and old user, for the new user do on EVENT_AFTER_SAVE since you don't need to remove it from the old group only assign the user their registered group, and use EVENT_BEFORE_SAVE for old user. – aodihis Nov 08 '22 at 03:13
  • Yep, I have that working now but with a bit of duplicated code. I'm going to try to learn about how to reuse things across different events. Thanks for your help! – supazu Nov 08 '22 at 03:22
1

So, are you sure you only have the necessary user group IDs at the end, in $finalGroupIds ?

Can you try to drop the keys after deleting(unset) unnecessary items(old groupIds) in your array?

$finalGroupIds = array_values(array_merge($currentGroupIds, [$newLocationGroup->id]));
RomanAvr
  • 669
  • 1
  • 12
  • Thanks for the reply. It seems like $oldLocationCategory = Craft::$app->getCategories()->getCategoryById($user->userLocation->id[0]); is returning the new category, rather than the exisiting one. Changing to EVENT_BEFORE_SAVE doesn't help...so I'm not quite sure how to get the exisiting category on the user. – supazu Nov 05 '22 at 02:23