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

562 lines
12 KiB
PHP
Raw Permalink Normal View History

2024-03-28 09:51:45 +03:00
<?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]);
}
}