chatUserClass = ChatUserBase::class; parent::init(); } public static function tableName() { return '{{%chat}}'; } public function behaviors() { return [TimestampBehavior::class]; } public function rules() { return [ [ [ 'type', 'status', 'created_at', 'updated_at', ], 'integer' ], [ 'status', 'default', 'value' => self::STATUS_STARTED, ] ]; } public function beforeDelete() { if (!parent::beforeDelete()) { return false; } foreach ($this->getChatHistories()->all() as $dataToDelete) { if (!$dataToDelete->delete()) { $errorFrom = "{$dataToDelete->tableName()} -> {$dataToDelete->id}\n"; Yii::error("Ошибка при удалении данных с портала. В таблице: {$errorFrom}"); return false; } } return true; } public function attributeLabels() { return []; } public function getChatHistories(): ActiveQuery { return $this->hasMany(ChatHistoryBase::class, ['chat_id' => 'id']); } public function getChatMessages(): ActiveQuery { return $this->hasMany(ChatMessageBase::class, ['chat_id' => 'id']); } public function getChatFiles(): ActiveQuery { return $this->hasMany(ChatFileBase::class, ['chat_id' => 'id']); } public function getChatUsers(): ActiveQuery { return $this->hasMany(ChatUserBase::class, ['chat_id' => 'id']); } public function getChatUserByUserId(int $userId): ?ChatUserBase { return $this ->getChatUsers() ->where(['user_id' => $userId]) ->one(); } public function getOrCreateChatUserByUserId(int $userId): ChatUserBase { $user = $this->getChatUserByUserId($userId); if ($user) { return $user; } $userRoles = RBACAuthAssignment::getRolesByUsersIds([$userId]); $transaction = Yii::$app->db->beginTransaction(); try { if (!$this->save()) { throw new RecordNotValid($this); } if (!$this->addUser((int)$userId, $userRoles)) { throw new ServerErrorHttpException('Не удалось создать пользователя для чата'); } $transaction->commit(); } catch (Throwable $th) { Yii::error("Ошибка получения пользователя: {$th->getMessage()}", 'ChatBase.getOrCreateChatUserByUserId'); $transaction->rollBack(); throw $th; } return $this->getChatUserByUserId($userId); } public static function getOrCreateChat(int $chatId, array $userIds): ChatBase { $chat = static::findOne($chatId); if (!$chat) { $chat = static::createNewChat($userIds); } return $chat; } public static function createNewChat(array $usersIds): ?ChatBase { $userRoles = RBACAuthAssignment::getRolesByUsersIds($usersIds); $transaction = Yii::$app->db->beginTransaction(); try { $chat = new static(); if (!$chat->save()) { throw new RecordNotValid($chat); } if (!$chat->addUsers($usersIds, $userRoles)) { throw new ServerErrorHttpException('Не удалось создать пользователя для чата'); } $transaction->commit(); } catch (Throwable $th) { Yii::error("Ошибка создания нового чата: {$th->getMessage()}", 'ChatBase.createNewChat'); $transaction->rollBack(); throw $th; } return $chat; } public function addUsers(array $usersIds, array $userRoles): bool { foreach ($usersIds as $userId) { $success = $this->addUser((int)$userId, $userRoles); if (!$success) { return false; } } return true; } public function addUser(int $userId, array $userRoles): bool { $this->chatUserClass = ChatUserBase::class; if (key_exists($userId, $userRoles)) { switch ($userRoles[$userId]) { case User::ROLE_MANAGER: $this->chatUserClass = ManagerChatUser::class; break; case User::ROLE_ABITURIENT: $this->chatUserClass = AbiturientChatUser::class; break; default: throw new ServerErrorHttpException('Не удалось определить роль пользователя чата'); } } $user = $this->chatUserClass::buildNewUser(User::findOne($userId)); $this->link('chatUsers', $user); if (!$user->save()) { throw new RecordNotValid($user); } return true; } public function addMessage(string $message, int $userId): bool { $user = $this->getOrCreateChatUserByUserId($userId); $message = ChatMessageBase::createNewMessage($this, $message, $user); $this->link('chatMessages', $message); if (!$message->save()) { throw new RecordNotValid($message); } return true; } public function addFiles(array $files, int $userId): bool { $user = Yii::$app->user->identity; $chatFilesClass = ChatFileBase::class; if ($user->isInRole(User::ROLE_MANAGER)) { $chatFilesClass = ManagerChatFile::class; } elseif ($user->isInRole(User::ROLE_ABITURIENT)) { $chatFilesClass = AbiturientChatFile::class; } $user = $this->getOrCreateChatUserByUserId($userId); $transaction = Yii::$app->db->beginTransaction(); try { $chatFilesClass::createNewFile($this, $files, $user); $transaction->commit(); } catch (Throwable $th) { Yii::error("Ошибка добавления файлов в чат: {$th->getMessage()}", 'ChatBase.addFiles'); $transaction->rollBack(); return false; } return true; } public function renderHistory($controller, $user = null): string { if (!$user) { $user = Yii::$app->user->identity; } $histories = $this->getChatHistories() ->with('message') ->with('message.author') ->orderBy(['created_at' => SORT_ASC]) ->all(); if (!$histories) { return ''; } $render = ''; foreach ($histories as $history) { $render .= $history->renderEvent($controller, $user); } $this->markAllFilesAsRead($user); $this->markAllMessagesAsRead($user); $this->markAllNotificationsAsRead($user); return $render; } private function markAllNotificationsAsRead(User $user): bool { $tnNotification = Notification::tableName(); $tnChatUser = ChatUserBase::tableName(); $sendersIdQuery = ChatUserBase::find() ->select("{$tnChatUser}.user_id") ->andWhere(["{$tnChatUser}.chat_id" => $this->id]) ->andWhere(['!=', "{$tnChatUser}.user_id", $user->id]); $transaction = Yii::$app->db->beginTransaction(); try { Notification::updateAll( [ 'read_at' => DateTimeHelper::mstime(), 'updated_at' => DateTimeHelper::mstime(), ], [ 'AND', [ "{$tnNotification}.shown" => true, "{$tnNotification}.category" => Notification::CATEGORY_CHAT, "{$tnNotification}.receiver_id" => $user->id, ], ['IN', "{$tnNotification}.read_at", [0, null]], ['IN', "{$tnNotification}.sender_id", $sendersIdQuery] ] ); $transaction->commit(); } catch (Throwable $th) { Yii::error("Ошибка пометки 'пузырей' как прочитанные: {$th->getMessage()}", 'ChatBase.markAllBlobsAsRead'); $transaction->rollBack(); return false; } return true; } private function markAllBlobsAsRead(User $user, string $blobClass): bool { $tnChatBlobBase = $blobClass::tableName(); $tnChatUser = ChatUserBase::tableName(); $chatUsersQuery = ChatUserBase::find() ->select("{$tnChatUser}.id") ->andWhere([ "{$tnChatUser}.chat_id" => $this->id, "{$tnChatUser}.user_id" => $user->id, ]); $transaction = Yii::$app->db->beginTransaction(); try { $blobClass::updateAll( [ 'updated_at' => time(), 'status' => $blobClass::STATUS_READ, ], [ 'and', [ "{$tnChatBlobBase}.chat_id" => $this->id, "{$tnChatBlobBase}.status" => $blobClass::STATUS_NEW, ], ['NOT IN', "{$tnChatBlobBase}.author_id", $chatUsersQuery] ] ); $transaction->commit(); } catch (Throwable $th) { Yii::error("Ошибка пометки 'пузырей' как прочитанные: {$th->getMessage()}", 'ChatBase.markAllBlobsAsRead'); $transaction->rollBack(); return false; } return true; } private function markAllFilesAsRead(User $user): bool { return static::markAllBlobsAsRead($user, ChatFileBase::class); } private function markAllMessagesAsRead(User $user): bool { return static::markAllBlobsAsRead($user, ChatMessageBase::class); } public function statusTransition(): void { if ($this->status == ChatBase::STATUS_ENDING) { $this->status = ChatBase::STATUS_OPEN_AGAIN; } if ($this->status == ChatBase::STATUS_STARTED) { $this->status = ChatBase::STATUS_ACTIVE; } } public function getCanStartingOrOpenAgain(): bool { return !in_array($this->status, [ChatBase::STATUS_ACTIVE, ChatBase::STATUS_OPEN_AGAIN]); } }