Skip to content

Commit c7762ba

Browse files
committed
5189: Refactored action URL validation. Added runtime check.
1 parent fdb5ee6 commit c7762ba

File tree

2 files changed

+86
-59
lines changed

2 files changed

+86
-59
lines changed

modules/os2forms_digital_post/src/Helper/MeMoHelper.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use DigitalPost\MeMo\MessageHeader;
1515
use DigitalPost\MeMo\Recipient;
1616
use DigitalPost\MeMo\Sender;
17+
use Drupal\Component\Utility\UrlHelper;
18+
use Drupal\Core\StringTranslation\TranslatableMarkup;
1719
use Drupal\os2forms_digital_post\Model\Document;
1820
use Drupal\os2forms_digital_post\Plugin\WebformHandler\WebformHandlerSF1601;
1921
use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult;
@@ -175,6 +177,9 @@ private function buildAction(array $options, WebformSubmissionInterface $submiss
175177
}
176178
elseif ($options['url']) {
177179
$url = $this->replaceTokens($options['url'], $submission);
180+
if ($message = self::validateActionUrl($url, $options)) {
181+
throw new \RuntimeException((string) $message);
182+
}
178183
$action->setEntryPoint(
179184
(new EntryPoint())
180185
->setUrl($url)
@@ -184,4 +189,67 @@ private function buildAction(array $options, WebformSubmissionInterface $submiss
184189
return $action;
185190
}
186191

192+
/**
193+
* Validate an action URL.
194+
*
195+
* @param string $url
196+
* The URL.
197+
* @param array $options
198+
* The options.
199+
*
200+
* @return \Drupal\Core\StringTranslation\TranslatableMarkup|null
201+
* A message if the URL is not valid for an action.
202+
*/
203+
public static function validateActionUrl(string $url, array $options): ?TranslatableMarkup {
204+
// URL must be absolute and use https (cf. https://digitaliser.dk/digital-post/nyhedsarkiv/2024/nov/oeget-validering-i-digital-post)
205+
if (!UrlHelper::isValid($url, absolute: TRUE)) {
206+
return new TranslatableMarkup('URL <code>@url</code> for action %action must be absolute, i.e. start with <code>https://</code>.', [
207+
'@url' => $url,
208+
'%action' => self::getTranslatedActionName($options['action']),
209+
]);
210+
}
211+
elseif ('https' !== parse_url($url, PHP_URL_SCHEME)) {
212+
return new TranslatableMarkup('URL <code>@url</code> for action %action must use the <code>https</code> scheme, i.e. start with <code>https://</code>.', [
213+
'@url' => $url,
214+
'%action' => self::getTranslatedActionName($options['action']),
215+
]);
216+
}
217+
}
218+
219+
/**
220+
* Translated action names.
221+
*
222+
* @var array|null
223+
*
224+
* @phpstan-var array<string, string>
225+
*/
226+
private static ?array $translatedActionNames = NULL;
227+
228+
/**
229+
* Get translated action names.
230+
*/
231+
public static function getTranslatedActionNames(): array {
232+
if (NULL === self::$translatedActionNames) {
233+
self::$translatedActionNames = [
234+
SF1601::ACTION_AFTALE => (string) new TranslatableMarkup('Aftale', [], ['context' => 'memo action']),
235+
SF1601::ACTION_BEKRAEFT => (string) new TranslatableMarkup('Bekræft', [], ['context' => 'memo action']),
236+
SF1601::ACTION_BETALING => (string) new TranslatableMarkup('Betaling', [], ['context' => 'memo action']),
237+
SF1601::ACTION_FORBEREDELSE => (string) new TranslatableMarkup('Forberedelse', [], ['context' => 'memo action']),
238+
SF1601::ACTION_INFORMATION => (string) new TranslatableMarkup('Information', [], ['context' => 'memo action']),
239+
SF1601::ACTION_SELVBETJENING => (string) new TranslatableMarkup('Selvbetjening', [], ['context' => 'memo action']),
240+
SF1601::ACTION_TILMELDING => (string) new TranslatableMarkup('Tilmelding', [], ['context' => 'memo action']),
241+
SF1601::ACTION_UNDERSKRIV => (string) new TranslatableMarkup('Underskriv', [], ['context' => 'memo action']),
242+
];
243+
}
244+
245+
return self::$translatedActionNames;
246+
}
247+
248+
/**
249+
* Get translated action name.
250+
*/
251+
public static function getTranslatedActionName(string $action): string {
252+
return self::$translatedActionNames[$action] ?? $action;
253+
}
254+
187255
}

modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php

Lines changed: 18 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace Drupal\os2forms_digital_post\Plugin\WebformHandler;
44

5-
use Drupal\Component\Utility\UrlHelper;
65
use Drupal\Core\Form\FormStateInterface;
6+
use Drupal\os2forms_digital_post\Helper\MeMoHelper;
77
use Drupal\os2forms_digital_post\Helper\WebformHelperSF1601;
88
use Drupal\webform\Plugin\WebformHandlerBase;
99
use Drupal\webform\WebformSubmissionInterface;
@@ -137,23 +137,18 @@ public function buildConfigurationForm(array $form, FormStateInterface $formStat
137137
'#description' => $this->t('Remove an action by clearing %action and saving.', ['%action' => (string) $this->t('Action')]),
138138
];
139139

140+
$form[self::MEMO_ACTIONS]['message'] = [
141+
'#markup' => $this->t('<strong>Important</strong>: All action URLs must be absolute and secure, i.e. start with <code>https://</code>.'),
142+
];
143+
140144
$form[self::MEMO_ACTIONS]['actions'] = [
141145
'#type' => 'table',
142146
];
143147

144-
$actionOptions = [
145-
// @todo Handle SF1601::ACTION_AFTALE.
146-
SF1601::ACTION_BEKRAEFT => $this->getTranslatedActionName(SF1601::ACTION_BEKRAEFT),
147-
SF1601::ACTION_BETALING => $this->getTranslatedActionName(SF1601::ACTION_BETALING),
148-
SF1601::ACTION_FORBEREDELSE => $this->getTranslatedActionName(SF1601::ACTION_FORBEREDELSE),
149-
SF1601::ACTION_INFORMATION => $this->getTranslatedActionName(SF1601::ACTION_INFORMATION),
150-
SF1601::ACTION_SELVBETJENING => $this->getTranslatedActionName(SF1601::ACTION_SELVBETJENING),
151-
SF1601::ACTION_TILMELDING => $this->getTranslatedActionName(SF1601::ACTION_TILMELDING),
152-
SF1601::ACTION_UNDERSKRIV => $this->getTranslatedActionName(SF1601::ACTION_UNDERSKRIV),
153-
];
148+
$actionOptions = MeMoHelper::getTranslatedActionNames();
154149
$actions = $this->configuration[self::MEMO_ACTIONS]['actions'] ?? [];
155150
for ($i = 0; $i <= count($actions); $i++) {
156-
$action = $actions[$i];
151+
$action = $actions[$i] ?? [];
157152
$form[self::MEMO_ACTIONS]['actions'][$i]['action'] = [
158153
'#type' => 'select',
159154
'#title' => $this->t('Action'),
@@ -276,31 +271,24 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
276271
}
277272
else {
278273
$url = $action['url'];
279-
// URL must be absolute and use https (cf. https://digitaliser.dk/digital-post/nyhedsarkiv/2024/nov/oeget-validering-i-digital-post)
280-
if (!UrlHelper::isValid($url, absolute: TRUE)) {
281-
$formState->setErrorByName(
282-
self::MEMO_ACTIONS . '][actions][' . $index . '][url',
283-
$this->t('Url <code>@url</code> for action %action must be absolute, i.e. start with <code>https://</code>.', [
284-
'@url' => $url,
285-
'%action' => $this->getTranslatedActionName($action['action']),
286-
])
287-
);
274+
// Add warning if URL contains tokens.
275+
if (preg_match('/\[[a-z_:]+/', $url)) {
276+
$message = $this->t('Make sure that the tokens in the URL <code>@url</code> for action %action expands to an absolute URL, i.e. something starting with <code>https://</code>.', [
277+
'@url' => $url,
278+
'%action' => MeMoHelper::getTranslatedActionName($action['action']),
279+
]);
280+
$this->messenger()->addWarning($message);
281+
continue;
288282
}
289-
elseif ('https' !== parse_url($url, PHP_URL_SCHEME)) {
290-
$formState->setErrorByName(
291-
self::MEMO_ACTIONS . '][actions][' . $index . '][url',
292-
$this->t('Url <code>@url</code> for action %action must use the <code>https</code> scheme, i.e. start with <code>https://</code>.', [
293-
'@url' => $url,
294-
'%action' => $this->getTranslatedActionName($action['action']),
295-
])
296-
);
283+
if ($message = MeMoHelper::validateActionUrl($url, $action)) {
284+
$formState->setErrorByName(self::MEMO_ACTIONS . '][actions][' . $index . '][url', $message);
297285
}
298286
}
299287
if (isset($definedActions[$action['action']])) {
300288
$formState->setErrorByName(
301289
self::MEMO_ACTIONS . '][actions][' . $index . '][action',
302290
$this->t('Action %action already defined.', [
303-
'%action' => $this->getTranslatedActionName($action['action']),
291+
'%action' => MeMoHelper::getTranslatedActionName($action['action']),
304292
])
305293
);
306294
}
@@ -359,33 +347,4 @@ public function postPurge(array $webformSubmissions) {
359347
$this->helper->deleteMessages($webformSubmissions);
360348
}
361349

362-
/**
363-
* Translated action names.
364-
*
365-
* @var array|null
366-
*
367-
* @phpstan-var array<string, string>
368-
*/
369-
private ?array $translatedActionNames = NULL;
370-
371-
/**
372-
* Get translated action name.
373-
*/
374-
private function getTranslatedActionName(string $action): string {
375-
if (NULL === $this->translatedActionNames) {
376-
$this->translatedActionNames = [
377-
SF1601::ACTION_AFTALE => (string) $this->t('Aftale', [], ['context' => 'memo action']),
378-
SF1601::ACTION_BEKRAEFT => (string) $this->t('Bekræft', [], ['context' => 'memo action']),
379-
SF1601::ACTION_BETALING => (string) $this->t('Betaling', [], ['context' => 'memo action']),
380-
SF1601::ACTION_FORBEREDELSE => (string) $this->t('Forberedelse', [], ['context' => 'memo action']),
381-
SF1601::ACTION_INFORMATION => (string) $this->t('Information', [], ['context' => 'memo action']),
382-
SF1601::ACTION_SELVBETJENING => (string) $this->t('Selvbetjening', [], ['context' => 'memo action']),
383-
SF1601::ACTION_TILMELDING => (string) $this->t('Tilmelding', [], ['context' => 'memo action']),
384-
SF1601::ACTION_UNDERSKRIV => (string) $this->t('Underskriv', [], ['context' => 'memo action']),
385-
];
386-
}
387-
388-
return $this->translatedActionNames[$action] ?? $action;
389-
}
390-
391350
}

0 commit comments

Comments
 (0)