Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add birthday calendar configuration to groupware personal settings UI #33397

Closed
wants to merge 10 commits into from
1 change: 1 addition & 0 deletions apps/dav/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<settings>
<admin>OCA\DAV\Settings\CalDAVSettings</admin>
<personal>OCA\DAV\Settings\AvailabilitySettings</personal>
<personal>OCA\DAV\Settings\BirthdayCalendarSettings</personal>
</settings>

<activity>
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@
'OCA\\DAV\\ServerFactory' => $baseDir . '/../lib/ServerFactory.php',
'OCA\\DAV\\Service\\AbsenceService' => $baseDir . '/../lib/Service/AbsenceService.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\BirthdayCalendarSettings' => $baseDir . '/../lib/Settings/BirthdayCalendarSettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => $baseDir . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php',
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\ServerFactory' => __DIR__ . '/..' . '/../lib/ServerFactory.php',
'OCA\\DAV\\Service\\AbsenceService' => __DIR__ . '/..' . '/../lib/Service/AbsenceService.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\BirthdayCalendarSettings' => __DIR__ . '/..' . '/../lib/Settings/BirthdayCalendarSettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => __DIR__ . '/..' . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php',
Expand Down
39 changes: 32 additions & 7 deletions apps/dav/lib/Listener/UserPreferenceListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Joas Schilling <[email protected]>
* @copyright Copyright (c) 2022 Cédric Neukom <[email protected]>
*
* @author Joas Schilling <[email protected]>
*
Expand All @@ -24,6 +25,7 @@
*/
namespace OCA\DAV\Listener;

use DateInterval;
use OCA\DAV\BackgroundJob\UserStatusAutomation;
use OCP\BackgroundJob\IJobList;
use OCP\Config\BeforePreferenceDeletedEvent;
Expand All @@ -42,14 +44,37 @@ public function __construct(IJobList $jobList) {

public function handle(Event $event): void {
if ($event instanceof BeforePreferenceSetEvent) {
if ($event->getAppId() === 'dav' && $event->getConfigKey() === 'user_status_automation' && $event->getConfigValue() === 'yes') {
$event->setValid(true);
if ($event->getAppId() === 'dav') {
switch ($event->getConfigKey()) {
case 'user_status_automation':
if ($event->getConfigValue() === 'yes') {
$event->setValid(true);

// Not the cleanest way, but we just add the job in the before event.
// If something ever turns wrong the first execution will remove the job again.
// We also first delete the current job, so the next run time is reset.
$this->jobList->remove(UserStatusAutomation::class, ['userId' => $event->getUserId()]);
$this->jobList->add(UserStatusAutomation::class, ['userId' => $event->getUserId()]);
}
break;

// Not the cleanest way, but we just add the job in the before event.
// If something ever turns wrong the first execution will remove the job again.
// We also first delete the current job, so the next run time is reset.
$this->jobList->remove(UserStatusAutomation::class, ['userId' => $event->getUserId()]);
$this->jobList->add(UserStatusAutomation::class, ['userId' => $event->getUserId()]);
case 'birthdayCalendarReminderOffset':
$configValue = $event->getConfigValue();
if (empty($configValue)) {
$event->setValid(true);
break;
}
if ($configValue[0] === '-') {
$configValue = substr($configValue, 1);
}
try {
new DateInterval($configValue);
$event->setValid(true);
} catch (\Exception $e) {
// Client attempted with an invalid duration string, do nothing
}
break;
}
}
} elseif ($event instanceof BeforePreferenceDeletedEvent) {
if ($event->getAppId() === 'dav' && $event->getConfigKey() === 'user_status_automation') {
Expand Down
77 changes: 77 additions & 0 deletions apps/dav/lib/Settings/BirthdayCalendarSettings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
/*
* @copyright 2022 Cédric Neukom <[email protected]>
*
* @author 2022 Cédric Neukom <[email protected]>
*
* @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);

namespace OCA\DAV\Settings;

use OCA\DAV\AppInfo\Application;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\Settings\ISettings;

class BirthdayCalendarSettings implements ISettings {
protected IConfig $config;
protected IInitialState $initialState;
protected ?string $userId;

public function __construct(IConfig $config,
IInitialState $initialState,
?string $userId) {
$this->config = $config;
$this->initialState = $initialState;
$this->userId = $userId;
}

public function getForm(): TemplateResponse {
$this->initialState->provideInitialState(
cneukom marked this conversation as resolved.
Show resolved Hide resolved
'userBirthdayCalendarEnabled',
$this->config->getUserValue(
$this->userId,
'dav',
'generateBirthdayCalendar',
'no'
) === 'yes'
);

$this->initialState->provideInitialState(
'userBirthdayCalendarReminderOffset',
$this->config->getUserValue(
$this->userId,
'dav',
'birthdayCalendarReminderOffset',
'PT9H'
),
);

return new TemplateResponse(Application::APP_ID, 'settings-personal-birthday-calendar');
}

public function getSection(): string {
return 'groupware';
}

public function getPriority(): int {
return 20;
}
}
68 changes: 68 additions & 0 deletions apps/dav/src/service/BirthdayCalendarService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @copyright 2022 Cédric Neukom <[email protected]>
*
* @author 2022 Cédric Neukom <[email protected]>
*
* @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 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/>.
*/
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { getClient } from '../dav/client'

const CALDAV_BIRTHDAY_CALENDAR = 'contact_birthdays'

/**
* Disable birthday calendar
*
* @returns {Promise<void>}
*/
export async function disableBirthdayCalendar() {
const client = getClient('calendars')
await client.customRequest(CALDAV_BIRTHDAY_CALENDAR, {
method: 'DELETE',
})
}

/**
* Enable birthday calendar
*
* @returns {Promise<void>}
*/
export async function enableBirthdayCalendar() {
const client = getClient('calendars')
await client.customRequest('', {
method: 'POST',
data: '<x3:enable-birthday-calendar xmlns:x3="http://nextcloud.com/ns"/>',
})
}

/**
* Save birthday reminder offset. Value must be a duration (e.g. -PT15H)
*
* @param reminderOffset
* @returns {Promise<AxiosResponse<any>>}
*/
export async function saveBirthdayReminder(reminderOffset) {
return await axios.post(
generateOcsUrl('/apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', {
appId: 'dav',
configKey: 'birthdayCalendarReminderOffset',
}),
{
configValue: reminderOffset,
}
)
}
9 changes: 9 additions & 0 deletions apps/dav/src/settings-personal-birthday-calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Vue from 'vue'
import { translate } from '@nextcloud/l10n'
import BirthdayCalendarSettings from './views/BirthdayCalendarSettings'

Vue.prototype.$t = translate

const View = Vue.extend(BirthdayCalendarSettings);

(new View({})).$mount('#settings-personal-birthday-calendar')
137 changes: 137 additions & 0 deletions apps/dav/src/views/BirthdayCalendarSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<!--
- @copyright Copyright (c) 2022 Cédric Neukom <[email protected]>
- @author Cédric Neukom <[email protected]>
-
- @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/>.
-
-->
<template>
<SettingsSection :title="$t('dav', 'Birthday Calendar')">
<CheckboxRadioSwitch :checked.sync="enableBirthdayCalendar">
{{ $t('dav', 'Enable birthday calendar') }}
</CheckboxRadioSwitch>

<div class="select-container">
<label for="birthdayReminder">
{{ $t('dav', 'Birthday reminder:') }}
</label>
<span class="time-zone-text">
<Multiselect v-model="birthdayReminder"
:options="birthdayReminderOptions"
:disabled="!enableBirthdayCalendar"
id="birthdayReminder"></Multiselect>
</span>
</div>

<Button :disabled="saving"
type="primary"
@click="save">
{{ $t('dav', 'Save') }}
</Button>
</SettingsSection>
</template>

<script>
import {
showError,
showSuccess,
} from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import Button from '@nextcloud/vue/dist/Components/Button'
import CheckboxRadioSwitch
from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch'
import SettingsSection from '@nextcloud/vue/dist/Components/SettingsSection'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import {
disableBirthdayCalendar,
enableBirthdayCalendar,
saveBirthdayReminder
} from '../service/BirthdayCalendarService'

export default {
name: 'BirthdayCalendarSettings',
components: {
Button,
CheckboxRadioSwitch,
SettingsSection,
Multiselect,
},
data() {
const birthdayReminderValues = [
'',
'PT9H',
'-PT15H',
'-P1DT15H',
'-P6DT15H',
]

const birthdayReminderOptions = [
t('dav', 'None'),
t('dav', 'Same day (9 AM)'),
t('dav', '1 day before (9 AM)'),
t('dav', '2 days before (9 AM)'),
t('dav', '1 week before (9 AM)'),
]

const initialBirthdayCalendarEnabled = loadState('dav', 'userBirthdayCalendarEnabled')
const initialBirthdayCalendarReminderOffset = loadState('dav', 'userBirthdayCalendarReminderOffset')

return {
saving: false,
isBirthdayCalendarEnabled: initialBirthdayCalendarEnabled,
enableBirthdayCalendar: initialBirthdayCalendarEnabled,
birthdayReminder: birthdayReminderOptions[birthdayReminderValues.indexOf(initialBirthdayCalendarReminderOffset)],
birthdayReminderOptions,
birthdayReminderValues,
}
},
methods: {
async save() {
try {
this.saving = true

await saveBirthdayReminder(this.birthdayReminderValues[this.birthdayReminderOptions.indexOf(this.birthdayReminder)])

if (this.isBirthdayCalendarEnabled && !this.enableBirthdayCalendar) {
await disableBirthdayCalendar()
} else if (!this.isBirthdayCalendarEnabled && this.enableBirthdayCalendar) {
await enableBirthdayCalendar()
}
this.isBirthdayCalendarEnabled = this.enableBirthdayCalendar

showSuccess(t('dav', 'Saved birthday calendar settings'))
} catch (e) {
console.error('could birthday calendar settings', e)

showError(t('dav', 'Failed to save birthday calendar settings'))
} finally {
this.saving = false
}
},
},
}
</script>

<style lang="scss">
.select-container {
padding: 12px 12px 12px 0;

> label {
padding-right: 22px;
font-weight: bold;
}
}
</style>
5 changes: 5 additions & 0 deletions apps/dav/templates/settings-personal-birthday-calendar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
\OCP\Util::addScript('dav', 'settings-personal-birthday-calendar');
?>

<div id="settings-personal-birthday-calendar"></div>
Loading
Loading