mirror of https://github.com/nextcloud/calendar
Revert "Revert "Create Talk rooms for appointments""
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
parent
2ec5e44395
commit
faa1c72e23
|
@ -15,7 +15,7 @@
|
|||
* ☑️ Tasks! See tasks with a due date directly in the calendar
|
||||
* 🙈 **We’re not reinventing the wheel!** Based on the great [c-dav library](https://github.com/nextcloud/cdav-library), [ical.js](https://github.com/mozilla-comm/ical.js) and [fullcalendar](https://github.com/fullcalendar/fullcalendar) libraries.
|
||||
]]></description>
|
||||
<version>4.5.0-beta.1</version>
|
||||
<version>4.5.0-beta.2</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Anna Larch</author>
|
||||
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>
|
||||
|
|
|
@ -24,6 +24,8 @@ declare(strict_types=1);
|
|||
namespace OCA\Calendar\AppInfo;
|
||||
|
||||
use OCA\Calendar\Dashboard\CalendarWidget;
|
||||
use OCA\Calendar\Events\BeforeAppointmentBookedEvent;
|
||||
use OCA\Calendar\Listener\AppointmentBookedListener;
|
||||
use OCA\Calendar\Listener\UserDeletedListener;
|
||||
use OCA\Calendar\Notification\Notifier;
|
||||
use OCA\Calendar\Profile\AppointmentsAction;
|
||||
|
@ -56,6 +58,7 @@ class Application extends App implements IBootstrap {
|
|||
$context->registerProfileLinkAction(AppointmentsAction::class);
|
||||
}
|
||||
|
||||
$context->registerEventListener(BeforeAppointmentBookedEvent::class, AppointmentBookedListener::class);
|
||||
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
|
||||
|
||||
$context->registerNotifierService(Notifier::class);
|
||||
|
|
|
@ -164,7 +164,8 @@ class AppointmentConfigController extends Controller {
|
|||
?array $calendarFreeBusyUris = null,
|
||||
?int $start = null,
|
||||
?int $end = null,
|
||||
?int $futureLimit = null): JsonResponse {
|
||||
?int $futureLimit = null,
|
||||
?bool $createTalkRoom = false): JsonResponse {
|
||||
if ($this->userId === null) {
|
||||
return JsonResponse::fail();
|
||||
}
|
||||
|
@ -192,7 +193,8 @@ class AppointmentConfigController extends Controller {
|
|||
$calendarFreeBusyUris,
|
||||
$start,
|
||||
$end,
|
||||
$futureLimit
|
||||
$futureLimit,
|
||||
$createTalkRoom
|
||||
);
|
||||
return JsonResponse::success($appointmentConfig);
|
||||
} catch (ServiceException $e) {
|
||||
|
@ -240,7 +242,8 @@ class AppointmentConfigController extends Controller {
|
|||
?array $calendarFreeBusyUris = null,
|
||||
?int $start = null,
|
||||
?int $end = null,
|
||||
?int $futureLimit = null): JsonResponse {
|
||||
?int $futureLimit = null,
|
||||
?bool $createTalkRoom = false): JsonResponse {
|
||||
if ($this->userId === null) {
|
||||
return JsonResponse::fail(null, Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
@ -274,6 +277,7 @@ class AppointmentConfigController extends Controller {
|
|||
$appointmentConfig->setStart($start);
|
||||
$appointmentConfig->setEnd($end);
|
||||
$appointmentConfig->setFutureLimit($futureLimit);
|
||||
$appointmentConfig->setCreateTalkRoom($createTalkRoom === true);
|
||||
|
||||
try {
|
||||
$appointmentConfig = $this->appointmentConfigService->update($appointmentConfig);
|
||||
|
|
|
@ -71,6 +71,8 @@ use function json_encode;
|
|||
* @method void setDailyMax(?int $max)
|
||||
* @method int|null getFutureLimit()
|
||||
* @method void setFutureLimit(?int $limit)
|
||||
* @method int|null getCreateTalkRoom()
|
||||
* @method void setCreateTalkRoom(bool $create)
|
||||
*/
|
||||
class AppointmentConfig extends Entity implements JsonSerializable {
|
||||
/** @var string */
|
||||
|
@ -127,6 +129,9 @@ class AppointmentConfig extends Entity implements JsonSerializable {
|
|||
/** @var int|null */
|
||||
protected $futureLimit;
|
||||
|
||||
/** @var bool */
|
||||
protected $createTalkRoom;
|
||||
|
||||
/** @var string */
|
||||
public const VISIBILITY_PUBLIC = 'PUBLIC';
|
||||
|
||||
|
@ -143,6 +148,7 @@ class AppointmentConfig extends Entity implements JsonSerializable {
|
|||
$this->addType('timeBeforeNextSlot', 'int');
|
||||
$this->addType('dailyMax', 'int');
|
||||
$this->addType('futureLimit', 'int');
|
||||
$this->addType('createTalkRoom', 'boolean');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,7 +211,8 @@ class AppointmentConfig extends Entity implements JsonSerializable {
|
|||
'totalLength' => $this->getTotalLength(),
|
||||
'timeBeforeNextSlot' => $this->getTimeBeforeNextSlot(),
|
||||
'dailyMax' => $this->getDailyMax(),
|
||||
'futureLimit' => $this->getFutureLimit()
|
||||
'futureLimit' => $this->getFutureLimit(),
|
||||
'createTalkRoom' => $this->getCreateTalkRoom(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class AppointmentConfigMapper extends QBMapper {
|
|||
*/
|
||||
public function findByIdForUser(int $id, string $userId) : AppointmentConfig {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'token', 'name', 'description', 'location', 'visibility', 'user_id', 'target_calendar_uri', 'calendar_freebusy_uris', 'availability', 'start', 'end', 'length', 'increment', 'preparation_duration', 'followup_duration', 'time_before_next_slot', 'daily_max', 'future_limit')
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
|
||||
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR));
|
||||
|
@ -64,7 +64,7 @@ class AppointmentConfigMapper extends QBMapper {
|
|||
*/
|
||||
public function findById(int $id) : AppointmentConfig {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'token', 'name', 'description', 'location', 'visibility', 'user_id', 'target_calendar_uri', 'calendar_freebusy_uris', 'availability', 'start', 'end', 'length', 'increment', 'preparation_duration', 'followup_duration', 'time_before_next_slot', 'daily_max', 'future_limit')
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
|
||||
return $this->findEntity($qb);
|
||||
|
@ -92,7 +92,7 @@ class AppointmentConfigMapper extends QBMapper {
|
|||
*/
|
||||
public function findAllForUser(string $userId, string $visibility = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'token', 'name', 'description', 'location', 'visibility', 'user_id', 'target_calendar_uri', 'calendar_freebusy_uris', 'availability', 'start', 'end', 'length', 'increment', 'preparation_duration', 'followup_duration', 'time_before_next_slot', 'daily_max', 'future_limit')
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR));
|
||||
if ($visibility !== null) {
|
||||
|
|
|
@ -85,6 +85,13 @@ class Booking extends Entity implements JsonSerializable {
|
|||
/** @var bool */
|
||||
protected $confirmed;
|
||||
|
||||
/**
|
||||
* Transient talk URL
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $talkUrl;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id', 'integer');
|
||||
$this->addType('apptConfigId', 'integer');
|
||||
|
@ -110,4 +117,12 @@ class Booking extends Entity implements JsonSerializable {
|
|||
'confirmed' => $this->isConfirmed(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTalkUrl(): ?string {
|
||||
return $this->talkUrl;
|
||||
}
|
||||
|
||||
public function setTalkUrl(string $talkUrl): void {
|
||||
$this->talkUrl = $talkUrl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2022 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\Calendar\Events;
|
||||
|
||||
use OCA\Calendar\Db\AppointmentConfig;
|
||||
use OCA\Calendar\Db\Booking;
|
||||
use OCP\EventDispatcher\Event;
|
||||
|
||||
class BeforeAppointmentBookedEvent extends Event {
|
||||
|
||||
/** @var Booking */
|
||||
private $booking;
|
||||
/** @var AppointmentConfig */
|
||||
|
||||
private $config;
|
||||
|
||||
public function __construct(Booking $booking, AppointmentConfig $config) {
|
||||
parent::__construct();
|
||||
|
||||
$this->booking = $booking;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getBooking(): Booking {
|
||||
return $this->booking;
|
||||
}
|
||||
|
||||
public function getConfig(): AppointmentConfig {
|
||||
return $this->config;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Calendar\Listener;
|
||||
|
||||
use OCA\Calendar\Events\AppointmentBookedEvent;
|
||||
use OCA\Calendar\Events\BeforeAppointmentBookedEvent;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\IL10N;
|
||||
|
@ -57,7 +57,7 @@ class AppointmentBookedListener implements IEventListener {
|
|||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if (!($event instanceof AppointmentBookedEvent)) {
|
||||
if (!($event instanceof BeforeAppointmentBookedEvent)) {
|
||||
// Don't care
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2022 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\Calendar\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version4050Date20230614163505 extends SimpleMigrationStep {
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$table = $schema->getTable('calendar_appt_configs');
|
||||
if (!$table->hasColumn('create_talk_room')) {
|
||||
$table->addColumn('create_talk_room', Types::BOOLEAN, [
|
||||
'notnull' => false,
|
||||
'default' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
|
@ -142,24 +142,6 @@ class AppointmentConfigService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $description
|
||||
* @param string|null $location
|
||||
* @param string $visibility
|
||||
* @param string $userId
|
||||
* @param string $targetCalendarUri
|
||||
* @param array $availability
|
||||
* @param int $length
|
||||
* @param int $increment
|
||||
* @param int $preparationDuration
|
||||
* @param int $followupDuration
|
||||
* @param int $buffer
|
||||
* @param int|null $dailyMax
|
||||
* @param string[] $calendarFreeBusyUris
|
||||
* @param int|null $start
|
||||
* @param int|null $end
|
||||
* @param int|null $futureLimit
|
||||
* @return AppointmentConfig
|
||||
* @throws ServiceException
|
||||
*/
|
||||
public function create(string $name,
|
||||
|
@ -178,7 +160,8 @@ class AppointmentConfigService {
|
|||
?array $calendarFreeBusyUris = [],
|
||||
?int $start = null,
|
||||
?int $end = null,
|
||||
?int $futureLimit = null): AppointmentConfig {
|
||||
?int $futureLimit = null,
|
||||
?bool $createTalkRoom = false): AppointmentConfig {
|
||||
try {
|
||||
$appointmentConfig = new AppointmentConfig();
|
||||
$appointmentConfig->setToken($this->random->generate(12, ISecureRandom::CHAR_HUMAN_READABLE));
|
||||
|
@ -199,6 +182,7 @@ class AppointmentConfigService {
|
|||
$appointmentConfig->setStart($start);
|
||||
$appointmentConfig->setEnd($end);
|
||||
$appointmentConfig->setFutureLimit($futureLimit);
|
||||
$appointmentConfig->setCreateTalkRoom($createTalkRoom === true);
|
||||
|
||||
return $this->mapper->insert($appointmentConfig);
|
||||
} catch (DbException $e) {
|
||||
|
|
|
@ -93,7 +93,12 @@ class BookingCalendarWriter {
|
|||
* @return string
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function write(AppointmentConfig $config, DateTimeImmutable $start, string $displayName, string $email, ?string $description = null) : string {
|
||||
public function write(AppointmentConfig $config,
|
||||
DateTimeImmutable $start,
|
||||
string $displayName,
|
||||
string $email,
|
||||
?string $description = null,
|
||||
?string $location = null) : string {
|
||||
$calendar = current($this->manager->getCalendarsForPrincipal($config->getPrincipalUri(), [$config->getTargetCalendarUri()]));
|
||||
if (!($calendar instanceof ICreateFromString)) {
|
||||
throw new RuntimeException('Could not find a public writable calendar for this principal');
|
||||
|
@ -166,6 +171,7 @@ class BookingCalendarWriter {
|
|||
$vcalendar->VEVENT->add($alarm);
|
||||
}
|
||||
|
||||
|
||||
if ($config->getLocation() !== null) {
|
||||
$vcalendar->VEVENT->add('LOCATION', $config->getLocation());
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ use InvalidArgumentException;
|
|||
use OCA\Calendar\Db\AppointmentConfig;
|
||||
use OCA\Calendar\Db\Booking;
|
||||
use OCA\Calendar\Db\BookingMapper;
|
||||
use OCA\Calendar\Events\BeforeAppointmentBookedEvent;
|
||||
use OCA\Calendar\Exception\ClientException;
|
||||
use OCA\Calendar\Exception\NoSlotFoundException;
|
||||
use OCA\Calendar\Exception\ServiceException;
|
||||
|
@ -39,6 +40,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
|
|||
use OCP\AppFramework\Http;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\DB\Exception as DbException;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUser;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
@ -72,6 +74,9 @@ class BookingService {
|
|||
/** @var MailService */
|
||||
private $mailService;
|
||||
|
||||
/** @var IEventDispatcher */
|
||||
private $eventDispatcher;
|
||||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
|
@ -83,6 +88,7 @@ class BookingService {
|
|||
BookingCalendarWriter $calendarWriter,
|
||||
ISecureRandom $random,
|
||||
MailService $mailService,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
LoggerInterface $logger) {
|
||||
$this->availabilityGenerator = $availabilityGenerator;
|
||||
$this->extrapolator = $extrapolator;
|
||||
|
@ -92,6 +98,7 @@ class BookingService {
|
|||
$this->bookingMapper = $bookingMapper;
|
||||
$this->random = $random;
|
||||
$this->mailService = $mailService;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
@ -111,10 +118,26 @@ class BookingService {
|
|||
throw new ClientException('Could not make sense of booking times');
|
||||
}
|
||||
|
||||
$calendar = $this->calendarWriter->write($config, $startObj, $booking->getDisplayName(), $booking->getEmail(), $booking->getDescription());
|
||||
// TODO: inject broker here and remove indirection
|
||||
$this->eventDispatcher->dispatchTyped(
|
||||
new BeforeAppointmentBookedEvent(
|
||||
$booking,
|
||||
$config,
|
||||
)
|
||||
);
|
||||
|
||||
$calendar = $this->calendarWriter->write(
|
||||
$config,
|
||||
$startObj,
|
||||
$booking->getDisplayName(),
|
||||
$booking->getEmail(),
|
||||
$booking->getDescription(),
|
||||
$config->getCreateTalkRoom() ? $booking->getTalkUrl() : $config->getLocation(),
|
||||
);
|
||||
$booking->setConfirmed(true);
|
||||
$this->bookingMapper->update($booking);
|
||||
|
||||
|
||||
try {
|
||||
$this->mailService->sendBookingInformationEmail($booking, $config, $calendar);
|
||||
$this->mailService->sendOrganizerBookingInformationEmail($booking, $config, $calendar);
|
||||
|
|
|
@ -41,6 +41,7 @@ use OCP\Mail\IEMailTemplate;
|
|||
use OCP\Mail\IMailer;
|
||||
use OCP\Notification\IManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
|
||||
class MailService {
|
||||
|
@ -128,7 +129,7 @@ class MailService {
|
|||
}
|
||||
|
||||
// Create Booking overview
|
||||
$this->addBulletList($template, $this->l10n, $booking, $config->getLocation());
|
||||
$this->addBulletList($template, $this->l10n, $booking, $config);
|
||||
|
||||
$bodyText = $this->l10n->t('This confirmation link expires in %s hours.', [(BookingService::EXPIRY / 3600)]);
|
||||
$template->addBodyText($bodyText);
|
||||
|
@ -198,7 +199,7 @@ class MailService {
|
|||
}
|
||||
|
||||
// Create Booking overview
|
||||
$this->addBulletList($template, $this->l10n, $booking, $config->getLocation());
|
||||
$this->addBulletList($template, $this->l10n, $booking, $config);
|
||||
|
||||
$bodyText = $this->l10n->t('If you wish to cancel the appointment after all, please contact your organizer by replying to this email or by visiting their profile page.');
|
||||
$template->addBodyText($bodyText);
|
||||
|
@ -228,7 +229,7 @@ class MailService {
|
|||
private function addBulletList(IEMailTemplate $template,
|
||||
IL10N $l10n,
|
||||
Booking $booking,
|
||||
?string $location = null):void {
|
||||
AppointmentConfig $config):void {
|
||||
$template->addBodyListItem($booking->getDisplayName(), $l10n->t('Appointment for:'));
|
||||
|
||||
$l = $this->lFactory->findGenericLanguage();
|
||||
|
@ -242,8 +243,19 @@ class MailService {
|
|||
|
||||
$template->addBodyListItem($relativeDateTime, $l10n->t('Date:'));
|
||||
|
||||
if (!empty($location)) {
|
||||
$template->addBodyListItem($location, $l10n->t('Where:'));
|
||||
if (!$booking->isConfirmed() && $config->getCreateTalkRoom()) {
|
||||
$template->addBodyListItem($l10n->t('You will receive a link with the confirmation email'), $l10n->t('Where:'));
|
||||
} elseif (!$booking->isConfirmed() && !empty($config->getLocation())) {
|
||||
$template->addBodyListItem($config->getLocation(), $l10n->t('Where:'));
|
||||
} elseif ($booking->isConfirmed() && $booking->getTalkUrl() !== null) {
|
||||
$template->addBodyListItem(
|
||||
'<a href="' . htmlspecialchars($booking->getTalkUrl()) . '">' . $booking->getTalkUrl() . '</a>',
|
||||
$l10n->t('Where:'),
|
||||
'',
|
||||
$booking->getTalkUrl(),
|
||||
);
|
||||
} elseif ($booking->isConfirmed() && !empty($config->getLocation())) {
|
||||
$template->addBodyListItem($config->getLocation(), $l10n->t('Where:'));
|
||||
}
|
||||
|
||||
if (!empty($booking->getDescription())) {
|
||||
|
@ -296,7 +308,7 @@ class MailService {
|
|||
}
|
||||
|
||||
// Create Booking overview
|
||||
$this->addBulletList($template, $this->l10n, $booking, $config->getLocation());
|
||||
$this->addBulletList($template, $this->l10n, $booking, $config);
|
||||
$template->addFooter();
|
||||
|
||||
$attachment = $this->mailer->createAttachment($calendar, 'appointment.ics', 'text/calendar');
|
||||
|
|
|
@ -38,7 +38,14 @@
|
|||
:value.sync="editing.name" />
|
||||
<TextInput class="appointment-config-modal__form__row"
|
||||
:label="t('calendar', 'Location')"
|
||||
:value.sync="editing.location" />
|
||||
:value.sync="editing.location"
|
||||
:disabled="isTalkEnabled && editing.createTalkRoom" />
|
||||
<div v-if="isTalkEnabled" class="appointment-config-modal__form__row">
|
||||
<NcCheckboxRadioSwitch :checked.sync="editing.createTalkRoom">
|
||||
{{ t('calendar', 'Create a Talk room') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<span class="appointment-config-modal__talk-room-description">{{ t('calendar', 'A unique link will be generated for every booked appointment and sent via the confirmation email') }}</span>
|
||||
</div>
|
||||
<TextArea class="appointment-config-modal__form__row"
|
||||
:label="t('calendar', 'Description')"
|
||||
:value.sync="editing.description" />
|
||||
|
@ -141,6 +148,7 @@
|
|||
<script>
|
||||
import { CalendarAvailability } from '@nextcloud/calendar-availability-vue'
|
||||
import Modal from '@nextcloud/vue/dist/Components/NcModal.js'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
|
||||
import TextInput from './AppointmentConfigModal/TextInput.vue'
|
||||
import TextArea from './AppointmentConfigModal/TextArea.vue'
|
||||
import AppointmentConfig from '../models/appointmentConfig.js'
|
||||
|
@ -170,6 +178,7 @@ export default {
|
|||
VisibilitySelect,
|
||||
Confirmation,
|
||||
NcButton,
|
||||
NcCheckboxRadioSwitch,
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
|
@ -193,6 +202,7 @@ export default {
|
|||
computed: {
|
||||
...mapGetters([
|
||||
'ownSortedCalendars',
|
||||
'isTalkEnabled',
|
||||
]),
|
||||
formTitle() {
|
||||
if (this.isNew) {
|
||||
|
@ -252,6 +262,10 @@ export default {
|
|||
this.enableFutureLimit = !!this.editing.futureLimit
|
||||
|
||||
this.showConfirmation = false
|
||||
// Disable Talk integration if Talk is no longer available
|
||||
if (!this.isTalkEnabled) {
|
||||
this.editing.createTalkRoom = false
|
||||
}
|
||||
},
|
||||
calendarUrlToUri(url) {
|
||||
// Trim trailing slash and split into URL parts
|
||||
|
@ -301,3 +315,11 @@ export default {
|
|||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.appointment-config-modal {
|
||||
&__talk-room-description {
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -56,8 +56,8 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: [Number, null, undefined],
|
||||
required: true,
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
defaultValue: {
|
||||
type: Number,
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
<input :id="id"
|
||||
type="text"
|
||||
:value="value"
|
||||
:disabled="disabled"
|
||||
@input="change">
|
||||
</div>
|
||||
</template>
|
||||
|
@ -44,6 +45,10 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -77,6 +77,9 @@ export default class AppointmentConfig {
|
|||
/** @member {?string[]} */
|
||||
calendarFreeBusyUris
|
||||
|
||||
/** @member {bool} */
|
||||
createTalkRoom
|
||||
|
||||
/**
|
||||
* Create a new AppointmentConfig from the given plain object data
|
||||
*
|
||||
|
@ -97,6 +100,7 @@ export default class AppointmentConfig {
|
|||
* @param {?number} data.dailyMax Max daily slots
|
||||
* @param {?number} data.futureLimit Limits how far in the future appointments can be booked
|
||||
* @param {?string[]} data.calendarFreeBusyUris URIs of calendars to check for conflicts
|
||||
* @param {bool} data.createTalkRoom Whether a Talk room should be created
|
||||
*/
|
||||
constructor(data) {
|
||||
data ??= {}
|
||||
|
@ -116,6 +120,7 @@ export default class AppointmentConfig {
|
|||
this.dailyMax = tryParseInt(data.dailyMax)
|
||||
this.futureLimit = tryParseInt(data.futureLimit)
|
||||
this.calendarFreeBusyUris = data.calendarFreeBusyUris
|
||||
this.createTalkRoom = data.createTalkRoom
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -227,6 +227,8 @@ Initial settings:
|
|||
|
||||
const getters = {
|
||||
|
||||
isTalkEnabled: (state) => state.talkEnabled,
|
||||
|
||||
/**
|
||||
* Gets the resolved timezone.
|
||||
* If the timezone is set to automatic, it returns the user's current timezone
|
||||
|
|
|
@ -42,6 +42,7 @@ use OCA\Calendar\Service\Appointments\MailService;
|
|||
use OCA\Calendar\Service\Appointments\SlotExtrapolator;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\Calendar\ICalendarQuery;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUser;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
@ -73,6 +74,9 @@ class BookingServiceTest extends TestCase {
|
|||
/** @var MailService|MockObject */
|
||||
private $mailService;
|
||||
|
||||
/** @var IEventDispatcher|MockObject */
|
||||
private $eventDispatcher;
|
||||
|
||||
/** @var MockObject|LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
|
@ -94,7 +98,9 @@ class BookingServiceTest extends TestCase {
|
|||
$this->bookingCalendarWriter = $this->createMock(BookingCalendarWriter::class);
|
||||
$this->random = $this->createMock(ISecureRandom::class);
|
||||
$this->mailService = $this->createMock(MailService::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->service = new BookingService(
|
||||
$this->availabilityGenerator,
|
||||
$this->extrapolator,
|
||||
|
@ -104,6 +110,7 @@ class BookingServiceTest extends TestCase {
|
|||
$this->bookingCalendarWriter,
|
||||
$this->random,
|
||||
$this->mailService,
|
||||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue