22
33namespace Drupal \os2forms_digital_post \Drush \Commands ;
44
5+ use DigitalPost \MeMo \Action ;
6+ use DigitalPost \MeMo \EntryPoint ;
7+ use DigitalPost \MeMo \Reservation ;
58use Drupal \Component \Serialization \Yaml ;
69use Drupal \Core \DependencyInjection \AutowireTrait ;
710use Drupal \Core \Utility \Token ;
1013use Drupal \os2forms_digital_post \Helper \Settings ;
1114use Drupal \os2forms_digital_post \Model \Document ;
1215use Drush \Commands \DrushCommands ;
16+ use ItkDev \Serviceplatformen \Service \SF1601 \Serializer ;
1317use ItkDev \Serviceplatformen \Service \SF1601 \SF1601 ;
1418use Symfony \Component \Console \Exception \InvalidArgumentException ;
19+ use Symfony \Component \Console \Exception \InvalidOptionException ;
1520use Symfony \Component \Console \Style \SymfonyStyle ;
1621use Symfony \Component \DependencyInjection \Attribute \Autowire ;
22+ use Symfony \Component \OptionsResolver \Exception \ExceptionInterface ;
23+ use Symfony \Component \OptionsResolver \Exception \InvalidOptionsException ;
24+ use Symfony \Component \OptionsResolver \Options ;
25+ use Symfony \Component \OptionsResolver \OptionsResolver ;
1726use Symfony \Contracts \HttpClient \Exception \ClientExceptionInterface ;
1827
1928/**
@@ -44,14 +53,20 @@ public function __construct(
4453 * @param array $options
4554 * The options.
4655 *
47- * @option string subject
48- * The subject. Can contain HTML.
49- * @option string message
50- * The message to send. Can contain HTML.
51- * @option string digital-post-type
52- * The digital post type to use.
53- * @option bool dump-digital-post-settings
54- * Dump digital post settings.
56+ * @option subject
57+ * The subject. Can contain HTML.
58+ * @option message
59+ * The message to send. Can contain HTML.
60+ * @option digital-post-type
61+ * The digital post type to use.
62+ * @option dump-digital-post-settings
63+ * Dump digital post settings.
64+ * @option memo-version
65+ * MeMo version (1.1 or 1.2). If not set, a proper default will be used.
66+ * @option action
67+ * MeMo actions, e.g. 'action=INFORMATION&label=Vigtig%20information&entrypoint=https://example.com'
68+ * @option filename
69+ * The main document filename (used to test invalid filenames (cf. https://digitaliser.dk/digital-post/nyhedsarkiv/2024/nov/oeget-validering-i-digital-post))
5570 *
5671 * @phpstan-param array<string> $recipients
5772 * @phpstan-param array<string, mixed> $options
@@ -66,6 +81,9 @@ public function send(
6681 'message ' => 'This is a test message from os2forms_digital_post sent on [current-date:html_datetime]. ' ,
6782 'digital-post-type ' => SF1601 ::TYPE_AUTOMATISK_VALG ,
6883 'dump-digital-post-settings ' => FALSE ,
84+ 'memo-version ' => NULL ,
85+ 'action ' => [],
86+ 'filename ' => 'os2forms_digital_post ' ,
6987 ],
7088 ): void {
7189 $ io = new SymfonyStyle ($ this ->input (), $ this ->output ());
@@ -89,7 +107,7 @@ public function send(
89107 $ document = new Document (
90108 $ content ,
91109 Document::MIME_TYPE_PDF ,
92- ' os2forms_digital_post. pdf '
110+ $ options [ ' filename ' ] . ' . pdf ',
93111 );
94112
95113 $ type = $ options ['digital-post-type ' ];
@@ -98,21 +116,41 @@ public function send(
98116 throw new InvalidArgumentException (sprintf ('Invalid type: %s. Must be one of %s. ' , $ quote ($ type ), implode (', ' , array_map ($ quote , SF1601 ::TYPES ))));
99117 }
100118
119+ $ meMoVersion = $ options ['memo-version ' ];
120+ if ($ meMoVersion ) {
121+ $ meMoVersion = (float ) $ meMoVersion ;
122+ $ allowedValues = [SF1601 ::MEMO_1_1 , SF1601 ::MEMO_1_2 ];
123+ if (!in_array ($ meMoVersion , $ allowedValues , TRUE )) {
124+ $ quote = static fn ($ value ) => var_export ($ value , TRUE );
125+ throw new InvalidArgumentException (sprintf (
126+ 'Invalid MeMo version: %s. Must be one of %s. ' ,
127+ $ quote ($ meMoVersion ),
128+ implode (', ' , array_map ($ quote , $ allowedValues ))
129+ ));
130+ }
131+ }
132+
101133 $ io ->section ('Digital post ' );
102134 $ io ->definitionList (
103135 ['Type ' => $ type ],
136+ ['Document ' => sprintf ('%s (%s) (sanitized: %s) ' , $ document ->filename , $ document ->mimeType , SF1601 ::sanitizeFilename ($ document ->filename ))],
104137 ['Subject ' => $ subject ],
105- ['Message ' => $ message ]
138+ ['Message ' => $ message ],
139+ ['MeMe version ' => $ meMoVersion ],
106140 );
107141
142+ $ actions = array_map ($ this ->buildAction (...), $ options ['action ' ]);
143+
108144 foreach ($ recipients as $ recipient ) {
109145 try {
110146 $ io ->writeln (sprintf ('Recipient: %s ' , $ recipient ));
111147 $ recipientLookupResult = $ this ->digitalPostHelper ->lookupRecipient ($ recipient );
112- $ actions = [];
113148
114149 $ meMoMessage = $ this ->digitalPostHelper ->getMeMoHelper ()->buildMessage ($ recipientLookupResult , $ senderLabel ,
115150 $ messageLabel , $ document , $ actions );
151+ if ($ meMoVersion ) {
152+ $ meMoMessage ->setMemoVersion ($ meMoVersion );
153+ }
116154 $ forsendelse = $ this ->digitalPostHelper ->getForsendelseHelper ()->buildForsendelse ($ recipientLookupResult ,
117155 $ messageLabel , $ document );
118156
@@ -122,7 +160,7 @@ public function send(
122160 $ forsendelse
123161 );
124162
125- $ io ->success (sprintf ('Digital post sent to %s ' , $ recipient ));
163+ $ io ->success (sprintf ('Digital post sent to %s (MeMo %s) ' , $ recipient, $ meMoMessage -> getMemoVersion () ));
126164 }
127165 catch (\Throwable $ throwable ) {
128166 $ io ->error (sprintf ('Error sending digital post to %s: ' , $ recipient ));
@@ -158,4 +196,91 @@ private function dumpDigitalPostSettings(SymfonyStyle $io): void {
158196 ]);
159197 }
160198
199+ /**
200+ * Build MeMo action.
201+ *
202+ * Lifted from KombiPostAfsendCommand::buildAction().
203+ *
204+ * @see KombiPostAfsendCommand::buildAction()
205+ */
206+ private function buildAction (string $ spec ): Action {
207+ parse_str ($ spec , $ options );
208+ $ resolver = $ this ->getActionOptionsResolver ();
209+ try {
210+ $ options = $ resolver ->resolve ($ options );
211+ }
212+ catch (ExceptionInterface $ exception ) {
213+ throw new InvalidOptionException (sprintf (
214+ 'Invalid action %s: %s ' ,
215+ json_encode ($ spec ),
216+ $ exception ->getMessage ()
217+ ));
218+ }
219+
220+ $ action = (new Action ())
221+ ->setActionCode ($ options ['action ' ])
222+ ->setLabel ($ options ['label ' ]);
223+ if (SF1601 ::ACTION_AFTALE === $ options ['action ' ]) {
224+ $ reservation = (new Reservation ())
225+ ->setStartDateTime (new \DateTime ('+2 days ' ))
226+ ->setEndDateTime (new \DateTime ('+2 days 1 hour ' ))
227+ ->setLocation ('Meeting room 1 ' )
228+ ->setAbstract ('Abstract ' )
229+ ->setDescription ('Description ' )
230+ ->setOrganizerName ('Organizer ' )
231+ ->
setOrganizerMail (
'[email protected] ' )
232+ ->setReservationUUID (Serializer::createUuid ());
233+ $ action ->setReservation ($ reservation );
234+ }
235+ elseif ($ options ['entrypoint ' ]) {
236+ $ action ->setEntryPoint (
237+ (new EntryPoint ())
238+ ->setUrl ($ options ['entrypoint ' ])
239+ );
240+ }
241+
242+ if ($ options ['endDateTime ' ]) {
243+ $ action ->setEndDateTime (new \DateTime ($ options ['endDateTime ' ]));
244+ }
245+
246+ return $ action ;
247+ }
248+
249+ /**
250+ * Get actions options resolver.
251+ *
252+ * @see KombiPostAfsendCommand::getActionOptionsResolver()
253+ */
254+ private function getActionOptionsResolver (): OptionsResolver {
255+ $ resolver = new OptionsResolver ();
256+ $ resolver
257+ ->setRequired ([
258+ 'action ' ,
259+ 'label ' ,
260+ ])
261+ ->setDefaults ([
262+ 'endDateTime ' => NULL ,
263+ 'entrypoint ' => NULL ,
264+ ])
265+ ->setInfo ('action ' , sprintf ('The action name (one of %s) ' , implode (', ' , SF1601 ::ACTIONS )))
266+ ->setInfo ('label ' , 'The action label ' )
267+ ->setInfo ('endDateTime ' , 'The end time e.g. "2022-12-02" or "14 days" ' )
268+ ->setInfo ('entrypoint ' , 'The entry point (an URL) ' )
269+ ->setAllowedValues ('action ' , static function ($ value ) {
270+ return in_array ($ value , SF1601 ::ACTIONS , TRUE );
271+ })
272+ ->setNormalizer ('entrypoint ' , static function (Options $ options , $ value ) {
273+ if (NULL === $ value && SF1601 ::ACTION_AFTALE !== $ options ['action ' ]) {
274+ throw new InvalidOptionsException (sprintf (
275+ 'Action entrypoint is required for all actions but %s ' ,
276+ SF1601 ::ACTION_AFTALE
277+ ));
278+ }
279+
280+ return $ value ;
281+ });
282+
283+ return $ resolver ;
284+ }
285+
161286}
0 commit comments