2020-06-02 06:48:37 -04:00
< ? php
declare ( strict_types = 1 );
/**
2024-05-29 05:32:54 -04:00
* SPDX - FileCopyrightText : 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX - License - Identifier : AGPL - 3.0 - or - later
2020-06-02 06:48:37 -04:00
*/
namespace OCA\UserStatus\Service ;
use OCA\UserStatus\Db\UserStatus ;
use OCA\UserStatus\Db\UserStatusMapper ;
use OCA\UserStatus\Exception\InvalidClearAtException ;
use OCA\UserStatus\Exception\InvalidMessageIdException ;
use OCA\UserStatus\Exception\InvalidStatusIconException ;
use OCA\UserStatus\Exception\InvalidStatusTypeException ;
use OCA\UserStatus\Exception\StatusMessageTooLongException ;
use OCP\AppFramework\Db\DoesNotExistException ;
use OCP\AppFramework\Utility\ITimeFactory ;
2022-02-15 08:55:40 -05:00
use OCP\DB\Exception ;
2021-07-08 12:26:27 -04:00
use OCP\IConfig ;
2022-03-24 10:57:17 -04:00
use OCP\IEmojiHelper ;
2023-09-25 08:47:02 -04:00
use OCP\IUserManager ;
2020-09-03 10:23:35 -04:00
use OCP\UserStatus\IUserStatus ;
2024-06-24 10:28:43 -04:00
use Psr\Log\LoggerInterface ;
2023-09-25 08:47:02 -04:00
use function in_array ;
2020-06-02 06:48:37 -04:00
/**
* Class StatusService
*
* @ package OCA\UserStatus\Service
*/
class StatusService {
2023-09-25 08:47:02 -04:00
private bool $shareeEnumeration ;
private bool $shareeEnumerationInGroupOnly ;
private bool $shareeEnumerationPhone ;
2021-07-08 12:26:27 -04:00
2020-09-02 05:49:14 -04:00
/**
* List of priorities ordered by their priority
*/
public const PRIORITY_ORDERED_STATUSES = [
2020-09-03 10:23:35 -04:00
IUserStatus :: ONLINE ,
IUserStatus :: AWAY ,
IUserStatus :: DND ,
2023-09-25 08:47:02 -04:00
IUserStatus :: BUSY ,
2020-09-03 10:23:35 -04:00
IUserStatus :: INVISIBLE ,
2021-08-11 04:36:24 -04:00
IUserStatus :: OFFLINE ,
2020-06-02 06:48:37 -04:00
];
2020-09-02 05:49:14 -04:00
/**
* List of statuses that persist the clear - up
* or UserLiveStatusEvents
*/
public const PERSISTENT_STATUSES = [
2020-09-03 10:23:35 -04:00
IUserStatus :: AWAY ,
2023-09-25 08:47:02 -04:00
IUserStatus :: BUSY ,
2020-09-03 10:23:35 -04:00
IUserStatus :: DND ,
IUserStatus :: INVISIBLE ,
2020-09-02 05:49:14 -04:00
];
/** @var int */
2020-09-30 10:17:18 -04:00
public const INVALIDATE_STATUS_THRESHOLD = 15 /* minutes */ * 60 /* seconds */ ;
2020-09-02 05:49:14 -04:00
2020-06-02 06:48:37 -04:00
/** @var int */
2020-09-02 05:49:14 -04:00
public const MAXIMUM_MESSAGE_LENGTH = 80 ;
2020-06-02 06:48:37 -04:00
2024-06-24 10:28:43 -04:00
public function __construct (
private UserStatusMapper $mapper ,
2023-11-23 04:22:34 -05:00
private ITimeFactory $timeFactory ,
private PredefinedStatusService $predefinedStatusService ,
private IEmojiHelper $emojiHelper ,
private IConfig $config ,
2024-06-24 10:28:43 -04:00
private IUserManager $userManager ,
private LoggerInterface $logger ,
) {
2023-09-25 08:47:02 -04:00
$this -> shareeEnumeration = $this -> config -> getAppValue ( 'core' , 'shareapi_allow_share_dialog_user_enumeration' , 'yes' ) === 'yes' ;
$this -> shareeEnumerationInGroupOnly = $this -> shareeEnumeration && $this -> config -> getAppValue ( 'core' , 'shareapi_restrict_user_enumeration_to_group' , 'no' ) === 'yes' ;
$this -> shareeEnumerationPhone = $this -> shareeEnumeration && $this -> config -> getAppValue ( 'core' , 'shareapi_restrict_user_enumeration_to_phone' , 'no' ) === 'yes' ;
2020-06-02 06:48:37 -04:00
}
/**
* @ param int | null $limit
* @ param int | null $offset
* @ return UserStatus []
*/
public function findAll ( ? int $limit = null , ? int $offset = null ) : array {
2021-07-08 12:26:27 -04:00
// Return empty array if user enumeration is disabled or limited to groups
// TODO: find a solution that scales to get only users from common groups if user enumeration is limited to
// groups. See discussion at https://github.com/nextcloud/server/pull/27879#discussion_r729715936
if ( ! $this -> shareeEnumeration || $this -> shareeEnumerationInGroupOnly || $this -> shareeEnumerationPhone ) {
return [];
}
2020-06-02 06:48:37 -04:00
return array_map ( function ( $status ) {
return $this -> processStatus ( $status );
}, $this -> mapper -> findAll ( $limit , $offset ));
}
2020-08-18 04:54:46 -04:00
/**
* @ param int | null $limit
* @ param int | null $offset
* @ return array
*/
public function findAllRecentStatusChanges ( ? int $limit = null , ? int $offset = null ) : array {
2021-07-08 12:26:27 -04:00
// Return empty array if user enumeration is disabled or limited to groups
// TODO: find a solution that scales to get only users from common groups if user enumeration is limited to
// groups. See discussion at https://github.com/nextcloud/server/pull/27879#discussion_r729715936
if ( ! $this -> shareeEnumeration || $this -> shareeEnumerationInGroupOnly || $this -> shareeEnumerationPhone ) {
return [];
}
2020-08-18 04:54:46 -04:00
return array_map ( function ( $status ) {
return $this -> processStatus ( $status );
}, $this -> mapper -> findAllRecent ( $limit , $offset ));
}
2020-06-02 06:48:37 -04:00
/**
* @ param string $userId
* @ return UserStatus
* @ throws DoesNotExistException
*/
2023-11-30 05:53:47 -05:00
public function findByUserId ( string $userId ) : UserStatus {
2023-11-23 19:49:30 -05:00
return $this -> processStatus ( $this -> mapper -> findByUserId ( $userId ));
2020-06-02 06:48:37 -04:00
}
2020-08-04 13:34:55 -04:00
/**
* @ param array $userIds
* @ return UserStatus []
*/
public function findByUserIds ( array $userIds ) : array {
return array_map ( function ( $status ) {
return $this -> processStatus ( $status );
}, $this -> mapper -> findByUserIds ( $userIds ));
}
2020-06-02 06:48:37 -04:00
/**
* @ param string $userId
* @ param string $status
* @ param int | null $statusTimestamp
* @ param bool $isUserDefined
* @ return UserStatus
* @ throws InvalidStatusTypeException
*/
public function setStatus ( string $userId ,
2023-11-23 04:22:34 -05:00
string $status ,
? int $statusTimestamp ,
bool $isUserDefined ) : UserStatus {
2020-06-02 06:48:37 -04:00
try {
$userStatus = $this -> mapper -> findByUserId ( $userId );
} catch ( DoesNotExistException $ex ) {
$userStatus = new UserStatus ();
$userStatus -> setUserId ( $userId );
}
// Check if status-type is valid
2023-09-25 08:47:02 -04:00
if ( ! in_array ( $status , self :: PRIORITY_ORDERED_STATUSES , true )) {
2020-06-02 06:48:37 -04:00
throw new InvalidStatusTypeException ( 'Status-type "' . $status . '" is not supported' );
}
2023-09-25 08:47:02 -04:00
2020-06-02 06:48:37 -04:00
if ( $statusTimestamp === null ) {
$statusTimestamp = $this -> timeFactory -> getTime ();
}
$userStatus -> setStatus ( $status );
$userStatus -> setStatusTimestamp ( $statusTimestamp );
$userStatus -> setIsUserDefined ( $isUserDefined );
2021-08-11 04:36:24 -04:00
$userStatus -> setIsBackup ( false );
2020-06-02 06:48:37 -04:00
if ( $userStatus -> getId () === null ) {
2025-05-30 02:53:18 -04:00
return $this -> insertWithoutThrowingUniqueConstrain ( $userStatus );
2020-06-02 06:48:37 -04:00
}
return $this -> mapper -> update ( $userStatus );
}
/**
* @ param string $userId
* @ param string $messageId
* @ param int | null $clearAt
* @ return UserStatus
* @ throws InvalidMessageIdException
* @ throws InvalidClearAtException
*/
public function setPredefinedMessage ( string $userId ,
2023-11-23 04:22:34 -05:00
string $messageId ,
? int $clearAt ) : UserStatus {
2020-06-02 06:48:37 -04:00
try {
$userStatus = $this -> mapper -> findByUserId ( $userId );
} catch ( DoesNotExistException $ex ) {
$userStatus = new UserStatus ();
$userStatus -> setUserId ( $userId );
2020-09-03 10:23:35 -04:00
$userStatus -> setStatus ( IUserStatus :: OFFLINE );
2020-06-02 06:48:37 -04:00
$userStatus -> setStatusTimestamp ( 0 );
$userStatus -> setIsUserDefined ( false );
2022-02-15 09:28:55 -05:00
$userStatus -> setIsBackup ( false );
2020-06-02 06:48:37 -04:00
}
if ( ! $this -> predefinedStatusService -> isValidId ( $messageId )) {
throw new InvalidMessageIdException ( 'Message-Id "' . $messageId . '" is not supported' );
}
// Check that clearAt is in the future
if ( $clearAt !== null && $clearAt < $this -> timeFactory -> getTime ()) {
throw new InvalidClearAtException ( 'ClearAt is in the past' );
}
$userStatus -> setMessageId ( $messageId );
$userStatus -> setCustomIcon ( null );
$userStatus -> setCustomMessage ( null );
$userStatus -> setClearAt ( $clearAt );
2023-09-21 11:05:06 -04:00
$userStatus -> setStatusMessageTimestamp ( $this -> timeFactory -> now () -> getTimestamp ());
2020-06-02 06:48:37 -04:00
if ( $userStatus -> getId () === null ) {
2025-05-30 02:53:18 -04:00
return $this -> insertWithoutThrowingUniqueConstrain ( $userStatus );
2020-06-02 06:48:37 -04:00
}
return $this -> mapper -> update ( $userStatus );
}
2022-02-15 09:28:55 -05:00
/**
* @ param string $userId
* @ param string $status
* @ param string $messageId
* @ param bool $createBackup
2023-11-23 19:49:30 -05:00
* @ param string | null $customMessage
2022-02-15 09:28:55 -05:00
* @ throws InvalidStatusTypeException
* @ throws InvalidMessageIdException
*/
public function setUserStatus ( string $userId ,
2023-11-23 04:22:34 -05:00
string $status ,
string $messageId ,
bool $createBackup ,
2023-11-23 19:49:30 -05:00
? string $customMessage = null ) : ? UserStatus {
2022-02-15 09:28:55 -05:00
// Check if status-type is valid
2023-09-25 08:47:02 -04:00
if ( ! in_array ( $status , self :: PRIORITY_ORDERED_STATUSES , true )) {
2022-02-15 09:28:55 -05:00
throw new InvalidStatusTypeException ( 'Status-type "' . $status . '" is not supported' );
}
if ( ! $this -> predefinedStatusService -> isValidId ( $messageId )) {
throw new InvalidMessageIdException ( 'Message-Id "' . $messageId . '" is not supported' );
}
2024-01-17 10:15:15 -05:00
try {
$userStatus = $this -> mapper -> findByUserId ( $userId );
} catch ( DoesNotExistException $e ) {
// We don't need to do anything
$userStatus = new UserStatus ();
$userStatus -> setUserId ( $userId );
}
2024-06-24 10:28:43 -04:00
$updateStatus = false ;
if ( $messageId === IUserStatus :: MESSAGE_OUT_OF_OFFICE ) {
// OUT_OF_OFFICE trumps AVAILABILITY, CALL and CALENDAR status
$updateStatus = $userStatus -> getMessageId () === IUserStatus :: MESSAGE_AVAILABILITY || $userStatus -> getMessageId () === IUserStatus :: MESSAGE_CALL || $userStatus -> getMessageId () === IUserStatus :: MESSAGE_CALENDAR_BUSY ;
} elseif ( $messageId === IUserStatus :: MESSAGE_AVAILABILITY ) {
// AVAILABILITY trumps CALL and CALENDAR status
$updateStatus = $userStatus -> getMessageId () === IUserStatus :: MESSAGE_CALL || $userStatus -> getMessageId () === IUserStatus :: MESSAGE_CALENDAR_BUSY ;
} elseif ( $messageId === IUserStatus :: MESSAGE_CALL ) {
// CALL trumps CALENDAR status
$updateStatus = $userStatus -> getMessageId () === IUserStatus :: MESSAGE_CALENDAR_BUSY ;
}
if ( $messageId === IUserStatus :: MESSAGE_OUT_OF_OFFICE || $messageId === IUserStatus :: MESSAGE_AVAILABILITY || $messageId === IUserStatus :: MESSAGE_CALL || $messageId === IUserStatus :: MESSAGE_CALENDAR_BUSY ) {
if ( $updateStatus ) {
$this -> logger -> debug ( 'User ' . $userId . ' is currently NOT available, overwriting status [status: ' . $userStatus -> getStatus () . ', messageId: ' . json_encode ( $userStatus -> getMessageId ()) . ']' , [ 'app' => 'dav' ]);
} else {
$this -> logger -> debug ( 'User ' . $userId . ' is currently NOT available, but we are NOT overwriting status [status: ' . $userStatus -> getStatus () . ', messageId: ' . json_encode ( $userStatus -> getMessageId ()) . ']' , [ 'app' => 'dav' ]);
}
}
// There should be a backup already or none is needed. So we take a shortcut.
if ( $updateStatus ) {
2024-01-17 10:15:15 -05:00
$userStatus -> setStatus ( $status );
$userStatus -> setStatusTimestamp ( $this -> timeFactory -> getTime ());
$userStatus -> setIsUserDefined ( true );
$userStatus -> setIsBackup ( false );
$userStatus -> setMessageId ( $messageId );
$userStatus -> setCustomIcon ( null );
$userStatus -> setCustomMessage ( $customMessage );
$userStatus -> setClearAt ( null );
$userStatus -> setStatusMessageTimestamp ( $this -> timeFactory -> now () -> getTimestamp ());
return $this -> mapper -> update ( $userStatus );
}
2022-02-15 09:28:55 -05:00
if ( $createBackup ) {
if ( $this -> backupCurrentStatus ( $userId ) === false ) {
2023-09-25 08:47:02 -04:00
return null ; // Already a status set automatically => abort.
2022-02-15 09:28:55 -05:00
}
// If we just created the backup
2024-01-17 10:15:15 -05:00
// we need to create a new status to insert
2024-06-24 10:28:43 -04:00
// Unfortunately there's no way to unset the DB ID on an Entity
2022-02-15 09:28:55 -05:00
$userStatus = new UserStatus ();
$userStatus -> setUserId ( $userId );
}
$userStatus -> setStatus ( $status );
$userStatus -> setStatusTimestamp ( $this -> timeFactory -> getTime ());
2022-03-11 07:03:00 -05:00
$userStatus -> setIsUserDefined ( true );
2022-02-15 09:28:55 -05:00
$userStatus -> setIsBackup ( false );
$userStatus -> setMessageId ( $messageId );
$userStatus -> setCustomIcon ( null );
2023-09-25 08:47:02 -04:00
$userStatus -> setCustomMessage ( $customMessage );
2022-02-15 09:28:55 -05:00
$userStatus -> setClearAt ( null );
2024-02-08 12:10:59 -05:00
if ( $this -> predefinedStatusService -> getTranslatedStatusForId ( $messageId ) !== null
|| ( $customMessage !== null && $customMessage !== '' )) {
// Only track status message ID if there is one
$userStatus -> setStatusMessageTimestamp ( $this -> timeFactory -> now () -> getTimestamp ());
} else {
$userStatus -> setStatusMessageTimestamp ( 0 );
}
2022-02-15 09:28:55 -05:00
if ( $userStatus -> getId () !== null ) {
2023-09-25 08:47:02 -04:00
return $this -> mapper -> update ( $userStatus );
2022-02-15 09:28:55 -05:00
}
2025-05-30 02:53:18 -04:00
return $this -> insertWithoutThrowingUniqueConstrain ( $userStatus );
2022-02-15 09:28:55 -05:00
}
2020-06-02 06:48:37 -04:00
/**
* @ param string $userId
* @ param string | null $statusIcon
2022-05-27 05:28:59 -04:00
* @ param string | null $message
2020-06-02 06:48:37 -04:00
* @ param int | null $clearAt
* @ return UserStatus
* @ throws InvalidClearAtException
* @ throws InvalidStatusIconException
* @ throws StatusMessageTooLongException
*/
public function setCustomMessage ( string $userId ,
2023-11-23 04:22:34 -05:00
? string $statusIcon ,
? string $message ,
? int $clearAt ) : UserStatus {
2020-06-02 06:48:37 -04:00
try {
$userStatus = $this -> mapper -> findByUserId ( $userId );
} catch ( DoesNotExistException $ex ) {
$userStatus = new UserStatus ();
$userStatus -> setUserId ( $userId );
2020-09-03 10:23:35 -04:00
$userStatus -> setStatus ( IUserStatus :: OFFLINE );
2020-06-02 06:48:37 -04:00
$userStatus -> setStatusTimestamp ( 0 );
$userStatus -> setIsUserDefined ( false );
}
// Check if statusIcon contains only one character
2022-03-24 10:57:17 -04:00
if ( $statusIcon !== null && ! $this -> emojiHelper -> isValidSingleEmoji ( $statusIcon )) {
2020-06-02 06:48:37 -04:00
throw new InvalidStatusIconException ( 'Status-Icon is longer than one character' );
}
// Check for maximum length of custom message
2022-05-27 05:28:59 -04:00
if ( $message !== null && \mb_strlen ( $message ) > self :: MAXIMUM_MESSAGE_LENGTH ) {
2020-09-02 05:49:14 -04:00
throw new StatusMessageTooLongException ( 'Message is longer than supported length of ' . self :: MAXIMUM_MESSAGE_LENGTH . ' characters' );
2020-06-02 06:48:37 -04:00
}
// Check that clearAt is in the future
if ( $clearAt !== null && $clearAt < $this -> timeFactory -> getTime ()) {
throw new InvalidClearAtException ( 'ClearAt is in the past' );
}
$userStatus -> setMessageId ( null );
$userStatus -> setCustomIcon ( $statusIcon );
$userStatus -> setCustomMessage ( $message );
$userStatus -> setClearAt ( $clearAt );
2023-09-21 11:05:06 -04:00
$userStatus -> setStatusMessageTimestamp ( $this -> timeFactory -> now () -> getTimestamp ());
2020-06-02 06:48:37 -04:00
if ( $userStatus -> getId () === null ) {
2025-05-30 02:53:18 -04:00
return $this -> insertWithoutThrowingUniqueConstrain ( $userStatus );
2020-06-02 06:48:37 -04:00
}
return $this -> mapper -> update ( $userStatus );
}
/**
* @ param string $userId
* @ return bool
*/
public function clearStatus ( string $userId ) : bool {
try {
$userStatus = $this -> mapper -> findByUserId ( $userId );
} catch ( DoesNotExistException $ex ) {
// if there is no status to remove, just return
return false ;
}
2020-09-03 10:23:35 -04:00
$userStatus -> setStatus ( IUserStatus :: OFFLINE );
2020-06-02 06:48:37 -04:00
$userStatus -> setStatusTimestamp ( 0 );
$userStatus -> setIsUserDefined ( false );
$this -> mapper -> update ( $userStatus );
return true ;
}
/**
* @ param string $userId
* @ return bool
*/
public function clearMessage ( string $userId ) : bool {
try {
$userStatus = $this -> mapper -> findByUserId ( $userId );
} catch ( DoesNotExistException $ex ) {
// if there is no status to remove, just return
return false ;
}
$userStatus -> setMessageId ( null );
$userStatus -> setCustomMessage ( null );
$userStatus -> setCustomIcon ( null );
$userStatus -> setClearAt ( null );
2023-09-21 11:05:06 -04:00
$userStatus -> setStatusMessageTimestamp ( 0 );
2020-06-02 06:48:37 -04:00
$this -> mapper -> update ( $userStatus );
return true ;
}
/**
* @ param string $userId
* @ return bool
*/
2021-11-19 09:09:05 -05:00
public function removeUserStatus ( string $userId ) : bool {
2020-06-02 06:48:37 -04:00
try {
2021-11-19 09:09:05 -05:00
$userStatus = $this -> mapper -> findByUserId ( $userId , false );
} catch ( DoesNotExistException $ex ) {
// if there is no status to remove, just return
return false ;
}
$this -> mapper -> delete ( $userStatus );
return true ;
}
public function removeBackupUserStatus ( string $userId ) : bool {
try {
$userStatus = $this -> mapper -> findByUserId ( $userId , true );
2020-06-02 06:48:37 -04:00
} catch ( DoesNotExistException $ex ) {
// if there is no status to remove, just return
return false ;
}
$this -> mapper -> delete ( $userStatus );
return true ;
}
/**
* Processes a status to check if custom message is still
* up to date and provides translated default status if needed
*
* @ param UserStatus $status
2021-07-08 12:26:27 -04:00
* @ return UserStatus
2020-06-02 06:48:37 -04:00
*/
private function processStatus ( UserStatus $status ) : UserStatus {
$clearAt = $status -> getClearAt ();
2020-09-02 06:25:02 -04:00
if ( $status -> getStatusTimestamp () < $this -> timeFactory -> getTime () - self :: INVALIDATE_STATUS_THRESHOLD
2020-09-03 10:23:35 -04:00
&& ( ! $status -> getIsUserDefined () || $status -> getStatus () === IUserStatus :: ONLINE )) {
2020-09-02 06:25:02 -04:00
$this -> cleanStatus ( $status );
}
2020-06-02 06:48:37 -04:00
if ( $clearAt !== null && $clearAt < $this -> timeFactory -> getTime ()) {
2022-05-27 05:28:59 -04:00
$this -> cleanStatus ( $status );
2020-09-02 05:49:14 -04:00
$this -> cleanStatusMessage ( $status );
2020-06-02 06:48:37 -04:00
}
if ( $status -> getMessageId () !== null ) {
$this -> addDefaultMessage ( $status );
}
return $status ;
}
2020-09-02 06:25:02 -04:00
/**
* @ param UserStatus $status
*/
private function cleanStatus ( UserStatus $status ) : void {
2021-06-04 06:03:16 -04:00
if ( $status -> getStatus () === IUserStatus :: OFFLINE && ! $status -> getIsUserDefined ()) {
return ;
}
2020-09-03 10:23:35 -04:00
$status -> setStatus ( IUserStatus :: OFFLINE );
2020-09-02 06:25:02 -04:00
$status -> setStatusTimestamp ( $this -> timeFactory -> getTime ());
$status -> setIsUserDefined ( false );
$this -> mapper -> update ( $status );
}
2020-06-02 06:48:37 -04:00
/**
* @ param UserStatus $status
*/
2020-09-02 05:49:14 -04:00
private function cleanStatusMessage ( UserStatus $status ) : void {
2020-06-02 06:48:37 -04:00
$status -> setMessageId ( null );
$status -> setCustomIcon ( null );
$status -> setCustomMessage ( null );
$status -> setClearAt ( null );
2023-09-21 11:05:06 -04:00
$status -> setStatusMessageTimestamp ( 0 );
2020-06-02 06:48:37 -04:00
$this -> mapper -> update ( $status );
}
/**
* @ param UserStatus $status
*/
private function addDefaultMessage ( UserStatus $status ) : void {
// If the message is predefined, insert the translated message and icon
$predefinedMessage = $this -> predefinedStatusService -> getDefaultStatusById ( $status -> getMessageId ());
2023-11-23 19:49:30 -05:00
if ( $predefinedMessage === null ) {
return ;
}
// If there is a custom message, don't overwrite it
2024-09-05 15:23:38 -04:00
if ( empty ( $status -> getCustomMessage ())) {
2020-06-02 06:48:37 -04:00
$status -> setCustomMessage ( $predefinedMessage [ 'message' ]);
2023-11-23 19:49:30 -05:00
}
2024-09-05 15:23:38 -04:00
if ( empty ( $status -> getCustomIcon ())) {
2020-06-02 06:48:37 -04:00
$status -> setCustomIcon ( $predefinedMessage [ 'icon' ]);
}
}
2021-08-11 04:36:24 -04:00
/**
2022-02-15 08:55:40 -05:00
* @ return bool false if there is already a backup . In this case abort the procedure .
2021-08-11 04:36:24 -04:00
*/
public function backupCurrentStatus ( string $userId ) : bool {
try {
2022-02-15 08:55:40 -05:00
$this -> mapper -> createBackupStatus ( $userId );
2021-08-11 04:36:24 -04:00
return true ;
2022-02-15 08:55:40 -05:00
} catch ( Exception $ex ) {
if ( $ex -> getReason () === Exception :: REASON_UNIQUE_CONSTRAINT_VIOLATION ) {
return false ;
}
throw $ex ;
2021-08-11 04:36:24 -04:00
}
}
2023-02-24 10:38:32 -05:00
public function revertUserStatus ( string $userId , string $messageId , bool $revertedManually = false ) : ? UserStatus {
2021-08-11 04:36:24 -04:00
try {
/** @var UserStatus $userStatus */
$backupUserStatus = $this -> mapper -> findByUserId ( $userId , true );
} catch ( DoesNotExistException $ex ) {
2021-11-19 09:09:05 -05:00
// No user status to revert, do nothing
2023-02-24 10:38:32 -05:00
return null ;
2021-08-11 04:36:24 -04:00
}
2022-02-10 10:51:30 -05:00
2022-07-22 05:02:37 -04:00
$deleted = $this -> mapper -> deleteCurrentStatusToRestoreBackup ( $userId , $messageId );
2022-02-10 10:51:30 -05:00
if ( ! $deleted ) {
// Another status is set automatically or no status, do nothing
2023-02-24 10:38:32 -05:00
return null ;
}
2024-04-17 05:32:16 -04:00
if ( $revertedManually ) {
if ( $backupUserStatus -> getStatus () === IUserStatus :: OFFLINE ) {
// When the user reverts the status manually they are online
$backupUserStatus -> setStatus ( IUserStatus :: ONLINE );
}
$backupUserStatus -> setStatusTimestamp ( $this -> timeFactory -> getTime ());
2021-08-11 04:36:24 -04:00
}
2022-02-10 10:51:30 -05:00
2021-08-11 04:36:24 -04:00
$backupUserStatus -> setIsBackup ( false );
// Remove the underscore prefix added when creating the backup
$backupUserStatus -> setUserId ( substr ( $backupUserStatus -> getUserId (), 1 ));
$this -> mapper -> update ( $backupUserStatus );
2023-02-24 10:38:32 -05:00
return $backupUserStatus ;
2021-08-11 04:36:24 -04:00
}
2022-02-10 11:28:05 -05:00
2022-07-22 05:02:37 -04:00
public function revertMultipleUserStatus ( array $userIds , string $messageId ) : void {
2022-02-10 11:28:05 -05:00
// Get all user statuses and the backups
$findById = $userIds ;
foreach ( $userIds as $userId ) {
$findById [] = '_' . $userId ;
}
$userStatuses = $this -> mapper -> findByUserIds ( $findById );
$backups = $restoreIds = $statuesToDelete = [];
foreach ( $userStatuses as $userStatus ) {
if ( ! $userStatus -> getIsBackup ()
2022-07-22 05:02:37 -04:00
&& $userStatus -> getMessageId () === $messageId ) {
2022-02-10 11:28:05 -05:00
$statuesToDelete [ $userStatus -> getUserId ()] = $userStatus -> getId ();
2023-11-23 04:22:34 -05:00
} elseif ( $userStatus -> getIsBackup ()) {
2022-02-10 11:28:05 -05:00
$backups [ $userStatus -> getUserId ()] = $userStatus -> getId ();
}
}
// For users with both (normal and backup) delete the status when matching
foreach ( $statuesToDelete as $userId => $statusId ) {
$backupUserId = '_' . $userId ;
if ( isset ( $backups [ $backupUserId ])) {
$restoreIds [] = $backups [ $backupUserId ];
}
}
$this -> mapper -> deleteByIds ( array_values ( $statuesToDelete ));
// For users that matched restore the previous status
$this -> mapper -> restoreBackupStatuses ( $restoreIds );
}
2025-05-30 02:53:18 -04:00
protected function insertWithoutThrowingUniqueConstrain ( UserStatus $userStatus ) : UserStatus {
try {
return $this -> mapper -> insert ( $userStatus );
} catch ( Exception $e ) {
// Ignore if a parallel request already set the status
if ( $e -> getReason () !== Exception :: REASON_UNIQUE_CONSTRAINT_VIOLATION ) {
throw $e ;
}
}
return $userStatus ;
}
2020-06-02 06:48:37 -04:00
}