Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.08% covered (warning)
87.08%
155 / 178
53.33% covered (warning)
53.33%
8 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Messaging
87.08% covered (warning)
87.08%
155 / 178
53.33% covered (warning)
53.33%
8 / 15
50.56
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%
31 / 31
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())
174            ->testProcessListLength($processList, self::isEmptyProcessListAllowed($status));
175        $mainProcess = $collection->getFirst();
176        $date = (new \DateTimeImmutable())->setTimestamp(0);
177        $client = (new Client());
178        if ($mainProcess) {
179            $collection = $collection->withoutProcessByStatus($mainProcess, $status);
180            $date = $mainProcess->getFirstAppointment()->toDateTime()->format('U');
181            $client = $mainProcess->getFirstClient();
182        }
183
184        $requestGroups = [];
185        if ($mainProcess) {
186            foreach ($mainProcess->requests as $request) {
187                if (! isset($requestGroups[$request->id])) {
188                    $requestGroups[$request->id] = [
189                        'request' => $request,
190                        'count' => 0
191                    ];
192                }
193                $requestGroups[$request->id]['count']++;
194            }
195        }
196
197        return [
198            'date' => $date,
199            'client' => $client,
200            'process' => $mainProcess,
201            'requestGroups' => $requestGroups,
202            'processList' => $collection->sortByAppointmentDate(),
203            'config' => $config,
204            'initiator' => $initiator,
205            'appointmentLink' => base64_encode(json_encode([
206                'id' => $mainProcess ? $mainProcess->id : '',
207                'authKey' => $mainProcess ? $mainProcess->authKey : ''
208            ]))
209        ];
210    }
211
212    public static function getScopeAdminProcessListContent(
213        \BO\Zmsentities\Collection\ProcessList $processList,
214        \BO\Zmsentities\Scope $scope,
215        \DateTimeInterface $dateTime
216    ) {
217        $message = self::twigView()->render(
218            'messaging/mail_scopeadmin_processlist.twig',
219            array(
220                'dateTime' => $dateTime,
221                'processList' => $processList,
222                'scope' => $scope
223            )
224        );
225        return $message;
226    }
227
228    public static function getNotificationContent(Process $process, Config $config, $status = 'appointment')
229    {
230        $appointment = $process->getFirstAppointment();
231        $template = self::getTemplate('notification', $status);
232        if (!$template) {
233            $exception = new \BO\Zmsentities\Exception\TemplateNotFound("Template for $process not found");
234            $exception->data = $process;
235            throw $exception;
236        }
237        $message = self::twigView()->render(
238            'messaging/' . $template,
239            array(
240                'date' => $appointment->toDateTime()->format('U'),
241                'client' => $process->getFirstClient(),
242                'process' => $process,
243                'config' => $config
244            )
245        );
246        return $message;
247    }
248
249    protected static function getTemplate($type, $status)
250    {
251        $template = null;
252        if (Property::__keyExists($type, self::$templates)) {
253            if (Property::__keyExists($status, self::$templates[$type])) {
254                $template = self::$templates[$type][$status];
255            }
256        }
257        return $template;
258    }
259
260    public static function getMailSubject(
261        Process $process,
262        Config $config,
263        $initiator = null,
264        $status = 'appointment',
265        $templateProvider = null
266    ) {
267        $appointment = $process->getFirstAppointment();
268        $parameters = [
269            'date' => $appointment ? $appointment->toDateTime()->format('U') : null,
270            'client' => $process->getFirstClient(),
271            'process' => $process,
272            'config' => $config,
273            'initiator' => $initiator,
274            'status' => $status
275        ];
276
277        $template = 'subjects.twig';
278
279        if ($templateProvider) {
280            $subject = self::dbTwigView($templateProvider)->render($template, $parameters);
281        } else {
282            $subject = self::twigView()->render('messaging/' . $template, $parameters);
283        }
284
285        return trim($subject);
286    }
287
288    public static function getMailIcs(
289        Process $process,
290        Config $config,
291        $status = 'appointment',
292        $initiator = null,
293        $now = false,
294        $templateProvider = false
295    ) {
296        $ics = new \BO\Zmsentities\Ics();
297        $message = self::getMailContent($process, $config, $initiator, $status, $templateProvider);
298        $ics->content = self::generateIcsContent($process, $config, $status, $now, $templateProvider, $message);
299
300        return $ics;
301    }
302
303    protected static function generateIcsContent(
304        Process $process,
305        Config $config,
306        $status = 'appointment',
307        $now = false,
308        $templateProvider = false,
309        $message = '' // Pass $message from getMailIcs, or query if not set
310    ) {
311        // If $message is not provided, retrieve it from the getMailContent query
312        if (empty($message)) {
313            $message = self::getMailContent($process, $config, null, $status, $templateProvider);
314        }
315
316        // Convert the email message to plain text for the ICS description
317        $plainTextDescription = self::getPlainText($message);
318
319        // Get the ICS template for the process status dynamically
320        $template = self::getTemplate('ics', $status);
321        if (!$template) {
322            throw new \Exception("ICS template for status $status not found");
323        }
324
325        // Extract the first appointment details
326        $appointment = $process->getFirstAppointment();
327        $currentYear = $appointment->getStartTime()->format('Y');
328
329        // Prepare parameters for ICS rendering, including the plain text description
330        $parameters = [
331            'date' => $appointment->toDateTime()->format('U'),
332            'startTime' => $appointment->getStartTime()->format('U'),
333            'endTime' => $appointment->getEndTime()->format('U'),
334            'startSummerTime' => \BO\Zmsentities\Helper\DateTime::getSummerTimeStartDateTime($currentYear)->format('U'),
335            'endSummerTime' => \BO\Zmsentities\Helper\DateTime::getSummerTimeEndDateTime($currentYear)->format('U'),
336            'process' => $process,
337            'timestamp' => (!$now) ? time() : $now,
338            'message' => $plainTextDescription // Pass the plain text email content to the ICS template
339        ];
340
341        // Render the ICS content using Twig and the fetched template
342        if ($templateProvider) {
343            $icsString = self::dbTwigView($templateProvider)->render($template, $parameters);
344        } else {
345            $icsString = self::twigView()->render('messaging/' . $template, $parameters);
346        }
347
348        // Decode HTML entities to plain text and ensure lines follow ICS standards
349        $icsString = html_entity_decode($icsString);
350        return self::getTextWithFoldedLines($icsString);
351    }
352
353
354
355
356
357
358
359
360    public static function getPlainText($content, $lineBreak = "\n")
361    {
362        $converter = new \League\HTMLToMarkdown\HtmlConverter();
363        $converter->getConfig()->setOption('remove_nodes', 'script');
364        $converter->getConfig()->setOption('strip_tags', true);
365        $converter->getConfig()->setOption('hard_break', true);
366        $converter->getConfig()->setOption('use_autolinks', false);
367        $text = $converter->convert($content);
368        $text = str_replace(',', '\,', $text);
369        $text = str_replace(';', '\;', $text);
370        $text = str_replace("\n", $lineBreak, $text);
371        return trim($text);
372    }
373
374    public static function getTextWithFoldedLines($content)
375    {
376        $newLines = [];
377        $lines = explode("\n", $content);
378        foreach ($lines as $text) {
379            $subline = '';
380            while (strlen($text) > 75) {
381                $line = mb_substr($text, 0, 72);
382                $llength = mb_strlen($line);
383                $subline .= $line . chr(13) . chr(10) . chr(32);
384                $text = mb_substr($text, $llength);
385            }
386            if (!empty($text) && 0 < strlen($subline)) {
387                $subline .= $text;
388            }
389            if (0 < strlen($subline)) {
390                $newLines[] = $subline;
391            }
392            if (!empty($text) && '' == $subline) {
393                $newLines[] = $text;
394            }
395        }
396        return implode(chr(13) . chr(10), $newLines);
397    }
398}