Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.01% covered (warning)
87.01%
154 / 177
53.33% covered (warning)
53.33%
8 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Messaging
87.01% covered (warning)
87.01%
154 / 177
53.33% covered (warning)
53.33%
8 / 15
50.64
0.00% covered (danger)
0.00%
0 / 1
 isIcsRequired
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 isEmptyProcessListAllowed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 twigView
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
4.09
 dbTwigView
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getMailContentPreview
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getMailContent
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
4.01
 generateMailParameters
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
7
 getScopeAdminProcessListContent
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getNotificationContent
81.25% covered (warning)
81.25%
13 / 16
0.00% covered (danger)
0.00%
0 / 1
2.03
 getTemplate
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getMailSubject
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
3.00
 getMailIcs
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 generateIcsContent
86.96% covered (warning)
86.96%
20 / 23
0.00% covered (danger)
0.00%
0 / 1
5.06
 getPlainText
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getTextWithFoldedLines
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
8
1<?php
2
3/**
4 *
5 * @package Zmsentities
6 * @copyright BerlinOnline Stadtportal GmbH & Co. KG
7 *
8 */
9
10namespace BO\Zmsentities\Helper;
11
12use BO\Zmsentities\Client;
13use BO\Zmsentities\Process;
14use BO\Zmsentities\Collection\ProcessList;
15use BO\Zmsentities\Config;
16use Twig\Loader\FilesystemLoader;
17use Twig\Environment;
18use Symfony\Bridge\Twig\Extension\TranslationExtension;
19use Twig\Extra\Intl\IntlExtension;
20
21/**
22 * @SuppressWarnings(Coupling)
23 *
24 */
25class Messaging
26{
27    public static $icsRequiredForStatus = [
28        'confirmed',
29        'appointment',
30        'deleted'
31    ];
32
33    public static $allowEmptyProcesses = [
34        'overview'
35    ];
36
37    public static function isIcsRequired(
38        \BO\Zmsentities\Config $config,
39        \BO\Zmsentities\Process $process,
40        $status
41    ) {
42        $client = $process->getFirstClient();
43        $noAttachmentDomains = $config->toProperty()->notifications->noAttachmentDomains->get();
44        $noAttachmentDomains = explode(',', (string)$noAttachmentDomains);
45        foreach ($noAttachmentDomains as $matching) {
46            if (trim($matching) && strpos($client->email, '@' . trim($matching))) {
47                return false;
48            }
49        }
50        return (in_array($status, self::$icsRequiredForStatus));
51    }
52
53    public static function isEmptyProcessListAllowed($status)
54    {
55        return (in_array($status, self::$allowEmptyProcesses));
56    }
57
58    protected static $templates = array(
59        'notification' => array(
60            'appointment' => 'notification_appointment.twig',
61            'confirmed' => 'notification_confirmation.twig',
62            'queued' => 'notification_confirmation.twig',
63            'called' => 'notification_headsup.twig',
64            'reminder' => 'notification_reminder.twig',
65            'pickup' => 'notification_pickup.twig',
66            'deleted' => 'notification_deleted.twig'
67        ),
68        'mail' => array(
69            'queued' => 'mail_queued.twig',
70            'appointment' => 'mail_confirmation.twig',
71            'reminder' => 'mail_reminder.twig',
72            'pickup' => 'mail_pickup.twig',
73            'deleted' => 'mail_delete.twig',
74            'blocked' => 'mail_delete.twig',
75            'survey' => 'mail_survey.twig',
76            'overview' => 'mail_processlist_overview.twig',
77            'preconfirmed' => 'mail_preconfirmed.twig'
78        ),
79        'ics' => array(
80            'appointment' => 'icsappointment.twig',
81            'deleted' => 'icsappointment_delete.twig'
82        ),
83        'admin' => array(
84            'deleted' => 'mail_admin_delete.twig',
85            'blocked' => 'mail_admin_delete.twig',
86            'updated' => 'mail_admin_update.twig'
87        )
88    );
89
90    protected static function twigView(): Environment
91    {
92        $templatePath = TemplateFinder::getTemplatePath();
93        $customTemplatesPath = 'custom_templates/';
94
95        if (getenv("ZMS_CUSTOM_TEMPLATES_PATH")) {
96            $customTemplatesPath = getenv("ZMS_CUSTOM_TEMPLATES_PATH");
97        }
98
99        $initialTemplatePaths = [];
100
101        if (is_dir($customTemplatesPath)) {
102            $initialTemplatePaths[] = $customTemplatesPath;
103        }
104
105        $initialTemplatePaths[] = $templatePath;
106
107        $loader = new FilesystemLoader($initialTemplatePaths);
108
109        if (is_dir($customTemplatesPath)) {
110            $loader->addPath($customTemplatesPath, 'zmsentities');
111        }
112
113        $loader->addPath($templatePath, 'zmsentities');
114        $twig = new Environment($loader, array(//'cache' => '/cache/',
115        ));
116        $twig->addExtension(new TranslationExtension());
117        $twig->addExtension(new IntlExtension());
118        return $twig;
119    }
120
121    protected static function dbTwigView($templateProvider)
122    {
123        $loader = new \Twig\Loader\ArrayLoader($templateProvider->getTemplates());
124        $twig = new \Twig\Environment($loader);
125        $twig->addExtension(new TranslationExtension());
126        $twig->addExtension(new IntlExtension());
127        return $twig;
128    }
129
130    public static function getMailContentPreview($templateContent, $process)
131    {
132        $parameters = self::generateMailParameters(
133            $process,
134            new Config(),
135            null,
136            'appointment'
137        );
138
139        return self::twigView()->createTemplate($templateContent)->render($parameters);
140    }
141
142    public static function getMailContent(
143        $processList,
144        Config $config,
145        $initiator = null,
146        $status = 'appointment',
147        $templateProvider = false
148    ) {
149        $parameters = self::generateMailParameters($processList, $config, $initiator, $status);
150
151        (new ProcessList())->testProcessListLength($processList, self::isEmptyProcessListAllowed($status));
152        $template = self::getTemplate('mail', $status);
153        if ($initiator) {
154            $template = self::getTemplate('admin', $status);
155        }
156        if (!$template) {
157            $exception = new \BO\Zmsentities\Exception\TemplateNotFound("Template for status $status not found");
158            $exception->data = $status;
159            throw $exception;
160        }
161
162        if ($templateProvider) {
163            $message = self::dbTwigView($templateProvider)->render($template, $parameters);
164        } else {
165            $message = self::twigView()->render('messaging/' . $template, $parameters);
166        }
167
168        return $message;
169    }
170
171    public static function generateMailParameters($processList, $config, $initiator, $status)
172    {
173        $collection = (new ProcessList())->testProcessListLength($processList, self::isEmptyProcessListAllowed($status));
174        $mainProcess = $collection->getFirst();
175        $date = (new \DateTimeImmutable())->setTimestamp(0);
176        $client = (new Client());
177        if ($mainProcess) {
178            $collection = $collection->withoutProcessByStatus($mainProcess, $status);
179            $date = $mainProcess->getFirstAppointment()->toDateTime()->format('U');
180            $client = $mainProcess->getFirstClient();
181        }
182
183        $requestGroups = [];
184        if ($mainProcess) {
185            foreach ($mainProcess->requests as $request) {
186                if (! isset($requestGroups[$request->id])) {
187                    $requestGroups[$request->id] = [
188                        'request' => $request,
189                        'count' => 0
190                    ];
191                }
192                $requestGroups[$request->id]['count']++;
193            }
194        }
195
196        return [
197            'date' => $date,
198            'client' => $client,
199            'process' => $mainProcess,
200            'requestGroups' => $requestGroups,
201            'processList' => $collection->sortByAppointmentDate(),
202            'config' => $config,
203            'initiator' => $initiator,
204            'appointmentLink' => base64_encode(json_encode([
205                'id' => $mainProcess ? $mainProcess->id : '',
206                'authKey' => $mainProcess ? $mainProcess->authKey : ''
207            ]))
208        ];
209    }
210
211    public static function getScopeAdminProcessListContent(
212        \BO\Zmsentities\Collection\ProcessList $processList,
213        \BO\Zmsentities\Scope $scope,
214        \DateTimeInterface $dateTime
215    ) {
216        $message = self::twigView()->render(
217            'messaging/mail_scopeadmin_processlist.twig',
218            array(
219                'dateTime' => $dateTime,
220                'processList' => $processList,
221                'scope' => $scope
222            )
223        );
224        return $message;
225    }
226
227    public static function getNotificationContent(Process $process, Config $config, $status = 'appointment')
228    {
229        $appointment = $process->getFirstAppointment();
230        $template = self::getTemplate('notification', $status);
231        if (!$template) {
232            $exception = new \BO\Zmsentities\Exception\TemplateNotFound("Template for $process not found");
233            $exception->data = $process;
234            throw $exception;
235        }
236        $message = self::twigView()->render(
237            'messaging/' . $template,
238            array(
239                'date' => $appointment->toDateTime()->format('U'),
240                'client' => $process->getFirstClient(),
241                'process' => $process,
242                'config' => $config
243            )
244        );
245        return $message;
246    }
247
248    protected static function getTemplate($type, $status)
249    {
250        $template = null;
251        if (Property::__keyExists($type, self::$templates)) {
252            if (Property::__keyExists($status, self::$templates[$type])) {
253                $template = self::$templates[$type][$status];
254            }
255        }
256        return $template;
257    }
258
259    public static function getMailSubject(
260        Process $process,
261        Config $config,
262        $initiator = null,
263        $status = 'appointment',
264        $templateProvider = null
265    ) {
266        $appointment = $process->getFirstAppointment();
267        $parameters = [
268            'date' => $appointment ? $appointment->toDateTime()->format('U') : null,
269            'client' => $process->getFirstClient(),
270            'process' => $process,
271            'config' => $config,
272            'initiator' => $initiator,
273            'status' => $status
274        ];
275
276        $template = 'subjects.twig';
277
278        if ($templateProvider) {
279            $subject = self::dbTwigView($templateProvider)->render($template, $parameters);
280        } else {
281            $subject = self::twigView()->render('messaging/' . $template, $parameters);
282        }
283
284        return trim($subject);
285    }
286
287    public static function getMailIcs(
288        Process $process,
289        Config $config,
290        $status = 'appointment',
291        $initiator = null,
292        $now = false,
293        $templateProvider = false
294    ) {
295        $ics = new \BO\Zmsentities\Ics();
296        $message = self::getMailContent($process, $config, $initiator, $status, $templateProvider);
297        $ics->content = self::generateIcsContent($process, $config, $status, $now, $templateProvider, $message);
298
299        return $ics;
300    }
301
302    protected static function generateIcsContent(
303        Process $process,
304        Config $config,
305        $status = 'appointment',
306        $now = false,
307        $templateProvider = false,
308        $message = '' // Pass $message from getMailIcs, or query if not set
309    ) {
310        // If $message is not provided, retrieve it from the getMailContent query
311        if (empty($message)) {
312            $message = self::getMailContent($process, $config, null, $status, $templateProvider);
313        }
314
315        // Convert the email message to plain text for the ICS description
316        $plainTextDescription = self::getPlainText($message);
317
318        // Get the ICS template for the process status dynamically
319        $template = self::getTemplate('ics', $status);
320        if (!$template) {
321            throw new \Exception("ICS template for status $status not found");
322        }
323
324        // Extract the first appointment details
325        $appointment = $process->getFirstAppointment();
326        $currentYear = $appointment->getStartTime()->format('Y');
327
328        // Prepare parameters for ICS rendering, including the plain text description
329        $parameters = [
330            'date' => $appointment->toDateTime()->format('U'),
331            'startTime' => $appointment->getStartTime()->format('U'),
332            'endTime' => $appointment->getEndTime()->format('U'),
333            'startSummerTime' => \BO\Zmsentities\Helper\DateTime::getSummerTimeStartDateTime($currentYear)->format('U'),
334            'endSummerTime' => \BO\Zmsentities\Helper\DateTime::getSummerTimeEndDateTime($currentYear)->format('U'),
335            'process' => $process,
336            'timestamp' => (!$now) ? time() : $now,
337            'message' => $plainTextDescription // Pass the plain text email content to the ICS template
338        ];
339
340        // Render the ICS content using Twig and the fetched template
341        if ($templateProvider) {
342            $icsString = self::dbTwigView($templateProvider)->render($template, $parameters);
343        } else {
344            $icsString = self::twigView()->render('messaging/' . $template, $parameters);
345        }
346
347        // Decode HTML entities to plain text and ensure lines follow ICS standards
348        $icsString = html_entity_decode($icsString);
349        return self::getTextWithFoldedLines($icsString);
350    }
351
352
353
354
355
356
357
358
359    public static function getPlainText($content, $lineBreak = "\n")
360    {
361        $converter = new \League\HTMLToMarkdown\HtmlConverter();
362        $converter->getConfig()->setOption('remove_nodes', 'script');
363        $converter->getConfig()->setOption('strip_tags', true);
364        $converter->getConfig()->setOption('hard_break', true);
365        $converter->getConfig()->setOption('use_autolinks', false);
366        $text = $converter->convert($content);
367        $text = str_replace(',', '\,', $text);
368        $text = str_replace(';', '\;', $text);
369        $text = str_replace("\n", $lineBreak, $text);
370        return trim($text);
371    }
372
373    public static function getTextWithFoldedLines($content)
374    {
375        $newLines = [];
376        $lines = explode("\n", $content);
377        foreach ($lines as $text) {
378            $subline = '';
379            while (strlen($text) > 75) {
380                $line = mb_substr($text, 0, 72);
381                $llength = mb_strlen($line);
382                $subline .= $line . chr(13) . chr(10) . chr(32);
383                $text = mb_substr($text, $llength);
384            }
385            if (!empty($text) && 0 < strlen($subline)) {
386                $subline .= $text;
387            }
388            if (0 < strlen($subline)) {
389                $newLines[] = $subline;
390            }
391            if (!empty($text) && '' == $subline) {
392                $newLines[] = $text;
393            }
394        }
395        return implode(chr(13) . chr(10), $newLines);
396    }
397}