aboutsummaryrefslogtreecommitdiffstats
path: root/apps/user_status/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/user_status/lib')
-rw-r--r--apps/user_status/lib/AppInfo/Application.php34
-rw-r--r--apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php34
-rw-r--r--apps/user_status/lib/Capabilities.php43
-rw-r--r--apps/user_status/lib/Connector/UserStatus.php45
-rw-r--r--apps/user_status/lib/Connector/UserStatusProvider.php39
-rw-r--r--apps/user_status/lib/ContactsMenu/StatusProvider.php53
-rw-r--r--apps/user_status/lib/Controller/HeartbeatController.php89
-rw-r--r--apps/user_status/lib/Controller/PredefinedStatusController.php53
-rw-r--r--apps/user_status/lib/Controller/StatusesController.php73
-rw-r--r--apps/user_status/lib/Controller/UserStatusController.php168
-rw-r--r--apps/user_status/lib/Dashboard/UserStatusWidget.php149
-rw-r--r--apps/user_status/lib/Db/UserStatus.php42
-rw-r--r--apps/user_status/lib/Db/UserStatusMapper.php58
-rw-r--r--apps/user_status/lib/Exception/InvalidClearAtException.php21
-rw-r--r--apps/user_status/lib/Exception/InvalidMessageIdException.php21
-rw-r--r--apps/user_status/lib/Exception/InvalidStatusIconException.php21
-rw-r--r--apps/user_status/lib/Exception/InvalidStatusTypeException.php21
-rw-r--r--apps/user_status/lib/Exception/StatusMessageTooLongException.php21
-rw-r--r--apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php46
-rw-r--r--apps/user_status/lib/Listener/OutOfOfficeStatusListener.php57
-rw-r--r--apps/user_status/lib/Listener/UserDeletedListener.php32
-rw-r--r--apps/user_status/lib/Listener/UserLiveStatusListener.php72
-rw-r--r--apps/user_status/lib/Migration/Version0001Date20200602134824.php25
-rw-r--r--apps/user_status/lib/Migration/Version0002Date20200902144824.php21
-rw-r--r--apps/user_status/lib/Migration/Version1000Date20201111130204.php21
-rw-r--r--apps/user_status/lib/Migration/Version1003Date20210809144824.php43
-rw-r--r--apps/user_status/lib/Migration/Version1008Date20230921144701.php54
-rw-r--r--apps/user_status/lib/Migration/Version2301Date20210809144824.php58
-rw-r--r--apps/user_status/lib/ResponseDefinitions.php44
-rw-r--r--apps/user_status/lib/Service/EmojiService.php102
-rw-r--r--apps/user_status/lib/Service/JSDataService.php35
-rw-r--r--apps/user_status/lib/Service/PredefinedStatusService.php71
-rw-r--r--apps/user_status/lib/Service/StatusService.php246
33 files changed, 869 insertions, 1043 deletions
diff --git a/apps/user_status/lib/AppInfo/Application.php b/apps/user_status/lib/AppInfo/Application.php
index 7a47fc45c95..5199c3fdbf0 100644
--- a/apps/user_status/lib/AppInfo/Application.php
+++ b/apps/user_status/lib/AppInfo/Application.php
@@ -3,40 +3,29 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
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 OCA\UserStatus\Dashboard\UserStatusWidget;
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\OutOfOfficeEndedEvent;
+use OCP\User\Events\OutOfOfficeScheduledEvent;
+use OCP\User\Events\OutOfOfficeStartedEvent;
use OCP\User\Events\UserDeletedEvent;
use OCP\User\Events\UserLiveStatusEvent;
use OCP\UserStatus\IManager;
@@ -71,6 +60,11 @@ class Application extends App implements IBootstrap {
$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);
+ $context->registerEventListener(OutOfOfficeStartedEvent::class, OutOfOfficeStatusListener::class);
+ $context->registerEventListener(OutOfOfficeEndedEvent::class, OutOfOfficeStatusListener::class);
$config = $this->getContainer()->query(IConfig::class);
$shareeEnumeration = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
diff --git a/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php b/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php
index 0d8f5ac431b..51a9c623a03 100644
--- a/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php
+++ b/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\BackgroundJob;
@@ -37,21 +20,18 @@ use OCP\BackgroundJob\TimedJob;
*/
class ClearOldStatusesBackgroundJob extends TimedJob {
- /** @var UserStatusMapper */
- private $mapper;
-
/**
* ClearOldStatusesBackgroundJob constructor.
*
* @param ITimeFactory $time
* @param UserStatusMapper $mapper
*/
- public function __construct(ITimeFactory $time,
- UserStatusMapper $mapper) {
+ public function __construct(
+ ITimeFactory $time,
+ private UserStatusMapper $mapper,
+ ) {
parent::__construct($time);
- $this->mapper = $mapper;
- // Run every time the cron is run
$this->setInterval(60);
}
@@ -61,7 +41,7 @@ class ClearOldStatusesBackgroundJob extends TimedJob {
protected function run($argument) {
$now = $this->time->getTime();
- $this->mapper->clearMessagesOlderThan($now);
+ $this->mapper->clearOlderThanClearAt($now);
$this->mapper->clearStatusesOlderThan($now - StatusService::INVALIDATE_STATUS_THRESHOLD, $now);
}
}
diff --git a/apps/user_status/lib/Capabilities.php b/apps/user_status/lib/Capabilities.php
index f5b35896a56..c3edbc032d6 100644
--- a/apps/user_status/lib/Capabilities.php
+++ b/apps/user_status/lib/Capabilities.php
@@ -3,30 +3,13 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus;
-use OCA\UserStatus\Service\EmojiService;
use OCP\Capabilities\ICapability;
+use OCP\IEmojiHelper;
/**
* Class Capabilities
@@ -34,27 +17,21 @@ use OCP\Capabilities\ICapability;
* @package OCA\UserStatus
*/
class Capabilities implements ICapability {
-
- /** @var EmojiService */
- private $emojiService;
-
- /**
- * Capabilities constructor.
- *
- * @param EmojiService $emojiService
- */
- public function __construct(EmojiService $emojiService) {
- $this->emojiService = $emojiService;
+ public function __construct(
+ private IEmojiHelper $emojiHelper,
+ ) {
}
/**
- * @inheritDoc
+ * @return array{user_status: array{enabled: bool, restore: bool, supports_emoji: bool, supports_busy: bool}}
*/
public function getCapabilities() {
return [
'user_status' => [
'enabled' => true,
- 'supports_emoji' => $this->emojiService->doesPlatformSupportEmoji(),
+ 'restore' => true,
+ 'supports_emoji' => $this->emojiHelper->doesPlatformSupportEmoji(),
+ 'supports_busy' => true,
],
];
}
diff --git a/apps/user_status/lib/Connector/UserStatus.php b/apps/user_status/lib/Connector/UserStatus.php
index ff05ded9e2b..04467a99e5e 100644
--- a/apps/user_status/lib/Connector/UserStatus.php
+++ b/apps/user_status/lib/Connector/UserStatus.php
@@ -3,31 +3,14 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Connector;
use DateTimeImmutable;
-use OCP\UserStatus\IUserStatus;
use OCA\UserStatus\Db;
+use OCP\UserStatus\IUserStatus;
class UserStatus implements IUserStatus {
@@ -46,21 +29,19 @@ class UserStatus implements IUserStatus {
/** @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();
+ public function __construct(
+ private Db\UserStatus $internalStatus,
+ ) {
+ $this->userId = $this->internalStatus->getUserId();
+ $this->status = $this->internalStatus->getStatus();
+ $this->message = $this->internalStatus->getCustomMessage();
+ $this->icon = $this->internalStatus->getCustomIcon();
- if ($status->getStatus() === IUserStatus::INVISIBLE) {
+ if ($this->internalStatus->getStatus() === IUserStatus::INVISIBLE) {
$this->status = IUserStatus::OFFLINE;
}
- if ($status->getClearAt() !== null) {
- $this->clearAt = DateTimeImmutable::createFromFormat('U', (string)$status->getClearAt());
+ if ($this->internalStatus->getClearAt() !== null) {
+ $this->clearAt = DateTimeImmutable::createFromFormat('U', (string)$this->internalStatus->getClearAt());
}
}
diff --git a/apps/user_status/lib/Connector/UserStatusProvider.php b/apps/user_status/lib/Connector/UserStatusProvider.php
index 46b89739f7c..e84d69d1eb2 100644
--- a/apps/user_status/lib/Connector/UserStatusProvider.php
+++ b/apps/user_status/lib/Connector/UserStatusProvider.php
@@ -3,44 +3,25 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Connector;
+use OC\UserStatus\ISettableProvider;
use OCA\UserStatus\Service\StatusService;
use OCP\UserStatus\IProvider;
-use OC\UserStatus\ISettableProvider;
class UserStatusProvider implements IProvider, ISettableProvider {
- /** @var StatusService */
- private $service;
-
/**
* UserStatusProvider constructor.
*
* @param StatusService $service
*/
- public function __construct(StatusService $service) {
- $this->service = $service;
+ public function __construct(
+ private StatusService $service,
+ ) {
}
/**
@@ -57,15 +38,15 @@ class UserStatusProvider implements IProvider, ISettableProvider {
return $userStatuses;
}
- public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup): void {
- $this->service->setUserStatus($userId, $status, $messageId, $createBackup);
+ 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, $status);
+ $this->service->revertUserStatus($userId, $messageId);
}
public function revertMultipleUserStatus(array $userIds, string $messageId, string $status): void {
- $this->service->revertMultipleUserStatus($userIds, $messageId, $status);
+ $this->service->revertMultipleUserStatus($userIds, $messageId);
}
}
diff --git a/apps/user_status/lib/ContactsMenu/StatusProvider.php b/apps/user_status/lib/ContactsMenu/StatusProvider.php
new file mode 100644
index 00000000000..6a6949b46ba
--- /dev/null
+++ b/apps/user_status/lib/ContactsMenu/StatusProvider.php
@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+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(),
+ );
+ }
+ }
+ }
+
+}
diff --git a/apps/user_status/lib/Controller/HeartbeatController.php b/apps/user_status/lib/Controller/HeartbeatController.php
index c11a63b4420..30f4af6572a 100644
--- a/apps/user_status/lib/Controller/HeartbeatController.php
+++ b/apps/user_status/lib/Controller/HeartbeatController.php
@@ -3,34 +3,19 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Controller;
use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\ResponseDefinitions;
use OCA\UserStatus\Service\StatusService;
-use OCP\AppFramework\Controller;
-use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
-use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IRequest;
@@ -38,47 +23,43 @@ use OCP\IUserSession;
use OCP\User\Events\UserLiveStatusEvent;
use OCP\UserStatus\IUserStatus;
-class HeartbeatController extends Controller {
-
- /** @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) {
+/**
+ * @psalm-import-type UserStatusPrivate from ResponseDefinitions
+ */
+class HeartbeatController extends OCSController {
+
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private IEventDispatcher $eventDispatcher,
+ private IUserSession $userSession,
+ private ITimeFactory $timeFactory,
+ private StatusService $service,
+ ) {
parent::__construct($appName, $request);
- $this->eventDispatcher = $eventDispatcher;
- $this->userSession = $userSession;
- $this->timeFactory = $timeFactory;
- $this->service = $service;
}
/**
- * @NoAdminRequired
+ * Keep the status alive
+ *
+ * @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, list<empty>, array{}>
*
- * @param string $status
- * @return JSONResponse
+ * 200: Status successfully updated
+ * 204: User has no status to keep alive
+ * 400: Invalid status to update
*/
- public function heartbeat(string $status): JSONResponse {
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'PUT', url: '/api/v1/heartbeat')]
+ public function heartbeat(string $status): DataResponse {
if (!\in_array($status, [IUserStatus::ONLINE, IUserStatus::AWAY], true)) {
- return new JSONResponse([], Http::STATUS_BAD_REQUEST);
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$user = $this->userSession->getUser();
if ($user === null) {
- return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
+ return new DataResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
}
$event = new UserLiveStatusEvent(
@@ -91,11 +72,11 @@ class HeartbeatController extends Controller {
$userStatus = $event->getUserStatus();
if (!$userStatus) {
- return new JSONResponse([], Http::STATUS_NO_CONTENT);
+ return new DataResponse([], Http::STATUS_NO_CONTENT);
}
/** @psalm-suppress UndefinedInterfaceMethod */
- return new JSONResponse($this->formatStatus($userStatus->getInternal()));
+ return new DataResponse($this->formatStatus($userStatus->getInternal()));
}
private function formatStatus(UserStatus $status): array {
diff --git a/apps/user_status/lib/Controller/PredefinedStatusController.php b/apps/user_status/lib/Controller/PredefinedStatusController.php
index ea1ff5209b8..70262d1108a 100644
--- a/apps/user_status/lib/Controller/PredefinedStatusController.php
+++ b/apps/user_status/lib/Controller/PredefinedStatusController.php
@@ -3,43 +3,27 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Controller;
+use OCA\UserStatus\ResponseDefinitions;
use OCA\UserStatus\Service\PredefinedStatusService;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
/**
- * Class DefaultStatusController
- *
* @package OCA\UserStatus\Controller
+ *
+ * @psalm-import-type UserStatusPredefined from ResponseDefinitions
*/
class PredefinedStatusController extends OCSController {
- /** @var PredefinedStatusService */
- private $predefinedStatusService;
-
/**
* AStatusController constructor.
*
@@ -47,22 +31,27 @@ class PredefinedStatusController extends OCSController {
* @param IRequest $request
* @param PredefinedStatusService $predefinedStatusService
*/
- public function __construct(string $appName,
- IRequest $request,
- PredefinedStatusService $predefinedStatusService) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private PredefinedStatusService $predefinedStatusService,
+ ) {
parent::__construct($appName, $request);
- $this->predefinedStatusService = $predefinedStatusService;
}
/**
- * @NoAdminRequired
+ * Get all predefined messages
+ *
+ * @return DataResponse<Http::STATUS_OK, list<UserStatusPredefined>, array{}>
*
- * @return DataResponse
+ * 200: Predefined statuses returned
*/
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/api/v1/predefined_statuses/')]
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 new DataResponse(array_values(array_filter($this->predefinedStatusService->getDefaultStatuses(), function (array $status) {
return !array_key_exists('visible', $status) || $status['visible'] === true;
- }));
+ })));
}
}
diff --git a/apps/user_status/lib/Controller/StatusesController.php b/apps/user_status/lib/Controller/StatusesController.php
index d30389e1716..44688c39023 100644
--- a/apps/user_status/lib/Controller/StatusesController.php
+++ b/apps/user_status/lib/Controller/StatusesController.php
@@ -3,43 +3,30 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Georg Ehrke
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
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\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
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.
*
@@ -47,35 +34,44 @@ class StatusesController extends OCSController {
* @param IRequest $request
* @param StatusService $service
*/
- public function __construct(string $appName,
- IRequest $request,
- StatusService $service) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private StatusService $service,
+ ) {
parent::__construct($appName, $request);
- $this->service = $service;
}
/**
- * @NoAdminRequired
+ * Find statuses of users
*
- * @param int|null $limit
- * @param int|null $offset
- * @return DataResponse
+ * @param int|null $limit Maximum number of statuses to find
+ * @param non-negative-int|null $offset Offset for finding statuses
+ * @return DataResponse<Http::STATUS_OK, list<UserStatusPublic>, array{}>
+ *
+ * 200: Statuses returned
*/
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/api/v1/statuses')]
public function findAll(?int $limit = null, ?int $offset = null): DataResponse {
$allStatuses = $this->service->findAll($limit, $offset);
- return new DataResponse(array_map(function ($userStatus) {
+ return new DataResponse(array_values(array_map(function ($userStatus) {
return $this->formatStatus($userStatus);
- }, $allStatuses));
+ }, $allStatuses)));
}
/**
- * @NoAdminRequired
+ * Find the status of a user
+ *
+ * @param string $userId ID of the user
+ * @return DataResponse<Http::STATUS_OK, UserStatusPublic, array{}>
+ * @throws OCSNotFoundException The user was not found
*
- * @param string $userId
- * @return DataResponse
- * @throws OCSNotFoundException
+ * 200: Status returned
*/
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/api/v1/statuses/{userId}')]
public function find(string $userId): DataResponse {
try {
$userStatus = $this->service->findByUserId($userId);
@@ -88,9 +84,10 @@ class StatusesController extends OCSController {
/**
* @param UserStatus $status
- * @return array{userId: string, message: string, icon: string, clearAt: int, status: string}
+ * @return UserStatusPublic
*/
private function formatStatus(UserStatus $status): array {
+ /** @var UserStatusType $visibleStatus */
$visibleStatus = $status->getStatus();
if ($visibleStatus === IUserStatus::INVISIBLE) {
$visibleStatus = IUserStatus::OFFLINE;
diff --git a/apps/user_status/lib/Controller/UserStatusController.php b/apps/user_status/lib/Controller/UserStatusController.php
index 8708a7c2aac..9b3807ce86e 100644
--- a/apps/user_status/lib/Controller/UserStatusController.php
+++ b/apps/user_status/lib/Controller/UserStatusController.php
@@ -3,84 +3,60 @@
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>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
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\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
-use OCP\ILogger;
use OCP\IRequest;
+use Psr\Log\LoggerInterface;
+/**
+ * @psalm-import-type UserStatusType from ResponseDefinitions
+ * @psalm-import-type UserStatusPrivate from ResponseDefinitions
+ */
class UserStatusController extends OCSController {
-
- /** @var string */
- private $userId;
-
- /** @var ILogger */
- private $logger;
-
- /** @var StatusService */
- private $service;
-
- /**
- * StatusesController constructor.
- *
- * @param string $appName
- * @param IRequest $request
- * @param string $userId
- * @param ILogger $logger;
- * @param StatusService $service
- */
- public function __construct(string $appName,
- IRequest $request,
- string $userId,
- ILogger $logger,
- StatusService $service) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private ?string $userId,
+ private LoggerInterface $logger,
+ private StatusService $service,
+ private CalendarStatusService $calendarStatusService,
+ ) {
parent::__construct($appName, $request);
- $this->userId = $userId;
- $this->logger = $logger;
- $this->service = $service;
}
/**
- * @NoAdminRequired
+ * Get the status of the current user
*
- * @return DataResponse
- * @throws OCSNotFoundException
+ * @return DataResponse<Http::STATUS_OK, UserStatusPrivate, array{}>
+ * @throws OCSNotFoundException The user was not found
+ *
+ * 200: The status was found successfully
*/
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/api/v1/user_status')]
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');
@@ -90,12 +66,16 @@ class UserStatusController extends OCSController {
}
/**
- * @NoAdminRequired
+ * Update the status type of the current user
+ *
+ * @param string $statusType The new status type
+ * @return DataResponse<Http::STATUS_OK, UserStatusPrivate, array{}>
+ * @throws OCSBadRequestException The status type is invalid
*
- * @param string $statusType
- * @return DataResponse
- * @throws OCSBadRequestException
+ * 200: The status was updated successfully
*/
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'PUT', url: '/api/v1/user_status/status')]
public function setStatus(string $statusType): DataResponse {
try {
$status = $this->service->setStatus($this->userId, $statusType, null, true);
@@ -109,15 +89,19 @@ class UserStatusController extends OCSController {
}
/**
- * @NoAdminRequired
+ * Set the message to a predefined message for the current user
+ *
+ * @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
*
- * @param string $messageId
- * @param int|null $clearAt
- * @return DataResponse
- * @throws OCSBadRequestException
+ * 200: The message was updated successfully
*/
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'PUT', url: '/api/v1/user_status/message/predefined')]
public function setPredefinedMessage(string $messageId,
- ?int $clearAt): DataResponse {
+ ?int $clearAt): DataResponse {
try {
$status = $this->service->setPredefinedMessage($this->userId, $messageId, $clearAt);
$this->service->removeBackupUserStatus($this->userId);
@@ -132,19 +116,24 @@ class UserStatusController extends OCSController {
}
/**
- * @NoAdminRequired
+ * Set the message to a custom message for the current user
*
- * @param string|null $statusIcon
- * @param string $message
- * @param int|null $clearAt
- * @return DataResponse
- * @throws OCSBadRequestException
+ * @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
+ * @throws OCSNotFoundException No status for the current user
+ *
+ * 200: The message was updated successfully
*/
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'PUT', url: '/api/v1/user_status/message/custom')]
public function setCustomMessage(?string $statusIcon,
- ?string $message,
- ?int $clearAt): DataResponse {
+ ?string $message,
+ ?int $clearAt): DataResponse {
try {
- if ($message !== null && $message !== '') {
+ if (($statusIcon !== null && $statusIcon !== '') || ($message !== null && $message !== '') || ($clearAt !== null && $clearAt !== 0)) {
$status = $this->service->setCustomMessage($this->userId, $statusIcon, $message, $clearAt);
} else {
$this->service->clearMessage($this->userId);
@@ -161,34 +150,51 @@ class UserStatusController extends OCSController {
} 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);
+ } catch (DoesNotExistException $ex) {
+ throw new OCSNotFoundException('No status for the current user');
}
}
/**
- * @NoAdminRequired
+ * Clear the message of the current user
*
- * @return DataResponse
+ * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
+ *
+ * 200: Message cleared successfully
*/
- public function clearStatus(): DataResponse {
- $this->service->clearStatus($this->userId);
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'DELETE', url: '/api/v1/user_status/message')]
+ public function clearMessage(): DataResponse {
+ $this->service->clearMessage($this->userId);
return new DataResponse([]);
}
/**
- * @NoAdminRequired
+ * Revert the status to the previous status
*
- * @return DataResponse
+ * @param string $messageId ID of the message to delete
+ *
+ * @return DataResponse<Http::STATUS_OK, UserStatusPrivate|list<empty>, array{}>
+ *
+ * 200: Status reverted
*/
- public function clearMessage(): DataResponse {
- $this->service->clearMessage($this->userId);
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'DELETE', url: '/api/v1/user_status/revert/{messageId}')]
+ 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 array
+ * @return UserStatusPrivate
*/
private function formatStatus(UserStatus $status): array {
+ /** @var UserStatusType $visibleStatus */
+ $visibleStatus = $status->getStatus();
return [
'userId' => $status->getUserId(),
'message' => $status->getCustomMessage(),
@@ -196,7 +202,7 @@ class UserStatusController extends OCSController {
'messageIsPredefined' => $status->getMessageId() !== null,
'icon' => $status->getCustomIcon(),
'clearAt' => $status->getClearAt(),
- 'status' => $status->getStatus(),
+ 'status' => $visibleStatus,
'statusIsUserDefined' => $status->getIsUserDefined(),
];
}
diff --git a/apps/user_status/lib/Dashboard/UserStatusWidget.php b/apps/user_status/lib/Dashboard/UserStatusWidget.php
index 10411dc7f9d..2870a2c1907 100644
--- a/apps/user_status/lib/Dashboard/UserStatusWidget.php
+++ b/apps/user_status/lib/Dashboard/UserStatusWidget.php
@@ -3,34 +3,25 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Dashboard;
use OCA\UserStatus\AppInfo\Application;
use OCA\UserStatus\Db\UserStatus;
use OCA\UserStatus\Service\StatusService;
-use OCP\Dashboard\IWidget;
-use OCP\IInitialStateService;
+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;
@@ -40,42 +31,27 @@ use OCP\UserStatus\IUserStatus;
*
* @package OCA\UserStatus
*/
-class UserStatusWidget implements IWidget {
-
- /** @var IL10N */
- private $l10n;
-
- /** @var IInitialStateService */
- private $initialStateService;
-
- /** @var IUserManager */
- private $userManager;
-
- /** @var IUserSession */
- private $userSession;
-
- /** @var StatusService */
- private $service;
-
+class UserStatusWidget implements IAPIWidget, IAPIWidgetV2, IIconWidget, IOptionWidget {
/**
* UserStatusWidget constructor
*
* @param IL10N $l10n
- * @param IInitialStateService $initialStateService
+ * @param IDateTimeFormatter $dateTimeFormatter
+ * @param IURLGenerator $urlGenerator
+ * @param IInitialState $initialStateService
* @param IUserManager $userManager
* @param IUserSession $userSession
* @param StatusService $service
*/
- public function __construct(IL10N $l10n,
- IInitialStateService $initialStateService,
- IUserManager $userManager,
- IUserSession $userSession,
- StatusService $service) {
- $this->l10n = $l10n;
- $this->initialStateService = $initialStateService;
- $this->userManager = $userManager;
- $this->userSession = $userSession;
- $this->service = $service;
+ public function __construct(
+ private IL10N $l10n,
+ private IDateTimeFormatter $dateTimeFormatter,
+ private IURLGenerator $urlGenerator,
+ private IInitialState $initialStateService,
+ private IUserManager $userManager,
+ private IUserSession $userSession,
+ private StatusService $service,
+ ) {
}
/**
@@ -103,7 +79,16 @@ class UserStatusWidget implements IWidget {
* @inheritDoc
*/
public function getIconClass(): string {
- return 'icon-user-status';
+ return 'icon-user-status-dark';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getIconUrl(): string {
+ return $this->urlGenerator->getAbsoluteURL(
+ $this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg')
+ );
}
/**
@@ -117,28 +102,22 @@ class UserStatusWidget implements IWidget {
* @inheritDoc
*/
public function load(): void {
- \OCP\Util::addScript(Application::APP_ID, 'dashboard');
-
- $currentUser = $this->userSession->getUser();
- if ($currentUser === null) {
- $this->initialStateService->provideInitialState(Application::APP_ID, 'dashboard_data', []);
- return;
- }
- $currentUserId = $currentUser->getUID();
+ }
+ 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(8, 0),
- static function (UserStatus $status) use ($currentUserId): bool {
- return $status->getUserId() !== $currentUserId;
+ $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,
- 7
+ $limit
);
-
- $this->initialStateService->provideInitialState(Application::APP_ID, 'dashboard_data', array_map(function (UserStatus $status): array {
+ return array_map(function (UserStatus $status): array {
$user = $this->userManager->get($status->getUserId());
$displayName = $status->getUserId();
if ($user !== null) {
@@ -153,8 +132,46 @@ class UserStatusWidget implements IWidget {
: $status->getStatus(),
'icon' => $status->getCustomIcon(),
'message' => $status->getCustomMessage(),
- 'timestamp' => $status->getStatusTimestamp(),
+ 'timestamp' => $status->getStatusMessageTimestamp(),
];
- }, $recentStatusUpdates));
+ }, $recentStatusUpdates);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getItems(string $userId, ?string $since = null, int $limit = 7): array {
+ $widgetItemsData = $this->getWidgetData($userId, $since, $limit);
+
+ return array_values(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('profile.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);
}
}
diff --git a/apps/user_status/lib/Db/UserStatus.php b/apps/user_status/lib/Db/UserStatus.php
index 8907c4a2c1b..b2da4a9e07a 100644
--- a/apps/user_status/lib/Db/UserStatus.php
+++ b/apps/user_status/lib/Db/UserStatus.php
@@ -3,29 +3,13 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Db;
use OCP\AppFramework\Db\Entity;
+use OCP\DB\Types;
/**
* Class UserStatus
@@ -42,16 +26,18 @@ use OCP\AppFramework\Db\Entity;
* @method void setStatusTimestamp(int $statusTimestamp)
* @method bool getIsUserDefined()
* @method void setIsUserDefined(bool $isUserDefined)
- * @method string getMessageId()
+ * @method string|null getMessageId()
* @method void setMessageId(string|null $messageId)
- * @method string getCustomIcon()
+ * @method string|null getCustomIcon()
* @method void setCustomIcon(string|null $customIcon)
- * @method string getCustomMessage()
+ * @method string|null getCustomMessage()
* @method void setCustomMessage(string|null $customMessage)
- * @method int getClearAt()
+ * @method int|null getClearAt()
* @method void setClearAt(int|null $clearAt)
- * @method setIsBackup(bool $true): void
+ * @method setIsBackup(bool $isBackup): void
* @method getIsBackup(): bool
+ * @method int getStatusMessageTimestamp()
+ * @method void setStatusMessageTimestamp(int $statusTimestamp)
*/
class UserStatus extends Entity {
@@ -82,15 +68,19 @@ class UserStatus extends Entity {
/** @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('statusTimestamp', Types::INTEGER);
$this->addType('isUserDefined', 'boolean');
$this->addType('messageId', 'string');
$this->addType('customIcon', 'string');
$this->addType('customMessage', 'string');
- $this->addType('clearAt', 'int');
+ $this->addType('clearAt', Types::INTEGER);
$this->addType('isBackup', 'boolean');
+ $this->addType('statusMessageTimestamp', Types::INTEGER);
}
}
diff --git a/apps/user_status/lib/Db/UserStatusMapper.php b/apps/user_status/lib/Db/UserStatusMapper.php
index f67cfcd472d..15982d44fd8 100644
--- a/apps/user_status/lib/Db/UserStatusMapper.php
+++ b/apps/user_status/lib/Db/UserStatusMapper.php
@@ -3,29 +3,13 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Db;
+use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@@ -75,11 +59,17 @@ class UserStatusMapper extends QBMapper {
$qb
->select('*')
->from($this->tableName)
- ->orderBy('status_timestamp', 'DESC')
- ->where($qb->expr()->notIn('status', $qb->createNamedParameter([IUserStatus::ONLINE, IUserStatus::AWAY, IUserStatus::OFFLINE], IQueryBuilder::PARAM_STR_ARRAY)))
- ->orWhere($qb->expr()->isNotNull('message_id'))
- ->orWhere($qb->expr()->isNotNull('custom_icon'))
- ->orWhere($qb->expr()->isNotNull('custom_message'));
+ ->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);
@@ -94,9 +84,9 @@ class UserStatusMapper extends QBMapper {
/**
* @param string $userId
* @return UserStatus
- * @throws \OCP\AppFramework\Db\DoesNotExistException
+ * @throws DoesNotExistException
*/
- public function findByUserId(string $userId, bool $isBackup = false):UserStatus {
+ public function findByUserId(string $userId, bool $isBackup = false): UserStatus {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
@@ -137,7 +127,7 @@ class UserStatusMapper extends QBMapper {
$qb->expr()->eq('status', $qb->createNamedParameter(IUserStatus::ONLINE))
));
- $qb->execute();
+ $qb->executeStatement();
}
/**
@@ -145,17 +135,13 @@ class UserStatusMapper extends QBMapper {
*
* @param int $timestamp
*/
- public function clearMessagesOlderThan(int $timestamp): void {
+ public function clearOlderThanClearAt(int $timestamp): void {
$qb = $this->db->getQueryBuilder();
- $qb->update($this->tableName)
- ->set('message_id', $qb->createNamedParameter(null))
- ->set('custom_icon', $qb->createNamedParameter(null))
- ->set('custom_message', $qb->createNamedParameter(null))
- ->set('clear_at', $qb->createNamedParameter(null))
+ $qb->delete($this->tableName)
->where($qb->expr()->isNotNull('clear_at'))
->andWhere($qb->expr()->lte('clear_at', $qb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT)));
- $qb->execute();
+ $qb->executeStatement();
}
@@ -164,15 +150,13 @@ class UserStatusMapper extends QBMapper {
*
* @param string $userId
* @param string $messageId
- * @param string $status
* @return bool True if an entry was deleted
*/
- public function deleteCurrentStatusToRestoreBackup(string $userId, string $messageId, string $status): bool {
+ 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('status', $qb->createNamedParameter($status)))
->andWhere($qb->expr()->eq('is_backup', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)));
return $qb->executeStatement() > 0;
}
diff --git a/apps/user_status/lib/Exception/InvalidClearAtException.php b/apps/user_status/lib/Exception/InvalidClearAtException.php
index a0ca08ade76..a3bd4dfa0d0 100644
--- a/apps/user_status/lib/Exception/InvalidClearAtException.php
+++ b/apps/user_status/lib/Exception/InvalidClearAtException.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Exception;
diff --git a/apps/user_status/lib/Exception/InvalidMessageIdException.php b/apps/user_status/lib/Exception/InvalidMessageIdException.php
index dbf8d83d970..1feb36a916a 100644
--- a/apps/user_status/lib/Exception/InvalidMessageIdException.php
+++ b/apps/user_status/lib/Exception/InvalidMessageIdException.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Exception;
diff --git a/apps/user_status/lib/Exception/InvalidStatusIconException.php b/apps/user_status/lib/Exception/InvalidStatusIconException.php
index 7a7c7947d5b..80dff2a7666 100644
--- a/apps/user_status/lib/Exception/InvalidStatusIconException.php
+++ b/apps/user_status/lib/Exception/InvalidStatusIconException.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Exception;
diff --git a/apps/user_status/lib/Exception/InvalidStatusTypeException.php b/apps/user_status/lib/Exception/InvalidStatusTypeException.php
index 12115054b2c..a09284be40e 100644
--- a/apps/user_status/lib/Exception/InvalidStatusTypeException.php
+++ b/apps/user_status/lib/Exception/InvalidStatusTypeException.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Exception;
diff --git a/apps/user_status/lib/Exception/StatusMessageTooLongException.php b/apps/user_status/lib/Exception/StatusMessageTooLongException.php
index c52f6079874..03d578abf46 100644
--- a/apps/user_status/lib/Exception/StatusMessageTooLongException.php
+++ b/apps/user_status/lib/Exception/StatusMessageTooLongException.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Exception;
diff --git a/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php b/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php
index daf7265d988..ab3a1e62beb 100644
--- a/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php
+++ b/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php
@@ -3,26 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Listener;
@@ -36,21 +18,14 @@ use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IInitialStateService;
use OCP\IUserSession;
+use OCP\Util;
+/** @template-implements IEventListener<BeforeTemplateRenderedEvent> */
class BeforeTemplateRenderedListener implements IEventListener {
/** @var ProfileManager */
private $profileManager;
- /** @var IUserSession */
- private $userSession;
-
- /** @var IInitialStateService */
- private $initialState;
-
- /** @var JSDataService */
- private $jsDataService;
-
/**
* BeforeTemplateRenderedListener constructor.
*
@@ -61,14 +36,11 @@ class BeforeTemplateRenderedListener implements IEventListener {
*/
public function __construct(
ProfileManager $profileManager,
- IUserSession $userSession,
- IInitialStateService $initialState,
- JSDataService $jsDataService
+ private IUserSession $userSession,
+ private IInitialStateService $initialState,
+ private JSDataService $jsDataService,
) {
$this->profileManager = $profileManager;
- $this->userSession = $userSession;
- $this->initialState = $initialState;
- $this->jsDataService = $jsDataService;
}
/**
@@ -97,7 +69,7 @@ class BeforeTemplateRenderedListener implements IEventListener {
return ['profileEnabled' => $this->profileManager->isProfileEnabled($user)];
});
- \OCP\Util::addScript('user_status', 'menu');
- \OCP\Util::addStyle('user_status', 'user-status-menu');
+ Util::addScript('user_status', 'menu');
+ Util::addStyle('user_status', 'user-status-menu');
}
}
diff --git a/apps/user_status/lib/Listener/OutOfOfficeStatusListener.php b/apps/user_status/lib/Listener/OutOfOfficeStatusListener.php
new file mode 100644
index 00000000000..6337d637896
--- /dev/null
+++ b/apps/user_status/lib/Listener/OutOfOfficeStatusListener.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+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\OutOfOfficeEndedEvent;
+use OCP\User\Events\OutOfOfficeScheduledEvent;
+use OCP\User\Events\OutOfOfficeStartedEvent;
+use OCP\UserStatus\IManager;
+use OCP\UserStatus\IUserStatus;
+
+/**
+ * Class UserDeletedListener
+ *
+ * @template-implements IEventListener<OutOfOfficeScheduledEvent|OutOfOfficeChangedEvent|OutOfOfficeClearedEvent|OutOfOfficeStartedEvent|OutOfOfficeEndedEvent>
+ *
+ */
+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
+ || $event instanceof OutOfOfficeStartedEvent
+ || $event instanceof OutOfOfficeEndedEvent
+ ) {
+ // 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()]);
+ }
+ }
+}
diff --git a/apps/user_status/lib/Listener/UserDeletedListener.php b/apps/user_status/lib/Listener/UserDeletedListener.php
index 0cacc89971a..bf021635156 100644
--- a/apps/user_status/lib/Listener/UserDeletedListener.php
+++ b/apps/user_status/lib/Listener/UserDeletedListener.php
@@ -3,50 +3,32 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Listener;
use OCA\UserStatus\Service\StatusService;
-use OCP\EventDispatcher\IEventListener;
use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\UserDeletedEvent;
/**
* Class UserDeletedListener
*
* @package OCA\UserStatus\Listener
+ * @template-implements IEventListener<UserDeletedEvent>
*/
class UserDeletedListener implements IEventListener {
- /** @var StatusService */
- private $service;
-
/**
* UserDeletedListener constructor.
*
* @param StatusService $service
*/
- public function __construct(StatusService $service) {
- $this->service = $service;
+ public function __construct(
+ private StatusService $service,
+ ) {
}
diff --git a/apps/user_status/lib/Listener/UserLiveStatusListener.php b/apps/user_status/lib/Listener/UserLiveStatusListener.php
index c015e684142..2db999d3712 100644
--- a/apps/user_status/lib/Listener/UserLiveStatusListener.php
+++ b/apps/user_status/lib/Listener/UserLiveStatusListener.php
@@ -3,52 +3,39 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Listener;
-use OCA\UserStatus\Db\UserStatus;
+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\EventDispatcher\IEventListener;
+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
+ * @template-implements IEventListener<UserLiveStatusEvent>
*/
class UserLiveStatusListener implements IEventListener {
- private UserStatusMapper $mapper;
- private ITimeFactory $timeFactory;
-
- public function __construct(UserStatusMapper $mapper,
- ITimeFactory $timeFactory) {
- $this->mapper = $mapper;
- $this->timeFactory = $timeFactory;
+ public function __construct(
+ private UserStatusMapper $mapper,
+ private StatusService $statusService,
+ private ITimeFactory $timeFactory,
+ private CalendarStatusService $calendarStatusService,
+ private LoggerInterface $logger,
+ ) {
}
/**
@@ -62,7 +49,8 @@ class UserLiveStatusListener implements IEventListener {
$user = $event->getUser();
try {
- $userStatus = $this->mapper->findByUserId($user->getUID());
+ $this->calendarStatusService->processCalendarStatus($user->getUID());
+ $userStatus = $this->statusService->findByUserId($user->getUID());
} catch (DoesNotExistException $ex) {
$userStatus = new UserStatus();
$userStatus->setUserId($user->getUID());
@@ -71,10 +59,16 @@ class UserLiveStatusListener implements IEventListener {
$userStatus->setIsUserDefined(false);
}
- // If the status is user-defined and one of the persistent statuses, we
+ // 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)) {
+ 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;
}
@@ -98,7 +92,19 @@ class UserLiveStatusListener implements IEventListener {
$userStatus->setIsUserDefined(false);
if ($userStatus->getId() === null) {
- $this->mapper->insert($userStatus);
+ 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);
}
diff --git a/apps/user_status/lib/Migration/Version0001Date20200602134824.php b/apps/user_status/lib/Migration/Version0001Date20200602134824.php
index 64490cc4207..678c2ec245a 100644
--- a/apps/user_status/lib/Migration/Version0001Date20200602134824.php
+++ b/apps/user_status/lib/Migration/Version0001Date20200602134824.php
@@ -3,32 +3,13 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Migration;
-use OCP\DB\Types;
use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
diff --git a/apps/user_status/lib/Migration/Version0002Date20200902144824.php b/apps/user_status/lib/Migration/Version0002Date20200902144824.php
index 0c222eff8cd..199d2a4cc6b 100644
--- a/apps/user_status/lib/Migration/Version0002Date20200902144824.php
+++ b/apps/user_status/lib/Migration/Version0002Date20200902144824.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Migration;
diff --git a/apps/user_status/lib/Migration/Version1000Date20201111130204.php b/apps/user_status/lib/Migration/Version1000Date20201111130204.php
index 8b20ed8306f..b0789684da0 100644
--- a/apps/user_status/lib/Migration/Version1000Date20201111130204.php
+++ b/apps/user_status/lib/Migration/Version1000Date20201111130204.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Migration;
diff --git a/apps/user_status/lib/Migration/Version1003Date20210809144824.php b/apps/user_status/lib/Migration/Version1003Date20210809144824.php
new file mode 100644
index 00000000000..7c6cf76adbe
--- /dev/null
+++ b/apps/user_status/lib/Migration/Version1003Date20210809144824.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+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;
+ }
+}
diff --git a/apps/user_status/lib/Migration/Version1008Date20230921144701.php b/apps/user_status/lib/Migration/Version1008Date20230921144701.php
new file mode 100644
index 00000000000..30ebbf37b0e
--- /dev/null
+++ b/apps/user_status/lib/Migration/Version1008Date20230921144701.php
@@ -0,0 +1,54 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\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();
+ }
+}
diff --git a/apps/user_status/lib/Migration/Version2301Date20210809144824.php b/apps/user_status/lib/Migration/Version2301Date20210809144824.php
deleted file mode 100644
index 947378484c6..00000000000
--- a/apps/user_status/lib/Migration/Version2301Date20210809144824.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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 Version2301Date20210809144824 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');
-
- $statusTable->addColumn('is_backup', Types::BOOLEAN, [
- 'notnull' => false,
- 'default' => false,
- ]);
-
- return $schema;
- }
-}
diff --git a/apps/user_status/lib/ResponseDefinitions.php b/apps/user_status/lib/ResponseDefinitions.php
new file mode 100644
index 00000000000..82f606dd301
--- /dev/null
+++ b/apps/user_status/lib/ResponseDefinitions.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+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,
+ * }
+ *
+ * @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 {
+}
diff --git a/apps/user_status/lib/Service/EmojiService.php b/apps/user_status/lib/Service/EmojiService.php
deleted file mode 100644
index 0f197933872..00000000000
--- a/apps/user_status/lib/Service/EmojiService.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2020, Georg Ehrke
- *
- * @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\Service;
-
-use OCP\IDBConnection;
-
-/**
- * Class EmojiService
- *
- * @package OCA\UserStatus\Service
- */
-class EmojiService {
-
- /** @var IDBConnection */
- private $db;
-
- /**
- * EmojiService constructor.
- *
- * @param IDBConnection $db
- */
- public function __construct(IDBConnection $db) {
- $this->db = $db;
- }
-
- /**
- * @return bool
- */
- public function doesPlatformSupportEmoji(): bool {
- return $this->db->supports4ByteText() &&
- \class_exists(\IntlBreakIterator::class);
- }
-
- /**
- * @param string $emoji
- * @return bool
- */
- public function isValidEmoji(string $emoji): bool {
- $intlBreakIterator = \IntlBreakIterator::createCharacterInstance();
- $intlBreakIterator->setText($emoji);
-
- $characterCount = 0;
- while ($intlBreakIterator->next() !== \IntlBreakIterator::DONE) {
- $characterCount++;
- }
-
- if ($characterCount !== 1) {
- return false;
- }
-
- $codePointIterator = \IntlBreakIterator::createCodePointInstance();
- $codePointIterator->setText($emoji);
-
- foreach ($codePointIterator->getPartsIterator() as $codePoint) {
- $codePointType = \IntlChar::charType($codePoint);
-
- // If the current code-point is an emoji or a modifier (like a skin-tone)
- // just continue and check the next character
- if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL ||
- $codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER ||
- $codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL ||
- $codePointType === \IntlChar::CHAR_CATEGORY_GENERAL_OTHER_TYPES) {
- continue;
- }
-
- // If it's neither a modifier nor an emoji, we only allow
- // a zero-width-joiner or a variation selector 16
- $codePointValue = \IntlChar::ord($codePoint);
- if ($codePointValue === 8205 || $codePointValue === 65039) {
- continue;
- }
-
- return false;
- }
-
- return true;
- }
-}
diff --git a/apps/user_status/lib/Service/JSDataService.php b/apps/user_status/lib/Service/JSDataService.php
index c08643ec64f..a777e97fe57 100644
--- a/apps/user_status/lib/Service/JSDataService.php
+++ b/apps/user_status/lib/Service/JSDataService.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Service;
@@ -31,22 +14,16 @@ 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 __construct(
+ private IUserSession $userSession,
+ private StatusService $statusService,
+ ) {
}
public function jsonSerialize(): array {
diff --git a/apps/user_status/lib/Service/PredefinedStatusService.php b/apps/user_status/lib/Service/PredefinedStatusService.php
index 354e0f16b32..599d5b8b52f 100644
--- a/apps/user_status/lib/Service/PredefinedStatusService.php
+++ b/apps/user_status/lib/Service/PredefinedStatusService.php
@@ -3,29 +3,13 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Service;
use OCP\IL10N;
+use OCP\UserStatus\IUserStatus;
/**
* Class DefaultStatusService
@@ -36,23 +20,26 @@ use OCP\IL10N;
* @package OCA\UserStatus\Service
*/
class PredefinedStatusService {
+ private const BE_RIGHT_BACK = 'be-right-back';
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';
-
- /** @var IL10N */
- private $l10n;
+ public const OUT_OF_OFFICE = 'out-of-office';
/**
* DefaultStatusService constructor.
*
* @param IL10N $l10n
*/
- public function __construct(IL10N $l10n) {
- $this->l10n = $l10n;
+ public function __construct(
+ private IL10N $l10n,
+ ) {
}
/**
@@ -79,6 +66,15 @@ class PredefinedStatusService {
],
],
[
+ 'id' => self::BE_RIGHT_BACK,
+ 'icon' => '⏳',
+ 'message' => $this->getTranslatedStatusForId(self::BE_RIGHT_BACK),
+ 'clearAt' => [
+ 'type' => 'period',
+ 'time' => 900,
+ ],
+ ],
+ [
'id' => self::REMOTE_WORK,
'icon' => '🏡',
'message' => $this->getTranslatedStatusForId(self::REMOTE_WORK),
@@ -109,6 +105,13 @@ class PredefinedStatusService {
'clearAt' => null,
'visible' => false,
],
+ [
+ 'id' => self::OUT_OF_OFFICE,
+ 'icon' => '🛑',
+ 'message' => $this->getTranslatedStatusForId(self::OUT_OF_OFFICE),
+ 'clearAt' => null,
+ 'visible' => false,
+ ],
];
}
@@ -144,9 +147,15 @@ class PredefinedStatusService {
case self::VACATIONING:
return '🌴';
+ case self::OUT_OF_OFFICE:
+ return '🛑';
+
case self::REMOTE_WORK:
return '🏡';
+ case self::BE_RIGHT_BACK:
+ return '⏳';
+
case self::CALL:
return '💬';
@@ -174,12 +183,18 @@ class PredefinedStatusService {
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');
+ case self::BE_RIGHT_BACK:
+ return $this->l10n->t('Be right back');
+
default:
return null;
}
@@ -195,8 +210,14 @@ class PredefinedStatusService {
self::COMMUTING,
self::SICK_LEAVE,
self::VACATIONING,
+ self::OUT_OF_OFFICE,
+ self::BE_RIGHT_BACK,
self::REMOTE_WORK,
- self::CALL,
+ IUserStatus::MESSAGE_CALL,
+ IUserStatus::MESSAGE_AVAILABILITY,
+ IUserStatus::MESSAGE_VACATION,
+ IUserStatus::MESSAGE_CALENDAR_BUSY,
+ IUserStatus::MESSAGE_CALENDAR_BUSY_TENTATIVE,
], true);
}
}
diff --git a/apps/user_status/lib/Service/StatusService.php b/apps/user_status/lib/Service/StatusService.php
index 5dd70e4ea5e..188eb26d1d7 100644
--- a/apps/user_status/lib/Service/StatusService.php
+++ b/apps/user_status/lib/Service/StatusService.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Georg Ehrke
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\UserStatus\Service;
@@ -37,8 +19,11 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\IConfig;
-use OCP\IUser;
+use OCP\IEmojiHelper;
+use OCP\IUserManager;
use OCP\UserStatus\IUserStatus;
+use Psr\Log\LoggerInterface;
+use function in_array;
/**
* Class StatusService
@@ -46,27 +31,9 @@ use OCP\UserStatus\IUserStatus;
* @package OCA\UserStatus\Service
*/
class StatusService {
-
- /** @var UserStatusMapper */
- private $mapper;
-
- /** @var ITimeFactory */
- private $timeFactory;
-
- /** @var PredefinedStatusService */
- private $predefinedStatusService;
-
- /** @var EmojiService */
- private $emojiService;
-
- /** @var bool */
- private $shareeEnumeration;
-
- /** @var bool */
- private $shareeEnumerationInGroupOnly;
-
- /** @var bool */
- private $shareeEnumerationPhone;
+ private bool $shareeEnumeration;
+ private bool $shareeEnumerationInGroupOnly;
+ private bool $shareeEnumerationPhone;
/**
* List of priorities ordered by their priority
@@ -75,6 +42,7 @@ class StatusService {
IUserStatus::ONLINE,
IUserStatus::AWAY,
IUserStatus::DND,
+ IUserStatus::BUSY,
IUserStatus::INVISIBLE,
IUserStatus::OFFLINE,
];
@@ -85,6 +53,7 @@ class StatusService {
*/
public const PERSISTENT_STATUSES = [
IUserStatus::AWAY,
+ IUserStatus::BUSY,
IUserStatus::DND,
IUserStatus::INVISIBLE,
];
@@ -95,27 +64,18 @@ class StatusService {
/** @var int */
public const MAXIMUM_MESSAGE_LENGTH = 80;
- /**
- * StatusService constructor.
- *
- * @param UserStatusMapper $mapper
- * @param ITimeFactory $timeFactory
- * @param PredefinedStatusService $defaultStatusService
- * @param EmojiService $emojiService
- * @param IConfig $config
- */
- public function __construct(UserStatusMapper $mapper,
- ITimeFactory $timeFactory,
- PredefinedStatusService $defaultStatusService,
- EmojiService $emojiService,
- IConfig $config) {
- $this->mapper = $mapper;
- $this->timeFactory = $timeFactory;
- $this->predefinedStatusService = $defaultStatusService;
- $this->emojiService = $emojiService;
- $this->shareeEnumeration = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
- $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
- $this->shareeEnumerationPhone = $this->shareeEnumeration && $config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
+ public function __construct(
+ private UserStatusMapper $mapper,
+ private ITimeFactory $timeFactory,
+ private PredefinedStatusService $predefinedStatusService,
+ private IEmojiHelper $emojiHelper,
+ private IConfig $config,
+ private IUserManager $userManager,
+ private LoggerInterface $logger,
+ ) {
+ $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';
}
/**
@@ -159,7 +119,7 @@ class StatusService {
* @return UserStatus
* @throws DoesNotExistException
*/
- public function findByUserId(string $userId):UserStatus {
+ public function findByUserId(string $userId): UserStatus {
return $this->processStatus($this->mapper->findByUserId($userId));
}
@@ -182,9 +142,9 @@ class StatusService {
* @throws InvalidStatusTypeException
*/
public function setStatus(string $userId,
- string $status,
- ?int $statusTimestamp,
- bool $isUserDefined): UserStatus {
+ string $status,
+ ?int $statusTimestamp,
+ bool $isUserDefined): UserStatus {
try {
$userStatus = $this->mapper->findByUserId($userId);
} catch (DoesNotExistException $ex) {
@@ -193,9 +153,10 @@ class StatusService {
}
// Check if status-type is valid
- if (!\in_array($status, self::PRIORITY_ORDERED_STATUSES, true)) {
+ 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();
}
@@ -206,7 +167,7 @@ class StatusService {
$userStatus->setIsBackup(false);
if ($userStatus->getId() === null) {
- return $this->mapper->insert($userStatus);
+ return $this->insertWithoutThrowingUniqueConstrain($userStatus);
}
return $this->mapper->update($userStatus);
@@ -221,8 +182,8 @@ class StatusService {
* @throws InvalidClearAtException
*/
public function setPredefinedMessage(string $userId,
- string $messageId,
- ?int $clearAt): UserStatus {
+ string $messageId,
+ ?int $clearAt): UserStatus {
try {
$userStatus = $this->mapper->findByUserId($userId);
} catch (DoesNotExistException $ex) {
@@ -247,9 +208,10 @@ class StatusService {
$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->insertWithoutThrowingUniqueConstrain($userStatus);
}
return $this->mapper->update($userStatus);
@@ -260,15 +222,17 @@ class StatusService {
* @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): void {
+ 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)) {
+ if (!in_array($status, self::PRIORITY_ORDERED_STATUSES, true)) {
throw new InvalidStatusTypeException('Status-type "' . $status . '" is not supported');
}
@@ -276,21 +240,58 @@ class StatusService {
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);
+ }
+
+ $updateStatus = false;
+ if ($messageId === IUserStatus::MESSAGE_OUT_OF_OFFICE) {
+ // OUT_OF_OFFICE trumps AVAILABILITY, CALL and CALENDAR status
+ $updateStatus = $userStatus->getMessageId() === IUserStatus::MESSAGE_AVAILABILITY || $userStatus->getMessageId() === IUserStatus::MESSAGE_CALL || $userStatus->getMessageId() === IUserStatus::MESSAGE_CALENDAR_BUSY;
+ } elseif ($messageId === IUserStatus::MESSAGE_AVAILABILITY) {
+ // AVAILABILITY trumps CALL and CALENDAR status
+ $updateStatus = $userStatus->getMessageId() === IUserStatus::MESSAGE_CALL || $userStatus->getMessageId() === IUserStatus::MESSAGE_CALENDAR_BUSY;
+ } elseif ($messageId === IUserStatus::MESSAGE_CALL) {
+ // CALL trumps CALENDAR status
+ $updateStatus = $userStatus->getMessageId() === IUserStatus::MESSAGE_CALENDAR_BUSY;
+ }
+
+ if ($messageId === IUserStatus::MESSAGE_OUT_OF_OFFICE || $messageId === IUserStatus::MESSAGE_AVAILABILITY || $messageId === IUserStatus::MESSAGE_CALL || $messageId === IUserStatus::MESSAGE_CALENDAR_BUSY) {
+ if ($updateStatus) {
+ $this->logger->debug('User ' . $userId . ' is currently NOT available, overwriting status [status: ' . $userStatus->getStatus() . ', messageId: ' . json_encode($userStatus->getMessageId()) . ']', ['app' => 'dav']);
+ } else {
+ $this->logger->debug('User ' . $userId . ' is currently NOT available, but we are NOT overwriting status [status: ' . $userStatus->getStatus() . ', messageId: ' . json_encode($userStatus->getMessageId()) . ']', ['app' => 'dav']);
+ }
+ }
+
+ // There should be a backup already or none is needed. So we take a shortcut.
+ if ($updateStatus) {
+ $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; // Already a status set automatically => abort.
+ return null; // Already a status set automatically => abort.
}
// If we just created the backup
+ // we need to create a new status to insert
+ // Unfortunately there's no way to unset the DB ID on an Entity
$userStatus = new UserStatus();
$userStatus->setUserId($userId);
- } else {
- try {
- $userStatus = $this->mapper->findByUserId($userId);
- } catch (DoesNotExistException $ex) {
- $userStatus = new UserStatus();
- $userStatus->setUserId($userId);
- }
}
$userStatus->setStatus($status);
@@ -299,20 +300,26 @@ class StatusService {
$userStatus->setIsBackup(false);
$userStatus->setMessageId($messageId);
$userStatus->setCustomIcon(null);
- $userStatus->setCustomMessage(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) {
- $this->mapper->update($userStatus);
- return;
+ return $this->mapper->update($userStatus);
}
- $this->mapper->insert($userStatus);
+ return $this->insertWithoutThrowingUniqueConstrain($userStatus);
}
/**
* @param string $userId
* @param string|null $statusIcon
- * @param string $message
+ * @param string|null $message
* @param int|null $clearAt
* @return UserStatus
* @throws InvalidClearAtException
@@ -320,9 +327,9 @@ class StatusService {
* @throws StatusMessageTooLongException
*/
public function setCustomMessage(string $userId,
- ?string $statusIcon,
- string $message,
- ?int $clearAt): UserStatus {
+ ?string $statusIcon,
+ ?string $message,
+ ?int $clearAt): UserStatus {
try {
$userStatus = $this->mapper->findByUserId($userId);
} catch (DoesNotExistException $ex) {
@@ -334,11 +341,11 @@ class StatusService {
}
// Check if statusIcon contains only one character
- if ($statusIcon !== null && !$this->emojiService->isValidEmoji($statusIcon)) {
+ 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 (\mb_strlen($message) > self::MAXIMUM_MESSAGE_LENGTH) {
+ 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
@@ -350,9 +357,10 @@ class StatusService {
$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->insertWithoutThrowingUniqueConstrain($userStatus);
}
return $this->mapper->update($userStatus);
@@ -394,6 +402,7 @@ class StatusService {
$userStatus->setCustomMessage(null);
$userStatus->setCustomIcon(null);
$userStatus->setClearAt(null);
+ $userStatus->setStatusMessageTimestamp(0);
$this->mapper->update($userStatus);
return true;
@@ -442,6 +451,7 @@ class StatusService {
$this->cleanStatus($status);
}
if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
+ $this->cleanStatus($status);
$this->cleanStatusMessage($status);
}
if ($status->getMessageId() !== null) {
@@ -474,6 +484,7 @@ class StatusService {
$status->setCustomIcon(null);
$status->setCustomMessage(null);
$status->setClearAt(null);
+ $status->setStatusMessageTimestamp(0);
$this->mapper->update($status);
}
@@ -484,8 +495,14 @@ class StatusService {
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) {
+ 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']);
}
}
@@ -505,28 +522,38 @@ class StatusService {
}
}
- public function revertUserStatus(string $userId, string $messageId, string $status): void {
+ 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;
+ return null;
}
- $deleted = $this->mapper->deleteCurrentStatusToRestoreBackup($userId, $messageId, $status);
+ $deleted = $this->mapper->deleteCurrentStatusToRestoreBackup($userId, $messageId);
if (!$deleted) {
// Another status is set automatically or no status, do nothing
- return;
+ return null;
+ }
+
+ if ($revertedManually) {
+ if ($backupUserStatus->getStatus() === IUserStatus::OFFLINE) {
+ // When the user reverts the status manually they are online
+ $backupUserStatus->setStatus(IUserStatus::ONLINE);
+ }
+ $backupUserStatus->setStatusTimestamp($this->timeFactory->getTime());
}
$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, string $status): void {
+ public function revertMultipleUserStatus(array $userIds, string $messageId): void {
// Get all user statuses and the backups
$findById = $userIds;
foreach ($userIds as $userId) {
@@ -537,10 +564,9 @@ class StatusService {
$backups = $restoreIds = $statuesToDelete = [];
foreach ($userStatuses as $userStatus) {
if (!$userStatus->getIsBackup()
- && $userStatus->getMessageId() === $messageId
- && $userStatus->getStatus() === $status) {
+ && $userStatus->getMessageId() === $messageId) {
$statuesToDelete[$userStatus->getUserId()] = $userStatus->getId();
- } else if ($userStatus->getIsBackup()) {
+ } elseif ($userStatus->getIsBackup()) {
$backups[$userStatus->getUserId()] = $userStatus->getId();
}
}
@@ -558,4 +584,16 @@ class StatusService {
// For users that matched restore the previous status
$this->mapper->restoreBackupStatuses($restoreIds);
}
+
+ protected function insertWithoutThrowingUniqueConstrain(UserStatus $userStatus): UserStatus {
+ try {
+ return $this->mapper->insert($userStatus);
+ } catch (Exception $e) {
+ // Ignore if a parallel request already set the status
+ if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
+ throw $e;
+ }
+ }
+ return $userStatus;
+ }
}