Skip to content

Commit a6afc97

Browse files
committed
Added hoeringsportal_anonymous_edit module
1 parent b2e962c commit a6afc97

25 files changed

+1151
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
88

9+
* [PR-564](https://github.com/itk-dev/deltag.aarhus.dk/pull/564)
10+
Added hoeringsportal_anonymous_edit module
911
* [PR-557](https://github.com/itk-dev/deltag.aarhus.dk/pull/557)
1012
* Change dialogue proposal backend
1113
* Add seperate view for dialogue proposal comments

config/sync/core.extension.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module:
4444
file_resup: 0
4545
filter: 0
4646
flag: 0
47+
hoeringsportal_anonymous_edit: 0
4748
hoeringsportal_audit_log: 0
4849
hoeringsportal_citizen_proposal: 0
4950
hoeringsportal_citizen_proposal_archiving: 0
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
uuid: 16550f09-0b5f-4b26-9763-f22c0b980ff4
2+
langcode: da
3+
status: true
4+
dependencies:
5+
module:
6+
- hoeringsportal_anonymous_edit
7+
id: hoeringsportal_anonymous_edit.content_recover
8+
configuration:
9+
email_body:
10+
content:
11+
value: "Gå til <a href=\"{{ url }}\">{{ url }}</a> for at finde dit eget indhold på deltag.aarhus.dk.\r\n"
12+
format: email_html
13+
email_subject:
14+
value: 'Dit indhold på deltag.aarhus.dk'
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Anonymous edit
2+
3+
This module keeps track of anonymous user's content and allows them to update
4+
and delete it.
5+
6+
The edit access is based on a long-lived cookie,
7+
`hoeringsportal_anonymous_edit_token`, in a browser. If the user deletes the
8+
cookie (or uses another browser), the cookie can be restored (or recovered?) via
9+
a URL sent to the user's email address (see [Recovering the edit
10+
token](#recovering-the-edit-token)).
11+
12+
When content, i.e. an instance of
13+
[`EntityInterface`](https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityInterface.php/interface/EntityInterface/11.x),
14+
is created by an anonymous user, the module dispatches an
15+
`HoeringsportalAnonymousEditEvent` event to let other modules tell if the
16+
content supports editing by anonymous users. It supported, the module ensures
17+
that an edit token (cookie) is set in the users browser and the token is
18+
attached to the content and any future content created by the user.
19+
20+
In addition to telling if the content is supported, the event subscriber can
21+
also set an email address to be associated with the content. This email can
22+
later be used to recover the edit token if need be.
23+
24+
See
25+
[AnonymousEditSubscriber.php](../hoeringsportal_dialogue/src/EventSubscriber/AnonymousEditSubscriber.php)
26+
for an example event subscriber implementation.
27+
28+
The module implements
29+
[`hook_entity_access`](https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_access/11.x)
30+
to allow anonymous users to update and delete content matching the current edit
31+
token.
32+
33+
## Content list
34+
35+
An anonymous user with a valid edtit token can find a list of its content on
36+
`/hoeringsportal-anonymous-edit/content`. The content is grouped by content type
37+
and bundle.
38+
39+
The content list uses the
40+
[`hoeringsportal-anonymous-edit-content-index.html.twig`](templates/hoeringsportal-anonymous-edit-content-index.html.twig)
41+
template file which can — and probably should be — overridden in the theme.
42+
43+
## Recovering the edit token
44+
45+
If the user looses its edit token, the token can be recovered if an email has
46+
been attached to a piece of content created by the user.
47+
48+
The `hoeringsportal_anonymous_edit.content_request` route
49+
(`/hoeringsportal-anonymous-edit/content/request`) lets the user enter an email
50+
address and request an email with a link to recover the edit token.
51+
52+
The `hoeringsportal_anonymous_edit.content_recover` route
53+
(`/hoeringsportal-anonymous-edit/content/recover/{token}`) asks the user to
54+
confirm the email address, and if the email matches the token, the edit token is
55+
restored and set as en edit token.
56+
57+
The recover email subject and body is managed on
58+
`/admin/config/system/mailer/policy/hoeringsportal_anonymous_edit.content_recover`.
59+
Twig can be used in both fields and the following variable is available (cf.
60+
[src/Plugin/EmailBuilder/EmailBuilder.php](src/Plugin/EmailBuilder/EmailBuilder.php)):
61+
62+
| Name | Description |
63+
|-------------|--------------------------|
64+
| recover_url | The absolute recover URL |
65+
66+
## Development
67+
68+
By default only errors (and higher levels) are logged. During development the
69+
module's log level can be set lower, e.g.
70+
71+
``` php
72+
# settings.local.php
73+
$settings['hoeringsportal_anonymous_edit']['log_level'] = \Drupal\Core\Logger\RfcLogLevel::DEBUG;
74+
```
75+
76+
Show log messages with
77+
78+
``` shell
79+
drush watchdog:show --type=hoeringsportal_anonymous_edit --extended
80+
```
81+
82+
Peek in the database table:
83+
84+
``` shell
85+
drush sql:query --extra=--table "SELECT * FROM hoeringsportal_anonymous_edit"
86+
```
87+
88+
---
89+
90+
* [ ] What happens if the same email is attached to multiple tokens?
91+
* [ ] What happens if a user has used multiple tokens?
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: "hoeringsportal_anonymous_edit"
2+
type: module
3+
description: "Allow anonymous users to edit their content"
4+
package: Custom
5+
core_version_requirement: ^10 || ^11
6+
7+
dependencies:
8+
- drupal:symfony_mailer
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Install functions for the hoeringsportal_anonymous_edit module.
6+
*/
7+
8+
/**
9+
* Implements hook_schema().
10+
*/
11+
function hoeringsportal_anonymous_edit_schema() {
12+
$schema['hoeringsportal_anonymous_edit'] = [
13+
'description' => 'Stores module data as key/value pairs per user.',
14+
'fields' => [
15+
'entity_type' => [
16+
'description' => 'The entity type.',
17+
'type' => 'varchar_ascii',
18+
'length' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
19+
'not null' => TRUE,
20+
'default' => '',
21+
],
22+
'entity_bundle' => [
23+
'description' => 'The entity bundle.',
24+
'type' => 'varchar_ascii',
25+
'length' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
26+
'not null' => TRUE,
27+
'default' => '',
28+
],
29+
'entity_uuid' => [
30+
'description' => 'The entity UUID.',
31+
'type' => 'varchar_ascii',
32+
'length' => 36,
33+
'not null' => TRUE,
34+
'default' => '',
35+
],
36+
'owner_token' => [
37+
'description' => 'The owner token (a UUID).',
38+
'type' => 'varchar_ascii',
39+
'length' => 36,
40+
'not null' => TRUE,
41+
'default' => '',
42+
],
43+
'owner_email' => [
44+
'description' => 'The owner email.',
45+
'type' => 'varchar_ascii',
46+
'length' => 255,
47+
'default' => '',
48+
],
49+
],
50+
'primary key' => [
51+
'entity_type',
52+
'entity_bundle',
53+
'entity_uuid',
54+
],
55+
'indexes' => [
56+
'owner' => [
57+
'owner_token',
58+
'owner_email',
59+
],
60+
],
61+
];
62+
63+
return $schema;
64+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Functions for the hoeringsportal_anonymous_edit module.
6+
*/
7+
8+
use Drupal\Core\Access\AccessResultInterface;
9+
use Drupal\Core\Entity\EntityInterface;
10+
use Drupal\Core\Session\AccountInterface;
11+
use Drupal\hoeringsportal_anonymous_edit\Helper\Helper;
12+
13+
/**
14+
* Implements hook_entity_insert().
15+
*/
16+
function hoeringsportal_anonymous_edit_entity_insert(EntityInterface $entity): void {
17+
_hoeringsportal_anonymous_edit_get_helper()->entityInsert($entity);
18+
}
19+
20+
/**
21+
* Implements hook_entity_delete().
22+
*/
23+
function hoeringsportal_anonymous_edit_entity_delete(EntityInterface $entity): void {
24+
_hoeringsportal_anonymous_edit_get_helper()->entityDelete($entity);
25+
}
26+
27+
/**
28+
* Implements hook_entity_access().
29+
*/
30+
function hoeringsportal_anonymous_edit_entity_access(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
31+
return _hoeringsportal_anonymous_edit_get_helper()->entityAccess($entity, $operation, $account);
32+
}
33+
34+
/**
35+
* Implements hook_theme().
36+
*/
37+
function hoeringsportal_anonymous_edit_theme($existing, $type, $theme, $path) : array {
38+
return [
39+
'hoeringsportal_anonymous_edit_content_index' => [
40+
'variables' => [
41+
'entities' => NULL,
42+
],
43+
],
44+
];
45+
}
46+
47+
/**
48+
* Get the hoeringsportal_anonymous_edit helper.
49+
*/
50+
function _hoeringsportal_anonymous_edit_get_helper(): Helper {
51+
return \Drupal::service(Helper::class);
52+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
hoeringsportal_anonymous_edit.content:
2+
path: "/hoeringsportal-anonymous-edit/content"
3+
defaults:
4+
_title: "Content"
5+
_controller: '\Drupal\hoeringsportal_anonymous_edit\Controller\HoeringsportalAnonymousEditController'
6+
options:
7+
no_cache: "TRUE"
8+
requirements:
9+
_user_is_logged_in: "FALSE"
10+
11+
hoeringsportal_anonymous_edit.content_request:
12+
path: "/hoeringsportal-anonymous-edit/content/request"
13+
defaults:
14+
_title: "Request"
15+
_form: 'Drupal\hoeringsportal_anonymous_edit\Form\RequestForm'
16+
requirements:
17+
_user_is_logged_in: "FALSE"
18+
19+
hoeringsportal_anonymous_edit.content_recover:
20+
path: "/hoeringsportal-anonymous-edit/content/recover/{token}"
21+
defaults:
22+
_title: "Recover"
23+
_form: 'Drupal\hoeringsportal_anonymous_edit\Form\RecoverForm'
24+
requirements:
25+
_user_is_logged_in: "FALSE"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
5+
hoeringsportal_anonymous_edit.logger:
6+
parent: logger.channel_base
7+
arguments: ["hoeringsportal_anonymous_edit"]
8+
9+
Drupal\hoeringsportal_anonymous_edit\Helper\ItemHelper:
10+
11+
Drupal\hoeringsportal_anonymous_edit\Helper\MailHelper:
12+
13+
Drupal\hoeringsportal_anonymous_edit\Helper\Helper:
14+
tags:
15+
- { name: event_subscriber }
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\hoeringsportal_anonymous_edit\Controller;
6+
7+
use Drupal\Core\Controller\ControllerBase;
8+
use Drupal\hoeringsportal_anonymous_edit\Helper\Helper;
9+
10+
/**
11+
* Returns responses for hoeringsportal_anonymous_edit routes.
12+
*/
13+
final class HoeringsportalAnonymousEditController extends ControllerBase {
14+
15+
public function __construct(
16+
private readonly Helper $helper,
17+
) {}
18+
19+
/**
20+
* Action!
21+
*/
22+
public function __invoke(): array {
23+
$entities = $this->helper->getContent();
24+
25+
$build['content'] = [
26+
'#type' => 'theme',
27+
'#theme' => 'hoeringsportal_anonymous_edit_content_index',
28+
'#entities' => $entities,
29+
];
30+
31+
return $build;
32+
}
33+
34+
}

0 commit comments

Comments
 (0)