From 09f9b1acb1dd78e3382d53f4ddae12e68208e38a Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 18 May 2026 11:25:41 +0200 Subject: [PATCH] Move digital signature confgurations to handler --- CHANGELOG.md | 4 + .../WebformElement/AttachmentElement.php | 25 --- modules/os2forms_digital_signature/README.md | 14 +- .../os2forms_digital_signature.info.yml | 1 + .../os2forms_digital_signature.install | 114 ++++++++++++ .../DigitalSignatureWebformHandler.php | 167 ++++++++++++++---- 6 files changed, 262 insertions(+), 63 deletions(-) create mode 100644 modules/os2forms_digital_signature/os2forms_digital_signature.install diff --git a/CHANGELOG.md b/CHANGELOG.md index ff239913..b8c7d67c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- 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. - [PR-322](https://github.com/OS2Forms/os2forms/pull/322) Update Digital Post handler error messages. - [PR-320](https://github.com/OS2Forms/os2forms/pull/320) 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_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..b66e19f9 --- /dev/null +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.install @@ -0,0 +1,114 @@ +getHandlers(); + $signatureHandler = NULL; + foreach ($handlers as $handler) { + if ($handler->getPluginId() === 'os2forms_digital_signature') { + $signatureHandler = $handler; + break; + } + } + if (!$signatureHandler) { + continue; + } + + $elements = $webform->getElementsDecodedAndFlattened(); + $signedKey = NULL; + $signedPosition = NULL; + $multiple = []; + foreach ($elements as $key => $element) { + if (!empty($element['#digital_signature'])) { + if ($signedKey === NULL) { + $signedKey = $key; + $signedPosition = $element['#digital_signature_position'] + ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT; + } + else { + $multiple[] = $key; + } + } + } + + if ($signedKey === NULL) { + $skipped[$webform->id()] = 'no element had #digital_signature set'; + continue; + } + + if ($multiple) { + $logger->warning('Webform @id has multiple elements flagged for signing (@all); migrating @chosen and ignoring the rest.', [ + '@id' => $webform->id(), + '@all' => implode(', ', array_merge([$signedKey], $multiple)), + '@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); + + // Strip the legacy properties from every element. + $rawElements = $webform->getElementsDecoded(); + _os2forms_digital_signature_strip_legacy_props($rawElements); + $webform->setElements($rawElements); + + $webform->save(); + $migrated++; + } + + if ($skipped) { + foreach ($skipped as $id => $reason) { + $logger->warning('Skipped webform @id during digital signature config migration: @reason. Configure the handler manually.', [ + '@id' => $id, + '@reason' => $reason, + ]); + } + } + + return t('Migrated digital signature config on @count webform(s).', ['@count' => $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..6b01f33c 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; @@ -88,6 +90,104 @@ class DigitalSignatureWebformHandler extends WebformHandlerBase { */ private readonly Settings $settings; + /** + * {@inheritdoc} + */ + 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 $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + + $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. + * + * Lists every element implementing WebformElementAttachmentInterface + * (covers os2forms_attachment and os2forms_digital_signature_document). + * + * @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; + } + $title = $element['#title'] ?? $key; + $type = $element['#type'] ?? ''; + $options[$key] = sprintf('%s (%s) [%s]', $title, $key, $type); + } + return $options; + } + /** * {@inheritdoc} */ @@ -177,59 +277,56 @@ 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]; + $element['#digital_signature'] = TRUE; + $element['#digital_signature_position'] = $this->configuration['signature_position'] + ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT; - 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