During some work extending this module to meet a client's requirement, it became apparent that the module did not have a way to clean up records for a deleted user. Having coded a function to purge a user's associations for another task, I want to give something back.

/**
 * Respond to user deletion in order to clean up Workbench Access
 * section association data that would otherwise become orphaned.
 *
 * @param $account
 *   The account that has been deleted.
 */
function workbench_access_user_delete($account) {

    // Delete the entity's entry from a fictional table of all entities.
    /*
    db_delete('example_entity')
        ->condition('type', $entity
        ->getEntityTypeId())
        ->condition('id', $entity
        ->id())
        ->execute();
    */

    //Get Access Schemes
    $scheme_storage = \Drupal::entityTypeManager()->getStorage('access_scheme');
    $scheme = $scheme_storage->loadMultiple();

    foreach ($schemes as $scheme) {        
        $user_storage = \Drupal::service('workbench_access.user_section_storage');
        //load section IDs for this user
        $section_ids = $user_storage->getUserSections($scheme, $account, FALSE);
        //remove user from all IDs
        $user_storage->removeUser($scheme, $account, $section_ids);
    }
}

There may be a better way to handle this, but I'll create a patch for testing!

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

alienzed created an issue. See original summary.

alienzed’s picture

FileSize
1.36 KB
agentrickard’s picture

I looked into this a bit, and the proper way to delete this is to load the SectionAssociation entity and then delete it.

So something like this:

$storage_handler = \Drupal::entityTypeManager()->getStorage('section_association');
$entities = $storage_handler->loadByProperties(['user_id' = $account->id()]);
$storage_handler->delete($entities);

This should load all sections that the user is assigned to and then delete them, since delete() here takes an array. The delete() also handles revisions.

See https://api.drupal.org/api/drupal/core%21includes%21entity.inc/function/...
See https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21...

alienzed’s picture

FileSize
773 bytes

Wonderful! Here is the patch, including a small formatting fix

agentrickard’s picture

Now, of course, we have to write a test.

alienzed’s picture

Wondering if we should write to the log prior to delete:

\Drupal::logger('nafr_authentication')->notice("Deleting user ". $account->id() . " from Workbench Access");

FYI I thought this wasn't working initially but I think CRON needs to be run for deletions to execute. Deletion of the user did not clear the database entries, but after running cron a few times manually the tables did clear.

agentrickard’s picture

Category: Bug report » Task

Logging shouldn't be necessary, since the deletion of the user is logged.

Not sure about Cron. That might be worth some research. Could also be a cache issue, but the delete() should handle that.

alienzed’s picture

Good to know, leaving the logging out then.

As for the test, I'm going a bit outside my experience level with this, but here's a first draft... any/all help appreciated here!

In plain english I've created a new file under tests/src/Functional called DeleteUserTest.php. It was based on NodeFormMenuTest.php and follows this procedure:

Create menu link
Create user
Associate user
Confirm Association
Delete User
Confirm Association is deleted

my apologies if that's super wrong :P

agentrickard’s picture

Category: Task » Bug report

We should probably associate two users, delete one, and ensure that the second one still exists.

I would also do a direct Equals comparison (or Empty) after the deletion. It might not be equal for unrelated reasons, so stricter checking is better.

Otherwise, the test looks solid.

agentrickard’s picture

Category: Bug report » Task
alienzed’s picture

Something like this or do I have too many assertions? I'd appreciate a review on this as I'm too new to Drupal tests be confident that this test is complete/correct.

/**
   * Tests that deleting a user clears their data from storage
   */
  public function testUserDelete() {
    // Set up a content type and menu scheme.
    $node_type = $this->createContentType(['type' => 'page']);
    $scheme = $this->setUpMenuScheme(['page'], ['main']);
    $user_storage = $this->container->get('workbench_access.user_section_storage');

    // Set up an editor
    $editor = $this->setUpEditorUser();

    // Set up a second editor
    $editor2 = $this->setUpEditorUser();

    // Set up a menu link for this test.
    $base_link = MenuLinkContent::create([
      'title' => 'Link 1',
      'link' => [['uri' => 'route:<front>']],
      'menu_name' => 'main',
    ]);
    $base_link->save();

    // Add the first user to the base section.
    $user_storage->addUser($scheme, $editor, [$base_link->getPluginId()]);
    // Add the second user to the base section.
    $user_storage->addUser($scheme, $editor2, [$base_link->getPluginId()]);

    //Set Expected User IDs
    $expected = [$editor->id(), $editor2->id()];

    //Get assigned users
    $existing_users = $user_storage->getEditors($scheme, $base_link->getPluginId());

    //assert that these are the same
    $this->assertEquals($expected, array_keys($existing_users));

    //get expected section ID
    $expected = [$base_link->getPluginId()];

    //Check sections for first user
    $existing = $user_storage->getUserSections($scheme, $editor);
    $this->assertEquals($expected, $existing);

    //Check sections for second user
    $existing = $user_storage->getUserSections($scheme, $editor2);
    $this->assertEquals($expected, $existing);

    // Delete the first user
    user_delete($editor->id());

    //Check sections for first user again - should be empty
    $existing = $user_storage->getUserSections($scheme, $editor);
    $this->assertEmpty($expected, $existing);

    //Check sections for second user
    $existing = $user_storage->getUserSections($scheme, $editor2);
    $this->assertEquals($expected, $existing);
  }
agentrickard’s picture

I like this. It's thorough. The checks on individual users might be removed, but there is no harm in having extra assertions like that. What's missing is this bit, to run after the user deletion:

    //Set Expected User IDs
    $expected = [$editor2->id()];

    //Get assigned users
    $existing_users = $user_storage->getEditors($scheme, $base_link->getPluginId());

    //assert that these are the same
    $this->assertEquals($expected, array_keys($existing_users));

This is the piece that is critical to test: "We had two users and deleted one, now we should have one user."

alienzed’s picture

FileSize
3.15 KB

redundant tests removed and critical test replaced!

alienzed’s picture

FileSize
3.3 KB

trying with the admin user as the second user

larowlan’s picture

This looks really good.

  1. +++ b/tests/src/Functional/DeleteUserTest.php
    @@ -0,0 +1,87 @@
    +class DeleteUserTest extends BrowserTestBase {
    ...
    +    $admin = $this->setUpAdminUser([
    +      'create page content',
    +      'edit any page content',
    

    As you're not driving around a browser here, this should be a KernelTest (Extend from KernelTestBase, not BrowserTestBase) as it will be an order of magnitude faster. There are some examples in the module you can base it off.

  2. +++ b/workbench_access.module
    @@ -188,3 +188,15 @@ function workbench_access_process_inline_entity_form(array &$element, FormStateI
    +    $storage_handler = \Drupal::entityTypeManager()->getStorage('section_association');
    +    $entities = $storage_handler->loadByProperties(['user_id' => $account->id()]);
    +    $storage_handler->delete($entities);
    

    nice!

alienzed’s picture

Thanks! I've made some other improvements, but want to confirm that my test works locally before trying again, so as not to use drupal.org as my primary test environment.

With that said, the previous test failed, to the best of my knowledge, due to the array not being as expected, meaning user deletion failed, I suppose... If anyone sees something obviously wrong with the test, feel free to speak up, if not I'll get it eventually :)

ahebrank’s picture

Category: Task » Bug report

Changing to "bug" since this breaks the list of assigned users on the editor assignment form.

To reproduce:

- set up an access scheme
- make a few users eligible for section assignment
- add the one with lowest UID to a section
- delete that user
- try to add other users to the section and they won't show up
- if you add users with lower UIDs than the deleted user, the entire list should show up as expected but the deleted user will be represented by a blank row

I haven't tried the patch here but will look into it now.

ahebrank’s picture

FileSize
3.57 KB

I've updated the patch. I don't think the previous implementation was correct to delete the entire association entity; this one just just unsets the relevant user_id "field" delta on the entity.

I've left the test as a BrowserTest since that has a bunch of needed traits for entity creation and user management that KernelTest does not.

agentrickard’s picture

Status: Active » Needs review
damontgomery’s picture

Status: Needs review » Reviewed & tested by the community

I was able to reproduce that deleting a user breaks the section with the current dev release as mentioned in #18. You can no longer add users or view the existing users.

I am using:
- Drupal: 8.7.9
- Workbench Access: 1.x-dev
- Config Split: 1.4
- Palantir.net Drupal Skeleton: https://github.com/palantirnet/drupal-skeleton
- Drush: 8.1.18
- PHPUnit: 6.5.14

Steps to reproduce:
- Enable workbench_access
- Create three users: /admin/people/create
- Edit the permissions for authenticated users and allow, "Allow all members of this role to be assigned to Workbench Access sections"
- Create a Tags term, /admin/structure/taxonomy/manage/tags/overview
- Create a new schema for taxonomy "Tags" and content type "Article", /admin/config/workflow/workbench_access/access_scheme/add
- Add the two example users to the example term section at /admin/config/workflow/workbench_access/example_scheme/sections/1/users
- Delete the first example user, "Delete the account and its content."
- View the list of users in the section, /admin/config/workflow/workbench_access/example_scheme/sections/1/users
- They are all gone, but only the deleted user should be gone
- Try to add the third example user to the section, /admin/config/workflow/workbench_access/example_scheme/sections/1/users
- No users are shown

With the patch in #18, the above works as expected. The PHPUnit test also passed.

I did get a deprecation notice which is a separate issue. I think this is from a base class.

Remaining self deprecation notices (1)

  1x: Drupal\Component\Plugin\ConfigurablePluginInterface is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. You should implement ConfigurableInterface and/or DependentPluginInterface directly as needed. If you implement ConfigurableInterface you may choose to implement ConfigurablePluginInterface in Drupal 8 as well for maximum compatibility, however this must be removed prior to Drupal 9. See https://www.drupal.org/node/2946161
    1x in DeleteUserTest::testUserDelete from Drupal\Tests\workbench_access\Functional
larowlan’s picture

+++ b/tests/src/Functional/DeleteUserTest.php
@@ -0,0 +1,83 @@
+    user_delete($editor->id());

should we use $editor->delete() here? I assume user_delete is one of those functions like entity_load that will eventually be deprecated

agentrickard’s picture

We should. All functions that depend on https://api.drupal.org/api/drupal/core%21includes%21entity.inc/function/... will be deprecated.

agentrickard’s picture

Status: Reviewed & tested by the community » Needs work
FileSize
2.46 KB

Test-only patch.

agentrickard’s picture

Last patch was malformed. Tests are green locally.

agentrickard’s picture

Status: Needs work » Needs review
agentrickard’s picture

We would need something similar for Roles, but that can go in another issue.

agentrickard’s picture

  • agentrickard committed c066ae9 on 8.x-1.x authored by ahebrank
    Issue #3023344 by alienzed, agentrickard, ahebrank, larowlan: Clean up...
agentrickard’s picture

Status: Needs review » Fixed

Committed.

Status: Fixed » Closed (fixed)

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