1<?php
2/* Copyright (c) 1998-2012 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4use ILIAS\BackgroundTasks\Implementation\Bucket\BasicBucket;
5
6/**
7 * @author Stefan Meyer <meyer@leifos.com>
8 * @author Michael Jansen <mjansen@databay.de>
9 */
10class ilMail
11{
12    const ILIAS_HOST = 'ilias';
13
14    /** @var ilLanguage */
15    protected $lng;
16
17    /** @var ilDBInterface */
18    protected $db;
19
20    /** @var ilFileDataMail */
21    protected $mfile;
22
23    /** @var ilMailOptions */
24    protected $mail_options;
25
26    /** @var ilMailbox */
27    protected $mailbox;
28
29    /** @var int */
30    public $user_id;
31
32    /** @var string */
33    protected $table_mail;
34
35    /** @var string */
36    protected $table_mail_saved;
37
38    /** @var array */
39    protected $mail_data = array();
40
41    /** @var int */
42    protected $mail_obj_ref_id;
43
44    /** @var bool */
45    protected $save_in_sentbox;
46
47    /** @var bool */
48    protected $appendInstallationSignature = false;
49
50    /** @var ilAppEventHandler */
51    private $eventHandler;
52
53    /** @var ilMailAddressTypeFactory */
54    private $mailAddressTypeFactory;
55
56    /** @var ilMailRfc822AddressParserFactory */
57    private $mailAddressParserFactory;
58
59    /** @var mixed|null */
60    protected $contextId = null;
61
62    /** @var array */
63    protected $contextParameters = [];
64
65    /** @var ilLogger */
66    protected $logger;
67
68    /** @var ilMailOptions[] */
69    protected $mailOptionsByUsrIdMap = [];
70
71    /** @var ilObjUser[] */
72    protected $userInstancesByIdMap = [];
73
74    /** @var callable|null */
75    protected $usrIdByLoginCallable = null;
76
77    /** @var int */
78    protected $maxRecipientCharacterLength = 998;
79
80    /** @var ilMailMimeSenderFactory */
81    protected $senderFactory;
82
83    /**
84     * @param integer $a_user_id
85     * @param ilMailAddressTypeFactory|null $mailAddressTypeFactory
86     * @param ilMailRfc822AddressParserFactory|null $mailAddressParserFactory
87     * @param ilAppEventHandler|null $eventHandler
88     * @param ilLogger|null $logger
89     * @param ilDBInterface|null $db
90     * @param ilLanguage|null $lng
91     * @param ilFileDataMail|null $mailFileData
92     * @param ilMailOptions|null $mailOptions
93     * @param ilMailbox|null $mailBox
94     * @param ilMailMimeSenderFactory|null $senderFactory
95     * @param callable|null $usrIdByLoginCallable
96     * @param int|null $mailAdminNodeRefId
97     */
98    public function __construct(
99        $a_user_id,
100        ilMailAddressTypeFactory $mailAddressTypeFactory = null,
101        ilMailRfc822AddressParserFactory $mailAddressParserFactory = null,
102        ilAppEventHandler $eventHandler = null,
103        ilLogger $logger = null,
104        ilDBInterface $db = null,
105        ilLanguage $lng = null,
106        ilFileDataMail $mailFileData = null,
107        ilMailOptions $mailOptions = null,
108        ilMailbox $mailBox = null,
109        ilMailMimeSenderFactory $senderFactory = null,
110        callable $usrIdByLoginCallable = null,
111        int $mailAdminNodeRefId = null
112    ) {
113        global $DIC;
114
115        if ($logger === null) {
116            $logger = ilLoggerFactory::getLogger('mail');
117        }
118        if ($mailAddressTypeFactory === null) {
119            $mailAddressTypeFactory = new ilMailAddressTypeFactory(null, $logger);
120        }
121        if ($mailAddressParserFactory === null) {
122            $mailAddressParserFactory = new ilMailRfc822AddressParserFactory();
123        }
124        if ($eventHandler === null) {
125            $eventHandler = $DIC->event();
126        }
127        if ($db === null) {
128            $db = $DIC->database();
129        }
130        if ($lng === null) {
131            $lng = $DIC->language();
132        }
133        if ($mailFileData === null) {
134            $mailFileData = new ilFileDataMail($a_user_id);
135        }
136        if ($mailOptions === null) {
137            $mailOptions = new ilMailOptions($a_user_id);
138        }
139        if ($mailBox === null) {
140            $mailBox = new ilMailbox($a_user_id);
141        }
142        if ($senderFactory === null) {
143            $senderFactory = $GLOBALS["DIC"]["mail.mime.sender.factory"];
144        }
145        if ($usrIdByLoginCallable === null) {
146            $usrIdByLoginCallable = function (string $login) {
147                return ilObjUser::_lookupId($login);
148            };
149        }
150
151        $this->user_id = (int) $a_user_id;
152        $this->mailAddressParserFactory = $mailAddressParserFactory;
153        $this->mailAddressTypeFactory = $mailAddressTypeFactory;
154        $this->eventHandler = $eventHandler;
155        $this->logger = $logger;
156        $this->db = $db;
157        $this->lng = $lng;
158        $this->mfile = $mailFileData;
159        $this->mail_options = $mailOptions;
160        $this->mailbox = $mailBox;
161        $this->senderFactory = $senderFactory;
162        $this->usrIdByLoginCallable = $usrIdByLoginCallable;
163
164        $this->mail_obj_ref_id = $mailAdminNodeRefId;
165        if (null === $this->mail_obj_ref_id) {
166            $this->readMailObjectReferenceId();
167        }
168
169        $this->lng->loadLanguageModule('mail');
170        $this->table_mail = 'mail';
171        $this->table_mail_saved = 'mail_saved';
172        $this->setSaveInSentbox(false);
173    }
174
175    /**
176     * @param string $contextId
177     * @return ilMail
178     */
179    public function withContextId(string $contextId) : self
180    {
181        $clone = clone $this;
182
183        $clone->contextId = $contextId;
184
185        return $clone;
186    }
187
188    /**
189     * @param array $parameters
190     * @return ilMail
191     */
192    public function withContextParameters(array $parameters) : self
193    {
194        $clone = clone $this;
195
196        $clone->contextParameters = $parameters;
197
198        return $clone;
199    }
200
201    /**
202     * @return bool
203     */
204    protected function isSystemMail() : bool
205    {
206        return $this->user_id == ANONYMOUS_USER_ID;
207    }
208
209    /**
210     * @param string $newRecipient
211     * @param string $existingRecipients
212     * @return bool
213     */
214    public function existsRecipient(string $newRecipient, string $existingRecipients) : bool
215    {
216        $newAddresses = new ilMailAddressListImpl($this->parseAddresses($newRecipient));
217        $addresses = new ilMailAddressListImpl($this->parseAddresses($existingRecipients));
218
219        $list = new ilMailDiffAddressList($newAddresses, $addresses);
220
221        $diffedAddresses = $list->value();
222
223        return count($diffedAddresses) === 0;
224    }
225
226    /**
227     * @param bool $saveInSentbox
228     */
229    public function setSaveInSentbox(bool $saveInSentbox) : void
230    {
231        $this->save_in_sentbox = (bool) $saveInSentbox;
232    }
233
234    /**
235     * @return bool
236     */
237    public function getSaveInSentbox() : bool
238    {
239        return (bool) $this->save_in_sentbox;
240    }
241
242    /**
243     * Read and set the mail object ref id (administration node)
244     */
245    protected function readMailObjectReferenceId() : void
246    {
247        $this->mail_obj_ref_id = (int) ilMailGlobalServices::getMailObjectRefId();
248    }
249
250    /**
251     * @return int
252     */
253    public function getMailObjectReferenceId() : int
254    {
255        return $this->mail_obj_ref_id;
256    }
257
258    /**
259     * Prepends the full name of each ILIAS login name (if user has a public profile) found
260     * in the passed string and brackets the ILIAS login name afterwards.
261     * @param string $recipients A string containing to, cc or bcc recipients
262     * @return string
263     */
264    public function formatNamesForOutput(string $recipients) : string
265    {
266        global $DIC;
267
268        $recipients = trim($recipients);
269        if (0 === strlen($recipients)) {
270            return $this->lng->txt('not_available');
271        }
272
273        $names = [];
274
275        $recipients = array_filter(array_map('trim', explode(',', $recipients)));
276        foreach ($recipients as $recipient) {
277            $usrId = ilObjUser::_lookupId($recipient);
278            if ($usrId > 0) {
279                $pp = ilObjUser::_lookupPref($usrId, 'public_profile');
280                if ($pp === 'g' || ($pp === 'y' && !$DIC->user()->isAnonymous())) {
281                    $user = $this->getUserInstanceById($usrId);
282                    $names[] = $user->getFullname() . ' [' . $recipient . ']';
283                    continue;
284                }
285            }
286
287            $names[] = $recipient;
288        }
289
290        return implode(', ', $names);
291    }
292
293    /**
294     * @param int $mailId
295     * @return array|null
296     */
297    public function getPreviousMail(int $mailId) : ?array
298    {
299        $this->db->setLimit(1, 0);
300
301        $query = implode(' ', [
302            "SELECT b.* FROM {$this->table_mail} a",
303            "INNER JOIN {$this->table_mail} b ON b.folder_id = a.folder_id",
304            'AND b.user_id = a.user_id AND b.send_time > a.send_time',
305            'WHERE a.user_id = %s AND a.mail_id = %s ORDER BY b.send_time ASC',
306        ]);
307        $res = $this->db->queryF(
308            $query,
309            ['integer', 'integer'],
310            [$this->user_id, $mailId]
311        );
312
313        $this->mail_data = $this->fetchMailData($this->db->fetchAssoc($res));
314
315        return $this->mail_data;
316    }
317
318    /**
319     * @param int $mailId
320     * @return array|null
321     */
322    public function getNextMail(int $mailId) : ?array
323    {
324        $this->db->setLimit(1, 0);
325
326        $query = implode(' ', [
327            "SELECT b.* FROM {$this->table_mail} a",
328            "INNER JOIN {$this->table_mail} b ON b.folder_id = a.folder_id",
329            'AND b.user_id = a.user_id AND b.send_time < a.send_time',
330            'WHERE a.user_id = %s AND a.mail_id = %s ORDER BY b.send_time DESC',
331        ]);
332        $res = $this->db->queryF(
333            $query,
334            ['integer', 'integer'],
335            [$this->user_id, $mailId]
336        );
337
338        $this->mail_data = $this->fetchMailData($this->db->fetchAssoc($res));
339
340        return $this->mail_data;
341    }
342
343    /**
344     * @param int $a_folder_id The id of the folder
345     * @param array $filter An optional filter array
346     * @return array
347     */
348    public function getMailsOfFolder($a_folder_id, $filter = []) : array
349    {
350        $mails = [];
351
352        $query =
353            "SELECT sender_id, m_subject, mail_id, m_status, send_time " .
354            "FROM {$this->table_mail} " .
355            "LEFT JOIN object_data ON obj_id = sender_id " .
356            "WHERE user_id = %s AND folder_id = %s " .
357            "AND ((sender_id > 0 AND sender_id IS NOT NULL AND obj_id IS NOT NULL) OR (sender_id = 0 OR sender_id IS NULL))";
358
359        if (isset($filter['status']) && strlen($filter['status']) > 0) {
360            $query .= ' AND m_status = ' . $this->db->quote($filter['status'], 'text');
361        }
362
363        $query .= " ORDER BY send_time DESC";
364
365        $res = $this->db->queryF(
366            $query,
367            ['integer', 'integer'],
368            [$this->user_id, $a_folder_id]
369        );
370
371        while ($row = $this->db->fetchAssoc($res)) {
372            $mails[] = $this->fetchMailData($row);
373        }
374
375        return array_filter($mails);
376    }
377
378    /**
379     * @param int $folderId
380     * @return int
381     */
382    public function countMailsOfFolder(int $folderId) : int
383    {
384        $res = $this->db->queryF(
385            "SELECT COUNT(*) FROM {$this->table_mail} WHERE user_id = %s AND folder_id = %s",
386            ['integer', 'integer'],
387            [$this->user_id, $folderId]
388        );
389
390        return $this->db->numRows($res);
391    }
392
393    /**
394     * @param int $folderId
395     */
396    public function deleteMailsOfFolder(int $folderId) : void
397    {
398        $mails = $this->getMailsOfFolder($folderId);
399        foreach ($mails as $mail_data) {
400            $this->deleteMails([$mail_data['mail_id']]);
401        }
402    }
403
404    /**
405     * @param int $mailId
406     * @return array|null
407     */
408    public function getMail(int $mailId) : ?array
409    {
410        $res = $this->db->queryF(
411            "SELECT * FROM {$this->table_mail} WHERE user_id = %s AND mail_id = %s",
412            ['integer', 'integer'],
413            [$this->user_id, $mailId]
414        );
415
416        $this->mail_data = $this->fetchMailData($this->db->fetchAssoc($res));
417
418        return $this->mail_data;
419    }
420
421    /**
422     * @param int[] $mailIds
423     */
424    public function markRead(array $mailIds) : void
425    {
426        $values = [];
427        $types = [];
428
429        $query = "UPDATE {$this->table_mail} SET m_status = %s WHERE user_id = %s ";
430        array_push($types, 'text', 'integer');
431        array_push($values, 'read', $this->user_id);
432
433        if (count($mailIds) > 0) {
434            $query .= ' AND ' . $this->db->in('mail_id', $mailIds, false, 'integer');
435        }
436
437        $this->db->manipulateF($query, $types, $values);
438    }
439
440    /**
441     * @param int[] $mailIds
442     */
443    public function markUnread(array $mailIds) : void
444    {
445        $values = array();
446        $types = array();
447
448        $query = "UPDATE {$this->table_mail} SET m_status = %s WHERE user_id = %s ";
449        array_push($types, 'text', 'integer');
450        array_push($values, 'unread', $this->user_id);
451
452        if (count($mailIds) > 0) {
453            $query .= ' AND ' . $this->db->in('mail_id', $mailIds, false, 'integer');
454        }
455
456        $this->db->manipulateF($query, $types, $values);
457    }
458
459    /**
460     * @param int[] $mailIds
461     * @param int $folderId
462     * @return bool
463     */
464    public function moveMailsToFolder(array $mailIds, int $folderId) : bool
465    {
466        $values = [];
467        $types = [];
468
469        $mailIds = array_filter(array_map('intval', $mailIds));
470
471        if (0 === count($mailIds)) {
472            return false;
473        }
474
475        $query =
476            "UPDATE {$this->table_mail} " .
477            "INNER JOIN mail_obj_data " .
478            "ON mail_obj_data.obj_id = %s AND mail_obj_data.user_id = %s " .
479            "SET {$this->table_mail}.folder_id = mail_obj_data.obj_id " .
480            "WHERE {$this->table_mail}.user_id = %s";
481        array_push($types, 'integer', 'integer', 'integer');
482        array_push($values, $folderId, $this->user_id, $this->user_id);
483
484        $query .= ' AND ' . $this->db->in('mail_id', $mailIds, false, 'integer');
485
486        $affectedRows = $this->db->manipulateF($query, $types, $values);
487
488        return $affectedRows > 0;
489    }
490
491    /**
492     * @param int[] $mailIds
493     */
494    public function deleteMails(array $mailIds) : void
495    {
496        $mailIds = array_filter(array_map('intval', $mailIds));
497        foreach ($mailIds as $id) {
498            $this->db->manipulateF(
499                "DELETE FROM {$this->table_mail} WHERE user_id = %s AND mail_id = %s",
500                ['integer', 'integer'],
501                [$this->user_id, $id]
502            );
503            $this->mfile->deassignAttachmentFromDirectory($id);
504        }
505    }
506
507    /**
508     * @param array|null $row
509     * @return array|null
510     */
511    protected function fetchMailData(?array $row) : ?array
512    {
513        if (!is_array($row) || empty($row)) {
514            return null;
515        }
516
517        $row['attachments'] = unserialize(stripslashes($row['attachments']));
518        $row['tpl_ctx_params'] = (array) (@json_decode($row['tpl_ctx_params'], true));
519
520        return $row;
521    }
522
523    /**
524     * @param int $usrId
525     * @param int $folderId
526     * @return int
527     */
528    public function getNewDraftId(int $usrId, int $folderId) : int
529    {
530        $nextId = (int) $this->db->nextId($this->table_mail);
531        $this->db->insert($this->table_mail, [
532            'mail_id' => ['integer', $nextId],
533            'user_id' => ['integer', $usrId],
534            'folder_id' => ['integer', $folderId],
535            'sender_id' => ['integer', $usrId]
536        ]);
537
538        return $nextId;
539    }
540
541    public function updateDraft(
542        $a_folder_id,
543        $a_attachments,
544        $a_rcp_to,
545        $a_rcp_cc,
546        $a_rcp_bcc,
547        $a_m_email,
548        $a_m_subject,
549        $a_m_message,
550        $a_draft_id = 0,
551        $a_use_placeholders = 0,
552        $a_tpl_context_id = null,
553        $a_tpl_context_params = []
554    ) {
555        $this->db->update(
556            $this->table_mail,
557            [
558                'folder_id' => ['integer', $a_folder_id],
559                'attachments' => ['clob', serialize($a_attachments)],
560                'send_time' => ['timestamp', date('Y-m-d H:i:s', time())],
561                'rcp_to' => ['clob', $a_rcp_to],
562                'rcp_cc' => ['clob', $a_rcp_cc],
563                'rcp_bcc' => ['clob', $a_rcp_bcc],
564                'm_status' => ['text', 'read'],
565                'm_email' => ['integer', $a_m_email],
566                'm_subject' => ['text', $a_m_subject],
567                'm_message' => ['clob', $a_m_message],
568                'use_placeholders' => ['integer', $a_use_placeholders],
569                'tpl_ctx_id' => ['text', $a_tpl_context_id],
570                'tpl_ctx_params' => ['blob', @json_encode((array) $a_tpl_context_params)]
571            ],
572            [
573                'mail_id' => ['integer', $a_draft_id]
574            ]
575        );
576
577        return $a_draft_id;
578    }
579
580    /**
581     * @param integer $folderId
582     * @param integer $senderUsrId
583     * @param array $attachments
584     * @param string $to
585     * @param string $cc
586     * @param string $bcc
587     * @param string $status
588     * @param integer $email
589     * @param string $subject
590     * @param string $message
591     * @param integer $usrId
592     * @param integer $usePlaceholders
593     * @param string|null $templateContextId
594     * @param array|null $templateContextParameters
595     * @return int
596     */
597    private function sendInternalMail(
598        $folderId,
599        $senderUsrId,
600        $attachments,
601        $to,
602        $cc,
603        $bcc,
604        $status,
605        $email,
606        $subject,
607        $message,
608        $usrId = 0,
609        $usePlaceholders = 0,
610        $templateContextId = null,
611        $templateContextParameters = []
612    ) : int {
613        $usrId = $usrId ? $usrId : $this->user_id;
614
615        if ($usePlaceholders) {
616            $message = $this->replacePlaceholders($message, $usrId);
617        }
618        $message = $this->formatLinebreakMessage((string) $message);
619        $message = str_ireplace(["<br />", "<br>", "<br/>"], "\n", $message);
620
621        if (!$usrId) {
622            $usrId = '0';
623        }
624        if (!$folderId) {
625            $folderId = '0';
626        }
627        if (!$senderUsrId) {
628            $senderUsrId = null;
629        }
630        if (!$attachments) {
631            $attachments = null;
632        }
633        if (!$to) {
634            $to = null;
635        }
636        if (!$cc) {
637            $cc = null;
638        }
639        if (!$bcc) {
640            $bcc = null;
641        }
642        if (!$status) {
643            $status = null;
644        }
645        if (!$email) {
646            $email = null;
647        }
648        if (!$subject) {
649            $subject = null;
650        }
651        if (!$message) {
652            $message = null;
653        }
654
655        $nextId = (int) $this->db->nextId($this->table_mail);
656        $this->db->insert($this->table_mail, array(
657            'mail_id' => array('integer', $nextId),
658            'user_id' => array('integer', $usrId),
659            'folder_id' => array('integer', $folderId),
660            'sender_id' => array('integer', $senderUsrId),
661            'attachments' => array('clob', serialize($attachments)),
662            'send_time' => array('timestamp', date('Y-m-d H:i:s', time())),
663            'rcp_to' => array('clob', $to),
664            'rcp_cc' => array('clob', $cc),
665            'rcp_bcc' => array('clob', $bcc),
666            'm_status' => array('text', $status),
667            'm_email' => array('integer', $email),
668            'm_subject' => array('text', $subject),
669            'm_message' => array('clob', $message),
670            'tpl_ctx_id' => array('text', $templateContextId),
671            'tpl_ctx_params' => array('blob', @json_encode((array) $templateContextParameters))
672        ));
673
674        $raiseEvent = (int) $usrId !== $this->mailbox->getUsrId();
675        if (!$raiseEvent) {
676            $raiseEvent = (int) $folderId !== $this->mailbox->getSentFolder();
677        }
678
679        if ($raiseEvent) {
680            $this->eventHandler->raise('Services/Mail', 'sentInternalMail', [
681                'id' => $nextId,
682                'subject' => (string) $subject,
683                'body' => (string) $message,
684                'from_usr_id' => (int) $senderUsrId,
685                'to_usr_id' => (int) $usrId,
686                'rcp_to' => (string) $to,
687                'rcp_cc' => (string) $cc,
688                'rcp_bcc' => (string) $bcc,
689            ]);
690        }
691
692        return $nextId;
693    }
694
695    /**
696     * @param string $message
697     * @param int $usrId
698     * @param boolean $replaceEmptyPlaceholders
699     * @return string
700     */
701    protected function replacePlaceholders(
702        string $message,
703        int $usrId = 0,
704        bool $replaceEmptyPlaceholders = true
705    ) : string {
706        try {
707            if ($this->contextId) {
708                $context = ilMailTemplateContextService::getTemplateContextById($this->contextId);
709            } else {
710                $context = new ilMailTemplateGenericContext();
711            }
712
713            $user = $usrId > 0 ? $this->getUserInstanceById($usrId) : null;
714
715            $processor = new ilMailTemplatePlaceholderResolver($context, $message);
716            $message = $processor->resolve($user, $this->contextParameters, $replaceEmptyPlaceholders);
717        } catch (Exception $e) {
718            $this->logger->error(__METHOD__ . ' has been called with invalid context.');
719        }
720
721        return $message;
722    }
723
724    /**
725     * @param string $to
726     * @param string $cc
727     * @param string $bcc
728     * @param string $subject
729     * @param string $message
730     * @param array $attachments
731     * @param int $sentMailId
732     * @param bool $usePlaceholders
733     * @return bool
734     */
735    protected function distributeMail(
736        string $to,
737        string $cc,
738        string $bcc,
739        string $subject,
740        string $message,
741        array $attachments,
742        int $sentMailId,
743        bool $usePlaceholders = false
744    ) : bool {
745        if ($usePlaceholders) {
746            $toUsrIds = $this->getUserIds([$to]);
747            $this->logger->debug(sprintf(
748                "Parsed TO user ids from given recipients for serial letter notification: %s",
749                implode(', ', $toUsrIds)
750            ));
751
752            $this->sendChanneledMails(
753                $to,
754                $cc,
755                $bcc,
756                $toUsrIds,
757                $subject,
758                $message,
759                $attachments,
760                $sentMailId,
761                true
762            );
763
764            $otherUsrIds = $this->getUserIds([$cc, $bcc]);
765            $this->logger->debug(sprintf(
766                "Parsed CC/BCC user ids from given recipients for serial letter notification: %s",
767                implode(', ', $otherUsrIds)
768            ));
769
770            $this->sendChanneledMails(
771                $to,
772                $cc,
773                $bcc,
774                $otherUsrIds,
775                $subject,
776                $this->replacePlaceholders($message, 0, false),
777                $attachments,
778                $sentMailId,
779                false
780            );
781        } else {
782            $usrIds = $this->getUserIds([$to, $cc, $bcc]);
783            $this->logger->debug(sprintf(
784                "Parsed TO/CC/BCC user ids from given recipients: %s",
785                implode(', ', $usrIds)
786            ));
787
788            $this->sendChanneledMails(
789                $to,
790                $cc,
791                $bcc,
792                $usrIds,
793                $subject,
794                $message,
795                $attachments,
796                $sentMailId,
797                false
798            );
799        }
800
801        return true;
802    }
803
804    /**
805     * @param string $to
806     * @param string $cc
807     * @param string $bcc
808     * @param array $usrIds
809     * @param string $subject
810     * @param string $message
811     * @param array $attachments
812     * @param int $sentMailId
813     * @param bool $usePlaceholders
814     */
815    protected function sendChanneledMails(
816        string $to,
817        string $cc,
818        string $bcc,
819        array $usrIds,
820        string $subject,
821        string $message,
822        array $attachments,
823        int $sentMailId,
824        bool $usePlaceholders = false
825    ) : void {
826        $usrIdToExternalEmailAddressesMap = [];
827        $usrIdToMessageMap = [];
828
829        foreach ($usrIds as $usrId) {
830            $user = $this->getUserInstanceById($usrId);
831            $mailOptions = $this->getMailOptionsByUserId($user->getId());
832
833            $canReadInternalMails = !$user->hasToAcceptTermsOfService() && $user->checkTimeLimit();
834
835            $individualMessage = $message;
836            if ($usePlaceholders) {
837                $individualMessage = $this->replacePlaceholders($message, $user->getId());
838                $usrIdToMessageMap[$user->getId()] = $individualMessage;
839            }
840
841            if ($user->getActive()) {
842                $wantsToReceiveExternalEmail = (
843                    $mailOptions->getIncomingType() == ilMailOptions::INCOMING_EMAIL ||
844                    $mailOptions->getIncomingType() == ilMailOptions::INCOMING_BOTH
845                );
846
847                if (!$canReadInternalMails || $wantsToReceiveExternalEmail) {
848                    $emailAddresses = $mailOptions->getExternalEmailAddresses();
849                    $usrIdToExternalEmailAddressesMap[$user->getId()] = $emailAddresses;
850
851                    if ($mailOptions->getIncomingType() == ilMailOptions::INCOMING_EMAIL) {
852                        $this->logger->debug(sprintf(
853                            "Recipient with id %s will only receive external emails sent to: %s",
854                            $user->getId(),
855                            implode(', ', $emailAddresses)
856                        ));
857                        continue;
858                    } else {
859                        $this->logger->debug(sprintf(
860                            "Recipient with id %s will additionally receive external emails " .
861                            "(because the user wants to receive it externally, or the user cannot access " .
862                            "the internal mail system) sent to: %s",
863                            $user->getId(),
864                            implode(', ', $emailAddresses)
865                        ));
866                    }
867                } else {
868                    $this->logger->debug(sprintf(
869                        "Recipient with id %s is does not want to receive external emails",
870                        $user->getId()
871                    ));
872                }
873            } else {
874                $this->logger->debug(sprintf(
875                    "Recipient with id %s is inactive and will not receive external emails",
876                    $user->getId()
877                ));
878            }
879
880            $mbox = clone $this->mailbox;
881            $mbox->setUsrId((int) $user->getId());
882            $recipientInboxId = $mbox->getInboxFolder();
883
884            $internalMailId = $this->sendInternalMail(
885                $recipientInboxId,
886                $this->user_id,
887                $attachments,
888                $to,
889                $cc,
890                '',
891                'unread',
892                0,
893                $subject,
894                $individualMessage,
895                $user->getId(),
896                0
897            );
898
899            if (count($attachments) > 0) {
900                $this->mfile->assignAttachmentsToDirectory($internalMailId, $sentMailId);
901            }
902        }
903
904        $this->delegateExternalEmails(
905            $subject,
906            $message,
907            $attachments,
908            $usePlaceholders,
909            $usrIdToExternalEmailAddressesMap,
910            $usrIdToMessageMap
911        );
912    }
913
914    /**
915     * @param string $subject
916     * @param string $message
917     * @param array $attachments
918     * @param bool $usePlaceholders
919     * @param array $usrIdToExternalEmailAddressesMap
920     * @param array $usrIdToMessageMap
921     */
922    protected function delegateExternalEmails(
923        string $subject,
924        string $message,
925        array $attachments,
926        bool $usePlaceholders,
927        array $usrIdToExternalEmailAddressesMap,
928        array $usrIdToMessageMap
929    ) : void {
930        if (1 === count($usrIdToExternalEmailAddressesMap)) {
931            if ($usePlaceholders) {
932                $message = array_values($usrIdToMessageMap)[0];
933            }
934
935            $usrIdToExternalEmailAddressesMap = array_values($usrIdToExternalEmailAddressesMap);
936            $firstAddresses = current($usrIdToExternalEmailAddressesMap);
937
938            $this->sendMimeMail(
939                implode(',', $firstAddresses),
940                '',
941                '',
942                $subject,
943                $this->formatLinebreakMessage($message),
944                (array) $attachments
945            );
946        } elseif (count($usrIdToExternalEmailAddressesMap) > 1) {
947            if ($usePlaceholders) {
948                foreach ($usrIdToExternalEmailAddressesMap as $usrId => $addresses) {
949                    if (0 === count($addresses)) {
950                        continue;
951                    }
952
953                    $this->sendMimeMail(
954                        implode(',', $addresses),
955                        '',
956                        '',
957                        $subject,
958                        $this->formatLinebreakMessage($usrIdToMessageMap[$usrId]),
959                        (array) $attachments
960                    );
961                }
962            } else {
963                $flattenEmailAddresses = iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator(
964                    $usrIdToExternalEmailAddressesMap
965                )), false);
966
967                $flattenEmailAddresses = array_unique($flattenEmailAddresses);
968
969                // https://mantis.ilias.de/view.php?id=23981 and https://www.ietf.org/rfc/rfc2822.txt
970                $remainingAddresses = '';
971                foreach ($flattenEmailAddresses as $emailAddress) {
972                    $sep = '';
973                    if (strlen($remainingAddresses) > 0) {
974                        $sep = ',';
975                    }
976
977                    $recipientsLineLength = ilStr::strLen($remainingAddresses) + ilStr::strLen($sep . $emailAddress);
978                    if ($recipientsLineLength >= $this->maxRecipientCharacterLength) {
979                        $this->sendMimeMail(
980                            '',
981                            '',
982                            $remainingAddresses,
983                            $subject,
984                            $this->formatLinebreakMessage($message),
985                            (array) $attachments
986                        );
987
988                        $remainingAddresses = '';
989                        $sep = '';
990                    }
991
992                    $remainingAddresses .= ($sep . $emailAddress);
993                }
994
995                if ('' !== $remainingAddresses) {
996                    $this->sendMimeMail(
997                        '',
998                        '',
999                        $remainingAddresses,
1000                        $subject,
1001                        $this->formatLinebreakMessage($message),
1002                        (array) $attachments
1003                    );
1004                }
1005            }
1006        }
1007    }
1008
1009    /**
1010     * @param string[] $recipients
1011     * @return int[]
1012     */
1013    protected function getUserIds(array $recipients) : array
1014    {
1015        $usrIds = array();
1016
1017        $joinedRecipients = implode(',', array_filter(array_map('trim', $recipients)));
1018
1019        $addresses = $this->parseAddresses($joinedRecipients);
1020        foreach ($addresses as $address) {
1021            $addressType = $this->mailAddressTypeFactory->getByPrefix($address);
1022            $usrIds = array_merge($usrIds, $addressType->resolve());
1023        }
1024
1025        return array_unique($usrIds);
1026    }
1027
1028    /**
1029     * @param string $to
1030     * @param string $cc
1031     * @param string $bcc
1032     * @param string $subject
1033     * @return   ilMailError[] An array of errors determined on validation
1034     */
1035    protected function checkMail(string $to, string $cc, string $bcc, string $subject) : array
1036    {
1037        $errors = [];
1038
1039        foreach ([
1040                     $subject => 'mail_add_subject',
1041                     $to => 'mail_add_recipient'
1042                 ] as $string => $error
1043        ) {
1044            if (0 === strlen($string)) {
1045                $errors[] = new ilMailError($error);
1046            }
1047        }
1048
1049        return $errors;
1050    }
1051
1052    /**
1053     * Check if recipients are valid
1054     * @param string $recipients
1055     * @return ilMailError[] An array of errors determined on validation
1056     * @throws ilMailException
1057     */
1058    protected function checkRecipients(string $recipients) : array
1059    {
1060        $errors = [];
1061
1062        try {
1063            $addresses = $this->parseAddresses($recipients);
1064            foreach ($addresses as $address) {
1065                $addressType = $this->mailAddressTypeFactory->getByPrefix($address);
1066                if (!$addressType->validate($this->user_id)) {
1067                    $newErrors = $addressType->getErrors();
1068                    $errors = array_merge($errors, $newErrors);
1069                }
1070            }
1071        } catch (ilException $e) {
1072            $colonPosition = strpos($e->getMessage(), ':');
1073            throw new ilMailException(
1074                ($colonPosition === false) ? $e->getMessage() : substr($e->getMessage(), $colonPosition + 2)
1075            );
1076        }
1077
1078        return $errors;
1079    }
1080
1081    /**
1082     * save post data in table
1083     * @access    public
1084     * @param int $a_user_id
1085     * @param array $a_attachments
1086     * @param string $a_rcp_to
1087     * @param string $a_rcp_cc
1088     * @param string $a_rcp_bcc
1089     * @param int $a_m_email
1090     * @param string $a_m_subject
1091     * @param string $a_m_message
1092     * @param int $a_use_placeholders
1093     * @param string|null $a_tpl_context_id
1094     * @param array|null $a_tpl_ctx_params
1095     * @return    bool
1096     */
1097    public function savePostData(
1098        $a_user_id,
1099        $a_attachments,
1100        $a_rcp_to,
1101        $a_rcp_cc,
1102        $a_rcp_bcc,
1103        $a_m_email,
1104        $a_m_subject,
1105        $a_m_message,
1106        $a_use_placeholders,
1107        $a_tpl_context_id = null,
1108        $a_tpl_ctx_params = array()
1109    ) {
1110        if (!$a_attachments) {
1111            $a_attachments = null;
1112        }
1113        if (!$a_rcp_to) {
1114            $a_rcp_to = null;
1115        }
1116        if (!$a_rcp_cc) {
1117            $a_rcp_cc = null;
1118        }
1119        if (!$a_rcp_bcc) {
1120            $a_rcp_bcc = null;
1121        }
1122        if (!$a_m_email) {
1123            $a_m_email = null;
1124        }
1125        if (!$a_m_message) {
1126            $a_m_message = null;
1127        }
1128        if (!$a_use_placeholders) {
1129            $a_use_placeholders = '0';
1130        }
1131
1132        $this->db->replace(
1133            $this->table_mail_saved,
1134            [
1135                'user_id' => ['integer', $this->user_id]
1136            ],
1137            [
1138                'attachments' => ['clob', serialize($a_attachments)],
1139                'rcp_to' => ['clob', $a_rcp_to],
1140                'rcp_cc' => ['clob', $a_rcp_cc],
1141                'rcp_bcc' => ['clob', $a_rcp_bcc],
1142                'm_email' => ['integer', $a_m_email],
1143                'm_subject' => ['text', $a_m_subject],
1144                'm_message' => ['clob', $a_m_message],
1145                'use_placeholders' => ['integer', $a_use_placeholders],
1146                'tpl_ctx_id' => ['text', $a_tpl_context_id],
1147                'tpl_ctx_params' => ['blob', json_encode((array) $a_tpl_ctx_params)]
1148            ]
1149        );
1150
1151        $this->getSavedData();
1152
1153        return true;
1154    }
1155
1156    /**
1157     * @return array|null
1158     */
1159    public function getSavedData() : ?array
1160    {
1161        $res = $this->db->queryF(
1162            "SELECT * FROM {$this->table_mail_saved} WHERE user_id = %s",
1163            ['integer'],
1164            [$this->user_id]
1165        );
1166
1167        $this->mail_data = $this->fetchMailData($this->db->fetchAssoc($res));
1168
1169        return $this->mail_data;
1170    }
1171
1172    /**
1173     * Should be used to enqueue a 'mail'. A validation is executed before, errors are returned
1174     * @param string $a_rcp_to
1175     * @param string $a_rcp_cc
1176     * @param string $a_rcp_bcc
1177     * @param string $a_m_subject
1178     * @param string $a_m_message
1179     * @param array $a_attachment
1180     * @param bool|int $a_use_placeholders
1181     * @return ilMailError[]
1182     */
1183    public function enqueue(
1184        $a_rcp_to,
1185        $a_rcp_cc,
1186        $a_rcp_bcc,
1187        $a_m_subject,
1188        $a_m_message,
1189        $a_attachment,
1190        $a_use_placeholders = 0
1191    ) : array {
1192        global $DIC;
1193
1194        $this->logger->debug(
1195            "New mail system task:" .
1196            " To: " . $a_rcp_to .
1197            " | CC: " . $a_rcp_cc .
1198            " | BCC: " . $a_rcp_bcc .
1199            " | Subject: " . $a_m_subject
1200        );
1201
1202        if ($a_attachment && !$this->mfile->checkFilesExist($a_attachment)) {
1203            return [new ilMailError('mail_attachment_file_not_exist', [$a_attachment])];
1204        }
1205
1206        $errors = $this->checkMail((string) $a_rcp_to, (string) $a_rcp_cc, (string) $a_rcp_bcc, (string) $a_m_subject);
1207        if (count($errors) > 0) {
1208            return $errors;
1209        }
1210
1211        $errors = $this->validateRecipients((string) $a_rcp_to, (string) $a_rcp_cc, (string) $a_rcp_bcc);
1212        if (count($errors) > 0) {
1213            return $errors;
1214        }
1215
1216        $rcp_to = $a_rcp_to;
1217        $rcp_cc = $a_rcp_cc;
1218        $rcp_bcc = $a_rcp_bcc;
1219
1220        if (null === $rcp_cc) {
1221            $rcp_cc = '';
1222        }
1223
1224        if (null === $rcp_bcc) {
1225            $rcp_bcc = '';
1226        }
1227
1228        $numberOfExternalAddresses = $this->getCountRecipients($rcp_to, $rcp_cc, $rcp_bcc, true);
1229        if (
1230            $numberOfExternalAddresses > 0 &&
1231            !$this->isSystemMail() &&
1232            !$DIC->rbac()->system()->checkAccessOfUser($this->user_id, 'smtp_mail', $this->mail_obj_ref_id)
1233        ) {
1234            return [new ilMailError('mail_no_permissions_write_smtp')];
1235        }
1236
1237        if ($this->appendInstallationSignature()) {
1238            $a_m_message .= self::_getInstallationSignature();
1239        }
1240
1241        if (ilContext::getType() == ilContext::CONTEXT_CRON) {
1242            return $this->sendMail(
1243                (string) $rcp_to,
1244                (string) $rcp_cc,
1245                (string) $rcp_bcc,
1246                (string) $a_m_subject,
1247                (string) $a_m_message,
1248                (array) $a_attachment,
1249                (bool) $a_use_placeholders
1250            );
1251        }
1252
1253        $taskFactory = $DIC->backgroundTasks()->taskFactory();
1254        $taskManager = $DIC->backgroundTasks()->taskManager();
1255
1256        $bucket = new BasicBucket();
1257        $bucket->setUserId($this->user_id);
1258
1259        $task = $taskFactory->createTask(ilMailDeliveryJob::class, [
1260            (int) $this->user_id,
1261            (string) $rcp_to,
1262            (string) $rcp_cc,
1263            (string) $rcp_bcc,
1264            (string) $a_m_subject,
1265            (string) $a_m_message,
1266            (string) serialize($a_attachment),
1267            (bool) $a_use_placeholders,
1268            (bool) $this->getSaveInSentbox(),
1269            (string) $this->contextId,
1270            (string) serialize($this->contextParameters)
1271        ]);
1272        $interaction = $taskFactory->createTask(ilMailDeliveryJobUserInteraction::class, [
1273            $task,
1274            (int) $this->user_id
1275        ]);
1276
1277        $bucket->setTask($interaction);
1278        $bucket->setTitle($this->lng->txt('mail_bg_task_title'));
1279        $bucket->setDescription(sprintf($this->lng->txt('mail_bg_task_desc'), $a_m_subject));
1280
1281        $this->logger->info('Delegated delivery to background task');
1282        $taskManager->run($bucket);
1283
1284        return [];
1285    }
1286
1287    /**
1288     * This method is used to finally send internal messages and external emails
1289     * To use the mail system as a consumer, please use \ilMail::enqueue
1290     * @param string $to
1291     * @param string $cc
1292     * @param string $bcc
1293     * @param string $subject
1294     * @param string $message
1295     * @param array $attachments
1296     * @param bool $usePlaceholders
1297     * @return ilMailError[]
1298     * @see \ilMail::enqueue()
1299     * @internal
1300     */
1301    public function sendMail(
1302        string $to,
1303        string $cc,
1304        string $bcc,
1305        string $subject,
1306        string $message,
1307        array $attachments,
1308        bool $usePlaceholders
1309    ) : array {
1310        $internalMessageId = $this->saveInSentbox(
1311            $attachments,
1312            $to,
1313            $cc,
1314            $bcc,
1315            $subject,
1316            $message
1317        );
1318
1319        if (count($attachments) > 0) {
1320            $this->mfile->assignAttachmentsToDirectory($internalMessageId, $internalMessageId);
1321            $this->mfile->saveFiles($internalMessageId, $attachments);
1322        }
1323
1324        $numberOfExternalAddresses = $this->getCountRecipients($to, $cc, $bcc, true);
1325
1326        if ($numberOfExternalAddresses > 0) {
1327            $externalMailRecipientsTo = $this->getEmailRecipients($to);
1328            $externalMailRecipientsCc = $this->getEmailRecipients($cc);
1329            $externalMailRecipientsBcc = $this->getEmailRecipients($bcc);
1330
1331            $this->logger->debug(
1332                "Parsed external email addresses from given recipients /" .
1333                " To: " . $externalMailRecipientsTo .
1334                " | CC: " . $externalMailRecipientsCc .
1335                " | BCC: " . $externalMailRecipientsBcc .
1336                " | Subject: " . $subject
1337            );
1338
1339            $this->sendMimeMail(
1340                $externalMailRecipientsTo,
1341                $externalMailRecipientsCc,
1342                $externalMailRecipientsBcc,
1343                $subject,
1344                $this->formatLinebreakMessage(
1345                    $usePlaceholders ? $this->replacePlaceholders($message, 0, false) : $message
1346                ),
1347                $attachments
1348            );
1349        } else {
1350            $this->logger->debug('No external email addresses given in recipient string');
1351        }
1352
1353        $errors = [];
1354
1355        if (!$this->distributeMail(
1356            $to,
1357            $cc,
1358            $bcc,
1359            $subject,
1360            $message,
1361            $attachments,
1362            $internalMessageId,
1363            $usePlaceholders
1364        )) {
1365            $errors['mail_send_error'] = new ilMailError('mail_send_error');
1366        }
1367
1368        if (!$this->getSaveInSentbox()) {
1369            $this->deleteMails([$internalMessageId]);
1370        }
1371
1372        return array_values($errors);
1373    }
1374
1375    /**
1376     * @param string $to
1377     * @param string $cc
1378     * @param string $bcc
1379     * @return ilMailError[] An array of errors determined on validation
1380     */
1381    public function validateRecipients(string $to, string $cc, string $bcc) : array
1382    {
1383        try {
1384            $errors = [];
1385            $errors = array_merge($errors, $this->checkRecipients($to));
1386            $errors = array_merge($errors, $this->checkRecipients($cc));
1387            $errors = array_merge($errors, $this->checkRecipients($bcc));
1388
1389            if (count($errors) > 0) {
1390                return array_merge([new ilMailError('mail_following_rcp_not_valid')], $errors);
1391            }
1392        } catch (ilMailException $e) {
1393            return [new ilMailError('mail_generic_rcp_error', [$e->getMessage()])];
1394        }
1395
1396        return [];
1397    }
1398
1399    /**
1400     * Stores a message in the sent bod of the current user
1401     * @param array $attachment
1402     * @param string $to
1403     * @param string $cc
1404     * @param string $bcc
1405     * @param string $subject
1406     * @param string $message
1407     * @return int mail id
1408     */
1409    protected function saveInSentbox(
1410        array $attachment,
1411        string $to,
1412        string $cc,
1413        string $bcc,
1414        string $subject,
1415        string $message
1416    ) : int {
1417        return $this->sendInternalMail(
1418            $this->mailbox->getSentFolder(),
1419            $this->user_id,
1420            $attachment,
1421            $to,
1422            $cc,
1423            $bcc,
1424            'read',
1425            0,
1426            $subject,
1427            $message,
1428            $this->user_id,
1429            0
1430        );
1431    }
1432
1433    /**
1434     * @param string $to
1435     * @param string $cc
1436     * @param string $bcc
1437     * @param string $subject
1438     * @param string $message
1439     * @param array $attachments
1440     */
1441    private function sendMimeMail(string $to, string $cc, string $bcc, $subject, $message, array $attachments) : void
1442    {
1443        $mailer = new ilMimeMail();
1444        $mailer->From($this->senderFactory->getSenderByUsrId((int) $this->user_id));
1445        $mailer->To($to);
1446        $mailer->Subject($subject, true);
1447        $mailer->Body($message);
1448
1449        if ($cc) {
1450            $mailer->Cc($cc);
1451        }
1452
1453        if ($bcc) {
1454            $mailer->Bcc($bcc);
1455        }
1456
1457        foreach ($attachments as $attachment) {
1458            $mailer->Attach(
1459                $this->mfile->getAbsoluteAttachmentPoolPathByFilename($attachment),
1460                '',
1461                'inline',
1462                $attachment
1463            );
1464        }
1465
1466        $mailer->Send();
1467    }
1468
1469    /**
1470     * @param string[] $attachments An array of attachments
1471     */
1472    public function saveAttachments(array $attachments) : void
1473    {
1474        $this->db->update(
1475            $this->table_mail_saved,
1476            [
1477                'attachments' => ['clob', serialize($attachments)]
1478            ],
1479            [
1480                'user_id' => ['integer', $this->user_id]
1481            ]
1482        );
1483    }
1484
1485    /**
1486     * Explode recipient string, allowed separators are ',' ';' ' '
1487     * Returns an array with recipient ilMailAddress instances
1488     * @param string $addresses
1489     * @return ilMailAddress[] An array with objects of type ilMailAddress
1490     */
1491    protected function parseAddresses($addresses) : array
1492    {
1493        if (strlen($addresses) > 0) {
1494            $this->logger->debug(sprintf(
1495                "Started parsing of recipient string: %s",
1496                $addresses
1497            ));
1498        }
1499
1500        $parser = $this->mailAddressParserFactory->getParser((string) $addresses);
1501        $parsedAddresses = $parser->parse();
1502
1503        if (strlen($addresses) > 0) {
1504            $this->logger->debug(sprintf(
1505                "Parsed addresses: %s",
1506                implode(',', array_map(function (ilMailAddress $address) {
1507                    return (string) $address;
1508                }, $parsedAddresses))
1509            ));
1510        }
1511
1512        return $parsedAddresses;
1513    }
1514
1515    /**
1516     * @param string $recipients
1517     * @param bool $onlyExternalAddresses
1518     * @return int
1519     */
1520    protected function getCountRecipient(string $recipients, $onlyExternalAddresses = true) : int
1521    {
1522        $addresses = new ilMailAddressListImpl($this->parseAddresses($recipients));
1523        if ($onlyExternalAddresses) {
1524            $addresses = new ilMailOnlyExternalAddressList(
1525                $addresses,
1526                self::ILIAS_HOST,
1527                $this->usrIdByLoginCallable
1528            );
1529        }
1530
1531        return count($addresses->value());
1532    }
1533
1534    /**
1535     * @param string $toRecipients
1536     * @param string $ccRecipients
1537     * @param $bccRecipients
1538     * @param bool $onlyExternalAddresses
1539     * @return int
1540     */
1541    protected function getCountRecipients(
1542        string $toRecipients,
1543        string $ccRecipients,
1544        string $bccRecipients,
1545        $onlyExternalAddresses = true
1546    ) : int {
1547        return (
1548            $this->getCountRecipient($toRecipients, $onlyExternalAddresses) +
1549            $this->getCountRecipient($ccRecipients, $onlyExternalAddresses) +
1550            $this->getCountRecipient($bccRecipients, $onlyExternalAddresses)
1551        );
1552    }
1553
1554    /**
1555     * @param string $recipients
1556     * @return string
1557     */
1558    protected function getEmailRecipients(string $recipients) : string
1559    {
1560        $addresses = new ilMailOnlyExternalAddressList(
1561            new ilMailAddressListImpl($this->parseAddresses($recipients)),
1562            self::ILIAS_HOST,
1563            $this->usrIdByLoginCallable
1564        );
1565
1566        $emailRecipients = array_map(function (ilMailAddress $address) {
1567            return (string) $address;
1568        }, $addresses->value());
1569
1570        return implode(',', $emailRecipients);
1571    }
1572
1573    /**
1574     * Get auto generated info string
1575     * @param ilLanguage $lang
1576     * @return string;
1577     */
1578    public static function _getAutoGeneratedMessageString(ilLanguage $lang = null) : string
1579    {
1580        global $DIC;
1581
1582        if (!($lang instanceof ilLanguage)) {
1583            $lang = ilLanguageFactory::_getLanguage();
1584        }
1585
1586        $lang->loadLanguageModule('mail');
1587
1588        return sprintf(
1589            $lang->txt('mail_auto_generated_info'),
1590            $DIC->settings()->get('inst_name', 'ILIAS ' . ((int) ILIAS_VERSION_NUMERIC)),
1591            ilUtil::_getHttpPath()
1592            ) . "\n\n";
1593    }
1594
1595    /**
1596     * @return string
1597     */
1598    public static function _getIliasMailerName() : string
1599    {
1600        /** @var ilMailMimeSenderFactory $senderFactory */
1601        $senderFactory = $GLOBALS["DIC"]["mail.mime.sender.factory"];
1602
1603        return $senderFactory->system()->getFromName();
1604    }
1605
1606    /**
1607     * @param bool|null $a_flag
1608     * @return self|bool
1609     */
1610    public function appendInstallationSignature(bool $a_flag = null)
1611    {
1612        if (null === $a_flag) {
1613            return $this->appendInstallationSignature;
1614        }
1615
1616        $this->appendInstallationSignature = $a_flag;
1617        return $this;
1618    }
1619
1620    /**
1621     * @return string The installation mail signature
1622     */
1623    public static function _getInstallationSignature() : string
1624    {
1625        global $DIC;
1626
1627        $signature = $DIC->settings()->get('mail_system_sys_signature');
1628
1629        $clientUrl = ilUtil::_getHttpPath();
1630        $clientdirs = glob(ILIAS_WEB_DIR . '/*', GLOB_ONLYDIR);
1631        if (is_array($clientdirs) && count($clientdirs) > 1) {
1632            $clientUrl .= '/login.php?client_id=' . CLIENT_ID; // #18051
1633        }
1634
1635        $signature = str_ireplace('[CLIENT_NAME]', $DIC['ilClientIniFile']->readVariable('client', 'name'), $signature);
1636        $signature = str_ireplace(
1637            '[CLIENT_DESC]',
1638            $DIC['ilClientIniFile']->readVariable('client', 'description'),
1639            $signature
1640        );
1641        $signature = str_ireplace('[CLIENT_URL]', $clientUrl, $signature);
1642
1643        if (!preg_match('/^[\n\r]+/', $signature)) {
1644            $signature = "\n" . $signature;
1645        }
1646
1647        return $signature;
1648    }
1649
1650    /**
1651     * @param int $a_usr_id
1652     * @param     $a_language ilLanguage|null
1653     * @return string
1654     */
1655    public static function getSalutation($a_usr_id, ilLanguage $a_language = null) : string
1656    {
1657        global $DIC;
1658
1659        $lang = ($a_language instanceof ilLanguage) ? $a_language : $DIC->language();
1660        $lang->loadLanguageModule('mail');
1661
1662        $gender = ilObjUser::_lookupGender($a_usr_id);
1663        $gender = $gender ? $gender : 'n';
1664        $name = ilObjUser::_lookupName($a_usr_id);
1665
1666        if (!strlen($name['firstname'])) {
1667            return $lang->txt('mail_salutation_anonymous') . ',';
1668        }
1669
1670        return
1671            $lang->txt('mail_salutation_' . $gender) . ' ' .
1672            ($name['title'] ? $name['title'] . ' ' : '') .
1673            ($name['firstname'] ? $name['firstname'] . ' ' : '') .
1674            $name['lastname'] . ',';
1675    }
1676
1677    /**
1678     * @param int $usrId
1679     * @return ilObjUser
1680     */
1681    protected function getUserInstanceById(int $usrId) : ilObjUser
1682    {
1683        if (!isset($this->userInstancesByIdMap[$usrId])) {
1684            $this->userInstancesByIdMap[$usrId] = new ilObjUser($usrId);
1685        }
1686
1687        return $this->userInstancesByIdMap[$usrId];
1688    }
1689
1690    /**
1691     * @param ilObjUser[] $userInstanceByIdMap
1692     * @internal
1693     */
1694    public function setUserInstanceById(array $userInstanceByIdMap) : void
1695    {
1696        $this->userInstancesByIdMap = $userInstanceByIdMap;
1697    }
1698
1699    /**
1700     * @param int $usrId
1701     * @return ilMailOptions
1702     */
1703    protected function getMailOptionsByUserId(int $usrId) : ilMailOptions
1704    {
1705        if (!isset($this->mailOptionsByUsrIdMap[$usrId])) {
1706            $this->mailOptionsByUsrIdMap[$usrId] = new ilMailOptions($usrId);
1707        }
1708
1709        return $this->mailOptionsByUsrIdMap[$usrId];
1710    }
1711
1712    /**
1713     * @param ilMailOptions[] $mailOptionsByUsrIdMap
1714     * @internal
1715     */
1716    public function setMailOptionsByUserIdMap(array $mailOptionsByUsrIdMap) : void
1717    {
1718        $this->mailOptionsByUsrIdMap = $mailOptionsByUsrIdMap;
1719    }
1720
1721    /**
1722     * @inheritdoc
1723     */
1724    public function formatLinebreakMessage(string $message) : string
1725    {
1726        return $message;
1727    }
1728}
1729