[ self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_FOLLOWED_CONVERSATION_UPDATED, //self::EVENT_MY_TEAM_MENTIONED, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY, ], self::MEDIUM_BROWSER => [ self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_FOLLOWED_CONVERSATION_UPDATED, //self::EVENT_MY_TEAM_MENTIONED, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY, ], ]; /** * List of events that occurred. */ public static $occurred_events = []; public $timestamps = false; /** * The attributes that are not mass assignable. * * @var array */ protected $guarded = ['id']; /** * Subscribed user. */ public function user() { return $this->belongsTo('App\User'); } /** * Add default subscriptions for user. * * @param int $user_id */ public static function addDefaultSubscriptions($user_id) { self::saveFromArray(self::getDefaultSubscriptions(), $user_id); } public static function getDefaultSubscriptions() { return Option::get('subscription_defaults', self::$default_subscriptions); } /** * Save subscriptions from passed array. * * @param array $subscriptions [description] * * @return [type] [description] */ public static function saveFromArray($new_subscriptions, $user_id) { $subscriptions = []; if (is_array($new_subscriptions)) { foreach ($new_subscriptions as $medium => $events) { foreach ($events as $event) { $subscriptions[] = [ 'user_id' => $user_id, 'medium' => $medium, 'event' => $event, ]; } } } self::where('user_id', $user_id)->delete(); self::insert($subscriptions); } /** * Check if subscription exists. */ public static function exists(array $params, $subscriptions = null) { if ($subscriptions) { // Look in the passed list foreach ($subscriptions as $subscription) { foreach ($params as $param_name => $param_value) { if ($subscription->$param_name != $param_value) { continue 2; } } return true; } } else { // Search in DB } return false; } /** * Detect users to notify by medium. */ public static function usersToNotify($event_type, $conversation, $threads, $mailbox_user_ids = null) { $users_to_notify = []; $thread = null; if (isset($threads[0])) { $thread = $threads[0]; } elseif (count($threads)) { $thread = array_shift(array_values($threads)); } if (!$thread) { return $users_to_notify; } // Ignore imported threads. if ($thread->imported) { return true; } // Detect events $events = []; $prev_thread = null; if (!empty($threads[1])) { $prev_thread = $threads[1]; } switch ($event_type) { case self::EVENT_TYPE_NEW: $events[] = self::EVENT_NEW_CONVERSATION; break; case self::EVENT_TYPE_ASSIGNED: $events[] = self::EVENT_CONVERSATION_ASSIGNED_TO_ME; $events[] = self::EVENT_CONVERSATION_ASSIGNED; break; case self::EVENT_TYPE_CUSTOMER_REPLIED: $events[] = self::EVENT_FOLLOWED_CONVERSATION_UPDATED; if (!empty($prev_thread) && $prev_thread->user_id) { $events[] = self::EVENT_CUSTOMER_REPLIED_TO_MY; $events[] = self::EVENT_CUSTOMER_REPLIED_TO_ASSIGNED; } else { $events[] = self::EVENT_CUSTOMER_REPLIED_TO_UNASSIGNED; } break; case self::EVENT_TYPE_USER_REPLIED: case self::EVENT_TYPE_USER_ADDED_NOTE: $events[] = self::EVENT_FOLLOWED_CONVERSATION_UPDATED; if (!empty($prev_thread) && $prev_thread->user_id) { $events[] = self::EVENT_USER_REPLIED_TO_MY; $events[] = self::EVENT_USER_REPLIED_TO_ASSIGNED; } else { $events[] = self::EVENT_USER_REPLIED_TO_UNASSIGNED; } break; } $events = \Eventy::filter('subscription.events_by_type', $events, $event_type, $thread); // Check if assigned user changed $user_changed = false; if ($event_type != self::EVENT_TYPE_ASSIGNED && $event_type != self::EVENT_TYPE_NEW) { if ($thread->type == Thread::TYPE_LINEITEM && $thread->action_type == Thread::ACTION_TYPE_USER_CHANGED) { $user_changed = true; } elseif ($prev_thread) { if ($prev_thread->user_id != $thread->user_id) { $user_changed = true; } } else { // Get prev thread if ($prev_thread && $prev_thread->user_id != $thread->user_id) { $user_changed = true; } } } if ($user_changed) { $events[] = self::EVENT_CONVERSATION_ASSIGNED_TO_ME; $events[] = self::EVENT_CONVERSATION_ASSIGNED; $events[] = self::EVENT_FOLLOWED_CONVERSATION_UPDATED; } $events = array_unique($events); // Detect subscribed users if (!$mailbox_user_ids) { $mailbox_user_ids = $conversation->mailbox->userIdsHavingAccess(); } $subscriptions = self::whereIn('user_id', $mailbox_user_ids) ->whereIn('event', $events) ->get(); $subscriptions = \Eventy::filter('subscription.subscriptions', $subscriptions, $conversation, $events, $thread); // Filter subscribers foreach ($subscriptions as $i => $subscription) { // Actions on conversation where user is assignee if (in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY]) && ($conversation->user_id != $subscription->user_id && !\Eventy::filter('subscription.is_user_assignee', false, $subscription, $conversation)) ) { continue; } // Check if user is following this conversation. if ($subscription->event == self::EVENT_FOLLOWED_CONVERSATION_UPDATED && !$conversation->isUserFollowing($subscription->user_id) ) { continue; } // Skip if user muted notifications for this mailbox //if ($subscription->user->isAdmin()) { // Mute notifications for events not related directly to the user. if (!in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_FOLLOWED_CONVERSATION_UPDATED, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY]) && !\Eventy::filter('subscription.is_related_to_user', false, $subscription, $thread) ) { $mailbox_settings = $conversation->mailbox->getUserSettings($subscription->user_id); if (!empty($mailbox_settings->mute)) { continue; } } //} if (\Eventy::filter('subscription.filter_out', false, $subscription, $thread)) { continue; } $users_to_notify[$subscription->medium][] = $subscription->user; $users_to_notify[$subscription->medium] = array_unique($users_to_notify[$subscription->medium]); } // Add menu notifications, for example. $users_to_notify = \Eventy::filter('subscription.users_to_notify', $users_to_notify, $event_type, $events, $thread); return $users_to_notify; } /** * Process events which occurred. */ public static function processEvents() { $notify = []; $delay = now()->addSeconds(Conversation::UNDO_TIMOUT); // Collect into notify array information about all users who need to be notified foreach (self::$occurred_events as $event) { // Get mailbox users ids $mailbox_user_ids = []; foreach (self::$mediums as $medium) { if (!empty($notify[$medium])) { foreach ($notify[$medium] as $conversation_id => $notify_info) { if ($notify_info['conversation']->mailbox_id == $event['conversation']->mailbox_id) { $mailbox_user_ids = $notify_info['mailbox_user_ids']; break 2; } } } } // Get users and threads from previous results to avoid repeated SQL queries. $users = []; $threads = []; foreach (self::$mediums as $medium) { if (empty($notify[$medium][$event['conversation']->id])) { $threads = $event['conversation']->getThreads(); break; } else { $users[$medium] = $notify[$medium][$event['conversation']->id]['users']; $threads = $notify[$medium][$event['conversation']->id]['threads']; } } $users_to_notify = self::usersToNotify($event['event_type'], $event['conversation'], $threads, $mailbox_user_ids); if (!$users_to_notify || !is_array($users_to_notify)) { continue; } foreach ($users_to_notify as $medium => $medium_users_to_notify) { // Remove current user from recipients if action caused by current user foreach ($medium_users_to_notify as $i => $user) { if ($user->id == $event['caused_by_user_id']) { unset($medium_users_to_notify[$i]); } } if (count($medium_users_to_notify)) { $notify[$medium][$event['conversation']->id] = [ // Users subarray contains all users who need to receive notification // for all events for the media. 'users' => array_unique(array_merge($users[$medium] ?? [], $medium_users_to_notify)), 'conversation' => $event['conversation'], 'threads' => $threads, 'mailbox_user_ids' => $mailbox_user_ids, ]; } } } // - Email notification (better to create them first) if (!empty($notify[self::MEDIUM_EMAIL])) { foreach ($notify[self::MEDIUM_EMAIL] as $conversation_id => $notify_info) { \App\Jobs\SendNotificationToUsers::dispatch($notify_info['users'], $notify_info['conversation'], $notify_info['threads']) ->delay($delay) ->onQueue('emails'); } } // - Menu notification (uses same medium as for Email, if email notifications are disabled - use Browser notificaitons) if (!empty($notify[self::MEDIUM_EMAIL]) || !empty($notify[self::MEDIUM_BROWSER]) || !empty($notify[self::MEDIUM_MENU]) ) { if (!empty($notify[self::MEDIUM_EMAIL])) { $notify_menu = $notify[self::MEDIUM_EMAIL] ?? []; } else { $notify_menu = $notify[self::MEDIUM_BROWSER] ?? []; } $notify_menu = $notify_menu + ($notify[self::MEDIUM_MENU] ?? []); foreach ($notify_menu as $notify_info) { $website_notification = new WebsiteNotification($notify_info['conversation'], self::chooseThread($notify_info['threads'])); $website_notification->delay($delay); \Notification::send($notify_info['users'], $website_notification); } } // Send broadcast notifications: // - Browser push notification $broadcasts = []; foreach ([self::MEDIUM_EMAIL, self::MEDIUM_BROWSER] as $medium) { if (empty($notify[$medium])) { continue; } foreach ($notify[$medium] as $notify_info) { $thread_id = self::chooseThread($notify_info['threads'])->id; foreach ($notify_info['users'] as $user) { $mediums = [$medium]; if (!empty($broadcasts[$thread_id]['mediums'])) { $mediums = array_unique(array_merge($mediums, $broadcasts[$thread_id]['mediums'])); } $broadcasts[$thread_id] = [ 'user' => $user, 'conversation' => $notify_info['conversation'], 'threads' => $notify_info['threads'], 'mediums' => $mediums, ]; } } } // \Notification::sendNow($notify_info['users'], new BroadcastNotification($notify_info['conversation'], $notify_info['threads'][0])); foreach ($broadcasts as $thread_id => $to_broadcast) { $broadcast_notification = new BroadcastNotification($to_broadcast['conversation'], self::chooseThread($to_broadcast['threads']), $to_broadcast['mediums']); $broadcast_notification->delay($delay); $to_broadcast['user']->notify($broadcast_notification); } // - Mobile \Eventy::action('subscription.process_events', $notify); self::$occurred_events = []; } /** * Get fist meaningful thread for the notification. */ public static function chooseThread($threads) { $actions_types = [ Thread::ACTION_TYPE_USER_CHANGED, ]; // First thread is the newest. foreach ($threads as $thread) { if ($thread->type == Thread::TYPE_LINEITEM && !in_array($thread->action_type, $actions_types)) { continue; } else { return $thread; } } return $threads[0]; } /** * Remember event type to process in ProcessSubscriptionEvents middleware on terminate. */ public static function registerEvent($event_type, $conversation, $caused_by_user_id, $process_now = false) { self::$occurred_events[] = [ 'event_type' => $event_type, 'conversation' => $conversation, 'caused_by_user_id' => $caused_by_user_id, ]; // Automatically add EVENT_TYPE_UPDATED if (!in_array($event_type, [self::EVENT_TYPE_UPDATED, self::EVENT_TYPE_NEW])) { self::$occurred_events[] = [ 'event_type' => self::EVENT_TYPE_UPDATED, 'conversation' => $conversation, 'caused_by_user_id' => $caused_by_user_id, ]; } if ($process_now) { self::processEvents(); } } }