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 +166 -43 1.122.1.0 View file →
@@ -7,9 +7,9 @@
7 7 use FluentBoards\App\Models\Relation;
8 8 use FluentBoards\App\Models\Task;
9 9
10 10
11 -class LabelService
11 +class LabelService
12 12 {
13 13 public function getLabelsByBoard($boardId)
14 14 {
15 15 return Label::where('board_id', $boardId)->orderBy('created_at', 'ASC')->get();
@@ -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,41 +114,81 @@
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 - do_action('fluent_boards/board_label', $label, Constant::ACTIVITY_ACTION_DELETED);
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
189 + $labelMap = [];
190 +
128 191 foreach($boardCopyFrom->labels as $label)
129 192 {
130 193 $labelToSave = array();
131 194 $labelToSave['title'] = $label->title;
@@ -134,15 +197,75 @@
134 197 $labelToSave['type'] = 'label';
135 198 $labelToSave['position'] = 0;
136 199 $labelToSave['color'] = $label->color;
137 200 $labelToSave['bg_color'] = $label->bg_color;
138 - Label::create($labelToSave);
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 + }
206 + $copiedLabel = Label::create($labelToSave);
207 +
208 + $labelMap[$label['id']] = $copiedLabel->id;
139 209 }
210 + return $labelMap;
140 211 }
141 - 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)
142 222 {
143 - $oneMinuteAgoTimestamp = current_time('timestamp') - 60;
144 - return Label::where('board_id', $boardId)
145 - ->where('updated_at', '>=', date_i18n('Y-m-d H:i:s', $oneMinuteAgoTimestamp))
146 - ->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;
147 254 }
148 -}
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 +}