Problem/Motivation

Currently we pretend that we have an API that allows a 3rd party to implement its own user cancellation method. Relevant hooks: hook_user_cancel_methods_alter() and hook_user_cancel().

However, if you create a custom method, let's say user_cancel_archive, the code for user_cancel_block method is also executed. It's easy to find why, by looking to _user_cancel() function. The default: case is the problem. But for my custom method I want to be able to set my own status message and send my own custom email. No luck, Drupal is settings its status message and sends the standard user block email. Also, it's not possible to exclude _user_cancel() from the batch operations.

I think that this bug was possible because we have no test for a custom user cancellation method. I've added a test to prove the problem.

Proposed resolution

Based on @alexpott comment/proposal from #14:

  • Deprecate the actual hook_user_cancel() hook.
  • Create a new event (AccountCancelEvent) and move the logic from hook_user_cancel() implementations in event subscribers.
  • Make sure the user event subscriber, which actually perform the user cancellation, runs last.
  • A third-party that needs to fully replace the default core's cancellation logic, is able to implement a subscriber with a higher priority, do its logic and stop the event propagation, so that downstream subscribers won't execute.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None.

Release notes snippet

Third-party that need to have a say on user account cancellation, should subscribe to \Drupal\user\Event\AccountCancelEvent event. A subscriber aiming to implement its own user account cancellation logic and avoid default core behavior should set a higher priority and bypass downstream subscribers by stopping the event propagation.

Issue fork drupal-3135592

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

claudiu.cristea created an issue. See original summary.

claudiu.cristea’s picture

Status: Active » Needs review
claudiu.cristea’s picture

StatusFileSize
new534 bytes
new3.73 KB

The fix.

claudiu.cristea’s picture

claudiu.cristea’s picture

Version: 8.8.x-dev » 8.9.x-dev
jyotimishra-developer’s picture

Hi, the patch applied successfully in Drupal 8.9.x version.
Great Work @claudiu.cristea

dimilias’s picture

This is indeed a good notice. This whole thing starts from user_cancel_methods which throws a hook to allow generate a new way of cancelling the account.
Then, as noted in the issue description, the user_cancel method does fire the default method when a new method is defined.
All other default methods are handled explicitly.

A few nitpicks.

  1. +++ b/core/modules/user/tests/src/Functional/UserCancelCustomMethodTest.php
    @@ -0,0 +1,60 @@
    +    $this->assertSession()->pageTextNotContains("{$account->getAccountName()} has been disabled.");
    

    Can we change the method used to ::getDisplayName? I don't think it will make the test fail though it is the one used by _user_cancel for the message display.

  2. +++ b/core/modules/user/tests/src/Functional/UserCancelCustomMethodTest.php
    @@ -0,0 +1,60 @@
    +   * Clicks on the conformation link sent by email.
    

    Typo in "conformation".

  3. +++ b/core/modules/user/tests/src/Functional/UserCancelCustomMethodTest.php
    @@ -0,0 +1,60 @@
    +    preg_match('#http.*#', $mail['body'], $found);
    

    If I am not mistaken, this will fetch a malformed URL unless the url is the last piece of string in the line.
    I tested this in https://www.functions-online.com/preg_match.html with the string "http://some.test.com. Hello world." and the whole sentense is returned.
    In the current case, this works because the default cancel email contains the \n\n[user:cancel-url]\n\n but I don't know if this is future proof. What do you think? We can keep it of course. The #http[^ \n]*# seems to cover all cases currently (it does not cover a change where the url ends with a fullstop as part of a sentence).

dimilias’s picture

Status: Needs review » Needs work
claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new959 bytes
new3.73 KB

@idimopoulos, thank you for review. I've fixed #7.1, 2. Regarding #7.3: the test runs against the testing profile and the modules default configuration. We know, that in the default configuration the URL ends with a new line. See https://git.drupalcode.org/project/drupal/-/blob/8.9.x/core/modules/user.... The used regex pattern just works with the default configuration, so why complicating it? In the eventuality that the message template will change in the future, this test will simply fail and the the regex will be fixed in the same patch.

dimilias’s picture

Status: Needs review » Reviewed & tested by the community

@claudiu.cristea to be honest, that was what I was thinking for the 3rd point but put it up there just to be sure. It is always better to have it written than just somebody having thought of it at some point.
This is fine as far as I am concerned. I don't know if more RTBCs have to be created. I am putting it in RTBC but I am not sure if any change record is needed since it is listed as a bug report, but stops sending notifications for custom cancellation methods.

claudiu.cristea’s picture

@idimopoulos

(...) but stops sending notifications for custom cancellation methods.

Indeed, this could look as a BC break, but I see no way to fix this. The current code is wrong and I have no idea how can we ensure BC in this case, Let's have an opinion from a core committer.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record
  1. +++ b/core/modules/user/tests/modules/user_cancel_test/user_cancel_test.info.yml
    @@ -0,0 +1,6 @@
    +core: 8.x
    

    This needs removing so we're compatible with 9.x

  2. +++ b/core/modules/user/user.module
    @@ -781,7 +781,6 @@ function _user_cancel($edit, $account, $method) {
       switch ($method) {
         case 'user_cancel_block':
         case 'user_cancel_block_unpublish':
    -    default:
    

    This is quite a big change. Someone might have implemented and be relying on it. There's plenty of contrib that implements the hook - see http://codcontrib.hank.vps-private.net/search?text=user_cancel_methods_a... - so this code is currently being triggered for them and and now it won't be.

    I think we need to consider another way for code to opt out from this.

claudiu.cristea’s picture

Status: Needs work » Reviewed & tested by the community

@alexpott, I see the problem but I don’t see how this could be fixed using other approach. Neither can I see a way to provide BC. Do we agree that this is a bug? If yes and we cannot find a way to preserve BC, does it mean that we’ll have to live with this bug forever? Why we cannot fix it in 9.0.0 and add a good change notice? Setting back to RTBC just to get a feedback from @alexpott or other core committer

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

@claudiu.cristea we've just released an RC there's no way we can make this in the same way in 9.0.0. We need a BC layer. How about a new hook whose cancel methods don't result in this being called. Or moving to some event system where the default is always the last subscriber and you can stop propagation if you don't want its effects.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

pfrenssen made their first commit to this issue’s fork.

pfrenssen’s picture

Fixed point 1 of #12. Point 2 is not yet addressed and requires a full rework of the patch.

claudiu.cristea’s picture

Version: 9.3.x-dev » 9.5.x-dev

Moving to 9.5

claudiu.cristea’s picture

Issue tags: +Bug Smash Initiative

Part of "Bug Smash Initiative"

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea

Assigning...

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new3.39 KB

Move the "test only" code in a patch.

claudiu.cristea’s picture

Status: Needs review » Needs work

The last submitted patch, 24: 3135592-bug-proof.patch, failed testing. View results

claudiu.cristea’s picture

claudiu.cristea’s picture

Version: 9.5.x-dev » 10.0.x-dev
Issue tags: -Needs tests

Too big change for D9

claudiu.cristea’s picture

Status: Needs work » Needs review
claudiu.cristea’s picture

Issue summary: View changes
Issue tags: -Needs release note

Fix IS, adding release notes snippet

claudiu.cristea’s picture

Assigned: claudiu.cristea » Unassigned

Unassigning.

larowlan’s picture

Hiding patches because this is an MR based issue

larowlan’s picture

Status: Needs review » Needs work
Related issues: +#3043725: Provide a Entity Handler for user cancelation

There's a fair bit of overlap with #3043725: Provide a Entity Handler for user cancelation here, it would be good to compare notes and join forces with the folks over there. Having multiple people working towards fixing a legacy system in different ways isn't ideal, but I think working together will make things move faster as you have multiple hands for things like reviews etc

Left some comments on the MR

larowlan’s picture

Title: Cannot implement a custom user cancellation method » Replace hook_user_cancel_methods_alter and user_cancel with events, allow custom user cancel methods

Updating title to describe all the things going on here for those seeing it in related links etc, please amend if I'm off piste

claudiu.cristea’s picture

Title: Replace hook_user_cancel_methods_alter and user_cancel with events, allow custom user cancel methods » Cannot implement a custom user cancellation method

@larowlan, thank you for review. However, I have to revert the title. The main goal of this ticket is to fix the custom user cancellation API. Modernizing the whole user cancellation process is just a necessity while achieving the main goal. It's #3043725: Provide a Entity Handler for user cancelation which is mainly dealing with modernizing the user cancellation.

Version: 10.0.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

ivnish’s picture

@claudiu.cristea any update here?

claudiu.cristea’s picture

paramnida made their first commit to this issue’s fork.

solideogloria’s picture

In addition, after creating a custom user cancellation handler, there's no way to select that method when using drush user:cancel. It'd be nice if there was a generic --method=[method] argument, rather than just --delete-content and --reassign-content.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

claudiu.cristea’s picture

Status: Needs work » Closed (duplicate)

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.