1
0
mirror of https://github.com/freescout-helpdesk/freescout.git synced 2024-11-25 03:43:33 +01:00
freescout/app/Subscription.php

420 lines
15 KiB
PHP
Raw Normal View History

2018-08-08 09:52:53 +02:00
<?php
/**
2018-08-08 09:56:03 +02:00
* todo: implement caching by saving all options in one cache variable on register_shutdown_function.
2018-08-08 09:52:53 +02:00
*/
2018-08-08 09:56:03 +02:00
2018-08-08 09:52:53 +02:00
namespace App;
2018-09-10 11:50:53 +02:00
use App\Notifications\BroadcastNotification;
2018-09-07 15:08:34 +02:00
use App\Notifications\WebsiteNotification;
2018-08-08 09:52:53 +02:00
use Illuminate\Database\Eloquent\Model;
class Subscription extends Model
{
// Event types
2018-08-09 10:14:48 +02:00
// Changing ticket status does not fire event
2018-08-08 09:56:03 +02:00
const EVENT_TYPE_NEW = 1;
const EVENT_TYPE_ASSIGNED = 2;
const EVENT_TYPE_UPDATED = 3;
2018-08-08 09:52:53 +02:00
const EVENT_TYPE_CUSTOMER_REPLIED = 4;
2018-08-08 09:56:03 +02:00
const EVENT_TYPE_USER_REPLIED = 5;
2018-09-15 15:37:29 +02:00
const EVENT_TYPE_USER_ADDED_NOTE = 6;
2018-08-08 09:52:53 +02:00
2018-09-15 15:37:29 +02:00
// Events
2018-08-08 09:52:53 +02:00
// Notify me when…
const EVENT_NEW_CONVERSATION = 1;
const EVENT_CONVERSATION_ASSIGNED_TO_ME = 2;
const EVENT_CONVERSATION_ASSIGNED = 6;
const EVENT_FOLLOWED_CONVERSATION_UPDATED = 13;
const EVENT_I_AM_MENTIONED = 14;
const EVENT_MY_TEAM_MENTIONED = 15;
// Notify me when a customer replies…
const EVENT_CUSTOMER_REPLIED_TO_UNASSIGNED = 4;
const EVENT_CUSTOMER_REPLIED_TO_MY = 3;
const EVENT_CUSTOMER_REPLIED_TO_ASSIGNED = 7;
// Notify me when another Help Scout user replies or adds a note…
const EVENT_USER_REPLIED_TO_UNASSIGNED = 8;
const EVENT_USER_REPLIED_TO_MY = 5;
const EVENT_USER_REPLIED_TO_ASSIGNED = 9;
// Mediums
2018-09-07 15:08:34 +02:00
const MEDIUM_EMAIL = 1; // This is also website notifications
2018-09-10 11:50:53 +02:00
const MEDIUM_BROWSER = 2; // Browser push notification
2018-08-08 09:52:53 +02:00
const MEDIUM_MOBILE = 3;
public static $mediums = [
self::MEDIUM_EMAIL,
self::MEDIUM_BROWSER,
self::MEDIUM_MOBILE,
];
public static $default_subscriptions = [
self::MEDIUM_EMAIL => [
self::EVENT_CONVERSATION_ASSIGNED_TO_ME,
self::EVENT_FOLLOWED_CONVERSATION_UPDATED,
self::EVENT_I_AM_MENTIONED,
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_I_AM_MENTIONED,
self::EVENT_MY_TEAM_MENTIONED,
self::EVENT_CUSTOMER_REPLIED_TO_MY,
self::EVENT_USER_REPLIED_TO_MY,
],
];
/**
2018-08-08 09:56:03 +02:00
* List of events that occured.
2018-08-08 09:52:53 +02:00
*/
public static $occured_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.
2018-08-08 09:56:03 +02:00
*
* @param int $user_id
2018-08-08 09:52:53 +02:00
*/
public static function addDefaultSubscriptions($user_id)
{
self::saveFromArray(self::$default_subscriptions, $user_id);
}
/**
* Save subscriptions from passed array.
2018-08-08 09:56:03 +02:00
*
* @param array $subscriptions [description]
*
* @return [type] [description]
2018-08-08 09:52:53 +02:00
*/
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,
];
}
}
}
2018-08-08 09:56:03 +02:00
self::where('user_id', $user_id)->delete();
self::insert($subscriptions);
2018-08-08 09:52:53 +02:00
}
/**
2018-08-08 09:56:03 +02:00
* Check if subscription exists.
2018-08-08 09:52:53 +02:00
*/
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;
}
}
2018-08-08 09:56:03 +02:00
2018-08-08 09:52:53 +02:00
return true;
}
} else {
// Search in DB
}
2018-08-08 09:56:03 +02:00
2018-08-08 09:52:53 +02:00
return false;
}
/**
2018-08-21 20:55:32 +02:00
* Detect users to notify by medium.
2018-08-08 09:52:53 +02:00
*/
public static function usersToNotify($event_type, $conversation, $threads, $mailbox_user_ids = null)
{
2018-09-20 10:13:33 +02:00
// We can not check imported here, as after conversation has been imported via API
// notifications has to be sent.
// if ($conversation->imported) {
// return true;
// }
2018-08-08 09:52:53 +02:00
// Detect events
$events = [];
$thread = $threads[0];
$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:
2018-09-15 15:37:29 +02:00
case self::EVENT_TYPE_USER_ADDED_NOTE:
2018-08-08 09:52:53 +02:00
$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;
// todo: EVENT_I_AM_MENTIONED, EVENT_MY_TEAM_MENTIONED
}
// 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)
2018-08-08 09:52:53 +02:00
->whereIn('event', $events)
->get();
// Filter subscribers
$users_to_notify = [];
foreach ($subscriptions as $i => $subscription) {
2019-12-25 14:17:17 +01:00
// 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
) {
continue;
}
2019-12-25 14:17:17 +01:00
// Check if user is following this conversation.
if ($subscription->event == self::EVENT_FOLLOWED_CONVERSATION_UPDATED
&& !$conversation->isUserFollowing($subscription->user_id)
2018-08-08 09:52:53 +02:00
) {
continue;
}
2018-11-12 10:12:57 +01:00
2018-08-08 09:52:53 +02:00
$users_to_notify[$subscription->medium][] = $subscription->user;
$users_to_notify[$subscription->medium] = array_unique($users_to_notify[$subscription->medium]);
}
2018-08-08 09:56:03 +02:00
2018-08-08 09:52:53 +02:00
return $users_to_notify;
}
/**
* Process events which occured.
*/
public static function processEvents()
{
$notify = [];
2019-06-24 13:51:40 +02:00
$delay = now()->addSeconds(Conversation::UNDO_TIMOUT);
2019-12-25 14:17:17 +01:00
// Collect into notify array information about all users who need to be notified
2018-08-08 09:52:53 +02:00
foreach (self::$occured_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;
}
}
}
}
2018-08-21 20:55:32 +02:00
// Get users and threads from previous results to avoid repeated SQL queries.
2018-08-08 09:52:53 +02:00
$users = [];
2018-08-21 20:55:32 +02:00
$threads = [];
2018-08-08 09:52:53 +02:00
foreach (self::$mediums as $medium) {
if (empty($notify[$medium][$event['conversation']->id])) {
2018-08-21 20:55:32 +02:00
$threads = $event['conversation']->getThreads();
2018-08-08 09:52:53 +02:00
break;
} else {
$users = $notify[$medium][$event['conversation']->id]['users'];
2018-08-21 20:55:32 +02:00
$threads = $notify[$medium][$event['conversation']->id]['threads'];
2018-08-08 09:52:53 +02:00
}
}
2018-08-21 20:55:32 +02:00
$users_to_notify = self::usersToNotify($event['event_type'], $event['conversation'], $threads, $mailbox_user_ids);
2018-08-08 09:52:53 +02:00
if (!$users_to_notify) {
continue;
}
2018-08-22 09:26:42 +02:00
2018-08-08 09:52:53 +02:00
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']) {
2018-08-09 10:14:48 +02:00
unset($medium_users_to_notify[$i]);
2018-08-08 09:52:53 +02:00
}
}
if (count($medium_users_to_notify)) {
$notify[$medium][$event['conversation']->id] = [
2018-08-21 20:55:32 +02:00
// Users subarray contains all users who need to receive notification for all events
2018-08-08 09:52:53 +02:00
'users' => array_unique(array_merge($users, $medium_users_to_notify)),
'conversation' => $event['conversation'],
2018-08-21 20:55:32 +02:00
'threads' => $threads,
2018-08-08 09:52:53 +02:00
'mailbox_user_ids' => $mailbox_user_ids,
];
}
}
}
2019-11-07 09:45:01 +01:00
// - Notify by email
// - Real-time menu notification (uses same medium as for email)
2018-08-08 09:52:53 +02:00
if (!empty($notify[self::MEDIUM_EMAIL])) {
2018-09-07 15:08:34 +02:00
// Email notification (better to create them first)
foreach ($notify[self::MEDIUM_EMAIL] as $conversation_id => $notify_info) {
2018-09-16 12:52:52 +02:00
\App\Jobs\SendNotificationToUsers::dispatch($notify_info['users'], $notify_info['conversation'], $notify_info['threads'])
2019-06-24 13:51:40 +02:00
->delay($delay)
2018-09-16 12:52:52 +02:00
->onQueue('emails');
2018-08-08 09:52:53 +02:00
}
// - Menu notification (uses same medium as for email)
2018-09-07 15:08:34 +02:00
foreach ($notify[self::MEDIUM_EMAIL] as $notify_info) {
$website_notification = new WebsiteNotification($notify_info['conversation'], self::chooseThread($notify_info['threads']));
2019-06-24 13:51:40 +02:00
$website_notification->delay($delay);
\Notification::send($notify_info['users'], $website_notification);
2018-09-10 11:50:53 +02:00
}
}
// 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;
2018-09-08 20:02:01 +02:00
foreach ($notify_info['users'] as $user) {
2018-09-10 11:50:53 +02:00
$mediums = [$medium];
if (!empty($broadcasts[$thread_id]['mediums'])) {
2018-09-10 11:50:53 +02:00
$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,
2018-11-12 10:12:57 +01:00
];
2018-09-08 20:02:01 +02:00
}
2018-09-07 15:08:34 +02:00
}
2018-08-08 09:52:53 +02:00
}
2018-09-10 11:50:53 +02:00
// \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']);
2019-06-24 13:51:40 +02:00
$broadcast_notification->delay($delay);
$to_broadcast['user']->notify($broadcast_notification);
2018-09-10 11:50:53 +02:00
}
2018-09-07 15:08:34 +02:00
2019-11-07 09:45:01 +01:00
// - Mobile
\Eventy::action('subscription.process_events', $notify);
2018-11-12 10:12:57 +01:00
2018-08-21 20:55:32 +02:00
self::$occured_events = [];
2018-08-08 09:52:53 +02:00
}
/**
* 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];
}
2018-08-08 09:52:53 +02:00
/**
* 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::$occured_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::$occured_events[] = [
'event_type' => self::EVENT_TYPE_UPDATED,
'conversation' => $conversation,
'caused_by_user_id' => $caused_by_user_id,
];
}
if ($process_now) {
self::processEvents();
}
}
}