| @@ -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 | } |