mirror of
https://gh.wpcy.net/https://github.com/LJPc-solutions/freescout-internal-conversations-module.git
synced 2026-07-16 05:58:35 +08:00
645 lines
25 KiB
PHP
645 lines
25 KiB
PHP
<?php
|
|
|
|
namespace Modules\InternalConversations\Providers;
|
|
|
|
use App\Conversation;
|
|
use App\ConversationFolder;
|
|
use App\Folder;
|
|
use App\Mailbox;
|
|
use App\Thread;
|
|
use App\User;
|
|
use Illuminate\Database\Eloquent\Factory;
|
|
use Illuminate\Support\ServiceProvider;
|
|
use Modules\Mentions\Providers\MentionsServiceProvider;
|
|
use Modules\Teams\Providers\TeamsServiceProvider as Teams;
|
|
use Modules\InternalConversations\Helpers\InternalNotification;
|
|
|
|
class InternalConversationsServiceProvider extends ServiceProvider {
|
|
const EVENT_IC_NEW_MESSAGE = 100;
|
|
const EVENT_IC_NEW_REPLY = 101;
|
|
|
|
/**
|
|
* Indicates if loading of the provider is deferred.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $defer = false;
|
|
|
|
/**
|
|
* Boot the application events.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function boot() {
|
|
$this->registerConfig();
|
|
$this->registerViews();
|
|
$this->registerFactories();
|
|
$this->loadMigrationsFrom( __DIR__ . '/../Database/Migrations' );
|
|
$this->hooks();
|
|
}
|
|
|
|
/**
|
|
* Module hooks.
|
|
*/
|
|
public function hooks() {
|
|
// Register notification types
|
|
InternalNotification::registerType('thumbs_up', 'gave you a 👍 on a note on');
|
|
InternalNotification::registerType('new_conversation', 'started an internal conversation');
|
|
InternalNotification::registerType('new_reply', 'replied to an internal conversation');
|
|
|
|
// Block core Subscription system from sending duplicate notifications for internal conversations.
|
|
// The InternalNotification helper handles all notifications for custom conversations.
|
|
\Eventy::addFilter( 'subscription.subscriptions', function ( $subscriptions, $conversation ) {
|
|
if ( $conversation->isCustom() ) {
|
|
return collect( [] );
|
|
}
|
|
|
|
return $subscriptions;
|
|
}, 20, 2 );
|
|
|
|
\Eventy::addAction( 'conversation.new.conv_switch_buttons', function ( $mailbox ) {
|
|
echo \View::make( 'internalconversations::partials/conv-switch-button', [] )->render();
|
|
} );
|
|
|
|
\Eventy::addAction( 'conversation.create_form.before_subject', function ( $conversation, $mailbox, $thread ) {
|
|
$users = [];
|
|
if ( $conversation && $conversation->type == Conversation::TYPE_CUSTOM ) {
|
|
$userIds = $conversation->getMeta( 'internal_conversations.users', [] );
|
|
foreach ( $userIds as $userId ) {
|
|
$user = User::find( $userId );
|
|
if ( $user ) {
|
|
$users[ $userId ] = $user->getFullName();
|
|
}
|
|
}
|
|
}
|
|
|
|
echo \View::make( 'internalconversations::partials/create-form-fields', [
|
|
'conversation' => $conversation,
|
|
'mailbox' => $mailbox,
|
|
'thread' => $thread,
|
|
'users' => $users,
|
|
] )->render();
|
|
}, 10, 3 );
|
|
|
|
\Eventy::addFilter( 'conversation.custom.identifier', function ( $styles ) {
|
|
return __( 'Internal conversation' );
|
|
} );
|
|
|
|
\Eventy::addFilter( 'conversation.reply_button.enabled', function ( $enabled, $conversation ) {
|
|
if ( ! ( $conversation instanceof Conversation ) ) {
|
|
return $enabled;
|
|
}
|
|
|
|
return $conversation->isCustom() ? false : $enabled;
|
|
}, 10, 2 );
|
|
|
|
// Add module's CSS file to the application layout.
|
|
\Eventy::addFilter( 'stylesheets', function ( $styles ) {
|
|
$styles[] = \Module::getPublicPath( 'internalconversations' ) . '/css/module.css';
|
|
|
|
return $styles;
|
|
} );
|
|
|
|
// Add module's JS file to the application layout.
|
|
\Eventy::addFilter( 'javascripts', function ( $javascripts ) {
|
|
$javascripts[] = \Module::getPublicPath( 'internalconversations' ) . '/js/laroute.js';
|
|
$javascripts[] = \Module::getPublicPath( 'internalconversations' ) . '/js/module.js';
|
|
|
|
return $javascripts;
|
|
} );
|
|
|
|
\Eventy::addAction( 'conversation.send_reply_save', function ( $conversation, $request ) {
|
|
if ( ! $conversation->isCustom() ) {
|
|
return;
|
|
}
|
|
|
|
$mailbox = $conversation->mailbox()->first();
|
|
|
|
/**
|
|
* @var Conversation $conversation
|
|
*/
|
|
$connectedUsers = $conversation->getMeta( 'internal_conversations.users', [] );
|
|
|
|
$users = $request->all()['users'] ?? [];
|
|
foreach ( $users as $user ) {
|
|
if ( ! $mailbox->userHasAccess( $user ) ) {
|
|
continue;
|
|
}
|
|
if ( ! in_array( (string) $user, $connectedUsers ) ) {
|
|
$connectedUsers[] = (string) $user;
|
|
}
|
|
}
|
|
|
|
if ( ! in_array( (string) auth()->user()->id, $connectedUsers ) ) {
|
|
$connectedUsers[] = (string) auth()->user()->id;
|
|
}
|
|
|
|
// Check for mentioned users in the message body
|
|
if ( class_exists( 'Modules\Mentions\Providers\MentionsServiceProvider' ) ) {
|
|
$body = $request->get( 'body', '' );
|
|
if ( $body ) {
|
|
$mentionedUsers = MentionsServiceProvider::getMentionedUsers( $body );
|
|
foreach ( $mentionedUsers as $userId ) {
|
|
// Check if user has mailbox access
|
|
if ( $mailbox->userHasAccess( $userId ) && ! in_array( (string) $userId, $connectedUsers ) ) {
|
|
$connectedUsers[] = (string) $userId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$conversation->setMeta( 'internal_conversations.users', $connectedUsers );
|
|
|
|
// Handle public conversation setting from form
|
|
if ( $request->has( 'internal_conversation_is_public' ) && $request->input( 'internal_conversation_is_public' ) ) {
|
|
$conversation->setMeta( 'internal_conversations.is_public', true );
|
|
} else {
|
|
$conversation->setMeta( 'internal_conversations.is_public', $conversation->getMeta( 'internal_conversations.is_public', false ) );
|
|
}
|
|
|
|
$conversation->last_reply_at = date( 'Y-m-d H:i:s' );
|
|
$conversation->last_reply_from = Conversation::PERSON_USER;
|
|
$conversation->save();
|
|
}, 10, 2 );
|
|
|
|
\Eventy::addAction( 'conversation.after_customer_sidebar', function ( $conversation ) {
|
|
if ( ! $conversation->isCustom() ) {
|
|
return;
|
|
}
|
|
/**
|
|
* @var Conversation $conversation
|
|
* @var Mailbox $mailbox
|
|
*/
|
|
$mailbox = $conversation->mailbox()->first();
|
|
|
|
$followers = [];
|
|
|
|
$users = $mailbox->usersAssignable( true );
|
|
|
|
$connectedUserIds = $conversation->getMeta( 'internal_conversations.users', [] );
|
|
$isPublic = $conversation->getMeta( 'internal_conversations.is_public', false );
|
|
|
|
// Check if current user has edit permission
|
|
$currentUserId = auth()->user()->id;
|
|
$canEditPublic = in_array( (string) $currentUserId, $connectedUserIds );
|
|
|
|
// Add followers first.
|
|
foreach ( $users as $i => $user ) {
|
|
foreach ( $connectedUserIds as $id ) {
|
|
if ( $id == $user->id ) {
|
|
$user->subscribed = true;
|
|
$followers[] = $user;
|
|
$users->forget( $i );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
foreach ( $users as $i => $user ) {
|
|
if ( ! in_array( $user->id, $connectedUserIds ) ) {
|
|
$user->subscribed = false;
|
|
$followers[] = $user;
|
|
}
|
|
}
|
|
|
|
echo \View::make( 'internalconversations::partials/sidebar', [
|
|
'conversation' => $conversation,
|
|
'followers' => $followers,
|
|
'canEditPublic' => $canEditPublic,
|
|
] )->render();
|
|
}, 10, 1 );
|
|
|
|
\Eventy::addFilter( 'folder.conversations_query', function ( $query, $folder, $user_id ) {
|
|
$customType = Conversation::TYPE_CUSTOM;
|
|
|
|
$teamIds = [];
|
|
if ( class_exists( Teams::class ) ) {
|
|
$teamsForUser = User::whereRaw( "last_name = '" . Teams::TEAM_USER_LAST_NAME . "' and email like '" . Teams::TEAM_USER_EMAIL_PREFIX . "%' and JSON_CONTAINS(emails,'\"$user_id\"','$')" )->get();
|
|
$teamIds = $teamsForUser->pluck( 'id' )->toArray();
|
|
}
|
|
$rawSqlBuilder = "JSON_CONTAINS(meta, '\"$user_id\"', '$.\"internal_conversations.users\"')";
|
|
|
|
foreach ( $teamIds as $teamId ) {
|
|
$rawSqlBuilder .= " OR JSON_CONTAINS(meta, '\"$teamId\"', '$.\"internal_conversations.users\"')";
|
|
}
|
|
|
|
if ( $folder->type == Folder::TYPE_DRAFTS ) {
|
|
$rawSqlBuilder .= ' OR (`conversations`.created_by_user_id = ' . $user_id . ' AND `conversations`.state = ' . Conversation::STATE_DRAFT . ')';
|
|
}
|
|
|
|
// Include public conversations
|
|
$rawSqlBuilder .= " OR JSON_EXTRACT(meta, '$.\"internal_conversations.is_public\"') = 'true'";
|
|
|
|
|
|
if ( $folder->type == Folder::TYPE_MINE ) {
|
|
$query_conversations = Conversation::where( 'user_id', $user_id )
|
|
->where( 'mailbox_id', $folder->mailbox_id )
|
|
->whereIn( 'status', [ Conversation::STATUS_ACTIVE, Conversation::STATUS_PENDING ] )
|
|
->where( 'state', Conversation::STATE_PUBLISHED );
|
|
|
|
} else if ( $folder->type == Folder::TYPE_ASSIGNED ) {
|
|
$query_conversations = $folder->conversations()
|
|
->where( 'user_id', '<>', $user_id )
|
|
->where( 'state', Conversation::STATE_PUBLISHED );
|
|
|
|
} else if ( $folder->type == Folder::TYPE_STARRED ) {
|
|
$starred_conversation_ids = Conversation::getUserStarredConversationIds( $folder->mailbox_id, $user_id );
|
|
$query_conversations = Conversation::whereIn( 'id', $starred_conversation_ids );
|
|
} else if ( $folder->isIndirect() ) {
|
|
$query_conversations = Conversation::select( 'conversations.*' )
|
|
->join( 'conversation_folder', 'conversations.id', '=', 'conversation_folder.conversation_id' )
|
|
->where( 'conversation_folder.folder_id', $folder->id );
|
|
if ( $folder->type != Folder::TYPE_DRAFTS ) {
|
|
$query_conversations->where( 'state', Conversation::STATE_PUBLISHED );
|
|
}
|
|
} else if ( $folder->type == Folder::TYPE_DELETED ) {
|
|
$query_conversations = $folder->conversations()->where( 'state', Conversation::STATE_DELETED );
|
|
} else {
|
|
$query_conversations = $folder->conversations()->where( 'state', Conversation::STATE_PUBLISHED );
|
|
}
|
|
|
|
|
|
$allowedConversations = $query_conversations->where( 'type', $customType )->whereRaw( $rawSqlBuilder )->pluck( 'id' )->toArray();
|
|
|
|
if ( count( $allowedConversations ) > 0 ) {
|
|
$query->whereRaw( "(`conversations`.type != $customType OR `conversations`.id IN (" . implode( ',', $allowedConversations ) . "))" );
|
|
} else {
|
|
$query->where( 'conversations.type', '!=', $customType );
|
|
}
|
|
|
|
|
|
return $query;
|
|
}, 10, 3 );
|
|
\Eventy::addFilter( 'globalmailbox.conversations_query', function ( $query, $folder, $user_id ) {
|
|
$customType = Conversation::TYPE_CUSTOM;
|
|
|
|
$teamIds = [];
|
|
if ( class_exists( Teams::class ) ) {
|
|
$teamsForUser = User::whereRaw( "last_name = '" . Teams::TEAM_USER_LAST_NAME . "' and email like '" . Teams::TEAM_USER_EMAIL_PREFIX . "%' and JSON_CONTAINS(emails,'\"$user_id\"','$')" )->get();
|
|
$teamIds = $teamsForUser->pluck( 'id' )->toArray();
|
|
}
|
|
$rawSqlBuilder = "JSON_CONTAINS(`conversations`.meta, '\"$user_id\"', '$.\"internal_conversations.users\"')";
|
|
|
|
foreach ( $teamIds as $teamId ) {
|
|
$rawSqlBuilder .= " OR JSON_CONTAINS(`conversations`.meta, '\"$teamId\"', '$.\"internal_conversations.users\"')";
|
|
}
|
|
|
|
if ( $folder->type == Folder::TYPE_DRAFTS ) {
|
|
$rawSqlBuilder .= ' OR (`conversations`.created_by_user_id = ' . $user_id . ' AND `conversations`.state = ' . Conversation::STATE_DRAFT . ')';
|
|
}
|
|
|
|
// Include public conversations
|
|
$rawSqlBuilder .= " OR JSON_EXTRACT(`conversations`.meta, '$.\"internal_conversations.is_public\"') = 'true'";
|
|
|
|
|
|
if ( is_int( $user_id ) ) {
|
|
$user = User::find( $user_id );
|
|
}
|
|
if ( empty( $mailbox_ids ) ) {
|
|
$mailbox_ids = $user->mailboxesIdsCanView();
|
|
}
|
|
if ( $folder->type == - 100 ) {
|
|
// Inbox.
|
|
$query_conversations = Conversation::whereIn( 'mailbox_id', $mailbox_ids )
|
|
->where( 'state', Conversation::STATE_PUBLISHED );
|
|
|
|
//} elseif ($folder->type == self::FOLDER_SENT) {
|
|
|
|
|
|
} else if ( $folder->type == Folder::TYPE_MINE ) {
|
|
// Get conversations from personal folder
|
|
$query_conversations = Conversation::where( 'user_id', $user->id )
|
|
->whereIn( 'status', [ Conversation::STATUS_ACTIVE, Conversation::STATUS_PENDING ] )
|
|
->where( 'state', Conversation::STATE_PUBLISHED );
|
|
|
|
} else if ( $folder->type == Folder::TYPE_ASSIGNED ) {
|
|
|
|
// Assigned - do not show my conversations
|
|
$query_conversations =
|
|
// This condition also removes from result records with user_id = null
|
|
Conversation::whereIn( 'mailbox_id', $mailbox_ids )
|
|
->where( 'user_id', '<>', $user->id )
|
|
->whereIn( 'status', [ Conversation::STATUS_ACTIVE, Conversation::STATUS_PENDING ] )
|
|
->where( 'state', Conversation::STATE_PUBLISHED );
|
|
|
|
} else if ( $folder->type == Folder::TYPE_STARRED ) {
|
|
$starred_conversation_ids = ConversationFolder::join( 'folders', 'conversation_folder.folder_id', '=', 'folders.id' )
|
|
->whereIn( 'folders.mailbox_id', $mailbox_ids )
|
|
->where( 'folders.user_id', $user->id )
|
|
->where( 'folders.type', Folder::TYPE_STARRED )
|
|
->pluck( 'conversation_folder.conversation_id' );
|
|
$query_conversations = Conversation::whereIn( 'id', $starred_conversation_ids );
|
|
|
|
} else if ( $folder->isIndirect() ) {
|
|
|
|
// Conversations are connected to folder via conversation_folder table.
|
|
$query_conversations = Conversation::select( 'conversations.*' )
|
|
->join( 'conversation_folder', 'conversations.id', '=', 'conversation_folder.conversation_id' )
|
|
->join( 'folders', 'conversation_folder.folder_id', '=', 'folders.id' )
|
|
->whereIn( 'folders.mailbox_id', $mailbox_ids )
|
|
->where( 'folders.type', $folder->type );
|
|
|
|
if ( $folder->type != Folder::TYPE_DRAFTS ) {
|
|
$query_conversations->where( 'state', Conversation::STATE_PUBLISHED );
|
|
}
|
|
|
|
} else if ( $folder->type == Folder::TYPE_DELETED ) {
|
|
$query_conversations = Conversation::whereIn( 'mailbox_id', $mailbox_ids )
|
|
->where( 'state', Conversation::STATE_DELETED );
|
|
} else if ( \Module::isActive( 'teams' ) && $folder->type == \Modules\Teams\Providers\TeamsServiceProvider::FOLDER_TYPE ) {
|
|
$team_id = - 1000 - $folder->id;
|
|
$query_conversations =
|
|
Conversation::whereIn( 'mailbox_id', $mailbox_ids )
|
|
->where( 'user_id', $team_id )
|
|
->whereIn( 'status', [ Conversation::STATUS_ACTIVE, Conversation::STATUS_PENDING ] )
|
|
->where( 'state', Conversation::STATE_PUBLISHED );
|
|
} else {
|
|
// Get all folders of this type.
|
|
$folder_ids = Folder::whereIn( 'mailbox_id', $mailbox_ids )
|
|
->where( 'type', $folder->type )
|
|
->pluck( 'id' );
|
|
$query_conversations = Conversation::whereIn( 'folder_id', $folder_ids )
|
|
->where( 'state', Conversation::STATE_PUBLISHED );
|
|
}
|
|
|
|
|
|
$allowedConversations = $query_conversations->where( 'conversations.type', $customType )->whereRaw( $rawSqlBuilder )->pluck( 'id' )->toArray();
|
|
|
|
if ( count( $allowedConversations ) > 0 ) {
|
|
$query->whereRaw( "(`conversations`.type != $customType OR `conversations`.id IN (" . implode( ',', $allowedConversations ) . "))" );
|
|
} else {
|
|
$query->where( 'conversations.type', '!=', $customType );
|
|
}
|
|
|
|
|
|
return $query;
|
|
}, 10, 3 );
|
|
\Eventy::addFilter( 'search.conversations.apply_filters', function ( $query, $filters, $q ) {
|
|
$user_id = auth()->user()->id;
|
|
$customType = Conversation::TYPE_CUSTOM;
|
|
|
|
$teamIds = [];
|
|
if ( class_exists( Teams::class ) ) {
|
|
$teamsForUser = User::whereRaw( "last_name = '" . Teams::TEAM_USER_LAST_NAME . "' and email like '" . Teams::TEAM_USER_EMAIL_PREFIX . "%' and JSON_CONTAINS(emails,'\"$user_id\"','$')" )->get();
|
|
$teamIds = $teamsForUser->pluck( 'id' )->toArray();
|
|
}
|
|
$rawSqlBuilder = "JSON_CONTAINS(`conversations`.meta, '\"$user_id\"', '$.\"internal_conversations.users\"')";
|
|
|
|
foreach ( $teamIds as $teamId ) {
|
|
$rawSqlBuilder .= " OR JSON_CONTAINS(`conversations`.meta, '\"$teamId\"', '$.\"internal_conversations.users\"')";
|
|
}
|
|
|
|
// Include public conversations
|
|
$rawSqlBuilder .= " OR JSON_EXTRACT(`conversations`.meta, '$.\"internal_conversations.is_public\"') = 'true'";
|
|
|
|
$query_conversations = Conversation::where( 'state', Conversation::STATE_PUBLISHED );
|
|
$allowedConversations = $query_conversations->where( 'type', $customType )->whereRaw( $rawSqlBuilder )->pluck( 'id' )->toArray();
|
|
|
|
if ( count( $allowedConversations ) > 0 ) {
|
|
$query->whereRaw( "(`conversations`.type != $customType OR `conversations`.id IN (" . implode( ',', $allowedConversations ) . "))" );
|
|
} else {
|
|
$query->where( 'conversations.type', '!=', $customType );
|
|
}
|
|
|
|
return $query;
|
|
}, 10, 3 );
|
|
|
|
\Eventy::addAction( 'javascript', function () {
|
|
if ( \Route::is( 'conversations.create' ) ) {
|
|
echo 'mentionsInitConv();';
|
|
}
|
|
} );
|
|
|
|
\Eventy::addAction( 'conversation.view.start', function ( Conversation $conversation, $request ) {
|
|
if ( ! $conversation->isCustom() ) {
|
|
return;
|
|
}
|
|
$user_id = auth()->user()->id;
|
|
|
|
// Check if conversation is public
|
|
$isPublic = $conversation->getMeta( 'internal_conversations.is_public', false );
|
|
if ( $isPublic ) {
|
|
// Public conversations are accessible to all users with mailbox access
|
|
$mailbox = $conversation->mailbox;
|
|
if ( $mailbox && $mailbox->userHasAccess( $user_id ) ) {
|
|
return; // Allow access
|
|
}
|
|
}
|
|
|
|
//redirect away if not allowed
|
|
$teamIds = [];
|
|
if ( class_exists( Teams::class ) ) {
|
|
$teamsForUser = User::whereRaw( "last_name = '" . Teams::TEAM_USER_LAST_NAME . "' and email like '" . Teams::TEAM_USER_EMAIL_PREFIX . "%' and JSON_CONTAINS(emails,'\"$user_id\"','$')" )->get();
|
|
$teamIds = $teamsForUser->pluck( 'id' )->toArray();
|
|
}
|
|
|
|
|
|
$connectedUsers = $conversation->getMeta( 'internal_conversations.users', [] );
|
|
|
|
if ( $conversation->state === Conversation::STATE_DRAFT ) {
|
|
$connectedUsers[] = $conversation->created_by_user_id;
|
|
}
|
|
|
|
if ( ! in_array( $user_id, $connectedUsers ) ) {
|
|
$teamIsConnected = false;
|
|
|
|
foreach ( $teamIds as $teamId ) {
|
|
if ( in_array( $teamId, $connectedUsers ) ) {
|
|
$teamIsConnected = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( $teamIsConnected === false ) {
|
|
abort( 403 );
|
|
}
|
|
}
|
|
}, 10, 2 );
|
|
|
|
if ( class_exists( 'Modules\Mentions\Providers\MentionsServiceProvider' ) ) {
|
|
\Eventy::addAction( 'conversation.note_added', function ( $conversation, $thread ) {
|
|
if ( ! $conversation->isCustom() ) {
|
|
return;
|
|
}
|
|
$mentionedUsers = MentionsServiceProvider::getMentionedUsers( $thread->body );
|
|
if ( count( $mentionedUsers ) === 0 ) {
|
|
return;
|
|
}
|
|
/** @var Conversation $conversation */
|
|
$connectedUsers = $conversation->getMeta( 'internal_conversations.users', [] );
|
|
foreach ( $mentionedUsers as $userId ) {
|
|
if ( in_array( (string) $userId, $connectedUsers ) ) {
|
|
continue;
|
|
}
|
|
$connectedUsers[] = (string) $userId;
|
|
}
|
|
$conversation->setMeta( 'internal_conversations.users', $connectedUsers );
|
|
// Ensure public state is preserved
|
|
$isPublic = $conversation->getMeta( 'internal_conversations.is_public', false );
|
|
$conversation->setMeta( 'internal_conversations.is_public', $isPublic );
|
|
$conversation->save();
|
|
}, 20, 2 );
|
|
}
|
|
|
|
\Eventy::addAction( 'thread.before_save_from_request', function ( Thread $thread, $request ) {
|
|
$conversationId = $thread->conversation_id;
|
|
$conversation = Conversation::find( $conversationId );
|
|
if ( ! $conversation->isCustom() ) {
|
|
return;
|
|
}
|
|
$thread->type = Thread::TYPE_NOTE;
|
|
}, 20, 2 );
|
|
|
|
ThumbsUpProvider::instance();
|
|
|
|
$this->registerEvents();
|
|
}
|
|
|
|
private function registerEvents() {
|
|
// Note added.
|
|
\Eventy::addAction( 'conversation.note_added', function ( Conversation $conversation, $thread ) {
|
|
if ( $conversation->isCustom() ) {
|
|
// If conversation is public, add the user to connected users
|
|
$isPublic = $conversation->getMeta( 'internal_conversations.is_public', false );
|
|
if ( $isPublic && $thread->created_by_user_id ) {
|
|
$connectedUsers = $conversation->getMeta( 'internal_conversations.users', [] );
|
|
$userId = (string) $thread->created_by_user_id;
|
|
|
|
if ( ! in_array( $userId, $connectedUsers ) ) {
|
|
$connectedUsers[] = $userId;
|
|
$conversation->setMeta( 'internal_conversations.users', $connectedUsers );
|
|
// Ensure public state is preserved
|
|
$conversation->setMeta( 'internal_conversations.is_public', true );
|
|
$conversation->save();
|
|
}
|
|
}
|
|
|
|
// Send notification to connected users
|
|
$actor = User::find($thread->created_by_user_id);
|
|
if ($actor) {
|
|
$isNewConversation = $conversation->threads()->count() <= 1;
|
|
$type = $isNewConversation ? 'new_conversation' : 'new_reply';
|
|
InternalNotification::sendToConnected($type, $actor, $conversation, $thread);
|
|
}
|
|
}
|
|
}, 20, 2 );
|
|
|
|
// Thumbs up given.
|
|
\Eventy::addAction( 'internal_conversation.thumbs_up', function ( Conversation $conversation, $thread, $userId ) {
|
|
$actor = User::find($userId);
|
|
$recipient = $thread->created_by_user()->first();
|
|
|
|
if ($actor && $recipient) {
|
|
InternalNotification::sendTo('thumbs_up', $actor, $conversation, $recipient, $thread);
|
|
}
|
|
}, 20, 3 );
|
|
|
|
\Eventy::addFilter( 'conversation.type_name', function ( $type ) {
|
|
if ( (int) $type === Conversation::TYPE_CUSTOM ) {
|
|
return __( 'Internal conversation' );
|
|
}
|
|
|
|
return $type;
|
|
}, 10, 1 );
|
|
}
|
|
|
|
/**
|
|
* Register the service provider.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register() {
|
|
$this->registerTranslations();
|
|
$this->overrideSendReplyToCustomerListener();
|
|
}
|
|
|
|
/**
|
|
* Workaround for the FreeScout core regression where
|
|
* App\Listeners\SendReplyToCustomer::handle() calls
|
|
* $conversation->customer->getMainEmail() without a null guard.
|
|
* Internal conversations have no customer, so the call produces
|
|
* a fatal error on UserCreatedConversation and UserReplied events.
|
|
*
|
|
* The container binding swaps the core listener for a subclass
|
|
* that short circuits on TYPE_CUSTOM conversations. Other listeners
|
|
* registered for the same events (SendNotificationToUsers,
|
|
* RefreshConversations) keep firing normally.
|
|
*
|
|
* Upstream tracking: https://github.com/freescout-help-desk/freescout/issues/5380
|
|
*
|
|
* Remove this override once FreeScout core adds a null guard.
|
|
*/
|
|
private function overrideSendReplyToCustomerListener() {
|
|
$this->app->bind( \App\Listeners\SendReplyToCustomer::class, function () {
|
|
return new class extends \App\Listeners\SendReplyToCustomer {
|
|
public function handle( $event ) {
|
|
$conversation = $event->conversation ?? null;
|
|
if ( $conversation && method_exists( $conversation, 'isCustom' ) && $conversation->isCustom() ) {
|
|
return;
|
|
}
|
|
parent::handle( $event );
|
|
}
|
|
};
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Register config.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function registerConfig() {
|
|
$this->publishes( [
|
|
__DIR__ . '/../Config/config.php' => config_path( 'internalconversations.php' ),
|
|
], 'config' );
|
|
$this->mergeConfigFrom(
|
|
__DIR__ . '/../Config/config.php', 'internalconversations'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register views.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function registerViews() {
|
|
$viewPath = resource_path( 'views/modules/internalconversations' );
|
|
|
|
$sourcePath = __DIR__ . '/../Resources/views';
|
|
|
|
$this->publishes( [
|
|
$sourcePath => $viewPath,
|
|
], 'views' );
|
|
|
|
$this->loadViewsFrom( array_merge( array_map( function ( $path ) {
|
|
return $path . '/modules/internalconversations';
|
|
}, \Config::get( 'view.paths' ) ), [ $sourcePath ] ), 'internalconversations' );
|
|
}
|
|
|
|
/**
|
|
* Register translations.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function registerTranslations() {
|
|
$this->loadJsonTranslationsFrom( __DIR__ . '/../Resources/lang' );
|
|
}
|
|
|
|
/**
|
|
* Register an additional directory of factories.
|
|
* @source https://github.com/sebastiaanluca/laravel-resource-flow/blob/develop/src/Modules/ModuleServiceProvider.php#L66
|
|
*/
|
|
public function registerFactories() {
|
|
if ( ! app()->environment( 'production' ) ) {
|
|
app( Factory::class )->load( __DIR__ . '/../Database/factories' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the services provided by the provider.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function provides() {
|
|
return [];
|
|
}
|
|
}
|