Skip to content

Commit 4b5c31b

Browse files
authored
Merge pull request #5 from OS2Forms/feature/FORMS-984-rest-webform-all-submissions
FORMS-984: Added rest webform endpoint for getting all submissions
2 parents f84e10f + 75949f0 commit 4b5c31b

File tree

5 files changed

+259
-22
lines changed

5 files changed

+259
-22
lines changed

.github/workflows/pr.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
runs-on: ubuntu-latest
2424
strategy:
2525
matrix:
26-
php-versions: [ '7.4', '8.0', '8.1' ]
26+
php-versions: [ '8.1' ]
2727
dependency-version: [ prefer-lowest, prefer-stable ]
2828
steps:
2929
- uses: actions/checkout@master
@@ -55,7 +55,7 @@ jobs:
5555
runs-on: ubuntu-latest
5656
strategy:
5757
matrix:
58-
php-versions: [ '7.4', '8.0', '8.1' ]
58+
php-versions: [ '8.1' ]
5959
dependency-version: [ prefer-lowest, prefer-stable ]
6060
steps:
6161
- uses: actions/checkout@master
@@ -88,7 +88,7 @@ jobs:
8888
runs-on: ubuntu-latest
8989
strategy:
9090
matrix:
91-
php-versions: [ '7.4', '8.0', '8.1' ]
91+
php-versions: [ '8.1' ]
9292
dependency-version: [ prefer-lowest, prefer-stable ]
9393
steps:
9494
- uses: actions/checkout@master

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ about writing changes to this log.
88

99
## [Unreleased]
1010

11+
- Added endpoint for getting all submissions on a webform.
12+
- **Updated** permissions such that users must be given access explicitly.
13+
1114
## [1.1.0]
1215

1316
### Added

README.md

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,28 @@ vendor/bin/drush pm:enable os2forms_rest_api
1515
We use [Key auth](https://www.drupal.org/project/key_auth) for authenticating
1616
api users.
1717

18-
A user can access the Webforrm REST API if
18+
A user can access the Webform REST API if
1919

20-
1. it has the “OS2Form REST API user” (`os2forms_rest_api_user`) role and
21-
2. has a generated key (User > Edit > Key authentication; `/user/«user
20+
1. it has the “OS2Form REST API user” (`os2forms_rest_api_user`) role,
21+
2. has been granted access to the form
22+
(see [Custom access control](#custom-access-control) )
23+
3. has a generated key (User > Edit > Key authentication; `/user/«user
2224
id»/key-auth`).
2325

24-
The “OS2Form REST API user” role gives read-only access to the API. To get read
26+
The “OS2Form REST API user” role gives read-only access to the API. To get write
2527
access, a user must also have the “OS2Form REST API user (write)”
2628
(`os2forms_rest_api_user_write`) role.
2729

2830
## Endpoints
2931

30-
| Name | Path | Methods |
31-
|--------------------|------------------------------------------------|---------|
32-
| Webform Elements | `/webform_rest/{webform_id}/elements` | GET |
33-
| Webform Fields | `/webform_rest/{webform_id}/fields` | GET |
34-
| Webform Submission | `/webform_rest/{webform_id}/submission/{uuid}` | GET |
35-
| Webform Submit | `/webform_rest/submit` | POST |
36-
| File | `/entity/file/{file_id}` | GET |
32+
| Name | Path | Methods |
33+
|---------------------|------------------------------------------------|---------|
34+
| Webform Elements | `/webform_rest/{webform_id}/elements` | GET |
35+
| Webform Fields | `/webform_rest/{webform_id}/fields` | GET |
36+
| Webform Submission | `/webform_rest/{webform_id}/submission/{uuid}` | GET |
37+
| Webform Submissions | `/webform_rest/{webform_id}/submissions` | GET |
38+
| Webform Submit | `/webform_rest/submit` | POST |
39+
| File | `/entity/file/{file_id}` | GET |
3740

3841
## Examples
3942

@@ -125,14 +128,46 @@ Response:
125128

126129
(the `sid` value is a webform submission uuid).
127130

131+
### Webform submissions
132+
133+
You can filter results based on submission time by
134+
adding query parameters to the URL:
135+
136+
| Name | Value | Example |
137+
|-------------|----------------------|--------------|
138+
| `starttime` | [PHP Date and Time Formats](https://www.php.net/manual/en/datetime.formats.php) | `yesterday` |
139+
| `endtime` | [PHP Date and Time Formats](https://www.php.net/manual/en/datetime.formats.php) | `2023-10-23` |
140+
141+
If left out, filtering upon the left out parameter will not be done.
142+
143+
This example requests all submissions on or after October 1st, 2023:
144+
145+
Request:
146+
147+
```sh
148+
> curl --silent --header 'api-key: …' 'https://127.0.0.1:8000/webform_rest/some_webform_id/submissions?starttime=2023-10-01'
149+
```
150+
151+
Response:
152+
153+
```json
154+
{
155+
"webform_id": "some_webform_id",
156+
"starttime": "2023-10-01",
157+
"submissions": {
158+
"123": "https://127.0.0.1:8000/da/webform_rest/some_webform_id/submission/44b1fe1b-ee96-481e-b941-d1219d1dcb55",
159+
"124": "https://127.0.0.1:8000/da/webform_rest/some_webform_id/submission/3652836d-3dab-4919-b880-e82cbbf3c24c"
160+
}
161+
}
162+
```
163+
128164
## Custom access control
129165

130-
To limit access to webforms, you can specify a list of API users that are
166+
To give access to webforms, you need to specify a list of API users that are
131167
allowed to access a webform's data via the API.
132168

133169
Go to Settings > General > Third party settings > OS2Forms > REST API to specify
134-
which users can access a webform's data. **If no users are specified, all API
135-
users can access the data.**
170+
which users can access a webform's data.
136171

137172
### Technical details
138173

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
namespace Drupal\os2forms_rest_api\Plugin\rest\resource;
4+
5+
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
6+
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
7+
use Drupal\Core\Url;
8+
use Drupal\os2forms_rest_api\WebformHelper;
9+
use Drupal\rest\ModifiedResourceResponse;
10+
use Drupal\rest\Plugin\ResourceBase;
11+
use Symfony\Component\DependencyInjection\ContainerInterface;
12+
use Symfony\Component\HttpFoundation\Response;
13+
14+
/**
15+
* Creates a rest resource for retrieving webform submissions.
16+
*
17+
* @RestResource(
18+
* id = "webform_rest_form_submissions",
19+
* label = @Translation("Webform - submissions for a form"),
20+
* uri_paths = {
21+
* "canonical" = "/webform_rest/{webform_id}/submissions"
22+
* }
23+
* )
24+
*/
25+
class WebformAllFormSubmissions extends ResourceBase {
26+
/**
27+
* Allowed DateTime query parameters and their operation.
28+
*/
29+
private const ALLOWED_DATETIME_QUERY_PARAMS = [
30+
'starttime' => '>=',
31+
'endtime' => '<=',
32+
];
33+
34+
/**
35+
* The current request.
36+
*
37+
* @var \Symfony\Component\HttpFoundation\Request
38+
*/
39+
private $currentRequest;
40+
41+
/**
42+
* The entity type manager object.
43+
*
44+
* @var \Drupal\Core\Entity\EntityTypeManager
45+
*/
46+
private $entityTypeManager;
47+
48+
/**
49+
* The webform helper.
50+
*
51+
* @var \Drupal\os2forms_rest_api\WebformHelper
52+
*/
53+
private $webformHelper;
54+
55+
/**
56+
* {@inheritdoc}
57+
*
58+
* @phpstan-param array<string, mixed> $configuration
59+
*/
60+
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
61+
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
62+
63+
$instance->entityTypeManager = $container->get('entity_type.manager');
64+
$instance->currentRequest = $container->get('request_stack')->getCurrentRequest();
65+
$instance->webformHelper = $container->get(WebformHelper::class);
66+
67+
return $instance;
68+
}
69+
70+
/**
71+
* Get submissions for a given webform.
72+
*
73+
* @param string $webform_id
74+
* Webform ID.
75+
*
76+
* @return \Drupal\rest\ModifiedResourceResponse
77+
* Response object.
78+
*/
79+
public function get(string $webform_id): ModifiedResourceResponse {
80+
if (empty($webform_id)) {
81+
$errors = [
82+
'error' => [
83+
'message' => 'Webform ID is required.',
84+
],
85+
];
86+
return new ModifiedResourceResponse($errors, Response::HTTP_BAD_REQUEST);
87+
}
88+
89+
// Attempt finding webform.
90+
$webform = $this->webformHelper->getWebform($webform_id);
91+
92+
if (NULL === $webform) {
93+
$errors = [
94+
'error' => [
95+
'message' => $this->t('Could not find webform with id :webform_id', [':webform_id' => $webform_id]),
96+
],
97+
];
98+
99+
return new ModifiedResourceResponse($errors, Response::HTTP_NOT_FOUND);
100+
}
101+
102+
// Webform access check.
103+
if (!$this->webformHelper->hasWebformAccess($webform, $this->webformHelper->getCurrentUser())) {
104+
$errors = [
105+
'error' => [
106+
'message' => $this->t('Access denied'),
107+
],
108+
];
109+
110+
return new ModifiedResourceResponse($errors, Response::HTTP_UNAUTHORIZED);
111+
}
112+
113+
$result = ['webform_id' => $webform_id];
114+
115+
try {
116+
$submissionEntityStorage = $this->entityTypeManager->getStorage('webform_submission');
117+
}
118+
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
119+
$errors = [
120+
'error' => [
121+
'message' => $this->t('Could not load webform submission storage'),
122+
],
123+
];
124+
125+
return new ModifiedResourceResponse($errors, Response::HTTP_INTERNAL_SERVER_ERROR);
126+
}
127+
128+
// Query for webform submissions with this webform_id.
129+
$submissionQuery = $submissionEntityStorage->getQuery()
130+
->condition('webform_id', $webform_id);
131+
132+
$requestQuery = $this->currentRequest->query;
133+
134+
foreach (self::ALLOWED_DATETIME_QUERY_PARAMS as $param => $operator) {
135+
$value = $requestQuery->get($param);
136+
137+
if (!empty($value)) {
138+
try {
139+
$dateTime = new \DateTimeImmutable($value);
140+
$submissionQuery->condition('created', $dateTime->getTimestamp(), $operator);
141+
$result[$param] = $value;
142+
}
143+
catch (\Exception $e) {
144+
$errors = [
145+
'error' => [
146+
'message' => $this->t('Invalid :param: :value', [':param' => $param, ':value' => $value]),
147+
],
148+
];
149+
150+
return new ModifiedResourceResponse($errors, Response::HTTP_BAD_REQUEST);
151+
}
152+
}
153+
}
154+
155+
// Complete query.
156+
$submissionQuery->accessCheck(FALSE);
157+
$sids = $submissionQuery->execute();
158+
159+
// Generate submission URLs.
160+
try {
161+
$result['submissions'] = array_map(
162+
static fn($submission) => Url::fromRoute(
163+
'rest.webform_rest_submission.GET',
164+
[
165+
'webform_id' => $webform_id,
166+
'uuid' => $submission->uuid(),
167+
]
168+
)
169+
->setAbsolute()
170+
->toString(TRUE)->getGeneratedUrl(),
171+
$submissionEntityStorage->loadMultiple($sids ?: [])
172+
);
173+
}
174+
catch (\Exception $e) {
175+
$errors = [
176+
'error' => [
177+
'message' => $this->t('Could not generate submission URLs'),
178+
],
179+
];
180+
181+
return new ModifiedResourceResponse($errors, Response::HTTP_INTERNAL_SERVER_ERROR);
182+
}
183+
184+
return new ModifiedResourceResponse($result);
185+
}
186+
187+
}

src/WebformHelper.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,10 @@ public function webformThirdPartySettingsFormAlter(array &$form, FormStateInterf
100100
$routes = [
101101
'rest.webform_rest_elements.GET',
102102
'rest.webform_rest_fields.GET',
103+
'rest.webform_rest_form_submissions.GET',
103104
'rest.webform_rest_submission.GET',
104105
];
106+
105107
$requireUuid = static function ($route) {
106108
return in_array(
107109
$route,
@@ -141,11 +143,11 @@ public function webformThirdPartySettingsFormAlter(array &$form, FormStateInterf
141143
/** @var \Drupal\user\Entity\User $apiUser */
142144
$apiUser = $this->entityTypeManager->getStorage('user')->load($this->currentUser->id());
143145
// Don't show API data links if current user is not included in
144-
// (non-empty) list of allowed users.
145-
if (!empty($allowedUsers) && !isset($allowedUsers[$apiUser->id()])) {
146+
// list of allowed users.
147+
if (!isset($allowedUsers[$apiUser->id()])) {
146148
$apiUser = NULL;
147149
}
148-
$apiKey = $apiUser ? $apiUser->api_key->value : NULL;
150+
$apiKey = $apiUser?->api_key->value;
149151
if (!empty($apiKey)) {
150152
$form['third_party_settings']['os2forms']['os2forms_rest_api']['api_info']['endpoints_test'] = [
151153
'#type' => 'fieldset',
@@ -258,7 +260,7 @@ public function hasWebformAccess(WebformInterface $webform, $user): bool {
258260

259261
$allowedUsers = $this->getAllowedUsers($webform);
260262

261-
return empty($allowedUsers) || isset($allowedUsers[$userId]);
263+
return isset($allowedUsers[$userId]);
262264
}
263265

264266
/**
@@ -278,7 +280,7 @@ private function loadUsers(array $spec): array {
278280
*
279281
* Note: This is only used to deny access to a file that is attached to a
280282
* webform (submission) that the user does not have permission to access.
281-
* Permission to access private files are handles elsewhere.
283+
* Permission to access private files are handled elsewhere.
282284
*
283285
* @phpstan-return int|array<string, string>|null
284286
*/
@@ -307,4 +309,14 @@ public function fileDownload(string $uri) {
307309
return NULL;
308310
}
309311

312+
/**
313+
* Return current user.
314+
*
315+
* @return \Drupal\Core\Session\AccountProxyInterface
316+
* The current user.
317+
*/
318+
public function getCurrentUser(): AccountProxyInterface {
319+
return $this->currentUser;
320+
}
321+
310322
}

0 commit comments

Comments
 (0)