PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.4.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.4.01
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / app / Hooks / Handlers / EmailNotificationHandler.php

EmailNotificationHandler.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.4.01, at app/Hooks/Handlers/EmailNotificationHandler.php

695 lines 26.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\App\Hooks\Handlers;
4
5 use FluentCommunity\App\Functions\Utility;
6 use FluentCommunity\App\Models\Comment;
7 use FluentCommunity\App\Models\Feed;
8 use FluentCommunity\App\Models\Notification;
9 use FluentCommunity\App\Models\BaseSpace;
10 use FluentCommunity\App\Models\SpaceUserPivot;
11 use FluentCommunity\App\Models\User;
12 use FluentCommunity\App\Models\XProfile;
13 use FluentCommunity\App\Services\Helper;
14 use FluentCommunity\App\Services\Libs\DailyDigest;
15 use FluentCommunity\App\Services\Libs\Mailer;
16 use FluentCommunity\App\Services\NotificationPref;
17 use FluentCommunity\App\Services\ProfileHelper;
18 use FluentCommunity\Framework\Support\Arr;
19
20 class EmailNotificationHandler
21 {
22 private $maxRunTime = 0;
23
24 public function register()
25 {
26 add_action('fluent_community/space_feed/created', [$this, 'handleSpaceFeedCreated'], 20, 1);
27 add_action('fluent_community/email_notify_new_posts', [$this, 'notifyOnPostCreatedAsync'], 10, 1);
28
29 add_action('fluent_community/comment_added', [$this, 'handleNewCommentEvent'], 30, 2);
30 add_action('fluent_community/comment_added_async', [$this, 'handleNewCommentNotificationAsync'], 10, 2);
31
32 /*
33 * This is an async request
34 */
35 add_action('fluent_community/email_notify_users_everyone_tag', [$this, 'emailNotifyUsersForEveryoneTag'], 10, 2);
36
37 add_action('fluent_community_send_daily_digest', [$this, 'maybeSendDailyDigest'], 10);
38 add_action('fluent_community/space/join_requested', [$this, 'handleCommunityJoinRequest'], 10, 2);
39 }
40
41 public function handleSpaceFeedCreated($feed)
42 {
43 if (did_action('fluent_community/feed/scheduling_everyone_tag')) {
44 return;
45 }
46
47 $space = $feed->space;
48 if (!$space) {
49 return false;
50 }
51
52 $types = ['np_by_member_mail'];
53 $spaceRole = $feed->user->getSpaceRole($feed->space);
54 if (in_array($spaceRole, ['admin', 'moderator'])) {
55 $types[] = 'np_by_admin_mail';
56 }
57
58 $hasSubscribers = User::query()->where(function ($query) use ($types, $space, $feed) {
59 $query->whereHas('notificationSubscriptions', function ($query) use ($types, $space) {
60 $query->whereIn('notification_type', $types)
61 ->where('object_id', $space->id)
62 ->where('is_read', 1);
63 });
64
65 do_action_ref_array('fluent_community/space_feed/email_notify_sub_query', [&$query, $feed, $space, $types]);
66
67 return $query;
68 })->exists();
69
70 if ($hasSubscribers || Arr::get($feed->meta, 'mentioned_user_ids', [])) {
71 // We are scheduling this after 2 minutes of the post publish for performance
72 as_schedule_single_action(time() + 120, 'fluent_community/email_notify_new_posts', [
73 $feed->id
74 ], 'fluent-community');
75 }
76 }
77
78 public function notifyOnPostCreatedAsync($feedId)
79 {
80 if (!$this->maxRunTime) {
81 $this->maxRunTime = Utility::getMaxRunTime();
82 }
83
84 if (is_numeric($feedId)) {
85 $feed = Feed::find($feedId);
86 } else {
87 $feed = $feedId;
88 }
89
90 if (!$feed || !$feed instanceof Feed) {
91 return;
92 }
93
94 $space = $feed->space;
95 if (!$space || !$feed->user) {
96 return;
97 }
98
99 $types = ['np_by_member_mail'];
100 $spaceRole = $feed->user->getSpaceRole($feed->space);
101 if (in_array($spaceRole, ['admin', 'moderator'])) {
102 $types[] = 'np_by_admin_mail';
103 }
104
105 $lastSendUserId = (int)$feed->getCustomMeta('_last_email_user_id', 0);
106 $usersQuery = User::query()->where(function ($query) use ($types, $space, $feed) {
107 $query->whereHas('notificationSubscriptions', function ($query) use ($types, $space) {
108 $query->whereIn('notification_type', $types)
109 ->where('object_id', $space->id)
110 ->where('is_read', 1);
111 });
112
113 $mentionedUserIds = Arr::get($feed->meta, 'mentioned_user_ids', []);
114
115 if ($mentionedUserIds) {
116 $query->orWhereIn('ID', $mentionedUserIds);
117 }
118
119 do_action_ref_array('fluent_community/space_feed/email_notify_sub_query', [&$query, $feed, $space, $types]);
120
121 return $query;
122 })
123 ->whereHas('space_pivot', function ($query) use ($space) {
124 $query->where('space_id', $space->id)
125 ->where('status', 'active');
126 })
127 ->when($lastSendUserId, function ($q) use ($lastSendUserId) {
128 $q->where('ID', '>', $lastSendUserId);
129 })
130 ->whereHas('xprofile', function ($query) {
131 return $query->where('status', 'active');
132 })
133 ->orderBy('ID', 'ASC')
134 ->limit(60);
135
136 $users = $usersQuery->get();
137
138 if ($users->isEmpty()) {
139 return; // It's done
140 }
141
142 $emailSubject = \sprintf(
143 /* translators: %1$s is the author name and %2$s is the post excerpt (max 30 chars) */
144 __('New Post By %1$s: %2$s', 'fluent-community'),
145 $feed->user->getDisplayName(),
146 $feed->getHumanExcerpt(30)
147 );
148
149 $emailBody = $feed->getFeedHtml(true);
150
151 /*
152 * must need to replace these two strings
153 * ##feed_permalink##
154 * ##email_notification_url##
155 */
156 $feedLink = $feed->getPermalink();
157
158 $startTime = microtime(true);
159 $maxSendPerSecond = 10; // max 10 emails per second
160
161 foreach ($users as $index => $user) {
162 $feed->updateCustomMeta('_last_email_user_id', $user->ID);
163 if ($user->ID == $feed->user_id) {
164 continue;
165 }
166
167 $newEmailBody = str_replace([
168 '##feed_permalink##',
169 '##email_notification_url##'
170 ], [
171 ProfileHelper::signUserUrlWithAuthHash($feedLink, $user->ID),
172 ProfileHelper::getSignedNotificationPrefUrl($user->ID)
173 ], $emailBody);
174
175 $notificationBagde = $this->getNotificationBadges($user->ID);
176 if ($notificationBagde) {
177 $newEmailBody = str_replace('<!--before_footer_section-->', $notificationBagde, $newEmailBody);
178 }
179
180 $hooksSections = apply_filters('fluent_community/new_feed_notification/email_sections', [
181 'before_content' => '',
182 'after_content' => ''
183 ], $user, $feed);
184
185 if (!empty($hooksSections['before_content'])) {
186 $newEmailBody = str_replace('<!--email_content_before-->', $hooksSections['before_content'], $newEmailBody);
187 }
188
189 if (!empty($hooksSections['after_content'])) {
190 $newEmailBody = str_replace('<!--email_content_after-->', $hooksSections['after_content'], $newEmailBody);
191 }
192
193 $mailer = new Mailer('', $emailSubject, $newEmailBody);
194 $mailer->to($user->user_email, $user->display_name);
195 $mailer->send();
196
197 if (microtime(true) - FLUENT_COMMUNITY_START_TIME > $this->maxRunTime) {
198 // It's been 45 seconds, let's stop and schedule the next one and schedule a new one
199 as_schedule_single_action(time(), 'fluent_community/email_notify_new_posts', [$feed->id], 'fluent-community');
200 return true;
201 }
202
203 if (($index + 1) % $maxSendPerSecond == 0) {
204 $timeTaken = microtime(true) - $startTime;
205 if ($timeTaken < 1) {
206 usleep((int)(1000000 - ($timeTaken * 1000000)));
207 }
208 $startTime = microtime(true);
209 }
210 }
211
212 return $this->notifyOnPostCreatedAsync($feed);
213 }
214
215 public function handleNewCommentEvent(Comment $comment, $feed)
216 {
217 // Check the comment mentioned users or not
218 if (Arr::get($comment->meta, 'mentioned_user_ids', [])) {
219 as_schedule_single_action(time(), 'fluent_community/comment_added_async', [$comment->id, 0], 'fluent-community');
220 return;
221 }
222
223 if ($comment->parent_id) {
224 $globalCommentStatus = $this->isEnabled('reply_my_com_mail');
225 } else {
226 $globalCommentStatus = $this->isEnabled('com_my_post_mail');
227 }
228
229 $notificationUserIds = [];
230 if ($comment->user_id != $feed->user_id && NotificationPref::willGetCommentEmail($feed->user_id, $globalCommentStatus)) {
231 as_schedule_single_action(time(), 'fluent_community/comment_added_async', [$comment->id, 0], 'fluent-community');
232 return true;
233 }
234
235 if ($comment->parent_id) {
236 $notificationUserIds = $comment->getCommentParentUserIds();
237 $notificationUserIds = array_filter($notificationUserIds, function ($userId) use ($globalCommentStatus) {
238 return NotificationPref::willGetCommentReplyEmail($userId, $globalCommentStatus);
239 });
240 }
241
242 if (!$notificationUserIds) {
243 return false;
244 }
245
246 as_schedule_single_action(time(), 'fluent_community/comment_added_async', [$comment->id, 0], 'fluent-community');
247 return true;
248 }
249
250 /*
251 * This is for the async notification for new comment
252 */
253 public function handleNewCommentNotificationAsync($commentId, $lastUserId = 0)
254 {
255 $comment = Comment::find($commentId);
256 if (!$comment) {
257 return true;
258 }
259
260 $feed = $comment->post;
261 if (!$feed) {
262 return;
263 }
264
265 if ($comment->parent_id) {
266 $globalCommentStatus = $this->isEnabled('reply_my_com_mail');
267 } else {
268 $globalCommentStatus = $this->isEnabled('com_my_post_mail');
269 }
270
271 $notificationUserIds = $comment->getCommentParentUserIds($lastUserId);
272 $notificationUserIds = array_filter($notificationUserIds, function ($userId) use ($globalCommentStatus) {
273 return NotificationPref::willGetCommentReplyEmail($userId, $globalCommentStatus);
274 });
275
276 $notificationUserIds = array_diff($notificationUserIds, [$comment->user_id]);
277 if ($comment->user_id != $feed->user_id && NotificationPref::willGetCommentEmail($feed->user_id, $globalCommentStatus)) {
278 // Add at the first
279 $notificationUserIds[] = $feed->user_id;
280 }
281
282 // the mentioned user ids
283 if ($mentionedUserIds = Arr::get($comment->meta, 'mentioned_user_ids', [])) {
284 foreach ($mentionedUserIds as $mentionedUserId) {
285 if (NotificationPref::willGetMentionEmail($mentionedUserId, $this->isEnabled('mention_mail'))) {
286 $notificationUserIds[] = $mentionedUserId;
287 }
288 }
289 }
290
291 if (!$notificationUserIds) {
292 return;
293 }
294
295 $notificationUserIds = array_unique($notificationUserIds);
296
297 $users = User::query()->whereIn('ID', $notificationUserIds)
298 ->whereHas('xprofile', function ($query) {
299 return $query->where('status', 'active');
300 })
301 ->when($lastUserId, function ($q) use ($lastUserId) {
302 $q->where('ID', '>', $lastUserId);
303 })
304 ->orderBy('ID', 'ASC')
305 ->get();
306
307 if ($users->isEmpty()) {
308 return; // it's done
309 }
310
311 $emailBody = $comment->getCommentHtml(true);
312 $emailSubject = $comment->getEmailSubject($feed);
313
314 $feedPermalik = $feed->getPermalink() . '?comment_id=' . $comment->id;
315 $usersCount = count($users);
316
317 $startTime = microtime(true);
318 $maxSendPerSecond = 10; // max 10 emails per second
319 foreach ($users as $index => $user) {
320 $lastUserId = $user->ID;
321 if ($user->ID == $comment->user_id) {
322 continue;
323 }
324
325 $newEmailBody = str_replace([
326 '##feed_permalink##',
327 '##email_notification_url##'
328 ], [
329 ProfileHelper::signUserUrlWithAuthHash($feedPermalik, $user->ID),
330 ProfileHelper::getSignedNotificationPrefUrl($user->ID)
331 ], $emailBody);
332
333 $notificationBagde = $this->getNotificationBadges($user->ID);
334 if ($notificationBagde) {
335 $newEmailBody = str_replace('<!--before_footer_section-->', $notificationBagde, $newEmailBody);
336 }
337
338 $hooksSections = apply_filters('fluent_community/comment_notification/email_sections', [
339 'before_content' => '',
340 'after_content' => ''
341 ], $user, $comment);
342
343 if (!empty($hooksSections['before_content'])) {
344 $newEmailBody = str_replace('<!--email_content_before-->', $hooksSections['before_content'], $newEmailBody);
345 }
346
347 if (!empty($hooksSections['after_content'])) {
348 $newEmailBody = str_replace('<!--email_content_after-->', $hooksSections['after_content'], $newEmailBody);
349 }
350
351 $mailer = new Mailer('', $emailSubject, $newEmailBody);
352 $mailer->to($user->user_email, $user->display_name);
353 $mailer->send();
354
355 if (microtime(true) - FLUENT_COMMUNITY_START_TIME > 50 && $index < ($usersCount - 1)) {
356 // It's been 45 seconds, let's stop and schedule the next one and schedule a new one
357 as_schedule_single_action(time(), 'fluent_community/comment_added_async', [$comment->id, $lastUserId], 'fluent-community');
358 return true;
359 }
360
361 if (($index + 1) % $maxSendPerSecond == 0) {
362 $timeTaken = microtime(true) - $startTime;
363 if ($timeTaken < 1) {
364 usleep((int)(1000000 - ($timeTaken * 1000000)));
365 }
366 $startTime = microtime(true);
367 }
368 }
369
370 // It's done
371 return true;
372 }
373
374 public function emailNotifyUsersForEveryoneTag($feedId, $lastSendUserId = 0)
375 {
376 if (!$this->maxRunTime) {
377 $this->maxRunTime = Utility::getMaxRunTime();
378 }
379
380 // Let's try to send email to all users of this space for this post
381 $feed = Feed::find($feedId);
382
383 if (!$feed || !$feed->space) {
384 return true;
385 }
386
387 if (!$feed->isEnabledForEveryoneTag()) {
388 return true;
389 }
390
391 $notification = Notification::where('action', 'space_feed/created')
392 ->where('feed_id', $feed->id)
393 ->first();
394
395 if (!$notification) {
396 return true;
397 }
398
399 $users = User::whereDoesntHave('notificationSubscriptions', function ($query) {
400 $query->where('notification_type', 'mention_mail')
401 ->where('is_read', 0);
402 })
403 ->whereHas('space_pivot', function ($query) use ($feed) {
404 $query->where('space_id', $feed->space_id)
405 ->where('status', 'active');
406 })
407 ->whereHas('xprofile', function ($query) {
408 return $query->where('status', 'active');
409 })
410 ->orderBy('ID', 'ASC')
411 ->when($lastSendUserId, function ($q) use ($lastSendUserId) {
412 $q->where('ID', '>', $lastSendUserId);
413 })
414 ->limit(100)
415 ->get();
416
417 if ($users->isEmpty()) {
418 return true; // it's done
419 }
420
421 $author = $feed->user;
422
423 $emailSubject = \sprintf(
424 /* translators: for admin post to send email all space members: %1$s is the feed title, %2$s is the author name and %3$s space name */
425 __('%1$s - %2$s [%3$s]', 'fluent-community'),
426 $feed->getHumanExcerpt(30),
427 $author->display_name,
428 $feed->space->title
429 );
430
431 $emailBody = $feed->getFeedHtml(true);
432 $feedPermalink = $feed->getPermalink();
433
434 $startTime = microtime(true);
435 $maxSendPerSecond = 10; // max 10 emails per second
436
437 foreach ($users as $index => $user) {
438 $lastSendUserId = $user->ID;
439 if ($user->ID == $author->ID) {
440 continue;
441 }
442
443 $newEmailBody = str_replace([
444 '##feed_permalink##',
445 '##email_notification_url##'
446 ], [
447 ProfileHelper::signUserUrlWithAuthHash($feedPermalink, $user->ID),
448 ProfileHelper::getSignedNotificationPrefUrl($user->ID)
449 ], $emailBody);
450
451 $notificationBagde = $this->getNotificationBadges($user->ID);
452 if ($notificationBagde) {
453 $newEmailBody = str_replace('<!--before_footer_section-->', $notificationBagde, $newEmailBody);
454 }
455
456 $hooksSections = apply_filters('fluent_community/new_feed_everybody_notification/email_sections', [
457 'before_content' => '',
458 'after_content' => ''
459 ], $user, $feed);
460
461 if (!empty($hooksSections['before_content'])) {
462 $newEmailBody = str_replace('<!--email_content_before-->', $hooksSections['before_content'], $newEmailBody);
463 }
464
465 if (!empty($hooksSections['after_content'])) {
466 $newEmailBody = str_replace('<!--email_content_after-->', $hooksSections['after_content'], $newEmailBody);
467 }
468
469 $mailer = new Mailer('', $emailSubject, $newEmailBody);
470 $mailer->to($user->user_email, $user->display_name);
471 $mailer->send();
472
473 if (microtime(true) - FLUENT_COMMUNITY_START_TIME > $this->maxRunTime) {
474 // It's been 45 seconds, let's stop and schedule the next one and schedule a new one
475 as_schedule_single_action(time(), 'fluent_community/email_notify_users_everyone_tag', [$feedId, $lastSendUserId], 'fluent-community');
476 return true;
477 }
478
479 if (($index + 1) % $maxSendPerSecond == 0) {
480 $timeTaken = microtime(true) - $startTime;
481 if ($timeTaken < 1) {
482 usleep((int)(1000000 - ($timeTaken * 1000000)));
483 }
484 $startTime = microtime(true);
485 }
486 }
487
488 sleep(1); // sleeping for 1 second
489 return $this->emailNotifyUsersForEveryoneTag($feedId, $lastSendUserId);
490 }
491
492 public function maybeSendDailyDigest()
493 {
494 if (!$this->maxRunTime) {
495 $this->maxRunTime = Utility::getMaxRunTime();
496 }
497
498 $settings = Utility::getEmailNotificationSettings();
499
500 $digestEmailDay = Arr::get($settings, 'digest_mail_day');
501 if (strtolower(gmdate('D', current_time('timestamp'))) != $digestEmailDay) {
502 return false;
503 }
504
505 $lastSentDate = Utility::getOption('last_digest_sent_date');
506 if ($lastSentDate && gmdate('Y-m-d', strtotime($lastSentDate)) == gmdate('Y-m-d', current_time('timestamp'))) {
507 return false; // already completed
508 }
509
510 $globalEnabled = Arr::get($settings, 'digest_email_status') === 'yes';
511 $lastSentUserId = Utility::getOption('last_digest_sent_user_id');
512
513 if ($globalEnabled) {
514 $users = User::whereDoesntHave('notification_records', function ($query) {
515 $query->where('notification_type', 'digest_mail')
516 ->where('is_read', 0);
517 })
518 ->whereHas('xprofile', function ($query) {
519 $query->where('status', 'active');
520 })
521 ->when($lastSentUserId, function ($q) use ($lastSentUserId) {
522 $q->where('ID', '>', $lastSentUserId);
523 })
524 ->limit(100)
525 ->orderBy('ID', 'ASC')
526 ->get();
527 } else {
528 $users = User::whereHas('notification_records', function ($query) {
529 $query->where('notification_type', 'digest_mail')
530 ->where('is_read', 1);
531 })
532 ->whereHas('xprofile', function ($query) {
533 $query->where('status', 'active');
534 })
535 ->when($lastSentUserId, function ($q) use ($lastSentUserId) {
536 $q->where('ID', '>', $lastSentUserId);
537 })
538 ->limit(100)
539 ->orderBy('ID', 'ASC')
540 ->get();
541 }
542
543 if ($users->isEmpty()) {
544 // It's done
545 Utility::updateOption('last_digest_sent_date', current_time('mysql'));
546 Utility::updateOption('last_digest_sent_user_id', 0);
547 return false;
548 }
549
550 $startAt = microtime(true);
551 $maxSendPerSecond = 10;
552 $sentCount = 0;
553
554 foreach ($users as $user) {
555 Utility::updateOption('last_digest_sent_user_id', $user->ID);
556 $emailDigest = new DailyDigest($user);
557 if ($emailDigest->send()) {
558 $sentCount++;
559 }
560
561 if (microtime(true) - FLUENT_COMMUNITY_START_TIME > $this->maxRunTime) {
562 // It's been 45 seconds, let's stop and schedule the next one and schedule a new one
563 as_schedule_single_action(time(), 'fluent_community_send_daily_digest', [], 'fluent-community', false);
564 return true;
565 }
566
567 if ($sentCount % $maxSendPerSecond === 0) {
568 $timeTaken = microtime(true) - $startAt;
569 if ($timeTaken < 1) {
570 usleep((int)(1000000 - ($timeTaken * 1000000)));
571 }
572 $startAt = microtime(true);
573 }
574 }
575
576 return $this->maybeSendDailyDigest();
577 }
578
579 private function isEnabled($key)
580 {
581 $settings = Utility::getEmailNotificationSettings();
582
583 return Arr::get($settings, $key) === 'yes';
584 }
585
586 public function handleCommunityJoinRequest(BaseSpace $space, $userId)
587 {
588 $xProfile = XProfile::where('user_id', $userId)->first();
589 if (!$xProfile) {
590 return;
591 }
592
593 $emailComposer = new \FluentCommunity\App\Services\Libs\EmailComposer();
594
595 $emailComposer->addBlock('paragraph', __('Hi Space Leader,', 'fluent-community'));
596 /* translators: %1$s is replaced by the name of the user who requested to join the space, %2$s is replaced by the title of the space */
597 $emailComposer->addBlock('paragraph', sprintf(__('You have a new join request from %1$s to join %2$s.', 'fluent-community'), '<b>' . $xProfile->display_name . '</b>', '<b>' . $space->title . '</b>'));
598 $emailComposer->addBlock('paragraph', __('Please review the request in the portal', 'fluent-community'));
599
600 $emailComposer->addBlock('button', __('View Pending Join Requests', 'fluent-community'), [
601 'link' => Helper::baseUrl('space/' . $space->slug . '/members?view_pending=yes')
602 ]);
603
604 $emailComposer->setDefaultLogo();
605
606 /* translators: %s is replaced by the title of the space */
607 $emailComposer->addFooterLine('paragraph', sprintf(__('You are getting this email because you are an admin/moderator at %s', 'fluent-community'), '<a style="text-decoration: underline !important;" href="' . $space->getPermalink() . '">' . $space->title . '</a>'));
608
609 $emailBody = $emailComposer->getHtml();
610
611 $emailSubject = \sprintf(
612 /* translators: %1$s is replaced by the name of the user who requested to join the space, %2$s is replaced by the title of the space */
613 __('%1$s requested to join %2$s', 'fluent-community'),
614 $xProfile->display_name,
615 $space->title
616 );
617
618 $moderatorsUserIds = SpaceUserPivot::where('space_id', $space->id)
619 ->whereIn('role', ['moderator', 'admin'])
620 ->pluck('user_id')
621 ->toArray();
622
623 if (!$moderatorsUserIds) {
624 return false;
625 }
626
627 $mailer = new Mailer('', $emailSubject, $emailBody);
628 if (count($moderatorsUserIds) == 1) {
629 $modUserId = $moderatorsUserIds[0];
630
631 $moderator = get_user_by('ID', $modUserId);
632
633 if (!$moderator || !$moderator->user_email) {
634 return;
635 }
636
637 $mailer->to($moderator->user_email, $moderator->display_name);
638 return $mailer->send();
639 }
640
641 // send by BCC by 12 chunks
642 $chunks = array_chunk($moderatorsUserIds, 12);
643
644 foreach ($chunks as $chunk) {
645
646 $mailer = new Mailer('', $emailSubject, $emailBody);
647
648 $users = User::whereIn('ID', $chunk)->get();
649 $first = null;
650 foreach ($users as $user) {
651 if (!$user || !$user->user_email) {
652 continue;
653 }
654 if (!$first) {
655 $mailer->to($user->user_email, $user->display_name);
656 $first = $user;
657 continue;
658 }
659 $mailer->addBCC($user->display_name . ' <' . $user->user_email . '>');
660 }
661
662 if ($first) {
663 $mailer->send();
664 sleep(1); // sleeping for 1 second
665 }
666 }
667
668 return true;
669 }
670
671 private function getNotificationBadges($userId)
672 {
673 $unreadCount = Notification::byStatus('unread', $userId)->count();
674 $unreadMessages = apply_filters('fluent_messaging/get_unread_message_count', 0, $userId);
675
676 $html = '';
677 if ($unreadCount) {
678 $notificationUrl = ProfileHelper::signUserUrlWithAuthHash(Helper::baseUrl('notifications'), $userId);
679 /* translators: %d is replaced by the number of unread notifications */
680 $html = '<a style="text-decoration: none;" href="' . $notificationUrl . '">' . sprintf(__('🔔 %d Unread Notifications.', 'fluent-community'), $unreadCount) . '</a>';
681 if ($unreadMessages) {
682 $html .= '<span style="margin: 0 10px;"> | </span>';
683 }
684 }
685
686 if ($unreadMessages) {
687 $chatUrl = ProfileHelper::signUserUrlWithAuthHash(Helper::baseUrl('chat'), $userId);
688 /* translators: %d is replaced by the number of unread messages */
689 $html .= '<a style="text-decoration: none;" href="' . $chatUrl . '">' . sprintf(__('✉️ %d Unread Messages', 'fluent-community'), $unreadMessages) . '</a>';
690 }
691
692 return $html;
693 }
694 }
695