diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 194b59f3..00382b62 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,17 +23,24 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + + - name: Mark repo as safe for git (container) + run: | + docker compose run --rm --user 0 php bash -lc \ + 'git config --system --add safe.directory /app || git config --global --add safe.directory /app' + + - name: Fix ownership (container) + run: | + docker compose run --rm --user 0 -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) php bash -lc 'chown -R ${HOST_UID}:${HOST_GID} /app' + - name: Validate composer files run: | - # Note that we don't use the --strict flag on validate due to the - # package drupal/config_entity_revisions 2.0.x-dev being considered a - # version cf. - # https://getcomposer.org/doc/articles/versions.md#branches - docker compose run --rm php composer validate composer.json + docker compose run --rm --user "$(id -u):$(id -g)" php composer validate composer.json + - name: Check that composer file is normalized run: | - docker compose run --rm php composer install - docker compose run --rm php composer normalize --dry-run + docker compose run --rm --user "$(id -u):$(id -g)" php composer install + docker compose run --rm --user "$(id -u):$(id -g)" php composer normalize --dry-run test-composer-files: name: Test composer files @@ -43,29 +50,50 @@ jobs: dependency-version: [ prefer-lowest, prefer-stable ] steps: - uses: actions/checkout@v4 + + - name: Fix ownership (container) + run: | + docker compose run --rm --user 0 -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) php bash -lc 'chown -R ${HOST_UID}:${HOST_GID} /app' + + - name: Debug compose + php + run: | + ls -la + docker compose config + docker compose run --rm php php -v + - name: Check that dependencies resolve. run: | - # Clean up before update (cf. https://www.drupal.org/project/simplesamlphp_auth/issues/3350773) rm -fr vendor/ - docker compose run --rm php composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + docker compose run --rm --user "$(id -u):$(id -g)" php composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction php-coding-standards: name: PHP coding standards runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + + - name: Fix ownership (container) + run: | + docker compose run --rm --user 0 -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) php bash -lc 'chown -R ${HOST_UID}:${HOST_GID} /app' + - name: Install Dependencies run: | - docker compose run --rm php composer install + docker compose run --rm --user "$(id -u):$(id -g)" php composer install + - name: PHPCS run: | - docker compose run --rm php composer coding-standards-check/phpcs + docker compose run --rm --user "$(id -u):$(id -g)" php composer coding-standards-check/phpcs php-code-analysis: name: PHP code analysis runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + + - name: Fix ownership (container) + run: | + docker compose run --rm --user 0 -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) php bash -lc 'chown -R ${HOST_UID}:${HOST_GID} /app' + - name: Code analysis run: | ./scripts/code-analysis diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f3e756..b0609571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,32 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +## [5.1.0] 2026-06-03 + +- [PR-326](https://github.com/OS2Forms/os2forms/pull/326) + Updating ckeditor -> ckeditor5. +- [PR-322](https://github.com/OS2Forms/os2forms/pull/322) + Update Digital Post handler error messages. +- [PR-320](https://github.com/OS2Forms/os2forms/pull/320) + Update README. +- [PR-301](https://github.com/OS2Forms/os2forms/pull/301) + Add address information to Digital Post shipments to ensure "*fjernprint*" + can be sent. +- Add option to add return address to Digital Post shipments. +- [PR-305](https://github.com/OS2Forms/os2forms/pull/305) + Fix IP validation in digital signature file download (CIDR support) +- [PR-317](https://github.com/OS2Forms/os2forms/pull/317) + Updated code analysis script. +- [PR-306](https://github.com/OS2Forms/os2forms/pull/306) + Made digital signature text placement configurable. +- git actions check +- [PR-289](https://github.com/OS2Forms/os2forms/pull/289) + Added required "Zoom control position" to map element +- [#246](https://github.com/OS2Forms/os2forms/issues/246) + Adding Datafordeler address lookup +- [#251](https://github.com/OS2Forms/os2forms/issues/251) + Updated Webform encrypt uninstall problem fix + ## [5.0.0] 2025-11-18 - [PR-192](https://github.com/OS2Forms/os2forms/pull/192) @@ -406,7 +432,8 @@ f/OS-115_dawa_address - Security in case of vulnerabilities. ``` -[Unreleased]: https://github.com/OS2Forms/os2forms/compare/4.1.0...HEAD +[Unreleased]: https://github.com/OS2Forms/os2forms/compare/5.0.0...HEAD +[5.0.0]: https://github.com/OS2Forms/os2forms/compare/4.1.0...5.0.0 [4.1.0]: https://github.com/OS2Forms/os2forms/compare/4.0.0...4.1.0 [4.0.0]: https://github.com/OS2Forms/os2forms/compare/3.22.2...4.0.0 [3.22.2]: https://github.com/OS2Forms/os2forms/compare/3.22.1...3.22.2 diff --git a/README.md b/README.md index f4a52e28..13ff01fb 100644 --- a/README.md +++ b/README.md @@ -2,42 +2,31 @@ ## Install -OS2Forms Drupal 8 module is available to download via composer. +OS2Forms Drupal 10 module is available to download via composer. ```sh composer require os2forms/os2forms -drush en os2forms +drush pm:install os2forms ``` If you don't have Drupal installed on you server, you will to need install it first. -Read more about [how to install drupal core](https://www.drupal.org/docs/8/install). +Read more about [how to install drupal core](https://www.drupal.org/docs/getting-started/installing-drupal). -We are recommending to install drupal via composer by using -[OS2Forms composer project](https://github.com/OS2Forms/composer-project). -By this way you will get standalone project with OS2Forms module on board, plus -all the other contrib modules you will probably need to configure OS2Forms to -your specific demands. - -```sh -composer create-project os2forms/composer-project:8.x-dev some-dir --no-interaction -``` - -To get more benefits on your Drupal project we are offering you to use +To get more benefits on your Drupal project we recommend you use [OS2web](https://packagist.org/packages/os2web/os2web) as installation -profile for Drupal. This profile is a part of OS2Forms composer project -mentioned above. +profile for Drupal. You can easy download and install OS2web installation profile to your composer based Drupal project with commands: ```sh composer require os2web/os2web -drush si os2web --db-url=mysql://db_user:db_pass@mysql_host/db_name --locale=da --site-name="OS2Forms" --account-pass=admin -y +drush site:install os2web --db-url=mysql://db_user:db_pass@mysql_host/db_name --locale=da --site-name="OS2Forms" --account-pass=admin -y ``` ## Update -Updating process for OS2forms module is similar to usual Drupal 8 module. +Updating process for OS2forms module is similar to usual Drupal 10 module. Use Composer's built-in command for listing packages that have updates available: ```sh @@ -46,53 +35,62 @@ composer outdated os2forms/os2forms ## Automated testing and code quality -See [OS2Forms testing and CI information](https://github.com/OS2Forms/docs#testing-and-ci) +See [OS2Forms testing and CI information](https://os2forms.github.io/os2forms-docs/for-developers.html#testing-and-ci) ## Contribution -OS2Forms project is opened for new features and os course bugfixes. -If you have any suggestion or you found a bug in project, you are very welcome -to create an issue in github repository issue tracker. -For issue description there is expected that you will provide clear and -sufficient information about your feature request or bug report. +The OS2Forms project is open for new features and bugfixes. If you have any +suggestion, or you found a bug in the project, you are very welcome to create +an issue in github repository issue tracker. For issue description was ask +that you will provide clear and sufficient information about your feature +request or bug report. ### Code review policy -See [OS2Forms code review policy](https://github.com/OS2Forms/docs#code-review) +See [OS2Forms code review policy](https://os2forms.github.io/os2forms-docs/for-developers.html#code-review) ### Git name convention -See [OS2Forms git name convention](https://github.com/OS2Forms/docs#git-guideline) +See [OS2Forms git name convention](https://os2forms.github.io/os2forms-docs/for-developers.html#git-guideline) ## Important notes ### Webforms -Each webform, including all its settings, is stored as configuration in db and -will(could) be exported as `yml` file via Drupal configuration management -system. And afterwards could be tracked by `git`. +Each webform, along with all its settings, is stored as configuration in the +database and can be exported as a `yml` file through Drupal's configuration +management system, making it trackable via `git`. -It means that all webform settings from drupal database will -be syncronized (exported/imported) with state stored in `yml` files from -configuration folder stored in git repository. Without proper actions webforms -could be deleted or reverted to state in `yml` during synchronization. +This means that webform settings in the Drupal database will be synchronized +(exported/imported) with the state defined in `yml` files located in the +configuration folder of your git repository. Without taking the appropriate +precautions, webforms may be deleted or reverted to the state captured in +those `yml` files during synchronization. -To avoid/prevent this behavior we recommend use `Config ignore` module, where -you can add all settings you do not want to export/import via configuration -management system. +To prevent this, we recommend using the +`Config ignore`-[module](https://www.drupal.org/project/config_ignore), which +allows you to exclude specific settings from the configuration management +export/import process. ### Serviceplatformen plugins -Settings for CPR and CVR serviceplantormen plugins are storing as configuration -in db and will(could) be exported as `yml` file via Drupal configuration -management system. And afterwards could be tracked by `git`. +Similar to webforms, settings for the CPR and CVR Serviceplatformen plugins +are stored as configuration in the database and can be exported as `yml` files +through Drupal's configuration management system, making them trackable via +`git`. + +Note that if your git repository is publicly accessible, these plugin settings +— which may contain sensitive information — will be exposed. As with webforms, +we recommend using the `Config ignore`-module to exclude them from the +export/import process. -If case you have public access to your git repository all setting from plugins -will be exposed for third persons. +### Other configuration -To avoid/prevent this behavior we recommend use `Config ignore` module, where -you can add all settings you do not want to export/import via configuration -management system. +The two cases above are just some examples of configuration that may be +sensitive or subject to unintended changes during synchronization. In general, +any configuration that is environment-specific, contains sensitive data, or is +managed directly in the database rather than through code should be considered +for exclusion via the `Config ignore`-module. ## Unstable features @@ -148,11 +146,12 @@ docker compose run --rm markdownlint markdownlint '**/*.md' We use [PHPStan](https://phpstan.org/) for static code analysis. -Running statis code analysis on a standalone Drupal module is a bit tricky, so we use a helper script to run the +Running static code analysis on a standalone Drupal module is a bit tricky, so we use a helper script to run the analysis: ```shell docker compose run --rm php ./scripts/code-analysis ``` -**Note**: Currently the code analysis is only run on the `os2forms_digital_post` sub-module (cf. [`phpstan.neon`](./phpstan.neon)). +**Note**: Currently the code analysis is only run on the `os2forms_digital_post` and `os2forms_fbs_handler` sub-modules +(cf. [`phpstan.neon`](./phpstan.neon)). diff --git a/compose.yaml b/compose.yaml index d606c09c..a64bf5fb 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,12 +2,22 @@ services: php: - image: ddev/ddev-php-base + image: ddev/ddev-php-base:v1.24.10-old profiles: - dev working_dir: /app volumes: - ./:/app + environment: + COMPOSER_HOME: /tmp/composer + COMPOSER_CACHE_DIR: /tmp/composer-cache + entrypoint: + - bash + - -lc + - | + git config --global --add safe.directory /app + exec "$@" + - bash markdownlint: image: itkdev/markdownlint diff --git a/composer.json b/composer.json index 17968e2f..ba5f18b2 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "os2forms/os2forms", - "description": "Drupal 8 OS2Form module provides advanced webform functionality for Danish Municipalities", + "description": "Drupal 10 OS2Form module provides advanced webform functionality for Danish Municipalities", "license": "EUPL-1.2", "type": "drupal-module", "require": { @@ -128,7 +128,7 @@ "Add custom hook (hook_webform_post_load_data) for audit logging": "https://gist.githubusercontent.com/cableman/d26898fc8f65ee0a31001bf391583b59/raw/6189dc4c2ceaabb19d25cc4b98b0b3028a6b0e1e/gistfile1.txt" }, "drupal/webform_encrypt": { - "Ensure data is base64 encoded (https://www.drupal.org/project/webform_encrypt/issues/3399414)": "https://git.drupalcode.org/project/webform_encrypt/-/merge_requests/4.patch", + "Ensure data is base64 encoded (https://www.drupal.org/project/webform_encrypt/issues/3399414)": "https://www.drupal.org/files/issues/2026-02-06/patch.diff", "PHP Warning if unserialize fails (https://www.drupal.org/project/webform_encrypt/issues/3292305)": "https://www.drupal.org/files/issues/2022-06-23/unserialize-php-notice.patch" }, "drupal/webform_node_element": { diff --git a/modules/os2forms_attachment/src/Element/AttachmentElement.php b/modules/os2forms_attachment/src/Element/AttachmentElement.php index cdf3ae44..bbc33f0b 100644 --- a/modules/os2forms_attachment/src/Element/AttachmentElement.php +++ b/modules/os2forms_attachment/src/Element/AttachmentElement.php @@ -2,6 +2,7 @@ namespace Drupal\os2forms_attachment\Element; +use Drupal\os2forms_attachment\Os2formsAttachmentPrintBuilder; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform_attachment\Element\WebformAttachmentBase; @@ -21,6 +22,7 @@ public function getInfo() { '#view_mode' => 'html', '#export_type' => 'pdf', '#digital_signature' => FALSE, + '#digital_signature_position' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT, '#template' => '', ]; } @@ -77,7 +79,8 @@ public static function getFileContent(array $element, WebformSubmissionInterface // Adding digital signature. if (isset($element['#digital_signature']) && $element['#digital_signature']) { - $file_path = $print_builder->savePrintableDigitalSignature([$webform_submission], $print_engine, $scheme, $file_name); + $signaturePosition = $element['#digital_signature_position'] ?? Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT; + $file_path = $print_builder->savePrintableDigitalSignature([$webform_submission], $print_engine, $scheme, $file_name, TRUE, $signaturePosition); } else { $file_path = $print_builder->savePrintable([$webform_submission], $print_engine, $scheme, $file_name); diff --git a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php index 1f8a9f38..c72f098c 100644 --- a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php +++ b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php @@ -18,6 +18,11 @@ */ class Os2formsAttachmentPrintBuilder extends PrintBuilder { + public const SIGNATURE_POSITION_FOOTER = 'footer'; + public const SIGNATURE_POSITION_HEADER = 'header'; + public const SIGNATURE_POSITION_AFTER_CONTENT = 'after_content'; + public const SIGNATURE_POSITION_BEFORE_CONTENT = 'before_content'; + /** * {@inheritdoc} */ @@ -44,16 +49,16 @@ public function printHtml(EntityInterface $entity, $use_default_css = TRUE, $opt /** * Modified version of the original savePrintable() function. * - * The only difference is modified call to prepareRenderer with digitalPost - * flag TRUE. + * The only difference is modified call to prepareRenderer with a + * signature position parameter. * * @see PrintBuilder::savePrintable() * * @return string * FALSE or the URI to the file. E.g. public://my-file.pdf. */ - public function savePrintableDigitalSignature(array $entities, PrintEngineInterface $print_engine, $scheme = 'public', $filename = FALSE, $use_default_css = TRUE) { - $renderer = $this->prepareRenderer($entities, $print_engine, $use_default_css, TRUE); + public function savePrintableDigitalSignature(array $entities, PrintEngineInterface $print_engine, $scheme = 'public', $filename = FALSE, $use_default_css = TRUE, string $signaturePosition = self::SIGNATURE_POSITION_AFTER_CONTENT) { + $renderer = $this->prepareRenderer($entities, $print_engine, $use_default_css, $signaturePosition); // Allow other modules to alter the generated Print object. $this->dispatcher->dispatch(new PreSendPrintEvent($print_engine, $entities), PrintEvents::PRE_SEND); @@ -82,15 +87,16 @@ public function savePrintableDigitalSignature(array $entities, PrintEngineInterf * The print engine. * @param bool $use_default_css * TRUE if we want the default CSS included. - * @param bool $digitalSignature - * If the digital signature message needs to be added. + * @param string $signaturePosition + * The position for the digital signature validation text. Empty string + * means no signature. Use the SIGNATURE_POSITION_* class constants. * * @return \Drupal\entity_print\Renderer\RendererInterface * A print renderer. * * @see PrintBuilder::prepareRenderer */ - protected function prepareRenderer(array $entities, PrintEngineInterface $print_engine, $use_default_css, $digitalSignature = FALSE) { + protected function prepareRenderer(array $entities, PrintEngineInterface $print_engine, $use_default_css, string $signaturePosition = '') { if (empty($entities)) { throw new \InvalidArgumentException('You must pass at least 1 entity'); } @@ -106,12 +112,30 @@ protected function prepareRenderer(array $entities, PrintEngineInterface $print_ '#attached' => [], ]; - // Adding hardcoded negative margin to avoid margins in
- // structure. That margin is automatically added in PDF and PDF only. $generatedHtml = (string) $renderer->generateHtml($entities, $render, $use_default_css, TRUE); - $generatedHtml .= ""; - if ($digitalSignature) { - $generatedHtml .= $this->t('You can validate the signature on this PDF file via validering.nemlog-in.dk.'); + + // Place signature according to the passed parameter. + if ($signaturePosition) { + $signatureHtml = '

' . $this->t('You can validate the signature on this PDF file via validering.nemlog-in.dk.') . '

'; + + switch ($signaturePosition) { + case self::SIGNATURE_POSITION_HEADER: + $generatedHtml = str_replace('', $signatureHtml . '', $generatedHtml); + break; + + case self::SIGNATURE_POSITION_BEFORE_CONTENT: + $generatedHtml = str_replace('
', '
' . $signatureHtml, $generatedHtml); + break; + + case self::SIGNATURE_POSITION_FOOTER: + $generatedHtml = str_replace('', $signatureHtml . '', $generatedHtml); + break; + + case self::SIGNATURE_POSITION_AFTER_CONTENT: + default: + $generatedHtml = str_replace('', $signatureHtml . '', $generatedHtml); + break; + } } $print_engine->addPage($generatedHtml); diff --git a/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php b/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php index 7ec580bd..e19c531a 100644 --- a/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php +++ b/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php @@ -5,6 +5,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Twig\WebformTwigExtension; use Drupal\webform\Utility\WebformElementHelper; +use Drupal\os2forms_attachment\Os2formsAttachmentPrintBuilder; use Drupal\webform_attachment\Plugin\WebformElement\WebformAttachmentBase; /** @@ -28,6 +29,7 @@ protected function defineDefaultProperties() { 'template' => '', 'export_type' => '', 'digital_signature' => '', + 'digital_signature_position' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT, 'exclude_empty' => '', 'exclude_empty_checkbox' => '', 'excluded_elements' => '', @@ -93,6 +95,23 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'checkbox', '#title' => $this->t('Digital signature'), ]; + $form['attachment']['digital_signature_position'] = [ + '#type' => 'select', + '#title' => $this->t('Digital signature position'), + '#description' => $this->t('Select where the digital signature validation text should be placed in the PDF document.'), + '#options' => [ + Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_FOOTER => $this->t('Footer (repeats on every page)'), + Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_HEADER => $this->t('Header (repeats on every page)'), + Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT => $this->t('After content (end of document)'), + Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_BEFORE_CONTENT => $this->t('Before content (start of document)'), + ], + '#default_value' => Os2formsAttachmentPrintBuilder::SIGNATURE_POSITION_AFTER_CONTENT, + '#states' => [ + 'visible' => [ + ':input[name="properties[digital_signature]"]' => ['checked' => TRUE], + ], + ], + ]; // Set #access so that help is always visible. WebformElementHelper::setPropertyRecursive($form['attachment']['help'], '#access', TRUE); diff --git a/modules/os2forms_dawa/js/dawa_address_autocomplete.js b/modules/os2forms_dawa/js/dawa_address_autocomplete.js new file mode 100644 index 00000000..12d0b053 --- /dev/null +++ b/modules/os2forms_dawa/js/dawa_address_autocomplete.js @@ -0,0 +1,17 @@ +(function ($, Drupal) { + // Overrides splitValues() function found in core/misc/autocomplete.js for + // DAWA elements. Danish addresses contain commas. Original splitValues() + // method splits upon commas. Combined with default extractLastTerm this + // results in unexpected behavior when using autocomplete with respect to + // danish addresses. Inspired by + // https://www.drupal.org/project/drupal/issues/2821181#comment-12012538. + Drupal.autocomplete.splitValues = function (value) { + // Check if the current autocomplete is for a focused DAWA address field + if ($('.ui-autocomplete-input:focus').closest('.os2forms-dawa-address').length) { + // For DAWA address fields, return the entire value as a single value + return [ value.trim() ]; + } + // For other fields, use the original behavior + return Drupal.autocomplete.splitValues(value); + }; +})(jQuery, Drupal); diff --git a/modules/os2forms_dawa/os2forms_dawa.libraries.yml b/modules/os2forms_dawa/os2forms_dawa.libraries.yml new file mode 100644 index 00000000..d2cf517b --- /dev/null +++ b/modules/os2forms_dawa/os2forms_dawa.libraries.yml @@ -0,0 +1,7 @@ +dawa_address_autocomplete: + version: 1.x + js: + js/dawa_address_autocomplete.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/modules/os2forms_dawa/os2forms_dawa.module b/modules/os2forms_dawa/os2forms_dawa.module index b6644c4d..f851fd9c 100644 --- a/modules/os2forms_dawa/os2forms_dawa.module +++ b/modules/os2forms_dawa/os2forms_dawa.module @@ -5,6 +5,8 @@ * OS2Forms Address autocomplete functionality module. */ +use Drupal\Core\Form\FormStateInterface; + /** * Implements hook_webform_migrate_d7_webform_element_ELEMENT_TYPE_alter(). * @@ -26,3 +28,12 @@ function os2forms_dawa_webform_migrate_d7_webform_element_address_autocomp_alter function os2forms_dawa_webform_migrate_d7_webform_element_addrs_autocomp_l_alter(&$markup, $indent, array $element) { $markup .= "$indent '#type': os2forms_dawa_address_matrikula\n"; } + +/** + * Implements hook_webform_element_alter(). + */ +function os2forms_dawa_webform_element_alter(array &$element, FormStateInterface $form_state, array $context) { + if ('os2forms_dawa_address' === $element['#type']) { + $element['#attached']['library'][] = 'os2forms_dawa/dawa_address_autocomplete'; + } +} diff --git a/modules/os2forms_dawa/os2forms_dawa.services.yml b/modules/os2forms_dawa/os2forms_dawa.services.yml deleted file mode 100644 index c8c63c95..00000000 --- a/modules/os2forms_dawa/os2forms_dawa.services.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - os2forms_dawa.service: - class: Drupal\os2forms_dawa\Service\DawaService - arguments: ['@plugin.manager.os2web_datalookup'] diff --git a/modules/os2forms_dawa/src/Controller/DawaElementController.php b/modules/os2forms_dawa/src/Controller/DawaElementController.php index 93787e68..5f513686 100644 --- a/modules/os2forms_dawa/src/Controller/DawaElementController.php +++ b/modules/os2forms_dawa/src/Controller/DawaElementController.php @@ -3,7 +3,7 @@ namespace Drupal\os2forms_dawa\Controller; use Drupal\Core\Controller\ControllerBase; -use Drupal\os2forms_dawa\Service\DawaService; +use Drupal\os2web_datalookup\Plugin\DataLookupManager; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -14,20 +14,20 @@ class DawaElementController extends ControllerBase { /** - * The DAWA service object. + * Datafordeler address lookup. * - * @var \Drupal\os2forms_dawa\Service\DawaService + * @var \Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DatafordelerAddressLookupInterface */ - protected $dawaService; + protected $datafordelerAddressLookup; /** * Constructs a DawaElementController object. * - * @param \Drupal\os2forms_dawa\Service\DawaService $os2forms_dawa_service - * The DAWA service object. + * @param \Drupal\os2web_datalookup\Plugin\DataLookupManager $dataLookupManager + * Datalookup manager. */ - public function __construct(DawaService $os2forms_dawa_service) { - $this->dawaService = $os2forms_dawa_service; + public function __construct(DataLookupManager $dataLookupManager) { + $this->datafordelerAddressLookup = $dataLookupManager->createInstance('datafordeler_address_lookup'); } /** @@ -35,7 +35,7 @@ public function __construct(DawaService $os2forms_dawa_service) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('os2forms_dawa.service') + $container->get('plugin.manager.os2web_datalookup') ); } @@ -61,18 +61,8 @@ public function autocomplete(Request $request, $element_type) { $matches = []; // Get the matches based on the element type. - switch ($element_type) { - case 'os2forms_dawa_address': - $matches = $this->dawaService->getAddressMatches($query); - break; - - case 'os2forms_dawa_block': - $matches = $this->dawaService->getBlockMatches($query); - break; - - case 'os2forms_dawa_matrikula': - $matches = $this->dawaService->getMatrikulaMatches($query); - break; + if ($element_type == 'os2forms_dawa_address') { + $matches = $this->datafordelerAddressLookup->getAddressMatches($query); } return new JsonResponse($matches); diff --git a/modules/os2forms_dawa/src/Element/DawaElementAddressMatrikula.php b/modules/os2forms_dawa/src/Element/DawaElementAddressMatrikula.php index fdfc0ea9..bd6d3167 100644 --- a/modules/os2forms_dawa/src/Element/DawaElementAddressMatrikula.php +++ b/modules/os2forms_dawa/src/Element/DawaElementAddressMatrikula.php @@ -96,11 +96,14 @@ public static function getCompositeElements(array $element) { private static function getMatrikulaOptions($addressValue, array $element) { $options = []; - /** @var \Drupal\os2forms_dawa\Service\DawaService $dawaService */ - $dawaService = \Drupal::service('os2forms_dawa.service'); + /** @var \Drupal\os2web_datalookup\Plugin\DataLookupManager $datalookupManager */ + $datalookupManager = \Drupal::service('plugin.manager.os2web_datalookup'); - /** @var \Drupal\os2forms_dawa\Plugin\os2web\DataLookup\DatafordelerDataLookupInterface $datafordelerLookup */ - $datafordelerLookup = \Drupal::service('plugin.manager.os2web_datalookup')->createInstance('datafordeler_data_lookup'); + /** @var \Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DatafordelerAddressLookupInterface $addressLookup */ + $addressLookup = $datalookupManager->createInstance('datafordeler_address_lookup'); + + /** @var \Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DatafordelerMatrikulaLookupInterface $matrikulaLookup */ + $matrikulaLookup = $datalookupManager->createInstance('datafordeler_matrikula_lookup'); // Getting address. $addressParams = new ParameterBag(); @@ -108,17 +111,17 @@ private static function getMatrikulaOptions($addressValue, array $element) { if (isset($element['#limit_by_municipality'])) { $addressParams->set('limit_by_municipality', $element['#limit_by_municipality']); } - $address = $dawaService->getSingleAddress($addressParams); + $address = $addressLookup->getSingleAddress($addressParams); if ($address) { - $addressAccessId = $address->getAccessAddressId(); + $addressHouseId = $address->getHouseId(); // Find matrikula list from the houseid (husnummer): - $matrikulaId = $datafordelerLookup->getMatrikulaId($addressAccessId); + $matrikulaId = $matrikulaLookup->getMatrikulaId($addressHouseId); // Find Matrikula entries from matrikulas ID. if ($matrikulaId) { - $matrikulaEnties = $datafordelerLookup->getMatrikulaEntries($matrikulaId); + $matrikulaEnties = $matrikulaLookup->getMatrikulaEntries($matrikulaId); foreach ($matrikulaEnties as $matrikula) { $matrikulaOption = $matrikula->getMatrikulaNumber() . ' ' . $matrikula->getOwnershipName(); diff --git a/modules/os2forms_dawa/src/Element/DawaElementBase.php b/modules/os2forms_dawa/src/Element/DawaElementBase.php index 6c723e47..b6e57453 100644 --- a/modules/os2forms_dawa/src/Element/DawaElementBase.php +++ b/modules/os2forms_dawa/src/Element/DawaElementBase.php @@ -34,26 +34,15 @@ public static function validateDawaElementBase(&$element, FormStateInterface $fo } if (!empty($value)) { - /** @var \Drupal\os2forms_dawa\Service\DawaService $dawaService*/ - $dawaService = \Drupal::service('os2forms_dawa.service'); + $matches = []; - $element_type = $element['#type']; + if ($element['#type'] == 'os2forms_dawa_address') { + /** @var \Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DatafordelerAddressLookupInterface $datafordelerAddressLookup */ + $datafordelerAddressLookup = \Drupal::service('plugin.manager.os2web_datalookup')->createInstance('datafordeler_address_lookup'); - $parameters = new ParameterBag($element['#autocomplete_route_parameters']); - $parameters->set('q', $value); - - switch ($element_type) { - case 'os2forms_dawa_address': - $matches = $dawaService->getAddressMatches($parameters); - break; - - case 'os2forms_dawa_block': - $matches = $dawaService->getBlockMatches($parameters); - break; - - case 'os2forms_dawa_matrikula': - $matches = $dawaService->getMatrikulaMatches($parameters); - break; + $parameters = new ParameterBag($element['#autocomplete_route_parameters']); + $parameters->set('q', $value); + $matches = $datafordelerAddressLookup->getAddressMatches($parameters); } // Checking if the current value is within the list of the values from an diff --git a/modules/os2forms_dawa/src/Entity/DatafordelerMatrikula.php b/modules/os2forms_dawa/src/Entity/DatafordelerMatrikula.php deleted file mode 100644 index 41be22ac..00000000 --- a/modules/os2forms_dawa/src/Entity/DatafordelerMatrikula.php +++ /dev/null @@ -1,79 +0,0 @@ -ownerLicenseCode = $json['properties']['ejerlavskode']; - $this->ownershipName = $json['properties']['ejerlavsnavn']; - $this->matrikulaNumber = $json['properties']['matrikelnummer']; - } - - /** - * Returns owner licence code. - * - * @return string - * Owners licence code. - */ - public function getOwnerLicenseCode(): string { - return $this->ownerLicenseCode; - } - - /** - * Returns ownership name. - * - * @return string - * ownership name. - */ - public function getOwnershipName(): string { - return $this->ownershipName; - } - - /** - * Returns makrikula number. - * - * @return string - * Matrikula number - */ - public function getMatrikulaNumber(): string { - return $this->matrikulaNumber; - } - -} diff --git a/modules/os2forms_dawa/src/Entity/DawaAddress.php b/modules/os2forms_dawa/src/Entity/DawaAddress.php deleted file mode 100644 index 89fe92a7..00000000 --- a/modules/os2forms_dawa/src/Entity/DawaAddress.php +++ /dev/null @@ -1,135 +0,0 @@ -id = $json['id']; - - if (isset($json['adgangsadresse']) && is_array($json['adgangsadresse'])) { - $this->municipalityCode = $json['adgangsadresse']['kommune']['kode']; - $this->propertyNumber = $json['adgangsadresse']['esrejendomsnr']; - $this->longitude = $json['adgangsadresse']['adgangspunkt']['koordinater'][0]; - $this->latitude = $json['adgangsadresse']['adgangspunkt']['koordinater'][1]; - $this->accessAddressId = $json['adgangsadresse']['id']; - } - } - - /** - * Gets address ID. - * - * @return string - * ID of the address. - */ - public function id() { - return $this->id; - } - - /** - * Gets municipality code. - * - * @return string - * Municipality code of the address. - */ - public function getMunicipalityCode() { - return $this->municipalityCode; - } - - /** - * Gets property number. - * - * @return string - * property number of the address. - */ - public function getPropertyNumber() { - return $this->propertyNumber; - } - - /** - * Gets latitude. - * - * @return float - * property latitude. - */ - public function getLatitude() { - return $this->latitude; - } - - /** - * Gets longitude. - * - * @return float - * property longitude. - */ - public function getLongitude() { - return $this->longitude; - } - - /** - * Gets Address access ID. - * - * @return string - * Address access ID. - */ - public function getAccessAddressId() { - return $this->accessAddressId; - } - -} diff --git a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddress.php b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddress.php index 93f809da..21a7779d 100644 --- a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddress.php +++ b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddress.php @@ -51,9 +51,9 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['autocomplete']['limit_by_municipality'] = [ '#type' => 'textfield', - '#title' => $this->t('Limit by municipality (-es)'), - '#pattern' => '^(\d{3},?)*$', - '#description' => $this->t('CSV list of municipalities codes, what will limit the address lookup.'), + '#title' => $this->t('Limit by municipality'), + '#pattern' => '^(\d{4})$', + '#description' => $this->t('Municipality code, what will limit the address lookup. Single number, 4 digits, e.g. 0661'), ]; return $form; diff --git a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddressMatrikula.php b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddressMatrikula.php index 5d211db0..f7d770d2 100644 --- a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddressMatrikula.php +++ b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementAddressMatrikula.php @@ -76,9 +76,9 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['autocomplete']['limit_by_municipality'] = [ '#type' => 'textfield', - '#title' => $this->t('Limit by municipality (-es)'), - '#pattern' => '^(\d{3},?)*$', - '#description' => $this->t('CSV list of municipalities codes, what will limit the address lookup.'), + '#title' => $this->t('Limit by municipality'), + '#pattern' => '^(\d{4})$', + '#description' => $this->t('Municipality code, what will limit the address lookup. Single number, 4 digits, e.g. 0661'), ]; return $form; diff --git a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementBlock.php b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementBlock.php index cb0b832f..45a20502 100644 --- a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementBlock.php +++ b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementBlock.php @@ -10,8 +10,8 @@ * * @WebformElement( * id = "os2forms_dawa_block", - * label = @Translation("DAWA Block (autocomplete)"), - * description = @Translation("Provides a DAWA Block Autocomplete element."), + * label = @Translation("DAWA Block (autocomplete) - DEPRECATED"), + * description = @Translation("Provides a DAWA Block Autocomplete element. This element is deprecated due to the API being phased out. There is no provided alternative for this element."), * category = @Translation("DAWA"), * ) */ diff --git a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementMatrikula.php b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementMatrikula.php index 34654769..464a45aa 100644 --- a/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementMatrikula.php +++ b/modules/os2forms_dawa/src/Plugin/WebformElement/DawaElementMatrikula.php @@ -10,8 +10,8 @@ * * @WebformElement( * id = "os2forms_dawa_matrikula", - * label = @Translation("DAWA Matrikula (autocomplete)"), - * description = @Translation("Provides a DAWA Matrikula Autocomplete element."), + * label = @Translation("DAWA Matrikula (autocomplete) - DEPRECATED"), + * description = @Translation("Provides a DAWA Matrikula Autocomplete element. This element is deprecated due to the API being phased out. There is no provided alternative for this element."), * category = @Translation("DAWA"), * ) */ diff --git a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php deleted file mode 100644 index 00df83a3..00000000 --- a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php +++ /dev/null @@ -1,162 +0,0 @@ -get('os2web_audit.logger'); - /** @var \Drupal\key\KeyRepositoryInterface $keyRepository */ - $keyRepository = $container->get('key.repository'); - /** @var \Drupal\Core\File\FileSystem $fileSystem */ - $fileSystem = $container->get('file_system'); - - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('http_client'), - $auditLogger, - $keyRepository, - $fileSystem, - ); - } - - /** - * {@inheritdoc} - */ - public function getMatrikulaId(string $addressAccessId) : ?string { - $url = "https://services.datafordeler.dk/DAR/DAR/3.0.0/rest/husnummerTilJordstykke"; - - $json = $this->httpClient->request('GET', $url, [ - 'query' => [ - 'husnummerid' => $addressAccessId, - ], - ])->getBody(); - - $jsonDecoded = json_decode($json, TRUE); - if (is_array($jsonDecoded)) { - if (NestedArray::keyExists($jsonDecoded, ['gældendeJordstykke', 'jordstykkeLokalId'])) { - return NestedArray::getValue($jsonDecoded, ['gældendeJordstykke', 'jordstykkeLokalId']); - } - } - - return NULL; - } - - /** - * {@inheritdoc} - */ - public function getMatrikulaEntries(string $matrikulaId) : array { - $matrikulaEntries = []; - $url = "https://services.datafordeler.dk/Matriklen2/Matrikel/2.0.0/rest/SamletFastEjendom"; - - $configuration = $this->getConfiguration(); - $json = $this->httpClient->request('GET', $url, [ - 'query' => [ - 'jordstykkeid' => $matrikulaId, - 'username' => $configuration['username'], - 'password' => $configuration['password'], - ], - ])->getBody(); - - $jsonDecoded = json_decode($json, TRUE); - - if (is_array($jsonDecoded)) { - if (NestedArray::keyExists($jsonDecoded, ['features', 0, 'properties', 'jordstykke'])) { - $jordstykker = NestedArray::getValue($jsonDecoded, ['features', 0, 'properties', 'jordstykke']); - foreach ($jordstykker as $jordstyk) { - try { - $matrikulaEntries[] = new DatafordelerMatrikula($jordstyk); - } - catch (\TypeError $e) { - // Could not create matrikula object. - } - } - } - } - - return $matrikulaEntries; - } - - /** - * {@inheritdoc} - */ - public function defaultConfiguration(): array { - return [ - 'username' => '', - 'password' => '', - ] + parent::defaultConfiguration(); - } - - /** - * {@inheritdoc} - */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $form['username'] = [ - '#type' => 'textfield', - '#title' => $this->t('Username for service calls'), - '#default_value' => $this->configuration['username'], - '#required' => TRUE, - '#description' => $this->t('Username required for performing API requests'), - ]; - $form['password'] = [ - '#type' => 'textfield', - '#title' => $this->t('Password for service calls'), - '#default_value' => $this->configuration['password'], - '#required' => TRUE, - '#description' => $this->t('Password required for performing API requests'), - ]; - - return $form; - } - - /** - * {@inheritdoc} - */ - public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { - $configuration = $this->getConfiguration(); - $configuration['username'] = $form_state->getValue('username'); - $configuration['password'] = $form_state->getValue('password'); - $this->setConfiguration($configuration); - } - -} diff --git a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookupInterface.php b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookupInterface.php deleted file mode 100644 index 16947249..00000000 --- a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookupInterface.php +++ /dev/null @@ -1,38 +0,0 @@ -dawaDataLookup = $manager->createInstance('dawa_data_lookup'); - } - - /** - * Returns response for 'os2forms_dawa_address' element autocomplete route. - * - * @param \Symfony\Component\HttpFoundation\ParameterBag $params - * The query params. - * @param string $fetchColumn - * The name of the column to return, set to NULL to get all columns. - * - * @return array - * Array of matches. - */ - public function getAddressMatches(ParameterBag $params, $fetchColumn = 'tekst') { - // Get autocomplete query. - $q = $params->get('q') ?: ''; - - $matches = []; - - $autocompletePath = $this->dawaDataLookup->getAddressAutocompletePath(); - $requestUrl = $autocompletePath . '?q=' . urlencode($q); - - // Adding limit by municipality limit, if present. - $limitByMunicipality = $params->get('limit_by_municipality') ?: ''; - if (!empty($limitByMunicipality)) { - $limit_by_municipality_arr = str_getcsv($limitByMunicipality); - $requestUrl .= '&kommunekode=' . implode('|', $limit_by_municipality_arr); - } - - $json = file_get_contents($requestUrl); - $jsonDecoded = json_decode($json, TRUE); - - if ($fetchColumn) { - if (is_array($jsonDecoded)) { - // Checking if remove_place_name is enabled. - $removePlaceName = $params->get('remove_place_name') ?: ''; - if ($removePlaceName) { - foreach ($jsonDecoded as $entry) { - $supplerendebynavn = $entry['adresse']['supplerendebynavn']; - - $text = $entry[$fetchColumn]; - if (!empty($supplerendebynavn)) { - $text = preg_replace("/$supplerendebynavn,/", '', $text); - } - - $matches[] = $text; - } - } - else { - $matches = array_column($jsonDecoded, $fetchColumn); - } - } - } - else { - $matches = $jsonDecoded; - } - - return $matches; - } - - /** - * Returns single address from address API. - * - * @param \Symfony\Component\HttpFoundation\ParameterBag $params - * The query params. - * - * @return \Drupal\os2forms_dawa\Entity\DawaAddress|null - * The found address. - */ - public function getSingleAddress(ParameterBag $params) { - $address = NULL; - - // Getting address_id. - $matches = $this->getAddressMatches($params, NULL); - if (!empty($matches) && isset($matches[0]['adresse'])) { - $address = new DawaAddress($matches[0]['adresse']); - } - - // Fetching address. - if ($address && $address->id()) { - $requestUrl = $this->dawaDataLookup->getAddressApiPath() . '/' . $address->id(); - - $json = file_get_contents($requestUrl); - $jsonDecoded = json_decode($json, TRUE); - - if (is_array($jsonDecoded) && !empty($jsonDecoded)) { - $address = new DawaAddress($jsonDecoded); - } - } - - return $address; - } - - /** - * Returns response for 'os2forms_dawa_block' element autocomplete route. - * - * @param \Symfony\Component\HttpFoundation\ParameterBag $params - * The query params. - * - * @return array - * Array of matches. - */ - public function getBlockMatches(ParameterBag $params) { - // Get autocomplete query. - $q = $params->get('q') ?: ''; - - $matches = []; - - $autocompletePath = $this->dawaDataLookup->getBlockAutocompletePath(); - $requestUrl = $autocompletePath . '?q=' . urlencode($q); - - $json = file_get_contents($requestUrl); - $jsonDecoded = json_decode($json, TRUE); - if (is_array($jsonDecoded)) { - // Checking if remove_code is enabled. - $removeCode = $params->get('remove_code') ?: ''; - if ($removeCode) { - foreach ($jsonDecoded as $entry) { - $code = $entry['ejerlav']['kode']; - - $text = $entry['tekst']; - if (!empty($code)) { - $text = preg_replace("/$code /", '', $text); - } - - $matches[] = $text; - } - } - else { - $matches = array_column($jsonDecoded, 'tekst'); - } - } - - return $matches; - } - - /** - * Returns response for 'os2forms_dawa_matrikula' element autocomplete route. - * - * @param \Symfony\Component\HttpFoundation\ParameterBag $params - * The query params. - * - * @return array - * Array of matches. - */ - public function getMatrikulaMatches(ParameterBag $params) { - // Get autocomplete query. - $q = $params->get('q') ?: ''; - - $matches = []; - - $autocompletePath = $this->dawaDataLookup->getMatrikulaAutocompletePath(); - $requestUrl = $autocompletePath . '?q=' . urlencode($q); - - // Adding limit by municipality limit, if present. - $limitByMunicipality = $params->get('limit_by_municipality') ?: ''; - if (!empty($limitByMunicipality)) { - $requestUrl .= '&kommunekode=' . $limitByMunicipality; - } - - // Adding property number, if present. - $limitByProperty = $params->get('limit_by_property') ?: ''; - if (!empty($limitByProperty)) { - $requestUrl .= '&esrejendomsnr=' . $limitByProperty; - } - - $json = file_get_contents($requestUrl); - $jsonDecoded = json_decode($json, TRUE); - if (is_array($jsonDecoded)) { - // Checking if remove_code is enabled. - $removeCode = $params->get('remove_code') ?: ''; - if ($removeCode) { - foreach ($jsonDecoded as $entry) { - $code = $entry['jordstykke']['ejerlav']['kode']; - - $text = $entry['tekst']; - if (!empty($code)) { - $text = preg_replace("/ \($code\)/", '', $text); - } - - $matches[] = $text; - } - } - else { - $matches = array_column($jsonDecoded, 'tekst'); - } - } - - return $matches; - } - -} diff --git a/modules/os2forms_digital_post/README.md b/modules/os2forms_digital_post/README.md index 999d87c7..e70143cb 100644 --- a/modules/os2forms_digital_post/README.md +++ b/modules/os2forms_digital_post/README.md @@ -80,3 +80,181 @@ of recipients: ``` shell drush os2forms-digital-post:test:send --help ``` + +## Fjernprint (physical digital post) + +To comply with the address placement in the envelope window (kuvert-rude) an +[event subscriber](src/EventSubscriber/Os2formsDigitalPostSubscriber.php) is +used to inject an address information element into the generated HTML before it is +converted to a PDF. + +We are only guaranteed to have the necessary information when in a digital +post context. For that reason, the injection of address information is only +done when in a digital post context. Note also that the information is only +injected – it is not styled. This allows flexibility across installations but +also means that it is up to individual installations to style it correctly. +This should be done in OS2Forms Attachment-templates, see +[Overwriting templates](https://github.com/OS2Forms/os2forms/tree/develop/modules/os2forms_attachment#overwriting-templates). + +Furthermore, a single-line sender address may be configured on the handler. +The value of this field will be injected into the HTML as a sender address, +which should be placed within the envelope window just above the recipient +address. As with the recipient information, it is up to individual +installations to style it correctly. + +To see the exact requirements for address and sender placement, see +[digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf](docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf). + +### The injected HTML + +Variations of the injected HTML include extended addresses, c/o and sender +address. + +Without extended address information, c/o or sender address: + +```html +
+
+
Jeppe
+
Test vej HouseNr
+
2100 Copenhagen
+
+
+``` + +With just an extended address: + +```html +
+
+
Jeppe
+
Test vej HouseNr Floor AppartmentNr
+
2100 Copenhagen
+
+
+``` + +With just c/o: + +```html +
+
+
Jeppe
+
c/o Mikkel
Test vej HouseNr
+
2100 Copenhagen
+
+
+``` + +With just the sender address: + +```html +
+
Dokk1, Hack Kampmanns Plads 2, 8000 Aarhus C
+
+
Jeppe
+
Test vej HouseNr
+
2100 Copenhagen
+
+
+``` + +With extended address information, c/o and sender address: + +```html +
+
Dokk1, Hack Kampmanns Plads 2, 8000 Aarhus C
+
+
Jeppe
+
c/o Mikkel
+
Test vej HouseNr Floor AppartmentNr
+
2100 Copenhagen
+
+
+``` + +### Styling of the HTML + +The following [SCSS](https://sass-lang.com/) can be used to style the injected HTML accordingly: + +```scss +$margin-top: 25mm; +// There is no exact measurement for margin right in the specifications +$margin-right: 10mm; +$margin-bottom: 20mm; +$margin-left: 17mm; +$page-width: 210mm; +$page-height: 297mm; +$envelope-window-height: 89mm; +$envelope-window-width: 115mm; +$recipient-window-height: 21mm; +$recipient-window-width: 59mm; + +@page { + size: A4; + margin: 0; +} + +body { + margin-top: $margin-top; + margin-right: $margin-right; + margin-bottom: $margin-bottom; + margin-left: $margin-left; +} + +header { + position: fixed; + top: 0; + height: $margin-top; + width: calc($page-width - $margin-left - $margin-right); + font-size: 12px; +} + +footer { + position: fixed; + bottom: 0; + height: $margin-bottom; + width: calc($page-width - $margin-left - $margin-right); + font-size: 12px; +} + +// Style the envelope window that may be injected by Digital Post. +// Note that top/left is made from the assumption that @page has margin 0. +// @see \Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber:onPrintRender +#envelope-window-digital-post { + position: absolute; + top: $margin-top; + left: $margin-left; + height: $envelope-window-height; + width: $envelope-window-width; + background: white +} + +// If envelope window is present, move webform content down +// @see os2forms_digital_post +#envelope-window-digital-post ~ * .webform-entity-print-body { + margin-top: $envelope-window-height; +} + +// Style the h-card div +#envelope-window-digital-post > .h-card { + position: absolute; + top: 16mm; + left: 4mm; + font-size: 10px; + height: $recipient-window-height; + width: $recipient-window-width; +} + +// Style the sender address div +#envelope-window-digital-post > #sender-address-digital-post { + position: absolute; + top: 12mm; + left: 4mm; + font-size: 8px; + height: 4mm; + width: 71mm; +} + +// More custom styling... +``` diff --git a/modules/os2forms_digital_post/docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf b/modules/os2forms_digital_post/docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf new file mode 100644 index 00000000..9f381c7a Binary files /dev/null and b/modules/os2forms_digital_post/docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf differ diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 745b88d2..aefb2d97 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -21,12 +21,14 @@ services: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - "@plugin.manager.element_info" - "@webform.token_manager" + - "@Drupal\\os2forms_digital_post\\EventSubscriber\\Os2formsDigitalPostSubscriber" Drupal\os2forms_digital_post\Helper\ForsendelseHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - "@plugin.manager.element_info" - "@webform.token_manager" + - "@Drupal\\os2forms_digital_post\\EventSubscriber\\Os2formsDigitalPostSubscriber" Drupal\os2forms_digital_post\Helper\DigitalPostHelper: arguments: @@ -69,3 +71,9 @@ services: - '@database' - '@Drupal\os2forms_digital_post\Helper\MeMoHelper' - '@logger.channel.os2forms_digital_post' + + Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber: + arguments: + - '@session' + tags: + - { name: 'event_subscriber' } diff --git a/modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigitalPostSubscriber.php b/modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigitalPostSubscriber.php new file mode 100644 index 00000000..647aacc7 --- /dev/null +++ b/modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigitalPostSubscriber.php @@ -0,0 +1,147 @@ +getHtml(); + + // Only modify HTML if there is exactly one submission. + if (count($event->getEntities()) === 1) { + $submission = $event->getEntities()[0]; + if ($submission instanceof WebformSubmissionInterface) { + // Check whether generation is for digital post. + if ($context = $this->getDigitalPostContext($submission)) { + $lookupResult = $context['lookupResult'] ?? NULL; + if (!$lookupResult instanceof CprLookupResult && !$lookupResult instanceof CompanyLookupResult) { + return; + } + + $senderAddress = $context['senderAddress'] ?? ''; + if (!is_string($senderAddress)) { + $senderAddress = ''; + } + + // Combine address parts. + $streetAddress = $lookupResult->getStreet(); + + if ($lookupResult->getHouseNr()) { + $streetAddress .= ' ' . $lookupResult->getHouseNr(); + } + + $extendedAddress = ''; + + if ($lookupResult->getFloor()) { + // Add a comma to align with danish address specifications. + $streetAddress .= ','; + $extendedAddress = $lookupResult->getFloor(); + } + if ($lookupResult->getApartmentNr()) { + $extendedAddress .= ' ' . $lookupResult->getApartmentNr(); + } + + // Generate address HTML. + $addressHtml = '
'; + if (!empty($senderAddress)) { + $addressHtml .= '
' . htmlspecialchars($senderAddress) . '
'; + } + $addressHtml .= '
'; + $addressHtml .= '
' . htmlspecialchars($lookupResult->getName()) . '
'; + if ($lookupResult instanceof CprLookupResult && $lookupResult->getCoName()) { + $addressHtml .= '
c/o ' . htmlspecialchars($lookupResult->getCoName()) . '
'; + } + $addressHtml .= '
'; + $addressHtml .= '' . htmlspecialchars($streetAddress) . ''; + if (!empty($extendedAddress)) { + $addressHtml .= ' ' . htmlspecialchars($extendedAddress) . ''; + } + $addressHtml .= '
'; + $addressHtml .= '
'; + $addressHtml .= '' . htmlspecialchars($lookupResult->getPostalCode()) . ''; + $addressHtml .= ' ' . htmlspecialchars($lookupResult->getCity()) . ''; + $addressHtml .= '
'; + $addressHtml .= '
'; + $addressHtml .= '
'; + + // Insert address HTML immediately after body opening tag. + $html = preg_replace('@]*>@', '${0}' . $addressHtml, $html); + } + } + } + + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + PrintEvents::POST_RENDER => ['onPrintRender'], + ]; + } + + /** + * Indicate Digital Post context in the current session. + */ + public function setDigitalPostContext(WebformSubmissionInterface $submission, CompanyLookupResult|CprLookupResult $lookupResult, string $senderAddress = ''): void { + $key = $this->createSessionKeyFromSubmission($submission); + $this->session->set($key, [ + 'lookupResult' => $lookupResult, + 'senderAddress' => $senderAddress, + ]); + } + + /** + * Check for Digital Post context in the current session. + * + * @return array|null + * - 'lookupResult': the lookup result + * - 'senderAddress': the sender address + */ + public function getDigitalPostContext(WebformSubmissionInterface $submission): ?array { + $key = $this->createSessionKeyFromSubmission($submission); + + return $this->session->get($key); + } + + /** + * Delete Digital Post context from the current request. + */ + public function deleteDigitalPostContext(WebformSubmissionInterface $submission): bool { + $key = $this->createSessionKeyFromSubmission($submission); + + return (bool) $this->session->remove($key); + } + + /** + * Create a session key from a submission that is unique to the submission. + */ + public function createSessionKeyFromSubmission(WebformSubmissionInterface $submission): string { + // Due to cloning of submission during attachment logic, we cannot use + // submission id or uuid. Webform serial, however, is copied along, so a + // combination of webform id and serial is used for uniqueness. + // @see \Drupal\os2forms_attachment\Element\AttachmentElement::overrideWebformSettings + return 'digital_post_context_' . $submission->getWebform()->id() . '_' . $submission->serial(); + } + +} diff --git a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php index c2f2b990..368ac1ac 100644 --- a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php +++ b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php @@ -4,9 +4,12 @@ use DigitalPost\MeMo\Message; use Drupal\Core\Render\ElementInfoManager; +use Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber; use Drupal\os2forms_digital_post\Exception\InvalidAttachmentElementException; use Drupal\os2forms_digital_post\Model\Document; use Drupal\os2forms_digital_post\Plugin\WebformHandler\WebformHandlerSF1601; +use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult; +use Drupal\os2web_datalookup\LookupResult\CprLookupResult; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\WebformTokenManagerInterface; use Drupal\webform_attachment\Element\WebformAttachmentBase; @@ -28,6 +31,7 @@ public function __construct( readonly protected ElementInfoManager $elementInfoManager, #[Autowire(service: 'webform.token_manager')] readonly protected WebformTokenManagerInterface $webformTokenManager, + readonly protected Os2formsDigitalPostSubscriber $digitalPostSubscriber, ) { } @@ -38,7 +42,7 @@ public function __construct( * * @phpstan-param array $handlerSettings */ - protected function getMainDocument(WebformSubmissionInterface $submission, array $handlerSettings): Document { + protected function getMainDocument(WebformSubmissionInterface $submission, array $handlerSettings, CprLookupResult|CompanyLookupResult $recipientData): Document { // Lifted from Drupal\webform_attachment\Controller\WebformAttachmentController::download. $element = $handlerSettings[WebformHandlerSF1601::MEMO_MESSAGE][WebformHandlerSF1601::ATTACHMENT_ELEMENT]; $element = $submission->getWebform()->getElement($element) ?: []; @@ -51,7 +55,16 @@ protected function getMainDocument(WebformSubmissionInterface $submission, array $fileName = $instance::getFileName($element, $submission); $mimeType = $instance::getFileMimeType($element, $submission); + + // The way to alter html generated from entities is through the + // PrintEvents::POST_RENDER event. See: + // @Drupal\entity_print\Renderer::generateHtml, + // To indicate digital post context and get the necessary information, + // we add a flag to the session. + $senderAddress = $handlerSettings[WebformHandlerSF1601::MEMO_MESSAGE][WebformHandlerSF1601::SENDER_ADDRESS] ?? ''; + $this->digitalPostSubscriber->setDigitalPostContext($submission, $recipientData, $senderAddress); $content = $instance::getFileContent($element, $submission); + $this->digitalPostSubscriber->deleteDigitalPostContext($submission); return new Document( $content, diff --git a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php index 42cbbea1..6254b63a 100644 --- a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php +++ b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php @@ -61,7 +61,7 @@ public function buildForsendelse(CprLookupResult|CompanyLookupResult $recipientD */ public function buildSubmissionForsendelse(WebformSubmissionInterface $submission, array $options, array $handlerSettings, CprLookupResult|CompanyLookupResult $recipientData): ForsendelseI { $label = $this->replaceTokens($options[WebformHandlerSF1601::MESSAGE_HEADER_LABEL], $submission); - $document = $this->getMainDocument($submission, $handlerSettings); + $document = $this->getMainDocument($submission, $handlerSettings, $recipientData); return $this->buildForsendelse($recipientData, $label, $document); } diff --git a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php index f32c567d..b3b66749 100644 --- a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php +++ b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php @@ -97,10 +97,11 @@ public function buildMessage(CprLookupResult|CompanyLookupResult $recipientData, * @phpstan-param array $options * @phpstan-param array $handlerSettings */ - public function buildWebformSubmissionMessage(WebformSubmissionInterface $submission, array $options, array $handlerSettings, CprLookupResult|CompanyLookupResult|null $recipientData = NULL): Message { + public function buildWebformSubmissionMessage(WebformSubmissionInterface $submission, array $options, array $handlerSettings, CprLookupResult|CompanyLookupResult $recipientData): Message { $senderLabel = $this->replaceTokens($options[WebformHandlerSF1601::SENDER_LABEL], $submission); $messageLabel = $this->replaceTokens($options[WebformHandlerSF1601::MESSAGE_HEADER_LABEL], $submission); - $document = $this->getMainDocument($submission, $handlerSettings); + $document = $this->getMainDocument($submission, $handlerSettings, $recipientData); + $actions = []; if (isset($handlerSettings[WebformHandlerSF1601::MEMO_ACTIONS]['actions'])) { foreach ($handlerSettings[WebformHandlerSF1601::MEMO_ACTIONS]['actions'] as $spec) { diff --git a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php index 60fae6a2..807af274 100644 --- a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php +++ b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php @@ -90,7 +90,7 @@ public function sendDigitalPost(WebformSubmissionInterface $submission, array $h $handlerMessageSettings = $handlerSettings[WebformHandlerSF1601::MEMO_MESSAGE]; $recipientIdentifierKey = $handlerMessageSettings[WebformHandlerSF1601::RECIPIENT_ELEMENT] ?? NULL; if (NULL === $recipientIdentifierKey) { - $message = 'Recipient identifier element (key: @element_key) not found in submission'; + $message = 'Recipient identifier element (key: @element_key) not found in handler settings'; $context = [ '@element_key' => WebformHandlerSF1601::RECIPIENT_ELEMENT, ]; @@ -115,7 +115,7 @@ public function sendDigitalPost(WebformSubmissionInterface $submission, array $h if (NULL === $recipientIdentifier) { $message = 'Recipient identifier element (key: @element_key) not found in submission'; $context = [ - '@element_key' => WebformHandlerSF1601::RECIPIENT_ELEMENT, + '@element_key' => $recipientIdentifierKey, ]; $this->error($message, $context); @@ -163,7 +163,9 @@ public function sendDigitalPost(WebformSubmissionInterface $submission, array $h WebformHandlerSF1601::SENDER_LABEL => $handlerMessageSettings[WebformHandlerSF1601::SENDER_LABEL], WebformHandlerSF1601::MESSAGE_HEADER_LABEL => $handlerMessageSettings[WebformHandlerSF1601::MESSAGE_HEADER_LABEL], ]; + $message = $this->meMoHelper->buildWebformSubmissionMessage($submission, $messageOptions, $handlerSettings, $lookupResult); + $forsendelseOptions = [ self::RECIPIENT_IDENTIFIER_TYPE => $recipientIdentifierType, self::RECIPIENT_IDENTIFIER => $recipientIdentifier, @@ -175,6 +177,7 @@ public function sendDigitalPost(WebformSubmissionInterface $submission, array $h WebformHandlerSF1601::SENDER_LABEL => $handlerMessageSettings[WebformHandlerSF1601::SENDER_LABEL], WebformHandlerSF1601::MESSAGE_HEADER_LABEL => $handlerMessageSettings[WebformHandlerSF1601::MESSAGE_HEADER_LABEL], ]; + $forsendelse = $this->forsendelseHelper->buildSubmissionForsendelse($submission, $forsendelseOptions, $handlerSettings, $lookupResult); $type = $handlerMessageSettings[WebformHandlerSF1601::TYPE] ?? SF1601::TYPE_DIGITAL_POST; diff --git a/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php b/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php index 0b1dc219..6f0e06a9 100644 --- a/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php +++ b/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php @@ -31,12 +31,18 @@ final class WebformHandlerSF1601 extends WebformHandlerBase { public const MESSAGE_HEADER_LABEL = 'message_header_label'; public const RECIPIENT_ELEMENT = 'recipient_element'; public const ATTACHMENT_ELEMENT = 'attachment_element'; + public const SENDER_ADDRESS = 'sender_address'; /** * Maximum length of sender label. */ private const SENDER_LABEL_MAX_LENGTH = 64; + /** + * Maximum length of sender address. + */ + private const SENDER_ADDRESS_MAX_LENGTH = 70; + /** * Maximum length of header label. */ @@ -131,6 +137,15 @@ public function buildConfigurationForm(array $form, FormStateInterface $formStat '#maxlength' => self::MESSAGE_HEADER_LABEL_MAX_LENGTH, ]; + $form[self::MEMO_MESSAGE][self::SENDER_ADDRESS] = [ + '#type' => 'textfield', + '#title' => $this->t('Sender address'), + '#description' => $this->t('Optional sender address shown on the printed document. Displayed as a single line above the recipient name. Maximum @max characters.', ['@max' => self::SENDER_ADDRESS_MAX_LENGTH]), + '#required' => FALSE, + '#default_value' => $this->configuration[self::MEMO_MESSAGE][self::SENDER_ADDRESS] ?? NULL, + '#maxlength' => self::SENDER_ADDRESS_MAX_LENGTH, + ]; + $form[self::MEMO_ACTIONS] = [ '#type' => 'fieldset', '#title' => $this->t('Actions'), diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.module b/modules/os2forms_digital_signature/os2forms_digital_signature.module index 6e12a210..a3618fda 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.module +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.module @@ -8,6 +8,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\os2forms_digital_signature\Form\SettingsForm; +use Symfony\Component\HttpFoundation\IpUtils; /** * Implements hook_cron(). @@ -57,18 +58,26 @@ function os2forms_digital_signature_file_download($uri) { $config = \Drupal::config(SettingsForm::$configName); $allowedIps = $config->get('os2forms_digital_signature_submission_allowed_ips'); - $allowedIpsArr = explode(',', $allowedIps); - $remoteIp = Drupal::request()->getClientIp(); + $allowedIpsArr = array_map('trim', explode(',', $allowedIps)); + // Remove empty entries (e.g. from trailing comma or empty config). + $allowedIpsArr = array_filter($allowedIpsArr); + $remoteIp = \Drupal::request()->getClientIp(); - // IP list is empty, or request IP is allowed. - if (empty($allowedIpsArr) || in_array($remoteIp, $allowedIpsArr)) { + // Check if remote IP matches any allowed IP or CIDR range. + if (empty($allowedIpsArr) || IpUtils::checkIp($remoteIp, $allowedIpsArr)) { $basename = basename($uri); return [ 'Content-disposition' => 'attachment; filename="' . $basename . '"', ]; } - // Otherwise - Deny access. + // Deny access and log warning. + \Drupal::logger('os2forms_digital_signature')->warning('File download denied for IP @ip on URI @uri. Allowed IPs: @allowed', [ + '@ip' => $remoteIp, + '@uri' => $uri, + '@allowed' => $allowedIps, + ]); + return -1; } diff --git a/modules/os2forms_digital_signature/src/Form/SettingsForm.php b/modules/os2forms_digital_signature/src/Form/SettingsForm.php index a05ec886..ab821c26 100644 --- a/modules/os2forms_digital_signature/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_signature/src/Form/SettingsForm.php @@ -40,12 +40,14 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['os2forms_digital_signature_remote_service_url'] = [ '#type' => 'textfield', '#title' => $this->t('Signature server URL'), + '#required' => TRUE, '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_remote_service_url'), '#description' => $this->t('E.g. https://signering.bellcom.dk/sign.php?'), ]; $form['os2forms_digital_signature_sign_hash_salt'] = [ '#type' => 'textfield', '#title' => $this->t('Hash Salt used for signature'), + '#required' => TRUE, '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_sign_hash_salt'), '#description' => $this->t('Must match hash salt on the signature server'), ]; @@ -53,11 +55,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#type' => 'textfield', '#title' => $this->t('List IPs which can download unsigned PDF submissions'), '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_submission_allowed_ips'), - '#description' => $this->t('Comma separated. e.g. 192.168.1.1,192.168.2.1'), + '#description' => $this->t('Comma separated. e.g. 192.168.1.1,192.168.2.1 or 172.16.0.0/16. If left empty no restrictions will be applied.'), ]; $form['os2forms_digital_signature_submission_retention_period'] = [ '#type' => 'textfield', '#title' => $this->t('Unsigned submission timespan (s)'), + '#required' => TRUE, '#default_value' => ($this->config(self::$configName)->get('os2forms_digital_signature_submission_retention_period')) ?? 300, '#description' => $this->t('How many seconds can unsigned submission exist before being automatically deleted'), ]; diff --git a/modules/os2forms_encrypt/os2forms_encrypt.install b/modules/os2forms_encrypt/os2forms_encrypt.install index 0a42e4ee..010722a4 100644 --- a/modules/os2forms_encrypt/os2forms_encrypt.install +++ b/modules/os2forms_encrypt/os2forms_encrypt.install @@ -17,3 +17,19 @@ function os2forms_encrypt_install() { module_set_weight('os2forms_encrypt', 9999); } + +/** + * Implements hook_uninstall(). + */ +function os2forms_encrypt_uninstall(): void { + $config_factory = \Drupal::configFactory(); + + $configs = [ + 'encrypt.profile.webform', + 'key.key.webform', + ]; + + foreach ($configs as $config_name) { + $config_factory->getEditable($config_name)->delete(); + } +} diff --git a/modules/os2forms_webform_maps/os2forms_webform_maps.libraries.yml b/modules/os2forms_webform_maps/os2forms_webform_maps.libraries.yml index c223f6c3..78ce35dd 100644 --- a/modules/os2forms_webform_maps/os2forms_webform_maps.libraries.yml +++ b/modules/os2forms_webform_maps/os2forms_webform_maps.libraries.yml @@ -1,8 +1,5 @@ webformmap: version: 1.x - css: - theme: - css/webform_map.css: {} js: js/webform_map.js: {} dependencies: diff --git a/modules/os2forms_webform_maps/src/Element/WebformLeafletMapField.php b/modules/os2forms_webform_maps/src/Element/WebformLeafletMapField.php index 87b079d0..5d36829f 100644 --- a/modules/os2forms_webform_maps/src/Element/WebformLeafletMapField.php +++ b/modules/os2forms_webform_maps/src/Element/WebformLeafletMapField.php @@ -5,6 +5,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; use Drupal\webform\Element\WebformCompositeFormElementTrait; +use Drupal\os2forms_webform_maps\Plugin\WebformElement\WebformLeafletMapField as WebformLeafletMapElement; /** * Provides a webform_map_field. @@ -36,7 +37,7 @@ public function getInfo() { '#minZoom' => 1, '#maxZoom' => 18, '#zoomFiner' => 0, - '#position' => 'topleft', + '#position' => WebformLeafletMapElement::LEAFLET_POSITION_TOP_LEFT, '#marker' => 'defaultMarker', '#drawPolyline' => 0, '#drawRectangle' => 0, @@ -90,6 +91,7 @@ public static function processWebformMapElement(&$element, FormStateInterface $f 'zoomFiner' => $element['#zoomFiner'], 'minZoom' => $element['#minZoom'], 'maxZoom' => $element['#maxZoom'], + 'zoomControlPosition' => $element['#zoomControlPosition'] ?? WebformLeafletMapElement::LEAFLET_POSITION_TOP_LEFT, 'center' => [ 'lat' => (float) $element['#lat'], 'lon' => (float) $element['#lon'], diff --git a/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php b/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php index d6a5afea..3b6aa405 100644 --- a/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php +++ b/modules/os2forms_webform_maps/src/Plugin/WebformElement/WebformLeafletMapField.php @@ -20,6 +20,13 @@ class WebformLeafletMapField extends WebformElementBase { use LeafletSettingsElementsTrait; + // Valid Leaflet control positions (cf. + // https://github.com/Leaflet/Leaflet/blob/main/src/control/Control.js). + const string LEAFLET_POSITION_TOP_LEFT = 'topleft'; + const string LEAFLET_POSITION_TOP_RIGHT = 'topright'; + const string LEAFLET_POSITION_BOTTOM_LEFT = 'bottomleft'; + const string LEAFLET_POSITION_BOTTOM_RIGHT = 'bottomright'; + /** * {@inheritdoc} */ @@ -33,10 +40,11 @@ public function defineDefaultProperties(): array { 'minZoom' => 1, 'maxZoom' => 18, 'zoomFiner' => 0, + 'zoomControlPosition' => self::LEAFLET_POSITION_TOP_LEFT, 'scrollWheelZoom' => 0, 'doubleClickZoom' => 1, - 'position' => 'topleft', + 'position' => self::LEAFLET_POSITION_TOP_LEFT, 'marker' => 'defaultMarker', 'drawPolyline' => 0, 'drawRectangle' => 0, @@ -72,6 +80,13 @@ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); $map_keys = array_keys(leaflet_map_get_info()); + $positionOptions = [ + self::LEAFLET_POSITION_TOP_LEFT => $this->t('topleft'), + self::LEAFLET_POSITION_TOP_RIGHT => $this->t('topright'), + self::LEAFLET_POSITION_BOTTOM_LEFT => $this->t('bottomleft'), + self::LEAFLET_POSITION_BOTTOM_RIGHT => $this->t('bottomright'), + ]; + $form['mapstyles'] = [ '#type' => 'fieldset', '#title' => $this->t('Map settings'), @@ -139,6 +154,11 @@ public function form(array $form, FormStateInterface $form_state) { '#step' => 1, '#description' => $this->t('Value that might/will be added to default Fit Elements Bounds Zoom. (-5 / +5)'), ], + 'zoomControlPosition' => [ + '#type' => 'select', + '#title' => $this->t('Zoom control position'), + '#options' => $positionOptions, + ], 'scrollWheelZoom' => [ '#type' => 'checkbox', '#title' => $this->t('Enable Scroll Wheel Zoom on click'), @@ -159,12 +179,7 @@ public function form(array $form, FormStateInterface $form_state) { 'position' => [ '#type' => 'select', '#title' => $this->t('Toolbar position.'), - '#options' => [ - 'topleft' => $this->t('topleft'), - 'topright' => $this->t('topright'), - 'bottomleft' => $this->t('bottomleft'), - 'bottomright' => $this->t('bottomright'), - ], + '#options' => $positionOptions, ], 'marker' => [ '#type' => 'radios', diff --git a/os2forms.info.yml b/os2forms.info.yml index d08e8e9c..d7e530d0 100644 --- a/os2forms.info.yml +++ b/os2forms.info.yml @@ -5,7 +5,6 @@ package: OS2Forms core_version_requirement: ^9 || ^10 dependencies: - - 'drupal:ckeditor' - 'drupal:editor' - 'drupal:entity_print' - 'drupal:eu_cookie_compliance' diff --git a/os2forms.install b/os2forms.install index b6085142..9d1becee 100644 --- a/os2forms.install +++ b/os2forms.install @@ -8,6 +8,7 @@ use Composer\InstalledVersions; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\filter\Entity\FilterFormat; use Drupal\taxonomy\Entity\Term; use Symfony\Component\Yaml\Yaml; @@ -237,3 +238,30 @@ function os2forms_update_103001() { function os2forms_update_103002() { \Drupal::service('module_installer')->install(['os2web_key']); } + +/** + * Implements hook_update_N(). + * + * Updating ckeditor -> ckeditor5. + */ +function os2forms_update_103003() { + \Drupal::service('module_installer')->install(['ckeditor5']); + + $editor_storage = \Drupal::entityTypeManager()->getStorage('editor'); + /** @var \Drupal\ckeditor5\SmartDefaultSettings $smart_defaults */ + $smart_defaults = \Drupal::service('ckeditor5.smart_default_settings'); + + foreach (FilterFormat::loadMultiple() as $format) { + /** @var \Drupal\editor\EditorInterface $editor */ + $editor = $editor_storage->load($format->id()); + if (!$editor || $editor->getEditor() !== 'ckeditor') { + continue; + } + + [$new_editor] = $smart_defaults + ->computeSmartDefaultSettings($editor, $format); + $new_editor->save(); + } + + \Drupal::service('module_installer')->uninstall(['ckeditor']); +} diff --git a/scripts/code-analysis b/scripts/code-analysis index ace9e282..a0bdb083 100755 --- a/scripts/code-analysis +++ b/scripts/code-analysis @@ -40,7 +40,7 @@ drupal_composer config --no-plugins allow-plugins true # Making Drupal 10 compatible drupal_composer require psr/http-message:^1.0 -drupal_composer require mglaman/composer-drupal-lenient +drupal_composer require mglaman/composer-drupal-lenient:^1.0 drupal_composer config --merge --json extra.drupal-lenient.allowed-list '["drupal/coc_forms_auto_export", "drupal/webform_node_element"]' drupal_composer require wikimedia/composer-merge-plugin