PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.0
2.2.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 All 43 releases
fluent-boards / app / Hooks / Handlers / ScheduleHandler.php

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

615 lines 26.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 use FluentBoards\App\Services\Libs\Mailer;
14
15 class ScheduleHandler
16 {
17 public function sendEmailForComment(
18 $commentId,
19 $recipientUserIds,
20 $current_user_id
21 ) {
22 $context = ['type' => 'comment', 'comment_id' => $commentId];
23 try {
24 $comment = Comment::find($commentId) ?? null;
25 if ( ! $comment) {
26 return;
27 }
28 if (!in_array($comment->type, ['comment', 'reply'])) {
29 return;
30 }
31
32 $task = Task::findOrFail($comment->task_id);
33 if ( ! $task) {
34 return;
35 }
36
37 $usersToSendEmail = $this->getEligibleCommentRecipientEmails(
38 $recipientUserIds,
39 $task->board_id
40 );
41
42 if (!$usersToSendEmail) {
43 return;
44 }
45
46 $board = $task->board;
47
48 $page_url = fluent_boards_page_url();
49 $redirectUrl = site_url('/') . '?redirect=to_task&taskId='.$task->id;
50
51 $userData = $this->getUserData($current_user_id);
52
53 $taskUrl = $this->taskUrl($page_url, $board->id, $task);
54
55 $isReply = $comment->type == 'reply';
56
57 if ($isReply) {
58 $parentComment = $comment->parent_id ? Comment::find($comment->parent_id) : null;
59
60 $data = [
61 'badge' => ['label' => __('Reply', 'fluent-boards'), 'tone' => 'accent'],
62 // translators: %s is the name of the person who replied
63 'heading' => sprintf(__('%s replied to your comment', 'fluent-boards'), $userData['display_name']),
64 'parent_comment' => $parentComment ? $parentComment->description : '',
65 'cta' => ['label' => __('Reply', 'fluent-boards'), 'url' => $redirectUrl],
66 'email_reason' => __('You’re getting this because someone replied to your comment.', 'fluent-boards'),
67 'pre_header' => wp_trim_words(wp_strip_all_tags($comment->description), 20),
68 ];
69 // translators: %s is the name of the person who replied
70 $mailSubject = sprintf(__('%s replied to your comment', 'fluent-boards'), $userData['display_name']);
71 } else {
72 $data = [
73 'badge' => ['label' => __('New comment', 'fluent-boards'), 'tone' => 'accent'],
74 // translators: %s is the name of the commenter
75 'heading' => sprintf(__('%s commented on a task', 'fluent-boards'), $userData['display_name']),
76 'cta' => ['label' => __('Reply to comment', 'fluent-boards'), 'url' => $redirectUrl],
77 'email_reason' => __('You’re getting this because of your email settings for this board.', 'fluent-boards'),
78 'pre_header' => wp_trim_words(wp_strip_all_tags($comment->description), 20),
79 ];
80 // translators: %1$s is the commenter name, %2$s is the task title
81 $mailSubject = sprintf(__('%1$s commented on “%2$s”', 'fluent-boards'), $userData['display_name'], $task->title);
82 }
83
84 $data = array_merge($this->getBrandingData(), $data, [
85 'context' => $this->contextLine($task->title, $taskUrl, $board->title, $page_url.'boards/'.$board->id),
86 'comment' => $comment->description,
87 'userData' => $userData,
88 'show_footer' => true,
89 ]);
90
91 $message = Helper::loadView('emails.comment2', $data);
92
93 foreach ($usersToSendEmail as $email) {
94 (new Mailer($email, $mailSubject, $message))->sendNotification($context);
95 }
96 } catch (\Throwable $e) {
97 Mailer::reportFailure($context, $e);
98 }
99 }
100
101 public function sendEmailForMention($commentId, $recipientUserIds, $current_user_id)
102 {
103 $context = ['type' => 'mention', 'comment_id' => $commentId];
104 try {
105 $comment = Comment::find($commentId) ?? null;
106 if ( ! $comment) {
107 return;
108 }
109
110 if (!in_array($comment->type, ['comment', 'reply'])) {
111 return;
112 }
113
114 $task = Task::findOrFail($comment->task_id);
115 if ( ! $task) {
116 return;
117 }
118
119 $usersToSendEmail = $this->getEligibleCommentRecipientEmails(
120 $recipientUserIds,
121 $task->board_id
122 );
123 if (!$usersToSendEmail) {
124 return;
125 }
126 $board = $task->board;
127
128 $page_url = fluent_boards_page_url();
129 $redirectUrl = site_url('/') . '?redirect=to_task&taskId='.$task->id;
130
131 $userData = $this->getUserData($current_user_id);
132
133 $taskUrl = $this->taskUrl($page_url, $board->id, $task);
134
135 $data = array_merge($this->getBrandingData(), [
136 'badge' => ['label' => __('Mentioned you', 'fluent-boards'), 'tone' => 'accent'],
137 // translators: %s is the name of the person who mentioned the recipient
138 'heading' => sprintf(__('%s mentioned you in a comment', 'fluent-boards'), $userData['display_name']),
139 'context' => $this->contextLine($task->title, $taskUrl, $board->title, $page_url.'boards/'.$board->id),
140 'comment' => $comment->description,
141 'userData' => $userData,
142 'cta' => ['label' => __('Reply to comment', 'fluent-boards'), 'url' => $redirectUrl],
143 'email_reason' => __('You’re getting this because someone mentioned you.', 'fluent-boards'),
144 'pre_header' => wp_trim_words(wp_strip_all_tags($comment->description), 20),
145 'show_footer' => true,
146 ]);
147
148 // translators: %1$s is the name of the person who mentioned the recipient, %2$s is the task title
149 $mailSubject = sprintf(__('%1$s mentioned you in “%2$s”', 'fluent-boards'), $userData['display_name'], $task->title);
150 $message = Helper::loadView('emails.comment2', $data);
151
152 foreach ($usersToSendEmail as $email) {
153 (new Mailer($email, $mailSubject, $message))->sendNotification($context);
154 }
155 } catch (\Throwable $e) {
156 Mailer::reportFailure($context, $e);
157 }
158 }
159
160 public function sendEmailForAddAssignee(
161 $taskId,
162 $newAssigneeId,
163 $current_user_id
164 ) {
165 $context = ['type' => 'add_assignee', 'task_id' => $taskId];
166 try {
167 $task = Task::findOrFail($taskId);
168 $board = $task->board;
169 $assignee = User::findOrFail($newAssigneeId);
170
171 $userData = $this->getUserData($current_user_id);
172 $page_url = fluent_boards_page_url();
173 $boardUrl = $page_url.'boards/'.$board->id;
174
175 if (null == $task->parent_id) {
176 // this is a task
177 $taskUrl = $this->taskUrl($page_url, $board->id, $task);
178
179 $data = [
180 'badge' => ['label' => __('Assigned to you', 'fluent-boards'), 'tone' => 'accent'],
181 // translators: %s is the name of the person who assigned the task
182 'heading' => sprintf(__('%s assigned you to a task', 'fluent-boards'), $userData['display_name']),
183 'task_card' => [
184 'title' => $task->title,
185 'rows' => $this->taskRows($task, $board, $boardUrl),
186 ],
187 'cta' => ['label' => __('Open task', 'fluent-boards'), 'url' => $taskUrl],
188 'pre_header' => $this->taskPreHeader($task, $board),
189 ];
190 // translators: %s is the task title
191 $mailSubject = sprintf(__('You’re assigned to “%s”', 'fluent-boards'), $task->title);
192 } else {
193 // this is a subtask; subtasks open inside their parent task
194 $parentTask = $task->parentTask($task->parent_id);
195 $taskUrl = $this->taskUrl($page_url, $board->id, $parentTask);
196
197 $data = [
198 'badge' => ['label' => __('Subtask assigned', 'fluent-boards'), 'tone' => 'accent'],
199 // translators: %s is the name of the person who assigned the subtask
200 'heading' => sprintf(__('%s assigned you to a subtask', 'fluent-boards'), $userData['display_name']),
201 'task_card' => [
202 'title' => $task->title,
203 'parent_title' => $parentTask->title,
204 'parent_url' => $taskUrl,
205 'rows' => $this->taskRows($task, $board, $boardUrl, false),
206 ],
207 'cta' => ['label' => __('Open subtask', 'fluent-boards'), 'url' => $taskUrl],
208 'pre_header' => $this->taskPreHeader($task, $board),
209 ];
210 // translators: %s is the subtask title
211 $mailSubject = sprintf(__('You’re assigned to subtask “%s”', 'fluent-boards'), $task->title);
212 }
213
214 $data = array_merge($this->getBrandingData(), $data, [
215 'email_reason' => __('You’re getting this because you were assigned to this task.', 'fluent-boards'),
216 'show_footer' => true,
217 'user' => $assignee,
218 'userData' => $userData,
219 ]);
220
221 $message = Helper::loadView('emails.assignee2', $data);
222
223 (new Mailer($assignee->user_email, $mailSubject, $message))->sendNotification($context);
224
225 } catch (\Throwable $e) {
226 Mailer::reportFailure($context, $e);
227 }
228 }
229
230 public function sendEmailForRemoveAssignee(
231 $taskId,
232 $newAssigneeId,
233 $current_user_id
234 ) {
235 $context = ['type' => 'remove_assignee', 'task_id' => $taskId];
236 try {
237 $task = Task::findOrFail($taskId);
238 $board = $task->board;
239 $assignee = User::findOrFail($newAssigneeId);
240
241 $userData = $this->getUserData($current_user_id);
242 $page_url = fluent_boards_page_url();
243 $boardUrl = $page_url.'boards/'.$board->id;
244
245 if (null == $task->parent_id) {
246 // this is a task
247 $taskUrl = $this->taskUrl($page_url, $board->id, $task);
248
249 $data = [
250 // translators: %s is the name of the person who removed the assignee
251 'heading' => sprintf(__('%s removed you from a task', 'fluent-boards'), $userData['display_name']),
252 'task_card' => [
253 'title' => $task->title,
254 'rows' => $this->taskRows($task, $board, $boardUrl, true, false),
255 'muted' => true,
256 ],
257 'note' => __('You won’t get updates about this task unless you’re mentioned or assigned again.', 'fluent-boards'),
258 'cta' => ['label' => __('View task', 'fluent-boards'), 'url' => $taskUrl, 'style' => 'secondary'],
259 ];
260 // translators: %s is the task title
261 $mailSubject = sprintf(__('You were removed from “%s”', 'fluent-boards'), $task->title);
262 } else {
263 // this is a subtask; subtasks open inside their parent task
264 $parentTask = $task->parentTask($task->parent_id);
265 $taskUrl = $this->taskUrl($page_url, $board->id, $parentTask);
266
267 $data = [
268 // translators: %s is the name of the person who removed the assignee
269 'heading' => sprintf(__('%s removed you from a subtask', 'fluent-boards'), $userData['display_name']),
270 'task_card' => [
271 'title' => $task->title,
272 'parent_title' => $parentTask->title,
273 'parent_url' => $taskUrl,
274 'rows' => [[__('Board', 'fluent-boards'), $this->link($board->title, $boardUrl)]],
275 'muted' => true,
276 ],
277 'note' => __('You won’t get updates about this subtask unless you’re mentioned or assigned again.', 'fluent-boards'),
278 'cta' => ['label' => __('View subtask', 'fluent-boards'), 'url' => $taskUrl, 'style' => 'secondary'],
279 ];
280 // translators: %s is the subtask title
281 $mailSubject = sprintf(__('You were removed from subtask “%s”', 'fluent-boards'), $task->title);
282 }
283
284 $data = array_merge($this->getBrandingData(), $data, [
285 'badge' => ['label' => __('Unassigned', 'fluent-boards'), 'tone' => 'neutral'],
286 'pre_header' => $this->taskPreHeader($task, $board),
287 'email_reason' => __('You’re getting this because you were assigned to this task.', 'fluent-boards'),
288 'show_footer' => true,
289 'userData' => $userData,
290 ]);
291
292 $message = Helper::loadView('emails.assignee2', $data);
293
294 (new Mailer($assignee->user_email, $mailSubject, $message))->sendNotification($context);
295
296 } catch (\Throwable $e) {
297 Mailer::reportFailure($context, $e);
298 }
299 }
300
301
302 public function sendEmailForChangeStage(
303 $taskId,
304 $newAssigneeEmails,
305 $current_user_id
306 ) {
307 $context = ['type' => 'stage_change', 'task_id' => $taskId];
308 try {
309 $task = Task::findOrFail($taskId);
310 $board = $task->board;
311
312 $userData = $this->getUserData($current_user_id);
313
314 $page_url = fluent_boards_page_url();
315 $taskUrl = $this->taskUrl($page_url, $board->id, $task);
316 $stage = $task->stage ? $task->stage->title : '';
317
318 $data = array_merge($this->getBrandingData(), [
319 'badge' => ['label' => __('Stage changed', 'fluent-boards'), 'tone' => 'accent'],
320 // translators: %s is the name of the person who moved the task
321 'heading' => sprintf(__('%s moved a task', 'fluent-boards'), $userData['display_name']),
322 'context' => $this->contextLine($task->title, $taskUrl, $board->title, $page_url.'boards/'.$board->id),
323 'change' => ['label' => __('Now in', 'fluent-boards'), 'to' => $stage],
324 'cta' => ['label' => __('Open task', 'fluent-boards'), 'url' => $taskUrl],
325 'pre_header' => $this->taskPreHeader($task, $board),
326 'email_reason' => __('You’re getting this because you’re assigned to this task.', 'fluent-boards'),
327 'show_footer' => true,
328 'userData' => $userData,
329 ]);
330
331 // translators: %1$s is the task title, %2$s is the stage title
332 $mailSubject = sprintf(__('“%1$s” moved to %2$s', 'fluent-boards'), $task->title, $stage);
333 $message = Helper::loadView('emails.assignee2', $data);
334
335 foreach ($newAssigneeEmails as $assignee_email) {
336 (new Mailer($assignee_email, $mailSubject, $message))->sendNotification($context);
337 }
338 } catch (\Throwable $e) {
339 Mailer::reportFailure($context, $e);
340 }
341 }
342
343 public function sendEmailForDueDateUpdate(
344 $taskId,
345 $newAssigneeEmails,
346 $current_user_id
347 ) {
348 $context = ['type' => 'due_date_update', 'task_id' => $taskId];
349 try {
350 $task = Task::findOrFail($taskId);
351 $board = $task->board;
352
353 $userData = $this->getUserData($current_user_id);
354
355 $page_url = fluent_boards_page_url();
356 $taskUrl = $this->taskUrl($page_url, $board->id, $task);
357 $dueDate = $task->due_at ? $this->formatDate($task->due_at) : __('No due date', 'fluent-boards');
358
359 $data = array_merge($this->getBrandingData(), [
360 'badge' => ['label' => __('Due date changed', 'fluent-boards'), 'tone' => 'accent'],
361 // translators: %s is the name of the person who changed the due date
362 'heading' => sprintf(__('%s changed a due date', 'fluent-boards'), $userData['display_name']),
363 'context' => $this->contextLine($task->title, $taskUrl, $board->title, $page_url.'boards/'.$board->id),
364 'change' => ['label' => __('Now due', 'fluent-boards'), 'to' => $dueDate],
365 'cta' => ['label' => __('Open task', 'fluent-boards'), 'url' => $taskUrl],
366 'pre_header' => $this->taskPreHeader($task, $board),
367 'email_reason' => __('You’re getting this because you’re assigned to this task.', 'fluent-boards'),
368 'show_footer' => true,
369 'userData' => $userData,
370 ]);
371
372 // translators: %s is the task title
373 $mailSubject = sprintf(__('Due date changed for “%s”', 'fluent-boards'), $task->title);
374 $message = Helper::loadView('emails.assignee2', $data);
375
376 foreach ($newAssigneeEmails as $assignee_email) {
377 (new Mailer($assignee_email, $mailSubject, $message))->sendNotification($context);
378 }
379 } catch (\Throwable $e) {
380 Mailer::reportFailure($context, $e);
381 }
382 }
383
384 public function sendEmailForArchivedTask(
385 $taskId,
386 $newAssigneeEmails,
387 $current_user_id
388 ) {
389 $context = ['type' => 'task_archived', 'task_id' => $taskId];
390 try {
391 $task = Task::findOrFail($taskId);
392 $board = $task->board;
393
394 $userData = $this->getUserData($current_user_id);
395
396 $page_url = fluent_boards_page_url();
397 $boardUrl = $page_url.'boards/'.$board->id;
398 $taskUrl = $this->taskUrl($page_url, $board->id, $task);
399
400 $data = array_merge($this->getBrandingData(), [
401 'badge' => ['label' => __('Archived', 'fluent-boards'), 'tone' => 'neutral'],
402 // translators: %s is the name of the person who archived the task
403 'heading' => sprintf(__('%s archived a task', 'fluent-boards'), $userData['display_name']),
404 'task_card' => [
405 'title' => $task->title,
406 'rows' => $this->taskRows($task, $board, $boardUrl, true, false),
407 'muted' => true,
408 ],
409 'note' => __('Archived tasks can be restored from the board’s archive.', 'fluent-boards'),
410 'cta' => ['label' => __('View task', 'fluent-boards'), 'url' => $taskUrl, 'style' => 'secondary'],
411 'pre_header' => $this->taskPreHeader($task, $board),
412 'email_reason' => __('You’re getting this because you’re assigned to this task.', 'fluent-boards'),
413 'show_footer' => true,
414 'userData' => $userData,
415 ]);
416
417 // translators: %s is the task title
418 $mailSubject = sprintf(__('“%s” was archived', 'fluent-boards'), $task->title);
419 $message = Helper::loadView('emails.assignee2', $data);
420
421 foreach ($newAssigneeEmails as $assignee_email) {
422 (new Mailer($assignee_email, $mailSubject, $message))->sendNotification($context);
423 }
424 } catch (\Throwable $e) {
425 Mailer::reportFailure($context, $e);
426 }
427 }
428
429 /**
430 * Resolve current recipient emails after revalidating board access and preferences.
431 *
432 * Email values are accepted only for queued actions created before recipient IDs
433 * were introduced. They are mapped back to current WordPress users before checks.
434 *
435 * @param array $queuedRecipients
436 * @param int $boardId
437 * @return array
438 */
439 private function getEligibleCommentRecipientEmails($queuedRecipients, $boardId)
440 {
441 $legacyEmails = [];
442 foreach ((array) $queuedRecipients as $queuedRecipient) {
443 if (is_string($queuedRecipient) && !ctype_digit($queuedRecipient)) {
444 $legacyEmail = sanitize_email($queuedRecipient);
445 if ($legacyEmail && is_email($legacyEmail)) {
446 $legacyEmails[strtolower($legacyEmail)] = $legacyEmail;
447 }
448 }
449 }
450
451 $legacyUserIdsByEmail = [];
452 if ($legacyEmails) {
453 $legacyUsers = User::whereIn('user_email', array_values($legacyEmails))
454 ->get(['ID', 'user_email']);
455 foreach ($legacyUsers as $legacyUser) {
456 $legacyUserIdsByEmail[strtolower($legacyUser->user_email)] = absint($legacyUser->ID);
457 }
458 }
459
460 $recipientUserIds = [];
461 $seenRecipientUserIds = [];
462 foreach ((array) $queuedRecipients as $queuedRecipient) {
463 $recipientUserId = 0;
464
465 if (is_int($queuedRecipient) || (is_string($queuedRecipient) && ctype_digit($queuedRecipient))) {
466 $recipientUserId = absint($queuedRecipient);
467 } elseif (is_string($queuedRecipient)) {
468 $legacyEmail = sanitize_email($queuedRecipient);
469 if ($legacyEmail) {
470 $recipientUserId = $legacyUserIdsByEmail[strtolower($legacyEmail)] ?? 0;
471 }
472 }
473
474 if (!$recipientUserId || isset($seenRecipientUserIds[$recipientUserId])) {
475 continue;
476 }
477 $seenRecipientUserIds[$recipientUserId] = true;
478 $recipientUserIds[] = $recipientUserId;
479 }
480
481 if (!$recipientUserIds) {
482 return [];
483 }
484
485 $eligibleRecipientIds = [];
486 $boardRelations = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
487 ->where('object_id', absint($boardId))
488 ->whereIn('foreign_id', $recipientUserIds)
489 ->get(['foreign_id', 'preferences']);
490
491 foreach ($boardRelations as $boardRelation) {
492 $preferences = maybe_unserialize($boardRelation->preferences);
493 if (!is_array($preferences)) {
494 continue;
495 }
496
497 if (
498 !array_key_exists(Constant::BOARD_EMAIL_COMMENT, $preferences) ||
499 $preferences[Constant::BOARD_EMAIL_COMMENT]
500 ) {
501 $eligibleRecipientIds[absint($boardRelation->foreign_id)] = true;
502 }
503 }
504
505 if (!$eligibleRecipientIds) {
506 return [];
507 }
508
509 $usersById = [];
510 $recipients = User::whereIn('ID', array_keys($eligibleRecipientIds))
511 ->get(['ID', 'user_email']);
512 foreach ($recipients as $recipient) {
513 $usersById[absint($recipient->ID)] = $recipient;
514 }
515
516 $recipientEmails = [];
517 foreach ($recipientUserIds as $recipientUserId) {
518 if (!isset($eligibleRecipientIds[$recipientUserId], $usersById[$recipientUserId])) {
519 continue;
520 }
521
522 $email = sanitize_email($usersById[$recipientUserId]->user_email);
523 if ($email && is_email($email)) {
524 $recipientEmails[] = $email;
525 }
526 }
527
528 return $recipientEmails;
529 }
530
531 private function getUserData($userId)
532 {
533 $currentUser = User::findOrFail($userId);
534 $gravaterPhoto = fluent_boards_user_avatar($currentUser->user_email,
535 $currentUser->display_name);
536
537 return [
538 'display_name' => $currentUser->display_name,
539 'photo' => $gravaterPhoto,
540 ];
541 }
542
543 /**
544 * Site branding passed to every email view (header logo or initial, footer name).
545 *
546 * @return array
547 */
548 private function getBrandingData()
549 {
550 return [
551 'site_url' => site_url(),
552 'site_title' => get_bloginfo('name'),
553 'site_logo' => fluent_boards_site_logo(),
554 ];
555 }
556
557 private function taskUrl($pageUrl, $boardId, $task)
558 {
559 return $pageUrl.'boards/'.$boardId.'/tasks/'.$task->id.'-'.substr($task->title, 0, 10);
560 }
561
562 private function link($text, $url)
563 {
564 return '<a class="fbs_email_link" target="_blank" href="'.esc_url($url).'" style="color: #335CFF; font-weight: 600; text-decoration: none;">'
565 .esc_html($text).'</a>';
566 }
567
568 /**
569 * "Task · Board" line under an email headline, both linked.
570 */
571 private function contextLine($taskTitle, $taskUrl, $boardTitle, $boardUrl)
572 {
573 return $this->link($taskTitle, $taskUrl).' &middot; '.$this->link($boardTitle, $boardUrl);
574 }
575
576 /**
577 * Label/value rows for the task card. Values are HTML and already escaped.
578 *
579 * @return array
580 */
581 private function taskRows($task, $board, $boardUrl, $withStage = true, $withDue = true)
582 {
583 $rows = [[__('Board', 'fluent-boards'), $this->link($board->title, $boardUrl)]];
584
585 if ($withStage && $task->stage) {
586 $rows[] = [__('Stage', 'fluent-boards'), esc_html($task->stage->title)];
587 }
588
589 if ($withDue && $task->due_at) {
590 $rows[] = [__('Due', 'fluent-boards'), esc_html($this->formatDate($task->due_at))];
591 }
592
593 return $rows;
594 }
595
596 private function taskPreHeader($task, $board)
597 {
598 $parts = [$board->title];
599 if ($task->due_at) {
600 // translators: %s is the due date
601 $parts[] = sprintf(__('Due %s', 'fluent-boards'), $this->formatDate($task->due_at));
602 }
603
604 return implode(' · ', $parts);
605 }
606
607 /**
608 * Due dates are stored in site time; format them with the site's date and time settings.
609 */
610 private function formatDate($dateTime)
611 {
612 return date_i18n(get_option('date_format').' '.get_option('time_format'), strtotime($dateTime));
613 }
614 }
615