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
]*>@', '${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