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 / ActivityHandler.php

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

513 lines 17.6 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\Stage;
7 use FluentBoards\App\Models\Task;
8 use FluentBoards\App\Models\User;
9 use FluentBoards\App\Services\Constant;
10 use FluentCrm\App\Models\Subscriber;
11 use FluentBoards\App\Models\Comment;
12 use FluentBoards\App\Services\Helper;
13
14 class ActivityHandler
15 {
16 public function createLogActivity($taskId, $action, $column, $oldValue = null, $newValue = null, $description = null, $settings = null )
17 {
18 $data = [
19 'object_type' => Constant::ACTIVITY_TASK,
20 'object_id' => $taskId,
21 'action' => $action, // action type: changed, updated, added, removed
22 'column' => $column,
23 'old_value' => $oldValue,
24 'new_value' => $newValue,
25 'description' => $description,
26 'settings' => $settings
27 ];
28 $userId = get_current_user_id();
29 if($userId == 0){
30 $task = Task::find($taskId);
31 $data['created_by'] = $task->created_by;
32 }
33
34 Helper::createActivity($data);
35 }
36 public function logMoveTaskToAnotherBoardActivity($task, $oldTask)
37 {
38 $old = $this->getBoardTitleById($oldTask->board_id);
39 $new = $this->getBoardTitleById($task->board_id);
40 $this->createLogActivity( $task->id,'changed', 'board', $old, $new);
41 }
42
43 public function logAssigneeAddedActivity($task, $newAssigneeId)
44 {
45 $currentUserId = get_current_user_id();
46 if($newAssigneeId == $currentUserId) {
47 $this->taskAssigneeJoin($task->id);
48 return;
49 }
50 $user = User::findOrFail($newAssigneeId);
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);
56 }
57 }
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
75 public function logTaskCreationActivity($task)
76 {
77 $this->createLogActivity( $task->id,'created', 'task', null, $task->title, null );
78 }
79
80 public function logTaskContentUpdatedActivity($task, $col, $oldTask = null)
81 {
82 // Description autosaves are intentionally excluded to avoid flooding the activity feed.
83 if ($col === 'description') {
84 return;
85 }
86
87 $action = 'updated';
88 if($task->parent_id){
89 $this->createLogActivity( $task->parent_id, $action, 'subtask', $oldTask->title, $task->title );
90 }else{
91 $this->createLogActivity( $task->id, $action, $col, $oldTask->title, $task->title );
92 }
93 }
94
95 public function taskLabelActivity($task, $label, $action)
96 {
97 $column = 'label';
98 $settings = [
99 'bg_color' => $label->bg_color,
100 'color' => $label->color,
101 'title' => $label->title,
102 ];
103
104 $this->createLogActivity($task->id, $action, $column, null, null, null, $settings);
105 }
106
107 public function logDueDateActivity($task, $oldDate)
108 {
109 $column = 'Due Date';
110 $action = 'changed';
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));
124 }
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')){
132 $old = gmdate("F j, Y", strtotime($oldDate));
133 }
134 }
135
136 $this->createLogActivity($task->id, $action, $column, $old, $new);
137 }
138 public function logDueDateRemoveActivity($task)
139 {
140 $this->createLogActivity( $task->id, 'removed', 'Due Date', null);
141 }
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
170 public function logPriorityChangeActivity($task, $old)
171 {
172 $new = $task->priority;
173 $this->createLogActivity($task->id, 'changed', 'priority', $old, $new);
174 }
175
176 public function logCommentCreateActivity($comment)
177 {
178 if($comment->parent_id) {
179 // dd($comment);
180 $parent = Comment::findOrFail($comment->parent_id);
181 $parentDescription = $parent->description;
182 $this->createLogActivity($comment->task_id, 'added', 'a reply');
183 }else{
184 $commentPlainText = wp_strip_all_tags($comment->description);
185 $this->createLogActivity($comment->task_id, 'added', 'comment', null, $commentPlainText, null);
186 }
187 }
188 public function logCommentUpdateActivity($comment, $oldComment)
189 {
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));
200 }
201 public function logCommentDeleteActivity($comment)
202 {
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);
207 }
208
209 public function logSubtaskAddedActivity($parentTask, $subTask)
210 {
211 $this->createLogActivity($parentTask->id, 'added', 'subtask', null, $subTask->title);
212 }
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
223 public function logSubtaskDeletedActivity($id, $subTaskTitle)
224 {
225 $this->createLogActivity($id, 'removed', 'subtask', null, $subTaskTitle);
226 }
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 }
236 public function logTaskCompletedOrReopenActivity($task, $status)
237 {
238 if($status == 'closed'){
239 if($task->parent_id){
240 $this->createLogActivity($task->parent_id, 'closed', 'subtask', $task->title);
241 }else{
242 $this->createLogActivity($task->id, 'closed', 'task', $task->title);
243 }
244 }else{
245 if($task->parent_id){
246 $this->createLogActivity($task->parent_id, 'reopened', 'subtask', $task->title);
247 }else{
248 $this->createLogActivity($task->id, 'reopened', 'task', $task->title);
249 }
250 }
251 }
252
253 public function logTaskStageUpdatedActivity($task, $oldStageId, $stageContext = null)
254 {
255 $oldStage = $stageContext['source'] ?? null;
256 $newStage = $stageContext['target'] ?? null;
257
258 if (!$oldStage instanceof Stage || !$newStage instanceof Stage) {
259 $oldStage = Stage::find($oldStageId);
260 $newStage = Stage::findOrFail($task->stage_id);
261 }
262
263 if ($newStage) {
264 $this->createLogActivity($task->id, 'changed', 'stage', $oldStage ? $oldStage->title : null, $newStage->title);
265 }
266 }
267
268 public function associateUserAddChangeRemoveActivity($oldAssociateId, $newAssociateId, $taskId)
269 {
270 $subsciber = new Subscriber();
271 $oldAssociateUser = $subsciber->find($oldAssociateId);
272 $newAssociateUser = $subsciber->find($newAssociateId);
273 $action = 'added';
274 $column = 'the associate email';
275 $old = null; $new = null;
276
277 if ($oldAssociateUser && $newAssociateUser) {
278 $old = $oldAssociateUser->email;
279 $new = $newAssociateUser->email;
280 $action = 'changed';
281 } elseif ($oldAssociateUser && !$newAssociateUser) {
282 $old = $oldAssociateUser->email;
283 $action = 'removed';
284 } else {
285 $new = $newAssociateUser->email;
286 }
287 $this->createLogActivity($taskId, $action, $column, $old, $new);
288 }
289
290 public function taskAssigneeJoin($id)
291 {
292 $this->createLogActivity($id, 'joined', 'task', null, null);
293 }
294
295 public function taskAssigneeLeave($id)
296 {
297 $this->createLogActivity($id, 'left', 'task', null, null);
298 }
299
300 public function labelManageForTaskActivity($id, $action)
301 {
302 $this->createLogActivity($id, $action, 'label', null, null);
303 }
304
305 private function getBoardTitleById($boardId)
306 {
307 return Board::findOrFail($boardId)->title; // it can be further optimized , we may limit multiple query here.
308 }
309
310 public function taskAddedFromFluentForms($task)
311 {
312 $taskActivity = [
313 'object_type' => Constant::ACTIVITY_TASK,
314 'object_id' => $task->id,
315 'action' => 'created', // action type: changed, updated, added, removed
316 'column' => 'task',
317 'old_value' => $task->title,
318 'new_value' => null,
319 'description' => " from Fluent Forms",
320 ];
321
322 $board = Board::find($task->board_id);
323 $settings = $board->settings ?? [];
324
325 if (isset($settings['tasks_count'])) {
326 if ($settings['tasks_count'] != 0) $settings['tasks_count'] += 1;
327 } else {
328 $settings['tasks_count'] = 1;
329 }
330 $board->settings = $settings;
331 $board->save();
332 $taskStage = $task->stage;
333 $settings = ['task_id' => $task->id];
334
335 $boardActivity = [
336 'object_type' => Constant::ACTIVITY_BOARD,
337 'object_id' => $task->board_id,
338 'action' => 'created', // action type: changed, updated, added, removed, created
339 'column' => 'task',
340 'old_value' => null,
341 'new_value' => $task->title,
342 'description' => 'on stage '. $taskStage->title . ' from Fluent Forms ',
343 'settings' => $settings
344 ];
345 if (intval($task->created_by) > 0) {
346 $taskActivity['created_by'] = intval($task->created_by);
347 $boardActivity['created_by'] = intval($task->created_by);
348
349 } else {
350 $taskActivity['description'] .= ' by '. $task['settings']['author']['name'] . " (" . $task['settings']['author']['email'] . ")";
351 $boardActivity['description'] .= ' by '. $task['settings']['author']['name'] . " (" . $task['settings']['author']['email'] . ")";
352 }
353 Helper::createActivity($taskActivity);
354 Helper::createActivity($boardActivity);
355
356 }
357 public function taskAttachmentAdded($attachment)
358 {
359 $this->createLogActivity($attachment->object_id , 'added', 'attachment', null, strlen($attachment->title) > 0 ? $attachment->title : $attachment->full_url);
360 }
361 public function taskAttachmentDeleted($attachment)
362 {
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 /** Record both recurrence links, retaining the full generated title in the source entry's settings. */
376 public function logRepeatTaskCreatedActivity($newTask, $parentTask)
377 {
378 $this->createLogActivity(
379 $newTask->id,
380 'created',
381 'repeat task',
382 $newTask->title,
383 $parentTask->title,
384 null,
385 $parentTask
386 );
387
388 // new_value is VARCHAR(50); keep a Unicode-safe preview and render settings.title.
389 $this->createLogActivity(
390 $parentTask->id,
391 'created',
392 'repeat task',
393 null,
394 mb_substr($newTask->title, 0, 50, 'UTF-8'),
395 null,
396 [
397 'id' => $newTask->id,
398 'board_id' => $newTask->board_id,
399 'title' => $newTask->title,
400 ]
401 );
402 }
403
404 public function logRepeatTaskSet($newTask)
405 {
406 $this->createLogActivity(
407 $newTask->id,
408 'set',
409 'Repeat Task',
410 null,
411 null,
412 );
413 }
414
415 public function logRepeatTaskUpdated($newTask)
416 {
417 $this->createLogActivity(
418 $newTask->id,
419 'updated',
420 'Repeat Task',
421 null,
422 null
423 );
424 }
425 public function taskMovedFromBoard($task, $oldBoard, $newBoard)
426 {
427 $this->createLogActivity($task->id, 'moved', 'board', $oldBoard->title, $newBoard->title);
428 }
429
430 /** Log meaningful task value additions, changes and removals, preserving zero and false. */
431 public function logCustomFieldActivity($taskId, $customField, $oldValue, $newValue, $isNewCustomField)
432 {
433 $fieldType = $customField->settings['custom_field_type'];
434 $oldValue = $this->normalizeCustomFieldActivityValue($isNewCustomField ? null : $oldValue, $fieldType);
435 $newValue = $this->normalizeCustomFieldActivityValue($newValue, $fieldType);
436
437 if ($oldValue === $newValue) {
438 return;
439 }
440
441 $action = Constant::ACTIVITY_ACTION_CHANGED;
442 if ($oldValue === null) {
443 $action = Constant::ACTIVITY_ACTION_ADDED;
444 } elseif ($newValue === null) {
445 $action = Constant::ACTIVITY_ACTION_REMOVED;
446 }
447
448 $settings = [
449 'custom_field_id' => $customField->id,
450 'custom_field_title' => $customField->title,
451 'custom_field_type' => $fieldType
452 ];
453
454 $this->createLogActivity(
455 $taskId,
456 $action,
457 /* translators: %s is the custom field title */
458 sprintf(__('custom field %s', 'fluent-boards'), $customField->title),
459 $this->formatValueForDisplay($oldValue, $fieldType),
460 $this->formatValueForDisplay($newValue, $fieldType),
461 null,
462 $settings
463 );
464 }
465
466 /** Compare logical values independently of transport types and selection order. */
467 private function normalizeCustomFieldActivityValue($value, $fieldType)
468 {
469 if ($value === null || $value === '' || $value === []) {
470 return null;
471 }
472 if ($fieldType === 'checkbox') {
473 return rest_sanitize_boolean($value);
474 }
475 if ($fieldType === 'multi-select') {
476 $value = array_values(array_unique(array_map('strval', (array) $value)));
477 sort($value, SORT_STRING);
478 return $value;
479 }
480 if ($fieldType === 'number' && is_numeric($value)) {
481 return (string) (0 + $value);
482 }
483 return (string) $value;
484 }
485
486 /** Format custom-field values without treating zero or an unchecked box as empty. */
487 private function formatValueForDisplay($value, $fieldType)
488 {
489 if ($value === null) {
490 return null;
491 }
492 if ($fieldType === 'checkbox') {
493 return $value ? __('checked', 'fluent-boards') : __('unchecked', 'fluent-boards');
494 }
495 if ($fieldType === 'multi-select') {
496 return implode(', ', $value);
497 }
498 if ($fieldType === 'date' && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value)) {
499 return mysql2date('M j, Y g:i A', $value);
500 }
501 return (string) $value;
502 }
503 public function taskCloned($originalTask, $clonedTask)
504 {
505 $this->createLogActivity(
506 $clonedTask->id,
507 'cloned',
508 'task',
509 'from ' . $originalTask->title,
510 );
511 }
512 }
513