This method uses the JSON:API Resources module.
It assumes:
- Upon cancellation, blocking the user is OK. (Admins will manually delete the accounts completely later.)
- Logged-in users will simply make a GET request to
www.example.com/jsonapi/MYMODULE/cancelaccount
to cancel their accounts (the app will provide an "Are you sure?" dialog and so forth).
There is probably a better way to do this but I am sharing my solution here because a Google search didn't reveal anything on this topic.
What to do
First, create a custom module.
Then, in MYMODULE.routing.yml
:
MYMODULE.user_cancel:
path: '/%jsonapi%/MYMODULE/cancelaccount'
defaults:
_jsonapi_resource: Drupal\MYMODULE\Resource\MYMODULEUserCancel
requirements:
# Give all authenticated users permission.
_permission: 'view own commerce_order'
options:
no_cache: 'TRUE
Next, in MYMODULE/src/Resource/MYMODULEUserCancel.php
:
<?php
namespace Drupal\MYMODULE\Resource;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Logger\LoggerChannelFactory;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\JsonApiResource\LinkCollection;
use Drupal\jsonapi\JsonApiResource\ResourceObject;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceResponse;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\ResourceType\ResourceTypeAttribute;
use Drupal\jsonapi_resources\Resource\EntityQueryResourceBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Processes a request for a collection containing a user's article nodes.
*
* @internal
*/
class MYMODULEUserCancel extends EntityQueryResourceBase implements ContainerInjectionInterface {
/**
* The current user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected AccountInterface $currentUser;
/**
* Logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactory
*/
protected $loggerFactory;
/**
* Constructs a new EntityResourceBase object.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Logger\LoggerChannelFactory $logger_factory
* The Watchdog logger.
*/
public function __construct(AccountInterface $account, LoggerChannelFactory $logger_factory) {
$this->currentUser = $account;
$this->loggerFactory = $logger_factory->get('MYMODULE');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('current_user'),
$container->get('logger.factory'),
);
}
/**
* Process the resource request.
*
* Code example:
* https://git.drupalcode.org/project/jsonapi_resources/tree/9.x-1.x/tests/modules/jsonapi_resources_test/src/Resource.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
* @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types
* The route resource types.
*
* @return \Drupal\jsonapi\ResourceResponse
* The response.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function process(Request $request, array $resource_types): ResourceResponse {
$uid = $this->currentUser->id();
// Make sure that we do not try to cancel user 1 or anonymous.
if ($uid !== 1 && $uid !== 0) {
$user_storage = $this->entityTypeManager->getStorage('user');
$current_user = $user_storage->load($uid);
// See: https://drupal.stackexchange.com/a/204422.
// Send the user the "account blocked" confirmation email.
_user_mail_notify('status_blocked', $current_user);
$current_user->set('status', FALSE);
$current_user->save();
$this->loggerFactory->notice("User account for UID @uid cancelled by JSON:API.", [
'@uid' => $current_user->id(),
]);
}
// Some data to return with JSON:API.
$custom_field_data = [
'cancelled' => TRUE,
];
$links = new LinkCollection([]);
$resource_type = reset($resource_types);
// To disable caching, set metadata.
$cacheability = new CacheableMetadata();
$cacheability->setCacheMaxAge(1);
$primary_data = new ResourceObject(
$cacheability,
$resource_type,
$current_user->uuid(),
NULL,
$custom_field_data,
$links
);
$top_level_data = new ResourceObjectData([$primary_data], 2);
$response = $this->createJsonapiResponse($top_level_data, $request, 201, []);
$response->addCacheableDependency($cacheability);
return $response;
}
/**
* {@inheritdoc}
*/
public function getRouteResourceTypes(Route $route, string $route_name): array {
$fields = [
'drupalUid' => new ResourceTypeAttribute('drupalUid'),
'status' => new ResourceTypeAttribute('status'),
];
$resource_type = new ResourceType('current_user', 'current_user', NULL, FALSE, TRUE, TRUE, FALSE, $fields);
// @todo Add role entities as a relatable resource type.
$resource_type->setRelatableResourceTypes([]);
return [$resource_type];
}
}