Jump to content

How to make a simple 'Delete My Account' link for users


modifiedcontent
 Share

Recommended Posts

I had a simple 'Delete My Account' link for users in my site that used to work fine, with a function like this:

Spoiler

if ( $input->post->forgetme ) {

	if ( $user->isSuperuser() ) {
	echo 'You should not be here...';
	$session->redirect('/');
	} 

	$items = $pages->find( 'name=' . $user->name );
		foreach($items as $item) {
		$item->delete();
		}
	$users->delete( $user );
	$session->logout();
	$session->redirect('/');
}

 

But it no longer works, after upgrades I guess. I get a fatal server error:

cannot be deleted: it is the current page being viewed, try $pages->trash() instead

Or when I use trash(), I get:

Exception: This page (id=XXXX) may not be placed in the trash

That error is thrown in core/PagesTrash.php by a check for isDeleteable(). I can't find info what makes a page 'deleteable' or not.

What is now the recommended way to do this?

 

Link to comment
Share on other sites

  • 5 months later...
On 4/25/2020 at 7:59 PM, modifiedcontent said:

I had a simple 'Delete My Account' link for users in my site that used to work fine, with a function like this:

 

On 4/25/2020 at 7:59 PM, modifiedcontent said:

I can't find info what makes a page 'deleteable' or not.

Stuff like is the page locked; is it a system page (e.g. repeater pages) is it currently being viewed (see below), it has children and you haven't told ProcessWire to do a recursive delete..etc

 

On 4/25/2020 at 7:59 PM, modifiedcontent said:

cannot be deleted: it is the current page being viewed, try $pages->trash() instead

Since 2019-04-04 you cannot delete the page currently being viewed: See these lines in wire/core/PagesEditor.php.

On 4/25/2020 at 7:59 PM, modifiedcontent said:

Exception: This page (id=XXXX) may not be placed in the trash

This is because wire/core/PagesTrash.php itself checks PagesEditor::isDeleteable which in this case has returned the info that the page cannot be deleted.

On 4/25/2020 at 7:59 PM, modifiedcontent said:

What is now the recommended way to do this?

Here's one way to do it:

You will need two templates. One for the 'forget me form' and another that will carry out the deletion, the logout, the redirect and, if needed, give confirmation/feedback to user.

In your template file with 'forget me form':

  • We do our checks for the post and check that user is not superuser.
  • We then try to find pages for that user.
  • If we find pages, we cache the PageArray.
  • We also cache the user. ProcessWire will cache this as the user ID. 
  • We redirect to the delete page
<?php namespace ProcessWire;

if ($input->post->forgetme) {
 if ($user->isSuperuser()) {
  echo 'You should not be here...';
  $session->redirect('/');
 }
 
 // @note: we need to tell ProcessWire we are only searching for pages at this point and not users as well!
 // Otherwise we'll get an error  when caching, about mixing multiple types in the cache
 //  as otherwise this selector will also find the user with that name along with the user pages
 $items = $pages->find('template!=user,name=' . $user->name);
 // we found items
 if ($items->count) {
  // save items to delete to cache
  // cache the pages to delete and the user to delete
  //  and expire after 10 minutes (600 seconds)
  $cache->save("forgetmepages", $items, 600);
  $cache->save("forgetmeuser", $user, 600);
  // redirect to the page that will carry out the cleanup
  $session->redirect("/confirm-delete/");
 }
}

 

In the 'delete confirmed' template file:

  • Here, we no longer have access to the $input->post->forgetme
  • Retrieve the caches with the items to delete and the ID of the user to delete that we saved above
  • The items cache is already a PageArray, so we just loop through it and delete the pages
  • We get the user to delete using their ID, just to be safe
  • Delete the user
  • Log them out
  • Redirect to home page
<?php namespace ProcessWire;

$forgetmePages = $cache->get('forgetmepages');
$forgetmeUserID = $cache->get('forgetmeuser');
$forgetmeUser = $users->get($forgetmeUserID);

foreach ($forgetmePages as $item) {
 $item->delete();
  // OR
  // $pages->delete($item);
//   $pages->trash($item);// if you want to trash the pages instead
}
$users->delete($forgetmeUser);
$session->logout();
$session->redirect('/');

 

Edited by kongondo
  • Like 1
Link to comment
Share on other sites

8 minutes ago, modifiedcontent said:

I'll go over your very clear instructions and report back.

Yes, please let me know. I did a number of tests and it worked for me.

9 minutes ago, modifiedcontent said:

UK coffee/brunch time

Speaking of which,  I better get to mine as well! :-).

  • Like 2
Link to comment
Share on other sites

1 hour ago, modifiedcontent said:

I'll go over your very clear instructions and report back.

Btw, the code I supplied is not robust enough to cover a scenario of simultaneous forget mes! It uses the same cache name and will at best lead to a race condition or at worst, miss deleting pages since these would have been replaced in the cache. One way to overcome this is to create a cache with a unique name, e.g. 'forgetmepages-1234' where 1234 is the ID of the logged in user we want to 'forget'. We would the retrieve the cache as $cache->get("forgetmepages-{$user->id}"); We will need to check that the cache exists before doing the foreach(){delete).

Secondly, and this is now probably covered in the above, it is best to check that we have a user ID at $cache->get("forgetmeuser->$user->id"); and that the (int) value of that matches the $user->id before we delete the user and his/her pages.

I hope this makes sense. 

  • Like 2
Link to comment
Share on other sites

Here's the updated code with the above robustness baked in.

In your template file with 'forget me form':

<?php namespace ProcessWire;

if ($input->post->forgetme) {
 if ($user->isSuperuser()) {
  echo 'You should not be here...';
  $session->redirect('/');
 }
 $items = $pages->find('template!=user,name=' . $user->name);
 if ($items->count) {
  // save items to delete to cache using unique cache names
  // cache the value, and expire after 10 minutes (600 seconds)
  $cache->save("forgetmepages-{$user->id}", $items, 600);
  $cache->save("forgetmeuser-{$user->id}", $user, 600);
  $session->redirect("/confirm-delete/");
 }
}

In the 'delete confirmed' template file:

<?php namespace ProcessWire;
// get the uniquely-named caches
$forgetmePages = $cache->get("forgetmepages-{$user->id}");
$forgetmeUserID = (int) $cache->get("forgetmeuser-{$user->id}");
// if we got the caches
if ($forgetmePages && $forgetmeUserID) {
 // also check if we have the right user
 if ($user->id === $forgetmeUserID) {
  foreach ($forgetmePages as $item) {
   $item->delete();
   //   $pages->trash($item);
  }
   // delete the user
  $users->delete($user);
   // log them out
  $session->logout();
 }
}
// get me outta here
$session->redirect('/');

 

Hope this helps. 

  • Like 1
Link to comment
Share on other sites

@kongondo, this worked very well. Thanks! Just followed your last post. In the 'delete confirmed' template file I added some last warning text, like:

'If you click the button below, all your data will be removed from our system and you will be locked out. Are you sure you want to do that?'

Button like this:

<form method=post>
  <input type=submit name=terminate value='yes, delete my account'>
</form>

The first 'delete account' button is similar. And then your code wrapped in something like this, on top of the 'delete confirmed' template file:

if( $input->post->terminate && !$user->isSuperuser() ) {
...
}

Checking for superuser again is not strictly necessary I guess and there are probably better ways, like in the first part.

I made the 'delete confirmed' template file hidden and only accessible for 'members', not for random guests.

I'll keep testing with this and will report back if I find problems or improvements/additions.

That use of $cache was new for me. Very interesting, useful.

Link to comment
Share on other sites

5 hours ago, modifiedcontent said:

this worked very well. Thanks! Just followed your last post. In the 'delete confirmed' template file I added some last warning text, like:

Glad you got it sorted :-). Those are nice improvements you added there.

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...