From de9a7a1f1f74d78731838d62983facf4b55e143e Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 11 Mar 2026 09:49:03 +0100 Subject: [PATCH 01/33] =?UTF-8?q?Added=20=E2=80=9CDisplay=20on=E2=80=9C=20?= =?UTF-8?q?options=20to=20Map=20element?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/os2forms_webform_maps/README.md | 6 + .../os2forms_webform_maps.module | 44 ------- .../WebformElement/WebformLeafletMapField.php | 109 ++++++++++++++++++ ...ent-base-html--webform-map-field.html.twig | 18 --- 4 files changed, 115 insertions(+), 62 deletions(-) delete mode 100644 modules/os2forms_webform_maps/templates/webform-element-base-html--webform-map-field.html.twig diff --git a/modules/os2forms_webform_maps/README.md b/modules/os2forms_webform_maps/README.md index c36b901..1fee0ea 100644 --- a/modules/os2forms_webform_maps/README.md +++ b/modules/os2forms_webform_maps/README.md @@ -14,3 +14,9 @@ the map. The data can be exported to PDF. ## Installation The module can be installed using the standard Drupal installation procedure. + +## Fetching GeoJSON using the API + +``` shell +@todo +``` diff --git a/modules/os2forms_webform_maps/os2forms_webform_maps.module b/modules/os2forms_webform_maps/os2forms_webform_maps.module index af545a5..17d33aa 100644 --- a/modules/os2forms_webform_maps/os2forms_webform_maps.module +++ b/modules/os2forms_webform_maps/os2forms_webform_maps.module @@ -5,22 +5,6 @@ * Module file for os2forms_webform_maps. */ -/** - * Implements hook_theme(). - */ -function os2forms_webform_maps_theme() { - return [ - 'webform_element_base_html__webform_map_field' => [ - 'variables' => [ - 'element' => [], - 'value' => NULL, - 'webform_submission' => NULL, - 'options' => [], - ], - ], - ]; -} - /** * Implements hook_locale_translation_projects_alter(). */ @@ -29,31 +13,3 @@ function os2forms_webform_maps_locale_translation_projects_alter(&$projects) { $path = $module_handler->getModule('os2forms_webform_maps')->getPath(); $projects['os2forms_webform_maps']['info']['interface translation server pattern'] = $path . '/translations/%language.po'; } - -/** - * Implements hook_preprocess_webform_element_base_html__webform_map_field(). - */ -function os2forms_webform_maps_preprocess_webform_element_base_html__webform_map_field(array &$variables) { - // Decode the plain text value once. - $decoded_value = json_decode($variables['value']['#plain_text']); - - // Use the decoded geojson property. - $variables['value']['#plain_text'] = $decoded_value->geojson; - - // Load the webform element base HTML template. - \Drupal::moduleHandler()->loadInclude('webform', 'inc', 'includes/webform.theme.template'); - template_preprocess_webform_element_base_html($variables); - - // Generate a unique ID for the map image. - $map_image_id = 'map-image-' . $variables['element']['#webform_key']; - - $variables['map_image'] = [ - '#type' => 'html_tag', - '#tag' => 'img', - '#attributes' => [ - 'class' => ['handler-help-message'], - 'id' => [$map_image_id], - 'src' => $decoded_value->image ?? '', - ], - ]; -} diff --git a/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php b/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php index 3b6aa40..f0619e5 100644 --- a/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php +++ b/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php @@ -5,6 +5,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\leaflet\LeafletSettingsElementsTrait; use Drupal\webform\Plugin\WebformElementBase; +use Drupal\webform\WebformSubmissionInterface; /** * Provides a 'webform_map_field' element. @@ -70,6 +71,10 @@ public function defineDefaultProperties(): array { 'circle_color' => '#3388FF', 'rectangle_color' => '#3388FF', + // Display settings. + 'display_image_on' => ['html', 'pdf'], + 'display_geojson_on' => ['text', 'html'], + ] + parent::defineDefaultProperties(); } @@ -344,7 +349,111 @@ public function form(array $form, FormStateInterface $form_state) { ], ]; + $form['display_settings'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Display settings'), + ]; + $form['display_settings']['display_settings_container'] = [ + 'display_image_on' => [ + '#type' => 'checkboxes', + '#title' => $this->t('Display image on'), + '#options' => [ + 'html' => $this->t('HTML'), + 'pdf' => $this->t('PDF'), + ], + ], + + 'display_geojson_on' => [ + '#type' => 'checkboxes', + '#title' => $this->t('Display GeoJSON on'), + '#options' => [ + 'html' => $this->t('HTML'), + 'pdf' => $this->t('PDF'), + 'text' => $this->t('Text'), + ], + ], + ]; + return $form; } + /** + * {@inheritdoc} + */ + protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []): array { + $value = $this->getMapValue($element, $webform_submission, $options); + + $imageId = 'map-image-' . $this->getKey($element); + + $build = []; + + $viewMode = $options['view_mode'] ?? 'overview'; + if ('table' === $viewMode) { + $viewMode = 'html'; + } + if ('html' === $viewMode && $options['pdf']) { + $viewMode = 'pdf'; + } + + // @todo Is this (i.e. $element['#display_image_on']) really the way to get element configuration? + $showImageOn = array_filter((array) ($element['#display_image_on'] ?? NULL)); + $includeImage = isset($showImageOn[$viewMode]); + + $showGeoJsonOn = array_filter((array) ($element['#display_geojson_on'] ?? NULL)); + $includeGeoJson = isset($showGeoJsonOn[$viewMode]); + + if ($includeImage) { + $build['image'] = [ + '#type' => 'html_tag', + '#tag' => 'img', + '#attributes' => [ + 'class' => ['handler-help-message'], + 'id' => $imageId, + 'src' => $value['image'], + ], + ]; + } + + if ($includeGeoJson) { + $build['geojson'] = [ + '#markup' => $value['geojson'], + ]; + } + + return $build; + } + + /** + * {@inheritdoc} + */ + protected function formatTextItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $value = $this->getMapValue($element, $webform_submission, $options); + + return $value['geojson']; + } + + /** + * Get structured map value. + * + * @return array{ + * image: string, + * geojson: string + * } + */ + private function getMapValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []): array { + $value = $this->getValue($element, $webform_submission, $options); + + try { + $data = json_decode($value, associative: TRUE, flags: JSON_THROW_ON_ERROR); + } + catch (\JsonException) { + $data = []; + } + + return $data + [ + 'image' => '', + 'geojson' => 'null', + ]; + } + } diff --git a/modules/os2forms_webform_maps/templates/webform-element-base-html--webform-map-field.html.twig b/modules/os2forms_webform_maps/templates/webform-element-base-html--webform-map-field.html.twig deleted file mode 100644 index cd3a19b..0000000 --- a/modules/os2forms_webform_maps/templates/webform-element-base-html--webform-map-field.html.twig +++ /dev/null @@ -1,18 +0,0 @@ -{# -/** - * @file - * Default theme implementation for a webform base element as html. - * - * Available variables: - * - element: The element. - * - title: The label for the element. - * - value: The content for the element. - * - item: The form item used to display the element. - * - options Associative array of options for element. - * - multiline: Flag to determine if value spans multiple lines. - * - email: Flag to determine if element is for an email. - */ -#} - {{ map_image }} - {{ item }} - From 7c5e04ea4a249c564a7d6a524c557f66be271dc0 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 11 Mar 2026 10:42:26 +0100 Subject: [PATCH 02/33] Added documentation on fetching GeoJSON using the API --- modules/os2forms_webform_maps/README.md | 53 ++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/modules/os2forms_webform_maps/README.md b/modules/os2forms_webform_maps/README.md index 1fee0ea..6c9e630 100644 --- a/modules/os2forms_webform_maps/README.md +++ b/modules/os2forms_webform_maps/README.md @@ -17,6 +17,55 @@ The module can be installed using the standard Drupal installation procedure. ## Fetching GeoJSON using the API -``` shell -@todo +Assume that we have a webform with ID `my_webform` and the webform has a Map element with ID `my_map`. We can then fetch +the data for a submission with UUID `c34d01c5-7bd9-4b15-8b01-5787959453c0` on the webform with a HTTP `GET` request (cf. +[OS2Forms REST API](https://github.com/itk-dev/os2forms_rest_api/blob/main/README.md)): + +``` shell name=api-fetch-submission-data +curl --silent --location --header 'api-key: my-api-key' http://127.0.0.1:8000/webform_rest/my_webform/submission/c34d01c5-7bd9-4b15-8b01-5787959453c0 +# {"entity":{"serial":[{"value":1}],"sid":[{"value":2}],"uuid":[{"value":"c34d01c5-7bd9-4b15-8b01-5787959453c0"}],…``` +``` + +The result, however, contains a lot of data and we need a little more work the extract just the GeoJSON data. + +Using [`jq`](https://jqlang.org/) (or similar tools), we can drill down into the full submission data to extract just +the GeoJSON data from the Map element (JSON decoding twice along the way): + +``` shell name=api-extract-map-element-data +curl --silent --location --header 'api-key: my-api-key' http://127.0.0.1:8000/webform_rest/my_webform/submission/c34d01c5-7bd9-4b15-8b01-5787959453c0 | jq '.data.my_map | fromjson | .geojson | fromjson' +``` + +The final result will be something like + +``` json +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 10.193674, + 56.158929 + ], + [ + 10.241758, + 56.155296 + ], + [ + 10.242788, + 56.130048 + ], + [ + 10.189209, + 56.129283 + ] + ] + } + } + ] +} ``` From ff515e3c4416480e31744535c3619a1e831d4b0f Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 11 Mar 2026 10:45:15 +0100 Subject: [PATCH 03/33] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fabc6c..075e7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- [PR-315](https://github.com/OS2Forms/os2forms/pull/315) + Added “Display on“ options to Map element - [#251](https://github.com/OS2Forms/os2forms/issues/251) Webform encrypt uninstall problem fix - git actions check From 3c9dffb1c3e7cd17e27b6d0a91cac8da5b44341e Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 11 Mar 2026 12:40:14 +0100 Subject: [PATCH 04/33] Handled "mail" display --- .../WebformElement/WebformLeafletMapField.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php b/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php index f0619e5..00327dc 100644 --- a/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php +++ b/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php @@ -72,9 +72,8 @@ public function defineDefaultProperties(): array { 'rectangle_color' => '#3388FF', // Display settings. - 'display_image_on' => ['html', 'pdf'], + 'display_image_on' => ['email', 'html', 'pdf'], 'display_geojson_on' => ['text', 'html'], - ] + parent::defineDefaultProperties(); } @@ -358,6 +357,7 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'checkboxes', '#title' => $this->t('Display image on'), '#options' => [ + 'email' => $this->t('Email'), 'html' => $this->t('HTML'), 'pdf' => $this->t('PDF'), ], @@ -367,6 +367,7 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'checkboxes', '#title' => $this->t('Display GeoJSON on'), '#options' => [ + 'email' => $this->t('Email'), 'html' => $this->t('HTML'), 'pdf' => $this->t('PDF'), 'text' => $this->t('Text'), @@ -387,11 +388,14 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we $build = []; - $viewMode = $options['view_mode'] ?? 'overview'; + $viewMode = $options['view_mode'] ?? 'results'; if ('table' === $viewMode) { $viewMode = 'html'; } - if ('html' === $viewMode && $options['pdf']) { + elseif ($options['email'] ?? FALSE) { + $viewMode = 'email'; + } + elseif ($options['pdf'] ?? FALSE) { $viewMode = 'pdf'; } @@ -411,12 +415,16 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we 'id' => $imageId, 'src' => $value['image'], ], + '#prefix' => '
', + '#suffix' => '
', ]; } if ($includeGeoJson) { $build['geojson'] = [ '#markup' => $value['geojson'], + '#prefix' => '
', + '#suffix' => '
', ]; } From 31f4ca5c1725e878bd9f4072159db188025604d1 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 12 Mar 2026 09:57:42 +0100 Subject: [PATCH 05/33] Updated URL --- modules/os2forms_webform_maps/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_webform_maps/README.md b/modules/os2forms_webform_maps/README.md index 6c9e630..d93aa5d 100644 --- a/modules/os2forms_webform_maps/README.md +++ b/modules/os2forms_webform_maps/README.md @@ -19,7 +19,7 @@ The module can be installed using the standard Drupal installation procedure. Assume that we have a webform with ID `my_webform` and the webform has a Map element with ID `my_map`. We can then fetch the data for a submission with UUID `c34d01c5-7bd9-4b15-8b01-5787959453c0` on the webform with a HTTP `GET` request (cf. -[OS2Forms REST API](https://github.com/itk-dev/os2forms_rest_api/blob/main/README.md)): +[OS2Forms REST API](https://github.com/OS2Forms/os2forms_rest_api/blob/main/README.md)): ``` shell name=api-fetch-submission-data curl --silent --location --header 'api-key: my-api-key' http://127.0.0.1:8000/webform_rest/my_webform/submission/c34d01c5-7bd9-4b15-8b01-5787959453c0 From 4a88b1659058e411825c61b1a9b55c264bb4e34f Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 1 May 2026 13:37:25 +0200 Subject: [PATCH 06/33] Added first, middle and last name webform elements --- CHANGELOG.md | 1 + .../src/Element/NemidFirstName.php | 64 +++++++++++++++++++ .../src/Element/NemidLastName.php | 64 +++++++++++++++++++ .../src/Element/NemidMiddleName.php | 64 +++++++++++++++++++ .../Plugin/WebformElement/NemidFirstName.php | 29 +++++++++ .../Plugin/WebformElement/NemidLastName.php | 29 +++++++++ .../Plugin/WebformElement/NemidMiddleName.php | 29 +++++++++ 7 files changed, 280 insertions(+) create mode 100644 modules/os2forms_nemid/src/Element/NemidFirstName.php create mode 100644 modules/os2forms_nemid/src/Element/NemidLastName.php create mode 100644 modules/os2forms_nemid/src/Element/NemidMiddleName.php create mode 100644 modules/os2forms_nemid/src/Plugin/WebformElement/NemidFirstName.php create mode 100644 modules/os2forms_nemid/src/Plugin/WebformElement/NemidLastName.php create mode 100644 modules/os2forms_nemid/src/Plugin/WebformElement/NemidMiddleName.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ff23991..47c891a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- Added NemID first name, middle name and last name webform elements. - [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_nemid/src/Element/NemidFirstName.php b/modules/os2forms_nemid/src/Element/NemidFirstName.php new file mode 100644 index 0000000..22be114 --- /dev/null +++ b/modules/os2forms_nemid/src/Element/NemidFirstName.php @@ -0,0 +1,64 @@ + [ + [$class, 'processNemidFirstName'], + [$class, 'processAjaxForm'], + ], + '#element_validate' => [ + [$class, 'validateNemidFirstName'], + ], + '#pre_render' => [ + [$class, 'preRenderNemidFirstName'], + ], + '#theme' => 'input__os2forms_nemid_first_name', + ]; + } + + /** + * Processes a 'os2forms_nemid_first_name' element. + */ + public static function processNemidFirstName(&$element, FormStateInterface $form_state, &$complete_form) { + // Here you can add and manipulate your element's properties and callbacks. + return $element; + } + + /** + * Webform element validation handler for #type 'os2forms_nemid_first_name'. + */ + public static function validateNemidFirstName(&$element, FormStateInterface $form_state, &$complete_form) { + // Here you can add custom validation logic. + } + + /** + * {@inheritdoc} + */ + public static function preRenderNemidFirstName(array $element) { + $element = parent::prerenderNemidElementBase($element); + static::setAttributes($element, ['form-text', 'os2forms-nemid-first-name']); + return $element; + } + +} diff --git a/modules/os2forms_nemid/src/Element/NemidLastName.php b/modules/os2forms_nemid/src/Element/NemidLastName.php new file mode 100644 index 0000000..251b114 --- /dev/null +++ b/modules/os2forms_nemid/src/Element/NemidLastName.php @@ -0,0 +1,64 @@ + [ + [$class, 'processNemidLastName'], + [$class, 'processAjaxForm'], + ], + '#element_validate' => [ + [$class, 'validateNemidLastName'], + ], + '#pre_render' => [ + [$class, 'preRenderNemidLastName'], + ], + '#theme' => 'input__os2forms_nemid_last_name', + ]; + } + + /** + * Processes a 'os2forms_nemid_last_name' element. + */ + public static function processNemidLastName(&$element, FormStateInterface $form_state, &$complete_form) { + // Here you can add and manipulate your element's properties and callbacks. + return $element; + } + + /** + * Webform element validation handler for #type 'os2forms_nemid_last_name'. + */ + public static function validateNemidLastName(&$element, FormStateInterface $form_state, &$complete_form) { + // Here you can add custom validation logic. + } + + /** + * {@inheritdoc} + */ + public static function preRenderNemidLastName(array $element) { + $element = parent::prerenderNemidElementBase($element); + static::setAttributes($element, ['form-text', 'os2forms-nemid-last-name']); + return $element; + } + +} diff --git a/modules/os2forms_nemid/src/Element/NemidMiddleName.php b/modules/os2forms_nemid/src/Element/NemidMiddleName.php new file mode 100644 index 0000000..48c038e --- /dev/null +++ b/modules/os2forms_nemid/src/Element/NemidMiddleName.php @@ -0,0 +1,64 @@ + [ + [$class, 'processNemidMiddleName'], + [$class, 'processAjaxForm'], + ], + '#element_validate' => [ + [$class, 'validateNemidMiddleName'], + ], + '#pre_render' => [ + [$class, 'preRenderNemidMiddleName'], + ], + '#theme' => 'input__os2forms_nemid_middle_name', + ]; + } + + /** + * Processes a 'os2forms_nemid_middle_name' element. + */ + public static function processNemidMiddleName(&$element, FormStateInterface $form_state, &$complete_form) { + // Here you can add and manipulate your element's properties and callbacks. + return $element; + } + + /** + * Webform element validation handler for #type 'os2forms_nemid_middle_name'. + */ + public static function validateNemidMiddleName(&$element, FormStateInterface $form_state, &$complete_form) { + // Here you can add custom validation logic. + } + + /** + * {@inheritdoc} + */ + public static function preRenderNemidMiddleName(array $element) { + $element = parent::prerenderNemidElementBase($element); + static::setAttributes($element, ['form-text', 'os2forms-nemid-middle-name']); + return $element; + } + +} diff --git a/modules/os2forms_nemid/src/Plugin/WebformElement/NemidFirstName.php b/modules/os2forms_nemid/src/Plugin/WebformElement/NemidFirstName.php new file mode 100644 index 0000000..1f0b246 --- /dev/null +++ b/modules/os2forms_nemid/src/Plugin/WebformElement/NemidFirstName.php @@ -0,0 +1,29 @@ + Date: Fri, 1 May 2026 13:46:12 +0200 Subject: [PATCH 07/33] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c891a..da4b1ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] -- Added NemID first name, middle name and last name webform elements. +- [PR-325](https://github.com/OS2Forms/os2forms/pull/325) + Added NemID first name, middle name and last name webform elements. - [PR-322](https://github.com/OS2Forms/os2forms/pull/322) Update Digital Post handler error messages. - [PR-320](https://github.com/OS2Forms/os2forms/pull/320) From 09f9b1acb1dd78e3382d53f4ddae12e68208e38a Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 18 May 2026 11:25:41 +0200 Subject: [PATCH 08/33] 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 ff23991..b8c7d67 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 e19c531..4f2215d 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 d863182..5dd93e2 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 29547e4..99abd7c 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 0000000..b66e19f --- /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 9a616bf..6b01f33 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 From 2bf2ec6720c62e7a0199d6917cbbaa12171d54b5 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Wed, 3 Jun 2026 12:23:48 +0300 Subject: [PATCH 09/33] Adding 5.1.0 version --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index edf75bb..b060957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +## [5.1.0] 2026-06-03 + - [PR-326](https://github.com/OS2Forms/os2forms/pull/326) Updating ckeditor -> ckeditor5. - [PR-322](https://github.com/OS2Forms/os2forms/pull/322) From 70fe8d34463b36ae577a7bdb7aaaf1b82d3918e6 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 23 Jun 2026 14:02:58 +0200 Subject: [PATCH 10/33] Allow configurating on which submission states handlers should run --- CHANGELOG.md | 8 ++ .../os2forms_digital_post.install | 32 ++++++++ .../WebformHandler/WebformHandlerSF1601.php | 41 ++++++++++ .../os2forms_digital_signature.install | 39 ++++++++++ .../DigitalSignatureWebformHandler.php | 74 ++++++++++++++++++- modules/os2forms_fasit/os2forms_fasit.install | 33 +++++++++ .../WebformHandler/FasitWebformHandler.php | 51 +++++++++++++ .../os2forms_fbs_handler.install | 39 ++++++++++ .../WebformHandler/FbsWebformHandler.php | 51 +++++++++++++ 9 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 modules/os2forms_digital_signature/os2forms_digital_signature.install create mode 100644 modules/os2forms_fbs_handler/os2forms_fbs_handler.install diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ddc05a..e57aca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- Added the ability to configure on which submission states handlers should run. + The default option is to run on the completed state. Changes was made to the + following handlers: + - Digital post + - Fasit + - FBS + - Digital signature + ## [5.1.0] 2026-06-03 - [PR-326](https://github.com/OS2Forms/os2forms/pull/326) diff --git a/modules/os2forms_digital_post/os2forms_digital_post.install b/modules/os2forms_digital_post/os2forms_digital_post.install index 48768e2..cb410b1 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 6f0e06a..ae633c0 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/os2forms_digital_signature.install b/modules/os2forms_digital_signature/os2forms_digital_signature.install new file mode 100644 index 0000000..4e5cb76 --- /dev/null +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.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_digital_signature') { + continue; + } + $handlers[$handlerKey]['settings']['additional']['states'] = [WebformSubmissionInterface::STATE_COMPLETED]; + $changed = TRUE; + } + + if ($changed) { + $config->set('handlers', $handlers)->save(); + } + } +} diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index 9a616bf..78252c0 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -7,6 +7,7 @@ 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; @@ -88,6 +89,9 @@ class DigitalSignatureWebformHandler extends WebformHandlerBase { */ private readonly Settings $settings; + private const string ADDITIONAL = 'additional'; + private const string STATES = 'states'; + /** * {@inheritdoc} */ @@ -107,14 +111,78 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} + * + * @phpstan-return array */ - public function preSave(WebformSubmissionInterface $webform_submission) { - $webform = $webform_submission->getWebform(); + public function defaultConfiguration() { + return [ + self::ADDITIONAL => [ + self::STATES => [WebformSubmissionInterface::STATE_COMPLETED], + ], + ]; + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $form + * @phpstan-return array + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + // 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.'), + // The digital signature logic locks after the first signing. Resigning + // does not make sense, so 'locked' is not an option here. + ], + '#access' => !$resultsDisabled, + '#default_value' => $resultsDisabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $states, + ]; + + return $this->setSettingsParents($form); + } - if ($webform_submission->isLocked()) { + /** + * {@inheritdoc} + * + * @phpstan-param array $form + * @phpstan-return void + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $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 preSave(WebformSubmissionInterface $webform_submission) { + $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; } + $webform = $webform_submission->getWebform(); + $attachment = $this->getSubmissionAttachment($webform_submission); if (!$attachment) { $this->logger->error('Attachment cannot be created webform: %webform, webform_submission: %webform_submission', diff --git a/modules/os2forms_fasit/os2forms_fasit.install b/modules/os2forms_fasit/os2forms_fasit.install index f862baf..8d471f4 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 84eef37..e726bf4 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 0000000..4fa0422 --- /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 aa1ae33..33c479c 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', From 48d83071a0c404f773477707711459e7ea1d9d4f Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 23 Jun 2026 14:08:20 +0200 Subject: [PATCH 11/33] Updated CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e57aca6..e5b2a42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] -- Added the ability to configure on which submission states handlers should run. +- [PR-333](https://github.com/OS2Forms/os2forms/pull/333) + Added the ability to configure on which submission states handlers should run. The default option is to run on the completed state. Changes was made to the following handlers: - Digital post From 06fdf2915d69a6da454ce40136d70b5531a869ed Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 26 Jun 2026 12:40:16 +0200 Subject: [PATCH 12/33] 336: Avoided inheriting data on page transition --- .../src/Plugin/EngineTasks/MaestroWebformInheritTask.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php b/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php index 9544cee..dd25f1c 100644 --- a/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php +++ b/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php @@ -144,6 +144,13 @@ public function prepareTaskForSave(array &$form, FormStateInterface $form_state, * Implements hook_webform_submission_form_alter(). */ public static function webformSubmissionFormAlter(array &$form, FormStateInterface $formState, string $formId) { + // Only inherit values on the initial form load. This skip condition is the + // complement of webform's own prepopulate guard. + // @see \Drupal\webform\WebformSubmissionForm::buildForm() + if ($formState->isRebuilding() && !$formState->get('is_ajax_restart')) { + return; + } + // @todo Clean up and align with MaestroHelper::maestroZeroUserNotification(). if ($queueID = self::getQueueIdFromRequest()) { $templateTask = MaestroEngine::getTemplateTaskByQueueID($queueID); From badc51ff531bf4cba2316afa0b6c1a59b2aac008 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 26 Jun 2026 12:51:31 +0200 Subject: [PATCH 13/33] 336: Updated CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ddc05a..5ba5db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- [337](https://github.com/OS2Forms/os2forms/pull/337) + Avoid inheriting data on page transition in MaestroWebformInheritTask. + ## [5.1.0] 2026-06-03 - [PR-326](https://github.com/OS2Forms/os2forms/pull/326) From f1217ecbbccb089d523689f443faf419582f0752 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 2 Jul 2026 10:43:04 +0200 Subject: [PATCH 14/33] Updated CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ba5db8..49cbfb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] -- [337](https://github.com/OS2Forms/os2forms/pull/337) +- [PR-337](https://github.com/OS2Forms/os2forms/pull/337) Avoid inheriting data on page transition in MaestroWebformInheritTask. ## [5.1.0] 2026-06-03 From ee0a2a2c4df15550d0e08fe67f44be5958a03437 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 20 Jul 2026 10:52:52 +0200 Subject: [PATCH 15/33] Ensured signed documents are fetched when getting file content --- .../src/Element/AttachmentElement.php | 17 +++++++++++++++++ .../DigitalSignatureWebformHandler.php | 3 --- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/os2forms_attachment/src/Element/AttachmentElement.php b/modules/os2forms_attachment/src/Element/AttachmentElement.php index bbc33f0..96f9a91 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_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index 6b01f33..782270b 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -307,9 +307,6 @@ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_s } $element = $elements[$elementKey]; - $element['#digital_signature'] = TRUE; - $element['#digital_signature_position'] = $this->configuration['signature_position'] - ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT; /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */ $element_plugin = $this->elementManager->getElementInstance($element); From 8c9e4b32c8f7fd6a95a4b68aa28c4fa89728137a Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 20 Jul 2026 11:05:07 +0200 Subject: [PATCH 16/33] Mimic original behaviour when migrating --- .../os2forms_digital_signature.install | 112 +++++++++++------- 1 file changed, 70 insertions(+), 42 deletions(-) diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.install b/modules/os2forms_digital_signature/os2forms_digital_signature.install index b66e19f..e9f3850 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.install +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.install @@ -9,13 +9,15 @@ use Drupal\os2forms_attachment\Os2formsAttachmentPrintBuilder; use Drupal\webform\Entity\Webform; /** - * Migrate per-element digital signature config onto the handler. + * Migrate digital signature configuration onto the handler. * * Previously the os2forms_attachment element exposed #digital_signature and - * #digital_signature_position properties. Both now live on the - * os2forms_digital_signature handler. This update walks every webform that - * has the handler enabled, lifts the values off the element into the handler - * config, and strips the element properties. + * #digital_signature_position properties, and the handler picked the element + * to sign by type: the first os2forms_digital_signature_document element, + * falling back to the first os2forms_attachment element. The configuration + * now lives on the os2forms_digital_signature handler, so this update + * replicates that selection into the handler config of every webform using + * the handler, and strips the legacy element properties from all webforms. */ function os2forms_digital_signature_update_10001(&$sandbox) { $logger = \Drupal::logger('os2forms_digital_signature'); @@ -33,53 +35,79 @@ function os2forms_digital_signature_update_10001(&$sandbox) { 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; + $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) { + $skipped[$webform->id()] = 'the handler is enabled but the webform has no os2forms_digital_signature_document or os2forms_attachment element'; + } + 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, + ]); } - else { - $multiple[] = $key; + 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; + $migrated++; } } - if ($signedKey === NULL) { - $skipped[$webform->id()] = 'no element had #digital_signature set'; - continue; + // 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 ($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, - ]); + if ($needsSave) { + $webform->save(); } - - // 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) { From e1abc9d2fb7c349f1cbce06e944ddca5b36503f7 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 20 Jul 2026 11:08:13 +0200 Subject: [PATCH 17/33] Ensured only os2forms_digital_signature_document and os2forms_attachment are up for selection in the signature handler --- .../WebformHandler/DigitalSignatureWebformHandler.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index 782270b..e36046c 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -162,8 +162,9 @@ public function getSummary() { /** * Build the dropdown options for the attachment element selector. * - * Lists every element implementing WebformElementAttachmentInterface - * (covers os2forms_attachment and os2forms_digital_signature_document). + * 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. @@ -181,8 +182,11 @@ protected function getAttachmentElementOptions(): array { if (!$element) { continue; } - $title = $element['#title'] ?? $key; $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; From f45f90081b34ef2cbb1a2332e3db9379760b91d5 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 20 Jul 2026 11:28:19 +0200 Subject: [PATCH 18/33] Added batching to the migration --- .../os2forms_digital_signature.install | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.install b/modules/os2forms_digital_signature/os2forms_digital_signature.install index e9f3850..5c36094 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.install +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.install @@ -18,14 +18,21 @@ use Drupal\webform\Entity\Webform; * now lives on the os2forms_digital_signature handler, so this update * replicates that selection into the handler config of every webform using * the handler, and strips the legacy element properties from all webforms. + * + * Processes the webforms in batches of 25 via the update sandbox. */ function os2forms_digital_signature_update_10001(&$sandbox) { $logger = \Drupal::logger('os2forms_digital_signature'); - $migrated = 0; - $skipped = []; + if (!isset($sandbox['ids'])) { + $sandbox['ids'] = array_values(\Drupal::entityQuery('webform')->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(); + $webforms = Webform::loadMultiple(array_splice($sandbox['ids'], 0, 25)); foreach ($webforms as $webform) { $handlers = $webform->getHandlers(); $signatureHandler = NULL; @@ -63,7 +70,9 @@ function os2forms_digital_signature_update_10001(&$sandbox) { } if ($signedKey === NULL) { - $skipped[$webform->id()] = 'the handler is enabled but the webform has no os2forms_digital_signature_document or os2forms_attachment element'; + $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) { @@ -89,7 +98,7 @@ function os2forms_digital_signature_update_10001(&$sandbox) { $config['settings']['signature_position'] = $signedPosition; $signatureHandler->setConfiguration($config); $needsSave = TRUE; - $migrated++; + $sandbox['migrated']++; } } @@ -110,16 +119,12 @@ function os2forms_digital_signature_update_10001(&$sandbox) { } } - 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, - ]); - } - } + // Anything below 1 makes the runner call us again. Done when no ids remain. + $sandbox['#finished'] = empty($sandbox['ids']) ? 1 : 0; - return t('Migrated digital signature config on @count webform(s).', ['@count' => $migrated]); + if ($sandbox['#finished'] >= 1) { + return t('Migrated digital signature config on @count webform(s).', ['@count' => $sandbox['migrated']]); + } } /** From 32529356218c63559406fe5acd0af0a9b358aeca Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 27 Aug 2026 11:13:43 +0200 Subject: [PATCH 19/33] Updated CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1547c18..c452635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +This is a hack so the CHANGELOG is messed up. +Generated by build_release.sh # OS2Forms Change Log From 093d173c264575789681782165d4f86fc911b119 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 27 Aug 2026 11:15:38 +0200 Subject: [PATCH 20/33] Added build script --- build_release.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100755 build_release.sh diff --git a/build_release.sh b/build_release.sh new file mode 100755 index 0000000..c1f358a --- /dev/null +++ b/build_release.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# selvbetjening.aarhuskommune.dk branch +# Setup upstream with OS2Forms CORE +git remote add upstream https://github.com/OS2Forms/os2forms.git +git fetch +git reset --hard 5.1.0 +git merge --no-ff origin/feature/maps-element-display +git checkout --theirs CHANGELOG.md +git add CHANGELOG.md +git commit -m "Merge remote-tracking branch 'origin/feature/maps-element-display' into selvbetjening.aarhuskommune.dk" +git merge --no-ff origin/fix/336-avoid-inheriting-data-on-page-transition +git checkout --theirs CHANGELOG.md +git add CHANGELOG.md +git commit -m "Merge remote-tracking branch 'origin/fix/336-avoid-inheriting-data-on-page-transition' into selvbetjening.aarhuskommune.dk" +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" + +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 +git commit -m "Updated CHANGELOG" From 8054008544bfda627211eff1b392d2064b4cca82 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 28 Aug 2026 08:57:02 +0200 Subject: [PATCH 21/33] Dev required os2web_datalookup with nemid elements data --- composer.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index ba5f18b..4c838fb 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ "itk-dev/serviceplatformen": "^1.7.1", "mglaman/composer-drupal-lenient": "^1.0", "os2web/os2web_audit": "^1.0", - "os2web/os2web_datalookup": "^3.0", + "os2web/os2web_datalookup": "dev-selvbetjening.aarhuskommune.dk as 3.4.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", @@ -76,16 +76,23 @@ "phpunit/phpunit": "^9.5", "wsdltophp/packagegenerator": "^4.0" }, - "repositories": { - "drupal": { + "repositories": [ + { + "name": "os2web/os2web_datalookup", + "type": "vcs", + "url": "https://github.com/itk-dev/os2web_datalookup" + }, + { + "name": "drupal", "type": "composer", "url": "https://packages.drupal.org/8" }, - "assets": { + { + "name": "assets", "type": "composer", "url": "https://asset-packagist.org" } - }, + ], "minimum-stability": "dev", "prefer-stable": true, "config": { From 05aceb58daec50640d96e34526302c38ad9650e2 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 28 Aug 2026 08:58:56 +0200 Subject: [PATCH 22/33] Ignored advisories --- composer.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4c838fb..c5737a7 100644 --- a/composer.json +++ b/composer.json @@ -107,7 +107,12 @@ "vaimo/composer-patches": true, "zaporylie/composer-drupal-optimizations": true }, - "sort-packages": true + "sort-packages": true, + "policy": { + "advisories": { + "block": false + } + } }, "extra": { "composer-exit-on-patch-failure": false, From 5b76663220977a1d9b074c31da9b32e141eb9476 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 28 Aug 2026 09:03:21 +0200 Subject: [PATCH 23/33] Normalized composer --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c5737a7..2a8b438 100644 --- a/composer.json +++ b/composer.json @@ -107,12 +107,12 @@ "vaimo/composer-patches": true, "zaporylie/composer-drupal-optimizations": true }, - "sort-packages": true, "policy": { "advisories": { "block": false } - } + }, + "sort-packages": true }, "extra": { "composer-exit-on-patch-failure": false, From 00a4e6a0c4bbe8645191d58f7157dcf9daf77225 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 31 Aug 2026 14:22:24 +0200 Subject: [PATCH 24/33] Updated CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb36739..fbba0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +This is a hack so the CHANGELOG is messed up. +Generated by build_release.sh # OS2Forms Change Log From 5a1441b8ab31a62172ff1484d01505597f815bea Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 31 Aug 2026 14:22:46 +0200 Subject: [PATCH 25/33] Update build_release script --- build_release.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build_release.sh b/build_release.sh index c1f358a..0ae94a1 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 From 5038fc64cc4e98d205bb049914534d4c81f26b75 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 31 Aug 2026 16:34:58 +0200 Subject: [PATCH 26/33] Fix dublicated update hook name --- .../os2forms_digital_signature.install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.install b/modules/os2forms_digital_signature/os2forms_digital_signature.install index a3d7b89..e971207 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.install +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.install @@ -53,7 +53,7 @@ function os2forms_digital_signature_update_10001(): void { * * Processes the webforms in batches of 25 via the update sandbox. */ -function os2forms_digital_signature_update_10001(&$sandbox) { +function os2forms_digital_signature_update_10002(&$sandbox) { $logger = \Drupal::logger('os2forms_digital_signature'); if (!isset($sandbox['ids'])) { From 84b3506c6a7a3e52a5e43c6f474c386eb5bbecb2 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 1 Sep 2026 08:56:33 +0200 Subject: [PATCH 27/33] Fixed further merge conflicts --- .../DigitalSignatureWebformHandler.php | 132 +++++++----------- 1 file changed, 52 insertions(+), 80 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index aff783b..523fe1f 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -93,6 +93,23 @@ class DigitalSignatureWebformHandler extends WebformHandlerBase { private const string ADDITIONAL = 'additional'; private const string STATES = 'states'; + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->moduleHandler = $container->get('module_handler'); + $instance->elementManager = $container->get('plugin.manager.webform.element'); + $instance->logger = $container->get('logger.channel.os2forms_digital_signature'); + $instance->fileSystem = $container->get('file_system'); + $instance->fileRepository = $container->get('file.repository'); + $instance->fileUrlGenerator = $container->get('file_url_generator'); + $instance->signingService = $container->get('os2forms_digital_signature.signing_service'); + $instance->settings = $container->get('settings'); + + return $instance; + } + /** * {@inheritdoc} */ @@ -100,6 +117,9 @@ public function defaultConfiguration() { return [ 'attachment_element' => '', 'signature_position' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT, + self::ADDITIONAL => [ + self::STATES => [WebformSubmissionInterface::STATE_COMPLETED], + ], ]; } @@ -132,7 +152,33 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['signature_position'], ]; - return $form; + // 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.'), + // The digital signature logic locks after the first signing. Resigning + // does not make sense, so 'locked' is not an option here. + ], + '#access' => !$resultsDisabled, + '#default_value' => $resultsDisabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $states, + ]; + + return $this->setSettingsParents($form); } /** @@ -145,6 +191,11 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s $this->configuration['attachment_element'] = $values['attachment_element'] ?? ''; $this->configuration['signature_position'] = $values['signature_position'] ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT; + + $additional = $form_state->getValue(self::ADDITIONAL); + // Clean up states. + $additional[self::STATES] = array_values(array_filter($additional[self::STATES])); + $this->configuration[self::ADDITIONAL] = $additional; } /** @@ -195,85 +246,6 @@ protected function getAttachmentElementOptions(): array { return $options; } - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); - $instance->moduleHandler = $container->get('module_handler'); - $instance->elementManager = $container->get('plugin.manager.webform.element'); - $instance->logger = $container->get('logger.channel.os2forms_digital_signature'); - $instance->fileSystem = $container->get('file_system'); - $instance->fileRepository = $container->get('file.repository'); - $instance->fileUrlGenerator = $container->get('file_url_generator'); - $instance->signingService = $container->get('os2forms_digital_signature.signing_service'); - $instance->settings = $container->get('settings'); - - return $instance; - } - - /** - * {@inheritdoc} - * - * @phpstan-return array - */ - public function defaultConfiguration() { - return [ - self::ADDITIONAL => [ - self::STATES => [WebformSubmissionInterface::STATE_COMPLETED], - ], - ]; - } - - /** - * {@inheritdoc} - * - * @phpstan-param array $form - * @phpstan-return array - */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - // 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.'), - // The digital signature logic locks after the first signing. Resigning - // does not make sense, so 'locked' is not an option here. - ], - '#access' => !$resultsDisabled, - '#default_value' => $resultsDisabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $states, - ]; - - return $this->setSettingsParents($form); - } - - /** - * {@inheritdoc} - * - * @phpstan-param array $form - * @phpstan-return void - */ - public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { - $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} */ From 931725e747cbf9e004f0dbd6fcaa194543d7fea7 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 1 Sep 2026 09:41:07 +0200 Subject: [PATCH 28/33] Reverted handler states from digital signature handler --- .../os2forms_digital_signature.install | 32 --------- .../DigitalSignatureWebformHandler.php | 68 +++++-------------- 2 files changed, 16 insertions(+), 84 deletions(-) diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.install b/modules/os2forms_digital_signature/os2forms_digital_signature.install index e971207..37b57d8 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.install +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.install @@ -7,38 +7,6 @@ use Drupal\os2forms_attachment\Os2formsAttachmentPrintBuilder; use Drupal\webform\Entity\Webform; -use Drupal\webform\WebformSubmissionInterface; - -/** - * Set states config to completed on existing digital signature handlers. - */ -function os2forms_digital_signature_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_digital_signature') { - continue; - } - $handlers[$handlerKey]['settings']['additional']['states'] = [WebformSubmissionInterface::STATE_COMPLETED]; - $changed = TRUE; - } - - if ($changed) { - $config->set('handlers', $handlers)->save(); - } - } -} /** * Migrate digital signature configuration onto the handler. diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index 523fe1f..85d57f1 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -90,24 +90,21 @@ class DigitalSignatureWebformHandler extends WebformHandlerBase { */ private readonly Settings $settings; - private const string ADDITIONAL = 'additional'; - private const string STATES = 'states'; - /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); - $instance->moduleHandler = $container->get('module_handler'); - $instance->elementManager = $container->get('plugin.manager.webform.element'); - $instance->logger = $container->get('logger.channel.os2forms_digital_signature'); - $instance->fileSystem = $container->get('file_system'); - $instance->fileRepository = $container->get('file.repository'); - $instance->fileUrlGenerator = $container->get('file_url_generator'); - $instance->signingService = $container->get('os2forms_digital_signature.signing_service'); - $instance->settings = $container->get('settings'); - - return $instance; + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->moduleHandler = $container->get('module_handler'); + $instance->elementManager = $container->get('plugin.manager.webform.element'); + $instance->logger = $container->get('logger.channel.os2forms_digital_signature'); + $instance->fileSystem = $container->get('file_system'); + $instance->fileRepository = $container->get('file.repository'); + $instance->fileUrlGenerator = $container->get('file_url_generator'); + $instance->signingService = $container->get('os2forms_digital_signature.signing_service'); + $instance->settings = $container->get('settings'); + + return $instance; } /** @@ -117,9 +114,6 @@ public function defaultConfiguration() { return [ 'attachment_element' => '', 'signature_position' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT, - self::ADDITIONAL => [ - self::STATES => [WebformSubmissionInterface::STATE_COMPLETED], - ], ]; } @@ -152,33 +146,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['signature_position'], ]; - // 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.'), - // The digital signature logic locks after the first signing. Resigning - // does not make sense, so 'locked' is not an option here. - ], - '#access' => !$resultsDisabled, - '#default_value' => $resultsDisabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $states, - ]; - - return $this->setSettingsParents($form); + return $this->setSettingsParents($form); } /** @@ -191,11 +159,6 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s $this->configuration['attachment_element'] = $values['attachment_element'] ?? ''; $this->configuration['signature_position'] = $values['signature_position'] ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT; - - $additional = $form_state->getValue(self::ADDITIONAL); - // Clean up states. - $additional[self::STATES] = array_values(array_filter($additional[self::STATES])); - $this->configuration[self::ADDITIONAL] = $additional; } /** @@ -250,9 +213,10 @@ protected function getAttachmentElementOptions(): array { * {@inheritdoc} */ public function preSave(WebformSubmissionInterface $webform_submission) { - $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)) { + // 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; } From bb7eff4fb3ae18a93a918ed2288f88f9695ce396 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 14 Sep 2026 10:27:13 +0200 Subject: [PATCH 29/33] 352: Set inherited Maestro task data on the submission entity at prepare time --- .../os2forms_forloeb/os2forms_forloeb.module | 16 ++++- .../EngineTasks/MaestroWebformInheritTask.php | 59 ++++++++++++------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/modules/os2forms_forloeb/os2forms_forloeb.module b/modules/os2forms_forloeb/os2forms_forloeb.module index 4636ec3..477a0d8 100644 --- a/modules/os2forms_forloeb/os2forms_forloeb.module +++ b/modules/os2forms_forloeb/os2forms_forloeb.module @@ -5,6 +5,7 @@ * Install, update and uninstall functions for the os2forms_forloeb. */ +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\maestro\Engine\MaestroEngine; use Drupal\os2forms_forloeb\MaestroHelper; @@ -12,6 +13,7 @@ use Drupal\os2forms_forloeb\Plugin\EngineTasks\MaestroWebformInheritTask; use Drupal\user\Entity\User; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; /** * Implements hook_maestro_interactive_handlers(). @@ -293,10 +295,18 @@ function os2forms_forloeb_preprocess_page(&$variables) { } /** - * Implements hook_webform_submission_form_alter(). + * Implements hook_entity_prepare_form(). + * + * Uses the generic hook rather than hook_webform_submission_prepare_form() + * because core invokes the generic hook first, and the inherited data should + * be set early. + * + * @see \Drupal\Core\Entity\EntityForm::init() */ -function os2forms_forloeb_webform_submission_form_alter(array &$form, FormStateInterface $formState, string $formId) { - MaestroWebformInheritTask::webformSubmissionFormAlter($form, $formState, $formId); +function os2forms_forloeb_entity_prepare_form(EntityInterface $entity, string $operation, FormStateInterface $formState) { + if ($entity instanceof WebformSubmissionInterface) { + MaestroWebformInheritTask::webformSubmissionPrepareForm($entity, $operation, $formState); + } } /** diff --git a/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php b/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php index 9544cee..c1e2838 100644 --- a/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php +++ b/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php @@ -2,13 +2,12 @@ namespace Drupal\os2forms_forloeb\Plugin\EngineTasks; -use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormStateInterface; use Drupal\maestro\Engine\MaestroEngine; use Drupal\maestro_webform\Plugin\EngineTasks\MaestroWebformTask; -use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\WebformSubmissionInterface; /** * Maestro Webform Task Plugin for Multiple Submissions. @@ -141,10 +140,41 @@ public function prepareTaskForSave(array &$form, FormStateInterface $form_state, } /** - * Implements hook_webform_submission_form_alter(). + * Implements hook_entity_prepare_form() for webform submissions. + * + * Sets inherited data on the webform submission entity. + * + * @see \Drupal\webform\WebformSubmissionForm::populateElements() + */ + public static function webformSubmissionPrepareForm(WebformSubmissionInterface $submission, string $operation, FormStateInterface $formState): void { + if ($source = self::getInheritedSourceSubmission()) { + $webform = $submission->getWebform(); + + // Only inherit values for elements on the target form. + $data = []; + foreach ($source->getData() as $key => $value) { + if ($webform->getElement($key)) { + $data[$key] = $value; + } + } + + if ($data) { + $submission->setData($data + $submission->getData()); + } + } + } + + /** + * Get the webform submission that the current request's task inherits from. + * + * Resolves the Maestro queue ID from the current request and, if the + * corresponding template task is a webform task configured to inherit + * another task's submission, loads that submission. + * + * @todo Align with MaestroHelper::maestroZeroUserNotification(), which + * resolves the process submission in a similar way. */ - public static function webformSubmissionFormAlter(array &$form, FormStateInterface $formState, string $formId) { - // @todo Clean up and align with MaestroHelper::maestroZeroUserNotification(). + public static function getInheritedSourceSubmission(): ?WebformSubmissionInterface { if ($queueID = self::getQueueIdFromRequest()) { $templateTask = MaestroEngine::getTemplateTaskByQueueID($queueID); if (self::isWebformTask($templateTask)) { @@ -152,26 +182,13 @@ public static function webformSubmissionFormAlter(array &$form, FormStateInterfa $processID = MaestroEngine::getProcessIdFromQueueId($queueID); $entityIdentifier = MaestroEngine::getAllEntityIdentifiersForProcess($processID)[$inheritWebformUniqueId] ?? NULL; if ('webform_submission' === ($entityIdentifier['entity_type'] ?? NULL)) { - $submission = WebformSubmission::load($entityIdentifier['entity_id']); - $data = $submission->getData(); - - // The target element may be hidden inside sections or field groups - // on the target form. Therefore, we need to load that form and get - // element information to properly set default element values nested - // inside the form. - if ($targetWebform = Webform::load($form['#webform_id'] ?? NULL)) { - foreach ($data as $key => $value) { - if ($targetElement = $targetWebform->getElement($key)) { - if ($element = &NestedArray::getValue($form['elements'], $targetElement['#webform_parents'])) { - $element['#default_value'] = $value; - } - } - } - } + return WebformSubmission::load($entityIdentifier['entity_id']); } } } } + + return NULL; } /** From 13c94a426e52117956f1bb1934526e8f3e107039 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 14 Sep 2026 10:36:41 +0200 Subject: [PATCH 30/33] 352: Updated CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ddc05a..8d6b888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- [PR-353](https://github.com/OS2Forms/os2forms/pull/353) + - Moved `WebformInheritTask` data inheriting from + `hook_webform_submission_form_alter` to `hook_entity_prepare_form`. + - Set data on the submission. + ## [5.1.0] 2026-06-03 - [PR-326](https://github.com/OS2Forms/os2forms/pull/326) From 2bf94b34ce84237e189d844bcc2332fc970bbafa Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 15 Sep 2026 10:41:44 +0200 Subject: [PATCH 31/33] 352: Code cleanup --- .../os2forms_forloeb/os2forms_forloeb.module | 4 +- .../EngineTasks/MaestroWebformInheritTask.php | 49 +++++++++++-------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/modules/os2forms_forloeb/os2forms_forloeb.module b/modules/os2forms_forloeb/os2forms_forloeb.module index 477a0d8..cbdd09e 100644 --- a/modules/os2forms_forloeb/os2forms_forloeb.module +++ b/modules/os2forms_forloeb/os2forms_forloeb.module @@ -299,9 +299,7 @@ function os2forms_forloeb_preprocess_page(&$variables) { * * Uses the generic hook rather than hook_webform_submission_prepare_form() * because core invokes the generic hook first, and the inherited data should - * be set early. - * - * @see \Drupal\Core\Entity\EntityForm::init() + * be set early (@see \Drupal\Core\Entity\EntityForm::init() for details). */ function os2forms_forloeb_entity_prepare_form(EntityInterface $entity, string $operation, FormStateInterface $formState) { if ($entity instanceof WebformSubmissionInterface) { diff --git a/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php b/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php index c1e2838..ea7de0d 100644 --- a/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php +++ b/modules/os2forms_forloeb/src/Plugin/EngineTasks/MaestroWebformInheritTask.php @@ -151,15 +151,14 @@ public static function webformSubmissionPrepareForm(WebformSubmissionInterface $ $webform = $submission->getWebform(); // Only inherit values for elements on the target form. - $data = []; - foreach ($source->getData() as $key => $value) { - if ($webform->getElement($key)) { - $data[$key] = $value; - } - } - - if ($data) { - $submission->setData($data + $submission->getData()); + $inheritedData = array_filter( + $source->getData(), + static fn (string $key) => NULL !== $webform->getElement($key), + ARRAY_FILTER_USE_KEY + ); + + if ($inheritedData) { + $submission->setData($inheritedData + $submission->getData()); } } } @@ -175,20 +174,28 @@ public static function webformSubmissionPrepareForm(WebformSubmissionInterface $ * resolves the process submission in a similar way. */ public static function getInheritedSourceSubmission(): ?WebformSubmissionInterface { - if ($queueID = self::getQueueIdFromRequest()) { - $templateTask = MaestroEngine::getTemplateTaskByQueueID($queueID); - if (self::isWebformTask($templateTask)) { - if ($inheritWebformUniqueId = ($templateTask['data'][self::INHERIT_WEBFORM_UNIQUE_ID] ?? NULL)) { - $processID = MaestroEngine::getProcessIdFromQueueId($queueID); - $entityIdentifier = MaestroEngine::getAllEntityIdentifiersForProcess($processID)[$inheritWebformUniqueId] ?? NULL; - if ('webform_submission' === ($entityIdentifier['entity_type'] ?? NULL)) { - return WebformSubmission::load($entityIdentifier['entity_id']); - } - } - } + $queueID = self::getQueueIdFromRequest(); + if (NULL === $queueID) { + return NULL; + } + + $templateTask = MaestroEngine::getTemplateTaskByQueueID($queueID); + if (!self::isWebformTask($templateTask)) { + return NULL; + } + + $inheritWebformUniqueId = $templateTask['data'][self::INHERIT_WEBFORM_UNIQUE_ID] ?? NULL; + if (!$inheritWebformUniqueId) { + return NULL; + } + + $processID = MaestroEngine::getProcessIdFromQueueId($queueID); + $entityIdentifier = MaestroEngine::getAllEntityIdentifiersForProcess($processID)[$inheritWebformUniqueId] ?? NULL; + if ('webform_submission' !== ($entityIdentifier['entity_type'] ?? NULL)) { + return NULL; } - return NULL; + return WebformSubmission::load($entityIdentifier['entity_id']); } /** From ae61c8531c13dc06b01d61804a135e91e7325e90 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 15 Sep 2026 15:03:22 +0200 Subject: [PATCH 32/33] Update build release script --- build_release.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build_release.sh b/build_release.sh index 0ae94a1..8975f06 100755 --- a/build_release.sh +++ b/build_release.sh @@ -27,6 +27,10 @@ git merge --no-ff origin/feature/move-digital-signature-configuration-to-handler 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" +git merge --no-ff origin/feature/maestro-inherit-data-on-prepare-form +git checkout --theirs CHANGELOG.md +git add CHANGELOG.md +git commit -m "Merge remote-tracking branch 'origin/feature/maestro-inherit-data-on-prepare-form' 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 From c550a3e51f60789784b6961e70e6b242c6373463 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 16 Sep 2026 09:08:07 +0200 Subject: [PATCH 33/33] Updated CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d6b888..55b61ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +This is a hack so the CHANGELOG is messed up. +Generated by build_release.sh # OS2Forms Change Log