PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / app / Hooks / Handlers / ScheduleHandler.php

ScheduleHandler.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.1.0, at app/Hooks/Handlers/ScheduleHandler.php

585 lines 23.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Hooks\Handlers;
4
5 use FluentBoards\App\Models\Board;
6 use FluentBoards\App\Models\Comment;
7 use FluentBoards\App\Models\Meta;
8 use FluentBoards\App\Models\Relation;
9 use FluentBoards\App\Models\Task;
10 use FluentBoards\App\Models\User;
11 use FluentBoards\App\Services\Constant;
12 use FluentBoards\App\Services\Helper;
13
14 class ScheduleHandler
15 {
16 public function sendEmailForComment(
17 $commentId,
18 $recipientUserIds,
19 $current_user_id
20 ) {
21 try {
22 $comment = Comment::find($commentId) ?? null;
23 if ( ! $comment) {
24 return;
25 }
26 if (!in_array($comment->type, ['comment', 'reply'])) {
27 return;
28 }
29
30 $task = Task::findOrFail($comment->task_id);
31 if ( ! $task) {
32 return;
33 }
34
35 $usersToSendEmail = $this->getEligibleCommentRecipientEmails(
36 $recipientUserIds,
37 $task->board_id
38 );
39
40 if (!$usersToSendEmail) {
41 return;
42 }
43
44 $board = $task->board;
45
46 $page_url = site_url('/') . '?redirect=to_task&taskId='.$task->id;
47
48 $user = $task->user($comment->created_by);
49
50 $userData = $this->getUserData($current_user_id);
51
52 $boardUrl = $page_url.'boards/'.$board->id;
53 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'.$task->id.'-'
54 .substr($task->title, 0, 10);
55
56 $userLinkTag = '<strong>'.htmlspecialchars($user->display_name)
57 .'</strong>';
58 $taskLinkTag = '<a target="_blank" href="'
59 .htmlspecialchars($taskUrl).'">'
60 .htmlspecialchars($task->title).'</a>';
61 $boardLinkTag = '<a target="_blank" href="'
62 .htmlspecialchars($boardUrl).'">'
63 .htmlspecialchars($board->title).'</a>';
64
65 // Consolidated translation with placeholders
66 // translators: %1$s is the task link, %2$s is the board link
67 $preparedBody = sprintf(__('commented on %1$s on %2$s board.', 'fluent-boards'), $taskLinkTag, $boardLinkTag);
68 $preHeader = __('New comment has been added on task','fluent-boards');
69 $mailSubject = __('New comment has been added on task','fluent-boards');
70
71
72 if ($comment->type == 'reply')
73 {
74 // translators: %1$s is the task link, %2$s is the board link
75 $preparedBody = sprintf(__('replied on your comment on %1$s on %2$s board.', 'fluent-boards'), $taskLinkTag, $boardLinkTag);
76
77 $preHeader = __('A reply has been added to your comment on a task.','fluent-boards');
78 $mailSubject = __('New Reply on Your Task Comment','fluent-boards');
79 }
80
81 $data = [
82 'body' => $preparedBody,
83 'comment_link' => $page_url,
84 'pre_header' => $preHeader,
85 'show_footer' => true,
86 'comment' => $comment->description,
87 'userData' => $userData,
88 'site_url' => site_url(),
89 'site_title' => get_bloginfo('name'),
90 'site_logo' => fluent_boards_site_logo(),
91 ];
92
93 $message = Helper::loadView('emails.comment2', $data);
94 $headers = ['Content-Type: text/html; charset=UTF-8'];
95
96 foreach ($usersToSendEmail as $email) {
97 \wp_mail($email, $mailSubject, $message, $headers);
98 }
99 } catch (\Exception $e) {
100 // do nothing // better to log here
101 }
102 }
103
104 public function sendEmailForMention($commentId, $recipientUserIds, $current_user_id)
105 {
106 try {
107 $comment = Comment::find($commentId) ?? null;
108 if ( ! $comment) {
109 return;
110 }
111
112 if (!in_array($comment->type, ['comment', 'reply'])) {
113 return;
114 }
115
116 $task = Task::findOrFail($comment->task_id);
117 if ( ! $task) {
118 return;
119 }
120
121 $usersToSendEmail = $this->getEligibleCommentRecipientEmails(
122 $recipientUserIds,
123 $task->board_id
124 );
125 if (!$usersToSendEmail) {
126 return;
127 }
128 // $assignees = $task->assignees;
129 $board = $task->board;
130
131 $page_url = site_url('/') . '?redirect=to_task&taskId='.$task->id;
132
133 $user = $task->user($comment->created_by);
134
135 $userData = $this->getUserData($current_user_id);
136
137 $boardUrl = $page_url.'boards/'.$board->id;
138 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'.$task->id.'-'
139 .substr($task->title, 0, 10);
140
141 $userLinkTag = '<strong>'.htmlspecialchars($user->display_name)
142 .'</strong>';
143 $taskLinkTag = '<a target="_blank" href="'
144 .htmlspecialchars($taskUrl).'">'
145 .htmlspecialchars($task->title).'</a>';
146 $boardLinkTag = '<a target="_blank" href="'
147 .htmlspecialchars($boardUrl).'">'
148 .htmlspecialchars($board->title).'</a>';
149
150 $commentLink = $taskUrl . '?comment='.$comment->id;
151
152 // translators: %1$s is the task link, %2$s is the board link
153 $bodyText = sprintf(__('mentioned you in a comment on %1$s on %2$s board.', 'fluent-boards'), $taskLinkTag, $boardLinkTag);
154
155 $data = [
156 'body' => $bodyText,
157 'comment_link' => $page_url,
158 'pre_header' => __('You are mentioned in a comment','fluent-boards'),
159 'show_footer' => true,
160 'comment' => $comment->description,
161 'userData' => $userData,
162 'site_url' => site_url(),
163 'site_title' => get_bloginfo('name'),
164 'site_logo' => fluent_boards_site_logo(),
165 ];
166
167 $mailSubject = __('You are mentioned in a comment','fluent-boards');
168 $message = Helper::loadView('emails.comment2', $data);
169 $headers = ['Content-Type: text/html; charset=UTF-8'];
170
171 foreach ($usersToSendEmail as $email) {
172 \wp_mail($email, $mailSubject, $message, $headers);
173 }
174 } catch (\Exception $e) {
175 // do nothing // better to log here
176 }
177 }
178
179 public function sendEmailForAddAssignee(
180 $taskId,
181 $newAssigneeId,
182 $current_user_id
183 ) {
184 try {
185 $task = Task::findOrFail($taskId);
186 $board = $task->board;
187 $assignee = User::findOrFail($newAssigneeId);
188
189 $userData = $this->getUserData($current_user_id);
190
191 $page_url = fluent_boards_page_url();
192 $boardUrl = $page_url.'boards/'.$board->id;
193 $boardLinkTag = '<a target="_blank" href="'
194 .htmlspecialchars($boardUrl).'">'
195 .htmlspecialchars($board->title).'</a>';
196
197 if (null == $task->parent_id) {
198 // this is a task
199 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'
200 .$task->id.'-'.substr($task->title, 0, 10);
201 $taskLinkTag = '<a target="_blank" href="'
202 .htmlspecialchars($taskUrl).'">'
203 .htmlspecialchars($task->title).'</a>';
204 // translators: %1$s is the task link, %2$s is the board link
205 $bodyText = sprintf(__('has assigned you to task %1$s on %2$s board.', 'fluent-boards'), $taskLinkTag, $boardLinkTag);
206
207 $data = [
208 'body' => $bodyText,
209 'pre_header' => __('you have been assigned to task','fluent-boards'),
210 'show_footer' => true,
211 'userData' => $userData,
212 'site_url' => site_url(),
213 'site_title' => get_bloginfo('name'),
214 'site_logo' => fluent_boards_site_logo(),
215 ];
216 } else {
217 // this is a subtask
218 $task = $task->parentTask($task->parent_id);
219
220 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'
221 .$task->id.'-'.substr($task->title, 0, 10);
222 $taskLinkTag = '<a target="_blank" href="'
223 .htmlspecialchars($taskUrl).'">'
224 .htmlspecialchars($task->title).'</a>';
225
226 // translators: %1$s is the subtask title, %2$s is the task link, %3$s is the board link
227 $bodyText = sprintf(__('has assigned you to subtask <strong>%1$s</strong> of task %2$s on the board %3$s', 'fluent-boards'), $task->title, $taskLinkTag, $boardLinkTag);
228
229 $data = [
230 'body' => $bodyText,
231 'pre_header' => __('you have been assigned to subtask','fluent-boards'),
232 'show_footer' => true, 'user' => $assignee,
233 'userData' => $userData,
234 'site_url' => site_url(),
235 'site_title' => get_bloginfo('name'),
236 'site_logo' => fluent_boards_site_logo(),
237 ];
238 }
239
240 $mailSubject = __('You have been assigned to task','fluent-boards');
241 $message = Helper::loadView('emails.assignee2', $data);
242 $headers = ['Content-Type: text/html; charset=UTF-8'];
243
244
245 \wp_mail($assignee->user_email, $mailSubject, $message, $headers);
246
247 } catch (\Exception $e) {
248 throw new \Exception(esc_html__('Error in sending mail to new assignees', 'fluent-boards'), 1);
249 }
250 }
251
252 public function sendEmailForRemoveAssignee(
253 $taskId,
254 $newAssigneeId,
255 $current_user_id
256 ) {
257 try {
258 $task = Task::findOrFail($taskId);
259 $board = $task->board;
260 $assignee = User::findOrFail($newAssigneeId);
261
262 $userData = $this->getUserData($current_user_id);
263
264 $page_url = fluent_boards_page_url();
265
266 $boardUrl = $page_url.'boards/'.$board->id;
267 $boardLinkTag = '<a target="_blank" href="'
268 .htmlspecialchars($boardUrl).'">'
269 .htmlspecialchars($board->title).'</a>';
270
271 if (null == $task->parent_id) {
272 // this is a task
273 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'
274 .$task->id.'-'.substr($task->title, 0, 10);
275 $taskLinkTag = '<a target="_blank" href="'
276 .htmlspecialchars($taskUrl).'">'
277 .htmlspecialchars($task->title).'</a>';
278 // translators: %1$s is the task link, %2$s is the board link
279 $bodyText = sprintf(__('has removed you from task %1$s on the board %2$s', 'fluent-boards'), $taskLinkTag, $boardLinkTag);
280
281 $data = [
282 'body' => $bodyText,
283 'pre_header' => __('you have been removed from task','fluent-boards'),
284 'show_footer' => true,
285 'userData' => $userData,
286 'site_url' => site_url(),
287 'site_title' => get_bloginfo('name'),
288 'site_logo' => fluent_boards_site_logo(),
289 ];
290 } else {
291 // this is a subtask
292 $task = $task->parentTask($task->parent_id);
293
294 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'
295 .$task->id.'-'.substr($task->title, 0, 10);
296 $taskLinkTag = '<a target="_blank" href="'
297 .htmlspecialchars($taskUrl).'">'
298 .htmlspecialchars($task->title).'</a>';
299
300 // translators: %1$s is the subtask title, %2$s is the task link, %3$s is the board link
301 $bodyText = sprintf(__('has removed you from subtask <strong>%1$s</strong> of task %2$s on the board %3$s', 'fluent-boards'), $task->title, $taskLinkTag, $boardLinkTag);
302
303 $data = [
304 'body' => $bodyText,
305 'pre_header' => __('you have been removed from subtask','fluent-boards'),
306 'show_footer' => true,
307 'userData' => $userData,
308 ];
309 }
310
311 $mailSubject = __('You have been removed from task','fluent-boards');
312 $message = Helper::loadView('emails.assignee2', $data);
313 $headers = ['Content-Type: text/html; charset=UTF-8'];
314
315 \wp_mail($assignee->user_email, $mailSubject, $message, $headers);
316
317 } catch (\Exception $e) {
318 throw new \Exception(esc_html__('Error in sending mail to new assignees', 'fluent-boards'), 1);
319 }
320 }
321
322
323 public function sendEmailForChangeStage(
324 $taskId,
325 $newAssigneeEmails,
326 $current_user_id
327 ) {
328 try {
329 $task = Task::findOrFail($taskId);
330 $board = $task->board;
331
332 $userData = $this->getUserData($current_user_id);
333
334 $page_url = fluent_boards_page_url();
335 $boardUrl = $page_url.'boards/'.$board->id;
336 $boardLinkTag = '<a target="_blank" href="'
337 .htmlspecialchars($boardUrl).'">'
338 .htmlspecialchars($board->title).'</a>';
339
340 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'.$task->id
341 .'-'.substr($task->title, 0, 10);
342 $taskLinkTag = '<a target="_blank" href="'
343 .htmlspecialchars($taskUrl).'">'
344 .htmlspecialchars($task->title).'</a>';
345
346 // translators: %1$s is the task link, %2$s is the stage title, %3$s is the board link
347 $bodyText = sprintf(__('has moved %1$s task to <strong>%2$s</strong> stage of board %3$s', 'fluent-boards'), $taskLinkTag, $task->stage->title, $boardLinkTag);
348
349 $data = [
350 'body' => $bodyText,
351 'pre_header' => __('Task stage has been changed','fluent-boards'),
352 'show_footer' => true,
353 'userData' => $userData,
354 'site_url' => site_url(),
355 'site_title' => get_bloginfo('name'),
356 'site_logo' => fluent_boards_site_logo(),
357 ];
358
359
360 $mailSubject = __('Task stage has been changed','fluent-boards');
361 $message = Helper::loadView('emails.assignee2', $data);
362 $headers = ['Content-Type: text/html; charset=UTF-8'];
363
364 foreach ($newAssigneeEmails as $assignee_email) {
365 \wp_mail($assignee_email, $mailSubject, $message, $headers);
366 }
367 } catch (\Exception $e) {
368 // Silent fail for email sending
369 }
370 }
371
372 public function sendEmailForDueDateUpdate(
373 $taskId,
374 $newAssigneeEmails,
375 $current_user_id
376 ) {
377 try {
378 $task = Task::findOrFail($taskId);
379 $board = $task->board;
380
381 $userData = $this->getUserData($current_user_id);
382
383 $page_url = fluent_boards_page_url();
384 $boardUrl = $page_url.'boards/'.$board->id;
385 $boardLinkTag = '<a target="_blank" href="'
386 .htmlspecialchars($boardUrl).'">'
387 .htmlspecialchars($board->title).'</a>';
388
389 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'.$task->id
390 .'-'.substr($task->title, 0, 10);
391 $taskLinkTag = '<a target="_blank" href="'
392 .htmlspecialchars($taskUrl).'">'
393 .htmlspecialchars($task->title).'</a>';
394
395 // translators: %1$s is the task link, %2$s is the due date, %3$s is the board link
396 $bodyText = sprintf(__('has updated due date of %1$s task to <strong>%2$s</strong> of board %3$s', 'fluent-boards'), $taskLinkTag, $task->due_at, $boardLinkTag);
397
398 $data = [
399 'body' => $bodyText,
400 'pre_header' => __('Task due date has been changed','fluent-boards'),
401 'show_footer' => true,
402 'userData' => $userData,
403 'site_url' => site_url(),
404 'site_title' => get_bloginfo('name'),
405 'site_logo' => fluent_boards_site_logo(),
406 ];
407
408
409 $mailSubject = __('Task due date has been changed','fluent-boards');
410 $message = Helper::loadView('emails.assignee2', $data);
411 $headers = ['Content-Type: text/html; charset=UTF-8'];
412
413 foreach ($newAssigneeEmails as $assignee_email) {
414 \wp_mail($assignee_email, $mailSubject, $message, $headers);
415 }
416 } catch (\Exception $e) {
417 throw new \Exception(esc_html__('Error in sending mail to new assignees', 'fluent-boards'), 1);
418 }
419 }
420
421 public function sendEmailForArchivedTask(
422 $taskId,
423 $newAssigneeEmails,
424 $current_user_id
425 ) {
426 try {
427 $task = Task::findOrFail($taskId);
428 $board = $task->board;
429
430 $userData = $this->getUserData($current_user_id);
431
432 $page_url = fluent_boards_page_url();
433 $boardUrl = $page_url.'boards/'.$board->id;
434 $boardLinkTag = '<a target="_blank" href="'
435 .htmlspecialchars($boardUrl).'">'
436 .htmlspecialchars($board->title).'</a>';
437
438 $taskUrl = $page_url.'boards/'.$board->id.'/tasks/'.$task->id
439 .'-'.substr($task->title, 0, 10);
440 $taskLinkTag = '<a target="_blank" href="'
441 .htmlspecialchars($taskUrl).'">'
442 .htmlspecialchars($task->title).'</a>';
443
444 // translators: %1$s is the task link, %2$s is the board link
445 $bodyText = sprintf(__('has archived %1$s task of board %2$s', 'fluent-boards'), $taskLinkTag, $boardLinkTag);
446
447 $data = [
448 'body' => $bodyText,
449 'pre_header' => __('Task has been archived','fluent-boards'),
450 'show_footer' => true,
451 'userData' => $userData,
452 'site_url' => site_url(),
453 'site_title' => get_bloginfo('name'),
454 'site_logo' => fluent_boards_site_logo(),
455 ];
456
457
458 $mailSubject = __('Task has been archived','fluent-boards');
459 $message = Helper::loadView('emails.assignee2', $data);
460 $headers = ['Content-Type: text/html; charset=UTF-8'];
461
462 foreach ($newAssigneeEmails as $assignee_email) {
463 \wp_mail($assignee_email, $mailSubject, $message, $headers);
464 }
465 } catch (\Exception $e) {
466 throw new \Exception(esc_html__('Error in sending mail to new assignees', 'fluent-boards'), 1);
467 }
468 }
469
470 /**
471 * Resolve current recipient emails after revalidating board access and preferences.
472 *
473 * Email values are accepted only for queued actions created before recipient IDs
474 * were introduced. They are mapped back to current WordPress users before checks.
475 *
476 * @param array $queuedRecipients
477 * @param int $boardId
478 * @return array
479 */
480 private function getEligibleCommentRecipientEmails($queuedRecipients, $boardId)
481 {
482 $legacyEmails = [];
483 foreach ((array) $queuedRecipients as $queuedRecipient) {
484 if (is_string($queuedRecipient) && !ctype_digit($queuedRecipient)) {
485 $legacyEmail = sanitize_email($queuedRecipient);
486 if ($legacyEmail && is_email($legacyEmail)) {
487 $legacyEmails[strtolower($legacyEmail)] = $legacyEmail;
488 }
489 }
490 }
491
492 $legacyUserIdsByEmail = [];
493 if ($legacyEmails) {
494 $legacyUsers = User::whereIn('user_email', array_values($legacyEmails))
495 ->get(['ID', 'user_email']);
496 foreach ($legacyUsers as $legacyUser) {
497 $legacyUserIdsByEmail[strtolower($legacyUser->user_email)] = absint($legacyUser->ID);
498 }
499 }
500
501 $recipientUserIds = [];
502 $seenRecipientUserIds = [];
503 foreach ((array) $queuedRecipients as $queuedRecipient) {
504 $recipientUserId = 0;
505
506 if (is_int($queuedRecipient) || (is_string($queuedRecipient) && ctype_digit($queuedRecipient))) {
507 $recipientUserId = absint($queuedRecipient);
508 } elseif (is_string($queuedRecipient)) {
509 $legacyEmail = sanitize_email($queuedRecipient);
510 if ($legacyEmail) {
511 $recipientUserId = $legacyUserIdsByEmail[strtolower($legacyEmail)] ?? 0;
512 }
513 }
514
515 if (!$recipientUserId || isset($seenRecipientUserIds[$recipientUserId])) {
516 continue;
517 }
518 $seenRecipientUserIds[$recipientUserId] = true;
519 $recipientUserIds[] = $recipientUserId;
520 }
521
522 if (!$recipientUserIds) {
523 return [];
524 }
525
526 $eligibleRecipientIds = [];
527 $boardRelations = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
528 ->where('object_id', absint($boardId))
529 ->whereIn('foreign_id', $recipientUserIds)
530 ->get(['foreign_id', 'preferences']);
531
532 foreach ($boardRelations as $boardRelation) {
533 $preferences = maybe_unserialize($boardRelation->preferences);
534 if (!is_array($preferences)) {
535 continue;
536 }
537
538 if (
539 !array_key_exists(Constant::BOARD_EMAIL_COMMENT, $preferences) ||
540 $preferences[Constant::BOARD_EMAIL_COMMENT]
541 ) {
542 $eligibleRecipientIds[absint($boardRelation->foreign_id)] = true;
543 }
544 }
545
546 if (!$eligibleRecipientIds) {
547 return [];
548 }
549
550 $usersById = [];
551 $recipients = User::whereIn('ID', array_keys($eligibleRecipientIds))
552 ->get(['ID', 'user_email']);
553 foreach ($recipients as $recipient) {
554 $usersById[absint($recipient->ID)] = $recipient;
555 }
556
557 $recipientEmails = [];
558 foreach ($recipientUserIds as $recipientUserId) {
559 if (!isset($eligibleRecipientIds[$recipientUserId], $usersById[$recipientUserId])) {
560 continue;
561 }
562
563 $email = sanitize_email($usersById[$recipientUserId]->user_email);
564 if ($email && is_email($email)) {
565 $recipientEmails[] = $email;
566 }
567 }
568
569 return $recipientEmails;
570 }
571
572 private function getUserData($userId)
573 {
574 $currentUser = User::findOrFail($userId);
575 $gravaterPhoto = fluent_boards_user_avatar($currentUser->user_email,
576 $currentUser->display_name);
577
578 return [
579 'display_name' => $currentUser->display_name,
580 'photo' => $gravaterPhoto,
581 ];
582 }
583
584 }
585