Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>tags/v29.0.0beta1
@@ -18,6 +18,7 @@ return array( | |||
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php', | |||
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => $baseDir . '/../lib/BackgroundJob/EventReminderJob.php', | |||
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', | |||
'OCA\\DAV\\BackgroundJob\\OutOfOfficeEventDispatcherJob' => $baseDir . '/../lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php', | |||
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => $baseDir . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php', | |||
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => $baseDir . '/../lib/BackgroundJob/RefreshWebcalJob.php', | |||
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', | |||
@@ -100,6 +101,7 @@ return array( | |||
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => $baseDir . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php', | |||
'OCA\\DAV\\CalDAV\\Status\\Status' => $baseDir . '/../lib/CalDAV/Status/Status.php', | |||
'OCA\\DAV\\CalDAV\\Status\\StatusService' => $baseDir . '/../lib/CalDAV/Status/StatusService.php', | |||
'OCA\\DAV\\CalDAV\\TimezoneService' => $baseDir . '/../lib/CalDAV/TimezoneService.php', | |||
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php', | |||
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObjectsCollection' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php', | |||
'OCA\\DAV\\CalDAV\\Trashbin\\Plugin' => $baseDir . '/../lib/CalDAV/Trashbin/Plugin.php', | |||
@@ -210,6 +212,8 @@ return array( | |||
'OCA\\DAV\\Db\\AbsenceMapper' => $baseDir . '/../lib/Db/AbsenceMapper.php', | |||
'OCA\\DAV\\Db\\Direct' => $baseDir . '/../lib/Db/Direct.php', | |||
'OCA\\DAV\\Db\\DirectMapper' => $baseDir . '/../lib/Db/DirectMapper.php', | |||
'OCA\\DAV\\Db\\Property' => $baseDir . '/../lib/Db/Property.php', | |||
'OCA\\DAV\\Db\\PropertyMapper' => $baseDir . '/../lib/Db/PropertyMapper.php', | |||
'OCA\\DAV\\Direct\\DirectFile' => $baseDir . '/../lib/Direct/DirectFile.php', | |||
'OCA\\DAV\\Direct\\DirectHome' => $baseDir . '/../lib/Direct/DirectHome.php', | |||
'OCA\\DAV\\Direct\\Server' => $baseDir . '/../lib/Direct/Server.php', |
@@ -33,6 +33,7 @@ class ComposerStaticInitDAV | |||
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php', | |||
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/EventReminderJob.php', | |||
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', | |||
'OCA\\DAV\\BackgroundJob\\OutOfOfficeEventDispatcherJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php', | |||
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php', | |||
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/RefreshWebcalJob.php', | |||
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', | |||
@@ -115,6 +116,7 @@ class ComposerStaticInitDAV | |||
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php', | |||
'OCA\\DAV\\CalDAV\\Status\\Status' => __DIR__ . '/..' . '/../lib/CalDAV/Status/Status.php', | |||
'OCA\\DAV\\CalDAV\\Status\\StatusService' => __DIR__ . '/..' . '/../lib/CalDAV/Status/StatusService.php', | |||
'OCA\\DAV\\CalDAV\\TimezoneService' => __DIR__ . '/..' . '/../lib/CalDAV/TimezoneService.php', | |||
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php', | |||
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObjectsCollection' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php', | |||
'OCA\\DAV\\CalDAV\\Trashbin\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/Plugin.php', | |||
@@ -225,6 +227,8 @@ class ComposerStaticInitDAV | |||
'OCA\\DAV\\Db\\AbsenceMapper' => __DIR__ . '/..' . '/../lib/Db/AbsenceMapper.php', | |||
'OCA\\DAV\\Db\\Direct' => __DIR__ . '/..' . '/../lib/Db/Direct.php', | |||
'OCA\\DAV\\Db\\DirectMapper' => __DIR__ . '/..' . '/../lib/Db/DirectMapper.php', | |||
'OCA\\DAV\\Db\\Property' => __DIR__ . '/..' . '/../lib/Db/Property.php', | |||
'OCA\\DAV\\Db\\PropertyMapper' => __DIR__ . '/..' . '/../lib/Db/PropertyMapper.php', | |||
'OCA\\DAV\\Direct\\DirectFile' => __DIR__ . '/..' . '/../lib/Direct/DirectFile.php', | |||
'OCA\\DAV\\Direct\\DirectHome' => __DIR__ . '/..' . '/../lib/Direct/DirectHome.php', | |||
'OCA\\DAV\\Direct\\Server' => __DIR__ . '/..' . '/../lib/Direct/Server.php', |
@@ -0,0 +1,92 @@ | |||
<?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\BackgroundJob; | |||
use OCA\DAV\CalDAV\TimezoneService; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\AppFramework\Db\DoesNotExistException; | |||
use OCP\AppFramework\Utility\ITimeFactory; | |||
use OCP\BackgroundJob\QueuedJob; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\IUserManager; | |||
use OCP\User\Events\OutOfOfficeEndedEvent; | |||
use OCP\User\Events\OutOfOfficeStartedEvent; | |||
use Psr\Log\LoggerInterface; | |||
class OutOfOfficeEventDispatcherJob extends QueuedJob { | |||
public const EVENT_START = 'start'; | |||
public const EVENT_END = 'end'; | |||
public function __construct( | |||
ITimeFactory $time, | |||
private AbsenceMapper $absenceMapper, | |||
private LoggerInterface $logger, | |||
private IEventDispatcher $eventDispatcher, | |||
private IUserManager $userManager, | |||
private TimezoneService $timezoneService, | |||
) { | |||
parent::__construct($time); | |||
} | |||
public function run($argument): void { | |||
$id = $argument['id']; | |||
$event = $argument['event']; | |||
try { | |||
$absence = $this->absenceMapper->findById($id); | |||
} catch (DoesNotExistException | \OCP\DB\Exception $e) { | |||
$this->logger->error('Failed to dispatch out-of-office event: ' . $e->getMessage(), [ | |||
'exception' => $e, | |||
'argument' => $argument, | |||
]); | |||
return; | |||
} | |||
$userId = $absence->getUserId(); | |||
$user = $this->userManager->get($userId); | |||
if ($user === null) { | |||
$this->logger->error("Failed to dispatch out-of-office event: User $userId does not exist", [ | |||
'argument' => $argument, | |||
]); | |||
return; | |||
} | |||
$data = $absence->toOutOufOfficeData( | |||
$user, | |||
$this->timezoneService->getUserTimezone($userId) ?? $this->timezoneService->getDefaultTimezone(), | |||
); | |||
if ($event === self::EVENT_START) { | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeStartedEvent($data)); | |||
} elseif ($event === self::EVENT_END) { | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeEndedEvent($data)); | |||
} else { | |||
$this->logger->error("Invalid out-of-office event: $event", [ | |||
'argument' => $argument, | |||
]); | |||
} | |||
} | |||
} |
@@ -0,0 +1,98 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCA\DAV\CalDAV; | |||
use OCA\DAV\Db\PropertyMapper; | |||
use OCP\Calendar\ICalendar; | |||
use OCP\Calendar\IManager; | |||
use OCP\IConfig; | |||
use Sabre\VObject\Component\VCalendar; | |||
use Sabre\VObject\Component\VTimeZone; | |||
use Sabre\VObject\Reader; | |||
use function array_reduce; | |||
class TimezoneService { | |||
public function __construct(private IConfig $config, | |||
private PropertyMapper $propertyMapper, | |||
private IManager $calendarManager) { | |||
} | |||
public function getUserTimezone(string $userId): ?string { | |||
$availabilityPropPath = 'calendars/' . $userId . '/inbox'; | |||
$availabilityProp = '{' . Plugin::NS_CALDAV . '}calendar-availability'; | |||
$availabilities = $this->propertyMapper->findPropertyByPathAndName($userId, $availabilityPropPath, $availabilityProp); | |||
if (!empty($availabilities)) { | |||
$availability = $availabilities[0]->getPropertyvalue(); | |||
/** @var VCalendar $vCalendar */ | |||
$vCalendar = Reader::read($availability); | |||
/** @var VTimeZone $vTimezone */ | |||
$vTimezone = $vCalendar->VTIMEZONE; | |||
// Sabre has a fallback to date_default_timezone_get | |||
return $vTimezone->getTimeZone()->getName(); | |||
} | |||
$principal = 'principals/users/' . $userId; | |||
$uri = $this->config->getUserValue($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI); | |||
$calendars = $this->calendarManager->getCalendarsForPrincipal($principal); | |||
/** @var ?VTimeZone $personalCalendarTimezone */ | |||
$personalCalendarTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) use ($uri) { | |||
if ($acc !== null) { | |||
return $acc; | |||
} | |||
if ($calendar->getUri() === $uri && !$calendar->isDeleted() && $calendar instanceof CalendarImpl) { | |||
return $calendar->getSchedulingTimezone(); | |||
} | |||
return null; | |||
}); | |||
if ($personalCalendarTimezone !== null) { | |||
return $personalCalendarTimezone->getTimeZone()->getName(); | |||
} | |||
// No timezone in the personalCalendarTimezone calendar or no personalCalendarTimezone calendar | |||
// Loop through all calendars until we find a timezone. | |||
/** @var ?VTimeZone $firstTimezone */ | |||
$firstTimezone = array_reduce($calendars, function (?VTimeZone $acc, ICalendar $calendar) { | |||
if ($acc !== null) { | |||
return $acc; | |||
} | |||
if (!$calendar->isDeleted() && $calendar instanceof CalendarImpl) { | |||
return $calendar->getSchedulingTimezone(); | |||
} | |||
return null; | |||
}); | |||
if ($firstTimezone !== null) { | |||
return $firstTimezone->getTimeZone()->getName(); | |||
} | |||
return null; | |||
} | |||
public function getDefaultTimezone(): string { | |||
return $this->config->getSystemValueString('default_timezone', 'UTC'); | |||
} | |||
} |
@@ -35,11 +35,12 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; | |||
use OCP\AppFramework\Http\JSONResponse; | |||
use OCP\AppFramework\Http\Response; | |||
use OCP\IRequest; | |||
use OCP\IUserSession; | |||
class AvailabilitySettingsController extends Controller { | |||
public function __construct( | |||
IRequest $request, | |||
private ?string $userId, | |||
private ?IUserSession $userSession, | |||
private AbsenceService $absenceService, | |||
) { | |||
parent::__construct(Application::APP_ID, $request); | |||
@@ -56,8 +57,8 @@ class AvailabilitySettingsController extends Controller { | |||
string $status, | |||
string $message, | |||
): Response { | |||
$userId = $this->userId; | |||
if ($userId === null) { | |||
$user = $this->userSession?->getUser(); | |||
if ($user === null) { | |||
return new JSONResponse([], Http::STATUS_FORBIDDEN); | |||
} | |||
@@ -68,7 +69,7 @@ class AvailabilitySettingsController extends Controller { | |||
} | |||
$absence = $this->absenceService->createOrUpdateAbsence( | |||
$userId, | |||
$user, | |||
$firstDay, | |||
$lastDay, | |||
$status, | |||
@@ -82,12 +83,12 @@ class AvailabilitySettingsController extends Controller { | |||
*/ | |||
#[NoAdminRequired] | |||
public function clearAbsence(): Response { | |||
$userId = $this->userId; | |||
if ($userId === null) { | |||
$user = $this->userSession?->getUser(); | |||
if ($user === null) { | |||
return new JSONResponse([], Http::STATUS_FORBIDDEN); | |||
} | |||
$this->absenceService->clearAbsence($userId); | |||
$this->absenceService->clearAbsence($user); | |||
return new JSONResponse([]); | |||
} | |||
@@ -26,7 +26,8 @@ declare(strict_types=1); | |||
namespace OCA\DAV\Db; | |||
use DateTimeImmutable; | |||
use DateTime; | |||
use DateTimeZone; | |||
use Exception; | |||
use InvalidArgumentException; | |||
use JsonSerializable; | |||
@@ -67,7 +68,7 @@ class Absence extends Entity implements JsonSerializable { | |||
$this->addType('message', 'string'); | |||
} | |||
public function toOutOufOfficeData(IUser $user): IOutOfOfficeData { | |||
public function toOutOufOfficeData(IUser $user, string $timezone): 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()); | |||
} | |||
@@ -75,8 +76,10 @@ class Absence extends Entity implements JsonSerializable { | |||
throw new Exception('Creating out-of-office data without ID'); | |||
} | |||
$startDate = new DateTimeImmutable($this->getFirstDay()); | |||
$endDate = new DateTimeImmutable($this->getLastDay()); | |||
$tz = new DateTimeZone($timezone); | |||
$startDate = new DateTime($this->getFirstDay(), $tz); | |||
$endDate = new DateTime($this->getLastDay(), $tz); | |||
$endDate->setTime(23, 59); | |||
return new OutOfOfficeData( | |||
(string)$this->getId(), | |||
$user, |
@@ -40,6 +40,31 @@ class AbsenceMapper extends QBMapper { | |||
parent::__construct($db, 'dav_absence', Absence::class); | |||
} | |||
/** | |||
* @throws DoesNotExistException | |||
* @throws \OCP\DB\Exception | |||
*/ | |||
public function findById(int $id): Absence { | |||
$qb = $this->db->getQueryBuilder(); | |||
$qb->select('*') | |||
->from($this->getTableName()) | |||
->where($qb->expr()->eq( | |||
'id', | |||
$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), | |||
IQueryBuilder::PARAM_INT), | |||
); | |||
try { | |||
return $this->findEntity($qb); | |||
} catch (MultipleObjectsReturnedException $e) { | |||
// Won't happen as id is the primary key | |||
throw new \RuntimeException( | |||
'The impossible has happened! The query returned multiple absence settings for one user.', | |||
0, | |||
$e, | |||
); | |||
} | |||
} | |||
/** | |||
* @throws DoesNotExistException | |||
* @throws \OCP\DB\Exception |
@@ -0,0 +1,53 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCA\DAV\Db; | |||
use OCP\AppFramework\Db\Entity; | |||
/** | |||
* @method string getUserid() | |||
* @method string getPropertypath() | |||
* @method string getPropertyname() | |||
* @method string getPropertyvalue() | |||
*/ | |||
class Property extends Entity { | |||
/** @var string|null */ | |||
protected $userid; | |||
/** @var string|null */ | |||
protected $propertypath; | |||
/** @var string|null */ | |||
protected $propertyname; | |||
/** @var string|null */ | |||
protected $propertyvalue; | |||
/** @var int|null */ | |||
protected $valuetype; | |||
} |
@@ -0,0 +1,57 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCA\DAV\Db; | |||
use OCP\AppFramework\Db\QBMapper; | |||
use OCP\IDBConnection; | |||
/** | |||
* @template-extends QBMapper<Property> | |||
*/ | |||
class PropertyMapper extends QBMapper { | |||
private const TABLE_NAME = 'properties'; | |||
public function __construct(IDBConnection $db) { | |||
parent::__construct($db, self::TABLE_NAME, Property::class); | |||
} | |||
/** | |||
* @return Property[] | |||
*/ | |||
public function findPropertyByPathAndName(string $userId, string $path, string $name): array { | |||
$selectQb = $this->db->getQueryBuilder(); | |||
$selectQb->select('*') | |||
->from(self::TABLE_NAME) | |||
->where( | |||
$selectQb->expr()->eq('userid', $selectQb->createNamedParameter($userId)), | |||
$selectQb->expr()->eq('propertypath', $selectQb->createNamedParameter($path)), | |||
$selectQb->expr()->eq('propertyname', $selectQb->createNamedParameter($name)), | |||
); | |||
return $this->findEntities($selectQb); | |||
} | |||
} |
@@ -27,11 +27,14 @@ declare(strict_types=1); | |||
namespace OCA\DAV\Service; | |||
use InvalidArgumentException; | |||
use OCA\DAV\BackgroundJob\OutOfOfficeEventDispatcherJob; | |||
use OCA\DAV\CalDAV\TimezoneService; | |||
use OCA\DAV\Db\Absence; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\AppFramework\Db\DoesNotExistException; | |||
use OCP\BackgroundJob\IJobList; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\IUserManager; | |||
use OCP\IUser; | |||
use OCP\User\Events\OutOfOfficeChangedEvent; | |||
use OCP\User\Events\OutOfOfficeClearedEvent; | |||
use OCP\User\Events\OutOfOfficeScheduledEvent; | |||
@@ -40,7 +43,8 @@ class AbsenceService { | |||
public function __construct( | |||
private AbsenceMapper $absenceMapper, | |||
private IEventDispatcher $eventDispatcher, | |||
private IUserManager $userManager, | |||
private IJobList $jobList, | |||
private TimezoneService $timezoneService, | |||
) { | |||
} | |||
@@ -52,61 +56,76 @@ class AbsenceService { | |||
* @throws InvalidArgumentException If no user with the given user id exists. | |||
*/ | |||
public function createOrUpdateAbsence( | |||
string $userId, | |||
IUser $user, | |||
string $firstDay, | |||
string $lastDay, | |||
string $status, | |||
string $message, | |||
): Absence { | |||
try { | |||
$absence = $this->absenceMapper->findByUserId($userId); | |||
$absence = $this->absenceMapper->findByUserId($user->getUID()); | |||
} catch (DoesNotExistException) { | |||
$absence = new Absence(); | |||
} | |||
$absence->setUserId($userId); | |||
$absence->setUserId($user->getUID()); | |||
$absence->setFirstDay($firstDay); | |||
$absence->setLastDay($lastDay); | |||
$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"); | |||
} | |||
if ($absence->getId() === null) { | |||
$persistedAbsence = $this->absenceMapper->insert($absence); | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeScheduledEvent( | |||
$persistedAbsence->toOutOufOfficeData($user) | |||
)); | |||
return $persistedAbsence; | |||
$absence = $this->absenceMapper->insert($absence); | |||
$eventData = $absence->toOutOufOfficeData( | |||
$user, | |||
$this->timezoneService->getUserTimezone($user->getUID()) ?? $this->timezoneService->getDefaultTimezone(), | |||
); | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeScheduledEvent($eventData)); | |||
} else { | |||
$absence = $this->absenceMapper->update($absence); | |||
$eventData = $absence->toOutOufOfficeData( | |||
$user, | |||
$this->timezoneService->getUserTimezone($user->getUID()) ?? $this->timezoneService->getDefaultTimezone(), | |||
); | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeChangedEvent($eventData)); | |||
} | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeChangedEvent( | |||
$absence->toOutOufOfficeData($user) | |||
)); | |||
return $this->absenceMapper->update($absence); | |||
$this->jobList->scheduleAfter( | |||
OutOfOfficeEventDispatcherJob::class, | |||
$eventData->getStartDate(), | |||
[ | |||
'id' => $absence->getId(), | |||
'event' => OutOfOfficeEventDispatcherJob::EVENT_START, | |||
], | |||
); | |||
$this->jobList->scheduleAfter( | |||
OutOfOfficeEventDispatcherJob::class, | |||
$eventData->getEndDate(), | |||
[ | |||
'id' => $absence->getId(), | |||
'event' => OutOfOfficeEventDispatcherJob::EVENT_END, | |||
], | |||
); | |||
return $absence; | |||
} | |||
/** | |||
* @throws \OCP\DB\Exception | |||
*/ | |||
public function clearAbsence(string $userId): void { | |||
public function clearAbsence(IUser $user): void { | |||
try { | |||
$absence = $this->absenceMapper->findByUserId($userId); | |||
$absence = $this->absenceMapper->findByUserId($user->getUID()); | |||
} 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->jobList->remove(OutOfOfficeEventDispatcherJob::class); | |||
$eventData = $absence->toOutOufOfficeData( | |||
$user, | |||
$this->timezoneService->getUserTimezone($user->getUID()) ?? $this->timezoneService->getDefaultTimezone(), | |||
); | |||
$this->eventDispatcher->dispatchTyped(new OutOfOfficeClearedEvent($eventData)); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCA\DAV\Tests\integration\Db; | |||
use OCA\DAV\Db\PropertyMapper; | |||
use Test\TestCase; | |||
/** | |||
* @group DB | |||
*/ | |||
class PropertyMapperTest extends TestCase { | |||
/** @var PropertyMapper */ | |||
private PropertyMapper $mapper; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
$this->mapper = \OC::$server->get(PropertyMapper::class); | |||
} | |||
public function testFindNonExistent(): void { | |||
$props = $this->mapper->findPropertyByPathAndName( | |||
'userthatdoesnotexist', | |||
'path/that/does/not/exist/either', | |||
'nope', | |||
); | |||
self::assertEmpty($props); | |||
} | |||
} |
@@ -0,0 +1,175 @@ | |||
<?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\Tests\unit\BackgroundJob; | |||
use OCA\DAV\BackgroundJob\OutOfOfficeEventDispatcherJob; | |||
use OCA\DAV\CalDAV\TimezoneService; | |||
use OCA\DAV\Db\Absence; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\AppFramework\Utility\ITimeFactory; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\IUser; | |||
use OCP\IUserManager; | |||
use OCP\User\Events\OutOfOfficeEndedEvent; | |||
use OCP\User\Events\OutOfOfficeStartedEvent; | |||
use PHPUnit\Framework\MockObject\MockObject; | |||
use Psr\Log\LoggerInterface; | |||
use Test\TestCase; | |||
class OutOfOfficeEventDispatcherJobTest extends TestCase { | |||
private OutOfOfficeEventDispatcherJob $job; | |||
/** @var MockObject|ITimeFactory */ | |||
private $timeFactory; | |||
/** @var MockObject|AbsenceMapper */ | |||
private $absenceMapper; | |||
/** @var MockObject|LoggerInterface */ | |||
private $logger; | |||
/** @var MockObject|IEventDispatcher */ | |||
private $eventDispatcher; | |||
/** @var MockObject|IUserManager */ | |||
private $userManager; | |||
private MockObject|TimezoneService $timezoneService; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
$this->timeFactory = $this->createMock(ITimeFactory::class); | |||
$this->absenceMapper = $this->createMock(AbsenceMapper::class); | |||
$this->logger = $this->createMock(LoggerInterface::class); | |||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class); | |||
$this->userManager = $this->createMock(IUserManager::class); | |||
$this->timezoneService = $this->createMock(TimezoneService::class); | |||
$this->job = new OutOfOfficeEventDispatcherJob( | |||
$this->timeFactory, | |||
$this->absenceMapper, | |||
$this->logger, | |||
$this->eventDispatcher, | |||
$this->userManager, | |||
$this->timezoneService, | |||
); | |||
} | |||
public function testDispatchStartEvent() { | |||
$this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); | |||
$absence = new Absence(); | |||
$absence->setId(200); | |||
$absence->setUserId('user'); | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
->willReturn('user'); | |||
$this->absenceMapper->expects(self::once()) | |||
->method('findById') | |||
->with(1) | |||
->willReturn($absence); | |||
$this->userManager->expects(self::once()) | |||
->method('get') | |||
->with('user') | |||
->willReturn($user); | |||
$this->eventDispatcher->expects(self::once()) | |||
->method('dispatchTyped') | |||
->with(self::callback(static function ($event): bool { | |||
self::assertInstanceOf(OutOfOfficeStartedEvent::class, $event); | |||
return true; | |||
})); | |||
$this->job->run([ | |||
'id' => 1, | |||
'event' => OutOfOfficeEventDispatcherJob::EVENT_START, | |||
]); | |||
} | |||
public function testDispatchStopEvent() { | |||
$this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); | |||
$absence = new Absence(); | |||
$absence->setId(200); | |||
$absence->setUserId('user'); | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
->willReturn('user'); | |||
$this->absenceMapper->expects(self::once()) | |||
->method('findById') | |||
->with(1) | |||
->willReturn($absence); | |||
$this->userManager->expects(self::once()) | |||
->method('get') | |||
->with('user') | |||
->willReturn($user); | |||
$this->eventDispatcher->expects(self::once()) | |||
->method('dispatchTyped') | |||
->with(self::callback(static function ($event): bool { | |||
self::assertInstanceOf(OutOfOfficeEndedEvent::class, $event); | |||
return true; | |||
})); | |||
$this->job->run([ | |||
'id' => 1, | |||
'event' => OutOfOfficeEventDispatcherJob::EVENT_END, | |||
]); | |||
} | |||
public function testDoesntDispatchUnknownEvent() { | |||
$this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); | |||
$absence = new Absence(); | |||
$absence->setId(100); | |||
$absence->setUserId('user'); | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
->willReturn('user'); | |||
$this->absenceMapper->expects(self::once()) | |||
->method('findById') | |||
->with(1) | |||
->willReturn($absence); | |||
$this->userManager->expects(self::once()) | |||
->method('get') | |||
->with('user') | |||
->willReturn($user); | |||
$this->eventDispatcher->expects(self::never()) | |||
->method('dispatchTyped'); | |||
$this->logger->expects(self::once()) | |||
->method('error'); | |||
$this->job->run([ | |||
'id' => 1, | |||
'event' => 'foobar', | |||
]); | |||
} | |||
} |
@@ -0,0 +1,161 @@ | |||
<?php | |||
/* | |||
* @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/>. | |||
*/ | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCA\DAV\Tests\unit\CalDAV; | |||
use DateTimeZone; | |||
use OCA\DAV\CalDAV\CalendarImpl; | |||
use OCA\DAV\CalDAV\TimezoneService; | |||
use OCA\DAV\Db\Property; | |||
use OCA\DAV\Db\PropertyMapper; | |||
use OCP\Calendar\ICalendar; | |||
use OCP\Calendar\IManager; | |||
use OCP\IConfig; | |||
use PHPUnit\Framework\MockObject\MockObject; | |||
use Sabre\VObject\Component\VTimeZone; | |||
use Test\TestCase; | |||
class TimezoneServiceTest extends TestCase { | |||
private IConfig|MockObject $config; | |||
private PropertyMapper|MockObject $propertyMapper; | |||
private IManager|MockObject $calendarManager; | |||
private TimezoneService $service; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
$this->config = $this->createMock(IConfig::class); | |||
$this->propertyMapper = $this->createMock(PropertyMapper::class); | |||
$this->calendarManager = $this->createMock(IManager::class); | |||
$this->service = new TimezoneService( | |||
$this->config, | |||
$this->propertyMapper, | |||
$this->calendarManager, | |||
); | |||
} | |||
public function testGetUserTimezoneFromAvailability(): void { | |||
$property = new Property(); | |||
$property->setPropertyvalue('BEGIN:VCALENDAR | |||
PRODID:Nextcloud DAV app | |||
BEGIN:VTIMEZONE | |||
TZID:Europe/Vienna | |||
END:VTIMEZONE | |||
END:VCALENDAR'); | |||
$this->propertyMapper->expects(self::once()) | |||
->method('findPropertyByPathAndName') | |||
->willReturn([ | |||
$property, | |||
]); | |||
$timezone = $this->service->getUserTimezone('test123'); | |||
self::assertNotNull($timezone); | |||
self::assertEquals('Europe/Vienna', $timezone); | |||
} | |||
public function testGetUserTimezoneFromPersonalCalendar(): void { | |||
$this->config->expects(self::once()) | |||
->method('getUserValue') | |||
->with('test123', 'dav', 'defaultCalendar') | |||
->willReturn('personal-1'); | |||
$other = $this->createMock(ICalendar::class); | |||
$other->method('getUri')->willReturn('other'); | |||
$personal = $this->createMock(CalendarImpl::class); | |||
$personal->method('getUri')->willReturn('personal-1'); | |||
$tz = new DateTimeZone('Europe/Berlin'); | |||
$vtz = $this->createMock(VTimeZone::class); | |||
$vtz->method('getTimeZone')->willReturn($tz); | |||
$personal->method('getSchedulingTimezone')->willReturn($vtz); | |||
$this->calendarManager->expects(self::once()) | |||
->method('getCalendarsForPrincipal') | |||
->with('principals/users/test123') | |||
->willReturn([ | |||
$other, | |||
$personal, | |||
]); | |||
$timezone = $this->service->getUserTimezone('test123'); | |||
self::assertNotNull($timezone); | |||
self::assertEquals('Europe/Berlin', $timezone); | |||
} | |||
public function testGetUserTimezoneFromAny(): void { | |||
$this->config->expects(self::once()) | |||
->method('getUserValue') | |||
->with('test123', 'dav', 'defaultCalendar') | |||
->willReturn('personal-1'); | |||
$other = $this->createMock(ICalendar::class); | |||
$other->method('getUri')->willReturn('other'); | |||
$personal = $this->createMock(CalendarImpl::class); | |||
$personal->method('getUri')->willReturn('personal-2'); | |||
$tz = new DateTimeZone('Europe/Prague'); | |||
$vtz = $this->createMock(VTimeZone::class); | |||
$vtz->method('getTimeZone')->willReturn($tz); | |||
$personal->method('getSchedulingTimezone')->willReturn($vtz); | |||
$this->calendarManager->expects(self::once()) | |||
->method('getCalendarsForPrincipal') | |||
->with('principals/users/test123') | |||
->willReturn([ | |||
$other, | |||
$personal, | |||
]); | |||
$timezone = $this->service->getUserTimezone('test123'); | |||
self::assertNotNull($timezone); | |||
self::assertEquals('Europe/Prague', $timezone); | |||
} | |||
public function testGetUserTimezoneNoneFound(): void { | |||
$timezone = $this->service->getUserTimezone('test123'); | |||
self::assertNull($timezone); | |||
} | |||
} |
@@ -228,6 +228,16 @@ $CONFIG = [ | |||
*/ | |||
'force_locale' => 'en_US', | |||
/** | |||
* This sets the default timezone on your Nextcloud server, using IANA | |||
* identifiers like ``Europe/Berlin`` or ``Pacific/Auckland``. The default | |||
* timezone parameter is only used when the timezone of the user can't be | |||
* determined. | |||
* | |||
* Defaults to ``UTC`` | |||
*/ | |||
'default_timezone' => 'Europe/Berlin', | |||
/** | |||
* ``true`` enables the Help menu item in the user menu (top right of the | |||
* Nextcloud Web interface). ``false`` removes the Help item. |
@@ -742,7 +742,9 @@ return array( | |||
'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\\OutOfOfficeEndedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeEndedEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeStartedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeStartedEvent.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', |
@@ -775,7 +775,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
'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\\OutOfOfficeEndedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeEndedEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php', | |||
'OCP\\User\\Events\\OutOfOfficeStartedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeStartedEvent.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', |
@@ -28,6 +28,7 @@ namespace OC\User; | |||
use JsonException; | |||
use OCA\DAV\AppInfo\Application; | |||
use OCA\DAV\CalDAV\TimezoneService; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\AppFramework\Db\DoesNotExistException; | |||
use OCP\ICache; | |||
@@ -46,6 +47,7 @@ class AvailabilityCoordinator implements IAvailabilityCoordinator { | |||
private AbsenceMapper $absenceMapper, | |||
private IConfig $config, | |||
private LoggerInterface $logger, | |||
private TimezoneService $timezoneService, | |||
) { | |||
$this->cache = $cacheFactory->createLocal('OutOfOfficeData'); | |||
} | |||
@@ -115,7 +117,10 @@ class AvailabilityCoordinator implements IAvailabilityCoordinator { | |||
return null; | |||
} | |||
$data = $absenceData->toOutOufOfficeData($user); | |||
$data = $absenceData->toOutOufOfficeData( | |||
$user, | |||
$this->timezoneService->getUserTimezone($user->getUID()) ?? $this->timezoneService->getDefaultTimezone(), | |||
); | |||
$this->setCachedOutOfOfficeData($data); | |||
return $data; | |||
} |
@@ -0,0 +1,51 @@ | |||
<?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 OCP\User\Events; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\User\IOutOfOfficeData; | |||
/** | |||
* Emitted when a user's out-of-office period ended | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
class OutOfOfficeEndedEvent 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,51 @@ | |||
<?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 OCP\User\Events; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\User\IOutOfOfficeData; | |||
/** | |||
* Emitted when a user's out-of-office period started | |||
* | |||
* @since 28.0.0 | |||
*/ | |||
class OutOfOfficeStartedEvent 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; | |||
} | |||
} |
@@ -28,6 +28,7 @@ namespace Test\User; | |||
use OC\User\AvailabilityCoordinator; | |||
use OC\User\OutOfOfficeData; | |||
use OCA\DAV\CalDAV\TimezoneService; | |||
use OCA\DAV\Db\Absence; | |||
use OCA\DAV\Db\AbsenceMapper; | |||
use OCP\ICache; | |||
@@ -45,6 +46,7 @@ class AvailabilityCoordinatorTest extends TestCase { | |||
private IConfig|MockObject $config; | |||
private AbsenceMapper $absenceMapper; | |||
private LoggerInterface $logger; | |||
private MockObject|TimezoneService $timezoneService; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
@@ -54,6 +56,7 @@ class AvailabilityCoordinatorTest extends TestCase { | |||
$this->absenceMapper = $this->createMock(AbsenceMapper::class); | |||
$this->config = $this->createMock(IConfig::class); | |||
$this->logger = $this->createMock(LoggerInterface::class); | |||
$this->timezoneService = $this->createMock(TimezoneService::class); | |||
$this->cacheFactory->expects(self::once()) | |||
->method('createLocal') | |||
@@ -64,6 +67,7 @@ class AvailabilityCoordinatorTest extends TestCase { | |||
$this->absenceMapper, | |||
$this->config, | |||
$this->logger, | |||
$this->timezoneService, | |||
); | |||
} | |||
@@ -86,6 +90,7 @@ class AvailabilityCoordinatorTest extends TestCase { | |||
$absence->setLastDay('2023-10-08'); | |||
$absence->setStatus('Vacation'); | |||
$absence->setMessage('On vacation'); | |||
$this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
@@ -101,13 +106,13 @@ class AvailabilityCoordinatorTest extends TestCase { | |||
->willReturn($absence); | |||
$this->cache->expects(self::once()) | |||
->method('set') | |||
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}', 300); | |||
->with('user', '{"id":"420","startDate":1696111200,"endDate":1696802340,"shortMessage":"Vacation","message":"On vacation"}', 300); | |||
$expected = new OutOfOfficeData( | |||
'420', | |||
$user, | |||
1696118400, | |||
1696723200, | |||
1696111200, | |||
1696802340, | |||
'Vacation', | |||
'On vacation', | |||
); | |||
@@ -149,6 +154,7 @@ class AvailabilityCoordinatorTest extends TestCase { | |||
$absence->setLastDay('2023-10-08'); | |||
$absence->setStatus('Vacation'); | |||
$absence->setMessage('On vacation'); | |||
$this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUID') | |||
@@ -164,13 +170,13 @@ class AvailabilityCoordinatorTest extends TestCase { | |||
->willReturn($absence); | |||
$this->cache->expects(self::once()) | |||
->method('set') | |||
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}', 300); | |||
->with('user', '{"id":"420","startDate":1696111200,"endDate":1696802340,"shortMessage":"Vacation","message":"On vacation"}', 300); | |||
$expected = new OutOfOfficeData( | |||
'420', | |||
$user, | |||
1696118400, | |||
1696723200, | |||
1696111200, | |||
1696802340, | |||
'Vacation', | |||
'On vacation', | |||
); |