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