mirror of
https://github.com/nextcloud/server.git
synced 2026-06-07 07:43:18 -04:00
feat(dav): dispatch out-of-office started and ended events
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
parent
9f9abd1a57
commit
d593206a30
20 changed files with 920 additions and 46 deletions
|
|
@ -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',
|
||||
|
|
|
|||
92
apps/dav/lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php
Normal file
92
apps/dav/lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php
Normal file
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
98
apps/dav/lib/CalDAV/TimezoneService.php
Normal file
98
apps/dav/lib/CalDAV/TimezoneService.php
Normal file
|
|
@ -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
|
||||
|
|
|
|||
53
apps/dav/lib/Db/Property.php
Normal file
53
apps/dav/lib/Db/Property.php
Normal file
|
|
@ -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;
|
||||
|
||||
}
|
||||
57
apps/dav/lib/Db/PropertyMapper.php
Normal file
57
apps/dav/lib/Db/PropertyMapper.php
Normal file
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
apps/dav/tests/integration/Db/PropertyMapperTest.php
Normal file
55
apps/dav/tests/integration/Db/PropertyMapperTest.php
Normal file
|
|
@ -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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
161
apps/dav/tests/unit/CalDAV/TimezoneServiceTest.php
Normal file
161
apps/dav/tests/unit/CalDAV/TimezoneServiceTest.php
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
51
lib/public/User/Events/OutOfOfficeEndedEvent.php
Normal file
51
lib/public/User/Events/OutOfOfficeEndedEvent.php
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
51
lib/public/User/Events/OutOfOfficeStartedEvent.php
Normal file
51
lib/public/User/Events/OutOfOfficeStartedEvent.php
Normal file
|
|
@ -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',
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue