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