UPSTREAM BASELINE: nextcloud/spreed v22.0.12 (без изменений)
Type checking / changes (push) Has been cancelled
Type checking / test (push) Has been cancelled
Type checking / typescript-summary (push) Has been cancelled
Node tests / changes (push) Has been cancelled
Node tests / test (push) Has been cancelled
Node tests / test-summary (push) Has been cancelled

Источник: https://github.com/nextcloud/spreed/archive/refs/tags/v22.0.12.tar.gz
С этого коммита ветка официального Nextcloud Talk отрезана (решение владельца 2026-07-06).
Все дальнейшие изменения — только наши; версии релизов: 22.0.12-f7.N.
This commit is contained in:
2026-07-06 14:07:50 +00:00
commit 01acfa3b40
1716 changed files with 613013 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setRoomId(int $roomId)
* @method int getRoomId()
* @method void setMessageId(int $messageId)
* @method int getMessageId()
* @method void setMessageTime(int $messageTime)
* @method int getMessageTime()
* @method void setObjectType(string $objectType)
* @method string getObjectType()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @method void setActorId(string $actorId)
* @method string getActorId()
*/
class Attachment extends Entity {
public const TYPE_AUDIO = 'audio';
public const TYPE_DECK_CARD = 'deckcard';
public const TYPE_FILE = 'file';
public const TYPE_LOCATION = 'location';
public const TYPE_MEDIA = 'media';
public const TYPE_OTHER = 'other';
public const TYPE_POLL = 'poll';
public const TYPE_RECORDING = 'recording';
public const TYPE_VOICE = 'voice';
public const ATTACHMENTS_NONE = 0;
public const ATTACHMENTS_ATLEAST_ONE = 1;
/** @var int */
protected $roomId;
/** @var int */
protected $messageId;
/** @var int */
protected $messageTime;
/** @var string */
protected $objectType;
/** @var string */
protected $actorType;
/** @var string */
protected $actorId;
public function __construct() {
$this->addType('roomId', Types::BIGINT);
$this->addType('messageId', Types::BIGINT);
$this->addType('messageTime', Types::BIGINT);
$this->addType('objectType', Types::STRING);
$this->addType('actorType', Types::STRING);
$this->addType('actorId', Types::STRING);
}
/**
* @return array
*/
public function asArray(): array {
return [
'id' => $this->getId(),
'room_id' => $this->getRoomId(),
'message_id' => $this->getMessageId(),
'message_time' => $this->getMessageTime(),
'object_type' => $this->getObjectType(),
'actor_type' => $this->getActorType(),
'actor_id' => $this->getActorId(),
];
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\QBMapper;
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Attachment mapRowToEntity(array $row)
* @method Attachment findEntity(IQueryBuilder $query)
* @method list<Attachment> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Attachment>
*/
class AttachmentMapper extends QBMapper {
use TTransactional;
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_attachments', Attachment::class);
}
public function createAttachmentFromRow(array $row): Attachment {
return $this->mapRowToEntity([
'id' => (int)$row['id'],
'room_id' => (int)$row['room_id'],
'message_id' => (int)$row['message_id'],
'message_time' => (int)$row['message_time'],
'object_type' => (string)$row['object_type'],
'actor_type' => (string)$row['actor_type'],
'actor_id' => (string)$row['actor_id'],
]);
}
/**
* @return list<Attachment>
* @throws \OCP\DB\Exception
*/
public function getAttachmentsByType(int $roomId, string $objectType, int $offset, int $limit): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
->setMaxResults($limit)
->orderBy('id', 'DESC');
if ($offset > 0) {
$query->andWhere($query->expr()->lt('message_id', $query->createNamedParameter($offset)));
}
return $this->findEntities($query);
}
public function deleteByMessageId(int $messageId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('message_id', $query->createNamedParameter($messageId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
public function deleteByRoomId(int $roomId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
$this->atomic(static function () use ($query): void {
$query->executeStatement();
}, $this->db);
}
}
+181
View File
@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setRoomId(int $roomId)
* @method int getRoomId()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @method void setActorId(string $actorId)
* @method string getActorId()
* @method void setDisplayName(string $displayName)
* @method void setPin(string $pin)
* @method null|string getPin()
* @method void setParticipantType(int $participantType)
* @method int getParticipantType()
* @method void setFavorite(bool $favorite)
* @method bool isFavorite()
* @method void setNotificationLevel(int $notificationLevel)
* @method int getNotificationLevel()
* @method void setNotificationCalls(int $notificationCalls)
* @method int getNotificationCalls()
* @method void setLastJoinedCall(int $lastJoinedCall)
* @method int getLastJoinedCall()
* @method void setLastReadMessage(int $lastReadMessage)
* @method int getLastReadMessage()
* @method void setLastMentionMessage(int $lastMentionMessage)
* @method int getLastMentionMessage()
* @method void setLastMentionDirect(int $lastMentionDirect)
* @method int getLastMentionDirect()
* @method void setReadPrivacy(int $readPrivacy)
* @method int getReadPrivacy()
* @method void setPermissions(int $permissions)
* @method void setArchived(bool $archived)
* @method bool isArchived()
* @method void setImportant(bool $important)
* @method bool isImportant()
* @method void setSensitive(bool $sensitive)
* @method bool isSensitive()
* @internal
* @method int getPermissions()
* @method void setAccessToken(string $accessToken)
* @method null|string getAccessToken()
* @method void setRemoteId(string $remoteId)
* @method string getRemoteId()
* @method void setInvitedCloudId(string $invitedCloudId)
* @method string getInvitedCloudId()
* @method void setPhoneNumber(?string $phoneNumber)
* @method null|string getPhoneNumber()
* @method void setCallId(?string $callId)
* @method null|string getCallId()
* @method void setState(int $state)
* @method int getState()
* @method void setUnreadMessages(int $unreadMessages)
* @method int getUnreadMessages()
* @method void setLastAttendeeActivity(int $lastAttendeeActivity)
* @method int getLastAttendeeActivity()
* @method void setHasUnreadThreads(bool $hasUnreadThreads)
* @method bool getHasUnreadThreads()
* @method void setHasUnreadThreadMentions(bool $hasUnreadThreadMentions)
* @method bool getHasUnreadThreadMentions()
* @method void setHasUnreadThreadDirects(bool $hasUnreadThreadDirects)
* @method bool getHasUnreadThreadDirects()
*/
class Attendee extends Entity {
public const ACTOR_USERS = 'users';
public const ACTOR_GROUPS = 'groups';
public const ACTOR_GUESTS = 'guests';
public const ACTOR_EMAILS = 'emails';
public const ACTOR_CIRCLES = 'circles';
public const ACTOR_BRIDGED = 'bridged';
public const ACTOR_BOTS = 'bots';
public const ACTOR_FEDERATED_USERS = 'federated_users';
public const ACTOR_PHONES = 'phones';
// Special actor IDs
public const ACTOR_BOT_PREFIX = 'bot-';
public const ACTOR_ID_CLI = 'cli';
public const ACTOR_ID_SYSTEM = 'system';
public const ACTOR_ID_SAMPLE = 'sample';
public const ACTOR_ID_CHANGELOG = 'changelog';
public const PERMISSIONS_DEFAULT = 0;
public const PERMISSIONS_CUSTOM = 1;
public const PERMISSIONS_CALL_START = 2;
public const PERMISSIONS_CALL_JOIN = 4;
public const PERMISSIONS_LOBBY_IGNORE = 8;
public const PERMISSIONS_PUBLISH_AUDIO = 16;
public const PERMISSIONS_PUBLISH_VIDEO = 32;
public const PERMISSIONS_PUBLISH_SCREEN = 64;
public const PERMISSIONS_CHAT = 128;
public const PERMISSIONS_MAX_DEFAULT // Max int (when all permissions are granted as default)
= self::PERMISSIONS_CALL_START
| self::PERMISSIONS_CALL_JOIN
| self::PERMISSIONS_LOBBY_IGNORE
| self::PERMISSIONS_PUBLISH_AUDIO
| self::PERMISSIONS_PUBLISH_VIDEO
| self::PERMISSIONS_PUBLISH_SCREEN
| self::PERMISSIONS_CHAT
;
public const PERMISSIONS_MAX_CUSTOM = self::PERMISSIONS_MAX_DEFAULT | self::PERMISSIONS_CUSTOM; // Max int (when all permissions are granted as custom)
public const PERMISSIONS_MODIFY_SET = 'set';
public const PERMISSIONS_MODIFY_REMOVE = 'remove';
public const PERMISSIONS_MODIFY_ADD = 'add';
protected int $roomId = 0;
protected string $actorType = '';
protected string $actorId = '';
protected ?string $displayName = null;
protected ?string $pin = null;
protected int $participantType = 0;
protected bool $favorite = false;
protected int $notificationLevel = 0;
protected int $notificationCalls = 0;
protected bool $archived = false;
protected bool $important = false;
protected bool $sensitive = false;
protected int $lastJoinedCall = 0;
protected int $lastReadMessage = 0;
protected int $lastMentionMessage = 0;
protected int $lastMentionDirect = 0;
protected int $readPrivacy = 0;
protected int $permissions = 0;
protected ?string $accessToken = null;
protected ?string $remoteId = null;
protected ?string $invitedCloudId = null;
protected ?string $phoneNumber = null;
protected ?string $callId = null;
protected int $state = 0;
protected int $unreadMessages = 0;
protected int $lastAttendeeActivity = 0;
protected bool $hasUnreadThreads = false;
protected bool $hasUnreadThreadMentions = false;
protected bool $hasUnreadThreadDirects = false;
public function __construct() {
$this->addType('roomId', Types::BIGINT);
$this->addType('actorType', Types::STRING);
$this->addType('actorId', Types::STRING);
$this->addType('displayName', Types::STRING);
$this->addType('pin', Types::STRING);
$this->addType('participantType', Types::SMALLINT);
$this->addType('favorite', Types::BOOLEAN);
$this->addType('archived', Types::BOOLEAN);
$this->addType('important', Types::BOOLEAN);
$this->addType('sensitive', Types::BOOLEAN);
$this->addType('notificationLevel', Types::INTEGER);
$this->addType('notificationCalls', Types::INTEGER);
$this->addType('lastJoinedCall', Types::INTEGER);
$this->addType('lastReadMessage', Types::INTEGER);
$this->addType('lastMentionMessage', Types::INTEGER);
$this->addType('lastMentionDirect', Types::BIGINT);
$this->addType('readPrivacy', Types::SMALLINT);
$this->addType('permissions', Types::INTEGER);
$this->addType('accessToken', Types::STRING);
$this->addType('remoteId', Types::STRING);
$this->addType('invitedCloudId', Types::STRING);
$this->addType('phoneNumber', Types::STRING);
$this->addType('callId', Types::STRING);
$this->addType('state', Types::SMALLINT);
$this->addType('unreadMessages', Types::BIGINT);
$this->addType('lastAttendeeActivity', Types::BIGINT);
$this->addType('hasUnreadThreads', Types::BOOLEAN);
$this->addType('hasUnreadThreadMentions', Types::BOOLEAN);
$this->addType('hasUnreadThreadDirects', Types::BOOLEAN);
}
public function getDisplayName(): string {
return (string)$this->displayName;
}
}
+319
View File
@@ -0,0 +1,319 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Attendee mapRowToEntity(array $row)
* @method Attendee findEntity(IQueryBuilder $query)
* @method list<Attendee> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Attendee>
*/
class AttendeeMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_attendees', Attendee::class);
}
/**
* @throws DoesNotExistException
*/
public function findByActor(int $roomId, string $actorType, string $actorId): Attendee {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId)))
->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId)));
return $this->findEntity($query);
}
/**
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function getById(int $id): Attendee {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
/**
* @return list<Attendee>
*/
public function getByAccessToken(string $accessToken): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('access_token', $query->createNamedParameter($accessToken)));
// There could be multiple in case of local federation,
// so we have to get all and afterwards check
// the actor id for the serverUrl.
return $this->findEntities($query);
}
/**
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function getByRemoteIdAndToken(int $id, string $token): Attendee {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('remote_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('access_token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR)));
return $this->findEntity($query);
}
/**
* @return list<Attendee>
*/
public function getActorsByType(int $roomId, string $actorType, ?int $lastJoinedCall = null): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)));
if ($lastJoinedCall !== null) {
$query->andWhere($query->expr()->gte('last_joined_call', $query->createNamedParameter($lastJoinedCall, IQueryBuilder::PARAM_INT)));
}
return $this->findEntities($query);
}
/**
* @return list<Attendee>
*/
public function getActorsByTypes(int $roomId, array $actorTypes, ?int $lastJoinedCall = null): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->in('actor_type', $query->createNamedParameter($actorTypes, IQueryBuilder::PARAM_STR_ARRAY)));
if ($lastJoinedCall !== null) {
$query->andWhere($query->expr()->gte('last_joined_call', $query->createNamedParameter($lastJoinedCall, IQueryBuilder::PARAM_INT)));
}
return $this->findEntities($query);
}
/**
* @return list<Attendee>
* @throws DBException
*/
public function getActorsByParticipantTypes(int $roomId, array $participantType): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
if (!empty($participantType)) {
$query->andWhere($query->expr()->in('participant_type', $query->createNamedParameter($participantType, IQueryBuilder::PARAM_INT_ARRAY)));
}
return $this->findEntities($query);
}
public function getActorsCountByType(int $roomId, string $actorType, ?int $lastJoinedCall = null): int {
$query = $this->db->getQueryBuilder();
$query->select($query->func()->count('*', 'num_actors'))
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)));
if ($lastJoinedCall !== null) {
$query->andWhere($query->expr()->gte('last_joined_call', $query->createNamedParameter($lastJoinedCall, IQueryBuilder::PARAM_INT)));
}
$result = $query->executeQuery();
$count = (int)$result->fetchOne();
$result->closeCursor();
return $count;
}
/**
* @param int[] $participantType
*/
public function countActorsByParticipantType(int $roomId, array $participantType): int {
$query = $this->db->getQueryBuilder();
$query->select($query->func()->count('*', 'num_actors'))
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->notIn('actor_type', $query->createNamedParameter([
Attendee::ACTOR_CIRCLES,
Attendee::ACTOR_GROUPS,
], IQueryBuilder::PARAM_STR_ARRAY)));
if (!empty($participantType)) {
$query->andWhere($query->expr()->in('participant_type', $query->createNamedParameter($participantType, IQueryBuilder::PARAM_INT_ARRAY)));
}
$result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
return (int)($row['num_actors'] ?? 0);
}
/**
* @param int[] $ids
* @return int Number of deleted entities
*/
public function deleteByIds(array $ids): int {
$delete = $this->db->getQueryBuilder();
$delete->delete($this->getTableName())
->where($delete->expr()->in('id', $delete->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
return $delete->executeStatement();
}
public function modifyPermissions(int $roomId, string $mode, int $newState): void {
if ($mode === Attendee::PERMISSIONS_MODIFY_SET) {
if ($newState !== Attendee::PERMISSIONS_DEFAULT) {
$newState |= Attendee::PERMISSIONS_CUSTOM;
}
$query = $this->getModifyPermissionsBaseQuery($roomId);
$query->set('permissions', $query->createNamedParameter($newState, IQueryBuilder::PARAM_INT));
$query->executeStatement();
} else {
foreach ([
Attendee::PERMISSIONS_CALL_JOIN,
Attendee::PERMISSIONS_CALL_START,
Attendee::PERMISSIONS_PUBLISH_AUDIO,
Attendee::PERMISSIONS_PUBLISH_VIDEO,
Attendee::PERMISSIONS_PUBLISH_SCREEN,
Attendee::PERMISSIONS_LOBBY_IGNORE,
] as $permission) {
if ($permission & $newState) {
$query = $this->getModifyPermissionsBaseQuery($roomId);
if ($mode === Attendee::PERMISSIONS_MODIFY_ADD) {
$this->addSinglePermission($query, $permission);
} elseif ($mode === Attendee::PERMISSIONS_MODIFY_REMOVE) {
$this->removeSinglePermission($query, $permission);
}
}
}
}
}
protected function getModifyPermissionsBaseQuery(int $roomId): IQueryBuilder {
$query = $this->db->getQueryBuilder();
$query->update($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->notIn('actor_type', $query->createNamedParameter([
Attendee::ACTOR_CIRCLES,
Attendee::ACTOR_GROUPS,
], IQueryBuilder::PARAM_STR_ARRAY)));
return $query;
}
protected function addSinglePermission(IQueryBuilder $query, int $permission): void {
$query->set('permissions', $query->func()->add(
'permissions',
$query->createNamedParameter($permission, IQueryBuilder::PARAM_INT)
));
$query->andWhere(
$query->expr()->neq(
$query->expr()->castColumn(
$query->expr()->bitwiseAnd(
'permissions',
$permission
),
IQueryBuilder::PARAM_INT
),
$query->createNamedParameter($permission, IQueryBuilder::PARAM_INT)
)
);
$query->andWhere(
$query->expr()->neq(
'permissions',
$query->createNamedParameter(Attendee::PERMISSIONS_DEFAULT, IQueryBuilder::PARAM_INT)
)
);
$query->executeStatement();
}
protected function removeSinglePermission(IQueryBuilder $query, int $permission): void {
$query->set('permissions', $query->func()->subtract(
'permissions',
$query->createNamedParameter($permission, IQueryBuilder::PARAM_INT)
));
$query->andWhere(
$query->expr()->eq(
$query->expr()->castColumn(
$query->expr()->bitwiseAnd(
'permissions',
$permission
),
IQueryBuilder::PARAM_INT
),
$query->createNamedParameter($permission, IQueryBuilder::PARAM_INT)
)
);
// Removing permissions does not need to be explicitly prevented when
// the attendee has default permissions, as in that case it will not be
// possible to remove the permissions anyway.
$query->executeStatement();
}
public function createAttendeeFromRow(array $row): Attendee {
return $this->mapRowToEntity([
'id' => $row['a_id'],
'room_id' => $row['room_id'],
'actor_type' => $row['actor_type'],
'actor_id' => $row['actor_id'],
'display_name' => (string)$row['display_name'],
'pin' => $row['pin'],
'participant_type' => (int)$row['participant_type'],
'favorite' => (bool)$row['favorite'],
'notification_level' => (int)$row['notification_level'],
'notification_calls' => (int)$row['notification_calls'],
'last_joined_call' => (int)$row['last_joined_call'],
'last_read_message' => (int)$row['last_read_message'],
'last_mention_message' => (int)$row['last_mention_message'],
'last_mention_direct' => (int)$row['last_mention_direct'],
'read_privacy' => (int)$row['read_privacy'],
'permissions' => (int)$row['permissions'],
'access_token' => (string)$row['access_token'],
'remote_id' => (string)$row['remote_id'],
'invited_cloud_id' => (string)$row['invited_cloud_id'],
'phone_number' => $row['phone_number'],
'call_id' => $row['call_id'],
'state' => (int)$row['state'],
'unread_messages' => (int)$row['unread_messages'],
'last_attendee_activity' => (int)$row['last_attendee_activity'],
'archived' => (bool)$row['archived'],
'important' => (bool)$row['important'],
'sensitive' => (bool)$row['sensitive'],
'has_unread_threads' => (bool)$row['has_unread_threads'],
'has_unread_thread_mentions' => (bool)$row['has_unread_thread_mentions'],
'has_unread_thread_directs' => (bool)$row['has_unread_thread_directs'],
]);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @psalm-import-type TalkBan from ResponseDefinitions
*
* @method void setId(int $id)
* @method int getId()
* @method void setModeratorActorType(string $moderatorActorType)
* @method string getModeratorActorType()
* @method void setModeratorActorId(string $moderatorActorId)
* @method string getModeratorActorId()
* @method void setModeratorDisplayname(?string $moderatorDisplayname)
* @method null|string getModeratorDisplayname()
* @method void setRoomId(int $roomId)
* @method int getRoomId()
* @method void setBannedActorType(string $bannedActorType)
* @method string getBannedActorType()
* @method void setBannedActorId(string $bannedActorId)
* @method string getBannedActorId()
* @method void setBannedDisplayname(?string $bannedDisplayname)
* @method null|string getBannedDisplayname()
* @method void setBannedTime(\DateTime $bannedTime)
* @method \DateTime getBannedTime()
* @method void setInternalNote(null|string $internalNote)
* @method null|string getInternalNote()
*/
class Ban extends Entity implements \JsonSerializable {
public const NOTE_MAX_LENGTH = 4000;
protected string $moderatorActorType = '';
protected string $moderatorActorId = '';
protected ?string $moderatorDisplayname = null;
protected int $roomId = 0;
protected string $bannedActorType = '';
protected string $bannedActorId = '';
protected ?string $bannedDisplayname = null;
protected ?\DateTime $bannedTime = null;
protected ?string $internalNote = null;
public function __construct() {
$this->addType('id', Types::BIGINT);
$this->addType('moderatorActorType', Types::STRING);
$this->addType('moderatorActorId', Types::STRING);
$this->addType('moderatorDisplayname', Types::STRING);
$this->addType('roomId', Types::BIGINT);
$this->addType('bannedActorType', Types::STRING);
$this->addType('bannedActorId', Types::STRING);
$this->addType('bannedDisplayname', Types::STRING);
$this->addType('bannedTime', Types::DATETIME);
$this->addType('internalNote', Types::TEXT);
}
/**
* @return TalkBan
*/
#[\Override]
public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'moderatorActorType' => $this->getModeratorActorType(),
'moderatorActorId' => $this->getModeratorActorId(),
'moderatorDisplayName' => $this->getModeratorDisplayname() ?? $this->getModeratorActorId(),
'bannedActorType' => $this->getBannedActorType(),
'bannedActorId' => $this->getBannedActorId(),
'bannedDisplayName' => $this->getBannedDisplayname() ?? $this->getBannedActorId(),
'bannedTime' => $this->getBannedTime()->getTimestamp(),
'internalNote' => $this->getInternalNote() ?? '',
];
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Ban mapRowToEntity(array $row)
* @method Ban findEntity(IQueryBuilder $query)
* @method list<Ban> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Ban>
*/
class BanMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_bans', Ban::class);
}
/**
* @throws DoesNotExistException
*/
public function findForBannedActorAndRoom(string $bannedActorType, string $bannedActorId, int $roomId): Ban {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('banned_actor_type', $query->createNamedParameter($bannedActorType, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('banned_actor_id', $query->createNamedParameter($bannedActorId, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
/**
* @return list<Ban>
*/
public function findByRoomId(int $roomId, ?string $bannedActorType = null): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->orderBy('id', 'ASC');
if ($bannedActorType !== null) {
$query->andWhere($query->expr()->eq('banned_actor_type', $query->createNamedParameter($bannedActorType, IQueryBuilder::PARAM_STR)));
}
return $this->findEntities($query);
}
public function findByUserId(string $userId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('banned_actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('banned_actor_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->orderBy('id', 'ASC');
return $this->findEntities($query);
}
/**
* @throws DoesNotExistException
*/
public function findByBanIdAndRoom(int $banId, int $roomId): Ban {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($banId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
public function updateDisplayNameForActor(string $actorType, string $actorId, string $displayName): void {
$update = $this->db->getQueryBuilder();
$update->update($this->getTableName())
->set('moderator_displayname', $update->createNamedParameter($displayName))
->where($update->expr()->eq('moderator_actor_type', $update->createNamedParameter($actorType)))
->andWhere($update->expr()->eq('moderator_actor_id', $update->createNamedParameter($actorId)));
$update->executeStatement();
$update = $this->db->getQueryBuilder();
$update->update($this->getTableName())
->set('banned_displayname', $update->createNamedParameter($displayName))
->where($update->expr()->eq('banned_actor_type', $update->createNamedParameter($actorType)))
->andWhere($update->expr()->eq('banned_actor_id', $update->createNamedParameter($actorId)));
$update->executeStatement();
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
class Bot {
public const STATE_DISABLED = 0;
public const STATE_ENABLED = 1;
public const STATE_NO_SETUP = 2;
public const STATE_UNAVAILABLE = 3;
public const FEATURE_NONE = 0;
public const FEATURE_WEBHOOK = 1;
public const FEATURE_RESPONSE = 2;
public const FEATURE_EVENT = 4;
public const FEATURE_REACTION = 8;
public const FEATURE_LABEL_NONE = 'none';
public const FEATURE_LABEL_WEBHOOK = 'webhook';
public const FEATURE_LABEL_RESPONSE = 'response';
public const FEATURE_LABEL_EVENT = 'event';
public const FEATURE_LABEL_REACTION = 'reaction';
public const URL_APP_PREFIX = 'nextcloudapp://';
public const URL_RESPONSE_ONLY_PREFIX = 'responseonly://';
public const FEATURE_MAP = [
self::FEATURE_NONE => self::FEATURE_LABEL_NONE,
self::FEATURE_WEBHOOK => self::FEATURE_LABEL_WEBHOOK,
self::FEATURE_RESPONSE => self::FEATURE_LABEL_RESPONSE,
self::FEATURE_EVENT => self::FEATURE_LABEL_EVENT,
self::FEATURE_REACTION => self::FEATURE_LABEL_REACTION,
];
public function __construct(
protected BotServer $botServer,
protected BotConversation $botConversation,
) {
}
public function getBotServer(): BotServer {
return $this->botServer;
}
public function getBotConversation(): BotConversation {
return $this->botConversation;
}
public function isEnabled(): bool {
return $this->botServer->getState() !== self::STATE_DISABLED
&& $this->botConversation->getState() !== self::STATE_DISABLED;
}
public static function featureFlagsToLabels(int $flags): string {
if ($flags === self::FEATURE_NONE) {
return self::FEATURE_LABEL_NONE;
}
$features = [];
foreach (self::FEATURE_MAP as $flag => $label) {
if ($flags & $flag) {
$features[] = $label;
}
}
return implode(', ', $features);
}
public static function featureLabelsToFlags(array $labels): int {
$reverseMap = array_flip(self::FEATURE_MAP);
$flags = 0;
foreach ($labels as $label) {
if ($label === self::FEATURE_LABEL_NONE) {
return self::FEATURE_NONE;
}
if (isset($reverseMap[$label])) {
$flags += $reverseMap[$label];
}
}
return $flags;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setBotId(int $botId)
* @method int getBotId()
* @method void setToken(string $token)
* @method string getToken()
* @method void setState(int $state)
* @method int getState()
*/
class BotConversation extends Entity implements \JsonSerializable {
protected int $botId = 0;
protected string $token = '';
protected int $state = Bot::STATE_DISABLED;
public function __construct() {
$this->addType('bot_id', Types::BIGINT);
$this->addType('token', Types::STRING);
$this->addType('state', Types::SMALLINT);
}
#[\Override]
public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'bot_id' => $this->getBotId(),
'token' => $this->getToken(),
'state' => $this->getState(),
];
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\QBMapper;
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method BotConversation mapRowToEntity(array $row)
* @method BotConversation findEntity(IQueryBuilder $query)
* @method list<BotConversation> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<BotConversation>
*/
class BotConversationMapper extends QBMapper {
use TTransactional;
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_bots_conversation', BotConversation::class);
}
/**
* @return list<BotConversation>
*/
public function findForToken(string $token): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('token', $query->createNamedParameter($token)));
return $this->findEntities($query);
}
public function deleteByBotId(int $botId): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('bot_id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)));
return $query->executeStatement();
}
public function deleteByBotIdAndTokens(int $botId, array $tokens): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('bot_id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->in('token', $query->createNamedParameter($tokens, IQueryBuilder::PARAM_STR_ARRAY)));
return $query->executeStatement();
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setName(string $name)
* @method string getName()
* @method void setUrl(string $url)
* @method string getUrl()
* @method void setUrlHash(string $urlHash)
* @method string getUrlHash()
* @method void setDescription(?string $description)
* @method null|string getDescription()
* @method void setSecret(string $secret)
* @method string getSecret()
* @method void setErrorCount(int $errorCount)
* @method int getErrorCount()
* @method void setLastErrorDate(?\DateTimeImmutable $lastErrorDate)
* @method ?\DateTimeImmutable getLastErrorDate()
* @method void setLastErrorMessage(string $lastErrorMessage)
* @method string getLastErrorMessage()
* @method void setState(int $state)
* @method int getState()
* @method void setFeatures(int $features)
* @method int getFeatures()
*/
class BotServer extends Entity implements \JsonSerializable {
protected string $name = '';
protected string $url = '';
protected string $urlHash = '';
protected ?string $description = null;
protected string $secret = '';
protected int $errorCount = 0;
protected ?\DateTimeImmutable $lastErrorDate = null;
protected ?string $lastErrorMessage = null;
protected int $state = Bot::STATE_DISABLED;
protected int $features = Bot::FEATURE_NONE;
public function __construct() {
$this->addType('name', Types::STRING);
$this->addType('url', Types::STRING);
$this->addType('url_hash', Types::STRING);
$this->addType('description', Types::STRING);
$this->addType('secret', Types::STRING);
$this->addType('error_count', Types::BIGINT);
$this->addType('last_error_date', Types::DATETIME);
$this->addType('last_error_message', Types::STRING);
$this->addType('state', Types::SMALLINT);
$this->addType('features', Types::INTEGER);
}
/**
* @return array{
* id: int,
* name: string,
* url: string,
* url_hash: string,
* description: ?string,
* secret: string,
* error_count: int,
* last_error_date: int,
* last_error_message: string,
* state: int,
* features: int,
* }
*/
#[\Override]
public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'name' => $this->getName(),
'url' => $this->getUrl(),
'url_hash' => $this->getUrlHash(),
'description' => $this->getDescription(),
'secret' => $this->getSecret(),
'error_count' => $this->getErrorCount(),
'last_error_date' => $this->getLastErrorDate() ? $this->getLastErrorDate()->getTimestamp() : 0,
'last_error_message' => $this->getLastErrorMessage(),
'state' => $this->getState(),
'features' => $this->getFeatures(),
];
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method BotServer mapRowToEntity(array $row)
* @method BotServer findEntity(IQueryBuilder $query)
* @method list<BotServer> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<BotServer>
*/
class BotServerMapper extends QBMapper {
use TTransactional;
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_bots_server', BotServer::class);
}
/**
* @throws DoesNotExistException
*/
public function findById(int $botId): BotServer {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
/**
* @throws DoesNotExistException
*/
public function findByUrlAndSecret(string $url, string $secret): BotServer {
$urlHash = sha1($url);
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('url_hash', $query->createNamedParameter($urlHash)))
->andWhere($query->expr()->eq('secret', $query->createNamedParameter($secret)));
return $this->findEntity($query);
}
/**
* @throws DoesNotExistException
*/
public function findByUrl(string $url): BotServer {
return $this->findByUrlHash(sha1($url));
}
/**
* @throws DoesNotExistException
*/
public function findByUrlHash(string $urlHash): BotServer {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('url_hash', $query->createNamedParameter($urlHash)));
return $this->findEntity($query);
}
public function deleteById(int $botId): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)));
return $query->executeStatement();
}
/**
* @return list<BotServer>
*/
public function findByIds(array $botIds): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->in('id', $query->createNamedParameter($botIds, IQueryBuilder::PARAM_INT_ARRAY)));
return $this->findEntities($query);
}
/**
* @return list<BotServer>
*/
public function getAllBots(): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName());
return $this->findEntities($query);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
class BreakoutRoom {
public const MODE_NOT_CONFIGURED = 0;
public const MODE_AUTOMATIC = 1;
public const MODE_MANUAL = 2;
public const MODE_FREE = 3;
public const STATUS_STOPPED = 0;
public const STATUS_STARTED = 1;
public const STATUS_ASSISTANCE_RESET = 0;
public const STATUS_ASSISTANCE_REQUESTED = 2;
public const MINIMUM_ROOM_AMOUNT = 1;
public const MAXIMUM_ROOM_AMOUNT = 20;
public const PARENT_OBJECT_TYPE = 'room';
}
+46
View File
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setToken(string $token)
* @method string getToken()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @method void setActorId(string $actorId)
* @method string getActorId()
* @method void setDateTime(\DateTime $dateTime)
* @method \DateTime getDateTime()
*/
class Consent extends Entity implements \JsonSerializable {
protected string $token = '';
protected string $actorType = '';
protected string $actorId = '';
protected ?\DateTime $dateTime = null;
public function __construct() {
$this->addType('token', Types::STRING);
$this->addType('actorType', Types::STRING);
$this->addType('actorId', Types::STRING);
$this->addType('dateTime', Types::DATETIME);
}
#[\Override]
public function jsonSerialize(): array {
return [
'token' => $this->getToken(),
'actorType' => $this->getActorType(),
'actorId' => $this->getActorId(),
'timestamp' => $this->getDateTime()->getTimestamp(),
];
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Consent mapRowToEntity(array $row)
* @method Consent findEntity(IQueryBuilder $query)
* @method list<Consent> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Consent>
*/
class ConsentMapper extends QBMapper {
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_consent', Consent::class);
}
/**
* @return list<Consent>
*/
public function findForToken(string $token): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('token', $query->createNamedParameter($token)));
return $this->findEntities($query);
}
public function deleteByToken(string $token): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('token', $query->createNamedParameter($token)));
return $query->executeStatement();
}
/**
* @return list<Consent>
*/
public function findForActor(string $actorType, string $actorId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId)));
return $this->findEntities($query);
}
public function deleteByActor(string $actorType, string $actorId): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId)));
return $query->executeStatement();
}
/**
* @return list<Consent>
*/
public function findForTokenByActor(string $token, string $actorType, string $actorId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('token', $query->createNamedParameter($token)))
->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId)));
return $this->findEntities($query);
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setUserId(string $userId)
* @method string getUserId()
* @method void setState(int $state)
* @method int getState()
* @method void setLocalRoomId(int $localRoomId)
* @method int getLocalRoomId()
* @method void setAccessToken(string $accessToken)
* @method string getAccessToken()
* @method void setRemoteServerUrl(string $remoteServerUrl)
* @method string getRemoteServerUrl()
* @method void setRemoteToken(string $remoteToken)
* @method string getRemoteToken()
* @method void setRemoteAttendeeId(int $remoteAttendeeId)
* @method int getRemoteAttendeeId()
* @method void setInviterCloudId(string $inviterCloudId)
* @method string getInviterCloudId()
* @method void setInviterDisplayName(string $inviterDisplayName)
* @method string getInviterDisplayName()
* @method void setLocalCloudId(string $localCloudId)
* @method string getLocalCloudId()
*/
class Invitation extends Entity implements \JsonSerializable {
public const STATE_PENDING = 0;
public const STATE_ACCEPTED = 1;
protected string $userId = '';
protected int $state = self::STATE_PENDING;
protected int $localRoomId = 0;
protected string $accessToken = '';
protected string $remoteServerUrl = '';
protected string $remoteToken = '';
protected int $remoteAttendeeId = 0;
protected string $inviterCloudId = '';
protected string $inviterDisplayName = '';
protected string $localCloudId = '';
public function __construct() {
$this->addType('userId', Types::STRING);
$this->addType('state', Types::SMALLINT);
$this->addType('localRoomId', Types::BIGINT);
$this->addType('accessToken', Types::STRING);
$this->addType('remoteServerUrl', Types::STRING);
$this->addType('remoteToken', Types::STRING);
$this->addType('remoteAttendeeId', Types::BIGINT);
$this->addType('inviterCloudId', Types::STRING);
$this->addType('inviterDisplayName', Types::STRING);
$this->addType('localCloudId', Types::STRING);
}
/**
* @return array{id: int, localCloudId: string, remoteAttendeeId: int, remoteServerUrl: string, remoteToken: string, state: int, userId: string, inviterCloudId: string, inviterDisplayName: string}
*/
#[\Override]
public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'userId' => $this->getUserId(),
'state' => $this->getState(),
'localCloudId' => $this->getLocalCloudId(),
'remoteServerUrl' => $this->getRemoteServerUrl(),
'remoteToken' => $this->getRemoteToken(),
'remoteAttendeeId' => $this->getRemoteAttendeeId(),
'inviterCloudId' => $this->getInviterCloudId(),
'inviterDisplayName' => $this->getInviterDisplayName() ?: $this->getInviterCloudId(),
];
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Circles\Model\Circle;
use OCP\Federation\ICloudId;
use OCP\IGroup;
use OCP\IUser;
class InvitationList {
/** @var array<string, IUser> */
protected array $validUsers = [];
/** @var list<string> */
protected array $invalidUsers = [];
/** @var array<string, ICloudId> */
protected array $validFederatedUsers = [];
/** @var list<string> */
protected array $invalidFederatedUsers = [];
/** @var array<string, IGroup> */
protected array $validGroups = [];
/** @var list<string> */
protected array $invalidGroups = [];
/** @var array<string, Circle> */
protected array $validTeams = [];
/** @var list<string> */
protected array $invalidTeams = [];
/** @var array<string, string> */
protected array $validEmails = [];
/** @var list<string> */
protected array $invalidEmails = [];
/** @var array<string, string> */
protected array $validPhoneNumbers = [];
/** @var list<string> */
protected array $invalidPhoneNumbers = [];
/**
* @param array<string, IUser> $valid
* @param list<string> $invalid
*/
public function setUserResults(array $valid, array $invalid): void {
$this->validUsers = $valid;
$this->invalidUsers = $invalid;
}
/**
* @param array<string, ICloudId> $valid
* @param list<string> $invalid
*/
public function setFederatedUserResults(array $valid, array $invalid): void {
$this->validFederatedUsers = $valid;
$this->invalidFederatedUsers = $invalid;
}
/**
* @param array<string, IGroup> $valid
* @param list<string> $invalid
*/
public function setGroupResults(array $valid, array $invalid): void {
$this->validGroups = $valid;
$this->invalidGroups = $invalid;
}
/**
* @param array<string, Circle> $valid
* @param list<string> $invalid
*/
public function setTeamResults(array $valid, array $invalid): void {
$this->validTeams = $valid;
$this->invalidTeams = $invalid;
}
/**
* @param array<string, string> $valid
* @param list<string> $invalid
*/
public function setEmailResults(array $valid, array $invalid): void {
$this->validEmails = $valid;
$this->invalidEmails = $invalid;
}
/**
* @param array<string, string> $valid
* @param list<string> $invalid
*/
public function setPhoneNumberResults(array $valid, array $invalid): void {
$this->validPhoneNumbers = $valid;
$this->invalidPhoneNumbers = $invalid;
}
/**
* @return array<string, IUser>
*/
public function getUsers(): array {
return $this->validUsers;
}
/**
* @return array<string, ICloudId>
*/
public function getFederatedUsers(): array {
return $this->validFederatedUsers;
}
/**
* @return array<string, IGroup>
*/
public function getGroup(): array {
return $this->validGroups;
}
/**
* @return array<string, Circle>
*/
public function getTeams(): array {
return $this->validTeams;
}
/**
* @return array<string, string>
*/
public function getEmails(): array {
return $this->validEmails;
}
/**
* @return array<string, string>
*/
public function getPhoneNumbers(): array {
return $this->validPhoneNumbers;
}
/**
* @return array<'users'|'federated_users'|'groups'|'emails'|'phones'|'teams', list<string>>
*/
public function getInvalidList(): array {
$response = [
'users' => $this->invalidUsers,
'federated_users' => $this->invalidFederatedUsers,
'groups' => $this->invalidGroups,
'teams' => $this->invalidTeams,
'emails' => $this->invalidEmails,
'phones' => $this->invalidPhoneNumbers,
];
return array_filter($response);
}
public function hasValidInvitations(): bool {
return !empty($this->validUsers)
|| !empty($this->validFederatedUsers)
|| !empty($this->validGroups)
|| !empty($this->validTeams)
|| !empty($this->validEmails)
|| !empty($this->validPhoneNumbers);
}
public function hasInvalidInvitations(): bool {
return !empty($this->invalidUsers)
|| !empty($this->invalidFederatedUsers)
|| !empty($this->invalidGroups)
|| !empty($this->invalidTeams)
|| !empty($this->invalidEmails)
|| !empty($this->invalidPhoneNumbers);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\Room;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
use SensitiveParameter;
/**
* Class InvitationMapper
*
* @package OCA\Talk\Model
*
* @method Invitation mapRowToEntity(array $row)
* @method Invitation findEntity(IQueryBuilder $query)
* @method list<Invitation> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Invitation>
*/
class InvitationMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_invitations', Invitation::class);
}
/**
* @throws DoesNotExistException
*/
public function getInvitationById(int $id): Invitation {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
return $this->findEntity($qb);
}
/**
* @throws DoesNotExistException
* @internal Does not check user relation
*/
public function getByRemoteServerAndAccessToken(
string $remoteServerUrl,
#[SensitiveParameter]
string $accessToken,
): Invitation {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('remote_server_url', $qb->createNamedParameter($remoteServerUrl)))
->andWhere($qb->expr()->eq('access_token', $qb->createNamedParameter($accessToken)));
return $this->findEntity($qb);
}
/**
* @throws DoesNotExistException
*/
public function getByRemoteAndAccessToken(
string $remoteServerUrl,
int $remoteAttendeeId,
#[SensitiveParameter]
string $accessToken,
): Invitation {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('remote_server_url', $qb->createNamedParameter($remoteServerUrl)))
->andWhere($qb->expr()->eq('remote_attendee_id', $qb->createNamedParameter($remoteAttendeeId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('access_token', $qb->createNamedParameter($accessToken)));
return $this->findEntity($qb);
}
/**
* @param IUser $user
* @return list<Invitation>
*/
public function getInvitationsForUser(IUser $user): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID())));
return $this->findEntities($qb);
}
/**
* @psalm-param Invitation::STATE_*|null $state
*/
public function countInvitationsForUser(IUser $user, ?int $state = null): int {
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*'))
->from($this->getTableName())
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID())));
if ($state !== null) {
$qb->andWhere($qb->expr()->eq('state', $qb->createNamedParameter($state)));
}
$result = $qb->executeQuery();
$count = (int)$result->fetchOne();
$result->closeCursor();
return $count;
}
/**
* @throws DoesNotExistException
*/
public function getInvitationForUserByLocalRoom(Room $room, string $userId, bool $caseInsensitive = false): Invitation {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('local_room_id', $query->createNamedParameter($room->getId())));
if ($caseInsensitive) {
$query->andWhere($query->expr()->eq($query->func()->lower('user_id'), $query->createNamedParameter(strtolower($userId))));
} else {
$query->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
}
return $this->findEntity($query);
}
public function countInvitationsForLocalRoom(Room $room): int {
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*', 'num_invitations'))
->from($this->getTableName())
->where($qb->expr()->eq('local_room_id', $qb->createNamedParameter($room->getId())));
$result = $qb->executeQuery();
$row = $result->fetch();
$result->closeCursor();
return (int)($row['num_invitations'] ?? 0);
}
}
+243
View File
@@ -0,0 +1,243 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Participant;
use OCA\Talk\ResponseDefinitions;
use OCA\Talk\Room;
use OCP\Comments\IComment;
use OCP\IL10N;
/**
* @psalm-import-type TalkChatMessage from ResponseDefinitions
*/
class Message {
public const METADATA_LAST_EDITED_BY_TYPE = 'last_edited_by_type';
public const METADATA_LAST_EDITED_BY_ID = 'last_edited_by_id';
public const METADATA_LAST_EDITED_TIME = 'last_edited_time';
public const METADATA_SILENT = 'silent';
public const METADATA_CAN_MENTION_ALL = 'can_mention_all';
public const METADATA_THREAD_ID = 'thread_id';
/** @var bool */
protected $visible = true;
/** @var string */
protected $type = '';
/** @var string */
protected $message = '';
/** @var string */
protected $rawMessage = '';
/** @var array */
protected $parameters = [];
/** @var string */
protected $actorType = '';
/** @var string */
protected $actorId = '';
/** @var string */
protected $actorDisplayName = '';
/** @var string */
protected $lastEditActorType = '';
/** @var string */
protected $lastEditActorId = '';
/** @var string */
protected $lastEditActorDisplayName = '';
/** @var int */
protected $lastEditTimestamp = 0;
public function __construct(
protected Room $room,
protected ?Participant $participant,
protected ?IComment $comment,
protected IL10N $l,
protected ?ProxyCacheMessage $proxy = null,
) {
}
/*
* Meta information
*/
public function getRoom(): Room {
return $this->room;
}
public function getComment(): ?IComment {
return $this->comment;
}
public function getL10n(): IL10N {
return $this->l;
}
public function getParticipant(): ?Participant {
return $this->participant;
}
/*
* Parsed message information
*/
public function getMessageId(): int {
return $this->comment ? (int)$this->comment->getId() : $this->proxy->getRemoteMessageId();
}
public function getExpirationDateTime(): ?\DateTimeInterface {
return $this->comment ? $this->comment->getExpireDate() : $this->proxy->getExpirationDatetime();
}
public function setVisibility(bool $visible): void {
$this->visible = $visible;
}
public function getVisibility(): bool {
return $this->visible;
}
public function setMessage(string $message, array $parameters, string $rawMessage = ''): void {
$this->message = $message;
$this->parameters = $parameters;
$this->rawMessage = $rawMessage;
}
public function getMessage(): string {
return $this->message;
}
public function getMessageParameters(): array {
return $this->parameters;
}
public function getMessageRaw(): string {
return $this->rawMessage;
}
public function setMessageType(string $type): void {
$this->type = $type;
}
public function getMessageType(): string {
return $this->type;
}
public function setActor(string $type, string $id, string $displayName): void {
$this->actorType = $type;
$this->actorId = $id;
$this->actorDisplayName = $displayName;
}
public function setLastEdit(string $type, string $id, string $displayName, int $timestamp): void {
$this->lastEditActorType = $type;
$this->lastEditActorId = $id;
$this->lastEditActorDisplayName = $displayName;
$this->lastEditTimestamp = $timestamp;
}
public function getActorType(): string {
return $this->actorType;
}
public function getActorId(): string {
return $this->actorId;
}
public function getActorDisplayName(): string {
return $this->actorDisplayName;
}
/**
* Specifies whether a message can be replied to
*/
public function isReplyable(): bool {
return $this->getMessageType() !== ChatManager::VERB_SYSTEM
&& $this->getMessageType() !== ChatManager::VERB_COMMAND
&& $this->getMessageType() !== ChatManager::VERB_MESSAGE_DELETED
&& $this->getMessageType() !== ChatManager::VERB_REACTION
&& $this->getMessageType() !== ChatManager::VERB_REACTION_DELETED
&& \in_array($this->getActorType(), [
Attendee::ACTOR_USERS,
Attendee::ACTOR_FEDERATED_USERS,
Attendee::ACTOR_GUESTS,
Attendee::ACTOR_EMAILS,
Attendee::ACTOR_BOTS,
], true);
}
/**
* @param string $format
* @psalm-param 'json'|'xml' $format
* @return TalkChatMessage
*/
public function toArray(string $format, ?Thread $thread): array {
$expireDate = $this->getComment()->getExpireDate();
$reactions = $this->getComment()->getReactions();
if ($format === 'json' && empty($reactions)) {
// Cheating here to make sure the reactions array is always a
// JSON object on the API, even when there is no reaction at all.
$reactions = new \stdClass();
}
$id = (int)$this->getComment()->getId();
$threadId = (int)$this->getComment()->getTopmostParentId() ?: $id;
$data = [
'id' => $id,
'token' => $this->getRoom()->getToken(),
'actorType' => $this->getActorType(),
'actorId' => $this->getActorId(),
'actorDisplayName' => $this->getActorDisplayName(),
'timestamp' => $this->getComment()->getCreationDateTime()->getTimestamp(),
'message' => $this->getMessage(),
'messageParameters' => $this->getMessageParameters(),
'systemMessage' => $this->getMessageType() === ChatManager::VERB_SYSTEM ? $this->getMessageRaw() : '',
'messageType' => $this->getMessageType(),
'isReplyable' => $this->isReplyable(),
'referenceId' => (string)$this->getComment()->getReferenceId(),
'reactions' => $reactions,
'expirationTimestamp' => $expireDate ? $expireDate->getTimestamp() : 0,
'markdown' => $this->getMessageType() === ChatManager::VERB_SYSTEM ? false : true,
'threadId' => $threadId,
];
if ($thread !== null) {
$data['isThread'] = true;
$data['threadTitle'] = $thread->getName();
$data['threadReplies'] = $thread->getNumReplies();
}
if ($this->lastEditActorType && $this->lastEditActorId && $this->lastEditTimestamp) {
$data['lastEditActorType'] = $this->lastEditActorType;
$data['lastEditActorId'] = $this->lastEditActorId;
$data['lastEditActorDisplayName'] = $this->lastEditActorDisplayName;
$data['lastEditTimestamp'] = $this->lastEditTimestamp;
}
if ($this->getMessageType() === ChatManager::VERB_MESSAGE_DELETED) {
$data['deleted'] = true;
}
$metaData = $this->getComment()->getMetaData() ?? [];
if (!empty($metaData[self::METADATA_SILENT])) {
$data[self::METADATA_SILENT] = true;
}
return $data;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @psalm-method int<1, max> getId()
* @method void setPhoneNumber(string $phoneNumber)
* @method string getPhoneNumber()
* @method void setActorId(string $actorId)
* @method string getActorId()
*/
class PhoneNumber extends Entity {
protected string $phoneNumber = '';
protected string $actorId = '';
public function __construct() {
$this->addType('phoneNumber', Types::STRING);
$this->addType('actorId', Types::STRING);
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method PhoneNumber mapRowToEntity(array $row)
* @method PhoneNumber findEntity(IQueryBuilder $query)
* @method list<PhoneNumber> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<PhoneNumber>
*/
class PhoneNumberMapper extends QBMapper {
use TTransactional;
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_phone_numbers', PhoneNumber::class);
}
/**
* @throws DoesNotExistException
*/
public function findByPhoneNumber(string $phoneNumber): PhoneNumber {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('phone_number', $query->createNamedParameter($phoneNumber, IQueryBuilder::PARAM_STR)))
->orderBy('id', 'ASC');
return $this->findEntity($query);
}
/**
* @return list<PhoneNumber>
*/
public function findByUser(string $userId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('actor_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
return $this->findEntities($query);
}
/**
* @return list<PhoneNumber>
*/
public function findByPhoneNumbers(array $phoneNumbers): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->in('phone_number', $query->createNamedParameter($phoneNumbers, IQueryBuilder::PARAM_STR_ARRAY)));
return $this->findEntities($query);
}
public function deleteByPhoneNumber(string $phoneNumber): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('phone_number', $query->createNamedParameter($phoneNumber, IQueryBuilder::PARAM_STR)));
$query->executeStatement();
}
public function deleteByUser(string $userId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('actor_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
$query->executeStatement();
}
}
+176
View File
@@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\Exceptions\PollPropertyException;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @psalm-method int<1, max> getId()
* @method void setRoomId(int $roomId)
* @method int getRoomId()
* @psalm-method int<1, max> getRoomId()
* @method string getQuestion()
* @psalm-method non-empty-string getQuestion()
* @method string getOptions()
* @method void setVotes(string $votes)
* @method string getVotes()
* @method void setNumVoters(int $numVoters)
* @method int getNumVoters()
* @psalm-method int<0, max> getNumVoters()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @psalm-method TalkActorTypes getActorType()
* @method void setActorId(string $actorId)
* @method string getActorId()
* @psalm-method non-empty-string getActorId()
* @method void setDisplayName(string $displayName)
* @method string getDisplayName()
* @method void setStatus(int $status)
* @method int getStatus()
* @psalm-method self::STATUS_* getStatus()
* @method void setResultMode(int $resultMode)
* @method int getResultMode()
* @psalm-method self::MODE_* getResultMode()
* @method void setMaxVotes(int $maxVotes)
* @method int getMaxVotes()
* @psalm-method int<0, max> getMaxVotes()
*
* @psalm-import-type TalkActorTypes from ResponseDefinitions
* @psalm-import-type TalkPoll from ResponseDefinitions
* @psalm-import-type TalkPollDraft from ResponseDefinitions
*/
class Poll extends Entity {
public const STATUS_OPEN = 0;
public const STATUS_CLOSED = 1;
public const STATUS_DRAFT = 2;
public const MODE_PUBLIC = 0;
public const MODE_HIDDEN = 1;
public const MAX_VOTES_UNLIMITED = 0;
protected int $roomId = 0;
protected string $question = '';
protected string $options = '';
protected string $votes = '';
protected int $numVoters = 0;
protected string $actorType = '';
protected string $actorId = '';
protected ?string $displayName = null;
protected int $status = self::STATUS_OPEN;
protected int $resultMode = self::MODE_PUBLIC;
protected int $maxVotes = self::MAX_VOTES_UNLIMITED;
public function __construct() {
$this->addType('roomId', Types::BIGINT);
$this->addType('question', Types::TEXT);
$this->addType('options', Types::TEXT);
$this->addType('votes', Types::TEXT);
$this->addType('numVoters', Types::BIGINT);
$this->addType('actorType', Types::STRING);
$this->addType('actorId', Types::STRING);
$this->addType('displayName', Types::STRING);
$this->addType('status', Types::SMALLINT);
$this->addType('resultMode', Types::SMALLINT);
$this->addType('maxVotes', Types::INTEGER);
}
/**
* @return TalkPoll
*/
public function renderAsPoll(): array {
$data = $this->renderAsDraft();
$votes = json_decode($this->getVotes(), true, 512, JSON_THROW_ON_ERROR);
// Because PHP is turning arrays with sequent numeric keys "{"0":x,"1":y,"2":z}" into "[x,y,z]"
// when json_encode() is used we have to prefix the keys with a string,
// to prevent breaking in the mobile apps.
$data['votes'] = [];
foreach ($votes as $option => $count) {
$data['votes']['option-' . $option] = $count;
}
$data['numVoters'] = $this->getNumVoters();
return $data;
}
/**
* @return TalkPollDraft
*/
public function renderAsDraft(): array {
return [
'id' => $this->getId(),
// The room id is not needed on the API level but only internally for optimising database queries
// 'roomId' => $this->getRoomId(),
'question' => $this->getQuestion(),
'options' => json_decode($this->getOptions(), true, 512, JSON_THROW_ON_ERROR),
'actorType' => $this->getActorType(),
'actorId' => $this->getActorId(),
'actorDisplayName' => $this->getDisplayName(),
'status' => $this->getStatus(),
'resultMode' => $this->getResultMode(),
'maxVotes' => $this->getMaxVotes(),
];
}
public function isDraft(): bool {
return $this->getStatus() === self::STATUS_DRAFT;
}
/**
* @param array $options
* @return void
* @throws PollPropertyException
*/
public function setOptions(array $options): void {
try {
$jsonOptions = json_encode($options, JSON_THROW_ON_ERROR, 1);
} catch (\Exception) {
throw new PollPropertyException(PollPropertyException::REASON_OPTIONS);
}
$validOptions = [];
foreach ($options as $option) {
if (!is_string($option)) {
throw new PollPropertyException(PollPropertyException::REASON_OPTIONS);
}
$option = trim($option);
if ($option !== '') {
$validOptions[] = $option;
}
}
if (count($validOptions) < 2) {
throw new PollPropertyException(PollPropertyException::REASON_OPTIONS);
}
if (strlen($jsonOptions) > 60_000) {
throw new PollPropertyException(PollPropertyException::REASON_OPTIONS);
}
$this->setter('options', [$jsonOptions]);
}
/**
* @param string $question
* @return void
* @throws PollPropertyException
*/
public function setQuestion(string $question): void {
$question = trim($question);
if ($question === '' || strlen($question) > 32_000) {
throw new PollPropertyException(PollPropertyException::REASON_QUESTION);
}
$this->setter('question', [$question]);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Poll findEntity(IQueryBuilder $query)
* @method list<Poll> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Poll>
*/
class PollMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_polls', Poll::class);
}
/**
* @return list<Poll>
*/
public function getDraftsByRoomId(int $roomId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('status', $query->createNamedParameter(Poll::STATUS_DRAFT, IQueryBuilder::PARAM_INT)))
->orderBy('id', 'ASC');
return $this->findEntities($query);
}
/**
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function getPollByRoomIdAndPollId(int $roomId, int $pollId): Poll {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($pollId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
public function deleteByRoomId(int $roomId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
public function deleteByPollId(int $pollId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($pollId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
}
+116
View File
@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setLocalToken(string $localToken)
* @method string getLocalToken()
* @method void setRemoteServerUrl(string $remoteServerUrl)
* @method string getRemoteServerUrl()
* @method void setRemoteToken(string $remoteToken)
* @method string getRemoteToken()
* @method void setRemoteMessageId(int $remoteMessageId)
* @method int getRemoteMessageId()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @method void setActorId(string $actorId)
* @method string getActorId()
* @method void setActorDisplayName(?string $actorDisplayName)
* @method string|null getActorDisplayName()
* @method void setMessageType(string $messageType)
* @method string getMessageType()
* @method void setSystemMessage(?string $systemMessage)
* @method string|null getSystemMessage()
* @method void setExpirationDatetime(?\DateTime $expirationDatetime)
* @method \DateTime|null getExpirationDatetime()
* @method void setMessage(?string $message)
* @method string|null getMessage()
* @method void setMessageParameters(?string $messageParameters)
* @method string|null getMessageParameters()
* @method void setCreationDatetime(?\DateTime $creationDatetime)
* @method \DateTime|null getCreationDatetime()
* @method void setMetaData(?string $metaData)
* @method string|null getMetaData()
*
* @psalm-import-type TalkChatProxyMessage from ResponseDefinitions
*/
class ProxyCacheMessage extends Entity implements \JsonSerializable {
public const METADATA_REPLY_TO_ACTOR_TYPE = 'replyToActorType';
public const METADATA_REPLY_TO_ACTOR_ID = 'replyToActorId';
public const METADATA_REPLY_TO_MESSAGE_ID = 'replyToMessageId';
protected string $localToken = '';
protected string $remoteServerUrl = '';
protected string $remoteToken = '';
protected int $remoteMessageId = 0;
protected string $actorType = '';
protected string $actorId = '';
protected ?string $actorDisplayName = null;
protected ?string $messageType = null;
protected ?string $systemMessage = null;
protected ?\DateTime $expirationDatetime = null;
protected ?string $message = null;
protected ?string $messageParameters = null;
protected ?\DateTime $creationDatetime = null;
protected ?string $metaData = null;
public function __construct() {
$this->addType('localToken', Types::STRING);
$this->addType('remoteServerUrl', Types::STRING);
$this->addType('remoteToken', Types::STRING);
$this->addType('remoteMessageId', Types::BIGINT);
$this->addType('actorType', Types::STRING);
$this->addType('actorId', Types::STRING);
$this->addType('actorDisplayName', Types::STRING);
$this->addType('messageType', Types::STRING);
$this->addType('systemMessage', Types::STRING);
$this->addType('expirationDatetime', Types::DATETIME);
$this->addType('message', Types::TEXT);
$this->addType('messageParameters', Types::TEXT);
$this->addType('creationDatetime', Types::DATETIME);
$this->addType('metaData', Types::TEXT);
}
public function getParsedMessageParameters(): array {
return json_decode($this->getMessageParameters() ?? '[]', true);
}
public function getParsedMetaData(): array {
return json_decode($this->getMetaData() ?? '[]', true);
}
/**
* @return TalkChatProxyMessage
*/
#[\Override]
public function jsonSerialize(): array {
$expirationTimestamp = 0;
if ($this->getExpirationDatetime()) {
$expirationTimestamp = $this->getExpirationDatetime()->getTimestamp();
}
return [
'actorType' => $this->getActorType(),
'actorId' => $this->getActorId(),
'actorDisplayName' => $this->getActorDisplayName() ?? '',
'timestamp' => $this->getCreationDatetime()->getTimestamp(),
'expirationTimestamp' => $expirationTimestamp,
'messageType' => $this->getMessageType(),
'systemMessage' => $this->getSystemMessage() ?? '',
'message' => $this->getMessage() ?? '',
'messageParameters' => $this->getParsedMessageParameters(),
];
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\Exceptions\InvalidRoomException;
use OCA\Talk\Room;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method ProxyCacheMessage mapRowToEntity(array $row)
* @method ProxyCacheMessage findEntity(IQueryBuilder $query)
* @method list<ProxyCacheMessage> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<ProxyCacheMessage>
*/
class ProxyCacheMessageMapper extends QBMapper {
use TTransactional;
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_proxy_messages', ProxyCacheMessage::class);
}
/**
* @throws DoesNotExistException
*/
public function findById(Room $chat, int $proxyId): ProxyCacheMessage {
if (!$chat->isFederatedConversation()) {
throw new InvalidRoomException('Can not call ProxyCacheMessageMapper::findById() with a non-federated chat.');
}
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($proxyId, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
/**
* @throws DoesNotExistException
*/
public function findByRemote(string $remoteServerUrl, string $remoteToken, int $remoteMessageId): ProxyCacheMessage {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('remote_server_url', $query->createNamedParameter($remoteServerUrl, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('remote_token', $query->createNamedParameter($remoteToken, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('remote_message_id', $query->createNamedParameter($remoteMessageId, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
public function deleteExpiredMessages(\DateTimeInterface $dateTime): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->isNotNull('expiration_datetime'))
->andWhere($query->expr()->lte('expiration_datetime', $query->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE)));
return $query->executeStatement();
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setUserId(string $userId)
* @method string getUserId()
* @method void setToken(string $token)
* @method string getToken()
* @method void setMessageId(int $messageId)
* @method int getMessageId()
* @method void setDateTime(\DateTime $dateTime)
* @method \DateTime getDateTime()
*
* @psalm-import-type TalkChatReminder from ResponseDefinitions
*/
class Reminder extends Entity implements \JsonSerializable {
public const NUM_UPCOMING_REMINDERS = 10;
protected string $userId = '';
protected string $token = '';
protected int $messageId = 0;
protected ?\DateTime $dateTime = null;
public function __construct() {
$this->addType('userId', Types::STRING);
$this->addType('token', Types::STRING);
$this->addType('messageId', Types::BIGINT);
$this->addType('dateTime', Types::DATETIME);
}
/**
* @return TalkChatReminder
*/
#[\Override]
public function jsonSerialize(): array {
return [
'userId' => $this->getUserId(),
'token' => $this->getToken(),
'messageId' => $this->getMessageId(),
'timestamp' => $this->getDateTime()->getTimestamp(),
];
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Reminder mapRowToEntity(array $row)
* @method Reminder findEntity(IQueryBuilder $query)
* @method list<Reminder> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Reminder>
*/
class ReminderMapper extends QBMapper {
use TTransactional;
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_reminders', Reminder::class);
}
public function findForUser(string $userId, int $limit): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->orderBy('date_time', 'ASC')
->setMaxResults($limit);
return $this->findEntities($query);
}
/**
* @throws DoesNotExistException
*/
public function findForUserAndMessage(string $userId, string $token, int $messageId): Reminder {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR)))
->andWhere($query->expr()->eq('message_id', $query->createNamedParameter($messageId, IQueryBuilder::PARAM_INT)));
return $this->findEntity($query);
}
/**
* @return list<Reminder>
*/
public function findRemindersToExecute(\DateTime $dateTime): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->lt('date_time', $query->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE), IQueryBuilder::PARAM_DATE));
return $this->findEntities($query);
}
public function deleteExecutedReminders(\DateTime $dateTime): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->lt('date_time', $query->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE), IQueryBuilder::PARAM_DATE));
$query->executeStatement();
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setRemoteServer(string $remoteServer)
* @method string getRemoteServer()
* @method void setNumAttempts(int $numAttempts)
* @method int getNumAttempts()
* @method void setNextRetry(\DateTime $nextRetry)
* @method \DateTime getNextRetry()
* @method void setNotificationType(string $notificationType)
* @method string getNotificationType()
* @method void setResourceType(string $resourceType)
* @method string getResourceType()
* @method void setProviderId(string $providerId)
* @method string getProviderId()
* @method void setNotification(string $notification)
* @method string getNotification()
*/
class RetryNotification extends Entity {
public const MAX_NUM_ATTEMPTS = 20;
protected string $remoteServer = '';
protected int $numAttempts = 0;
protected ?\DateTime $nextRetry = null;
protected string $notificationType = '';
protected string $resourceType = '';
protected string $providerId = '';
protected string $notification = '';
public function __construct() {
$this->addType('remoteServer', Types::STRING);
$this->addType('numAttempts', Types::INTEGER);
$this->addType('nextRetry', Types::DATETIME);
$this->addType('notificationType', Types::STRING);
$this->addType('resourceType', Types::STRING);
$this->addType('providerId', Types::STRING);
$this->addType('notification', Types::TEXT);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method RetryNotification mapRowToEntity(array $row)
* @method RetryNotification findEntity(IQueryBuilder $query)
* @method list<RetryNotification> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<RetryNotification>
*/
class RetryNotificationMapper extends QBMapper {
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_retry_ocm', RetryNotification::class);
}
/**
* @return list<RetryNotification>
*/
public function getAllDue(\DateTimeInterface $dueDateTime, ?int $limit = 500): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->lte('next_retry', $query->createNamedParameter($dueDateTime, IQueryBuilder::PARAM_DATE), IQueryBuilder::PARAM_DATE));
if ($limit !== null) {
$query->setMaxResults($limit)
->orderBy('next_retry', 'ASC')
->addOrderBy('id', 'ASC');
}
return $this->findEntities($query);
}
public function deleteByProviderId($providerId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('provider_id', $query->createNamedParameter($providerId)));
$query->executeStatement();
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\DB\QueryBuilder\IQueryBuilder;
class SelectHelper {
public function selectRoomsTable(IQueryBuilder $query, string $alias = 'r'): void {
if ($alias !== '') {
$alias .= '.';
}
$query->addSelect($alias . 'type')
->addSelect($alias . 'read_only')
->addSelect($alias . 'lobby_state')
->addSelect($alias . 'sip_enabled')
->addSelect($alias . 'assigned_hpb')
->addSelect($alias . 'token')
->addSelect($alias . 'name')
->addSelect($alias . 'description')
->addSelect($alias . 'password')
->addSelect($alias . 'avatar')
->addSelect($alias . 'active_since')
->addSelect($alias . 'default_permissions')
->addSelect($alias . 'call_permissions')
->addSelect($alias . 'call_flag')
->addSelect($alias . 'last_activity')
->addSelect($alias . 'last_message')
->addSelect($alias . 'lobby_timer')
->addSelect($alias . 'object_type')
->addSelect($alias . 'object_id')
->addSelect($alias . 'listable')
->addSelect($alias . 'message_expiration')
->addSelect($alias . 'remote_server')
->addSelect($alias . 'remote_token')
->addSelect($alias . 'breakout_room_mode')
->addSelect($alias . 'breakout_room_status')
->addSelect($alias . 'call_recording')
->addSelect($alias . 'recording_consent')
->addSelect($alias . 'has_federation')
->addSelect($alias . 'mention_permissions')
->addSelect($alias . 'transcription_language')
->selectAlias($alias . 'id', 'r_id');
}
public function selectAttendeesTable(IQueryBuilder $query, string $alias = 'a'): void {
if ($alias !== '') {
$alias .= '.';
}
$query->addSelect($alias . 'room_id')
->addSelect($alias . 'actor_type')
->addSelect($alias . 'actor_id')
->addSelect($alias . 'display_name')
->addSelect($alias . 'pin')
->addSelect($alias . 'participant_type')
->addSelect($alias . 'favorite')
->addSelect($alias . 'notification_level')
->addSelect($alias . 'notification_calls')
->addSelect($alias . 'last_joined_call')
->addSelect($alias . 'last_read_message')
->addSelect($alias . 'last_mention_message')
->addSelect($alias . 'last_mention_direct')
->addSelect($alias . 'read_privacy')
->addSelect($alias . 'permissions')
->addSelect($alias . 'access_token')
->addSelect($alias . 'remote_id')
->addSelect($alias . 'invited_cloud_id')
->addSelect($alias . 'phone_number')
->addSelect($alias . 'call_id')
->addSelect($alias . 'state')
->addSelect($alias . 'unread_messages')
->addSelect($alias . 'last_attendee_activity')
->addSelect($alias . 'archived')
->addSelect($alias . 'important')
->addSelect($alias . 'sensitive')
->addSelect($alias . 'has_unread_threads')
->addSelect($alias . 'has_unread_thread_mentions')
->addSelect($alias . 'has_unread_thread_directs')
->selectAlias($alias . 'id', 'a_id');
}
public function selectSessionsTable(IQueryBuilder $query, string $alias = 's'): void {
if ($alias !== '') {
$alias .= '.';
}
$query->addSelect($alias . 'attendee_id')
->addSelect($alias . 'session_id')
->addSelect($alias . 'in_call')
->addSelect($alias . 'last_ping')
->selectAlias($alias . 'state', 's_state')
->selectAlias($alias . 'id', 's_id');
}
public function selectSessionsTableMax(IQueryBuilder $query, string $alias = 's'): void {
if ($alias !== '') {
$alias .= '.';
}
$query->selectAlias($query->func()->max($alias . 'attendee_id'), 'attendee_id')
->selectAlias($query->func()->max($alias . 'session_id'), 'session_id')
// BIT_OR would be better, but SQLite does not support something like it.
->selectAlias($query->func()->max($alias . 'in_call'), 'in_call')
->selectAlias($query->func()->max($alias . 'last_ping'), 'last_ping')
->selectAlias($query->func()->max($alias . 'state'), 's_state')
->selectAlias($query->func()->max($alias . 'id'), 's_id');
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* A session is the "I'm online in this conversation" state of Talk, you get one
* when opening the conversation while the inCall flag tells if you are just
* online (chatting), or in a call (with audio, camera or even sip).
*
* @method void setAttendeeId(int $attendeeId)
* @method string getAttendeeId()
* @method void setSessionId(string $sessionId)
* @method string getSessionId()
* @method void setInCall(int $inCall)
* @method int getInCall()
* @method void setLastPing(int $lastPing)
* @method int getLastPing()
* @method void setState(int $state)
* @method int getState()
*/
class Session extends Entity {
public const STATE_INACTIVE = 0;
public const STATE_ACTIVE = 1;
public const SESSION_TIMEOUT = 30;
public const SESSION_TIMEOUT_KILL = self::SESSION_TIMEOUT * 3 + 10;
/** @var int */
protected $attendeeId;
/** @var string */
protected $sessionId;
/** @var int */
protected $inCall;
/** @var int */
protected $lastPing;
/** @var int */
protected $state;
public function __construct() {
$this->addType('attendeeId', Types::BIGINT);
$this->addType('sessionId', Types::STRING);
$this->addType('inCall', Types::INTEGER);
$this->addType('lastPing', Types::INTEGER);
$this->addType('state', Types::SMALLINT);
}
/**
* @return array
*/
public function asArray(): array {
return [
'id' => $this->getId(),
'attendee_id' => $this->getAttendeeId(),
'session_id' => $this->getSessionId(),
'in_call' => $this->getInCall(),
'last_ping' => $this->getLastPing(),
];
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\Participant;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Session mapRowToEntity(array $row)
* @method Session findEntity(IQueryBuilder $query)
* @method list<Session> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Session>
*/
class SessionMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_sessions', Session::class);
}
/**
* @throws DoesNotExistException
*/
public function findBySessionId(string $sessionId): Session {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('session_id', $query->createNamedParameter($sessionId)));
return $this->findEntity($query);
}
/**
* @return list<Session>
*/
public function findByAttendeeId(int $attendeeId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('attendee_id', $query->createNamedParameter($attendeeId)));
return $this->findEntities($query);
}
/**
* @return int Number of deleted entities
*/
public function deleteByAttendeeId(int $attendeeId): int {
$delete = $this->db->getQueryBuilder();
$delete->delete($this->getTableName())
->where($delete->expr()->eq('attendee_id', $delete->createNamedParameter($attendeeId, IQueryBuilder::PARAM_INT)));
return $delete->executeStatement();
}
/**
* @param int[] $ids
* @return int Number of deleted entities
*/
public function deleteByIds(array $ids): int {
$delete = $this->db->getQueryBuilder();
$delete->delete($this->getTableName())
->where($delete->expr()->in('id', $delete->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
return $delete->executeStatement();
}
/**
* @param string[] $sessionIds
*/
public function resetInCallByIds(array $sessionIds): void {
$update = $this->db->getQueryBuilder();
$update->update($this->getTableName())
->set('in_call', $update->createNamedParameter(Participant::FLAG_DISCONNECTED, IQueryBuilder::PARAM_INT))
->where($update->expr()->in('session_id', $update->createNamedParameter($sessionIds, IQueryBuilder::PARAM_STR_ARRAY)));
$update->executeStatement();
}
public function createSessionFromRow(array $row): Session {
return $this->mapRowToEntity([
'id' => $row['s_id'],
'session_id' => $row['session_id'],
'attendee_id' => (int)$row['a_id'],
'in_call' => (int)$row['in_call'],
'last_ping' => (int)$row['last_ping'],
'state' => (int)$row['s_state'],
]);
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\ResponseDefinitions;
use OCA\Talk\Room;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setRoomId(int $roomId)
* @method int getRoomId()
* @method void setLastMessageId(int $lastMessageId)
* @method int getLastMessageId()
* @method void setNumReplies(int $numReplies)
* @method int getNumReplies()
* @method void setLastActivity(\DateTime $lastActivity)
* @method \DateTime|null getLastActivity()
* @method void setName(string $name)
*
* @psalm-import-type TalkThread from ResponseDefinitions
*/
class Thread extends Entity {
public const THREAD_NONE = 0;
public const THREAD_CREATE = -1;
protected int $roomId = 0;
protected int $lastMessageId = 0;
protected int $numReplies = 0;
protected ?\DateTime $lastActivity = null;
protected string $name = '';
public function __construct() {
$this->addType('roomId', Types::BIGINT);
$this->addType('lastMessageId', Types::BIGINT);
$this->addType('numReplies', Types::BIGINT);
$this->addType('lastActivity', Types::DATETIME);
$this->addType('name', Types::STRING);
}
public static function createFromRow(array $row): Thread {
$thread = new Thread();
$thread->setId((int)$row['t_id']);
$thread->setRoomId((int)$row['room_id']);
$thread->setLastMessageId((int)$row['last_message_id']);
$thread->setNumReplies((int)$row['num_replies']);
$thread->setLastActivity(new \DateTime($row['last_activity']));
$thread->setName($row['name']);
return $thread;
}
/**
* @param string $json
* @return Thread
* @throws \JsonException
*/
public static function fromJson(string $json): Thread {
$row = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
$thread = new Thread();
$thread->setId((int)$row['id']);
$thread->setRoomId((int)$row['room_id']);
$thread->setLastMessageId((int)$row['last_message_id']);
$thread->setNumReplies((int)$row['num_replies']);
$thread->setLastActivity(new \DateTime('@' . $row['last_activity']));
$thread->setName($row['name']);
return $thread;
}
/**
* @return string
* @throws \JsonException
*/
public function toJson(): string {
return json_encode([
'id' => $this->getId(),
'room_id' => $this->getRoomId(),
'last_message_id' => $this->getLastMessageId(),
'num_replies' => $this->getNumReplies(),
'last_activity' => $this->getLastActivity()?->getTimestamp() ?? 0,
'name' => $this->getName(),
], flags: JSON_THROW_ON_ERROR);
}
public function getName(): string {
if ($this->name !== '') {
return $this->name;
}
// FIXME temporary workaround against empty titles
return 'Thread #' . $this->getId();
}
/**
* @return TalkThread
*/
public function toArray(Room $room): array {
return [
'id' => max(1, $this->getId()),
// 'roomId' => max(1, $this->getRoomId()),
'roomToken' => $room->getToken(),
'lastMessageId' => max(0, $this->getLastMessageId()),
'numReplies' => max(0, $this->getNumReplies()),
'lastActivity' => max(0, $this->getLastActivity()?->getTimestamp() ?? 0),
'title' => $this->getName(),
];
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\Participant;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setRoomId(int $roomId)
* @method int getRoomId()
* @method void setThreadId(int $threadId)
* @method int getThreadId()
* @method void setAttendeeId(int $attendeeId)
* @method int getAttendeeId()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @method void setActorId(string $actorId)
* @method string getActorId()
* @method void setNotificationLevel(int $notificationLevel)
* @method int getNotificationLevel()
*
* @psalm-import-type TalkThreadAttendee from ResponseDefinitions
*/
class ThreadAttendee extends Entity implements \JsonSerializable {
protected int $roomId = 0;
protected int $threadId = 0;
protected int $attendeeId = 0;
protected string $actorType = '';
protected string $actorId = '';
protected int $notificationLevel = 0;
public function __construct() {
$this->addType('roomId', Types::BIGINT);
$this->addType('threadId', Types::BIGINT);
$this->addType('attendeeId', Types::BIGINT);
$this->addType('actorType', Types::STRING);
$this->addType('actorId', Types::STRING);
$this->addType('notificationLevel', Types::INTEGER);
}
public static function createFromRow(array $row): ThreadAttendee {
$attendee = new ThreadAttendee();
$attendee->setRoomId((int)$row['room_id']);
$attendee->setThreadId((int)$row['thread_id']);
$attendee->setAttendeeId((int)$row['attendee_id']);
$attendee->setNotificationLevel((int)$row['notification_level']);
$attendee->setActorType($row['actor_type']);
$attendee->setActorId($row['actor_id']);
return $attendee;
}
public static function createFromParticipant(int $threadId, Participant $participant): ThreadAttendee {
$attendee = new ThreadAttendee();
$attendee->setRoomId($participant->getRoom()->getId());
$attendee->setThreadId($threadId);
$attendee->setAttendeeId($participant->getAttendee()->getId());
$attendee->setNotificationLevel(Participant::NOTIFY_DEFAULT);
$attendee->setActorType($participant->getAttendee()->getActorType());
$attendee->setActorId($participant->getAttendee()->getActorId());
return $attendee;
}
/**
* @return TalkThreadAttendee
*/
#[\Override]
public function jsonSerialize(): array {
return [
'notificationLevel' => min(3, max(0, $this->getNotificationLevel())),
];
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\Participant;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method ThreadAttendee mapRowToEntity(array $row)
* @method ThreadAttendee findEntity(IQueryBuilder $query)
* @method list<ThreadAttendee> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<ThreadAttendee>
*/
class ThreadAttendeeMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_thread_attendees', ThreadAttendee::class);
}
public function deleteByRoomId(int $roomId): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
IQueryBuilder::PARAM_INT,
));
return $query->executeStatement();
}
/**
* @param list<int> $threadIds
* @return list<ThreadAttendee>
*/
public function findAttendeeByThreadIds(string $actorType, string $actorId, int $roomId, array $threadIds): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq(
'actor_type',
$query->createNamedParameter($actorType),
))
->andWhere($query->expr()->eq(
'actor_id',
$query->createNamedParameter($actorId),
))
->andWhere($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId),
))
->andWhere($query->expr()->in(
'thread_id',
$query->createNamedParameter($threadIds, IQueryBuilder::PARAM_INT_ARRAY),
IQueryBuilder::PARAM_INT_ARRAY,
));
return $this->findEntities($query);
}
/**
* @throws DoesNotExistException if the item does not exist
*/
public function findAttendeeByThreadId(string $actorType, string $actorId, int $roomId, int $threadId): ThreadAttendee {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq(
'actor_type',
$query->createNamedParameter($actorType),
))
->andWhere($query->expr()->eq(
'actor_id',
$query->createNamedParameter($actorId),
))
->andWhere($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId),
))
->andWhere($query->expr()->eq(
'thread_id',
$query->createNamedParameter($threadId),
));
return $this->findEntity($query);
}
/**
* @return list<ThreadAttendee>
*/
public function findAttendeesForNotification(int $roomId, int $threadId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
))
->andWhere($query->expr()->eq(
'thread_id',
$query->createNamedParameter($threadId, IQueryBuilder::PARAM_INT),
))
->andWhere($query->expr()->neq(
'notification_level',
$query->createNamedParameter(Participant::NOTIFY_DEFAULT, IQueryBuilder::PARAM_INT),
));
return $this->findEntities($query);
}
}
+126
View File
@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Thread findEntity(IQueryBuilder $query)
* @method list<Thread> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Thread>
*/
class ThreadMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_threads', Thread::class);
}
/**
* @param non-negative-int $roomId
* @param non-negative-int $threadId
* @throws DoesNotExistException
*/
public function findById(int $roomId, int $threadId): Thread {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq(
'id',
$query->createNamedParameter($threadId, IQueryBuilder::PARAM_INT),
IQueryBuilder::PARAM_INT,
))
->andWhere($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
IQueryBuilder::PARAM_INT,
));
return $this->findEntity($query);
}
/**
* @param non-negative-int $roomId
* @param list<non-negative-int> $threadIds
* @return list<Thread>
*/
public function findByIds(int $roomId, array $threadIds): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->in(
'id',
$query->createNamedParameter($threadIds, IQueryBuilder::PARAM_INT_ARRAY),
IQueryBuilder::PARAM_INT,
))
->andWhere($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
IQueryBuilder::PARAM_INT,
));
return $this->findEntities($query);
}
/**
* @param list<non-negative-int> $threadIds
* @return list<Thread>
*/
public function getForIds(array $threadIds): array {
$threads = [];
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName());
foreach (array_chunk($threadIds, 1000) as $ids) {
$query->where($query->expr()->in(
'id',
$query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY),
IQueryBuilder::PARAM_INT,
));
$threads[] = $this->findEntities($query);
}
return array_merge(...$threads);
}
/**
* @param int<1, 50> $limit
* @return list<Thread>
*/
public function getRecentByRoomId(int $roomId, int $limit): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
IQueryBuilder::PARAM_INT,
))
->orderBy('last_activity', 'DESC')
->setMaxResults($limit);
return $this->findEntities($query);
}
public function deleteByRoomId(int $roomId): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq(
'room_id',
$query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
IQueryBuilder::PARAM_INT,
));
return $query->executeStatement();
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* @method void setPollId(int $pollId)
* @method int getPollId()
* @method void setRoomId(int $roomId)
* @method int getRoomId()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @method void setActorId(string $actorId)
* @method string getActorId()
* @method void setDisplayName(string $displayName)
* @method string getDisplayName()
* @method void setOptionId(int $optionId)
* @method int getOptionId()
*
* @psalm-import-type TalkPollVote from ResponseDefinitions
*/
class Vote extends Entity {
protected int $pollId = 0;
protected int $roomId = 0;
protected string $actorType = '';
protected string $actorId = '';
protected ?string $displayName = null;
protected ?int $optionId = null;
public function __construct() {
$this->addType('pollId', Types::BIGINT);
$this->addType('roomId', Types::BIGINT);
$this->addType('actorType', Types::STRING);
$this->addType('actorId', Types::STRING);
$this->addType('displayName', Types::STRING);
$this->addType('optionId', Types::INTEGER);
}
/**
* @return TalkPollVote
*/
public function asArray(): array {
return [
// The ids are not needed on the API level but only internally for optimising database queries
// 'id' => $this->getId(),
// 'pollId' => $this->getPollId(),
// 'roomId' => $this->getRoomId(),
'actorType' => $this->getActorType(),
'actorId' => $this->getActorId(),
'actorDisplayName' => $this->getDisplayName(),
'optionId' => $this->getOptionId(),
];
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Vote findEntity(IQueryBuilder $query)
* @method list<Vote> findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Vote>
*/
class VoteMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_poll_votes', Vote::class);
}
/**
* @return list<Vote>
*/
public function findByPollId(int $pollId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('poll_id', $query->createNamedParameter($pollId)));
return $this->findEntities($query);
}
/**
* @return list<Vote>
*/
public function findByPollIdForActor(int $pollId, string $actorType, string $actorId): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('poll_id', $query->createNamedParameter($pollId)))
->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId)));
return $this->findEntities($query);
}
public function deleteByRoomId(int $roomId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
public function deleteByPollId(int $pollId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('poll_id', $query->createNamedParameter($pollId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
public function deleteVotesByActor(int $pollId, string $actorType, string $actorId): void {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
->where($query->expr()->eq('poll_id', $query->createNamedParameter($pollId)))
->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId)));
$query->executeStatement();
}
}