migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\AppInfo;
use OCA\UserStatus\Capabilities;
use OCA\UserStatus\Connector\UserStatusProvider;
use OCA\UserStatus\Dashboard\UserStatusWidget;
use OCA\UserStatus\Listener\BeforeTemplateRenderedListener;
use OCA\UserStatus\Listener\OutOfOfficeStatusListener;
use OCA\UserStatus\Listener\UserDeletedListener;
use OCA\UserStatus\Listener\UserLiveStatusListener;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\IConfig;
use OCP\User\Events\OutOfOfficeChangedEvent;
use OCP\User\Events\OutOfOfficeClearedEvent;
use OCP\User\Events\OutOfOfficeScheduledEvent;
use OCP\User\Events\UserDeletedEvent;
use OCP\User\Events\UserLiveStatusEvent;
use OCP\UserStatus\IManager;
/**
* Class Application
*
* @package OCA\UserStatus\AppInfo
*/
class Application extends App implements IBootstrap {
/** @var string */
public const APP_ID = 'user_status';
/**
* Application constructor.
*
* @param array $urlParams
*/
public function __construct(array $urlParams = []) {
parent::__construct(self::APP_ID, $urlParams);
}
/**
* @inheritDoc
*/
public function register(IRegistrationContext $context): void {
// Register OCS Capabilities
$context->registerCapability(Capabilities::class);
// Register Event Listeners
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(UserLiveStatusEvent::class, UserLiveStatusListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerEventListener(OutOfOfficeChangedEvent::class, OutOfOfficeStatusListener::class);
$context->registerEventListener(OutOfOfficeScheduledEvent::class, OutOfOfficeStatusListener::class);
$context->registerEventListener(OutOfOfficeClearedEvent::class, OutOfOfficeStatusListener::class);
$config = $this->getContainer()->query(IConfig::class);
$shareeEnumeration = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
$shareeEnumerationInGroupOnly = $shareeEnumeration && $config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
$shareeEnumerationPhone = $shareeEnumeration && $config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
// Register the Dashboard panel if user enumeration is enabled and not limited
if ($shareeEnumeration && !$shareeEnumerationInGroupOnly && !$shareeEnumerationPhone) {
$context->registerDashboardWidget(UserStatusWidget::class);
}
}
public function boot(IBootContext $context): void {
/** @var IManager $userStatusManager */
$userStatusManager = $context->getServerContainer()->get(IManager::class);
$userStatusManager->registerProvider(UserStatusProvider::class);
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\BackgroundJob;
use OCA\UserStatus\Db\UserStatusMapper;
use OCA\UserStatus\Service\StatusService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
/**
* Class ClearOldStatusesBackgroundJob
*
* @package OCA\UserStatus\BackgroundJob
*/
class ClearOldStatusesBackgroundJob extends TimedJob {
/** @var UserStatusMapper */
private $mapper;
/**
* ClearOldStatusesBackgroundJob constructor.
*
* @param ITimeFactory $time
* @param UserStatusMapper $mapper
*/
public function __construct(ITimeFactory $time,
UserStatusMapper $mapper) {
parent::__construct($time);
$this->mapper = $mapper;
// Run every time the cron is run
$this->setInterval(60);
}
/**
* @inheritDoc
*/
protected function run($argument) {
$now = $this->time->getTime();
$this->mapper->clearOlderThanClearAt($now);
$this->mapper->clearStatusesOlderThan($now - StatusService::INVALIDATE_STATUS_THRESHOLD, $now);
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus;
use OCP\Capabilities\ICapability;
use OCP\IEmojiHelper;
/**
* Class Capabilities
*
* @package OCA\UserStatus
*/
class Capabilities implements ICapability {
private IEmojiHelper $emojiHelper;
public function __construct(IEmojiHelper $emojiHelper) {
$this->emojiHelper = $emojiHelper;
}
/**
* @return array{user_status: array{enabled: bool, restore: bool, supports_emoji: bool}}
*/
public function getCapabilities() {
return [
'user_status' => [
'enabled' => true,
'restore' => true,
'supports_emoji' => $this->emojiHelper->doesPlatformSupportEmoji(),
],
];
}
}
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Connector;
use DateTimeImmutable;
use OCA\UserStatus\Db;
use OCP\UserStatus\IUserStatus;
class UserStatus implements IUserStatus {
/** @var string */
private $userId;
/** @var string */
private $status;
/** @var string|null */
private $message;
/** @var string|null */
private $icon;
/** @var DateTimeImmutable|null */
private $clearAt;
/** @var Db\UserStatus */
private $internalStatus;
public function __construct(Db\UserStatus $status) {
$this->internalStatus = $status;
$this->userId = $status->getUserId();
$this->status = $status->getStatus();
$this->message = $status->getCustomMessage();
$this->icon = $status->getCustomIcon();
if ($status->getStatus() === IUserStatus::INVISIBLE) {
$this->status = IUserStatus::OFFLINE;
}
if ($status->getClearAt() !== null) {
$this->clearAt = DateTimeImmutable::createFromFormat('U', (string)$status->getClearAt());
}
}
/**
* @inheritDoc
*/
public function getUserId(): string {
return $this->userId;
}
/**
* @inheritDoc
*/
public function getStatus(): string {
return $this->status;
}
/**
* @inheritDoc
*/
public function getMessage(): ?string {
return $this->message;
}
/**
* @inheritDoc
*/
public function getIcon(): ?string {
return $this->icon;
}
/**
* @inheritDoc
*/
public function getClearAt(): ?DateTimeImmutable {
return $this->clearAt;
}
public function getInternal(): Db\UserStatus {
return $this->internalStatus;
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Connector;
use OC\UserStatus\ISettableProvider;
use OCA\UserStatus\Service\StatusService;
use OCP\UserStatus\IProvider;
class UserStatusProvider implements IProvider, ISettableProvider {
/** @var StatusService */
private $service;
/**
* UserStatusProvider constructor.
*
* @param StatusService $service
*/
public function __construct(StatusService $service) {
$this->service = $service;
}
/**
* @inheritDoc
*/
public function getUserStatuses(array $userIds): array {
$statuses = $this->service->findByUserIds($userIds);
$userStatuses = [];
foreach ($statuses as $status) {
$userStatuses[$status->getUserId()] = new UserStatus($status);
}
return $userStatuses;
}
public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup, ?string $customMessage = null): void {
$this->service->setUserStatus($userId, $status, $messageId, $createBackup, $customMessage);
}
public function revertUserStatus(string $userId, string $messageId, string $status): void {
$this->service->revertUserStatus($userId, $messageId);
}
public function revertMultipleUserStatus(array $userIds, string $messageId, string $status): void {
$this->service->revertMultipleUserStatus($userIds, $messageId);
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\UserStatus\ContactsMenu;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Service\StatusService;
use OCP\Contacts\ContactsMenu\IBulkProvider;
use OCP\Contacts\ContactsMenu\IEntry;
use function array_combine;
use function array_filter;
use function array_map;
class StatusProvider implements IBulkProvider {
public function __construct(private StatusService $statusService) {
}
public function process(array $entries): void {
$uids = array_filter(
array_map(fn (IEntry $entry): ?string => $entry->getProperty('UID'), $entries)
);
$statuses = $this->statusService->findByUserIds($uids);
/** @var array<string, UserStatus> $indexed */
$indexed = array_combine(
array_map(fn (UserStatus $status) => $status->getUserId(), $statuses),
$statuses
);
foreach ($entries as $entry) {
$uid = $entry->getProperty('UID');
if ($uid !== null && isset($indexed[$uid])) {
$status = $indexed[$uid];
$entry->setStatus(
$status->getStatus(),
$status->getCustomMessage(),
$status->getStatusMessageTimestamp(),
$status->getCustomIcon(),
);
}
}
}
}
@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Controller;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\ResponseDefinitions;
use OCA\UserStatus\Service\StatusService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\User\Events\UserLiveStatusEvent;
use OCP\UserStatus\IUserStatus;
/**
* @psalm-import-type UserStatusPrivate from ResponseDefinitions
*/
class HeartbeatController extends OCSController {
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var IUserSession */
private $userSession;
/** @var ITimeFactory */
private $timeFactory;
/** @var StatusService */
private $service;
public function __construct(string $appName,
IRequest $request,
IEventDispatcher $eventDispatcher,
IUserSession $userSession,
ITimeFactory $timeFactory,
StatusService $service) {
parent::__construct($appName, $request);
$this->eventDispatcher = $eventDispatcher;
$this->userSession = $userSession;
$this->timeFactory = $timeFactory;
$this->service = $service;
}
/**
* Keep the status alive
*
* @NoAdminRequired
*
* @param string $status Only online, away
*
* @return DataResponse<Http::STATUS_OK, UserStatusPrivate, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NO_CONTENT, array<empty>, array{}>
* 200: Status successfully updated
* 204: User has no status to keep alive
* 400: Invalid status to update
*/
public function heartbeat(string $status): DataResponse {
if (!\in_array($status, [IUserStatus::ONLINE, IUserStatus::AWAY], true)) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$user = $this->userSession->getUser();
if ($user === null) {
return new DataResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
}
$event = new UserLiveStatusEvent(
$user,
$status,
$this->timeFactory->getTime()
);
$this->eventDispatcher->dispatchTyped($event);
$userStatus = $event->getUserStatus();
if (!$userStatus) {
return new DataResponse([], Http::STATUS_NO_CONTENT);
}
/** @psalm-suppress UndefinedInterfaceMethod */
return new DataResponse($this->formatStatus($userStatus->getInternal()));
}
private function formatStatus(UserStatus $status): array {
return [
'userId' => $status->getUserId(),
'message' => $status->getCustomMessage(),
'messageId' => $status->getMessageId(),
'messageIsPredefined' => $status->getMessageId() !== null,
'icon' => $status->getCustomIcon(),
'clearAt' => $status->getClearAt(),
'status' => $status->getStatus(),
'statusIsUserDefined' => $status->getIsUserDefined(),
];
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Controller;
use OCA\UserStatus\ResponseDefinitions;
use OCA\UserStatus\Service\PredefinedStatusService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
/**
* @package OCA\UserStatus\Controller
*
* @psalm-import-type UserStatusPredefined from ResponseDefinitions
*/
class PredefinedStatusController extends OCSController {
/** @var PredefinedStatusService */
private $predefinedStatusService;
/**
* AStatusController constructor.
*
* @param string $appName
* @param IRequest $request
* @param PredefinedStatusService $predefinedStatusService
*/
public function __construct(string $appName,
IRequest $request,
PredefinedStatusService $predefinedStatusService) {
parent::__construct($appName, $request);
$this->predefinedStatusService = $predefinedStatusService;
}
/**
* Get all predefined messages
*
* @NoAdminRequired
*
* @return DataResponse<Http::STATUS_OK, UserStatusPredefined[], array{}>
*
* 200: Predefined statuses returned
*/
public function findAll():DataResponse {
// Filtering out the invisible one, that should only be set by API
return new DataResponse(array_filter($this->predefinedStatusService->getDefaultStatuses(), function (array $status) {
return !array_key_exists('visible', $status) || $status['visible'] === true;
}));
}
}
@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Controller;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\ResponseDefinitions;
use OCA\UserStatus\Service\StatusService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\UserStatus\IUserStatus;
/**
* @psalm-import-type UserStatusType from ResponseDefinitions
* @psalm-import-type UserStatusPublic from ResponseDefinitions
*/
class StatusesController extends OCSController {
/** @var StatusService */
private $service;
/**
* StatusesController constructor.
*
* @param string $appName
* @param IRequest $request
* @param StatusService $service
*/
public function __construct(string $appName,
IRequest $request,
StatusService $service) {
parent::__construct($appName, $request);
$this->service = $service;
}
/**
* Find statuses of users
*
* @NoAdminRequired
*
* @param int|null $limit Maximum number of statuses to find
* @param int|null $offset Offset for finding statuses
* @return DataResponse<Http::STATUS_OK, UserStatusPublic[], array{}>
*
* 200: Statuses returned
*/
public function findAll(?int $limit = null, ?int $offset = null): DataResponse {
$allStatuses = $this->service->findAll($limit, $offset);
return new DataResponse(array_map(function ($userStatus) {
return $this->formatStatus($userStatus);
}, $allStatuses));
}
/**
* Find the status of a user
*
* @NoAdminRequired
*
* @param string $userId ID of the user
* @return DataResponse<Http::STATUS_OK, UserStatusPublic, array{}>
* @throws OCSNotFoundException The user was not found
*
* 200: Status returned
*/
public function find(string $userId): DataResponse {
try {
$userStatus = $this->service->findByUserId($userId);
} catch (DoesNotExistException $ex) {
throw new OCSNotFoundException('No status for the requested userId');
}
return new DataResponse($this->formatStatus($userStatus));
}
/**
* @param UserStatus $status
* @return UserStatusPublic
*/
private function formatStatus(UserStatus $status): array {
/** @var UserStatusType $visibleStatus */
$visibleStatus = $status->getStatus();
if ($visibleStatus === IUserStatus::INVISIBLE) {
$visibleStatus = IUserStatus::OFFLINE;
}
return [
'userId' => $status->getUserId(),
'message' => $status->getCustomMessage(),
'icon' => $status->getCustomIcon(),
'clearAt' => $status->getClearAt(),
'status' => $visibleStatus,
];
}
}
@@ -0,0 +1,224 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Simon Spannagel <simonspa@kth.se>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Controller;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Db\UserStatus;
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 OCA\UserStatus\ResponseDefinitions;
use OCA\UserStatus\Service\StatusService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type UserStatusType from ResponseDefinitions
* @psalm-import-type UserStatusPrivate from ResponseDefinitions
*/
class UserStatusController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
private string $userId,
private LoggerInterface $logger,
private StatusService $service,
private CalendarStatusService $calendarStatusService,
) {
parent::__construct($appName, $request);
}
/**
* Get the status of the current user
*
* @NoAdminRequired
*
* @return DataResponse<Http::STATUS_OK, UserStatusPrivate, array{}>
* @throws OCSNotFoundException The user was not found
*
* 200: The status was found successfully
*/
public function getStatus(): DataResponse {
try {
$this->calendarStatusService->processCalendarStatus($this->userId);
$userStatus = $this->service->findByUserId($this->userId);
} catch (DoesNotExistException $ex) {
throw new OCSNotFoundException('No status for the current user');
}
return new DataResponse($this->formatStatus($userStatus));
}
/**
* Update the status type of the current user
*
* @NoAdminRequired
*
* @param string $statusType The new status type
* @return DataResponse<Http::STATUS_OK, UserStatusPrivate, array{}>
* @throws OCSBadRequestException The status type is invalid
*
* 200: The status was updated successfully
*/
public function setStatus(string $statusType): DataResponse {
try {
$status = $this->service->setStatus($this->userId, $statusType, null, true);
$this->service->removeBackupUserStatus($this->userId);
return new DataResponse($this->formatStatus($status));
} catch (InvalidStatusTypeException $ex) {
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid status type "' . $statusType . '"');
throw new OCSBadRequestException($ex->getMessage(), $ex);
}
}
/**
* Set the message to a predefined message for the current user
*
* @NoAdminRequired
*
* @param string $messageId ID of the predefined message
* @param int|null $clearAt When the message should be cleared
* @return DataResponse<Http::STATUS_OK, UserStatusPrivate, array{}>
* @throws OCSBadRequestException The clearAt or message-id is invalid
*
* 200: The message was updated successfully
*/
public function setPredefinedMessage(string $messageId,
?int $clearAt): DataResponse {
try {
$status = $this->service->setPredefinedMessage($this->userId, $messageId, $clearAt);
$this->service->removeBackupUserStatus($this->userId);
return new DataResponse($this->formatStatus($status));
} catch (InvalidClearAtException $ex) {
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid clearAt value "' . $clearAt . '"');
throw new OCSBadRequestException($ex->getMessage(), $ex);
} catch (InvalidMessageIdException $ex) {
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid message-id "' . $messageId . '"');
throw new OCSBadRequestException($ex->getMessage(), $ex);
}
}
/**
* Set the message to a custom message for the current user
*
* @NoAdminRequired
*
* @param string|null $statusIcon Icon of the status
* @param string|null $message Message of the status
* @param int|null $clearAt When the message should be cleared
* @return DataResponse<Http::STATUS_OK, UserStatusPrivate, array{}>
* @throws OCSBadRequestException The clearAt or icon is invalid or the message is too long
*
* 200: The message was updated successfully
*/
public function setCustomMessage(?string $statusIcon,
?string $message,
?int $clearAt): DataResponse {
try {
if (($message !== null && $message !== '') || ($clearAt !== null && $clearAt !== 0)) {
$status = $this->service->setCustomMessage($this->userId, $statusIcon, $message, $clearAt);
} else {
$this->service->clearMessage($this->userId);
$status = $this->service->findByUserId($this->userId);
}
$this->service->removeBackupUserStatus($this->userId);
return new DataResponse($this->formatStatus($status));
} catch (InvalidClearAtException $ex) {
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid clearAt value "' . $clearAt . '"');
throw new OCSBadRequestException($ex->getMessage(), $ex);
} catch (InvalidStatusIconException $ex) {
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid icon value "' . $statusIcon . '"');
throw new OCSBadRequestException($ex->getMessage(), $ex);
} catch (StatusMessageTooLongException $ex) {
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to a too long status message.');
throw new OCSBadRequestException($ex->getMessage(), $ex);
}
}
/**
* Clear the message of the current user
*
* @NoAdminRequired
*
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* 200: Message cleared successfully
*/
public function clearMessage(): DataResponse {
$this->service->clearMessage($this->userId);
return new DataResponse([]);
}
/**
* Revert the status to the previous status
*
* @NoAdminRequired
*
* @param string $messageId ID of the message to delete
*
* @return DataResponse<Http::STATUS_OK, UserStatusPrivate|array<empty>, array{}>
*
* 200: Status reverted
*/
public function revertStatus(string $messageId): DataResponse {
$backupStatus = $this->service->revertUserStatus($this->userId, $messageId, true);
if ($backupStatus) {
return new DataResponse($this->formatStatus($backupStatus));
}
return new DataResponse([]);
}
/**
* @param UserStatus $status
* @return UserStatusPrivate
*/
private function formatStatus(UserStatus $status): array {
/** @var UserStatusType $visibleStatus */
$visibleStatus = $status->getStatus();
return [
'userId' => $status->getUserId(),
'message' => $status->getCustomMessage(),
'messageId' => $status->getMessageId(),
'messageIsPredefined' => $status->getMessageId() !== null,
'icon' => $status->getCustomIcon(),
'clearAt' => $status->getClearAt(),
'status' => $visibleStatus,
'statusIsUserDefined' => $status->getIsUserDefined(),
];
}
}
@@ -0,0 +1,208 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Dashboard;
use OCA\UserStatus\AppInfo\Application;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Service\StatusService;
use OCP\AppFramework\Services\IInitialState;
use OCP\Dashboard\IAPIWidget;
use OCP\Dashboard\IAPIWidgetV2;
use OCP\Dashboard\IIconWidget;
use OCP\Dashboard\IOptionWidget;
use OCP\Dashboard\Model\WidgetItem;
use OCP\Dashboard\Model\WidgetItems;
use OCP\Dashboard\Model\WidgetOptions;
use OCP\IDateTimeFormatter;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\UserStatus\IUserStatus;
/**
* Class UserStatusWidget
*
* @package OCA\UserStatus
*/
class UserStatusWidget implements IAPIWidget, IAPIWidgetV2, IIconWidget, IOptionWidget {
private IL10N $l10n;
private IDateTimeFormatter $dateTimeFormatter;
private IURLGenerator $urlGenerator;
private IInitialState $initialStateService;
private IUserManager $userManager;
private IUserSession $userSession;
private StatusService $service;
/**
* UserStatusWidget constructor
*
* @param IL10N $l10n
* @param IDateTimeFormatter $dateTimeFormatter
* @param IURLGenerator $urlGenerator
* @param IInitialState $initialStateService
* @param IUserManager $userManager
* @param IUserSession $userSession
* @param StatusService $service
*/
public function __construct(IL10N $l10n,
IDateTimeFormatter $dateTimeFormatter,
IURLGenerator $urlGenerator,
IInitialState $initialStateService,
IUserManager $userManager,
IUserSession $userSession,
StatusService $service) {
$this->l10n = $l10n;
$this->dateTimeFormatter = $dateTimeFormatter;
$this->urlGenerator = $urlGenerator;
$this->initialStateService = $initialStateService;
$this->userManager = $userManager;
$this->userSession = $userSession;
$this->service = $service;
}
/**
* @inheritDoc
*/
public function getId(): string {
return Application::APP_ID;
}
/**
* @inheritDoc
*/
public function getTitle(): string {
return $this->l10n->t('Recent statuses');
}
/**
* @inheritDoc
*/
public function getOrder(): int {
return 5;
}
/**
* @inheritDoc
*/
public function getIconClass(): string {
return 'icon-user-status-dark';
}
/**
* @inheritDoc
*/
public function getIconUrl(): string {
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg')
);
}
/**
* @inheritDoc
*/
public function getUrl(): ?string {
return null;
}
/**
* @inheritDoc
*/
public function load(): void {
}
private function getWidgetData(string $userId, ?string $since = null, int $limit = 7): array {
// Fetch status updates and filter current user
$recentStatusUpdates = array_slice(
array_filter(
$this->service->findAllRecentStatusChanges($limit + 1, 0),
static function (UserStatus $status) use ($userId, $since): bool {
return $status->getUserId() !== $userId
&& ($since === null || $status->getStatusTimestamp() > (int) $since);
}
),
0,
$limit
);
return array_map(function (UserStatus $status): array {
$user = $this->userManager->get($status->getUserId());
$displayName = $status->getUserId();
if ($user !== null) {
$displayName = $user->getDisplayName();
}
return [
'userId' => $status->getUserId(),
'displayName' => $displayName,
'status' => $status->getStatus() === IUserStatus::INVISIBLE
? IUserStatus::OFFLINE
: $status->getStatus(),
'icon' => $status->getCustomIcon(),
'message' => $status->getCustomMessage(),
'timestamp' => $status->getStatusMessageTimestamp(),
];
}, $recentStatusUpdates);
}
/**
* @inheritDoc
*/
public function getItems(string $userId, ?string $since = null, int $limit = 7): array {
$widgetItemsData = $this->getWidgetData($userId, $since, $limit);
return array_map(function (array $widgetData) {
$formattedDate = $this->dateTimeFormatter->formatTimeSpan($widgetData['timestamp']);
return new WidgetItem(
$widgetData['displayName'],
$widgetData['icon'] . ($widgetData['icon'] ? ' ' : '') . $widgetData['message'] . ', ' . $formattedDate,
// https://nextcloud.local/index.php/u/julien
$this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute('core.ProfilePage.index', ['targetUserId' => $widgetData['userId']])
),
$this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute('core.avatar.getAvatar', ['userId' => $widgetData['userId'], 'size' => 44])
),
(string) $widgetData['timestamp']
);
}, $widgetItemsData);
}
/**
* @inheritDoc
*/
public function getItemsV2(string $userId, ?string $since = null, int $limit = 7): WidgetItems {
$items = $this->getItems($userId, $since, $limit);
return new WidgetItems(
$items,
count($items) === 0 ? $this->l10n->t('No recent status changes') : '',
);
}
public function getWidgetOptions(): WidgetOptions {
return new WidgetOptions(true);
}
}
@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Db;
use OCP\AppFramework\Db\Entity;
/**
* Class UserStatus
*
* @package OCA\UserStatus\Db
*
* @method int getId()
* @method void setId(int $id)
* @method string getUserId()
* @method void setUserId(string $userId)
* @method string getStatus()
* @method void setStatus(string $status)
* @method int getStatusTimestamp()
* @method void setStatusTimestamp(int $statusTimestamp)
* @method bool getIsUserDefined()
* @method void setIsUserDefined(bool $isUserDefined)
* @method string|null getMessageId()
* @method void setMessageId(string|null $messageId)
* @method string|null getCustomIcon()
* @method void setCustomIcon(string|null $customIcon)
* @method string|null getCustomMessage()
* @method void setCustomMessage(string|null $customMessage)
* @method int|null getClearAt()
* @method void setClearAt(int|null $clearAt)
* @method setIsBackup(bool $isBackup): void
* @method getIsBackup(): bool
* @method int getStatusMessageTimestamp()
* @method void setStatusMessageTimestamp(int $statusTimestamp)
*/
class UserStatus extends Entity {
/** @var string */
public $userId;
/** @var string */
public $status;
/** @var int */
public $statusTimestamp;
/** @var boolean */
public $isUserDefined;
/** @var string|null */
public $messageId;
/** @var string|null */
public $customIcon;
/** @var string|null */
public $customMessage;
/** @var int|null */
public $clearAt;
/** @var bool $isBackup */
public $isBackup;
/** @var int */
protected $statusMessageTimestamp = 0;
public function __construct() {
$this->addType('userId', 'string');
$this->addType('status', 'string');
$this->addType('statusTimestamp', 'int');
$this->addType('isUserDefined', 'boolean');
$this->addType('messageId', 'string');
$this->addType('customIcon', 'string');
$this->addType('customMessage', 'string');
$this->addType('clearAt', 'int');
$this->addType('isBackup', 'boolean');
$this->addType('statusMessageTimestamp', 'int');
}
}
@@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Db;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\UserStatus\IUserStatus;
/**
* @template-extends QBMapper<UserStatus>
*/
class UserStatusMapper extends QBMapper {
/**
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'user_status');
}
/**
* @param int|null $limit
* @param int|null $offset
* @return UserStatus[]
*/
public function findAll(?int $limit = null, ?int $offset = null):array {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
->from($this->tableName);
if ($limit !== null) {
$qb->setMaxResults($limit);
}
if ($offset !== null) {
$qb->setFirstResult($offset);
}
return $this->findEntities($qb);
}
/**
* @param int|null $limit
* @param int|null $offset
* @return array
*/
public function findAllRecent(?int $limit = null, ?int $offset = null): array {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
->from($this->tableName)
->orderBy('status_message_timestamp', 'DESC')
->where($qb->expr()->andX(
$qb->expr()->neq('status_message_timestamp', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
$qb->expr()->orX(
$qb->expr()->notIn('status', $qb->createNamedParameter([IUserStatus::ONLINE, IUserStatus::AWAY, IUserStatus::OFFLINE], IQueryBuilder::PARAM_STR_ARRAY)),
$qb->expr()->isNotNull('message_id'),
$qb->expr()->isNotNull('custom_icon'),
$qb->expr()->isNotNull('custom_message'),
),
$qb->expr()->notLike('user_id', $qb->createNamedParameter($this->db->escapeLikeParameter('_') . '%'))
));
if ($limit !== null) {
$qb->setMaxResults($limit);
}
if ($offset !== null) {
$qb->setFirstResult($offset);
}
return $this->findEntities($qb);
}
/**
* @param string $userId
* @return UserStatus
* @throws \OCP\AppFramework\Db\DoesNotExistException
*/
public function findByUserId(string $userId, bool $isBackup = false): UserStatus {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
->from($this->tableName)
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($isBackup ? '_' . $userId : $userId, IQueryBuilder::PARAM_STR)));
return $this->findEntity($qb);
}
/**
* @param array $userIds
* @return array
*/
public function findByUserIds(array $userIds): array {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
->from($this->tableName)
->where($qb->expr()->in('user_id', $qb->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
return $this->findEntities($qb);
}
/**
* @param int $olderThan
* @param int $now
*/
public function clearStatusesOlderThan(int $olderThan, int $now): void {
$qb = $this->db->getQueryBuilder();
$qb->update($this->tableName)
->set('status', $qb->createNamedParameter(IUserStatus::OFFLINE))
->set('is_user_defined', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))
->set('status_timestamp', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
->where($qb->expr()->lte('status_timestamp', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->neq('status', $qb->createNamedParameter(IUserStatus::OFFLINE)))
->andWhere($qb->expr()->orX(
$qb->expr()->eq('is_user_defined', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL), IQueryBuilder::PARAM_BOOL),
$qb->expr()->eq('status', $qb->createNamedParameter(IUserStatus::ONLINE))
));
$qb->execute();
}
/**
* Clear all statuses older than a given timestamp
*
* @param int $timestamp
*/
public function clearOlderThanClearAt(int $timestamp): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->isNotNull('clear_at'))
->andWhere($qb->expr()->lte('clear_at', $qb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT)));
$qb->execute();
}
/**
* Deletes a user status so we can restore the backup
*
* @param string $userId
* @param string $messageId
* @return bool True if an entry was deleted
*/
public function deleteCurrentStatusToRestoreBackup(string $userId, string $messageId): bool {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
->andWhere($qb->expr()->eq('message_id', $qb->createNamedParameter($messageId)))
->andWhere($qb->expr()->eq('is_backup', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)));
return $qb->executeStatement() > 0;
}
public function deleteByIds(array $ids): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
$qb->executeStatement();
}
/**
* @param string $userId
* @return bool
* @throws \OCP\DB\Exception
*/
public function createBackupStatus(string $userId): bool {
// Prefix user account with an underscore because user_id is marked as unique
// in the table. Starting a username with an underscore is not allowed so this
// shouldn't create any trouble.
$qb = $this->db->getQueryBuilder();
$qb->update($this->tableName)
->set('is_backup', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))
->set('user_id', $qb->createNamedParameter('_' . $userId))
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
return $qb->executeStatement() > 0;
}
public function restoreBackupStatuses(array $ids): void {
$qb = $this->db->getQueryBuilder();
$qb->update($this->tableName)
->set('is_backup', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))
->set('user_id', $qb->func()->substring('user_id', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)))
->where($qb->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
$qb->executeStatement();
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Exception;
class InvalidClearAtException extends \Exception {
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Exception;
class InvalidMessageIdException extends \Exception {
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Exception;
class InvalidStatusIconException extends \Exception {
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Exception;
class InvalidStatusTypeException extends \Exception {
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Exception;
class StatusMessageTooLongException extends \Exception {
}
@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Listener;
use OC\Profile\ProfileManager;
use OCA\UserStatus\AppInfo\Application;
use OCA\UserStatus\Service\JSDataService;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IInitialStateService;
use OCP\IUserSession;
class BeforeTemplateRenderedListener implements IEventListener {
/** @var ProfileManager */
private $profileManager;
/** @var IUserSession */
private $userSession;
/** @var IInitialStateService */
private $initialState;
/** @var JSDataService */
private $jsDataService;
/**
* BeforeTemplateRenderedListener constructor.
*
* @param ProfileManager $profileManager
* @param IUserSession $userSession
* @param IInitialStateService $initialState
* @param JSDataService $jsDataService
*/
public function __construct(
ProfileManager $profileManager,
IUserSession $userSession,
IInitialStateService $initialState,
JSDataService $jsDataService
) {
$this->profileManager = $profileManager;
$this->userSession = $userSession;
$this->initialState = $initialState;
$this->jsDataService = $jsDataService;
}
/**
* @inheritDoc
*/
public function handle(Event $event): void {
$user = $this->userSession->getUser();
if ($user === null) {
return;
}
if (!($event instanceof BeforeTemplateRenderedEvent)) {
// Unrelated
return;
}
if (!$event->isLoggedIn() || $event->getResponse()->getRenderAs() !== TemplateResponse::RENDER_AS_USER) {
return;
}
$this->initialState->provideLazyInitialState(Application::APP_ID, 'status', function () {
return $this->jsDataService;
});
$this->initialState->provideLazyInitialState(Application::APP_ID, 'profileEnabled', function () use ($user) {
return ['profileEnabled' => $this->profileManager->isProfileEnabled($user)];
});
\OCP\Util::addScript('user_status', 'menu');
\OCP\Util::addStyle('user_status', 'user-status-menu');
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Anna Larch <anna.larch@gmx.net>
*
* @author Anna Larch <anna.larch@gmx.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Listener;
use OCA\DAV\BackgroundJob\UserStatusAutomation;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\OutOfOfficeChangedEvent;
use OCP\User\Events\OutOfOfficeClearedEvent;
use OCP\User\Events\OutOfOfficeScheduledEvent;
use OCP\UserStatus\IManager;
use OCP\UserStatus\IUserStatus;
/**
* Class UserDeletedListener
*
* @template-implements IEventListener<OutOfOfficeScheduledEvent|OutOfOfficeChangedEvent|OutOfOfficeClearedEvent>
*
*/
class OutOfOfficeStatusListener implements IEventListener {
public function __construct(private IJobList $jobsList,
private ITimeFactory $time,
private IManager $manager) {
}
/**
* @inheritDoc
*/
public function handle(Event $event): void {
if($event instanceof OutOfOfficeClearedEvent) {
$this->manager->revertUserStatus($event->getData()->getUser()->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND);
$this->jobsList->scheduleAfter(UserStatusAutomation::class, $this->time->getTime(), ['userId' => $event->getData()->getUser()->getUID()]);
return;
}
if ($event instanceof OutOfOfficeScheduledEvent
|| $event instanceof OutOfOfficeChangedEvent) {
// This might be overwritten by the office hours automation, but that is ok. This is just in case no office hours are set
$this->jobsList->scheduleAfter(UserStatusAutomation::class, $this->time->getTime(), ['userId' => $event->getData()->getUser()->getUID()]);
}
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Listener;
use OCA\UserStatus\Service\StatusService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\UserDeletedEvent;
/**
* Class UserDeletedListener
*
* @package OCA\UserStatus\Listener
*/
class UserDeletedListener implements IEventListener {
/** @var StatusService */
private $service;
/**
* UserDeletedListener constructor.
*
* @param StatusService $service
*/
public function __construct(StatusService $service) {
$this->service = $service;
}
/**
* @inheritDoc
*/
public function handle(Event $event): void {
if (!($event instanceof UserDeletedEvent)) {
// Unrelated
return;
}
$user = $event->getUser();
$this->service->removeUserStatus($user->getUID());
}
}
@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Listener;
use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
use OCA\UserStatus\Connector\UserStatus as ConnectorUserStatus;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Db\UserStatusMapper;
use OCA\UserStatus\Service\StatusService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\UserLiveStatusEvent;
use OCP\UserStatus\IUserStatus;
use Psr\Log\LoggerInterface;
/**
* Class UserDeletedListener
*
* @package OCA\UserStatus\Listener
*/
class UserLiveStatusListener implements IEventListener {
private UserStatusMapper $mapper;
private StatusService $statusService;
private ITimeFactory $timeFactory;
public function __construct(UserStatusMapper $mapper,
StatusService $statusService,
ITimeFactory $timeFactory,
private CalendarStatusService $calendarStatusService,
private LoggerInterface $logger) {
$this->mapper = $mapper;
$this->statusService = $statusService;
$this->timeFactory = $timeFactory;
}
/**
* @inheritDoc
*/
public function handle(Event $event): void {
if (!($event instanceof UserLiveStatusEvent)) {
// Unrelated
return;
}
$user = $event->getUser();
try {
$this->calendarStatusService->processCalendarStatus($user->getUID());
$userStatus = $this->statusService->findByUserId($user->getUID());
} catch (DoesNotExistException $ex) {
$userStatus = new UserStatus();
$userStatus->setUserId($user->getUID());
$userStatus->setStatus(IUserStatus::OFFLINE);
$userStatus->setStatusTimestamp(0);
$userStatus->setIsUserDefined(false);
}
// If the status is user-defined and one of the persistent status, we
// will not override it.
if ($userStatus->getIsUserDefined() &&
\in_array($userStatus->getStatus(), StatusService::PERSISTENT_STATUSES, true)) {
return;
}
// Don't overwrite the "away" calendar status if it's set
if($userStatus->getMessageId() === IUserStatus::MESSAGE_CALENDAR_BUSY) {
$event->setUserStatus(new ConnectorUserStatus($userStatus));
return;
}
$needsUpdate = false;
// If the current status is older than 5 minutes,
// treat it as outdated and update
if ($userStatus->getStatusTimestamp() < ($this->timeFactory->getTime() - StatusService::INVALIDATE_STATUS_THRESHOLD)) {
$needsUpdate = true;
}
// If the emitted status is more important than the current status
// treat it as outdated and update
if (array_search($event->getStatus(), StatusService::PRIORITY_ORDERED_STATUSES) < array_search($userStatus->getStatus(), StatusService::PRIORITY_ORDERED_STATUSES)) {
$needsUpdate = true;
}
if ($needsUpdate) {
$userStatus->setStatus($event->getStatus());
$userStatus->setStatusTimestamp($event->getTimestamp());
$userStatus->setIsUserDefined(false);
if ($userStatus->getId() === null) {
try {
$this->mapper->insert($userStatus);
} catch (Exception $e) {
if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
// A different process might have written another status
// update to the DB while we're processing our stuff.
// We can safely ignore it as we're only changing between AWAY and ONLINE
// and not doing anything with the message or icon.
$this->logger->debug('Unique constraint violation for live user status', ['exception' => $e]);
return;
}
throw $e;
}
} else {
$this->mapper->update($userStatus);
}
}
$event->setUserStatus(new ConnectorUserStatus($userStatus));
}
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Migration;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Class Version0001Date20200602134824
*
* @package OCA\UserStatus\Migration
*/
class Version0001Date20200602134824 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
* @since 20.0.0
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$statusTable = $schema->createTable('user_status');
$statusTable->addColumn('id', Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
'length' => 20,
'unsigned' => true,
]);
$statusTable->addColumn('user_id', Types::STRING, [
'notnull' => true,
'length' => 255,
]);
$statusTable->addColumn('status', Types::STRING, [
'notnull' => true,
'length' => 255,
]);
$statusTable->addColumn('status_timestamp', Types::INTEGER, [
'notnull' => true,
'length' => 11,
'unsigned' => true,
]);
$statusTable->addColumn('is_user_defined', Types::BOOLEAN, [
'notnull' => false,
]);
$statusTable->addColumn('message_id', Types::STRING, [
'notnull' => false,
'length' => 255,
]);
$statusTable->addColumn('custom_icon', Types::STRING, [
'notnull' => false,
'length' => 255,
]);
$statusTable->addColumn('custom_message', Types::TEXT, [
'notnull' => false,
]);
$statusTable->addColumn('clear_at', Types::INTEGER, [
'notnull' => false,
'length' => 11,
'unsigned' => true,
]);
$statusTable->setPrimaryKey(['id']);
$statusTable->addUniqueIndex(['user_id'], 'user_status_uid_ix');
$statusTable->addIndex(['clear_at'], 'user_status_clr_ix');
return $schema;
}
}
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Migration;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Class Version0001Date20200602134824
*
* @package OCA\UserStatus\Migration
*/
class Version0002Date20200902144824 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
* @since 20.0.0
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$statusTable = $schema->getTable('user_status');
$statusTable->addIndex(['status_timestamp'], 'user_status_tstmp_ix');
$statusTable->addIndex(['is_user_defined', 'status'], 'user_status_iud_ix');
return $schema;
}
}
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version1000Date20201111130204 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$result = $this->ensureColumnIsNullable($schema, 'user_status', 'is_user_defined');
return $result ? $schema : null;
}
protected function ensureColumnIsNullable(ISchemaWrapper $schema, string $tableName, string $columnName): bool {
$table = $schema->getTable($tableName);
$column = $table->getColumn($columnName);
if ($column->getNotnull()) {
$column->setNotnull(false);
return true;
}
return false;
}
}
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Carl Schwan <carl@carlschwan.eu>
*
* @author Carl Schwan <carl@carlschwan.eu>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Migration;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* @package OCA\UserStatus\Migration
*/
class Version1003Date20210809144824 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
* @since 23.0.0
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$statusTable = $schema->getTable('user_status');
if (!$statusTable->hasColumn('is_backup')) {
$statusTable->addColumn('is_backup', Types::BOOLEAN, [
'notnull' => false,
'default' => false,
]);
}
return $schema;
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version1008Date20230921144701 extends SimpleMigrationStep {
public function __construct(private IDBConnection $connection) {
}
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$statusTable = $schema->getTable('user_status');
if (!($statusTable->hasColumn('status_message_timestamp'))) {
$statusTable->addColumn('status_message_timestamp', Types::INTEGER, [
'notnull' => true,
'length' => 11,
'unsigned' => true,
'default' => 0,
]);
}
if (!$statusTable->hasIndex('user_status_mtstmp_ix')) {
$statusTable->addIndex(['status_message_timestamp'], 'user_status_mtstmp_ix');
}
return $schema;
}
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
$qb = $this->connection->getQueryBuilder();
$update = $qb->update('user_status')
->set('status_message_timestamp', 'status_timestamp');
$update->executeStatement();
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
*
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus;
/**
* @psalm-type UserStatusClearAtTimeType = "day"|"week"
*
* @psalm-type UserStatusClearAt = array{
* type: "period"|"end-of",
* time: int|UserStatusClearAtTimeType,
* }
*
* @psalm-type UserStatusPredefined = array{
* id: string,
* icon: string,
* message: string,
* clearAt: ?UserStatusClearAt,
* visible: ?bool,
* }
*
* @psalm-type UserStatusType = "online"|"away"|"dnd"|"busy"|"offline"|"invisible"
*
* @psalm-type UserStatusPublic = array{
* userId: string,
* message: ?string,
* icon: ?string,
* clearAt: ?int,
* status: UserStatusType,
* }
*
* @psalm-type UserStatusPrivate = UserStatusPublic&array{
* messageId: ?string,
* messageIsPredefined: bool,
* statusIsUserDefined: bool,
* }
*/
class ResponseDefinitions {
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Service;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IUserSession;
use OCP\UserStatus\IUserStatus;
class JSDataService implements \JsonSerializable {
/** @var IUserSession */
private $userSession;
/** @var StatusService */
private $statusService;
/**
* JSDataService constructor.
*
* @param IUserSession $userSession
* @param StatusService $statusService
*/
public function __construct(IUserSession $userSession,
StatusService $statusService) {
$this->userSession = $userSession;
$this->statusService = $statusService;
}
public function jsonSerialize(): array {
$user = $this->userSession->getUser();
if ($user === null) {
return [];
}
try {
$status = $this->statusService->findByUserId($user->getUID());
} catch (DoesNotExistException $ex) {
return [
'userId' => $user->getUID(),
'message' => null,
'messageId' => null,
'messageIsPredefined' => false,
'icon' => null,
'clearAt' => null,
'status' => IUserStatus::OFFLINE,
'statusIsUserDefined' => false,
];
}
return [
'userId' => $status->getUserId(),
'message' => $status->getCustomMessage(),
'messageId' => $status->getMessageId(),
'messageIsPredefined' => $status->getMessageId() !== null,
'icon' => $status->getCustomIcon(),
'clearAt' => $status->getClearAt(),
'status' => $status->getStatus(),
'statusIsUserDefined' => $status->getIsUserDefined(),
];
}
}
@@ -0,0 +1,225 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Service;
use OCP\IL10N;
use OCP\UserStatus\IUserStatus;
/**
* Class DefaultStatusService
*
* We are offering a set of default statuses, so we can
* translate them into different languages.
*
* @package OCA\UserStatus\Service
*/
class PredefinedStatusService {
private const MEETING = 'meeting';
private const COMMUTING = 'commuting';
private const SICK_LEAVE = 'sick-leave';
private const VACATIONING = 'vacationing';
private const REMOTE_WORK = 'remote-work';
/**
* @deprecated See \OCP\UserStatus\IUserStatus::MESSAGE_CALL
*/
public const CALL = 'call';
public const OUT_OF_OFFICE = 'out-of-office';
/** @var IL10N */
private $l10n;
/**
* DefaultStatusService constructor.
*
* @param IL10N $l10n
*/
public function __construct(IL10N $l10n) {
$this->l10n = $l10n;
}
/**
* @return array
*/
public function getDefaultStatuses(): array {
return [
[
'id' => self::MEETING,
'icon' => '📅',
'message' => $this->getTranslatedStatusForId(self::MEETING),
'clearAt' => [
'type' => 'period',
'time' => 3600,
],
],
[
'id' => self::COMMUTING,
'icon' => '🚌',
'message' => $this->getTranslatedStatusForId(self::COMMUTING),
'clearAt' => [
'type' => 'period',
'time' => 1800,
],
],
[
'id' => self::REMOTE_WORK,
'icon' => '🏡',
'message' => $this->getTranslatedStatusForId(self::REMOTE_WORK),
'clearAt' => [
'type' => 'end-of',
'time' => 'day',
],
],
[
'id' => self::SICK_LEAVE,
'icon' => '🤒',
'message' => $this->getTranslatedStatusForId(self::SICK_LEAVE),
'clearAt' => [
'type' => 'end-of',
'time' => 'day',
],
],
[
'id' => self::VACATIONING,
'icon' => '🌴',
'message' => $this->getTranslatedStatusForId(self::VACATIONING),
'clearAt' => null,
],
[
'id' => self::CALL,
'icon' => '💬',
'message' => $this->getTranslatedStatusForId(self::CALL),
'clearAt' => null,
'visible' => false,
],
[
'id' => self::OUT_OF_OFFICE,
'icon' => '🛑',
'message' => $this->getTranslatedStatusForId(self::OUT_OF_OFFICE),
'clearAt' => null,
'visible' => false,
],
];
}
/**
* @param string $id
* @return array|null
*/
public function getDefaultStatusById(string $id): ?array {
foreach ($this->getDefaultStatuses() as $status) {
if ($status['id'] === $id) {
return $status;
}
}
return null;
}
/**
* @param string $id
* @return string|null
*/
public function getIconForId(string $id): ?string {
switch ($id) {
case self::MEETING:
return '📅';
case self::COMMUTING:
return '🚌';
case self::SICK_LEAVE:
return '🤒';
case self::VACATIONING:
return '🌴';
case self::OUT_OF_OFFICE:
return '🛑';
case self::REMOTE_WORK:
return '🏡';
case self::CALL:
return '💬';
default:
return null;
}
}
/**
* @param string $lang
* @param string $id
* @return string|null
*/
public function getTranslatedStatusForId(string $id): ?string {
switch ($id) {
case self::MEETING:
return $this->l10n->t('In a meeting');
case self::COMMUTING:
return $this->l10n->t('Commuting');
case self::SICK_LEAVE:
return $this->l10n->t('Out sick');
case self::VACATIONING:
return $this->l10n->t('Vacationing');
case self::OUT_OF_OFFICE:
return $this->l10n->t('Out of office');
case self::REMOTE_WORK:
return $this->l10n->t('Working remotely');
case self::CALL:
return $this->l10n->t('In a call');
default:
return null;
}
}
/**
* @param string $id
* @return bool
*/
public function isValidId(string $id): bool {
return \in_array($id, [
self::MEETING,
self::COMMUTING,
self::SICK_LEAVE,
self::VACATIONING,
self::OUT_OF_OFFICE,
self::REMOTE_WORK,
IUserStatus::MESSAGE_CALL,
IUserStatus::MESSAGE_AVAILABILITY,
IUserStatus::MESSAGE_VACATION,
IUserStatus::MESSAGE_CALENDAR_BUSY,
IUserStatus::MESSAGE_CALENDAR_BUSY_TENTATIVE,
], true);
}
}
@@ -0,0 +1,579 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Anna Larch <anna.larch@gmx.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
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;
use OCP\DB\Exception;
use OCP\IConfig;
use OCP\IEmojiHelper;
use OCP\IUserManager;
use OCP\UserStatus\IUserStatus;
use function in_array;
/**
* Class StatusService
*
* @package OCA\UserStatus\Service
*/
class StatusService {
private bool $shareeEnumeration;
private bool $shareeEnumerationInGroupOnly;
private bool $shareeEnumerationPhone;
/**
* List of priorities ordered by their priority
*/
public const PRIORITY_ORDERED_STATUSES = [
IUserStatus::ONLINE,
IUserStatus::AWAY,
IUserStatus::DND,
IUserStatus::BUSY,
IUserStatus::INVISIBLE,
IUserStatus::OFFLINE,
];
/**
* List of statuses that persist the clear-up
* or UserLiveStatusEvents
*/
public const PERSISTENT_STATUSES = [
IUserStatus::AWAY,
IUserStatus::BUSY,
IUserStatus::DND,
IUserStatus::INVISIBLE,
];
/** @var int */
public const INVALIDATE_STATUS_THRESHOLD = 15 /* minutes */ * 60 /* seconds */;
/** @var int */
public const MAXIMUM_MESSAGE_LENGTH = 80;
public function __construct(private UserStatusMapper $mapper,
private ITimeFactory $timeFactory,
private PredefinedStatusService $predefinedStatusService,
private IEmojiHelper $emojiHelper,
private IConfig $config,
private IUserManager $userManager) {
$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';
}
/**
* @param int|null $limit
* @param int|null $offset
* @return UserStatus[]
*/
public function findAll(?int $limit = null, ?int $offset = null): array {
// 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 [];
}
return array_map(function ($status) {
return $this->processStatus($status);
}, $this->mapper->findAll($limit, $offset));
}
/**
* @param int|null $limit
* @param int|null $offset
* @return array
*/
public function findAllRecentStatusChanges(?int $limit = null, ?int $offset = null): array {
// 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 [];
}
return array_map(function ($status) {
return $this->processStatus($status);
}, $this->mapper->findAllRecent($limit, $offset));
}
/**
* @param string $userId
* @return UserStatus
* @throws DoesNotExistException
*/
public function findByUserId(string $userId): UserStatus {
return $this->processStatus($this->mapper->findByUserId($userId));
}
/**
* @param array $userIds
* @return UserStatus[]
*/
public function findByUserIds(array $userIds):array {
return array_map(function ($status) {
return $this->processStatus($status);
}, $this->mapper->findByUserIds($userIds));
}
/**
* @param string $userId
* @param string $status
* @param int|null $statusTimestamp
* @param bool $isUserDefined
* @return UserStatus
* @throws InvalidStatusTypeException
*/
public function setStatus(string $userId,
string $status,
?int $statusTimestamp,
bool $isUserDefined): UserStatus {
try {
$userStatus = $this->mapper->findByUserId($userId);
} catch (DoesNotExistException $ex) {
$userStatus = new UserStatus();
$userStatus->setUserId($userId);
}
// Check if status-type is valid
if (!in_array($status, self::PRIORITY_ORDERED_STATUSES, true)) {
throw new InvalidStatusTypeException('Status-type "' . $status . '" is not supported');
}
if ($statusTimestamp === null) {
$statusTimestamp = $this->timeFactory->getTime();
}
$userStatus->setStatus($status);
$userStatus->setStatusTimestamp($statusTimestamp);
$userStatus->setIsUserDefined($isUserDefined);
$userStatus->setIsBackup(false);
if ($userStatus->getId() === null) {
return $this->mapper->insert($userStatus);
}
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,
string $messageId,
?int $clearAt): UserStatus {
try {
$userStatus = $this->mapper->findByUserId($userId);
} catch (DoesNotExistException $ex) {
$userStatus = new UserStatus();
$userStatus->setUserId($userId);
$userStatus->setStatus(IUserStatus::OFFLINE);
$userStatus->setStatusTimestamp(0);
$userStatus->setIsUserDefined(false);
$userStatus->setIsBackup(false);
}
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);
$userStatus->setStatusMessageTimestamp($this->timeFactory->now()->getTimestamp());
if ($userStatus->getId() === null) {
return $this->mapper->insert($userStatus);
}
return $this->mapper->update($userStatus);
}
/**
* @param string $userId
* @param string $status
* @param string $messageId
* @param bool $createBackup
* @param string|null $customMessage
* @throws InvalidStatusTypeException
* @throws InvalidMessageIdException
*/
public function setUserStatus(string $userId,
string $status,
string $messageId,
bool $createBackup,
?string $customMessage = null): ?UserStatus {
// Check if status-type is valid
if (!in_array($status, self::PRIORITY_ORDERED_STATUSES, true)) {
throw new InvalidStatusTypeException('Status-type "' . $status . '" is not supported');
}
if (!$this->predefinedStatusService->isValidId($messageId)) {
throw new InvalidMessageIdException('Message-Id "' . $messageId . '" is not supported');
}
try {
$userStatus = $this->mapper->findByUserId($userId);
} catch (DoesNotExistException $e) {
// We don't need to do anything
$userStatus = new UserStatus();
$userStatus->setUserId($userId);
}
// CALL trumps CALENDAR status, but we don't need to do anything but overwrite the message
if ($userStatus->getMessageId() === IUserStatus::MESSAGE_CALENDAR_BUSY && $messageId === IUserStatus::MESSAGE_CALL) {
$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);
}
if ($createBackup) {
if ($this->backupCurrentStatus($userId) === false) {
return null; // Already a status set automatically => abort.
}
// If we just created the backup
// we need to create a new status to insert
// Unfortunatley there's no way to unset the DB ID on an Entity
$userStatus = new UserStatus();
$userStatus->setUserId($userId);
}
$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);
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);
}
if ($userStatus->getId() !== null) {
return $this->mapper->update($userStatus);
}
return $this->mapper->insert($userStatus);
}
/**
* @param string $userId
* @param string|null $statusIcon
* @param string|null $message
* @param int|null $clearAt
* @return UserStatus
* @throws InvalidClearAtException
* @throws InvalidStatusIconException
* @throws StatusMessageTooLongException
*/
public function setCustomMessage(string $userId,
?string $statusIcon,
?string $message,
?int $clearAt): UserStatus {
try {
$userStatus = $this->mapper->findByUserId($userId);
} catch (DoesNotExistException $ex) {
$userStatus = new UserStatus();
$userStatus->setUserId($userId);
$userStatus->setStatus(IUserStatus::OFFLINE);
$userStatus->setStatusTimestamp(0);
$userStatus->setIsUserDefined(false);
}
// Check if statusIcon contains only one character
if ($statusIcon !== null && !$this->emojiHelper->isValidSingleEmoji($statusIcon)) {
throw new InvalidStatusIconException('Status-Icon is longer than one character');
}
// Check for maximum length of custom message
if ($message !== null && \mb_strlen($message) > self::MAXIMUM_MESSAGE_LENGTH) {
throw new StatusMessageTooLongException('Message is longer than supported length of ' . self::MAXIMUM_MESSAGE_LENGTH . ' characters');
}
// 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);
$userStatus->setStatusMessageTimestamp($this->timeFactory->now()->getTimestamp());
if ($userStatus->getId() === null) {
return $this->mapper->insert($userStatus);
}
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;
}
$userStatus->setStatus(IUserStatus::OFFLINE);
$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);
$userStatus->setStatusMessageTimestamp(0);
$this->mapper->update($userStatus);
return true;
}
/**
* @param string $userId
* @return bool
*/
public function removeUserStatus(string $userId): bool {
try {
$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);
} 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
* @return UserStatus
*/
private function processStatus(UserStatus $status): UserStatus {
$clearAt = $status->getClearAt();
if ($status->getStatusTimestamp() < $this->timeFactory->getTime() - self::INVALIDATE_STATUS_THRESHOLD
&& (!$status->getIsUserDefined() || $status->getStatus() === IUserStatus::ONLINE)) {
$this->cleanStatus($status);
}
if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
$this->cleanStatus($status);
$this->cleanStatusMessage($status);
}
if ($status->getMessageId() !== null) {
$this->addDefaultMessage($status);
}
return $status;
}
/**
* @param UserStatus $status
*/
private function cleanStatus(UserStatus $status): void {
if ($status->getStatus() === IUserStatus::OFFLINE && !$status->getIsUserDefined()) {
return;
}
$status->setStatus(IUserStatus::OFFLINE);
$status->setStatusTimestamp($this->timeFactory->getTime());
$status->setIsUserDefined(false);
$this->mapper->update($status);
}
/**
* @param UserStatus $status
*/
private function cleanStatusMessage(UserStatus $status): void {
$status->setMessageId(null);
$status->setCustomIcon(null);
$status->setCustomMessage(null);
$status->setClearAt(null);
$status->setStatusMessageTimestamp(0);
$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());
if ($predefinedMessage === null) {
return;
}
// If there is a custom message, don't overwrite it
if(empty($status->getCustomMessage())) {
$status->setCustomMessage($predefinedMessage['message']);
}
if(empty($status->getCustomIcon())) {
$status->setCustomIcon($predefinedMessage['icon']);
}
}
/**
* @return bool false if there is already a backup. In this case abort the procedure.
*/
public function backupCurrentStatus(string $userId): bool {
try {
$this->mapper->createBackupStatus($userId);
return true;
} catch (Exception $ex) {
if ($ex->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
return false;
}
throw $ex;
}
}
public function revertUserStatus(string $userId, string $messageId, bool $revertedManually = false): ?UserStatus {
try {
/** @var UserStatus $userStatus */
$backupUserStatus = $this->mapper->findByUserId($userId, true);
} catch (DoesNotExistException $ex) {
// No user status to revert, do nothing
return null;
}
$deleted = $this->mapper->deleteCurrentStatusToRestoreBackup($userId, $messageId);
if (!$deleted) {
// Another status is set automatically or no status, do nothing
return null;
}
if ($revertedManually && $backupUserStatus->getStatus() === IUserStatus::OFFLINE) {
// When the user reverts the status manually they are online
$backupUserStatus->setStatus(IUserStatus::ONLINE);
}
$backupUserStatus->setIsBackup(false);
// Remove the underscore prefix added when creating the backup
$backupUserStatus->setUserId(substr($backupUserStatus->getUserId(), 1));
$this->mapper->update($backupUserStatus);
return $backupUserStatus;
}
public function revertMultipleUserStatus(array $userIds, string $messageId): void {
// 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()
&& $userStatus->getMessageId() === $messageId) {
$statuesToDelete[$userStatus->getUserId()] = $userStatus->getId();
} elseif ($userStatus->getIsBackup()) {
$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);
}
}