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
← All changes | app/Hooks/Handlers/ActivityHandler.php +253 -64 1.212.1.0 View file →
@@ -3,8 +3,9 @@
3 3 namespace FluentBoards\App\Hooks\Handlers;
4 4
5 5 use FluentBoards\App\Models\Board;
6 6 use FluentBoards\App\Models\Stage;
7 +use FluentBoards\App\Models\Task;
7 8 use FluentBoards\App\Models\User;
8 9 use FluentBoards\App\Services\Constant;
9 10 use FluentCrm\App\Models\Subscriber;
10 11 use FluentBoards\App\Models\Comment;
@@ -23,8 +24,13 @@
23 24 'new_value' => $newValue,
24 25 'description' => $description,
25 26 'settings' => $settings
26 27 ];
28 + $userId = get_current_user_id();
29 + if($userId == 0){
30 + $task = Task::find($taskId);
31 + $data['created_by'] = $task->created_by;
32 + }
27 33
28 34 Helper::createActivity($data);
29 35 }
30 36 public function logMoveTaskToAnotherBoardActivity($task, $oldTask)
@@ -33,16 +39,40 @@
33 39 $new = $this->getBoardTitleById($task->board_id);
34 40 $this->createLogActivity( $task->id,'changed', 'board', $old, $new);
35 41 }
36 42
37 - public function logAssigneeChangeActivity($task, $newAssigneeId, $operation)
43 + public function logAssigneeAddedActivity($task, $newAssigneeId)
38 44 {
45 + $currentUserId = get_current_user_id();
46 + if($newAssigneeId == $currentUserId) {
47 + $this->taskAssigneeJoin($task->id);
48 + return;
49 + }
39 50 $user = User::findOrFail($newAssigneeId);
40 - if($user) {
41 - $this->createLogActivity($task->id, $operation, 'assignee', null, $user->display_name);
51 + $currentUserId = get_current_user_id();
52 + if($currentUserId === $user->ID){
53 + $this->taskAssigneeJoin($task->id);
54 + } elseif ($user) {
55 + $this->createLogActivity($task->id, 'added', 'assignee', null, $user->display_name);
42 56 }
43 57 }
44 58
59 + public function logAssigneeRemovedActivity($task, $newAssigneeId)
60 + {
61 + $currentUserId = get_current_user_id();
62 + if($newAssigneeId == $currentUserId) {
63 + $this->taskAssigneeLeave($task->id);
64 + return;
65 + }
66 + $user = User::findOrFail($newAssigneeId);
67 + $currentUserId = get_current_user_id();
68 + if($currentUserId === $user->ID){
69 + $this->taskAssigneeLeave($task->id);
70 + } else if($user) {
71 + $this->createLogActivity($task->id, 'removed', 'assignee', null, $user->display_name);
72 + }
73 + }
74 +
45 75 public function logTaskCreationActivity($task)
46 76 {
47 77 $this->createLogActivity( $task->id,'created', 'task', null, $task->title, null );
48 78 }
@@ -48,17 +78,18 @@
48 78 }
49 79
50 80 public function logTaskContentUpdatedActivity($task, $col, $oldTask = null)
51 81 {
82 + // Description autosaves are intentionally excluded to avoid flooding the activity feed.
83 + if ($col === 'description') {
84 + return;
85 + }
86 +
52 87 $action = 'updated';
53 - if($col == 'description'){
54 - $this->createLogActivity( $task->id, $action, $col, $oldTask->description, $task->description );
88 + if($task->parent_id){
89 + $this->createLogActivity( $task->parent_id, $action, 'subtask', $oldTask->title, $task->title );
55 90 }else{
56 - if($task->parent_id){
57 - $this->createLogActivity( $task->parent_id, $action, 'subtask', $oldTask->title, $task->title );
58 - }else{
59 - $this->createLogActivity( $task->id, $action, $col, $oldTask->title, $task->title );
60 - }
91 + $this->createLogActivity( $task->id, $action, $col, $oldTask->title, $task->title );
61 92 }
62 93 }
63 94
64 95 public function taskLabelActivity($task, $label, $action)
@@ -72,61 +103,71 @@
72 103
73 104 $this->createLogActivity($task->id, $action, $column, null, null, null, $settings);
74 105 }
75 106
76 - public function logDueDateActivity($task, $oldDate, $dateType)
107 + public function logDueDateActivity($task, $oldDate)
77 108 {
78 - $column = '';
109 + $column = 'Due Date';
79 110 $action = 'changed';
80 - if($dateType == 'Due Date'){
81 - if($oldDate){
82 - $new = gmdate("F j, Y, g:i a", strtotime($task->due_at));
83 - $old = gmdate("F j, Y, g:i a", strtotime($oldDate));
84 - if($new == $old)
85 - return;
111 +
112 + if($oldDate){
113 + $new = gmdate("F j, Y, g:i a", strtotime($task->due_at));
114 + $old = gmdate("F j, Y, g:i a", strtotime($oldDate));
115 + if($new == $old)
116 + return;
117 + }
118 + $newDueDate = $task->due_at;
119 + $new = null;
120 + if ($newDueDate) {
121 + $new = gmdate("F j, Y, g:i a", strtotime($newDueDate));
122 + if(str_contains($new, '12:00 am')){
123 + $new = gmdate("F j, Y", strtotime($newDueDate));
86 124 }
87 - $column = 'Due Date';
88 - $newDueDate = $task->due_at;
89 - $new = null;
90 - if ($newDueDate) {
91 - $new = gmdate("F j, Y, g:i a", strtotime($newDueDate));
92 - if(str_contains($new, '12:00 am')){
93 - $new = gmdate("F j, Y", strtotime($newDueDate));
94 - }
95 - }
96 - $old = null;
97 - if(!$oldDate){
98 - $action = 'added';
99 - }else{
100 - $old = gmdate("F j, Y, g:i a", strtotime($oldDate));
101 - if(str_contains($old, '12:00 am')){
102 - $old = gmdate("F j, Y", strtotime($oldDate));
103 - }
104 - }
105 - }else {
106 - if($oldDate){
107 - $new = gmdate("F j, Y", strtotime($task->started_at));
125 + }
126 + $old = null;
127 + if(!$oldDate){
128 + $action = 'added';
129 + }else{
130 + $old = gmdate("F j, Y, g:i a", strtotime($oldDate));
131 + if(str_contains($old, '12:00 am')){
108 132 $old = gmdate("F j, Y", strtotime($oldDate));
109 - if($new == $old)
110 - return;
111 133 }
112 - $column = 'Start Date';
113 - $newStartDate = $task->started_at;
114 - $new = null;
115 - if ($newStartDate) {
116 - $new = gmdate("F j, Y", strtotime($newStartDate));
117 - }
118 - $old = null;
119 - if(!$oldDate){
120 - $action = 'added';
121 - }else{
122 - $old = gmdate("F j, Y", strtotime($oldDate));
123 - }
124 134 }
125 135
126 136 $this->createLogActivity($task->id, $action, $column, $old, $new);
127 137 }
138 + public function logDueDateRemoveActivity($task)
139 + {
140 + $this->createLogActivity( $task->id, 'removed', 'Due Date', null);
141 + }
128 142
143 + public function logStartDateActivity($task, $oldDate)
144 + {
145 + $column = 'Start Date';
146 + $action = 'changed';
147 +
148 + if($oldDate){
149 + $new = gmdate("F j, Y", strtotime($task->started_at));
150 + $old = gmdate("F j, Y", strtotime($oldDate));
151 + if($new == $old)
152 + return;
153 + }
154 + $newStartDate = $task->started_at;
155 + $new = null;
156 + if ($newStartDate) {
157 + $new = gmdate("F j, Y", strtotime($newStartDate));
158 + }
159 + $old = null;
160 + if(!$oldDate){
161 + $action = 'added';
162 + }else{
163 + $old = gmdate("F j, Y", strtotime($oldDate));
164 + }
165 +
166 + $this->createLogActivity($task->id, $action, $column, $old, $new);
167 + }
168 +
169 +
129 170 public function logPriorityChangeActivity($task, $old)
130 171 {
131 172 $new = $task->priority;
132 173 $this->createLogActivity($task->id, 'changed', 'priority', $old, $new);
@@ -139,19 +180,31 @@
139 180 $parent = Comment::findOrFail($comment->parent_id);
140 181 $parentDescription = $parent->description;
141 182 $this->createLogActivity($comment->task_id, 'added', 'a reply');
142 183 }else{
143 - $commentPlainText = strip_tags($comment->description);
184 + $commentPlainText = wp_strip_all_tags($comment->description);
144 185 $this->createLogActivity($comment->task_id, 'added', 'comment', null, $commentPlainText, null);
145 186 }
146 187 }
147 - public function logCommentUpdateActivity($id, $oldComment, $newComment)
188 + public function logCommentUpdateActivity($comment, $oldComment)
148 189 {
149 - $this->createLogActivity($id, 'updated', 'comment', $oldComment, $newComment);
190 + $taskId = $comment->task_id;
191 + $newComment = $comment->settings['raw_description'] ?? $comment->description;
192 +
193 + // Activity previews are plain text, while comment drafts now contain inline HTML.
194 + $plainText = static function ($content) {
195 + $content = preg_replace('~<br\s*/?>|</p\s*>~i', "\n", $content);
196 + return trim(html_entity_decode(wp_strip_all_tags($content), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
197 + };
198 +
199 + $this->createLogActivity($taskId, 'updated', 'comment', $plainText($oldComment), $plainText($newComment));
150 200 }
151 - public function logCommentDeleteActivity($id, $commentDescription)
201 + public function logCommentDeleteActivity($comment)
152 202 {
153 - $this->createLogActivity($id, 'removed', 'comment', null, $commentDescription, null);
203 + $commentDescription = wp_strip_all_tags($comment->settings['raw_description'] ?? $comment->description );
204 + $taskId = $comment->task_id;
205 +
206 + $this->createLogActivity($taskId, 'removed', 'comment', null, $commentDescription, null);
154 207 }
155 208
156 209 public function logSubtaskAddedActivity($parentTask, $subTask)
157 210 {
@@ -157,20 +210,37 @@
157 210 {
158 211 $this->createLogActivity($parentTask->id, 'added', 'subtask', null, $subTask->title);
159 212 }
160 213
214 + public function logSubtaskCloneActivity($parentTask, $subTask)
215 + {
216 + $this->createLogActivity($parentTask->id, 'cloned', 'subtask', null, $subTask->title);
217 + }
218 + public function logSubtaskGroupAddedActivity($task_id, $subTaskGroup)
219 + {
220 + $this->createLogActivity($task_id, 'added', 'subtask group', null, $subTaskGroup->value);
221 + }
222 +
161 223 public function logSubtaskDeletedActivity($id, $subTaskTitle)
162 224 {
163 225 $this->createLogActivity($id, 'removed', 'subtask', null, $subTaskTitle);
164 226 }
165 227
228 + public function logSubtaskGroupDeletedActivity($task_id, $subTaskGroup)
229 + {
230 + $this->createLogActivity($task_id, 'removed', 'subtask group', null, $subTaskGroup->value);
231 + }
232 + public function logSubtaskGroupTitleUpdatedActivity($oldTitle, $group)
233 + {
234 + $this->createLogActivity($group->task_id, 'changed', 'subtask group title', $oldTitle, $group->value);
235 + }
166 236 public function logTaskCompletedOrReopenActivity($task, $status)
167 237 {
168 - if($status == 'completed'){
238 + if($status == 'closed'){
169 239 if($task->parent_id){
170 - $this->createLogActivity($task->parent_id, 'completed', 'subtask', $task->title);
240 + $this->createLogActivity($task->parent_id, 'closed', 'subtask', $task->title);
171 241 }else{
172 - $this->createLogActivity($task->id, 'completed', 'task', $task->title);
242 + $this->createLogActivity($task->id, 'closed', 'task', $task->title);
173 243 }
174 244 }else{
175 245 if($task->parent_id){
176 246 $this->createLogActivity($task->parent_id, 'reopened', 'subtask', $task->title);
@@ -179,12 +249,18 @@
179 249 }
180 250 }
181 251 }
182 252
183 - public function logTaskStageUpdatedActivity($task, $oldStageId)
253 + public function logTaskStageUpdatedActivity($task, $oldStageId, $stageContext = null)
184 254 {
185 - $oldStage = Stage::findOrFail($oldStageId);
186 - $newStage = Stage::findOrFail($task->stage_id);
255 + $oldStage = $stageContext['source'] ?? null;
256 + $newStage = $stageContext['target'] ?? null;
257 +
258 + if (!$oldStage instanceof Stage || !$newStage instanceof Stage) {
259 + $oldStage = Stage::findOrFail($oldStageId);
260 + $newStage = Stage::findOrFail($task->stage_id);
261 + }
262 +
187 263 if($oldStage && $newStage){
188 264 $this->createLogActivity($task->id, 'changed', 'stage', $oldStage->title, $newStage->title);
189 265 }
190 266 }
@@ -284,6 +360,119 @@
284 360 }
285 361 public function taskAttachmentDeleted($attachment)
286 362 {
287 363 $this->createLogActivity($attachment->object_id , 'deleted', 'attachment', null, strlen($attachment->title) > 0 ? $attachment->title : $attachment->full_url);
364 + }
365 +
366 + public function taskArchived($task)
367 + {
368 + if(!$task->archived_at){
369 + $this->createLogActivity($task->id, 'restored', 'task', $task->title);
370 + }else{
371 + $this->createLogActivity($task->id, 'archived', 'task', $task->title);
372 + }
373 + }
374 +
375 + public function logRepeatTaskCreatedActivity($newTask, $parentTask)
376 + {
377 + $this->createLogActivity(
378 + $newTask->id,
379 + 'created',
380 + 'repeat task',
381 + $newTask->title,
382 + $parentTask->title,
383 + null,
384 + $parentTask
385 + );
386 + }
387 +
388 + public function logRepeatTaskSet($newTask)
389 + {
390 + $this->createLogActivity(
391 + $newTask->id,
392 + 'set',
393 + 'Repeat Task',
394 + null,
395 + null,
396 + );
397 + }
398 +
399 + public function logRepeatTaskUpdated($newTask)
400 + {
401 + $this->createLogActivity(
402 + $newTask->id,
403 + 'updated',
404 + 'Repeat Task',
405 + null,
406 + null
407 + );
408 + }
409 + public function taskMovedFromBoard($task, $oldBoard, $newBoard)
410 + {
411 + $this->createLogActivity($task->id, 'moved', 'board', $oldBoard->title, $newBoard->title);
412 + }
413 +
414 + public function logCustomFieldActivity($taskId, $customField, $oldValue, $newValue, $isNewCustomField)
415 + {
416 + // Format values for display in activity
417 + $displayNewValue = $this->formatValueForDisplay($newValue, $customField->settings['custom_field_type']);
418 +
419 + $action = 'changed';
420 + $column = $customField->title;
421 +
422 + // Ensure we have a value to display
423 + if (empty($displayNewValue)) {
424 + $displayNewValue = 'empty';
425 + }
426 +
427 + // Handle old value display logic
428 + $displayOldValue = null;
429 + if (!$isNewCustomField && $oldValue !== null && $oldValue !== '') {
430 + // Format old value for display if it exists
431 + $displayOldValue = $this->formatValueForDisplay($oldValue, $customField->settings['custom_field_type']);
432 + }
433 +
434 + $settings = [
435 + 'custom_field_id' => $customField->id,
436 + 'custom_field_title' => $customField->title,
437 + 'custom_field_type' => $customField->settings['custom_field_type']
438 + ];
439 +
440 + $this->createLogActivity($taskId, $action, $column, $displayOldValue, $displayNewValue, null, $settings);
441 + }
442 +
443 + /**
444 + * Format custom field value for display in activity logs
445 + */
446 + private function formatValueForDisplay($value, $fieldType)
447 + {
448 + if ($value === null || $value === '') {
449 + return 'empty';
450 + }
451 +
452 + switch ($fieldType) {
453 + case 'checkbox':
454 + return $value ? 'checked' : 'unchecked';
455 + case 'date':
456 + // If it's already formatted as Y-m-d H:i:s, convert to readable format
457 + if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value)) {
458 + return gmdate('M j, Y g:i A', strtotime($value));
459 + }
460 + return $value;
461 + case 'select':
462 + case 'text':
463 + case 'textarea':
464 + case 'number':
465 + default:
466 + return (string) $value;
467 + }
468 + }
469 + public function taskCloned($originalTask, $clonedTask)
470 + {
471 + $this->createLogActivity(
472 + $clonedTask->id,
473 + 'cloned',
474 + 'task',
475 + 'from ' . $originalTask->title,
476 + );
288 477 }
289 478 }