Skip to content

Commit 0cb2f9c

Browse files
committed
Added linked data
1 parent 932335e commit 0cb2f9c

File tree

8 files changed

+297
-5
lines changed

8 files changed

+297
-5
lines changed

.github/workflows/pr.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ jobs:
105105
# Restore Drupal composer repository.
106106
composer --no-interaction --working-dir=drupal config repositories.drupal composer https://packages.drupal.org/8
107107
108+
composer --no-interaction --working-dir=drupal config --no-plugins allow-plugins.cweagans/composer-patches true
109+
# @see https://getcomposer.org/doc/03-cli.md#modifying-extra-values
110+
composer --no-interaction --working-dir=drupal config --no-plugins --json extra.enable-patching true
111+
108112
# Require our module.
109113
composer --no-interaction --working-dir=drupal require 'os2forms/os2forms_rest_api:*'
110114

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,35 @@ details.
144144
In order to make documents accessible for api users the Key auth
145145
`authentication_provider` service has been overwritten to be global. See
146146
[os2forms_rest_api.services](os2forms_rest_api.services.yml).
147+
148+
## Linked data
149+
150+
To make using the REST API easier we add linked data to `GET` responses:
151+
152+
```json
153+
{
154+
155+
"data": {
156+
"file": "87",
157+
"name": "The book",
158+
"linked": {
159+
"file": {
160+
"87": {
161+
"id": "87",
162+
"url": "http://os2forms.example.com/system/files/webform/os2forms/1/cover.jpg",
163+
"mime_type": "image/jpeg",
164+
"size": "96757"
165+
}
166+
}
167+
}
168+
}
169+
}
170+
```
171+
172+
### Technical details on linked data
173+
174+
In order to add linked data, we apply a patch,
175+
[webform_rest_submission.patch](patches/webform_rest_submission.patch), to the
176+
Webform REST module and implement an event subscriber,
177+
[WebformSubmissionDataEventSubscriber](src/EventSubscriber/WebformSubmissionDataEventSubscriber.php),
178+
to add the linked data.

composer.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
}
1919
],
2020
"require": {
21+
"cweagans/composer-patches": "^1.7",
2122
"drupal/key_auth": "^2.0",
2223
"drupal/webform_rest": "^4.0"
2324
},
@@ -49,7 +50,16 @@
4950
"config": {
5051
"sort-packages": true,
5152
"allow-plugins": {
52-
"dealerdirect/phpcodesniffer-composer-installer": true
53+
"dealerdirect/phpcodesniffer-composer-installer": true,
54+
"cweagans/composer-patches": true
55+
}
56+
},
57+
"extra": {
58+
"enable-patching": true,
59+
"patches": {
60+
"drupal/webform_rest": {
61+
"Added ability to modify response data sent from Webform Submission endpoint": "https://raw.githubusercontent.com/itk-dev/os2forms_rest_api/feature/linked-data/patches/webform_rest_submission.patch"
62+
}
5363
}
5464
}
5565
}

os2forms_rest_api.services.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
services:
2+
logger.channel.os2forms_rest_api:
3+
parent: logger.channel_base
4+
arguments: [ 'os2forms_rest_api' ]
5+
26
Drupal\os2forms_rest_api\WebformHelper:
37
arguments:
48
- '@entity_type.manager'
59
- '@current_user'
610
- '@key_auth.authentication.key_auth'
711
- '@request_stack'
812

9-
Drupal\os2forms_rest_api\EventSubscriber\EventSubscriber:
13+
Drupal\os2forms_rest_api\EventSubscriber\WebformAccessEventSubscriber:
1014
arguments:
1115
- '@current_route_match'
1216
- '@current_user'
1317
- '@Drupal\os2forms_rest_api\WebformHelper'
1418
tags:
1519
- { name: 'event_subscriber' }
1620

21+
Drupal\os2forms_rest_api\EventSubscriber\WebformSubmissionDataEventSubscriber:
22+
arguments:
23+
- '@entity_type.manager'
24+
- '@logger.channel.os2forms_rest_api'
25+
tags:
26+
- { name: 'event_subscriber' }
27+
1728
# Overwrite, adding global tag
1829
# @see https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/altering-existing-services-providing-dynamic-services
1930
key_auth.authentication.key_auth:
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
diff --git a/src/Event/WebformSubmissionDataEvent.php b/src/Event/WebformSubmissionDataEvent.php
2+
new file mode 100644
3+
index 0000000..c378f45
4+
--- /dev/null
5+
+++ b/src/Event/WebformSubmissionDataEvent.php
6+
@@ -0,0 +1,52 @@
7+
+<?php
8+
+
9+
+namespace Drupal\webform_rest\Event;
10+
+
11+
+use Drupal\Component\EventDispatcher\Event;
12+
+use Drupal\webform\WebformSubmissionInterface;
13+
+
14+
+/**
15+
+ * Class WebformSubmissionDataEvent, an event to modify the data part of the response sent from calling GET webform submission.
16+
+ */
17+
+class WebformSubmissionDataEvent extends Event
18+
+{
19+
+ /**
20+
+ * @var WebformSubmissionInterface
21+
+ */
22+
+ private WebformSubmissionInterface $webformSubmission;
23+
+
24+
+ /**
25+
+ * @var array
26+
+ */
27+
+ private array $data;
28+
+
29+
+ /**
30+
+ * Construct for injection dependency.
31+
+ *
32+
+ * @param array $data
33+
+ * @param WebformSubmissionInterface $webformSubmission
34+
+ */
35+
+ public function __construct(WebformSubmissionInterface $webformSubmission, array $data) {
36+
+ $this->webformSubmission = $webformSubmission;
37+
+ $this->setData($data);
38+
+ }
39+
+
40+
+ /**
41+
+ * @return WebformSubmissionInterface
42+
+ */
43+
+ public function getWebformSubmission(): WebformSubmissionInterface
44+
+ {
45+
+ return $this->webformSubmission;
46+
+ }
47+
+
48+
+ public function getData(): array
49+
+ {
50+
+ return $this->data;
51+
+ }
52+
+
53+
+ public function setData(array $data)
54+
+ {
55+
+ $this->data = $data;
56+
+ return $this;
57+
+ }
58+
+}
59+
diff --git a/src/Plugin/rest/resource/WebformSubmissionResource.php b/src/Plugin/rest/resource/WebformSubmissionResource.php
60+
index d2e08c5..a9cacf7 100644
61+
--- a/src/Plugin/rest/resource/WebformSubmissionResource.php
62+
+++ b/src/Plugin/rest/resource/WebformSubmissionResource.php
63+
@@ -5,6 +5,7 @@ namespace Drupal\webform_rest\Plugin\rest\resource;
64+
use Drupal\webform\WebformSubmissionForm;
65+
use Drupal\rest\Plugin\ResourceBase;
66+
use Drupal\rest\ModifiedResourceResponse;
67+
+use Drupal\webform_rest\Event\WebformSubmissionDataEvent;
68+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
69+
use Symfony\Component\DependencyInjection\ContainerInterface;
70+
71+
@@ -35,6 +36,13 @@ class WebformSubmissionResource extends ResourceBase {
72+
*/
73+
protected $request;
74+
75+
+ /**
76+
+ * An event dispatcher instance to use for dispatching events.
77+
+ *
78+
+ * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
79+
+ */
80+
+ protected $eventDispatcher;
81+
+
82+
/**
83+
* {@inheritdoc}
84+
*/
85+
@@ -42,6 +50,7 @@ class WebformSubmissionResource extends ResourceBase {
86+
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
87+
$instance->entityTypeManager = $container->get('entity_type.manager');
88+
$instance->request = $container->get('request_stack');
89+
+ $instance->eventDispatcher = $container->get('event_dispatcher');
90+
return $instance;
91+
}
92+
93+
@@ -91,9 +100,13 @@ class WebformSubmissionResource extends ResourceBase {
94+
// Grab submission data.
95+
$data = $webform_submission->getData();
96+
97+
+ // Dispatch WebformSubmissionDataEvent to allow modification of data.
98+
+ $event = new WebformSubmissionDataEvent($webform_submission, $data);
99+
+ $this->eventDispatcher->dispatch($event);
100+
+
101+
$response = [
102+
'entity' => $webform_submission,
103+
- 'data' => $data,
104+
+ 'data' => $event->getData(),
105+
];
106+
107+
// Return the submission.

src/EventSubscriber/EventSubscriber.php renamed to src/EventSubscriber/WebformAccessEventSubscriber.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
use Symfony\Component\HttpKernel\KernelEvents;
1313

1414
/**
15-
* Event subscriber.
15+
* Webform access event subscriber.
1616
*/
17-
class EventSubscriber implements EventSubscriberInterface {
17+
class WebformAccessEventSubscriber implements EventSubscriberInterface {
1818
/**
1919
* The route match.
2020
*
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
namespace Drupal\os2forms_rest_api\EventSubscriber;
4+
5+
use Drupal\Core\Entity\EntityTypeManagerInterface;
6+
use Drupal\file\FileInterface;
7+
use Drupal\webform\WebformInterface;
8+
use Drupal\webform_rest\Event\WebformSubmissionDataEvent;
9+
use Psr\Log\LoggerAwareTrait;
10+
use Psr\Log\LoggerInterface;
11+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
12+
13+
/**
14+
* WebformSubmissionEventSubscriber, for updating Webform Submission GET data.
15+
*/
16+
class WebformSubmissionDataEventSubscriber implements EventSubscriberInterface {
17+
use LoggerAwareTrait;
18+
19+
/**
20+
* Entity type manager.
21+
*
22+
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
23+
*/
24+
private EntityTypeManagerInterface $entityTypeManager;
25+
26+
/**
27+
* Map from entity type to webform element types.
28+
*/
29+
private const LINKED_ELEMENT_TYPES = [
30+
'file' => [
31+
'webform_image_file',
32+
'webform_document_file',
33+
'webform_video_file',
34+
'webform_audio_file',
35+
'managed_file',
36+
],
37+
];
38+
39+
/**
40+
* Constructor.
41+
*/
42+
public function __construct(EntityTypeManagerInterface $entityTypeManager, LoggerInterface $logger) {
43+
$this->entityTypeManager = $entityTypeManager;
44+
$this->setLogger($logger);
45+
}
46+
47+
/**
48+
* Event handler.
49+
*/
50+
public function onWebformSubmissionDataEvent(WebformSubmissionDataEvent $event): void {
51+
$linkedData = $this->buildLinked($event->getWebformSubmission()->getWebform(), $event->getData());
52+
53+
if (!empty($linkedData)) {
54+
$event->setData($event->getData() + ['linked' => $linkedData]);
55+
}
56+
}
57+
58+
/**
59+
* Builds linked entity data.
60+
*
61+
* @see https://support.deskpro.com/en/guides/developers/deskpro-api/basics/sideloading
62+
*
63+
* @phpstan-param array<string, mixed> $data
64+
* @phpstan-return array<string, mixed>
65+
*/
66+
private function buildLinked(WebformInterface $webform, array $data): array {
67+
$linked = [];
68+
$elements = $webform->getElementsDecodedAndFlattened();
69+
70+
foreach ($elements as $name => $element) {
71+
if (!isset($data[$name])) {
72+
continue;
73+
}
74+
75+
$linkedEntityType = NULL;
76+
if (isset($element['#target_type'])) {
77+
$linkedEntityType = $element['#target_type'];
78+
}
79+
else {
80+
foreach (self::LINKED_ELEMENT_TYPES as $entityType => $elementTypes) {
81+
if (in_array($element['#type'], $elementTypes, TRUE)) {
82+
$linkedEntityType = $entityType;
83+
break;
84+
}
85+
}
86+
}
87+
88+
if (NULL !== $linkedEntityType) {
89+
// $data[$name] is either a string id i.e. '127',
90+
// or an array of string ids i.e. ['127', '128'].
91+
// Casting to array allow us to handle both cases the same way.
92+
$values = (array) $data[$name];
93+
$entities = $this->entityTypeManager->getStorage($linkedEntityType)->loadMultiple($values);
94+
95+
foreach ($entities as $value => $entity) {
96+
$link = [];
97+
if ($entity instanceof FileInterface) {
98+
$link = [
99+
'id' => $entity->id(),
100+
'url' => $entity->createFileUrl(FALSE),
101+
'mime_type' => $entity->getMimeType(),
102+
'size' => $entity->getSize(),
103+
];
104+
}
105+
else {
106+
$this->logger->warning(sprintf('Unhandled linked entity type %s', $linkedEntityType));
107+
}
108+
if (!empty($link)) {
109+
$linked[$name][$value] = $link;
110+
}
111+
}
112+
}
113+
}
114+
return $linked;
115+
}
116+
117+
/**
118+
* {@inheritdoc}
119+
*/
120+
public static function getSubscribedEvents() {
121+
return [
122+
WebformSubmissionDataEvent::class => ['onWebformSubmissionDataEvent'],
123+
];
124+
}
125+
126+
}

src/WebformHelper.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ private function getAllowedUsers(WebformInterface $webform): array {
251251
* True if user has access to the webform.
252252
*/
253253
public function hasWebformAccess(WebformInterface $webform, $user): bool {
254-
$userId = $user instanceof AccountInterface ? $user->id() : $user;
254+
// AccountInterface::id() should return an `int` but actually returns a
255+
// `string`.
256+
$userId = (int) ($user instanceof AccountInterface ? $user->id() : $user);
255257
assert(is_int($userId));
256258

257259
$allowedUsers = $this->getAllowedUsers($webform);

0 commit comments

Comments
 (0)