Passed
Push — master ( d3709f...632433 )
by
unknown
20:51 queued 09:40
created

MessageHelper   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 343
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 160
dl 0
loc 343
rs 4.5599
c 2
b 0
f 0
wmc 58

11 Methods

Rating   Name   Duplication   Size   Complexity  
A forwardAttachments() 0 9 3
A saveAttachments() 0 20 4
A __construct() 0 11 2
A addSenderAsReceiver() 0 17 2
B sendMessageSimple() 0 55 8
A attachAudioMessage() 0 16 4
A processAttachments() 0 22 4
C sendMessage() 0 72 12
B buildFromAddress() 0 32 11
B sendEmailNotification() 0 27 7
A getGroupById() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like MessageHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MessageHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Helpers;
8
9
use Chamilo\CoreBundle\Entity\Message;
10
use Chamilo\CoreBundle\Entity\MessageAttachment;
11
use Chamilo\CoreBundle\Entity\MessageRelUser;
12
use Chamilo\CoreBundle\Entity\User;
13
use Chamilo\CoreBundle\Entity\Usergroup;
14
use Chamilo\CoreBundle\Repository\MessageRepository;
15
use Chamilo\CoreBundle\Repository\Node\UserRepository;
16
use Chamilo\CoreBundle\Settings\SettingsManager;
17
use Doctrine\ORM\EntityManagerInterface;
18
use Exception;
19
use Symfony\Component\HttpFoundation\File\UploadedFile;
20
use Symfony\Component\HttpFoundation\RequestStack;
21
use Symfony\Component\HttpFoundation\Session\SessionInterface;
22
use Symfony\Component\Mailer\MailerInterface;
23
use Symfony\Component\Mime\Address;
24
use Symfony\Component\Mime\Email;
25
26
use const PHP_SAPI;
27
use const UPLOAD_ERR_OK;
28
29
class MessageHelper
30
{
31
    private ?SessionInterface $session = null;
32
33
    public function __construct(
34
        private readonly EntityManagerInterface $entityManager,
35
        private readonly MessageRepository $messageRepository,
36
        private readonly UserRepository $userRepository,
37
        private readonly RequestStack $requestStack,
38
        private readonly AccessUrlHelper $accessUrlHelper,
39
        private readonly SettingsManager $settingsManager,
40
        private readonly MailerInterface $mailer
41
    ) {
42
        if (PHP_SAPI !== 'cli') {
43
            $this->session = $this->requestStack->getSession();
44
        }
45
    }
46
47
    /**
48
     * Sends a simple message with optional attachments and notifications to HR users.
49
     */
50
    public function sendMessageSimple(
51
        int $receiverUserId,
52
        string $subject,
53
        string $message,
54
        int $senderId = 0,
55
        bool $sendCopyToDrhUsers = false,
56
        bool $uploadFiles = true,
57
        array $attachmentList = []
58
    ): ?int {
59
        $files = $_FILES ?: [];
60
        if (false === $uploadFiles) {
61
            $files = [];
62
        }
63
64
        if (!empty($attachmentList)) {
65
            $files = $attachmentList;
66
        }
67
68
        $result = $this->sendMessage(
69
            $receiverUserId,
70
            $subject,
71
            $message,
72
            $files,
73
            [],
74
            0,
75
            0,
76
            0,
77
            $senderId
78
        );
79
80
        if ($sendCopyToDrhUsers) {
81
            $accessUrl = $this->accessUrlHelper->getCurrent();
82
            if (null !== $accessUrl) {
83
                $drhList = $this->userRepository->getDrhListFromUser($receiverUserId, $accessUrl->getId());
84
                if (!empty($drhList)) {
85
                    $receiverInfo = $this->userRepository->find($receiverUserId);
86
87
                    foreach ($drhList as $drhUser) {
88
                        $drhMessage = \sprintf(
89
                            'Copy of message sent to %s',
90
                            $receiverInfo->getFirstname().' '.$receiverInfo->getLastname()
91
                        ).' <br />'.$message;
92
93
                        $this->sendMessageSimple(
94
                            $drhUser->getId(),
95
                            $subject,
96
                            $drhMessage,
97
                            $senderId
98
                        );
99
                    }
100
                }
101
            }
102
        }
103
104
        return $result;
105
    }
106
107
    /**
108
     * Sends a message with attachments, forwards, and additional settings.
109
     */
110
    public function sendMessage(
111
        int $receiverUserId,
112
        string $subject,
113
        string $content,
114
        array $attachments = [],
115
        array $fileCommentList = [],
116
        int $groupId = 0,
117
        int $parentId = 0,
118
        int $editMessageId = 0,
119
        int $senderId = 0,
120
        int $forwardId = 0,
121
        bool $checkCurrentAudioId = false,
122
        bool $forceTitleWhenSendingEmail = false,
123
        ?int $msgType = null
124
    ): ?int {
125
        $sender = $this->userRepository->find($senderId);
126
        $receiver = $this->userRepository->find($receiverUserId);
127
128
        if (!$sender || !$receiver || !$receiver->isActive()) {
129
            return null;
130
        }
131
132
        $totalFileSize = 0;
133
        $attachmentList = $this->processAttachments($attachments, $fileCommentList, $totalFileSize);
134
135
        if ($totalFileSize > (int) $this->settingsManager->getSetting('message.message_max_upload_filesize')) {
136
            throw new Exception('Files size exceeds allowed limit.');
137
        }
138
139
        $parent = $this->messageRepository->find($parentId);
140
141
        if ($editMessageId) {
142
            $message = $this->messageRepository->find($editMessageId);
143
            if ($message) {
144
                $message->setTitle($subject);
145
                $message->setContent($content);
146
            }
147
        } else {
148
            $message = new Message();
149
            $message->setSender($sender)
150
                ->addReceiverTo($receiver)
151
                ->setTitle($subject)
152
                ->setContent($content)
153
                ->setGroup($groupId ? $this->getGroupById($groupId) : null)
154
                ->setParent($parent)
155
            ;
156
157
            if (null !== $msgType) {
158
                $message->setMsgType($msgType);
159
            }
160
        }
161
162
        $this->entityManager->persist($message);
163
        $this->entityManager->flush();
164
165
        if ($forwardId) {
166
            $this->forwardAttachments($forwardId, $message);
167
        }
168
169
        if ($checkCurrentAudioId) {
170
            $this->attachAudioMessage($message);
171
        }
172
173
        $this->saveAttachments($attachmentList, $message);
174
175
        $this->addSenderAsReceiver($message, $sender);
176
177
        if ($forceTitleWhenSendingEmail) {
178
            $this->sendEmailNotification($receiver, $sender, $subject, $content, $attachmentList);
179
        }
180
181
        return $message->getId();
182
    }
183
184
    /**
185
     * Processes attachments, calculates total file size, and returns the attachment list.
186
     *
187
     * @param mixed $totalFileSize
188
     */
189
    private function processAttachments(array $attachments, array $fileCommentList, &$totalFileSize): array
190
    {
191
        $attachmentList = [];
192
        foreach ($attachments as $index => $attachment) {
193
            $comment = $fileCommentList[$index] ?? '';
194
            $size = $attachment['size'] ?? 0;
195
196
            if (\is_array($size)) {
197
                foreach ($size as $s) {
198
                    $totalFileSize += $s;
199
                }
200
            } else {
201
                $totalFileSize += $size;
202
            }
203
204
            $attachmentList[] = [
205
                'file' => $attachment,
206
                'comment' => $comment,
207
            ];
208
        }
209
210
        return $attachmentList;
211
    }
212
213
    /**
214
     * Forwards attachments from one message to another.
215
     */
216
    private function forwardAttachments(int $forwardId, Message $message): void
217
    {
218
        $forwardMessage = $this->messageRepository->find($forwardId);
219
        if ($forwardMessage) {
220
            foreach ($forwardMessage->getAttachments() as $attachment) {
221
                $message->addAttachment($attachment);
222
            }
223
            $this->entityManager->persist($message);
224
            $this->entityManager->flush();
225
        }
226
    }
227
228
    /**
229
     * Attaches an audio message from the current session to the message.
230
     */
231
    private function attachAudioMessage(Message $message): void
232
    {
233
        if ($this->session && $this->session->has('current_audio')) {
234
            $audio = $this->session->get('current_audio');
235
236
            if (!empty($audio['name'])) {
237
                $attachment = new MessageAttachment();
238
                $attachment->setFilename($audio['name'])
239
                    ->setComment('audio_message')
240
                    ->setMessage($message)
241
                ;
242
243
                $message->addAttachment($attachment);
244
245
                $this->entityManager->persist($attachment);
246
                $this->entityManager->flush();
247
            }
248
        }
249
    }
250
251
    /**
252
     * Saves the provided attachments and links them to the message.
253
     */
254
    private function saveAttachments(array $attachments, Message $message): void
255
    {
256
        foreach ($attachments as $attachment) {
257
            $file = $attachment['file'];
258
            $comment = $attachment['comment'] ?? '';
259
260
            if ($file instanceof UploadedFile && UPLOAD_ERR_OK === $file->getError()) {
261
                $attachmentEntity = new MessageAttachment();
262
                $attachmentEntity->setFilename($file->getClientOriginalName())
263
                    ->setSize($file->getSize())
264
                    ->setPath($file->getRealPath())
265
                    ->setMessage($message)
266
                    ->setComment($comment)
267
                ;
268
269
                $message->addAttachment($attachmentEntity);
270
                $this->entityManager->persist($attachmentEntity);
271
            }
272
        }
273
        $this->entityManager->flush();
274
    }
275
276
    /**
277
     * Adds the sender as a receiver in the message to keep track of the sent message.
278
     */
279
    private function addSenderAsReceiver(Message $message, User $sender): void
280
    {
281
        $messageRelUserRepository = $this->entityManager->getRepository(MessageRelUser::class);
282
        $existingRelation = $messageRelUserRepository->findOneBy([
283
            'message' => $message,
284
            'receiver' => $sender,
285
            'receiverType' => MessageRelUser::TYPE_SENDER,
286
        ]);
287
288
        if (!$existingRelation) {
289
            $messageRelUserSender = new MessageRelUser();
290
            $messageRelUserSender->setMessage($message)
291
                ->setReceiver($sender)
292
                ->setReceiverType(MessageRelUser::TYPE_SENDER)
293
            ;
294
            $this->entityManager->persist($messageRelUserSender);
295
            $this->entityManager->flush();
296
        }
297
    }
298
299
    private function sendEmailNotification(User $receiver, User $sender, string $subject, string $content, array $attachmentList): void
300
    {
301
        $toAddress = $receiver->getEmail();
302
        if (!filter_var($toAddress, FILTER_VALIDATE_EMAIL)) {
303
            return;
304
        }
305
306
        $from = $this->buildFromAddress();
307
308
        try {
309
            $email = (new Email())
310
                ->from($from)
311
                ->to(new Address($toAddress, $receiver->getFullname() ?: $receiver->getUsername()))
312
                ->subject($subject)
313
                ->text($content)
314
                ->html($content);
315
316
            foreach ($attachmentList as $att) {
317
                $file = $att['file'] ?? null;
318
                if ($file instanceof UploadedFile && UPLOAD_ERR_OK === $file->getError()) {
319
                    $email->attachFromPath($file->getRealPath(), $file->getClientOriginalName());
320
                }
321
            }
322
323
            $this->mailer->send($email);
324
        } catch (\Throwable $e) {
325
            error_log('Failed to send email: ' . $e->getMessage());
326
        }
327
    }
328
    /**
329
     * Constructs the FROM as an Address, prioritizing configuration;
330
     * If there is no valid value, infers the current domain and uses noreply@{domain}.
331
     */
332
    private function buildFromAddress(): Address
333
    {
334
        $fromName = $this->settingsManager->getSetting('mail.mailer_from_name')
335
            ?: $this->settingsManager->getSetting('platform.site_name', true)
336
                ?: 'Chamilo';
337
338
        $candidates = [
339
            $this->settingsManager->getSetting('mail.mailer_from_email'),
340
            $this->settingsManager->getSetting('mail.mailer_from_address'),
341
            $this->settingsManager->getSetting('platform.administrator_email'),
342
        ];
343
        foreach ($candidates as $cand) {
344
            if ($cand && filter_var($cand, FILTER_VALIDATE_EMAIL)) {
345
                return new Address($cand, $fromName);
346
            }
347
        }
348
349
        $host = null;
350
        $accessUrl = $this->accessUrlHelper->getCurrent();
351
        if ($accessUrl && method_exists($accessUrl, 'getUrl')) {
352
            $host = parse_url((string) $accessUrl->getUrl(), PHP_URL_HOST);
353
        }
354
        if (!$host) {
355
            $req = $this->requestStack->getCurrentRequest();
356
            $host = $req?->getHost();
357
        }
358
359
        if (!$host || !str_contains($host, '.')) {
360
            $host = 'example.org';
361
        }
362
363
        return new Address('noreply@' . $host, $fromName);
364
    }
365
366
    /**
367
     * Retrieves a user group by its ID.
368
     */
369
    private function getGroupById(int $groupId)
370
    {
371
        return $this->entityManager->getRepository(Usergroup::class)->find($groupId);
372
    }
373
}
374