zend-blog-3-backend

Форк
0
284 строки · 8.5 Кб
1
<?php
2

3
namespace App\Service;
4

5
use App\DTO\EmailMessageDTO;
6
use App\Entity\Comment;
7
use App\Entity\EmailSubscriptionSettings;
8
use App\Repository\EmailSubscriptionSettingsRepository;
9
use App\Utils\HashId;
10
use App\Utils\VerifyEmail;
11
use Psr\Log\LoggerInterface;
12
use Swift_Mailer;
13
use Swift_Message;
14
use Twig\Environment as TwigEnvironment;
15
use Xelbot\Telegram\Robot;
16

17
class Mailer
18
{
19
    /**
20
     * @var Swift_Mailer
21
     */
22
    private $mailer;
23

24
    /**
25
     * @var TwigEnvironment
26
     */
27
    private $twig;
28

29
    /**
30
     * @var string
31
     */
32
    private $emailFrom;
33

34
    /**
35
     * @var Robot
36
     */
37
    private $bot;
38

39
    /**
40
     * @var string
41
     */
42
    private $frontendSite;
43

44
    private EmailSubscriptionSettingsRepository $subscriptionRepository;
45

46
    private LoggerInterface $logger;
47

48
    /**
49
     * @param Swift_Mailer $mailer
50
     * @param TwigEnvironment $twig
51
     * @param Robot $bot
52
     * @param EmailSubscriptionSettingsRepository $subscriptionRepository
53
     * @param string $frontendSite
54
     * @param string $emailFrom
55
     */
56
    public function __construct(
57
        Swift_Mailer $mailer,
58
        TwigEnvironment $twig,
59
        Robot $bot,
60
        EmailSubscriptionSettingsRepository $subscriptionRepository,
61
        LoggerInterface $logger,
62
        string $frontendSite,
63
        string $emailFrom
64
    ) {
65
        $this->mailer = $mailer;
66
        $this->twig = $twig;
67
        $this->emailFrom = $emailFrom;
68
        $this->bot = $bot;
69
        $this->subscriptionRepository = $subscriptionRepository;
70
        $this->frontendSite = $frontendSite;
71
        $this->logger = $logger;
72
    }
73

74
    public function newComment(Comment $comment, string $emailTo, bool $spool = true)
75
    {
76
        $emailTo = VerifyEmail::normalize($emailTo);
77
        $context = $this->twig->mergeGlobals($this->context($comment));
78

79
        $template = $this->twig->load('mails/newComment.html.twig');
80
        $textTemplate = $this->twig->load('mails/newComment.txt.twig');
81

82
        $message = new EmailMessageDTO();
83

84
        $message->subject = 'Новый комментарий';
85
        $message->from = $this->emailFrom;
86
        $message->to = $emailTo;
87
        $message->messageHtml = $template->render($context);
88
        $message->messageText = $textTemplate->render($context);
89

90
        $spool ? $this->queueMessage($message) : $this->send($message);
91
    }
92

93
    public function send(EmailMessageDTO $messageDTO): bool
94
    {
95
        if ($this->isBlocked($messageDTO)) {
96
            // successfully sent to black hole :)
97
            return true;
98
        }
99

100
        $successfullySent = false;
101
        try {
102
            $message = (new Swift_Message())
103
                ->setSubject($messageDTO->subject)
104
                ->setFrom($messageDTO->from)
105
                ->setTo($messageDTO->to)
106
            ;
107

108
            if ($messageDTO->messageHtml) {
109
                $message->addPart($messageDTO->messageHtml, 'text/html');
110
            }
111
            if ($messageDTO->messageText) {
112
                $message->addPart($messageDTO->messageText, 'text/plain');
113
            }
114

115
            if ($messageDTO->unsubscribeLink) {
116
                $headers = $message->getHeaders();
117
                $headers->addTextHeader(
118
                    'List-Unsubscribe',
119
                    sprintf('<%s%s>', $this->frontendSite, $messageDTO->unsubscribeLink)
120
                );
121
                $headers->addTextHeader('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click');
122
            }
123

124
            $successfullySent = $this->mailer->send($message) > 0;
125
        } catch (\Throwable $e) {
126
            $this->logger->error('email sent error', ['exception' => $e]);
127
            $this->bot->sendMessage(
128
                'email sent error: ' . $e->getMessage()
129
                . "\n\nfile: " . $e->getFile()
130
                . "\nline: " . $e->getLine()
131
            );
132
        }
133

134
        return $successfullySent;
135
    }
136

137
    /**
138
     * Save message to spool
139
     *
140
     * @param EmailMessageDTO $messageDTO
141
     */
142
    public function queueMessage(EmailMessageDTO $messageDTO): void
143
    {
144
        if ($this->isBlocked($messageDTO)) {
145
            return;
146
        }
147

148
        try {
149
            $randomBytes = bin2hex(random_bytes(3));
150
        } catch (\Exception $e) {
151
            $randomBytes = dechex(mt_rand(0, 255) + 256 * mt_rand(0, 255) + 65536 * mt_rand(0, 255));
152
        }
153

154
        $fileName = sprintf(
155
            '%s/%X%s.message',
156
            $this->spoolPath(),
157
            (int)date('U'),
158
            strtoupper($randomBytes)
159
        );
160

161
        $fp = fopen($fileName, 'wb');
162
        fwrite($fp, serialize($messageDTO));
163
        fclose($fp);
164
    }
165

166
    public function spoolSend($messageLimit = null, $timeLimit = null): int
167
    {
168
        $count = 0;
169
        $time = time();
170
        foreach (new \DirectoryIterator($this->spoolPath()) as $fileInfo) {
171
            $file = $fileInfo->getRealPath();
172
            if (substr($file, -8) !== '.message') {
173
                continue;
174
            }
175

176
            if (rename($file, $file . '.sending')) {
177
                $message = unserialize(
178
                    file_get_contents($file . '.sending'),
179
                    ['allowed_classes' => [EmailMessageDTO::class]]
180
                );
181
                if ($this->send($message)) {
182
                    $count++;
183
                    unlink($file . '.sending');
184
                }
185
            } else {
186
                continue;
187
            }
188

189
            if ($messageLimit && $count >= $messageLimit) {
190
                break;
191
            }
192
            if ($timeLimit && (time() - $time) >= $timeLimit) {
193
                break;
194
            }
195
        }
196

197
        return $count;
198
    }
199

200
    public function replyComment(Comment $comment)
201
    {
202
        $parent = $comment->getParent();
203
        if ($parent) {
204
            $emailTo = null;
205
            $recipient = 'undefined';
206
            if ($user = $parent->getUser()) {
207
                $emailTo = $user->getEmail();
208
                $recipient = $user->getUsername();
209
            } elseif ($commentator = $parent->getCommentator()) {
210
                $emailTo = $commentator->isValidEmail() ? $commentator->getEmail() : null;
211
                $recipient = $commentator->getName();
212
            }
213

214
            if ($emailTo) {
215
                $emailTo = VerifyEmail::normalize($emailTo);
216
                $unsubscribeLink = $this->unsubscribeLink($emailTo, EmailSubscriptionSettings::TYPE_COMMENT_REPLY);
217

218
                $context = $this->twig->mergeGlobals(array_merge(
219
                    $this->context($comment),
220
                    [
221
                        'unsubscribeLink' => $unsubscribeLink,
222
                    ],
223
                ));
224

225
                $template = $this->twig->load('mails/replyComment.html.twig');
226
                $textTemplate = $this->twig->load('mails/replyComment.txt.twig');
227

228
                $message = new EmailMessageDTO();
229

230
                $message->subject = 'Ответ на комментарий';
231
                $message->from = $this->emailFrom;
232
                $message->to = [$emailTo => $recipient];
233
                $message->messageHtml = $template->render($context);
234
                $message->messageText = $textTemplate->render($context);
235
                $message->unsubscribeLink = $unsubscribeLink;
236

237
                $this->queueMessage($message);
238
            }
239
        }
240
    }
241

242
    private function spoolPath(): string
243
    {
244
        return APP_VAR_DIR . '/spool';
245
    }
246

247
    private function context(Comment $comment): array
248
    {
249
        $username = 'undefined';
250
        if ($user = $comment->getUser()) {
251
            $username = $user->getUsername();
252
        } elseif ($commentator = $comment->getCommentator()) {
253
            $username = $commentator->getName();
254
        }
255

256
        return [
257
            'topicTitle' => $comment->getPost()->getTitle(),
258
            'topicUrl' => '/article/' . $comment->getPost()->getUrl(),
259
            'username' => $username,
260
            'commentText' => $comment->getText(),
261
            'avatar' => $comment->getAvatarHash() . '.png',
262
        ];
263
    }
264

265
    private function isBlocked(EmailMessageDTO $messageDTO): bool
266
    {
267
        if ($messageDTO->type === 0) {
268
            return false;
269
        }
270

271
        $email = $messageDTO->getRecipientEmail();
272
        $settings = $this->subscriptionRepository->findOneBy(['email' => $email, 'type' => $messageDTO->type]);
273

274
        return $settings && $settings->isBlockSending();
275
    }
276

277
    private function unsubscribeLink(string $email, int $type): string
278
    {
279
        $settings = $this->subscriptionRepository->findOrCreate($email, $type);
280
        $hash = HashId::hash($settings->getId(), mt_rand(1, 9999));
281

282
        return '/email-unsubscribe/' . $hash;
283
    }
284
}
285

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.