[skipci] Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at> Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>tags/v28.0.0beta2
@@ -35,5 +35,6 @@ return [ | |||
], | |||
'ocs' => [ | |||
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'], | |||
['name' => 'out_of_office#getCurrentOutOfOfficeData', 'url' => '/api/v1/outOfOffice/{userId}', 'verb' => 'GET'], | |||
], | |||
]; |
@@ -192,6 +192,7 @@ return array( | |||
'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php', | |||
'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php', | |||
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php', | |||
'OCA\\DAV\\Controller\\OutOfOfficeController' => $baseDir . '/../lib/Controller/OutOfOfficeController.php', | |||
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php', | |||
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php', | |||
'OCA\\DAV\\DAV\\PublicAuth' => $baseDir . '/../lib/DAV/PublicAuth.php', | |||
@@ -305,6 +306,7 @@ return array( | |||
'OCA\\DAV\\Profiler\\ProfilerPlugin' => $baseDir . '/../lib/Profiler/ProfilerPlugin.php', | |||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php', | |||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php', | |||
'OCA\\DAV\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php', | |||
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php', | |||
'OCA\\DAV\\Search\\ACalendarSearchProvider' => $baseDir . '/../lib/Search/ACalendarSearchProvider.php', | |||
'OCA\\DAV\\Search\\ContactsSearchProvider' => $baseDir . '/../lib/Search/ContactsSearchProvider.php', |
@@ -207,6 +207,7 @@ class ComposerStaticInitDAV | |||
'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php', | |||
'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php', | |||
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php', | |||
'OCA\\DAV\\Controller\\OutOfOfficeController' => __DIR__ . '/..' . '/../lib/Controller/OutOfOfficeController.php', | |||
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php', | |||
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php', | |||
'OCA\\DAV\\DAV\\PublicAuth' => __DIR__ . '/..' . '/../lib/DAV/PublicAuth.php', | |||
@@ -320,6 +321,7 @@ class ComposerStaticInitDAV | |||
'OCA\\DAV\\Profiler\\ProfilerPlugin' => __DIR__ . '/..' . '/../lib/Profiler/ProfilerPlugin.php', | |||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php', | |||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php', | |||
'OCA\\DAV\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php', | |||
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php', | |||
'OCA\\DAV\\Search\\ACalendarSearchProvider' => __DIR__ . '/..' . '/../lib/Search/ACalendarSearchProvider.php', | |||
'OCA\\DAV\\Search\\ContactsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/ContactsSearchProvider.php', |
@@ -0,0 +1,78 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud> | |||
* | |||
* @author Richard Steinmetz <richard@steinmetz.cloud> | |||
* | |||
* @license AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU 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 General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCA\DAV\Controller; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCA\DAV\ResponseDefinitions; | |||
use OCP\AppFramework\Db\DoesNotExistException; | |||
use OCP\AppFramework\Http; | |||
use OCP\AppFramework\Http\DataResponse; | |||
use OCP\AppFramework\OCSController; | |||
use OCP\IRequest; | |||
/** | |||
* @psalm-import-type DAVOutOfOfficeData from ResponseDefinitions | |||
*/ | |||
class OutOfOfficeController extends OCSController { | |||
public function __construct( | |||
string $appName, | |||
IRequest $request, | |||
private AbsenceMapper $absenceMapper, | |||
) { | |||
parent::__construct($appName, $request); | |||
} | |||
/** | |||
* Get the currently configured out-of-office data of a user. | |||
* | |||
* @NoAdminRequired | |||
* @NoCSRFRequired | |||
* | |||
* @param string $userId The user id to get out-of-office data for. | |||
* @return DataResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, ?DAVOutOfOfficeData, array{}> | |||
* | |||
* 200: Out-of-office data | |||
* 404: No out-of-office data was found | |||
*/ | |||
public function getCurrentOutOfOfficeData(string $userId): DataResponse { | |||
try { | |||
$data = $this->absenceMapper->findByUserId($userId); | |||
} catch (DoesNotExistException) { | |||
return new DataResponse(null, Http::STATUS_NOT_FOUND); | |||
} | |||
return new DataResponse([ | |||
'id' => $data->getId(), | |||
'userId' => $data->getUserId(), | |||
'firstDay' => $data->getFirstDay(), | |||
'lastDay' => $data->getLastDay(), | |||
'status' => $data->getStatus(), | |||
'message' => $data->getMessage(), | |||
]); | |||
} | |||
} |
@@ -26,8 +26,13 @@ declare(strict_types=1); | |||
namespace OCA\DAV\Db; | |||
use DateTimeImmutable; | |||
use InvalidArgumentException; | |||
use JsonSerializable; | |||
use OC\User\OutOfOfficeData; | |||
use OCP\AppFramework\Db\Entity; | |||
use OCP\IUser; | |||
use OCP\User\IOutOfOfficeData; | |||
/** | |||
* @method string getUserId() | |||
@@ -43,8 +48,13 @@ use OCP\AppFramework\Db\Entity; | |||
*/ | |||
class Absence extends Entity implements JsonSerializable { | |||
protected string $userId = ''; | |||
/** Inclusive, formatted as YYYY-MM-DD */ | |||
protected string $firstDay = ''; | |||
/** Inclusive, formatted as YYYY-MM-DD */ | |||
protected string $lastDay = ''; | |||
protected string $status = ''; | |||
protected string $message = ''; | |||
@@ -56,6 +66,24 @@ class Absence extends Entity implements JsonSerializable { | |||
$this->addType('message', 'string'); | |||
} | |||
public function toOutOufOfficeData(IUser $user): IOutOfOfficeData { | |||
if ($user->getUID() !== $this->getUserId()) { | |||
throw new InvalidArgumentException("The user doesn't match the user id of this absence! Expected " . $this->getUserId() . ", got " . $user->getUID()); | |||
} | |||
//$user = $userManager->get($this->getUserId()); | |||
$startDate = new DateTimeImmutable($this->getFirstDay()); | |||
$endDate = new DateTimeImmutable($this->getLastDay()); | |||
return new OutOfOfficeData( | |||
(string)$this->getId(), | |||
$user, | |||
$startDate->getTimestamp(), | |||
$endDate->getTimestamp(), | |||
$this->getStatus(), | |||
$this->getMessage(), | |||
); | |||
} | |||
public function jsonSerialize(): array { | |||
return [ | |||
'userId' => $this->userId, |
@@ -0,0 +1,40 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud> | |||
* | |||
* @author Richard Steinmetz <richard@steinmetz.cloud> | |||
* | |||
* @license AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU 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 General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCA\DAV; | |||
/** | |||
* @psalm-type DAVOutOfOfficeData = array{ | |||
* id: int, | |||
* userId: string, | |||
* firstDay: string, | |||
* lastDay: string, | |||
* status: string, | |||
* message: string, | |||
* } | |||
*/ | |||
class ResponseDefinitions { | |||
} |
@@ -26,18 +26,30 @@ declare(strict_types=1); | |||
namespace OCA\DAV\Service; | |||
use InvalidArgumentException; | |||
use OCA\DAV\Db\Absence; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\AppFramework\Db\DoesNotExistException; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\IUserManager; | |||
use OCP\User\Events\OutOfOfficeChangedEvent; | |||
use OCP\User\Events\OutOfOfficeClearedEvent; | |||
use OCP\User\Events\OutOfOfficeScheduledEvent; | |||
class AbsenceService { | |||
public function __construct( | |||
private AbsenceMapper $absenceMapper, | |||
private IEventDispatcher $eventDispatcher, | |||
private IUserManager $userManager, | |||
) { | |||
} | |||
/** | |||
* @param string $firstDay The first day (inclusive) of the absence formatted as YYYY-MM-DD. | |||
* @param string $lastDay The last day (inclusive) of the absence formatted as YYYY-MM-DD. | |||
* | |||
* @throws \OCP\DB\Exception | |||
* @throws InvalidArgumentException If no user with the given user id exists. | |||
*/ | |||
public function createOrUpdateAbsence( | |||
string $userId, | |||
@@ -58,9 +70,19 @@ class AbsenceService { | |||
$absence->setStatus($status); | |||
$absence->setMessage($message); | |||
// TODO: this method should probably just take a IUser instance | |||
$user = $this->userManager->get($userId); | |||
if ($user === null) { | |||
throw new InvalidArgumentException("User $userId does not exist"); | |||
} | |||
$eventData = $absence->toOutOufOfficeData($user); | |||
if ($absence->getId() === null) { | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeScheduledEvent($eventData)); | |||
return $this->absenceMapper->insert($absence); | |||
} | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeChangedEvent($eventData)); | |||
return $this->absenceMapper->update($absence); | |||
} | |||
@@ -68,7 +90,20 @@ class AbsenceService { | |||
* @throws \OCP\DB\Exception | |||
*/ | |||
public function clearAbsence(string $userId): void { | |||
$this->absenceMapper->deleteByUserId($userId); | |||
try { | |||
$absence = $this->absenceMapper->findByUserId($userId); | |||
} catch (DoesNotExistException $e) { | |||
// Nothing to clear | |||
return; | |||
} | |||
$this->absenceMapper->delete($absence); | |||
// TODO: this method should probably just take a IUser instance | |||
$user = $this->userManager->get($userId); | |||
if ($user === null) { | |||
throw new InvalidArgumentException("User $userId does not exist"); | |||
} | |||
$eventData = $absence->toOutOufOfficeData($user); | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeClearedEvent($eventData)); | |||
} | |||
} | |||
@@ -65,6 +65,38 @@ | |||
"type": "string" | |||
} | |||
} | |||
}, | |||
"OutOfOfficeData": { | |||
"type": "object", | |||
"required": [ | |||
"id", | |||
"userId", | |||
"firstDay", | |||
"lastDay", | |||
"status", | |||
"message" | |||
], | |||
"properties": { | |||
"id": { | |||
"type": "integer", | |||
"format": "int64" | |||
}, | |||
"userId": { | |||
"type": "string" | |||
}, | |||
"firstDay": { | |||
"type": "string" | |||
}, | |||
"lastDay": { | |||
"type": "string" | |||
}, | |||
"status": { | |||
"type": "string" | |||
}, | |||
"message": { | |||
"type": "string" | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
@@ -186,6 +218,108 @@ | |||
} | |||
} | |||
} | |||
}, | |||
"/ocs/v2.php/apps/dav/api/v1/outOfOffice/{userId}": { | |||
"get": { | |||
"operationId": "out_of_office-get-current-out-of-office-data", | |||
"summary": "Get the currently configured out-of-office data of a user.", | |||
"tags": [ | |||
"out_of_office" | |||
], | |||
"security": [ | |||
{ | |||
"bearer_auth": [] | |||
}, | |||
{ | |||
"basic_auth": [] | |||
} | |||
], | |||
"parameters": [ | |||
{ | |||
"name": "userId", | |||
"in": "path", | |||
"description": "The user id to get out-of-office data for.", | |||
"required": true, | |||
"schema": { | |||
"type": "string" | |||
} | |||
}, | |||
{ | |||
"name": "OCS-APIRequest", | |||
"in": "header", | |||
"description": "Required to be true for the API request to pass", | |||
"required": true, | |||
"schema": { | |||
"type": "boolean", | |||
"default": true | |||
} | |||
} | |||
], | |||
"responses": { | |||
"200": { | |||
"description": "Out-of-office data", | |||
"content": { | |||
"application/json": { | |||
"schema": { | |||
"type": "object", | |||
"required": [ | |||
"ocs" | |||
], | |||
"properties": { | |||
"ocs": { | |||
"type": "object", | |||
"required": [ | |||
"meta", | |||
"data" | |||
], | |||
"properties": { | |||
"meta": { | |||
"$ref": "#/components/schemas/OCSMeta" | |||
}, | |||
"data": { | |||
"$ref": "#/components/schemas/OutOfOfficeData", | |||
"nullable": true | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
"404": { | |||
"description": "No out-of-office data was found", | |||
"content": { | |||
"application/json": { | |||
"schema": { | |||
"type": "object", | |||
"required": [ | |||
"ocs" | |||
], | |||
"properties": { | |||
"ocs": { | |||
"type": "object", | |||
"required": [ | |||
"meta", | |||
"data" | |||
], | |||
"properties": { | |||
"meta": { | |||
"$ref": "#/components/schemas/OCSMeta" | |||
}, | |||
"data": { | |||
"$ref": "#/components/schemas/OutOfOfficeData", | |||
"nullable": true | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
"tags": [] |
@@ -731,6 +731,9 @@ return array( | |||
'OCP\\User\\Events\\BeforeUserLoggedInEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInEvent.php', | |||
'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php', | |||
'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeChangedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeChangedEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeClearedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeClearedEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php', | |||
'OCP\\User\\Events\\PasswordUpdatedEvent' => $baseDir . '/lib/public/User/Events/PasswordUpdatedEvent.php', | |||
'OCP\\User\\Events\\PostLoginEvent' => $baseDir . '/lib/public/User/Events/PostLoginEvent.php', | |||
'OCP\\User\\Events\\UserChangedEvent' => $baseDir . '/lib/public/User/Events/UserChangedEvent.php', | |||
@@ -742,6 +745,8 @@ return array( | |||
'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php', | |||
'OCP\\User\\Events\\UserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/UserLoggedOutEvent.php', | |||
'OCP\\User\\GetQuotaEvent' => $baseDir . '/lib/public/User/GetQuotaEvent.php', | |||
'OCP\\User\\IAvailabilityCoordinator' => $baseDir . '/lib/public/User/IAvailabilityCoordinator.php', | |||
'OCP\\User\\IOutOfOfficeData' => $baseDir . '/lib/public/User/IOutOfOfficeData.php', | |||
'OCP\\Util' => $baseDir . '/lib/public/Util.php', | |||
'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', | |||
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php', | |||
@@ -1762,6 +1767,7 @@ return array( | |||
'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php', | |||
'OC\\UserStatus\\ISettableProvider' => $baseDir . '/lib/private/UserStatus/ISettableProvider.php', | |||
'OC\\UserStatus\\Manager' => $baseDir . '/lib/private/UserStatus/Manager.php', | |||
'OC\\User\\AvailabilityCoordinator' => $baseDir . '/lib/private/User/AvailabilityCoordinator.php', | |||
'OC\\User\\Backend' => $baseDir . '/lib/private/User/Backend.php', | |||
'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php', | |||
'OC\\User\\DisplayNameCache' => $baseDir . '/lib/private/User/DisplayNameCache.php', | |||
@@ -1771,6 +1777,7 @@ return array( | |||
'OC\\User\\LoginException' => $baseDir . '/lib/private/User/LoginException.php', | |||
'OC\\User\\Manager' => $baseDir . '/lib/private/User/Manager.php', | |||
'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php', | |||
'OC\\User\\OutOfOfficeData' => $baseDir . '/lib/private/User/OutOfOfficeData.php', | |||
'OC\\User\\Session' => $baseDir . '/lib/private/User/Session.php', | |||
'OC\\User\\User' => $baseDir . '/lib/private/User/User.php', | |||
'OC_API' => $baseDir . '/lib/private/legacy/OC_API.php', |
@@ -764,6 +764,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
'OCP\\User\\Events\\BeforeUserLoggedInEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInEvent.php', | |||
'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php', | |||
'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeChangedEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeClearedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeClearedEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php', | |||
'OCP\\User\\Events\\PasswordUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PasswordUpdatedEvent.php', | |||
'OCP\\User\\Events\\PostLoginEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PostLoginEvent.php', | |||
'OCP\\User\\Events\\UserChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserChangedEvent.php', | |||
@@ -775,6 +778,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php', | |||
'OCP\\User\\Events\\UserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedOutEvent.php', | |||
'OCP\\User\\GetQuotaEvent' => __DIR__ . '/../../..' . '/lib/public/User/GetQuotaEvent.php', | |||
'OCP\\User\\IAvailabilityCoordinator' => __DIR__ . '/../../..' . '/lib/public/User/IAvailabilityCoordinator.php', | |||
'OCP\\User\\IOutOfOfficeData' => __DIR__ . '/../../..' . '/lib/public/User/IOutOfOfficeData.php', | |||
'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', | |||
'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', | |||
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php', | |||
@@ -1795,6 +1800,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php', | |||
'OC\\UserStatus\\ISettableProvider' => __DIR__ . '/../../..' . '/lib/private/UserStatus/ISettableProvider.php', | |||
'OC\\UserStatus\\Manager' => __DIR__ . '/../../..' . '/lib/private/UserStatus/Manager.php', | |||
'OC\\User\\AvailabilityCoordinator' => __DIR__ . '/../../..' . '/lib/private/User/AvailabilityCoordinator.php', | |||
'OC\\User\\Backend' => __DIR__ . '/../../..' . '/lib/private/User/Backend.php', | |||
'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php', | |||
'OC\\User\\DisplayNameCache' => __DIR__ . '/../../..' . '/lib/private/User/DisplayNameCache.php', | |||
@@ -1804,6 +1810,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
'OC\\User\\LoginException' => __DIR__ . '/../../..' . '/lib/private/User/LoginException.php', | |||
'OC\\User\\Manager' => __DIR__ . '/../../..' . '/lib/private/User/Manager.php', | |||
'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php', | |||
'OC\\User\\OutOfOfficeData' => __DIR__ . '/../../..' . '/lib/private/User/OutOfOfficeData.php', | |||
'OC\\User\\Session' => __DIR__ . '/../../..' . '/lib/private/User/Session.php', | |||
'OC\\User\\User' => __DIR__ . '/../../..' . '/lib/private/User/User.php', | |||
'OC_API' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_API.php', |
@@ -0,0 +1,111 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* @author Richard Steinmetz <richard@steinmetz.cloud> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OC\User; | |||
use JsonException; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\AppFramework\Db\DoesNotExistException; | |||
use OCP\ICache; | |||
use OCP\ICacheFactory; | |||
use OCP\IUser; | |||
use OCP\User\IAvailabilityCoordinator; | |||
use OCP\User\IOutOfOfficeData; | |||
use Psr\Log\LoggerInterface; | |||
class AvailabilityCoordinator implements IAvailabilityCoordinator { | |||
private ICache $cache; | |||
public function __construct( | |||
ICacheFactory $cacheFactory, | |||
private AbsenceMapper $absenceMapper, | |||
private LoggerInterface $logger, | |||
) { | |||
$this->cache = $cacheFactory->createLocal('OutOfOfficeData'); | |||
} | |||
private function getCachedOutOfOfficeData(IUser $user): ?OutOfOfficeData { | |||
$cachedString = $this->cache->get($user->getUID()); | |||
if ($cachedString === null) { | |||
return null; | |||
} | |||
try { | |||
$cachedData = json_decode($cachedString, true, 10, JSON_THROW_ON_ERROR); | |||
} catch (JsonException $e) { | |||
$this->logger->error('Failed to deserialize cached out-of-office data: ' . $e->getMessage(), [ | |||
'exception' => $e, | |||
'json' => $cachedString, | |||
]); | |||
return null; | |||
} | |||
return new OutOfOfficeData( | |||
$cachedData['id'], | |||
$user, | |||
$cachedData['startDate'], | |||
$cachedData['endDate'], | |||
$cachedData['shortMessage'], | |||
$cachedData['message'], | |||
); | |||
} | |||
private function setCachedOutOfOfficeData(IOutOfOfficeData $data): void { | |||
try { | |||
$cachedString = json_encode([ | |||
'id' => $data->getId(), | |||
'startDate' => $data->getStartDate(), | |||
'endDate' => $data->getEndDate(), | |||
'shortMessage' => $data->getShortMessage(), | |||
'message' => $data->getMessage(), | |||
], JSON_THROW_ON_ERROR); | |||
} catch (JsonException $e) { | |||
$this->logger->error('Failed to serialize out-of-office data: ' . $e->getMessage(), [ | |||
'exception' => $e, | |||
]); | |||
return; | |||
} | |||
$this->cache->set($data->getUser()->getUID(), $cachedString, 300); | |||
} | |||
public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData { | |||
$cachedData = $this->getCachedOutOfOfficeData($user); | |||
if ($cachedData !== null) { | |||
return $cachedData; | |||
} | |||
try { | |||
$absenceData = $this->absenceMapper->findByUserId($user->getUID()); | |||
} catch (DoesNotExistException $e) { | |||
return null; | |||
} | |||
$data = $absenceData->toOutOufOfficeData($user); | |||
$this->setCachedOutOfOfficeData($data); | |||
return $data; | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OC\User; | |||
use OCP\IUser; | |||
use OCP\User\IOutOfOfficeData; | |||
class OutOfOfficeData implements IOutOfOfficeData { | |||
public function __construct(private string $id, | |||
private IUser $user, | |||
private int $startDate, | |||
private int $endDate, | |||
private string $shortMessage, | |||
private string $message) { | |||
} | |||
public function getId(): string { | |||
return $this->id; | |||
} | |||
public function getUser(): IUser { | |||
return $this->user; | |||
} | |||
public function getStartDate(): int { | |||
return $this->startDate; | |||
} | |||
public function getEndDate(): int { | |||
return $this->endDate; | |||
} | |||
public function getShortMessage(): string { | |||
return $this->shortMessage; | |||
} | |||
public function getMessage(): string { | |||
return $this->message; | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCP\User\Events; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\User\IOutOfOfficeData; | |||
/** | |||
* Emitted when a user's out-of-office period has changed | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
class OutOfOfficeChangedEvent extends Event { | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
public function __construct(private IOutOfOfficeData $data) { | |||
parent::__construct(); | |||
} | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
public function getData(): IOutOfOfficeData { | |||
return $this->data; | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCP\User\Events; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\User\IOutOfOfficeData; | |||
/** | |||
* Emitted when a user's out-of-office period is cleared | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
class OutOfOfficeClearedEvent extends Event { | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
public function __construct(private IOutOfOfficeData $data) { | |||
parent::__construct(); | |||
} | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
public function getData(): IOutOfOfficeData { | |||
return $this->data; | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCP\User\Events; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\User\IOutOfOfficeData; | |||
/** | |||
* Emitted when a user's out-of-office period is scheduled | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
class OutOfOfficeScheduledEvent extends Event { | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
public function __construct(private IOutOfOfficeData $data) { | |||
parent::__construct(); | |||
} | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
public function getData(): IOutOfOfficeData { | |||
return $this->data; | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCP\User; | |||
use OCP\IUser; | |||
/** | |||
* Coordinator for availability and out-of-office messages | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
interface IAvailabilityCoordinator { | |||
/** | |||
* Get the user's out-of-office message, if any | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData; | |||
} |
@@ -0,0 +1,77 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCP\User; | |||
use OCP\IUser; | |||
/** | |||
* DTO to hold out-of-office information of a user | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
interface IOutOfOfficeData { | |||
/** | |||
* Get the unique token assigned to the current out-of-office event | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
public function getId(): string; | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
public function getUser(): IUser; | |||
/** | |||
* Get the accurate out-of-office start date | |||
* | |||
* This event is not guaranteed to be emitted exactly at start date | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
public function getStartDate(): int; | |||
/** | |||
* Get the (preliminary) out-of-office end date | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
public function getEndDate(): int; | |||
/** | |||
* Get the short summary text displayed in the user status and similar | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
public function getShortMessage(): string; | |||
/** | |||
* Get the long out-of-office message for auto responders and similar | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
public function getMessage(): string; | |||
} |
@@ -0,0 +1,164 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud> | |||
* | |||
* @author Richard Steinmetz <richard@steinmetz.cloud> | |||
* | |||
* @license AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU 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 General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace Test\User; | |||
use OC\User\AvailabilityCoordinator; | |||
use OC\User\OutOfOfficeData; | |||
use OCA\DAV\Db\Absence; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\ICache; | |||
use OCP\ICacheFactory; | |||
use OCP\IUser; | |||
use Psr\Log\LoggerInterface; | |||
use Test\TestCase; | |||
class AvailabilityCoordinatorTest extends TestCase { | |||
private AvailabilityCoordinator $availabilityCoordinator; | |||
private ICacheFactory $cacheFactory; | |||
private ICache $cache; | |||
private AbsenceMapper $absenceMapper; | |||
private LoggerInterface $logger; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
$this->cacheFactory = $this->createMock(ICacheFactory::class); | |||
$this->cache = $this->createMock(ICache::class); | |||
$this->absenceMapper = $this->createMock(AbsenceMapper::class); | |||
$this->logger = $this->createMock(LoggerInterface::class); | |||
$this->cacheFactory->expects(self::once()) | |||
->method('createLocal') | |||
->willReturn($this->cache); | |||
$this->availabilityCoordinator = new AvailabilityCoordinator( | |||
$this->cacheFactory, | |||
$this->absenceMapper, | |||
$this->logger, | |||
); | |||
} | |||
public function testGetOutOfOfficeData(): void { | |||
$absence = new Absence(); | |||
$absence->setId(420); | |||
$absence->setUserId('user'); | |||
$absence->setFirstDay('2023-10-01'); | |||
$absence->setLastDay('2023-10-08'); | |||
$absence->setStatus('Vacation'); | |||
$absence->setMessage('On vacation'); | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
->willReturn('user'); | |||
$this->cache->expects(self::once()) | |||
->method('get') | |||
->with('user') | |||
->willReturn(null); | |||
$this->absenceMapper->expects(self::once()) | |||
->method('findByUserId') | |||
->with('user') | |||
->willReturn($absence); | |||
$this->cache->expects(self::once()) | |||
->method('set') | |||
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}', 300); | |||
$expected = new OutOfOfficeData( | |||
'420', | |||
$user, | |||
1696118400, | |||
1696723200, | |||
'Vacation', | |||
'On vacation', | |||
); | |||
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user); | |||
self::assertEquals($expected, $actual); | |||
} | |||
public function testGetOutOfOfficeDataWithCachedData(): void { | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
->willReturn('user'); | |||
$this->cache->expects(self::once()) | |||
->method('get') | |||
->with('user') | |||
->willReturn('{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}'); | |||
$this->absenceMapper->expects(self::never()) | |||
->method('findByUserId'); | |||
$this->cache->expects(self::never()) | |||
->method('set'); | |||
$expected = new OutOfOfficeData( | |||
'420', | |||
$user, | |||
1696118400, | |||
1696723200, | |||
'Vacation', | |||
'On vacation', | |||
); | |||
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user); | |||
self::assertEquals($expected, $actual); | |||
} | |||
public function testGetOutOfOfficeDataWithInvalidCachedData(): void { | |||
$absence = new Absence(); | |||
$absence->setId(420); | |||
$absence->setUserId('user'); | |||
$absence->setFirstDay('2023-10-01'); | |||
$absence->setLastDay('2023-10-08'); | |||
$absence->setStatus('Vacation'); | |||
$absence->setMessage('On vacation'); | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
->willReturn('user'); | |||
$this->cache->expects(self::once()) | |||
->method('get') | |||
->with('user') | |||
->willReturn('{"id":"420",}'); | |||
$this->absenceMapper->expects(self::once()) | |||
->method('findByUserId') | |||
->with('user') | |||
->willReturn($absence); | |||
$this->cache->expects(self::once()) | |||
->method('set') | |||
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}', 300); | |||
$expected = new OutOfOfficeData( | |||
'420', | |||
$user, | |||
1696118400, | |||
1696723200, | |||
'Vacation', | |||
'On vacation', | |||
); | |||
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user); | |||
self::assertEquals($expected, $actual); | |||
} | |||
} |