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/Hooks/Handlers/TaskHandler.php +114 -22 1.122.1.0 View file →
@@ -3,11 +3,16 @@
3 3 namespace FluentBoards\App\Hooks\Handlers;
4 4
5 5 use DateTimeImmutable;
6 6 use Exception;
7 +use FluentBoards\App\App;
7 8 use FluentBoards\App\Models\Activity;
9 +use FluentBoards\App\Models\Meta;
10 +use FluentBoards\App\Models\TaskMeta;
11 +use FluentBoards\App\Models\Task;
8 12 use FluentBoards\App\Models\User;
9 13 use FluentBoards\App\Services\Constant;
14 +use FluentBoards\App\Services\NotificationService;
10 15 use FluentCrm\App\Models\Subscriber;
11 16 use FluentBoards\App\Models\Relation;
12 17 use FluentCrm\App\Services\ContactsQuery;
13 18 use FluentCrm\App\Services\PermissionManager as CRMPermissionManager;
@@ -23,8 +28,9 @@
23 28 public function taskDeleted($task)
24 29 {
25 30 // Delete all activities and comments related to this task
26 31 Activity::where('object_id', $task->id)->where('object_type', Constant::ACTIVITY_TASK)->delete();
32 + Meta::where('object_id', $task->id)->where('object_type', Constant::REPEAT_TASK_META)->delete();
27 33 }
28 34
29 35 public function searchAssignees($options, $search, $includedIds)
30 36 {
@@ -48,20 +54,18 @@
48 54 'right_side_value' => $user->user_email,
49 55 ];
50 56 }
51 57
52 - return $formattedUsers;
53 -
54 58 if (!$includedIds) {
55 59 return $formattedUsers;
56 60 }
61 +
57 62 if (!is_array($includedIds)) {
58 - // $includedIds = [$includedIds];
59 63 $includedIds = [$includedIds];
60 64 }
61 65
62 - // $includedIds = array_diff($includedIds, $pushedIds);
63 - //
66 + $includedIds = array_diff(array_map('intval', $includedIds), $pushedIds);
67 +
64 68 if ($includedIds) {
65 69 $users = get_users([
66 70 'include' => $includedIds,
67 71 ]);
@@ -67,18 +71,16 @@
67 71 ]);
68 72
69 73 foreach ($users as $user) {
70 74 $formattedUsers[] = [
71 - 'id3' => $user->ID,
72 - 'title' => $user->display_name . ' (' . $user->user_email . ')',
73 - 'photo_' => /* get photo by gravitar or something */ get_avatar_url($user->user_email, ['size' => 50]),
74 - 'left_side_value' => $user->display_name,
75 - 'right_side_value' => $user->user_email,
76 - 'right_side_value1' => $user->user_email,
75 + 'id' => $user->ID,
76 + 'title' => $user->display_name . ' (' . $user->user_email . ')',
77 + 'photo' => get_avatar_url($user->user_email),
78 + 'left_side_value' => $user->display_name,
79 + 'right_side_value' => $user->user_email,
77 80 ];
78 81 }
79 82 }
80 - // dd($formattedUsers);
81 83
82 84 return $formattedUsers;
83 85 }
84 86
@@ -83,8 +85,13 @@
83 85 }
84 86
85 87 public function searchNonBoardWordpressUsers()
86 88 {
89 + // This enumerates every WordPress user with their email; only user managers may see it.
90 + if (!current_user_can('list_users')) {
91 + return [];
92 + }
93 +
87 94 $superAdmin = Relation::select('user_id')->distinct()->pluck('user_id');
88 95 $users = User::whereDoesntHave('boards')->whereNotIn('ID', $superAdmin)->get();
89 96
90 97 $formattedUsers = [];
@@ -130,10 +137,10 @@
130 137 $formattedSubscribers[] = [
131 138 'id' => $subscriber->id,
132 139 'title' => $subscriber->full_name . ' (' . $subscriber->email . ')',
133 140 'photo' => $subscriber->photo,
134 - 'left_side_value' => $subscriber->full_name,
135 - 'right_side_value' => $subscriber->email,
141 + 'name' => $subscriber->full_name,
142 + 'email' => $subscriber->email,
136 143 ];
137 144 }
138 145
139 146 if (!is_array($includedIds)) {
@@ -152,10 +159,10 @@
152 159 $formattedSubscribers[] = [
153 160 'id' => $subscriber->id,
154 161 'title' => $subscriber->full_name . ' (' . $subscriber->email . ')',
155 162 'photo' => $subscriber->photo,
156 - 'left_side_value' => $subscriber->full_name,
157 - 'right_side_value' => $subscriber->email,
163 + 'name' => $subscriber->full_name,
164 + 'email' => $subscriber->email,
158 165 ];
159 166 }
160 167 }
161 168
@@ -164,20 +171,105 @@
164 171
165 172
166 173
167 174 /**
168 - * Summary of taskAttachmentDeleted
169 - * @param mixed $task
170 - * @param mixed $deleteUrl
175 + * Delete an attachment file only after the owning database transaction commits.
176 + *
177 + * @param mixed $deletedAttachment
178 + * @param int|null $boardId
171 179 * @return void
172 180 */
173 - public function taskAttachmentDeleted($deletedAttachment)
181 + public function taskAttachmentDeleted($deletedAttachment, $boardId = null)
174 182 {
183 + if ($deletedAttachment->attachment_type === 'url') {
184 + return;
185 + }
186 +
187 + if ($boardId === null) {
188 + $task = Task::find(absint($deletedAttachment->object_id));
189 + $boardId = $task ? $task->board_id : null;
190 + }
191 +
192 + $dbInstance = App::getInstance('db');
193 +
194 + if ($dbInstance->inTransaction()) {
195 + $attachmentSnapshot = clone $deletedAttachment;
196 + $dbInstance->afterCommit(function () use ($attachmentSnapshot, $boardId) {
197 + try {
198 + $this->fileHandler->deleteAttachmentFile($attachmentSnapshot, $boardId);
199 + } catch (\Throwable $e) {
200 + error_log(sprintf(
201 + 'FluentBoards: Failed to delete committed task attachment file: %s',
202 + sanitize_text_field($e->getMessage())
203 + ));
204 + }
205 + });
206 +
207 + return;
208 + }
209 +
175 210 try {
176 - $deleteUrl = $deletedAttachment->full_url;
177 - $this->fileHandler->deleteFileByUrl($deleteUrl);
211 + $this->fileHandler->deleteAttachmentFile($deletedAttachment, $boardId);
178 212 } catch (Exception $e) {
179 213 wp_send_json_error($e->getMessage());
180 214 }
181 215 }
182 216
217 + /**
218 + * Auto-watch newly created tasks when the board preference allows it.
219 + */
220 + public function onTaskCreated($task)
221 + {
222 + $userId = get_current_user_id();
223 +
224 + if (!$userId) {
225 + return;
226 + }
227 +
228 + if ($this->shouldAutoWatchForBoard($task->board_id, Constant::GLOBAL_EMAIL_NOTIFICATION_CREATING_TASK)) {
229 + $task->watchers()->syncWithoutDetaching([$userId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
230 + }
231 + }
232 +
233 + /**
234 + * Auto-watch commented tasks when the board preference allows it.
235 + */
236 + public function onCommentCreated($comment)
237 + {
238 + $task = $comment->task;
239 + $userId = get_current_user_id();
240 +
241 + if (!$userId) {
242 + return;
243 + }
244 +
245 + if ($this->shouldAutoWatchForBoard($task->board_id, Constant::GLOBAL_EMAIL_NOTIFICATION_COMMENTING)) {
246 + $task->watchers()->syncWithoutDetaching([$userId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
247 + }
248 + }
249 +
250 + /**
251 + * Auto-watch assigned tasks when the board preference allows it.
252 + */
253 + public function onAssignAnotherUser($task, $assigneeId)
254 + {
255 + if ($this->shouldAutoWatchForBoard($task->board_id, Constant::GLOBAL_EMAIL_NOTIFICATION_ASSIGNING)) {
256 + $task->watchers()->syncWithoutDetaching([$assigneeId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
257 + }
258 + }
259 +
260 + /**
261 + * Determine auto-watch behavior from the current user's board preference.
262 + */
263 + private function shouldAutoWatchForBoard($boardId, $preferenceKey)
264 + {
265 + return (new NotificationService())->isBoardAutoWatchEnabled($boardId, get_current_user_id(), $preferenceKey);
266 + }
267 +
268 + public function taskCloned($originalTask, $clonedTask)
269 + {
270 + Activity::where('object_id', $clonedTask->id)
271 + ->where('object_type', Constant::ACTIVITY_TASK)
272 + ->delete();
273 + do_action('fluent_boards/task_cloned_activity', $originalTask, $clonedTask);
274 + }
183 275 }