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/Services/LabelService.php +158 -40 1.322.1.0 View file →
@@ -16,26 +16,48 @@
16 16 }
17 17
18 18 public function getLabelsByBoardUsedInTasks($boardId)
19 19 {
20 - $boardLabel = Label::where('board_id', $boardId)->where('type', 'label')->orderBy('created_at', 'ASC')->get();
20 + $boardLabels = Label::where('board_id', $boardId)
21 + ->where('type', 'label')
22 + ->orderBy('created_at', 'ASC')
23 + ->get();
24 +
25 + if ($boardLabels->isEmpty()) {
26 + return [];
27 + }
28 +
29 + // One relation query for the whole board rather than an exists() per label.
30 + $usedIds = Relation::where('object_type', Constant::OBJECT_TYPE_TASK_LABEL)
31 + ->whereIn('foreign_id', $boardLabels->pluck('id')->all())
32 + ->distinct()
33 + ->pluck('foreign_id')
34 + ->all();
35 +
36 + $usedIds = array_map('intval', $usedIds);
37 +
21 38 $usedLabel = [];
22 - foreach ($boardLabel as $label) {
23 - $exist = Relation::where('foreign_id', $label->id)->where('object_type', 'task_label')->exists();
24 - if ($exist) {
39 + foreach ($boardLabels as $label) {
40 + if (in_array((int) $label->id, $usedIds, true)) {
25 41 $usedLabel[] = $label;
26 42 }
27 43 }
44 +
28 45 return $usedLabel;
29 46 }
30 47
31 48 public function createLabel($labelData, $boardId)
32 49 {
50 + $labelData = $this->normalizeLabelColorData($labelData);
51 +
33 52 $label = new Label();
34 53 $label->board_id = $boardId;
35 - $label->title = $labelData['label'];
36 - $label->bg_color = $labelData['bg_color'];
37 - $label->color = $labelData['color'];
54 + $label->title = $labelData['label'] ?? '';
55 + $label->bg_color = $labelData['bg_color'] ?? '';
56 + $label->color = $labelData['color'] ?? '';
57 + if (isset($labelData['settings'])) {
58 + $label->settings = $labelData['settings'];
59 + }
38 60 $label->save();
39 61
40 62 return $label;
41 63 }
@@ -41,26 +63,26 @@
41 63 }
42 64
43 65 public function createDefaultLabel($boardId)
44 66 {
45 - $defaultColors = [
46 - "green" => "#4bce97",
47 - "yellow" => "#f5cd47",
48 - "orange" => "#fea362",
49 - "red" => "#f87168",
50 - "purple" => "#9f8fef"
51 - ];
67 + $defaultColors = ['green-bold', 'yellow-bold', 'orange-bold', 'red-bold', 'purple-bold'];
52 68
53 69 $data = [];
54 70
55 - foreach ($defaultColors as $index => $bg_color)
71 + foreach ($defaultColors as $presetId)
56 72 {
73 + $preset = Constant::getLabelColorPreset($presetId);
74 + $colorName = strtok($presetId, '-');
57 75 $data[] = [
58 76 'board_id' => $boardId,
59 - 'slug' => $index,
77 + // Titles match the create-board modal defaults, otherwise boards created
78 + // outside that modal end up with colour chips carrying no text.
79 + 'title' => ucfirst($colorName),
80 + 'slug' => $colorName,
60 81 'type' => 'label',
61 - 'bg_color' => $bg_color,
62 - 'color' => Constant::TEXT_COLOR_MAP[$index],
82 + 'bg_color' => $preset['light_bg_color'],
83 + 'color' => $preset['light_text_color'],
84 + 'settings' => maybe_serialize([Constant::LABEL_COLOR_PRESET_SETTING => $presetId]),
63 85 'created_at' => current_time('mysql'),
64 86 'updated_at' => current_time('mysql')
65 87 ];
66 88 }
@@ -67,15 +89,16 @@
67 89
68 90 Label::insert($data);
69 91 }
70 92
71 - public function createLabelForTask($labelData)
93 + public function createLabelForTask($labelData, $boardId = null)
72 94 {
73 - $task = Task::findOrFail($labelData['task_id']);
95 + $task = $boardId ? (new TaskService())->findTaskOnBoard($labelData['task_id'], $boardId) : Task::findOrFail($labelData['task_id']);
96 + $label = $this->findLabelOnBoard($labelData['board_term_id'], $boardId ?: $task->board_id);
74 97
75 - $task->labels()->syncWithoutDetaching([$labelData['boardTerm_id'] => ['object_type' => Constant::OBJECT_TYPE_TASK_LABEL]]);
98 + $task->labels()->syncWithoutDetaching([$labelData['board_term_id'] => ['object_type' => Constant::OBJECT_TYPE_TASK_LABEL]]);
76 99
77 - $label = $task->labels->find($labelData['boardTerm_id']);
100 + $label = $task->labels->find($label->id);
78 101
79 102 do_action('fluent_boards/task_label',$task, $label, 'added');
80 103
81 104 return $label;
@@ -80,11 +103,11 @@
80 103
81 104 return $label;
82 105 }
83 106
84 - public function getLabelsByTask($taskId)
107 + public function getLabelsByTask($taskId, $boardId = null)
85 108 {
86 - $task = Task::findOrFail($taskId);
109 + $task = $boardId ? (new TaskService())->findTaskOnBoard($taskId, $boardId) : Task::findOrFail($taskId);
87 110 return $task->labels;
88 111 }
89 112
90 113 public function labelsByBoardId($boardId)
@@ -91,37 +114,75 @@
91 114 {
92 115 return Label::where('board_id', $boardId)->whereNull('archived_at')->get();
93 116 }
94 117
95 - public function deleteLabelOfTask($taskId, $labelId)
118 + public function deleteLabelOfTask($taskId, $labelId, $boardId = null)
96 119 {
97 - $task = Task::findOrFail($taskId);
120 + $task = $boardId ? (new TaskService())->findTaskOnBoard($taskId, $boardId) : Task::findOrFail($taskId);
121 + $label = $this->findLabelOnBoard($labelId, $boardId ?: $task->board_id);
98 122 $task->labels()->detach($labelId);
99 - $label = Label::findOrFail($labelId);
100 123 do_action('fluent_boards/task_label',$task, $label, 'removed');
101 124 }
102 125
103 - public function deleteLabelOfBoard($labelId)
126 + public function deleteLabelOfBoard($labelId, $boardId = null)
104 127 {
105 - $label = Label::findOrFail($labelId);
106 - $boardId = $label->board_id;
128 + $label = $boardId ? $this->findLabelOnBoard($labelId, $boardId) : Label::findOrFail($labelId);
129 + $label->tasks()->detach();
107 130 $label->delete();
108 131
109 132 do_action('fluent_boards/board_label_deleted', $label);
110 133 }
111 134
112 - public function editLabelofBoard($labelData, $id)
135 + public function editLabelofBoard($labelData, $id, $boardId = null)
113 136 {
114 - $label = Label::findOrFail($id);
115 - $label->title = $labelData['label'];
116 - if ($label->bg_color != $labelData['bg_color']) {
137 + $label = $boardId ? $this->findLabelOnBoard($id, $boardId) : Label::findOrFail($id);
138 + $labelData = $this->normalizeLabelColorData($labelData, $label);
139 + $label->title = $labelData['label'] ?? $label->title;
140 +
141 + // Background and text colour move independently: coupling them dropped a
142 + // text-colour-only change on the floor while still reporting success.
143 + if (isset($labelData['bg_color']) && $labelData['bg_color'] !== '') {
117 144 $label->bg_color = $labelData['bg_color'];
145 + }
146 +
147 + if (isset($labelData['color']) && $labelData['color'] !== '') {
118 148 $label->color = $labelData['color'];
119 149 }
150 +
151 + if (array_key_exists('settings', $labelData)) {
152 + if (array_key_exists('color_preset', $labelData) && $labelData['color_preset'] === '') {
153 + $label->replaceSettings($labelData['settings']);
154 + } else {
155 + $label->settings = $labelData['settings'];
156 + }
157 + }
158 +
120 159 $label->save();
121 160 return $label;
122 161 }
123 162
163 + /**
164 + * Resolve a label only when it belongs to the requested board.
165 + *
166 + * @param int $labelId
167 + * @param int $boardId
168 + * @return Label
169 + * @throws \Exception
170 + */
171 + public function findLabelOnBoard($labelId, $boardId)
172 + {
173 + $label = Label::where('id', absint($labelId))
174 + ->where('board_id', absint($boardId))
175 + ->where('type', 'label')
176 + ->first();
177 +
178 + if (!$label) {
179 + throw new \Exception(esc_html__('Label not found', 'fluent-boards'));
180 + }
181 +
182 + return $label;
183 + }
184 +
124 185 public function copyLabelsOfBoard($boardId, $board)
125 186 {
126 187 $boardCopyFrom = Board::findOrFail($boardId);
127 188
@@ -136,8 +197,13 @@
136 197 $labelToSave['type'] = 'label';
137 198 $labelToSave['position'] = 0;
138 199 $labelToSave['color'] = $label->color;
139 200 $labelToSave['bg_color'] = $label->bg_color;
201 + $settings = (array) $label->settings;
202 + $presetId = $settings[Constant::LABEL_COLOR_PRESET_SETTING] ?? '';
203 + if (Constant::getLabelColorPreset($presetId)) {
204 + $labelToSave['settings'] = [Constant::LABEL_COLOR_PRESET_SETTING => $presetId];
205 + }
140 206 $copiedLabel = Label::create($labelToSave);
141 207
142 208 $labelMap[$label['id']] = $copiedLabel->id;
143 209 }
@@ -142,12 +208,64 @@
142 208 $labelMap[$label['id']] = $copiedLabel->id;
143 209 }
144 210 return $labelMap;
145 211 }
146 - public function getLastOneMinuteUpdatedLabels($boardId)
212 +
213 + /**
214 + * Converts a selected preset into stable light-mode fallback colors.
215 + *
216 + * @param array $labelData
217 + * @param Label|null $label
218 + * @return array
219 + * @throws \Exception
220 + */
221 + private function normalizeLabelColorData($labelData, $label = null)
147 222 {
148 - $oneMinuteAgoTimestamp = current_time('timestamp') - 60;
149 - return Label::where('board_id', $boardId)
150 - ->where('updated_at', '>=', date_i18n('Y-m-d H:i:s', $oneMinuteAgoTimestamp))
151 - ->get();
223 + if (!array_key_exists('color_preset', $labelData)) {
224 + return $labelData;
225 + }
226 +
227 + $presetId = $labelData['color_preset'];
228 + $settings = $label ? (array) $label->settings : [];
229 +
230 + // Framework request extraction can represent an omitted optional field
231 + // as null. That must preserve an existing preset rather than reject it.
232 + if ($presetId === null) {
233 + unset($labelData['color_preset']);
234 + return $labelData;
235 + }
236 +
237 + if ($presetId === '') {
238 + unset($settings[Constant::LABEL_COLOR_PRESET_SETTING]);
239 + $labelData['settings'] = $settings;
240 + return $labelData;
241 + }
242 +
243 + $preset = Constant::getLabelColorPreset($presetId);
244 + if (!$preset) {
245 + throw new \Exception(esc_html__('Invalid label color preset', 'fluent-boards'));
246 + }
247 +
248 + $settings[Constant::LABEL_COLOR_PRESET_SETTING] = $preset['id'];
249 + $labelData['settings'] = $settings;
250 + $labelData['bg_color'] = $preset['light_bg_color'];
251 + $labelData['color'] = $preset['light_text_color'];
252 +
253 + return $labelData;
152 254 }
153 -}
255 + public function getLastOneMinuteUpdatedLabels($boardId, $lastUpdated = null, $includeArchived = true)
256 + {
257 + if (!$lastUpdated) {
258 + $oneMinuteAgoTimestamp = current_time('timestamp') - 60;
259 + $lastUpdated = date_i18n('Y-m-d H:i:s', $oneMinuteAgoTimestamp);
260 + }
261 +
262 + $labelsQuery = Label::where('board_id', $boardId)
263 + ->where('updated_at', '>=', $lastUpdated);
264 +
265 + if (!$includeArchived) {
266 + $labelsQuery->whereNull('archived_at');
267 + }
268 +
269 + return $labelsQuery->get();
270 + }
271 +}