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    ];
31
32    public static $allowEmptyProcesses = [
33        'overview'
34    ];
35
36    public static function isIcsRequired(
37        \BO\Zmsentities\Config $config,
38        \BO\Zmsentities\Process $process,
39        $status
40    ) {
41        $client = $process->getFirstClient();
42        $noAttachmentDomains = $config->toProperty()->notifications->noAttachmentDomains->get();
43        $noAttachmentDomains = explode(',', (string)$noAttachmentDomains);
44        foreach ($noAttachmentDomains as $matching) {
45            if (trim($matching) && strpos($client->email, '@' . trim($matching))) {
46                return false;
47            }
48        }
49        return (in_array($status, self::$icsRequiredForStatus));
50    }
51
52    public static function isEmptyProcessListAllowed($status)
53    {
54        return (in_array($status, self::$allowEmptyProcesses));
55    }
56
57    protected static $templates = array(
58        'notification' => array(
59            'appointment' => 'notification_appointment.twig',
60            'confirmed' => 'notification_confirmation.twig',
61            'queued' => 'notification_confirmation.twig',
62            'called' => 'notification_headsup.twig',
63            'reminder' => 'notification_reminder.twig',
64            'pickup' => 'notification_pickup.twig',
65            'deleted' => 'notification_deleted.twig'
66        ),
67        'mail' => array(
68            'queued' => 'mail_queued.twig',
69            'appointment' => 'mail_confirmation.twig',
70            'reminder' => 'mail_reminder.twig',
71            'pickup' => 'mail_pickup.twig',
72            'deleted' => 'mail_delete.twig',
73            'blocked' => 'mail_delete.twig',
74            'survey' => 'mail_survey.twig',
75            'overview' => 'mail_processlist_overview.twig',
76            'preconfirmed' => 'mail_preconfirmed.twig'
77        ),
78        'ics' => array(
79            'appointment' => 'icsappointment.twig',
80            'deleted' => 'icsappointment_delete.twig'
81        ),
82        'admin' => array(
83            'deleted' => 'mail_admin_delete.twig',
84            'blocked' => 'mail_admin_delete.twig',
85            'updated' => 'mail_admin_update.twig'
86        )
87    );
88
89    protected static function twigView(): Environment
90    {
91        $templatePath = TemplateFinder::getTemplatePath();
92        $customTemplatesPath = 'custom_templates/';
93
94        if (getenv("ZMS_CUSTOM_TEMPLATES_PATH")) {
95            $customTemplatesPath = getenv("ZMS_CUSTOM_TEMPLATES_PATH");
96        }
97
98        $initialTemplatePaths = [];
99
100        if (is_dir($customTemplatesPath)) {
101            $initialTemplatePaths[] = $customTemplatesPath;
102        }
103
104        $initialTemplatePaths[] = $templatePath;
105
106        $loader = new FilesystemLoader($initialTemplatePaths);
107
108        if (is_dir($customTemplatesPath)) {
109            $loader->addPath($customTemplatesPath, 'zmsentities');
110        }
111
112        $loader->addPath($templatePath, 'zmsentities');
113        $twig = new Environment($loader, array(//'cache' => '/cache/',
114        ));
115        $twig->addExtension(new TranslationExtension());
116        $twig->addExtension(new IntlExtension());
117        return $twig;
118    }
119
120    protected static function dbTwigView($templateProvider)
121    {
122        $loader = new \Twig\Loader\ArrayLoader($templateProvider->getTemplates());
123        $twig = new \Twig\Environment($loader);
124        $twig->addExtension(new TranslationExtension());
125        $twig->addExtension(new IntlExtension());
126        return $twig;
127    }
128
129    public static function getMailContentPreview($templateContent, $process)
130    {
131        $parameters = self::generateMailParameters(
132            $process,
133            new Config(),
134            null,
135            'appointment'
136        );
137
138        return self::twigView()->createTemplate($templateContent)->render($parameters);
139    }
140
141    public static function getMailContent(
142        $processList,
143        Config $config,
144        $initiator = null,
145        $status = 'appointment',
146        $templateProvider = false
147    ) {
148        $parameters = self::generateMailParameters($processList, $config, $initiator, $status);
149
150        (new ProcessList())->testProcessListLength($processList, self::isEmptyProcessListAllowed($status));
151        $template = self::getTemplate('mail', $status);
152        if ($initiator) {
153            $template = self::getTemplate('admin', $status);
154        }
155        if (!$template) {
156            $exception = new \BO\Zmsentities\Exception\TemplateNotFound("Template for status $status not found");
157            $exception->data = $status;
158            throw $exception;
159        }
160
161        if ($templateProvider) {
162            $message = self::dbTwigView($templateProvider)->render($template, $parameters);
163        } else {
164            $message = self::twigView()->render('messaging/' . $template, $parameters);
165        }
166
167        return $message;
168    }
169
170    public static function generateMailParameters($processList, $config, $initiator, $status)
171    {
172        $collection = (new ProcessList())
173            ->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}