portal.mkgtu.ru/common/modules/abiturient/models/chat/ChatBase.php

562 lines
12 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace common\modules\abiturient\models\chat;
use backend\models\RBACAuthAssignment;
use common\components\DateTimeHelper;
use common\models\errors\RecordNotValid;
use common\models\notification\Notification;
use common\models\traits\HtmlPropsEncoder;
use common\models\User;
use Throwable;
use Yii;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use yii\web\Controller;
use yii\web\ServerErrorHttpException;
class ChatBase extends ActiveRecord
{
use HtmlPropsEncoder;
public const STATUS_STARTED = 1;
public const STATUS_ACTIVE = 2;
public const STATUS_ENDING = 3;
public const STATUS_OPEN_AGAIN = 4;
public const ID_FOR_NEW_CHAT = 'new';
public const ID_FOR_ARCHIVE_CHAT = 'archive';
public $chatUserClass;
public function init()
{
$this->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]);
}
}