From dd2a971f4ffcece1663d886c3f952eee7d1f0b25 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 10 Nov 2025 15:05:30 +0100 Subject: [PATCH 01/62] os2forms_fordelingskomponent # Conflicts: # README.md --- .gitignore | 2 + README.md | 9 + Taskfile.yml | 31 ++ compose.yaml | 5 + composer.json | 38 ++ .../README.md | 11 + .../webform.webform.os2forms_fdk_example.yml | 245 +++++++++++++ ...orms_fordelingskomponent_examples.info.yml | 8 + os2forms_fordelingskomponent.info.yml | 13 + os2forms_fordelingskomponent.links.menu.yml | 5 + os2forms_fordelingskomponent.routing.yml | 20 ++ os2forms_fordelingskomponent.services.yml | 15 + phpstan.neon | 8 + ...rdelingskomponentRoutingInfoController.php | 65 ++++ src/Exception/Exception.php | 10 + .../InvalidAttachmentElementException.php | 10 + src/Exception/SubmissionNotFoundException.php | 10 + src/Form/SettingsForm.php | 240 +++++++++++++ src/Helper/FordelingskomponentHelper.php | 330 ++++++++++++++++++ src/Helper/WebformHelperSF2900.php | 248 +++++++++++++ src/Model/Attachment.php | 30 ++ .../JobType/FordelingskomponentSF2900.php | 66 ++++ .../WebformHandler/WebformHandlerSF2900.php | 193 ++++++++++ 23 files changed, 1612 insertions(+) create mode 100644 .gitignore create mode 100644 Taskfile.yml create mode 100644 compose.yaml create mode 100644 composer.json create mode 100644 modules/os2forms_fordelingskomponent_examples/README.md create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml create mode 100644 modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml create mode 100644 os2forms_fordelingskomponent.info.yml create mode 100644 os2forms_fordelingskomponent.links.menu.yml create mode 100644 os2forms_fordelingskomponent.routing.yml create mode 100644 os2forms_fordelingskomponent.services.yml create mode 100644 phpstan.neon create mode 100644 src/Controller/Os2formsFordelingskomponentRoutingInfoController.php create mode 100644 src/Exception/Exception.php create mode 100644 src/Exception/InvalidAttachmentElementException.php create mode 100644 src/Exception/SubmissionNotFoundException.php create mode 100644 src/Form/SettingsForm.php create mode 100644 src/Helper/FordelingskomponentHelper.php create mode 100644 src/Helper/WebformHelperSF2900.php create mode 100644 src/Model/Attachment.php create mode 100644 src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php create mode 100644 src/Plugin/WebformHandler/WebformHandlerSF2900.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7579f74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock diff --git a/README.md b/README.md index 0c82f6c..d1c1120 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ # Fordelingskomponent for Drupal + +## Example keys + +| Key | Type | Provider | +|-------------------------|----------------|----------| +| SF2900 Certificate | Certificate | File | +| SF2900 SFTP private key | Authentication | File | + +Note: The "SFTP private key" key must be passwordless. diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..8963edb --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,31 @@ +# https://taskfile.dev + +version: '3' + +tasks: + compose: + cmds: + - docker compose {{.TASK_ARGS}} {{.CLI_ARGS}} + internal: true + + composer: + desc: Run composer inside docker compose setup, e.g. `task {{.TASK}} -- install` + cmds: + - task: compose + vars: + TASK_ARGS: run --rm php composer {{.TASK_ARGS}} + + coding-standards:coding-standards:check: + desc: Apply and check coding-standards + cmds: + - task: composer + vars: + TASK_ARGS: coding-standards-apply + - task: composer + vars: + TASK_ARGS: coding-standards-check + + default: + cmds: + - task --list + silent: true diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..7798cd8 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,5 @@ +services: + php: + image: itkdev/php8.3-fpm:latest + volumes: + - .:/app diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..bd9e18c --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "os2forms/os2forms_fordelingskomponent", + "description": "Fordelingskomponent integration for OS2Forms", + "type": "drupal-module", + "authors": [ + { + "name": "Mikkel Ricky", + "email": "rimi@aarhus.dk" + } + ], + "require": { + "drush/drush": "^12 || ^13", + "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten", + "os2web/os2web_audit": "^1.1", + "os2web/os2web_key": "^1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.49@dev", + "drupal/coder": "9.x-dev", + "mglaman/phpstan-drupal": "dev-main", + "phpstan/extension-installer": "1.4.x-dev" + }, + "repositories": [ + { + "type": "composer", + "url": "https://packages.drupal.org/8" + } + ], + "minimum-stability": "dev", + "config": { + "allow-plugins": { + "cweagans/composer-patches": true, + "ergebnis/composer-normalize": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + } +} diff --git a/modules/os2forms_fordelingskomponent_examples/README.md b/modules/os2forms_fordelingskomponent_examples/README.md new file mode 100644 index 0000000..62a4fb5 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/README.md @@ -0,0 +1,11 @@ +# OS2Forms Fordelingskomponent examples + +Example forms for OS2Forms Fordelingskomponent + +## Installation + +```sh +drush pm:enable os2forms_fordelingskomponent_examples +``` + +Go to `/admin/structure/webform?category=Example` to see the example forms. diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml new file mode 100644 index 0000000..7ba7643 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml @@ -0,0 +1,245 @@ +langcode: da +status: open +dependencies: + module: + - os2forms_fordelingskomponent + - webform_encrypt + - webform_revisions + enforced: + module: + - os2forms_fordelingskomponent_examples +third_party_settings: + webform_encrypt: + element: + message: + encrypt: true + encrypt_profile: webform + recipient_cpr: + encrypt: true + encrypt_profile: webform + digital_post_content_pdf: + encrypt: true + encrypt_profile: webform +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2forms_fdk_example +title: 'OS2Forms Fordelingskomponent example' +description: 'Simple example form with a Fordelingskomponent handler' +category: Example +elements: |- + message: + '#type': textarea + '#title': Message + '#required': true + '#default_value': |- + [current-date:long] + + [random:hash:sha512] + recipient_cpr: + '#type': textfield + '#title': 'Recipient cpr' + '#required': true + '#default_value': '1705880000' + digital_post_content_pdf: + '#type': 'webform_entity_print_attachment:pdf' + '#title': 'Fordelingskomponent (PDF)' + '#display_on': view + '#filename': hat-og-briller.pdf +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: '' + ajax_effect: '' + ajax_speed: null + page: true + page_submit_path: '' + page_confirm_path: '' + page_theme_name: '' + form_title: both + form_submit_once: false + form_open_message: '' + form_close_message: '' + form_exception_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + form_attributes: { } + form_method: '' + form_action: '' + share: false + share_node: false + share_theme_name: '' + share_title: true + share_page_body_attributes: { } + submission_label: '' + submission_exception_message: '' + submission_locked_message: '' + submission_log: false + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: '' + wizard_prev_button_label: '' + wizard_next_button_label: '' + wizard_toggle: false + wizard_toggle_show_label: '' + wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + draft_pending_single_message: '' + draft_pending_multiple_message: '' + confirmation_type: message + confirmation_url: '' + confirmation_title: '' + confirmation_message: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + os2forms_fordelingskomponent_sf2900: + id: os2forms_fordelingskomponent_sf2900 + handler_id: os2forms_fordelingskomponent_sf2900 + label: 'Fordelingskomponent (sf2900)' + notes: '' + status: true + conditions: { } + weight: 0 + settings: { } +variants: { } diff --git a/modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml b/modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml new file mode 100644 index 0000000..84b5eff --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml @@ -0,0 +1,8 @@ +name: 'OS2Forms Fordelingskomponent examples' +type: module +description: 'Example forms for OS2Forms Fordelingskomponent.' +package: 'OS2Forms' + +core_version_requirement: ^9 || ^10 +dependencies: + - 'os2forms_fordelingskomponent:os2forms_fordelingskomponent' diff --git a/os2forms_fordelingskomponent.info.yml b/os2forms_fordelingskomponent.info.yml new file mode 100644 index 0000000..ff73b5c --- /dev/null +++ b/os2forms_fordelingskomponent.info.yml @@ -0,0 +1,13 @@ +name: 'Fordelingskomponent' +type: module +description: 'Fordelingsskomponent integration for OS2Forms' +package: OS2Forms +core_version_requirement: ^10 || ^11 +dependencies: + - advancedqueue:advancedqueue + - 'os2web_key:os2web_key' + - 'os2web_audit:os2web_audit' + # Why don't we get this dependency implicitly from os2web_key? + - 'key:key' + +configure: os2forms_fordelingskomponent.admin.settings diff --git a/os2forms_fordelingskomponent.links.menu.yml b/os2forms_fordelingskomponent.links.menu.yml new file mode 100644 index 0000000..1d95f3f --- /dev/null +++ b/os2forms_fordelingskomponent.links.menu.yml @@ -0,0 +1,5 @@ +os2forms_fordelingskomponent.settings: + title: OS2Forms Fordelingskomponent + description: Configure the OS2Forms Fordelingskomponent module + parent: system.admin_config_system + route_name: os2forms_fordelingskomponent.settings diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml new file mode 100644 index 0000000..0954dd5 --- /dev/null +++ b/os2forms_fordelingskomponent.routing.yml @@ -0,0 +1,20 @@ +os2forms_fordelingskomponent.settings: + path: '/admin/os2forms_fordelingskomponent/settings' + defaults: + _title: 'Fordelingskomponent settings' + _form: 'Drupal\os2forms_fordelingskomponent\Form\SettingsForm' + requirements: + _permission: 'administer site configuration' + +os2forms_fordelingskomponent.routing_info: + path: '/os2forms-fordelingskomponent/routing/{webform}/{handler}' + defaults: + _title: 'Fordelingskomponent routing info' + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentRoutingInfoController' + options: + parameters: + webform: + type: 'entity:webform' + requirements: + _permission: 'administer site configuration' + diff --git a/os2forms_fordelingskomponent.services.yml b/os2forms_fordelingskomponent.services.yml new file mode 100644 index 0000000..f31e2be --- /dev/null +++ b/os2forms_fordelingskomponent.services.yml @@ -0,0 +1,15 @@ +services: + _defaults: + autowire: true + + logger.channel.os2forms_fordelingskomponent: + parent: logger.channel_base + arguments: [ 'os2forms_fordelingskomponent' ] + + logger.channel.os2forms_fordelingskomponent_submission: + parent: logger.channel_base + arguments: [ 'webform_submission' ] + + Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper: + + Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900: diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..a41d209 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +parameters: + paths: + - . + level: 5 + customRulesetUsed: true + reportUnmatchedIgnoredErrors: false + excludePaths: + - vendor diff --git a/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php new file mode 100644 index 0000000..e027faa --- /dev/null +++ b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php @@ -0,0 +1,65 @@ +webformStorage = $entityTypeManager->getStorage('webform'); + } + + /** + * Builds the response. + */ + public function __invoke(WebformInterface $webform, string $handler): array { + try { + $handler = $webform->getHandler($handler); + } + catch (\Exception) { + $handler = NULL; + } + + if (!$handler instanceof WebformHandlerSF2900) { + throw new NotFoundHttpException(); + } + + $settings = $this->helper->getHandlerConfiguration($handler->getConfiguration()); + $info = $this->helper->getRoutingInfo( + routingMyndighed: $settings[FordelingskomponentHelper::ROUTING_MYNDIGHED], + kleEmne: $settings[FordelingskomponentHelper::KLE_EMNE], + handlingFacet: $settings[FordelingskomponentHelper::HANDLING_FACET] ?: null, + ); + return [ + 'stuff' => [ + '#prefix' => '
',
+        '#suffix' => '
',
+        '#markup' => Yaml::encode([
+          'webform' => $webform->label(),
+          'handler' => $handler->label(),
+          'settings' => $settings,
+          'info' => json_encode($info->jsonSerialize(), JSON_PRETTY_PRINT),
+        ]),
+      ],
+    ];
+  }
+
+}
diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php
new file mode 100644
index 0000000..673c0f5
--- /dev/null
+++ b/src/Exception/Exception.php
@@ -0,0 +1,10 @@
+queueStorage = $entityTypeManager->getStorage('advancedqueue_queue');
+  }
+
+  /**
+   *
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('config.typed'),
+      $container->get('entity_type.manager'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId(): string {
+    return 'os2forms_fordelingskomponent_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames(): array {
+    return [self::CONFIG_NAME];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state): array {
+    $config = $this->config(self::CONFIG_NAME);
+
+    $form['test_mode'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Test mode'),
+      '#default_value' => $config->get(self::TEST_MODE),
+    ];
+
+    $this->buildFormSf2900($form, $form_state);
+    $this->buildFormProcessing($form, $form_state);
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   *
+   */
+  private function buildFormSf2900(array &$form, FormStateInterface $formState): void {
+    $config = $this->config(self::CONFIG_NAME)->get(self::SECTION_SF2900) ?? [];
+
+    $form[self::SECTION_SF2900] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('SF2900'),
+      '#tree' => TRUE,
+    ];
+
+    $form[self::SECTION_SF2900][self::SENDER_ID] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Sender ID'),
+      '#required' => TRUE,
+      '#default_value' => $config[self::SENDER_ID] ?? NULL,
+      '#description' => $this->t('Sender ID (CVR).'),
+    ];
+
+    $form[self::SECTION_SF2900][self::ROUTING_MYNDIGHED] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Routing myndighed'),
+      // '#required' => TRUE,
+      '#default_value' => $config[self::ROUTING_MYNDIGHED] ?? NULL,
+      '#description' => $this->t('Default routing myndighed (CVR). May be overwritten by handler settings.'),
+    ];
+
+    $form[self::SECTION_SF2900][self::REGISTRERING_IT_SYSTEM] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Registrering it-system'),
+      // '#required' => TRUE,
+      '#default_value' => $config[self::REGISTRERING_IT_SYSTEM] ?? NULL,
+      '#description' => $this->t('@todo: Registrering it-system (UUID).'),
+    ];
+
+    $form[self::SECTION_SF2900]['certificate'] = [
+      '#type' => 'key_select',
+      '#key_filters' => [
+        'type' => 'os2web_key_certificate',
+      ],
+      '#title' => $this->t('Certificate'),
+      '#required' => TRUE,
+      '#default_value' => $config['certificate'] ?? NULL,
+      '#description' => $this->t('Passwordless certificate.'),
+    ];
+
+    $form[self::SECTION_SF2900][self::SECTION_SFTP] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('SFTP'),
+      '#tree' => TRUE,
+    ];
+
+    $form[self::SECTION_SF2900][self::SECTION_SFTP][self::USERNAME] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Username'),
+      '#required' => TRUE,
+      '#default_value' => $config[self::SECTION_SFTP][self::USERNAME] ?? NULL,
+      '#description' => $this->t('SFTP username.'),
+    ];
+
+    $form[self::SECTION_SF2900][self::SECTION_SFTP][self::PRIVATE_KEY] = [
+      '#type' => 'key_select',
+      '#title' => $this->t('Private key'),
+      '#required' => TRUE,
+      '#default_value' => $config[self::SECTION_SFTP][self::PRIVATE_KEY] ?? NULL,
+      '#description' => $this->t('SFTP private key.'),
+    ];
+
+  }
+
+  /**
+   *
+   */
+  private function buildFormProcessing(array &$form, FormStateInterface $formState): void {
+    $config = $this->config(self::CONFIG_NAME)->get(self::SECTION_PROCESSING);
+
+    $form[self::SECTION_PROCESSING] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Processing'),
+      '#tree' => TRUE,
+    ];
+
+    $defaultValue = $config[self::QUEUE] ?? NULL;
+    $description = empty($defaultValue)
+      ? $this->t('Optional queue for fordelingskomponent jobs. If no queue is specified, all fordelingskomponent jobs are run immediately.')
+      : $this->t("Optional queue for fordelingskomponent jobs. If no queue is specified, all fordelingskomponent jobs are run immediately. The queue must be run via Drupal's cron or via drush advancedqueue:queue:process @queue (in a cron job).",
+        [
+          '@queue' => $defaultValue,
+          ':queue_url' => '/admin/config/system/queues/jobs/' . urlencode($defaultValue),
+        ]);
+    $form[self::SECTION_PROCESSING][self::QUEUE] = [
+      '#type' => 'select',
+      '#title' => $this->t('Queue'),
+      '#options' => array_map(
+        static fn(EntityInterface $queue) => $queue->label(),
+        $this->queueStorage->loadMultiple()
+      ),
+      '#empty_option' => $this->t('No queue'),
+      '#default_value' => $defaultValue,
+      '#description' => $description,
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state): void {
+    $value = $form_state->getValue(self::SECTION_SF2900)[self::SENDER_ID] ?? '';
+    if (!FordelingskomponentHelper::isValidCVR($value)) {
+      $form_state->setErrorByName('sf2900][sender_id', $this->t('The sender ID is not a valid CVR.'));
+    }
+
+    $value = $form_state->getValue(self::SECTION_SF2900)[self::ROUTING_MYNDIGHED] ?? '';
+    if (!empty($value) && !FordelingskomponentHelper::isValidCVR($value)) {
+      $form_state->setErrorByName('sf2900][routing_myndighed', $this->t('The routing myndighed is not a valid CVR.'));
+    }
+
+    $value = $form_state->getValue(self::SECTION_SF2900)[self::REGISTRERING_IT_SYSTEM] ?? '';
+    if (!empty($value) && !FordelingskomponentHelper::isValidUUID($value)) {
+      $form_state->setErrorByName('sf2900][registrering_it_system', $this->t('The registrering it system is not a valid UUID.'));
+    }
+
+    parent::validateForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state): void {
+    $this->config(self::CONFIG_NAME)
+      ->set(self::TEST_MODE, $form_state->getValue(self::TEST_MODE))
+      ->set(self::SECTION_SF2900, $form_state->getValue(self::SECTION_SF2900))
+      ->set(self::SECTION_PROCESSING, $form_state->getValue(self::SECTION_PROCESSING))
+      ->save();
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php
new file mode 100644
index 0000000..9f6926e
--- /dev/null
+++ b/src/Helper/FordelingskomponentHelper.php
@@ -0,0 +1,330 @@
+configFactory->get(SettingsForm::CONFIG_NAME);
+  }
+
+  /**
+   *
+   */
+  public function getRoutingInfo(
+    string $routingMyndighed,
+    string $kleEmne,
+    ?string $handlingFacet,
+  ): mixed {
+    return $this->sf2900()->getModtagerList(
+      routingMyndighed: $routingMyndighed,
+      routingKLEEmne: $kleEmne,
+      routingHandlingFacet: $handlingFacet,
+    );
+  }
+
+  /**
+   *
+   */
+  public function sendJournalpost(
+    WebformSubmissionInterface $submission,
+    Attachment $attachment,
+    array $configuration,
+    // @todo
+    string $brugervendtNoegle,
+    string $titel,
+    string $beskrivelse,
+  ) {
+    $msg = sprintf('Fordelingskomponent afsend journalpost.');
+    // If the cause is a submission, add webform id to audit logging message.
+    $msg .= $submission ? sprintf(' Webform id %s.', $submission->getWebform()->id()) : '';
+    $this->auditLogger->info('Fordelingskomponent', $msg);
+  }
+
+  /**
+   * @return array
+   *    [The response, The kombi post message].
+   *
+   * @phpstan-return array
+   */
+  public function sendDokument(
+    WebformSubmissionInterface $submission,
+    Attachment $attachment,
+    array $configuration,
+    // @todo
+    string $brugervendtNoegle,
+    string $titel,
+    string $beskrivelse,
+  ) {
+    $sf2900 = $this->sf2900();
+    $sftp = $sf2900->sftp();
+
+    $transactionId = Serializer::createUuid();
+    $dokumentFilNavn = $sftp->putContents($attachment->contents, $attachment->filename);
+
+    $virkning = $this->buildVirkning($configuration);
+
+    $id = Serializer::createUuid();
+    $fraTidsPunkt = new \DateTime();
+    $brevDato = new \DateTime();
+    $routingKLEEmne = $configuration[self::KLE_EMNE];
+    $registreringItSystem = $configuration[SettingsForm::REGISTRERING_IT_SYSTEM];
+
+    $routingMyndighed = $configuration[self::ROUTING_MYNDIGHED];
+    $routingHandlingFacet = $configuration[self::HANDLING_FACET] ?: null;
+    // @todo This is probably not correct!
+    $routingModtagerAktoer = $configuration[SettingsForm::SENDER_ID];
+
+    $dokument = new DistributionDokumentType(
+      iD: $id,
+      kLEEmneForslag: $routingKLEEmne,
+      // handlingFacetForslag: null,
+      registrering: new DokumentRegistreringType(
+        fraTidsPunkt: SF2900::formatDateTime($fraTidsPunkt),
+        livscyklusKode: LivscyklusKodeType::VALUE_OPRETTET,
+        registreringItSystem: new UUID_URN($registreringItSystem),
+        relationListe: new RelationsListe(
+          variantListe: new VariantListeType([
+            new VariantType(
+              // If we don't clone the “virking", the XML serializer adds IDs en references which SF2900 does not handle.
+              virkning: $this->clone($virkning),
+              rolle: VariantRolleType::VALUE_VARIANT,
+              indeks: '1',
+              variantAttributter: new VariantAttributterType(
+                variantType: 'PDF',
+              ),
+              delAttributter: new DelAttributterType(
+                delTekst: 'Hele dokumentet',
+              ),
+            ),
+          ]),
+        ),
+        tilstandsListe: [
+          new TilstandListeType(
+            tilstand: [
+              new TilstandType(
+              // @todo
+                fremdrift: FremdriftType::VALUE_ENDELIGT,
+                virkning: $this->clone($virkning),
+              ),
+            ]
+          ),
+        ],
+        attributListe: new AttributterListeType([
+          new AttributterType(
+            brugervendtNoegleTekst: $brugervendtNoegle ?? $titel,
+            titelTekst: $titel,
+            beskrivelseTekst: $beskrivelse,
+            dokumenttype: DokumenttypeType::VALUE_ANDEN,
+            retning: RetningType::VALUE_UDGAAENDE,
+            brevdato: SF2900::formatDate($brevDato),
+            virkning: $this->clone($virkning),
+          ),
+        ]),
+      // importTidspunkt: null,
+      // brugerRef: null,
+      )
+    );
+
+    $response = $sf2900->afsend(
+      transactionId: $transactionId,
+      document: $dokument,
+      routingMyndighed: $routingMyndighed,
+      routingKLEEmne: $routingKLEEmne,
+      routingHandlingFacet: $routingHandlingFacet,
+      routingModtagerAktoer: $routingModtagerAktoer,
+      dokumentFilNavn: $dokumentFilNavn,
+    );
+
+    $msg = sprintf('Fordelingskomponent afsend dokument.');
+    // If the cause is a submission, add webform id to audit logging message.
+    $msg .= $submission ? sprintf(' Webform id %s.', $submission->getWebform()->id()) : '';
+    $this->auditLogger->info('Fordelingskomponent', $msg);
+
+    return [$response, $sf2900->getLastRequest()];
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param mixed $level
+   *   The level.
+   * @param string $message
+   *   The message.
+   * @param array $context
+   *   The context.
+   *
+   * @phpstan-param array $context
+   */
+  public function log($level, $message, array $context = []): void {
+    $this->logger->log($level, $message, $context);
+    // @see https://www.drupal.org/node/3020595
+    if (isset($context['webform_submission']) && $context['webform_submission'] instanceof WebformSubmissionInterface) {
+      $this->submissionLogger->log($level, $message, $context);
+    }
+  }
+
+  /**
+   *
+   */
+  public static function isValidCVR(string $value): bool {
+    return (bool) preg_match('/^[0-9]{8}$/', $value);
+  }
+
+  /**
+   *
+   */
+  public static function isValidUuid(string $value): bool {
+    return (bool) preg_match('/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/', $value);
+  }
+
+  private SF2900 $sf2900;
+
+  /**
+   * Get a singleton instance of SF2900.
+   */
+  private function sf2900(): SF2900 {
+    if (!isset($this->sf2900)) {
+      $options = $this->getModuleConfig()->get('sf2900');
+      $certificateKey = $this->keyRepository->getKey($options['certificate']);
+      // @todo Handle other key types?
+      $certificates = $this->keyHelper->getCertificates($certificateKey);
+      $certificate = implode(PHP_EOL, $certificates);
+
+      $privateKeyKey = $this->keyRepository->getKey($options['sftp']['private_key']);
+      $privateKey = $privateKeyKey->getKeyValue();
+      $sf2900options = [
+        'authority_cvr' => $options['sender_id'],
+        'certificate' => $certificate,
+        'sftp' => [
+          'private_key' => $privateKey,
+          // $options['sftp']['private_key_pass'] ?? '',
+      //          'private_key_password' => '',
+          'username' => $options['sftp']['username'],
+        ],
+      ];
+
+      $this->sf2900 = new SF2900($this->eventDispatcher, $sf2900options);
+    }
+
+    return $this->sf2900;
+  }
+
+  /**
+   * Get handler settings combined with select module setting.
+   *
+   * @param \Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900 $handlerSettings
+   *
+   * @return mixed
+   */
+  public function getHandlerConfiguration(
+    array $handlerSettings,
+  ) {
+    $settings = $handlerSettings;
+    $options = $this->getModuleConfig()->get('sf2900');
+
+    $settings += [
+      FordelingskomponentHelper::ROUTING_MYNDIGHED => $options[FordelingskomponentHelper::ROUTING_MYNDIGHED],
+      SettingsForm::REGISTRERING_IT_SYSTEM => $options[SettingsForm::REGISTRERING_IT_SYSTEM],
+      SettingsForm::SENDER_ID => $options[SettingsForm::SENDER_ID],
+    ];
+
+    return $settings;
+  }
+
+  private function buildVirkning(array $configuration): VirkningType
+  {
+    $aktoer = $configuration[SettingsForm::REGISTRERING_IT_SYSTEM];
+
+    return new VirkningType(
+      aktoer: new UUID_URN($aktoer),
+      aktoerType: AktoerTypeType::VALUE_IT_SYSTEM,
+    // fraTidsPunkt: null,
+    // tilTidspunkt: null,
+    // noteTekst: null,
+    );
+  }
+
+
+  /**
+   * Deep clone.
+   *
+   * @param AbstractStructBase $object
+   *
+   * @return AbstractStructBase
+   */
+  private function clone(AbstractStructBase $object): AbstractStructBase
+  {
+    return unserialize(serialize($object));
+  }
+
+}
diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php
new file mode 100644
index 0000000..d680d90
--- /dev/null
+++ b/src/Helper/WebformHelperSF2900.php
@@ -0,0 +1,248 @@
+webformSubmissionStorage = $entityTypeManager->getStorage('webform_submission');
+    $this->queueStorage = $entityTypeManager->getStorage('advancedqueue_queue');
+  }
+
+  /**
+   * Afsend med Fordelingskomponenten.
+   *
+   * @param \Drupal\webform\WebformSubmissionInterface $submission
+   *   The submission.
+   * @param array $handlerSettings
+   *   The Handler settings.
+   * @param array $submissionData
+   *   Submission data. Only for overriding during testing and development.
+   *
+   * @return array
+   *   [The response, The kombi post message].
+   *
+   * @phpstan-param array $handlerSettings
+   * @phpstan-param array $submissionData
+   */
+  public function afsend(WebformSubmissionInterface $submission, array $handlerSettings, array $submissionData = []): array
+  {
+    $submissionData = $submissionData + $submission->getData();
+    $configuration = $this->helper->getHandlerConfiguration($handlerSettings);
+    $attachment = $this->getAttachment($submission, $handlerSettings);
+
+    $titel = __METHOD__;
+    $beskrivelse = __FILE__;
+    $brugervendtNoegle = __METHOD__;
+
+    return $this->helper->sendDokument(
+      $submission,
+      $attachment, $configuration,
+      titel: $titel,
+      beskrivelse: $beskrivelse,
+      brugervendtNoegle: $brugervendtNoegle,
+    );
+  }
+
+  /**
+   * Get main document.
+   *
+   * @see WebformAttachmentController::download()
+   *
+   * @phpstan-param array $handlerSettings
+   */
+  protected function getAttachment(WebformSubmissionInterface $submission, array $handlerSettings): Attachment {
+    // Lifted from Drupal\webform_attachment\Controller\WebformAttachmentController::download.
+    $element = $handlerSettings[WebformHandlerSF2900::ATTACHMENT_ELEMENT];
+    $element = $submission->getWebform()->getElement($element) ?: [];
+    [$type] = explode(':', $element['#type']);
+    $instance = $this->elementInfoManager->createInstance($type);
+
+    if (!$instance instanceof WebformAttachmentBase) {
+      throw new InvalidAttachmentElementException(sprintf('Attachment element must be an instance of %s. Found %s.', WebformAttachmentBase::class, get_class($instance)));
+    }
+
+    $fileName = $instance::getFileName($element, $submission);
+    $mimeType = $instance::getFileMimeType($element, $submission);
+    $content = $instance::getFileContent($element, $submission);
+
+    return new Attachment(
+      $content,
+      $mimeType,
+      $fileName
+    );
+  }
+
+
+  /**
+   * Load webform submission by id.
+   */
+  public function loadSubmission(int $id): ?WebformSubmissionInterface {
+    return $this->webformSubmissionStorage->load($id);
+  }
+
+  /**
+   * Load queue.
+   */
+  private function loadQueue(): ?QueueInterface {
+    $processingSettings = $this->helper->getModuleConfig()->get(SettingsForm::SECTION_PROCESSING);
+
+    /** @var \Drupal\advancedqueue\Entity\QueueInterface $queue */
+    $queue = $this->queueStorage->load($processingSettings['queue'] ?? null);
+
+    return $queue;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param mixed $level
+   *   The level.
+   * @param string $message
+   *   The message.
+   * @param array $context
+   *   The context.
+   *
+   * @phpstan-param array $context
+   */
+  public function log($level, $message, array $context = []): void {
+    $this->logger->log($level, $message, $context);
+    // @see https://www.drupal.org/node/3020595
+    if (isset($context['webform_submission']) && $context['webform_submission'] instanceof WebformSubmissionInterface) {
+      $this->submissionLogger->log($level, $message, $context);
+    }
+  }
+
+  /**
+   * Create a job.
+   *
+   * @see self::processJob()
+   *
+   * @phpstan-param array $handlerConfiguration
+   */
+  public function createJob(WebformSubmissionInterface $webformSubmission, array $handlerConfiguration): ?Job {
+    $context = [
+      'handler_id' => WebformHandlerSF2900::ID,
+      'webform_submission' => $webformSubmission,
+    ];
+
+    try {
+      $job = Job::create(FordelingskomponentSF2900::class, [
+        'formId' => $webformSubmission->getWebform()->id(),
+        'submissionId' => $webformSubmission->id(),
+        'handlerConfiguration' => $handlerConfiguration,
+      ]);
+      $queue = $this->loadQueue();
+      if (null !== $queue) {
+        $queue->enqueueJob($job);
+        $context['@queue'] = $queue->id();
+        $this->notice('Job for afsend added to the queue @queue.', $context + [
+            'operation' => 'Fordelingskomponent afsend queued',
+          ]);
+      } else {
+        $this->processJob($job);
+      }
+
+      return $job;
+    }
+    catch (\Exception $exception) {
+      $this->error('Error creating job for afsen.', $context + [
+        'operation' => 'Fordelingskomponent afsend failed',
+      ]);
+      return NULL;
+    }
+  }
+
+  /**
+   * Process a job.
+   *
+   * @see self::createJob()
+   */
+  public function processJob(Job $job): JobResult {
+    $payload = $job->getPayload();
+
+    $context = [
+      'handler_id' => WebformHandlerSF2900::ID,
+      'operation' => 'fordelingskomponent afsend',
+    ];
+    try {
+      $submissionId = $payload['submissionId'];
+      $submission = $this->loadSubmission($submissionId);
+      if (NULL === $submission) {
+        $message = 'Cannot load submission @submissionId';
+        $context = [
+          '@submissionId' => $submissionId,
+        ];
+        $this->error($message, $context);
+
+        throw new SubmissionNotFoundException(str_replace(array_keys($context), array_values($context),
+          $message));
+      }
+
+      $context['webform_submission'] = $submission;
+      $this->afsend($submission, $payload['handlerConfiguration']);
+
+      $this->notice('Fordelingskomponent afsendt', $context);
+
+      return JobResult::success();
+    }
+    catch (\Exception $e) {
+      $this->error('Error: @message', $context + [
+        '@message' => $e->getMessage(),
+      ]);
+
+      return JobResult::failure($e->getMessage());
+    }
+  }
+
+}
diff --git a/src/Model/Attachment.php b/src/Model/Attachment.php
new file mode 100644
index 0000000..c753ad8
--- /dev/null
+++ b/src/Model/Attachment.php
@@ -0,0 +1,30 @@
+mimeType;
+  }
+
+}
diff --git a/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php b/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php
new file mode 100644
index 0000000..d45efed
--- /dev/null
+++ b/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php
@@ -0,0 +1,66 @@
+ $configuration
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get(WebformHelperSF2900::class)
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @phpstan-param array $configuration
+   */
+  public function __construct(
+    array $configuration,
+    $plugin_id,
+    $plugin_definition,
+    WebformHelperSF2900 $helper,
+  ) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->helper = $helper;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process(Job $job): JobResult {
+    return $this->helper->processJob($job);
+  }
+
+}
diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php
new file mode 100644
index 0000000..263d01c
--- /dev/null
+++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php
@@ -0,0 +1,193 @@
+helper = $container->get(WebformHelperSF2900::class);
+
+    return $instance;
+  }
+
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state)
+  {
+    $form[FordelingskomponentHelper::KLE_EMNE] = [
+      '#title' => $this->t('KLE-emne'),
+      '#type' => 'textfield',
+      '#default_value' => $this->configuration[FordelingskomponentHelper::KLE_EMNE],
+      '#required' => true,
+      '#attributes' => [
+        'pattern' => FordelingskomponentHelper::KLE_EMNE_PATTERN,
+      ],
+      '#description' => $this->t('KLE-emne (format: dd.dd.dd)'),
+    ];
+    $form[FordelingskomponentHelper::HANDLING_FACET] = [
+      '#title' => $this->t('Handling-facet'),
+      '#type' => 'textfield',
+      '#default_value' => $this->configuration[FordelingskomponentHelper::HANDLING_FACET],
+      '#attributes' => [
+        'pattern' => FordelingskomponentHelper::HANDLING_FACET_PATTERN,
+      ],
+    ];
+
+    $availableElements = $this->getAttachmentElements();
+    $form[static::ATTACHMENT_ELEMENT] = [
+      '#type' => 'select',
+      '#title' => $this->t('Element that contains the document to send'),
+      '#required' => TRUE,
+      '#default_value' => $this->configuration[static::ATTACHMENT_ELEMENT] ?? NULL,
+      '#options' => $availableElements,
+    ];
+
+    return parent::buildConfigurationForm($form, $form_state); // TODO: Change the autogenerated stub
+  }
+
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state)
+  {
+    $kleEMne = $form_state->getValue(FordelingskomponentHelper::KLE_EMNE);
+    if (!preg_match('/' . FordelingskomponentHelper::KLE_EMNE_PATTERN . '/', $kleEMne)) {
+      $form_state->setErrorByName(FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne]));
+    }
+
+    $handling_facet = $form_state->getValue(FordelingskomponentHelper::HANDLING_FACET);
+    if (!empty($handling_facet) && !preg_match('/' . FordelingskomponentHelper::HANDLING_FACET_PATTERN . '/', $handling_facet)) {
+      $form_state->setErrorByName(FordelingskomponentHelper::HANDLING_FACET,
+        $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $handling_facet]));
+    }
+
+    parent::validateConfigurationForm($form, $form_state);
+  }
+
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state)
+  {
+    parent::submitConfigurationForm($form, $form_state);
+
+    $this->configuration[FordelingskomponentHelper::KLE_EMNE] = $form_state->getValue(FordelingskomponentHelper::KLE_EMNE);
+    $this->configuration[FordelingskomponentHelper::HANDLING_FACET] = $form_state->getValue(FordelingskomponentHelper::HANDLING_FACET);
+    $this->configuration[static::ATTACHMENT_ELEMENT] = $form_state->getValue(static::ATTACHMENT_ELEMENT);
+
+  }
+
+  public function postSave(WebformSubmissionInterface $webform_submission, $update = true)
+  {
+    // Run only when submission is completed.
+    if (!$webform_submission->isCompleted()) {
+      return;
+    }
+
+    $this->helper->createJob($webform_submission, $this->configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @phpstan-return void
+   */
+  public function postDelete(WebformSubmissionInterface $webform_submission) {
+    $this->helper->deleteMessages([$webform_submission]);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @phpstan-return void
+   */
+  public function postPurge(array $webform_submissions) {
+    $this->helper->deleteMessages($webform_submissions);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary()
+  {
+    $kleEmne = $this->configuration[FordelingskomponentHelper::KLE_EMNE];
+    $handlingFacet = $this->configuration[FordelingskomponentHelper::HANDLING_FACET];
+
+    $build = [
+      'info' => [
+        '#prefix' => '
', + '#suffix' => '
', + '#markup' => $this->t('KLE-emne: %kle_emne; Handling-facet: %handling_facet', + [ + '%kle_emne' => $kleEmne, + '%handling_facet' => $handlingFacet, + ]), + ], + ]; + + if ($kleEmne) { + $build['routing_info'] = [ + '#prefix' => '
', + '#suffix' => '
', + ] + + Link::createFromRoute( + $this->t('Show routing info'), + 'os2forms_fordelingskomponent.routing_info', [ + 'webform' => $this->getWebform()->id(), + 'handler' => $this->getHandlerId(), + ] + )->toRenderable(); + } + + return $build; + } + + /** + * Get attachment elements. + * + * @phpstan-return array + */ + private function getAttachmentElements(): array { + $elements = $this->getWebform()->getElementsDecodedAndFlattened(); + + $elementTypes = [ + 'webform_entity_print_attachment:pdf', + 'os2forms_attachment', + ]; + $elements = array_filter( + $elements, + static function (array $element) use ($elementTypes) { + return in_array($element['#type'], $elementTypes, TRUE); + } + ); + + return array_map(static function (array $element) { + return $element['#title']; + }, $elements); + } + +} From 9c6d96922187f4b63662e8781f83f05bfe50fb82 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 12 Nov 2025 15:21:56 +0100 Subject: [PATCH 02/62] Added GitHub Action workflows --- .editorconfig | 27 +++ .env | 2 + .github/workflows/changelog.yaml | 29 +++ .github/workflows/composer.yaml | 80 +++++++ .github/workflows/markdown.yaml | 44 ++++ .github/workflows/php.yaml | 59 ++++++ .github/workflows/yaml.yaml | 41 ++++ .markdownlint.jsonc | 22 ++ .markdownlintignore | 12 ++ .phpcs.xml.dist | 31 +++ CHANGELOG.md | 10 + Taskfile.yml | 4 +- compose.override.yaml | 4 + compose.yaml | 26 ++- composer.json | 5 +- .../webform.webform.os2forms_fdk_example.yml | 196 +++++++++--------- ...orms_fordelingskomponent_examples.info.yml | 8 +- os2forms_fordelingskomponent.info.yml | 10 +- os2forms_fordelingskomponent.routing.yml | 15 +- os2forms_fordelingskomponent.services.yml | 6 +- ...rdelingskomponentRoutingInfoController.php | 11 +- src/Exception/Exception.php | 2 +- src/Form/SettingsForm.php | 15 +- src/Helper/FordelingskomponentHelper.php | 61 +++--- src/Helper/WebformHelperSF2900.php | 15 +- src/Model/Attachment.php | 6 +- .../WebformHandler/WebformHandlerSF2900.php | 50 +++-- 27 files changed, 596 insertions(+), 195 deletions(-) create mode 100644 .editorconfig create mode 100644 .env create mode 100644 .github/workflows/changelog.yaml create mode 100644 .github/workflows/composer.yaml create mode 100644 .github/workflows/markdown.yaml create mode 100644 .github/workflows/php.yaml create mode 100644 .github/workflows/yaml.yaml create mode 100644 .markdownlint.jsonc create mode 100644 .markdownlintignore create mode 100644 .phpcs.xml.dist create mode 100644 CHANGELOG.md create mode 100644 compose.override.yaml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..96c12d6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +# This file is copied from config/drupal-module/.editorconfig in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +# EditorConfig is awesome: https://editorconfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = LF +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,css,scss}] +indent_size = 2 + +[*.{yml,yaml}] +indent_size = 2 + +[config/sync/**/*.{yml,yaml}] +indent_size = 2 + +[*.{php,install,module,theme}] +indent_size = 2 diff --git a/.env b/.env new file mode 100644 index 0000000..20ab01d --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +COMPOSE_PROJECT_NAME=os2forms_fordelingskomponent +ITKDEV_TEMPLATE=drupal-module diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml new file mode 100644 index 0000000..483da6e --- /dev/null +++ b/.github/workflows/changelog.yaml @@ -0,0 +1,29 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/changelog.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Changelog +### +### Checks that changelog has been updated + +name: Changelog + +on: + pull_request: + +jobs: + changelog: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Git fetch + run: git fetch + + - name: Check that changelog has been updated. + run: git diff --exit-code origin/${{ github.base_ref }} -- CHANGELOG.md && exit 1 || exit 0 diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml new file mode 100644 index 0000000..6c3a30c --- /dev/null +++ b/.github/workflows/composer.yaml @@ -0,0 +1,80 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/composer.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Composer +### +### Validates composer.json and checks that it's normalized. +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. +### 2. [ergebnis/composer-normalize](https://github.com/ergebnis/composer-normalize) +### is a dev requirement in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev ergebnis/composer-normalize +### ``` +### +### Normalize `composer.json` by running +### +### ``` shell +### docker compose run --rm phpfpm composer normalize +### ``` + +name: Composer + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + composer-validate: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer validate --strict + + composer-normalized: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm composer normalize --dry-run + + composer-audit: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer audit diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml new file mode 100644 index 0000000..f8bcf09 --- /dev/null +++ b/.github/workflows/markdown.yaml @@ -0,0 +1,44 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/markdown.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Markdown +### +### Lints Markdown files (`**/*.md`) in the project. +### +### [markdownlint-cli configuration +### files](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration), +### `.markdownlint.jsonc` and `.markdownlintignore`, control what is actually +### linted and how. +### +### #### Assumptions +### +### 1. A docker compose service named `markdownlint` for running `markdownlint` +### (from +### [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli)) +### exists. + +name: Markdown + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + markdown-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm markdownlint markdownlint '**/*.md' diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml new file mode 100644 index 0000000..95c22ed --- /dev/null +++ b/.github/workflows/php.yaml @@ -0,0 +1,59 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/drupal-module/php.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Drupal module PHP +### +### Checks that PHP code adheres to the [Drupal coding +### standards](https://www.drupal.org/docs/develop/standards). +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. +### 2. [drupal/coder](https://www.drupal.org/project/coder) is a dev requirement +### in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev drupal/coder +### ``` +### +### Clean up and check code by running +### +### ``` shell +### docker compose run --rm phpfpm vendor/bin/phpcbf +### docker compose run --rm phpfpm vendor/bin/phpcs +### ``` +### +### > [!NOTE] +### > The template adds `.phpcs.xml.dist` as [a configuration file for +### > PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#using-a-default-configuration-file) +### > and this makes it possible to override the actual configuration used in a +### > project by adding a more important configuration file, e.g. `.phpcs.xml`. + +name: PHP + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + coding-standards: + name: PHP - Check Coding Standards + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm vendor/bin/phpcs diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml new file mode 100644 index 0000000..8c60963 --- /dev/null +++ b/.github/workflows/yaml.yaml @@ -0,0 +1,41 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/yaml.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### YAML +### +### Validates YAML files. +### +### #### Assumptions +### +### 1. A docker compose service named `prettier` for running +### [Prettier](https://prettier.io/) exists. +### +### #### Symfony YAML +### +### Symfony's YAML config files use 4 spaces for indentation and single quotes. +### Therefore we use a [Prettier configuration +### file](https://prettier.io/docs/configuration), `.prettierrc.yaml`, to make +### Prettier format YAML files in the `config/` folder like Symfony expects. + +name: YAML + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + yaml-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm prettier '**/*.{yml,yaml}' --check diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..0253096 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,22 @@ +// This file is copied from config/markdown/.markdownlint.jsonc in https://github.com/itk-dev/devops_itkdev-docker. +// Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +// markdownlint-cli configuration file (cf. https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration) +{ + "default": true, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md + "line-length": { + "line_length": 120, + "code_blocks": false, + "tables": false + }, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md + "no-duplicate-heading": { + "siblings_only": true + }, + // https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md033.md + "no-inline-html": { + "allowed_elements": ["details", "summary"] + } +} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..d143ace --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,12 @@ +# This file is copied from config/markdown/.markdownlintignore in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +# https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#ignoring-files +vendor/ +node_modules/ +LICENSE.md +# Drupal +web/*.md +web/core/ +web/libraries/ +web/*/contrib/ diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist new file mode 100644 index 0000000..a97dd83 --- /dev/null +++ b/.phpcs.xml.dist @@ -0,0 +1,31 @@ + + + + + + The coding standard. + + . + + + node_modules + vendor + *.css + *.js + + + + + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ecc79af --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/OS2Forms/os2forms_fordelingskomponent diff --git a/Taskfile.yml b/Taskfile.yml index 8963edb..73629c6 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,6 +1,6 @@ # https://taskfile.dev -version: '3' +version: "3" tasks: compose: @@ -13,7 +13,7 @@ tasks: cmds: - task: compose vars: - TASK_ARGS: run --rm php composer {{.TASK_ARGS}} + TASK_ARGS: run --rm phpfpm composer {{.TASK_ARGS}} coding-standards:coding-standards:check: desc: Apply and check coding-standards diff --git a/compose.override.yaml b/compose.override.yaml new file mode 100644 index 0000000..e81a3bd --- /dev/null +++ b/compose.override.yaml @@ -0,0 +1,4 @@ +services: + phpfpm: + # OS2Forms is not yet ready for PHP 8.4 (!) + image: itkdev/php8.3-fpm:latest diff --git a/compose.yaml b/compose.yaml index 7798cd8..0b9f650 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,5 +1,27 @@ +# itk-version: 3.2.4 + services: - php: - image: itkdev/php8.3-fpm:latest + phpfpm: + image: itkdev/php8.4-fpm:latest + user: ${COMPOSE_USER:-deploy} volumes: - .:/app + + # Code checks tools + markdownlint: + image: itkdev/markdownlint + profiles: + - dev + volumes: + - ./:/md + + prettier: + # Prettier does not (yet, fcf. + # https://github.com/prettier/prettier/issues/15206) have an official + # docker image. + # https://hub.docker.com/r/jauderho/prettier is good candidate (cf. https://hub.docker.com/search?q=prettier&sort=updated_at&order=desc) + image: jauderho/prettier + profiles: + - dev + volumes: + - ./:/work diff --git a/composer.json b/composer.json index bd9e18c..34b61d4 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "os2forms/os2forms_fordelingskomponent", "description": "Fordelingskomponent integration for OS2Forms", + "license": "EUPL-1.2", "type": "drupal-module", "authors": [ { @@ -15,8 +16,8 @@ "os2web/os2web_key": "^1.0" }, "require-dev": { - "ergebnis/composer-normalize": "^2.49@dev", "drupal/coder": "9.x-dev", + "ergebnis/composer-normalize": "^2.49@dev", "mglaman/phpstan-drupal": "dev-main", "phpstan/extension-installer": "1.4.x-dev" }, @@ -30,8 +31,8 @@ "config": { "allow-plugins": { "cweagans/composer-patches": true, - "ergebnis/composer-normalize": true, "dealerdirect/phpcodesniffer-composer-installer": true, + "ergebnis/composer-normalize": true, "phpstan/extension-installer": true } } diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml index 7ba7643..47c9ac3 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml @@ -27,8 +27,8 @@ uid: 1 template: false archive: false id: os2forms_fdk_example -title: 'OS2Forms Fordelingskomponent example' -description: 'Simple example form with a Fordelingskomponent handler' +title: "OS2Forms Fordelingskomponent example" +description: "Simple example form with a Fordelingskomponent handler" category: Example elements: |- message: @@ -49,32 +49,32 @@ elements: |- '#title': 'Fordelingskomponent (PDF)' '#display_on': view '#filename': hat-og-briller.pdf -css: '' -javascript: '' +css: "" +javascript: "" settings: ajax: false ajax_scroll_top: form - ajax_progress_type: '' - ajax_effect: '' + ajax_progress_type: "" + ajax_effect: "" ajax_speed: null page: true - page_submit_path: '' - page_confirm_path: '' - page_theme_name: '' + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" form_title: both form_submit_once: false - form_open_message: '' - form_close_message: '' - form_exception_message: '' + form_open_message: "" + form_close_message: "" + form_exception_message: "" form_previous_submissions: true form_confidential: false - form_confidential_message: '' + form_confidential_message: "" form_disable_remote_addr: false form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: '' + form_prepopulate_source_entity_type: "" form_unsaved: false form_disable_back: false form_submit_back: false @@ -86,91 +86,91 @@ settings: form_details_toggle: false form_reset: false form_access_denied: default - form_access_denied_title: '' - form_access_denied_message: '' - form_access_denied_attributes: { } - form_file_limit: '' - form_attributes: { } - form_method: '' - form_action: '' + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" share: false share_node: false - share_theme_name: '' + share_theme_name: "" share_title: true - share_page_body_attributes: { } - submission_label: '' - submission_exception_message: '' - submission_locked_message: '' + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" submission_log: false - submission_excluded_elements: { } + submission_excluded_elements: {} submission_exclude_empty: false submission_exclude_empty_checkbox: false - submission_views: { } - submission_views_replace: { } - submission_user_columns: { } + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} submission_user_duplicate: false submission_access_denied: default - submission_access_denied_title: '' - submission_access_denied_message: '' - submission_access_denied_attributes: { } - previous_submission_message: '' - previous_submissions_message: '' + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" autofill: false - autofill_message: '' - autofill_excluded_elements: { } + autofill_message: "" + autofill_excluded_elements: {} wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false wizard_progress_link: false wizard_progress_states: false - wizard_start_label: '' + wizard_start_label: "" wizard_preview_link: false wizard_confirmation: true - wizard_confirmation_label: '' + wizard_confirmation_label: "" wizard_auto_forward: true wizard_auto_forward_hide_next_button: false wizard_keyboard: true - wizard_track: '' - wizard_prev_button_label: '' - wizard_next_button_label: '' + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" wizard_toggle: false - wizard_toggle_show_label: '' - wizard_toggle_hide_label: '' + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" wizard_page_type: container wizard_page_title_tag: h2 preview: 0 - preview_label: '' - preview_title: '' - preview_message: '' - preview_attributes: { } - preview_excluded_elements: { } + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} preview_exclude_empty: true preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false - draft_saved_message: '' - draft_loaded_message: '' - draft_pending_single_message: '' - draft_pending_multiple_message: '' + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" confirmation_type: message - confirmation_url: '' - confirmation_title: '' - confirmation_message: '' - confirmation_attributes: { } + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} confirmation_back: true - confirmation_back_label: '' - confirmation_back_attributes: { } + confirmation_back_label: "" + confirmation_back_attributes: {} confirmation_exclude_query: false confirmation_exclude_token: false confirmation_update: false limit_total: null limit_total_interval: null - limit_total_message: '' + limit_total_message: "" limit_total_unique: false limit_user: null limit_user_interval: null - limit_user_message: '' + limit_user_message: "" limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null @@ -190,56 +190,56 @@ access: roles: - anonymous - authenticated - users: { } - permissions: { } + users: {} + permissions: {} view_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} purge_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} view_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} administer: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} test: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} configuration: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} handlers: os2forms_fordelingskomponent_sf2900: id: os2forms_fordelingskomponent_sf2900 handler_id: os2forms_fordelingskomponent_sf2900 - label: 'Fordelingskomponent (sf2900)' - notes: '' + label: "Fordelingskomponent (sf2900)" + notes: "" status: true - conditions: { } + conditions: {} weight: 0 - settings: { } -variants: { } + settings: {} +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml b/modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml index 84b5eff..9a964bb 100644 --- a/modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml +++ b/modules/os2forms_fordelingskomponent_examples/os2forms_fordelingskomponent_examples.info.yml @@ -1,8 +1,8 @@ -name: 'OS2Forms Fordelingskomponent examples' +name: "OS2Forms Fordelingskomponent examples" type: module -description: 'Example forms for OS2Forms Fordelingskomponent.' -package: 'OS2Forms' +description: "Example forms for OS2Forms Fordelingskomponent." +package: "OS2Forms" core_version_requirement: ^9 || ^10 dependencies: - - 'os2forms_fordelingskomponent:os2forms_fordelingskomponent' + - "os2forms_fordelingskomponent:os2forms_fordelingskomponent" diff --git a/os2forms_fordelingskomponent.info.yml b/os2forms_fordelingskomponent.info.yml index ff73b5c..25916dc 100644 --- a/os2forms_fordelingskomponent.info.yml +++ b/os2forms_fordelingskomponent.info.yml @@ -1,13 +1,13 @@ -name: 'Fordelingskomponent' +name: "Fordelingskomponent" type: module -description: 'Fordelingsskomponent integration for OS2Forms' +description: "Fordelingsskomponent integration for OS2Forms" package: OS2Forms core_version_requirement: ^10 || ^11 dependencies: - advancedqueue:advancedqueue - - 'os2web_key:os2web_key' - - 'os2web_audit:os2web_audit' + - "os2web_key:os2web_key" + - "os2web_audit:os2web_audit" # Why don't we get this dependency implicitly from os2web_key? - - 'key:key' + - "key:key" configure: os2forms_fordelingskomponent.admin.settings diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index 0954dd5..f9541a9 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -1,20 +1,19 @@ os2forms_fordelingskomponent.settings: - path: '/admin/os2forms_fordelingskomponent/settings' + path: "/admin/os2forms_fordelingskomponent/settings" defaults: - _title: 'Fordelingskomponent settings' + _title: "Fordelingskomponent settings" _form: 'Drupal\os2forms_fordelingskomponent\Form\SettingsForm' requirements: - _permission: 'administer site configuration' + _permission: "administer site configuration" os2forms_fordelingskomponent.routing_info: - path: '/os2forms-fordelingskomponent/routing/{webform}/{handler}' + path: "/os2forms-fordelingskomponent/routing/{webform}/{handler}" defaults: - _title: 'Fordelingskomponent routing info' + _title: "Fordelingskomponent routing info" _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentRoutingInfoController' options: parameters: webform: - type: 'entity:webform' + type: "entity:webform" requirements: - _permission: 'administer site configuration' - + _permission: "administer site configuration" diff --git a/os2forms_fordelingskomponent.services.yml b/os2forms_fordelingskomponent.services.yml index f31e2be..9428453 100644 --- a/os2forms_fordelingskomponent.services.yml +++ b/os2forms_fordelingskomponent.services.yml @@ -4,11 +4,11 @@ services: logger.channel.os2forms_fordelingskomponent: parent: logger.channel_base - arguments: [ 'os2forms_fordelingskomponent' ] + arguments: ["os2forms_fordelingskomponent"] logger.channel.os2forms_fordelingskomponent_submission: - parent: logger.channel_base - arguments: [ 'webform_submission' ] + parent: logger.channel_base + arguments: ["webform_submission"] Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper: diff --git a/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php index e027faa..5f1251a 100644 --- a/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php +++ b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php @@ -5,8 +5,6 @@ namespace Drupal\os2forms_fordelingskomponent\Controller; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Serialization\Yaml; use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900; @@ -18,13 +16,12 @@ */ final class Os2formsFordelingskomponentRoutingInfoController extends ControllerBase { - private EntityStorageInterface $webformStorage; - + /** + * Constructor. + */ public function __construct( - EntityTypeManagerInterface $entityTypeManager, private readonly FordelingskomponentHelper $helper, ) { - $this->webformStorage = $entityTypeManager->getStorage('webform'); } /** @@ -46,7 +43,7 @@ public function __invoke(WebformInterface $webform, string $handler): array { $info = $this->helper->getRoutingInfo( routingMyndighed: $settings[FordelingskomponentHelper::ROUTING_MYNDIGHED], kleEmne: $settings[FordelingskomponentHelper::KLE_EMNE], - handlingFacet: $settings[FordelingskomponentHelper::HANDLING_FACET] ?: null, + handlingFacet: $settings[FordelingskomponentHelper::HANDLING_FACET] ?: NULL, ); return [ 'stuff' => [ diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php index 673c0f5..4721e2d 100644 --- a/src/Exception/Exception.php +++ b/src/Exception/Exception.php @@ -3,7 +3,7 @@ namespace Drupal\os2forms_fordelingskomponent\Exception; /** - * Exception. + * Base Exception for module. */ class Exception extends \Exception { diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index a70d0bf..b95b8f3 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -47,6 +47,9 @@ final class SettingsForm extends ConfigFormBase { */ private readonly EntityStorageInterface $queueStorage; + /** + * Constructor. + */ public function __construct( ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, @@ -57,7 +60,7 @@ public function __construct( } /** - * + * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( @@ -100,7 +103,7 @@ public function buildForm(array $form, FormStateInterface $form_state): array { } /** - * + * Build form section "SF2900". */ private function buildFormSf2900(array &$form, FormStateInterface $formState): void { $config = $this->config(self::CONFIG_NAME)->get(self::SECTION_SF2900) ?? []; @@ -171,7 +174,7 @@ private function buildFormSf2900(array &$form, FormStateInterface $formState): v } /** - * + * Build form section "Processing". */ private function buildFormProcessing(array &$form, FormStateInterface $formState): void { $config = $this->config(self::CONFIG_NAME)->get(self::SECTION_PROCESSING); @@ -208,17 +211,17 @@ private function buildFormProcessing(array &$form, FormStateInterface $formState */ public function validateForm(array &$form, FormStateInterface $form_state): void { $value = $form_state->getValue(self::SECTION_SF2900)[self::SENDER_ID] ?? ''; - if (!FordelingskomponentHelper::isValidCVR($value)) { + if (!FordelingskomponentHelper::isValidCvr($value)) { $form_state->setErrorByName('sf2900][sender_id', $this->t('The sender ID is not a valid CVR.')); } $value = $form_state->getValue(self::SECTION_SF2900)[self::ROUTING_MYNDIGHED] ?? ''; - if (!empty($value) && !FordelingskomponentHelper::isValidCVR($value)) { + if (!empty($value) && !FordelingskomponentHelper::isValidCvr($value)) { $form_state->setErrorByName('sf2900][routing_myndighed', $this->t('The routing myndighed is not a valid CVR.')); } $value = $form_state->getValue(self::SECTION_SF2900)[self::REGISTRERING_IT_SYSTEM] ?? ''; - if (!empty($value) && !FordelingskomponentHelper::isValidUUID($value)) { + if (!empty($value) && !FordelingskomponentHelper::isValidUuid($value)) { $form_state->setErrorByName('sf2900][registrering_it_system', $this->t('The registrering it system is not a valid UUID.')); } diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 9f6926e..b59c082 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -71,14 +71,14 @@ public function __construct( } /** - * + * Get module config. */ public function getModuleConfig(): ImmutableConfig { return $this->configFactory->get(SettingsForm::CONFIG_NAME); } /** - * + * Get routing info. */ public function getRoutingInfo( string $routingMyndighed, @@ -93,13 +93,12 @@ public function getRoutingInfo( } /** - * + * Send journalpost. */ public function sendJournalpost( WebformSubmissionInterface $submission, Attachment $attachment, array $configuration, - // @todo string $brugervendtNoegle, string $titel, string $beskrivelse, @@ -111,8 +110,10 @@ public function sendJournalpost( } /** + * Send dokument. + * * @return array - * [The response, The kombi post message]. + * [The response, The kombi post message]. * * @phpstan-return array */ @@ -120,7 +121,7 @@ public function sendDokument( WebformSubmissionInterface $submission, Attachment $attachment, array $configuration, - // @todo + // @todo Hvad er brugervendt nøgle? string $brugervendtNoegle, string $titel, string $beskrivelse, @@ -140,14 +141,14 @@ public function sendDokument( $registreringItSystem = $configuration[SettingsForm::REGISTRERING_IT_SYSTEM]; $routingMyndighed = $configuration[self::ROUTING_MYNDIGHED]; - $routingHandlingFacet = $configuration[self::HANDLING_FACET] ?: null; + $routingHandlingFacet = $configuration[self::HANDLING_FACET] ?: NULL; // @todo This is probably not correct! $routingModtagerAktoer = $configuration[SettingsForm::SENDER_ID]; $dokument = new DistributionDokumentType( iD: $id, kLEEmneForslag: $routingKLEEmne, - // handlingFacetForslag: null, + // handlingFacetForslag: null,. registrering: new DokumentRegistreringType( fraTidsPunkt: SF2900::formatDateTime($fraTidsPunkt), livscyklusKode: LivscyklusKodeType::VALUE_OPRETTET, @@ -155,7 +156,8 @@ public function sendDokument( relationListe: new RelationsListe( variantListe: new VariantListeType([ new VariantType( - // If we don't clone the “virking", the XML serializer adds IDs en references which SF2900 does not handle. + // If we don't clone the “virking", the XML serializer adds an + // ID and references which SF2900 does not handle. virkning: $this->clone($virkning), rolle: VariantRolleType::VALUE_VARIANT, indeks: '1', @@ -172,7 +174,7 @@ public function sendDokument( new TilstandListeType( tilstand: [ new TilstandType( - // @todo + // @todo Hvad er fremdrift? fremdrift: FremdriftType::VALUE_ENDELIGT, virkning: $this->clone($virkning), ), @@ -191,7 +193,7 @@ public function sendDokument( ), ]), // importTidspunkt: null, - // brugerRef: null, + // brugerRef: null,. ) ); @@ -234,19 +236,22 @@ public function log($level, $message, array $context = []): void { } /** - * + * Check if a string is a valid CVR. */ - public static function isValidCVR(string $value): bool { + public static function isValidCvr(string $value): bool { return (bool) preg_match('/^[0-9]{8}$/', $value); } /** - * + * Check if a string is a valid UUID. */ public static function isValidUuid(string $value): bool { return (bool) preg_match('/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/', $value); } + /** + * The SF2900 singleton. + */ private SF2900 $sf2900; /** @@ -268,7 +273,7 @@ private function sf2900(): SF2900 { 'sftp' => [ 'private_key' => $privateKey, // $options['sftp']['private_key_pass'] ?? '', - // 'private_key_password' => '', + // 'private_key_password' => '', 'username' => $options['sftp']['username'], ], ]; @@ -283,12 +288,14 @@ private function sf2900(): SF2900 { * Get handler settings combined with select module setting. * * @param \Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900 $handlerSettings + * The handler settings. * - * @return mixed + * @return array + * The combined configuration. */ public function getHandlerConfiguration( array $handlerSettings, - ) { + ): array { $settings = $handlerSettings; $options = $this->getModuleConfig()->get('sf2900'); @@ -301,8 +308,10 @@ public function getHandlerConfiguration( return $settings; } - private function buildVirkning(array $configuration): VirkningType - { + /** + * Build a Virkning object. + */ + private function buildVirkning(array $configuration): VirkningType { $aktoer = $configuration[SettingsForm::REGISTRERING_IT_SYSTEM]; return new VirkningType( @@ -310,20 +319,20 @@ private function buildVirkning(array $configuration): VirkningType aktoerType: AktoerTypeType::VALUE_IT_SYSTEM, // fraTidsPunkt: null, // tilTidspunkt: null, - // noteTekst: null, + // noteTekst: null,. ); } - /** - * Deep clone. + * Deep clone an object. * - * @param AbstractStructBase $object + * @param \WsdlToPhp\PackageBase\AbstractStructBase $object + * The object to clone. * - * @return AbstractStructBase + * @return \WsdlToPhp\PackageBase\AbstractStructBase + * The cloned object. */ - private function clone(AbstractStructBase $object): AbstractStructBase - { + private function clone(AbstractStructBase $object): AbstractStructBase { return unserialize(serialize($object)); } diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index d680d90..c6581f5 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -75,8 +75,7 @@ public function __construct( * @phpstan-param array $handlerSettings * @phpstan-param array $submissionData */ - public function afsend(WebformSubmissionInterface $submission, array $handlerSettings, array $submissionData = []): array - { + public function afsend(WebformSubmissionInterface $submission, array $handlerSettings, array $submissionData = []): array { $submissionData = $submissionData + $submission->getData(); $configuration = $this->helper->getHandlerConfiguration($handlerSettings); $attachment = $this->getAttachment($submission, $handlerSettings); @@ -123,7 +122,6 @@ protected function getAttachment(WebformSubmissionInterface $submission, array $ ); } - /** * Load webform submission by id. */ @@ -138,7 +136,7 @@ private function loadQueue(): ?QueueInterface { $processingSettings = $this->helper->getModuleConfig()->get(SettingsForm::SECTION_PROCESSING); /** @var \Drupal\advancedqueue\Entity\QueueInterface $queue */ - $queue = $this->queueStorage->load($processingSettings['queue'] ?? null); + $queue = $this->queueStorage->load($processingSettings['queue'] ?? NULL); return $queue; } @@ -183,13 +181,14 @@ public function createJob(WebformSubmissionInterface $webformSubmission, array $ 'handlerConfiguration' => $handlerConfiguration, ]); $queue = $this->loadQueue(); - if (null !== $queue) { + if (NULL !== $queue) { $queue->enqueueJob($job); $context['@queue'] = $queue->id(); $this->notice('Job for afsend added to the queue @queue.', $context + [ - 'operation' => 'Fordelingskomponent afsend queued', - ]); - } else { + 'operation' => 'Fordelingskomponent afsend queued', + ]); + } + else { $this->processJob($job); } diff --git a/src/Model/Attachment.php b/src/Model/Attachment.php index c753ad8..d55b1e3 100644 --- a/src/Model/Attachment.php +++ b/src/Model/Attachment.php @@ -5,8 +5,7 @@ /** * The Document class. */ -final class Attachment -{ +final class Attachment { public const MIME_TYPE_PDF = 'application/pdf'; /** @@ -22,8 +21,7 @@ public function __construct( /** * Check if this document is a PDF. */ - public function isPdf(): bool - { + public function isPdf(): bool { return static::MIME_TYPE_PDF === $this->mimeType; } diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 263d01c..40e1ce7 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -24,31 +24,37 @@ * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_REQUIRED, * ) */ -final class WebformHandlerSF2900 extends WebformHandlerBase -{ +final class WebformHandlerSF2900 extends WebformHandlerBase { use StringTranslationTrait; public const string ID = 'os2forms_fordelingskomponent_sf2900'; public const ATTACHMENT_ELEMENT = 'attachment_element'; + /** + * The webform helper. + */ private readonly WebformHelperSF2900 $helper; - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) - { + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->helper = $container->get(WebformHelperSF2900::class); return $instance; } - public function buildConfigurationForm(array $form, FormStateInterface $form_state) - { + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form[FordelingskomponentHelper::KLE_EMNE] = [ '#title' => $this->t('KLE-emne'), '#type' => 'textfield', '#default_value' => $this->configuration[FordelingskomponentHelper::KLE_EMNE], - '#required' => true, + '#required' => TRUE, '#attributes' => [ 'pattern' => FordelingskomponentHelper::KLE_EMNE_PATTERN, ], @@ -72,11 +78,14 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#options' => $availableElements, ]; - return parent::buildConfigurationForm($form, $form_state); // TODO: Change the autogenerated stub + // @todo Change the autogenerated stub + return parent::buildConfigurationForm($form, $form_state); } - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) - { + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { $kleEMne = $form_state->getValue(FordelingskomponentHelper::KLE_EMNE); if (!preg_match('/' . FordelingskomponentHelper::KLE_EMNE_PATTERN . '/', $kleEMne)) { $form_state->setErrorByName(FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne])); @@ -91,8 +100,10 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form parent::validateConfigurationForm($form, $form_state); } - public function submitConfigurationForm(array &$form, FormStateInterface $form_state) - { + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); $this->configuration[FordelingskomponentHelper::KLE_EMNE] = $form_state->getValue(FordelingskomponentHelper::KLE_EMNE); @@ -101,8 +112,10 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s } - public function postSave(WebformSubmissionInterface $webform_submission, $update = true) - { + /** + * {@inheritdoc} + */ + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { // Run only when submission is completed. if (!$webform_submission->isCompleted()) { return; @@ -132,8 +145,7 @@ public function postPurge(array $webform_submissions) { /** * {@inheritdoc} */ - public function getSummary() - { + public function getSummary() { $kleEmne = $this->configuration[FordelingskomponentHelper::KLE_EMNE]; $handlingFacet = $this->configuration[FordelingskomponentHelper::HANDLING_FACET]; @@ -151,9 +163,9 @@ public function getSummary() if ($kleEmne) { $build['routing_info'] = [ - '#prefix' => '
', - '#suffix' => '
', - ] + '#prefix' => '
', + '#suffix' => '
', + ] + Link::createFromRoute( $this->t('Show routing info'), 'os2forms_fordelingskomponent.routing_info', [ From 8459288a6c97fda7367c96c4d644f585f971ca7b Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 12 Nov 2025 16:20:50 +0100 Subject: [PATCH 03/62] Set up code analysis --- .github/workflows/project.yaml | 30 +++++ composer.json | 12 +- rector.php | 21 ++++ scripts/.env | 2 + scripts/base | 118 ++++++++++++++++++ scripts/code-analysis | 13 ++ scripts/compose.yaml | 23 ++++ scripts/rector | 13 ++ src/Form/SettingsForm.php | 6 +- src/Model/Attachment.php | 10 +- .../JobType/FordelingskomponentSF2900.php | 12 +- .../WebformHandler/WebformHandlerSF2900.php | 14 +-- 12 files changed, 246 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/project.yaml create mode 100644 rector.php create mode 100644 scripts/.env create mode 100644 scripts/base create mode 100755 scripts/code-analysis create mode 100644 scripts/compose.yaml create mode 100755 scripts/rector diff --git a/.github/workflows/project.yaml b/.github/workflows/project.yaml new file mode 100644 index 0000000..da5ae3d --- /dev/null +++ b/.github/workflows/project.yaml @@ -0,0 +1,30 @@ +name: Project + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + code-analysis: + name: PHP - Code analysis + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - run: | + ./scripts/code-analysis + + rector: + name: PHP - Rector + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - run: | + ./scripts/rector diff --git a/composer.json b/composer.json index 34b61d4..bf0acff 100644 --- a/composer.json +++ b/composer.json @@ -10,16 +10,17 @@ } ], "require": { + "drupal/webform": "^6.1", "drush/drush": "^12 || ^13", "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten", - "os2web/os2web_audit": "^1.1", + "os2web/os2web_audit": "^1.2", "os2web/os2web_key": "^1.0" }, "require-dev": { - "drupal/coder": "9.x-dev", - "ergebnis/composer-normalize": "^2.49@dev", - "mglaman/phpstan-drupal": "dev-main", - "phpstan/extension-installer": "1.4.x-dev" + "drupal/coder": "^8.3", + "ergebnis/composer-normalize": "^2.49", + "mglaman/phpstan-drupal": "^2.0", + "phpstan/extension-installer": "^1.4" }, "repositories": [ { @@ -28,6 +29,7 @@ } ], "minimum-stability": "dev", + "prefer-stable": true, "config": { "allow-plugins": { "cweagans/composer-patches": true, diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..44fe530 --- /dev/null +++ b/rector.php @@ -0,0 +1,21 @@ +withPaths([ + __DIR__ . '/src', + // __DIR__ . '/tests', + ]) + ->withSets([ + Drupal10SetList::DRUPAL_10, + ]) + ->withPhpSets(php83: TRUE) + ->withTypeCoverageLevel(0); diff --git a/scripts/.env b/scripts/.env new file mode 100644 index 0000000..4ce8811 --- /dev/null +++ b/scripts/.env @@ -0,0 +1,2 @@ +COMPOSE_PROJECT_NAME=drupal-module +MODULE_NAME=os2forms_fordelingskomponent diff --git a/scripts/base b/scripts/base new file mode 100644 index 0000000..5454107 --- /dev/null +++ b/scripts/base @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +set -o errexit -o errtrace -o noclobber -o nounset -o pipefail +IFS=$'\n\t' + +execute_name=execute + +usage() { + (cat >&2 </dev/null); then + (cat >&2 <&2 <get('config.factory'), @@ -87,6 +88,7 @@ protected function getEditableConfigNames(): array { /** * {@inheritdoc} */ + #[\Override] public function buildForm(array $form, FormStateInterface $form_state): array { $config = $this->config(self::CONFIG_NAME); @@ -191,7 +193,7 @@ private function buildFormProcessing(array &$form, FormStateInterface $formState : $this->t("Optional queue for fordelingskomponent jobs. If no queue is specified, all fordelingskomponent jobs are run immediately. The queue must be run via Drupal's cron or via drush advancedqueue:queue:process @queue (in a cron job).", [ '@queue' => $defaultValue, - ':queue_url' => '/admin/config/system/queues/jobs/' . urlencode($defaultValue), + ':queue_url' => '/admin/config/system/queues/jobs/' . urlencode((string) $defaultValue), ]); $form[self::SECTION_PROCESSING][self::QUEUE] = [ '#type' => 'select', @@ -209,6 +211,7 @@ private function buildFormProcessing(array &$form, FormStateInterface $formState /** * {@inheritdoc} */ + #[\Override] public function validateForm(array &$form, FormStateInterface $form_state): void { $value = $form_state->getValue(self::SECTION_SF2900)[self::SENDER_ID] ?? ''; if (!FordelingskomponentHelper::isValidCvr($value)) { @@ -231,6 +234,7 @@ public function validateForm(array &$form, FormStateInterface $form_state): void /** * {@inheritdoc} */ + #[\Override] public function submitForm(array &$form, FormStateInterface $form_state): void { $this->config(self::CONFIG_NAME) ->set(self::TEST_MODE, $form_state->getValue(self::TEST_MODE)) diff --git a/src/Model/Attachment.php b/src/Model/Attachment.php index d55b1e3..0dc66c2 100644 --- a/src/Model/Attachment.php +++ b/src/Model/Attachment.php @@ -5,16 +5,16 @@ /** * The Document class. */ -final class Attachment { - public const MIME_TYPE_PDF = 'application/pdf'; +final readonly class Attachment { + public const string MIME_TYPE_PDF = 'application/pdf'; /** * Constructor. */ public function __construct( - readonly public string $contents, - readonly public string $mimeType, - readonly public string $filename, + public string $contents, + public string $mimeType, + public string $filename, ) { } diff --git a/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php b/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php index d45efed..6e872c7 100644 --- a/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php +++ b/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php @@ -20,12 +20,6 @@ * ) */ final class FordelingskomponentSF2900 extends JobTypeBase implements ContainerFactoryPluginInterface { - /** - * The webform helper. - * - * @var \Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900 - */ - private WebformHelperSF2900 $helper; /** * {@inheritdoc} @@ -50,10 +44,12 @@ public function __construct( array $configuration, $plugin_id, $plugin_definition, - WebformHelperSF2900 $helper, + /** + * The webform helper. + */ + private readonly WebformHelperSF2900 $helper, ) { parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->helper = $helper; } /** diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 40e1ce7..1d3c2c1 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -29,7 +29,7 @@ final class WebformHandlerSF2900 extends WebformHandlerBase { public const string ID = 'os2forms_fordelingskomponent_sf2900'; - public const ATTACHMENT_ELEMENT = 'attachment_element'; + public const string ATTACHMENT_ELEMENT = 'attachment_element'; /** * The webform helper. @@ -87,12 +87,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta */ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { $kleEMne = $form_state->getValue(FordelingskomponentHelper::KLE_EMNE); - if (!preg_match('/' . FordelingskomponentHelper::KLE_EMNE_PATTERN . '/', $kleEMne)) { + if (!preg_match('/' . FordelingskomponentHelper::KLE_EMNE_PATTERN . '/', (string) $kleEMne)) { $form_state->setErrorByName(FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne])); } $handling_facet = $form_state->getValue(FordelingskomponentHelper::HANDLING_FACET); - if (!empty($handling_facet) && !preg_match('/' . FordelingskomponentHelper::HANDLING_FACET_PATTERN . '/', $handling_facet)) { + if (!empty($handling_facet) && !preg_match('/' . FordelingskomponentHelper::HANDLING_FACET_PATTERN . '/', (string) $handling_facet)) { $form_state->setErrorByName(FordelingskomponentHelper::HANDLING_FACET, $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $handling_facet])); } @@ -192,14 +192,10 @@ private function getAttachmentElements(): array { ]; $elements = array_filter( $elements, - static function (array $element) use ($elementTypes) { - return in_array($element['#type'], $elementTypes, TRUE); - } + static fn(array $element) => in_array($element['#type'], $elementTypes, TRUE) ); - return array_map(static function (array $element) { - return $element['#title']; - }, $elements); + return array_map(static fn(array $element) => $element['#title'], $elements); } } From 21b2ca10a0c0816122e8d5dd41083f8ce1a854f7 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 13 Nov 2025 12:04:26 +0100 Subject: [PATCH 04/62] Updated example forms --- .../README.md | 21 +- .../webform.webform.os2forms_fdk_example.yml | 245 --------------- ...bform.webform.os2forms_fdk_example_000.yml | 283 ++++++++++++++++++ ...bform.webform.os2forms_fdk_example_001.yml | 283 ++++++++++++++++++ ...rmsFordelingskomponentExamplesCommands.php | 68 +++++ src/Helper/WebformHelperSF2900.php | 5 + 6 files changed, 658 insertions(+), 247 deletions(-) delete mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml create mode 100644 modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php diff --git a/modules/os2forms_fordelingskomponent_examples/README.md b/modules/os2forms_fordelingskomponent_examples/README.md index 62a4fb5..2c15ba6 100644 --- a/modules/os2forms_fordelingskomponent_examples/README.md +++ b/modules/os2forms_fordelingskomponent_examples/README.md @@ -4,8 +4,25 @@ Example forms for OS2Forms Fordelingskomponent ## Installation -```sh +``` shell drush pm:enable os2forms_fordelingskomponent_examples ``` -Go to `/admin/structure/webform?category=Example` to see the example forms. +Go to `/admin/structure/webform?category=Fordelingskomponent` to see the example forms. + +## Updating the examples + +Run + +``` shell +drush os2forms_fordelingskomponent_examples:export-examples +``` + +to update all example forms. + +Test the newly exported config by reinstalling the `os2forms_fordelingskomponent_examples` module + +``` shell +drush pm:uninstall os2forms_fordelingskomponent_examples +drush pm:install os2forms_fordelingskomponent_examples +``` diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml deleted file mode 100644 index 47c9ac3..0000000 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example.yml +++ /dev/null @@ -1,245 +0,0 @@ -langcode: da -status: open -dependencies: - module: - - os2forms_fordelingskomponent - - webform_encrypt - - webform_revisions - enforced: - module: - - os2forms_fordelingskomponent_examples -third_party_settings: - webform_encrypt: - element: - message: - encrypt: true - encrypt_profile: webform - recipient_cpr: - encrypt: true - encrypt_profile: webform - digital_post_content_pdf: - encrypt: true - encrypt_profile: webform -weight: 0 -open: null -close: null -uid: 1 -template: false -archive: false -id: os2forms_fdk_example -title: "OS2Forms Fordelingskomponent example" -description: "Simple example form with a Fordelingskomponent handler" -category: Example -elements: |- - message: - '#type': textarea - '#title': Message - '#required': true - '#default_value': |- - [current-date:long] - - [random:hash:sha512] - recipient_cpr: - '#type': textfield - '#title': 'Recipient cpr' - '#required': true - '#default_value': '1705880000' - digital_post_content_pdf: - '#type': 'webform_entity_print_attachment:pdf' - '#title': 'Fordelingskomponent (PDF)' - '#display_on': view - '#filename': hat-og-briller.pdf -css: "" -javascript: "" -settings: - ajax: false - ajax_scroll_top: form - ajax_progress_type: "" - ajax_effect: "" - ajax_speed: null - page: true - page_submit_path: "" - page_confirm_path: "" - page_theme_name: "" - form_title: both - form_submit_once: false - form_open_message: "" - form_close_message: "" - form_exception_message: "" - form_previous_submissions: true - form_confidential: false - form_confidential_message: "" - form_disable_remote_addr: false - form_convert_anonymous: false - form_prepopulate: false - form_prepopulate_source_entity: false - form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: "" - form_unsaved: false - form_disable_back: false - form_submit_back: false - form_disable_autocomplete: false - form_novalidate: false - form_disable_inline_errors: false - form_required: false - form_autofocus: false - form_details_toggle: false - form_reset: false - form_access_denied: default - form_access_denied_title: "" - form_access_denied_message: "" - form_access_denied_attributes: {} - form_file_limit: "" - form_attributes: {} - form_method: "" - form_action: "" - share: false - share_node: false - share_theme_name: "" - share_title: true - share_page_body_attributes: {} - submission_label: "" - submission_exception_message: "" - submission_locked_message: "" - submission_log: false - submission_excluded_elements: {} - submission_exclude_empty: false - submission_exclude_empty_checkbox: false - submission_views: {} - submission_views_replace: {} - submission_user_columns: {} - submission_user_duplicate: false - submission_access_denied: default - submission_access_denied_title: "" - submission_access_denied_message: "" - submission_access_denied_attributes: {} - previous_submission_message: "" - previous_submissions_message: "" - autofill: false - autofill_message: "" - autofill_excluded_elements: {} - wizard_progress_bar: true - wizard_progress_pages: false - wizard_progress_percentage: false - wizard_progress_link: false - wizard_progress_states: false - wizard_start_label: "" - wizard_preview_link: false - wizard_confirmation: true - wizard_confirmation_label: "" - wizard_auto_forward: true - wizard_auto_forward_hide_next_button: false - wizard_keyboard: true - wizard_track: "" - wizard_prev_button_label: "" - wizard_next_button_label: "" - wizard_toggle: false - wizard_toggle_show_label: "" - wizard_toggle_hide_label: "" - wizard_page_type: container - wizard_page_title_tag: h2 - preview: 0 - preview_label: "" - preview_title: "" - preview_message: "" - preview_attributes: {} - preview_excluded_elements: {} - preview_exclude_empty: true - preview_exclude_empty_checkbox: false - draft: none - draft_multiple: false - draft_auto_save: false - draft_saved_message: "" - draft_loaded_message: "" - draft_pending_single_message: "" - draft_pending_multiple_message: "" - confirmation_type: message - confirmation_url: "" - confirmation_title: "" - confirmation_message: "" - confirmation_attributes: {} - confirmation_back: true - confirmation_back_label: "" - confirmation_back_attributes: {} - confirmation_exclude_query: false - confirmation_exclude_token: false - confirmation_update: false - limit_total: null - limit_total_interval: null - limit_total_message: "" - limit_total_unique: false - limit_user: null - limit_user_interval: null - limit_user_message: "" - limit_user_unique: false - entity_limit_total: null - entity_limit_total_interval: null - entity_limit_user: null - entity_limit_user_interval: null - purge: all - purge_days: 30 - results_disabled: false - results_disabled_ignore: false - results_customize: false - token_view: false - token_update: false - token_delete: false - serial_disabled: false -access: - create: - roles: - - anonymous - - authenticated - users: {} - permissions: {} - view_any: - roles: {} - users: {} - permissions: {} - update_any: - roles: {} - users: {} - permissions: {} - delete_any: - roles: {} - users: {} - permissions: {} - purge_any: - roles: {} - users: {} - permissions: {} - view_own: - roles: {} - users: {} - permissions: {} - update_own: - roles: {} - users: {} - permissions: {} - delete_own: - roles: {} - users: {} - permissions: {} - administer: - roles: {} - users: {} - permissions: {} - test: - roles: {} - users: {} - permissions: {} - configuration: - roles: {} - users: {} - permissions: {} -handlers: - os2forms_fordelingskomponent_sf2900: - id: os2forms_fordelingskomponent_sf2900 - handler_id: os2forms_fordelingskomponent_sf2900 - label: "Fordelingskomponent (sf2900)" - notes: "" - status: true - conditions: {} - weight: 0 - settings: {} -variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml new file mode 100644 index 0000000..505b7f2 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml @@ -0,0 +1,283 @@ +langcode: da +status: open +dependencies: + module: + - os2forms + - os2forms_fordelingskomponent + - webform_encrypt + - webform_entity_print + - webform_revisions + enforced: + module: + - os2forms_fordelingskomponent_examples +third_party_settings: + webform_encrypt: + element: + message: + encrypt: true + encrypt_profile: webform + afsend_content_pdf: + encrypt: true + encrypt_profile: webform + os2forms: + os2forms_email_handler: + enabled: 0 + email_recipients: '' + os2forms_nemid: + session_type: '' + webform_type: '' + nemlogin_auto_redirect: 0 + os2forms_nemlogin_openid_connect: + authentication_settings: + user_claim: '' + element_key: '' + error_message: '' + os2forms_nemid_address_protection: + nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour + nemlogin_hide_message: '' + os2forms_rest_api: + allowed_users: null + os2forms_sync: + publish: 0 + os2forms_webform_submission_log: + emails: '' + webform_entity_print: + template: + header: '' + footer: '' + css: '' + os2form_header: '' + os2form_colophon: '' + os2form_footer: '' + export_types: + pdf: + enabled: true + link_text: '' + link_attributes: { } + word_docx: + enabled: false + link_text: '' + link_attributes: { } +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2forms_fdk_example_000 +title: 'OS2Forms Fordelingskomponent example' +description: '

Simple example form with a Fordelingskomponent handler

' +categories: + - Example + - Fordelingskomponent +elements: |- + message: + '#type': textarea + '#title': Message + '#required': true + '#default_value': |- + [current-date:long] + + [random:hash:sha512] + afsend_content_pdf: + '#type': 'webform_entity_print_attachment:pdf' + '#title': 'Fordelingskomponent (PDF)' + '#display_on': view + '#filename': hat-og-briller.pdf +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: '' + ajax_effect: '' + ajax_speed: null + page: true + page_submit_path: '' + page_confirm_path: '' + page_theme_name: '' + form_title: both + form_submit_once: false + form_open_message: '' + form_close_message: '' + form_exception_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + form_attributes: { } + form_method: '' + form_action: '' + share: false + share_node: false + share_theme_name: '' + share_title: true + share_page_body_attributes: { } + submission_label: '' + submission_exception_message: '' + submission_locked_message: '' + submission_log: false + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: '' + wizard_prev_button_label: '' + wizard_next_button_label: '' + wizard_toggle: false + wizard_toggle_show_label: '' + wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + draft_pending_single_message: '' + draft_pending_multiple_message: '' + confirmation_type: message + confirmation_url: '' + confirmation_title: '' + confirmation_message: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + os2forms_fordelingskomponent_sf2900: + id: os2forms_fordelingskomponent_sf2900 + handler_id: os2forms_fordelingskomponent_sf2900 + label: 'Fordelingskomponent (sf2900)' + notes: '' + status: true + conditions: { } + weight: 0 + settings: + kle_emne: 01.01.01 + handling_facet: '' + attachment_element: digital_post_content_pdf +variants: { } diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml new file mode 100644 index 0000000..b47f1a9 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml @@ -0,0 +1,283 @@ +langcode: da +status: open +dependencies: + module: + - os2forms + - os2forms_fordelingskomponent + - webform_encrypt + - webform_entity_print + - webform_revisions + enforced: + module: + - os2forms_fordelingskomponent_examples +third_party_settings: + webform_encrypt: + element: + message: + encrypt: true + encrypt_profile: webform + afsend_content_pdf: + encrypt: true + encrypt_profile: webform + os2forms: + os2forms_email_handler: + enabled: 0 + email_recipients: '' + os2forms_nemid: + session_type: '' + webform_type: '' + nemlogin_auto_redirect: 0 + os2forms_nemlogin_openid_connect: + authentication_settings: + user_claim: '' + element_key: '' + error_message: '' + os2forms_nemid_address_protection: + nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour + nemlogin_hide_message: '' + os2forms_rest_api: + allowed_users: null + os2forms_sync: + publish: 0 + os2forms_webform_submission_log: + emails: '' + webform_entity_print: + template: + header: '' + footer: '' + css: '' + os2form_header: '' + os2form_colophon: '' + os2form_footer: '' + export_types: + pdf: + enabled: true + link_text: '' + link_attributes: { } + word_docx: + enabled: false + link_text: '' + link_attributes: { } +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2forms_fdk_example_001 +title: 'OS2Forms Fordelingskomponent example' +description: '

Simple example form with a Fordelingskomponent handler

' +categories: + - Example + - Fordelingskomponent +elements: |- + message: + '#type': textarea + '#title': Message + '#required': true + '#default_value': |- + [current-date:long] + + [random:hash:sha512] + afsend_content_pdf: + '#type': 'webform_entity_print_attachment:pdf' + '#title': 'Fordelingskomponent (PDF)' + '#display_on': view + '#filename': hat-og-briller.pdf +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: '' + ajax_effect: '' + ajax_speed: null + page: true + page_submit_path: '' + page_confirm_path: '' + page_theme_name: '' + form_title: both + form_submit_once: false + form_open_message: '' + form_close_message: '' + form_exception_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + form_attributes: { } + form_method: '' + form_action: '' + share: false + share_node: false + share_theme_name: '' + share_title: true + share_page_body_attributes: { } + submission_label: '' + submission_exception_message: '' + submission_locked_message: '' + submission_log: false + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: '' + wizard_prev_button_label: '' + wizard_next_button_label: '' + wizard_toggle: false + wizard_toggle_show_label: '' + wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + draft_pending_single_message: '' + draft_pending_multiple_message: '' + confirmation_type: message + confirmation_url: '' + confirmation_title: '' + confirmation_message: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + os2forms_fordelingskomponent_sf2900: + id: os2forms_fordelingskomponent_sf2900 + handler_id: os2forms_fordelingskomponent_sf2900 + label: 'Fordelingskomponent (sf2900)' + notes: '' + status: true + conditions: { } + weight: 0 + settings: + kle_emne: 01.01.01 + handling_facet: '' + attachment_element: digital_post_content_pdf +variants: { } diff --git a/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php b/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php new file mode 100644 index 0000000..4d8f850 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php @@ -0,0 +1,68 @@ +configManager->getConfigFactory(); + $configNames = array_values( + array_filter( + $configFactory->listAll(), + static fn (string $name): bool => (bool) preg_match('/^webform\.webform\.os2forms_fdk_/', $name) + ) + ); + + $moduleDir = $this->moduleExtensionList->getPath('os2forms_fordelingskomponent_examples'); + $targetDir = $moduleDir . '/config/install'; + + foreach ($configNames as $name) { + $targetName = $targetDir . '/' . $name . '.yml'; + + $this->io()->section($name); + $config = $configFactory->getEditable($name); + foreach (static::$configKeysToClear as $key) { + $this->io()->writeln(dt('Clearing key %key', ['%key' => $key])); + $config->clear($key); + } + // @todo (Hon) Can we use the config manager (or factory) to do this? + file_put_contents($targetName, Yaml::encode($config->get())); + $this->io()->success(dt('Config written to %file', ['%file' => $targetName])); + } + } + +} diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index c6581f5..decca27 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -244,4 +244,9 @@ public function processJob(Job $job): JobResult { } } + public function deleteMessages(array $array) + { + // @todo Clean up + } + } From 44faf4fadb63d7e7e5b806c0f372fb7e3fa99126 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 13 Nov 2025 15:13:44 +0100 Subject: [PATCH 05/62] Made code analysis work --- .phpcs.xml.dist | 4 +- ...bform.webform.os2forms_fdk_example_000.yml | 232 +++++++++--------- ...bform.webform.os2forms_fdk_example_001.yml | 232 +++++++++--------- ...rmsFordelingskomponentExamplesCommands.php | 3 + phpstan.neon | 4 +- scripts/base | 37 ++- src/Helper/WebformHelperSF2900.php | 6 +- 7 files changed, 267 insertions(+), 251 deletions(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index a97dd83..4c416fd 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -8,10 +8,8 @@ . - node_modules vendor - *.css - *.js + rector.php diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml index 505b7f2..5e884b6 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml @@ -22,42 +22,42 @@ third_party_settings: os2forms: os2forms_email_handler: enabled: 0 - email_recipients: '' + email_recipients: "" os2forms_nemid: - session_type: '' - webform_type: '' + session_type: "" + webform_type: "" nemlogin_auto_redirect: 0 os2forms_nemlogin_openid_connect: authentication_settings: - user_claim: '' - element_key: '' - error_message: '' + user_claim: "" + element_key: "" + error_message: "" os2forms_nemid_address_protection: nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour - nemlogin_hide_message: '' + nemlogin_hide_message: "" os2forms_rest_api: allowed_users: null os2forms_sync: publish: 0 os2forms_webform_submission_log: - emails: '' + emails: "" webform_entity_print: template: - header: '' - footer: '' - css: '' - os2form_header: '' - os2form_colophon: '' - os2form_footer: '' + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" export_types: pdf: enabled: true - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} word_docx: enabled: false - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} weight: 0 open: null close: null @@ -65,8 +65,8 @@ uid: 1 template: false archive: false id: os2forms_fdk_example_000 -title: 'OS2Forms Fordelingskomponent example' -description: '

Simple example form with a Fordelingskomponent handler

' +title: "OS2Forms Fordelingskomponent example" +description: "

Simple example form with a Fordelingskomponent handler

" categories: - Example - Fordelingskomponent @@ -84,32 +84,32 @@ elements: |- '#title': 'Fordelingskomponent (PDF)' '#display_on': view '#filename': hat-og-briller.pdf -css: '' -javascript: '' +css: "" +javascript: "" settings: ajax: false ajax_scroll_top: form - ajax_progress_type: '' - ajax_effect: '' + ajax_progress_type: "" + ajax_effect: "" ajax_speed: null page: true - page_submit_path: '' - page_confirm_path: '' - page_theme_name: '' + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" form_title: both form_submit_once: false - form_open_message: '' - form_close_message: '' - form_exception_message: '' + form_open_message: "" + form_close_message: "" + form_exception_message: "" form_previous_submissions: true form_confidential: false - form_confidential_message: '' + form_confidential_message: "" form_disable_remote_addr: false form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: '' + form_prepopulate_source_entity_type: "" form_unsaved: false form_disable_back: false form_submit_back: false @@ -121,91 +121,91 @@ settings: form_details_toggle: false form_reset: false form_access_denied: default - form_access_denied_title: '' - form_access_denied_message: '' - form_access_denied_attributes: { } - form_file_limit: '' - form_attributes: { } - form_method: '' - form_action: '' + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" share: false share_node: false - share_theme_name: '' + share_theme_name: "" share_title: true - share_page_body_attributes: { } - submission_label: '' - submission_exception_message: '' - submission_locked_message: '' + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" submission_log: false - submission_excluded_elements: { } + submission_excluded_elements: {} submission_exclude_empty: false submission_exclude_empty_checkbox: false - submission_views: { } - submission_views_replace: { } - submission_user_columns: { } + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} submission_user_duplicate: false submission_access_denied: default - submission_access_denied_title: '' - submission_access_denied_message: '' - submission_access_denied_attributes: { } - previous_submission_message: '' - previous_submissions_message: '' + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" autofill: false - autofill_message: '' - autofill_excluded_elements: { } + autofill_message: "" + autofill_excluded_elements: {} wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false wizard_progress_link: false wizard_progress_states: false - wizard_start_label: '' + wizard_start_label: "" wizard_preview_link: false wizard_confirmation: true - wizard_confirmation_label: '' + wizard_confirmation_label: "" wizard_auto_forward: true wizard_auto_forward_hide_next_button: false wizard_keyboard: true - wizard_track: '' - wizard_prev_button_label: '' - wizard_next_button_label: '' + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" wizard_toggle: false - wizard_toggle_show_label: '' - wizard_toggle_hide_label: '' + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" wizard_page_type: container wizard_page_title_tag: h2 preview: 0 - preview_label: '' - preview_title: '' - preview_message: '' - preview_attributes: { } - preview_excluded_elements: { } + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} preview_exclude_empty: true preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false - draft_saved_message: '' - draft_loaded_message: '' - draft_pending_single_message: '' - draft_pending_multiple_message: '' + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" confirmation_type: message - confirmation_url: '' - confirmation_title: '' - confirmation_message: '' - confirmation_attributes: { } + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} confirmation_back: true - confirmation_back_label: '' - confirmation_back_attributes: { } + confirmation_back_label: "" + confirmation_back_attributes: {} confirmation_exclude_query: false confirmation_exclude_token: false confirmation_update: false limit_total: null limit_total_interval: null - limit_total_message: '' + limit_total_message: "" limit_total_unique: false limit_user: null limit_user_interval: null - limit_user_message: '' + limit_user_message: "" limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null @@ -225,59 +225,59 @@ access: roles: - anonymous - authenticated - users: { } - permissions: { } + users: {} + permissions: {} view_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} purge_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} view_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} administer: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} test: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} configuration: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} handlers: os2forms_fordelingskomponent_sf2900: id: os2forms_fordelingskomponent_sf2900 handler_id: os2forms_fordelingskomponent_sf2900 - label: 'Fordelingskomponent (sf2900)' - notes: '' + label: "Fordelingskomponent (sf2900)" + notes: "" status: true - conditions: { } + conditions: {} weight: 0 settings: kle_emne: 01.01.01 - handling_facet: '' + handling_facet: "" attachment_element: digital_post_content_pdf -variants: { } +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml index b47f1a9..e511d21 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml @@ -22,42 +22,42 @@ third_party_settings: os2forms: os2forms_email_handler: enabled: 0 - email_recipients: '' + email_recipients: "" os2forms_nemid: - session_type: '' - webform_type: '' + session_type: "" + webform_type: "" nemlogin_auto_redirect: 0 os2forms_nemlogin_openid_connect: authentication_settings: - user_claim: '' - element_key: '' - error_message: '' + user_claim: "" + element_key: "" + error_message: "" os2forms_nemid_address_protection: nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour - nemlogin_hide_message: '' + nemlogin_hide_message: "" os2forms_rest_api: allowed_users: null os2forms_sync: publish: 0 os2forms_webform_submission_log: - emails: '' + emails: "" webform_entity_print: template: - header: '' - footer: '' - css: '' - os2form_header: '' - os2form_colophon: '' - os2form_footer: '' + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" export_types: pdf: enabled: true - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} word_docx: enabled: false - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} weight: 0 open: null close: null @@ -65,8 +65,8 @@ uid: 1 template: false archive: false id: os2forms_fdk_example_001 -title: 'OS2Forms Fordelingskomponent example' -description: '

Simple example form with a Fordelingskomponent handler

' +title: "OS2Forms Fordelingskomponent example" +description: "

Simple example form with a Fordelingskomponent handler

" categories: - Example - Fordelingskomponent @@ -84,32 +84,32 @@ elements: |- '#title': 'Fordelingskomponent (PDF)' '#display_on': view '#filename': hat-og-briller.pdf -css: '' -javascript: '' +css: "" +javascript: "" settings: ajax: false ajax_scroll_top: form - ajax_progress_type: '' - ajax_effect: '' + ajax_progress_type: "" + ajax_effect: "" ajax_speed: null page: true - page_submit_path: '' - page_confirm_path: '' - page_theme_name: '' + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" form_title: both form_submit_once: false - form_open_message: '' - form_close_message: '' - form_exception_message: '' + form_open_message: "" + form_close_message: "" + form_exception_message: "" form_previous_submissions: true form_confidential: false - form_confidential_message: '' + form_confidential_message: "" form_disable_remote_addr: false form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: '' + form_prepopulate_source_entity_type: "" form_unsaved: false form_disable_back: false form_submit_back: false @@ -121,91 +121,91 @@ settings: form_details_toggle: false form_reset: false form_access_denied: default - form_access_denied_title: '' - form_access_denied_message: '' - form_access_denied_attributes: { } - form_file_limit: '' - form_attributes: { } - form_method: '' - form_action: '' + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" share: false share_node: false - share_theme_name: '' + share_theme_name: "" share_title: true - share_page_body_attributes: { } - submission_label: '' - submission_exception_message: '' - submission_locked_message: '' + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" submission_log: false - submission_excluded_elements: { } + submission_excluded_elements: {} submission_exclude_empty: false submission_exclude_empty_checkbox: false - submission_views: { } - submission_views_replace: { } - submission_user_columns: { } + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} submission_user_duplicate: false submission_access_denied: default - submission_access_denied_title: '' - submission_access_denied_message: '' - submission_access_denied_attributes: { } - previous_submission_message: '' - previous_submissions_message: '' + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" autofill: false - autofill_message: '' - autofill_excluded_elements: { } + autofill_message: "" + autofill_excluded_elements: {} wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false wizard_progress_link: false wizard_progress_states: false - wizard_start_label: '' + wizard_start_label: "" wizard_preview_link: false wizard_confirmation: true - wizard_confirmation_label: '' + wizard_confirmation_label: "" wizard_auto_forward: true wizard_auto_forward_hide_next_button: false wizard_keyboard: true - wizard_track: '' - wizard_prev_button_label: '' - wizard_next_button_label: '' + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" wizard_toggle: false - wizard_toggle_show_label: '' - wizard_toggle_hide_label: '' + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" wizard_page_type: container wizard_page_title_tag: h2 preview: 0 - preview_label: '' - preview_title: '' - preview_message: '' - preview_attributes: { } - preview_excluded_elements: { } + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} preview_exclude_empty: true preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false - draft_saved_message: '' - draft_loaded_message: '' - draft_pending_single_message: '' - draft_pending_multiple_message: '' + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" confirmation_type: message - confirmation_url: '' - confirmation_title: '' - confirmation_message: '' - confirmation_attributes: { } + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} confirmation_back: true - confirmation_back_label: '' - confirmation_back_attributes: { } + confirmation_back_label: "" + confirmation_back_attributes: {} confirmation_exclude_query: false confirmation_exclude_token: false confirmation_update: false limit_total: null limit_total_interval: null - limit_total_message: '' + limit_total_message: "" limit_total_unique: false limit_user: null limit_user_interval: null - limit_user_message: '' + limit_user_message: "" limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null @@ -225,59 +225,59 @@ access: roles: - anonymous - authenticated - users: { } - permissions: { } + users: {} + permissions: {} view_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} purge_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} view_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} administer: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} test: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} configuration: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} handlers: os2forms_fordelingskomponent_sf2900: id: os2forms_fordelingskomponent_sf2900 handler_id: os2forms_fordelingskomponent_sf2900 - label: 'Fordelingskomponent (sf2900)' - notes: '' + label: "Fordelingskomponent (sf2900)" + notes: "" status: true - conditions: { } + conditions: {} weight: 0 settings: kle_emne: 01.01.01 - handling_facet: '' + handling_facet: "" attachment_element: digital_post_content_pdf -variants: { } +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php b/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php index 4d8f850..b819482 100644 --- a/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php +++ b/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php @@ -27,6 +27,9 @@ public function __construct( parent::__construct(); } + /** + * The config keys to clear. + */ private static array $configKeysToClear = [ 'uuid', '_core', diff --git a/phpstan.neon b/phpstan.neon index a41d209..c8c4284 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,4 +5,6 @@ parameters: customRulesetUsed: true reportUnmatchedIgnoredErrors: false excludePaths: - - vendor + - rector.php + # Ignore any vendor folder (https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files) + - vendor (?) diff --git a/scripts/base b/scripts/base index 5454107..9e3d1f3 100644 --- a/scripts/base +++ b/scripts/base @@ -77,22 +77,33 @@ setup() { compose down --remove-orphans compose pull compose up --detach --remove-orphans --wait + # Allow all plugins to run. + composer --no-plugins config allow-plugins true composer config minimum-stability dev - # https://getcomposer.org/doc/03-cli.md#modifying-repositories - composer config repositories.module '{ - "type": "path", - "url": "web/modules/contrib/os2forms_fordelingskomponent", - "options": { - "symlink": false, - "versions": { - "os2forms/os2forms_fordelingskomponent": "1.0-dev" - } - } - }' - - composer require os2forms/os2forms_fordelingskomponent --with-all-dependencies + # -------------------------------------------------------------------------------------------------------------------- + # We need to install dev requirements from our module, so we use + # wikimedia/composer-merge-plugin to merge our module composer.json into + # Drupal's. + # + # wikimedia/composer-merge-plugin is dead and has some issues + # https://github.com/wikimedia/composer-merge-plugin/issues/159 » + # https://github.com/wikimedia/composer-merge-plugin/pull/253 + # + # Install wikimedia/composer-merge-plugin without any configuration + composer require wikimedia/composer-merge-plugin --with-all-dependencies + # Patch to make COMPOSER_IGNORE_PLATFORM_REQS=1 have effect + shell sh -c 'cd vendor/wikimedia/composer-merge-plugin/ && curl https://patch-diff.githubusercontent.com/raw/wikimedia/composer-merge-plugin/pull/253.diff | patch --strip=1' + + # Configure wikimedia/composer-merge-plugin + composer --no-plugins config extra.merge-plugin.include "$module_path/composer.json" + # Use --json to actually set a boolean value (rather that a string value, e.g. "true") + composer --no-plugins config extra.merge-plugin.merge-extra --json true + composer --no-plugins config extra.merge-plugin.merge-extra-deep --json true + composer update + # Our module and its dev requirements are now installed. + # -------------------------------------------------------------------------------------------------------------------- # Reset Drupal installation compose exec drupal sh -c 'find . -name .ht.sqlite -ls -delete; rm web/sites/default/settings.php' || true diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index decca27..df50804 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -244,8 +244,10 @@ public function processJob(Job $job): JobResult { } } - public function deleteMessages(array $array) - { + /** + * Delete messages. + */ + public function deleteMessages(array $array) { // @todo Clean up } From e826f030b96bd7879dc515c07fc4ca764d5f3089 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 14 Jan 2026 13:00:38 +0100 Subject: [PATCH 06/62] Cleaned up --- ...bform.webform.os2forms_fdk_example_000.yml | 10 ++- ...bform.webform.os2forms_fdk_example_001.yml | 10 ++- os2forms_fordelingskomponent.routing.yml | 2 +- scripts/compose.yaml | 1 - ...rdelingskomponentRoutingInfoController.php | 3 +- src/Helper/FordelingskomponentHelper.php | 11 +++- src/Helper/WebformHelperSF2900.php | 20 ++++-- .../WebformHandler/WebformHandlerSF2900.php | 64 ++++++++++++++----- 8 files changed, 89 insertions(+), 32 deletions(-) diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml index 5e884b6..93119bc 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml @@ -277,7 +277,11 @@ handlers: conditions: {} weight: 0 settings: - kle_emne: 01.01.01 - handling_facet: "" - attachment_element: digital_post_content_pdf + sf2900: + kle_emne: 01.01.01 + handling_facet: G87 + attachment_element: afsend_content_pdf + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml index e511d21..990a08f 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml @@ -277,7 +277,11 @@ handlers: conditions: {} weight: 0 settings: - kle_emne: 01.01.01 - handling_facet: "" - attachment_element: digital_post_content_pdf + sf2900: + kle_emne: 01.01.01 + handling_facet: G87 + attachment_element: afsend_content_pdf + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" variants: {} diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index f9541a9..b9c3083 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -1,4 +1,4 @@ -os2forms_fordelingskomponent.settings: +os2forms_fordelingskomponent.admin.settings: path: "/admin/os2forms_fordelingskomponent/settings" defaults: _title: "Fordelingskomponent settings" diff --git a/scripts/compose.yaml b/scripts/compose.yaml index 99db3fe..cb08be5 100644 --- a/scripts/compose.yaml +++ b/scripts/compose.yaml @@ -19,5 +19,4 @@ services: # Let the module path, i.e. the mounted path, be known in the container MODULE_PATH: web/modules/contrib/$MODULE_NAME # https://getcomposer.org/doc/03-cli.md#composer-ignore-platform-req-or-composer-ignore-platform-reqs - COMPOSER_IGNORE_PLATFORM_REQ: ext-soap,ext-xsl COMPOSER_IGNORE_PLATFORM_REQS: 1 diff --git a/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php index 5f1251a..dfc5d03 100644 --- a/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php +++ b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php @@ -39,12 +39,13 @@ public function __invoke(WebformInterface $webform, string $handler): array { throw new NotFoundHttpException(); } - $settings = $this->helper->getHandlerConfiguration($handler->getConfiguration()); + $settings = $this->helper->getHandlerConfiguration($handler); $info = $this->helper->getRoutingInfo( routingMyndighed: $settings[FordelingskomponentHelper::ROUTING_MYNDIGHED], kleEmne: $settings[FordelingskomponentHelper::KLE_EMNE], handlingFacet: $settings[FordelingskomponentHelper::HANDLING_FACET] ?: NULL, ); + return [ 'stuff' => [ '#prefix' => '
',
diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php
index b59c082..0fd5e50 100644
--- a/src/Helper/FordelingskomponentHelper.php
+++ b/src/Helper/FordelingskomponentHelper.php
@@ -8,6 +8,7 @@
 use Drupal\key\KeyRepositoryInterface;
 use Drupal\os2forms_fordelingskomponent\Form\SettingsForm;
 use Drupal\os2forms_fordelingskomponent\Model\Attachment;
+use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900;
 use Drupal\os2web_audit\Service\Logger as AuditLogger;
 use Drupal\os2web_key\KeyHelper;
 use Drupal\webform\WebformSubmissionInterface;
@@ -51,6 +52,9 @@ final class FordelingskomponentHelper implements LoggerInterface {
   public const string HANDLING_FACET = 'handling_facet';
   public const string KLE_EMNE_PATTERN = '^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$';
   public const string HANDLING_FACET_PATTERN = '^[A-Z,Æ,Ø,Å][0-9][0-9]$';
+  public const string TITEL = 'titel';
+  public const string BESKRIVELSE = 'beskrivelse';
+  public const string BRUGERVENDT_NOEGLE = 'brugervendt_noegle';
 
   /**
    * Constructor.
@@ -143,7 +147,7 @@ public function sendDokument(
     $routingMyndighed = $configuration[self::ROUTING_MYNDIGHED];
     $routingHandlingFacet = $configuration[self::HANDLING_FACET] ?: NULL;
     // @todo This is probably not correct!
-    $routingModtagerAktoer = $configuration[SettingsForm::SENDER_ID];
+    $routingModtagerAktoer = NULL;
 
     $dokument = new DistributionDokumentType(
       iD: $id,
@@ -294,8 +298,11 @@ private function sf2900(): SF2900 {
    *   The combined configuration.
    */
   public function getHandlerConfiguration(
-    array $handlerSettings,
+    array|WebformHandlerSF2900 $handlerSettings,
   ): array {
+    if ($handlerSettings instanceof WebformHandlerSF2900) {
+      $handlerSettings = (array) $handlerSettings->getSetting(WebformHandlerSF2900::SECTION_SF2900);
+    }
     $settings = $handlerSettings;
     $options = $this->getModuleConfig()->get('sf2900');
 
diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php
index df50804..bd66aa3 100644
--- a/src/Helper/WebformHelperSF2900.php
+++ b/src/Helper/WebformHelperSF2900.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Logger\LoggerChannelInterface;
 use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\Utility\Token;
 use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException;
 use Drupal\os2forms_fordelingskomponent\Exception\SubmissionNotFoundException;
 use Drupal\os2forms_fordelingskomponent\Form\SettingsForm;
@@ -17,6 +18,7 @@
 use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900;
 use Drupal\webform\WebformSubmissionInterface;
 use Drupal\webform\WebformSubmissionStorageInterface;
+use Drupal\webform\WebformTokenManagerInterface;
 use Drupal\webform_attachment\Element\WebformAttachmentBase;
 use Psr\Log\LoggerInterface;
 use Psr\Log\LoggerTrait;
@@ -48,8 +50,10 @@ final class WebformHelperSF2900 implements LoggerInterface {
   public function __construct(
     EntityTypeManagerInterface $entityTypeManager,
     #[Autowire(service: 'plugin.manager.element_info')]
-    readonly protected ElementInfoManagerInterface $elementInfoManager,
+    private readonly ElementInfoManagerInterface $elementInfoManager,
     private readonly FordelingskomponentHelper $helper,
+    #[Autowire(service: 'webform.token_manager')]
+    private readonly WebformTokenManagerInterface $webformTokenManager,
     #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent')]
     private readonly LoggerChannelInterface $logger,
     #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent_submission')]
@@ -80,9 +84,9 @@ public function afsend(WebformSubmissionInterface $submission, array $handlerSet
     $configuration = $this->helper->getHandlerConfiguration($handlerSettings);
     $attachment = $this->getAttachment($submission, $handlerSettings);
 
-    $titel = __METHOD__;
-    $beskrivelse = __FILE__;
-    $brugervendtNoegle = __METHOD__;
+    $titel = $this->replaceTokens($configuration[FordelingskomponentHelper::TITEL] ?? '', $submission);
+    $beskrivelse = $this->replaceTokens($configuration[FordelingskomponentHelper::BESKRIVELSE] ?? '', $submission);
+    $brugervendtNoegle = $this->replaceTokens($configuration[FordelingskomponentHelper::BRUGERVENDT_NOEGLE] ?? '', $submission);
 
     return $this->helper->sendDokument(
       $submission,
@@ -251,4 +255,12 @@ public function deleteMessages(array $array) {
     // @todo Clean up
   }
 
+  /**
+   * Replace tokens.
+   */
+  private function replaceTokens(string $text, WebformSubmissionInterface $submission): string {
+    return $this->webformTokenManager->replace($text, $submission);
+  }
+
+
 }
diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php
index 1d3c2c1..02df01a 100644
--- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php
+++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
+use Drupal\Core\Lock\NullLockBackend;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper;
 use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900;
@@ -29,6 +30,7 @@ final class WebformHandlerSF2900 extends WebformHandlerBase {
 
   public const string ID = 'os2forms_fordelingskomponent_sf2900';
 
+  public const string SECTION_SF2900 = 'sf2900';
   public const string ATTACHMENT_ELEMENT = 'attachment_element';
 
   /**
@@ -50,50 +52,81 @@ public static function create(ContainerInterface $container, array $configuratio
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
-    $form[FordelingskomponentHelper::KLE_EMNE] = [
+    $form[static::SECTION_SF2900] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Fordelingskomponent'),
+      '#tree' => true,
+    ];
+
+    $configuration = $this->configuration[static::SECTION_SF2900] ?? NULL;
+
+    $form[static::SECTION_SF2900][FordelingskomponentHelper::KLE_EMNE] = [
       '#title' => $this->t('KLE-emne'),
       '#type' => 'textfield',
-      '#default_value' => $this->configuration[FordelingskomponentHelper::KLE_EMNE],
+      '#default_value' => $configuration[FordelingskomponentHelper::KLE_EMNE] ?? NULL,
       '#required' => TRUE,
       '#attributes' => [
         'pattern' => FordelingskomponentHelper::KLE_EMNE_PATTERN,
       ],
       '#description' => $this->t('KLE-emne (format: dd.dd.dd)'),
     ];
-    $form[FordelingskomponentHelper::HANDLING_FACET] = [
+
+    $form[static::SECTION_SF2900][FordelingskomponentHelper::HANDLING_FACET] = [
       '#title' => $this->t('Handling-facet'),
       '#type' => 'textfield',
-      '#default_value' => $this->configuration[FordelingskomponentHelper::HANDLING_FACET],
+      '#default_value' => $configuration[FordelingskomponentHelper::HANDLING_FACET] ?? NULL,
       '#attributes' => [
         'pattern' => FordelingskomponentHelper::HANDLING_FACET_PATTERN,
       ],
     ];
 
     $availableElements = $this->getAttachmentElements();
-    $form[static::ATTACHMENT_ELEMENT] = [
+    $form[static::SECTION_SF2900][static::ATTACHMENT_ELEMENT] = [
       '#type' => 'select',
       '#title' => $this->t('Element that contains the document to send'),
+      '#default_value' => $configuration[static::ATTACHMENT_ELEMENT] ?? NULL,
       '#required' => TRUE,
-      '#default_value' => $this->configuration[static::ATTACHMENT_ELEMENT] ?? NULL,
       '#options' => $availableElements,
     ];
 
-    // @todo Change the autogenerated stub
-    return parent::buildConfigurationForm($form, $form_state);
+    $form[static::SECTION_SF2900][FordelingskomponentHelper::BRUGERVENDT_NOEGLE] = [
+      '#title' => $this->t('Brugervendt nøgle'),
+      '#type' => 'textfield',
+      '#default_value' => $configuration[FordelingskomponentHelper::BRUGERVENDT_NOEGLE] ?? null,
+      '#required' => true,
+      '#description' => 'WHAT IS THIS?!',
+    ];
+
+    $form[static::SECTION_SF2900][FordelingskomponentHelper::TITEL] = [
+      '#title' => $this->t('Titel'),
+      '#type' => 'textfield',
+      '#default_value' => $configuration[FordelingskomponentHelper::TITEL] ?? null,
+      '#required' => true,
+    ];
+
+    $form[static::SECTION_SF2900][FordelingskomponentHelper::BESKRIVELSE] = [
+      '#title' => $this->t('Beskrivelse'),
+      '#type' => 'textarea',
+      '#default_value' => $configuration[FordelingskomponentHelper::BESKRIVELSE] ?? NULL,
+      '#required' => TRUE,
+    ];
+
+    return $this->setSettingsParents($form);
   }
 
   /**
    * {@inheritdoc}
    */
   public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
-    $kleEMne = $form_state->getValue(FordelingskomponentHelper::KLE_EMNE);
+    $values = $form_state->getValue(static::SECTION_SF2900);
+    $kleEMne = $values[FordelingskomponentHelper::KLE_EMNE];
     if (!preg_match('/' . FordelingskomponentHelper::KLE_EMNE_PATTERN . '/', (string) $kleEMne)) {
-      $form_state->setErrorByName(FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne]));
+      $form_state->setErrorByName(static::SECTION_SF2900.']['.FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne]));
     }
 
-    $handling_facet = $form_state->getValue(FordelingskomponentHelper::HANDLING_FACET);
+    $handling_facet = $values[FordelingskomponentHelper::HANDLING_FACET];
     if (!empty($handling_facet) && !preg_match('/' . FordelingskomponentHelper::HANDLING_FACET_PATTERN . '/', (string) $handling_facet)) {
-      $form_state->setErrorByName(FordelingskomponentHelper::HANDLING_FACET,
+      $form_state->setErrorByName(static::SECTION_SF2900.']['.FordelingskomponentHelper::HANDLING_FACET,
         $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $handling_facet]));
     }
 
@@ -106,10 +139,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
   public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
     parent::submitConfigurationForm($form, $form_state);
 
-    $this->configuration[FordelingskomponentHelper::KLE_EMNE] = $form_state->getValue(FordelingskomponentHelper::KLE_EMNE);
-    $this->configuration[FordelingskomponentHelper::HANDLING_FACET] = $form_state->getValue(FordelingskomponentHelper::HANDLING_FACET);
-    $this->configuration[static::ATTACHMENT_ELEMENT] = $form_state->getValue(static::ATTACHMENT_ELEMENT);
-
+    $this->configuration[static::SECTION_SF2900] = $form_state->getValue(static::SECTION_SF2900);
   }
 
   /**
@@ -121,7 +151,7 @@ public function postSave(WebformSubmissionInterface $webform_submission, $update
       return;
     }
 
-    $this->helper->createJob($webform_submission, $this->configuration);
+    $this->helper->createJob($webform_submission, $this->configuration[static::SECTION_SF2900]);
   }
 
   /**

From 9ee3b7a63a3ca70555d25f620f122a11f52e9644 Mon Sep 17 00:00:00 2001
From: Mikkel Ricky 
Date: Tue, 20 Jan 2026 13:38:30 +0100
Subject: [PATCH 07/62] Added some tests

---
 .gitignore                                    |   1 +
 Taskfile.yml                                  |   4 +
 compose.yaml                                  |   3 +-
 composer.json                                 |  10 +-
 .../webform.webform.os2_fdk_kp_anmoding.yml   | 392 ++++++++++++++++++
 phpunit.xml                                   |  27 ++
 src/Exception/InvalidXmlTemplateException.php |  10 +
 src/Exception/RuntimeException.php            |  10 +
 src/Helper/XmlHelper.php                      | 117 ++++++
 .../WebformHandler/WebformHandlerSF2900.php   |  15 +-
 tests/Unit/AbstractTestCase.php               |  12 +
 tests/Unit/Helper/XmlHelperTest.php           | 203 +++++++++
 tests/resources/xsd/Anmodning.xsd             | 180 ++++++++
 13 files changed, 974 insertions(+), 10 deletions(-)
 create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml
 create mode 100644 phpunit.xml
 create mode 100644 src/Exception/InvalidXmlTemplateException.php
 create mode 100644 src/Exception/RuntimeException.php
 create mode 100644 src/Helper/XmlHelper.php
 create mode 100644 tests/Unit/AbstractTestCase.php
 create mode 100644 tests/Unit/Helper/XmlHelperTest.php
 create mode 100644 tests/resources/xsd/Anmodning.xsd

diff --git a/.gitignore b/.gitignore
index 7579f74..722dce6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 vendor
 composer.lock
+*.cache
diff --git a/Taskfile.yml b/Taskfile.yml
index 73629c6..82f882d 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -29,3 +29,7 @@ tasks:
     cmds:
       - task --list
     silent: true
+
+  xdebug:test:
+    cmds:
+      - PHP_XDEBUG_MODE=debug PHP_XDEBUG_WITH_REQUEST=yes PHP_IDE_CONFIG=serverName=localhost docker compose run --env PHP_XDEBUG_MODE --env PHP_XDEBUG_WITH_REQUEST --env PHP_IDE_CONFIG --rm phpfpm vendor/bin/phpunit {{.CLI_ARGS}}
diff --git a/compose.yaml b/compose.yaml
index 0b9f650..dd414ae 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -5,7 +5,8 @@ services:
     image: itkdev/php8.4-fpm:latest
     user: ${COMPOSE_USER:-deploy}
     volumes:
-      - .:/app
+      - .:/app-os2forms_fordelingskomponent
+    working_dir: /app-os2forms_fordelingskomponent
 
   # Code checks tools
   markdownlint:
diff --git a/composer.json b/composer.json
index bf0acff..ff639fc 100644
--- a/composer.json
+++ b/composer.json
@@ -10,6 +10,7 @@
         }
     ],
     "require": {
+        "ext-dom": "*",
         "drupal/webform": "^6.1",
         "drush/drush": "^12 || ^13",
         "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten",
@@ -20,7 +21,8 @@
         "drupal/coder": "^8.3",
         "ergebnis/composer-normalize": "^2.49",
         "mglaman/phpstan-drupal": "^2.0",
-        "phpstan/extension-installer": "^1.4"
+        "phpstan/extension-installer": "^1.4",
+        "phpunit/phpunit": "^9.6"
     },
     "repositories": [
         {
@@ -30,6 +32,12 @@
     ],
     "minimum-stability": "dev",
     "prefer-stable": true,
+    "autoload-dev": {
+        "psr-4": {
+            "Drupal\\os2forms_fordelingskomponent\\": "src/",
+            "Drupal\\os2forms_fordelingskomponent\\Test\\": "tests/"
+        }
+    },
     "config": {
         "allow-plugins": {
             "cweagans/composer-patches": true,
diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml
new file mode 100644
index 0000000..77c8b5a
--- /dev/null
+++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml
@@ -0,0 +1,392 @@
+langcode: da
+status: open
+dependencies:
+  module:
+    - os2forms
+    - os2forms_fordelingskomponent
+    - webform_encrypt
+    - webform_entity_print
+    - webform_revisions
+  enforced:
+    module:
+      - os2forms_fordelingskomponent_examples
+third_party_settings:
+  webform_encrypt:
+    element:
+      message:
+        encrypt: true
+        encrypt_profile: webform
+      afsend_content_pdf:
+        encrypt: true
+        encrypt_profile: webform
+  os2forms:
+    os2forms_email_handler:
+      enabled: 0
+      email_recipients: ""
+    os2forms_nemid:
+      session_type: ""
+      webform_type: ""
+      nemlogin_auto_redirect: 0
+      os2forms_nemlogin_openid_connect:
+        authentication_settings:
+          user_claim: ""
+          element_key: ""
+          error_message: ""
+    os2forms_nemid_address_protection:
+      nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour
+      nemlogin_hide_message: ""
+    os2forms_rest_api:
+      allowed_users: null
+    os2forms_sync:
+      publish: 0
+    os2forms_webform_submission_log:
+      emails: ""
+  webform_entity_print:
+    template:
+      header: ""
+      footer: ""
+      css: ""
+      os2form_header: ""
+      os2form_colophon: ""
+      os2form_footer: ""
+    export_types:
+      pdf:
+        enabled: true
+        link_text: ""
+        link_attributes: {}
+      word_docx:
+        enabled: false
+        link_text: ""
+        link_attributes: {}
+weight: 0
+open: null
+close: null
+uid: 1
+template: false
+archive: false
+id: os2_fdk_kp_anmoding
+title: "OS2Forms Fordelingskomponent: Anmodning (KP)"
+description: ""
+categories:
+  - Example
+  - Fordelingskomponent
+css: ""
+javascript: ""
+settings:
+  ajax: false
+  ajax_scroll_top: form
+  ajax_progress_type: ""
+  ajax_effect: ""
+  ajax_speed: null
+  page: true
+  page_submit_path: ""
+  page_confirm_path: ""
+  page_theme_name: ""
+  form_title: both
+  form_submit_once: false
+  form_open_message: ""
+  form_close_message: ""
+  form_exception_message: ""
+  form_previous_submissions: true
+  form_confidential: false
+  form_confidential_message: ""
+  form_disable_remote_addr: false
+  form_convert_anonymous: false
+  form_prepopulate: false
+  form_prepopulate_source_entity: false
+  form_prepopulate_source_entity_required: false
+  form_prepopulate_source_entity_type: ""
+  form_unsaved: false
+  form_disable_back: false
+  form_submit_back: false
+  form_disable_autocomplete: false
+  form_novalidate: false
+  form_disable_inline_errors: false
+  form_required: false
+  form_autofocus: false
+  form_details_toggle: false
+  form_reset: false
+  form_access_denied: default
+  form_access_denied_title: ""
+  form_access_denied_message: ""
+  form_access_denied_attributes: {}
+  form_file_limit: ""
+  form_attributes: {}
+  form_method: ""
+  form_action: ""
+  share: false
+  share_node: false
+  share_theme_name: ""
+  share_title: true
+  share_page_body_attributes: {}
+  submission_label: ""
+  submission_exception_message: ""
+  submission_locked_message: ""
+  submission_log: false
+  submission_excluded_elements: {}
+  submission_exclude_empty: false
+  submission_exclude_empty_checkbox: false
+  submission_views: {}
+  submission_views_replace: {}
+  submission_user_columns: {}
+  submission_user_duplicate: false
+  submission_access_denied: default
+  submission_access_denied_title: ""
+  submission_access_denied_message: ""
+  submission_access_denied_attributes: {}
+  previous_submission_message: ""
+  previous_submissions_message: ""
+  autofill: false
+  autofill_message: ""
+  autofill_excluded_elements: {}
+  wizard_progress_bar: true
+  wizard_progress_pages: false
+  wizard_progress_percentage: false
+  wizard_progress_link: false
+  wizard_progress_states: false
+  wizard_start_label: ""
+  wizard_preview_link: false
+  wizard_confirmation: true
+  wizard_confirmation_label: ""
+  wizard_auto_forward: true
+  wizard_auto_forward_hide_next_button: false
+  wizard_keyboard: true
+  wizard_track: ""
+  wizard_prev_button_label: ""
+  wizard_next_button_label: ""
+  wizard_toggle: false
+  wizard_toggle_show_label: ""
+  wizard_toggle_hide_label: ""
+  wizard_page_type: container
+  wizard_page_title_tag: h2
+  preview: 0
+  preview_label: ""
+  preview_title: ""
+  preview_message: ""
+  preview_attributes: {}
+  preview_excluded_elements: {}
+  preview_exclude_empty: true
+  preview_exclude_empty_checkbox: false
+  draft: none
+  draft_multiple: false
+  draft_auto_save: false
+  draft_saved_message: ""
+  draft_loaded_message: ""
+  draft_pending_single_message: ""
+  draft_pending_multiple_message: ""
+  confirmation_type: message
+  confirmation_url: ""
+  confirmation_title: ""
+  confirmation_message: ""
+  confirmation_attributes: {}
+  confirmation_back: true
+  confirmation_back_label: ""
+  confirmation_back_attributes: {}
+  confirmation_exclude_query: false
+  confirmation_exclude_token: false
+  confirmation_update: false
+  limit_total: null
+  limit_total_interval: null
+  limit_total_message: ""
+  limit_total_unique: false
+  limit_user: null
+  limit_user_interval: null
+  limit_user_message: ""
+  limit_user_unique: false
+  entity_limit_total: null
+  entity_limit_total_interval: null
+  entity_limit_user: null
+  entity_limit_user_interval: null
+  purge: all
+  purge_days: 30
+  results_disabled: false
+  results_disabled_ignore: false
+  results_customize: false
+  token_view: false
+  token_update: false
+  token_delete: false
+  serial_disabled: false
+access:
+  create:
+    roles:
+      - anonymous
+      - authenticated
+    users: {}
+    permissions: {}
+  view_any:
+    roles: {}
+    users: {}
+    permissions: {}
+  update_any:
+    roles: {}
+    users: {}
+    permissions: {}
+  delete_any:
+    roles: {}
+    users: {}
+    permissions: {}
+  purge_any:
+    roles: {}
+    users: {}
+    permissions: {}
+  view_own:
+    roles: {}
+    users: {}
+    permissions: {}
+  update_own:
+    roles: {}
+    users: {}
+    permissions: {}
+  delete_own:
+    roles: {}
+    users: {}
+    permissions: {}
+  administer:
+    roles: {}
+    users: {}
+    permissions: {}
+  test:
+    roles: {}
+    users: {}
+    permissions: {}
+  configuration:
+    roles: {}
+    users: {}
+    permissions: {}
+handlers:
+  os2forms_fordelingskomponent_sf2900:
+    id: os2forms_fordelingskomponent_sf2900
+    handler_id: os2forms_fordelingskomponent_sf2900
+    label: "Fordelingskomponent (sf2900)"
+    notes: ""
+    status: true
+    conditions: {}
+    weight: 0
+    settings:
+      sf2900:
+        kle_emne: 01.01.01
+        handling_facet: G87
+        attachment_element: afsend_content_pdf
+        brugervendt_noegle: "What is “Brugervendt nøgle”?"
+        titel: "Your document"
+        beskrivelse: "This is a very important document"
+
+        xml_structure:
+          - name: Anmodning
+            elements:
+          - name: Header
+            elements:
+              - name:
+
+        xml_stuff:
+          Anmodning:
+            Header:
+              # Static values
+              Myndighed: 'urn:oio:cvr-nr:00000000'
+              ModtagetDato: '2006-05-04'
+              KLE: KLE
+            AnsoegerOplysninger:
+              Ansoeger:
+                # Submission values
+                Fornavn: '[webform_submission:values:fornavn]'
+                Efternavn: '[webform_submission:values:efternavn]'
+                Personnummer: '[webform_submission:values:personnummer]'
+            Sagstype:
+              AlmindeligtHelbredstillaeg: '[webform_submission:values:almindeligthelbredstillaeg]'
+            Underskriftsoplysninger:
+              Underskrift: '[webform_submission:values:underskrift]'
+              Underskriftsdato: '[webform_submission:completed:custom:Y-m-d]'
+
+        xml_template: |
+          
+          
+              
+ urn:oio:cvr-nr:00000000 + [webform_submission:completed:custom:Y-m-d] + KLE0 +
+ + + Fornavn0 + Efternavn0 + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2006-05-04 + +
+ + xml_template: | + + +
+ urn:oio:cvr-nr:{{ handlers.settings.cvr }} + {{ webform_submission.complete|date(Y-m-d) }} + {{ handlers.settings.kle }} +
+ + + {{ webform_submission.values.fornavn }} + {{ webform_submission.values.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ webform_submission.complete|date('Y-m-d') }} + +
+ + xsd_url: module://os2forms_fordelingskomponent_examples/resources/xsd/Anmodning.xsd + +variants: {} + +elements: |- + afsend_content_pdf: + '#type': 'entity_print_attachment:pdf' + '#title': 'Fordelingskomponent (PDF)' + '#display_on': view + '#filename': hat-og-briller.pdf + ansoeger_oplysninger: + '#type': fieldset + '#title': AnsoegerOplysninger + ansoeger: + '#type': fieldset + '#title': Ansøger + fornavn: + '#type': textfield + '#title': Fornavn + mellemnavn: + '#type': textfield + '#title': Mellemnavn + efternavn: + '#type': textfield + '#title': Efternavn + personnummer: + '#type': textfield + '#title': Personnummer + telefonnummer: + '#type': textfield + '#title': Telefonnummer + sagstype: + '#type': fieldset + '#title': Sagstype + almindeligt_helbredstillaeg: + '#type': select + '#title': Almindeligt helbredstillaeg + '#options': + Medicin: Medicin + Tandbehandling: Tandbehandling + Fodbehandling: Fodbehandling + Fysioterapi: Fysioterapi + Kiropraktik: Kiropraktik + Psykologhjaelp: Psykologhjaelp + Hoereappartbehandling: Hoereapparatbehandling diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..766495c --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,27 @@ + + + + + tests + + + + + + src + + + diff --git a/src/Exception/InvalidXmlTemplateException.php b/src/Exception/InvalidXmlTemplateException.php new file mode 100644 index 0000000..a366baf --- /dev/null +++ b/src/Exception/InvalidXmlTemplateException.php @@ -0,0 +1,10 @@ +checkXml($template); + return $this->twig->createTemplate($template)->render($context); + } + catch (\Throwable $exception) { + throw new InvalidXmlTemplateException($exception->getMessage(), $exception->getCode(), $exception); + } + } + + /** + * Validate XML using an XSD. + */ + public function validate(string $xml, string $xsdUrl): void { + $this->checkXml($xml); + + // https://www.php.net/manual/en/function.libxml-use-internal-errors.php + $useInternalErrors = libxml_use_internal_errors(TRUE); + + try { + $reader = new \XMLReader(); + $path = tempnam(sys_get_temp_dir(), 'os2forms_fordelingskomponent'); + file_put_contents($path, $xml); + $reader->open($path); + + $reader->setParserProperty(\XMLReader::VALIDATE, TRUE); + $reader->setSchema($xsdUrl); + + $errors = []; + + while ($reader->read()) { + if (!$reader->isValid()) { + $error = \libxml_get_last_error(); + if ($error instanceof \libXMLError) { + if (!str_contains($error->message, 'no DTD found')) { + $errors[] = $error; + } + } + } + } + + if ($errors) { + $message = $this->formatLibXmlErrors($errors); + + libxml_clear_errors(); + + throw new InvalidXmlTemplateException('Error validating XML:' . PHP_EOL . $message); + } + + } finally { + if (isset($path) && is_file($path)) { + unlink($path); + } + libxml_clear_errors(); + libxml_use_internal_errors($useInternalErrors); + } + } + + /** + * Check that XML is well-formed. + */ + private function checkXml(string $template): void { + // https://www.php.net/manual/en/function.libxml-use-internal-errors.php + $useInternalErrors = \libxml_use_internal_errors(TRUE); + try { + $doc = new \DomDocument(); + if (!$doc->loadXML($template)) { + $message = $this->formatLibXmlErrors(libxml_get_errors()); + + libxml_clear_errors(); + + throw new InvalidXmlTemplateException('Error loading XML:' . PHP_EOL . $message); + } + } finally { + libxml_use_internal_errors($useInternalErrors); + } + } + + /** + * Format a list of XML errors. + */ + private function formatLibXmlErrors(array $errors): string { + return implode( + PHP_EOL, + array_unique( + array_map( + static fn (\LibXMLError $error): string => sprintf('%d:%d: %s', $error->line, $error->column, $error->message), + $errors + ) + ) + ); + } + +} diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 02df01a..63488bc 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -4,7 +4,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; -use Drupal\Core\Lock\NullLockBackend; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; @@ -55,7 +54,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $form[static::SECTION_SF2900] = [ '#type' => 'fieldset', '#title' => $this->t('Fordelingskomponent'), - '#tree' => true, + '#tree' => TRUE, ]; $configuration = $this->configuration[static::SECTION_SF2900] ?? NULL; @@ -92,16 +91,16 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $form[static::SECTION_SF2900][FordelingskomponentHelper::BRUGERVENDT_NOEGLE] = [ '#title' => $this->t('Brugervendt nøgle'), '#type' => 'textfield', - '#default_value' => $configuration[FordelingskomponentHelper::BRUGERVENDT_NOEGLE] ?? null, - '#required' => true, + '#default_value' => $configuration[FordelingskomponentHelper::BRUGERVENDT_NOEGLE] ?? NULL, + '#required' => TRUE, '#description' => 'WHAT IS THIS?!', ]; $form[static::SECTION_SF2900][FordelingskomponentHelper::TITEL] = [ '#title' => $this->t('Titel'), '#type' => 'textfield', - '#default_value' => $configuration[FordelingskomponentHelper::TITEL] ?? null, - '#required' => true, + '#default_value' => $configuration[FordelingskomponentHelper::TITEL] ?? NULL, + '#required' => TRUE, ]; $form[static::SECTION_SF2900][FordelingskomponentHelper::BESKRIVELSE] = [ @@ -121,12 +120,12 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $values = $form_state->getValue(static::SECTION_SF2900); $kleEMne = $values[FordelingskomponentHelper::KLE_EMNE]; if (!preg_match('/' . FordelingskomponentHelper::KLE_EMNE_PATTERN . '/', (string) $kleEMne)) { - $form_state->setErrorByName(static::SECTION_SF2900.']['.FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne])); + $form_state->setErrorByName(static::SECTION_SF2900 . '][' . FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne])); } $handling_facet = $values[FordelingskomponentHelper::HANDLING_FACET]; if (!empty($handling_facet) && !preg_match('/' . FordelingskomponentHelper::HANDLING_FACET_PATTERN . '/', (string) $handling_facet)) { - $form_state->setErrorByName(static::SECTION_SF2900.']['.FordelingskomponentHelper::HANDLING_FACET, + $form_state->setErrorByName(static::SECTION_SF2900 . '][' . FordelingskomponentHelper::HANDLING_FACET, $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $handling_facet])); } diff --git a/tests/Unit/AbstractTestCase.php b/tests/Unit/AbstractTestCase.php new file mode 100644 index 0000000..45e8bdf --- /dev/null +++ b/tests/Unit/AbstractTestCase.php @@ -0,0 +1,12 @@ +helper = new XmlHelper(new Environment(new ArrayLoader())); + } + + /** + * @covers \Drupal\os2forms_fordelingskomponent\Helper\XmlHelper + * @dataProvider provideRenderData + */ + public function testRender( + string $template, + array $context, + string|InvalidXmlTemplateException $expected, + ) { + if ($expected instanceof InvalidXmlTemplateException) { + $this->expectException($expected::class); + } + + $actual = $this->helper->render($template, $context); + $this->assertXmlStringEqualsXmlString($expected, $actual); + } + + /** + * @covers \Drupal\os2forms_fordelingskomponent\Helper\XmlHelper + * @dataProvider provideValidateData + */ + public function testValidate( + string $template, + string $xsdUrl, + ?InvalidXmlTemplateException $expected = NULL, + ) { + if ($expected instanceof InvalidXmlTemplateException) { + $this->expectException($expected::class); + } + + $this->helper->validate($template, $xsdUrl); + $this->assertTrue(TRUE); + } + + /** + * Data provider. + */ + public static function provideRenderData(): iterable { + yield [ + 'This is not an XML document', + [], + new InvalidXmlTemplateException(), + ]; + + yield [ + <<<'XML' + + +
+ urn:oio:cvr-nr:{{ handlers.settings.cvr }} + {{ webform_submission.completed|date('Y-m-d') }} + {{ handlers.settings.kle }} +
+ + + {{ webform_submission.data.fornavn }} + {{ webform_submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ webform_submission.completed|date('Y-m-d') }} + +
+XML, + [ + 'handlers' => [ + 'settings' => [ + 'cvr' => '12345678', + 'kle' => '01.02.03', + ], + ], + 'webform_submission' => self::createWebformSubmission([ + 'completed' => (new \DateTimeImmutable('2001-01-01T00:00:00Z'))->getTimestamp(), + 'data' => [ + 'fornavn' => 'Anders', + 'efternavn' => 'And', + ], + ]), + ], + <<<'XML' + + +
+ urn:oio:cvr-nr:12345678 + 2001-01-01 + 01.02.03 +
+ + + Anders + And + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2001-01-01 + +
+XML, + ]; + } + + /** + * Create webform submission. + */ + private static function createWebformSubmission(array $values): array { + return $values; + } + + /** + * Data provider. + */ + public static function provideValidateData(): iterable { + yield [ + <<<'XML' + + +
+ urn:oio:cvr-nr:12345678 + 2001-01-01 + 01.02.03 +
+ + + Anders + And + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2001-01-01 + +
+XML, + 'file://' . realpath(__DIR__ . '/../../resources/xsd/Anmodning.xsd'), + ]; + + yield [ + <<<'XML' + + + + + Anders + And + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2001-01-01 + + +XML, + 'file://' . realpath(__DIR__ . '/../../resources/xsd/Anmodning.xsd'), + new InvalidXmlTemplateException(), + ]; + } + +} diff --git a/tests/resources/xsd/Anmodning.xsd b/tests/resources/xsd/Anmodning.xsd new file mode 100644 index 0000000..05b0c90 --- /dev/null +++ b/tests/resources/xsd/Anmodning.xsd @@ -0,0 +1,180 @@ + + + + + + Anmodning om refusion + + + + + + + + + + + + + + + + + - Overslag +- Faktura +- Bilag + + + + + + + + + + + + + + + Ved ansoegning med login + + + + + + + + + + + + + + Ved ansoegning uden login + + + + + + + + + + + + + + + + + + + + - Medicin +- Tandbehandling +- Fodbehandling +- Fysioterapi +- Kiropraktik +- Psykologhjaelp +- Hoereapparatbehandling + + + + + - Tandprotese +- Fodbehandling +- Briller + + + + + + + + + + + + + + + + + + + + + + + + + + + + Personnummer + + + + + + + + + Telefonnummer + + + + + + + + Angiver dokumenttype + + + + + + + + + + Angiver typen af almindeligt helbredstillaeg + + + + + + + + + + + + + + Angiver typen af udvidet helbredstillaeg + + + + + + + + + + Angiver generell max længde for strænger + + + + + + + + Myndighedstype + + + + + + From 384cb84e02204be3dab2e7203ba305988fce768c Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 6 Feb 2026 15:20:26 +0100 Subject: [PATCH 08/62] Cleaned up and improved XML helper --- .github/workflows/twig.yaml | 48 +++ .twig-cs-fixer.dist.php | 19 + Taskfile.yml | 111 +++++- composer.json | 6 +- .../webform.webform.os2_fdk_kp_anmoding.yml | 40 +- .../webform.webform.os2_fdk_kp_anmoding_2.yml | 347 ++++++++++++++++++ .../templates/os2_fdk_kp_anmoding.xml.twig | 22 ++ .../resources/xsd/Anmodning.xsd | 0 src/Helper/WebformHelperSF2900.php | 2 - src/Helper/XmlHelper.php | 59 ++- tests/Unit/Helper/XmlHelperTest.php | 176 ++------- .../Unit/Helper/XmlHelperTestDataProvider.php | 266 ++++++++++++++ 12 files changed, 905 insertions(+), 191 deletions(-) create mode 100644 .github/workflows/twig.yaml create mode 100644 .twig-cs-fixer.dist.php create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding_2.yml create mode 100644 modules/os2forms_fordelingskomponent_examples/resources/templates/os2_fdk_kp_anmoding.xml.twig rename {tests => modules/os2forms_fordelingskomponent_examples}/resources/xsd/Anmodning.xsd (100%) create mode 100644 tests/Unit/Helper/XmlHelperTestDataProvider.php diff --git a/.github/workflows/twig.yaml b/.github/workflows/twig.yaml new file mode 100644 index 0000000..9b0e343 --- /dev/null +++ b/.github/workflows/twig.yaml @@ -0,0 +1,48 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/twig.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Twig +### +### Validates Twig files +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. +### 2. [vincentlanglet/twig-cs-fixer](https://github.com/VincentLanglet/Twig-CS-Fixer) +### is a dev requirement in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev vincentlanglet/twig-cs-fixer +### ``` +### +### 3. A [Configuration +### file](https://github.com/VincentLanglet/Twig-CS-Fixer/blob/main/docs/configuration.md#configuration-file) +### in the root of the project defines which files to check and rules to use. + +name: Twig + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + twig-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + + - run: | + docker network create frontend + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint diff --git a/.twig-cs-fixer.dist.php b/.twig-cs-fixer.dist.php new file mode 100644 index 0000000..c4a03e4 --- /dev/null +++ b/.twig-cs-fixer.dist.php @@ -0,0 +1,19 @@ +in(__DIR__); +// … that are not ignored by VCS +$finder->ignoreVCSIgnored(true); + +$config = new TwigCsFixer\Config\Config(); +$config->setFinder($finder); + +// @see https://github.com/VincentLanglet/Twig-CS-Fixer/pull/134#issuecomment-1756924889 +$config->addTokenParser(new Drupal\Core\Template\TwigTransTokenParser()); + +return $config; diff --git a/Taskfile.yml b/Taskfile.yml index 82f882d..5df9781 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -15,21 +15,112 @@ tasks: vars: TASK_ARGS: run --rm phpfpm composer {{.TASK_ARGS}} - coding-standards:coding-standards:check: - desc: Apply and check coding-standards + coding-standards:apply: + desc: "Apply coding standards" cmds: - - task: composer - vars: - TASK_ARGS: coding-standards-apply - - task: composer - vars: - TASK_ARGS: coding-standards-check + # - task: coding-standards:javascript:apply + - task: coding-standards:markdown:apply + - task: coding-standards:php:apply + # - task: coding-standards:styles:apply + - task: coding-standards:twig:apply + - task: coding-standards:yaml:apply + silent: true + + coding-standards:check: + desc: "Apply coding standards" + cmds: + # - task: coding-standards:javascript:check + - task: coding-standards:markdown:check + - task: coding-standards:php:check + # - task: coding-standards:styles:check + - task: coding-standards:twig:check + - task: coding-standards:yaml:check + silent: true + + coding-standards:javascript:apply: + desc: "Apply coding standards for javascript" + cmds: + # Cf. .github/workflows/javascript.yaml + - docker compose run --rm prettier 'web/themes/custom/**/js/**/*.js' --write + + coding-standards:javascript:check: + desc: "Apply coding standards for javascript" + cmds: + - task: coding-standards:javascript:apply + # Cf. .github/workflows/javascript.yaml + - docker compose run --rm prettier 'web/themes/custom/**/js/**/*.js' --check + + coding-standards:markdown:apply: + desc: "Apply coding standards for Markdown" + cmds: + # Cf. .github/workflows/markdown.yaml + - docker compose run --rm markdownlint markdownlint '**/*.md' --fix + + coding-standards:markdown:check: + desc: "Apply and check coding standards for Markdown" + cmds: + - task: coding-standards:markdown:apply + # Cf. .github/workflows/markdown.yaml + - docker compose run --rm markdownlint markdownlint '**/*.md' + + coding-standards:php:apply: + desc: "Apply coding standards for PHP" + cmds: + # Cf. .github/workflows/php.yaml + - docker compose run --rm phpfpm vendor/bin/phpcbf + silent: true - default: + coding-standards:php:check: + desc: "Apply and check coding standards for PHP" cmds: - - task --list + - task: coding-standards:php:apply + # Cf. .github/workflows/php.yaml + - docker compose run --rm phpfpm vendor/bin/phpcs silent: true + coding-standards:styles:apply: + desc: "Apply coding standards for styles" + cmds: + # Cf. .github/workflows/styles.yaml + - docker compose run --rm prettier 'web/themes/custom/**/css/**/*.{css,scss}' --write + + coding-standards:styles:check: + desc: "Apply coding standards for styles" + cmds: + - task: coding-standards:styles:apply + # Cf. .github/workflows/styles.yaml + - docker compose run --rm prettier 'web/themes/custom/**/css/**/*.{css,scss}' --check + + coding-standards:twig:apply: + desc: "Apply coding standards for Twig" + cmds: + - docker compose run --rm phpfpm vendor/bin/twig-cs-fixer fix + silent: true + + coding-standards:twig:check: + desc: "Apply and check coding standards for Twig" + cmds: + - task: coding-standards:twig:apply + - docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint + silent: true + + coding-standards:yaml:apply: + desc: "Apply coding standards for YAML" + cmds: + # Cf. .github/workflows/yaml.yaml + - docker compose run --rm prettier '**/*.{yml,yaml}' --write + + coding-standards:yaml:check: + desc: "Apply coding standards for YAML" + cmds: + - task: coding-standards:yaml:apply + # Cf. .github/workflows/yaml.yaml + - docker compose run --rm prettier '**/*.{yml,yaml}' --check + + test: + cmds: + - docker compose run --env PHP_XDEBUG_MODE --env PHP_XDEBUG_WITH_REQUEST --env PHP_IDE_CONFIG --rm phpfpm vendor/bin/phpunit {{.CLI_ARGS}} + xdebug:test: cmds: - PHP_XDEBUG_MODE=debug PHP_XDEBUG_WITH_REQUEST=yes PHP_IDE_CONFIG=serverName=localhost docker compose run --env PHP_XDEBUG_MODE --env PHP_XDEBUG_WITH_REQUEST --env PHP_IDE_CONFIG --rm phpfpm vendor/bin/phpunit {{.CLI_ARGS}} diff --git a/composer.json b/composer.json index ff639fc..3ad68d4 100644 --- a/composer.json +++ b/composer.json @@ -15,14 +15,16 @@ "drush/drush": "^12 || ^13", "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten", "os2web/os2web_audit": "^1.2", - "os2web/os2web_key": "^1.0" + "os2web/os2web_key": "^1.0", + "drupal/system_stream_wrapper": "^2.1" }, "require-dev": { "drupal/coder": "^8.3", "ergebnis/composer-normalize": "^2.49", "mglaman/phpstan-drupal": "^2.0", "phpstan/extension-installer": "^1.4", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.6", + "vincentlanglet/twig-cs-fixer": "^3.13" }, "repositories": [ { diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml index 77c8b5a..5c1615d 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml @@ -282,44 +282,20 @@ handlers: Anmodning: Header: # Static values - Myndighed: 'urn:oio:cvr-nr:00000000' - ModtagetDato: '2006-05-04' + Myndighed: "urn:oio:cvr-nr:00000000" + ModtagetDato: "2006-05-04" KLE: KLE AnsoegerOplysninger: Ansoeger: # Submission values - Fornavn: '[webform_submission:values:fornavn]' - Efternavn: '[webform_submission:values:efternavn]' - Personnummer: '[webform_submission:values:personnummer]' + Fornavn: "[webform_submission:values:fornavn]" + Efternavn: "[webform_submission:values:efternavn]" + Personnummer: "[webform_submission:values:personnummer]" Sagstype: - AlmindeligtHelbredstillaeg: '[webform_submission:values:almindeligthelbredstillaeg]' + AlmindeligtHelbredstillaeg: "[webform_submission:values:almindeligthelbredstillaeg]" Underskriftsoplysninger: - Underskrift: '[webform_submission:values:underskrift]' - Underskriftsdato: '[webform_submission:completed:custom:Y-m-d]' - - xml_template: | - - -
- urn:oio:cvr-nr:00000000 - [webform_submission:completed:custom:Y-m-d] - KLE0 -
- - - Fornavn0 - Efternavn0 - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - 2006-05-04 - -
+ Underskrift: "[webform_submission:values:underskrift]" + Underskriftsdato: "[webform_submission:completed:custom:Y-m-d]" xml_template: | diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding_2.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding_2.yml new file mode 100644 index 0000000..bafad86 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding_2.yml @@ -0,0 +1,347 @@ +langcode: da +status: open +dependencies: + module: + - os2forms + - os2forms_fordelingskomponent + - webform_encrypt + - webform_entity_print + - webform_revisions + enforced: + module: + - os2forms_fordelingskomponent_examples +third_party_settings: + webform_encrypt: + element: + message: + encrypt: true + encrypt_profile: webform + afsend_content_pdf: + encrypt: true + encrypt_profile: webform + os2forms: + os2forms_email_handler: + enabled: 0 + email_recipients: "" + os2forms_nemid: + session_type: "" + webform_type: "" + nemlogin_auto_redirect: 0 + os2forms_nemlogin_openid_connect: + authentication_settings: + user_claim: "" + element_key: "" + error_message: "" + os2forms_nemid_address_protection: + nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour + nemlogin_hide_message: "" + os2forms_rest_api: + allowed_users: null + os2forms_sync: + publish: 0 + os2forms_webform_submission_log: + emails: "" + webform_entity_print: + template: + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" + export_types: + pdf: + enabled: true + link_text: "" + link_attributes: {} + word_docx: + enabled: false + link_text: "" + link_attributes: {} +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2_fdk_kp_anmoding_2 +title: "OS2Forms Fordelingskomponent: Anmodning (KP) – II" +description: "" +categories: + - Example + - Fordelingskomponent +css: "" +javascript: "" +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: "" + ajax_effect: "" + ajax_speed: null + page: true + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" + form_title: both + form_submit_once: false + form_open_message: "" + form_close_message: "" + form_exception_message: "" + form_previous_submissions: true + form_confidential: false + form_confidential_message: "" + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: "" + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" + share: false + share_node: false + share_theme_name: "" + share_title: true + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" + submission_log: false + submission_excluded_elements: {} + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" + autofill: false + autofill_message: "" + autofill_excluded_elements: {} + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: "" + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: "" + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" + wizard_toggle: false + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" + confirmation_type: message + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} + confirmation_back: true + confirmation_back_label: "" + confirmation_back_attributes: {} + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: "" + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: "" + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: {} + permissions: {} + view_any: + roles: {} + users: {} + permissions: {} + update_any: + roles: {} + users: {} + permissions: {} + delete_any: + roles: {} + users: {} + permissions: {} + purge_any: + roles: {} + users: {} + permissions: {} + view_own: + roles: {} + users: {} + permissions: {} + update_own: + roles: {} + users: {} + permissions: {} + delete_own: + roles: {} + users: {} + permissions: {} + administer: + roles: {} + users: {} + permissions: {} + test: + roles: {} + users: {} + permissions: {} + configuration: + roles: {} + users: {} + permissions: {} +handlers: + os2forms_fordelingskomponent_sf2900: + id: os2forms_fordelingskomponent_sf2900 + handler_id: os2forms_fordelingskomponent_sf2900 + label: "Fordelingskomponent (sf2900)" + notes: "" + status: true + conditions: {} + weight: 0 + settings: + sf2900: + kle_emne: 01.01.01 + handling_facet: G87 + attachment_element: afsend_content_pdf + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" + + xml_structure: + - name: Anmodning + elements: + - name: Header + elements: + - name: + + xml_stuff: + Anmodning: + Header: + # Static values + Myndighed: "urn:oio:cvr-nr:00000000" + ModtagetDato: "2006-05-04" + KLE: KLE + AnsoegerOplysninger: + Ansoeger: + # Submission values + Fornavn: "[webform_submission:values:fornavn]" + Efternavn: "[webform_submission:values:efternavn]" + Personnummer: "[webform_submission:values:personnummer]" + Sagstype: + AlmindeligtHelbredstillaeg: "[webform_submission:values:almindeligthelbredstillaeg]" + Underskriftsoplysninger: + Underskrift: "[webform_submission:values:underskrift]" + Underskriftsdato: "[webform_submission:completed:custom:Y-m-d]" + + xml_template: | + module://os2forms_fordelingskomponent_examples/resources/templates/os2_fdk_kp_anmoding.xml.twig + + xsd_url: module://os2forms_fordelingskomponent_examples/resources/xsd/Anmodning.xsd + +variants: {} + +elements: |- + afsend_content_pdf: + '#type': 'entity_print_attachment:pdf' + '#title': 'Fordelingskomponent (PDF)' + '#display_on': view + '#filename': hat-og-briller.pdf + ansoeger_oplysninger: + '#type': fieldset + '#title': AnsoegerOplysninger + ansoeger: + '#type': fieldset + '#title': Ansøger + fornavn: + '#type': textfield + '#title': Fornavn + mellemnavn: + '#type': textfield + '#title': Mellemnavn + efternavn: + '#type': textfield + '#title': Efternavn + personnummer: + '#type': textfield + '#title': Personnummer + telefonnummer: + '#type': textfield + '#title': Telefonnummer + sagstype: + '#type': fieldset + '#title': Sagstype + almindeligt_helbredstillaeg: + '#type': select + '#title': Almindeligt helbredstillaeg + '#options': + Medicin: Medicin + Tandbehandling: Tandbehandling + Fodbehandling: Fodbehandling + Fysioterapi: Fysioterapi + Kiropraktik: Kiropraktik + Psykologhjaelp: Psykologhjaelp + Hoereappartbehandling: Hoereapparatbehandling diff --git a/modules/os2forms_fordelingskomponent_examples/resources/templates/os2_fdk_kp_anmoding.xml.twig b/modules/os2forms_fordelingskomponent_examples/resources/templates/os2_fdk_kp_anmoding.xml.twig new file mode 100644 index 0000000..1c585f5 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/resources/templates/os2_fdk_kp_anmoding.xml.twig @@ -0,0 +1,22 @@ + + +
+ urn:oio:cvr-nr:{{ handlers.settings.cvr }} + {{ webform_submission.complete|date(Y - m - d) }} + {{ handlers.settings.kle }} +
+ + + {{ webform_submission.values.fornavn }} + {{ webform_submission.values.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ webform_submission.complete|date('Y-m-d') }} + +
diff --git a/tests/resources/xsd/Anmodning.xsd b/modules/os2forms_fordelingskomponent_examples/resources/xsd/Anmodning.xsd similarity index 100% rename from tests/resources/xsd/Anmodning.xsd rename to modules/os2forms_fordelingskomponent_examples/resources/xsd/Anmodning.xsd diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index bd66aa3..c523d29 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -9,7 +9,6 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\Render\ElementInfoManagerInterface; -use Drupal\Core\Utility\Token; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; use Drupal\os2forms_fordelingskomponent\Exception\SubmissionNotFoundException; use Drupal\os2forms_fordelingskomponent\Form\SettingsForm; @@ -262,5 +261,4 @@ private function replaceTokens(string $text, WebformSubmissionInterface $submiss return $this->webformTokenManager->replace($text, $submission); } - } diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index c13c68a..9de122c 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -4,6 +4,7 @@ use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; use Twig\Environment; +use Twig\TemplateWrapper; /** * XML helper. @@ -18,13 +19,52 @@ public function __construct( ) { } + /** + * Wrapper to enable strict variables on the Twig environment. + * + * To not break the Twig instance for others, we restore the strict variables + * state on the instance after running our code. + * + * Important: All code in this class that uses the Twig instance must be run + * with this wrapper. + */ + private function useTwig(callable $callback): mixed { + try { + $strictVariables = $this->twig->isStrictVariables(); + $this->twig->enableStrictVariables(); + return $callback(); + } finally { + if ($strictVariables) { + $this->twig->enableStrictVariables(); + } + else { + $this->twig->disableStrictVariables(); + } + } + } + /** * Render XML template. */ public function render(string $template, array $context): string { try { $this->checkXml($template); - return $this->twig->createTemplate($template)->render($context); + + return $this->useTwig( + fn () => $this->createTemplate($template)->render($context) + ); + } + catch (\Throwable $exception) { + throw new InvalidXmlTemplateException($exception->getMessage(), $exception->getCode(), $exception); + } + } + + /** + * Check that Twig template is valid, i.e. has no syntax errors. + */ + public function validateTemplate(string $template): void { + try { + $this->createTemplate($template); } catch (\Throwable $exception) { throw new InvalidXmlTemplateException($exception->getMessage(), $exception->getCode(), $exception); @@ -32,11 +72,15 @@ public function render(string $template, array $context): string { } /** - * Validate XML using an XSD. + * Check that XML is valid. Optionally validate using an XSD. */ - public function validate(string $xml, string $xsdUrl): void { + public function validateXml(string $xml, ?string $xsdUrl = NULL): void { $this->checkXml($xml); + if (NULL === $xsdUrl) { + return; + } + // https://www.php.net/manual/en/function.libxml-use-internal-errors.php $useInternalErrors = libxml_use_internal_errors(TRUE); @@ -99,6 +143,15 @@ private function checkXml(string $template): void { } } + /** + * Create a Twig template. + */ + private function createTemplate(string $template): TemplateWrapper { + return $this->useTwig( + fn() => $this->twig->createTemplate($template) + ); + } + /** * Format a list of XML errors. */ diff --git a/tests/Unit/Helper/XmlHelperTest.php b/tests/Unit/Helper/XmlHelperTest.php index 7f2fd70..7d5ffae 100644 --- a/tests/Unit/Helper/XmlHelperTest.php +++ b/tests/Unit/Helper/XmlHelperTest.php @@ -12,6 +12,11 @@ * Xml helper test case. */ class XmlHelperTest extends AbstractTestCase { + /** + * The Twig environment. + */ + private Environment $twig; + /** * The XML helper. */ @@ -22,12 +27,24 @@ class XmlHelperTest extends AbstractTestCase { */ protected function setUp(): void { // @todo Do this is the right way. - $this->helper = new XmlHelper(new Environment(new ArrayLoader())); + $this->twig = new Environment(new ArrayLoader()); + $this->helper = new XmlHelper($this->twig); + } + + /** + * @covers \Drupal\os2forms_fordelingskomponent\Helper\XmlHelper + */ + public function testStrictVariables(): void { + $this->assertFalse($this->twig->isStrictVariables()); + $this->helper->validateTemplate(''); + $this->helper->validateXml(''); + $this->helper->render('', []); + $this->assertFalse($this->twig->isStrictVariables()); } /** * @covers \Drupal\os2forms_fordelingskomponent\Helper\XmlHelper - * @dataProvider provideRenderData + * @dataProvider \Drupal\os2forms_fordelingskomponent\Test\Unit\Helper\XmlHelperTestDataProvider::provideRenderData */ public function testRender( string $template, @@ -44,160 +61,35 @@ public function testRender( /** * @covers \Drupal\os2forms_fordelingskomponent\Helper\XmlHelper - * @dataProvider provideValidateData + * @dataProvider \Drupal\os2forms_fordelingskomponent\Test\Unit\Helper\XmlHelperTestDataProvider::provideValidateTemplateData() */ - public function testValidate( + public function testValidateTemplate( string $template, - string $xsdUrl, ?InvalidXmlTemplateException $expected = NULL, ) { if ($expected instanceof InvalidXmlTemplateException) { $this->expectException($expected::class); } - $this->helper->validate($template, $xsdUrl); + $this->helper->validateTemplate($template); $this->assertTrue(TRUE); } /** - * Data provider. - */ - public static function provideRenderData(): iterable { - yield [ - 'This is not an XML document', - [], - new InvalidXmlTemplateException(), - ]; - - yield [ - <<<'XML' - - -
- urn:oio:cvr-nr:{{ handlers.settings.cvr }} - {{ webform_submission.completed|date('Y-m-d') }} - {{ handlers.settings.kle }} -
- - - {{ webform_submission.data.fornavn }} - {{ webform_submission.data.efternavn }} - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - {{ webform_submission.completed|date('Y-m-d') }} - -
-XML, - [ - 'handlers' => [ - 'settings' => [ - 'cvr' => '12345678', - 'kle' => '01.02.03', - ], - ], - 'webform_submission' => self::createWebformSubmission([ - 'completed' => (new \DateTimeImmutable('2001-01-01T00:00:00Z'))->getTimestamp(), - 'data' => [ - 'fornavn' => 'Anders', - 'efternavn' => 'And', - ], - ]), - ], - <<<'XML' - - -
- urn:oio:cvr-nr:12345678 - 2001-01-01 - 01.02.03 -
- - - Anders - And - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - 2001-01-01 - -
-XML, - ]; - } - - /** - * Create webform submission. - */ - private static function createWebformSubmission(array $values): array { - return $values; - } - - /** - * Data provider. + * @covers \Drupal\os2forms_fordelingskomponent\Helper\XmlHelper + * @dataProvider \Drupal\os2forms_fordelingskomponent\Test\Unit\Helper\XmlHelperTestDataProvider::provideValidateXmlData() */ - public static function provideValidateData(): iterable { - yield [ - <<<'XML' - - -
- urn:oio:cvr-nr:12345678 - 2001-01-01 - 01.02.03 -
- - - Anders - And - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - 2001-01-01 - -
-XML, - 'file://' . realpath(__DIR__ . '/../../resources/xsd/Anmodning.xsd'), - ]; + public function testValidateXml( + string $template, + string $xsdUrl, + ?InvalidXmlTemplateException $expected = NULL, + ) { + if ($expected instanceof InvalidXmlTemplateException) { + $this->expectException($expected::class); + } - yield [ - <<<'XML' - - - - - Anders - And - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - 2001-01-01 - - -XML, - 'file://' . realpath(__DIR__ . '/../../resources/xsd/Anmodning.xsd'), - new InvalidXmlTemplateException(), - ]; + $this->helper->validateXml($template, $xsdUrl); + $this->assertTrue(TRUE); } } diff --git a/tests/Unit/Helper/XmlHelperTestDataProvider.php b/tests/Unit/Helper/XmlHelperTestDataProvider.php new file mode 100644 index 0000000..6895166 --- /dev/null +++ b/tests/Unit/Helper/XmlHelperTestDataProvider.php @@ -0,0 +1,266 @@ + + + {{ name }} + +XML, + [], + new InvalidXmlTemplateException(), + ]; + + yield [ + <<<'XML' + + +
+ urn:oio:cvr-nr:{{ handlers.settings.cvr }} + {{ webform_submission.completed|date('Y-m-d') }} + {{ handlers.settings.kle }} +
+ + + {{ webform_submission.data.fornavn }} + {{ webform_submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ webform_submission.completed|date('Y-m-d') }} + +
+XML, + [ + 'handlers' => [ + 'settings' => [ + 'cvr' => '12345678', + 'kle' => '01.02.03', + ], + ], + 'webform_submission' => self::createWebformSubmission([ + 'completed' => (new \DateTimeImmutable('2001-01-01T00:00:00Z'))->getTimestamp(), + 'data' => [ + 'fornavn' => 'Anders', + 'efternavn' => 'And', + ], + ]), + ], + <<<'XML' + + +
+ urn:oio:cvr-nr:12345678 + 2001-01-01 + 01.02.03 +
+ + + Anders + And + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2001-01-01 + +
+XML, + ]; + + yield [ + <<<'XML' + + +
+ urn:oio:cvr-nr:{{ handlers.settings.cvr }} + {{ webform_submission.completed|date('Y-m-d') }} + {{ handlers.settings.kle }} +
+ +{% if webform_submission.data.fornavn == "And" %} + + {{ webform_submission.data.fornavn }} + {{ webform_submission.data.efternavn }} + urn:oio:cpr:0000000000 + +{% else %} + + {{ webform_submission.data.fornavn }} + {{ webform_submission.data.efternavn }} + urn:oio:cpr:0000000000 + +{% endif %} + + + Medicin + + + Underskrift0 + {{ webform_submission.completed|date('Y-m-d') }} + +
+XML, + [ + 'handlers' => [ + 'settings' => [ + 'cvr' => '12345678', + 'kle' => '01.02.03', + ], + ], + 'webform_submission' => self::createWebformSubmission([ + 'completed' => (new \DateTimeImmutable('2001-01-01T00:00:00Z'))->getTimestamp(), + 'data' => [ + 'fornavn' => 'Anders', + 'efternavn' => 'And', + ], + ]), + ], + <<<'XML' + + +
+ urn:oio:cvr-nr:12345678 + 2001-01-01 + 01.02.03 +
+ + + Anders + And + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2001-01-01 + +
+XML, + ]; + } + + /** + * Create webform submission. + */ + private static function createWebformSubmission(array $values): array { + return $values; + } + + /** + * Data provider. + */ + public static function provideValidateTemplateData(): iterable { + yield [ + <<<'XML' + + + {{ name } + +XML, + new InvalidXmlTemplateException(), + ]; + + yield [ + <<<'XML' + + + {% if name == "And" %} + {{ name }} + +XML, + new InvalidXmlTemplateException(), + ]; + + } + + /** + * Data provider. + */ + public static function provideValidateXmlData(): iterable { + yield [ + <<<'XML' + + +
+ urn:oio:cvr-nr:12345678 + 2001-01-01 + 01.02.03 +
+ + + Anders + And + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2001-01-01 + +
+XML, + self::RESOURCE_PATH . '/xsd/Anmodning.xsd', + ]; + + yield [ + <<<'XML' + + + + + Anders + And + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2001-01-01 + + +XML, + self::RESOURCE_PATH . '/xsd/Anmodning.xsd', + new InvalidXmlTemplateException(), + ]; + } + +} From 08493c1254dc0ebce52d850ee9b84b3cade4a44d Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 6 Feb 2026 15:20:39 +0100 Subject: [PATCH 09/62] Cleaned up --- src/Plugin/WebformHandler/WebformHandlerSF2900.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 63488bc..5cd0376 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -175,8 +175,8 @@ public function postPurge(array $webform_submissions) { * {@inheritdoc} */ public function getSummary() { - $kleEmne = $this->configuration[FordelingskomponentHelper::KLE_EMNE]; - $handlingFacet = $this->configuration[FordelingskomponentHelper::HANDLING_FACET]; + $kleEmne = $this->configuration[self::SECTION_SF2900][FordelingskomponentHelper::KLE_EMNE]; + $handlingFacet = $this->configuration[self::SECTION_SF2900][FordelingskomponentHelper::HANDLING_FACET]; $build = [ 'info' => [ From 8ccec621e7d0936f951fbee32a700f52f47b4baa Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 9 Feb 2026 14:45:59 +0100 Subject: [PATCH 10/62] Added preview stuff --- README.md | 18 +++-- composer.json | 6 +- .../webform.webform.os2_fdk_kp_anmoding.yml | 38 ++------- os2forms_fordelingskomponent.info.yml | 2 + os2forms_fordelingskomponent.module | 15 ++++ os2forms_fordelingskomponent.routing.yml | 28 +++++++ os2forms_fordelingskomponent.services.yml | 4 + ...lingskomponentPayloadPreviewController.php | 81 +++++++++++++++++++ ...omponentPayloadPreviewRenderController.php | 76 +++++++++++++++++ src/Exception/RuntimeException.php | 2 +- src/Helper/XmlHelper.php | 37 ++++++++- src/Hook/Hooks.php | 47 +++++++++++ .../WebformHandler/WebformHandlerSF2900.php | 23 ++++-- ...onent-payload-preview-render-xml.html.twig | 32 ++++++++ ...delingskomponent-payload-preview.html.twig | 59 ++++++++++++++ 15 files changed, 418 insertions(+), 50 deletions(-) create mode 100644 os2forms_fordelingskomponent.module create mode 100644 src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php create mode 100644 src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php create mode 100644 src/Hook/Hooks.php create mode 100644 templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig create mode 100644 templates/os2forms-fordelingskomponent-payload-preview.html.twig diff --git a/README.md b/README.md index d1c1120..2e00c2b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ -# Fordelingskomponent for Drupal +# Fordelingskomponent for OS2Forms -## Example keys +## Installation -| Key | Type | Provider | -|-------------------------|----------------|----------| -| SF2900 Certificate | Certificate | File | -| SF2900 SFTP private key | Authentication | File | +1. Create two keys (on `/admin/config/system/keys`) -Note: The "SFTP private key" key must be passwordless. + | Key | Type | Provider | + |-------------------------|----------------|----------| + | SF2900 Certificate | Certificate | File | + | SF2900 SFTP private key | Authentication | File | + + Note: The "SFTP private key" key must be passwordless. + +2. Go to `/admin/os2forms_fordelingskomponent/settings` and configure the Fordelingskomponent module. diff --git a/composer.json b/composer.json index 3ad68d4..3581e49 100644 --- a/composer.json +++ b/composer.json @@ -11,12 +11,12 @@ ], "require": { "ext-dom": "*", - "drupal/webform": "^6.1", + "drupal/system_stream_wrapper": "^2.1", + "drupal/webform": "^6.2", "drush/drush": "^12 || ^13", "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten", "os2web/os2web_audit": "^1.2", - "os2web/os2web_key": "^1.0", - "drupal/system_stream_wrapper": "^2.1" + "os2web/os2web_key": "^1.0" }, "require-dev": { "drupal/coder": "^8.3", diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml index 5c1615d..2df7125 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml @@ -271,44 +271,18 @@ handlers: titel: "Your document" beskrivelse: "This is a very important document" - xml_structure: - - name: Anmodning - elements: - - name: Header - elements: - - name: - - xml_stuff: - Anmodning: - Header: - # Static values - Myndighed: "urn:oio:cvr-nr:00000000" - ModtagetDato: "2006-05-04" - KLE: KLE - AnsoegerOplysninger: - Ansoeger: - # Submission values - Fornavn: "[webform_submission:values:fornavn]" - Efternavn: "[webform_submission:values:efternavn]" - Personnummer: "[webform_submission:values:personnummer]" - Sagstype: - AlmindeligtHelbredstillaeg: "[webform_submission:values:almindeligthelbredstillaeg]" - Underskriftsoplysninger: - Underskrift: "[webform_submission:values:underskrift]" - Underskriftsdato: "[webform_submission:completed:custom:Y-m-d]" - xml_template: |
- urn:oio:cvr-nr:{{ handlers.settings.cvr }} - {{ webform_submission.complete|date(Y-m-d) }} - {{ handlers.settings.kle }} + urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + {{ submission.completed.value|date("Y-m-d") }} + {{ handler.settings.sf2900.kle_emne }}
- {{ webform_submission.values.fornavn }} - {{ webform_submission.values.efternavn }} + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} urn:oio:cpr:0000000000 @@ -317,7 +291,7 @@ handlers: Underskrift0 - {{ webform_submission.complete|date('Y-m-d') }} + {{ submission.completed.value|date("Y-m-d") }}
diff --git a/os2forms_fordelingskomponent.info.yml b/os2forms_fordelingskomponent.info.yml index 25916dc..599c6d7 100644 --- a/os2forms_fordelingskomponent.info.yml +++ b/os2forms_fordelingskomponent.info.yml @@ -9,5 +9,7 @@ dependencies: - "os2web_audit:os2web_audit" # Why don't we get this dependency implicitly from os2web_key? - "key:key" + - "system_stream_wrapper:system_stream_wrapper" + - "webform:webform" configure: os2forms_fordelingskomponent.admin.settings diff --git a/os2forms_fordelingskomponent.module b/os2forms_fordelingskomponent.module new file mode 100644 index 0000000..966f48a --- /dev/null +++ b/os2forms_fordelingskomponent.module @@ -0,0 +1,15 @@ +theme($existing, $type, $theme, $path); +} diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index b9c3083..71aa150 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -17,3 +17,31 @@ os2forms_fordelingskomponent.routing_info: type: "entity:webform" requirements: _permission: "administer site configuration" + +os2forms_fordelingskomponent.fordelingskomponent_payload.preview: + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{handler}/preview" + defaults: + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentPayloadPreviewController' + _title: "Fordelingskomponent payload preview" + options: + parameters: + webform: + type: "entity:webform" + handler: + type: "entity:webform_handler" + requirements: + _permission: "view any webform submission" + +os2forms_fordelingskomponent.fordelingskomponent_payload.preview_render: + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{handler}/preview/render/{submission}" + defaults: + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentPayloadPreviewRenderController' + _title: "Fordelingskomponent payload preview" + options: + parameters: + webform: + type: "entity:webform" + submission: + type: "entity:webform_submission" + requirements: + _permission: "view any webform submission" diff --git a/os2forms_fordelingskomponent.services.yml b/os2forms_fordelingskomponent.services.yml index 9428453..694675b 100644 --- a/os2forms_fordelingskomponent.services.yml +++ b/os2forms_fordelingskomponent.services.yml @@ -13,3 +13,7 @@ services: Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper: Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900: + + Drupal\os2forms_fordelingskomponent\Helper\XmlHelper: + + Drupal\os2forms_fordelingskomponent\Hook\Hooks: diff --git a/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php b/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php new file mode 100644 index 0000000..d0a5b78 --- /dev/null +++ b/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php @@ -0,0 +1,81 @@ +submissionStorage = $entityTypeManager->getStorage('webform_submission'); + } + + /** + * Builds the response. + */ + public function __invoke(Request $request, WebformInterface $webform, string $handler): array|Response { + $handler = $webform->getHandler($handler); + $submissionIds = array_keys($this->submissionStorage->getQuery() + ->accessCheck() + ->condition('webform_id', $webform->id()) + ->sort('created', 'DESC') + ->execute()); + $currentSubmission = (int) $request->query->get('submission'); + $index = array_search($currentSubmission, $submissionIds); + if (FALSE === $index) { + $currentSubmission = reset($submissionIds) ?: NULL; + $index = array_search($currentSubmission, $submissionIds); + } + + $previewUrls = array_map( + static fn($submission) => Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_payload.preview', [ + 'webform' => $webform->id(), + 'handler' => $handler->getHandlerId(), + 'submission' => $submission, + ]), + array_filter([ + 'prev' => $submissionIds[$index + 1] ?? NULL, + 'self' => $currentSubmission, + 'next' => $submissionIds[$index - 1] ?? NULL, + ]) + ); + + $renderUrl = NULL !== $currentSubmission + ? Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_payload.preview_render', [ + 'webform' => $webform->id(), + 'handler' => $handler->getHandlerId(), + 'submission' => $currentSubmission, + ]) + : NULL; + + return [ + '#theme' => 'os2forms_fordelingskomponent_payload_preview', + '#webform' => $webform, + '#handler' => $handler, + '#submission' => $currentSubmission, + '#return_url' => $webform->toUrl('handlers'), + '#render_url' => $renderUrl, + '#preview_urls' => $previewUrls, + ]; + + } + +} diff --git a/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php b/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php new file mode 100644 index 0000000..26cafac --- /dev/null +++ b/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php @@ -0,0 +1,76 @@ +getHandler($handler); + $handlerSettings = $handler->getSetting('sf2900'); + + $exceptions = []; + $warnings = []; + + $template = $handlerSettings['xml_template'] ?? NULL; + if (NULL === $template) { + // @todo Handle this + } + + /** @var ?string $xml */ + $xml = NULL; + try { + $context = $this->xmlHelper->getRenderContext($handler, $submission); + $xml = $this->xmlHelper->render($template, $context); + + $this->xmlHelper->validateXml($xml); + + $xsdUrl = $handlerSettings['xsd_url'] ?? NULL; + if (NULL === $xsdUrl) { + $warnings[] = new \RuntimeException('XSD URL not defined'); + } + else { + $this->xmlHelper->validateXml($xml, $xsdUrl, loadXsdContent: TRUE); + } + } + catch (Exception $e) { + $exceptions[] = $e; + } + + $build = [ + '#theme' => 'os2forms_fordelingskomponent_payload_preview_render_xml', + '#webform' => $webform, + '#handler' => $handler, + '#submission' => $submission, + '#exceptions' => $exceptions, + '#warnings' => $warnings, + '#template' => $template, + '#context' => $context, + '#xml' => $xml ?? NULL, + ]; + + return new Response((string) $this->renderer->renderRoot($build)); + } + +} diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 5e7ca66..08aad0f 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -5,6 +5,6 @@ /** * Base RuntimeException for module. */ -class RuntimeException extends \RuntimeException { +class RuntimeException extends Exception { } diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index 9de122c..7f4787f 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -2,7 +2,12 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; +use Drupal\Core\Config\ImmutableConfig; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; +use Drupal\webform\Plugin\WebformHandlerInterface; +use Drupal\webform\WebformSubmissionInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Twig\Environment; use Twig\TemplateWrapper; @@ -10,13 +15,20 @@ * XML helper. */ class XmlHelper { + /** + * The module config. + */ + private ImmutableConfig $moduleSettings; /** * Constructor. */ public function __construct( + #[Autowire(service: 'twig')] private readonly Environment $twig, + ConfigFactoryInterface $configFactory, ) { + $this->moduleSettings = $configFactory->get('os2forms_fordelingskomponent.settings'); } /** @@ -59,6 +71,19 @@ public function render(string $template, array $context): string { } } + /** + * Get render context. + */ + public function getRenderContext(WebformHandlerInterface $handler, WebformSubmissionInterface $submission) { + return [ + 'module' => [ + 'settings' => $this->moduleSettings->get(), + ], + 'handler' => $handler, + 'submission' => $submission, + ]; + } + /** * Check that Twig template is valid, i.e. has no syntax errors. */ @@ -73,8 +98,11 @@ public function validateTemplate(string $template): void { /** * Check that XML is valid. Optionally validate using an XSD. + * + * @throws \Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException + * An exception. */ - public function validateXml(string $xml, ?string $xsdUrl = NULL): void { + public function validateXml(string $xml, ?string $xsdUrl = NULL, bool $loadXsdContent = FALSE): void { $this->checkXml($xml); if (NULL === $xsdUrl) { @@ -84,6 +112,13 @@ public function validateXml(string $xml, ?string $xsdUrl = NULL): void { // https://www.php.net/manual/en/function.libxml-use-internal-errors.php $useInternalErrors = libxml_use_internal_errors(TRUE); + if ($loadXsdContent) { + $content = file_get_contents($xsdUrl); + if (FALSE === $content) { + throw new InvalidXmlTemplateException(sprintf('Error loading XSD: %s', $xsdUrl)); + } + } + try { $reader = new \XMLReader(); $path = tempnam(sys_get_temp_dir(), 'os2forms_fordelingskomponent'); diff --git a/src/Hook/Hooks.php b/src/Hook/Hooks.php new file mode 100644 index 0000000..77e076c --- /dev/null +++ b/src/Hook/Hooks.php @@ -0,0 +1,47 @@ + [ + 'variables' => [ + 'webform' => NULL, + 'handler' => NULL, + 'submission' => NULL, + 'return_url' => NULL, + 'render_url' => NULL, + 'preview_urls' => [ + 'prev' => NULL, + 'self' => NULL, + 'next' => NULL, + ], + ], + ], + + 'os2forms_fordelingskomponent_payload_preview_render_xml' => [ + 'variables' => [ + 'webform' => NULL, + 'handler' => NULL, + 'submission' => NULL, + 'exceptions' => NULL, + 'warnings' => NULL, + 'context' => NULL, + 'template' => NULL, + 'xml' => NULL, + ], + ], + ]; + } + } + +} diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 5cd0376..5e5cfd9 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -190,20 +190,31 @@ public function getSummary() { ], ]; - if ($kleEmne) { - $build['routing_info'] = [ - '#prefix' => '
', - '#suffix' => '
', + $items = []; + + $items[] = Link::createFromRoute( + $this->t('Preview payload'), + 'os2forms_fordelingskomponent.fordelingskomponent_payload.preview', [ + 'webform' => $this->getWebform()->id(), + 'handler' => $this->getHandlerId(), ] - + Link::createFromRoute( + ); + + if ($kleEmne) { + $items[] = Link::createFromRoute( $this->t('Show routing info'), 'os2forms_fordelingskomponent.routing_info', [ 'webform' => $this->getWebform()->id(), 'handler' => $this->getHandlerId(), ] - )->toRenderable(); + ); } + $build['links'] = [ + '#theme' => 'item_list', + '#items' => $items, + ]; + return $build; } diff --git a/templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig b/templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig new file mode 100644 index 0000000..215d31f --- /dev/null +++ b/templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig @@ -0,0 +1,32 @@ + + + + + + {{ 'Preview'|trans }} + + + +
+
+ {{ 'Template'|trans }} + +
{{ template }}
+
+ + {% for exception in exceptions %} + + {% endfor %} + + {% for warning in warnings %} + + {% endfor %} + +
{{ xml }}
+
+ + diff --git a/templates/os2forms-fordelingskomponent-payload-preview.html.twig b/templates/os2forms-fordelingskomponent-payload-preview.html.twig new file mode 100644 index 0000000..5591b5f --- /dev/null +++ b/templates/os2forms-fordelingskomponent-payload-preview.html.twig @@ -0,0 +1,59 @@ +{# + /** + * @file + * Template for Fordelingskomponent payload preview. + * + * Available variables: + * - preview_urls: The preview URLs + * - prev: Previous submission preview URL (if any) + * - self: The current preview URL (if any) + * - next: Next submission preview URL (if any) + * - webform: The webform + * - handler: The handler ID + * - submission: The submission ID + * - return_url: The return URL (to list of webform handlers) + * - render_url: The render URL to render the actual preview + */ + #} +
+ + + + +{# +
+
+#} + + {% if render_url %} + + {% endif %} +
From b853f949331b2539c5b739037238748493b1dcdf Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 10 Feb 2026 13:32:40 +0100 Subject: [PATCH 11/62] More stuff --- README.md | 50 ++++++++++++++++ drush.services.yml | 14 +++++ .../webform.webform.os2_fdk_kp_anmoding.yml | 4 ++ os2forms_fordelingskomponent.routing.yml | 6 +- ...lingskomponentPayloadPreviewController.php | 8 +-- ...omponentPayloadPreviewRenderController.php | 4 +- .../Commands/SendJournalnotatCommand.php | 59 +++++++++++++++++++ src/Drush/Commands/SftpLsCommand.php | 59 +++++++++++++++++++ src/Helper/FordelingskomponentHelper.php | 2 +- src/Hook/Hooks.php | 2 +- .../WebformHandler/WebformHandlerSF2900.php | 10 +++- ...onent-payload-preview-render-xml.html.twig | 22 +++---- 12 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 drush.services.yml create mode 100644 src/Drush/Commands/SendJournalnotatCommand.php create mode 100644 src/Drush/Commands/SftpLsCommand.php diff --git a/README.md b/README.md index 2e00c2b..4af489a 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,53 @@ Note: The "SFTP private key" key must be passwordless. 2. Go to `/admin/os2forms_fordelingskomponent/settings` and configure the Fordelingskomponent module. + +## Console commands + +``` shell +drush os2forms-fordelingskomponent:sftp:ls +``` + +--- + +``` shell name=key-create-sf2900_certificate +drush config:set --input-format=yaml key.key.sf2900_certificate '?' - <<'EOF' +langcode: da +status: true +dependencies: + module: + - os2web_key +id: sf2900_certificate +label: 'SF2900 Certificate' +description: '' +key_type: os2web_key_certificate +key_type_settings: + passphrase: … + input_format: pfx + output_format: pem +key_provider: file +key_provider_settings: + file_location: /app/cert/OS2Forms_FordelingUdvikling.p12 + strip_line_breaks: false +key_input: none +key_input_settings: { } +EOF +``` + +``` shell name=key-create-sf2900_sftp_private_key +drush config:set --input-format=yaml key.key.sf2900_sftp_private_key '?' - <<'EOF' +langcode: da +status: true +dependencies: { } +id: sf2900_sftp_private_key +label: 'SF2900 SFTP private key' +description: '' +key_type: authentication +key_type_settings: { } +key_provider: file +key_provider_settings: + file_location: /app/cert/OS2Forms_FordelingUdvikling-sftp-nopass + strip_line_breaks: false +key_input: none +key_input_settings: { } +``` diff --git a/drush.services.yml b/drush.services.yml new file mode 100644 index 0000000..1ec4f7c --- /dev/null +++ b/drush.services.yml @@ -0,0 +1,14 @@ +services: + os2forms_fordelingskomponent.sftp.ls: + class: Drupal\os2forms_fordelingskomponent\Drush\Commands\SftpLsCommand + arguments: + - '@Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper' + tags: + - { name: console.command } + + os2forms_fordelingskomponent.sned.journalnotat: + class: Drupal\os2forms_fordelingskomponent\Drush\Commands\SendJournalnotat + arguments: + - '@Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper' + tags: + - { name: console.command } diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml index 2df7125..af421df 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml @@ -271,6 +271,10 @@ handlers: titel: "Your document" beskrivelse: "This is a very important document" + distribution_type: DISTRIBUTION_DOKUMENT_TYPE + # DISTRIBUTION_JOURNAL_POST_TYPE + # DISTRIBUTION_FORMULAR_TYPE + xml_template: | diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index 71aa150..2dc39d4 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -19,7 +19,7 @@ os2forms_fordelingskomponent.routing_info: _permission: "administer site configuration" os2forms_fordelingskomponent.fordelingskomponent_payload.preview: - path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{handler}/preview" + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{webform_handler}/preview" defaults: _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentPayloadPreviewController' _title: "Fordelingskomponent payload preview" @@ -27,13 +27,11 @@ os2forms_fordelingskomponent.fordelingskomponent_payload.preview: parameters: webform: type: "entity:webform" - handler: - type: "entity:webform_handler" requirements: _permission: "view any webform submission" os2forms_fordelingskomponent.fordelingskomponent_payload.preview_render: - path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{handler}/preview/render/{submission}" + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{webform_handler}/preview/render/{submission}" defaults: _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentPayloadPreviewRenderController' _title: "Fordelingskomponent payload preview" diff --git a/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php b/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php index d0a5b78..96bd412 100644 --- a/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php @@ -31,8 +31,8 @@ public function __construct( /** * Builds the response. */ - public function __invoke(Request $request, WebformInterface $webform, string $handler): array|Response { - $handler = $webform->getHandler($handler); + public function __invoke(Request $request, WebformInterface $webform, string $webform_handler): array|Response { + $handler = $webform->getHandler($webform_handler); $submissionIds = array_keys($this->submissionStorage->getQuery() ->accessCheck() ->condition('webform_id', $webform->id()) @@ -48,7 +48,7 @@ public function __invoke(Request $request, WebformInterface $webform, string $ha $previewUrls = array_map( static fn($submission) => Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_payload.preview', [ 'webform' => $webform->id(), - 'handler' => $handler->getHandlerId(), + 'webform_handler' => $handler->getHandlerId(), 'submission' => $submission, ]), array_filter([ @@ -61,7 +61,7 @@ public function __invoke(Request $request, WebformInterface $webform, string $ha $renderUrl = NULL !== $currentSubmission ? Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_payload.preview_render', [ 'webform' => $webform->id(), - 'handler' => $handler->getHandlerId(), + 'webform_handler' => $handler->getHandlerId(), 'submission' => $currentSubmission, ]) : NULL; diff --git a/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php b/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php index 26cafac..756e3d5 100644 --- a/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php +++ b/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php @@ -26,8 +26,8 @@ public function __construct( /** * Builds the response. */ - public function __invoke(WebformInterface $webform, string $handler, WebformSubmissionInterface $submission): Response { - $handler = $webform->getHandler($handler); + public function __invoke(WebformInterface $webform, string $webform_handler, WebformSubmissionInterface $submission): Response { + $handler = $webform->getHandler($webform_handler); $handlerSettings = $handler->getSetting('sf2900'); $exceptions = []; diff --git a/src/Drush/Commands/SendJournalnotatCommand.php b/src/Drush/Commands/SendJournalnotatCommand.php new file mode 100644 index 0000000..84be7cd --- /dev/null +++ b/src/Drush/Commands/SendJournalnotatCommand.php @@ -0,0 +1,59 @@ +addArgument('dir', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'List of directory paths', [SftpHelper::INCOMING_FOLDER]); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + $sftp = $this->helper->sf2900()->sftp(); + $dirs = (array) $input->getArgument('dir'); + foreach ($dirs as $dir) { + // @todo getFiles does not complain when using an invalid directory … + $files = $sftp->getFiles($dir); + $files = array_filter($files, fn (string $file) => !preg_match('/^[.]+$/', $file)); + $io->section($dir); + foreach ($files as $file) { + $io->writeln($file); + } + } + + return self::SUCCESS; + } + +} diff --git a/src/Drush/Commands/SftpLsCommand.php b/src/Drush/Commands/SftpLsCommand.php new file mode 100644 index 0000000..14c3646 --- /dev/null +++ b/src/Drush/Commands/SftpLsCommand.php @@ -0,0 +1,59 @@ +addArgument('dir', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'List of directory paths', [SftpHelper::INCOMING_FOLDER]); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + $sftp = $this->helper->sf2900()->sftp(); + $dirs = (array) $input->getArgument('dir'); + foreach ($dirs as $dir) { + // @todo getFiles does not complain when using an invalid directory … + $files = $sftp->getFiles($dir); + $files = array_filter($files, fn (string $file) => !preg_match('/^[.]+$/', $file)); + $io->section($dir); + foreach ($files as $file) { + $io->writeln($file); + } + } + + return self::SUCCESS; + } + +} diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 0fd5e50..b960f1f 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -261,7 +261,7 @@ public static function isValidUuid(string $value): bool { /** * Get a singleton instance of SF2900. */ - private function sf2900(): SF2900 { + public function sf2900(): SF2900 { if (!isset($this->sf2900)) { $options = $this->getModuleConfig()->get('sf2900'); $certificateKey = $this->keyRepository->getKey($options['certificate']); diff --git a/src/Hook/Hooks.php b/src/Hook/Hooks.php index 77e076c..44a7db4 100644 --- a/src/Hook/Hooks.php +++ b/src/Hook/Hooks.php @@ -10,7 +10,7 @@ class Hooks { /** * Implements hook_theme(). */ - public function theme(array $existing, string $type, string $theme, string $path) : array { + public function theme(array $existing, string $type, string $theme, string $path): array { { return [ 'os2forms_fordelingskomponent_payload_preview' => [ diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 5e5cfd9..d4e5c27 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -196,7 +196,15 @@ public function getSummary() { $this->t('Preview payload'), 'os2forms_fordelingskomponent.fordelingskomponent_payload.preview', [ 'webform' => $this->getWebform()->id(), - 'handler' => $this->getHandlerId(), + 'webform_handler' => $this->getHandlerId(), + ] + ); + + $items[] = Link::createFromRoute( + $this->t('Edit handler'), + 'entity.webform.handler.edit_form', [ + 'webform' => $this->getWebform()->id(), + 'webform_handler' => $this->getHandlerId(), ] ); diff --git a/templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig b/templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig index 215d31f..e0751b8 100644 --- a/templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig +++ b/templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig @@ -14,19 +14,19 @@
{{ template }}
- {% for exception in exceptions %} - - {% endfor %} + {% for exception in exceptions %} + + {% endfor %} - {% for warning in warnings %} - - {% endfor %} + {% for warning in warnings %} + + {% endfor %} -
{{ xml }}
+
{{ xml }}
From fbba31e985805a223c093de0ae184c67261ad57a Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 10 Feb 2026 15:35:16 +0100 Subject: [PATCH 12/62] Fixed composer stuff --- .github/workflows/composer.yaml | 5 +++++ .github/workflows/php.yaml | 5 +++++ .github/workflows/twig.yaml | 6 ++++++ .twig-cs-fixer.dist.php | 3 --- README.md | 19 +++++++++++++++++++ Taskfile.yml | 15 +++++++++++++++ compose.yaml | 4 ++++ composer.json | 22 ++++++++++++++++------ scripts/base | 9 +++++++++ 9 files changed, 79 insertions(+), 9 deletions(-) diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml index 6c3a30c..b7f05e9 100644 --- a/.github/workflows/composer.yaml +++ b/.github/workflows/composer.yaml @@ -62,6 +62,11 @@ jobs: docker network create frontend - run: | + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.* + docker compose run --rm phpfpm composer install docker compose run --rm phpfpm composer normalize --dry-run diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index 95c22ed..c4e5269 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -55,5 +55,10 @@ jobs: docker network create frontend - run: | + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.* + docker compose run --rm phpfpm composer install docker compose run --rm phpfpm vendor/bin/phpcs diff --git a/.github/workflows/twig.yaml b/.github/workflows/twig.yaml index 9b0e343..1e050de 100644 --- a/.github/workflows/twig.yaml +++ b/.github/workflows/twig.yaml @@ -44,5 +44,11 @@ jobs: - run: | docker network create frontend + + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.* + docker compose run --rm phpfpm composer install docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint diff --git a/.twig-cs-fixer.dist.php b/.twig-cs-fixer.dist.php index c4a03e4..0a0f295 100644 --- a/.twig-cs-fixer.dist.php +++ b/.twig-cs-fixer.dist.php @@ -13,7 +13,4 @@ $config = new TwigCsFixer\Config\Config(); $config->setFinder($finder); -// @see https://github.com/VincentLanglet/Twig-CS-Fixer/pull/134#issuecomment-1756924889 -$config->addTokenParser(new Drupal\Core\Template\TwigTransTokenParser()); - return $config; diff --git a/README.md b/README.md index 4af489a..47196e6 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,22 @@ key_provider_settings: key_input: none key_input_settings: { } ``` + +## Development + +``` shell +task composer:install +``` + +### Composer install hacks + +``` shell name=composer-install-hack +# Create a temporary composer file to install https://github.com/mglaman/composer-drupal-lenient before the real install needs it. +docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction +docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true +docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient +docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.* + +# Now we can install what we actually need. +docker compose run --rm phpfpm composer install +``` diff --git a/Taskfile.yml b/Taskfile.yml index 5df9781..4a8d6a0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -15,6 +15,21 @@ tasks: vars: TASK_ARGS: run --rm phpfpm composer {{.TASK_ARGS}} + composer:install: + desc: Run composer inside docker compose setup, e.g. `task {{.TASK}} -- install` + cmds: + - rm -fr composer.lock vendor + - | + # Create a temporary composer file to install https://github.com/mglaman/composer-drupal-lenient before the real install needs it. + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient + docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.* + + - task: composer + vars: + TASK_ARGS: install + coding-standards:apply: desc: "Apply coding standards" cmds: diff --git a/compose.yaml b/compose.yaml index dd414ae..77994c4 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,6 +7,10 @@ services: volumes: - .:/app-os2forms_fordelingskomponent working_dir: /app-os2forms_fordelingskomponent + environment: + # https://getcomposer.org/doc/03-cli.md#composer-no-security-blocking + # @see https://github.com/OS2Forms/os2forms/issues/245 + COMPOSER_NO_SECURITY_BLOCKING: 1 # Code checks tools markdownlint: diff --git a/composer.json b/composer.json index 3581e49..aa2f294 100644 --- a/composer.json +++ b/composer.json @@ -12,15 +12,13 @@ "require": { "ext-dom": "*", "drupal/system_stream_wrapper": "^2.1", - "drupal/webform": "^6.2", "drush/drush": "^12 || ^13", - "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten", - "os2web/os2web_audit": "^1.2", - "os2web/os2web_key": "^1.0" + "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten as 1.7.9999", + "os2forms/os2forms": "^5.0" }, "require-dev": { "drupal/coder": "^8.3", - "ergebnis/composer-normalize": "^2.49", + "ergebnis/composer-normalize": "^2.50", "mglaman/phpstan-drupal": "^2.0", "phpstan/extension-installer": "^1.4", "phpunit/phpunit": "^9.6", @@ -45,7 +43,19 @@ "cweagans/composer-patches": true, "dealerdirect/phpcodesniffer-composer-installer": true, "ergebnis/composer-normalize": true, - "phpstan/extension-installer": true + "mglaman/composer-drupal-lenient": true, + "phpstan/extension-installer": true, + "simplesamlphp/composer-module-installer": false, + "simplesamlphp/composer-xmlprovider-installer": false, + "zaporylie/composer-drupal-optimizations": true + } + }, + "extra": { + "drupal-lenient": { + "allowed-list": [ + "drupal/coc_forms_auto_export", + "drupal/webform_node_element" + ] } } } diff --git a/scripts/base b/scripts/base index 9e3d1f3..4175061 100644 --- a/scripts/base +++ b/scripts/base @@ -77,6 +77,15 @@ setup() { compose down --remove-orphans compose pull compose up --detach --remove-orphans --wait + + # @todo Do this in a dockerfile. + compose exec drupal sh -c 'apt update && apt --yes install git' + + COMPOSER=composer.lenient.json composer init --no-interaction + COMPOSER=composer.lenient.json composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true + COMPOSER=composer.lenient.json composer require mglaman/composer-drupal-lenient + # COMPOSER=composer.lenient.json rm composer.lenient.* + # Allow all plugins to run. composer --no-plugins config allow-plugins true From 4ff86369d78b59601d8e0e5f5b7a5f9c26dd465b Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 10 Feb 2026 17:26:12 +0100 Subject: [PATCH 13/62] Cleaned up --- README.md | 7 +++ drush.services.yml | 16 +++++- src/Drush/Commands/AbstractCommand.php | 21 +++++++ .../Commands/SendJournalnotatCommand.php | 18 ------ src/Drush/Commands/SftpGetCommand.php | 56 +++++++++++++++++++ src/Drush/Commands/SftpLsCommand.php | 14 +---- src/Drush/Commands/SftpPutCommand.php | 54 ++++++++++++++++++ src/Helper/FordelingskomponentHelper.php | 1 + 8 files changed, 157 insertions(+), 30 deletions(-) create mode 100644 src/Drush/Commands/AbstractCommand.php create mode 100644 src/Drush/Commands/SftpGetCommand.php create mode 100644 src/Drush/Commands/SftpPutCommand.php diff --git a/README.md b/README.md index 47196e6..4c109c0 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,13 @@ Note: The "SFTP private key" key must be passwordless. + You can use `ssh-keygen` to remove the password from a certificate: + + ``` shell + cp cert/sf2900-sftp cert/sf2900-sftp-nopass + ssh-keygen -p -N "" -f cert/sf2900-sftp-nopass + ``` + 2. Go to `/admin/os2forms_fordelingskomponent/settings` and configure the Fordelingskomponent module. ## Console commands diff --git a/drush.services.yml b/drush.services.yml index 1ec4f7c..395470d 100644 --- a/drush.services.yml +++ b/drush.services.yml @@ -6,8 +6,22 @@ services: tags: - { name: console.command } + os2forms_fordelingskomponent.sftp.put: + class: Drupal\os2forms_fordelingskomponent\Drush\Commands\SftpPutCommand + arguments: + - '@Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper' + tags: + - { name: console.command } + + os2forms_fordelingskomponent.sftp.get: + class: Drupal\os2forms_fordelingskomponent\Drush\Commands\SftpGetCommand + arguments: + - '@Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper' + tags: + - { name: console.command } + os2forms_fordelingskomponent.sned.journalnotat: - class: Drupal\os2forms_fordelingskomponent\Drush\Commands\SendJournalnotat + class: Drupal\os2forms_fordelingskomponent\Drush\Commands\SendJournalnotatCommand arguments: - '@Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper' tags: diff --git a/src/Drush/Commands/AbstractCommand.php b/src/Drush/Commands/AbstractCommand.php new file mode 100644 index 0000000..8af51a4 --- /dev/null +++ b/src/Drush/Commands/AbstractCommand.php @@ -0,0 +1,21 @@ +addArgument('dir', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'List of directory paths', [SftpHelper::INCOMING_FOLDER]); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output): int { - $io = new SymfonyStyle($input, $output); - $sftp = $this->helper->sf2900()->sftp(); - $dirs = (array) $input->getArgument('dir'); - foreach ($dirs as $dir) { - // @todo getFiles does not complain when using an invalid directory … - $files = $sftp->getFiles($dir); - $files = array_filter($files, fn (string $file) => !preg_match('/^[.]+$/', $file)); - $io->section($dir); - foreach ($files as $file) { - $io->writeln($file); - } - } - return self::SUCCESS; } diff --git a/src/Drush/Commands/SftpGetCommand.php b/src/Drush/Commands/SftpGetCommand.php new file mode 100644 index 0000000..0fc8141 --- /dev/null +++ b/src/Drush/Commands/SftpGetCommand.php @@ -0,0 +1,56 @@ +addArgument('filename', InputArgument::REQUIRED, 'Name of file to get') + ->addArgument('dir', InputArgument::OPTIONAL, 'Directory', SftpHelper::OUTGOING_FOLDER); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + $sftp = $this->helper->sf2900()->sftp(); + + $filename = $input->getArgument('filename'); + $dir = $input->getArgument('dir'); + + try { + $contents = $sftp->getContents($filename, $dir); + + echo $contents; + + return self::SUCCESS; + } + catch (\Exception $exception) { + $io->error($exception->getMessage()); + + return self::FAILURE; + } + } + +} diff --git a/src/Drush/Commands/SftpLsCommand.php b/src/Drush/Commands/SftpLsCommand.php index 14c3646..967cfd7 100644 --- a/src/Drush/Commands/SftpLsCommand.php +++ b/src/Drush/Commands/SftpLsCommand.php @@ -4,10 +4,8 @@ namespace Drupal\os2forms_fordelingskomponent\Drush\Commands; -use SF2900\SftpHelper; -use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; +use ItkDev\Serviceplatformen\Service\SF2900\SF2900\SftpHelper; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -18,13 +16,7 @@ name: 'os2forms-fordelingskomponent:sftp:ls', description: 'List files on SFTP server', )] -final class SftpLsCommand extends Command { - - public function __construct( - private readonly FordelingskomponentHelper $helper, - ) { - parent::__construct(); - } +final class SftpLsCommand extends AbstractCommand { /** * {@inheritdoc} @@ -33,7 +25,7 @@ public function __construct( */ protected function configure(): void { $this - ->addArgument('dir', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'List of directory paths', [SftpHelper::INCOMING_FOLDER]); + ->addArgument('dir', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'List of directory paths', [SftpHelper::OUTGOING_FOLDER]); } /** diff --git a/src/Drush/Commands/SftpPutCommand.php b/src/Drush/Commands/SftpPutCommand.php new file mode 100644 index 0000000..b012f9b --- /dev/null +++ b/src/Drush/Commands/SftpPutCommand.php @@ -0,0 +1,54 @@ +addArgument('filename', InputArgument::REQUIRED, 'Name of file to put') + ->addArgument('dir', InputArgument::OPTIONAL, 'Target directory', [SftpHelper::OUTGOING_FOLDER]); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + $sftp = $this->helper->sf2900()->sftp(); + + $filename = $input->getArgument('filename'); + + try { + $result = $sftp->putFile($filename); + $io->success(sprintf('File %s put on SFTP server as %s', $filename, $result)); + + return self::SUCCESS; + } + catch (\Exception $exception) { + $io->error($exception->getMessage()); + + return self::FAILURE; + } + } + +} diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index b960f1f..9a6ff02 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -272,6 +272,7 @@ public function sf2900(): SF2900 { $privateKeyKey = $this->keyRepository->getKey($options['sftp']['private_key']); $privateKey = $privateKeyKey->getKeyValue(); $sf2900options = [ + 'test_mode' => (bool) $this->getModuleConfig()->get('test_mode'), 'authority_cvr' => $options['sender_id'], 'certificate' => $certificate, 'sftp' => [ From d3376a8090372cd49461330425b21a245409c473 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 17 Feb 2026 16:41:12 +0100 Subject: [PATCH 14/62] Updated handler --- .../README.md | 7 + .../webform.webform.os2_fdk_kp_anmoding.yml | 170 ++++++- .../{xsd => SP/SF2900_XSD}/Anmodning.xsd | 0 .../resources/SP/SF2900_XSD/SP241.xsd | 180 +++++++ .../resources/SP/SF2900_XSD/SP242.xsd | 206 ++++++++ .../resources/SP/SF2900_XSD/SP246.xsd | 275 +++++++++++ .../resources/SP/SF2900_XSD/SP501.xsd | 457 ++++++++++++++++++ ...omponentPayloadPreviewRenderController.php | 2 +- src/Drush/Commands/AbstractCommand.php | 2 + .../Commands/SendJournalnotatCommand.php | 10 +- src/Helper/FordelingskomponentHelper.php | 17 +- src/Helper/WebformHelperSF2900.php | 6 +- src/Helper/XmlHelper.php | 2 +- .../WebformHandler/WebformHandlerSF2900.php | 153 +++++- 14 files changed, 1435 insertions(+), 52 deletions(-) rename modules/os2forms_fordelingskomponent_examples/resources/{xsd => SP/SF2900_XSD}/Anmodning.xsd (100%) create mode 100644 modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP241.xsd create mode 100644 modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP242.xsd create mode 100644 modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP246.xsd create mode 100644 modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP501.xsd diff --git a/modules/os2forms_fordelingskomponent_examples/README.md b/modules/os2forms_fordelingskomponent_examples/README.md index 2c15ba6..6ef9777 100644 --- a/modules/os2forms_fordelingskomponent_examples/README.md +++ b/modules/os2forms_fordelingskomponent_examples/README.md @@ -26,3 +26,10 @@ Test the newly exported config by reinstalling the `os2forms_fordelingskomponent drush pm:uninstall os2forms_fordelingskomponent_examples drush pm:install os2forms_fordelingskomponent_examples ``` + +Alternatively, import a single webform, e.g. + +``` shell +drush config:set --input-format=yaml webform.webform.os2_fdk_kp_anmoding '?' - < config/install/webform.webform.os2_fdk_kp_anmoding.yml +# drush config:get webform.webform.os2_fdk_kp_anmoding +``` diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml index af421df..98eb0fc 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml @@ -265,15 +265,15 @@ handlers: settings: sf2900: kle_emne: 01.01.01 - handling_facet: G87 - attachment_element: afsend_content_pdf + handling_facet: "" + attachment_element: attachment brugervendt_noegle: "What is “Brugervendt nøgle”?" titel: "Your document" beskrivelse: "This is a very important document" - distribution_type: DISTRIBUTION_DOKUMENT_TYPE - # DISTRIBUTION_JOURNAL_POST_TYPE - # DISTRIBUTION_FORMULAR_TYPE + # distribution_type: JOURNALPOST + # distribution_type: DOKUMENT + distribution_type: FORMULAR xml_template: | @@ -299,16 +299,151 @@ handlers:
- xsd_url: module://os2forms_fordelingskomponent_examples/resources/xsd/Anmodning.xsd + xsd_url: module://os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/Anmodning.xsd + + os2forms_fordelingskomponent_sf2900_malformed_xml: + id: os2forms_fordelingskomponent_sf2900 + handler_id: os2forms_fordelingskomponent_sf2900_malformed_xml + label: "Fordelingskomponent (sf2900) (malformed XML)" + notes: "" + status: false + conditions: {} + weight: 0 + settings: + sf2900: + kle_emne: 01.01.01 + handling_facet: "" + attachment_element: attachment + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" + + # distribution_type: JOURNALPOST + # distribution_type: DOKUMENT + distribution_type: FORMULAR + + xml_template: | + + +
+ urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + {{ submission.completed.value|date("Y-m-d") }} + {{ handler.settings.sf2900.kle_emne }} + + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ submission.completed.value|date("Y-m-d") }} + + + + xsd_url: module://os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/Anmodning.xsd + + os2forms_fordelingskomponent_sf2900_invalid_twig: + id: os2forms_fordelingskomponent_sf2900 + handler_id: os2forms_fordelingskomponent_sf2900_invalid_twig + label: "Fordelingskomponent (sf2900) (invalid Twig)" + notes: "" + status: false + conditions: {} + weight: 0 + settings: + sf2900: + kle_emne: 01.01.01 + handling_facet: "" + attachment_element: attachment + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" + + # distribution_type: JOURNALPOST + # distribution_type: DOKUMENT + distribution_type: FORMULAR + + xml_template: | + + +
+ urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + {{ submission.completed.value|date("Y-m-d") }} + {{ handler.settings.sf2900.kle_emne +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ submission.completed.value|date("Y-m-d") }} + +
+ + xsd_url: module://os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/Anmodning.xsd + + os2forms_fordelingskomponent_sf2900_invalid_xml: + id: os2forms_fordelingskomponent_sf2900 + handler_id: os2forms_fordelingskomponent_sf2900_invalid_xml + label: "Fordelingskomponent (sf2900) (invalid XML)" + notes: "" + status: false + conditions: {} + weight: 0 + settings: + sf2900: + kle_emne: 01.01.01 + handling_facet: "" + attachment_element: attachment + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" + + # distribution_type: JOURNALPOST + # distribution_type: DOKUMENT + distribution_type: FORMULAR + + xml_template: | + + +
+ urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + {{ submission.completed.value|date("Y-m-d") }} + {{ handler.settings.sf2900.kle_emne }} +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ submission.completed.value|date("Y-m-d") }} + +
+ + xsd_url: module://os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP501.xsd variants: {} elements: |- - afsend_content_pdf: - '#type': 'entity_print_attachment:pdf' - '#title': 'Fordelingskomponent (PDF)' - '#display_on': view - '#filename': hat-og-briller.pdf ansoeger_oplysninger: '#type': fieldset '#title': AnsoegerOplysninger @@ -344,3 +479,16 @@ elements: |- Kiropraktik: Kiropraktik Psykologhjaelp: Psykologhjaelp Hoereappartbehandling: Hoereapparatbehandling + afsend_content_pdf: + '#type': 'entity_print_attachment:pdf' + '#title': 'Fordelingskomponent (PDF) hest' + '#display_on': view + '#filename': hat-og-briller.pdf + attachment: + '#type': os2forms_attachment + '#title': attachment + '#export_type': pdf + '#digital_signature': 0 + '#excluded_elements': { } + '#exclude_empty': 0 + '#exclude_empty_checkbox': 0 diff --git a/modules/os2forms_fordelingskomponent_examples/resources/xsd/Anmodning.xsd b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/Anmodning.xsd similarity index 100% rename from modules/os2forms_fordelingskomponent_examples/resources/xsd/Anmodning.xsd rename to modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/Anmodning.xsd diff --git a/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP241.xsd b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP241.xsd new file mode 100644 index 0000000..5891e6f --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP241.xsd @@ -0,0 +1,180 @@ + + + + + + Ansoegning om helbredstillaeg + + + + + + + + + + + + + + + + - Overslag +- Faktura +- Bilag + + + + + + + + + + + + + + + Ved ansoegning med login + + + + + + + + + + + + + + Ved ansoegning uden login + + + + + + + + + + + + + + + + + Medlem af Sygeforsikringen »danmark« + + + + + + - Gruppe 1 +- Gruppe 2 +- Gruppe 5 +- Gruppe E +- Gruppe N +- Gruppe S +- Gruppe Basis + + + + + + + + + + + Kender rettigheder og pligter om behandling af personoplysninger + + + + + + + + + + + + + + + + + + + + + + + + Personnummer + + + + + + + + + Telefonnummer + + + + + + + + Tilkendegiver accept + + + + + + + + Angiver dokumenttype + + + + + + + + + + Angiver sygesikringstype + + + + + + + + + + + + + + Angiver generell max længde for strænger + + + + + + + + Myndighedstype + + + + + + diff --git a/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP242.xsd b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP242.xsd new file mode 100644 index 0000000..fe79ec8 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP242.xsd @@ -0,0 +1,206 @@ + + + + + + Ansoegning om udvidet helbredstillaeg + + + + + + + + + + + + + + + + + - Overslag +- Faktura +- Bilag + + + + + + + + + + + + + + + Ved ansoegning med login + + + + + + + + + + + + + + Ved ansoegning uden login + + + + + + + + + + + + + + + + + - Tandprotese +Tilbud (VT 701, VT 704 eller tilsvarende) fra tandtekniker/tandlaege afgives via digital loesning på virk.dk +- Briller +Specificeret overslag (SP 246 eller tilsvarende) fra optiker/oejenlæge skal medunderskrives og afgives via digital +loesning på virk.dk +- Fodbehandling +Ansoegning (SP 247 eller tilsvarende) skal medunderskrives af fodterapeut/fodplejer og afgives via +digital loesning på virk.dk + + + + + Medlem af Sygeforsikringen »danmark« + + + + + + - Gruppe 1 +- Gruppe 2 +- Gruppe 5 +- Gruppe E +- Gruppe N +- Gruppe S +- Gruppe Basis + + + + + + + + + + + + + + + + Samtykke til at kommunen kan tage kontakt til den konkrete behandler (tandlaege/tandtekniker/ +fodplejer/fodterapeut eller optiker) for eventuel afklaring + + + + + Kender rettigheder og pligter om behandling af personoplysninger + + + + + + + + + + + + + + + + Personnummer + + + + + + + + + Telefonnummer + + + + + + + + Tilkendegiver accept + + + + + + + + Angiver dokumenttype + + + + + + + + + + Angiver typen af udvidet helbredstillaeg + + + + + + + + + + Angiver sygesikringstype + + + + + + + + + + + + + + Angiver generell max længde for strænger + + + + + + + + Myndighedstype + + + + + + diff --git a/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP246.xsd b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP246.xsd new file mode 100644 index 0000000..ad73b94 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP246.xsd @@ -0,0 +1,275 @@ + + + + + + Specificeret overslag fra optiker/oejenlaege + + + + + + + + + + + + + + + + - Overslag + - Faktura + - Bilag + + + + + + + + + + + + + + + Har briller/kontaktlinser + + + + + + Brillens/kontaktlinsernes alder + + + + + + + + Anden/yderligere/mere specifik begrundelse + + + + + Specielle forhold, fx allergi, der goer sig gaeldende for valg af stel/glas (husk laegeerklaering) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + I alt + + + + + + + + Optiker/oejenlaege maa videregive overslaget til kommunen til brug for behandling af ansoegning om udvidet helbredstillaeg og kender rettigheder og pligter om behandling af personoplysninger + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Personnummer + + + + + + + + + Telefonnummer + + + + + + + + E-mail address + + + + + + + + Oeje specifikation + + + + + + + + + + + Tilkendegiver accept + + + + + + + + Angiver dokumenttype + + + + + + + + + + Angiver begrundelsen for ny brille + + + + + + + + + + + Myndighedstype + + + + + + + + Angiver generell max længde for strænger + + + + + + + + Angiver max længde for decimaler + + + + + + + + + Angiver max længde for decimaler + + + + + + + + + + Angiver max længde for positive heltal + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP501.xsd b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP501.xsd new file mode 100644 index 0000000..7d93503 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/resources/SP/SF2900_XSD/SP501.xsd @@ -0,0 +1,457 @@ + + + + + + Ansoegning om personligt tillaeg + + + + + + + + + + + + + + + + - Overslag + - Faktura + - Bilag + + + + + + + + + + + + + + + Ved ansoegning med login + + + + + + + + + + + + + + + Ved ansoegning uden login + + + + + + + + + + + + + + + + + Oplysninger fra ansoenging som samliv, formaal mm. + + + + + + + + + + + + + + + + + + Oplysninger om borger og evt aegtefaelles formue + + + + + + Oplysninger om borgers formue + + + + + + + + + + + + + + + + + + Oplysninger om aegtefaelle/samlevers formue + + + + + + + + + + + + + + + + + + + + + Oplysninger om borger og evt aegtefaelles indtaegter per maaned + + + + + + Oplysninger om borgers indtaegter per maaned + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Oplysninger om aegtefaelle/samlevers indtaegter per maaned + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Oplysninger om borger og evt aegtefaelles udgifter per maaned + + + + + + Oplysninger om borgers udgifter per maaned + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Oplysninger om aegtefaelle/samlevers udgifter per maaned + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Medlem af Sygeforsikringen »danmark« + + + + + + + + + + Kender rettigheder og pligter om behandling af personoplysninger + + + + + + + + + + + + + + + + + + + + + + + + Personnummer + + + + + + + + + Telefonnummer + + + + + + + + Tilkendegiver accept + + + + + + + + Angiver dokumenttype + + + + + + + + + + E-mail address + + + + + + + + Angiver civilstand + + + + + + + + + + + Angiver boform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Angiver sygesikringstype + + + + + + + + + + + + + + Myndighedstype + + + + + + + + KLE format for personligt- og helbredstillæg + + + + + + diff --git a/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php b/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php index 756e3d5..f5cb7de 100644 --- a/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php +++ b/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php @@ -66,7 +66,7 @@ public function __invoke(WebformInterface $webform, string $webform_handler, Web '#exceptions' => $exceptions, '#warnings' => $warnings, '#template' => $template, - '#context' => $context, + '#context' => $context ?? [], '#xml' => $xml ?? NULL, ]; diff --git a/src/Drush/Commands/AbstractCommand.php b/src/Drush/Commands/AbstractCommand.php index 8af51a4..ec48176 100644 --- a/src/Drush/Commands/AbstractCommand.php +++ b/src/Drush/Commands/AbstractCommand.php @@ -4,6 +4,7 @@ namespace Drupal\os2forms_fordelingskomponent\Drush\Commands; +use Drupal\Core\DependencyInjection\AutowireTrait; use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; use Symfony\Component\Console\Command\Command; @@ -11,6 +12,7 @@ * Abstract base command. */ abstract class AbstractCommand extends Command { + use AutowireTrait; public function __construct( protected readonly FordelingskomponentHelper $helper, diff --git a/src/Drush/Commands/SendJournalnotatCommand.php b/src/Drush/Commands/SendJournalnotatCommand.php index 3dae6bd..8131e36 100644 --- a/src/Drush/Commands/SendJournalnotatCommand.php +++ b/src/Drush/Commands/SendJournalnotatCommand.php @@ -4,9 +4,7 @@ namespace Drupal\os2forms_fordelingskomponent\Drush\Commands; -use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -15,13 +13,7 @@ name: 'os2forms-fordelingskomponent:send:journalnotat', description: 'Send journalnotat', )] -final class SendJournalnotatCommand extends Command { - - public function __construct( - private readonly FordelingskomponentHelper $helper, - ) { - parent::__construct(); - } +final class SendJournalnotatCommand extends AbstractCommand { /** * {@inheritdoc} diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 9a6ff02..bdffc42 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -18,6 +18,7 @@ use ItkDev\Serviceplatformen\SF2900\EnumType\DokumenttypeType; use ItkDev\Serviceplatformen\SF2900\EnumType\FremdriftType; use ItkDev\Serviceplatformen\SF2900\EnumType\LivscyklusKodeType; +use ItkDev\Serviceplatformen\SF2900\EnumType\ObjektTypeType; use ItkDev\Serviceplatformen\SF2900\EnumType\RetningType; use ItkDev\Serviceplatformen\SF2900\EnumType\VariantRolleType; use ItkDev\Serviceplatformen\SF2900\StructType\AttributterListeType; @@ -56,6 +57,18 @@ final class FordelingskomponentHelper implements LoggerInterface { public const string BESKRIVELSE = 'beskrivelse'; public const string BRUGERVENDT_NOEGLE = 'brugervendt_noegle'; + public const string DISTRIBUTION_TYPE = 'distribution_type'; + public const string DISTRIBUTION_TYPE_JOURNALPOST = ObjektTypeType::VALUE_JOURNALPOST; + public const string DISTRIBUTION_TYPE_DOKUMENT = ObjektTypeType::VALUE_DOKUMENT; + public const string DISTRIBUTION_TYPE_FORMULAR = ObjektTypeType::VALUE_FORMULAR; + + public const string JOURNALPOST_MESSAGE = 'journalpost_message'; + + public const string ATTACHMENT_ELEMENT = 'attachment_element'; + + public const string XML_TEMPLATE = 'xml_template'; + public const string XSD_URL = 'xsd_url'; + /** * Constructor. */ @@ -109,7 +122,7 @@ public function sendJournalpost( ) { $msg = sprintf('Fordelingskomponent afsend journalpost.'); // If the cause is a submission, add webform id to audit logging message. - $msg .= $submission ? sprintf(' Webform id %s.', $submission->getWebform()->id()) : ''; + $msg .= sprintf(' Webform id %s.', $submission->getWebform()->id()); $this->auditLogger->info('Fordelingskomponent', $msg); } @@ -213,7 +226,7 @@ public function sendDokument( $msg = sprintf('Fordelingskomponent afsend dokument.'); // If the cause is a submission, add webform id to audit logging message. - $msg .= $submission ? sprintf(' Webform id %s.', $submission->getWebform()->id()) : ''; + $msg .= sprintf(' Webform id %s.', $submission->getWebform()->id()); $this->auditLogger->info('Fordelingskomponent', $msg); return [$response, $sf2900->getLastRequest()]; diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index c523d29..e275e2d 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -105,7 +105,7 @@ public function afsend(WebformSubmissionInterface $submission, array $handlerSet */ protected function getAttachment(WebformSubmissionInterface $submission, array $handlerSettings): Attachment { // Lifted from Drupal\webform_attachment\Controller\WebformAttachmentController::download. - $element = $handlerSettings[WebformHandlerSF2900::ATTACHMENT_ELEMENT]; + $element = $handlerSettings[FordelingskomponentHelper::ATTACHMENT_ELEMENT]; $element = $submission->getWebform()->getElement($element) ?: []; [$type] = explode(':', $element['#type']); $instance = $this->elementInfoManager->createInstance($type); @@ -138,7 +138,7 @@ public function loadSubmission(int $id): ?WebformSubmissionInterface { private function loadQueue(): ?QueueInterface { $processingSettings = $this->helper->getModuleConfig()->get(SettingsForm::SECTION_PROCESSING); - /** @var \Drupal\advancedqueue\Entity\QueueInterface $queue */ + /** @var ?\Drupal\advancedqueue\Entity\QueueInterface $queue */ $queue = $this->queueStorage->load($processingSettings['queue'] ?? NULL); return $queue; @@ -198,7 +198,7 @@ public function createJob(WebformSubmissionInterface $webformSubmission, array $ return $job; } catch (\Exception $exception) { - $this->error('Error creating job for afsen.', $context + [ + $this->error('Error creating job for afsend.', $context + [ 'operation' => 'Fordelingskomponent afsend failed', ]); return NULL; diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index 7f4787f..fd489e9 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -46,7 +46,7 @@ private function useTwig(callable $callback): mixed { $this->twig->enableStrictVariables(); return $callback(); } finally { - if ($strictVariables) { + if (isset($strictVariables) && $strictVariables) { $this->twig->enableStrictVariables(); } else { diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index d4e5c27..f7306f0 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -5,8 +5,10 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; +use Drupal\os2forms_fordelingskomponent\Helper\XmlHelper; use Drupal\webform\Plugin\WebformHandlerBase; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -30,19 +32,24 @@ final class WebformHandlerSF2900 extends WebformHandlerBase { public const string ID = 'os2forms_fordelingskomponent_sf2900'; public const string SECTION_SF2900 = 'sf2900'; - public const string ATTACHMENT_ELEMENT = 'attachment_element'; /** * The webform helper. */ private readonly WebformHelperSF2900 $helper; + /** + * The XML helper. + */ + private readonly XmlHelper $xmlHelper; + /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->helper = $container->get(WebformHelperSF2900::class); + $instance->xmlHelper = $container->get(XmlHelper::class); return $instance; } @@ -51,15 +58,15 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $form[static::SECTION_SF2900] = [ + $form[self::SECTION_SF2900] = [ '#type' => 'fieldset', '#title' => $this->t('Fordelingskomponent'), '#tree' => TRUE, ]; - $configuration = $this->configuration[static::SECTION_SF2900] ?? NULL; + $configuration = $this->configuration[self::SECTION_SF2900] ?? NULL; - $form[static::SECTION_SF2900][FordelingskomponentHelper::KLE_EMNE] = [ + $form[self::SECTION_SF2900][FordelingskomponentHelper::KLE_EMNE] = [ '#title' => $this->t('KLE-emne'), '#type' => 'textfield', '#default_value' => $configuration[FordelingskomponentHelper::KLE_EMNE] ?? NULL, @@ -70,7 +77,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#description' => $this->t('KLE-emne (format: dd.dd.dd)'), ]; - $form[static::SECTION_SF2900][FordelingskomponentHelper::HANDLING_FACET] = [ + $form[self::SECTION_SF2900][FordelingskomponentHelper::HANDLING_FACET] = [ '#title' => $this->t('Handling-facet'), '#type' => 'textfield', '#default_value' => $configuration[FordelingskomponentHelper::HANDLING_FACET] ?? NULL, @@ -79,16 +86,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ], ]; - $availableElements = $this->getAttachmentElements(); - $form[static::SECTION_SF2900][static::ATTACHMENT_ELEMENT] = [ - '#type' => 'select', - '#title' => $this->t('Element that contains the document to send'), - '#default_value' => $configuration[static::ATTACHMENT_ELEMENT] ?? NULL, - '#required' => TRUE, - '#options' => $availableElements, - ]; - - $form[static::SECTION_SF2900][FordelingskomponentHelper::BRUGERVENDT_NOEGLE] = [ + $form[self::SECTION_SF2900][FordelingskomponentHelper::BRUGERVENDT_NOEGLE] = [ '#title' => $this->t('Brugervendt nøgle'), '#type' => 'textfield', '#default_value' => $configuration[FordelingskomponentHelper::BRUGERVENDT_NOEGLE] ?? NULL, @@ -96,37 +94,139 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#description' => 'WHAT IS THIS?!', ]; - $form[static::SECTION_SF2900][FordelingskomponentHelper::TITEL] = [ + $form[self::SECTION_SF2900][FordelingskomponentHelper::TITEL] = [ '#title' => $this->t('Titel'), '#type' => 'textfield', '#default_value' => $configuration[FordelingskomponentHelper::TITEL] ?? NULL, '#required' => TRUE, ]; - $form[static::SECTION_SF2900][FordelingskomponentHelper::BESKRIVELSE] = [ + $form[self::SECTION_SF2900][FordelingskomponentHelper::BESKRIVELSE] = [ '#title' => $this->t('Beskrivelse'), '#type' => 'textarea', '#default_value' => $configuration[FordelingskomponentHelper::BESKRIVELSE] ?? NULL, '#required' => TRUE, ]; + $this->buildDistributionForm($form, $configuration, $form_state); + return $this->setSettingsParents($form); } + /** + * Build distribution form. + */ + private function buildDistributionForm(array &$form, array $configuration): void { + $form[self::SECTION_SF2900][FordelingskomponentHelper::DISTRIBUTION_TYPE] = [ + '#title' => $this->t('Distribution type'), + '#type' => 'select', + '#options' => [ + FordelingskomponentHelper::DISTRIBUTION_TYPE_JOURNALPOST => $this->t('Journalpost'), + FordelingskomponentHelper::DISTRIBUTION_TYPE_DOKUMENT => $this->t('Dokument'), + FordelingskomponentHelper::DISTRIBUTION_TYPE_FORMULAR => $this->t('Formular'), + ], + '#default_value' => $configuration[FordelingskomponentHelper::DISTRIBUTION_TYPE] ?? NULL, + '#required' => TRUE, + ]; + + /* + * Set "visible" and "required" states on element depending on + * distribution types. + */ + $setStates = function (array &$element, array $distributionTypes, string $connector = 'or', bool $require = TRUE): void { + $conditions = array_map(static fn (string $type) => ['value' => $type], $distributionTypes); + // Insert connector between all conditions. + $numberOfValues = count($conditions); + for ($i = 0; $i < $numberOfValues - 1; $i++) { + array_splice($conditions, 2 * $i + 1, 0, [$connector]); + } + $state = [ + ':input[name="settings[' . self::SECTION_SF2900 . '][' . FordelingskomponentHelper::DISTRIBUTION_TYPE . ']"]' => $conditions, + ]; + $element['#states']['visible'][] = $state; + if ($require) { + $element['#states']['required'][] = $state; + } + }; + + $availableElements = $this->getAttachmentElements(); + $form[self::SECTION_SF2900][FordelingskomponentHelper::ATTACHMENT_ELEMENT] = [ + '#type' => 'select', + '#title' => $this->t('Element that contains the document to send'), + '#default_value' => $configuration[FordelingskomponentHelper::ATTACHMENT_ELEMENT] ?? NULL, + '#options' => $availableElements, + ]; + $setStates($form[self::SECTION_SF2900][FordelingskomponentHelper::ATTACHMENT_ELEMENT], [ + FordelingskomponentHelper::DISTRIBUTION_TYPE_DOKUMENT, + FordelingskomponentHelper::DISTRIBUTION_TYPE_FORMULAR, + ]); + + $form[self::SECTION_SF2900][FordelingskomponentHelper::XML_TEMPLATE] = [ + '#type' => 'textarea', + '#rows' => 30, + '#title' => $this->t('XML template'), + '#default_value' => $configuration[FordelingskomponentHelper::XML_TEMPLATE] ?? NULL, + ]; + $setStates($form[self::SECTION_SF2900][FordelingskomponentHelper::XML_TEMPLATE], [ + FordelingskomponentHelper::DISTRIBUTION_TYPE_FORMULAR, + ]); + + $form[self::SECTION_SF2900][FordelingskomponentHelper::XSD_URL] = [ + '#type' => 'textfield', + '#title' => $this->t('XSD URL'), + '#default_value' => $configuration[FordelingskomponentHelper::XSD_URL] ?? NULL, + ]; + $setStates($form[self::SECTION_SF2900][FordelingskomponentHelper::XSD_URL], [ + FordelingskomponentHelper::DISTRIBUTION_TYPE_FORMULAR, + ], require: FALSE); + + $form[self::SECTION_SF2900][FordelingskomponentHelper::JOURNALPOST_MESSAGE] = [ + '#type' => 'textarea', + '#rows' => 30, + '#title' => $this->t('Message'), + '#default_value' => $configuration[FordelingskomponentHelper::JOURNALPOST_MESSAGE] ?? NULL, + ]; + $setStates($form[self::SECTION_SF2900][FordelingskomponentHelper::JOURNALPOST_MESSAGE], [ + FordelingskomponentHelper::DISTRIBUTION_TYPE_JOURNALPOST, + ]); + } + /** * {@inheritdoc} */ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { - $values = $form_state->getValue(static::SECTION_SF2900); + $values = $form_state->getValue(self::SECTION_SF2900); $kleEMne = $values[FordelingskomponentHelper::KLE_EMNE]; if (!preg_match('/' . FordelingskomponentHelper::KLE_EMNE_PATTERN . '/', (string) $kleEMne)) { - $form_state->setErrorByName(static::SECTION_SF2900 . '][' . FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne])); + $form_state->setErrorByName(self::SECTION_SF2900 . '][' . FordelingskomponentHelper::KLE_EMNE, $this->t('Invalid KLE-emne: %kle_emne.', ['%kle_emne' => $kleEMne])); } - $handling_facet = $values[FordelingskomponentHelper::HANDLING_FACET]; - if (!empty($handling_facet) && !preg_match('/' . FordelingskomponentHelper::HANDLING_FACET_PATTERN . '/', (string) $handling_facet)) { - $form_state->setErrorByName(static::SECTION_SF2900 . '][' . FordelingskomponentHelper::HANDLING_FACET, - $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $handling_facet])); + $handlingFacet = $values[FordelingskomponentHelper::HANDLING_FACET]; + if (!empty($handlingFacet) && !preg_match('/' . FordelingskomponentHelper::HANDLING_FACET_PATTERN . '/', (string) $handlingFacet)) { + $form_state->setErrorByName(self::SECTION_SF2900 . '][' . FordelingskomponentHelper::HANDLING_FACET, + $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $handlingFacet])); + } + + $type = $values[FordelingskomponentHelper::DISTRIBUTION_TYPE]; + if (FordelingskomponentHelper::DISTRIBUTION_TYPE_FORMULAR === $type) { + $template = (string) ($values[FordelingskomponentHelper::XML_TEMPLATE] ?? NULL); + try { + $this->xmlHelper->validateXml($template); + $this->xmlHelper->validateTemplate($template); + } + catch (InvalidXmlTemplateException $e) { + $form_state->setErrorByName(self::SECTION_SF2900 . '][' . FordelingskomponentHelper::XML_TEMPLATE, + $this->t('Invalid XML template: %message.', ['%message' => $e->getMessage()])); + } + + $url = (string) ($values[FordelingskomponentHelper::XSD_URL] ?? NULL); + if ($url) { + $contents = @file_get_contents($url); + if (FALSE === $contents) { + $form_state->setErrorByName(self::SECTION_SF2900 . '][' . FordelingskomponentHelper::XSD_URL, + $this->t('Cannot read XSD URL %url.', ['%url' => $url])); + } + } } parent::validateConfigurationForm($form, $form_state); @@ -138,19 +238,22 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); - $this->configuration[static::SECTION_SF2900] = $form_state->getValue(static::SECTION_SF2900); + $this->configuration[self::SECTION_SF2900] = $form_state->getValue(self::SECTION_SF2900); } /** * {@inheritdoc} */ public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { + if (!$this->isEnabled()) { + return; + } // Run only when submission is completed. if (!$webform_submission->isCompleted()) { return; } - $this->helper->createJob($webform_submission, $this->configuration[static::SECTION_SF2900]); + $this->helper->createJob($webform_submission, $this->configuration[self::SECTION_SF2900]); } /** From dd089cb162bd5552a6502848c14733a05e94651a Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 18 Feb 2026 16:22:59 +0100 Subject: [PATCH 15/62] Test and refactoring --- .../webform.webform.os2_fdk_kp_anmoding.yml | 16 +- ...bform.webform.os2forms_fdk_example_000.yml | 12 +- ...bform.webform.os2forms_fdk_example_001.yml | 2 +- os2forms_fordelingskomponent.routing.yml | 17 +- ...ntDistributionObjectPreviewController.php} | 11 +- ...ributionObjectPreviewRenderController.php} | 43 +-- src/Helper/FordelingskomponentHelper.php | 264 +++++++++++++----- src/Helper/WebformHelperSF2900.php | 38 ++- src/Helper/XmlHelper.php | 5 +- src/Hook/Hooks.php | 9 +- .../WebformHandler/WebformHandlerSF2900.php | 7 +- ...stribution-object-preview-render.html.twig | 57 ++++ ...ent-distribution-object-preview.html.twig} | 8 +- ...onent-payload-preview-render-xml.html.twig | 32 --- 14 files changed, 346 insertions(+), 175 deletions(-) rename src/Controller/{Os2formsFordelingskomponentPayloadPreviewController.php => Os2formsFordelingskomponentDistributionObjectPreviewController.php} (85%) rename src/Controller/{Os2formsFordelingskomponentPayloadPreviewRenderController.php => Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php} (50%) create mode 100644 templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig rename templates/{os2forms-fordelingskomponent-payload-preview.html.twig => os2forms-fordelingskomponent-distribution-object-preview.html.twig} (82%) delete mode 100644 templates/os2forms-fordelingskomponent-payload-preview-render-xml.html.twig diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml index 98eb0fc..41af46f 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml @@ -279,9 +279,9 @@ handlers:
- urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.sf2900.kle_emne }} + {{ handler.settings.kle_emne }}
@@ -326,9 +326,9 @@ handlers:
- urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.sf2900.kle_emne }} + {{ handler.settings.kle_emne }} {{ submission.data.fornavn }} @@ -372,9 +372,9 @@ handlers:
- urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.sf2900.kle_emne + {{ handler.settings.kle_emne
@@ -419,9 +419,9 @@ handlers:
- urn:oio:cvr-nr:{{ handler.settings.sf2900.cvr|default(module.settings.sf2900.sender_id|default("")) }} + urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.sf2900.kle_emne }} + {{ handler.settings.kle_emne }}
diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml index 93119bc..b0048d1 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_000.yml @@ -279,9 +279,13 @@ handlers: settings: sf2900: kle_emne: 01.01.01 - handling_facet: G87 + handling_facet: '' + brugervendt_noegle: 'What is “Brugervendt nøgle”?' + titel: 'Your document' + beskrivelse: 'This is a very important document' + distribution_type: DOKUMENT attachment_element: afsend_content_pdf - brugervendt_noegle: "What is “Brugervendt nøgle”?" - titel: "Your document" - beskrivelse: "This is a very important document" + xml_template: '' + xsd_url: '' + journalpost_message: '' variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml index 990a08f..437df83 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_example_001.yml @@ -279,7 +279,7 @@ handlers: settings: sf2900: kle_emne: 01.01.01 - handling_facet: G87 + handling_facet: "" attachment_element: afsend_content_pdf brugervendt_noegle: "What is “Brugervendt nøgle”?" titel: "Your document" diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index 2dc39d4..b75902e 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -18,23 +18,24 @@ os2forms_fordelingskomponent.routing_info: requirements: _permission: "administer site configuration" -os2forms_fordelingskomponent.fordelingskomponent_payload.preview: - path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{webform_handler}/preview" +os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview: + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/fordelingsobjekt/{webform_handler}/preview" defaults: - _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentPayloadPreviewController' - _title: "Fordelingskomponent payload preview" + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentDistributionObjectPreviewController' + _title: "Fordelingskomponent fordelingsobjekt preview" options: parameters: webform: type: "entity:webform" requirements: + # @todo Harden this permission to allow only access when user has access to webform. _permission: "view any webform submission" -os2forms_fordelingskomponent.fordelingskomponent_payload.preview_render: - path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/payload/{webform_handler}/preview/render/{submission}" +os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview_render: + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/fordelingsobjekt/{webform_handler}/preview/render/{submission}" defaults: - _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentPayloadPreviewRenderController' - _title: "Fordelingskomponent payload preview" + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentDistributionObjectPreviewRenderController' + _title: "Fordelingskomponent fordelingsobjekt preview" options: parameters: webform: diff --git a/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php similarity index 85% rename from src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php rename to src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 96bd412..3bd7340 100644 --- a/src/Controller/Os2formsFordelingskomponentPayloadPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -15,7 +15,7 @@ /** * Returns responses for Fordelingskomponent routes. */ -final class Os2formsFordelingskomponentPayloadPreviewController extends ControllerBase { +final class Os2formsFordelingskomponentDistributionObjectPreviewController extends ControllerBase { /** * The webform submission storage. @@ -33,6 +33,8 @@ public function __construct( */ public function __invoke(Request $request, WebformInterface $webform, string $webform_handler): array|Response { $handler = $webform->getHandler($webform_handler); + + // Get previous, self and next submission IDs. $submissionIds = array_keys($this->submissionStorage->getQuery() ->accessCheck() ->condition('webform_id', $webform->id()) @@ -45,8 +47,9 @@ public function __invoke(Request $request, WebformInterface $webform, string $we $index = array_search($currentSubmission, $submissionIds); } + $routeName = $request->attributes->get('_route'); $previewUrls = array_map( - static fn($submission) => Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_payload.preview', [ + static fn($submission) => Url::fromRoute($routeName, [ 'webform' => $webform->id(), 'webform_handler' => $handler->getHandlerId(), 'submission' => $submission, @@ -59,7 +62,7 @@ public function __invoke(Request $request, WebformInterface $webform, string $we ); $renderUrl = NULL !== $currentSubmission - ? Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_payload.preview_render', [ + ? Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview_render', [ 'webform' => $webform->id(), 'webform_handler' => $handler->getHandlerId(), 'submission' => $currentSubmission, @@ -67,7 +70,7 @@ public function __invoke(Request $request, WebformInterface $webform, string $we : NULL; return [ - '#theme' => 'os2forms_fordelingskomponent_payload_preview', + '#theme' => 'os2forms_fordelingskomponent_distribution_object_preview', '#webform' => $webform, '#handler' => $handler, '#submission' => $currentSubmission, diff --git a/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php similarity index 50% rename from src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php rename to src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php index f5cb7de..6076ad8 100644 --- a/src/Controller/Os2formsFordelingskomponentPayloadPreviewRenderController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php @@ -6,8 +6,7 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Render\RendererInterface; -use Drupal\os2forms_fordelingskomponent\Exception\Exception; -use Drupal\os2forms_fordelingskomponent\Helper\XmlHelper; +use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\HttpFoundation\Response; @@ -15,10 +14,10 @@ /** * Returns responses for Fordelingskomponent routes. */ -final class Os2formsFordelingskomponentPayloadPreviewRenderController extends ControllerBase { +final class Os2formsFordelingskomponentDistributionObjectPreviewRenderController extends ControllerBase { public function __construct( - private readonly XmlHelper $xmlHelper, + private readonly WebformHelperSF2900 $helper, private readonly RendererInterface $renderer, ) { } @@ -33,41 +32,29 @@ public function __invoke(WebformInterface $webform, string $webform_handler, Web $exceptions = []; $warnings = []; - $template = $handlerSettings['xml_template'] ?? NULL; - if (NULL === $template) { - // @todo Handle this - } - - /** @var ?string $xml */ + // @todo Set these for a better preview in case of errors. $xml = NULL; - try { - $context = $this->xmlHelper->getRenderContext($handler, $submission); - $xml = $this->xmlHelper->render($template, $context); + $context = []; - $this->xmlHelper->validateXml($xml); - - $xsdUrl = $handlerSettings['xsd_url'] ?? NULL; - if (NULL === $xsdUrl) { - $warnings[] = new \RuntimeException('XSD URL not defined'); - } - else { - $this->xmlHelper->validateXml($xml, $xsdUrl, loadXsdContent: TRUE); - } + try { + $distributionObject = $this->helper->buildDistributionObject($submission, $handlerSettings); } - catch (Exception $e) { - $exceptions[] = $e; + catch (\Exception $exception) { + $exceptions[] = $exception; } $build = [ - '#theme' => 'os2forms_fordelingskomponent_payload_preview_render_xml', + '#theme' => 'os2forms_fordelingskomponent_distribution_object_preview_render', '#webform' => $webform, '#handler' => $handler, + '#handler_settings' => $handlerSettings, '#submission' => $submission, '#exceptions' => $exceptions, '#warnings' => $warnings, - '#template' => $template, - '#context' => $context ?? [], - '#xml' => $xml ?? NULL, + '#distribution_object' => $distributionObject ?? NULL, + '#distribution_type' => $handlerSettings['distribution_type'] ?? NULL, + '#context' => $context, + '#xml' => $xml, ]; return new Response((string) $this->renderer->renderRoot($build)); diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index bdffc42..447d011 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -6,6 +6,8 @@ use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\key\KeyRepositoryInterface; +use Drupal\os2forms_fordelingskomponent\Exception\Exception; +use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; use Drupal\os2forms_fordelingskomponent\Form\SettingsForm; use Drupal\os2forms_fordelingskomponent\Model\Attachment; use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900; @@ -25,7 +27,12 @@ use ItkDev\Serviceplatformen\SF2900\StructType\AttributterType; use ItkDev\Serviceplatformen\SF2900\StructType\DelAttributterType; use ItkDev\Serviceplatformen\SF2900\StructType\DistributionDokumentType; +use ItkDev\Serviceplatformen\SF2900\StructType\DistributionFormularType; +use ItkDev\Serviceplatformen\SF2900\StructType\DistributionJournalPostType; use ItkDev\Serviceplatformen\SF2900\StructType\DokumentRegistreringType; +use ItkDev\Serviceplatformen\SF2900\StructType\FormularType; +use ItkDev\Serviceplatformen\SF2900\StructType\FormularXMLType; +use ItkDev\Serviceplatformen\SF2900\StructType\MeddelelseType; use ItkDev\Serviceplatformen\SF2900\StructType\RelationsListe; use ItkDev\Serviceplatformen\SF2900\StructType\TilstandListeType; use ItkDev\Serviceplatformen\SF2900\StructType\TilstandType; @@ -75,6 +82,7 @@ final class FordelingskomponentHelper implements LoggerInterface { public function __construct( private readonly ConfigFactoryInterface $configFactory, private readonly EventDispatcherInterface $eventDispatcher, + private readonly XmlHelper $xmlHelper, #[Autowire(service: 'key.repository')] private readonly KeyRepositoryInterface $keyRepository, private readonly KeyHelper $keyHelper, @@ -126,6 +134,192 @@ public function sendJournalpost( $this->auditLogger->info('Fordelingskomponent', $msg); } + /** + * + */ + public function buildDistributionObject( + WebformSubmissionInterface $submission, + array $configuration, + string $brugervendtNoegle, + string $titel, + string $beskrivelse, + ): DistributionFormularType|DistributionDokumentType|DistributionJournalPostType { + $virkning = $this->buildVirkning($configuration); + + $id = Serializer::createUuid(); + $fraTidsPunkt = new \DateTime(); + $brevDato = new \DateTime(); + $registreringItSystem = $configuration[SettingsForm::REGISTRERING_IT_SYSTEM]; + + $routingKLEEmne = $configuration[self::KLE_EMNE]; + $handlingFacet = (string) ($configuration[self::HANDLING_FACET] ?? NULL); + if (empty(trim($handlingFacet))) { + $handlingFacet = NULL; + } + + $type = $configuration[self::DISTRIBUTION_TYPE]; + $distributionObject = match ($type) { + self::DISTRIBUTION_TYPE_JOURNALPOST => $this->buildDistributionJournalPostType( + + ), + self::DISTRIBUTION_TYPE_DOKUMENT => $this->buildDistributionDokumentType( + $id, + $fraTidsPunkt, + $brevDato, + $registreringItSystem, + $routingKLEEmne, + $handlingFacet, + $virkning, + $brugervendtNoegle, + $titel, + $beskrivelse, + $submission, + $configuration, + ), + self::DISTRIBUTION_TYPE_FORMULAR => $this->buildDistributionFormularType( + $id, + $fraTidsPunkt, + $brevDato, + $registreringItSystem, + $routingKLEEmne, + $handlingFacet, + $submission, + $configuration, + ), + default => throw new Exception(sprintf('Invalid distribution type: %s', $type)), + }; + + return $distributionObject; + } + + /** + * + */ + private function buildDistributionJournalPostType(): DistributionJournalPostType { + throw new \RuntimeException(__METHOD__ . ' not implemented'); + } + + /** + * + */ + private function buildDistributionDokumentType( + $id, + $fraTidsPunkt, + $brevDato, + $registreringItSystem, + $routingKLEEmne, + ?string $handlingFacetForslag, + VirkningType $virkning, + string $brugervendtNoegle, + string $titel, + string $beskrivelse, + WebformSubmissionInterface $submission, + array $configuration, + ): DistributionDokumentType { + return new DistributionDokumentType( + iD: $id, + kLEEmneForslag: $routingKLEEmne, + handlingFacetForslag: $handlingFacetForslag, + registrering: new DokumentRegistreringType( + fraTidsPunkt: SF2900::formatDateTime($fraTidsPunkt), + livscyklusKode: LivscyklusKodeType::VALUE_OPRETTET, + registreringItSystem: new UUID_URN($registreringItSystem), + relationListe: new RelationsListe( + variantListe: new VariantListeType([ + new VariantType( + // If we don't clone the “virking", the XML serializer adds an + // ID and references which SF2900 does not handle. + virkning: $this->clone($virkning), + rolle: VariantRolleType::VALUE_VARIANT, + indeks: '1', + variantAttributter: new VariantAttributterType( + variantType: 'PDF', + ), + delAttributter: new DelAttributterType( + delTekst: 'Hele dokumentet', + ), + ), + ]), + ), + tilstandsListe: [ + new TilstandListeType( + tilstand: [ + new TilstandType( + // @todo Hvad er fremdrift? + fremdrift: FremdriftType::VALUE_ENDELIGT, + virkning: $this->clone($virkning), + ), + ] + ), + ], + attributListe: new AttributterListeType([ + new AttributterType( + brugervendtNoegleTekst: $brugervendtNoegle, + titelTekst: $titel, + beskrivelseTekst: $beskrivelse, + dokumenttype: DokumenttypeType::VALUE_ANDEN, + retning: RetningType::VALUE_UDGAAENDE, + brevdato: SF2900::formatDate($brevDato), + virkning: $this->clone($virkning), + ), + ]), + // importTidspunkt: null, + // brugerRef: null,. + ) + ); + } + + /** + * @throws \Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException + * @throws \Drupal\os2forms_fordelingskomponent\Exception\RuntimeException + */ + private function buildDistributionFormularType( + $id, + $fraTidsPunkt, + $brevDato, + $registreringItSystem, + $routingKLEEmne, + ?string $handlingFacetForslag, + WebformSubmissionInterface $submission, + array $configuration, + ): DistributionFormularType { + $template = $configuration[self::XML_TEMPLATE] ?? NULL; + if (NULL === $template) { + throw new RuntimeException('Missing XML template'); + } + + /** @var ?string $xml */ + $xml = NULL; + $context = $this->xmlHelper->getRenderContext($configuration, $submission); + $xml = $this->xmlHelper->render($template, $context); + + $this->xmlHelper->validateXml($xml); + + $xsdUrl = $configuration[self::XSD_URL] ?? NULL; + if (NULL !== $xsdUrl) { + $this->xmlHelper->validateXml($xml, $xsdUrl, loadXsdContent: TRUE); + } + + $meddelelse = new MeddelelseType( + // @todo What is this?! + formularType: __METHOD__, + formular: new FormularType( + // @todo + titelTekst: __METHOD__, + formatNavn: __METHOD__, + formularIndhold: __METHOD__, + formularXML: new FormularXMLType($xml), + ), + ); + + return new DistributionFormularType( + iD: $id, + kLEEmneForslag: $routingKLEEmne, + meddelelse: $meddelelse, + handlingFacetForslag: $handlingFacetForslag, + ); + } + /** * Send dokument. * @@ -136,84 +330,22 @@ public function sendJournalpost( */ public function sendDokument( WebformSubmissionInterface $submission, + DistributionFormularType|DistributionDokumentType|DistributionJournalPostType $dokument, Attachment $attachment, array $configuration, - // @todo Hvad er brugervendt nøgle? - string $brugervendtNoegle, - string $titel, - string $beskrivelse, ) { + $sf2900 = $this->sf2900(); $sftp = $sf2900->sftp(); - - $transactionId = Serializer::createUuid(); $dokumentFilNavn = $sftp->putContents($attachment->contents, $attachment->filename); - $virkning = $this->buildVirkning($configuration); - - $id = Serializer::createUuid(); - $fraTidsPunkt = new \DateTime(); - $brevDato = new \DateTime(); - $routingKLEEmne = $configuration[self::KLE_EMNE]; - $registreringItSystem = $configuration[SettingsForm::REGISTRERING_IT_SYSTEM]; - + $transactionId = Serializer::createUuid(); $routingMyndighed = $configuration[self::ROUTING_MYNDIGHED]; + $routingKLEEmne = $configuration[self::KLE_EMNE]; $routingHandlingFacet = $configuration[self::HANDLING_FACET] ?: NULL; // @todo This is probably not correct! $routingModtagerAktoer = NULL; - $dokument = new DistributionDokumentType( - iD: $id, - kLEEmneForslag: $routingKLEEmne, - // handlingFacetForslag: null,. - registrering: new DokumentRegistreringType( - fraTidsPunkt: SF2900::formatDateTime($fraTidsPunkt), - livscyklusKode: LivscyklusKodeType::VALUE_OPRETTET, - registreringItSystem: new UUID_URN($registreringItSystem), - relationListe: new RelationsListe( - variantListe: new VariantListeType([ - new VariantType( - // If we don't clone the “virking", the XML serializer adds an - // ID and references which SF2900 does not handle. - virkning: $this->clone($virkning), - rolle: VariantRolleType::VALUE_VARIANT, - indeks: '1', - variantAttributter: new VariantAttributterType( - variantType: 'PDF', - ), - delAttributter: new DelAttributterType( - delTekst: 'Hele dokumentet', - ), - ), - ]), - ), - tilstandsListe: [ - new TilstandListeType( - tilstand: [ - new TilstandType( - // @todo Hvad er fremdrift? - fremdrift: FremdriftType::VALUE_ENDELIGT, - virkning: $this->clone($virkning), - ), - ] - ), - ], - attributListe: new AttributterListeType([ - new AttributterType( - brugervendtNoegleTekst: $brugervendtNoegle ?? $titel, - titelTekst: $titel, - beskrivelseTekst: $beskrivelse, - dokumenttype: DokumenttypeType::VALUE_ANDEN, - retning: RetningType::VALUE_UDGAAENDE, - brevdato: SF2900::formatDate($brevDato), - virkning: $this->clone($virkning), - ), - ]), - // importTidspunkt: null, - // brugerRef: null,. - ) - ); - $response = $sf2900->afsend( transactionId: $transactionId, document: $dokument, diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index e275e2d..06ee5b0 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -19,6 +19,9 @@ use Drupal\webform\WebformSubmissionStorageInterface; use Drupal\webform\WebformTokenManagerInterface; use Drupal\webform_attachment\Element\WebformAttachmentBase; +use ItkDev\Serviceplatformen\SF2900\StructType\DistributionDokumentType; +use ItkDev\Serviceplatformen\SF2900\StructType\DistributionFormularType; +use ItkDev\Serviceplatformen\SF2900\StructType\DistributionJournalPostType; use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -62,6 +65,26 @@ public function __construct( $this->queueStorage = $entityTypeManager->getStorage('advancedqueue_queue'); } + /** + * + */ + public function buildDistributionObject(WebformSubmissionInterface $submission, array $handlerSettings, array $submissionData = []): DistributionFormularType|DistributionDokumentType|DistributionJournalPostType { + $submissionData += $submission->getData(); + $configuration = $this->helper->getHandlerConfiguration($handlerSettings); + + $titel = $this->replaceTokens($configuration[FordelingskomponentHelper::TITEL] ?? '', $submission); + $beskrivelse = $this->replaceTokens($configuration[FordelingskomponentHelper::BESKRIVELSE] ?? '', $submission); + $brugervendtNoegle = $this->replaceTokens($configuration[FordelingskomponentHelper::BRUGERVENDT_NOEGLE] ?? '', $submission); + + return $this->helper->buildDistributionObject( + $submission, + $configuration, + titel: $titel, + beskrivelse: $beskrivelse, + brugervendtNoegle: $brugervendtNoegle, + ); + } + /** * Afsend med Fordelingskomponenten. * @@ -79,20 +102,15 @@ public function __construct( * @phpstan-param array $submissionData */ public function afsend(WebformSubmissionInterface $submission, array $handlerSettings, array $submissionData = []): array { - $submissionData = $submissionData + $submission->getData(); - $configuration = $this->helper->getHandlerConfiguration($handlerSettings); + $distributionObject = $this->buildDistributionObject($submission, $handlerSettings, $submissionData); $attachment = $this->getAttachment($submission, $handlerSettings); - - $titel = $this->replaceTokens($configuration[FordelingskomponentHelper::TITEL] ?? '', $submission); - $beskrivelse = $this->replaceTokens($configuration[FordelingskomponentHelper::BESKRIVELSE] ?? '', $submission); - $brugervendtNoegle = $this->replaceTokens($configuration[FordelingskomponentHelper::BRUGERVENDT_NOEGLE] ?? '', $submission); + $configuration = $this->helper->getHandlerConfiguration($handlerSettings); return $this->helper->sendDokument( $submission, - $attachment, $configuration, - titel: $titel, - beskrivelse: $beskrivelse, - brugervendtNoegle: $brugervendtNoegle, + $distributionObject, + $attachment, + $configuration, ); } diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index fd489e9..f4f9661 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -5,7 +5,6 @@ use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; -use Drupal\webform\Plugin\WebformHandlerInterface; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Twig\Environment; @@ -74,12 +73,12 @@ public function render(string $template, array $context): string { /** * Get render context. */ - public function getRenderContext(WebformHandlerInterface $handler, WebformSubmissionInterface $submission) { + public function getRenderContext(array $handlerSettings, WebformSubmissionInterface $submission) { return [ 'module' => [ 'settings' => $this->moduleSettings->get(), ], - 'handler' => $handler, + 'handler' => ['settings' => $handlerSettings], 'submission' => $submission, ]; } diff --git a/src/Hook/Hooks.php b/src/Hook/Hooks.php index 44a7db4..c515aa8 100644 --- a/src/Hook/Hooks.php +++ b/src/Hook/Hooks.php @@ -13,7 +13,7 @@ class Hooks { public function theme(array $existing, string $type, string $theme, string $path): array { { return [ - 'os2forms_fordelingskomponent_payload_preview' => [ + 'os2forms_fordelingskomponent_distribution_object_preview' => [ 'variables' => [ 'webform' => NULL, 'handler' => NULL, @@ -28,16 +28,17 @@ public function theme(array $existing, string $type, string $theme, string $path ], ], - 'os2forms_fordelingskomponent_payload_preview_render_xml' => [ + 'os2forms_fordelingskomponent_distribution_object_preview_render' => [ 'variables' => [ 'webform' => NULL, 'handler' => NULL, + 'handler_settings' => NULL, 'submission' => NULL, 'exceptions' => NULL, 'warnings' => NULL, 'context' => NULL, - 'template' => NULL, - 'xml' => NULL, + 'distribution_object' => NULL, + 'distribution_type' => NULL, ], ], ]; diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index f7306f0..3c114f9 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -245,9 +245,6 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s * {@inheritdoc} */ public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { - if (!$this->isEnabled()) { - return; - } // Run only when submission is completed. if (!$webform_submission->isCompleted()) { return; @@ -296,8 +293,8 @@ public function getSummary() { $items = []; $items[] = Link::createFromRoute( - $this->t('Preview payload'), - 'os2forms_fordelingskomponent.fordelingskomponent_payload.preview', [ + $this->t('Preview distribution object'), + 'os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview', [ 'webform' => $this->getWebform()->id(), 'webform_handler' => $this->getHandlerId(), ] diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig new file mode 100644 index 0000000..98c2cc7 --- /dev/null +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig @@ -0,0 +1,57 @@ + + + + + + {{ 'Preview'|trans }} + + + + +
+ {% for exception in exceptions %} + + {% endfor %} + + {% for warning in warnings %} + + {% endfor %} + + {% if distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Helper\\FordelingskomponentHelper::DISTRIBUTION_TYPE_JOURNALPOST') %} +{{ dump() }} + {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Helper\\FordelingskomponentHelper::DISTRIBUTION_TYPE_DOKUMENT') %} + {{ distribution_type }} +
{{ distribution_object|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
+ {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Helper\\FordelingskomponentHelper::DISTRIBUTION_TYPE_FORMULAR') %} + {% set xml = distribution_object.meddelelse.formular.formularXml.any %} + {% set xml_template = handler_settings['xml_template']|default(null) %} + {% if xml_template %} +
+ {{ 'XML template'|trans }} + + +
+ {% endif %} + +
+ {{ 'XML'|trans }} + +
+ {% else %} + + {% endif %} + +
+ + diff --git a/templates/os2forms-fordelingskomponent-payload-preview.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig similarity index 82% rename from templates/os2forms-fordelingskomponent-payload-preview.html.twig rename to templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig index 5591b5f..bfd8516 100644 --- a/templates/os2forms-fordelingskomponent-payload-preview.html.twig +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig @@ -36,10 +36,14 @@ ', '#markup' => $this->t('KLE-emne: %kle_emne; Handling-facet: %handling_facet', [ - '%kle_emne' => $kleEmne, - '%handling_facet' => $handlingFacet, + '%kle_emne' => $settings->distributionContext->kleEmne, + '%handling_facet' => $settings->distributionContext->handlingFacet, ]), ], ]; @@ -300,12 +352,12 @@ public function getSummary() { ] ); - if ($kleEmne) { + if ($settings->distributionContext->kleEmne) { $items[] = Link::createFromRoute( $this->t('Show routing info'), 'os2forms_fordelingskomponent.routing_info', [ 'webform' => $this->getWebform()->id(), - 'handler' => $this->getHandlerId(), + 'webform_handler' => $this->getHandlerId(), ] ); } diff --git a/src/Settings.php b/src/Settings.php new file mode 100644 index 0000000..a328b58 --- /dev/null +++ b/src/Settings.php @@ -0,0 +1,90 @@ +config = $configFactory->get(self::CONFIG_NAME); + } + + /** + * + */ + public function getGeneralSettings(): GeneralSettings { + return new GeneralSettings($this->getValue(GeneralSettings::NAME)); + } + + /** + * + */ + public function getSenderSettings(array $values = []): SenderSettings { + return (new SenderSettings($this->getValue(SenderSettings::NAME))) + ->apply($values); + } + + /** + * + */ + public function getDistributionContextSettings(array $values = []): DistributionContextSettings { + return (new DistributionContextSettings($this->getValue(DistributionContextSettings::NAME))) + ->apply($values); + } + + /** + * + */ + public function getDistributionObjectSettings(array $values = []): DistributionObjectSettings { + return (new DistributionObjectSettings($this->getValue(DistributionObjectSettings::NAME))) + ->apply($values); + } + + /** + * Get handler settings. + * + * The settings are the global settings with handler specific settings on top. + */ + public function getHandlerSettings(WebformHandlerSF2900 $handler): HandlerSettings { + $handlerSettings = $handler->getSettings(); + $settings = new HandlerSettings([ + HandlerSettings::SENDER => $this->getSenderSettings($handlerSettings[SenderSettings::NAME] ?? []), + HandlerSettings::DISTRIBUTION_CONTEXT => $this->getDistributionContextSettings($handlerSettings[DistributionContextSettings::NAME] ?? []), + HandlerSettings::DISTRIBUTION_OBJECT => $this->getDistributionObjectSettings($handlerSettings[DistributionObjectSettings::NAME] ?? []), + ]); + + return $settings; + } + + /** + * Get settings value. + * + * @return array + * The settings values. + */ + private function getValue(string $section): array { + $values = $this->config->get($section); + + return is_array($values) ? $values : []; + } + +} diff --git a/src/Settings/AbstractSettings.php b/src/Settings/AbstractSettings.php new file mode 100644 index 0000000..dbec11b --- /dev/null +++ b/src/Settings/AbstractSettings.php @@ -0,0 +1,141 @@ + + * $settingsProperties = [ + * 'items' => SomeNestedSettings::class, + * ]; + * + * + * @var array + */ + protected static array $settingsProperties = []; + + /** + * List properties. + * + * Map from name to AbstractSettings type, e.g. + * + * + * $listProperties = [ + * 'items' => SomeNestedSettings::class, + * ]; + * + * + * @var array + */ + protected static array $listProperties = []; + + /** + * Nullable properties. + * + * List of properties that must be set to null if the value is a blank string. + * + * @var array + */ + protected static array $nullableProperties = []; + + /** + * The values. + * + * @var array + */ + protected array $values; + + /** + * Constructor. + * + * @param array $values + * The values. + * @param bool $throwExceptionOnMissingProperty + * If set, an exception is thrown when setting an undefined property. + * If not set, undefined properties are silently ignored. + */ + public function __construct(array $values, bool $throwExceptionOnMissingProperty = FALSE) { + $this->values = []; + $this->apply($values, $throwExceptionOnMissingProperty); + } + + /** + * + */ + public function apply(array $values, bool $throwExceptionOnMissingProperty = FALSE): static { + foreach (static::$listProperties as $property => $class) { + if (isset($values[$property]) && is_array($values[$property])) { + $values[$property] = array_map(static fn(array $vals) => new $class($vals), $values[$property]); + } + } + + foreach (static::$settingsProperties as $property => $class) { + if (isset($values[$property]) && is_array($values[$property])) { + $values[$property] = new $class($values[$property]); + } + } + + foreach ($values as $key => $value) { + $name = self::kebab2camel($key); + if (!property_exists($this, $name)) { + if ($throwExceptionOnMissingProperty) { + throw new \RuntimeException( + $name !== $key + ? sprintf('Property "%s" ("%s") does not exist in class %s.', + $name, $key, static::class) + : sprintf('Property "%s" does not exist in class %s.', $name, + static::class) + ); + } + else { + continue; + } + } + if (isset(static::$nullableProperties[$key]) && empty(trim((string) $value))) { + $value = NULL; + } + $this->$name = $value; + $this->values[self::camel2kebab($name)] = $value; + } + + return $this; + } + + /** + * + */ + public function toArray(): array { + return $this->values; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize(): array { + return $this->toArray(); + } + + /** + * Convert kebab_case to camelCase. + */ + public static function kebab2camel(string $value): string { + return lcfirst(str_replace('_', '', ucwords($value, '_'))); + } + + /** + * Convert camelCase to kebab_case. + * + * @see https://stackoverflow.com/a/40514305/2502647 + */ + public static function camel2kebab(string $value): string { + return strtolower(preg_replace('/(?<=\d)(?=[A-Za-z])|(?<=[A-Za-z])(?=\d)|(?<=[a-z])(?=[A-Z])/', '_', $value)); + } + +} diff --git a/src/Settings/DistributionContextSettings.php b/src/Settings/DistributionContextSettings.php new file mode 100644 index 0000000..8f9a0f8 --- /dev/null +++ b/src/Settings/DistributionContextSettings.php @@ -0,0 +1,38 @@ + TRUE, + ]; + + public const string KLE_EMNE_PATTERN = '^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$'; + public const string KLE_EMNE = 'kle_emne'; + public ?string $kleEmne = NULL; + + public const string HANDLING_FACET_PATTERN = '^[A-Z,Æ,Ø,Å][0-9][0-9]$'; + public const string HANDLING_FACET = 'handling_facet'; + public ?string $handlingFacet = NULL; + + public const string BRUGERVENDT_NOEGLE = 'brugervendt_noegle'; + public ?string $brugervendtNoegle = NULL; + + // @todo Use this? + public const ROUTING_MODTAGER_AKTOER = 'routing_modtager_aktoer'; + public ?string $routingModtagerAktoer = NULL; + + public const string TITEL = 'titel'; + public ?string $titel = NULL; + + public const string BESKRIVELSE = 'beskrivelse'; + public ?string $beskrivelse = NULL; + +} diff --git a/src/Settings/DistributionObjectSettings.php b/src/Settings/DistributionObjectSettings.php new file mode 100644 index 0000000..4c92328 --- /dev/null +++ b/src/Settings/DistributionObjectSettings.php @@ -0,0 +1,37 @@ + SenderSettings::class, + self::DISTRIBUTION_CONTEXT => DistributionContextSettings::class, + self::DISTRIBUTION_OBJECT => DistributionObjectSettings::class, + ]; + + const string SENDER = 'sender'; + public ?SenderSettings $sender = NULL; + + const string DISTRIBUTION_CONTEXT = 'distribution_context'; + public ?DistributionContextSettings $distributionContext = NULL; + + const string DISTRIBUTION_OBJECT = 'distribution_object'; + public ?DistributionObjectSettings $distributionObject = NULL; + +} diff --git a/src/Settings/SenderSettings.php b/src/Settings/SenderSettings.php new file mode 100644 index 0000000..4d3744f --- /dev/null +++ b/src/Settings/SenderSettings.php @@ -0,0 +1,37 @@ + SftpSettings::class, + ]; + + const string ROUTING_MYNDIGHED = 'routing_myndighed'; + public ?string $routingMyndighed = NULL; + + const string SENDER_ID = 'sender_id'; + public ?string $senderId = NULL; + + const string REGISTRERING_IT_SYSTEM = 'registrering_it_system'; + public ?string $registreringItSystem = NULL; + + const string CERTIFICATE = 'certificate'; + public ?string $certificate = NULL; + + const string SFTP = 'sftp'; + public ?SftpSettings $sftp = NULL; + + public function __construct(array $values, bool $throwExceptionOnMissingProperty = FALSE) { + $this->sftp = new SftpSettings([]); + parent::__construct($values, $throwExceptionOnMissingProperty); + } + +} diff --git a/src/Settings/SenderSettings/SftpSettings.php b/src/Settings/SenderSettings/SftpSettings.php new file mode 100644 index 0000000..5c6a125 --- /dev/null +++ b/src/Settings/SenderSettings/SftpSettings.php @@ -0,0 +1,19 @@ + +{% block styles %} + +{% endblock %} + +{% block navigation %} + +{% endblock %} + +{% block content %}{% endblock %} + +{% block handler_setting %} + {{ _self.render_handler_settings(handler_settings) }} +{% endblock %} + + +{% macro render_handler_settings(handler_settings) %} +
+ {{ 'Handler settings'|trans }} + {{ _self.render_json(handler_settings) }} +
+{% endmacro %} + +{% macro render_json(value) %} +
{{ value|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
+{% endmacro %} diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig index 137a94d..aedb705 100644 --- a/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig @@ -26,33 +26,64 @@ {% endfor %} - {% if distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Helper\\FordelingskomponentHelper::DISTRIBUTION_TYPE_JOURNALPOST') %} + {% set distribution_type = handler_settings.distributionObject.distributionType %} + {% if distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_JOURNALPOST') %} {{ distribution_type }}
{{ distribution_object|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
- {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Helper\\FordelingskomponentHelper::DISTRIBUTION_TYPE_DOKUMENT') %} + {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_DOKUMENT') %} {{ distribution_type }}
{{ distribution_object|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
- {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Helper\\FordelingskomponentHelper::DISTRIBUTION_TYPE_FORMULAR') %} - {% set xml = distribution_object.meddelelse.formular.formularXml.any %} - {% set xml_template = handler_settings['xml_template']|default(null) %} - {% if xml_template %} -
- {{ 'XML template'|trans }} - - + {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR') %} + {% if distribution_object %} + {% set xml = distribution_object.meddelelse.formular.formularXml.any %} + {% set xml_template = handler_settings['xml_template']|default(null) %} + {% if xml_template %} +
+ {{ 'XML template'|trans }} + + +
+ {% endif %} + +
+ {{ 'XML'|trans }} + +
+ {% elseif xml %} + {% if xml.template %} +
+ {{ 'XML template'|trans }} + +
{{ xml.template }}
+
+ {% endif %} + +
+ {{ 'XML'|trans }} +
{{ xml.rendered }}
- {% endif %} -
- {{ 'XML'|trans }} - -
+ {% if xml.context %} +
+ {{ 'XML context'|trans }} + +
{{ _self.render_json(xml.context) }}
+
+ {% endif %} + + {% endif %} {% else %} {% endif %} + + + +{% macro render_json(value) %} +
{{ value|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
+{% endmacro %} diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig index bfd8516..54891d0 100644 --- a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig @@ -1,3 +1,5 @@ +{% extends "@os2forms_fordelingskomponent/base.html.twig" %} + {# /** * @file @@ -10,54 +12,21 @@ * - next: Next submission preview URL (if any) * - webform: The webform * - handler: The handler ID - * - submission: The submission ID + * - submission: The submission ID * - return_url: The return URL (to list of webform handlers) * - render_url: The render URL to render the actual preview */ #} -
- +{% block content %} - - -{# -
-
-#} +
+ {{ 'XML Template'|t }} +
{{ handler_settings.distributionObject.xmlTemplate }}
+
- {% if render_url %} - - {% endif %} -
+ {% if render_url %} + + {% endif %} +{% endblock %} diff --git a/templates/os2forms-fordelingskomponent-routing-info.html.twig b/templates/os2forms-fordelingskomponent-routing-info.html.twig new file mode 100644 index 0000000..e89f754 --- /dev/null +++ b/templates/os2forms-fordelingskomponent-routing-info.html.twig @@ -0,0 +1,19 @@ +{% extends "@os2forms_fordelingskomponent/base.html.twig" %} + +{#{{% import "@os2forms_fordelingskomponent//_partials.html.twig" as _partials %}}#} +{#{% include('module://os2forms_fordelingskomponent/templates/_partials.html.twig') %}#} +{# + /** + * @file + * Template for Fordelingskomponent routing info. + * + * Available variables: + * - webform: The webform + * - handler: The handler + * - return_url: The return URL (to list of webform handlers) + */ + #} +{% block content %} +{{ _self.render_json(info) }} + +{% endblock %} From d7aef81fd3d7950bd7fdf9feafcc655f58e7524b Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Sat, 21 Feb 2026 00:05:43 +0100 Subject: [PATCH 18/62] Cleaned up --- ...tributionObjectPreviewRenderController.php | 6 +- src/Helper/FordelingskomponentHelper.php | 130 ++++++++---------- src/Helper/WebformHelperSF2900.php | 16 +-- src/Helper/XmlHelper.php | 8 -- .../WebformHandler/WebformHandlerSF2900.php | 10 +- src/Settings/DistributionObjectSettings.php | 2 +- src/Settings/SenderSettings.php | 2 + ...nent-distribution-object-preview.html.twig | 3 - ...fordelingskomponent-routing-info.html.twig | 3 +- 9 files changed, 75 insertions(+), 105 deletions(-) diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php index 64232ad..419a50e 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php @@ -7,8 +7,8 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Render\RendererInterface; use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; -use Drupal\os2forms_fordelingskomponent\Helper\XmlHelper; use Drupal\os2forms_fordelingskomponent\Hook\ThemeHooks; +use Drupal\os2forms_fordelingskomponent\Model\Attachment; use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900; use Drupal\os2forms_fordelingskomponent\Settings; use Drupal\webform\WebformInterface; @@ -25,7 +25,6 @@ public function __construct( private readonly Settings $settings, private readonly WebformHelperSF2900 $helper, private readonly RendererInterface $renderer, - private readonly XmlHelper $xmlHelper, ) { } @@ -52,7 +51,8 @@ public function __invoke(WebformInterface $webform, string $webform_handler, Web $distributionObject = NULL; $xml = []; try { - $distributionObject = $this->helper->buildDistributionObject($handlerSettings, $submission); + $attachment = new Attachment('preview', Attachment::MIME_TYPE_PDF, 'preview.pdf'); + $distributionObject = $this->helper->buildDistributionObject($handlerSettings, $submission, $attachment); } catch (\Exception $exception) { $exceptions[] = $exception; diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 87c0c9f..89621b9 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -51,7 +51,6 @@ use Psr\Log\LoggerTrait; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; -use WsdlToPhp\PackageBase\AbstractStructBase; /** * Fordelingskomponent helper. @@ -61,16 +60,6 @@ final class FordelingskomponentHelper implements LoggerInterface { use LoggerTrait; - public const string ROUTING_MYNDIGHED = 'routing_myndighed'; - - public const string KLE_EMNE = 'kle_emne'; - - public const string HANDLING_FACET = 'handling_facet'; - public const string TITEL = 'titel'; - public const string BESKRIVELSE = 'beskrivelse'; - - public const string ATTACHMENT_ELEMENT = 'attachment_element'; - /** * Constructor. */ @@ -133,11 +122,9 @@ public function buildDistributionObject( ), DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR => $this->buildDistributionFormularType( id: $id, - fraTidsPunkt: $fraTidsPunkt, - brevDato: $brevDato, - submission: $submission, - handlerSettings: $handlerSettings, - attachment: $attachment, + submission: $submission, + handlerSettings: $handlerSettings, + attachment: $attachment, ), default => throw new Exception(sprintf('Invalid distribution type: %s', $type)), }; @@ -194,57 +181,57 @@ private function buildDistributionDokumentType( ): DistributionDokumentType { return new DistributionDokumentType( iD: $id, - kLEEmneForslag: $handlerSettings->distributionContext->kleEmne, - handlingFacetForslag: $handlerSettings->distributionContext->handlingFacet, - registrering: new DokumentRegistreringType( - fraTidsPunkt: SF2900::formatDateTime($fraTidsPunkt), - livscyklusKode: LivscyklusKodeType::VALUE_OPRETTET, - registreringItSystem: new UUID_URN($handlerSettings->sender->registreringItSystem), - relationListe: new RelationsListe( - variantListe: new VariantListeType([ - new VariantType( - // If we don't clone the “virking", the XML serializer adds an - // ID and references which SF2900 does not handle. - virkning: $this->clone($virkning), - rolle: VariantRolleType::VALUE_VARIANT, - indeks: '1', - variantAttributter: new VariantAttributterType( + kLEEmneForslag: $handlerSettings->distributionContext->kleEmne, + registrering: new DokumentRegistreringType( + fraTidsPunkt: SF2900::formatDateTime($fraTidsPunkt), + livscyklusKode: LivscyklusKodeType::VALUE_OPRETTET, + registreringItSystem: new UUID_URN($handlerSettings->sender->registreringItSystem), + relationListe: new RelationsListe( + variantListe: new VariantListeType([ + new VariantType( + // If we don't clone the “virking", the XML serializer adds an + // ID and references which SF2900 does not handle. + virkning: $this->cloneVirkning($virkning), + rolle: VariantRolleType::VALUE_VARIANT, + indeks: '1', + variantAttributter: new VariantAttributterType( + // @todo What to use here? + variantType: Attachment::FORMAT_NAME_PDF, + ), + delAttributter: new DelAttributterType( // @todo What to use here? - variantType: Attachment::FORMAT_NAME_PDF, + delTekst: 'Hele dokumentet', + ), ), - delAttributter: new DelAttributterType( + ]), + ), + tilstandsListe: [ + new TilstandListeType( + tilstand: [ + new TilstandType( + // @todo Hvad er fremdrift? + fremdrift: FremdriftType::VALUE_ENDELIGT, + virkning: $this->cloneVirkning($virkning), + ), + ] + ), + ], + attributListe: new AttributterListeType([ + new AttributterType( + brugervendtNoegleTekst: $handlerSettings->distributionContext->brugervendtNoegle, + titelTekst: $handlerSettings->distributionContext->titel, + beskrivelseTekst: $handlerSettings->distributionContext->beskrivelse, // @todo What to use here? - delTekst: 'Hele dokumentet', - ), + dokumenttype: DokumenttypeType::VALUE_ANDEN, + retning: RetningType::VALUE_UDGAAENDE, + brevdato: SF2900::formatDate($brevDato), + virkning: $this->cloneVirkning($virkning), ), ]), + // importTidspunkt: null, + // brugerRef: null,. ), - tilstandsListe: [ - new TilstandListeType( - tilstand: [ - new TilstandType( - // @todo Hvad er fremdrift? - fremdrift: FremdriftType::VALUE_ENDELIGT, - virkning: $this->clone($virkning), - ), - ] - ), - ], - attributListe: new AttributterListeType([ - new AttributterType( - brugervendtNoegleTekst: $handlerSettings->distributionContext->brugervendtNoegle, - titelTekst: $handlerSettings->distributionContext->titel, - beskrivelseTekst: $handlerSettings->distributionContext->beskrivelse, - // @todo What to use here? - dokumenttype: DokumenttypeType::VALUE_ANDEN, - retning: RetningType::VALUE_UDGAAENDE, - brevdato: SF2900::formatDate($brevDato), - virkning: $this->clone($virkning), - ), - ]), - // importTidspunkt: null, - // brugerRef: null,. - ) + handlingFacetForslag: $handlerSettings->distributionContext->handlingFacet ); } @@ -253,8 +240,6 @@ private function buildDistributionDokumentType( */ private function buildDistributionFormularType( string $id, - \DateTimeInterface $fraTidsPunkt, - \DateTimeInterface $brevDato, WebformSubmissionInterface $submission, HandlerSettings $handlerSettings, Attachment $attachment, @@ -272,13 +257,20 @@ private function buildDistributionFormularType( $formatNavn = pathinfo($attachment->filename, PATHINFO_EXTENSION); $formularIndhold = base64_encode($attachment->contents); + // The XML will be embedded in an SOAP:Envelope element, so we have to + // make sure that the XML declaration is not included when embedding. + // Passing a DOMDocument to FormularXMLType takes care of this. + $dom = new \DOMDocument(); + $dom->loadXML($xml); + $formularXML = new FormularXMLType($dom); + $meddelelse = new MeddelelseType( formularType: $handlerSettings->distributionObject->formularType, formular: new FormularType( titelTekst: $titelTekst, formatNavn: $formatNavn, formularIndhold: $formularIndhold, - formularXML: new FormularXMLType($xml), + formularXML: $formularXML, ), ); @@ -439,16 +431,16 @@ private function buildVirkning(HandlerSettings $handlerSettings): VirkningType { } /** - * Deep clone an object. + * Deep clone a virking. * - * @param \WsdlToPhp\PackageBase\AbstractStructBase $object + * @param \ItkDev\Serviceplatformen\SF2900\StructType\VirkningType $virkning * The object to clone. * - * @return \WsdlToPhp\PackageBase\AbstractStructBase + * @return \ItkDev\Serviceplatformen\SF2900\StructType\VirkningType * The cloned object. */ - private function clone(AbstractStructBase $object): AbstractStructBase { - return unserialize(serialize($object)); + private function cloneVirkning(VirkningType $virkning): VirkningType { + return unserialize(serialize($virkning)); } } diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 90d7cb5..9aa549a 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -8,7 +8,7 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; -use Drupal\Core\Render\ElementInfoManagerInterface; +use Drupal\Core\Render\ElementInfoManager; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; use Drupal\os2forms_fordelingskomponent\Exception\SubmissionNotFoundException; use Drupal\os2forms_fordelingskomponent\Model\Attachment; @@ -56,7 +56,7 @@ public function __construct( EntityTypeManagerInterface $entityTypeManager, private readonly Settings $settings, #[Autowire(service: 'plugin.manager.element_info')] - private readonly ElementInfoManagerInterface $elementInfoManager, + private readonly ElementInfoManager $elementInfoManager, private readonly FordelingskomponentHelper $helper, #[Autowire(service: 'webform.token_manager')] private readonly WebformTokenManagerInterface $webformTokenManager, @@ -92,16 +92,8 @@ public function renderXml(HandlerSettings $handlerSettings, WebformSubmissionInt /** * Afsend med Fordelingskomponenten. * - * @param \Drupal\webform\WebformSubmissionInterface $submission - * The submission. - * @param array $handlerSettings - * The Handler settings. - * * @return array * [The response, The kombi post message]. - * - * @phpstan-param array $handlerSettings - * @phpstan-param array $submissionData */ public function afsend(WebformSubmissionInterface $submission, HandlerSettings $handlerSettings): array { $attachment = $this->getAttachment($submission, $handlerSettings); @@ -119,8 +111,6 @@ public function afsend(WebformSubmissionInterface $submission, HandlerSettings $ * Get main document. * * @see WebformAttachmentController::download() - * - * @phpstan-param array $handlerSettings */ protected function getAttachment(WebformSubmissionInterface $submission, HandlerSettings $handlerSettings): ?Attachment { if (!in_array($handlerSettings->distributionObject->distributionType, [ @@ -197,8 +187,6 @@ public function log($level, $message, array $context = []): void { * Create a job. * * @see self::processJob() - * - * @phpstan-param array $handlerConfiguration */ public function createJob(WebformSubmissionInterface $webformSubmission, WebformHandlerSF2900 $handler): ?Job { $context = [ diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index a405a47..5b6bb63 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -2,8 +2,6 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; -use Drupal\Core\Config\ImmutableConfig; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; use Drupal\webform\WebformSubmissionInterface; @@ -15,10 +13,6 @@ * XML helper. */ class XmlHelper { - /** - * The module config. - */ - private ImmutableConfig $moduleSettings; /** * Constructor. @@ -26,9 +20,7 @@ class XmlHelper { public function __construct( #[Autowire(service: 'twig')] private readonly Environment $twig, - ConfigFactoryInterface $configFactory, ) { - $this->moduleSettings = $configFactory->get('os2forms_fordelingskomponent.settings'); } /** diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index a341a18..ed11060 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -39,17 +39,17 @@ final class WebformHandlerSF2900 extends WebformHandlerBase { /** * The settings. */ - private readonly Settings $settingsService; + private Settings $settingsService; /** * The webform helper. */ - private readonly WebformHelperSF2900 $helper; + private WebformHelperSF2900 $helper; /** * The XML helper. */ - private readonly XmlHelper $xmlHelper; + private XmlHelper $xmlHelper; /** * {@inheritdoc} @@ -256,7 +256,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $type = $form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectSettings::DISTRIBUTION_TYPE] ?? ''; if (DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR === $type) { - $template = (string) $form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectSettings::XML_TEMPLATE] ?? ''; + $template = (string) ($form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectSettings::XML_TEMPLATE] ?? NULL); try { $this->xmlHelper->validateXml($template); $this->xmlHelper->validateTemplate($template); @@ -266,7 +266,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $this->t('Invalid XML template: %message.', ['%message' => $e->getMessage()])); } - $url = (string) $form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectSettings::XSD_URL] ?? ''; + $url = (string) ($form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectSettings::XSD_URL] ?? NULL); if ($url) { $contents = @file_get_contents($url); if (FALSE === $contents) { diff --git a/src/Settings/DistributionObjectSettings.php b/src/Settings/DistributionObjectSettings.php index 4c92328..756cb1b 100644 --- a/src/Settings/DistributionObjectSettings.php +++ b/src/Settings/DistributionObjectSettings.php @@ -5,7 +5,7 @@ use ItkDev\Serviceplatformen\SF2900\EnumType\ObjektTypeType; /** - * General DistributionContext settings, i.e. the ones that do not depend o a specific distribution object. + * Distribution object settings. * * @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf2900/2.4/SF2900%20-%20Fordelingskomponent%20V2.4.pdf#page=34 */ diff --git a/src/Settings/SenderSettings.php b/src/Settings/SenderSettings.php index 4d3744f..9611cd2 100644 --- a/src/Settings/SenderSettings.php +++ b/src/Settings/SenderSettings.php @@ -5,6 +5,8 @@ use Drupal\os2forms_fordelingskomponent\Settings\SenderSettings\SftpSettings; /** + * Sender settings. + * * @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf2900/2.4/SF2900%20-%20Fordelingskomponent%20V2.4.pdf */ final class SenderSettings extends AbstractSettings { diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig index 54891d0..f7ab2c4 100644 --- a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig @@ -18,9 +18,6 @@ */ #} {% block content %} - -{{ dump() }} -
{{ 'XML Template'|t }}
{{ handler_settings.distributionObject.xmlTemplate }}
diff --git a/templates/os2forms-fordelingskomponent-routing-info.html.twig b/templates/os2forms-fordelingskomponent-routing-info.html.twig index e89f754..c61c417 100644 --- a/templates/os2forms-fordelingskomponent-routing-info.html.twig +++ b/templates/os2forms-fordelingskomponent-routing-info.html.twig @@ -14,6 +14,5 @@ */ #} {% block content %} -{{ _self.render_json(info) }} - + {{ _self.render_json(info) }} {% endblock %} From 8238be4238632190ada8e6f673b510d0e02f9a3e Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Sat, 21 Feb 2026 11:33:34 +0100 Subject: [PATCH 19/62] Code analysis --- phpstan.neon | 4 ++++ scripts/base | 14 +++++++------- ...omponentDistributionObjectPreviewController.php | 6 ++---- src/Helper/WebformHelperSF2900.php | 3 ++- src/Hook/ThemeHooks.php | 6 +++--- src/Plugin/WebformHandler/WebformHandlerSF2900.php | 3 +++ src/Settings.php | 2 +- src/Settings/AbstractSettings.php | 2 +- src/Settings/DistributionContextSettings.php | 2 +- 9 files changed, 24 insertions(+), 18 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index c8c4284..716b6c6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,3 +8,7 @@ parameters: - rector.php # Ignore any vendor folder (https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files) - vendor (?) + + ignoreErrors: + - '#Call to method Drupal\\Core\\Entity\\Query\\QueryInterface::accessCheck\(\) will always evaluate to true.#' + - '#Call to method PHPUnit\\Framework\\Assert::assertTrue\(\) with true will always evaluate to true.#' diff --git a/scripts/base b/scripts/base index 4175061..d12b23d 100644 --- a/scripts/base +++ b/scripts/base @@ -81,15 +81,13 @@ setup() { # @todo Do this in a dockerfile. compose exec drupal sh -c 'apt update && apt --yes install git' - COMPOSER=composer.lenient.json composer init --no-interaction - COMPOSER=composer.lenient.json composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true - COMPOSER=composer.lenient.json composer require mglaman/composer-drupal-lenient - # COMPOSER=composer.lenient.json rm composer.lenient.* - # Allow all plugins to run. composer --no-plugins config allow-plugins true + composer --no-plugins config minimum-stability dev - composer config minimum-stability dev + composer --no-plugins config extra.drupal-lenient.allow-all --json true + # composer --no-plugins config extra.drupal-lenient.allowed-list --json '[ "drupal/coc_forms_auto_export", "drupal/webform_node_element" ]' + composer require mglaman/composer-drupal-lenient --with-all-dependencies # -------------------------------------------------------------------------------------------------------------------- # We need to install dev requirements from our module, so we use @@ -103,6 +101,7 @@ setup() { # Install wikimedia/composer-merge-plugin without any configuration composer require wikimedia/composer-merge-plugin --with-all-dependencies # Patch to make COMPOSER_IGNORE_PLATFORM_REQS=1 have effect + # https://github.com/wikimedia/composer-merge-plugin/pull/253 shell sh -c 'cd vendor/wikimedia/composer-merge-plugin/ && curl https://patch-diff.githubusercontent.com/raw/wikimedia/composer-merge-plugin/pull/253.diff | patch --strip=1' # Configure wikimedia/composer-merge-plugin @@ -110,6 +109,7 @@ setup() { # Use --json to actually set a boolean value (rather that a string value, e.g. "true") composer --no-plugins config extra.merge-plugin.merge-extra --json true composer --no-plugins config extra.merge-plugin.merge-extra-deep --json true + composer update # Our module and its dev requirements are now installed. # -------------------------------------------------------------------------------------------------------------------- @@ -117,7 +117,7 @@ setup() { # Reset Drupal installation compose exec drupal sh -c 'find . -name .ht.sqlite -ls -delete; rm web/sites/default/settings.php' || true # Install a minimal Drupal site. - drush --yes site:install --db-url='sqlite://sites/default/files/.ht.sqlite?module=sqlite' minimal + drush --yes site:install --db-url='sqlite://sites/default/files/.ht.sqlite?module=sqlite' minimal -vvv # Uncomment this line if you need to debug # shell bash diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 3f8635d..0076b7c 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -13,7 +13,6 @@ use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionStorageInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -24,7 +23,7 @@ final class Os2formsFordelingskomponentDistributionObjectPreviewController exten /** * The webform submission storage. */ - private WebformSubmissionStorageInterface $submissionStorage; + private readonly WebformSubmissionStorageInterface $submissionStorage; public function __construct( private readonly Settings $settings, @@ -36,7 +35,7 @@ public function __construct( /** * Builds the response. */ - public function __invoke(Request $request, WebformInterface $webform, string $webform_handler): array|Response { + public function __invoke(Request $request, WebformInterface $webform, string $webform_handler): array { try { $handler = $webform->getHandler($webform_handler); } @@ -93,7 +92,6 @@ public function __invoke(Request $request, WebformInterface $webform, string $we '#render_url' => $renderUrl, '#preview_urls' => $previewUrls, ]; - } } diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 9aa549a..3472c07 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -130,7 +130,7 @@ protected function getAttachment(WebformSubmissionInterface $submission, Handler $instance = $this->elementInfoManager->createInstance($type); if (!$instance instanceof WebformAttachmentBase) { - throw new InvalidAttachmentElementException(sprintf('Attachment element must be an instance of %s. Found %s.', WebformAttachmentBase::class, get_class($instance))); + throw new InvalidAttachmentElementException(sprintf('Attachment element must be an instance of %s. Found %s.', WebformAttachmentBase::class, $instance::class)); } $fileName = $instance::getFileName($element, $submission); @@ -217,6 +217,7 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform catch (\Exception $exception) { $this->error('Error creating job for afsend.', $context + [ 'operation' => 'Fordelingskomponent afsend failed', + 'exception' => $exception, ]); return NULL; } diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php index bb3cf26..def210c 100644 --- a/src/Hook/ThemeHooks.php +++ b/src/Hook/ThemeHooks.php @@ -6,9 +6,9 @@ * Theme hook implementations. */ final class ThemeHooks { - const ROUTING_INFO = 'os2forms_fordelingskomponent_routing_info'; - const DISTRIBUTION_OBJECT_PREVIEW = 'os2forms_fordelingskomponent_distribution_object_preview'; - const DISTRIBUTION_OBJECT_PREVIEW_RENDER = 'os2forms_fordelingskomponent_distribution_object_preview_render'; + const string ROUTING_INFO = 'os2forms_fordelingskomponent_routing_info'; + const string DISTRIBUTION_OBJECT_PREVIEW = 'os2forms_fordelingskomponent_distribution_object_preview'; + const string DISTRIBUTION_OBJECT_PREVIEW_RENDER = 'os2forms_fordelingskomponent_distribution_object_preview_render'; /** * Implements hook_theme(). diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index ed11060..3dd4b2b 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -54,6 +54,7 @@ final class WebformHandlerSF2900 extends WebformHandlerBase { /** * {@inheritdoc} */ + #[\Override] public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->settingsService = $container->get(Settings::class); @@ -66,6 +67,7 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ + #[\Override] public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form[DistributionContextSettings::NAME] = [ '#type' => 'fieldset', @@ -327,6 +329,7 @@ public function postPurge(array $webform_submissions) { /** * {@inheritdoc} */ + #[\Override] public function getSummary() { $settings = $this->settingsService->getHandlerSettings($this); diff --git a/src/Settings.php b/src/Settings.php index a328b58..b1076a7 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -20,7 +20,7 @@ class Settings { /** * The config. */ - private ImmutableConfig $config; + private readonly ImmutableConfig $config; public function __construct( ConfigFactoryInterface $configFactory, diff --git a/src/Settings/AbstractSettings.php b/src/Settings/AbstractSettings.php index dbec11b..f568ea1 100644 --- a/src/Settings/AbstractSettings.php +++ b/src/Settings/AbstractSettings.php @@ -135,7 +135,7 @@ public static function kebab2camel(string $value): string { * @see https://stackoverflow.com/a/40514305/2502647 */ public static function camel2kebab(string $value): string { - return strtolower(preg_replace('/(?<=\d)(?=[A-Za-z])|(?<=[A-Za-z])(?=\d)|(?<=[a-z])(?=[A-Z])/', '_', $value)); + return strtolower((string) preg_replace('/(?<=\d)(?=[A-Za-z])|(?<=[A-Za-z])(?=\d)|(?<=[a-z])(?=[A-Z])/', '_', $value)); } } diff --git a/src/Settings/DistributionContextSettings.php b/src/Settings/DistributionContextSettings.php index 8f9a0f8..96985b6 100644 --- a/src/Settings/DistributionContextSettings.php +++ b/src/Settings/DistributionContextSettings.php @@ -26,7 +26,7 @@ final class DistributionContextSettings extends AbstractSettings { public ?string $brugervendtNoegle = NULL; // @todo Use this? - public const ROUTING_MODTAGER_AKTOER = 'routing_modtager_aktoer'; + public const string ROUTING_MODTAGER_AKTOER = 'routing_modtager_aktoer'; public ?string $routingModtagerAktoer = NULL; public const string TITEL = 'titel'; From 08be1fc17924225fce4e986a1fe7e129d56d86e0 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Sat, 21 Feb 2026 12:27:52 +0100 Subject: [PATCH 20/62] Cleaned up --- .phpcs.xml.dist | 6 +- .../webform.webform.os2forms_fdk_dokument.yml | 254 ++++++++-------- .../webform.webform.os2forms_fdk_formular.yml | 252 ++++++++-------- ...bform.webform.os2forms_fdk_journalpost.yml | 256 ++++++++-------- ...bform.webform.os2forms_fdk_kp_anmoding.yml | 284 +++++++++--------- src/Helper/FordelingskomponentHelper.php | 4 +- src/Helper/WebformHelperSF2900.php | 4 +- .../WebformHandler/WebformHandlerSF2900.php | 4 +- src/Settings.php | 10 +- src/Settings/AbstractSettings.php | 4 +- src/Settings/SenderSettings/SftpSettings.php | 2 +- templates/base.html.twig | 50 ++- ...stribution-object-preview-render.html.twig | 6 - ...nent-distribution-object-preview.html.twig | 32 +- ...fordelingskomponent-routing-info.html.twig | 24 +- 15 files changed, 593 insertions(+), 599 deletions(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 4c416fd..083aa41 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -12,6 +12,7 @@ rector.php + @@ -25,5 +26,8 @@ - + + + src/Settings/ + diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_dokument.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_dokument.yml index 9c0bea4..1dc56a1 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_dokument.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_dokument.yml @@ -22,42 +22,42 @@ third_party_settings: os2forms: os2forms_email_handler: enabled: 0 - email_recipients: '' + email_recipients: "" os2forms_nemid: - session_type: '' - webform_type: '' + session_type: "" + webform_type: "" nemlogin_auto_redirect: 0 os2forms_nemlogin_openid_connect: authentication_settings: - user_claim: '' - element_key: '' - error_message: '' + user_claim: "" + element_key: "" + error_message: "" os2forms_nemid_address_protection: nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour - nemlogin_hide_message: '' + nemlogin_hide_message: "" os2forms_rest_api: allowed_users: null os2forms_sync: publish: 0 os2forms_webform_submission_log: - emails: '' + emails: "" webform_entity_print: template: - header: '' - footer: '' - css: '' - os2form_header: '' - os2form_colophon: '' - os2form_footer: '' + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" export_types: pdf: enabled: true - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} word_docx: enabled: false - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} weight: 0 open: null close: null @@ -66,8 +66,8 @@ template: false archive: false id: os2forms_fdk_dokument title: 'OS2Forms Fordelingskomponent example with "dokument"' -description: '' -categories: { } +description: "" +categories: {} elements: |- message: '#type': textarea @@ -85,32 +85,32 @@ elements: |- '#excluded_elements': { } '#exclude_empty': 0 '#exclude_empty_checkbox': 0 -css: '' -javascript: '' +css: "" +javascript: "" settings: ajax: false ajax_scroll_top: form - ajax_progress_type: '' - ajax_effect: '' + ajax_progress_type: "" + ajax_effect: "" ajax_speed: null page: true - page_submit_path: '' - page_confirm_path: '' - page_theme_name: '' + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" form_title: both form_submit_once: false - form_open_message: '' - form_close_message: '' - form_exception_message: '' + form_open_message: "" + form_close_message: "" + form_exception_message: "" form_previous_submissions: true form_confidential: false - form_confidential_message: '' + form_confidential_message: "" form_disable_remote_addr: false form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: '' + form_prepopulate_source_entity_type: "" form_unsaved: false form_disable_back: false form_submit_back: false @@ -122,91 +122,91 @@ settings: form_details_toggle: false form_reset: false form_access_denied: default - form_access_denied_title: '' - form_access_denied_message: '' - form_access_denied_attributes: { } - form_file_limit: '' - form_attributes: { } - form_method: '' - form_action: '' + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" share: false share_node: false - share_theme_name: '' + share_theme_name: "" share_title: true - share_page_body_attributes: { } - submission_label: '' - submission_exception_message: '' - submission_locked_message: '' + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" submission_log: false - submission_excluded_elements: { } + submission_excluded_elements: {} submission_exclude_empty: false submission_exclude_empty_checkbox: false - submission_views: { } - submission_views_replace: { } - submission_user_columns: { } + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} submission_user_duplicate: false submission_access_denied: default - submission_access_denied_title: '' - submission_access_denied_message: '' - submission_access_denied_attributes: { } - previous_submission_message: '' - previous_submissions_message: '' + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" autofill: false - autofill_message: '' - autofill_excluded_elements: { } + autofill_message: "" + autofill_excluded_elements: {} wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false wizard_progress_link: false wizard_progress_states: false - wizard_start_label: '' + wizard_start_label: "" wizard_preview_link: false wizard_confirmation: true - wizard_confirmation_label: '' + wizard_confirmation_label: "" wizard_auto_forward: true wizard_auto_forward_hide_next_button: false wizard_keyboard: true - wizard_track: '' - wizard_prev_button_label: '' - wizard_next_button_label: '' + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" wizard_toggle: false - wizard_toggle_show_label: '' - wizard_toggle_hide_label: '' + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" wizard_page_type: container wizard_page_title_tag: h2 preview: 0 - preview_label: '' - preview_title: '' - preview_message: '' - preview_attributes: { } - preview_excluded_elements: { } + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} preview_exclude_empty: true preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false - draft_saved_message: '' - draft_loaded_message: '' - draft_pending_single_message: '' - draft_pending_multiple_message: '' + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" confirmation_type: message - confirmation_url: '' - confirmation_title: '' - confirmation_message: '' - confirmation_attributes: { } + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} confirmation_back: true - confirmation_back_label: '' - confirmation_back_attributes: { } + confirmation_back_label: "" + confirmation_back_attributes: {} confirmation_exclude_query: false confirmation_exclude_token: false confirmation_update: false limit_total: null limit_total_interval: null - limit_total_message: '' + limit_total_message: "" limit_total_unique: false limit_user: null limit_user_interval: null - limit_user_message: '' + limit_user_message: "" limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null @@ -226,79 +226,79 @@ access: roles: - anonymous - authenticated - users: { } - permissions: { } + users: {} + permissions: {} view_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} purge_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} view_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} administer: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} test: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} configuration: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} handlers: os2forms_fordelingskomponent_sf2900: id: os2forms_fordelingskomponent_sf2900 handler_id: os2forms_fordelingskomponent_sf2900 - label: 'Fordelingskomponent (sf2900)' - notes: '' + label: "Fordelingskomponent (sf2900)" + notes: "" status: true - conditions: { } + conditions: {} weight: 0 settings: sf2900: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'What is “Brugervendt nøgle”?' - titel: 'Your document' - beskrivelse: 'This is a very important document' + handling_facet: "" + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" distribution_type: JOURNALPOST attachment_element: afsend_content_pdf - xml_template: '' - xsd_url: '' - journalpost_message: 'Dette er et fordelingsobjekt fra [site:base-url].' + xml_template: "" + xsd_url: "" + journalpost_message: "Dette er et fordelingsobjekt fra [site:base-url]." distribution_context: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'Brugervendt nøgle' + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" titel: Titel beskrivelse: Beskrivelse distribution_object: distribution_type: DOKUMENT - journalpost_message: '' + journalpost_message: "" attachment_element: attachment - xml_template: '' - xsd_url: '' -variants: { } + xml_template: "" + xsd_url: "" +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_formular.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_formular.yml index ff82649..dc71d33 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_formular.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_formular.yml @@ -22,42 +22,42 @@ third_party_settings: os2forms: os2forms_email_handler: enabled: 0 - email_recipients: '' + email_recipients: "" os2forms_nemid: - session_type: '' - webform_type: '' + session_type: "" + webform_type: "" nemlogin_auto_redirect: 0 os2forms_nemlogin_openid_connect: authentication_settings: - user_claim: '' - element_key: '' - error_message: '' + user_claim: "" + element_key: "" + error_message: "" os2forms_nemid_address_protection: nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour - nemlogin_hide_message: '' + nemlogin_hide_message: "" os2forms_rest_api: allowed_users: null os2forms_sync: publish: 0 os2forms_webform_submission_log: - emails: '' + emails: "" webform_entity_print: template: - header: '' - footer: '' - css: '' - os2form_header: '' - os2form_colophon: '' - os2form_footer: '' + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" export_types: pdf: enabled: true - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} word_docx: enabled: false - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} weight: 0 open: null close: null @@ -66,8 +66,8 @@ template: false archive: false id: os2forms_fdk_formular title: 'OS2Forms Fordelingskomponent example with "formular"' -description: '' -categories: { } +description: "" +categories: {} elements: |- message: '#type': textarea @@ -85,32 +85,32 @@ elements: |- '#excluded_elements': { } '#exclude_empty': 0 '#exclude_empty_checkbox': 0 -css: '' -javascript: '' +css: "" +javascript: "" settings: ajax: false ajax_scroll_top: form - ajax_progress_type: '' - ajax_effect: '' + ajax_progress_type: "" + ajax_effect: "" ajax_speed: null page: true - page_submit_path: '' - page_confirm_path: '' - page_theme_name: '' + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" form_title: both form_submit_once: false - form_open_message: '' - form_close_message: '' - form_exception_message: '' + form_open_message: "" + form_close_message: "" + form_exception_message: "" form_previous_submissions: true form_confidential: false - form_confidential_message: '' + form_confidential_message: "" form_disable_remote_addr: false form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: '' + form_prepopulate_source_entity_type: "" form_unsaved: false form_disable_back: false form_submit_back: false @@ -122,91 +122,91 @@ settings: form_details_toggle: false form_reset: false form_access_denied: default - form_access_denied_title: '' - form_access_denied_message: '' - form_access_denied_attributes: { } - form_file_limit: '' - form_attributes: { } - form_method: '' - form_action: '' + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" share: false share_node: false - share_theme_name: '' + share_theme_name: "" share_title: true - share_page_body_attributes: { } - submission_label: '' - submission_exception_message: '' - submission_locked_message: '' + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" submission_log: false - submission_excluded_elements: { } + submission_excluded_elements: {} submission_exclude_empty: false submission_exclude_empty_checkbox: false - submission_views: { } - submission_views_replace: { } - submission_user_columns: { } + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} submission_user_duplicate: false submission_access_denied: default - submission_access_denied_title: '' - submission_access_denied_message: '' - submission_access_denied_attributes: { } - previous_submission_message: '' - previous_submissions_message: '' + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" autofill: false - autofill_message: '' - autofill_excluded_elements: { } + autofill_message: "" + autofill_excluded_elements: {} wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false wizard_progress_link: false wizard_progress_states: false - wizard_start_label: '' + wizard_start_label: "" wizard_preview_link: false wizard_confirmation: true - wizard_confirmation_label: '' + wizard_confirmation_label: "" wizard_auto_forward: true wizard_auto_forward_hide_next_button: false wizard_keyboard: true - wizard_track: '' - wizard_prev_button_label: '' - wizard_next_button_label: '' + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" wizard_toggle: false - wizard_toggle_show_label: '' - wizard_toggle_hide_label: '' + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" wizard_page_type: container wizard_page_title_tag: h2 preview: 0 - preview_label: '' - preview_title: '' - preview_message: '' - preview_attributes: { } - preview_excluded_elements: { } + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} preview_exclude_empty: true preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false - draft_saved_message: '' - draft_loaded_message: '' - draft_pending_single_message: '' - draft_pending_multiple_message: '' + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" confirmation_type: message - confirmation_url: '' - confirmation_title: '' - confirmation_message: '' - confirmation_attributes: { } + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} confirmation_back: true - confirmation_back_label: '' - confirmation_back_attributes: { } + confirmation_back_label: "" + confirmation_back_attributes: {} confirmation_exclude_query: false confirmation_exclude_token: false confirmation_update: false limit_total: null limit_total_interval: null - limit_total_message: '' + limit_total_message: "" limit_total_unique: false limit_user: null limit_user_interval: null - limit_user_message: '' + limit_user_message: "" limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null @@ -226,80 +226,80 @@ access: roles: - anonymous - authenticated - users: { } - permissions: { } + users: {} + permissions: {} view_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} purge_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} view_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} administer: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} test: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} configuration: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} handlers: os2forms_fordelingskomponent_sf2900: id: os2forms_fordelingskomponent_sf2900 handler_id: os2forms_fordelingskomponent_sf2900 - label: 'Fordelingskomponent (sf2900)' - notes: '' + label: "Fordelingskomponent (sf2900)" + notes: "" status: true - conditions: { } + conditions: {} weight: 0 settings: sf2900: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'What is “Brugervendt nøgle”?' - titel: 'Your document' - beskrivelse: 'This is a very important document' + handling_facet: "" + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" distribution_type: JOURNALPOST attachment_element: afsend_content_pdf - xml_template: '' - xsd_url: '' - journalpost_message: 'Dette er et fordelingsobjekt fra [site:base-url].' + xml_template: "" + xsd_url: "" + journalpost_message: "Dette er et fordelingsobjekt fra [site:base-url]." distribution_context: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'Brugervendt nøgle' + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" titel: Titel beskrivelse: Beskrivelse distribution_object: distribution_type: FORMULAR - journalpost_message: '' + journalpost_message: "" attachment_element: attachment formular_type: Hest xml_template: "\r\n\r\n {{ webform_submission.data.message }}\r\n" - xsd_url: '' -variants: { } + xsd_url: "" +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_journalpost.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_journalpost.yml index fb9fa1a..d7ad58d 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_journalpost.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_journalpost.yml @@ -19,42 +19,42 @@ third_party_settings: os2forms: os2forms_email_handler: enabled: 0 - email_recipients: '' + email_recipients: "" os2forms_nemid: - session_type: '' - webform_type: '' + session_type: "" + webform_type: "" nemlogin_auto_redirect: 0 os2forms_nemlogin_openid_connect: authentication_settings: - user_claim: '' - element_key: '' - error_message: '' + user_claim: "" + element_key: "" + error_message: "" os2forms_nemid_address_protection: nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour - nemlogin_hide_message: '' + nemlogin_hide_message: "" os2forms_rest_api: allowed_users: null os2forms_sync: publish: 0 os2forms_webform_submission_log: - emails: '' + emails: "" webform_entity_print: template: - header: '' - footer: '' - css: '' - os2form_header: '' - os2form_colophon: '' - os2form_footer: '' + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" export_types: pdf: enabled: true - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} word_docx: enabled: false - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} weight: 0 open: null close: null @@ -63,8 +63,8 @@ template: false archive: false id: os2forms_fdk_journalpost title: 'OS2Forms Fordelingskomponent example with "journalpost"' -description: '' -categories: { } +description: "" +categories: {} elements: |- message: '#type': textarea @@ -74,32 +74,32 @@ elements: |- [current-date:long] [random:hash:sha512] -css: '' -javascript: '' +css: "" +javascript: "" settings: ajax: false ajax_scroll_top: form - ajax_progress_type: '' - ajax_effect: '' + ajax_progress_type: "" + ajax_effect: "" ajax_speed: null page: true - page_submit_path: '' - page_confirm_path: '' - page_theme_name: '' + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" form_title: both form_submit_once: false - form_open_message: '' - form_close_message: '' - form_exception_message: '' + form_open_message: "" + form_close_message: "" + form_exception_message: "" form_previous_submissions: true form_confidential: false - form_confidential_message: '' + form_confidential_message: "" form_disable_remote_addr: false form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: '' + form_prepopulate_source_entity_type: "" form_unsaved: false form_disable_back: false form_submit_back: false @@ -111,91 +111,91 @@ settings: form_details_toggle: false form_reset: false form_access_denied: default - form_access_denied_title: '' - form_access_denied_message: '' - form_access_denied_attributes: { } - form_file_limit: '' - form_attributes: { } - form_method: '' - form_action: '' + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" share: false share_node: false - share_theme_name: '' + share_theme_name: "" share_title: true - share_page_body_attributes: { } - submission_label: '' - submission_exception_message: '' - submission_locked_message: '' + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" submission_log: false - submission_excluded_elements: { } + submission_excluded_elements: {} submission_exclude_empty: false submission_exclude_empty_checkbox: false - submission_views: { } - submission_views_replace: { } - submission_user_columns: { } + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} submission_user_duplicate: false submission_access_denied: default - submission_access_denied_title: '' - submission_access_denied_message: '' - submission_access_denied_attributes: { } - previous_submission_message: '' - previous_submissions_message: '' + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" autofill: false - autofill_message: '' - autofill_excluded_elements: { } + autofill_message: "" + autofill_excluded_elements: {} wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false wizard_progress_link: false wizard_progress_states: false - wizard_start_label: '' + wizard_start_label: "" wizard_preview_link: false wizard_confirmation: true - wizard_confirmation_label: '' + wizard_confirmation_label: "" wizard_auto_forward: true wizard_auto_forward_hide_next_button: false wizard_keyboard: true - wizard_track: '' - wizard_prev_button_label: '' - wizard_next_button_label: '' + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" wizard_toggle: false - wizard_toggle_show_label: '' - wizard_toggle_hide_label: '' + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" wizard_page_type: container wizard_page_title_tag: h2 preview: 0 - preview_label: '' - preview_title: '' - preview_message: '' - preview_attributes: { } - preview_excluded_elements: { } + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} preview_exclude_empty: true preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false - draft_saved_message: '' - draft_loaded_message: '' - draft_pending_single_message: '' - draft_pending_multiple_message: '' + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" confirmation_type: message - confirmation_url: '' - confirmation_title: '' - confirmation_message: '' - confirmation_attributes: { } + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} confirmation_back: true - confirmation_back_label: '' - confirmation_back_attributes: { } + confirmation_back_label: "" + confirmation_back_attributes: {} confirmation_exclude_query: false confirmation_exclude_token: false confirmation_update: false limit_total: null limit_total_interval: null - limit_total_message: '' + limit_total_message: "" limit_total_unique: false limit_user: null limit_user_interval: null - limit_user_message: '' + limit_user_message: "" limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null @@ -215,80 +215,80 @@ access: roles: - anonymous - authenticated - users: { } - permissions: { } + users: {} + permissions: {} view_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} purge_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} view_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} administer: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} test: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} configuration: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} handlers: os2forms_fordelingskomponent_sf2900: id: os2forms_fordelingskomponent_sf2900 handler_id: os2forms_fordelingskomponent_sf2900 - label: 'Fordelingskomponent (sf2900)' - notes: '' + label: "Fordelingskomponent (sf2900)" + notes: "" status: true - conditions: { } + conditions: {} weight: 0 settings: sf2900: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'What is “Brugervendt nøgle”?' - titel: 'Your document' - beskrivelse: 'This is a very important document' + handling_facet: "" + brugervendt_noegle: "What is “Brugervendt nøgle”?" + titel: "Your document" + beskrivelse: "This is a very important document" distribution_type: JOURNALPOST attachment_element: afsend_content_pdf - xml_template: '' - xsd_url: '' - journalpost_message: 'Dette er et fordelingsobjekt fra [site:base-url].' + xml_template: "" + xsd_url: "" + journalpost_message: "Dette er et fordelingsobjekt fra [site:base-url]." distribution_context: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'Brugervendt nøgle' + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" titel: Titel beskrivelse: Beskrivelse distribution_object: distribution_type: JOURNALPOST journalpost_message: "Besked: [webform_submission:values:message]\r\n\r\n[current-date:html_datetime]" - attachment_element: '' - formular_type: '' - xml_template: '' - xsd_url: '' -variants: { } + attachment_element: "" + formular_type: "" + xml_template: "" + xsd_url: "" +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml index 6a9d601..766a823 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml @@ -49,42 +49,42 @@ third_party_settings: os2forms: os2forms_email_handler: enabled: 0 - email_recipients: '' + email_recipients: "" os2forms_nemid: - session_type: '' - webform_type: '' + session_type: "" + webform_type: "" nemlogin_auto_redirect: 0 os2forms_nemlogin_openid_connect: authentication_settings: - user_claim: '' - element_key: '' - error_message: '' + user_claim: "" + element_key: "" + error_message: "" os2forms_nemid_address_protection: nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour - nemlogin_hide_message: '' + nemlogin_hide_message: "" os2forms_rest_api: allowed_users: null os2forms_sync: publish: 0 os2forms_webform_submission_log: - emails: '' + emails: "" webform_entity_print: template: - header: '' - footer: '' - css: '' - os2form_header: '' - os2form_colophon: '' - os2form_footer: '' + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" export_types: pdf: enabled: true - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} word_docx: enabled: false - link_text: '' - link_attributes: { } + link_text: "" + link_attributes: {} weight: 0 open: null close: null @@ -92,8 +92,8 @@ uid: 1 template: false archive: false id: os2forms_fdk_kp_anmoding -title: 'OS2Forms Fordelingskomponent: Anmodning (KP)' -description: '' +title: "OS2Forms Fordelingskomponent: Anmodning (KP)" +description: "" categories: - Example - Fordelingskomponent @@ -146,32 +146,32 @@ elements: |- '#excluded_elements': { } '#exclude_empty': 0 '#exclude_empty_checkbox': 0 -css: '' -javascript: '' +css: "" +javascript: "" settings: ajax: false ajax_scroll_top: form - ajax_progress_type: '' - ajax_effect: '' + ajax_progress_type: "" + ajax_effect: "" ajax_speed: null page: true - page_submit_path: '' - page_confirm_path: '' - page_theme_name: '' + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" form_title: both form_submit_once: false - form_open_message: '' - form_close_message: '' - form_exception_message: '' + form_open_message: "" + form_close_message: "" + form_exception_message: "" form_previous_submissions: true form_confidential: false - form_confidential_message: '' + form_confidential_message: "" form_disable_remote_addr: false form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: '' + form_prepopulate_source_entity_type: "" form_unsaved: false form_disable_back: false form_submit_back: false @@ -183,91 +183,91 @@ settings: form_details_toggle: false form_reset: false form_access_denied: default - form_access_denied_title: '' - form_access_denied_message: '' - form_access_denied_attributes: { } - form_file_limit: '' - form_attributes: { } - form_method: '' - form_action: '' + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" share: false share_node: false - share_theme_name: '' + share_theme_name: "" share_title: true - share_page_body_attributes: { } - submission_label: '' - submission_exception_message: '' - submission_locked_message: '' + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" submission_log: false - submission_excluded_elements: { } + submission_excluded_elements: {} submission_exclude_empty: false submission_exclude_empty_checkbox: false - submission_views: { } - submission_views_replace: { } - submission_user_columns: { } + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} submission_user_duplicate: false submission_access_denied: default - submission_access_denied_title: '' - submission_access_denied_message: '' - submission_access_denied_attributes: { } - previous_submission_message: '' - previous_submissions_message: '' + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" autofill: false - autofill_message: '' - autofill_excluded_elements: { } + autofill_message: "" + autofill_excluded_elements: {} wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false wizard_progress_link: false wizard_progress_states: false - wizard_start_label: '' + wizard_start_label: "" wizard_preview_link: false wizard_confirmation: true - wizard_confirmation_label: '' + wizard_confirmation_label: "" wizard_auto_forward: true wizard_auto_forward_hide_next_button: false wizard_keyboard: true - wizard_track: '' - wizard_prev_button_label: '' - wizard_next_button_label: '' + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" wizard_toggle: false - wizard_toggle_show_label: '' - wizard_toggle_hide_label: '' + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" wizard_page_type: container wizard_page_title_tag: h2 preview: 0 - preview_label: '' - preview_title: '' - preview_message: '' - preview_attributes: { } - preview_excluded_elements: { } + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} preview_exclude_empty: true preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false - draft_saved_message: '' - draft_loaded_message: '' - draft_pending_single_message: '' - draft_pending_multiple_message: '' + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" confirmation_type: message - confirmation_url: '' - confirmation_title: '' - confirmation_message: '' - confirmation_attributes: { } + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} confirmation_back: true - confirmation_back_label: '' - confirmation_back_attributes: { } + confirmation_back_label: "" + confirmation_back_attributes: {} confirmation_exclude_query: false confirmation_exclude_token: false confirmation_update: false limit_total: null limit_total_interval: null - limit_total_message: '' + limit_total_message: "" limit_total_unique: false limit_user: null limit_user_interval: null - limit_user_message: '' + limit_user_message: "" limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null @@ -287,135 +287,135 @@ access: roles: - anonymous - authenticated - users: { } - permissions: { } + users: {} + permissions: {} view_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} purge_any: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} view_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} update_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} delete_own: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} administer: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} test: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} configuration: - roles: { } - users: { } - permissions: { } + roles: {} + users: {} + permissions: {} handlers: fordelingskomponent_sf2900: id: os2forms_fordelingskomponent_sf2900 handler_id: fordelingskomponent_sf2900 label: Fordelingskomponent - notes: '' + notes: "" status: true - conditions: { } + conditions: {} weight: -50 settings: distribution_context: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'Brugervendt nøgle' + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" titel: Titel beskrivelse: Beskrivelse distribution_object: distribution_type: FORMULAR - journalpost_message: '' + journalpost_message: "" attachment_element: attachment formular_type: test xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n
\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n
\r\n" - xsd_url: 'module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd' + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_malformed_xml: id: os2forms_fordelingskomponent_sf2900 handler_id: fordelingskomponent_sf2900_malformed_xml - label: 'Fordelingskomponent (malformed XML)' - notes: '' + label: "Fordelingskomponent (malformed XML)" + notes: "" status: false - conditions: { } + conditions: {} weight: -49 settings: distribution_context: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'Brugervendt nøgle' + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" titel: Titel beskrivelse: Beskrivelse distribution_object: distribution_type: FORMULAR - journalpost_message: '' - attachment_element: '' + journalpost_message: "" + attachment_element: "" formular_type: test xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" - xsd_url: 'module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd' + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_invalid_twig: id: os2forms_fordelingskomponent_sf2900 handler_id: fordelingskomponent_sf2900_invalid_twig - label: 'Fordelingskomponent (invalid Twig)' - notes: '' + label: "Fordelingskomponent (invalid Twig)" + notes: "" status: false - conditions: { } + conditions: {} weight: -48 settings: distribution_context: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'Brugervendt nøgle' + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" titel: Titel beskrivelse: Beskrivelse distribution_object: distribution_type: FORMULAR - journalpost_message: '' - attachment_element: '' + journalpost_message: "" + attachment_element: "" formular_type: test xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n
\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n
\r\n" - xsd_url: 'module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd' + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_invalid_xml: id: os2forms_fordelingskomponent_sf2900 handler_id: fordelingskomponent_sf2900_invalid_xml - label: 'Fordelingskomponent (invalid XML)' - notes: '' + label: "Fordelingskomponent (invalid XML)" + notes: "" status: false - conditions: { } + conditions: {} weight: -47 settings: distribution_context: kle_emne: 01.01.01 - handling_facet: '' - brugervendt_noegle: 'Brugervendt nøgle' + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" titel: Titel beskrivelse: Beskrivelse distribution_object: distribution_type: FORMULAR - journalpost_message: '' - attachment_element: '' + journalpost_message: "" + attachment_element: "" formular_type: test xml_template: "\r\n\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" - xsd_url: 'module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd' -variants: { } + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" +variants: {} diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 89621b9..7fd033b 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -133,7 +133,7 @@ public function buildDistributionObject( } /** - * + * Build distribution object for "Journalnotat". */ private function buildDistributionJournalPostType( string $id, @@ -169,7 +169,7 @@ private function buildDistributionJournalPostType( } /** - * + * Build distribution object for "Dokument". */ private function buildDistributionDokumentType( string $id, diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 3472c07..d32e596 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -70,7 +70,7 @@ public function __construct( } /** - * + * Build distribution object. */ public function buildDistributionObject(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, ?Attachment $attachment): DistributionFormularType|DistributionDokumentType|DistributionJournalPostType { $handlerSettings = $this->replaceTokens($handlerSettings, $submission); @@ -83,7 +83,7 @@ public function buildDistributionObject(HandlerSettings $handlerSettings, Webfor } /** - * + * Render XML. */ public function renderXml(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, bool $validateXml = TRUE): XmlRenderResult { return $this->helper->renderXml($handlerSettings, $submission, validateXml: $validateXml); diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 3dd4b2b..36b2265 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -85,7 +85,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta } /** - * + * Build distribution context form section. */ private function buildConfigurationFormDistributionContext(): array { $settings = $this->settingsService->getDistributionContextSettings((array) ($this->getSettings()[DistributionContextSettings::NAME] ?? NULL)); @@ -140,7 +140,7 @@ private function buildConfigurationFormDistributionContext(): array { } /** - * + * Build distribution object form section. */ private function buildConfigurationFormDistributionObject(): array { $settings = $this->settingsService->getDistributionObjectSettings((array) ($this->getSettings()[DistributionObjectSettings::NAME] ?? NULL)); diff --git a/src/Settings.php b/src/Settings.php index b1076a7..0875104 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -12,7 +12,7 @@ use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; /** - * + * Settings for module and handler. */ class Settings { const string CONFIG_NAME = 'os2forms_fordelingskomponent.settings'; @@ -29,14 +29,14 @@ public function __construct( } /** - * + * Get general settings. */ public function getGeneralSettings(): GeneralSettings { return new GeneralSettings($this->getValue(GeneralSettings::NAME)); } /** - * + * Get sender settings. */ public function getSenderSettings(array $values = []): SenderSettings { return (new SenderSettings($this->getValue(SenderSettings::NAME))) @@ -44,7 +44,7 @@ public function getSenderSettings(array $values = []): SenderSettings { } /** - * + * Get distribution context settings. */ public function getDistributionContextSettings(array $values = []): DistributionContextSettings { return (new DistributionContextSettings($this->getValue(DistributionContextSettings::NAME))) @@ -52,7 +52,7 @@ public function getDistributionContextSettings(array $values = []): Distribution } /** - * + * Get distribution object settings. */ public function getDistributionObjectSettings(array $values = []): DistributionObjectSettings { return (new DistributionObjectSettings($this->getValue(DistributionObjectSettings::NAME))) diff --git a/src/Settings/AbstractSettings.php b/src/Settings/AbstractSettings.php index f568ea1..0c898a1 100644 --- a/src/Settings/AbstractSettings.php +++ b/src/Settings/AbstractSettings.php @@ -67,7 +67,7 @@ public function __construct(array $values, bool $throwExceptionOnMissingProperty } /** - * + * Apply values to settings. */ public function apply(array $values, bool $throwExceptionOnMissingProperty = FALSE): static { foreach (static::$listProperties as $property => $class) { @@ -109,7 +109,7 @@ public function apply(array $values, bool $throwExceptionOnMissingProperty = FAL } /** - * + * Convert settings to array. */ public function toArray(): array { return $this->values; diff --git a/src/Settings/SenderSettings/SftpSettings.php b/src/Settings/SenderSettings/SftpSettings.php index 5c6a125..5f49ca7 100644 --- a/src/Settings/SenderSettings/SftpSettings.php +++ b/src/Settings/SenderSettings/SftpSettings.php @@ -5,7 +5,7 @@ use Drupal\os2forms_fordelingskomponent\Settings\AbstractSettings; /** - * + * SFTP settings. */ final class SftpSettings extends AbstractSettings { const string NAME = 'sftp'; diff --git a/templates/base.html.twig b/templates/base.html.twig index d8bf4f9..225ca88 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,39 +1,37 @@ -{#{% import "module://os2forms_fordelingskomponent/templates/_partials.html.twig" as _partials %}#} -{#{% include('module://os2forms_fordelingskomponent/templates/_partials.html.twig') %}#} {# - /** - * @file - * Template for Fordelingskomponent routing info. - * - * Available variables: - * - webform: The webform - * - handler: The handler - * - return_url: The return URL (to list of webform handlers) - */ - #} +/** + * @file + * Template for Fordelingskomponent routing info. + * + * Available variables: + * - webform: The webform + * - handler: The handler + * - return_url: The return URL (to list of webform handlers) + */ +#}
{% block styles %} {% endblock %} diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig index aedb705..9ac88be 100644 --- a/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig @@ -78,12 +78,6 @@
{% endif %} - - - -{% macro render_json(value) %} -
{{ value|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
-{% endmacro %} diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig index f7ab2c4..75ad659 100644 --- a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig @@ -1,22 +1,22 @@ -{% extends "@os2forms_fordelingskomponent/base.html.twig" %} +{% extends '@os2forms_fordelingskomponent/base.html.twig' %} {# - /** - * @file - * Template for Fordelingskomponent payload preview. - * - * Available variables: - * - preview_urls: The preview URLs - * - prev: Previous submission preview URL (if any) - * - self: The current preview URL (if any) - * - next: Next submission preview URL (if any) - * - webform: The webform - * - handler: The handler ID +/** + * @file + * Template for Fordelingskomponent payload preview. + * + * Available variables: + * - preview_urls: The preview URLs + * - prev: Previous submission preview URL (if any) + * - self: The current preview URL (if any) + * - next: Next submission preview URL (if any) + * - webform: The webform + * - handler: The handler ID * - submission: The submission ID - * - return_url: The return URL (to list of webform handlers) - * - render_url: The render URL to render the actual preview - */ - #} + * - return_url: The return URL (to list of webform handlers) + * - render_url: The render URL to render the actual preview + */ +#} {% block content %}
{{ 'XML Template'|t }} diff --git a/templates/os2forms-fordelingskomponent-routing-info.html.twig b/templates/os2forms-fordelingskomponent-routing-info.html.twig index c61c417..327a95c 100644 --- a/templates/os2forms-fordelingskomponent-routing-info.html.twig +++ b/templates/os2forms-fordelingskomponent-routing-info.html.twig @@ -1,18 +1,16 @@ -{% extends "@os2forms_fordelingskomponent/base.html.twig" %} +{% extends '@os2forms_fordelingskomponent/base.html.twig' %} -{#{{% import "@os2forms_fordelingskomponent//_partials.html.twig" as _partials %}}#} -{#{% include('module://os2forms_fordelingskomponent/templates/_partials.html.twig') %}#} {# - /** - * @file - * Template for Fordelingskomponent routing info. - * - * Available variables: - * - webform: The webform - * - handler: The handler - * - return_url: The return URL (to list of webform handlers) - */ - #} +/** + * @file + * Template for Fordelingskomponent routing info. + * + * Available variables: + * - webform: The webform + * - handler: The handler + * - return_url: The return URL (to list of webform handlers) + */ +#} {% block content %} {{ _self.render_json(info) }} {% endblock %} From 2fa532bd44d745bfdcc450baa4a9a63c7c8af580 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Sat, 21 Feb 2026 22:04:09 +0100 Subject: [PATCH 21/62] Refactored preview --- os2forms_fordelingskomponent.routing.yml | 19 +---- ...entDistributionObjectPreviewController.php | 61 ++++++++++---- ...tributionObjectPreviewRenderController.php | 83 ------------------- src/Hook/ThemeHooks.php | 5 +- templates/base.html.twig | 42 ++++++++-- ...stribution-object-preview-render.html.twig | 83 ------------------- ...nent-distribution-object-preview.html.twig | 63 ++++++++++++-- 7 files changed, 146 insertions(+), 210 deletions(-) delete mode 100644 src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php delete mode 100644 templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index 197ec1e..0fd9a66 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -19,28 +19,17 @@ os2forms_fordelingskomponent.routing_info: _permission: "administer site configuration" os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview: - path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/fordelingsobjekt/{webform_handler}/preview" + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/fordelingsobjekt/{webform_handler}/preview/{webform_submission}" defaults: _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentDistributionObjectPreviewController' _title: "Fordelingskomponent fordelingsobjekt preview" + webform_submission: null options: parameters: webform: type: "entity:webform" - requirements: - # @todo Harden this permission to allow only access when user has access to webform. - _permission: "view any webform submission" - -os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview_render: - path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/fordelingsobjekt/{webform_handler}/preview/render/{submission}" - defaults: - _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentDistributionObjectPreviewRenderController' - _title: "Fordelingskomponent fordelingsobjekt preview" - options: - parameters: - webform: - type: "entity:webform" - submission: + webform_submission: type: "entity:webform_submission" requirements: + # @todo Harden this permission to allow only access when user has access to webform. _permission: "view any webform submission" diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 0076b7c..469b33e 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -7,10 +7,14 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Url; +use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; use Drupal\os2forms_fordelingskomponent\Hook\ThemeHooks; +use Drupal\os2forms_fordelingskomponent\Model\Attachment; use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900; use Drupal\os2forms_fordelingskomponent\Settings; +use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\WebformSubmissionStorageInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -27,6 +31,7 @@ final class Os2formsFordelingskomponentDistributionObjectPreviewController exten public function __construct( private readonly Settings $settings, + private readonly WebformHelperSF2900 $helper, EntityTypeManagerInterface $entityTypeManager, ) { $this->submissionStorage = $entityTypeManager->getStorage('webform_submission'); @@ -35,7 +40,7 @@ public function __construct( /** * Builds the response. */ - public function __invoke(Request $request, WebformInterface $webform, string $webform_handler): array { + public function __invoke(Request $request, WebformInterface $webform, string $webform_handler, ?WebformSubmissionInterface $webform_submission): array { try { $handler = $webform->getHandler($webform_handler); } @@ -55,19 +60,22 @@ public function __invoke(Request $request, WebformInterface $webform, string $we ->condition('webform_id', $webform->id()) ->sort('created', 'DESC') ->execute()); - $currentSubmissionId = (int) $request->query->get('submission'); + $currentSubmissionId = $webform_submission?->id(); $index = array_search($currentSubmissionId, $submissionIds); if (FALSE === $index) { $currentSubmissionId = reset($submissionIds) ?: NULL; $index = array_search($currentSubmissionId, $submissionIds); } + if ($currentSubmissionId) { + $webform_submission = $this->submissionStorage->load($currentSubmissionId); + } $routeName = $request->attributes->get('_route'); - $previewUrls = array_map( + $links = array_map( static fn($submission) => Url::fromRoute($routeName, [ 'webform' => $webform->id(), 'webform_handler' => $handler->getHandlerId(), - 'submission' => $submission, + 'webform_submission' => $submission, ]), array_filter([ 'prev' => $submissionIds[$index + 1] ?? NULL, @@ -76,21 +84,46 @@ public function __invoke(Request $request, WebformInterface $webform, string $we ]) ); - $renderUrl = NULL !== $currentSubmissionId - ? Url::fromRoute('os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview_render', [ - 'webform' => $webform->id(), - 'webform_handler' => $handler->getHandlerId(), - 'submission' => $currentSubmissionId, - ]) - : NULL; - return [ '#theme' => ThemeHooks::DISTRIBUTION_OBJECT_PREVIEW, '#webform' => $webform, + '#submission' => $webform_submission, '#handler' => $handler, '#handler_settings' => $handlerSettings, - '#render_url' => $renderUrl, - '#preview_urls' => $previewUrls, + '#preview' => $webform_submission ? $this->renderPreview($handler, $handlerSettings, $webform_submission) : NULL, + '#links' => $links, + ]; + } + + /** + * Render preview of distribution object. + */ + public function renderPreview(WebformHandlerSF2900 $handler, HandlerSettings $handlerSettings, WebformSubmissionInterface $submission): array { + $exceptions = []; + $warnings = []; + + $distributionObject = NULL; + $xml = []; + try { + $attachment = new Attachment('preview', Attachment::MIME_TYPE_PDF, 'preview.pdf'); + $distributionObject = $this->helper->buildDistributionObject($handlerSettings, $submission, $attachment); + } + catch (\Exception $exception) { + $exceptions[] = $exception; + } + + try { + $xml = $this->helper->renderXml($handlerSettings, $submission, validateXml: FALSE); + } + catch (\Throwable) { + // Silently ignore any errors. + } + + return [ + 'exceptions' => $exceptions, + 'warnings' => $warnings, + 'distribution_object' => $distributionObject, + 'xml' => $xml, ]; } diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php deleted file mode 100644 index 419a50e..0000000 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewRenderController.php +++ /dev/null @@ -1,83 +0,0 @@ -getHandler($webform_handler); - } - catch (\Exception) { - $handler = NULL; - } - - if (!$handler instanceof WebformHandlerSF2900) { - throw new NotFoundHttpException(); - } - - $handlerSettings = $this->settings->getHandlerSettings($handler); - - $exceptions = []; - $warnings = []; - - $distributionObject = NULL; - $xml = []; - try { - $attachment = new Attachment('preview', Attachment::MIME_TYPE_PDF, 'preview.pdf'); - $distributionObject = $this->helper->buildDistributionObject($handlerSettings, $submission, $attachment); - } - catch (\Exception $exception) { - $exceptions[] = $exception; - } - - try { - $xml = $this->helper->renderXml($handlerSettings, $submission, validateXml: FALSE); - } - catch (\Throwable) { - // Silently ignore any errors. - } - - $build = [ - '#theme' => ThemeHooks::DISTRIBUTION_OBJECT_PREVIEW_RENDER, - '#webform' => $webform, - '#handler' => $handler, - '#handler_settings' => $handlerSettings, - '#submission' => $submission, - '#exceptions' => $exceptions, - '#warnings' => $warnings, - '#distribution_object' => $distributionObject, - '#xml' => $xml, - ]; - - return new Response((string) $this->renderer->renderRoot($build)); - } - -} diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php index def210c..69b2bae 100644 --- a/src/Hook/ThemeHooks.php +++ b/src/Hook/ThemeHooks.php @@ -29,10 +29,11 @@ public function theme(array $existing, string $type, string $theme, string $path self::DISTRIBUTION_OBJECT_PREVIEW => [ 'variables' => [ 'webform' => NULL, + 'submission' => NULL, 'handler' => NULL, 'handler_settings' => NULL, - 'render_url' => NULL, - 'preview_urls' => [ + 'preview' => NULL, + 'links' => [ 'prev' => NULL, 'self' => NULL, 'next' => NULL, diff --git a/templates/base.html.twig b/templates/base.html.twig index 225ca88..5086f96 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -31,6 +31,11 @@ [disabled] { opacity: 50%; } + + iframe { + width: 100%; + height: 20rem; + } } {% endblock %} @@ -38,14 +43,14 @@ {% block navigation %}
{% endmacro %} -{% macro render_json(value) %} -
{{ value|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
+{% macro render_json(value, label) %} +
+ {{ label }} + + {% set data_url = 'data:application/json,' ~ (value|json_encode|url_encode) %} + + {{ 'Open data in new tab'|trans }} + +
{{ value|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
+
+{% endmacro %} + +{% macro render_xml(value, label) %} +
+ {{ label }} + + {% set data_url = 'data:text/xml,' ~ (value|url_encode) %} + + {{ 'Open data in new tab'|trans }} + +
+ {{ 'Raw content'|trans }} +
{{ value }}
+
+
{% endmacro %} diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig deleted file mode 100644 index 9ac88be..0000000 --- a/templates/os2forms-fordelingskomponent-distribution-object-preview-render.html.twig +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - {{ 'Preview'|trans }} - - - - -
- {% for exception in exceptions %} - - {% endfor %} - - {% for warning in warnings %} - - {% endfor %} - - {% set distribution_type = handler_settings.distributionObject.distributionType %} - {% if distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_JOURNALPOST') %} - {{ distribution_type }} -
{{ distribution_object|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
- {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_DOKUMENT') %} - {{ distribution_type }} -
{{ distribution_object|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
- {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR') %} - {% if distribution_object %} - {% set xml = distribution_object.meddelelse.formular.formularXml.any %} - {% set xml_template = handler_settings['xml_template']|default(null) %} - {% if xml_template %} -
- {{ 'XML template'|trans }} - - -
- {% endif %} - -
- {{ 'XML'|trans }} - -
- {% elseif xml %} - {% if xml.template %} -
- {{ 'XML template'|trans }} - -
{{ xml.template }}
-
- {% endif %} - -
- {{ 'XML'|trans }} -
{{ xml.rendered }}
-
- - {% if xml.context %} -
- {{ 'XML context'|trans }} - -
{{ _self.render_json(xml.context) }}
-
- {% endif %} - - {% endif %} - {% else %} - - {% endif %} - -
- - diff --git a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig index 75ad659..2086548 100644 --- a/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig +++ b/templates/os2forms-fordelingskomponent-distribution-object-preview.html.twig @@ -18,12 +18,63 @@ */ #} {% block content %} -
- {{ 'XML Template'|t }} -
{{ handler_settings.distributionObject.xmlTemplate }}
-
+ {% for exception in preview.exceptions %} + + {% endfor %} - {% if render_url %} - + {% for warning in preview.warnings %} + + {% endfor %} + + {% set distribution_type = handler_settings.distributionObject.distributionType %} + {% set distribution_object = preview.distribution_object|default(null) %} + {% if distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_JOURNALPOST') %} + {{ distribution_type }} + {{ _self.render_json(distribution_object) }} + {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_DOKUMENT') %} + {{ distribution_type }} + {{ _self.render_json(distribution_object) }} + {% elseif distribution_type == constant('\\Drupal\\os2forms_fordelingskomponent\\Settings\\DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR') %} + {{ distribution_type }} + {% if distribution_object %} +
+ {{ 'Distribution object'|trans }} + {{ _self.render_json(distribution_object) }} +
+ {% endif %} + + {% set xml = distribution_object.meddelelse.formular.formularXml.any|default(preview.xml.rendered) %} + {% if xml %} +
+ {{ 'XML'|trans }} + + {{ _self.render_xml(xml) }} +
+ {% endif %} + + {% set xml_template = handler_settings.distributionObject.xmlTemplate|default(null) %} + {% if xml_template %} +
+ {{ 'XML template'|trans }} + + {{ _self.render_xml(xml_template) }} +
+ {% endif %} + + {% if preview.xml.context %} +
+ {{ 'XML render context'|trans }} + + {{ _self.render_json(preview.xml.context) }} +
+ {% endif %} + {% else %} + {% endif %} {% endblock %} From 2990635d0b2757f7ecc1d5fd5e50b79e7b0dac40 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 23 Feb 2026 15:10:39 +0100 Subject: [PATCH 22/62] Updated route access checks --- os2forms_fordelingskomponent.routing.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index 0fd9a66..891afe7 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -16,7 +16,7 @@ os2forms_fordelingskomponent.routing_info: webform: type: "entity:webform" requirements: - _permission: "administer site configuration" + _entity_access: 'webform.update' os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview: path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/fordelingsobjekt/{webform_handler}/preview/{webform_submission}" @@ -31,5 +31,4 @@ os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview: webform_submission: type: "entity:webform_submission" requirements: - # @todo Harden this permission to allow only access when user has access to webform. - _permission: "view any webform submission" + _entity_access: 'webform.update' From cbac5519ca390b965b7f229b6a24147c6d11c01b Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 23 Feb 2026 15:32:41 +0100 Subject: [PATCH 23/62] Updated stuff --- os2forms_fordelingskomponent.routing.yml | 5 ++-- ...entDistributionObjectPreviewController.php | 20 +++++----------- src/Helper/WebformHelperSF2900.php | 24 +++++++++++++++++++ .../WebformHandler/WebformHandlerSF2900.php | 17 +++++++------ 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index 891afe7..84d7fce 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -16,14 +16,13 @@ os2forms_fordelingskomponent.routing_info: webform: type: "entity:webform" requirements: - _entity_access: 'webform.update' + _entity_access: "webform.update" os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview: path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/fordelingsobjekt/{webform_handler}/preview/{webform_submission}" defaults: _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentDistributionObjectPreviewController' _title: "Fordelingskomponent fordelingsobjekt preview" - webform_submission: null options: parameters: webform: @@ -31,4 +30,4 @@ os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview: webform_submission: type: "entity:webform_submission" requirements: - _entity_access: 'webform.update' + _entity_access: "webform_submission.view" diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 469b33e..9158984 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -40,7 +40,7 @@ public function __construct( /** * Builds the response. */ - public function __invoke(Request $request, WebformInterface $webform, string $webform_handler, ?WebformSubmissionInterface $webform_submission): array { + public function __invoke(Request $request, WebformInterface $webform, string $webform_handler, WebformSubmissionInterface $webform_submission): array { try { $handler = $webform->getHandler($webform_handler); } @@ -55,27 +55,19 @@ public function __invoke(Request $request, WebformInterface $webform, string $we $handlerSettings = $this->settings->getHandlerSettings($handler); // Get previous, self and next submission IDs. - $submissionIds = array_keys($this->submissionStorage->getQuery() - ->accessCheck() - ->condition('webform_id', $webform->id()) - ->sort('created', 'DESC') - ->execute()); - $currentSubmissionId = $webform_submission?->id(); + $submissionIds = array_values($this->helper->loadSubmissionIds($webform)); + $currentSubmissionId = $webform_submission->id(); $index = array_search($currentSubmissionId, $submissionIds); if (FALSE === $index) { - $currentSubmissionId = reset($submissionIds) ?: NULL; - $index = array_search($currentSubmissionId, $submissionIds); - } - if ($currentSubmissionId) { - $webform_submission = $this->submissionStorage->load($currentSubmissionId); + throw new NotFoundHttpException(); } $routeName = $request->attributes->get('_route'); $links = array_map( - static fn($submission) => Url::fromRoute($routeName, [ + static fn($submissionId) => Url::fromRoute($routeName, [ 'webform' => $webform->id(), 'webform_handler' => $handler->getHandlerId(), - 'webform_submission' => $submission, + 'webform_submission' => $submissionId, ]), array_filter([ 'prev' => $submissionIds[$index + 1] ?? NULL, diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index d32e596..0311fba 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -18,6 +18,7 @@ use Drupal\os2forms_fordelingskomponent\Settings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings; use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; +use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\WebformSubmissionStorageInterface; use Drupal\webform\WebformTokenManagerInterface; @@ -151,6 +152,29 @@ public function loadSubmission(int $id): ?WebformSubmissionInterface { return $this->webformSubmissionStorage->load($id); } + /** + * Load submission IDs for a webform. + */ + public function loadSubmissionIds(WebformInterface $webform): array { + return $this->webformSubmissionStorage->getQuery() + ->accessCheck() + ->condition('webform_id', $webform->id()) + ->sort('created', 'DESC') + ->sort('sid', 'DESC') + ->execute(); + } + + /** + * Load latest submission on a webform. + */ + public function loadLatestSubmission(WebformInterface $webform): ?WebformSubmissionInterface { + $submissionIds = $this->loadSubmissionIds($webform); + + $id = reset($submissionIds); + + return $id ? $this->loadSubmission($id) : NULL; + } + /** * Load queue. */ diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 36b2265..7dde426 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -347,13 +347,16 @@ public function getSummary() { $items = []; - $items[] = Link::createFromRoute( - $this->t('Preview distribution object'), - 'os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview', [ - 'webform' => $this->getWebform()->id(), - 'webform_handler' => $this->getHandlerId(), - ] - ); + if ($submission = $this->helper->loadLatestSubmission($this->getWebform())) { + $items[] = Link::createFromRoute( + $this->t('Preview distribution object'), + 'os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview', [ + 'webform' => $this->getWebform()->id(), + 'webform_handler' => $this->getHandlerId(), + 'webform_submission' => $submission->id(), + ] + ); + } if ($settings->distributionContext->kleEmne) { $items[] = Link::createFromRoute( From d05568cff55488eb9447fbf69f8f0d66fb42c65f Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 25 Feb 2026 08:05:25 +0100 Subject: [PATCH 24/62] More stuff --- os2forms_fordelingskomponent.install | 15 +++ os2forms_fordelingskomponent.services.yml | 6 + src/Helper/FordelingskomponentHelper.php | 106 +++++++++++++-- src/Hook/InstallHooks.php | 138 ++++++++++++++++++++ src/Model/AnvenderForsendelse.php | 29 ++++ src/Model/AnvenderForsendelseRepository.php | 108 +++++++++++++++ src/Model/TransactionContext.php | 23 ++++ src/Settings.php | 1 + src/Settings/HandlerSettings.php | 3 + 9 files changed, 415 insertions(+), 14 deletions(-) create mode 100644 os2forms_fordelingskomponent.install create mode 100644 src/Hook/InstallHooks.php create mode 100644 src/Model/AnvenderForsendelse.php create mode 100644 src/Model/AnvenderForsendelseRepository.php create mode 100644 src/Model/TransactionContext.php diff --git a/os2forms_fordelingskomponent.install b/os2forms_fordelingskomponent.install new file mode 100644 index 0000000..583f311 --- /dev/null +++ b/os2forms_fordelingskomponent.install @@ -0,0 +1,15 @@ +schema(); +} diff --git a/os2forms_fordelingskomponent.services.yml b/os2forms_fordelingskomponent.services.yml index a976501..ca6e109 100644 --- a/os2forms_fordelingskomponent.services.yml +++ b/os2forms_fordelingskomponent.services.yml @@ -13,9 +13,15 @@ services: Drupal\os2forms_fordelingskomponent\Settings: Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper: + tags: + - { name: 'event_subscriber' } Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900: Drupal\os2forms_fordelingskomponent\Helper\XmlHelper: + Drupal\os2forms_fordelingskomponent\Hook\InstallHooks: + Drupal\os2forms_fordelingskomponent\Hook\ThemeHooks: + + Drupal\os2forms_fordelingskomponent\Model\AnvenderForsendelseRepository: diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 7fd033b..d8aba8b 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -7,7 +7,10 @@ use Drupal\os2forms_fordelingskomponent\Exception\Exception; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; +use Drupal\os2forms_fordelingskomponent\Model\AnvenderForsendelse; +use Drupal\os2forms_fordelingskomponent\Model\AnvenderForsendelseRepository; use Drupal\os2forms_fordelingskomponent\Model\Attachment; +use Drupal\os2forms_fordelingskomponent\Model\TransactionContext; use Drupal\os2forms_fordelingskomponent\Model\XmlRenderResult; use Drupal\os2forms_fordelingskomponent\Settings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings; @@ -16,6 +19,7 @@ use Drupal\os2web_key\KeyHelper; use Drupal\webform\WebformSubmissionInterface; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; +use ItkDev\Serviceplatformen\Service\SF2900\Event\AfterServiceCallEvent; use ItkDev\Serviceplatformen\Service\SF2900\SF2900; use ItkDev\Serviceplatformen\SF2900\EnumType\AktoerTypeType; use ItkDev\Serviceplatformen\SF2900\EnumType\DokumenttypeType; @@ -32,6 +36,8 @@ use ItkDev\Serviceplatformen\SF2900\StructType\DistributionJournalPostType; use ItkDev\Serviceplatformen\SF2900\StructType\DokumentRegistreringType; use ItkDev\Serviceplatformen\SF2900\StructType\FordelingsmodtagerListResponseType; +use ItkDev\Serviceplatformen\SF2900\StructType\FordelingsobjektAfsendRequestType; +use ItkDev\Serviceplatformen\SF2900\StructType\FordelingsobjektAfsendResponseType; use ItkDev\Serviceplatformen\SF2900\StructType\FormularType; use ItkDev\Serviceplatformen\SF2900\StructType\FormularXMLType; use ItkDev\Serviceplatformen\SF2900\StructType\JournalNotatEgenskaberType; @@ -50,6 +56,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -57,7 +64,7 @@ * * @template T */ -final class FordelingskomponentHelper implements LoggerInterface { +final class FordelingskomponentHelper implements LoggerInterface, EventSubscriberInterface { use LoggerTrait; /** @@ -70,6 +77,7 @@ public function __construct( #[Autowire(service: 'key.repository')] private readonly KeyRepositoryInterface $keyRepository, private readonly KeyHelper $keyHelper, + private readonly AnvenderForsendelseRepository $anvenderForsendelseRepository, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent')] private readonly LoggerChannelInterface $logger, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent_submission')] @@ -86,7 +94,7 @@ public function getRoutingInfo(HandlerSettings $handlerSettings): ?Fordelingsmod return $this->sf2900()->getModtagerList( routingMyndighed: (string) $handlerSettings->sender->routingMyndighed, routingKLEEmne: (string) $handlerSettings->distributionContext->kleEmne, - routingHandlingFacet: $handlerSettings->distributionContext->handlingFacet, + routingHandlingFacet: $handlerSettings->distributionContext->handlingFacet, ); } @@ -108,17 +116,17 @@ public function buildDistributionObject( $distributionObject = match ($type) { DistributionObjectSettings::DISTRIBUTION_TYPE_JOURNALPOST => $this->buildDistributionJournalPostType( id: $id, - fraTidsPunkt: $fraTidsPunkt, - virkning: $virkning, - handlerSettings: $handlerSettings, + fraTidsPunkt: $fraTidsPunkt, + virkning: $virkning, + handlerSettings: $handlerSettings, ), DistributionObjectSettings::DISTRIBUTION_TYPE_DOKUMENT => $this->buildDistributionDokumentType( id: $id, - fraTidsPunkt: $fraTidsPunkt, - brevDato: $brevDato, - virkning: $virkning, - submission: $submission, - handlerSettings: $handlerSettings, + fraTidsPunkt: $fraTidsPunkt, + brevDato: $brevDato, + virkning: $virkning, + submission: $submission, + handlerSettings: $handlerSettings, ), DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR => $this->buildDistributionFormularType( id: $id, @@ -180,7 +188,7 @@ private function buildDistributionDokumentType( HandlerSettings $handlerSettings, ): DistributionDokumentType { return new DistributionDokumentType( - iD: $id, + iD: $id, kLEEmneForslag: $handlerSettings->distributionContext->kleEmne, registrering: new DokumentRegistreringType( fraTidsPunkt: SF2900::formatDateTime($fraTidsPunkt), @@ -195,7 +203,7 @@ private function buildDistributionDokumentType( rolle: VariantRolleType::VALUE_VARIANT, indeks: '1', variantAttributter: new VariantAttributterType( - // @todo What to use here? + // @todo What to use here? variantType: Attachment::FORMAT_NAME_PDF, ), delAttributter: new DelAttributterType( @@ -285,7 +293,11 @@ private function buildDistributionFormularType( /** * Render XML. */ - public function renderXml(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, bool $validateXml = TRUE): XmlRenderResult { + public function renderXml( + HandlerSettings $handlerSettings, + WebformSubmissionInterface $submission, + bool $validateXml = TRUE, + ): XmlRenderResult { $template = $handlerSettings->distributionObject->xmlTemplate; if (empty(trim((string) $template))) { throw new RuntimeException('Missing XML template'); @@ -295,7 +307,7 @@ public function renderXml(HandlerSettings $handlerSettings, WebformSubmissionInt return new XmlRenderResult( rendered: $this->xmlHelper->render($template, $context, validateXml: $validateXml), - template: $template, + template: $template, context: $context, ); } @@ -327,6 +339,12 @@ public function sendDokument( $transactionId = Serializer::createUuid(); + $this->setTransactionContext($transactionId, new TransactionContext( + transactionId: $transactionId, + handlerSettings: $handlerSettings, + submission: $submission, + )); + $response = $sf2900->afsend( transactionId: $transactionId, document: $dokument, @@ -443,4 +461,64 @@ private function cloneVirkning(VirkningType $virkning): VirkningType { return unserialize(serialize($virkning)); } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + // BeforeServiceCallEvent::class => 'beforeServiceCall',. + AfterServiceCallEvent::class => 'afterServiceCall', + ]; + } + + /** + * AfterServiceCallEvent event handler. + */ + public function afterServiceCall(AfterServiceCallEvent $event): void { + $request = $event->getRequest(); + $response = $event->getResponse(); + + if ($request instanceof FordelingsobjektAfsendRequestType) { + assert($response instanceof FordelingsobjektAfsendResponseType); + $anvenderTransaktionsId = $request->getAnmodning()->getDistributionContext()->getAnvenderTransaktionsID(); + $context = $this->getTransactionContext($anvenderTransaktionsId); + $this->anvenderForsendelseRepository->save( + new AnvenderForsendelse( + webformHandlerId: $context->handlerSettings->handlerId, + webformSubmissionId: $context->submission->id(), + anvenderTransaktionsId: $anvenderTransaktionsId, + request: $request, + distributionTransaktionsId: $response->getDistributionContext()->getDistributionTransktionsID(), + response: $response + ) + ); + } + } + + /** + * The transaction contexts. + * + * @var array + */ + private array $transactionContexts = []; + + /** + * Set transaction context. + */ + private function setTransactionContext( + string $transactionId, + TransactionContext $transactionContext, + ) { + $this->transactionContexts[$transactionId] = $transactionContext; + } + + /** + * Get transaction context. + */ + private function getTransactionContext( + string $transactionId, + ): TransactionContext { + return $this->transactionContexts[$transactionId]; + } + } diff --git a/src/Hook/InstallHooks.php b/src/Hook/InstallHooks.php new file mode 100644 index 0000000..979ced2 --- /dev/null +++ b/src/Hook/InstallHooks.php @@ -0,0 +1,138 @@ + [ + 'anvender_transaktions_id' => [ + 'description' => 'UUID', + 'type' => 'varchar', + 'length' => 36, + 'not null' => TRUE, + ], + 'created_at' => [ + 'description' => 'The Unix timestamp when the item was created.', + 'type' => 'int', + 'size' => 'big', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + 'updated_at' => [ + 'description' => 'The Unix timestamp when the item was updated.', + 'type' => 'int', + 'size' => 'big', + 'unsigned' => TRUE, + ], + 'distribution_transaktions_id' => [ + 'description' => 'UUID', + 'type' => 'varchar', + 'length' => 36, + ], + 'request' => [ + 'description' => 'The request (serialized).', + 'type' => 'text', + 'size' => 'big', + 'not null' => TRUE, + ], + 'response' => [ + 'description' => 'The response (serialized).', + 'type' => 'text', + 'size' => 'big', + ], + ], + 'primary key' => [ + 'anvender_transaktions_id', + ], + 'indexes' => [ + 'distribution_transaktions_id' => [ + 'distribution_transaktions_id', + ], + ], + ]; + + $createTable = static function (string $description, array $fields = [], array $foreignKeys = [], array $indexes = []) use ($baseSchema): array { + $table = $baseSchema + [ + 'description' => $description, + ]; + + foreach ($fields as $name => $spec) { + $table['fields'][$name] = $spec; + } + + foreach ($foreignKeys as $name => $spec) { + $table['foreign keys'][$name] = $spec; + } + + foreach ($indexes as $name => $spec) { + $table['indexes'][$name] = $spec; + } + + return $table; + }; + + $schema[self::TABLE_ANVENDER_FORSENDELSE] = $createTable( + description: 'Stores data on forsendelser.', + fields: [ + 'webform_handler_id' => [ + 'description' => 'Webform handler ID.', + 'type' => 'varchar', + 'length' => '256', + ], + 'webform_submission_id' => [ + 'description' => 'Webform submission ID. References {webform_submissions}.sid.', + 'type' => 'int', + 'size' => 'big', + 'unsigned' => TRUE, + ], + 'delivered_at' => [ + 'description' => 'The Unix timestamp when the item was delivered.', + 'type' => 'int', + 'size' => 'big', + 'unsigned' => TRUE, + ], + ], + foreignKeys: [ + 'webform_submission_id' => [ + 'table' => 'webform_submission', + 'columns' => [ + 'webform_submission_id' => 'sid', + ], + ], + ], + indexes: [ + 'webform_submission' => ['webform_handler_id', 'webform_submission_id'], + ] + ); + + $schema[self::TABLE_ANVENDER_KVITTERING] = $createTable( + description: 'Stores data on kvitteringer.', + ); + + $schema[self::TABLE_MODTAGER_FORSENDELSE] = $createTable( + description: 'Stores data on forsendelser.', + fields: [ + 'confirmed_at' => [ + 'description' => 'The Unix timestamp when the item was delivered.', + 'type' => 'int', + 'size' => 'big', + 'unsigned' => TRUE, + ], + ], + ); + + return $schema; + } + +} diff --git a/src/Model/AnvenderForsendelse.php b/src/Model/AnvenderForsendelse.php new file mode 100644 index 0000000..c55eae2 --- /dev/null +++ b/src/Model/AnvenderForsendelse.php @@ -0,0 +1,29 @@ +database + ->select(InstallHooks::TABLE_ANVENDER_FORSENDELSE, 't') + ->fields('t'); + + foreach ($criteria as $field => $condition) { + if (is_array($condition)) { + $query->condition(...$condition); + } + else { + $query->condition($field, $condition, '='); + } + } + + $statement = $query->execute(); + assert(NULL !== $statement); + $result = $statement->fetchAll(); + return array_map( + static fn(object $row) => new AnvenderForsendelse( + webformHandlerId: $row->webform_handler_id, + webformSubmissionId: $row->webform_submission_id, + anvenderTransaktionsId: $row->anvender_transaktions_id, + request: unserialize($row->request, options: ['allowed_classes' => TRUE]), + distributionTransaktionsId: $row->distribution_transaktions_id, + response: unserialize($row->response, options: ['allowed_classes' => TRUE]), + createdAt: $row->created_at, + updatedAt: $row->updated_at, + deliveredAt: $row->delivered_at, + ), + array: $result + ); + } + + /** + * Load forsendelser for a submission. + * + * @return AnvenderForsendelse[] + * The result. + */ + public function loadBySubmission(WebformSubmissionInterface $submission): array { + return $this->loadBy(['webform_submission_id' => $submission->id()]); + } + + /** + * Load forsendelse by transaktions-id. + */ + public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId): ?AnvenderForsendelse { + $result = $this->loadBy(['anvender_transaktions_id' => $anvenderTransaktionsId]); + + if (1 !== count($result)) { + return NULL; + } + + return reset($result); + } + + /** + * Save forsendelse. + */ + public function save(AnvenderForsendelse $forsendelse): string { + $now = \Drupal::time()->getCurrentTime(); + $forsendelse->createdAt ??= $now; + $forsendelse->updatedAt = $now; + return $this->database + // @todo Use upsert. + ->insert(InstallHooks::TABLE_ANVENDER_FORSENDELSE) + ->fields([ + 'webform_handler_id' => $forsendelse->webformHandlerId, + 'webform_submission_id' => $forsendelse->webformSubmissionId, + 'anvender_transaktions_id' => $forsendelse->anvenderTransaktionsId, + 'request' => serialize($forsendelse->request), + 'distribution_transaktions_id' => $forsendelse->distributionTransaktionsId, + 'response' => serialize($forsendelse->response), + 'created_at' => $forsendelse->createdAt, + 'updated_at' => $forsendelse->updatedAt, + 'delivered_at' => NULL, + ]) + ->execute(); + } + +} diff --git a/src/Model/TransactionContext.php b/src/Model/TransactionContext.php new file mode 100644 index 0000000..a463b1a --- /dev/null +++ b/src/Model/TransactionContext.php @@ -0,0 +1,23 @@ +getSettings(); $settings = new HandlerSettings([ + HandlerSettings::HANDLER_ID => $handler->gethandlerId(), HandlerSettings::SENDER => $this->getSenderSettings($handlerSettings[SenderSettings::NAME] ?? []), HandlerSettings::DISTRIBUTION_CONTEXT => $this->getDistributionContextSettings($handlerSettings[DistributionContextSettings::NAME] ?? []), HandlerSettings::DISTRIBUTION_OBJECT => $this->getDistributionObjectSettings($handlerSettings[DistributionObjectSettings::NAME] ?? []), diff --git a/src/Settings/HandlerSettings.php b/src/Settings/HandlerSettings.php index cfe590b..968ffde 100644 --- a/src/Settings/HandlerSettings.php +++ b/src/Settings/HandlerSettings.php @@ -12,6 +12,9 @@ final class HandlerSettings extends AbstractSettings { self::DISTRIBUTION_OBJECT => DistributionObjectSettings::class, ]; + const string HANDLER_ID = 'handler_id'; + public string $handlerId; + const string SENDER = 'sender'; public ?SenderSettings $sender = NULL; From 544028083fad1064f2c0a268a00ccb7bfd81ec87 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 25 Feb 2026 12:28:21 +0100 Subject: [PATCH 25/62] Cleaned up and improved --- .../webform.webform.os2_fdk_kp_anmoding.yml | 492 ------------------ os2forms_fordelingskomponent.routing.yml | 24 + os2forms_fordelingskomponent.services.yml | 4 +- src/Controller/AbstractController.php | 35 ++ ...entDistributionObjectPreviewController.php | 15 +- ...ngskomponentFordelingsobjectController.php | 50 ++ ...rdelingskomponentRoutingInfoController.php | 17 +- src/Helper/FordelingskomponentHelper.php | 5 +- src/Helper/WebformHelperSF2900.php | 10 +- src/Hook/InstallHooks.php | 13 +- src/Model/AnvenderForsendelseRepository.php | 108 ---- .../AnvenderForsendelse.php | 3 +- .../WebformHandler/WebformHandlerSF2900.php | 28 +- .../AnvenderForsendelseRepository.php | 174 +++++++ 14 files changed, 330 insertions(+), 648 deletions(-) delete mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml create mode 100644 src/Controller/AbstractController.php create mode 100644 src/Controller/Os2formsFordelingskomponentFordelingsobjectController.php delete mode 100644 src/Model/AnvenderForsendelseRepository.php rename src/Model/{ => Fordelingskomponent}/AnvenderForsendelse.php (87%) create mode 100644 src/Repository/AnvenderForsendelseRepository.php diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml deleted file mode 100644 index 9a1dc08..0000000 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2_fdk_kp_anmoding.yml +++ /dev/null @@ -1,492 +0,0 @@ -langcode: da -status: open -dependencies: - module: - - os2forms - - os2forms_fordelingskomponent - - webform_encrypt - - webform_entity_print - - webform_revisions - enforced: - module: - - os2forms_fordelingskomponent_examples -third_party_settings: - webform_encrypt: - element: - message: - encrypt: true - encrypt_profile: webform - afsend_content_pdf: - encrypt: true - encrypt_profile: webform - os2forms: - os2forms_email_handler: - enabled: 0 - email_recipients: "" - os2forms_nemid: - session_type: "" - webform_type: "" - nemlogin_auto_redirect: 0 - os2forms_nemlogin_openid_connect: - authentication_settings: - user_claim: "" - element_key: "" - error_message: "" - os2forms_nemid_address_protection: - nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour - nemlogin_hide_message: "" - os2forms_rest_api: - allowed_users: null - os2forms_sync: - publish: 0 - os2forms_webform_submission_log: - emails: "" - webform_entity_print: - template: - header: "" - footer: "" - css: "" - os2form_header: "" - os2form_colophon: "" - os2form_footer: "" - export_types: - pdf: - enabled: true - link_text: "" - link_attributes: {} - word_docx: - enabled: false - link_text: "" - link_attributes: {} -weight: 0 -open: null -close: null -uid: 1 -template: false -archive: false -id: os2_fdk_kp_anmoding -title: "OS2Forms Fordelingskomponent: Anmodning (KP)" -description: "" -categories: - - Example - - Fordelingskomponent -css: "" -javascript: "" -settings: - ajax: false - ajax_scroll_top: form - ajax_progress_type: "" - ajax_effect: "" - ajax_speed: null - page: true - page_submit_path: "" - page_confirm_path: "" - page_theme_name: "" - form_title: both - form_submit_once: false - form_open_message: "" - form_close_message: "" - form_exception_message: "" - form_previous_submissions: true - form_confidential: false - form_confidential_message: "" - form_disable_remote_addr: false - form_convert_anonymous: false - form_prepopulate: false - form_prepopulate_source_entity: false - form_prepopulate_source_entity_required: false - form_prepopulate_source_entity_type: "" - form_unsaved: false - form_disable_back: false - form_submit_back: false - form_disable_autocomplete: false - form_novalidate: false - form_disable_inline_errors: false - form_required: false - form_autofocus: false - form_details_toggle: false - form_reset: false - form_access_denied: default - form_access_denied_title: "" - form_access_denied_message: "" - form_access_denied_attributes: {} - form_file_limit: "" - form_attributes: {} - form_method: "" - form_action: "" - share: false - share_node: false - share_theme_name: "" - share_title: true - share_page_body_attributes: {} - submission_label: "" - submission_exception_message: "" - submission_locked_message: "" - submission_log: false - submission_excluded_elements: {} - submission_exclude_empty: false - submission_exclude_empty_checkbox: false - submission_views: {} - submission_views_replace: {} - submission_user_columns: {} - submission_user_duplicate: false - submission_access_denied: default - submission_access_denied_title: "" - submission_access_denied_message: "" - submission_access_denied_attributes: {} - previous_submission_message: "" - previous_submissions_message: "" - autofill: false - autofill_message: "" - autofill_excluded_elements: {} - wizard_progress_bar: true - wizard_progress_pages: false - wizard_progress_percentage: false - wizard_progress_link: false - wizard_progress_states: false - wizard_start_label: "" - wizard_preview_link: false - wizard_confirmation: true - wizard_confirmation_label: "" - wizard_auto_forward: true - wizard_auto_forward_hide_next_button: false - wizard_keyboard: true - wizard_track: "" - wizard_prev_button_label: "" - wizard_next_button_label: "" - wizard_toggle: false - wizard_toggle_show_label: "" - wizard_toggle_hide_label: "" - wizard_page_type: container - wizard_page_title_tag: h2 - preview: 0 - preview_label: "" - preview_title: "" - preview_message: "" - preview_attributes: {} - preview_excluded_elements: {} - preview_exclude_empty: true - preview_exclude_empty_checkbox: false - draft: none - draft_multiple: false - draft_auto_save: false - draft_saved_message: "" - draft_loaded_message: "" - draft_pending_single_message: "" - draft_pending_multiple_message: "" - confirmation_type: message - confirmation_url: "" - confirmation_title: "" - confirmation_message: "" - confirmation_attributes: {} - confirmation_back: true - confirmation_back_label: "" - confirmation_back_attributes: {} - confirmation_exclude_query: false - confirmation_exclude_token: false - confirmation_update: false - limit_total: null - limit_total_interval: null - limit_total_message: "" - limit_total_unique: false - limit_user: null - limit_user_interval: null - limit_user_message: "" - limit_user_unique: false - entity_limit_total: null - entity_limit_total_interval: null - entity_limit_user: null - entity_limit_user_interval: null - purge: all - purge_days: 30 - results_disabled: false - results_disabled_ignore: false - results_customize: false - token_view: false - token_update: false - token_delete: false - serial_disabled: false -access: - create: - roles: - - anonymous - - authenticated - users: {} - permissions: {} - view_any: - roles: {} - users: {} - permissions: {} - update_any: - roles: {} - users: {} - permissions: {} - delete_any: - roles: {} - users: {} - permissions: {} - purge_any: - roles: {} - users: {} - permissions: {} - view_own: - roles: {} - users: {} - permissions: {} - update_own: - roles: {} - users: {} - permissions: {} - delete_own: - roles: {} - users: {} - permissions: {} - administer: - roles: {} - users: {} - permissions: {} - test: - roles: {} - users: {} - permissions: {} - configuration: - roles: {} - users: {} - permissions: {} -handlers: - os2forms_fordelingskomponent_sf2900: - id: os2forms_fordelingskomponent_sf2900 - handler_id: os2forms_fordelingskomponent_sf2900 - label: "Fordelingskomponent (sf2900)" - notes: "" - status: true - conditions: {} - weight: 0 - settings: - distribution_context: - kle_emne: 01.01.01 - handling_facet: "" - brugervendt_noegle: "What is “Brugervendt nøgle”?" - titel: "Your document" - beskrivelse: "This is a very important document" - - distribution_object: - # distribution_type: JOURNALPOST - # distribution_type: DOKUMENT - distribution_type: FORMULAR - - # attachment_element: attachment - - xml_template: | - - -
- urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} - {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.kle_emne }} -
- - - {{ submission.data.fornavn }} - {{ submission.data.efternavn }} - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - {{ submission.completed.value|date("Y-m-d") }} - -
- - xsd_url: module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd - - os2forms_fordelingskomponent_sf2900_malformed_xml: - id: os2forms_fordelingskomponent_sf2900 - handler_id: os2forms_fordelingskomponent_sf2900_malformed_xml - label: "Fordelingskomponent (sf2900) (malformed XML)" - notes: "" - status: false - conditions: {} - weight: 0 - settings: - distribution_context: - kle_emne: 01.01.01 - handling_facet: "" - brugervendt_noegle: "What is “Brugervendt nøgle”?" - titel: "Your document" - beskrivelse: "This is a very important document" - - distribution_object: - distribution_type: FORMULAR - - xml_template: | - - -
- urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} - {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.kle_emne }} - - - {{ submission.data.fornavn }} - {{ submission.data.efternavn }} - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - {{ submission.completed.value|date("Y-m-d") }} - - - - xsd_url: module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd - - os2forms_fordelingskomponent_sf2900_invalid_twig: - id: os2forms_fordelingskomponent_sf2900 - handler_id: os2forms_fordelingskomponent_sf2900_invalid_twig - label: "Fordelingskomponent (sf2900) (invalid Twig)" - notes: "" - status: false - conditions: {} - weight: 0 - settings: - distribution_context: - kle_emne: 01.01.01 - handling_facet: "" - brugervendt_noegle: "What is “Brugervendt nøgle”?" - titel: "Your document" - beskrivelse: "This is a very important document" - - distribution_object: - distribution_type: FORMULAR - - xml_template: | - - -
- urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} - {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.kle_emne -
- - - {{ submission.data.fornavn }} - {{ submission.data.efternavn }} - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - {{ submission.completed.value|date("Y-m-d") }} - -
- - xsd_url: module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd - - os2forms_fordelingskomponent_sf2900_invalid_xml: - id: os2forms_fordelingskomponent_sf2900 - handler_id: os2forms_fordelingskomponent_sf2900_invalid_xml - label: "Fordelingskomponent (sf2900) (invalid XML)" - notes: "" - status: false - conditions: {} - weight: 0 - settings: - sf2900: - kle_emne: 01.01.01 - handling_facet: "" - attachment_element: attachment - brugervendt_noegle: "What is “Brugervendt nøgle”?" - titel: "Your document" - beskrivelse: "This is a very important document" - - # distribution_type: JOURNALPOST - # distribution_type: DOKUMENT - distribution_type: FORMULAR - - xml_template: | - - -
- urn:oio:cvr-nr:{{ handler.settings.cvr|default(module.settings.sf2900.sender_id|default("")) }} - {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.kle_emne }} -
- - - {{ submission.data.fornavn }} - {{ submission.data.efternavn }} - urn:oio:cpr:0000000000 - - - - Medicin - - - Underskrift0 - {{ submission.completed.value|date("Y-m-d") }} - -
- - xsd_url: module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/SP501.xsd - -variants: {} - -elements: |- - ansoeger_oplysninger: - '#type': fieldset - '#title': AnsoegerOplysninger - ansoeger: - '#type': fieldset - '#title': Ansøger - fornavn: - '#type': textfield - '#title': Fornavn - mellemnavn: - '#type': textfield - '#title': Mellemnavn - efternavn: - '#type': textfield - '#title': Efternavn - personnummer: - '#type': textfield - '#title': Personnummer - telefonnummer: - '#type': textfield - '#title': Telefonnummer - sagstype: - '#type': fieldset - '#title': Sagstype - almindeligt_helbredstillaeg: - '#type': select - '#title': Almindeligt helbredstillaeg - '#options': - Medicin: Medicin - Tandbehandling: Tandbehandling - Fodbehandling: Fodbehandling - Fysioterapi: Fysioterapi - Kiropraktik: Kiropraktik - Psykologhjaelp: Psykologhjaelp - Hoereappartbehandling: Hoereapparatbehandling - afsend_content_pdf: - '#type': 'entity_print_attachment:pdf' - '#title': 'Fordelingskomponent (PDF) hest' - '#display_on': view - '#filename': hat-og-briller.pdf - attachment: - '#type': os2forms_attachment - '#title': attachment - '#export_type': pdf - '#digital_signature': 0 - '#excluded_elements': { } - '#exclude_empty': 0 - '#exclude_empty_checkbox': 0 diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index 84d7fce..e0ba8c9 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -31,3 +31,27 @@ os2forms_fordelingskomponent.fordelingskomponent_distribution_object.preview: type: "entity:webform_submission" requirements: _entity_access: "webform_submission.view" + +os2forms_fordelingskomponent.distribution_object.index: + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/{webform_handler}/fordelingsobject" + defaults: + _title: "Fordelingskomponent fordelingsobject index" + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentFordelingsobjectController::index' + options: + parameters: + webform: + type: "entity:webform" + requirements: + _entity_access: "webform.update" + +os2forms_fordelingskomponent.distribution_object.show: + path: "/admin/structure/webform/manage/{webform}/os2forms_fordelingskomponent/{webform_handler}/fordelingsobject/{anvender_transaktions_id}" + defaults: + _title: "Fordelingskomponent fordelingsobject show" + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Os2formsFordelingskomponentFordelingsobjectController::show' + options: + parameters: + webform: + type: "entity:webform" + requirements: + _entity_access: "webform.update" diff --git a/os2forms_fordelingskomponent.services.yml b/os2forms_fordelingskomponent.services.yml index ca6e109..740a7ca 100644 --- a/os2forms_fordelingskomponent.services.yml +++ b/os2forms_fordelingskomponent.services.yml @@ -14,7 +14,7 @@ services: Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper: tags: - - { name: 'event_subscriber' } + - { name: "event_subscriber" } Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900: @@ -24,4 +24,4 @@ services: Drupal\os2forms_fordelingskomponent\Hook\ThemeHooks: - Drupal\os2forms_fordelingskomponent\Model\AnvenderForsendelseRepository: + Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository: diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php new file mode 100644 index 0000000..ec51720 --- /dev/null +++ b/src/Controller/AbstractController.php @@ -0,0 +1,35 @@ +getHandler($handlerID); + } + catch (\Exception) { + $handler = NULL; + } + + if (!$handler instanceof WebformHandlerSF2900) { + throw new NotFoundHttpException(); + } + + return $handler; + } + +} diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 9158984..84e3c34 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -4,7 +4,6 @@ namespace Drupal\os2forms_fordelingskomponent\Controller; -use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Url; use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; @@ -22,7 +21,7 @@ /** * Returns responses for Fordelingskomponent routes. */ -final class Os2formsFordelingskomponentDistributionObjectPreviewController extends ControllerBase { +final class Os2formsFordelingskomponentDistributionObjectPreviewController extends AbstractController { /** * The webform submission storage. @@ -41,17 +40,7 @@ public function __construct( * Builds the response. */ public function __invoke(Request $request, WebformInterface $webform, string $webform_handler, WebformSubmissionInterface $webform_submission): array { - try { - $handler = $webform->getHandler($webform_handler); - } - catch (\Exception) { - $handler = NULL; - } - - if (!$handler instanceof WebformHandlerSF2900) { - throw new NotFoundHttpException(); - } - + $handler = $this->getHandler($webform, $webform_handler); $handlerSettings = $this->settings->getHandlerSettings($handler); // Get previous, self and next submission IDs. diff --git a/src/Controller/Os2formsFordelingskomponentFordelingsobjectController.php b/src/Controller/Os2formsFordelingskomponentFordelingsobjectController.php new file mode 100644 index 0000000..41bb5d4 --- /dev/null +++ b/src/Controller/Os2formsFordelingskomponentFordelingsobjectController.php @@ -0,0 +1,50 @@ +getHandler($webform, $webform_handler); + $items = $this->repository->loadByWebformAndHandler($webform, $handler); + + return new JsonResponse($items); + } + + /** + * Builds the response. + */ + public function show(WebformInterface $webform, string $webform_handler, string $anvender_transaktions_id): Response { + $handler = $this->getHandler($webform, $webform_handler); + + $item = $this->repository->loadByAnvenderTransaktionsId($anvender_transaktions_id); + if (NULL === $item || $item->webformId !== $webform->id() || $item->webformHandlerId != $handler->getHandlerId()) { + throw new NotFoundHttpException(); + } + + return new JsonResponse($item); + } + +} diff --git a/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php index c2bd7ec..1940065 100644 --- a/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php +++ b/src/Controller/Os2formsFordelingskomponentRoutingInfoController.php @@ -4,18 +4,15 @@ namespace Drupal\os2forms_fordelingskomponent\Controller; -use Drupal\Core\Controller\ControllerBase; use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; use Drupal\os2forms_fordelingskomponent\Hook\ThemeHooks; -use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900; use Drupal\os2forms_fordelingskomponent\Settings; use Drupal\webform\WebformInterface; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Returns responses for Fordelingskomponent routes. */ -final class Os2formsFordelingskomponentRoutingInfoController extends ControllerBase { +final class Os2formsFordelingskomponentRoutingInfoController extends AbstractController { /** * Constructor. @@ -30,17 +27,7 @@ public function __construct( * Builds the response. */ public function __invoke(WebformInterface $webform, string $webform_handler): array { - try { - $handler = $webform->getHandler($webform_handler); - } - catch (\Exception) { - $handler = NULL; - } - - if (!$handler instanceof WebformHandlerSF2900) { - throw new NotFoundHttpException(); - } - + $handler = $this->getHandler($webform, $webform_handler); $handlerSettings = $this->settings->getHandlerSettings($handler); $info = $this->helper->getRoutingInfo($handlerSettings); diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index d8aba8b..499a1a9 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -7,11 +7,11 @@ use Drupal\os2forms_fordelingskomponent\Exception\Exception; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; -use Drupal\os2forms_fordelingskomponent\Model\AnvenderForsendelse; -use Drupal\os2forms_fordelingskomponent\Model\AnvenderForsendelseRepository; use Drupal\os2forms_fordelingskomponent\Model\Attachment; +use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse; use Drupal\os2forms_fordelingskomponent\Model\TransactionContext; use Drupal\os2forms_fordelingskomponent\Model\XmlRenderResult; +use Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository; use Drupal\os2forms_fordelingskomponent\Settings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings; use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; @@ -484,6 +484,7 @@ public function afterServiceCall(AfterServiceCallEvent $event): void { $context = $this->getTransactionContext($anvenderTransaktionsId); $this->anvenderForsendelseRepository->save( new AnvenderForsendelse( + webformId: $context->submission->getWebform()->id(), webformHandlerId: $context->handlerSettings->handlerId, webformSubmissionId: $context->submission->id(), anvenderTransaktionsId: $anvenderTransaktionsId, diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 0311fba..76b1e3e 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -15,6 +15,7 @@ use Drupal\os2forms_fordelingskomponent\Model\XmlRenderResult; use Drupal\os2forms_fordelingskomponent\Plugin\AdvancedQueue\JobType\FordelingskomponentSF2900; use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900; +use Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository; use Drupal\os2forms_fordelingskomponent\Settings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings; use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; @@ -59,6 +60,7 @@ public function __construct( #[Autowire(service: 'plugin.manager.element_info')] private readonly ElementInfoManager $elementInfoManager, private readonly FordelingskomponentHelper $helper, + private readonly AnvenderForsendelseRepository $anvenderForsendelseRepository, #[Autowire(service: 'webform.token_manager')] private readonly WebformTokenManagerInterface $webformTokenManager, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent')] @@ -293,8 +295,14 @@ public function processJob(Job $job): JobResult { /** * Delete messages. + * + * @param \Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900 $handler + * The handler. + * @param \Drupal\webform\WebformSubmissionInterface[] $webform_submissions + * The webform submissions. */ - public function deleteMessages(array $array) { + public function deleteMessages(WebformHandlerSF2900 $handler, array $webform_submissions) { + $this->anvenderForsendelseRepository->deleteBySubmissions($webform_submissions); // @todo Clean up } diff --git a/src/Hook/InstallHooks.php b/src/Hook/InstallHooks.php index 979ced2..497aa69 100644 --- a/src/Hook/InstallHooks.php +++ b/src/Hook/InstallHooks.php @@ -6,9 +6,9 @@ * Install hook implementations. */ final class InstallHooks { - const TABLE_ANVENDER_FORSENDELSE = 'os2forms_fordelingskomponent_anvender_forsendelse'; - const TABLE_ANVENDER_KVITTERING = 'os2forms_fordelingskomponent_anvender_kvittering'; - const TABLE_MODTAGER_FORSENDELSE = 'os2forms_fordelingskomponent_modtager_forsendelse'; + const string TABLE_ANVENDER_FORSENDELSE = 'os2forms_fordelingskomponent_anvender_forsendelse'; + const string TABLE_ANVENDER_KVITTERING = 'os2forms_fordelingskomponent_anvender_kvittering'; + const string TABLE_MODTAGER_FORSENDELSE = 'os2forms_fordelingskomponent_modtager_forsendelse'; /** * Implements hook_schema(). @@ -85,6 +85,11 @@ public function schema(): array { $schema[self::TABLE_ANVENDER_FORSENDELSE] = $createTable( description: 'Stores data on forsendelser.', fields: [ + 'webform_id' => [ + 'description' => 'Webform ID.', + 'type' => 'varchar', + 'length' => '256', + ], 'webform_handler_id' => [ 'description' => 'Webform handler ID.', 'type' => 'varchar', @@ -112,7 +117,7 @@ public function schema(): array { ], ], indexes: [ - 'webform_submission' => ['webform_handler_id', 'webform_submission_id'], + 'webform_submission' => ['webform_id', 'webform_handler_id', 'webform_submission_id'], ] ); diff --git a/src/Model/AnvenderForsendelseRepository.php b/src/Model/AnvenderForsendelseRepository.php deleted file mode 100644 index 60fb685..0000000 --- a/src/Model/AnvenderForsendelseRepository.php +++ /dev/null @@ -1,108 +0,0 @@ -database - ->select(InstallHooks::TABLE_ANVENDER_FORSENDELSE, 't') - ->fields('t'); - - foreach ($criteria as $field => $condition) { - if (is_array($condition)) { - $query->condition(...$condition); - } - else { - $query->condition($field, $condition, '='); - } - } - - $statement = $query->execute(); - assert(NULL !== $statement); - $result = $statement->fetchAll(); - return array_map( - static fn(object $row) => new AnvenderForsendelse( - webformHandlerId: $row->webform_handler_id, - webformSubmissionId: $row->webform_submission_id, - anvenderTransaktionsId: $row->anvender_transaktions_id, - request: unserialize($row->request, options: ['allowed_classes' => TRUE]), - distributionTransaktionsId: $row->distribution_transaktions_id, - response: unserialize($row->response, options: ['allowed_classes' => TRUE]), - createdAt: $row->created_at, - updatedAt: $row->updated_at, - deliveredAt: $row->delivered_at, - ), - array: $result - ); - } - - /** - * Load forsendelser for a submission. - * - * @return AnvenderForsendelse[] - * The result. - */ - public function loadBySubmission(WebformSubmissionInterface $submission): array { - return $this->loadBy(['webform_submission_id' => $submission->id()]); - } - - /** - * Load forsendelse by transaktions-id. - */ - public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId): ?AnvenderForsendelse { - $result = $this->loadBy(['anvender_transaktions_id' => $anvenderTransaktionsId]); - - if (1 !== count($result)) { - return NULL; - } - - return reset($result); - } - - /** - * Save forsendelse. - */ - public function save(AnvenderForsendelse $forsendelse): string { - $now = \Drupal::time()->getCurrentTime(); - $forsendelse->createdAt ??= $now; - $forsendelse->updatedAt = $now; - return $this->database - // @todo Use upsert. - ->insert(InstallHooks::TABLE_ANVENDER_FORSENDELSE) - ->fields([ - 'webform_handler_id' => $forsendelse->webformHandlerId, - 'webform_submission_id' => $forsendelse->webformSubmissionId, - 'anvender_transaktions_id' => $forsendelse->anvenderTransaktionsId, - 'request' => serialize($forsendelse->request), - 'distribution_transaktions_id' => $forsendelse->distributionTransaktionsId, - 'response' => serialize($forsendelse->response), - 'created_at' => $forsendelse->createdAt, - 'updated_at' => $forsendelse->updatedAt, - 'delivered_at' => NULL, - ]) - ->execute(); - } - -} diff --git a/src/Model/AnvenderForsendelse.php b/src/Model/Fordelingskomponent/AnvenderForsendelse.php similarity index 87% rename from src/Model/AnvenderForsendelse.php rename to src/Model/Fordelingskomponent/AnvenderForsendelse.php index c55eae2..2a4fe48 100644 --- a/src/Model/AnvenderForsendelse.php +++ b/src/Model/Fordelingskomponent/AnvenderForsendelse.php @@ -1,6 +1,6 @@ helper->deleteMessages([$webform_submission]); + $this->helper->deleteMessages($this, [$webform_submission]); } /** @@ -323,7 +323,7 @@ public function postDelete(WebformSubmissionInterface $webform_submission) { * @phpstan-return void */ public function postPurge(array $webform_submissions) { - $this->helper->deleteMessages($webform_submissions); + $this->helper->deleteMessages($this, $webform_submissions); } /** @@ -347,6 +347,16 @@ public function getSummary() { $items = []; + if ($settings->distributionContext->kleEmne) { + $items[] = Link::createFromRoute( + $this->t('Show routing info'), + 'os2forms_fordelingskomponent.routing_info', [ + 'webform' => $this->getWebform()->id(), + 'webform_handler' => $this->getHandlerId(), + ] + ); + } + if ($submission = $this->helper->loadLatestSubmission($this->getWebform())) { $items[] = Link::createFromRoute( $this->t('Preview distribution object'), @@ -356,16 +366,14 @@ public function getSummary() { 'webform_submission' => $submission->id(), ] ); - } - if ($settings->distributionContext->kleEmne) { $items[] = Link::createFromRoute( - $this->t('Show routing info'), - 'os2forms_fordelingskomponent.routing_info', [ - 'webform' => $this->getWebform()->id(), - 'webform_handler' => $this->getHandlerId(), - ] - ); + $this->t('Distribution objects'), + 'os2forms_fordelingskomponent.distribution_object.list', [ + 'webform' => $this->getWebform()->id(), + 'webform_handler' => $this->getHandlerId(), + ] + ); } $items[] = Link::createFromRoute( diff --git a/src/Repository/AnvenderForsendelseRepository.php b/src/Repository/AnvenderForsendelseRepository.php new file mode 100644 index 0000000..dab7d7b --- /dev/null +++ b/src/Repository/AnvenderForsendelseRepository.php @@ -0,0 +1,174 @@ +database + ->select(InstallHooks::TABLE_ANVENDER_FORSENDELSE, 't') + ->fields('t'); + + foreach ($conditions as $condition) { + $query->condition(...$condition); + } + + $statement = $query->execute(); + assert(NULL !== $statement); + $result = $statement->fetchAll(); + return array_map( + static fn(object $row) => new AnvenderForsendelse( + webformId: $row->webform_id, + webformHandlerId: $row->webform_handler_id, + webformSubmissionId: $row->webform_submission_id, + anvenderTransaktionsId: $row->anvender_transaktions_id, + request: unserialize($row->request, options: ['allowed_classes' => TRUE]), + distributionTransaktionsId: $row->distribution_transaktions_id, + response: unserialize($row->response, options: ['allowed_classes' => TRUE]), + createdAt: $row->created_at, + updatedAt: $row->updated_at, + deliveredAt: $row->delivered_at, + ), + array: $result + ); + } + + /** + * Load forsendelser for a submission. + * + * @return \Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse[] + * The result. + */ + public function loadBySubmission(WebformSubmissionInterface $submission): array { + return $this->loadBy([ + ['webform_submission_id', $submission->id()], + ]); + } + + /** + * Load by ... + */ + public function loadByWebformAndHandler(WebformInterface $webform, WebformHandlerSF2900 $handler) { + return $this->loadBy([ + ['webform_id', $webform->id()], + ['webform_handler_id', $handler->gethandlerId()], + ]); + } + + /** + * Delete by submission. + * + * @param \Drupal\webform\WebformSubmissionInterface[] $submissions + * The submissions. + */ + public function deleteBySubmissions(array $submissions): int { + if (empty($submissions)) { + return 0; + } + + try { + $ids = array_map(static fn(WebformSubmissionInterface $submission) => $submission->id(), $submissions); + return $this->database->delete(InstallHooks::TABLE_ANVENDER_FORSENDELSE) + ->condition('webform_submission_id', $ids, 'IN') + ->execute(); + } + catch (\Exception $exception) { + $this->logger->error('Error deleting forsendelser for submissions @submissions: @message', [ + '@submission' => implode(', ', $ids), + '@message' => $exception->getMessage(), + 'exception' => $exception, + ]); + } + } + + /** + * Load forsendelse by transaktions-id. + */ + public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId): ?AnvenderForsendelse { + $result = $this->loadBy([ + ['anvender_transaktions_id', $anvenderTransaktionsId], + ]); + + if (1 !== count($result)) { + return NULL; + } + + return reset($result); + } + + /** + * Save forsendelse. + */ + public function save(AnvenderForsendelse $forsendelse): bool { + try { + $now = \Drupal::time()->getCurrentTime(); + $forsendelse->createdAt ??= $now; + $forsendelse->updatedAt = $now; + + $fields = [ + 'webform_id' => $forsendelse->webformId, + 'webform_handler_id' => $forsendelse->webformHandlerId, + 'webform_submission_id' => $forsendelse->webformSubmissionId, + 'anvender_transaktions_id' => $forsendelse->anvenderTransaktionsId, + 'request' => serialize($forsendelse->request), + 'distribution_transaktions_id' => $forsendelse->distributionTransaktionsId, + 'response' => serialize($forsendelse->response), + 'created_at' => $forsendelse->createdAt, + 'updated_at' => $forsendelse->updatedAt, + 'delivered_at' => $forsendelse->deliveredAt, + ]; + if (NULL === $this->loadByAnvenderTransaktionsId($forsendelse->anvenderTransaktionsId)) { + $this->database + ->insert(InstallHooks::TABLE_ANVENDER_FORSENDELSE) + ->fields($fields) + ->execute(); + } + else { + $this->database + ->update(InstallHooks::TABLE_ANVENDER_FORSENDELSE) + ->condition('anvender_transaktions_id', $forsendelse->anvenderTransaktionsId) + ->fields($fields) + ->execute(); + } + + return TRUE; + } + catch (\Exception $exception) { + $this->logger->error('Error saving forsendelse: @message', [ + '@message' => $exception->getMessage(), + 'exception' => $exception, + ]); + } + + return FALSE; + } + +} From 04a1c5691b172468b1de84bb12591fe2fdaf0161 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 2 Mar 2026 12:09:38 +0100 Subject: [PATCH 26/62] Cleaned up --- composer.json | 1 + .../WebformHandler/WebformHandlerSF2900.php | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index aa2f294..69dfc37 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ ], "require": { "ext-dom": "*", + "ext-xsl": "*", "drupal/system_stream_wrapper": "^2.1", "drush/drush": "^12 || ^13", "itk-dev/serviceplatformen": "dev-feature/SF2900-Fordelingskomponenten as 1.7.9999", diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 1b4f024..1d1f268 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -13,6 +13,7 @@ use Drupal\os2forms_fordelingskomponent\Settings\DistributionContextSettings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings; use Drupal\webform\Plugin\WebformHandlerBase; +use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -64,6 +65,13 @@ public static function create(ContainerInterface $container, array $configuratio return $instance; } + /** + * {@inheritdoc} + */ + public function getOffCanvasWidth(): string { + return WebformDialogHelper::DIALOG_NONE; + } + /** * {@inheritdoc} */ @@ -376,14 +384,6 @@ public function getSummary() { ); } - $items[] = Link::createFromRoute( - $this->t('Edit handler'), - 'entity.webform.handler.edit_form', [ - 'webform' => $this->getWebform()->id(), - 'webform_handler' => $this->getHandlerId(), - ] - ); - $build['links'] = [ '#theme' => 'item_list', '#items' => $items, From 832b1c29cf5715475a83669fccc83911a5bfd032 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 2 Mar 2026 13:07:30 +0100 Subject: [PATCH 27/62] FordelingskvitteringModtag --- README.md | 28 + composer.json | 1 + os2forms_fordelingskomponent.routing.yml | 11 + os2forms_fordelingskomponent.services.yml | 2 + .../DistributionServiceAnvenderV2.wsdl | 58 ++ .../DistributionServiceModtagerV2.wsdl | 56 ++ .../DistributionServiceMsgV2.xsd | 40 ++ .../DistributionServiceTypesV2.xsd | 550 ++++++++++++++++++ .../AbstractSoapController.php | 79 +++ .../FordelingskvitteringModtagController.php | 78 +++ src/Exception/SoapException.php | 10 + .../AnvenderKvittering.php | 26 + src/Repository/AbstractRepository.php | 23 + .../AnvenderForsendelseRepository.php | 33 +- .../AnvenderKvitteringRepository.php | 114 ++++ 15 files changed, 1090 insertions(+), 19 deletions(-) create mode 100644 resources/sf2900/SF2900_EP_MS1-2/DistributionServiceAnvenderV2.wsdl create mode 100644 resources/sf2900/SF2900_EP_MS1-2/DistributionServiceModtagerV2.wsdl create mode 100644 resources/sf2900/SF2900_EP_MS1-2/DistributionServiceMsgV2.xsd create mode 100644 resources/sf2900/SF2900_EP_MS1-2/DistributionServiceTypesV2.xsd create mode 100644 src/Controller/Fordelingskomponent/AbstractSoapController.php create mode 100644 src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php create mode 100644 src/Exception/SoapException.php create mode 100644 src/Model/Fordelingskomponent/AnvenderKvittering.php create mode 100644 src/Repository/AbstractRepository.php create mode 100644 src/Repository/AnvenderKvitteringRepository.php diff --git a/README.md b/README.md index 4c109c0..9d0423c 100644 --- a/README.md +++ b/README.md @@ -88,3 +88,31 @@ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer. # Now we can install what we actually need. docker compose run --rm phpfpm composer install ``` + +### Kvittering + +[Kom godt i gang - Fordelingskomponenten, side +27](https://digitaliseringskataloget.dk/files/integration-files/150620210923/Kom%20godt%20i%20gang%20-%20Fordelingskomponenten.pdf#page=27) + +``` shell name=test-anvender-FordelingskvitteringModtag substitutions='«base url»: http://selvbetjening.local.itkdev.dk' +# curl --verbose --insecure --location «base url»/os2forms-fordelingskomponent/sf2900/2.4/FordelingskvitteringModtag --head + +curl --verbose --insecure --location «base url»/os2forms-fordelingskomponent/sf2900/2.4/FordelingskvitteringModtag --header 'content-type: application/soap+xml' --data @- <<'XML' + + + + + + ACCEPTERET + Forretning + + + 752bc93a-37cb-46db-9fb1-d5f4f7e3964e + d8101c99-0262-4a97-ac75-5685a6c6494a + + + + + +XML +``` diff --git a/composer.json b/composer.json index 69dfc37..79acdaa 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ ], "require": { "ext-dom": "*", + "ext-soap": "*", "ext-xsl": "*", "drupal/system_stream_wrapper": "^2.1", "drush/drush": "^12 || ^13", diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index e0ba8c9..eb32433 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -55,3 +55,14 @@ os2forms_fordelingskomponent.distribution_object.show: type: "entity:webform" requirements: _entity_access: "webform.update" + +os2forms_fordelingskomponent.sf2900_2_4_FordelingskvitteringModtag: + path: '/os2forms-fordelingskomponent/sf2900/2.4/FordelingskvitteringModtag' + defaults: + _title: 'SF2900 2.4 FordelingskvitteringModtag' + _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Fordelingskomponent\FordelingskvitteringModtagController' + methods: [HEAD, POST] + requirements: + _permission: 'access content' + options: + no_cache: 'TRUE' diff --git a/os2forms_fordelingskomponent.services.yml b/os2forms_fordelingskomponent.services.yml index 740a7ca..fc385df 100644 --- a/os2forms_fordelingskomponent.services.yml +++ b/os2forms_fordelingskomponent.services.yml @@ -25,3 +25,5 @@ services: Drupal\os2forms_fordelingskomponent\Hook\ThemeHooks: Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository: + + Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository: diff --git a/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceAnvenderV2.wsdl b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceAnvenderV2.wsdl new file mode 100644 index 0000000..0aece94 --- /dev/null +++ b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceAnvenderV2.wsdl @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceModtagerV2.wsdl b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceModtagerV2.wsdl new file mode 100644 index 0000000..820feea --- /dev/null +++ b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceModtagerV2.wsdl @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceMsgV2.xsd b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceMsgV2.xsd new file mode 100644 index 0000000..b650b15 --- /dev/null +++ b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceMsgV2.xsd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceTypesV2.xsd b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceTypesV2.xsd new file mode 100644 index 0000000..480238e --- /dev/null +++ b/resources/sf2900/SF2900_EP_MS1-2/DistributionServiceTypesV2.xsd @@ -0,0 +1,550 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Controller/Fordelingskomponent/AbstractSoapController.php b/src/Controller/Fordelingskomponent/AbstractSoapController.php new file mode 100644 index 0000000..673bc0c --- /dev/null +++ b/src/Controller/Fordelingskomponent/AbstractSoapController.php @@ -0,0 +1,79 @@ +wsdl, [ + 'classmap' => ClassMap::get(), + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, + ]); + + $server->setObject($this); + + $response = new Response(); + $response->headers->set('content-type', 'application/soap+xml'); + + ob_start(); + try { + $server->handle(); + } + catch (\Throwable $e) { + $this->logger->error('SOAP server error: @message', [ + '@message' => $e->getMessage(), + 'exception' => $e, + ]); + $server->fault($e->getCode(), $e->getMessage()); + } + $response->setContent(ob_get_clean()); + + // Returning the response will result in + // + // nginx-1 | 2026/03/02 12:46:21 [error] 36#36: *13 upstream sent + // duplicate header line: "Content-Length: 302", previous value: + // "Content-Length: 302" while reading response header from upstream … + // . + $this->sendResponse($response); + + // Ensure nginx and proxy do not cache. + $response->headers->set('x-accel-buffering', 'no'); + // Ensure browser do not cache. + $response->headers->set('cache-control', 'no-cache, no-store, private'); + + return $response; + } + + /** + * Send response. + */ + private function sendResponse(Response $response): void { + foreach ($response->headers->all() as $name => $value) { + header($name . ': ' . reset($value)); + } + echo $response->getContent(); + exit(); + } + +} diff --git a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php new file mode 100644 index 0000000..38a26b4 --- /dev/null +++ b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php @@ -0,0 +1,78 @@ +getForretningskvittering(); + // @todo Do something with the kvittering. + $context = $request->getDistributionContext(); + + $forsendelse = $this->forsendelseRepository->loadByAnvenderTransaktionsId( + anvenderTransaktionsId: $context->getAnvenderTransaktionsID(), + distributionTransaktionsId: $context->getDistributionTransktionsID(), + ); + if (NULL === $forsendelse) { + throw new SoapException(sprintf('Forsendelse %s not found.', $context->getAnvenderTransaktionsID())); + } + + $forsendelse->deliveredAt = $this->time->getRequestTime(); + $this->forsendelseRepository->save($forsendelse); + + $response = new FordelingskvitteringModtagAnvenderResponseType(); + + $kvittering = $this->kvitteringRepository->loadByAnvenderTransaktionsId( + anvenderTransaktionsId: $context->getAnvenderTransaktionsID(), + distributionTransaktionsId: $context->getDistributionTransktionsID(), + ); + if (NULL === $kvittering) { + $kvittering = new AnvenderKvittering( + anvenderTransaktionsId: $context->getAnvenderTransaktionsID(), + distributionTransaktionsId: $context->getDistributionTransktionsID(), + request: $request, + response: $response, + ); + } + + $this->kvitteringRepository->save($kvittering); + + return $response; + } + +} diff --git a/src/Exception/SoapException.php b/src/Exception/SoapException.php new file mode 100644 index 0000000..0402b9d --- /dev/null +++ b/src/Exception/SoapException.php @@ -0,0 +1,10 @@ +database - ->select(InstallHooks::TABLE_ANVENDER_FORSENDELSE, 't') + ->select(self::TABLE, 't') ->fields('t'); foreach ($conditions as $condition) { @@ -96,7 +87,7 @@ public function deleteBySubmissions(array $submissions): int { try { $ids = array_map(static fn(WebformSubmissionInterface $submission) => $submission->id(), $submissions); - return $this->database->delete(InstallHooks::TABLE_ANVENDER_FORSENDELSE) + return $this->database->delete(self::TABLE) ->condition('webform_submission_id', $ids, 'IN') ->execute(); } @@ -112,10 +103,14 @@ public function deleteBySubmissions(array $submissions): int { /** * Load forsendelse by transaktions-id. */ - public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId): ?AnvenderForsendelse { - $result = $this->loadBy([ + public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId, ?string $distributionTransaktionsId = NULL): ?AnvenderForsendelse { + $criteria = [ ['anvender_transaktions_id', $anvenderTransaktionsId], - ]); + ]; + if (NULL !== $distributionTransaktionsId) { + $criteria[] = ['distribution_transaktions_id', $distributionTransaktionsId]; + } + $result = $this->loadBy($criteria); if (1 !== count($result)) { return NULL; @@ -129,7 +124,7 @@ public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId): ?A */ public function save(AnvenderForsendelse $forsendelse): bool { try { - $now = \Drupal::time()->getCurrentTime(); + $now = $this->time->getRequestTime(); $forsendelse->createdAt ??= $now; $forsendelse->updatedAt = $now; @@ -147,13 +142,13 @@ public function save(AnvenderForsendelse $forsendelse): bool { ]; if (NULL === $this->loadByAnvenderTransaktionsId($forsendelse->anvenderTransaktionsId)) { $this->database - ->insert(InstallHooks::TABLE_ANVENDER_FORSENDELSE) + ->insert(self::TABLE) ->fields($fields) ->execute(); } else { $this->database - ->update(InstallHooks::TABLE_ANVENDER_FORSENDELSE) + ->update(self::TABLE) ->condition('anvender_transaktions_id', $forsendelse->anvenderTransaktionsId) ->fields($fields) ->execute(); diff --git a/src/Repository/AnvenderKvitteringRepository.php b/src/Repository/AnvenderKvitteringRepository.php new file mode 100644 index 0000000..1ba48e5 --- /dev/null +++ b/src/Repository/AnvenderKvitteringRepository.php @@ -0,0 +1,114 @@ +database + ->select(self::TABLE, 't') + ->fields('t'); + + foreach ($conditions as $condition) { + $query->condition(...$condition); + } + + $statement = $query->execute(); + assert(NULL !== $statement); + $result = $statement->fetchAll(); + return array_map( + static fn(object $row) => new AnvenderKvittering( + anvenderTransaktionsId: $row->anvender_transaktions_id, + distributionTransaktionsId: $row->distribution_transaktions_id, + request: unserialize($row->request, options: ['allowed_classes' => TRUE]), + response: unserialize($row->response, options: ['allowed_classes' => TRUE]), + createdAt: $row->created_at, + updatedAt: $row->updated_at, + ), + array: $result + ); + } + + /** + * Load kvittering by transaktions-id. + */ + public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId, ?string $distributionTransaktionsId = NULL): ?AnvenderKvittering { + $criteria = [ + ['anvender_transaktions_id', $anvenderTransaktionsId], + ]; + if (NULL !== $distributionTransaktionsId) { + $criteria[] = ['distribution_transaktions_id', $distributionTransaktionsId]; + } + $result = $this->loadBy($criteria); + + if (1 !== count($result)) { + return NULL; + } + + return reset($result); + } + + /** + * Save kvittering. + */ + public function save(AnvenderKvittering $kvittering): bool { + try { + $now = $this->time->getRequestTime(); + $kvittering->createdAt ??= $now; + $kvittering->updatedAt = $now; + + $fields = [ + 'anvender_transaktions_id' => $kvittering->anvenderTransaktionsId, + 'request' => serialize($kvittering->request), + 'distribution_transaktions_id' => $kvittering->distributionTransaktionsId, + 'response' => serialize($kvittering->response), + 'created_at' => $kvittering->createdAt, + 'updated_at' => $kvittering->updatedAt, + ]; + if (NULL === $this->loadByAnvenderTransaktionsId( + anvenderTransaktionsId: $kvittering->anvenderTransaktionsId, + distributionTransaktionsId: $kvittering->distributionTransaktionsId, + )) { + $this->database + ->insert(self::TABLE) + ->fields($fields) + ->execute(); + } + else { + $this->database + ->update(self::TABLE) + ->condition('anvender_transaktions_id', $kvittering->anvenderTransaktionsId) + ->condition('distribution_transaktions_id', $kvittering->distributionTransaktionsId) + ->fields($fields) + ->execute(); + } + + return TRUE; + } + catch (\Exception $exception) { + $this->logger->error('Error saving kvittering: @message', [ + '@message' => $exception->getMessage(), + 'exception' => $exception, + ]); + } + + return FALSE; + } + +} From aa8db20a9986bace2c8934f3592acefddd21a35d Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 2 Mar 2026 15:41:22 +0100 Subject: [PATCH 28/62] Cleaned up --- os2forms_fordelingskomponent.routing.yml | 8 ++++---- ...lingskomponentDistributionObjectPreviewController.php | 9 +-------- src/Repository/AnvenderForsendelseRepository.php | 3 +++ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/os2forms_fordelingskomponent.routing.yml b/os2forms_fordelingskomponent.routing.yml index eb32433..98c171a 100644 --- a/os2forms_fordelingskomponent.routing.yml +++ b/os2forms_fordelingskomponent.routing.yml @@ -57,12 +57,12 @@ os2forms_fordelingskomponent.distribution_object.show: _entity_access: "webform.update" os2forms_fordelingskomponent.sf2900_2_4_FordelingskvitteringModtag: - path: '/os2forms-fordelingskomponent/sf2900/2.4/FordelingskvitteringModtag' + path: "/os2forms-fordelingskomponent/sf2900/2.4/FordelingskvitteringModtag" defaults: - _title: 'SF2900 2.4 FordelingskvitteringModtag' + _title: "SF2900 2.4 FordelingskvitteringModtag" _controller: '\Drupal\os2forms_fordelingskomponent\Controller\Fordelingskomponent\FordelingskvitteringModtagController' methods: [HEAD, POST] requirements: - _permission: 'access content' + _permission: "access content" options: - no_cache: 'TRUE' + no_cache: "TRUE" diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 84e3c34..83752d9 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -23,17 +23,10 @@ */ final class Os2formsFordelingskomponentDistributionObjectPreviewController extends AbstractController { - /** - * The webform submission storage. - */ - private readonly WebformSubmissionStorageInterface $submissionStorage; - public function __construct( private readonly Settings $settings, private readonly WebformHelperSF2900 $helper, - EntityTypeManagerInterface $entityTypeManager, ) { - $this->submissionStorage = $entityTypeManager->getStorage('webform_submission'); } /** @@ -71,7 +64,7 @@ public function __invoke(Request $request, WebformInterface $webform, string $we '#submission' => $webform_submission, '#handler' => $handler, '#handler_settings' => $handlerSettings, - '#preview' => $webform_submission ? $this->renderPreview($handler, $handlerSettings, $webform_submission) : NULL, + '#preview' => $this->renderPreview($handler, $handlerSettings, $webform_submission), '#links' => $links, ]; } diff --git a/src/Repository/AnvenderForsendelseRepository.php b/src/Repository/AnvenderForsendelseRepository.php index 2dc72ae..4980625 100644 --- a/src/Repository/AnvenderForsendelseRepository.php +++ b/src/Repository/AnvenderForsendelseRepository.php @@ -81,6 +81,7 @@ public function loadByWebformAndHandler(WebformInterface $webform, WebformHandle * The submissions. */ public function deleteBySubmissions(array $submissions): int { + // @todo Delete kvitteringer. if (empty($submissions)) { return 0; } @@ -98,6 +99,8 @@ public function deleteBySubmissions(array $submissions): int { 'exception' => $exception, ]); } + + return 0; } /** From 04edde2baced7f2c2acbe0301b2cc6dbe3ae8952 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 6 Mar 2026 16:13:36 +0100 Subject: [PATCH 29/62] Fixed route name --- src/Plugin/WebformHandler/WebformHandlerSF2900.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 1d1f268..eb3c2fd 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -377,7 +377,7 @@ public function getSummary() { $items[] = Link::createFromRoute( $this->t('Distribution objects'), - 'os2forms_fordelingskomponent.distribution_object.list', [ + 'os2forms_fordelingskomponent.distribution_object.index', [ 'webform' => $this->getWebform()->id(), 'webform_handler' => $this->getHandlerId(), ] From da92bc6fd4c9effb7aeff615d7cc2d7d5a0c62f1 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 9 Mar 2026 14:51:50 +0100 Subject: [PATCH 30/62] Cleaned up --- ...msFordelingskomponentDistributionObjectPreviewController.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 83752d9..4ebaf1e 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -4,7 +4,6 @@ namespace Drupal\os2forms_fordelingskomponent\Controller; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Url; use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; use Drupal\os2forms_fordelingskomponent\Hook\ThemeHooks; @@ -14,7 +13,6 @@ use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; -use Drupal\webform\WebformSubmissionStorageInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; From 86007c0cd11e2fe93d75e451a1cdeb050ce73bd3 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 10 Mar 2026 11:38:08 +0100 Subject: [PATCH 31/62] =?UTF-8?q?Handled=20=E2=80=9CDokumenter=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...bform.webform.os2forms_fdk_kp_anmoding.yml | 67 +++++++++- src/Helper/FordelingskomponentHelper.php | 74 ++++++++++- src/Helper/WebformHelperSF2900.php | 4 +- src/Helper/XmlHelper.php | 4 +- src/Model/DistributionFormular.php | 32 +++++ src/Model/DistributionObjectFiles.php | 115 ++++++++++++++++++ 6 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 src/Model/DistributionFormular.php create mode 100644 src/Model/DistributionObjectFiles.php diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml index 766a823..cd33816 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml @@ -46,6 +46,15 @@ third_party_settings: attachment: encrypt: true encrypt_profile: webform + dokumenter_overslag: + encrypt: true + encrypt_profile: webform + dokumenter_faktura: + encrypt: true + encrypt_profile: webform + dokumenter_bilag: + encrypt: true + encrypt_profile: webform os2forms: os2forms_email_handler: enabled: 0 @@ -124,7 +133,7 @@ elements: |- '#title': Sagstype almindeligt_helbredstillaeg: '#type': select - '#title': Almindeligt helbredstillaeg + '#title': 'Almindeligt helbredstillaeg' '#options': Medicin: Medicin Tandbehandling: Tandbehandling @@ -134,7 +143,7 @@ elements: |- Psykologhjaelp: Psykologhjaelp Hoereappartbehandling: Hoereapparatbehandling afsend_content_pdf: - '#type': 'entity_print_attachment:pdf' + '#type': 'webform_entity_print_attachment:pdf' '#title': 'Fordelingskomponent (PDF) hest' '#display_on': view '#filename': hat-og-briller.pdf @@ -146,6 +155,18 @@ elements: |- '#excluded_elements': { } '#exclude_empty': 0 '#exclude_empty_checkbox': 0 + dokumenter_overslag: + '#type': webform_document_file + '#title': Overslag + '#multiple': 3 + dokumenter_faktura: + '#type': webform_document_file + '#title': Faktura + '#multiple': 3 + dokumenter_bilag: + '#type': webform_document_file + '#title': Bilag + '#multiple': 3 css: "" javascript: "" settings: @@ -350,7 +371,47 @@ handlers: journalpost_message: "" attachment_element: attachment formular_type: test - xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n
\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n
\r\n" + xml_template: | + + +
+ urn:oio:cvr-nr:{{ handler.settings.sender.senderId }} + {{ submission.completed.value|date("Y-m-d") }} + {{ handler.settings.distributionContext.kleEmne }} + {% for file in files.dokumenter_overslag|default([]) %} + + {{ file.sftp_filename }} + Overslag + + {% endfor %} + {% for file in files.dokumenter_faktura|default([]) %} + + {{ file.sftp_filename }} + Faktura + + {% endfor %} + {% for file in files.dokumenter_bilag|default([]) %} + + {{ file.sftp_filename }} + Bilag + + {% endfor %} +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ submission.completed.value|date("Y-m-d") }} + +
xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_malformed_xml: id: os2forms_fordelingskomponent_sf2900 diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 499a1a9..2dbe1f9 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -2,12 +2,16 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; +use Drupal\file\FileStorageInterface; use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_fordelingskomponent\Exception\Exception; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; use Drupal\os2forms_fordelingskomponent\Model\Attachment; +use Drupal\os2forms_fordelingskomponent\Model\DistributionFormular; +use Drupal\os2forms_fordelingskomponent\Model\DistributionObjectFiles; use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse; use Drupal\os2forms_fordelingskomponent\Model\TransactionContext; use Drupal\os2forms_fordelingskomponent\Model\XmlRenderResult; @@ -67,6 +71,11 @@ final class FordelingskomponentHelper implements LoggerInterface, EventSubscriberInterface { use LoggerTrait; + /** + * The file storage. + */ + private FileStorageInterface $fileStorage; + /** * Constructor. */ @@ -78,6 +87,7 @@ public function __construct( private readonly KeyRepositoryInterface $keyRepository, private readonly KeyHelper $keyHelper, private readonly AnvenderForsendelseRepository $anvenderForsendelseRepository, + EntityTypeManagerInterface $entityTypeManager, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent')] private readonly LoggerChannelInterface $logger, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent_submission')] @@ -85,6 +95,7 @@ public function __construct( #[Autowire(service: 'os2web_audit.logger')] private readonly AuditLogger $auditLogger, ) { + $this->fileStorage = $entityTypeManager->getStorage('file'); } /** @@ -251,8 +262,9 @@ private function buildDistributionFormularType( WebformSubmissionInterface $submission, HandlerSettings $handlerSettings, Attachment $attachment, - ): DistributionFormularType { - $xml = $this->renderXml($handlerSettings, $submission)->rendered; + ): DistributionFormular { + $files = $this->buildFiles($handlerSettings, $submission); + $xml = $this->renderXml($handlerSettings, $submission, $files)->rendered; $xsdUrl = $handlerSettings->distributionObject->xsdUrl; $this->xmlHelper->validateXml($xml); @@ -282,12 +294,13 @@ private function buildDistributionFormularType( ), ); - return new DistributionFormularType( + return (new DistributionFormular( iD: $id, kLEEmneForslag: $handlerSettings->distributionContext->kleEmne, meddelelse: $meddelelse, handlingFacetForslag: $handlerSettings->distributionContext->handlingFacet, - ); + )) + ->setFiles($files); } /** @@ -296,6 +309,7 @@ private function buildDistributionFormularType( public function renderXml( HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, + ?DistributionObjectFiles $files, bool $validateXml = TRUE, ): XmlRenderResult { $template = $handlerSettings->distributionObject->xmlTemplate; @@ -303,7 +317,7 @@ public function renderXml( throw new RuntimeException('Missing XML template'); } - $context = $this->xmlHelper->getRenderContext($handlerSettings, $submission); + $context = $this->xmlHelper->getRenderContext($handlerSettings, $submission, $files); return new XmlRenderResult( rendered: $this->xmlHelper->render($template, $context, validateXml: $validateXml), @@ -312,6 +326,44 @@ public function renderXml( ); } + private const FILE_ELEMENT_TYPES = [ + 'managed_file', + 'webform_document_file', + 'webform_image_file', + ]; + + /** + * Build files for a districution object. + */ + public function buildFiles(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission): DistributionObjectFiles { + $files = new DistributionObjectFiles(); + $elements = $submission->getWebform()->getElementsDecodedAndFlattened(); + $fileElements = array_filter($elements, + static fn(array $element) => in_array($element['#type'] ?? NULL, self::FILE_ELEMENT_TYPES)); + foreach ($fileElements as $type => $_) { + $values = $submission->getData()[$type] ?? NULL; + if ($values) { + foreach ($values as $index => $id) { + /** @var \Drupal\file\Entity\FileInterface $file */ + $file = $this->fileStorage->load($id); + $sftpFilename = implode('_', [ + 'os2forms_fordelingskomponent', + $handlerSettings->handlerId, + $submission->uuid(), + $file->getFilename(), + ]); + + $files->addFile($type, + sftpFilename: $sftpFilename, + file: $file + ); + } + } + } + + return $files; + } + /** * Send dokument. * @@ -336,6 +388,18 @@ public function sendDokument( $sftp = $sf2900->sftp(); $dokumentFilNavn = $sftp->putContents($attachment->contents, $attachment->filename); } + // Upload files if any. + elseif ($dokument instanceof DistributionFormular) { + $files = $dokument->getFiles(); + $sftp = $sf2900->sftp(); + foreach ($files as $items) { + foreach ($items as $item) { + /** @var \Drupal\file\Entity\File $file */ + $file = $item['file']; + $sftp->putFile($file->getFileUri(), $file->getFilename(), $item['sftp_filename']); + } + } + } $transactionId = Serializer::createUuid(); diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 76b1e3e..5bb1cd7 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -89,7 +89,9 @@ public function buildDistributionObject(HandlerSettings $handlerSettings, Webfor * Render XML. */ public function renderXml(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, bool $validateXml = TRUE): XmlRenderResult { - return $this->helper->renderXml($handlerSettings, $submission, validateXml: $validateXml); + $files = $this->helper->buildFiles($handlerSettings, $submission); + + return $this->helper->renderXml($handlerSettings, $submission, files: $files, validateXml: $validateXml); } /** diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index 5b6bb63..b7f96f6 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -3,6 +3,7 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; +use Drupal\os2forms_fordelingskomponent\Model\DistributionObjectFiles; use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -68,11 +69,12 @@ public function render(string $template, array $context, bool $validateXml = TRU /** * Get render context. */ - public function getRenderContext(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission) { + public function getRenderContext(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, DistributionObjectFiles $files) { return [ 'handler' => ['settings' => $handlerSettings], 'submission' => $submission, 'webform_submission' => $submission, + 'files' => $files, ]; } diff --git a/src/Model/DistributionFormular.php b/src/Model/DistributionFormular.php new file mode 100644 index 0000000..fc2b52e --- /dev/null +++ b/src/Model/DistributionFormular.php @@ -0,0 +1,32 @@ +files; + } + + /** + * Set files. + */ + public function setFiles(DistributionObjectFiles $files): static { + $this->files = $files; + + return $this; + } + +} diff --git a/src/Model/DistributionObjectFiles.php b/src/Model/DistributionObjectFiles.php new file mode 100644 index 0000000..998ccfc --- /dev/null +++ b/src/Model/DistributionObjectFiles.php @@ -0,0 +1,115 @@ + [ + * [ + * 'sftp_filename' => …, + * 'file' => instance of \Drupal\file\FileInterface + * ], + * ], + * ] + * @endcode + */ +final class DistributionObjectFiles implements \ArrayAccess, \Iterator, \Countable { + /** + * The files data. + */ + private array $files = []; + + /** + * Constructor. + */ + public function __construct() { + } + + /** + * Add a file. + */ + public function addFile(string $type, string $sftpFilename, FileInterface $file) { + $this->files[$type][] = [ + 'sftp_filename' => $sftpFilename, + 'file' => $file, + ]; + } + + /** + * {@inheritdoc} + */ + public function offsetExists(mixed $offset): bool { + return isset($this->files[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet(mixed $offset): mixed { + return $this->files[$offset] ?? NULL; + } + + /** + * {@inheritdoc} + */ + public function offsetSet(mixed $offset, mixed $value): void { + throw new \RuntimeException(__METHOD__ . ' is not implemented'); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset(mixed $offset): void { + throw new \RuntimeException(__METHOD__ . ' is not implemented'); + } + + /** + * {@inheritdoc} + */ + public function count(): int { + return count($this->files); + } + + /** + * {@inheritdoc} + */ + public function current(): mixed { + return current($this->files); + } + + /** + * {@inheritdoc} + */ + public function next(): void { + next($this->files); + } + + /** + * {@inheritdoc} + */ + public function key(): mixed { + return key($this->files); + } + + /** + * {@inheritdoc} + */ + public function valid(): bool { + return NULL !== key($this->files); + } + + /** + * {@inheritdoc} + */ + public function rewind(): void { + reset($this->files); + } + +} From 7d93a27824c777f58f5d41aa7967c9d324471f23 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 20 Apr 2026 17:11:27 +0200 Subject: [PATCH 32/62] Added trigger files --- README.md | 4 +- ...bform.webform.os2forms_fdk_kp_anmoding.yml | 13 +- resources/README.md | 3 + .../xsd/RouteParameters.xsd | 12 ++ .../xsd/SFTPDynamicRoutingInfo.xsd | 46 ++++++ .../xsd/SFTPTypes.xsd | 118 +++++++++++++++ .../Commands/SendJournalnotatCommand.php | 5 + src/Helper/FordelingskomponentHelper.php | 135 ++++++++++++++---- src/Helper/WebformHelperSF2900.php | 2 +- src/Helper/XmlHelper.php | 3 +- src/Model/DistributionFormular.php | 26 ++-- src/Model/DistributionObjectFiles.php | 115 --------------- .../WebformHandler/WebformHandlerSF2900.php | 18 +++ src/Settings/DistributionObjectSettings.php | 6 + 14 files changed, 348 insertions(+), 158 deletions(-) create mode 100644 resources/README.md create mode 100644 resources/ServiceContract-SFTP-20230926/xsd/RouteParameters.xsd create mode 100644 resources/ServiceContract-SFTP-20230926/xsd/SFTPDynamicRoutingInfo.xsd create mode 100644 resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd delete mode 100644 src/Model/DistributionObjectFiles.php diff --git a/README.md b/README.md index 9d0423c..2e05de4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ | SF2900 Certificate | Certificate | File | | SF2900 SFTP private key | Authentication | File | - Note: The "SFTP private key" key must be passwordless. + Note: The "SFTP private key" key must be passwordless[^1]. You can use `ssh-keygen` to remove the password from a certificate: @@ -20,6 +20,8 @@ 2. Go to `/admin/os2forms_fordelingskomponent/settings` and configure the Fordelingskomponent module. +[^1] It takes a very long time to read a key with a password (reference?) + ## Console commands ``` shell diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml index cd33816..9e49c0e 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml @@ -370,7 +370,9 @@ handlers: distribution_type: FORMULAR journalpost_message: "" attachment_element: attachment - formular_type: test + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" xml_template: | @@ -432,7 +434,8 @@ handlers: distribution_type: FORMULAR journalpost_message: "" attachment_element: "" - formular_type: test + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_invalid_twig: @@ -454,7 +457,8 @@ handlers: distribution_type: FORMULAR journalpost_message: "" attachment_element: "" - formular_type: test + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n
\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n
\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_invalid_xml: @@ -476,7 +480,8 @@ handlers: distribution_type: FORMULAR journalpost_message: "" attachment_element: "" - formular_type: test + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 xml_template: "\r\n\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" variants: {} diff --git a/resources/README.md b/resources/README.md new file mode 100644 index 0000000..53a8d4e --- /dev/null +++ b/resources/README.md @@ -0,0 +1,3 @@ +# Resources + +* [`ServiceContract-SFTP-20230926/`](./ServiceContract-SFTP-20230926) () diff --git a/resources/ServiceContract-SFTP-20230926/xsd/RouteParameters.xsd b/resources/ServiceContract-SFTP-20230926/xsd/RouteParameters.xsd new file mode 100644 index 0000000..434a3ab --- /dev/null +++ b/resources/ServiceContract-SFTP-20230926/xsd/RouteParameters.xsd @@ -0,0 +1,12 @@ + + + + + + + + + A rule must be valid for this date to be applicable for routing. + + + diff --git a/resources/ServiceContract-SFTP-20230926/xsd/SFTPDynamicRoutingInfo.xsd b/resources/ServiceContract-SFTP-20230926/xsd/SFTPDynamicRoutingInfo.xsd new file mode 100644 index 0000000..2296b5c --- /dev/null +++ b/resources/ServiceContract-SFTP-20230926/xsd/SFTPDynamicRoutingInfo.xsd @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Supplerende parameter der anvendes i routing, når routing er konfigureret til det. +Hvis der skal anvendes en RouteParameter, er det defineret specifikt for den enkelte integration. + + + + + + + + + + + + + diff --git a/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd b/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd new file mode 100644 index 0000000..6bdd911 --- /dev/null +++ b/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Drush/Commands/SendJournalnotatCommand.php b/src/Drush/Commands/SendJournalnotatCommand.php index 8131e36..21ff359 100644 --- a/src/Drush/Commands/SendJournalnotatCommand.php +++ b/src/Drush/Commands/SendJournalnotatCommand.php @@ -7,6 +7,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; // phpcs:disable Drupal.Commenting.ClassComment.Missing #[AsCommand( @@ -27,6 +28,10 @@ protected function configure(): void { * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + + $io->warning('This command is a no-op'); + return self::SUCCESS; } diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 2dbe1f9..fb73408 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; +use Drupal\file\Entity\File; use Drupal\file\FileStorageInterface; use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_fordelingskomponent\Exception\Exception; @@ -11,7 +12,6 @@ use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; use Drupal\os2forms_fordelingskomponent\Model\Attachment; use Drupal\os2forms_fordelingskomponent\Model\DistributionFormular; -use Drupal\os2forms_fordelingskomponent\Model\DistributionObjectFiles; use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse; use Drupal\os2forms_fordelingskomponent\Model\TransactionContext; use Drupal\os2forms_fordelingskomponent\Model\XmlRenderResult; @@ -263,7 +263,7 @@ private function buildDistributionFormularType( HandlerSettings $handlerSettings, Attachment $attachment, ): DistributionFormular { - $files = $this->buildFiles($handlerSettings, $submission); + $files = $this->buildFileGroups($handlerSettings, $submission); $xml = $this->renderXml($handlerSettings, $submission, $files)->rendered; $xsdUrl = $handlerSettings->distributionObject->xsdUrl; @@ -300,7 +300,7 @@ private function buildDistributionFormularType( meddelelse: $meddelelse, handlingFacetForslag: $handlerSettings->distributionContext->handlingFacet, )) - ->setFiles($files); + ->setFileGroups($files); } /** @@ -309,7 +309,7 @@ private function buildDistributionFormularType( public function renderXml( HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, - ?DistributionObjectFiles $files, + ?array $files, bool $validateXml = TRUE, ): XmlRenderResult { $template = $handlerSettings->distributionObject->xmlTemplate; @@ -333,35 +333,44 @@ public function renderXml( ]; /** - * Build files for a districution object. + * Build files for a distribution object. + * + * @return \Drupal\file\Entity\FileGroups + * The file groups. + * + * @phpstan-import-type FileGroups from DistributionFormular */ - public function buildFiles(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission): DistributionObjectFiles { - $files = new DistributionObjectFiles(); + public function buildFileGroups(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission): array { + $groups = []; $elements = $submission->getWebform()->getElementsDecodedAndFlattened(); $fileElements = array_filter($elements, static fn(array $element) => in_array($element['#type'] ?? NULL, self::FILE_ELEMENT_TYPES)); foreach ($fileElements as $type => $_) { $values = $submission->getData()[$type] ?? NULL; if ($values) { - foreach ($values as $index => $id) { - /** @var \Drupal\file\Entity\FileInterface $file */ - $file = $this->fileStorage->load($id); - $sftpFilename = implode('_', [ - 'os2forms_fordelingskomponent', - $handlerSettings->handlerId, - $submission->uuid(), - $file->getFilename(), - ]); - - $files->addFile($type, - sftpFilename: $sftpFilename, - file: $file - ); + /** @var \Drupal\file\FileInterface[] $files */ + $files = $this->fileStorage->loadMultiple($values); + foreach ($files as $file) { + $groups[$type][] = [ + 'sftp_filename' => $this->getSftpFilename($handlerSettings, $submission, $file->getFilename()), + 'file' => $file, + ]; } } } - return $files; + return $groups; + } + + /** + * Get SFTP filename. + */ + private function getSftpFilename(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, string $filename): string { + return implode('_', [ + uniqid('os2forms_fordelingskomponent_'), + md5($handlerSettings->handlerId . '||' . $submission->uuid()), + $filename, + ]); } /** @@ -378,31 +387,34 @@ public function sendDokument( ?Attachment $attachment, HandlerSettings $handlerSettings, ) { - $sf2900 = $this->sf2900(); + $transactionId = Serializer::createUuid(); + $dokumentFilNavn = NULL; if ($dokument instanceof DistributionDokumentType) { if (NULL === $attachment) { throw new InvalidAttachmentElementException(sprintf('Missing attachment for %s', $dokument::class)); } $sftp = $sf2900->sftp(); - $dokumentFilNavn = $sftp->putContents($attachment->contents, $attachment->filename); + $sftpFilename = $this->getSftpFilename($handlerSettings, $submission, $attachment->filename); + $dokumentFilNavn = $sftp->putContents($attachment->contents, $attachment->filename, $sftpFilename); + // @todo Create trigger object. } // Upload files if any. elseif ($dokument instanceof DistributionFormular) { - $files = $dokument->getFiles(); + $files = $dokument->getFileGroups(); $sftp = $sf2900->sftp(); foreach ($files as $items) { foreach ($items as $item) { /** @var \Drupal\file\Entity\File $file */ $file = $item['file']; $sftp->putFile($file->getFileUri(), $file->getFilename(), $item['sftp_filename']); + $triggerObject = $this->buildTriggerFile($file, $item['sftp_filename'], $handlerSettings, $transactionId); + $sftp->putContents($triggerObject, $item['sftp_filename'], $item['sftp_filename'] . '.trigger'); } } } - $transactionId = Serializer::createUuid(); - $this->setTransactionContext($transactionId, new TransactionContext( transactionId: $transactionId, handlerSettings: $handlerSettings, @@ -586,4 +598,73 @@ private function getTransactionContext( return $this->transactionContexts[$transactionId]; } + // @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf#page=16 + private const string TRIGGER_FILE_TEMPLATE = <<<'XML' + + + + + + + + ROUTING_V1_0_0 + + + + + + + + + + + + + +XML; + + /** + * Build trigger file. + * + * @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf + */ + private function buildTriggerFile(File $file, string $sftpFilename, HandlerSettings $handlerSettings, string $transactionId): string { + $dom = new \DOMDocument(); + $dom->loadXML(self::TRIGGER_FILE_TEMPLATE); + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace('ns2', 'http://serviceplatformen.dk/xml/wsdl/soap11/SFTP/1/types'); + $setValue = function (string $expression, mixed $value) use ($xpath) { + $nodes = $xpath->query($expression); + if (!$nodes || 1 !== $nodes->count()) { + throw new \RuntimeException(sprintf('No unique node found for expression %s', $expression)); + } + /** @var \DOMElement $node */ + $node = $nodes->item(0); + $node->nodeValue = $value; + }; + + $setValue('//FileDescriptor/FileName', $sftpFilename); + $setValue('//FileDescriptor/SizeInBytes', $file->getSize()); + $setValue('//FileDescriptor/Sender', $handlerSettings->sender->sftp->username); + $setValue('//FileDescriptor/SendersFileId', $file->uuid()); + + $infRef = $handlerSettings->distributionObject->filspecifikation; + $senderItSystem = $handlerSettings->sender->registreringItSystem; + $senderAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->sender->routingMyndighed; + $timestamp = SF2900::formatDateTime(new \DateTimeImmutable()); + // @todo Get this from a recipient lookup. + $recipientItSystem = 'cfcf2769-9a1f-4d3b-b6d2-ddfb3a2f9dd6'; + $recipientAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->distributionObject->recipientAuthority; + + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/InfRef', $infRef); + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderIt-system', $senderItSystem); + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderAuthority', $senderAuthority); + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/TransactionId', $transactionId); + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderTimestamp', $timestamp); + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system', $recipientItSystem); + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientAuthority', $recipientAuthority); + + return $dom->saveXML(); + } + } diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 5bb1cd7..74179fd 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -89,7 +89,7 @@ public function buildDistributionObject(HandlerSettings $handlerSettings, Webfor * Render XML. */ public function renderXml(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, bool $validateXml = TRUE): XmlRenderResult { - $files = $this->helper->buildFiles($handlerSettings, $submission); + $files = $this->helper->buildFileGroups($handlerSettings, $submission); return $this->helper->renderXml($handlerSettings, $submission, files: $files, validateXml: $validateXml); } diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index b7f96f6..bd83843 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -3,7 +3,6 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; -use Drupal\os2forms_fordelingskomponent\Model\DistributionObjectFiles; use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -69,7 +68,7 @@ public function render(string $template, array $context, bool $validateXml = TRU /** * Get render context. */ - public function getRenderContext(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, DistributionObjectFiles $files) { + public function getRenderContext(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, array $files) { return [ 'handler' => ['settings' => $handlerSettings], 'submission' => $submission, diff --git a/src/Model/DistributionFormular.php b/src/Model/DistributionFormular.php index fc2b52e..995e557 100644 --- a/src/Model/DistributionFormular.php +++ b/src/Model/DistributionFormular.php @@ -5,26 +5,36 @@ use ItkDev\Serviceplatformen\SF2900\StructType\DistributionFormularType; /** - * DistributionFormularType extended with files. + * DistributionFormularType extended with file groups. + * + * @see https://phpstan.org/writing-php-code/phpdoc-types#local-type-aliases + * + * @phpstan-type FileGroups array> */ final class DistributionFormular extends DistributionFormularType { /** * The files. */ - protected DistributionObjectFiles $files; + protected array $fileGroups; /** - * Get files. + * Get file groups. + * + * @return FileGroups + * The file groups. */ - public function getFiles(): DistributionObjectFiles { - return $this->files; + public function getFileGroups(): array { + return $this->fileGroups; } /** - * Set files. + * Set file groups. + * + * @param FileGroups $fileGroups + * The file groups. */ - public function setFiles(DistributionObjectFiles $files): static { - $this->files = $files; + public function setFileGroups(array $fileGroups): static { + $this->fileGroups = $fileGroups; return $this; } diff --git a/src/Model/DistributionObjectFiles.php b/src/Model/DistributionObjectFiles.php deleted file mode 100644 index 998ccfc..0000000 --- a/src/Model/DistributionObjectFiles.php +++ /dev/null @@ -1,115 +0,0 @@ - [ - * [ - * 'sftp_filename' => …, - * 'file' => instance of \Drupal\file\FileInterface - * ], - * ], - * ] - * @endcode - */ -final class DistributionObjectFiles implements \ArrayAccess, \Iterator, \Countable { - /** - * The files data. - */ - private array $files = []; - - /** - * Constructor. - */ - public function __construct() { - } - - /** - * Add a file. - */ - public function addFile(string $type, string $sftpFilename, FileInterface $file) { - $this->files[$type][] = [ - 'sftp_filename' => $sftpFilename, - 'file' => $file, - ]; - } - - /** - * {@inheritdoc} - */ - public function offsetExists(mixed $offset): bool { - return isset($this->files[$offset]); - } - - /** - * {@inheritdoc} - */ - public function offsetGet(mixed $offset): mixed { - return $this->files[$offset] ?? NULL; - } - - /** - * {@inheritdoc} - */ - public function offsetSet(mixed $offset, mixed $value): void { - throw new \RuntimeException(__METHOD__ . ' is not implemented'); - } - - /** - * {@inheritdoc} - */ - public function offsetUnset(mixed $offset): void { - throw new \RuntimeException(__METHOD__ . ' is not implemented'); - } - - /** - * {@inheritdoc} - */ - public function count(): int { - return count($this->files); - } - - /** - * {@inheritdoc} - */ - public function current(): mixed { - return current($this->files); - } - - /** - * {@inheritdoc} - */ - public function next(): void { - next($this->files); - } - - /** - * {@inheritdoc} - */ - public function key(): mixed { - return key($this->files); - } - - /** - * {@inheritdoc} - */ - public function valid(): bool { - return NULL !== key($this->files); - } - - /** - * {@inheritdoc} - */ - public function rewind(): void { - reset($this->files); - } - -} diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index eb3c2fd..4f9decd 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -221,6 +221,24 @@ private function buildConfigurationFormDistributionObject(): array { DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, ], require: FALSE); + $section[DistributionObjectSettings::FILSPECIFIKATION] = [ + '#type' => 'textfield', + '#title' => $this->t('Filspecifikation (InfRef)'), + '#default_value' => $settings->filspecifikation, + ]; + $setStates($section[DistributionObjectSettings::FILSPECIFIKATION], [ + DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, + ], require: FALSE); + + $section[DistributionObjectSettings::RECIPIENT_AUTHORITY] = [ + '#type' => 'textfield', + '#title' => $this->t('Recipient authority'), + '#default_value' => $settings->recipientAuthority, + ]; + $setStates($section[DistributionObjectSettings::RECIPIENT_AUTHORITY], [ + DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, + ], require: FALSE); + $section[DistributionObjectSettings::XML_TEMPLATE] = [ '#type' => 'textarea', '#title' => $this->t('XML template'), diff --git a/src/Settings/DistributionObjectSettings.php b/src/Settings/DistributionObjectSettings.php index 756cb1b..7e65a35 100644 --- a/src/Settings/DistributionObjectSettings.php +++ b/src/Settings/DistributionObjectSettings.php @@ -28,6 +28,12 @@ final class DistributionObjectSettings extends AbstractSettings { public const string FORMULAR_TYPE = 'formular_type'; public ?string $formularType = ''; + public const string FILSPECIFIKATION = 'filspecifikation'; + public ?string $filspecifikation = ''; + + public const string RECIPIENT_AUTHORITY = 'recipient_authority'; + public ?string $recipientAuthority = ''; + public const string XML_TEMPLATE = 'xml_template'; public ?string $xmlTemplate = NULL; From 1349021859ed1bc673ee968c3eb5d38f038ccdc8 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 20 Apr 2026 17:30:56 +0200 Subject: [PATCH 33/62] Files settings --- ...bform.webform.os2forms_fdk_kp_anmoding.yml | 17 ++++++++++----- .../WebformHandler/WebformHandlerSF2900.php | 18 ++++++++++------ src/Settings/DistributionObjectSettings.php | 21 +++++++++++++------ .../FilesSettings.php | 17 +++++++++++++++ 4 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 src/Settings/DistributionObjectSettings/FilesSettings.php diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml index 9e49c0e..9ca9755 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml @@ -371,8 +371,9 @@ handlers: journalpost_message: "" attachment_element: attachment formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 - filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 - recipient_authority: "55133018" + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" xml_template: | @@ -435,7 +436,9 @@ handlers: journalpost_message: "" attachment_element: "" formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 - filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_invalid_twig: @@ -458,7 +461,9 @@ handlers: journalpost_message: "" attachment_element: "" formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 - filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n
\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n
\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" fordelingskomponent_sf2900_invalid_xml: @@ -481,7 +486,9 @@ handlers: journalpost_message: "" attachment_element: "" formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 - filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" xml_template: "\r\n\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" variants: {} diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 4f9decd..e6bbc2d 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -12,6 +12,7 @@ use Drupal\os2forms_fordelingskomponent\Settings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionContextSettings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings; +use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings\FilesSettings as DistributionObjectFilesSettings; use Drupal\webform\Plugin\WebformHandlerBase; use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\WebformSubmissionInterface; @@ -221,21 +222,26 @@ private function buildConfigurationFormDistributionObject(): array { DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, ], require: FALSE); - $section[DistributionObjectSettings::FILSPECIFIKATION] = [ + $section[DistributionObjectSettings::FILES] = [ + '#type' => 'fieldset', + '#title' => $this->t('Files'), + ]; + + $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::FILSPECIFIKATION] = [ '#type' => 'textfield', '#title' => $this->t('Filspecifikation (InfRef)'), - '#default_value' => $settings->filspecifikation, + '#default_value' => $settings->files->filspecifikation, ]; - $setStates($section[DistributionObjectSettings::FILSPECIFIKATION], [ + $setStates($section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::FILSPECIFIKATION], [ DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, ], require: FALSE); - $section[DistributionObjectSettings::RECIPIENT_AUTHORITY] = [ + $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_AUTHORITY] = [ '#type' => 'textfield', '#title' => $this->t('Recipient authority'), - '#default_value' => $settings->recipientAuthority, + '#default_value' => $settings->files->recipientAuthority, ]; - $setStates($section[DistributionObjectSettings::RECIPIENT_AUTHORITY], [ + $setStates($section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_AUTHORITY], [ DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, ], require: FALSE); diff --git a/src/Settings/DistributionObjectSettings.php b/src/Settings/DistributionObjectSettings.php index 7e65a35..d7bed52 100644 --- a/src/Settings/DistributionObjectSettings.php +++ b/src/Settings/DistributionObjectSettings.php @@ -2,6 +2,8 @@ namespace Drupal\os2forms_fordelingskomponent\Settings; +use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings\FilesSettings; +use Drupal\os2forms_fordelingskomponent\Settings\SenderSettings\SftpSettings; use ItkDev\Serviceplatformen\SF2900\EnumType\ObjektTypeType; /** @@ -12,6 +14,10 @@ final class DistributionObjectSettings extends AbstractSettings { const string NAME = 'distribution_object'; + protected static array $settingsProperties = [ + self::FILES => FilesSettings::class, + ]; + public const string DISTRIBUTION_TYPE = 'distribution_type'; public ?string $distributionType = NULL; @@ -28,16 +34,19 @@ final class DistributionObjectSettings extends AbstractSettings { public const string FORMULAR_TYPE = 'formular_type'; public ?string $formularType = ''; - public const string FILSPECIFIKATION = 'filspecifikation'; - public ?string $filspecifikation = ''; - - public const string RECIPIENT_AUTHORITY = 'recipient_authority'; - public ?string $recipientAuthority = ''; - public const string XML_TEMPLATE = 'xml_template'; public ?string $xmlTemplate = NULL; public const string XSD_URL = 'xsd_url'; public ?string $xsdUrl = NULL; + const string FILES = 'files'; + public ?FilesSettings $files = NULL; + + public function __construct(array $values, bool $throwExceptionOnMissingProperty = FALSE) { + $this->files = new FilesSettings([]); + parent::__construct($values, $throwExceptionOnMissingProperty); + } + + } diff --git a/src/Settings/DistributionObjectSettings/FilesSettings.php b/src/Settings/DistributionObjectSettings/FilesSettings.php new file mode 100644 index 0000000..2aa9bd8 --- /dev/null +++ b/src/Settings/DistributionObjectSettings/FilesSettings.php @@ -0,0 +1,17 @@ + Date: Tue, 21 Apr 2026 12:52:16 +0200 Subject: [PATCH 34/62] Recipient ID --- src/Helper/FordelingskomponentHelper.php | 23 +++++-- .../WebformHandler/WebformHandlerSF2900.php | 65 +++++++++++++++---- src/Settings/DistributionContextSettings.php | 4 ++ 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index fb73408..7cb438d 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -49,7 +49,9 @@ use ItkDev\Serviceplatformen\SF2900\StructType\JournalPostRelationsListeType; use ItkDev\Serviceplatformen\SF2900\StructType\JournalPostType; use ItkDev\Serviceplatformen\SF2900\StructType\MeddelelseType; +use ItkDev\Serviceplatformen\SF2900\StructType\ModtagerMedEndpointType; use ItkDev\Serviceplatformen\SF2900\StructType\RelationsListe; +use ItkDev\Serviceplatformen\SF2900\StructType\TilgaengeligeModtagereType; use ItkDev\Serviceplatformen\SF2900\StructType\TilstandListeType; use ItkDev\Serviceplatformen\SF2900\StructType\TilstandType; use ItkDev\Serviceplatformen\SF2900\StructType\UUID_URN; @@ -642,26 +644,39 @@ private function buildTriggerFile(File $file, string $sftpFilename, HandlerSetti $node = $nodes->item(0); $node->nodeValue = $value; }; + $removeElement = function (string $expression) use ($xpath) { + $nodes = $xpath->query($expression); + if (!$nodes || 1 !== $nodes->count()) { + throw new \RuntimeException(sprintf('No unique node found for expression %s', $expression)); + } + /** @var \DOMElement $node */ + $node = $nodes->item(0); + $node->parentNode->removeChild($node); + }; $setValue('//FileDescriptor/FileName', $sftpFilename); $setValue('//FileDescriptor/SizeInBytes', $file->getSize()); $setValue('//FileDescriptor/Sender', $handlerSettings->sender->sftp->username); $setValue('//FileDescriptor/SendersFileId', $file->uuid()); - $infRef = $handlerSettings->distributionObject->filspecifikation; + $infRef = $handlerSettings->distributionObject->files->filspecifikation; $senderItSystem = $handlerSettings->sender->registreringItSystem; $senderAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->sender->routingMyndighed; $timestamp = SF2900::formatDateTime(new \DateTimeImmutable()); // @todo Get this from a recipient lookup. - $recipientItSystem = 'cfcf2769-9a1f-4d3b-b6d2-ddfb3a2f9dd6'; - $recipientAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->distributionObject->recipientAuthority; + $recipientItSystem = trim((string) $handlerSettings->distributionContext->recipientItSystem); + $recipientAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->distributionObject->files->recipientAuthority; $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/InfRef', $infRef); $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderIt-system', $senderItSystem); $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderAuthority', $senderAuthority); $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/TransactionId', $transactionId); $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderTimestamp', $timestamp); - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system', $recipientItSystem); + if (empty($recipientItSystem)) { + $removeElement('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system'); + } else { + $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system', $recipientItSystem); + } $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientAuthority', $recipientAuthority); return $dom->saveXML(); diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index e6bbc2d..d1752d0 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -100,17 +100,38 @@ private function buildConfigurationFormDistributionContext(): array { $settings = $this->settingsService->getDistributionContextSettings((array) ($this->getSettings()[DistributionContextSettings::NAME] ?? NULL)); $globalSettings = $this->settingsService->getDistributionContextSettings(); + $section[DistributionContextSettings::RECIPIENT_IT_SYSTEM] = [ + '#title' => $this->t('Recipient IT system'), + '#type' => 'textfield', + '#attributes' => [ + 'pattern' => DistributionContextSettings::RECIPIENT_IT_SYSTEM_PATTERN, + ], + '#default_value' => $settings->recipientItSystem, + '#description' => $this->t('Uuid of recipient IT system. If set, any routing rules (using %kle_emne and %handling_facet) will be ignored.', [ + '%kle_emne' => $this->t('KLE-emne'), + '%handling_facet' => $this->t('Handling facet'), + ]), + ]; + $section[DistributionContextSettings::KLE_EMNE] = [ '#title' => $this->t('KLE-emne'), '#type' => 'textfield', '#default_value' => $settings->kleEmne, - // @todo Show default global value for all fields. + // @todo Show default global value for all fields. '#placeholder' => $globalSettings->kleEmne, '#required' => TRUE, '#attributes' => [ 'pattern' => DistributionContextSettings::KLE_EMNE_PATTERN, ], '#description' => $this->t('KLE-emne (format: dd.dd.dd)'), +// '#states' => [ +// 'visible' => [ +// ':input[name="settings[' . DistributionContextSettings::NAME . '][' . DistributionContextSettings::RECIPIENT_IT_SYSTEM . ']"]' => ['value' => ''], +// ], +// 'required' => [ +// ':input[name="settings[' . DistributionContextSettings::NAME . '][' . DistributionContextSettings::RECIPIENT_IT_SYSTEM . ']"]' => ['value' => ''], +// ], +// ], ]; $section[DistributionContextSettings::HANDLING_FACET] = [ @@ -121,6 +142,11 @@ private function buildConfigurationFormDistributionContext(): array { 'pattern' => DistributionContextSettings::HANDLING_FACET_PATTERN, ], '#description' => $this->t('handlingfacet (format: [A-Å]dd)'), +// '#states' => [ +// 'visible' => [ +// ':input[name="settings[' . DistributionContextSettings::NAME . '][' . DistributionContextSettings::RECIPIENT_IT_SYSTEM . ']"]' => ['value' => ''], +// ], +// ], ]; $section[DistributionContextSettings::BRUGERVENDT_NOEGLE] = [ @@ -269,24 +295,35 @@ private function buildConfigurationFormDistributionObject(): array { /** * {@inheritdoc} */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { - $setError = static fn (string|array $path, TranslatableMarkup $message) => $form_state->setErrorByName(implode('][', (array) $path), $message); + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) + { + $setError = static fn(string|array $path, TranslatableMarkup $message) => $form_state->setErrorByName(implode('][', + (array)$path), $message); - $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::KLE_EMNE] ?? ''; - if (!preg_match('/' . DistributionContextSettings::KLE_EMNE_PATTERN . '/', $value)) { + $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::RECIPIENT_IT_SYSTEM] ?? ''; + if ($value && !preg_match('/' . DistributionContextSettings::RECIPIENT_IT_SYSTEM_PATTERN . '/', $value)) { $setError( - [DistributionContextSettings::NAME, DistributionContextSettings::KLE_EMNE], - $this->t('Invalid KLE-emne: %value.', ['%value' => $value]) + [DistributionContextSettings::NAME, DistributionContextSettings::RECIPIENT_IT_SYSTEM], + $this->t('Invalid recipient IT system: %value.', ['%value' => $value]) ); } - $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::HANDLING_FACET] ?? ''; - if (!empty($value) && !preg_match('/' . DistributionContextSettings::HANDLING_FACET_PATTERN . '/', (string) $value)) { + $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::KLE_EMNE] ?? ''; + if (!preg_match('/' . DistributionContextSettings::KLE_EMNE_PATTERN . '/', $value)) { $setError( - [DistributionContextSettings::NAME, DistributionContextSettings::HANDLING_FACET], - $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $value]) - ); - } + [DistributionContextSettings::NAME, DistributionContextSettings::KLE_EMNE], + $this->t('Invalid KLE-emne: %value.', ['%value' => $value]) + ); + } + + $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::HANDLING_FACET] ?? ''; + if (!empty($value) && !preg_match('/' . DistributionContextSettings::HANDLING_FACET_PATTERN . '/', + (string)$value)) { + $setError( + [DistributionContextSettings::NAME, DistributionContextSettings::HANDLING_FACET], + $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $value]) + ); + } $type = $form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectSettings::DISTRIBUTION_TYPE] ?? ''; if (DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR === $type) { @@ -379,7 +416,7 @@ public function getSummary() { $items = []; - if ($settings->distributionContext->kleEmne) { + if (true || $settings->distributionContext->kleEmne) { $items[] = Link::createFromRoute( $this->t('Show routing info'), 'os2forms_fordelingskomponent.routing_info', [ diff --git a/src/Settings/DistributionContextSettings.php b/src/Settings/DistributionContextSettings.php index 96985b6..568c085 100644 --- a/src/Settings/DistributionContextSettings.php +++ b/src/Settings/DistributionContextSettings.php @@ -14,6 +14,10 @@ final class DistributionContextSettings extends AbstractSettings { self::HANDLING_FACET => TRUE, ]; + public const string RECIPIENT_IT_SYSTEM_PATTERN = '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'; + public const string RECIPIENT_IT_SYSTEM = 'recipient_it_system'; + public ?string $recipientItSystem = NULL; + public const string KLE_EMNE_PATTERN = '^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$'; public const string KLE_EMNE = 'kle_emne'; public ?string $kleEmne = NULL; From 30ff6479924c57abd385723d1450acd07224a08f Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 23 Apr 2026 11:25:21 +0200 Subject: [PATCH 35/62] More stuff --- ...m.webform.os2forms_fdk_kp_anmoding_dev.yml | 442 ++++++++++++++++++ .../FordelingsmodtagerListCommand.php | 51 ++ src/Helper/FordelingskomponentHelper.php | 10 +- .../WebformHandler/WebformHandlerSF2900.php | 88 ++-- src/Settings/AbstractSettings.php | 3 + src/Settings/DistributionContextSettings.php | 7 +- src/Settings/DistributionObjectSettings.php | 2 - .../FilesSettings.php | 11 +- 8 files changed, 562 insertions(+), 52 deletions(-) create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding_dev.yml create mode 100644 src/Drush/Commands/FordelingsmodtagerListCommand.php diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding_dev.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding_dev.yml new file mode 100644 index 0000000..5b35337 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding_dev.yml @@ -0,0 +1,442 @@ +langcode: da +status: open +dependencies: + module: + - os2forms + - os2forms_fordelingskomponent + - webform_encrypt + - webform_entity_print + - webform_revisions + enforced: + module: + - os2forms_fordelingskomponent_examples +third_party_settings: + webform_encrypt: + element: + ansoeger_oplysninger: + encrypt: true + encrypt_profile: webform + ansoeger: + encrypt: true + encrypt_profile: webform + fornavn: + encrypt: true + encrypt_profile: webform + mellemnavn: + encrypt: true + encrypt_profile: webform + efternavn: + encrypt: true + encrypt_profile: webform + personnummer: + encrypt: true + encrypt_profile: webform + telefonnummer: + encrypt: true + encrypt_profile: webform + sagstype: + encrypt: true + encrypt_profile: webform + almindeligt_helbredstillaeg: + encrypt: true + encrypt_profile: webform + afsend_content_pdf: + encrypt: true + encrypt_profile: webform + attachment: + encrypt: true + encrypt_profile: webform + dokumenter_bilag: + encrypt: true + encrypt_profile: webform + os2forms: + os2forms_email_handler: + enabled: 0 + email_recipients: "" + os2forms_nemid: + session_type: "" + webform_type: "" + nemlogin_auto_redirect: 0 + os2forms_nemlogin_openid_connect: + authentication_settings: + user_claim: "" + element_key: "" + error_message: "" + os2forms_nemid_address_protection: + nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour + nemlogin_hide_message: "" + os2forms_rest_api: + allowed_users: null + os2forms_sync: + publish: 0 + os2forms_webform_submission_log: + emails: "" + webform_entity_print: + template: + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" + export_types: + pdf: + enabled: true + link_text: "" + link_attributes: {} + word_docx: + enabled: false + link_text: "" + link_attributes: {} +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2forms_fdk_kp_anmoding_dev +title: "OS2Forms Fordelingskomponent: Anmodning (KP) – dev" +description: "" +categories: + - Example + - Fordelingskomponent +elements: |- + ansoeger_oplysninger: + '#type': fieldset + '#title': AnsoegerOplysninger + ansoeger: + '#type': fieldset + '#title': Ansøger + fornavn: + '#type': textfield + '#title': Fornavn + mellemnavn: + '#type': textfield + '#title': Mellemnavn + efternavn: + '#type': textfield + '#title': Efternavn + personnummer: + '#type': textfield + '#title': Personnummer + telefonnummer: + '#type': textfield + '#title': Telefonnummer + sagstype: + '#type': fieldset + '#title': Sagstype + almindeligt_helbredstillaeg: + '#type': select + '#title': 'Almindeligt helbredstillaeg' + '#options': + Medicin: Medicin + Tandbehandling: Tandbehandling + Fodbehandling: Fodbehandling + Fysioterapi: Fysioterapi + Kiropraktik: Kiropraktik + Psykologhjaelp: Psykologhjaelp + Hoereappartbehandling: Hoereapparatbehandling + afsend_content_pdf: + '#type': 'webform_entity_print_attachment:pdf' + '#title': 'Fordelingskomponent (PDF) hest' + '#display_on': view + '#filename': hat-og-briller.pdf + attachment: + '#type': os2forms_attachment + '#title': attachment + '#export_type': pdf + '#digital_signature': 0 + '#excluded_elements': { } + '#exclude_empty': 0 + '#exclude_empty_checkbox': 0 + dokumenter_bilag: + '#type': webform_document_file + '#title': Bilag + '#multiple': 3 +css: "" +javascript: "" +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: "" + ajax_effect: "" + ajax_speed: null + page: true + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" + form_title: both + form_submit_once: false + form_open_message: "" + form_close_message: "" + form_exception_message: "" + form_previous_submissions: true + form_confidential: false + form_confidential_message: "" + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: "" + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" + share: false + share_node: false + share_theme_name: "" + share_title: true + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" + submission_log: false + submission_excluded_elements: {} + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" + autofill: false + autofill_message: "" + autofill_excluded_elements: {} + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: "" + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: "" + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" + wizard_toggle: false + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" + confirmation_type: message + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} + confirmation_back: true + confirmation_back_label: "" + confirmation_back_attributes: {} + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: "" + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: "" + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: {} + permissions: {} + view_any: + roles: {} + users: {} + permissions: {} + update_any: + roles: {} + users: {} + permissions: {} + delete_any: + roles: {} + users: {} + permissions: {} + purge_any: + roles: {} + users: {} + permissions: {} + view_own: + roles: {} + users: {} + permissions: {} + update_own: + roles: {} + users: {} + permissions: {} + delete_own: + roles: {} + users: {} + permissions: {} + administer: + roles: {} + users: {} + permissions: {} + test: + roles: {} + users: {} + permissions: {} + configuration: + roles: {} + users: {} + permissions: {} +handlers: + fordelingskomponent_sf2900: + id: os2forms_fordelingskomponent_sf2900 + handler_id: fordelingskomponent_sf2900 + label: Fordelingskomponent + notes: "" + status: true + conditions: {} + weight: -50 + settings: + distribution_context: + routing_modtager_aktoer: "" + kle_emne: 32.03.13 + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" + titel: Titel + beskrivelse: Beskrivelse + distribution_object: + distribution_type: FORMULAR + journalpost_message: "" + attachment_element: attachment + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_it_system: 6b6afca0-e4fb-4c04-b2d4-bce99dedbcad + recipient_authority: "55133018" + xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n {% for file in files.dokumenter_overslag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Overslag\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_faktura|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Faktura\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_bilag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Bilag\r\n \r\n {% endfor %}\r\n
\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n
\r\n" + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" + fordelingskomponent_sf2900_malformed_xml: + id: os2forms_fordelingskomponent_sf2900 + handler_id: fordelingskomponent_sf2900_malformed_xml + label: "Fordelingskomponent (malformed XML)" + notes: "" + status: false + conditions: {} + weight: -49 + settings: + distribution_context: + kle_emne: 01.01.01 + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" + titel: Titel + beskrivelse: Beskrivelse + distribution_object: + distribution_type: FORMULAR + journalpost_message: "" + attachment_element: "" + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" + xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" + fordelingskomponent_sf2900_invalid_twig: + id: os2forms_fordelingskomponent_sf2900 + handler_id: fordelingskomponent_sf2900_invalid_twig + label: "Fordelingskomponent (invalid Twig)" + notes: "" + status: false + conditions: {} + weight: -48 + settings: + distribution_context: + kle_emne: 01.01.01 + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" + titel: Titel + beskrivelse: Beskrivelse + distribution_object: + distribution_type: FORMULAR + journalpost_message: "" + attachment_element: "" + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" + xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.senderId\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distributionContext.kleEmne }}\r\n
\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n
\r\n" + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" + fordelingskomponent_sf2900_invalid_xml: + id: os2forms_fordelingskomponent_sf2900 + handler_id: fordelingskomponent_sf2900_invalid_xml + label: "Fordelingskomponent (invalid XML)" + notes: "" + status: false + conditions: {} + weight: -47 + settings: + distribution_context: + kle_emne: 01.01.01 + handling_facet: "" + brugervendt_noegle: "Brugervendt nøgle" + titel: Titel + beskrivelse: Beskrivelse + distribution_object: + distribution_type: FORMULAR + journalpost_message: "" + attachment_element: "" + formular_type: BehandlingsudgifterRefusionsanmodningFormular_1 + files: + filspecifikation: BehandlingsudgifterRefusionsanmodningBilag_1 + recipient_authority: "55133018" + xml_template: "\r\n\r\n \r\n \r\n {{ submission.data.fornavn }}\r\n {{ submission.data.efternavn }}\r\n urn:oio:cpr:0000000000\r\n \r\n \r\n \r\n Medicin\r\n \r\n \r\n Underskrift0\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n \r\n\r\n" + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/Anmodning.xsd" +variants: {} diff --git a/src/Drush/Commands/FordelingsmodtagerListCommand.php b/src/Drush/Commands/FordelingsmodtagerListCommand.php new file mode 100644 index 0000000..55d9ee8 --- /dev/null +++ b/src/Drush/Commands/FordelingsmodtagerListCommand.php @@ -0,0 +1,51 @@ +addArgument('routingMyndighed', InputArgument::REQUIRED, 'The routing myndighed') + ->addArgument('routingKleEmne', InputArgument::REQUIRED, 'The KLE-emne') + ->addArgument('routingHandlingFacet', InputArgument::OPTIONAL, 'The routingHandlingFacet'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + + $routingMyndighed = $input->getArgument('routingMyndighed'); + $routingKLEEmne = $input->getArgument('routingKleEmne'); + $routingHandlingFacet = $input->getArgument('routingHandlingFacet'); + + $info = $this->helper->sf2900()->getModtagerList( + routingMyndighed: $routingMyndighed, + routingKLEEmne: $routingKLEEmne, + routingHandlingFacet: $routingHandlingFacet, + ); + $io->writeln(json_encode($info, JSON_PRETTY_PRINT)); + + return self::SUCCESS; + } + +} diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 7cb438d..45783f4 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -49,9 +49,7 @@ use ItkDev\Serviceplatformen\SF2900\StructType\JournalPostRelationsListeType; use ItkDev\Serviceplatformen\SF2900\StructType\JournalPostType; use ItkDev\Serviceplatformen\SF2900\StructType\MeddelelseType; -use ItkDev\Serviceplatformen\SF2900\StructType\ModtagerMedEndpointType; use ItkDev\Serviceplatformen\SF2900\StructType\RelationsListe; -use ItkDev\Serviceplatformen\SF2900\StructType\TilgaengeligeModtagereType; use ItkDev\Serviceplatformen\SF2900\StructType\TilstandListeType; use ItkDev\Serviceplatformen\SF2900\StructType\TilstandType; use ItkDev\Serviceplatformen\SF2900\StructType\UUID_URN; @@ -663,8 +661,9 @@ private function buildTriggerFile(File $file, string $sftpFilename, HandlerSetti $senderItSystem = $handlerSettings->sender->registreringItSystem; $senderAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->sender->routingMyndighed; $timestamp = SF2900::formatDateTime(new \DateTimeImmutable()); - // @todo Get this from a recipient lookup. - $recipientItSystem = trim((string) $handlerSettings->distributionContext->recipientItSystem); + + // @todo Get this from a recipient lookup if not set. + $recipientItSystem = trim((string) $handlerSettings->distributionObject->files->recipientItSystem); $recipientAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->distributionObject->files->recipientAuthority; $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/InfRef', $infRef); @@ -674,7 +673,8 @@ private function buildTriggerFile(File $file, string $sftpFilename, HandlerSetti $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderTimestamp', $timestamp); if (empty($recipientItSystem)) { $removeElement('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system'); - } else { + } + else { $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system', $recipientItSystem); } $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientAuthority', $recipientAuthority); diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index d1752d0..a63f575 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -100,14 +100,14 @@ private function buildConfigurationFormDistributionContext(): array { $settings = $this->settingsService->getDistributionContextSettings((array) ($this->getSettings()[DistributionContextSettings::NAME] ?? NULL)); $globalSettings = $this->settingsService->getDistributionContextSettings(); - $section[DistributionContextSettings::RECIPIENT_IT_SYSTEM] = [ - '#title' => $this->t('Recipient IT system'), + $section[DistributionContextSettings::ROUTING_MODTAGER_AKTOER] = [ + '#title' => $this->t('Routing modtager aktoer'), '#type' => 'textfield', '#attributes' => [ - 'pattern' => DistributionContextSettings::RECIPIENT_IT_SYSTEM_PATTERN, + 'pattern' => DistributionContextSettings::ROUTING_MODTAGER_AKTOER_PATTERN, ], - '#default_value' => $settings->recipientItSystem, - '#description' => $this->t('Uuid of recipient IT system. If set, any routing rules (using %kle_emne and %handling_facet) will be ignored.', [ + '#default_value' => $settings->routingModtagerAktoer, + '#description' => $this->t('Routing modtager aktoer (UUID). If set, any routing rules (using %kle_emne and %handling_facet) will be ignored.', [ '%kle_emne' => $this->t('KLE-emne'), '%handling_facet' => $this->t('Handling facet'), ]), @@ -124,14 +124,6 @@ private function buildConfigurationFormDistributionContext(): array { 'pattern' => DistributionContextSettings::KLE_EMNE_PATTERN, ], '#description' => $this->t('KLE-emne (format: dd.dd.dd)'), -// '#states' => [ -// 'visible' => [ -// ':input[name="settings[' . DistributionContextSettings::NAME . '][' . DistributionContextSettings::RECIPIENT_IT_SYSTEM . ']"]' => ['value' => ''], -// ], -// 'required' => [ -// ':input[name="settings[' . DistributionContextSettings::NAME . '][' . DistributionContextSettings::RECIPIENT_IT_SYSTEM . ']"]' => ['value' => ''], -// ], -// ], ]; $section[DistributionContextSettings::HANDLING_FACET] = [ @@ -142,11 +134,6 @@ private function buildConfigurationFormDistributionContext(): array { 'pattern' => DistributionContextSettings::HANDLING_FACET_PATTERN, ], '#description' => $this->t('handlingfacet (format: [A-Å]dd)'), -// '#states' => [ -// 'visible' => [ -// ':input[name="settings[' . DistributionContextSettings::NAME . '][' . DistributionContextSettings::RECIPIENT_IT_SYSTEM . ']"]' => ['value' => ''], -// ], -// ], ]; $section[DistributionContextSettings::BRUGERVENDT_NOEGLE] = [ @@ -257,14 +244,31 @@ private function buildConfigurationFormDistributionObject(): array { '#type' => 'textfield', '#title' => $this->t('Filspecifikation (InfRef)'), '#default_value' => $settings->files->filspecifikation, + '#description' => $this->t('Filspecifikation matching %formular_type', [ + '%formular_type' => $this->t('Formulartype'), + ]), ]; $setStates($section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::FILSPECIFIKATION], [ DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, ], require: FALSE); - $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_AUTHORITY] = [ + $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM] = [ + '#title' => $this->t('Recipient IT system'), '#type' => 'textfield', + '#attributes' => [ + 'pattern' => DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM_PATTERN, + ], + '#default_value' => $settings->files->recipientItSystem, + '#description' => $this->t('Recipient IT system (UUID). Leave empty for implicit file routing.'), + ]; + + $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_AUTHORITY] = [ '#title' => $this->t('Recipient authority'), + '#type' => 'textfield', + '#attributes' => [ + 'pattern' => DistributionObjectFilesSettings::RECIPIENT_AUTHORITY_PATTERN, + ], + '#default_value' => $settings->files->recipientAuthority, ]; $setStates($section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_AUTHORITY], [ @@ -295,16 +299,16 @@ private function buildConfigurationFormDistributionObject(): array { /** * {@inheritdoc} */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) - { + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { $setError = static fn(string|array $path, TranslatableMarkup $message) => $form_state->setErrorByName(implode('][', - (array)$path), $message); + (array) $path), $message); - $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::RECIPIENT_IT_SYSTEM] ?? ''; - if ($value && !preg_match('/' . DistributionContextSettings::RECIPIENT_IT_SYSTEM_PATTERN . '/', $value)) { + $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::ROUTING_MODTAGER_AKTOER] ?? ''; + if (!empty($value) + && !preg_match('/' . DistributionContextSettings::ROUTING_MODTAGER_AKTOER_PATTERN . '/', (string) $value)) { $setError( - [DistributionContextSettings::NAME, DistributionContextSettings::RECIPIENT_IT_SYSTEM], - $this->t('Invalid recipient IT system: %value.', ['%value' => $value]) + [DistributionContextSettings::NAME, DistributionContextSettings::ROUTING_MODTAGER_AKTOER], + $this->t('Invalid routing modtager aktør: %value.', ['%value' => $value]) ); } @@ -312,18 +316,26 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form if (!preg_match('/' . DistributionContextSettings::KLE_EMNE_PATTERN . '/', $value)) { $setError( [DistributionContextSettings::NAME, DistributionContextSettings::KLE_EMNE], - $this->t('Invalid KLE-emne: %value.', ['%value' => $value]) - ); - } + $this->t('Invalid KLE-emne: %value.', ['%value' => $value]) + ); + } - $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::HANDLING_FACET] ?? ''; - if (!empty($value) && !preg_match('/' . DistributionContextSettings::HANDLING_FACET_PATTERN . '/', - (string)$value)) { - $setError( - [DistributionContextSettings::NAME, DistributionContextSettings::HANDLING_FACET], - $this->t('Invalid Handling-facet: %handling_facet.', ['%handling_facet' => $value]) - ); - } + $value = $form_state->getValue(DistributionContextSettings::NAME)[DistributionContextSettings::HANDLING_FACET] ?? ''; + if (!empty($value) + && !preg_match('/' . DistributionContextSettings::HANDLING_FACET_PATTERN . '/', (string) $value)) { + $setError( + [DistributionContextSettings::NAME, DistributionContextSettings::HANDLING_FACET], + $this->t('Invalid Handling-facet: %value.', ['%value' => $value]) + ); + } + + $value = $form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM] ?? ''; + if ($value && !preg_match('/' . DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM_PATTERN . '/', $value)) { + $setError( + [DistributionObjectSettings::NAME, DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM], + $this->t('Invalid recipient IT system: %value.', ['%value' => $value]) + ); + } $type = $form_state->getValue(DistributionObjectSettings::NAME)[DistributionObjectSettings::DISTRIBUTION_TYPE] ?? ''; if (DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR === $type) { @@ -416,7 +428,7 @@ public function getSummary() { $items = []; - if (true || $settings->distributionContext->kleEmne) { + if (TRUE || $settings->distributionContext->kleEmne) { $items[] = Link::createFromRoute( $this->t('Show routing info'), 'os2forms_fordelingskomponent.routing_info', [ diff --git a/src/Settings/AbstractSettings.php b/src/Settings/AbstractSettings.php index 0c898a1..0786a9a 100644 --- a/src/Settings/AbstractSettings.php +++ b/src/Settings/AbstractSettings.php @@ -6,6 +6,9 @@ * Abstract settings. */ abstract class AbstractSettings implements \JsonSerializable { + protected const string UUID_PATTERN = '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'; + protected const string CVR_PATTERN = '^[0-9]{8}$'; + /** * Settings properties. * diff --git a/src/Settings/DistributionContextSettings.php b/src/Settings/DistributionContextSettings.php index 568c085..b18e4ec 100644 --- a/src/Settings/DistributionContextSettings.php +++ b/src/Settings/DistributionContextSettings.php @@ -11,13 +11,10 @@ final class DistributionContextSettings extends AbstractSettings { const string NAME = 'distribution_context'; protected static array $nullableProperties = [ + self::ROUTING_MODTAGER_AKTOER => TRUE, self::HANDLING_FACET => TRUE, ]; - public const string RECIPIENT_IT_SYSTEM_PATTERN = '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'; - public const string RECIPIENT_IT_SYSTEM = 'recipient_it_system'; - public ?string $recipientItSystem = NULL; - public const string KLE_EMNE_PATTERN = '^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$'; public const string KLE_EMNE = 'kle_emne'; public ?string $kleEmne = NULL; @@ -29,7 +26,7 @@ final class DistributionContextSettings extends AbstractSettings { public const string BRUGERVENDT_NOEGLE = 'brugervendt_noegle'; public ?string $brugervendtNoegle = NULL; - // @todo Use this? + public const string ROUTING_MODTAGER_AKTOER_PATTERN = self::UUID_PATTERN; public const string ROUTING_MODTAGER_AKTOER = 'routing_modtager_aktoer'; public ?string $routingModtagerAktoer = NULL; diff --git a/src/Settings/DistributionObjectSettings.php b/src/Settings/DistributionObjectSettings.php index d7bed52..99b0a7d 100644 --- a/src/Settings/DistributionObjectSettings.php +++ b/src/Settings/DistributionObjectSettings.php @@ -3,7 +3,6 @@ namespace Drupal\os2forms_fordelingskomponent\Settings; use Drupal\os2forms_fordelingskomponent\Settings\DistributionObjectSettings\FilesSettings; -use Drupal\os2forms_fordelingskomponent\Settings\SenderSettings\SftpSettings; use ItkDev\Serviceplatformen\SF2900\EnumType\ObjektTypeType; /** @@ -48,5 +47,4 @@ public function __construct(array $values, bool $throwExceptionOnMissingProperty parent::__construct($values, $throwExceptionOnMissingProperty); } - } diff --git a/src/Settings/DistributionObjectSettings/FilesSettings.php b/src/Settings/DistributionObjectSettings/FilesSettings.php index 2aa9bd8..ca3adb1 100644 --- a/src/Settings/DistributionObjectSettings/FilesSettings.php +++ b/src/Settings/DistributionObjectSettings/FilesSettings.php @@ -4,13 +4,20 @@ use Drupal\os2forms_fordelingskomponent\Settings\AbstractSettings; -class FilesSettings extends AbstractSettings -{ +/** + * Files settings. + */ +class FilesSettings extends AbstractSettings { const string NAME = 'files'; public const string FILSPECIFIKATION = 'filspecifikation'; public ?string $filspecifikation = ''; + public const string RECIPIENT_IT_SYSTEM_PATTERN = self::UUID_PATTERN; + public const string RECIPIENT_IT_SYSTEM = 'recipient_it_system'; + public ?string $recipientItSystem = NULL; + + public const string RECIPIENT_AUTHORITY_PATTERN = self::CVR_PATTERN; public const string RECIPIENT_AUTHORITY = 'recipient_authority'; public ?string $recipientAuthority = ''; From 8bec6a5bcb811c828f79aa31720b5ab34789e780 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 24 Apr 2026 11:34:46 +0200 Subject: [PATCH 36/62] Added some XML examples --- resources/.gitignore | 2 + .../SP/examples/SF2900_XSD/Anmodning.xml | 40 +++++++++++++++++ .../SP/examples/SF2900_XSD/SP241-Fuldmagt.xml | 45 +++++++++++++++++++ .../SF2900_XSD/SP241-Sygeforsikring.xml | 41 +++++++++++++++++ resources/SP/examples/SF2900_XSD/SP241.xml | 38 ++++++++++++++++ .../SP/twig/SF2900_XSD/Anmodning.twig.xml | 40 +++++++++++++++++ resources/Taskfile.yml | 18 ++++++++ 7 files changed, 224 insertions(+) create mode 100644 resources/.gitignore create mode 100644 resources/SP/examples/SF2900_XSD/Anmodning.xml create mode 100644 resources/SP/examples/SF2900_XSD/SP241-Fuldmagt.xml create mode 100644 resources/SP/examples/SF2900_XSD/SP241-Sygeforsikring.xml create mode 100644 resources/SP/examples/SF2900_XSD/SP241.xml create mode 100644 resources/SP/twig/SF2900_XSD/Anmodning.twig.xml create mode 100644 resources/Taskfile.yml diff --git a/resources/.gitignore b/resources/.gitignore new file mode 100644 index 0000000..859b8db --- /dev/null +++ b/resources/.gitignore @@ -0,0 +1,2 @@ +.task + diff --git a/resources/SP/examples/SF2900_XSD/Anmodning.xml b/resources/SP/examples/SF2900_XSD/Anmodning.xml new file mode 100644 index 0000000..0e0a92d --- /dev/null +++ b/resources/SP/examples/SF2900_XSD/Anmodning.xml @@ -0,0 +1,40 @@ + + +
+ urn:oio:cvr-nr:12345678 + 2026-04-24 + {{ handler.settings.distributionContext.kleEmne }} + + + {{ file.sftp_filename }} + Overslag + + + + + {{ file.sftp_filename }} + Faktura + + + + + {{ file.sftp_filename }} + Bilag + + +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + 2026-04-24 + +
diff --git a/resources/SP/examples/SF2900_XSD/SP241-Fuldmagt.xml b/resources/SP/examples/SF2900_XSD/SP241-Fuldmagt.xml new file mode 100644 index 0000000..111f2da --- /dev/null +++ b/resources/SP/examples/SF2900_XSD/SP241-Fuldmagt.xml @@ -0,0 +1,45 @@ + + +
+ urn:oio:cvr-nr:12345678 + 2026-04-24 + {{ handler.settings.distributionContext.kleEmne }} + + + {{ file.sftp_filename }} + Overslag + + + + + {{ file.sftp_filename }} + Faktura + + + + + {{ file.sftp_filename }} + Bilag + + +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + GRUPPE_BASIS + + Accepteret + + Underskrift0 + 2026-04-26 + + + + urn:oio:cpr:1111111111 + +
diff --git a/resources/SP/examples/SF2900_XSD/SP241-Sygeforsikring.xml b/resources/SP/examples/SF2900_XSD/SP241-Sygeforsikring.xml new file mode 100644 index 0000000..c718c5e --- /dev/null +++ b/resources/SP/examples/SF2900_XSD/SP241-Sygeforsikring.xml @@ -0,0 +1,41 @@ + + +
+ urn:oio:cvr-nr:12345678 + 2026-04-24 + {{ handler.settings.distributionContext.kleEmne }} + + + {{ file.sftp_filename }} + Overslag + + + + + {{ file.sftp_filename }} + Faktura + + + + + {{ file.sftp_filename }} + Bilag + + +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + GRUPPE_BASIS + + Accepteret + + Underskrift0 + 2026-04-26 + +
diff --git a/resources/SP/examples/SF2900_XSD/SP241.xml b/resources/SP/examples/SF2900_XSD/SP241.xml new file mode 100644 index 0000000..63c9b56 --- /dev/null +++ b/resources/SP/examples/SF2900_XSD/SP241.xml @@ -0,0 +1,38 @@ + + +
+ urn:oio:cvr-nr:12345678 + 2026-04-24 + {{ handler.settings.distributionContext.kleEmne }} + + + {{ file.sftp_filename }} + Overslag + + + + + {{ file.sftp_filename }} + Faktura + + + + + {{ file.sftp_filename }} + Bilag + + +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + Accepteret + + Underskrift0 + 2026-04-26 + +
diff --git a/resources/SP/twig/SF2900_XSD/Anmodning.twig.xml b/resources/SP/twig/SF2900_XSD/Anmodning.twig.xml new file mode 100644 index 0000000..5281bdb --- /dev/null +++ b/resources/SP/twig/SF2900_XSD/Anmodning.twig.xml @@ -0,0 +1,40 @@ + + +
+ urn:oio:cvr-nr:{{ handler.settings.sender.senderId }} + {{ submission.completed.value|date("Y-m-d") }} + {{ handler.settings.distributionContext.kleEmne }} + {% for file in files.dokumenter_overslag|default([]) %} + + {{ file.sftp_filename }} + Overslag + + {% endfor %} + {% for file in files.dokumenter_faktura|default([]) %} + + {{ file.sftp_filename }} + Faktura + + {% endfor %} + {% for file in files.dokumenter_bilag|default([]) %} + + {{ file.sftp_filename }} + Bilag + + {% endfor %} +
+ + + {{ submission.data.fornavn }} + {{ submission.data.efternavn }} + urn:oio:cpr:0000000000 + + + + Medicin + + + Underskrift0 + {{ submission.completed.value|date("Y-m-d") }} + +
diff --git a/resources/Taskfile.yml b/resources/Taskfile.yml new file mode 100644 index 0000000..e45d5a5 --- /dev/null +++ b/resources/Taskfile.yml @@ -0,0 +1,18 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json + +version: '3' + +tasks: + xml:validate: + desc: Validate all XML files using their XSD file + sources: + - 'SP/examples/**/*.xml' + cmds: + - for: sources + # https://taskfile.dev/docs/reference/templating#regular-expressions + # cmd: echo xmlstarlet val --err --xsd {{ regexReplaceAll "\\.xml" (.ITEM | replace "/examples/" "/" | "\\.xml" ".xsd" }} {{.ITEM}} + cmd: xmlstarlet val --err --xsd {{ regexReplaceAll "(-[^-]+)?\\.xml$" (.ITEM | replace "/examples/" "/") ".xsd" }} {{.ITEM}} + # Force the task to always run + # (cf. https://taskfile.dev/docs/guide#using-programmatic-checks-to-indicate-a-task-is-up-to-date) + status: + - false From ca36627d9c3292388c382733eb123ab388bef6c1 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 24 Apr 2026 11:35:04 +0200 Subject: [PATCH 37/62] Updated installation instructions --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e05de4..480d1e5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ ssh-keygen -p -N "" -f cert/sf2900-sftp-nopass ``` -2. Go to `/admin/os2forms_fordelingskomponent/settings` and configure the Fordelingskomponent module. +2. Create a queue (on `/admin/config/system/queues`) for Fordelingskomponent handler jobs. +3. Go to `/admin/os2forms_fordelingskomponent/settings` and configure the Fordelingskomponent module. [^1] It takes a very long time to read a key with a password (reference?) @@ -118,3 +119,12 @@ curl --verbose --insecure --location «base url»/os2forms-fordelingskomponent/s XML ``` + +## References + +* [Fælleskommunal Filudveksling](https://digitaliseringskataloget.dk/l%C3%B8sninger/filudveksling) + * [Vejledning til Serviceplatformens SFTP-service](https://docs.kombit.dk/latest/d312b273) + +## KP-formularer + +* From 22b0480b66a13d85e30143c74a1a005b1426ffe5 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 24 Apr 2026 13:21:09 +0200 Subject: [PATCH 38/62] Added SP241 example --- .../README.md | 10 +- .../webform.webform.os2forms_fdk_kp_sp241.yml | 362 ++++++++++++++++++ ...rmsFordelingskomponentExamplesCommands.php | 4 +- resources/Taskfile.yml | 4 +- 4 files changed, 374 insertions(+), 6 deletions(-) create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml diff --git a/modules/os2forms_fordelingskomponent_examples/README.md b/modules/os2forms_fordelingskomponent_examples/README.md index 04ab9ef..4728314 100644 --- a/modules/os2forms_fordelingskomponent_examples/README.md +++ b/modules/os2forms_fordelingskomponent_examples/README.md @@ -18,7 +18,7 @@ to create a new example webform it must be have an ID like `os2forms_fdk_my_exam Run ``` shell -drush os2forms_fordelingskomponent_examples:export-examples +drush os2forms-fordelingskomponent:examples:export ``` to export all example webforms. @@ -30,7 +30,13 @@ drush pm:uninstall os2forms_fordelingskomponent_examples drush pm:install os2forms_fordelingskomponent_examples ``` -Alternatively, import a single webform, e.g. +Alternatively, import all examples: + +``` shell +drush config:import --partial --source module://os2forms_fordelingskomponent_examples/config/install +``` + +Or a single webform, e.g. ``` shell drush config:set --input-format=yaml webform.webform.os2forms_fdk_kp_anmoding '?' - < config/install/webform.webform.os2forms_fdk_kp_anmoding.yml diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml new file mode 100644 index 0000000..79a5a80 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml @@ -0,0 +1,362 @@ +langcode: da +status: open +dependencies: + module: + - os2forms + - os2forms_permissions_by_term + - webform_encrypt + - webform_entity_print + - webform_revisions +third_party_settings: + os2forms: + os2forms_email_handler: + enabled: 0 + email_recipients: "" + os2forms_nemid: + session_type: "" + webform_type: personal + nemlogin_auto_redirect: 1 + os2forms_nemlogin_openid_connect: + authentication_settings: + user_claim: "" + element_key: "" + error_message: "" + os2forms_nemid_address_protection: + nemlogin_hide_form: os2forms_nemlogin_address_protection_default_behaviour + nemlogin_hide_message: "" + os2forms_rest_api: + allowed_users: null + os2forms_sync: + publish: 0 + os2forms_webform_submission_log: + emails: "" + webform_entity_print: + template: + header: "" + footer: "" + css: "" + os2form_header: "" + os2form_colophon: "" + os2form_footer: "" + export_types: + pdf: + enabled: true + link_text: "" + link_attributes: {} + word_docx: + enabled: false + link_text: "" + link_attributes: {} + webform_encrypt: + element: + ansoeger_fornavn: + encrypt: true + encrypt_profile: webform + ansoeger_mellemnavn: + encrypt: true + encrypt_profile: webform + ansoeger_efternavn: + encrypt: true + encrypt_profile: webform + ansoeger_personnummer: + encrypt: true + encrypt_profile: webform + ansoeger_telefonnummer: + encrypt: true + encrypt_profile: webform + sygeforsikring: + encrypt: true + encrypt_profile: webform + sygeforsikring_gruppe: + encrypt: true + encrypt_profile: webform + erklaering: + encrypt: true + encrypt_profile: webform + underskriftsoplysninger_underskrift: + encrypt: true + encrypt_profile: webform + underskriftsoplysninger_underskriftsdato: + encrypt: true + encrypt_profile: webform + fuldmagt: + encrypt: true + encrypt_profile: webform + fuldmagt_fuldmagtdokumentnavn: + encrypt: true + encrypt_profile: webform + fuldmagt_fuldmagthaverspersonnummer: + encrypt: true + encrypt_profile: webform +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2forms_fdk_kp_sp241 +title: 'Ansøgning om helbredstillæg (SP241)' +description: "" +categories: + - KP + - SP +elements: |- + ansoeger_fornavn: + '#type': textfield + '#title': Fornavn + '#required': true + ansoeger_mellemnavn: + '#type': textfield + '#title': Mellemnavn + ansoeger_efternavn: + '#type': textfield + '#title': Efternavn + '#required': true + ansoeger_personnummer: + '#type': textfield + '#title': Personnummer + '#required': true + ansoeger_telefonnummer: + '#type': textfield + '#title': Telefonnummer + '#pattern': \d+ + sygeforsikring: + '#type': checkbox + '#title': Sygeforsikring + sygeforsikring_gruppe: + '#type': select + '#title': Gruppe + '#options': + GRUPPE_1: 'Gruppe 1' + GRUPPE_2: 'Gruppe 2' + GRUPPE_5: 'Gruppe 5' + GRUPPE_E: 'Gruppe E' + GRUPPE_N: 'Gruppe N' + GRUPPE_S: 'Gruppe S' + GRUPPE_BASIS: 'Basis' + '#states': + visible: + ':input[name="sygeforsikring"]': + checked: true + required: + ':input[name="sygeforsikring"]': + checked: true + erklaering: + '#type': checkbox + '#title': Erklæring + '#required': true + underskriftsoplysninger_underskrift: + '#type': textfield + '#title': Underskrift + underskriftsoplysninger_underskriftsdato: + '#type': date + '#title': Underskriftsdato + '#default_value': today + fuldmagt: + '#type': checkbox + '#title': Fuldmagt + fuldmagt_fuldmagtdokumentnavn: + '#type': textfield + '#title': Navn + '#states': + visible: + ':input[name="fuldmagt"]': + checked: true + required: + ':input[name="fuldmagt"]': + checked: true + fuldmagt_fuldmagthaverspersonnummer: + '#type': textfield + '#title': Personnummer + '#states': + visible: + ':input[name="fuldmagt"]': + checked: true + required: + ':input[name="fuldmagt"]': + checked: true +css: "" +javascript: "" +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: "" + ajax_effect: "" + ajax_speed: null + page: true + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" + form_title: both + form_submit_once: false + form_open_message: "" + form_close_message: "" + form_exception_message: "" + form_previous_submissions: true + form_confidential: false + form_confidential_message: "" + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: "" + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" + share: false + share_node: false + share_theme_name: "" + share_title: true + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" + submission_log: false + submission_excluded_elements: {} + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" + autofill: false + autofill_message: "" + autofill_excluded_elements: {} + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: "" + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: "" + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" + wizard_toggle: false + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" + confirmation_type: page + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} + confirmation_back: true + confirmation_back_label: "" + confirmation_back_attributes: {} + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: "" + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: "" + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: {} + permissions: {} + view_any: + roles: {} + users: {} + permissions: {} + update_any: + roles: {} + users: {} + permissions: {} + delete_any: + roles: {} + users: {} + permissions: {} + purge_any: + roles: {} + users: {} + permissions: {} + view_own: + roles: {} + users: {} + permissions: {} + update_own: + roles: {} + users: {} + permissions: {} + delete_own: + roles: {} + users: {} + permissions: {} + administer: + roles: {} + users: {} + permissions: {} + test: + roles: {} + users: {} + permissions: {} + configuration: + roles: {} + users: {} + permissions: {} +handlers: {} +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php b/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php index 975eff0..9768ac9 100644 --- a/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php +++ b/modules/os2forms_fordelingskomponent_examples/src/Drush/Commands/Os2formsFordelingskomponentExamplesCommands.php @@ -45,7 +45,7 @@ public function __construct( /** * Command description here. */ - #[CLI\Command(name: 'os2forms_fordelingskomponent_examples:export-examples')] + #[CLI\Command(name: 'os2forms-fordelingskomponent:examples:export')] public function commandName() { $io = $this->io(); @@ -79,7 +79,7 @@ public function commandName() { $io->writeln(dt('Clearing key %key', ['%key' => $key])); $config->clear($key); } - // @todo (Hon) Can we use the config manager (or factory) to do this? + // @todo (How) Can we use the config manager (or factory) to do this? file_put_contents($targetName, Yaml::encode($config->get())); $io->success(dt('Config written to %file', ['%file' => $targetName])); } diff --git a/resources/Taskfile.yml b/resources/Taskfile.yml index e45d5a5..fbe2a95 100644 --- a/resources/Taskfile.yml +++ b/resources/Taskfile.yml @@ -1,12 +1,12 @@ # yaml-language-server: $schema=https://taskfile.dev/schema.json -version: '3' +version: "3" tasks: xml:validate: desc: Validate all XML files using their XSD file sources: - - 'SP/examples/**/*.xml' + - "SP/examples/**/*.xml" cmds: - for: sources # https://taskfile.dev/docs/reference/templating#regular-expressions From c9bf867ab4f04abd9c6b086457777d1d27c6a679 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 24 Apr 2026 14:29:39 +0200 Subject: [PATCH 39/62] Cleaned up preview --- ...entDistributionObjectPreviewController.php | 2 +- src/Helper/FordelingskomponentHelper.php | 21 ++++++++++++++--- src/Helper/XmlHelper.php | 3 +-- src/Model/XmlRenderResult.php | 23 ++++++++++++++++++- src/Settings/AbstractSettings.php | 14 +++++++++-- templates/base.html.twig | 9 ++++---- 6 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php index 4ebaf1e..1e63080 100644 --- a/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php +++ b/src/Controller/Os2formsFordelingskomponentDistributionObjectPreviewController.php @@ -95,7 +95,7 @@ public function renderPreview(WebformHandlerSF2900 $handler, HandlerSettings $ha 'exceptions' => $exceptions, 'warnings' => $warnings, 'distribution_object' => $distributionObject, - 'xml' => $xml, + 'xml' => $xml->withContextAsArray(), ]; } diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 45783f4..4bb60b8 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -264,7 +264,12 @@ private function buildDistributionFormularType( Attachment $attachment, ): DistributionFormular { $files = $this->buildFileGroups($handlerSettings, $submission); - $xml = $this->renderXml($handlerSettings, $submission, $files)->rendered; + $renderResult = $this->renderXml($handlerSettings, $submission, $files); + if ($renderResult->exception) { + throw $renderResult->exception; + } + + $xml = (string) $renderResult->rendered; $xsdUrl = $handlerSettings->distributionObject->xsdUrl; $this->xmlHelper->validateXml($xml); @@ -319,10 +324,20 @@ public function renderXml( $context = $this->xmlHelper->getRenderContext($handlerSettings, $submission, $files); + $rendered = NULL; + $exception = NULL; + try { + $rendered = $this->xmlHelper->render($template, $context, validateXml: $validateXml); + } + catch (\Exception $e) { + $exception = $e; + } + return new XmlRenderResult( - rendered: $this->xmlHelper->render($template, $context, validateXml: $validateXml), template: $template, context: $context, + rendered: $rendered, + exception: $exception, ); } @@ -349,7 +364,7 @@ public function buildFileGroups(HandlerSettings $handlerSettings, WebformSubmiss $values = $submission->getData()[$type] ?? NULL; if ($values) { /** @var \Drupal\file\FileInterface[] $files */ - $files = $this->fileStorage->loadMultiple($values); + $files = $this->fileStorage->loadMultiple((array) $values); foreach ($files as $file) { $groups[$type][] = [ 'sftp_filename' => $this->getSftpFilename($handlerSettings, $submission, $file->getFilename()), diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index bd83843..172f1d3 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -70,10 +70,9 @@ public function render(string $template, array $context, bool $validateXml = TRU */ public function getRenderContext(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, array $files) { return [ - 'handler' => ['settings' => $handlerSettings], 'submission' => $submission, - 'webform_submission' => $submission, 'files' => $files, + 'handler' => ['settings' => $handlerSettings->toArray()], ]; } diff --git a/src/Model/XmlRenderResult.php b/src/Model/XmlRenderResult.php index 36b556b..36e00c9 100644 --- a/src/Model/XmlRenderResult.php +++ b/src/Model/XmlRenderResult.php @@ -2,6 +2,8 @@ namespace Drupal\os2forms_fordelingskomponent\Model; +use Drupal\webform\WebformSubmissionInterface; + /** * The Document class. */ @@ -11,10 +13,29 @@ * Constructor. */ public function __construct( - public string $rendered, public string $template, public array $context, + public ?string $rendered, + public ?\Exception $exception, ) { } + /** + * Convert objects in context to arrays. + */ + public function withContextAsArray(): self { + $context = $this->context; + if (($context['submission'] ?? NULL) instanceof WebformSubmissionInterface) { + $context['submission'] = $context['submission']->toArray(TRUE, TRUE); + } + // @todo Convert 'handler' and 'files' + return new self( + $this->template, + $context, + $this->rendered, + $this->exception, + ); + + } + } diff --git a/src/Settings/AbstractSettings.php b/src/Settings/AbstractSettings.php index 0786a9a..4a73380 100644 --- a/src/Settings/AbstractSettings.php +++ b/src/Settings/AbstractSettings.php @@ -114,8 +114,18 @@ public function apply(array $values, bool $throwExceptionOnMissingProperty = FAL /** * Convert settings to array. */ - public function toArray(): array { - return $this->values; + public function toArray(bool $recursive = true): array { + $values = $this->values; + + if ($recursive) { + foreach ($values as &$value) { + if ($value instanceof self) { + $value = $value->toArray($recursive); + } + } + } + + return $values; } /** diff --git a/templates/base.html.twig b/templates/base.html.twig index 5086f96..155c644 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -83,9 +83,11 @@
{{ label }} + {# {% set data_url = 'data:application/json,' ~ (value|json_encode|url_encode) %} {{ 'Open data in new tab'|trans }} + #}
{{ value|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_UNICODE')) }}
@@ -95,13 +97,12 @@
{{ label }} + {# {% set data_url = 'data:text/xml,' ~ (value|url_encode) %} {{ 'Open data in new tab'|trans }} + #} -
- {{ 'Raw content'|trans }} -
{{ value }}
-
+
{{ value }}
{% endmacro %} From 0a29c00a8626ebee1e73b917f54401e3b3042dab Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 24 Apr 2026 15:08:13 +0200 Subject: [PATCH 40/62] Updated stuff --- .../webform.webform.os2forms_fdk_kp_sp241.yml | 74 +++++++++++++++++-- src/Helper/FordelingskomponentHelper.php | 22 ++++-- .../WebformHandler/WebformHandlerSF2900.php | 15 +++- .../FilesSettings.php | 3 + 4 files changed, 101 insertions(+), 13 deletions(-) diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml index 79a5a80..82e05be 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml @@ -3,7 +3,7 @@ status: open dependencies: module: - os2forms - - os2forms_permissions_by_term + - os2forms_fordelingskomponent - webform_encrypt - webform_entity_print - webform_revisions @@ -88,6 +88,18 @@ third_party_settings: fuldmagt_fuldmagthaverspersonnummer: encrypt: true encrypt_profile: webform + dokumenter_overslag: + encrypt: true + encrypt_profile: webform + dokumenter_faktura: + encrypt: true + encrypt_profile: webform + dokumenter_bilag: + encrypt: true + encrypt_profile: webform + kvittering: + encrypt: true + encrypt_profile: webform weight: 0 open: null close: null @@ -95,11 +107,12 @@ uid: 1 template: false archive: false id: os2forms_fdk_kp_sp241 -title: 'Ansøgning om helbredstillæg (SP241)' +title: "SP241 (eksempel): Ansøgning om helbredstillæg" description: "" categories: - KP - SP + - Eksempel elements: |- ansoeger_fornavn: '#type': textfield @@ -116,6 +129,7 @@ elements: |- '#type': textfield '#title': Personnummer '#required': true + '#pattern': '\d{10}' ansoeger_telefonnummer: '#type': textfield '#title': Telefonnummer @@ -133,9 +147,9 @@ elements: |- GRUPPE_E: 'Gruppe E' GRUPPE_N: 'Gruppe N' GRUPPE_S: 'Gruppe S' - GRUPPE_BASIS: 'Basis' + GRUPPE_BASIS: Basis '#states': - visible: + _visible: ':input[name="sygeforsikring"]': checked: true required: @@ -159,7 +173,7 @@ elements: |- '#type': textfield '#title': Navn '#states': - visible: + _visible: ':input[name="fuldmagt"]': checked: true required: @@ -168,13 +182,31 @@ elements: |- fuldmagt_fuldmagthaverspersonnummer: '#type': textfield '#title': Personnummer + '#pattern': '\d{10}' '#states': - visible: + _visible: ':input[name="fuldmagt"]': checked: true required: ':input[name="fuldmagt"]': checked: true + dokumenter_overslag: + '#type': webform_document_file + '#title': Overslag + dokumenter_faktura: + '#type': webform_document_file + '#title': Faktura + dokumenter_bilag: + '#type': webform_document_file + '#title': Bilag + kvittering: + '#type': os2forms_attachment + '#title': kvittering + '#export_type': pdf + '#digital_signature': 0 + '#excluded_elements': { } + '#exclude_empty': 0 + '#exclude_empty_checkbox': 0 css: "" javascript: "" settings: @@ -358,5 +390,33 @@ access: roles: {} users: {} permissions: {} -handlers: {} +handlers: + fordelingskomponent_sf2900: + id: os2forms_fordelingskomponent_sf2900 + handler_id: fordelingskomponent_sf2900 + label: "Fordelingskomponent (sf2900)" + notes: "" + status: true + conditions: {} + weight: 0 + settings: + distribution_context: + routing_modtager_aktoer: "" + kle_emne: 32.03.12 + handling_facet: G01 + brugervendt_noegle: "@todo Skal skjules for “formular“" + titel: "@todo Titel SP241" + beskrivelse: "@todo Beskrivelse\r\n\r\nSe https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf#page=14 for at finde formulartype mm.\r\n\r\n”KP – Ekstern Test” er et Netcompany system og har CVR-nr 14814833." + distribution_object: + distribution_type: FORMULAR + journalpost_message: "" + attachment_element: kvittering + formular_type: HelbredstillægAnsøgningFormular_1 + files: + filspecifikation: HelbredstillægAnsøgningBilag_1 + recipient_it_system_look_up: 1 + recipient_it_system: "" + recipient_authority: "14814833" + xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }}\r\n 2026-04-24\r\n {{ handler.settings.distribution_context.kle_emne }}\r\n {% for file in files.dokumenter_overslag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Overslag\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_faktura|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Faktura\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_bilag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Bilag\r\n \r\n {% endfor %}\r\n
\r\n \r\n \r\n {{ submission.data.ansoeger_fornavn }}\r\n {% if submission.data.ansoeger_mellemnavn|default(false) %}\r\n {{ submission.data.ansoeger_mellemnavn }}\r\n {% endif %}\r\n {{ submission.data.ansoeger_efternavn }}\r\n urn:oio:cpr:{{ submission.data.ansoeger_personnummer }}\r\n \r\n \r\n Accepteret\r\n \r\n Underskrift0\r\n 2026-04-26\r\n \r\n
\r\n" + xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/SP241.xsd" variants: {} diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 4bb60b8..055f923 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -419,12 +419,21 @@ public function sendDokument( elseif ($dokument instanceof DistributionFormular) { $files = $dokument->getFileGroups(); $sftp = $sf2900->sftp(); + $recipientItSystem = NULL; + if ($handlerSettings->distributionObject->files->recipientItSystemLookUp) { + $routingInfo = $this->getRoutingInfo($handlerSettings); + $system = $routingInfo->getSystemer()->getSystem(); + if (1 !== count($system)) { + throw new \RuntimeException('Cannot find single recipient system'); + } + $recipientItSystem = $system[0]->getSystemUUID(); + } foreach ($files as $items) { foreach ($items as $item) { /** @var \Drupal\file\Entity\File $file */ $file = $item['file']; $sftp->putFile($file->getFileUri(), $file->getFilename(), $item['sftp_filename']); - $triggerObject = $this->buildTriggerFile($file, $item['sftp_filename'], $handlerSettings, $transactionId); + $triggerObject = $this->buildTriggerFile($file, $item['sftp_filename'], $handlerSettings, $transactionId, recipientItSystem: $recipientItSystem); $sftp->putContents($triggerObject, $item['sftp_filename'], $item['sftp_filename'] . '.trigger'); } } @@ -614,6 +623,7 @@ private function getTransactionContext( } // @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf#page=16 + // @todo Generate classes from resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd. private const string TRIGGER_FILE_TEMPLATE = <<<'XML' @@ -643,7 +653,7 @@ private function getTransactionContext( * * @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf */ - private function buildTriggerFile(File $file, string $sftpFilename, HandlerSettings $handlerSettings, string $transactionId): string { + private function buildTriggerFile(File $file, string $sftpFilename, HandlerSettings $handlerSettings, string $transactionId, ?string $recipientItSystem = NULL): string { $dom = new \DOMDocument(); $dom->loadXML(self::TRIGGER_FILE_TEMPLATE); $xpath = new \DOMXPath($dom); @@ -677,8 +687,7 @@ private function buildTriggerFile(File $file, string $sftpFilename, HandlerSetti $senderAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->sender->routingMyndighed; $timestamp = SF2900::formatDateTime(new \DateTimeImmutable()); - // @todo Get this from a recipient lookup if not set. - $recipientItSystem = trim((string) $handlerSettings->distributionObject->files->recipientItSystem); + $recipientItSystem = trim((string) ($recipientItSystem ?? $handlerSettings->distributionObject->files->recipientItSystem)); $recipientAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->distributionObject->files->recipientAuthority; $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/InfRef', $infRef); @@ -694,7 +703,10 @@ private function buildTriggerFile(File $file, string $sftpFilename, HandlerSetti } $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientAuthority', $recipientAuthority); - return $dom->saveXML(); + $xml = $dom->saveXML(); + $this->xmlHelper->validateXml($xml, 'module://os2forms_fordelingskomponent/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd'); + + return $xml; } } diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index a63f575..3975e04 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -252,6 +252,14 @@ private function buildConfigurationFormDistributionObject(): array { DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, ], require: FALSE); + $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM_LOOK_UP] = [ + '#title' => $this->t('Look up %recipient_it_system based on distribution object routing', [ + '%recipient_it_system' => $this->t('Recipient IT system'), + ]), + '#type' => 'checkbox', + '#default_value' => $settings->files->recipientItSystemLookUp, + ]; + $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM] = [ '#title' => $this->t('Recipient IT system'), '#type' => 'textfield', @@ -260,6 +268,11 @@ private function buildConfigurationFormDistributionObject(): array { ], '#default_value' => $settings->files->recipientItSystem, '#description' => $this->t('Recipient IT system (UUID). Leave empty for implicit file routing.'), + '#states' => [ + 'visible' => [ + ':input[name="settings[' . DistributionObjectSettings::NAME . '][' . DistributionObjectSettings::FILES . '][' . DistributionObjectFilesSettings::RECIPIENT_IT_SYSTEM_LOOK_UP . ']"]' => ['checked' => FALSE], + ], + ], ]; $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_AUTHORITY] = [ @@ -268,8 +281,8 @@ private function buildConfigurationFormDistributionObject(): array { '#attributes' => [ 'pattern' => DistributionObjectFilesSettings::RECIPIENT_AUTHORITY_PATTERN, ], - '#default_value' => $settings->files->recipientAuthority, + '#description' => $this->t('CVR for recipient'), ]; $setStates($section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::RECIPIENT_AUTHORITY], [ DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, diff --git a/src/Settings/DistributionObjectSettings/FilesSettings.php b/src/Settings/DistributionObjectSettings/FilesSettings.php index ca3adb1..fd72936 100644 --- a/src/Settings/DistributionObjectSettings/FilesSettings.php +++ b/src/Settings/DistributionObjectSettings/FilesSettings.php @@ -17,6 +17,9 @@ class FilesSettings extends AbstractSettings { public const string RECIPIENT_IT_SYSTEM = 'recipient_it_system'; public ?string $recipientItSystem = NULL; + public const string RECIPIENT_IT_SYSTEM_LOOK_UP = 'recipient_it_system_look_up'; + public bool $recipientItSystemLookUp = TRUE; + public const string RECIPIENT_AUTHORITY_PATTERN = self::CVR_PATTERN; public const string RECIPIENT_AUTHORITY = 'recipient_authority'; public ?string $recipientAuthority = ''; From 51332b7e6ed3cfffcde484f0da153cb29652d219 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 24 Apr 2026 16:41:56 +0200 Subject: [PATCH 41/62] UTF-8 --- src/Helper/FordelingskomponentHelper.php | 2 +- src/Settings/AbstractSettings.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 055f923..2601fce 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -625,7 +625,7 @@ private function getTransactionContext( // @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf#page=16 // @todo Generate classes from resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd. private const string TRIGGER_FILE_TEMPLATE = <<<'XML' - + diff --git a/src/Settings/AbstractSettings.php b/src/Settings/AbstractSettings.php index 4a73380..08f85b5 100644 --- a/src/Settings/AbstractSettings.php +++ b/src/Settings/AbstractSettings.php @@ -114,7 +114,7 @@ public function apply(array $values, bool $throwExceptionOnMissingProperty = FAL /** * Convert settings to array. */ - public function toArray(bool $recursive = true): array { + public function toArray(bool $recursive = TRUE): array { $values = $this->values; if ($recursive) { From 842912ac37dde515eb85fb67ba65673f39dec5fd Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 24 Apr 2026 16:51:44 +0200 Subject: [PATCH 42/62] Cleaned up --- scripts/base | 2 +- src/Helper/FordelingskomponentHelper.php | 4 +--- src/Plugin/WebformHandler/WebformHandlerSF2900.php | 2 +- templates/base.html.twig | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/base b/scripts/base index d12b23d..def2c9d 100644 --- a/scripts/base +++ b/scripts/base @@ -87,7 +87,7 @@ setup() { composer --no-plugins config extra.drupal-lenient.allow-all --json true # composer --no-plugins config extra.drupal-lenient.allowed-list --json '[ "drupal/coc_forms_auto_export", "drupal/webform_node_element" ]' - composer require mglaman/composer-drupal-lenient --with-all-dependencies + composer require mglaman/composer-drupal-lenient:^1.0 --with-all-dependencies # -------------------------------------------------------------------------------------------------------------------- # We need to install dev requirements from our module, so we use diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 2601fce..0442ffd 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -350,10 +350,8 @@ public function renderXml( /** * Build files for a distribution object. * - * @return \Drupal\file\Entity\FileGroups + * @return array> * The file groups. - * - * @phpstan-import-type FileGroups from DistributionFormular */ public function buildFileGroups(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission): array { $groups = []; diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 3975e04..2506571 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -441,7 +441,7 @@ public function getSummary() { $items = []; - if (TRUE || $settings->distributionContext->kleEmne) { + if ($settings->distributionContext->kleEmne) { $items[] = Link::createFromRoute( $this->t('Show routing info'), 'os2forms_fordelingskomponent.routing_info', [ diff --git a/templates/base.html.twig b/templates/base.html.twig index 155c644..0b5f4b3 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -57,7 +57,7 @@ {% endif %}
{{ 'Back to webform handlers'|trans({'%handler': handler.label}) }} From 68fa21621743453bc35f4dead66baad5a48e7c19 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 27 Apr 2026 11:47:13 +0200 Subject: [PATCH 43/62] Renamed and stuff --- README.md | 7 ++++ os2forms_fordelingskomponent.services.yml | 6 ++-- ...eException.php => InvalidXmlException.php} | 2 +- src/Helper/FordelingskomponentHelper.php | 13 +++++++- src/Helper/WebformHelperSF2900.php | 27 ++++++++------- src/Helper/XmlHelper.php | 16 ++++----- ...2FormsFordelingskomponentLoggerChannel.php | 33 +++++++++++++++++++ .../WebformHandler/WebformHandlerSF2900.php | 4 +-- tests/Unit/Helper/XmlHelperTest.php | 14 ++++---- .../Unit/Helper/XmlHelperTestDataProvider.php | 12 +++---- 10 files changed, 93 insertions(+), 41 deletions(-) rename src/Exception/{InvalidXmlTemplateException.php => InvalidXmlException.php} (64%) create mode 100644 src/Logger/Os2FormsFordelingskomponentLoggerChannel.php diff --git a/README.md b/README.md index 480d1e5..63089fc 100644 --- a/README.md +++ b/README.md @@ -128,3 +128,10 @@ XML ## KP-formularer * + +## Debugging + +``` php +# settings.local.php +$settings['os2forms_fordelingskomponent']['log_level'] = \Drupal\Core\Logger\RfcLogLevel::DEBUG; +``` diff --git a/os2forms_fordelingskomponent.services.yml b/os2forms_fordelingskomponent.services.yml index fc385df..4c20c62 100644 --- a/os2forms_fordelingskomponent.services.yml +++ b/os2forms_fordelingskomponent.services.yml @@ -4,14 +4,14 @@ services: logger.channel.os2forms_fordelingskomponent: parent: logger.channel_base + # @todo This does not work as expected. + # class: Drupal\os2forms_fordelingskomponent\Logger\Os2FormsFordelingskomponentLoggerChannel arguments: ["os2forms_fordelingskomponent"] logger.channel.os2forms_fordelingskomponent_submission: parent: logger.channel_base arguments: ["webform_submission"] - Drupal\os2forms_fordelingskomponent\Settings: - Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper: tags: - { name: "event_subscriber" } @@ -27,3 +27,5 @@ services: Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository: Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository: + + Drupal\os2forms_fordelingskomponent\Settings: diff --git a/src/Exception/InvalidXmlTemplateException.php b/src/Exception/InvalidXmlException.php similarity index 64% rename from src/Exception/InvalidXmlTemplateException.php rename to src/Exception/InvalidXmlException.php index a366baf..ff4d4fd 100644 --- a/src/Exception/InvalidXmlTemplateException.php +++ b/src/Exception/InvalidXmlException.php @@ -5,6 +5,6 @@ /** * Invalid XML template exception. */ -class InvalidXmlTemplateException extends RuntimeException { +class InvalidXmlException extends RuntimeException { } diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 0442ffd..9fccd25 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -9,6 +9,7 @@ use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_fordelingskomponent\Exception\Exception; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; +use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlException; use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; use Drupal\os2forms_fordelingskomponent\Model\Attachment; use Drupal\os2forms_fordelingskomponent\Model\DistributionFormular; @@ -702,7 +703,17 @@ private function buildTriggerFile(File $file, string $sftpFilename, HandlerSetti $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientAuthority', $recipientAuthority); $xml = $dom->saveXML(); - $this->xmlHelper->validateXml($xml, 'module://os2forms_fordelingskomponent/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd'); + + try { + $this->xmlHelper->validateXml($xml, 'module://os2forms_fordelingskomponent/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd'); + } + catch (InvalidXmlException $e) { + $this->logger->error('Invalid XML in trigger file: %message.', [ + '%message' => $e->getMessage(), + 'exception' => $e, + ]); + throw new RuntimeException('Invalid XML in trigger file: %message.', ['%message' => $e->getMessage()]); + } return $xml; } diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 74179fd..b9e839d 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -10,6 +10,7 @@ use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\Render\ElementInfoManager; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; +use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; use Drupal\os2forms_fordelingskomponent\Exception\SubmissionNotFoundException; use Drupal\os2forms_fordelingskomponent\Model\Attachment; use Drupal\os2forms_fordelingskomponent\Model\XmlRenderResult; @@ -182,11 +183,15 @@ public function loadLatestSubmission(WebformInterface $webform): ?WebformSubmiss /** * Load queue. */ - private function loadQueue(): ?QueueInterface { - $id = $this->settings->getGeneralSettings()->queue; + private function loadQueue(): QueueInterface { + $id = $this->settings->getGeneralSettings()->queue ?? NULL; /** @var ?\Drupal\advancedqueue\Entity\QueueInterface $queue */ - $queue = $this->queueStorage->load($id ?? NULL); + $queue = $this->queueStorage->load($id); + + if (NULL === $queue) { + throw new RuntimeException('Cannot load queue %queue_id', ['%queue_id' => $id]); + } return $queue; } @@ -229,16 +234,11 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform 'handlerSettings' => $this->settings->getHandlerSettings($handler)->toArray(), ]); $queue = $this->loadQueue(); - if (NULL !== $queue) { - $queue->enqueueJob($job); - $context['@queue'] = $queue->id(); - $this->notice('Job for afsend added to the queue @queue.', $context + [ - 'operation' => 'Fordelingskomponent afsend queued', - ]); - } - else { - $this->processJob($job); - } + $queue->enqueueJob($job); + $context['@queue'] = $queue->id(); + $this->notice('Job for afsend added to the queue @queue.', $context + [ + 'operation' => 'Fordelingskomponent afsend queued', + ]); return $job; } @@ -282,7 +282,6 @@ public function processJob(Job $job): JobResult { $this->afsend($submission, $handlerSettings); $this->notice('Fordelingskomponent afsendt', $context); - return JobResult::success(); } catch (\Exception $e) { diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index 172f1d3..3ac7d55 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -2,7 +2,7 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; -use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; +use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlException; use Drupal\os2forms_fordelingskomponent\Settings\HandlerSettings; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -61,7 +61,7 @@ public function render(string $template, array $context, bool $validateXml = TRU ); } catch (\Throwable $exception) { - throw new InvalidXmlTemplateException($exception->getMessage(), $exception->getCode(), $exception); + throw new InvalidXmlException($exception->getMessage(), $exception->getCode(), $exception); } } @@ -84,14 +84,14 @@ public function validateTemplate(string $template): void { $this->createTemplate($template); } catch (\Throwable $exception) { - throw new InvalidXmlTemplateException($exception->getMessage(), $exception->getCode(), $exception); + throw new InvalidXmlException($exception->getMessage(), $exception->getCode(), $exception); } } /** * Check that XML is valid. Optionally validate using an XSD. * - * @throws \Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException + * @throws \Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlException * An exception. */ public function validateXml(string $xml, ?string $xsdUrl = NULL, bool $loadXsdContent = FALSE): void { @@ -107,7 +107,7 @@ public function validateXml(string $xml, ?string $xsdUrl = NULL, bool $loadXsdCo if ($loadXsdContent) { $content = file_get_contents($xsdUrl); if (FALSE === $content) { - throw new InvalidXmlTemplateException(sprintf('Error loading XSD: %s', $xsdUrl)); + throw new InvalidXmlException(sprintf('Error loading XSD: %s', $xsdUrl)); } } @@ -138,7 +138,7 @@ public function validateXml(string $xml, ?string $xsdUrl = NULL, bool $loadXsdCo libxml_clear_errors(); - throw new InvalidXmlTemplateException('Error validating XML:' . PHP_EOL . $message); + throw new InvalidXmlException('Error validating XML:' . PHP_EOL . $message); } } finally { @@ -163,11 +163,11 @@ private function checkXml(string $template): void { libxml_clear_errors(); - throw new InvalidXmlTemplateException('Error loading XML:' . PHP_EOL . $message); + throw new InvalidXmlException('Error loading XML:' . PHP_EOL . $message); } } catch (\Throwable $exception) { - throw new InvalidXmlTemplateException($exception->getMessage(), $exception->getCode(), $exception); + throw new InvalidXmlException($exception->getMessage(), $exception->getCode(), $exception); } finally { libxml_use_internal_errors($useInternalErrors); } diff --git a/src/Logger/Os2FormsFordelingskomponentLoggerChannel.php b/src/Logger/Os2FormsFordelingskomponentLoggerChannel.php new file mode 100644 index 0000000..dcc393f --- /dev/null +++ b/src/Logger/Os2FormsFordelingskomponentLoggerChannel.php @@ -0,0 +1,33 @@ +levelTranslation[$level] ?? RfcLogLevel::ERROR; + $logLevel = $this->getLogLevel(); + if ($logLevel >= $rfcLogLevel) { + parent::log($level, $message, $context); + } + } + + /** + * Get log level. + */ + private function getLogLevel(): int { + return (int) (Settings::get('os2forms_fordelingskomponent')['log_level'] ?? RfcLogLevel::ERROR); + } + +} diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 2506571..6a7a21b 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -6,7 +6,7 @@ use Drupal\Core\Link; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; +use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlException; use Drupal\os2forms_fordelingskomponent\Helper\WebformHelperSF2900; use Drupal\os2forms_fordelingskomponent\Helper\XmlHelper; use Drupal\os2forms_fordelingskomponent\Settings; @@ -357,7 +357,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $this->xmlHelper->validateXml($template); $this->xmlHelper->validateTemplate($template); } - catch (InvalidXmlTemplateException $e) { + catch (InvalidXmlException $e) { $form_state->setErrorByName(self::SECTION_SF2900 . '][' . DistributionObjectSettings::XML_TEMPLATE, $this->t('Invalid XML template: %message.', ['%message' => $e->getMessage()])); } diff --git a/tests/Unit/Helper/XmlHelperTest.php b/tests/Unit/Helper/XmlHelperTest.php index 7d5ffae..6ad71c3 100644 --- a/tests/Unit/Helper/XmlHelperTest.php +++ b/tests/Unit/Helper/XmlHelperTest.php @@ -2,7 +2,7 @@ namespace Drupal\os2forms_fordelingskomponent\Test\Unit\Helper; -use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; +use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlException; use Drupal\os2forms_fordelingskomponent\Helper\XmlHelper; use Drupal\os2forms_fordelingskomponent\Test\Unit\AbstractTestCase; use Twig\Environment; @@ -49,9 +49,9 @@ public function testStrictVariables(): void { public function testRender( string $template, array $context, - string|InvalidXmlTemplateException $expected, + string|InvalidXmlException $expected, ) { - if ($expected instanceof InvalidXmlTemplateException) { + if ($expected instanceof InvalidXmlException) { $this->expectException($expected::class); } @@ -65,9 +65,9 @@ public function testRender( */ public function testValidateTemplate( string $template, - ?InvalidXmlTemplateException $expected = NULL, + ?InvalidXmlException $expected = NULL, ) { - if ($expected instanceof InvalidXmlTemplateException) { + if ($expected instanceof InvalidXmlException) { $this->expectException($expected::class); } @@ -82,9 +82,9 @@ public function testValidateTemplate( public function testValidateXml( string $template, string $xsdUrl, - ?InvalidXmlTemplateException $expected = NULL, + ?InvalidXmlException $expected = NULL, ) { - if ($expected instanceof InvalidXmlTemplateException) { + if ($expected instanceof InvalidXmlException) { $this->expectException($expected::class); } diff --git a/tests/Unit/Helper/XmlHelperTestDataProvider.php b/tests/Unit/Helper/XmlHelperTestDataProvider.php index 6895166..e2acd6c 100644 --- a/tests/Unit/Helper/XmlHelperTestDataProvider.php +++ b/tests/Unit/Helper/XmlHelperTestDataProvider.php @@ -2,7 +2,7 @@ namespace Drupal\os2forms_fordelingskomponent\Test\Unit\Helper; -use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlTemplateException; +use Drupal\os2forms_fordelingskomponent\Exception\InvalidXmlException; /** * Xml helper test data provider. @@ -17,7 +17,7 @@ public static function provideRenderData(): iterable { yield [ 'This is not an XML document', [], - new InvalidXmlTemplateException(), + new InvalidXmlException(), ]; yield [ @@ -28,7 +28,7 @@ public static function provideRenderData(): iterable { XML, [], - new InvalidXmlTemplateException(), + new InvalidXmlException(), ]; yield [ @@ -190,7 +190,7 @@ public static function provideValidateTemplateData(): iterable { {{ name } XML, - new InvalidXmlTemplateException(), + new InvalidXmlException(), ]; yield [ @@ -201,7 +201,7 @@ public static function provideValidateTemplateData(): iterable { {{ name }} XML, - new InvalidXmlTemplateException(), + new InvalidXmlException(), ]; } @@ -259,7 +259,7 @@ public static function provideValidateXmlData(): iterable { XML, self::RESOURCE_PATH . '/xsd/Anmodning.xsd', - new InvalidXmlTemplateException(), + new InvalidXmlException(), ]; } From 4df218ddc075f110b3bc498f4bd4c837582efed3 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 27 Apr 2026 16:36:41 +0200 Subject: [PATCH 44/62] Refactored code and handled file upload before sending distribution object --- src/Helper/FordelingskomponentHelper.php | 90 +++++++++++---- src/Helper/WebformHelperSF2900.php | 104 ++++++++++++++---- .../JobType/FordelingskomponentSF2900.php | 7 +- 3 files changed, 157 insertions(+), 44 deletions(-) diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 9fccd25..6478d07 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -2,6 +2,7 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; +use ItkDev\Serviceplatformen\Service\SF2900\SF2900\SftpHelper; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\file\Entity\File; @@ -395,28 +396,16 @@ private function getSftpFilename(HandlerSettings $handlerSettings, WebformSubmis * * @phpstan-return array */ - public function sendDokument( - WebformSubmissionInterface $submission, - DistributionFormularType|DistributionDokumentType|DistributionJournalPostType $dokument, - ?Attachment $attachment, + public function uploadFiles( + DistributionFormularType|DistributionDokumentType|DistributionJournalPostType $distributionObject, HandlerSettings $handlerSettings, - ) { + ): array { $sf2900 = $this->sf2900(); $transactionId = Serializer::createUuid(); - $dokumentFilNavn = NULL; - if ($dokument instanceof DistributionDokumentType) { - if (NULL === $attachment) { - throw new InvalidAttachmentElementException(sprintf('Missing attachment for %s', $dokument::class)); - } - $sftp = $sf2900->sftp(); - $sftpFilename = $this->getSftpFilename($handlerSettings, $submission, $attachment->filename); - $dokumentFilNavn = $sftp->putContents($attachment->contents, $attachment->filename, $sftpFilename); - // @todo Create trigger object. - } - // Upload files if any. - elseif ($dokument instanceof DistributionFormular) { - $files = $dokument->getFileGroups(); + $triggerObjects = []; + if ($distributionObject instanceof DistributionFormular) { + $files = $distributionObject->getFileGroups(); $sftp = $sf2900->sftp(); $recipientItSystem = NULL; if ($handlerSettings->distributionObject->files->recipientItSystemLookUp) { @@ -432,12 +421,75 @@ public function sendDokument( /** @var \Drupal\file\Entity\File $file */ $file = $item['file']; $sftp->putFile($file->getFileUri(), $file->getFilename(), $item['sftp_filename']); - $triggerObject = $this->buildTriggerFile($file, $item['sftp_filename'], $handlerSettings, $transactionId, recipientItSystem: $recipientItSystem); + $triggerObject = $this->buildTriggerFile($file, $item['sftp_filename'], $handlerSettings, $transactionId, + recipientItSystem: $recipientItSystem); $sftp->putContents($triggerObject, $item['sftp_filename'], $item['sftp_filename'] . '.trigger'); + $triggerObjects[] = $triggerObject; } } } + return $triggerObjects; + } + + /** + * Check if files are delivered. + */ + public function checkFilesDelivered( + array $triggerObjects, + ): bool { + foreach ($triggerObjects as $triggerObject) { + try { + $sxe = new \SimpleXMLElement($triggerObject); + $filename = (string) $sxe->xpath('//FileDescriptor/FileName')[0]; + if (empty($filename)) { + throw new \RuntimeException('Cannot get file name'); + } + + $receipt = $this->sf2900()->sftp()->getContents($filename . '.sftpreceipt', SftpHelper::INCOMING_FOLDER); + $receiptXse = new \SimpleXMLElement($receipt); + $message = (string) $receiptXse->xpath('//Receipt/Message')[0]; + if ('SUCCESS' !== $message) { + throw new \RuntimeException(sprintf('Message for %s: %s', $filename, $message)); + } + } + catch (\Exception $exception) { + $this->logger->error($exception->getMessage()); + return FALSE; + } + } + + return TRUE; + } + + /** + * Send dokument. + * + * @return array + * [The response, The kombi post message]. + * + * @phpstan-return array + */ + public function sendDokument( + WebformSubmissionInterface $submission, + DistributionFormularType|DistributionDokumentType|DistributionJournalPostType $dokument, + ?Attachment $attachment, + HandlerSettings $handlerSettings, + ) { + $sf2900 = $this->sf2900(); + $transactionId = Serializer::createUuid(); + + $dokumentFilNavn = NULL; + if ($dokument instanceof DistributionDokumentType) { + if (NULL === $attachment) { + throw new InvalidAttachmentElementException(sprintf('Missing attachment for %s', $dokument::class)); + } + $sftp = $sf2900->sftp(); + $sftpFilename = $this->getSftpFilename($handlerSettings, $submission, $attachment->filename); + $dokumentFilNavn = $sftp->putContents($attachment->contents, $attachment->filename, $sftpFilename); + // @todo Create trigger object? + } + $this->setTransactionContext($transactionId, new TransactionContext( transactionId: $transactionId, handlerSettings: $handlerSettings, diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index b9e839d..4397f79 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -9,10 +9,12 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\Render\ElementInfoManager; +use Drupal\Core\State\StateInterface; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; use Drupal\os2forms_fordelingskomponent\Exception\SubmissionNotFoundException; use Drupal\os2forms_fordelingskomponent\Model\Attachment; +use Drupal\os2forms_fordelingskomponent\Model\DistributionFormular; use Drupal\os2forms_fordelingskomponent\Model\XmlRenderResult; use Drupal\os2forms_fordelingskomponent\Plugin\AdvancedQueue\JobType\FordelingskomponentSF2900; use Drupal\os2forms_fordelingskomponent\Plugin\WebformHandler\WebformHandlerSF2900; @@ -64,6 +66,7 @@ public function __construct( private readonly AnvenderForsendelseRepository $anvenderForsendelseRepository, #[Autowire(service: 'webform.token_manager')] private readonly WebformTokenManagerInterface $webformTokenManager, + private readonly StateInterface $state, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent')] private readonly LoggerChannelInterface $logger, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent_submission')] @@ -95,24 +98,6 @@ public function renderXml(HandlerSettings $handlerSettings, WebformSubmissionInt return $this->helper->renderXml($handlerSettings, $submission, files: $files, validateXml: $validateXml); } - /** - * Afsend med Fordelingskomponenten. - * - * @return array - * [The response, The kombi post message]. - */ - public function afsend(WebformSubmissionInterface $submission, HandlerSettings $handlerSettings): array { - $attachment = $this->getAttachment($submission, $handlerSettings); - $distributionObject = $this->buildDistributionObject($handlerSettings, $submission, $attachment); - - return $this->helper->sendDokument( - $submission, - $distributionObject, - $attachment, - $handlerSettings, - ); - } - /** * Get main document. * @@ -221,17 +206,21 @@ public function log($level, $message, array $context = []): void { * * @see self::processJob() */ - public function createJob(WebformSubmissionInterface $webformSubmission, WebformHandlerSF2900 $handler): ?Job { + public function createJob(WebformSubmissionInterface $webformSubmission, WebformHandlerSF2900|HandlerSettings $handlerSettings, ?array $payload = []): ?Job { $context = [ 'handler_id' => WebformHandlerSF2900::ID, 'webform_submission' => $webformSubmission, ]; try { - $job = Job::create(FordelingskomponentSF2900::class, [ + if ($handlerSettings instanceof WebformHandlerSF2900) { + $handlerSettings = $this->settings->getHandlerSettings($handlerSettings); + } + + $job = Job::create(FordelingskomponentSF2900::class, $payload + [ 'formId' => $webformSubmission->getWebform()->id(), 'submissionId' => $webformSubmission->id(), - 'handlerSettings' => $this->settings->getHandlerSettings($handler)->toArray(), + 'handlerSettings' => $handlerSettings->toArray(), ]); $queue = $this->loadQueue(); $queue->enqueueJob($job); @@ -258,7 +247,6 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform */ public function processJob(Job $job): JobResult { $payload = $job->getPayload(); - $context = [ 'handler_id' => WebformHandlerSF2900::ID, 'operation' => 'fordelingskomponent afsend', @@ -279,9 +267,50 @@ public function processJob(Job $job): JobResult { $context['webform_submission'] = $submission; $handlerSettings = new HandlerSettings($payload['handlerSettings']); - $this->afsend($submission, $handlerSettings); - $this->notice('Fordelingskomponent afsendt', $context); + $attachment = $this->getAttachment($submission, $handlerSettings); + $distributionObject = $this->buildDistributionObject($handlerSettings, $submission, $attachment); + + $sftpRoutingRequired = $distributionObject instanceof DistributionFormular + && !empty($distributionObject->getFileGroups()); + + if (!$sftpRoutingRequired) { + // No SFTP files uplead and awaiting delivery needed. + $this->helper->sendDokument($submission, $distributionObject, $attachment, $handlerSettings); + $this->notice('Fordelingskomponent afsendt', $context); + } + else { + // Start a sequence of jobs: + // + // 1. Upload files and trigger files. When done, create a job to + // 2. Check that all files have been delivered. Finally + // 3. Send distribution object. + $state = $this->getJobState($job); + switch ($state) { + case self::STATE_UPLOAD_FILES: + $files = $this->helper->uploadFiles($distributionObject, $handlerSettings); + $this->notice('Fordelingskomponent files uploaded', $context); + $this->createJob($submission, $handlerSettings, [self::PAYLOAD_CHECK_FILES => $files]); + break; + + case self::STATE_CHECK_FILES: + $files = $payload[self::PAYLOAD_CHECK_FILES]; + if (!$this->helper->checkFilesDelivered($files)) { + return JobResult::failure(sprintf('Files not yet delivered')); + } + else { + $this->notice('Fordelingskomponent files delivered', $context); + $this->createJob($submission, $handlerSettings, [self::PAYLOAD_FILES_DELIVERED => TRUE]); + } + break; + + case self::STATE_SEND_DISTRIBUTION_OBJECT: + $this->helper->sendDokument($submission, $distributionObject, $attachment, $handlerSettings); + $this->notice('Fordelingskomponent afsendt', $context); + break; + } + } + return JobResult::success(); } catch (\Exception $e) { @@ -321,4 +350,31 @@ private function replaceTokens(HandlerSettings $handlerSettings, WebformSubmissi return $handlerSettings; } + private const string STATE_UPLOAD_FILES = 'upload_files'; + + private const string PAYLOAD_CHECK_FILES = 'check_files'; + private const string STATE_CHECK_FILES = 'check_files'; + + private const string PAYLOAD_FILES_DELIVERED = 'files_delivered'; + private const string STATE_SEND_DISTRIBUTION_OBJECT = 'send_distribution_object'; + + /** + * Get state for a job. + * + * This is only used when we must upload files and hence the first state (and + * the default) is "upload files". + */ + private function getJobState(Job $job): string { + $payload = $job->getPayload(); + + if (isset($payload[self::PAYLOAD_FILES_DELIVERED])) { + return self::STATE_SEND_DISTRIBUTION_OBJECT; + } + if (isset($payload[self::PAYLOAD_CHECK_FILES])) { + return self::STATE_CHECK_FILES; + } + + return self::STATE_UPLOAD_FILES; + } + } diff --git a/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php b/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php index 6e872c7..bcd724b 100644 --- a/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php +++ b/src/Plugin/AdvancedQueue/JobType/FordelingskomponentSF2900.php @@ -12,10 +12,15 @@ /** * Fordelingskomponent. * + * In addition to sending distribution objects, this job is used to upload + * files and check that they've been delivired. We may have to wait quite + * some time before the files have been delivered and therefore we set + * "max_retries" higher the we usually do. + * * @AdvancedQueueJobType( * id = "Drupal\os2forms_fordelingskomponent\Plugin\AdvancedQueue\JobType\FordelingskomponentSF2900", * label = @Translation("Fordelingskomponent (sf2900)"), - * max_retries = 5, + * max_retries = 20, * retry_delay = 60, * ) */ From 792bef51fa990689ae3ea46b95a03e99f7936789 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 27 Apr 2026 16:43:24 +0200 Subject: [PATCH 45/62] Updated example --- .../config/install/webform.webform.os2forms_fdk_kp_sp241.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml index 82e05be..eeb56d4 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml @@ -416,7 +416,7 @@ handlers: filspecifikation: HelbredstillægAnsøgningBilag_1 recipient_it_system_look_up: 1 recipient_it_system: "" - recipient_authority: "14814833" + recipient_authority: "55133018" xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }}\r\n 2026-04-24\r\n {{ handler.settings.distribution_context.kle_emne }}\r\n {% for file in files.dokumenter_overslag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Overslag\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_faktura|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Faktura\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_bilag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Bilag\r\n \r\n {% endfor %}\r\n
\r\n \r\n \r\n {{ submission.data.ansoeger_fornavn }}\r\n {% if submission.data.ansoeger_mellemnavn|default(false) %}\r\n {{ submission.data.ansoeger_mellemnavn }}\r\n {% endif %}\r\n {{ submission.data.ansoeger_efternavn }}\r\n urn:oio:cpr:{{ submission.data.ansoeger_personnummer }}\r\n \r\n \r\n Accepteret\r\n \r\n Underskrift0\r\n 2026-04-26\r\n \r\n
\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/SP241.xsd" variants: {} From 6065953bc6f605ad4652410ecd01bd4b0c913636 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 28 Apr 2026 14:58:39 +0200 Subject: [PATCH 46/62] Misc --- README.md | 4 ++ ...bform.webform.os2forms_fdk_kp_anmoding.yml | 2 +- .../webform.webform.os2forms_fdk_kp_sp241.yml | 45 ++++++++++++++++++- os2forms_fordelingskomponent.install | 2 + .../WebformHandler/WebformHandlerSF2900.php | 3 ++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 63089fc..d0297d8 100644 --- a/README.md +++ b/README.md @@ -135,3 +135,7 @@ XML # settings.local.php $settings['os2forms_fordelingskomponent']['log_level'] = \Drupal\Core\Logger\RfcLogLevel::DEBUG; ``` + +``` shell +drush sql:query --extra='--table' "SELECT (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_forsendelse) AS os2forms_fordelingskomponent_anvender_forsendelse, (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_kvittering) AS os2forms_fordelingskomponent_anvender_kvittering;" +``` diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml index 9ca9755..9d413f2 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml @@ -411,7 +411,7 @@ handlers: Medicin - Underskrift0 + {{ submission.data.ansoeger_fornavn }}{% if submission.data.ansoeger_mellemnavn|default(false) %} {{ submission.data.ansoeger_mellemnavn }}{% endif %} {{ submission.data.ansoeger_efternavn }} {{ submission.completed.value|date("Y-m-d") }} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml index eeb56d4..d4b4ea1 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml @@ -406,7 +406,7 @@ handlers: handling_facet: G01 brugervendt_noegle: "@todo Skal skjules for “formular“" titel: "@todo Titel SP241" - beskrivelse: "@todo Beskrivelse\r\n\r\nSe https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf#page=14 for at finde formulartype mm.\r\n\r\n”KP – Ekstern Test” er et Netcompany system og har CVR-nr 14814833." + beskrivelse: "@todo Beskrivelse\r\n\r\nSe https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf#page=14 for at finde formulartype mm." distribution_object: distribution_type: FORMULAR journalpost_message: "" @@ -417,6 +417,47 @@ handlers: recipient_it_system_look_up: 1 recipient_it_system: "" recipient_authority: "55133018" - xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }}\r\n 2026-04-24\r\n {{ handler.settings.distribution_context.kle_emne }}\r\n {% for file in files.dokumenter_overslag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Overslag\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_faktura|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Faktura\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_bilag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Bilag\r\n \r\n {% endfor %}\r\n
\r\n \r\n \r\n {{ submission.data.ansoeger_fornavn }}\r\n {% if submission.data.ansoeger_mellemnavn|default(false) %}\r\n {{ submission.data.ansoeger_mellemnavn }}\r\n {% endif %}\r\n {{ submission.data.ansoeger_efternavn }}\r\n urn:oio:cpr:{{ submission.data.ansoeger_personnummer }}\r\n \r\n \r\n Accepteret\r\n \r\n Underskrift0\r\n 2026-04-26\r\n \r\n
\r\n" + xml_template: | + + +
+ urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }} + {{ submission.completed.value|date("Y-m-d") }} + {{ handler.settings.distribution_context.kle_emne }} + {% for file in files.dokumenter_overslag|default([]) %} + + {{ file.sftp_filename }} + Overslag + + {% endfor %} + {% for file in files.dokumenter_faktura|default([]) %} + + {{ file.sftp_filename }} + Faktura + + {% endfor %} + {% for file in files.dokumenter_bilag|default([]) %} + + {{ file.sftp_filename }} + Bilag + + {% endfor %} +
+ + + {{ submission.data.ansoeger_fornavn }} + {% if submission.data.ansoeger_mellemnavn|default(false) %} + {{ submission.data.ansoeger_mellemnavn }} + {% endif %} + {{ submission.data.ansoeger_efternavn }} + urn:oio:cpr:{{ submission.data.ansoeger_personnummer_cpr_ }} + + + Accepteret + + {{ submission.data.ansoeger_fornavn }}{% if submission.data.ansoeger_mellemnavn|default(false) %} {{ submission.data.ansoeger_mellemnavn }}{% endif %} {{ submission.data.ansoeger_efternavn }} + {{ submission.completed.value|date("Y-m-d") }} + +
xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/SP241.xsd" variants: {} diff --git a/os2forms_fordelingskomponent.install b/os2forms_fordelingskomponent.install index 583f311..50f01f9 100644 --- a/os2forms_fordelingskomponent.install +++ b/os2forms_fordelingskomponent.install @@ -9,6 +9,8 @@ use Drupal\os2forms_fordelingskomponent\Hook\InstallHooks; /** * Implements hook_schema(). + * + * @see InstallHooks::schema() */ function os2forms_fordelingskomponent_schema(): array { return \Drupal::service(InstallHooks::class)->schema(); diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index 6a7a21b..d2d7dd2 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -239,6 +239,9 @@ private function buildConfigurationFormDistributionObject(): array { '#type' => 'fieldset', '#title' => $this->t('Files'), ]; + $setStates($section[DistributionObjectSettings::FILES], [ + DistributionObjectSettings::DISTRIBUTION_TYPE_FORMULAR, + ]); $section[DistributionObjectSettings::FILES][DistributionObjectFilesSettings::FILSPECIFIKATION] = [ '#type' => 'textfield', From a4cbf64f3da80e5fba6eb7223fa8a411df6f01f9 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 28 Apr 2026 15:59:35 +0200 Subject: [PATCH 47/62] Allow prepopulating example webform --- .../README.md | 5 ++ .../webform.webform.os2forms_fdk_kp_sp241.yml | 66 +++++-------------- 2 files changed, 21 insertions(+), 50 deletions(-) diff --git a/modules/os2forms_fordelingskomponent_examples/README.md b/modules/os2forms_fordelingskomponent_examples/README.md index 4728314..5f29176 100644 --- a/modules/os2forms_fordelingskomponent_examples/README.md +++ b/modules/os2forms_fordelingskomponent_examples/README.md @@ -42,3 +42,8 @@ Or a single webform, e.g. drush config:set --input-format=yaml webform.webform.os2forms_fdk_kp_anmoding '?' - < config/install/webform.webform.os2forms_fdk_kp_anmoding.yml # drush config:get webform.webform.os2forms_fdk_kp_anmoding ``` + +## Testing + +Fill out an example webform with test data and pre-populate some fields: +`/webform/os2forms_fdk_kp_sp241/test?ansoeger_personnummer=1234567890&ansoeger_telefonnummer=12345678&fuldmagt_fuldmagthaverspersonnummer=0987654321` diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml index d4b4ea1..2392d95 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml @@ -4,6 +4,7 @@ dependencies: module: - os2forms - os2forms_fordelingskomponent + - os2forms_permissions_by_term - webform_encrypt - webform_entity_print - webform_revisions @@ -73,9 +74,6 @@ third_party_settings: erklaering: encrypt: true encrypt_profile: webform - underskriftsoplysninger_underskrift: - encrypt: true - encrypt_profile: webform underskriftsoplysninger_underskriftsdato: encrypt: true encrypt_profile: webform @@ -108,35 +106,41 @@ template: false archive: false id: os2forms_fdk_kp_sp241 title: "SP241 (eksempel): Ansøgning om helbredstillæg" -description: "" +description: "

/webform/os2forms_fdk_kp_sp241/test?ansoeger_personnummer=1234567890&ansoeger_telefonnummer=12345678

" categories: + - Eksempel - KP - SP - - Eksempel elements: |- ansoeger_fornavn: '#type': textfield '#title': Fornavn '#required': true + '#prepopulate': true ansoeger_mellemnavn: '#type': textfield '#title': Mellemnavn + '#prepopulate': true ansoeger_efternavn: '#type': textfield '#title': Efternavn '#required': true + '#prepopulate': true ansoeger_personnummer: '#type': textfield '#title': Personnummer '#required': true '#pattern': '\d{10}' + '#prepopulate': true ansoeger_telefonnummer: '#type': textfield '#title': Telefonnummer '#pattern': \d+ + '#prepopulate': true sygeforsikring: '#type': checkbox '#title': Sygeforsikring + '#prepopulate': true sygeforsikring_gruppe: '#type': select '#title': Gruppe @@ -148,6 +152,7 @@ elements: |- GRUPPE_N: 'Gruppe N' GRUPPE_S: 'Gruppe S' GRUPPE_BASIS: Basis + '#prepopulate': true '#states': _visible: ':input[name="sygeforsikring"]': @@ -159,19 +164,20 @@ elements: |- '#type': checkbox '#title': Erklæring '#required': true - underskriftsoplysninger_underskrift: - '#type': textfield - '#title': Underskrift + '#prepopulate': true underskriftsoplysninger_underskriftsdato: '#type': date '#title': Underskriftsdato + '#disabled': true '#default_value': today fuldmagt: '#type': checkbox '#title': Fuldmagt + '#prepopulate': true fuldmagt_fuldmagtdokumentnavn: '#type': textfield '#title': Navn + '#prepopulate': true '#states': _visible: ':input[name="fuldmagt"]': @@ -183,6 +189,7 @@ elements: |- '#type': textfield '#title': Personnummer '#pattern': '\d{10}' + '#prepopulate': true '#states': _visible: ':input[name="fuldmagt"]': @@ -417,47 +424,6 @@ handlers: recipient_it_system_look_up: 1 recipient_it_system: "" recipient_authority: "55133018" - xml_template: | - - -
- urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }} - {{ submission.completed.value|date("Y-m-d") }} - {{ handler.settings.distribution_context.kle_emne }} - {% for file in files.dokumenter_overslag|default([]) %} - - {{ file.sftp_filename }} - Overslag - - {% endfor %} - {% for file in files.dokumenter_faktura|default([]) %} - - {{ file.sftp_filename }} - Faktura - - {% endfor %} - {% for file in files.dokumenter_bilag|default([]) %} - - {{ file.sftp_filename }} - Bilag - - {% endfor %} -
- - - {{ submission.data.ansoeger_fornavn }} - {% if submission.data.ansoeger_mellemnavn|default(false) %} - {{ submission.data.ansoeger_mellemnavn }} - {% endif %} - {{ submission.data.ansoeger_efternavn }} - urn:oio:cpr:{{ submission.data.ansoeger_personnummer_cpr_ }} - - - Accepteret - - {{ submission.data.ansoeger_fornavn }}{% if submission.data.ansoeger_mellemnavn|default(false) %} {{ submission.data.ansoeger_mellemnavn }}{% endif %} {{ submission.data.ansoeger_efternavn }} - {{ submission.completed.value|date("Y-m-d") }} - -
+ xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distribution_context.kle_emne }}\r\n {% for file in files.dokumenter_overslag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Overslag\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_faktura|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Faktura\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_bilag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Bilag\r\n \r\n {% endfor %}\r\n
\r\n \r\n \r\n {{ submission.data.ansoeger_fornavn }}\r\n {% if submission.data.ansoeger_mellemnavn|default(false) %}\r\n {{ submission.data.ansoeger_mellemnavn }}\r\n {% endif %}\r\n {{ submission.data.ansoeger_efternavn }}\r\n urn:oio:cpr:{{ submission.data.ansoeger_personnummer }}\r\n \r\n \r\n Accepteret\r\n \r\n {{ submission.data.ansoeger_fornavn }}{% if submission.data.ansoeger_mellemnavn|default(false) %} {{ submission.data.ansoeger_mellemnavn }}{% endif %} {{ submission.data.ansoeger_efternavn }}\r\n {{ submission.data.underskriftsoplysninger_underskriftsdato|date(\"Y-m-d\") }}\r\n \r\n
\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/SP241.xsd" variants: {} From a0917d02aa9e4aacc7dbf358a41f1f63cbacd727 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 28 Apr 2026 16:03:21 +0200 Subject: [PATCH 48/62] Added debug module --- ...s2forms_fordelingskomponent_debug.info.yml | 8 +++ ...s_fordelingskomponent_debug.links.task.yml | 5 ++ .../os2forms_fordelingskomponent_debug.module | 15 ++++ ...orms_fordelingskomponent_debug.routing.yml | 45 ++++++++++++ ...rms_fordelingskomponent_debug.services.yml | 5 ++ ...ngskomponentDebugForsendelseController.php | 37 ++++++++++ ...ingskomponentDebugKvitteringController.php | 35 ++++++++++ ...FordelingskomponentDebugSftpController.php | 69 +++++++++++++++++++ .../src/Hook/ThemeHooks.php | 43 ++++++++++++ ...ingskomponent-debug-forsendelser.html.twig | 27 ++++++++ ...ingskomponent-debug-kvitteringer.html.twig | 6 ++ ...elingskomponent-debug-sftp-files.html.twig | 11 +++ 12 files changed, 306 insertions(+) create mode 100644 modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.info.yml create mode 100644 modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml create mode 100644 modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module create mode 100644 modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml create mode 100644 modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.services.yml create mode 100644 modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php create mode 100644 modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php create mode 100644 modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php create mode 100644 modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php create mode 100644 modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig create mode 100644 modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig create mode 100644 modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.info.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.info.yml new file mode 100644 index 0000000..ed88670 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.info.yml @@ -0,0 +1,8 @@ +name: "os2forms_fordelingskomponent_debug" +type: module +description: "os2forms_fordelingskomponent_debug" +package: OS2Forms +core_version_requirement: ^10 || ^11 + +dependencies: + - os2forms_fordelingskomponent:os2forms_fordelingskomponent diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml new file mode 100644 index 0000000..034eede --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml @@ -0,0 +1,5 @@ +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse: + title: "Fordelingskomponentforsendelser" + route_name: os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse + parent_id: entity.webform_submission.canonical + weight: 999 diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module new file mode 100644 index 0000000..1616b60 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module @@ -0,0 +1,15 @@ +theme($existing, $type, $theme, $path); +} diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml new file mode 100644 index 0000000..7a55224 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml @@ -0,0 +1,45 @@ +# Drupal does not seem to support using / in route parameters (cf. https://drupal.stackexchange.com/questions/175758/slashes-in-single-route-parameter-or-other-ways-to-handle-a-menu-tail-with-dynam) +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp: + path: "/admin/os2forms-fordelingskomponent-debug/os2forms-fordelingskomponent-debug-sftp/{dir}" + defaults: &defaults + _title: "Fordelingskomponent SFTP" + _controller: '\Drupal\os2forms_fordelingskomponent_debug\Controller\Os2formsFordelingskomponentDebugSftpController' + dir: / + methods: &methods + - GET + requirements: &requirements + _permission: "administer site configuration" + +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp_filename: + path: "/admin/os2forms-fordelingskomponent-debug/os2forms-fordelingskomponent-debug-sftp/{dir}/{filename}" + defaults: *defaults + methods: *methods + requirements: *requirements + +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse: + path: "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/os2forms-fordelingskomponent-debug-forsendelse" + defaults: + _title: "Fordelingskomponentforsendelser" + _controller: '\Drupal\os2forms_fordelingskomponent_debug\Controller\Os2formsFordelingskomponentDebugForsendelseController' + methods: *methods + requirements: *requirements + options: + parameters: + webform: + type: "entity:webform" + webform_submission: + type: "entity:webform_submission" + +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_kvittering: + path: "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/os2forms-fordelingskomponent-debug-forsendelse/kvittering/{anvender_transaktions_id}" + defaults: + _title: "Fordelingskomponentkvitteringer" + _controller: '\Drupal\os2forms_fordelingskomponent_debug\Controller\Os2formsFordelingskomponentDebugKvitteringController' + methods: *methods + requirements: *requirements + options: + parameters: + webform: + type: "entity:webform" + webform_submission: + type: "entity:webform_submission" diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.services.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.services.yml new file mode 100644 index 0000000..420ed1b --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.services.yml @@ -0,0 +1,5 @@ +services: + _defaults: + autowire: true + + Drupal\os2forms_fordelingskomponent_debug\Hook\ThemeHooks: diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php new file mode 100644 index 0000000..f35571e --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php @@ -0,0 +1,37 @@ +repository->loadBySubmission($webform_submission); + + return [ + '#theme' => ThemeHooks::FORSENDELSER, + '#items' => $items, + '#webform' => $webform, + '#webform_submission' => $webform_submission, + ]; + } + +} diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php new file mode 100644 index 0000000..6967bf7 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php @@ -0,0 +1,35 @@ +repository->loadByAnvenderTransaktionsId($anvender_transaktions_id); + + return [ + '#theme' => ThemeHooks::KVITTERINGER, + '#items' => $items, + ]; + } + +} diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php new file mode 100644 index 0000000..c84db36 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php @@ -0,0 +1,69 @@ +normalizePath(implode('/', array_filter([$dir, $filename]))); + + $sftp = $this->helper->sf2900()->sftp(); + + if (preg_match('/\.[^.]+$/', $path)) { + $content = $sftp->getContents($filename, $dir); + + $contentType = match(pathinfo($path, PATHINFO_EXTENSION)) { + 'sftpreceipt', 'trigger' => $this->mimeTypeGuesser->guessMimeType('name.xml'), + default => $this->mimeTypeGuesser->guessMimeType($path) + }; + + return new Response($content, Response::HTTP_OK, [ + 'Content-Type' => $contentType, + ]); + } + else { + $files = $sftp->getFiles($path); + + // Filter out . and .. + $files = array_filter($files, static fn (string $name) => !preg_match('/^\.+$/', $name)); + + $files = array_map(fn (string $name) => $this->normalizePath($path . '/' . $name), $files); + + return [ + '#theme' => ThemeHooks::SFTP_FILES, + '#files' => $files, + '#parent_dir' => dirname($path), + ]; + } + } + + /** + * Normalize file path. + */ + private function normalizePath(string $path): string { + return '/' . trim(preg_replace('@/+@', '/', $path), '/'); + } + +} diff --git a/modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php b/modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php new file mode 100644 index 0000000..07e9f6c --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php @@ -0,0 +1,43 @@ + [ + 'variables' => [ + 'files' => NULL, + 'parent_dir' => NULL, + ], + ], + + self::FORSENDELSER => [ + 'variables' => [ + 'items' => NULL, + 'webform' => NULL, + 'webform_submission' => NULL, + ], + ], + + self::KVITTERINGER => [ + 'variables' => [ + 'items' => NULL, + ], + ], + ]; + } + } + +} diff --git a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig new file mode 100644 index 0000000..53367ac --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig @@ -0,0 +1,27 @@ +{# @var Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse[] items #} + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + + {% endfor %} + +
anvenderTransaktionsIddistributionTransaktionsIdkvitteringwebform handlerscreatedAtupdatedAtdeliveredAt
{{ item.anvenderTransaktionsId }}{{ item.distributionTransaktionsId }}kvitteringwebform handlers{{ item.createdAt|date('Y-m-d H:i:s') }}{{ item.updatedAt|date('Y-m-d H:i:s') }}{{ item.deliveredAt ? item.deliveredAt|date('Y-m-d H:i:s') : '–' }}
diff --git a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig new file mode 100644 index 0000000..ee094c4 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig @@ -0,0 +1,6 @@ +{# @var Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse[] items #} +
    +{% for item in items %} +
  1. {{ item.anvenderTransaktionsId }} {{ dump(item) }}
  2. +{% endfor %} +
diff --git a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig new file mode 100644 index 0000000..ac1743e --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig @@ -0,0 +1,11 @@ +{% if parent_dir %} + {{ parent_dir }} +{% endif %} + +
    +{% for file in files %} + {# We just append the file path to the URL path. This is a hack! #} + {% set url = path('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp') ~ file %} +
  1. {{ file }}
  2. +{% endfor %} +
From 1d54a80bd2fbb014b56b5c98be2796f3e625da56 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 28 Apr 2026 23:54:20 +0200 Subject: [PATCH 49/62] Cleaned up debug module --- .../os2forms_fordelingskomponent_debug.module | 15 --- ...orms_fordelingskomponent_debug.routing.yml | 2 +- .../src/Controller/AbstractController.php | 29 ++++ ...ngskomponentDebugForsendelseController.php | 124 ++++++++++++++++-- ...ingskomponentDebugKvitteringController.php | 38 +++++- ...FordelingskomponentDebugSftpController.php | 60 +++++---- .../src/Hook/ThemeHooks.php | 43 ------ ...ingskomponent-debug-forsendelser.html.twig | 27 ---- ...ingskomponent-debug-kvitteringer.html.twig | 6 - ...elingskomponent-debug-sftp-files.html.twig | 11 -- 10 files changed, 216 insertions(+), 139 deletions(-) delete mode 100644 modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module create mode 100644 modules/os2forms_fordelingskomponent_debug/src/Controller/AbstractController.php delete mode 100644 modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php delete mode 100644 modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig delete mode 100644 modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig delete mode 100644 modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module deleted file mode 100644 index 1616b60..0000000 --- a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.module +++ /dev/null @@ -1,15 +0,0 @@ -theme($existing, $type, $theme, $path); -} diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml index 7a55224..9b3296e 100644 --- a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml @@ -4,7 +4,7 @@ os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp: defaults: &defaults _title: "Fordelingskomponent SFTP" _controller: '\Drupal\os2forms_fordelingskomponent_debug\Controller\Os2formsFordelingskomponentDebugSftpController' - dir: / + dir: ~ methods: &methods - GET requirements: &requirements diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/AbstractController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/AbstractController.php new file mode 100644 index 0000000..a4ba3f6 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/AbstractController.php @@ -0,0 +1,29 @@ +format(\DateTimeImmutable::ATOM) : NULL; + } + + /** + * Render YAML. + */ + protected function renderYaml(?\JsonSerializable $value): string { + return '
' . Yaml::dump(json_decode(json_encode($value), TRUE),
+        inline: PHP_INT_MAX) . '
'; + } + +} diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php index f35571e..f23ba8e 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php @@ -4,16 +4,18 @@ namespace Drupal\os2forms_fordelingskomponent_debug\Controller; -use Drupal\Core\Controller\ControllerBase; +use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse; +use Drupal\Core\Url; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository; -use Drupal\os2forms_fordelingskomponent_debug\Hook\ThemeHooks; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Returns responses for os2forms_fordelingskomponent_debug routes. */ -final class Os2formsFordelingskomponentDebugForsendelseController extends ControllerBase { +final class Os2formsFordelingskomponentDebugForsendelseController extends AbstractController { public function __construct( private readonly AnvenderForsendelseRepository $repository, @@ -23,14 +25,120 @@ public function __construct( /** * Builds the response. */ - public function __invoke(WebformInterface $webform, WebformSubmissionInterface $webform_submission): array { + public function __invoke(Request $request, WebformInterface $webform, WebformSubmissionInterface $webform_submission): array { + if ($anvenderTransaktionsId = $request->query->get('anvenderTransaktionsId')) { + if ($item = $this->repository->loadByAnvenderTransaktionsId($anvenderTransaktionsId)) { + return $this->itemDetails($item); + } + + throw new NotFoundHttpException(); + } $items = $this->repository->loadBySubmission($webform_submission); + // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Table.php/class/Table/10 + $header = [ + 'anvenderTransaktionsId' => $this->t('anvenderTransaktionsId'), + 'distributionTransaktionsId' => $this->t('distributionTransaktionsId'), + 'receipts' => $this->t('Receipts'), + 'webform handlers' => $this->t('Webform handlers'), + 'createdAt' => $this->t('Created at'), + 'updatedAt' => $this->t('Updated at'), + 'deliveredAt' => $this->t('Delivered at'), + ]; + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'anvenderTransaktionsId' => [ + 'data' => [ + // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Link.php/class/Link/10 + '#title' => $item->anvenderTransaktionsId, + '#type' => 'link', + '#url' => Url::fromRoute('', [ + 'anvenderTransaktionsId' => $item->anvenderTransaktionsId, + ]), + ], + ], + 'distributionTransaktionsId' => [ + 'data' => [ + '#markup' => $item->distributionTransaktionsId, + ], + ], + 'receipts' => [ + 'data' => [ + '#title' => $this->t('Receipts'), + '#type' => 'link', + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_kvittering', [ + 'webform' => $item->webformId, + 'webform_submission' => $item->webformSubmissionId, + 'anvender_transaktions_id' => $item->anvenderTransaktionsId, + ]), + ], + ], + 'webform handlers' => [ + 'data' => [ + '#title' => $this->t('Webform handlers'), + '#type' => 'link', + '#url' => Url::fromRoute('entity.webform.handlers', [ + 'webform' => $item->webformId, + ]), + ], + ], + 'createdAt' => [ + 'data' => [ + '#markup' => $this->formatDatetime($item->createdAt), + ], + ], + 'updatedAt' => [ + 'data' => [ + '#markup' => $this->formatDatetime($item->updatedAt), + ], + ], + 'deliveredAt' => [ + 'data' => [ + '#markup' => $this->formatDatetime($item->deliveredAt), + ], + ], + ]; + } + + return [ + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => $this->t('No entries available.'), + ]; + } + + /** + * Build item details. + */ + private function itemDetails(AnvenderForsendelse $item) { return [ - '#theme' => ThemeHooks::FORSENDELSER, - '#items' => $items, - '#webform' => $webform, - '#webform_submission' => $webform_submission, + [ + '#type' => 'item', + '#title' => $this->t('anvenderTransaktionsId'), + '#markup' => $item->anvenderTransaktionsId, + ], + [ + '#type' => 'item', + '#title' => $this->t('distributionTransaktionsId'), + '#markup' => $item->distributionTransaktionsId, + ], + [ + '#type' => 'item', + '#title' => $this->t('Created at'), + '#markup' => $this->formatDatetime($item->createdAt), + ], + [ + '#type' => 'item', + '#title' => $this->t('Updated at'), + '#markup' => $this->formatDatetime($item->updatedAt), + ], + [ + '#type' => 'item', + '#title' => $this->t('Request'), + '#markup' => $this->renderYaml($item->request), + ], ]; } diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php index 6967bf7..984b890 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php @@ -4,16 +4,15 @@ namespace Drupal\os2forms_fordelingskomponent_debug\Controller; -use Drupal\Core\Controller\ControllerBase; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository; -use Drupal\os2forms_fordelingskomponent_debug\Hook\ThemeHooks; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Returns responses for os2forms_fordelingskomponent_debug routes. */ -final class Os2formsFordelingskomponentDebugKvitteringController extends ControllerBase { +final class Os2formsFordelingskomponentDebugKvitteringController extends AbstractController { public function __construct( private readonly AnvenderKvitteringRepository $repository, @@ -24,11 +23,38 @@ public function __construct( * Builds the response. */ public function __invoke(WebformInterface $webform, WebformSubmissionInterface $webform_submission, string $anvender_transaktions_id): array { - $items = $this->repository->loadByAnvenderTransaktionsId($anvender_transaktions_id); + $item = $this->repository->loadByAnvenderTransaktionsId($anvender_transaktions_id); + + if (NULL === $item) { + throw new NotFoundHttpException(); + } return [ - '#theme' => ThemeHooks::KVITTERINGER, - '#items' => $items, + [ + '#type' => 'item', + '#title' => $this->t('anvenderTransaktionsId'), + '#markup' => $item->anvenderTransaktionsId, + ], + [ + '#type' => 'item', + '#title' => $this->t('distributionTransaktionsId'), + '#markup' => $item->distributionTransaktionsId, + ], + [ + '#type' => 'item', + '#title' => $this->t('Created at'), + '#markup' => $this->formatDatetime($item->createdAt), + ], + [ + '#type' => 'item', + '#title' => $this->t('Updated at'), + '#markup' => $this->formatDatetime($item->updatedAt), + ], + [ + '#type' => 'item', + '#title' => $this->t('Request'), + '#markup' => $this->renderYaml($item->request), + ], ]; } diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php index c84db36..08a2cbc 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php @@ -4,9 +4,8 @@ namespace Drupal\os2forms_fordelingskomponent_debug\Controller; -use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Url; use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; -use Drupal\os2forms_fordelingskomponent_debug\Hook\ThemeHooks; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Mime\MimeTypeGuesserInterface; @@ -14,7 +13,7 @@ /** * Returns responses for os2forms_fordelingskomponent_debug routes. */ -final class Os2formsFordelingskomponentDebugSftpController extends ControllerBase { +final class Os2formsFordelingskomponentDebugSftpController extends AbstractController { public function __construct( private readonly FordelingskomponentHelper $helper, @@ -26,17 +25,15 @@ public function __construct( /** * Builds the response. */ - public function __invoke(string $dir = '/', ?string $filename = NULL): array|Response { - $path = $this->normalizePath(implode('/', array_filter([$dir, $filename]))); - + public function __invoke(?string $dir, ?string $filename = NULL): array|Response { $sftp = $this->helper->sf2900()->sftp(); - if (preg_match('/\.[^.]+$/', $path)) { + if (NULL !== $filename && preg_match('/\.[^.]+$/', $filename)) { $content = $sftp->getContents($filename, $dir); - $contentType = match(pathinfo($path, PATHINFO_EXTENSION)) { + $contentType = match(pathinfo($filename, PATHINFO_EXTENSION)) { 'sftpreceipt', 'trigger' => $this->mimeTypeGuesser->guessMimeType('name.xml'), - default => $this->mimeTypeGuesser->guessMimeType($path) + default => $this->mimeTypeGuesser->guessMimeType($filename) }; return new Response($content, Response::HTTP_OK, [ @@ -44,26 +41,45 @@ public function __invoke(string $dir = '/', ?string $filename = NULL): array|Res ]); } else { - $files = $sftp->getFiles($path); + $files = $sftp->getFiles($dir ?? '.', recursive: TRUE, raw: TRUE); // Filter out . and .. - $files = array_filter($files, static fn (string $name) => !preg_match('/^\.+$/', $name)); + $files = array_filter($files, static fn (string $name) => !preg_match('/^\.+$/', $name), ARRAY_FILTER_USE_KEY); - $files = array_map(fn (string $name) => $this->normalizePath($path . '/' . $name), $files); + $header = [ + 'filepath' => $this->t('Path'), + 'atime' => $this->t('Last accessed at'), + 'mtime' => $this->t('Last modified at'), + ]; + $rows = []; + foreach ($files as $stat) { + $rows[] = [ + 'filepath' => [ + 'data' => [ + '#title' => '/' . ($dir ? $dir . '/' : '') . $stat->filename, + '#type' => 'link', + '#url' => $dir + ? Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp_filename', [ + 'dir' => $dir, + 'filename' => $stat->filename, + ]) + : Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp', [ + 'dir' => $stat->filename, + ]), + ], + ], + 'atime' => $this->formatDatetime($stat->atime ?? NULL), + 'mtime' => $this->formatDatetime($stat->mtime ?? NULL), + ]; + } return [ - '#theme' => ThemeHooks::SFTP_FILES, - '#files' => $files, - '#parent_dir' => dirname($path), + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => $this->t('No entries available.'), ]; } } - /** - * Normalize file path. - */ - private function normalizePath(string $path): string { - return '/' . trim(preg_replace('@/+@', '/', $path), '/'); - } - } diff --git a/modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php b/modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php deleted file mode 100644 index 07e9f6c..0000000 --- a/modules/os2forms_fordelingskomponent_debug/src/Hook/ThemeHooks.php +++ /dev/null @@ -1,43 +0,0 @@ - [ - 'variables' => [ - 'files' => NULL, - 'parent_dir' => NULL, - ], - ], - - self::FORSENDELSER => [ - 'variables' => [ - 'items' => NULL, - 'webform' => NULL, - 'webform_submission' => NULL, - ], - ], - - self::KVITTERINGER => [ - 'variables' => [ - 'items' => NULL, - ], - ], - ]; - } - } - -} diff --git a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig deleted file mode 100644 index 53367ac..0000000 --- a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-forsendelser.html.twig +++ /dev/null @@ -1,27 +0,0 @@ -{# @var Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse[] items #} - - - - - - - - - - - - - - {% for item in items %} - - - - - - - - - - {% endfor %} - -
anvenderTransaktionsIddistributionTransaktionsIdkvitteringwebform handlerscreatedAtupdatedAtdeliveredAt
{{ item.anvenderTransaktionsId }}{{ item.distributionTransaktionsId }}kvitteringwebform handlers{{ item.createdAt|date('Y-m-d H:i:s') }}{{ item.updatedAt|date('Y-m-d H:i:s') }}{{ item.deliveredAt ? item.deliveredAt|date('Y-m-d H:i:s') : '–' }}
diff --git a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig deleted file mode 100644 index ee094c4..0000000 --- a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-kvitteringer.html.twig +++ /dev/null @@ -1,6 +0,0 @@ -{# @var Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse[] items #} -
    -{% for item in items %} -
  1. {{ item.anvenderTransaktionsId }} {{ dump(item) }}
  2. -{% endfor %} -
diff --git a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig b/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig deleted file mode 100644 index ac1743e..0000000 --- a/modules/os2forms_fordelingskomponent_debug/templates/os2forms-fordelingskomponent-debug-sftp-files.html.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% if parent_dir %} - {{ parent_dir }} -{% endif %} - -
    -{% for file in files %} - {# We just append the file path to the URL path. This is a hack! #} - {% set url = path('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp') ~ file %} -
  1. {{ file }}
  2. -{% endfor %} -
From 899d60e981ad716c9cef3f3fcde00070d6351d6f Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 30 Apr 2026 08:22:11 +0200 Subject: [PATCH 50/62] Misc --- ...ngskomponentDebugForsendelseController.php | 5 +++ ...FordelingskomponentDebugSftpController.php | 7 +++- .../FordelingskvitteringModtagController.php | 28 ++++++++-------- .../AnvenderForsendelseRepository.php | 6 +++- .../AnvenderKvitteringRepository.php | 33 +++++-------------- 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php index f23ba8e..f87c9e8 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php @@ -139,6 +139,11 @@ private function itemDetails(AnvenderForsendelse $item) { '#title' => $this->t('Request'), '#markup' => $this->renderYaml($item->request), ], + [ + '#type' => 'item', + '#title' => $this->t('Response'), + '#markup' => $this->renderYaml($item->response), + ], ]; } diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php index 08a2cbc..83cb46e 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugSftpController.php @@ -7,6 +7,7 @@ use Drupal\Core\Url; use Drupal\os2forms_fordelingskomponent\Helper\FordelingskomponentHelper; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Mime\MimeTypeGuesserInterface; @@ -25,7 +26,7 @@ public function __construct( /** * Builds the response. */ - public function __invoke(?string $dir, ?string $filename = NULL): array|Response { + public function __invoke(Request $request, ?string $dir, ?string $filename = NULL): array|Response { $sftp = $this->helper->sf2900()->sftp(); if (NULL !== $filename && preg_match('/\.[^.]+$/', $filename)) { @@ -46,6 +47,10 @@ public function __invoke(?string $dir, ?string $filename = NULL): array|Response // Filter out . and .. $files = array_filter($files, static fn (string $name) => !preg_match('/^\.+$/', $name), ARRAY_FILTER_USE_KEY); + if ($filter = ($request->query->all()['filter'] ?? NULL)) { + $files = array_filter($files, static fn (string $filename) => array_all($filter, fn (string $value) => str_contains($filename, $value)), ARRAY_FILTER_USE_KEY); + } + $header = [ 'filepath' => $this->t('Path'), 'atime' => $this->t('Last accessed at'), diff --git a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php index 38a26b4..fcd4b09 100644 --- a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php +++ b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php @@ -6,10 +6,10 @@ use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Logger\LoggerChannelInterface; -use Drupal\os2forms_fordelingskomponent\Exception\SoapException; use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository; +use ItkDev\Serviceplatformen\SF2900\EnumType\ForretningsValideringsKodeType; use ItkDev\Serviceplatformen\SF2900\StructType\FordelingskvitteringModtagAnvenderRequestType; use ItkDev\Serviceplatformen\SF2900\StructType\FordelingskvitteringModtagAnvenderResponseType; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -49,29 +49,27 @@ public function FordelingskvitteringModtag( distributionTransaktionsId: $context->getDistributionTransktionsID(), ); if (NULL === $forsendelse) { - throw new SoapException(sprintf('Forsendelse %s not found.', $context->getAnvenderTransaktionsID())); + $this->logger->warning(sprintf('Kvittering: Forsendelse %s not found.', $context->getAnvenderTransaktionsID())); } - $forsendelse->deliveredAt = $this->time->getRequestTime(); - $this->forsendelseRepository->save($forsendelse); - $response = new FordelingskvitteringModtagAnvenderResponseType(); - $kvittering = $this->kvitteringRepository->loadByAnvenderTransaktionsId( + // We may receive multiple receipts. + $kvittering = new AnvenderKvittering( + id: NULL, anvenderTransaktionsId: $context->getAnvenderTransaktionsID(), distributionTransaktionsId: $context->getDistributionTransktionsID(), + request: $request, + response: $response, ); - if (NULL === $kvittering) { - $kvittering = new AnvenderKvittering( - anvenderTransaktionsId: $context->getAnvenderTransaktionsID(), - distributionTransaktionsId: $context->getDistributionTransktionsID(), - request: $request, - response: $response, - ); - } - $this->kvitteringRepository->save($kvittering); + // @todo Should we set a status code (rather than deliveredAt) on the forsendelse? + if (NULL !== $forsendelse && $request->getForretningskvittering()->getForretningsValideringsKode() === ForretningsValideringsKodeType::VALUE_ACCEPTERET) { + $forsendelse->deliveredAt = $this->time->getRequestTime(); + $this->forsendelseRepository->save($forsendelse); + } + return $response; } diff --git a/src/Repository/AnvenderForsendelseRepository.php b/src/Repository/AnvenderForsendelseRepository.php index 4980625..0fc0aa4 100644 --- a/src/Repository/AnvenderForsendelseRepository.php +++ b/src/Repository/AnvenderForsendelseRepository.php @@ -23,7 +23,7 @@ final class AnvenderForsendelseRepository extends AbstractRepository { * @return \Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse[] * The list of forsendelser. */ - private function loadBy(array $conditions = []): array { + private function loadBy(array $conditions = [], array $orderBy = ['created_at' => 'DESC']): array { $query = $this->database ->select(self::TABLE, 't') ->fields('t'); @@ -32,6 +32,10 @@ private function loadBy(array $conditions = []): array { $query->condition(...$condition); } + foreach ($orderBy as $field => $direction) { + $query->orderBy($field, $direction); + } + $statement = $query->execute(); assert(NULL !== $statement); $result = $statement->fetchAll(); diff --git a/src/Repository/AnvenderKvitteringRepository.php b/src/Repository/AnvenderKvitteringRepository.php index 1ba48e5..7544d68 100644 --- a/src/Repository/AnvenderKvitteringRepository.php +++ b/src/Repository/AnvenderKvitteringRepository.php @@ -47,21 +47,18 @@ private function loadBy(array $conditions = []): array { /** * Load kvittering by transaktions-id. + * + * @return \Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering[] */ - public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId, ?string $distributionTransaktionsId = NULL): ?AnvenderKvittering { + public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId, ?string $distributionTransaktionsId = NULL): array { $criteria = [ ['anvender_transaktions_id', $anvenderTransaktionsId], ]; if (NULL !== $distributionTransaktionsId) { $criteria[] = ['distribution_transaktions_id', $distributionTransaktionsId]; } - $result = $this->loadBy($criteria); - if (1 !== count($result)) { - return NULL; - } - - return reset($result); + return $this->loadBy($criteria); } /** @@ -81,23 +78,11 @@ public function save(AnvenderKvittering $kvittering): bool { 'created_at' => $kvittering->createdAt, 'updated_at' => $kvittering->updatedAt, ]; - if (NULL === $this->loadByAnvenderTransaktionsId( - anvenderTransaktionsId: $kvittering->anvenderTransaktionsId, - distributionTransaktionsId: $kvittering->distributionTransaktionsId, - )) { - $this->database - ->insert(self::TABLE) - ->fields($fields) - ->execute(); - } - else { - $this->database - ->update(self::TABLE) - ->condition('anvender_transaktions_id', $kvittering->anvenderTransaktionsId) - ->condition('distribution_transaktions_id', $kvittering->distributionTransaktionsId) - ->fields($fields) - ->execute(); - } + + $this->database + ->insert(self::TABLE) + ->fields($fields) + ->execute(); return TRUE; } From 4919cc526b27b449dbd3505de11e89d6223c198c Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 30 Apr 2026 10:36:35 +0200 Subject: [PATCH 51/62] Cleaned up --- ...ngskomponentDebugForsendelseController.php | 12 +-- ...ingskomponentDebugKvitteringController.php | 79 ++++++++++++++++++- .../FordelingskvitteringModtagController.php | 17 +--- src/Helper/FordelingskomponentHelper.php | 59 +++++++++++--- src/Helper/WebformHelperSF2900.php | 59 +++++++++----- src/Hook/InstallHooks.php | 20 ++++- .../AnvenderKvittering.php | 1 + .../AnvenderForsendelseRepository.php | 2 + .../AnvenderKvitteringRepository.php | 26 +++++- 9 files changed, 219 insertions(+), 56 deletions(-) diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php index f87c9e8..edaba65 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php @@ -26,7 +26,7 @@ public function __construct( * Builds the response. */ public function __invoke(Request $request, WebformInterface $webform, WebformSubmissionInterface $webform_submission): array { - if ($anvenderTransaktionsId = $request->query->get('anvenderTransaktionsId')) { + if ($anvenderTransaktionsId = $request->query->get('anvender_transaktions_id')) { if ($item = $this->repository->loadByAnvenderTransaktionsId($anvenderTransaktionsId)) { return $this->itemDetails($item); } @@ -53,8 +53,10 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Link.php/class/Link/10 '#title' => $item->anvenderTransaktionsId, '#type' => 'link', - '#url' => Url::fromRoute('', [ - 'anvenderTransaktionsId' => $item->anvenderTransaktionsId, + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse', [ + 'webform' => $webform->id(), + 'webform_submission' => $webform_submission->id(), + 'anvender_transaktions_id' => $item->anvenderTransaktionsId, ]), ], ], @@ -68,8 +70,8 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub '#title' => $this->t('Receipts'), '#type' => 'link', '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_kvittering', [ - 'webform' => $item->webformId, - 'webform_submission' => $item->webformSubmissionId, + 'webform' => $webform->id(), + 'webform_submission' => $webform_submission->id(), 'anvender_transaktions_id' => $item->anvenderTransaktionsId, ]), ], diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php index 984b890..ebb0463 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php @@ -4,9 +4,12 @@ namespace Drupal\os2forms_fordelingskomponent_debug\Controller; +use Drupal\Core\Url; +use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -22,13 +25,83 @@ public function __construct( /** * Builds the response. */ - public function __invoke(WebformInterface $webform, WebformSubmissionInterface $webform_submission, string $anvender_transaktions_id): array { - $item = $this->repository->loadByAnvenderTransaktionsId($anvender_transaktions_id); + public function __invoke(Request $request, WebformInterface $webform, WebformSubmissionInterface $webform_submission, string $anvender_transaktions_id): array { + if ($id = (int) $request->query->get('id')) { + if ($item = $this->repository->load($id)) { + return $this->itemDetails($item); + } - if (NULL === $item) { throw new NotFoundHttpException(); } + $items = $this->repository->loadByAnvenderTransaktionsId($anvender_transaktions_id); + + // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Table.php/class/Table/10 + $header = [ + 'id' => $this->t('ID'), + 'anvenderTransaktionsId' => $this->t('anvenderTransaktionsId'), + 'distributionTransaktionsId' => $this->t('distributionTransaktionsId'), + 'createdAt' => $this->t('Created at'), + 'updatedAt' => $this->t('Updated at'), + ]; + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'id' => [ + 'data' => [ + '#title' => $item->id, + '#type' => 'link', + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_kvittering', + [ + 'webform' => $webform->id(), + 'webform_submission' => $webform_submission->id(), + 'anvender_transaktions_id' => $item->anvenderTransaktionsId, + 'id' => $item->id, + ]), + ], + ], + 'anvenderTransaktionsId' => [ + 'data' => [ + '#title' => $item->anvenderTransaktionsId, + '#type' => 'link', + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse', + [ + 'webform' => $webform->id(), + 'webform_submission' => $webform_submission->id(), + 'anvender_transaktions_id' => $item->anvenderTransaktionsId, + ]), + ], + ], + 'distributionTransaktionsId' => [ + 'data' => [ + '#markup' => $item->distributionTransaktionsId, + ], + ], + 'createdAt' => [ + 'data' => [ + '#markup' => $this->formatDatetime($item->createdAt), + ], + ], + 'updatedAt' => [ + 'data' => [ + '#markup' => $this->formatDatetime($item->updatedAt), + ], + ], + ]; + } + + return [ + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => $this->t('No entries available.'), + ]; + } + + /** + * Build item details. + */ + private function itemDetails(AnvenderKvittering $item) { return [ [ '#type' => 'item', diff --git a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php index fcd4b09..5872042 100644 --- a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php +++ b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php @@ -35,6 +35,9 @@ public function __construct( /** * FordelingskvitteringModtag handler. + * + * Note that we don't connect to receipt directly to a distribution. They are + * related by transaction IDs. */ // phpcs:ignore Drupal.NamingConventions.ValidFunctionName.ScopeNotCamelCaps public function FordelingskvitteringModtag( @@ -44,14 +47,6 @@ public function FordelingskvitteringModtag( // @todo Do something with the kvittering. $context = $request->getDistributionContext(); - $forsendelse = $this->forsendelseRepository->loadByAnvenderTransaktionsId( - anvenderTransaktionsId: $context->getAnvenderTransaktionsID(), - distributionTransaktionsId: $context->getDistributionTransktionsID(), - ); - if (NULL === $forsendelse) { - $this->logger->warning(sprintf('Kvittering: Forsendelse %s not found.', $context->getAnvenderTransaktionsID())); - } - $response = new FordelingskvitteringModtagAnvenderResponseType(); // We may receive multiple receipts. @@ -64,12 +59,6 @@ public function FordelingskvitteringModtag( ); $this->kvitteringRepository->save($kvittering); - // @todo Should we set a status code (rather than deliveredAt) on the forsendelse? - if (NULL !== $forsendelse && $request->getForretningskvittering()->getForretningsValideringsKode() === ForretningsValideringsKodeType::VALUE_ACCEPTERET) { - $forsendelse->deliveredAt = $this->time->getRequestTime(); - $this->forsendelseRepository->save($forsendelse); - } - return $response; } diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 6478d07..3b502c3 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -2,6 +2,7 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; +use Drupal\webform\Entity\WebformSubmission; use ItkDev\Serviceplatformen\Service\SF2900\SF2900\SftpHelper; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; @@ -380,10 +381,15 @@ public function buildFileGroups(HandlerSettings $handlerSettings, WebformSubmiss /** * Get SFTP filename. */ - private function getSftpFilename(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, string $filename): string { + private function getSftpFilename( + HandlerSettings $handlerSettings, + WebformSubmissionInterface $submission, + string $filename, + ): string { return implode('_', [ uniqid('os2forms_fordelingskomponent_'), - md5($handlerSettings->handlerId . '||' . $submission->uuid()), + $handlerSettings->handlerId, + $submission->uuid(), $filename, ]); } @@ -399,6 +405,7 @@ private function getSftpFilename(HandlerSettings $handlerSettings, WebformSubmis public function uploadFiles( DistributionFormularType|DistributionDokumentType|DistributionJournalPostType $distributionObject, HandlerSettings $handlerSettings, + WebformSubmissionInterface $submission, ): array { $sf2900 = $this->sf2900(); $transactionId = Serializer::createUuid(); @@ -421,7 +428,7 @@ public function uploadFiles( /** @var \Drupal\file\Entity\File $file */ $file = $item['file']; $sftp->putFile($file->getFileUri(), $file->getFilename(), $item['sftp_filename']); - $triggerObject = $this->buildTriggerFile($file, $item['sftp_filename'], $handlerSettings, $transactionId, + $triggerObject = $this->buildTriggerFile($file, $item['sftp_filename'], $handlerSettings, $submission, $transactionId, recipientItSystem: $recipientItSystem); $sftp->putContents($triggerObject, $item['sftp_filename'], $item['sftp_filename'] . '.trigger'); $triggerObjects[] = $triggerObject; @@ -434,10 +441,17 @@ public function uploadFiles( /** * Check if files are delivered. + * + * @todo Report back if delivery has failed, i.e. if receipts exist but + * report errors. */ public function checkFilesDelivered( array $triggerObjects, + WebformSubmissionInterface $submission, ): bool { + $context = [ + 'webform_submission' => $submission, + ]; foreach ($triggerObjects as $triggerObject) { try { $sxe = new \SimpleXMLElement($triggerObject); @@ -445,16 +459,28 @@ public function checkFilesDelivered( if (empty($filename)) { throw new \RuntimeException('Cannot get file name'); } + $this->debug('Checking file %filename', $context + [ + '%filename' => $filename, + ]); $receipt = $this->sf2900()->sftp()->getContents($filename . '.sftpreceipt', SftpHelper::INCOMING_FOLDER); $receiptXse = new \SimpleXMLElement($receipt); - $message = (string) $receiptXse->xpath('//Receipt/Message')[0]; - if ('SUCCESS' !== $message) { - throw new \RuntimeException(sprintf('Message for %s: %s', $filename, $message)); + $status = (string) $receiptXse->xpath('//Receipt/Message')[0]; + + $this->debug('`Status for file %filename: %status', $context + [ + '%filename' => $filename, + '%status' => $status, + ]); + if ('SUCCESS' !== $status) { + throw new \RuntimeException(sprintf('Message for %s: %s', $filename, $status)); } } catch (\Exception $exception) { - $this->logger->error($exception->getMessage()); + $this->logger->warning('Error checking file %filename: %message', $context + [ + '%filename' => $filename ?? NULL, + '%message' => $exception->getMessage(), + 'exception' => $exception, + ]); return FALSE; } } @@ -636,10 +662,10 @@ public function afterServiceCall(AfterServiceCallEvent $event): void { $this->anvenderForsendelseRepository->save( new AnvenderForsendelse( webformId: $context->submission->getWebform()->id(), - webformHandlerId: $context->handlerSettings->handlerId, + webformHandlerId: $context->handlerSettings->handlerId, webformSubmissionId: $context->submission->id(), anvenderTransaktionsId: $anvenderTransaktionsId, - request: $request, + request: $request, distributionTransaktionsId: $response->getDistributionContext()->getDistributionTransktionsID(), response: $response ) @@ -704,7 +730,14 @@ private function getTransactionContext( * * @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf */ - private function buildTriggerFile(File $file, string $sftpFilename, HandlerSettings $handlerSettings, string $transactionId, ?string $recipientItSystem = NULL): string { + private function buildTriggerFile( + File $file, + string $sftpFilename, + HandlerSettings $handlerSettings, + WebformSubmission $submission, + string $transactionId, + ?string $recipientItSystem = NULL, + ): string { $dom = new \DOMDocument(); $dom->loadXML(self::TRIGGER_FILE_TEMPLATE); $xpath = new \DOMXPath($dom); @@ -757,14 +790,16 @@ private function buildTriggerFile(File $file, string $sftpFilename, HandlerSetti $xml = $dom->saveXML(); try { - $this->xmlHelper->validateXml($xml, 'module://os2forms_fordelingskomponent/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd'); + $this->xmlHelper->validateXml($xml, + 'module://os2forms_fordelingskomponent/resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd'); } catch (InvalidXmlException $e) { $this->logger->error('Invalid XML in trigger file: %message.', [ + 'webform_submission' => $submission, '%message' => $e->getMessage(), 'exception' => $e, ]); - throw new RuntimeException('Invalid XML in trigger file: %message.', ['%message' => $e->getMessage()]); + throw new RuntimeException(sprintf('Invalid XML in trigger file: %s', $e->getMessage())); } return $xml; diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 4397f79..5352fbc 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -206,7 +206,7 @@ public function log($level, $message, array $context = []): void { * * @see self::processJob() */ - public function createJob(WebformSubmissionInterface $webformSubmission, WebformHandlerSF2900|HandlerSettings $handlerSettings, ?array $payload = []): ?Job { + public function createJob(WebformSubmissionInterface $webformSubmission, WebformHandlerSF2900|HandlerSettings $handlerSettings, ?string $state = NULL, ?array $payload = []): ?Job { $context = [ 'handler_id' => WebformHandlerSF2900::ID, 'webform_submission' => $webformSubmission, @@ -217,7 +217,11 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform $handlerSettings = $this->settings->getHandlerSettings($handlerSettings); } - $job = Job::create(FordelingskomponentSF2900::class, $payload + [ + $job = Job::create(FordelingskomponentSF2900::class, [ + self::PAYLOAD_KEY => [ + self::PAYLOAD_STATE => $state, + ] + $payload, + ] + [ 'formId' => $webformSubmission->getWebform()->id(), 'submissionId' => $webformSubmission->id(), 'handlerSettings' => $handlerSettings->toArray(), @@ -225,8 +229,12 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform $queue = $this->loadQueue(); $queue->enqueueJob($job); $context['@queue'] = $queue->id(); - $this->notice('Job for afsend added to the queue @queue.', $context + [ - 'operation' => 'Fordelingskomponent afsend queued', + $this->notice('Fordelingskomponent job added to the queue @queue.', $context + [ + 'operation' => match ($state) { + self::STATE_UPLOAD_FILES => 'Fordelingskomponent upload files', + self::STATE_CHECK_FILES => 'Fordelingskomponent check files', + default => 'Fordelingskomponent afsend', + }, ]); return $job; @@ -285,28 +293,29 @@ public function processJob(Job $job): JobResult { // 1. Upload files and trigger files. When done, create a job to // 2. Check that all files have been delivered. Finally // 3. Send distribution object. - $state = $this->getJobState($job); + [$state, $info] = $this->getJobState($job); switch ($state) { case self::STATE_UPLOAD_FILES: - $files = $this->helper->uploadFiles($distributionObject, $handlerSettings); + $files = $this->helper->uploadFiles($distributionObject, $handlerSettings, $submission); $this->notice('Fordelingskomponent files uploaded', $context); - $this->createJob($submission, $handlerSettings, [self::PAYLOAD_CHECK_FILES => $files]); + $this->createJob($submission, $handlerSettings, self::STATE_CHECK_FILES, [self::PAYLOAD_FILES => $files]); break; case self::STATE_CHECK_FILES: - $files = $payload[self::PAYLOAD_CHECK_FILES]; - if (!$this->helper->checkFilesDelivered($files)) { + $files = $info[self::PAYLOAD_FILES]; + if (!$this->helper->checkFilesDelivered($files, $submission)) { + $this->notice('Fordelingskomponent files not yet delivered', $context); return JobResult::failure(sprintf('Files not yet delivered')); } else { $this->notice('Fordelingskomponent files delivered', $context); - $this->createJob($submission, $handlerSettings, [self::PAYLOAD_FILES_DELIVERED => TRUE]); + $this->createJob($submission, $handlerSettings, self::STATE_SEND_DISTRIBUTION_OBJECT, [self::PAYLOAD_FILES_DELIVERED => TRUE]); } break; case self::STATE_SEND_DISTRIBUTION_OBJECT: $this->helper->sendDokument($submission, $distributionObject, $attachment, $handlerSettings); - $this->notice('Fordelingskomponent afsendt', $context); + $this->notice('Fordelingskomponent distribution object afsendt', $context); break; } } @@ -350,9 +359,11 @@ private function replaceTokens(HandlerSettings $handlerSettings, WebformSubmissi return $handlerSettings; } + private const string PAYLOAD_KEY = 'os2forms_fordelingskomponent'; + private const string PAYLOAD_STATE = 'state'; private const string STATE_UPLOAD_FILES = 'upload_files'; - private const string PAYLOAD_CHECK_FILES = 'check_files'; + private const string PAYLOAD_FILES = 'files'; private const string STATE_CHECK_FILES = 'check_files'; private const string PAYLOAD_FILES_DELIVERED = 'files_delivered'; @@ -361,20 +372,28 @@ private function replaceTokens(HandlerSettings $handlerSettings, WebformSubmissi /** * Get state for a job. * - * This is only used when we must upload files and hence the first state (and - * the default) is "upload files". + * The state is computed based on data set in the job payload. + * + * This is only used when we must upload files (and check that they're ready + * before sending the actual distribution object) and hence the first state + * (and the default) is "upload files". + * + * @return array + * A job state and the job info. */ - private function getJobState(Job $job): string { + private function getJobState(Job $job): array { $payload = $job->getPayload(); - if (isset($payload[self::PAYLOAD_FILES_DELIVERED])) { - return self::STATE_SEND_DISTRIBUTION_OBJECT; + $info = $payload[self::PAYLOAD_KEY] ?? NULL; + $state = self::STATE_UPLOAD_FILES; + if (isset($info[self::PAYLOAD_FILES_DELIVERED])) { + $state = self::STATE_SEND_DISTRIBUTION_OBJECT; } - if (isset($payload[self::PAYLOAD_CHECK_FILES])) { - return self::STATE_CHECK_FILES; + elseif (isset($info[self::PAYLOAD_FILES])) { + $state = self::STATE_CHECK_FILES; } - return self::STATE_UPLOAD_FILES; + return [$state, $info]; } } diff --git a/src/Hook/InstallHooks.php b/src/Hook/InstallHooks.php index 497aa69..69f4ee8 100644 --- a/src/Hook/InstallHooks.php +++ b/src/Hook/InstallHooks.php @@ -56,13 +56,16 @@ public function schema(): array { 'anvender_transaktions_id', ], 'indexes' => [ + 'anvender_transaktions_id' => [ + 'anvender_transaktions_id', + ], 'distribution_transaktions_id' => [ 'distribution_transaktions_id', ], ], ]; - $createTable = static function (string $description, array $fields = [], array $foreignKeys = [], array $indexes = []) use ($baseSchema): array { + $createTable = static function (string $description, array $fields = [], array $primaryKey = [], array $foreignKeys = [], array $indexes = []) use ($baseSchema): array { $table = $baseSchema + [ 'description' => $description, ]; @@ -71,6 +74,10 @@ public function schema(): array { $table['fields'][$name] = $spec; } + if ($primaryKey) { + $table['primary key'] = $primaryKey; + } + foreach ($foreignKeys as $name => $spec) { $table['foreign keys'][$name] = $spec; } @@ -123,6 +130,17 @@ public function schema(): array { $schema[self::TABLE_ANVENDER_KVITTERING] = $createTable( description: 'Stores data on kvitteringer.', + fields: [ + 'id' => [ + 'description' => 'Unique ID', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + ], + primaryKey: [ + 'id', + ], ); $schema[self::TABLE_MODTAGER_FORSENDELSE] = $createTable( diff --git a/src/Model/Fordelingskomponent/AnvenderKvittering.php b/src/Model/Fordelingskomponent/AnvenderKvittering.php index 7b30777..684cece 100644 --- a/src/Model/Fordelingskomponent/AnvenderKvittering.php +++ b/src/Model/Fordelingskomponent/AnvenderKvittering.php @@ -14,6 +14,7 @@ final class AnvenderKvittering { * Constructor. */ public function __construct( + public readonly ?int $id, public readonly string $anvenderTransaktionsId, public readonly string $distributionTransaktionsId, public readonly FordelingskvitteringModtagAnvenderRequestType $request, diff --git a/src/Repository/AnvenderForsendelseRepository.php b/src/Repository/AnvenderForsendelseRepository.php index 0fc0aa4..d79df47 100644 --- a/src/Repository/AnvenderForsendelseRepository.php +++ b/src/Repository/AnvenderForsendelseRepository.php @@ -19,6 +19,8 @@ final class AnvenderForsendelseRepository extends AbstractRepository { * * @param array $conditions * The criteria. + * @param array $orderBy + * The order. * * @return \Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse[] * The list of forsendelser. diff --git a/src/Repository/AnvenderKvitteringRepository.php b/src/Repository/AnvenderKvitteringRepository.php index 7544d68..7e99db0 100644 --- a/src/Repository/AnvenderKvitteringRepository.php +++ b/src/Repository/AnvenderKvitteringRepository.php @@ -16,11 +16,13 @@ final class AnvenderKvitteringRepository extends AbstractRepository { * * @param array $conditions * The criteria. + * @param array $orderBy + * The order. * * @return \Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering[] * The list of kvittering. */ - private function loadBy(array $conditions = []): array { + private function loadBy(array $conditions = [], array $orderBy = ['created_at' => 'DESC']): array { $query = $this->database ->select(self::TABLE, 't') ->fields('t'); @@ -29,11 +31,16 @@ private function loadBy(array $conditions = []): array { $query->condition(...$condition); } + foreach ($orderBy as $field => $direction) { + $query->orderBy($field, $direction); + } + $statement = $query->execute(); assert(NULL !== $statement); $result = $statement->fetchAll(); return array_map( static fn(object $row) => new AnvenderKvittering( + id: $row->id, anvenderTransaktionsId: $row->anvender_transaktions_id, distributionTransaktionsId: $row->distribution_transaktions_id, request: unserialize($row->request, options: ['allowed_classes' => TRUE]), @@ -45,10 +52,27 @@ private function loadBy(array $conditions = []): array { ); } + /** + * Load kvittering by ID. + * + * @return ?\Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering + * The kvittering if any. + */ + public function load(int $id): ?AnvenderKvittering { + $criteria = [ + ['id', $id], + ]; + + $result = $this->loadBy($criteria); + + return 1 === count($result) ? reset($result) : NULL; + } + /** * Load kvittering by transaktions-id. * * @return \Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering[] + * The kvitteringer. */ public function loadByAnvenderTransaktionsId(string $anvenderTransaktionsId, ?string $distributionTransaktionsId = NULL): array { $criteria = [ From 236938667ce0452337ef2bdf7ce7eed107d764d9 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 30 Apr 2026 16:33:56 +0200 Subject: [PATCH 52/62] Validated XML before starting queue job show --- .../FordelingskvitteringModtagController.php | 1 - src/Helper/WebformHelperSF2900.php | 18 +++++++++++++++++- src/Helper/XmlHelper.php | 2 +- .../WebformHandler/WebformHandlerSF2900.php | 1 + 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php index 5872042..a2f3f7f 100644 --- a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php +++ b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php @@ -9,7 +9,6 @@ use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository; -use ItkDev\Serviceplatformen\SF2900\EnumType\ForretningsValideringsKodeType; use ItkDev\Serviceplatformen\SF2900\StructType\FordelingskvitteringModtagAnvenderRequestType; use ItkDev\Serviceplatformen\SF2900\StructType\FordelingskvitteringModtagAnvenderResponseType; use Symfony\Component\DependencyInjection\Attribute\Autowire; diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 5352fbc..5095ac0 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -217,6 +217,12 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform $handlerSettings = $this->settings->getHandlerSettings($handlerSettings); } + if (NULL === $state) { + // In initial job creating (right after submission), validate the + // submission and abort on error. + $this->validateSubmission($webformSubmission, $handlerSettings); + } + $job = Job::create(FordelingskomponentSF2900::class, [ self::PAYLOAD_KEY => [ self::PAYLOAD_STATE => $state, @@ -240,7 +246,8 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform return $job; } catch (\Exception $exception) { - $this->error('Error creating job for afsend.', $context + [ + $this->error('Error creating job for fordelingskomponent: %message', $context + [ + '%message' => $exception->getMessage(), 'operation' => 'Fordelingskomponent afsend failed', 'exception' => $exception, ]); @@ -396,4 +403,13 @@ private function getJobState(Job $job): array { return [$state, $info]; } + /** + * Validate a submission. + */ + public function validateSubmission(WebformSubmissionInterface $submission, HandlerSettings $handlerSettings): void { + $attachment = $this->getAttachment($submission, $handlerSettings); + + $this->helper->buildDistributionObject($submission, $handlerSettings, $attachment); + } + } diff --git a/src/Helper/XmlHelper.php b/src/Helper/XmlHelper.php index 3ac7d55..093b2a5 100644 --- a/src/Helper/XmlHelper.php +++ b/src/Helper/XmlHelper.php @@ -70,7 +70,7 @@ public function render(string $template, array $context, bool $validateXml = TRU */ public function getRenderContext(HandlerSettings $handlerSettings, WebformSubmissionInterface $submission, array $files) { return [ - 'submission' => $submission, + 'submission' => $submission->toArray(TRUE), 'files' => $files, 'handler' => ['settings' => $handlerSettings->toArray()], ]; diff --git a/src/Plugin/WebformHandler/WebformHandlerSF2900.php b/src/Plugin/WebformHandler/WebformHandlerSF2900.php index d2d7dd2..4d91c5f 100644 --- a/src/Plugin/WebformHandler/WebformHandlerSF2900.php +++ b/src/Plugin/WebformHandler/WebformHandlerSF2900.php @@ -398,6 +398,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s */ public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { // Run only when submission is completed. + // @todo Run on update? if (!$webform_submission->isCompleted()) { return; } From a920355d77841a137af27468d33dadbf5330158f Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 30 Apr 2026 16:39:29 +0200 Subject: [PATCH 53/62] Cleaned up --- .../FordelingskvitteringModtagController.php | 2 -- src/Helper/FordelingskomponentHelper.php | 2 +- src/Helper/WebformHelperSF2900.php | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php index a2f3f7f..a45ff64 100644 --- a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php +++ b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php @@ -23,9 +23,7 @@ final class FordelingskvitteringModtagController extends AbstractSoapController protected string $wsdl = 'file://' . __DIR__ . '/../../../resources/sf2900/SF2900_EP_MS1-2/DistributionServiceAnvenderV2.wsdl'; public function __construct( - private readonly AnvenderForsendelseRepository $forsendelseRepository, private readonly AnvenderKvitteringRepository $kvitteringRepository, - private readonly TimeInterface $time, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent')] LoggerChannelInterface $logger, ) { diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 3b502c3..6483277 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -734,7 +734,7 @@ private function buildTriggerFile( File $file, string $sftpFilename, HandlerSettings $handlerSettings, - WebformSubmission $submission, + WebformSubmissionInterface $submission, string $transactionId, ?string $recipientItSystem = NULL, ): string { diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index 5095ac0..cb43f69 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -66,7 +66,6 @@ public function __construct( private readonly AnvenderForsendelseRepository $anvenderForsendelseRepository, #[Autowire(service: 'webform.token_manager')] private readonly WebformTokenManagerInterface $webformTokenManager, - private readonly StateInterface $state, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent')] private readonly LoggerChannelInterface $logger, #[Autowire(service: 'logger.channel.os2forms_fordelingskomponent_submission')] From 3bde914f2184c3a9b017b5de5de48ff7d22079e2 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 1 May 2026 13:06:36 +0200 Subject: [PATCH 54/62] Added and used permission --- README.md | 11 ++++++++--- .../os2forms_fordelingskomponent_debug.routing.yml | 2 +- os2forms_fordelingskomponent.permissions.yml | 3 +++ 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 os2forms_fordelingskomponent.permissions.yml diff --git a/README.md b/README.md index d0297d8..53de384 100644 --- a/README.md +++ b/README.md @@ -131,9 +131,14 @@ XML ## Debugging -``` php -# settings.local.php -$settings['os2forms_fordelingskomponent']['log_level'] = \Drupal\Core\Logger\RfcLogLevel::DEBUG; +``` shell +drush sql:query --extra='--table' "SELECT (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_forsendelse) AS os2forms_fordelingskomponent_anvender_forsendelse, (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_kvittering) AS os2forms_fordelingskomponent_anvender_kvittering" +# Forsendelser +drush sql:query --extra='--table' "SELECT webform_id, webform_submission_id, anvender_transaktions_id, distribution_transaktions_id FROM os2forms_fordelingskomponent_anvender_forsendelse" +# Kvitteringer +drush sql:query --extra='--table' "SELECT anvender_transaktions_id, distribution_transaktions_id FROM os2forms_fordelingskomponent_anvender_kvittering" +# Forsendelser med kvitteringer +drush sql:query --extra='--table' "SELECT webform_id, webform_submission_id, anvender_transaktions_id, distribution_transaktions_id FROM os2forms_fordelingskomponent_anvender_forsendelse WHERE anvender_transaktions_id IN (SELECT anvender_transaktions_id FROM os2forms_fordelingskomponent_anvender_kvittering)" ``` ``` shell diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml index 9b3296e..504b455 100644 --- a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml @@ -8,7 +8,7 @@ os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp: methods: &methods - GET requirements: &requirements - _permission: "administer site configuration" + _permission: "view os2forms_fordelingskomponent objects" os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp_filename: path: "/admin/os2forms-fordelingskomponent-debug/os2forms-fordelingskomponent-debug-sftp/{dir}/{filename}" diff --git a/os2forms_fordelingskomponent.permissions.yml b/os2forms_fordelingskomponent.permissions.yml new file mode 100644 index 0000000..fa191be --- /dev/null +++ b/os2forms_fordelingskomponent.permissions.yml @@ -0,0 +1,3 @@ +view os2forms_fordelingskomponent objects: + title: 'View Fordelingskomponent objects' + description: 'View distribution objects sent to and receipts received from Fordelingskomponenten' From 7b4f3a180609ef77e7bd42842c863d76b6572c84 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 1 May 2026 13:55:54 +0200 Subject: [PATCH 55/62] Cleaned up --- composer.json | 1 + ...s_fordelingskomponent_debug.links.task.yml | 5 ++- ...orms_fordelingskomponent_debug.routing.yml | 4 +- ...ngskomponentDebugForsendelseController.php | 40 +++++++------------ ...ingskomponentDebugKvitteringController.php | 19 ++++----- os2forms_fordelingskomponent.permissions.yml | 4 +- .../FordelingskvitteringModtagController.php | 2 - src/Helper/FordelingskomponentHelper.php | 1 - src/Helper/WebformHelperSF2900.php | 3 +- .../Unit/Helper/XmlHelperTestDataProvider.php | 6 +-- 10 files changed, 36 insertions(+), 49 deletions(-) diff --git a/composer.json b/composer.json index 79acdaa..f07d09e 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ } ], "require": { + "php": "^8.3", "ext-dom": "*", "ext-soap": "*", "ext-xsl": "*", diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml index 034eede..eb22c23 100644 --- a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.links.task.yml @@ -1,5 +1,6 @@ -os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse: +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_forsendelse: title: "Fordelingskomponentforsendelser" - route_name: os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse + route_name: os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_forsendelse parent_id: entity.webform_submission.canonical weight: 999 + base_route: os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_forsendelse diff --git a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml index 504b455..89314cc 100644 --- a/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml +++ b/modules/os2forms_fordelingskomponent_debug/os2forms_fordelingskomponent_debug.routing.yml @@ -16,7 +16,7 @@ os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_sftp_filen methods: *methods requirements: *requirements -os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse: +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_forsendelse: path: "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/os2forms-fordelingskomponent-debug-forsendelse" defaults: _title: "Fordelingskomponentforsendelser" @@ -30,7 +30,7 @@ os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendels webform_submission: type: "entity:webform_submission" -os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_kvittering: +os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_kvittering: path: "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/os2forms-fordelingskomponent-debug-forsendelse/kvittering/{anvender_transaktions_id}" defaults: _title: "Fordelingskomponentkvitteringer" diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php index edaba65..5c453f4 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugForsendelseController.php @@ -4,9 +4,9 @@ namespace Drupal\os2forms_fordelingskomponent_debug\Controller; -use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderForsendelse; use Drupal\Core\Url; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository; +use Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\HttpFoundation\Request; @@ -19,6 +19,7 @@ final class Os2formsFordelingskomponentDebugForsendelseController extends Abstra public function __construct( private readonly AnvenderForsendelseRepository $repository, + private readonly AnvenderKvitteringRepository $kvitteringRepository, ) { } @@ -27,12 +28,9 @@ public function __construct( */ public function __invoke(Request $request, WebformInterface $webform, WebformSubmissionInterface $webform_submission): array { if ($anvenderTransaktionsId = $request->query->get('anvender_transaktions_id')) { - if ($item = $this->repository->loadByAnvenderTransaktionsId($anvenderTransaktionsId)) { - return $this->itemDetails($item); - } - - throw new NotFoundHttpException(); + return $this->itemDetails($anvenderTransaktionsId); } + $items = $this->repository->loadBySubmission($webform_submission); // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Table.php/class/Table/10 @@ -40,20 +38,19 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub 'anvenderTransaktionsId' => $this->t('anvenderTransaktionsId'), 'distributionTransaktionsId' => $this->t('distributionTransaktionsId'), 'receipts' => $this->t('Receipts'), - 'webform handlers' => $this->t('Webform handlers'), 'createdAt' => $this->t('Created at'), 'updatedAt' => $this->t('Updated at'), - 'deliveredAt' => $this->t('Delivered at'), ]; $rows = []; foreach ($items as $item) { + $receipts = $this->kvitteringRepository->loadByAnvenderTransaktionsId($item->anvenderTransaktionsId); $rows[] = [ 'anvenderTransaktionsId' => [ 'data' => [ // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Link.php/class/Link/10 '#title' => $item->anvenderTransaktionsId, '#type' => 'link', - '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse', [ + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_forsendelse', [ 'webform' => $webform->id(), 'webform_submission' => $webform_submission->id(), 'anvender_transaktions_id' => $item->anvenderTransaktionsId, @@ -67,24 +64,15 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub ], 'receipts' => [ 'data' => [ - '#title' => $this->t('Receipts'), + '#title' => count($receipts), '#type' => 'link', - '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_kvittering', [ + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_kvittering', [ 'webform' => $webform->id(), 'webform_submission' => $webform_submission->id(), 'anvender_transaktions_id' => $item->anvenderTransaktionsId, ]), ], ], - 'webform handlers' => [ - 'data' => [ - '#title' => $this->t('Webform handlers'), - '#type' => 'link', - '#url' => Url::fromRoute('entity.webform.handlers', [ - 'webform' => $item->webformId, - ]), - ], - ], 'createdAt' => [ 'data' => [ '#markup' => $this->formatDatetime($item->createdAt), @@ -95,11 +83,6 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub '#markup' => $this->formatDatetime($item->updatedAt), ], ], - 'deliveredAt' => [ - 'data' => [ - '#markup' => $this->formatDatetime($item->deliveredAt), - ], - ], ]; } @@ -114,7 +97,12 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub /** * Build item details. */ - private function itemDetails(AnvenderForsendelse $item) { + private function itemDetails(string $anvenderTransaktionsId) { + $item = $this->repository->loadByAnvenderTransaktionsId($anvenderTransaktionsId); + if (NULL === $item) { + throw new NotFoundHttpException(); + } + return [ [ '#type' => 'item', diff --git a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php index ebb0463..af5354b 100644 --- a/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php +++ b/modules/os2forms_fordelingskomponent_debug/src/Controller/Os2formsFordelingskomponentDebugKvitteringController.php @@ -5,7 +5,6 @@ namespace Drupal\os2forms_fordelingskomponent_debug\Controller; use Drupal\Core\Url; -use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; @@ -27,11 +26,7 @@ public function __construct( */ public function __invoke(Request $request, WebformInterface $webform, WebformSubmissionInterface $webform_submission, string $anvender_transaktions_id): array { if ($id = (int) $request->query->get('id')) { - if ($item = $this->repository->load($id)) { - return $this->itemDetails($item); - } - - throw new NotFoundHttpException(); + return $this->itemDetails($id); } $items = $this->repository->loadByAnvenderTransaktionsId($anvender_transaktions_id); @@ -51,7 +46,7 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub 'data' => [ '#title' => $item->id, '#type' => 'link', - '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_kvittering', + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_kvittering', [ 'webform' => $webform->id(), 'webform_submission' => $webform_submission->id(), @@ -64,7 +59,7 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub 'data' => [ '#title' => $item->anvenderTransaktionsId, '#type' => 'link', - '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_debug_forsendelse', + '#url' => Url::fromRoute('os2forms_fordelingskomponent_debug.os2forms_fordelingskomponent_forsendelse', [ 'webform' => $webform->id(), 'webform_submission' => $webform_submission->id(), @@ -101,7 +96,13 @@ public function __invoke(Request $request, WebformInterface $webform, WebformSub /** * Build item details. */ - private function itemDetails(AnvenderKvittering $item) { + private function itemDetails(int $id) { + $item = $this->repository->load($id); + + if (NULL === $item) { + throw new NotFoundHttpException(); + } + return [ [ '#type' => 'item', diff --git a/os2forms_fordelingskomponent.permissions.yml b/os2forms_fordelingskomponent.permissions.yml index fa191be..5186263 100644 --- a/os2forms_fordelingskomponent.permissions.yml +++ b/os2forms_fordelingskomponent.permissions.yml @@ -1,3 +1,3 @@ view os2forms_fordelingskomponent objects: - title: 'View Fordelingskomponent objects' - description: 'View distribution objects sent to and receipts received from Fordelingskomponenten' + title: "View Fordelingskomponent objects" + description: "View distribution objects sent to and receipts received from Fordelingskomponenten" diff --git a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php index a45ff64..7111087 100644 --- a/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php +++ b/src/Controller/Fordelingskomponent/FordelingskvitteringModtagController.php @@ -4,10 +4,8 @@ namespace Drupal\os2forms_fordelingskomponent\Controller\Fordelingskomponent; -use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\os2forms_fordelingskomponent\Model\Fordelingskomponent\AnvenderKvittering; -use Drupal\os2forms_fordelingskomponent\Repository\AnvenderForsendelseRepository; use Drupal\os2forms_fordelingskomponent\Repository\AnvenderKvitteringRepository; use ItkDev\Serviceplatformen\SF2900\StructType\FordelingskvitteringModtagAnvenderRequestType; use ItkDev\Serviceplatformen\SF2900\StructType\FordelingskvitteringModtagAnvenderResponseType; diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 6483277..04635e3 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -2,7 +2,6 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; -use Drupal\webform\Entity\WebformSubmission; use ItkDev\Serviceplatformen\Service\SF2900\SF2900\SftpHelper; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; diff --git a/src/Helper/WebformHelperSF2900.php b/src/Helper/WebformHelperSF2900.php index cb43f69..4fa5c20 100644 --- a/src/Helper/WebformHelperSF2900.php +++ b/src/Helper/WebformHelperSF2900.php @@ -9,7 +9,6 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\Render\ElementInfoManager; -use Drupal\Core\State\StateInterface; use Drupal\os2forms_fordelingskomponent\Exception\InvalidAttachmentElementException; use Drupal\os2forms_fordelingskomponent\Exception\RuntimeException; use Drupal\os2forms_fordelingskomponent\Exception\SubmissionNotFoundException; @@ -174,7 +173,7 @@ private function loadQueue(): QueueInterface { $queue = $this->queueStorage->load($id); if (NULL === $queue) { - throw new RuntimeException('Cannot load queue %queue_id', ['%queue_id' => $id]); + throw new RuntimeException(sprintf('Cannot load queue %s', $id)); } return $queue; diff --git a/tests/Unit/Helper/XmlHelperTestDataProvider.php b/tests/Unit/Helper/XmlHelperTestDataProvider.php index e2acd6c..cda74da 100644 --- a/tests/Unit/Helper/XmlHelperTestDataProvider.php +++ b/tests/Unit/Helper/XmlHelperTestDataProvider.php @@ -8,7 +8,7 @@ * Xml helper test data provider. */ final class XmlHelperTestDataProvider { - private const string RESOURCE_PATH = 'file://' . __DIR__ . '/../../../modules/os2forms_fordelingskomponent_examples/resources'; + private const string RESOURCE_PATH = 'file://' . __DIR__ . '/../../../resources'; /** * Data provider. @@ -235,7 +235,7 @@ public static function provideValidateXmlData(): iterable { XML, - self::RESOURCE_PATH . '/xsd/Anmodning.xsd', + self::RESOURCE_PATH . '/SP/SF2900_XSD/Anmodning.xsd', ]; yield [ @@ -258,7 +258,7 @@ public static function provideValidateXmlData(): iterable { XML, - self::RESOURCE_PATH . '/xsd/Anmodning.xsd', + self::RESOURCE_PATH . '/SP/SF2900_XSD/Anmodning.xsd', new InvalidXmlException(), ]; } From 34d99131378413afde833899e44e8fbba421374c Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Sat, 2 May 2026 00:01:38 +0200 Subject: [PATCH 56/62] Used serializer for XML stuff --- src/Helper/FordelingskomponentHelper.php | 138 ++++++++++------------- 1 file changed, 62 insertions(+), 76 deletions(-) diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 04635e3..6606b23 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -2,6 +2,11 @@ namespace Drupal\os2forms_fordelingskomponent\Helper; +use Digitaliseringskataloget\Sftp\FileContentDescriptorType; +use Digitaliseringskataloget\Sftp\FileDescriptorType; +use Digitaliseringskataloget\Sftp\SFTPDynamicRoutingInfoType; +use Digitaliseringskataloget\Sftp\TechnicalReceipt; +use Digitaliseringskataloget\Sftp\Trigger; use ItkDev\Serviceplatformen\Service\SF2900\SF2900\SftpHelper; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; @@ -24,7 +29,7 @@ use Drupal\os2web_audit\Service\Logger as AuditLogger; use Drupal\os2web_key\KeyHelper; use Drupal\webform\WebformSubmissionInterface; -use ItkDev\Serviceplatformen\Service\SF1601\Serializer; +use ItkDev\Serviceplatformen\Service\SF2900\Serializer; use ItkDev\Serviceplatformen\Service\SF2900\Event\AfterServiceCallEvent; use ItkDev\Serviceplatformen\Service\SF2900\SF2900; use ItkDev\Serviceplatformen\SF2900\EnumType\AktoerTypeType; @@ -78,6 +83,11 @@ final class FordelingskomponentHelper implements LoggerInterface, EventSubscribe */ private FileStorageInterface $fileStorage; + /** + * The serializer. + */ + private Serializer $serializer; + /** * Constructor. */ @@ -98,6 +108,7 @@ public function __construct( private readonly AuditLogger $auditLogger, ) { $this->fileStorage = $entityTypeManager->getStorage('file'); + $this->serializer = new Serializer(); } /** @@ -438,8 +449,10 @@ public function uploadFiles( return $triggerObjects; } + private const string SFTP_MESSAGE_SUCCESS = 'SUCCESS'; + /** - * Check if files are delivered. + * Check if all files are delivered. * * @todo Report back if delivery has failed, i.e. if receipts exist but * report errors. @@ -451,10 +464,10 @@ public function checkFilesDelivered( $context = [ 'webform_submission' => $submission, ]; - foreach ($triggerObjects as $triggerObject) { + foreach ($triggerObjects as $xml) { try { - $sxe = new \SimpleXMLElement($triggerObject); - $filename = (string) $sxe->xpath('//FileDescriptor/FileName')[0]; + $trigger = $this->serializer->deserialize($xml, Trigger::class); + $filename = $trigger->getFileDescriptor()->getFileName(); if (empty($filename)) { throw new \RuntimeException('Cannot get file name'); } @@ -463,20 +476,34 @@ public function checkFilesDelivered( ]); $receipt = $this->sf2900()->sftp()->getContents($filename . '.sftpreceipt', SftpHelper::INCOMING_FOLDER); - $receiptXse = new \SimpleXMLElement($receipt); - $status = (string) $receiptXse->xpath('//Receipt/Message')[0]; + $receipt = $this->serializer->deserialize($receipt, TechnicalReceipt::class); + $errors = $receipt->getErrorMessage(); + if (!empty($errors)) { + $error = $errors[0]; + $this->logger->error('Error checking file %filename: %code_description (%code): %description', $context + [ + '%filename' => $filename, + '%code' => $error->getErrorCode(), + '%code_description' => $error->getErrorCodeDescription(), + '%description' => $error->getErrorDescription(), + 'error' => $this->serializer->serialize($error), + ]); + + throw new \RuntimeException(sprintf('SFTP error for %s: %s', $filename, $error->getErrorCodeDescription())); + } + + $message = $receipt->getReceipt()->getMessage(); $this->debug('`Status for file %filename: %status', $context + [ '%filename' => $filename, - '%status' => $status, + '%status' => $message, ]); - if ('SUCCESS' !== $status) { - throw new \RuntimeException(sprintf('Message for %s: %s', $filename, $status)); + if (self::SFTP_MESSAGE_SUCCESS !== $message) { + throw new \RuntimeException(sprintf('Message for %s: %s', $filename, $message)); } } catch (\Exception $exception) { $this->logger->warning('Error checking file %filename: %message', $context + [ - '%filename' => $filename ?? NULL, + '%filename' => $filename, '%message' => $exception->getMessage(), 'exception' => $exception, ]); @@ -698,31 +725,7 @@ private function getTransactionContext( return $this->transactionContexts[$transactionId]; } - // @see https://rimi-itk.github.io/digitaliseringskataloget.dk/digitaliseringskataloget.dk/sf1415/0.6/Integrationsbeskrivelse_SF1415.pdf#page=16 - // @todo Generate classes from resources/ServiceContract-SFTP-20230926/xsd/SFTPTypes.xsd. - private const string TRIGGER_FILE_TEMPLATE = <<<'XML' - - - - - - - - ROUTING_V1_0_0 - - - - - - - - - - - - - -XML; + private const string ROUTING_V1_0_0 = 'ROUTING_V1_0_0'; /** * Build trigger file. @@ -737,56 +740,39 @@ private function buildTriggerFile( string $transactionId, ?string $recipientItSystem = NULL, ): string { - $dom = new \DOMDocument(); - $dom->loadXML(self::TRIGGER_FILE_TEMPLATE); - $xpath = new \DOMXPath($dom); - $xpath->registerNamespace('ns2', 'http://serviceplatformen.dk/xml/wsdl/soap11/SFTP/1/types'); - $setValue = function (string $expression, mixed $value) use ($xpath) { - $nodes = $xpath->query($expression); - if (!$nodes || 1 !== $nodes->count()) { - throw new \RuntimeException(sprintf('No unique node found for expression %s', $expression)); - } - /** @var \DOMElement $node */ - $node = $nodes->item(0); - $node->nodeValue = $value; - }; - $removeElement = function (string $expression) use ($xpath) { - $nodes = $xpath->query($expression); - if (!$nodes || 1 !== $nodes->count()) { - throw new \RuntimeException(sprintf('No unique node found for expression %s', $expression)); - } - /** @var \DOMElement $node */ - $node = $nodes->item(0); - $node->parentNode->removeChild($node); - }; - - $setValue('//FileDescriptor/FileName', $sftpFilename); - $setValue('//FileDescriptor/SizeInBytes', $file->getSize()); - $setValue('//FileDescriptor/Sender', $handlerSettings->sender->sftp->username); - $setValue('//FileDescriptor/SendersFileId', $file->uuid()); + $trigger = new Trigger(); + $trigger->setFileDescriptor( + (new FileDescriptorType()) + ->setFileName($sftpFilename) + ->setSizeInBytes($file->getSize()) + ->setSender($handlerSettings->sender->sftp->username) + ->setSendersFileId($file->uuid()) + ->setRecipients([self::ROUTING_V1_0_0]) + ); $infRef = $handlerSettings->distributionObject->files->filspecifikation; $senderItSystem = $handlerSettings->sender->registreringItSystem; $senderAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->sender->routingMyndighed; - $timestamp = SF2900::formatDateTime(new \DateTimeImmutable()); $recipientItSystem = trim((string) ($recipientItSystem ?? $handlerSettings->distributionObject->files->recipientItSystem)); $recipientAuthority = 'urn:oio:cvr-nr:' . $handlerSettings->distributionObject->files->recipientAuthority; - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/InfRef', $infRef); - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderIt-system', $senderItSystem); - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderAuthority', $senderAuthority); - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/TransactionId', $transactionId); - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/SenderTimestamp', $timestamp); - if (empty($recipientItSystem)) { - $removeElement('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system'); + $routingInfo = (new SFTPDynamicRoutingInfoType()) + ->setInfRef($infRef) + ->setSenderItSystem($senderItSystem) + ->setSenderAuthority($senderAuthority) + ->setTransactionId($transactionId) + ->setSenderTimestamp(new \DateTime()) + ->setRecipientAuthority($recipientAuthority); + if (!empty($recipientItSystem)) { + $routingInfo->setRecipientItSystem($recipientItSystem); } - else { - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientIt-system', $recipientItSystem); - } - $setValue('//FileContentDescriptor/SFTPDynamicRoutingInfo/RecipientAuthority', $recipientAuthority); + $trigger->setFileContentDescriptor( + (new FileContentDescriptorType()) + ->setSFTPDynamicRoutingInfo($routingInfo) + ); - $xml = $dom->saveXML(); + $xml = $this->serializer->serialize($trigger); try { $this->xmlHelper->validateXml($xml, From a130baa0d4cd659d3252b2b703a9c8ca040153ca Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Sat, 2 May 2026 00:01:49 +0200 Subject: [PATCH 57/62] Updated example webform --- .../webform.webform.os2forms_fdk_kp_sp241.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml index 2392d95..0d36ba2 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml @@ -15,8 +15,8 @@ third_party_settings: email_recipients: "" os2forms_nemid: session_type: "" - webform_type: personal - nemlogin_auto_redirect: 1 + webform_type: "" + nemlogin_auto_redirect: 0 os2forms_nemlogin_openid_connect: authentication_settings: user_claim: "" @@ -50,6 +50,9 @@ third_party_settings: link_attributes: {} webform_encrypt: element: + markup: + encrypt: true + encrypt_profile: webform ansoeger_fornavn: encrypt: true encrypt_profile: webform @@ -106,12 +109,15 @@ template: false archive: false id: os2forms_fdk_kp_sp241 title: "SP241 (eksempel): Ansøgning om helbredstillæg" -description: "

/webform/os2forms_fdk_kp_sp241/test?ansoeger_personnummer=1234567890&ansoeger_telefonnummer=12345678

" +description: "" categories: - Eksempel - KP - SP elements: |- + markup: + '#type': webform_markup + '#markup': '

Udfyld med testdata

' ansoeger_fornavn: '#type': textfield '#title': Fornavn @@ -424,6 +430,6 @@ handlers: recipient_it_system_look_up: 1 recipient_it_system: "" recipient_authority: "55133018" - xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }}\r\n {{ submission.completed.value|date(\"Y-m-d\") }}\r\n {{ handler.settings.distribution_context.kle_emne }}\r\n {% for file in files.dokumenter_overslag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Overslag\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_faktura|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Faktura\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_bilag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Bilag\r\n \r\n {% endfor %}\r\n
\r\n \r\n \r\n {{ submission.data.ansoeger_fornavn }}\r\n {% if submission.data.ansoeger_mellemnavn|default(false) %}\r\n {{ submission.data.ansoeger_mellemnavn }}\r\n {% endif %}\r\n {{ submission.data.ansoeger_efternavn }}\r\n urn:oio:cpr:{{ submission.data.ansoeger_personnummer }}\r\n \r\n \r\n Accepteret\r\n \r\n {{ submission.data.ansoeger_fornavn }}{% if submission.data.ansoeger_mellemnavn|default(false) %} {{ submission.data.ansoeger_mellemnavn }}{% endif %} {{ submission.data.ansoeger_efternavn }}\r\n {{ submission.data.underskriftsoplysninger_underskriftsdato|date(\"Y-m-d\") }}\r\n \r\n
\r\n" + xml_template: "\r\n\r\n
\r\n urn:oio:cvr-nr:{{ handler.settings.sender.sender_id }}\r\n {{ submission.completed|date(\"Y-m-d\") }}\r\n {{ handler.settings.distribution_context.kle_emne }}\r\n {% for file in files.dokumenter_overslag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Overslag\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_faktura|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Faktura\r\n \r\n {% endfor %}\r\n {% for file in files.dokumenter_bilag|default([]) %}\r\n \r\n {{ file.sftp_filename }}\r\n Bilag\r\n \r\n {% endfor %}\r\n
\r\n \r\n \r\n {{ submission.data.ansoeger_fornavn }}\r\n {% if submission.data.ansoeger_mellemnavn|default(false) %}\r\n {{ submission.data.ansoeger_mellemnavn }}\r\n {% endif %}\r\n {{ submission.data.ansoeger_efternavn }}\r\n urn:oio:cpr:{{ submission.data.ansoeger_personnummer }}\r\n \r\n \r\n Accepteret\r\n \r\n {{ submission.data.ansoeger_fornavn }}{% if submission.data.ansoeger_mellemnavn|default(false) %} {{ submission.data.ansoeger_mellemnavn }}{% endif %} {{ submission.data.ansoeger_efternavn }}\r\n {{ submission.data.underskriftsoplysninger_underskriftsdato|date(\"Y-m-d\") }}\r\n \r\n
\r\n" xsd_url: "module://os2forms_fordelingskomponent/resources/SP/SF2900_XSD/SP241.xsd" variants: {} From 88fe36cdedcf4f421c1e6bdb1c1ad670e2ec3c2d Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 4 May 2026 10:33:50 +0200 Subject: [PATCH 58/62] Cleaned up --- src/Form/SettingsForm.php | 3 --- src/Helper/FordelingskomponentHelper.php | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index ecaf9c1..0617f2a 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -27,9 +27,6 @@ final class SettingsForm extends ConfigFormBase { use StringTranslationTrait; use AutowireTrait; - public const string SENDER_ID = 'sender_id'; - public const string REGISTRERING_IT_SYSTEM = 'registrering_it_system'; - /** * The queue storage. * diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 6606b23..881be53 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -503,7 +503,7 @@ public function checkFilesDelivered( } catch (\Exception $exception) { $this->logger->warning('Error checking file %filename: %message', $context + [ - '%filename' => $filename, + '%filename' => $filename ?? NULL, '%message' => $exception->getMessage(), 'exception' => $exception, ]); From 7074bb63206e354cb50428fdbeed84bce9681a09 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 6 May 2026 10:51:48 +0200 Subject: [PATCH 59/62] Updated examples --- ...bform.webform.os2forms_fdk_kp_anmoding.yml | 4 +- .../webform.webform.os2forms_fdk_kp_sp241.yml | 2 +- .../webform.webform.os2forms_fdk_kp_sp242.yml | 358 ++++++++++++++++++ .../webform.webform.os2forms_fdk_kp_sp246.yml | 357 +++++++++++++++++ 4 files changed, 719 insertions(+), 2 deletions(-) create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp242.yml create mode 100644 modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp246.yml diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml index 9d413f2..cea9fb3 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_anmoding.yml @@ -104,8 +104,10 @@ id: os2forms_fdk_kp_anmoding title: "OS2Forms Fordelingskomponent: Anmodning (KP)" description: "" categories: - - Example + - Eksempel - Fordelingskomponent + - KP + - SP elements: |- ansoeger_oplysninger: '#type': fieldset diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml index 0d36ba2..fb0af74 100644 --- a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp241.yml @@ -4,7 +4,6 @@ dependencies: module: - os2forms - os2forms_fordelingskomponent - - os2forms_permissions_by_term - webform_encrypt - webform_entity_print - webform_revisions @@ -112,6 +111,7 @@ title: "SP241 (eksempel): Ansøgning om helbredstillæg" description: "" categories: - Eksempel + - Fordelingskomponent - KP - SP elements: |- diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp242.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp242.yml new file mode 100644 index 0000000..ad27ae2 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp242.yml @@ -0,0 +1,358 @@ +langcode: da +status: open +dependencies: + module: + - webform_encrypt + - webform_revisions +third_party_settings: + webform_encrypt: + element: + markup: + encrypt: true + encrypt_profile: webform + ansoeger_fornavn: + encrypt: true + encrypt_profile: webform + ansoeger_mellemnavn: + encrypt: true + encrypt_profile: webform + ansoeger_efternavn: + encrypt: true + encrypt_profile: webform + ansoeger_personnummer: + encrypt: true + encrypt_profile: webform + ansoeger_telefonnummer: + encrypt: true + encrypt_profile: webform + udvidethelbredstillaeg: + encrypt: true + encrypt_profile: webform + sygeforsikring_gruppe: + encrypt: true + encrypt_profile: webform + underskriftsoplysninger_underskrift: + encrypt: true + encrypt_profile: webform + underskriftsoplysninger_underskriftsdato: + encrypt: true + encrypt_profile: webform + samtykke: + encrypt: true + encrypt_profile: webform + erklaering: + encrypt: true + encrypt_profile: webform + fuldmagt_fuldmagtdokumentnavn: + encrypt: true + encrypt_profile: webform + fuldmagt_fuldmagthaverspersonnummer: + encrypt: true + encrypt_profile: webform + dokumenter_overslag: + encrypt: true + encrypt_profile: webform + dokumenter_faktura: + encrypt: true + encrypt_profile: webform + dokumenter_bilag: + encrypt: true + encrypt_profile: webform + kvittering: + encrypt: true + encrypt_profile: webform +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2forms_fdk_kp_sp242 +title: "SP242 (eksempel): Ansøgning om udvidet helbredstillæg" +description: "" +categories: + - Eksempel + - Fordelingskomponent + - KP + - SP +elements: |- + markup: + '#type': webform_markup + '#markup': '

Bygget ud fra https://github.com/itk-dev/os2forms_fordelingskomponent/blob/os2forms_fordelingskomponent/resources/SP/SF2900_XSD/SP242.xsd

Udfyld med testdata

' + ansoeger_fornavn: + '#type': textfield + '#title': Fornavn + '#required': true + '#prepopulate': true + ansoeger_mellemnavn: + '#type': textfield + '#title': Mellemnavn + '#prepopulate': true + ansoeger_efternavn: + '#type': textfield + '#title': Efternavn + '#required': true + '#prepopulate': true + ansoeger_personnummer: + '#type': textfield + '#title': Personnummer + '#required': true + '#pattern': '\d{10}' + '#prepopulate': true + ansoeger_telefonnummer: + '#type': textfield + '#title': Telefonnummer + '#pattern': \d+ + '#prepopulate': true + udvidethelbredstillaeg: + '#type': select + '#title': 'Udvidet helbredstillæg' + '#required': true + '#options': + Tandprotese: Tandprotese + Briller: Briller + Fodbehandling: Fodbehandling + '#prepopulate': true + sygeforsikring_gruppe: + '#type': select + '#title': Gruppe + '#options': + GRUPPE_1: 'Gruppe 1' + GRUPPE_2: 'Gruppe 2' + GRUPPE_5: 'Gruppe 5' + GRUPPE_E: 'Gruppe E' + GRUPPE_N: 'Gruppe N' + GRUPPE_S: 'Gruppe S' + GRUPPE_BASIS: Basis + '#prepopulate': true + underskriftsoplysninger_underskrift: + '#type': textfield + '#title': Underskrift + '#prepopulate': true + underskriftsoplysninger_underskriftsdato: + '#type': date + '#title': Underskriftsdato + '#disabled': true + '#default_value': today + '#prepopulate': true + samtykke: + '#type': checkbox + '#title': Samtykke + '#required': true + '#prepopulate': true + erklaering: + '#type': checkbox + '#title': Erklæring + '#required': true + '#prepopulate': true + fuldmagt_fuldmagtdokumentnavn: + '#type': textfield + '#title': Navn + '#prepopulate': true + fuldmagt_fuldmagthaverspersonnummer: + '#type': textfield + '#pattern': '\d{10}' + '#title': Personnummer + '#prepopulate': true + dokumenter_overslag: + '#type': webform_document_file + '#title': Overslag + dokumenter_faktura: + '#type': webform_document_file + '#title': Faktura + dokumenter_bilag: + '#type': webform_document_file + '#title': Bilag + kvittering: + '#type': os2forms_attachment + '#title': kvittering + '#export_type': pdf + '#digital_signature': 0 + '#excluded_elements': { } + '#exclude_empty': 0 + '#exclude_empty_checkbox': 0 +css: "" +javascript: "" +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: "" + ajax_effect: "" + ajax_speed: null + page: true + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" + form_title: both + form_submit_once: false + form_open_message: "" + form_close_message: "" + form_exception_message: "" + form_previous_submissions: true + form_confidential: false + form_confidential_message: "" + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: "" + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" + share: false + share_node: false + share_theme_name: "" + share_title: true + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" + submission_log: false + submission_excluded_elements: {} + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" + autofill: false + autofill_message: "" + autofill_excluded_elements: {} + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: "" + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: "" + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" + wizard_toggle: false + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" + confirmation_type: page + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} + confirmation_back: true + confirmation_back_label: "" + confirmation_back_attributes: {} + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: "" + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: "" + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: {} + permissions: {} + view_any: + roles: {} + users: {} + permissions: {} + update_any: + roles: {} + users: {} + permissions: {} + delete_any: + roles: {} + users: {} + permissions: {} + purge_any: + roles: {} + users: {} + permissions: {} + view_own: + roles: {} + users: {} + permissions: {} + update_own: + roles: {} + users: {} + permissions: {} + delete_own: + roles: {} + users: {} + permissions: {} + administer: + roles: {} + users: {} + permissions: {} + test: + roles: {} + users: {} + permissions: {} + configuration: + roles: {} + users: {} + permissions: {} +handlers: {} +variants: {} diff --git a/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp246.yml b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp246.yml new file mode 100644 index 0000000..69df990 --- /dev/null +++ b/modules/os2forms_fordelingskomponent_examples/config/install/webform.webform.os2forms_fdk_kp_sp246.yml @@ -0,0 +1,357 @@ +langcode: da +status: open +dependencies: + module: + - webform_encrypt + - webform_revisions +third_party_settings: + webform_encrypt: + element: + aarsag: + encrypt: true + encrypt_profile: webform + aarsag_harbriller_alder: + encrypt: true + encrypt_profile: webform + aarsag_andenbegrundelse: + encrypt: true + encrypt_profile: webform + aarsag_specielleforhold: + encrypt: true + encrypt_profile: webform + overslag_brilleglas: + encrypt: true + encrypt_profile: webform + overslag_brilleglas_venstre_styrke: + encrypt: true + encrypt_profile: webform + overslag_brilleglas_hoejre_styrke: + encrypt: true + encrypt_profile: webform + overslag_brilleglas_pris: + encrypt: true + encrypt_profile: webform + overslag_brillestel: + encrypt: true + encrypt_profile: webform + overslag_brillestel_navn: + encrypt: true + encrypt_profile: webform + overslag_brillestel_pris: + encrypt: true + encrypt_profile: webform + overslag_pris: + encrypt: true + encrypt_profile: webform + computed_total: + encrypt: true + encrypt_profile: webform +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: os2forms_fdk_kp_sp246 +title: os2forms_fdk_kp_sp246 +description: "" +categories: {} +elements: |- + aarsag: + '#type': select + '#title': Årsag + '#options': + HarBriller: HarBriller + AndenBegrundelse: AndenBegrundelse + SpecielleForhold: SpecielleForhold + '#required': true + aarsag_harbriller_alder: + '#type': number + '#title': 'Brillens/kontaktlinsernes alder' + '#required': true + '#states': + visible: + ':input[name="aarsag"]': + value: HarBriller + '#min': 0 + '#max': 100 + aarsag_andenbegrundelse: + '#type': textfield + '#title': 'Anden/yderligere/mere specifik begrundelse' + '#maxlength': 200 + '#required': true + '#states': + visible: + ':input[name="aarsag"]': + value: AndenBegrundelse + aarsag_specielleforhold: + '#type': textfield + '#title': 'Specielle forhold, fx allergi, der goer sig gaeldende for valg af stel/glas (husk laegeerklaering)' + '#maxlength': 200 + '#required': true + '#states': + visible: + ':input[name="aarsag"]': + value: SpecielleForhold + overslag_brilleglas: + '#type': checkbox + '#title': Overslag_Brilleglas + overslag_brilleglas_venstre_styrke: + '#type': textfield + '#title': Overslag_Brilleglas_Venstre_Styrke + '#input_mask': "'alias': 'numeric', 'radixPoint': ',', 'digits': 1, 'digitsOptional': false" + '#required': true + '#states': + visible: + ':input[name="overslag_brilleglas"]': + checked: true + overslag_brilleglas_hoejre_styrke: + '#type': textfield + '#title': Overslag_Brilleglas_Hoejre_Styrke + '#input_mask': "'alias': 'numeric', 'radixPoint': ',', 'digits': 1, 'digitsOptional': false" + '#required': true + '#states': + visible: + ':input[name="overslag_brilleglas"]': + checked: true + overslag_brilleglas_pris: + '#type': textfield + '#title': Overslag_Brilleglas_Pris + '#input_mask': "'alias': 'numeric', 'radixPoint': ',', 'digits': 2, 'digitsOptional': false, 'allowMinus': false" + '#required': true + '#states': + visible: + ':input[name="overslag_brilleglas"]': + checked: true + overslag_brillestel: + '#type': checkbox + '#title': Overslag_Brillestel + overslag_brillestel_navn: + '#type': textfield + '#title': Overslag_Brillestel_Navn + '#required': true + '#states': + visible: + ':input[name="overslag_brillestel"]': + checked: true + overslag_brillestel_pris: + '#type': textfield + '#title': Overslag_Brillestel_Pris + '#input_mask': "'alias': 'numeric', 'radixPoint': ',', 'digits': 2, 'digitsOptional': false, 'allowMinus': false" + '#required': true + '#states': + visible: + ':input[name="overslag_brillestel"]': + checked: true + overslag_pris: + '#type': textfield + '#title': 'I alt' + '#input_mask': "'alias': 'numeric', 'radixPoint': ',', 'digits': 2, 'digitsOptional': false, 'allowMinus': false" + '#required': true + '#states': + visible: + - ':input[name="overslag_brilleglas"]': + checked: true + - or + - ':input[name="overslag_brillestel"]': + checked: true + computed_total: + '#type': webform_computed_twig + '#title': 'Computed total' + '#template': | + {{ [ + (data.overslag_brilleglas_pris)|json_encode, + (0 + data.overslag_brilleglas_pris)|json_encode, + ]|json_encode }} + {{ data.overslag_brillestel_pris|json_encode }} + {% set overslag_brilleglas_pris = data.overslag_brilleglas_pris|default(0)|replace({'.': ''})|replace({',': '.'}) %} + {% set overslag_brillestel_pris = data.overslag_brillestel_pris|default(0)|replace({'.': ''})|replace({',': '.'}) %} + + {{ (overslag_brilleglas_pris + overslag_brillestel_pris)|number_format(2, ',', '') }} + '#store': true + '#ajax': true +css: "" +javascript: "" +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: "" + ajax_effect: "" + ajax_speed: null + page: true + page_submit_path: "" + page_confirm_path: "" + page_theme_name: "" + form_title: both + form_submit_once: false + form_open_message: "" + form_close_message: "" + form_exception_message: "" + form_previous_submissions: true + form_confidential: false + form_confidential_message: "" + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: "" + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: "" + form_access_denied_message: "" + form_access_denied_attributes: {} + form_file_limit: "" + form_attributes: {} + form_method: "" + form_action: "" + share: false + share_node: false + share_theme_name: "" + share_title: true + share_page_body_attributes: {} + submission_label: "" + submission_exception_message: "" + submission_locked_message: "" + submission_log: false + submission_excluded_elements: {} + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: {} + submission_views_replace: {} + submission_user_columns: {} + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: "" + submission_access_denied_message: "" + submission_access_denied_attributes: {} + previous_submission_message: "" + previous_submissions_message: "" + autofill: false + autofill_message: "" + autofill_excluded_elements: {} + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: "" + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: "" + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: "" + wizard_prev_button_label: "" + wizard_next_button_label: "" + wizard_toggle: false + wizard_toggle_show_label: "" + wizard_toggle_hide_label: "" + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: "" + preview_title: "" + preview_message: "" + preview_attributes: {} + preview_excluded_elements: {} + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: "" + draft_loaded_message: "" + draft_pending_single_message: "" + draft_pending_multiple_message: "" + confirmation_type: page + confirmation_url: "" + confirmation_title: "" + confirmation_message: "" + confirmation_attributes: {} + confirmation_back: true + confirmation_back_label: "" + confirmation_back_attributes: {} + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: "" + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: "" + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: {} + permissions: {} + view_any: + roles: {} + users: {} + permissions: {} + update_any: + roles: {} + users: {} + permissions: {} + delete_any: + roles: {} + users: {} + permissions: {} + purge_any: + roles: {} + users: {} + permissions: {} + view_own: + roles: {} + users: {} + permissions: {} + update_own: + roles: {} + users: {} + permissions: {} + delete_own: + roles: {} + users: {} + permissions: {} + administer: + roles: {} + users: {} + permissions: {} + test: + roles: {} + users: {} + permissions: {} + configuration: + roles: {} + users: {} + permissions: {} +handlers: {} +variants: {} From 5c4ff9fdc9a7e87985ef3a64f1f90f7e420d3505 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 6 May 2026 15:23:36 +0200 Subject: [PATCH 60/62] Updated SQL queries --- README.md | 46 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 53de384..c2a0bc5 100644 --- a/README.md +++ b/README.md @@ -132,15 +132,45 @@ XML ## Debugging ``` shell -drush sql:query --extra='--table' "SELECT (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_forsendelse) AS os2forms_fordelingskomponent_anvender_forsendelse, (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_kvittering) AS os2forms_fordelingskomponent_anvender_kvittering" +# Latest receipts +drush sql:query " +SELECT k.anvender_transaktions_id, FROM_UNIXTIME(k.created_at) AS created_at +FROM os2forms_fordelingskomponent_anvender_kvittering AS k +ORDER BY k.created_at DESC +LIMIT 10 +" + +# Latest receipts with submission IDs +drush sql:query " +SELECT k.anvender_transaktions_id, FROM_UNIXTIME(k.created_at) AS created_at, f.webform_id, f.webform_submission_id, +CONCAT('/admin/structure/webform/manage/', f.webform_id, '/submission/', f.webform_submission_id,'/os2forms-fordelingskomponent-debug-forsendelse') AS path +FROM os2forms_fordelingskomponent_anvender_kvittering AS k JOIN os2forms_fordelingskomponent_anvender_forsendelse AS f ON f.anvender_transaktions_id = k.anvender_transaktions_id +ORDER BY k.created_at DESC +LIMIT 10 +" + +drush sql:query " +SELECT + (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_forsendelse) AS os2forms_fordelingskomponent_anvender_forsendelse, + (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_kvittering) AS os2forms_fordelingskomponent_anvender_kvittering +" + # Forsendelser -drush sql:query --extra='--table' "SELECT webform_id, webform_submission_id, anvender_transaktions_id, distribution_transaktions_id FROM os2forms_fordelingskomponent_anvender_forsendelse" +drush sql:query " +SELECT webform_id, webform_submission_id, anvender_transaktions_id, distribution_transaktions_id +FROM os2forms_fordelingskomponent_anvender_forsendelse +" + # Kvitteringer -drush sql:query --extra='--table' "SELECT anvender_transaktions_id, distribution_transaktions_id FROM os2forms_fordelingskomponent_anvender_kvittering" -# Forsendelser med kvitteringer -drush sql:query --extra='--table' "SELECT webform_id, webform_submission_id, anvender_transaktions_id, distribution_transaktions_id FROM os2forms_fordelingskomponent_anvender_forsendelse WHERE anvender_transaktions_id IN (SELECT anvender_transaktions_id FROM os2forms_fordelingskomponent_anvender_kvittering)" -``` +drush sql:query " +SELECT anvender_transaktions_id, distribution_transaktions_id +FROM os2forms_fordelingskomponent_anvender_kvittering +" -``` shell -drush sql:query --extra='--table' "SELECT (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_forsendelse) AS os2forms_fordelingskomponent_anvender_forsendelse, (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_kvittering) AS os2forms_fordelingskomponent_anvender_kvittering;" +# Forsendelser med kvitteringer +drush sql:query " +SELECT webform_id, webform_submission_id, anvender_transaktions_id, distribution_transaktions_id +FROM os2forms_fordelingskomponent_anvender_forsendelse +WHERE anvender_transaktions_id IN (SELECT anvender_transaktions_id FROM os2forms_fordelingskomponent_anvender_kvittering) +" ``` From 506657fcc3abd2db1907064854ed8712c74e5704 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 6 May 2026 16:01:21 +0200 Subject: [PATCH 61/62] =?UTF-8?q?Stopped=20generating=20random=20file=20na?= =?UTF-8?q?mes=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Helper/FordelingskomponentHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helper/FordelingskomponentHelper.php b/src/Helper/FordelingskomponentHelper.php index 881be53..715f76b 100644 --- a/src/Helper/FordelingskomponentHelper.php +++ b/src/Helper/FordelingskomponentHelper.php @@ -397,7 +397,7 @@ private function getSftpFilename( string $filename, ): string { return implode('_', [ - uniqid('os2forms_fordelingskomponent_'), + 'os2forms_fordelingskomponent', $handlerSettings->handlerId, $submission->uuid(), $filename, From 27a6e5ee0e680dd2411f7056c4b69b870c72bfcb Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 6 May 2026 16:35:21 +0200 Subject: [PATCH 62/62] Added SQL query --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index c2a0bc5..a723888 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,15 @@ ORDER BY k.created_at DESC LIMIT 10 " +# "Forsendelser" with number of receipts +drush sql:query " +SELECT f.anvender_transaktions_id, FROM_UNIXTIME(f.created_at) AS created_at, +(SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_kvittering AS k WHERE k.anvender_transaktions_id = f.anvender_transaktions_id) AS '#receipts', +CONCAT('/admin/structure/webform/manage/', f.webform_id, '/submission/', f.webform_submission_id,'/os2forms-fordelingskomponent-debug-forsendelse') AS path +FROM os2forms_fordelingskomponent_anvender_forsendelse AS f +ORDER BY created_at DESC +" + drush sql:query " SELECT (SELECT COUNT(*) FROM os2forms_fordelingskomponent_anvender_forsendelse) AS os2forms_fordelingskomponent_anvender_forsendelse,