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/Models/Task.php +61 -3 1.95.2 → 2.2.0 View file →
@@ -6,14 +6,15 @@
6 6 use FluentBoards\App\Services\Helper;
7 7 use FluentBoards\App\Services\TaskService;
8 8 use FluentBoards\Framework\Database\Orm\Builder;
9 9 use FluentBoardsPro\App\Models\TaskAttachment;
10 -use FluentBoardsPro\App\Models\CustomField;
10 +use FluentBoards\App\Models\CustomField;
11 11 use FluentBoardsPro\App\Services\Constant as ProConstant;
12 12 use FluentCrm\App\Models\Subscriber;
13 13
14 14 class Task extends Model
15 15 {
16 +
16 17 protected $table = 'fbs_tasks';
17 18
18 19 protected $guarded = ['id'];
19 20
@@ -99,8 +100,18 @@
99 100 self::adjustSubtaskCount($model->parent_id);
100 101 }
101 102 });
102 103
104 + static::updated(function ($model) {
105 + if ($model->isDirty('status') && $model->getOriginal('status') === 'open' && $model->status === 'closed') {
106 + // Use persisted attributes without stale stage/board relations from the move.
107 + $closedTask = $model->fresh();
108 + if ($closedTask && $closedTask->status === 'closed') {
109 + do_action('fluent_boards/task_closed', $closedTask);
110 + }
111 + }
112 + });
113 +
103 114 /* global scope for task type which means only type = task will be fetched from everywhere in */
104 115 static::addGlobalScope('type', function (Builder $builder) {
105 116 $builder->where('type', '=', 'task')
106 117 ->orWhere('type', '=', 'roadmap');
@@ -222,8 +233,30 @@
222 233 {
223 234 return $this->belongsTo(Board::class, 'board_id', 'id');
224 235 }
225 236
237 + /**
238 + * Exclude tasks that belong to template boards.
239 + */
240 + public function scopeExcludeTemplateBoards($query)
241 + {
242 + return $query->whereHas('board', function ($boardQuery) {
243 + $boardQuery->excludeTemplates();
244 + });
245 + }
246 +
247 + /**
248 + * Limit tasks to active, non-template boards available in this install.
249 + */
250 + public function scopeOnActiveAvailableBoards($query)
251 + {
252 + return $query->whereHas('board', function ($boardQuery) {
253 + $boardQuery->whereNull('archived_at')
254 + ->excludeTemplates()
255 + ->availableInCurrentInstall();
256 + });
257 + }
258 +
226 259 public function assignees()
227 260 {
228 261 return $this->belongsToMany(
229 262 User::class,
@@ -250,8 +283,13 @@
250 283 }
251 284
252 285 public function attachments() //may not need in future
253 286 {
287 + if (!defined('FLUENT_BOARDS_PRO_VERSION')) {
288 + return $this->hasMany(Attachment::class, 'id', 'id')
289 + ->where('id', 0);
290 + }
291 +
254 292 return $this->hasMany(TaskAttachment::class,
255 293 'object_id', 'id')
256 294 ->where('object_type', Constant::OBJECT_TYPE_TASK);
257 295 }
@@ -329,10 +367,15 @@
329 367 return 0;
330 368 }
331 369 }
332 370
371 + /** Use batch-loaded metadata when preparing task-list responses. */
333 372 public function getMetaAttribute()
334 373 {
374 + if ($this->relationLoaded('taskMeta')) {
375 + return $this->getRelation('taskMeta')->pluck('value', 'key');
376 + }
377 +
335 378 return $this->taskMeta()->get()->pluck('value', 'key');
336 379 }
337 380
338 381 public function stage()
@@ -369,8 +412,14 @@
369 412
370 413 public function createTask($data)
371 414 {
372 415 $data = apply_filters('fluent_boards/before_task_create', $data);
416 +
417 + // Keep newly-created tasks unprioritized unless a priority is explicitly selected.
418 + if (!array_key_exists('priority', $data)) {
419 + $data['priority'] = '';
420 + }
421 +
373 422 $createdTask = Task::create($data);
374 423
375 424 if ( ! empty($data['assignees'])) {
376 425 $assignees = array_filter(array_map('intval', $data['assignees']));
@@ -490,8 +539,9 @@
490 539
491 540 return $default;
492 541 }
493 542
543 + /** Persist metadata and invalidate any snapshot loaded for serialization. */
494 544 public function updateMeta($key, $value)
495 545 {
496 546 $exist = TaskMeta::where('task_id', $this->id)->where('key', $key)
497 547 ->first();
@@ -506,8 +556,10 @@
506 556 'value' => $value,
507 557 ]);
508 558 }
509 559
560 + $this->unsetRelation('taskMeta');
561 +
510 562 return $exist;
511 563 }
512 564
513 565 /**
@@ -512,9 +564,9 @@
512 564
513 565 /**
514 566 * Virtual attribute: pinned state from task meta (for API/frontend).
515 567 * Tasks without is_pinned meta are treated as unpinned (0).
516 - * Reads from the already-loaded 'meta' attribute to avoid an extra DB query per task.
568 + * Uses the batch-loaded metadata relation when preparing task lists.
517 569 *
518 570 * @return int 1 if pinned, 0 otherwise
519 571 */
520 572 public function getIsPinnedAttribute()
@@ -844,9 +896,9 @@
844 896 'priority' => [
845 897 'field' => __('Priority', 'fluent-boards'),
846 898 'type' => 'text',
847 899 'rules' => 'optional',
848 - 'description' => __('Priority of the task (low | medium | high). Example: "medium" ', 'fluent-boards'),
900 + 'description' => __('Priority of the task (urgent | high | medium | low). Leave empty for no priority. Example: "medium"', 'fluent-boards'),
849 901 ],
850 902 'due_at' => [
851 903 'field' => __('Due Date', 'fluent-boards'),
852 904 'type' => 'date',
@@ -928,10 +980,16 @@
928 980 public function repeatTaskMeta()
929 981 {
930 982 return $this->hasOne(Meta::class, 'object_id', 'id')->where('object_type', Constant::REPEAT_TASK_META);
931 983 }
984 +
985 + /** Reuse batch-loaded repeat settings, including a known missing relation. */
932 986 public function getRepeatTaskMetaAttribute()
933 987 {
988 + if ($this->relationLoaded('repeatTaskMeta')) {
989 + return $this->getRelation('repeatTaskMeta');
990 + }
991 +
934 992 return $this->repeatTaskMeta()->first();
935 993 }
936 994 public function taskCustomFields()
937 995 {