diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4526355..fbba0e97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,8 +13,10 @@ before starting to add changes. Use example [placed in the end of the page](#exa
## [Unreleased]
-- [PR-325](https://github.com/OS2Forms/os2forms/pull/325)
- Added NemID first name, middle name and last name webform elements.
+- Moved digital signature configuration (which attachment to sign and the
+ signature validation text position) from the *OS2Forms Attachment* element
+ onto the *Digital Signature* webform handler. Includes an automatic
+ migration update hook for existing webforms.
## [5.1.0] 2026-06-03
diff --git a/build_release.sh b/build_release.sh
index c1f358a2..0ae94a1e 100755
--- a/build_release.sh
+++ b/build_release.sh
@@ -17,6 +17,17 @@ git merge --no-ff origin/feature/add-first-middle-and-last-name-nemid-elements
git checkout --theirs CHANGELOG.md
git add CHANGELOG.md
git commit -m "Merge remote-tracking branch 'origin/feature/add-first-middle-and-last-name-nemid-elements' into selvbetjening.aarhuskommune.dk"
+git merge --no-ff origin/feature/add-states-configuration-to-handlers
+git checkout --theirs CHANGELOG.md
+git add CHANGELOG.md
+git commit -m "Merge remote-tracking branch 'origin/feature/add-states-configuration-to-handlers' into selvbetjening.aarhuskommune.dk"
+git merge --no-ff origin/feature/move-digital-signature-configuration-to-handler
+# Manually handle conflicts in modules/os2forms_digital_signature/os2forms_digital_signature.install
+# git add modules/os2forms_digital_signature/os2forms_digital_signature.install
+git checkout --theirs CHANGELOG.md
+git add CHANGELOG.md
+git commit -m "Merge remote-tracking branch 'origin/feature/move-digital-signature-configuration-to-handler' into selvbetjening.aarhuskommune.dk"
+
docker run --rm --volume .:/app itkdev/php8.4-fpm:latest sed --in-place '1i This is a hack so the CHANGELOG is messed up. \nGenerated by build_release.sh' CHANGELOG.md
git add CHANGELOG.md
diff --git a/modules/os2forms_attachment/src/Element/AttachmentElement.php b/modules/os2forms_attachment/src/Element/AttachmentElement.php
index bbc33f0b..96f9a91e 100644
--- a/modules/os2forms_attachment/src/Element/AttachmentElement.php
+++ b/modules/os2forms_attachment/src/Element/AttachmentElement.php
@@ -58,6 +58,23 @@ public static function getFileContent(array $element, WebformSubmissionInterface
if ($element['#export_type'] === 'pdf') {
$file_path = NULL;
+ // Digital signature settings live on the digital signature handler.
+ // Resolving them here ensures all consumers of the element (email
+ // handlers, attachment downloads) serve an already signed document
+ // and render the validation text consistently.
+ $elementKey = $element['#webform_key'] ?? NULL;
+ if ($elementKey !== NULL) {
+ foreach ($webform_submission->getWebform()->getHandlers('os2forms_digital_signature') as $handler) {
+ $settings = $handler->getConfiguration()['settings'] ?? [];
+ if ($handler->isEnabled() && ($settings['attachment_element'] ?? '') === $elementKey) {
+ $element['#digital_signature'] = TRUE;
+ $element['#digital_signature_position'] = $settings['signature_position']
+ ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT;
+ break;
+ }
+ }
+ }
+
// If attachment with digital signatur, check if we already have one.
if (isset($element['#digital_signature']) && $element['#digital_signature']) {
// Get scheme.
diff --git a/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php b/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php
index e19c531a..4f2215d6 100644
--- a/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php
+++ b/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php
@@ -5,7 +5,6 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\webform\Twig\WebformTwigExtension;
use Drupal\webform\Utility\WebformElementHelper;
-use Drupal\os2forms_attachment\Os2formsAttachmentPrintBuilder;
use Drupal\webform_attachment\Plugin\WebformElement\WebformAttachmentBase;
/**
@@ -28,8 +27,6 @@ protected function defineDefaultProperties() {
'view_mode' => 'html',
'template' => '',
'export_type' => '',
- 'digital_signature' => '',
- 'digital_signature_position' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT,
'exclude_empty' => '',
'exclude_empty_checkbox' => '',
'excluded_elements' => '',
@@ -91,28 +88,6 @@ public function form(array $form, FormStateInterface $form_state) {
'html' => $this->t('HTML'),
],
];
- $form['attachment']['digital_signature'] = [
- '#type' => 'checkbox',
- '#title' => $this->t('Digital signature'),
- ];
- $form['attachment']['digital_signature_position'] = [
- '#type' => 'select',
- '#title' => $this->t('Digital signature position'),
- '#description' => $this->t('Select where the digital signature validation text should be placed in the PDF document.'),
- '#options' => [
- Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_FOOTER => $this->t('Footer (repeats on every page)'),
- Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_HEADER => $this->t('Header (repeats on every page)'),
- Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT => $this->t('After content (end of document)'),
- Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_BEFORE_CONTENT => $this->t('Before content (start of document)'),
- ],
- '#default_value' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT,
- '#states' => [
- 'visible' => [
- ':input[name="properties[digital_signature]"]' => ['checked' => TRUE],
- ],
- ],
- ];
-
// Set #access so that help is always visible.
WebformElementHelper::setPropertyRecursive($form['attachment']['help'], '#access', TRUE);
diff --git a/modules/os2forms_digital_post/os2forms_digital_post.install b/modules/os2forms_digital_post/os2forms_digital_post.install
index 48768e21..cb410b1d 100644
--- a/modules/os2forms_digital_post/os2forms_digital_post.install
+++ b/modules/os2forms_digital_post/os2forms_digital_post.install
@@ -6,6 +6,7 @@
*/
use Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper;
+use Drupal\webform\WebformSubmissionInterface;
/**
* Implements hook_schema().
@@ -26,3 +27,34 @@ function os2forms_digital_post_update_9001(): void {
'os2web_key',
], TRUE);
}
+
+/**
+ * Set states config to completed on existing digital post handlers.
+ */
+function os2forms_digital_post_update_10001(): void {
+ // To avoid having to load full webforms we load and update webform configs.
+ $configFactory = \Drupal::configFactory();
+
+ foreach ($configFactory->listAll('webform.webform.') as $name) {
+ $config = $configFactory->getEditable($name);
+ $handlers = $config->get('handlers');
+ if (!is_array($handlers)) {
+ continue;
+ }
+
+ $changed = FALSE;
+
+ foreach ($handlers as $handlerKey => $handler) {
+ // $handler['id'] is the handler plugin id.
+ if (($handler['id'] ?? NULL) !== 'digital_post_sf1601') {
+ continue;
+ }
+ $handlers[$handlerKey]['settings']['additional']['states'] = [WebformSubmissionInterface::STATE_COMPLETED];
+ $changed = TRUE;
+ }
+
+ if ($changed) {
+ $config->set('handlers', $handlers)->save();
+ }
+ }
+}
diff --git a/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php b/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php
index 6f0e06a9..ae633c01 100644
--- a/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php
+++ b/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php
@@ -32,6 +32,8 @@ final class WebformHandlerSF1601 extends WebformHandlerBase {
public const RECIPIENT_ELEMENT = 'recipient_element';
public const ATTACHMENT_ELEMENT = 'attachment_element';
public const SENDER_ADDRESS = 'sender_address';
+ private const string ADDITIONAL = 'additional';
+ private const string STATES = 'states';
/**
* Maximum length of sender label.
@@ -76,6 +78,9 @@ public static function create(ContainerInterface $container, array $configuratio
public function defaultConfiguration() {
return [
'debug' => FALSE,
+ self::ADDITIONAL => [
+ self::STATES => [WebformSubmissionInterface::STATE_COMPLETED],
+ ],
];
}
@@ -203,6 +208,31 @@ public function buildConfigurationForm(array $form, FormStateInterface $formStat
'#default_value' => $this->configuration['debug'] ?? NULL,
];
+ // Additional.
+ // Lifted from EmailWebformHandler::buildConfigurationForm().
+ $resultsDisabled = (bool) $this->getWebform()->getSetting('results_disabled');
+ $form[self::ADDITIONAL] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Additional settings'),
+ ];
+ // Settings: States.
+ $states = (array) ($this->configuration[self::ADDITIONAL][self::STATES] ?? NULL);
+ $form[self::ADDITIONAL][self::STATES] = [
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Run handler when …'),
+ '#options' => [
+ WebformSubmissionInterface::STATE_DRAFT_CREATED => $this->t('draft is created.'),
+ WebformSubmissionInterface::STATE_DRAFT_UPDATED => $this->t('draft is updated.'),
+ WebformSubmissionInterface::STATE_CONVERTED => $this->t('anonymous submission is converted to authenticated.'),
+ WebformSubmissionInterface::STATE_COMPLETED => $this->t('submission is completed.'),
+ WebformSubmissionInterface::STATE_UPDATED => $this->t('submission is updated.'),
+ WebformSubmissionInterface::STATE_DELETED => $this->t('submission is deleted.'),
+ WebformSubmissionInterface::STATE_LOCKED => $this->t('submission is locked.'),
+ ],
+ '#access' => !$resultsDisabled,
+ '#default_value' => $resultsDisabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $states,
+ ];
+
return $this->setSettingsParents($form);
}
@@ -333,6 +363,11 @@ static function (array $action) {
$this->configuration[self::MEMO_ACTIONS] = $actions;
$this->configuration['debug'] = (bool) $formState->getValue('debug');
+
+ $additional = $formState->getValue(self::ADDITIONAL);
+ // Clean up states.
+ $additional[self::STATES] = array_values(array_filter($additional[self::STATES]));
+ $this->configuration[self::ADDITIONAL] = $additional;
}
/**
@@ -341,6 +376,12 @@ static function (array $action) {
* @phpstan-return void
*/
public function postSave(WebformSubmissionInterface $webformSubmission, $update = TRUE) {
+ $submissionState = $webformSubmission->getWebform()->getSetting('results_disabled') ? WebformSubmissionInterface::STATE_COMPLETED : $webformSubmission->getState();
+ $enabledStates = (array) ($this->configuration[self::ADDITIONAL][self::STATES] ?? NULL);
+ if (!in_array($submissionState, $enabledStates)) {
+ return;
+ }
+
$this->helper->createJob($webformSubmission, $this->configuration);
}
diff --git a/modules/os2forms_digital_signature/README.md b/modules/os2forms_digital_signature/README.md
index d863182e..5dd93e2c 100644
--- a/modules/os2forms_digital_signature/README.md
+++ b/modules/os2forms_digital_signature/README.md
@@ -13,12 +13,20 @@ The signature server consists of two parts. A frontend module
### Activating Digital Signature
-1. Add the OS2forms attachment element to the form.
-2. Indicate that the OS2Forms attachment requires a digital signature.
-3. Add the Digital Signature Handler to the webform.
+1. Add an attachment-style element to the form (either *OS2Forms Attachment* — generated PDF — or
+ *OS2forms digital signature document* — uploaded PDF).
+2. Add the **Digital Signature** handler to the webform.
+3. In the handler configuration, pick the attachment element to sign from the **Attachment element to sign**
+ dropdown, and choose where the signature validation text should be placed in the generated PDF
+ (the position only applies to *OS2Forms Attachment* elements; it is ignored for uploaded PDFs).
4. If the form requires an email handler, ensure the trigger is set to **...when submission is locked** in the handler’s
*Additional settings*.
+> [!NOTE]
+> Prior to this release, the attachment element exposed `Digital signature` and `Digital signature position` properties
+> directly. These settings have moved to the handler. Existing forms are migrated automatically by
+> `os2forms_digital_signature_update_10001()`.
+
### Flow Explained
1. Upon form submission, a PDF is generated, saved in the private directory, and sent to the signature service via URL.
diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml
index 29547e43..99abd7c5 100644
--- a/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml
+++ b/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml
@@ -5,5 +5,6 @@ package: 'OS2Forms'
core_version_requirement: ^9 || ^10
dependencies:
- 'webform:webform'
+ - 'os2forms:os2forms_attachment'
configure: os2forms_digital_signature.settings
diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.install b/modules/os2forms_digital_signature/os2forms_digital_signature.install
new file mode 100644
index 00000000..37b57d85
--- /dev/null
+++ b/modules/os2forms_digital_signature/os2forms_digital_signature.install
@@ -0,0 +1,147 @@
+accessCheck(FALSE)->execute());
+ $sandbox['migrated'] = 0;
+ }
+
+ // array_splice() removes the chunk from the sandbox, so the next pass
+ // continues with the remaining ids.
+ /** @var \Drupal\webform\WebformInterface[] $webforms */
+ $webforms = Webform::loadMultiple(array_splice($sandbox['ids'], 0, 25));
+ foreach ($webforms as $webform) {
+ $handlers = $webform->getHandlers();
+ $signatureHandler = NULL;
+ foreach ($handlers as $handler) {
+ if ($handler->getPluginId() === 'os2forms_digital_signature') {
+ $signatureHandler = $handler;
+ break;
+ }
+ }
+
+ $elements = $webform->getElementsDecodedAndFlattened();
+ $needsSave = FALSE;
+
+ if ($signatureHandler) {
+ // Mirror the pre-update runtime selection: the handler signed the
+ // first os2forms_digital_signature_document element, falling back to
+ // the first os2forms_attachment element. The #digital_signature
+ // property never influenced the selection, only the rendering of the
+ // validation text.
+ $signedKey = NULL;
+ $signedPosition = Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT;
+ $candidates = [];
+ foreach (['os2forms_digital_signature_document', 'os2forms_attachment'] as $type) {
+ foreach ($elements as $key => $element) {
+ if (($element['#type'] ?? NULL) === $type) {
+ $candidates[] = $key;
+ if ($signedKey === NULL) {
+ $signedKey = $key;
+ if ($type === 'os2forms_attachment') {
+ $signedPosition = $element['#digital_signature_position'] ?? $signedPosition;
+ }
+ }
+ }
+ }
+ }
+
+ if ($signedKey === NULL) {
+ $logger->warning('Skipped webform @id during digital signature config migration: the handler is enabled but the webform has no os2forms_digital_signature_document or os2forms_attachment element. Configure the handler manually.', [
+ '@id' => $webform->id(),
+ ]);
+ }
+ else {
+ if (count($candidates) > 1) {
+ $logger->warning('Webform @id has multiple signable elements (@all); migrating @chosen to match the pre-update type-priority selection.', [
+ '@id' => $webform->id(),
+ '@all' => implode(', ', $candidates),
+ '@chosen' => $signedKey,
+ ]);
+ }
+ foreach ($elements as $key => $element) {
+ if (!empty($element['#digital_signature']) && $key !== $signedKey) {
+ $logger->warning('Webform @id: element @key had digital signature enabled, but @chosen is migrated instead to match the pre-update behavior.', [
+ '@id' => $webform->id(),
+ '@key' => $key,
+ '@chosen' => $signedKey,
+ ]);
+ }
+ }
+
+ // Write the new config onto the handler.
+ $config = $signatureHandler->getConfiguration();
+ $config['settings']['attachment_element'] = $signedKey;
+ $config['settings']['signature_position'] = $signedPosition;
+ $signatureHandler->setConfiguration($config);
+ $needsSave = TRUE;
+ $sandbox['migrated']++;
+ }
+ }
+
+ // Strip the legacy properties whether or not the handler is present, so
+ // no webform is left with inert digital signature configuration.
+ foreach ($elements as $element) {
+ if (array_key_exists('#digital_signature', $element) || array_key_exists('#digital_signature_position', $element)) {
+ $rawElements = $webform->getElementsDecoded();
+ _os2forms_digital_signature_strip_legacy_props($rawElements);
+ $webform->setElements($rawElements);
+ $needsSave = TRUE;
+ break;
+ }
+ }
+
+ if ($needsSave) {
+ $webform->save();
+ }
+ }
+
+ // Anything below 1 makes the runner call us again. Done when no ids remain.
+ $sandbox['#finished'] = empty($sandbox['ids']) ? 1 : 0;
+
+ if ($sandbox['#finished'] >= 1) {
+ return t('Migrated digital signature config on @count webform(s).', ['@count' => $sandbox['migrated']]);
+ }
+}
+
+/**
+ * Recursively remove legacy digital signature element properties.
+ *
+ * @param array $elements
+ * The decoded elements tree, modified in place.
+ */
+function _os2forms_digital_signature_strip_legacy_props(array &$elements) {
+ foreach ($elements as $key => &$value) {
+ if (!is_array($value)) {
+ continue;
+ }
+ if (str_starts_with((string) $key, '#')) {
+ continue;
+ }
+ unset($value['#digital_signature'], $value['#digital_signature_position']);
+ _os2forms_digital_signature_strip_legacy_props($value);
+ }
+}
diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php
index 9a616bfc..85d57f16 100644
--- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php
+++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php
@@ -7,9 +7,11 @@
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
+use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\file\FileRepositoryInterface;
+use Drupal\os2forms_attachment\Os2formsAttachmentPrintBuilder;
use Drupal\os2forms_digital_signature\Service\SigningService;
use Drupal\webform\Plugin\WebformElementManagerInterface;
use Drupal\webform\Plugin\WebformHandlerBase;
@@ -108,13 +110,118 @@ public static function create(ContainerInterface $container, array $configuratio
/**
* {@inheritdoc}
*/
- public function preSave(WebformSubmissionInterface $webform_submission) {
- $webform = $webform_submission->getWebform();
+ public function defaultConfiguration() {
+ return [
+ 'attachment_element' => '',
+ 'signature_position' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form = parent::buildConfigurationForm($form, $form_state);
+
+ $form['attachment_element'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Attachment element to sign'),
+ '#description' => $this->t('Select the webform element whose generated or uploaded PDF should be signed.'),
+ '#options' => $this->getAttachmentElementOptions(),
+ '#empty_option' => $this->t('- Select -'),
+ '#default_value' => $this->configuration['attachment_element'],
+ '#required' => TRUE,
+ ];
+
+ $form['signature_position'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Signature validation text position'),
+ '#description' => $this->t('Where the digital signature validation text is placed in the generated PDF. Only applies when the selected element is an OS2Forms Attachment; ignored for uploaded PDF documents.'),
+ '#options' => [
+ Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_FOOTER => $this->t('Footer (repeats on every page)'),
+ Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_HEADER => $this->t('Header (repeats on every page)'),
+ Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT => $this->t('After content (end of document)'),
+ Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_BEFORE_CONTENT => $this->t('Before content (start of document)'),
+ ],
+ '#default_value' => $this->configuration['signature_position'],
+ ];
+
+ return $this->setSettingsParents($form);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::submitConfigurationForm($form, $form_state);
- if ($webform_submission->isLocked()) {
+ $values = $form_state->getValues();
+ $this->configuration['attachment_element'] = $values['attachment_element'] ?? '';
+ $this->configuration['signature_position'] = $values['signature_position']
+ ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSummary() {
+ $elementKey = $this->configuration['attachment_element'] ?? '';
+ $position = $this->configuration['signature_position'] ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT;
+
+ return [
+ '#markup' => $this->t('Sign attachment: @element
Signature position: @position', [
+ '@element' => $elementKey !== '' ? $elementKey : $this->t('not configured'),
+ '@position' => $position,
+ ]),
+ ];
+ }
+
+ /**
+ * Build the dropdown options for the attachment element selector.
+ *
+ * The signing flow requires a PDF, so only the two element types that
+ * provide one are listed: os2forms_digital_signature_document (uploaded
+ * PDF) and os2forms_attachment (generated PDF).
+ *
+ * @return array
+ * Map of element key => human label.
+ */
+ protected function getAttachmentElementOptions(): array {
+ $webform = $this->getWebform();
+ if (!$webform) {
+ return [];
+ }
+
+ $elements = $webform->getElementsInitializedAndFlattened();
+ $options = [];
+ foreach ($webform->getElementsAttachments() as $key) {
+ $element = $elements[$key] ?? NULL;
+ if (!$element) {
+ continue;
+ }
+ $type = $element['#type'] ?? '';
+ if (!in_array($type, ['os2forms_digital_signature_document', 'os2forms_attachment'], TRUE)) {
+ continue;
+ }
+ $title = $element['#title'] ?? $key;
+ $options[$key] = sprintf('%s (%s) [%s]', $title, $key, $type);
+ }
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function preSave(WebformSubmissionInterface $webform_submission) {
+ // Signing redirects the user to the signing flow, so it only makes sense
+ // on an actual submission. Drafts are skipped, and a locked submission is
+ // already signed (signing locks it).
+ if ($webform_submission->isDraft() || $webform_submission->isLocked()) {
return;
}
+ $webform = $webform_submission->getWebform();
+
$attachment = $this->getSubmissionAttachment($webform_submission);
if (!$attachment) {
$this->logger->error('Attachment cannot be created webform: %webform, webform_submission: %webform_submission',
@@ -177,59 +284,53 @@ public function preSave(WebformSubmissionInterface $webform_submission) {
/**
* Get OS2forms file attachment.
*
+ * Resolves the attachment element configured on the handler, asks its
+ * plugin for the email attachment payload, and returns the first item.
+ *
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* A webform submission.
*
* @return array|null
- * Array of attachment data.
+ * Array of attachment data, or NULL when no attachment is available.
*
* @throws \Exception
*/
protected function getSubmissionAttachment(WebformSubmissionInterface $webform_submission) {
- $attachments = NULL;
- $attachment = NULL;
-
- // Getting all element types that are added to the webform.
- //
- // Priority is the following: check for os2forms_digital_signature_document,
- // is not found try serving os2forms_attachment.
- $elementTypes = array_column($this->getWebform()->getElementsDecodedAndFlattened(), '#type');
- $attachmentType = '';
- if (in_array('os2forms_digital_signature_document', $elementTypes)) {
- $attachmentType = 'os2forms_digital_signature_document';
- }
- elseif (in_array('os2forms_attachment', $elementTypes)) {
- $attachmentType = 'os2forms_attachment';
+ $elementKey = $this->configuration['attachment_element'] ?? '';
+ if ($elementKey === '') {
+ $this->logger->error('Digital signature handler has no attachment_element configured for webform %webform.', [
+ '%webform' => $this->getWebform()->id(),
+ ]);
+ return NULL;
}
$elements = $this->getWebform()->getElementsInitializedAndFlattened();
- $element_attachments = $this->getWebform()->getElementsAttachments();
- foreach ($element_attachments as $element_attachment) {
- // Check if the element attachment key is excluded and should not attach
- // any files.
- if (isset($this->configuration['excluded_elements'][$element_attachment])) {
- continue;
- }
+ if (!isset($elements[$elementKey])) {
+ $this->logger->error('Configured attachment element %element does not exist on webform %webform.', [
+ '%element' => $elementKey,
+ '%webform' => $this->getWebform()->id(),
+ ]);
+ return NULL;
+ }
- $element = $elements[$element_attachment];
+ $element = $elements[$elementKey];
- if ($element['#type'] == $attachmentType) {
- /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */
- $element_plugin = $this->elementManager->getElementInstance($element);
- $attachments = $element_plugin->getEmailAttachments($element, $webform_submission);
+ /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */
+ $element_plugin = $this->elementManager->getElementInstance($element);
+ $attachments = $element_plugin->getEmailAttachments($element, $webform_submission);
- // If we are dealing with an uploaded file, attach the FID.
- if ($fid = $webform_submission->getElementData($element_attachment)) {
- $attachments[0]['fid'] = $fid;
- }
- break;
- }
+ if (empty($attachments)) {
+ return NULL;
}
- if (!empty($attachments)) {
- $attachment = reset($attachments);
+ // If the source is an uploaded managed file, attach the FID so the
+ // signed file can replace the upload rather than creating a new file.
+ if ($fid = $webform_submission->getElementData($elementKey)) {
+ $attachments[0]['fid'] = $fid;
}
+ $attachment = reset($attachments);
+
// For SwiftMailer && Mime Mail use filecontent and not the filepath.
// @see \Drupal\swiftmailer\Plugin\Mail\SwiftMailer::attachAsMimeMail
// @see \Drupal\mimemail\Utility\MimeMailFormatHelper::mimeMailFile
diff --git a/modules/os2forms_fasit/os2forms_fasit.install b/modules/os2forms_fasit/os2forms_fasit.install
index f862bafb..8d471f49 100644
--- a/modules/os2forms_fasit/os2forms_fasit.install
+++ b/modules/os2forms_fasit/os2forms_fasit.install
@@ -5,6 +5,8 @@
* Install hooks for os2forms_fasit.
*/
+use Drupal\webform\WebformSubmissionInterface;
+
/**
* Install Key module.
*/
@@ -13,3 +15,34 @@ function os2forms_fasit_update_9001(): void {
'key',
], TRUE);
}
+
+/**
+ * Set states config to completed on existing fasit handlers.
+ */
+function os2forms_fasit_update_10001(): void {
+ // To avoid having to load full webforms we load and update webform configs.
+ $configFactory = \Drupal::configFactory();
+
+ foreach ($configFactory->listAll('webform.webform.') as $name) {
+ $config = $configFactory->getEditable($name);
+ $handlers = $config->get('handlers');
+ if (!is_array($handlers)) {
+ continue;
+ }
+
+ $changed = FALSE;
+
+ foreach ($handlers as $handlerKey => $handler) {
+ // $handler['id'] is the handler plugin id.
+ if (($handler['id'] ?? NULL) !== 'os2forms_fasit') {
+ continue;
+ }
+ $handlers[$handlerKey]['settings']['additional']['states'] = [WebformSubmissionInterface::STATE_COMPLETED];
+ $changed = TRUE;
+ }
+
+ if ($changed) {
+ $config->set('handlers', $handlers)->save();
+ }
+ }
+}
diff --git a/modules/os2forms_fasit/src/Plugin/WebformHandler/FasitWebformHandler.php b/modules/os2forms_fasit/src/Plugin/WebformHandler/FasitWebformHandler.php
index 84eef379..e726bf44 100644
--- a/modules/os2forms_fasit/src/Plugin/WebformHandler/FasitWebformHandler.php
+++ b/modules/os2forms_fasit/src/Plugin/WebformHandler/FasitWebformHandler.php
@@ -35,6 +35,8 @@ class FasitWebformHandler extends WebformHandlerBase {
public const FASIT_HANDLER_DOCUMENT_DESCRIPTION = 'document_description';
public const FASIT_HANDLER_CPR_ELEMENT = 'cpr_element';
public const FASIT_HANDLER_ATTACHMENT_ELEMENT = 'attachment_element';
+ private const string ADDITIONAL = 'additional';
+ private const string STATES = 'states';
/**
* The submission logger.
@@ -79,6 +81,19 @@ public static function create(ContainerInterface $container, array $configuratio
);
}
+ /**
+ * {@inheritdoc}
+ *
+ * @phpstan-return array
+ */
+ public function defaultConfiguration() {
+ return [
+ self::ADDITIONAL => [
+ self::STATES => [WebformSubmissionInterface::STATE_COMPLETED],
+ ],
+ ];
+ }
+
/**
* {@inheritdoc}
*
@@ -129,6 +144,31 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
'#size' => 5,
];
+ // Additional.
+ // Lifted from EmailWebformHandler::buildConfigurationForm().
+ $resultsDisabled = (bool) $this->getWebform()->getSetting('results_disabled');
+ $form[self::ADDITIONAL] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Additional settings'),
+ ];
+ // Settings: States.
+ $states = (array) ($this->configuration[self::ADDITIONAL][self::STATES] ?? NULL);
+ $form[self::ADDITIONAL][self::STATES] = [
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Run handler when …'),
+ '#options' => [
+ WebformSubmissionInterface::STATE_DRAFT_CREATED => $this->t('draft is created.'),
+ WebformSubmissionInterface::STATE_DRAFT_UPDATED => $this->t('draft is updated.'),
+ WebformSubmissionInterface::STATE_CONVERTED => $this->t('anonymous submission is converted to authenticated.'),
+ WebformSubmissionInterface::STATE_COMPLETED => $this->t('submission is completed.'),
+ WebformSubmissionInterface::STATE_UPDATED => $this->t('submission is updated.'),
+ WebformSubmissionInterface::STATE_DELETED => $this->t('submission is deleted.'),
+ WebformSubmissionInterface::STATE_LOCKED => $this->t('submission is locked.'),
+ ],
+ '#access' => !$resultsDisabled,
+ '#default_value' => $resultsDisabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $states,
+ ];
+
return $this->setSettingsParents($form);
}
@@ -143,12 +183,23 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
$this->configuration[self::FASIT_HANDLER_GENERAL][self::FASIT_HANDLER_DOCUMENT_DESCRIPTION] = $form_state->getValue(self::FASIT_HANDLER_GENERAL)[self::FASIT_HANDLER_DOCUMENT_DESCRIPTION];
$this->configuration[self::FASIT_HANDLER_GENERAL][self::FASIT_HANDLER_CPR_ELEMENT] = $form_state->getValue(self::FASIT_HANDLER_GENERAL)[self::FASIT_HANDLER_CPR_ELEMENT];
$this->configuration[self::FASIT_HANDLER_GENERAL][self::FASIT_HANDLER_ATTACHMENT_ELEMENT] = $form_state->getValue(self::FASIT_HANDLER_GENERAL)[self::FASIT_HANDLER_ATTACHMENT_ELEMENT];
+
+ $additional = $form_state->getValue(self::ADDITIONAL);
+ // Clean up states.
+ $additional[self::STATES] = array_values(array_filter($additional[self::STATES]));
+ $this->configuration[self::ADDITIONAL] = $additional;
}
/**
* {@inheritdoc}
*/
public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE): void {
+ $submissionState = $webform_submission->getWebform()->getSetting('results_disabled') ? WebformSubmissionInterface::STATE_COMPLETED : $webform_submission->getState();
+ $enabledStates = (array) ($this->configuration[self::ADDITIONAL][self::STATES] ?? NULL);
+ if (!in_array($submissionState, $enabledStates)) {
+ return;
+ }
+
$queueStorage = $this->entityTypeManager->getStorage('advancedqueue_queue');
/** @var \Drupal\advancedqueue\Entity\Queue $queue */
$queue = $queueStorage->load('fasit_queue');
diff --git a/modules/os2forms_fbs_handler/os2forms_fbs_handler.install b/modules/os2forms_fbs_handler/os2forms_fbs_handler.install
new file mode 100644
index 00000000..4fa0422f
--- /dev/null
+++ b/modules/os2forms_fbs_handler/os2forms_fbs_handler.install
@@ -0,0 +1,39 @@
+listAll('webform.webform.') as $name) {
+ $config = $configFactory->getEditable($name);
+ $handlers = $config->get('handlers');
+ if (!is_array($handlers)) {
+ continue;
+ }
+
+ $changed = FALSE;
+
+ foreach ($handlers as $handlerKey => $handler) {
+ // $handler['id'] is the handler plugin id.
+ if (($handler['id'] ?? NULL) !== 'os2forms_fbs') {
+ continue;
+ }
+ $handlers[$handlerKey]['settings']['additional']['states'] = [WebformSubmissionInterface::STATE_COMPLETED];
+ $changed = TRUE;
+ }
+
+ if ($changed) {
+ $config->set('handlers', $handlers)->save();
+ }
+ }
+}
diff --git a/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php b/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php
index aa1ae330..33c479c5 100644
--- a/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php
+++ b/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php
@@ -42,6 +42,8 @@ final class FbsWebformHandler extends WebformHandlerBase {
* The queue id.
*/
private const QUEUE_ID = 'os2forms_fbs_handler';
+ private const string ADDITIONAL = 'additional';
+ private const string STATES = 'states';
/**
* Constructs an FbsWebformHandler object.
@@ -89,6 +91,19 @@ public static function create(ContainerInterface $container, array $configuratio
);
}
+ /**
+ * {@inheritdoc}
+ *
+ * @phpstan-return array
+ */
+ public function defaultConfiguration() {
+ return [
+ self::ADDITIONAL => [
+ self::STATES => [WebformSubmissionInterface::STATE_COMPLETED],
+ ],
+ ];
+ }
+
/**
* {@inheritdoc}
*
@@ -146,6 +161,31 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
'#default_value' => $this->configuration['password'] ?? '',
];
+ // Additional.
+ // Lifted from EmailWebformHandler::buildConfigurationForm().
+ $resultsDisabled = (bool) $this->getWebform()->getSetting('results_disabled');
+ $form[self::ADDITIONAL] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Additional settings'),
+ ];
+ // Settings: States.
+ $states = (array) ($this->configuration[self::ADDITIONAL][self::STATES] ?? NULL);
+ $form[self::ADDITIONAL][self::STATES] = [
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Run handler when …'),
+ '#options' => [
+ WebformSubmissionInterface::STATE_DRAFT_CREATED => $this->t('draft is created.'),
+ WebformSubmissionInterface::STATE_DRAFT_UPDATED => $this->t('draft is updated.'),
+ WebformSubmissionInterface::STATE_CONVERTED => $this->t('anonymous submission is converted to authenticated.'),
+ WebformSubmissionInterface::STATE_COMPLETED => $this->t('submission is completed.'),
+ WebformSubmissionInterface::STATE_UPDATED => $this->t('submission is updated.'),
+ WebformSubmissionInterface::STATE_DELETED => $this->t('submission is deleted.'),
+ WebformSubmissionInterface::STATE_LOCKED => $this->t('submission is locked.'),
+ ],
+ '#access' => !$resultsDisabled,
+ '#default_value' => $resultsDisabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $states,
+ ];
+
return $this->setSettingsParents($form);
}
@@ -164,12 +204,23 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
->getValue(['wrapper', 'username']);
$this->configuration['password'] = $form_state
->getValue(['wrapper', 'password']);
+
+ $additional = $form_state->getValue(self::ADDITIONAL);
+ // Clean up states.
+ $additional[self::STATES] = array_values(array_filter($additional[self::STATES]));
+ $this->configuration[self::ADDITIONAL] = $additional;
}
/**
* {@inheritdoc}
*/
public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE): void {
+ $submissionState = $webform_submission->getWebform()->getSetting('results_disabled') ? WebformSubmissionInterface::STATE_COMPLETED : $webform_submission->getState();
+ $enabledStates = (array) ($this->configuration[self::ADDITIONAL][self::STATES] ?? NULL);
+ if (!in_array($submissionState, $enabledStates)) {
+ return;
+ }
+
$logger_context = [
'handler_id' => 'os2forms_fbs',
'channel' => 'webform_submission',