Skip to content

Commit f64cd24

Browse files
authored
Merge pull request #301 from itk-dev/feature/address-in-digital-post-pdfs
288: Add address element to generated html during digital post
2 parents c14172e + ec1fe42 commit f64cd24

10 files changed

Lines changed: 373 additions & 4 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ before starting to add changes. Use example [placed in the end of the page](#exa
1111

1212
## [Unreleased]
1313

14+
- [PR-301](https://github.com/OS2Forms/os2forms/pull/301)
15+
Add address information to Digital Post shipments to ensure "*fjernprint*"
16+
can be sent.
17+
- Add option to add return address to Digital Post shipments.
1418
- [PR-305](https://github.com/OS2Forms/os2forms/pull/305)
1519
Fix IP validation in digital signature file download (CIDR support)
1620
- [PR-317](https://github.com/OS2Forms/os2forms/pull/317)

modules/os2forms_digital_post/README.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,181 @@ of recipients:
8080
``` shell
8181
drush os2forms-digital-post:test:send --help
8282
```
83+
84+
## Fjernprint (physical digital post)
85+
86+
To comply with the address placement in the envelope window (kuvert-rude) an
87+
[event subscriber](src/EventSubscriber/Os2formsDigitalPostSubscriber.php) is
88+
used to inject an address information element into the generated HTML before it is
89+
converted to a PDF.
90+
91+
We are only guaranteed to have the necessary information when in a digital
92+
post context. For that reason, the injection of address information is only
93+
done when in a digital post context. Note also that the information is only
94+
injected – it is not styled. This allows flexibility across installations but
95+
also means that it is up to individual installations to style it correctly.
96+
This should be done in OS2Forms Attachment-templates, see
97+
[Overwriting templates](https://github.com/OS2Forms/os2forms/tree/develop/modules/os2forms_attachment#overwriting-templates).
98+
99+
Furthermore, a single-line sender address may be configured on the handler.
100+
The value of this field will be injected into the HTML as a sender address,
101+
which should be placed within the envelope window just above the recipient
102+
address. As with the recipient information, it is up to individual
103+
installations to style it correctly.
104+
105+
To see the exact requirements for address and sender placement, see
106+
[digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf](docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf).
107+
108+
### The injected HTML
109+
110+
Variations of the injected HTML include extended addresses, c/o and sender
111+
address.
112+
113+
Without extended address information, c/o or sender address:
114+
115+
```html
116+
<div id="envelope-window-digital-post">
117+
<div class="h-card">
118+
<div class="p-name">Jeppe</div>
119+
<div><span class="p-street-address">Test vej HouseNr</span></div>
120+
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
121+
</div>
122+
</div>
123+
```
124+
125+
With just an extended address:
126+
127+
```html
128+
<div id="envelope-window-digital-post">
129+
<div class="h-card">
130+
<div class="p-name">Jeppe</div>
131+
<div><span class="p-street-address">Test vej HouseNr</span> <span class="p-extended-address">Floor AppartmentNr</span></div>
132+
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
133+
</div>
134+
</div>
135+
```
136+
137+
With just c/o:
138+
139+
```html
140+
<div id="envelope-window-digital-post">
141+
<div class="h-card">
142+
<div class="p-name">Jeppe</div>
143+
<div class="p-name">c/o Mikkel</div><div><span class="p-street-address">Test vej HouseNr</span></div>
144+
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
145+
</div>
146+
</div>
147+
```
148+
149+
With just the sender address:
150+
151+
```html
152+
<div id="envelope-window-digital-post">
153+
<div id="sender-address-digital-post">Dokk1, Hack Kampmanns Plads 2, 8000 Aarhus C</div>
154+
<div class="h-card">
155+
<div class="p-name">Jeppe</div>
156+
<div><span class="p-street-address">Test vej HouseNr</span></div>
157+
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
158+
</div>
159+
</div>
160+
```
161+
162+
With extended address information, c/o and sender address:
163+
164+
```html
165+
<div id="envelope-window-digital-post">
166+
<div id="sender-address-digital-post">Dokk1, Hack Kampmanns Plads 2, 8000 Aarhus C</div>
167+
<div class="h-card">
168+
<div class="p-name">Jeppe</div>
169+
<div class="p-name">c/o Mikkel</div>
170+
<div><span class="p-street-address">Test vej HouseNr</span> <span class="p-extended-address">Floor AppartmentNr</span></div>
171+
<div><span class="p-postal-code">2100</span> <span class="p-locality">Copenhagen</span></div>
172+
</div>
173+
</div>
174+
```
175+
176+
### Styling of the HTML
177+
178+
The following [SCSS](https://sass-lang.com/) can be used to style the injected HTML accordingly:
179+
180+
```scss
181+
$margin-top: 25mm;
182+
// There is no exact measurement for margin right in the specifications
183+
$margin-right: 10mm;
184+
$margin-bottom: 20mm;
185+
$margin-left: 17mm;
186+
$page-width: 210mm;
187+
$page-height: 297mm;
188+
$envelope-window-height: 89mm;
189+
$envelope-window-width: 115mm;
190+
$recipient-window-height: 21mm;
191+
$recipient-window-width: 59mm;
192+
193+
@page {
194+
size: A4;
195+
margin: 0;
196+
}
197+
198+
body {
199+
margin-top: $margin-top;
200+
margin-right: $margin-right;
201+
margin-bottom: $margin-bottom;
202+
margin-left: $margin-left;
203+
}
204+
205+
header {
206+
position: fixed;
207+
top: 0;
208+
height: $margin-top;
209+
width: calc($page-width - $margin-left - $margin-right);
210+
font-size: 12px;
211+
}
212+
213+
footer {
214+
position: fixed;
215+
bottom: 0;
216+
height: $margin-bottom;
217+
width: calc($page-width - $margin-left - $margin-right);
218+
font-size: 12px;
219+
}
220+
221+
// Style the envelope window that may be injected by Digital Post.
222+
// Note that top/left is made from the assumption that @page has margin 0.
223+
// @see \Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber:onPrintRender
224+
#envelope-window-digital-post {
225+
position: absolute;
226+
top: $margin-top;
227+
left: $margin-left;
228+
height: $envelope-window-height;
229+
width: $envelope-window-width;
230+
background: white
231+
}
232+
233+
// If envelope window is present, move webform content down
234+
// @see os2forms_digital_post
235+
#envelope-window-digital-post ~ * .webform-entity-print-body {
236+
margin-top: $envelope-window-height;
237+
}
238+
239+
// Style the h-card div
240+
#envelope-window-digital-post > .h-card {
241+
position: absolute;
242+
top: 16mm;
243+
left: 4mm;
244+
font-size: 10px;
245+
height: $recipient-window-height;
246+
width: $recipient-window-width;
247+
}
248+
249+
// Style the sender address div
250+
#envelope-window-digital-post > #sender-address-digital-post {
251+
position: absolute;
252+
top: 12mm;
253+
left: 4mm;
254+
font-size: 8px;
255+
height: 4mm;
256+
width: 71mm;
257+
}
258+
259+
// More custom styling...
260+
```
Binary file not shown.

modules/os2forms_digital_post/os2forms_digital_post.services.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ services:
2121
- "@Drupal\\os2forms_digital_post\\Helper\\Settings"
2222
- "@plugin.manager.element_info"
2323
- "@webform.token_manager"
24+
- "@Drupal\\os2forms_digital_post\\EventSubscriber\\Os2formsDigitalPostSubscriber"
2425

2526
Drupal\os2forms_digital_post\Helper\ForsendelseHelper:
2627
arguments:
2728
- "@Drupal\\os2forms_digital_post\\Helper\\Settings"
2829
- "@plugin.manager.element_info"
2930
- "@webform.token_manager"
31+
- "@Drupal\\os2forms_digital_post\\EventSubscriber\\Os2formsDigitalPostSubscriber"
3032

3133
Drupal\os2forms_digital_post\Helper\DigitalPostHelper:
3234
arguments:
@@ -69,3 +71,9 @@ services:
6971
- '@database'
7072
- '@Drupal\os2forms_digital_post\Helper\MeMoHelper'
7173
- '@logger.channel.os2forms_digital_post'
74+
75+
Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber:
76+
arguments:
77+
- '@session'
78+
tags:
79+
- { name: 'event_subscriber' }
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?php
2+
3+
namespace Drupal\os2forms_digital_post\EventSubscriber;
4+
5+
use Drupal\entity_print\Event\PrintEvents;
6+
use Drupal\entity_print\Event\PrintHtmlAlterEvent;
7+
use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult;
8+
use Drupal\os2web_datalookup\LookupResult\CprLookupResult;
9+
use Drupal\webform\WebformSubmissionInterface;
10+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
11+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
12+
13+
/**
14+
* Used to alter the generated PDF to align with digital post requirements.
15+
*/
16+
final class Os2formsDigitalPostSubscriber implements EventSubscriberInterface {
17+
18+
public function __construct(private readonly SessionInterface $session) {
19+
}
20+
21+
/**
22+
* Post render entity_print event.
23+
*
24+
* Injects an envelope-window element containing address information.
25+
*/
26+
public function onPrintRender(PrintHtmlAlterEvent $event): void {
27+
$html = &$event->getHtml();
28+
29+
// Only modify HTML if there is exactly one submission.
30+
if (count($event->getEntities()) === 1) {
31+
$submission = $event->getEntities()[0];
32+
if ($submission instanceof WebformSubmissionInterface) {
33+
// Check whether generation is for digital post.
34+
if ($context = $this->getDigitalPostContext($submission)) {
35+
$lookupResult = $context['lookupResult'] ?? NULL;
36+
if (!$lookupResult instanceof CprLookupResult && !$lookupResult instanceof CompanyLookupResult) {
37+
return;
38+
}
39+
40+
$senderAddress = $context['senderAddress'] ?? '';
41+
if (!is_string($senderAddress)) {
42+
$senderAddress = '';
43+
}
44+
45+
// Combine address parts.
46+
$streetAddress = $lookupResult->getStreet();
47+
48+
if ($lookupResult->getHouseNr()) {
49+
$streetAddress .= ' ' . $lookupResult->getHouseNr();
50+
}
51+
52+
$extendedAddress = '';
53+
54+
if ($lookupResult->getFloor()) {
55+
// Add a comma to align with danish address specifications.
56+
$streetAddress .= ',';
57+
$extendedAddress = $lookupResult->getFloor();
58+
}
59+
if ($lookupResult->getApartmentNr()) {
60+
$extendedAddress .= ' ' . $lookupResult->getApartmentNr();
61+
}
62+
63+
// Generate address HTML.
64+
$addressHtml = '<div id="envelope-window-digital-post">';
65+
if (!empty($senderAddress)) {
66+
$addressHtml .= '<div id="sender-address-digital-post">' . htmlspecialchars($senderAddress) . '</div>';
67+
}
68+
$addressHtml .= '<div class="h-card">';
69+
$addressHtml .= '<div class="p-name">' . htmlspecialchars($lookupResult->getName()) . '</div>';
70+
if ($lookupResult instanceof CprLookupResult && $lookupResult->getCoName()) {
71+
$addressHtml .= '<div class="p-name p-co-name">c/o ' . htmlspecialchars($lookupResult->getCoName()) . '</div>';
72+
}
73+
$addressHtml .= '<div>';
74+
$addressHtml .= '<span class="p-street-address">' . htmlspecialchars($streetAddress) . '</span>';
75+
if (!empty($extendedAddress)) {
76+
$addressHtml .= ' <span class="p-extended-address">' . htmlspecialchars($extendedAddress) . '</span>';
77+
}
78+
$addressHtml .= '</div>';
79+
$addressHtml .= '<div>';
80+
$addressHtml .= '<span class="p-postal-code">' . htmlspecialchars($lookupResult->getPostalCode()) . '</span>';
81+
$addressHtml .= ' <span class="p-locality">' . htmlspecialchars($lookupResult->getCity()) . '</span>';
82+
$addressHtml .= '</div>';
83+
$addressHtml .= '</div>';
84+
$addressHtml .= '</div>';
85+
86+
// Insert address HTML immediately after body opening tag.
87+
$html = preg_replace('@<body[^>]*>@', '${0}' . $addressHtml, $html);
88+
}
89+
}
90+
}
91+
92+
}
93+
94+
/**
95+
* {@inheritdoc}
96+
*/
97+
public static function getSubscribedEvents(): array {
98+
return [
99+
PrintEvents::POST_RENDER => ['onPrintRender'],
100+
];
101+
}
102+
103+
/**
104+
* Indicate Digital Post context in the current session.
105+
*/
106+
public function setDigitalPostContext(WebformSubmissionInterface $submission, CompanyLookupResult|CprLookupResult $lookupResult, string $senderAddress = ''): void {
107+
$key = $this->createSessionKeyFromSubmission($submission);
108+
$this->session->set($key, [
109+
'lookupResult' => $lookupResult,
110+
'senderAddress' => $senderAddress,
111+
]);
112+
}
113+
114+
/**
115+
* Check for Digital Post context in the current session.
116+
*
117+
* @return array<string, mixed>|null
118+
* - 'lookupResult': the lookup result
119+
* - 'senderAddress': the sender address
120+
*/
121+
public function getDigitalPostContext(WebformSubmissionInterface $submission): ?array {
122+
$key = $this->createSessionKeyFromSubmission($submission);
123+
124+
return $this->session->get($key);
125+
}
126+
127+
/**
128+
* Delete Digital Post context from the current request.
129+
*/
130+
public function deleteDigitalPostContext(WebformSubmissionInterface $submission): bool {
131+
$key = $this->createSessionKeyFromSubmission($submission);
132+
133+
return (bool) $this->session->remove($key);
134+
}
135+
136+
/**
137+
* Create a session key from a submission that is unique to the submission.
138+
*/
139+
public function createSessionKeyFromSubmission(WebformSubmissionInterface $submission): string {
140+
// Due to cloning of submission during attachment logic, we cannot use
141+
// submission id or uuid. Webform serial, however, is copied along, so a
142+
// combination of webform id and serial is used for uniqueness.
143+
// @see \Drupal\os2forms_attachment\Element\AttachmentElement::overrideWebformSettings
144+
return 'digital_post_context_' . $submission->getWebform()->id() . '_' . $submission->serial();
145+
}
146+
147+
}

0 commit comments

Comments
 (0)