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
← All changes | app/Hooks/Handlers/ActivityHandler.php +97 -50 1.95.3 → 2.2.0 View file →
@@ -78,17 +78,18 @@
78 78 }
79 79
80 80 public function logTaskContentUpdatedActivity($task, $col, $oldTask = null)
81 81 {
82 + // Description autosaves are intentionally excluded to avoid flooding the activity feed.
83 + if ($col === 'description') {
84 + return;
85 + }
86 +
82 87 $action = 'updated';
83 - if($col == 'description'){
84 - $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 );
85 90 }else{
86 - if($task->parent_id){
87 - $this->createLogActivity( $task->parent_id, $action, 'subtask', $oldTask->title, $task->title );
88 - }else{
89 - $this->createLogActivity( $task->id, $action, $col, $oldTask->title, $task->title );
90 - }
91 + $this->createLogActivity( $task->id, $action, $col, $oldTask->title, $task->title );
91 92 }
92 93 }
93 94
94 95 public function taskLabelActivity($task, $label, $action)
@@ -188,9 +189,15 @@
188 189 {
189 190 $taskId = $comment->task_id;
190 191 $newComment = $comment->settings['raw_description'] ?? $comment->description;
191 192
192 - $this->createLogActivity($taskId, 'updated', 'comment', $oldComment, $newComment);
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));
193 200 }
194 201 public function logCommentDeleteActivity($comment)
195 202 {
196 203 $commentDescription = wp_strip_all_tags($comment->settings['raw_description'] ?? $comment->description );
@@ -242,15 +249,21 @@
242 249 }
243 250 }
244 251 }
245 252
246 - public function logTaskStageUpdatedActivity($task, $oldStageId)
253 + public function logTaskStageUpdatedActivity($task, $oldStageId, $stageContext = null)
247 254 {
248 - $oldStage = Stage::findOrFail($oldStageId);
249 - $newStage = Stage::findOrFail($task->stage_id);
250 - if($oldStage && $newStage){
251 - $this->createLogActivity($task->id, 'changed', 'stage', $oldStage->title, $newStage->title);
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);
252 261 }
262 +
263 + if ($newStage) {
264 + $this->createLogActivity($task->id, 'changed', 'stage', $oldStage ? $oldStage->title : null, $newStage->title);
265 + }
253 266 }
254 267
255 268 public function associateUserAddChangeRemoveActivity($oldAssociateId, $newAssociateId, $taskId)
256 269 {
@@ -358,8 +371,9 @@
358 371 $this->createLogActivity($task->id, 'archived', 'task', $task->title);
359 372 }
360 373 }
361 374
375 + /** Record both recurrence links, retaining the full generated title in the source entry's settings. */
362 376 public function logRepeatTaskCreatedActivity($newTask, $parentTask)
363 377 {
364 378 $this->createLogActivity(
365 379 $newTask->id,
@@ -369,8 +383,23 @@
369 383 $parentTask->title,
370 384 null,
371 385 $parentTask
372 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 + );
373 402 }
374 403
375 404 public function logRepeatTaskSet($newTask)
376 405 {
@@ -397,62 +426,80 @@
397 426 {
398 427 $this->createLogActivity($task->id, 'moved', 'board', $oldBoard->title, $newBoard->title);
399 428 }
400 429
430 + /** Log meaningful task value additions, changes and removals, preserving zero and false. */
401 431 public function logCustomFieldActivity($taskId, $customField, $oldValue, $newValue, $isNewCustomField)
402 432 {
403 - // Format values for display in activity
404 - $displayNewValue = $this->formatValueForDisplay($newValue, $customField->settings['custom_field_type']);
405 -
406 - $action = 'changed';
407 - $column = $customField->title;
408 -
409 - // Ensure we have a value to display
410 - if (empty($displayNewValue)) {
411 - $displayNewValue = 'empty';
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;
412 439 }
413 440
414 - // Handle old value display logic
415 - $displayOldValue = null;
416 - if (!$isNewCustomField && $oldValue !== null && $oldValue !== '') {
417 - // Format old value for display if it exists
418 - $displayOldValue = $this->formatValueForDisplay($oldValue, $customField->settings['custom_field_type']);
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;
419 446 }
420 447
421 448 $settings = [
422 449 'custom_field_id' => $customField->id,
423 450 'custom_field_title' => $customField->title,
424 - 'custom_field_type' => $customField->settings['custom_field_type']
451 + 'custom_field_type' => $fieldType
425 452 ];
426 453
427 - $this->createLogActivity($taskId, $action, $column, $displayOldValue, $displayNewValue, null, $settings);
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 + );
428 464 }
429 465
430 - /**
431 - * Format custom field value for display in activity logs
432 - */
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. */
433 487 private function formatValueForDisplay($value, $fieldType)
434 488 {
435 - if ($value === null || $value === '') {
436 - return 'empty';
489 + if ($value === null) {
490 + return null;
437 491 }
438 -
439 - switch ($fieldType) {
440 - case 'checkbox':
441 - return $value ? 'checked' : 'unchecked';
442 - case 'date':
443 - // If it's already formatted as Y-m-d H:i:s, convert to readable format
444 - if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value)) {
445 - return gmdate('M j, Y g:i A', strtotime($value));
446 - }
447 - return $value;
448 - case 'select':
449 - case 'text':
450 - case 'textarea':
451 - case 'number':
452 - default:
453 - return (string) $value;
492 + if ($fieldType === 'checkbox') {
493 + return $value ? __('checked', 'fluent-boards') : __('unchecked', 'fluent-boards');
454 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;
455 502 }
456 503 public function taskCloned($originalTask, $clonedTask)
457 504 {
458 505 $this->createLogActivity(