Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0d8224a
288: Add address element to generated html during digital post
jekuaitk Feb 6, 2026
ba26c19
288: Updated CHANGELOG
jekuaitk Feb 6, 2026
e0815dc
288: Added comment to preg_replace
jekuaitk Feb 10, 2026
1fca0a2
288: Ensured required fields are set before accessing them
jekuaitk Feb 10, 2026
d25ba3e
288: Switched to h-card and saving lookup result in session
jekuaitk Feb 10, 2026
cfdd08f
288: Updated README with injected html and example styling
jekuaitk Feb 10, 2026
e3a719a
288: Cleaned up example styling
jekuaitk Feb 10, 2026
fdaafd5
Update modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigi…
jekuaitk Feb 16, 2026
a552beb
Update modules/os2forms_digital_post/README.md
jekuaitk Feb 16, 2026
7ed8d8f
Update modules/os2forms_digital_post/README.md
jekuaitk Feb 16, 2026
0c39739
Update modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigi…
jekuaitk Feb 16, 2026
a6e6f1f
Added missing comma in address
jekuaitk Feb 19, 2026
7c057aa
Updated README
jekuaitk Feb 19, 2026
a99952c
Cleanup
jekuaitk Feb 23, 2026
eeba74b
Ensure address is added to both digital post pdf and fjernprint pdf
jekuaitk Mar 9, 2026
de9a7a1
Added “Display on“ options to Map element
rimi-itk Mar 11, 2026
7c5e04e
Added documentation on fetching GeoJSON using the API
rimi-itk Mar 11, 2026
ff515e3
Updated changelog
rimi-itk Mar 11, 2026
3c9dffb
Handled "mail" display
rimi-itk Mar 11, 2026
31f4ca5
Updated URL
rimi-itk Mar 12, 2026
5fdc5d4
Adjusted when context is set and removed
jekuaitk Mar 12, 2026
ed4c707
Added option to add sender address to digital post
jekuaitk Mar 13, 2026
8fbeeaa
Updated README with styling of sender adress
jekuaitk Mar 13, 2026
381f438
Added max length for sender and checks for context being correctly set
jekuaitk Mar 16, 2026
ee98943
Update modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigi…
jekuaitk Mar 16, 2026
5a644a1
Update modules/os2forms_digital_post/src/Plugin/WebformHandler/Webfor…
jekuaitk Mar 16, 2026
4335ee3
Updated digital post README with sender address
jekuaitk Mar 16, 2026
e0794f6
Updated CHANGELOG
jekuaitk Mar 23, 2026
fcbd689
Merge branch 'develop' into feature/maps-element-display
jekuaitk Mar 23, 2026
333faf1
Merge branch 'develop' into feature/maps-element-display
jekuaitk Mar 23, 2026
af09cfc
Merge branch 'feature/maps-element-display' into release/5.1.2-and-ma…
jekuaitk Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ before starting to add changes. Use example [placed in the end of the page](#exa

## [Unreleased]

- [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-315](https://github.com/OS2Forms/os2forms/pull/315)
Added “Display on“ options to Map element
- [PR-317](https://github.com/OS2Forms/os2forms/pull/317)
Updated code analysis script.
- [PR-306](https://github.com/OS2Forms/os2forms/pull/306)
Expand Down
179 changes: 179 additions & 0 deletions modules/os2forms_digital_post/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,182 @@ 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
<div id="envelope-window-digital-post">
<div class="h-card">
<div class="p-name">Jeppe</div>
<div><span class="p-street-address">Test vej HouseNr</span></div>
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
</div>
</div>
```

With just an extended address:

```html
<div id="envelope-window-digital-post">
<div class="h-card">
<div class="p-name">Jeppe</div>
<div><span class="p-street-address">Test vej HouseNr</span> <span class="p-extended-address">Floor AppartmentNr</span></div>
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
</div>
</div>
```

With just c/o:

```html
<div id="envelope-window-digital-post">
<div class="h-card">
<div class="p-name">Jeppe</div>
<div class="p-name">c/o Mikkel</div><div><span class="p-street-address">Test vej HouseNr</span></div>
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
</div>
</div>
```

With just the sender address:

```html
<div id="envelope-window-digital-post">
<div id="sender-address-digital-post">Dokk1, Hack Kampmanns Plads 2, 8000 Aarhus C</div>
<div class="h-card">
<div class="p-name">Jeppe</div>
<div><span class="p-street-address">Test vej HouseNr</span></div>
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
</div>
</div>
```


With extended address information, c/o and sender address:

```html
<div id="envelope-window-digital-post">
<div id="sender-address-digital-post">Dokk1, Hack Kampmanns Plads 2, 8000 Aarhus C</div>
<div class="h-card">
<div class="p-name">Jeppe</div>
<div class="p-name">c/o Mikkel</div>
<div><span class="p-street-address">Test vej HouseNr</span> <span class="p-extended-address">Floor AppartmentNr</span></div>
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
</div>
</div>
```

### 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...
```
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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' }
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

namespace Drupal\os2forms_digital_post\EventSubscriber;

use Drupal\entity_print\Event\PrintEvents;
use Drupal\entity_print\Event\PrintHtmlAlterEvent;
use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult;
use Drupal\os2web_datalookup\LookupResult\CprLookupResult;
use Drupal\webform\WebformSubmissionInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

/**
* Used to alter the generated PDF to align with digital post requirements.
*/
final class Os2formsDigitalPostSubscriber implements EventSubscriberInterface {

public function __construct(private readonly SessionInterface $session) {
}

/**
* Post render entity_print event.
*
* Injects an envelope-window element containing address information.
*/
public function onPrintRender(PrintHtmlAlterEvent $event): void {
$html = &$event->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 = '<div id="envelope-window-digital-post">';
if (!empty($senderAddress)) {
$addressHtml .= '<div id="sender-address-digital-post">' . htmlspecialchars($senderAddress) . '</div>';
}
$addressHtml .= '<div class="h-card">';
$addressHtml .= '<div class="p-name">' . htmlspecialchars($lookupResult->getName()) . '</div>';
if ($lookupResult instanceof CprLookupResult && $lookupResult->getCoName()) {
$addressHtml .= '<div class="p-name p-co-name">c/o ' . htmlspecialchars($lookupResult->getCoName()) . '</div>';
}
$addressHtml .= '<div>';
$addressHtml .= '<span class="p-street-address">' . htmlspecialchars($streetAddress) . '</span>';
if (!empty($extendedAddress)) {
$addressHtml .= ' <span class="p-extended-address">' . htmlspecialchars($extendedAddress) . '</span>';
}
$addressHtml .= '</div>';
$addressHtml .= '<div>';
$addressHtml .= '<span class="p-postal-code">' . htmlspecialchars($lookupResult->getPostalCode()) . '</span>';
$addressHtml .= ' <span class="p-locality">' . htmlspecialchars($lookupResult->getCity()) . '</span>';
$addressHtml .= '</div>';
$addressHtml .= '</div>';
$addressHtml .= '</div>';

// Insert address HTML immediately after body opening tag.
$html = preg_replace('@<body[^>]*>@', '${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.
*/
public function getDigitalPostContext(WebformSubmissionInterface $submission): ?array {

Check failure on line 117 in modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigitalPostSubscriber.php

View workflow job for this annotation

GitHub Actions / PHP code analysis

Method Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber::getDigitalPostContext() return type has no value type specified in iterable type 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();
}

}
Loading
Loading