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 +88 -35 1.232.1.0 View file →
@@ -3,12 +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;
10 -use FluentBoards\App\Services\OptionService;
14 +use FluentBoards\App\Services\NotificationService;
11 15 use FluentCrm\App\Models\Subscriber;
12 16 use FluentBoards\App\Models\Relation;
13 17 use FluentCrm\App\Services\ContactsQuery;
14 18 use FluentCrm\App\Services\PermissionManager as CRMPermissionManager;
@@ -24,8 +28,9 @@
24 28 public function taskDeleted($task)
25 29 {
26 30 // Delete all activities and comments related to this task
27 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();
28 33 }
29 34
30 35 public function searchAssignees($options, $search, $includedIds)
31 36 {
@@ -49,20 +54,18 @@
49 54 'right_side_value' => $user->user_email,
50 55 ];
51 56 }
52 57
53 - return $formattedUsers;
54 -
55 58 if (!$includedIds) {
56 59 return $formattedUsers;
57 60 }
61 +
58 62 if (!is_array($includedIds)) {
59 - // $includedIds = [$includedIds];
60 63 $includedIds = [$includedIds];
61 64 }
62 65
63 - // $includedIds = array_diff($includedIds, $pushedIds);
64 - //
66 + $includedIds = array_diff(array_map('intval', $includedIds), $pushedIds);
67 +
65 68 if ($includedIds) {
66 69 $users = get_users([
67 70 'include' => $includedIds,
68 71 ]);
@@ -68,18 +71,16 @@
68 71 ]);
69 72
70 73 foreach ($users as $user) {
71 74 $formattedUsers[] = [
72 - 'id3' => $user->ID,
73 - 'title' => $user->display_name . ' (' . $user->user_email . ')',
74 - 'photo_' => /* get photo by gravitar or something */ get_avatar_url($user->user_email, ['size' => 50]),
75 - 'left_side_value' => $user->display_name,
76 - 'right_side_value' => $user->user_email,
77 - '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,
78 80 ];
79 81 }
80 82 }
81 - // dd($formattedUsers);
82 83
83 84 return $formattedUsers;
84 85 }
85 86
@@ -84,8 +85,13 @@
84 85 }
85 86
86 87 public function searchNonBoardWordpressUsers()
87 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 +
88 94 $superAdmin = Relation::select('user_id')->distinct()->pluck('user_id');
89 95 $users = User::whereDoesntHave('boards')->whereNotIn('ID', $superAdmin)->get();
90 96
91 97 $formattedUsers = [];
@@ -165,58 +171,105 @@
165 171
166 172
167 173
168 174 /**
169 - * Summary of taskAttachmentDeleted
170 - * @param mixed $task
171 - * @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
172 179 * @return void
173 180 */
174 - public function taskAttachmentDeleted($deletedAttachment)
181 + public function taskAttachmentDeleted($deletedAttachment, $boardId = null)
175 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 +
176 210 try {
177 - $deleteUrl = $deletedAttachment->full_url;
178 - $this->fileHandler->deleteFileByUrl($deleteUrl);
211 + $this->fileHandler->deleteAttachmentFile($deletedAttachment, $boardId);
179 212 } catch (Exception $e) {
180 213 wp_send_json_error($e->getMessage());
181 214 }
182 215 }
183 216
217 + /**
218 + * Auto-watch newly created tasks when the board preference allows it.
219 + */
184 220 public function onTaskCreated($task)
185 221 {
186 - $currentSettings = $this->getGlobalNotificationSettings();
187 - $shouldWatch = isset($currentSettings['watch_on_creating_task']) && $currentSettings['watch_on_creating_task'];
222 + $userId = get_current_user_id();
188 223
189 - if ($shouldWatch) {
190 - $task->watchers()->syncWithoutDetaching([get_current_user_id() => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
224 + if (!$userId) {
225 + return;
191 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 + }
192 231 }
193 232
233 + /**
234 + * Auto-watch commented tasks when the board preference allows it.
235 + */
194 236 public function onCommentCreated($comment)
195 237 {
196 238 $task = $comment->task;
197 - $currentSettings = $this->getGlobalNotificationSettings();
198 - $shouldWatch = isset($currentSettings['watch_on_commenting']) && $currentSettings['watch_on_commenting'];
239 + $userId = get_current_user_id();
199 240
200 - if ($shouldWatch) {
201 - $task->watchers()->syncWithoutDetaching([get_current_user_id() => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
241 + if (!$userId) {
242 + return;
202 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 + }
203 248 }
204 249
250 + /**
251 + * Auto-watch assigned tasks when the board preference allows it.
252 + */
205 253 public function onAssignAnotherUser($task, $assigneeId)
206 254 {
207 - $currentSettings = $this->getGlobalNotificationSettings();
208 - $shouldWatch = isset($currentSettings['watch_on_assigning']) && $currentSettings['watch_on_assigning'];
209 -
210 - if ($shouldWatch) {
255 + if ($this->shouldAutoWatchForBoard($task->board_id, Constant::GLOBAL_EMAIL_NOTIFICATION_ASSIGNING)) {
211 256 $task->watchers()->syncWithoutDetaching([$assigneeId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
212 257 }
213 258 }
214 259
215 - private function getGlobalNotificationSettings()
260 + /**
261 + * Determine auto-watch behavior from the current user's board preference.
262 + */
263 + private function shouldAutoWatchForBoard($boardId, $preferenceKey)
216 264 {
217 - $globalSettings = (new OptionService())->getGlobalNotificationSettings();
265 + return (new NotificationService())->isBoardAutoWatchEnabled($boardId, get_current_user_id(), $preferenceKey);
266 + }
218 267
219 - return maybe_unserialize($globalSettings->value);
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);
220 274 }
221 -
222 275 }