mirror of
https://github.com/nextcloud/server.git
synced 2026-02-27 18:37:17 +01:00
feat(ldap): sync additional properties to profile and SAB
Synced from LDAP to profile: - Date of birth Synced from LDAP to SAB (via the profile): - Biography - Date of birth Original code by Jake Nabasny (GitHub: @slapcat) Co-authored-by: Jake Nabasny <jake@nabasny.com> Co-authored-by: Richard Steinmetz <richard@steinmetz.cloud> Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
committed by
Richard Steinmetz
parent
57a7f09a72
commit
f863290572
1
AUTHORS
1
AUTHORS
@@ -183,6 +183,7 @@
|
||||
- J0WI <J0WI@users.noreply.github.com>
|
||||
- Jaakko Salo <jaakkos@gmail.com>
|
||||
- Jacob Neplokh <me@jacobneplokh.com>
|
||||
- Jake Nabasny <jake@nabasny.com>
|
||||
- Jakob Sack <mail@jakobsack.de>
|
||||
- Jakub Onderka <ahoj@jakubonderka.cz>
|
||||
- James Guo <i@ze3kr.com>
|
||||
|
||||
@@ -7,14 +7,17 @@
|
||||
*/
|
||||
namespace OCA\DAV\CardDAV;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Exception;
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\IImage;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\VObject\Component\VCard;
|
||||
use Sabre\VObject\Property\Text;
|
||||
use Sabre\VObject\Property\VCard\Date;
|
||||
|
||||
class Converter {
|
||||
/** @var IURLGenerator */
|
||||
@@ -23,8 +26,12 @@ class Converter {
|
||||
private $accountManager;
|
||||
private IUserManager $userManager;
|
||||
|
||||
public function __construct(IAccountManager $accountManager,
|
||||
IUserManager $userManager, IURLGenerator $urlGenerator) {
|
||||
public function __construct(
|
||||
IAccountManager $accountManager,
|
||||
IUserManager $userManager,
|
||||
IURLGenerator $urlGenerator,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
$this->accountManager = $accountManager;
|
||||
$this->userManager = $userManager;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
@@ -114,6 +121,24 @@ class Converter {
|
||||
case IAccountManager::PROPERTY_ROLE:
|
||||
$vCard->add(new Text($vCard, 'TITLE', $property->getValue(), ['X-NC-SCOPE' => $scope]));
|
||||
break;
|
||||
case IAccountManager::PROPERTY_BIOGRAPHY:
|
||||
$vCard->add(new Text($vCard, 'NOTE', $property->getValue(), ['X-NC-SCOPE' => $scope]));
|
||||
break;
|
||||
case IAccountManager::PROPERTY_BIRTHDATE:
|
||||
try {
|
||||
$birthdate = new DateTimeImmutable($property->getValue());
|
||||
} catch (Exception $e) {
|
||||
// Invalid date -> just skip the property
|
||||
$this->logger->info("Failed to parse user's birthdate for the SAB: " . $property->getValue(), [
|
||||
'exception' => $e,
|
||||
'userId' => $user->getUID(),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
$dateProperty = new Date($vCard, 'BDAY', null, ['X-NC-SCOPE' => $scope]);
|
||||
$dateProperty->setDateTime($birthdate);
|
||||
$vCard->add($dateProperty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
class ConverterTest extends TestCase {
|
||||
@@ -30,12 +31,16 @@ class ConverterTest extends TestCase {
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
/** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $logger;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->accountManager = $this->createMock(IAccountManager::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +92,7 @@ class ConverterTest extends TestCase {
|
||||
$user = $this->getUserMock((string)$displayName, $eMailAddress, $cloudId);
|
||||
$accountManager = $this->getAccountManager($user);
|
||||
|
||||
$converter = new Converter($accountManager, $this->userManager, $this->urlGenerator);
|
||||
$converter = new Converter($accountManager, $this->userManager, $this->urlGenerator, $this->logger);
|
||||
$vCard = $converter->createCardFromUser($user);
|
||||
if ($expectedVCard !== null) {
|
||||
$this->assertInstanceOf('Sabre\VObject\Component\VCard', $vCard);
|
||||
@@ -108,7 +113,7 @@ class ConverterTest extends TestCase {
|
||||
->willReturn('Manager');
|
||||
$accountManager = $this->getAccountManager($user);
|
||||
|
||||
$converter = new Converter($accountManager, $this->userManager, $this->urlGenerator);
|
||||
$converter = new Converter($accountManager, $this->userManager, $this->urlGenerator, $this->logger);
|
||||
$vCard = $converter->createCardFromUser($user);
|
||||
|
||||
$this->compareData(
|
||||
@@ -196,7 +201,7 @@ class ConverterTest extends TestCase {
|
||||
* @param $fullName
|
||||
*/
|
||||
public function testNameSplitter($expected, $fullName): void {
|
||||
$converter = new Converter($this->accountManager, $this->userManager, $this->urlGenerator);
|
||||
$converter = new Converter($this->accountManager, $this->userManager, $this->urlGenerator, $this->logger);
|
||||
$r = $converter->splitFullName($fullName);
|
||||
$r = implode(';', $r);
|
||||
$this->assertEquals($expected, $r);
|
||||
|
||||
@@ -905,6 +905,7 @@ class UsersController extends AUserData {
|
||||
$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDATE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
|
||||
@@ -915,6 +916,7 @@ class UsersController extends AUserData {
|
||||
$permittedFields[] = IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDATE . self::SCOPE_SUFFIX;
|
||||
|
||||
$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
|
||||
|
||||
@@ -1085,6 +1087,7 @@ class UsersController extends AUserData {
|
||||
case IAccountManager::PROPERTY_ROLE:
|
||||
case IAccountManager::PROPERTY_HEADLINE:
|
||||
case IAccountManager::PROPERTY_BIOGRAPHY:
|
||||
case IAccountManager::PROPERTY_BIRTHDATE:
|
||||
$userAccount = $this->accountManager->getAccount($targetUser);
|
||||
try {
|
||||
$userProperty = $userAccount->getProperty($key);
|
||||
@@ -1131,6 +1134,7 @@ class UsersController extends AUserData {
|
||||
case IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX:
|
||||
case IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX:
|
||||
case IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX:
|
||||
case IAccountManager::PROPERTY_BIRTHDATE . self::SCOPE_SUFFIX:
|
||||
case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
|
||||
$propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
|
||||
$userAccount = $this->accountManager->getAccount($targetUser);
|
||||
|
||||
@@ -326,6 +326,8 @@ class UsersController extends Controller {
|
||||
* @param string|null $twitterScope
|
||||
* @param string|null $fediverse
|
||||
* @param string|null $fediverseScope
|
||||
* @param string|null $birthdate
|
||||
* @param string|null $birthdateScope
|
||||
*
|
||||
* @return DataResponse
|
||||
*/
|
||||
@@ -343,7 +345,9 @@ class UsersController extends Controller {
|
||||
?string $twitter = null,
|
||||
?string $twitterScope = null,
|
||||
?string $fediverse = null,
|
||||
?string $fediverseScope = null
|
||||
?string $fediverseScope = null,
|
||||
?string $birthdate = null,
|
||||
?string $birthdateScope = null,
|
||||
) {
|
||||
$user = $this->userSession->getUser();
|
||||
if (!$user instanceof IUser) {
|
||||
@@ -383,6 +387,7 @@ class UsersController extends Controller {
|
||||
IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
|
||||
IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
|
||||
IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
|
||||
IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
|
||||
];
|
||||
$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
|
||||
foreach ($updatable as $property => $data) {
|
||||
@@ -424,6 +429,8 @@ class UsersController extends Controller {
|
||||
'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
|
||||
'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
|
||||
'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
|
||||
'birthdate' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getValue(),
|
||||
'birthdateScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getScope(),
|
||||
'message' => $this->l10n->t('Settings saved'),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -167,6 +167,7 @@ class PersonalInfo implements ISettings {
|
||||
'role' => $this->getProperty($account, IAccountManager::PROPERTY_ROLE),
|
||||
'headline' => $this->getProperty($account, IAccountManager::PROPERTY_HEADLINE),
|
||||
'biography' => $this->getProperty($account, IAccountManager::PROPERTY_BIOGRAPHY),
|
||||
'birthdate' => $this->getProperty($account, IAccountManager::PROPERTY_BIRTHDATE),
|
||||
];
|
||||
|
||||
$accountParameters = [
|
||||
|
||||
137
apps/settings/src/components/PersonalInfo/BirthdaySection.vue
Normal file
137
apps/settings/src/components/PersonalInfo/BirthdaySection.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<HeaderBar :scope="birthdate.scope"
|
||||
:input-id="inputId"
|
||||
:readable="birthdate.readable" />
|
||||
|
||||
<template>
|
||||
<NcDateTimePickerNative :id="inputId"
|
||||
type="date"
|
||||
label=""
|
||||
:value="value"
|
||||
@input="onInput" />
|
||||
</template>
|
||||
|
||||
<p class="property__helper-text-message">
|
||||
{{ t('settings', 'Enter your date of birth') }}
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeaderBar from './shared/HeaderBar.vue'
|
||||
import AccountPropertySection from './shared/AccountPropertySection.vue'
|
||||
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
|
||||
import { NcDateTimePickerNative } from '@nextcloud/vue'
|
||||
import debounce from 'debounce'
|
||||
import { savePrimaryAccountProperty } from '../../service/PersonalInfo/PersonalInfoService'
|
||||
import { handleError } from '../../utils/handlers'
|
||||
import AlertCircle from 'vue-material-design-icons/AlertCircleOutline.vue'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
|
||||
const { birthdate } = loadState('settings', 'personalInfoParameters', {})
|
||||
|
||||
export default {
|
||||
name: 'BirthdaySection',
|
||||
|
||||
components: {
|
||||
AlertCircle,
|
||||
AccountPropertySection,
|
||||
NcDateTimePickerNative,
|
||||
HeaderBar,
|
||||
},
|
||||
|
||||
data() {
|
||||
let initialValue = null
|
||||
if (birthdate.value) {
|
||||
initialValue = new Date(birthdate.value)
|
||||
}
|
||||
|
||||
return {
|
||||
birthdate: {
|
||||
...birthdate,
|
||||
readable: NAME_READABLE_ENUM[birthdate.name],
|
||||
},
|
||||
initialValue,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
inputId() {
|
||||
return `account-property-${birthdate.name}`
|
||||
},
|
||||
value: {
|
||||
get() {
|
||||
return new Date(this.birthdate.value)
|
||||
},
|
||||
/** @param {Date} value */
|
||||
set(value) {
|
||||
const day = value.getDate().toString().padStart(2, '0')
|
||||
const month = (value.getMonth() + 1).toString().padStart(2, '0')
|
||||
const year = value.getFullYear()
|
||||
this.birthdate.value = `${year}-${month}-${day}`
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onInput(e) {
|
||||
this.value = e
|
||||
this.debouncePropertyChange(this.value)
|
||||
},
|
||||
|
||||
debouncePropertyChange: debounce(async function(value) {
|
||||
await this.updateProperty(value)
|
||||
}, 500),
|
||||
|
||||
async updateProperty(value) {
|
||||
try {
|
||||
const responseData = await savePrimaryAccountProperty(
|
||||
this.birthdate.name,
|
||||
value,
|
||||
)
|
||||
this.handleResponse({
|
||||
value,
|
||||
status: responseData.ocs?.meta?.status,
|
||||
})
|
||||
} catch (error) {
|
||||
this.handleResponse({
|
||||
errorMessage: t('settings', 'Unable to update date of birth'),
|
||||
error,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse({ value, status, errorMessage, error }) {
|
||||
if (status === 'ok') {
|
||||
this.initialValue = value
|
||||
} else {
|
||||
this.$emit('update:value', this.initialValue)
|
||||
handleError(error, errorMessage)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
section {
|
||||
padding: 10px 10px;
|
||||
|
||||
&::v-deep button:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.property__helper-text-message {
|
||||
color: var(--color-text-maxcontrast);
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -44,6 +44,7 @@ export const ACCOUNT_PROPERTY_ENUM = Object.freeze({
|
||||
ROLE: 'role',
|
||||
TWITTER: 'twitter',
|
||||
WEBSITE: 'website',
|
||||
BIRTHDATE: 'birthdate',
|
||||
})
|
||||
|
||||
/** Enum of account properties to human readable account property names */
|
||||
@@ -62,6 +63,7 @@ export const ACCOUNT_PROPERTY_READABLE_ENUM = Object.freeze({
|
||||
TWITTER: t('settings', 'X (formerly Twitter)'),
|
||||
FEDIVERSE: t('settings', 'Fediverse (e.g. Mastodon)'),
|
||||
WEBSITE: t('settings', 'Website'),
|
||||
BIRTHDATE: t('settings', 'Date of birth'),
|
||||
})
|
||||
|
||||
export const NAME_READABLE_ENUM = Object.freeze({
|
||||
@@ -79,6 +81,7 @@ export const NAME_READABLE_ENUM = Object.freeze({
|
||||
[ACCOUNT_PROPERTY_ENUM.TWITTER]: ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER,
|
||||
[ACCOUNT_PROPERTY_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE,
|
||||
[ACCOUNT_PROPERTY_ENUM.WEBSITE]: ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE,
|
||||
[ACCOUNT_PROPERTY_ENUM.BIRTHDATE]: ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE,
|
||||
})
|
||||
|
||||
/** Enum of profile specific sections to human readable names */
|
||||
@@ -102,6 +105,7 @@ export const PROPERTY_READABLE_KEYS_ENUM = Object.freeze({
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER]: ACCOUNT_PROPERTY_ENUM.TWITTER,
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_ENUM.FEDIVERSE,
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: ACCOUNT_PROPERTY_ENUM.WEBSITE,
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE]: ACCOUNT_PROPERTY_ENUM.BIRTHDATE,
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -144,6 +148,7 @@ export const PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM = Object.freeze({
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
|
||||
[ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
|
||||
})
|
||||
|
||||
/** List of readable account properties which aren't published to the lookup server */
|
||||
@@ -152,6 +157,7 @@ export const UNPUBLISHED_READABLE_PROPERTIES = Object.freeze([
|
||||
ACCOUNT_PROPERTY_READABLE_ENUM.HEADLINE,
|
||||
ACCOUNT_PROPERTY_READABLE_ENUM.ORGANISATION,
|
||||
ACCOUNT_PROPERTY_READABLE_ENUM.ROLE,
|
||||
ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE,
|
||||
])
|
||||
|
||||
/** Scope suffix */
|
||||
|
||||
@@ -42,6 +42,7 @@ import RoleSection from './components/PersonalInfo/RoleSection.vue'
|
||||
import HeadlineSection from './components/PersonalInfo/HeadlineSection.vue'
|
||||
import BiographySection from './components/PersonalInfo/BiographySection.vue'
|
||||
import ProfileVisibilitySection from './components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue'
|
||||
import BirthdaySection from './components/PersonalInfo/BirthdaySection.vue'
|
||||
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
|
||||
@@ -64,6 +65,7 @@ const TwitterView = Vue.extend(TwitterSection)
|
||||
const FediverseView = Vue.extend(FediverseSection)
|
||||
const LanguageView = Vue.extend(LanguageSection)
|
||||
const LocaleView = Vue.extend(LocaleSection)
|
||||
const BirthdayView = Vue.extend(BirthdaySection)
|
||||
|
||||
new AvatarView().$mount('#vue-avatar-section')
|
||||
new DetailsView().$mount('#vue-details-section')
|
||||
@@ -76,6 +78,7 @@ new TwitterView().$mount('#vue-twitter-section')
|
||||
new FediverseView().$mount('#vue-fediverse-section')
|
||||
new LanguageView().$mount('#vue-language-section')
|
||||
new LocaleView().$mount('#vue-locale-section')
|
||||
new BirthdayView().$mount('#vue-birthday-section')
|
||||
|
||||
if (profileEnabledGlobally) {
|
||||
const ProfileView = Vue.extend(ProfileSection)
|
||||
|
||||
@@ -70,6 +70,9 @@ script('settings', [
|
||||
<div class="personal-settings-setting-box">
|
||||
<div id="vue-location-section"></div>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box">
|
||||
<div id="vue-birthday-section"></div>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box personal-settings-language-box">
|
||||
<div id="vue-language-section"></div>
|
||||
</div>
|
||||
|
||||
@@ -240,7 +240,12 @@ class UsersControllerTest extends \Test\TestCase {
|
||||
),
|
||||
IAccountManager::PROPERTY_FEDIVERSE => $this->buildPropertyMock(
|
||||
IAccountManager::PROPERTY_FEDIVERSE,
|
||||
'Default twitter',
|
||||
'Default fediverse',
|
||||
IAccountManager::SCOPE_LOCAL,
|
||||
),
|
||||
IAccountManager::PROPERTY_BIRTHDATE => $this->buildPropertyMock(
|
||||
IAccountManager::PROPERTY_BIRTHDATE,
|
||||
'Default birthdate',
|
||||
IAccountManager::SCOPE_LOCAL,
|
||||
),
|
||||
];
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"displayname":{"name":"displayname","value":"Steve Smith","scope":"v2-local","verified":"0","verificationData":""},"address":{"name":"address","value":"123 Water St","scope":"v2-local","verified":"0","verificationData":""},"website":{"name":"website","value":"https:\/\/example.org","scope":"v2-local","verified":"0","verificationData":""},"email":{"name":"email","value":"steve@example.org","scope":"v2-federated","verified":"0","verificationData":""},"avatar":{"name":"avatar","value":"","scope":"v2-local","verified":"0","verificationData":""},"phone":{"name":"phone","value":"+12178515387","scope":"v2-private","verified":"0","verificationData":""},"twitter":{"name":"twitter","value":"steve","scope":"v2-federated","verified":"0","verificationData":""},"fediverse":{"name":"fediverse","value":"@steve@floss.social","scope":"v2-federated","verified":"0","verificationData":""},"organisation":{"name":"organisation","value":"Mytery Machine","scope":"v2-private","verified":"0","verificationData":""},"role":{"name":"role","value":"Manager","scope":"v2-private","verified":"0","verificationData":""},"headline":{"name":"headline","value":"I am Steve","scope":"v2-local","verified":"0","verificationData":""},"biography":{"name":"biography","value":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris porttitor ullamcorper dictum. Sed fermentum ut ligula scelerisque semper. Aliquam interdum convallis tellus eu dapibus. Integer in justo sollicitudin, hendrerit ligula sit amet, blandit sem.\n\nSuspendisse consectetur ultrices accumsan. Quisque sagittis bibendum lectus ut placerat. Mauris tincidunt ornare neque, et pulvinar tortor porttitor eu.","scope":"v2-local","verified":"0","verificationData":""},"profile_enabled":{"name":"profile_enabled","value":"1","scope":"v2-local","verified":"0","verificationData":""},"additional_mail":[{"name":"additional_mail","value":"steve@example.com","scope":"v2-published","verified":"0","verificationData":""},{"name":"additional_mail","value":"steve@earth.world","scope":"v2-local","verified":"0","verificationData":""}]}
|
||||
{"displayname":{"name":"displayname","value":"Steve Smith","scope":"v2-local","verified":"0","verificationData":""},"address":{"name":"address","value":"123 Water St","scope":"v2-local","verified":"0","verificationData":""},"website":{"name":"website","value":"https:\/\/example.org","scope":"v2-local","verified":"0","verificationData":""},"email":{"name":"email","value":"steve@example.org","scope":"v2-federated","verified":"1","verificationData":""},"avatar":{"name":"avatar","value":"","scope":"v2-local","verified":"0","verificationData":""},"phone":{"name":"phone","value":"+12178515387","scope":"v2-private","verified":"0","verificationData":""},"twitter":{"name":"twitter","value":"steve","scope":"v2-federated","verified":"0","verificationData":""},"fediverse":{"name":"fediverse","value":"@steve@floss.social","scope":"v2-federated","verified":"0","verificationData":""},"organisation":{"name":"organisation","value":"Mytery Machine","scope":"v2-private","verified":"0","verificationData":""},"role":{"name":"role","value":"Manager","scope":"v2-private","verified":"0","verificationData":""},"headline":{"name":"headline","value":"I am Steve","scope":"v2-local","verified":"0","verificationData":""},"biography":{"name":"biography","value":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris porttitor ullamcorper dictum. Sed fermentum ut ligula scelerisque semper. Aliquam interdum convallis tellus eu dapibus. Integer in justo sollicitudin, hendrerit ligula sit amet, blandit sem.\n\nSuspendisse consectetur ultrices accumsan. Quisque sagittis bibendum lectus ut placerat. Mauris tincidunt ornare neque, et pulvinar tortor porttitor eu.","scope":"v2-local","verified":"0","verificationData":""},"birthdate":{"name":"birthdate","value":"","scope":"v2-local","verified":"0","verificationData":""},"profile_enabled":{"name":"profile_enabled","value":"1","scope":"v2-local","verified":"0","verificationData":""},"additional_mail":[{"name":"additional_mail","value":"steve@example.com","scope":"v2-published","verified":"0","verificationData":""},{"name":"additional_mail","value":"steve@earth.world","scope":"v2-local","verified":"0","verificationData":""}]}
|
||||
@@ -1 +1 @@
|
||||
{"displayname":{"name":"displayname","value":"Emma Jones","scope":"v2-federated","verified":"0","verificationData":""},"address":{"name":"address","value":"920 Grass St","scope":"v2-local","verified":"0","verificationData":""},"website":{"name":"website","value":"","scope":"v2-local","verified":"0","verificationData":""},"email":{"name":"email","value":"","scope":"v2-federated","verified":"0","verificationData":""},"avatar":{"name":"avatar","value":"","scope":"v2-federated","verified":"0","verificationData":""},"phone":{"name":"phone","value":"","scope":"v2-local","verified":"0","verificationData":""},"twitter":{"name":"twitter","value":"","scope":"v2-local","verified":"0","verificationData":""},"fediverse":{"name":"fediverse","value":"","scope":"v2-local","verified":"0","verificationData":""},"organisation":{"name":"organisation","value":"","scope":"v2-local","verified":"0","verificationData":""},"role":{"name":"role","value":"","scope":"v2-local","verified":"0","verificationData":""},"headline":{"name":"headline","value":"","scope":"v2-local","verified":"0","verificationData":""},"biography":{"name":"biography","value":"","scope":"v2-local","verified":"0","verificationData":""},"profile_enabled":{"name":"profile_enabled","value":"1","scope":"v2-local","verified":"0","verificationData":""},"additional_mail":[]}
|
||||
{"displayname":{"name":"displayname","value":"Emma Jones","scope":"v2-federated","verified":"0","verificationData":""},"address":{"name":"address","value":"920 Grass St","scope":"v2-local","verified":"0","verificationData":""},"website":{"name":"website","value":"","scope":"v2-local","verified":"0","verificationData":""},"email":{"name":"email","value":"","scope":"v2-federated","verified":"1","verificationData":""},"avatar":{"name":"avatar","value":"","scope":"v2-federated","verified":"0","verificationData":""},"phone":{"name":"phone","value":"","scope":"v2-local","verified":"0","verificationData":""},"twitter":{"name":"twitter","value":"","scope":"v2-local","verified":"0","verificationData":""},"fediverse":{"name":"fediverse","value":"","scope":"v2-local","verified":"0","verificationData":""},"organisation":{"name":"organisation","value":"","scope":"v2-local","verified":"0","verificationData":""},"role":{"name":"role","value":"","scope":"v2-local","verified":"0","verificationData":""},"headline":{"name":"headline","value":"","scope":"v2-local","verified":"0","verificationData":""},"biography":{"name":"biography","value":"","scope":"v2-local","verified":"0","verificationData":""},"birthdate":{"name":"birthdate","value":"","scope":"v2-local","verified":"0","verificationData":""},"profile_enabled":{"name":"profile_enabled","value":"1","scope":"v2-local","verified":"0","verificationData":""},"additional_mail":[]}
|
||||
@@ -80,6 +80,7 @@ return array(
|
||||
'OCA\\User_LDAP\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
|
||||
'OCA\\User_LDAP\\PagedResults\\TLinkId' => $baseDir . '/../lib/PagedResults/TLinkId.php',
|
||||
'OCA\\User_LDAP\\Proxy' => $baseDir . '/../lib/Proxy.php',
|
||||
'OCA\\User_LDAP\\Service\\BirthdateParserService' => $baseDir . '/../lib/Service/BirthdateParserService.php',
|
||||
'OCA\\User_LDAP\\Service\\UpdateGroupsService' => $baseDir . '/../lib/Service/UpdateGroupsService.php',
|
||||
'OCA\\User_LDAP\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
|
||||
'OCA\\User_LDAP\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php',
|
||||
|
||||
@@ -95,6 +95,7 @@ class ComposerStaticInitUser_LDAP
|
||||
'OCA\\User_LDAP\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
|
||||
'OCA\\User_LDAP\\PagedResults\\TLinkId' => __DIR__ . '/..' . '/../lib/PagedResults/TLinkId.php',
|
||||
'OCA\\User_LDAP\\Proxy' => __DIR__ . '/..' . '/../lib/Proxy.php',
|
||||
'OCA\\User_LDAP\\Service\\BirthdateParserService' => __DIR__ . '/..' . '/../lib/Service/BirthdateParserService.php',
|
||||
'OCA\\User_LDAP\\Service\\UpdateGroupsService' => __DIR__ . '/..' . '/../lib/Service/UpdateGroupsService.php',
|
||||
'OCA\\User_LDAP\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
|
||||
'OCA\\User_LDAP\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php',
|
||||
|
||||
@@ -167,6 +167,10 @@ OCA = OCA || {};
|
||||
$element: $('#ldap_attr_biography'),
|
||||
setMethod: 'setBiographyAttribute'
|
||||
},
|
||||
ldap_attr_birthdate: {
|
||||
$element: $('#ldap_attr_birthdate'),
|
||||
setMethod: 'setBirthdateAttribute'
|
||||
},
|
||||
};
|
||||
this.setManagedItems(items);
|
||||
},
|
||||
@@ -498,6 +502,15 @@ OCA = OCA || {};
|
||||
this.setElementValue(this.managedItems.ldap_attr_biography.$element, attribute);
|
||||
},
|
||||
|
||||
/**
|
||||
* sets the attribute for the Nextcloud user profile birthday
|
||||
*
|
||||
* @param {string} attribute
|
||||
*/
|
||||
setBirthdateAttribute: function(attribute) {
|
||||
this.setElementValue(this.managedItems.ldap_attr_birthdate.$element, attribute);
|
||||
},
|
||||
|
||||
/**
|
||||
* deals with the result of the Test Connection test
|
||||
*
|
||||
|
||||
@@ -205,6 +205,8 @@ class Configuration {
|
||||
'ldapAttributeHeadline' => null,
|
||||
'ldapAttributeBiography' => null,
|
||||
'ldapAdminGroup' => '',
|
||||
'ldapAttributeBirthDate' => null,
|
||||
'ldapAttributeAnniversaryDate' => null,
|
||||
];
|
||||
|
||||
public function __construct(string $configPrefix, bool $autoRead = true) {
|
||||
@@ -562,6 +564,8 @@ class Configuration {
|
||||
'ldap_attr_headline' => '',
|
||||
'ldap_attr_biography' => '',
|
||||
'ldap_admin_group' => '',
|
||||
'ldap_attr_birthdate' => '',
|
||||
'ldap_attr_anniversarydate' => '',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -639,6 +643,8 @@ class Configuration {
|
||||
'ldap_attr_headline' => 'ldapAttributeHeadline',
|
||||
'ldap_attr_biography' => 'ldapAttributeBiography',
|
||||
'ldap_admin_group' => 'ldapAdminGroup',
|
||||
'ldap_attr_birthdate' => 'ldapAttributeBirthDate',
|
||||
'ldap_attr_anniversarydate' => 'ldapAttributeAnniversaryDate',
|
||||
];
|
||||
return $array;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ use Psr\Log\LoggerInterface;
|
||||
* @property string $ldapAttributeHeadline
|
||||
* @property string $ldapAttributeBiography
|
||||
* @property string $ldapAdminGroup
|
||||
* @property string $ldapAttributeBirthDate
|
||||
*/
|
||||
class Connection extends LDAPUtility {
|
||||
private ?\LDAP\Connection $ldapConnectionRes = null;
|
||||
|
||||
44
apps/user_ldap/lib/Service/BirthdateParserService.php
Normal file
44
apps/user_ldap/lib/Service/BirthdateParserService.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\User_LDAP\Service;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class BirthdateParserService {
|
||||
/**
|
||||
* Try to parse the birthdate from LDAP.
|
||||
* Supports LDAP's generalized time syntax, YYYYMMDD and YYYY-MM-DD.
|
||||
*
|
||||
* @throws InvalidArgumentException If the format of then given date is unknown
|
||||
*/
|
||||
public function parseBirthdate(string $value): DateTimeImmutable {
|
||||
// Minimum LDAP generalized date is "1994121610Z" with 11 chars
|
||||
// While maximum other format is "1994-12-16" with 10 chars
|
||||
if (strlen($value) > strlen('YYYY-MM-DD')) {
|
||||
// Probably LDAP generalized time syntax
|
||||
$value = substr($value, 0, 8);
|
||||
}
|
||||
|
||||
// Should be either YYYYMMDD or YYYY-MM-DD
|
||||
if (!preg_match('/^(\d{8}|\d{4}-\d{2}-\d{2})$/', $value)) {
|
||||
throw new InvalidArgumentException("Unknown date format: $value");
|
||||
}
|
||||
|
||||
try {
|
||||
return new DateTimeImmutable($value);
|
||||
} catch (Exception $e) {
|
||||
throw new InvalidArgumentException(
|
||||
"Unknown date format: $value",
|
||||
0,
|
||||
$e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,7 @@ class Manager {
|
||||
$this->access->getConnection()->ldapAttributeRole,
|
||||
$this->access->getConnection()->ldapAttributeHeadline,
|
||||
$this->access->getConnection()->ldapAttributeBiography,
|
||||
$this->access->getConnection()->ldapAttributeBirthDate,
|
||||
];
|
||||
|
||||
$homeRule = (string)$this->access->getConnection()->homeFolderNamingRule;
|
||||
|
||||
@@ -32,11 +32,13 @@
|
||||
*/
|
||||
namespace OCA\User_LDAP\User;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OC\Accounts\AccountManager;
|
||||
use OCA\User_LDAP\Access;
|
||||
use OCA\User_LDAP\Connection;
|
||||
use OCA\User_LDAP\Exceptions\AttributeNotSet;
|
||||
use OCA\User_LDAP\FilesystemHelper;
|
||||
use OCA\User_LDAP\Service\BirthdateParserService;
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\Accounts\PropertyDoesNotExistException;
|
||||
use OCP\IAvatarManager;
|
||||
@@ -107,6 +109,8 @@ class User {
|
||||
*/
|
||||
protected $avatarImage;
|
||||
|
||||
protected BirthdateParserService $birthdateParser;
|
||||
|
||||
/**
|
||||
* DB config keys for user preferences
|
||||
*/
|
||||
@@ -140,6 +144,7 @@ class User {
|
||||
$this->avatarManager = $avatarManager;
|
||||
$this->userManager = $userManager;
|
||||
$this->notificationManager = $notificationManager;
|
||||
$this->birthdateParser = new BirthdateParserService();
|
||||
|
||||
\OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
|
||||
}
|
||||
@@ -324,6 +329,22 @@ class User {
|
||||
} elseif (!empty($attr)) { // configured, but not defined
|
||||
$profileValues[\OCP\Accounts\IAccountManager::PROPERTY_BIOGRAPHY] = "";
|
||||
}
|
||||
//User Profile Field - birthday
|
||||
$attr = strtolower($this->connection->ldapAttributeBirthDate);
|
||||
if (!empty($attr) && !empty($ldapEntry[$attr][0])) {
|
||||
$value = $ldapEntry[$attr][0];
|
||||
try {
|
||||
$birthdate = $this->birthdateParser->parseBirthdate($value);
|
||||
$profileValues[\OCP\Accounts\IAccountManager::PROPERTY_BIRTHDATE]
|
||||
= $birthdate->format("Y-m-d");
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// Invalid date -> just skip the property
|
||||
$this->logger->info("Failed to parse user's birthdate from LDAP: $value", [
|
||||
'exception' => $e,
|
||||
'userId' => $username,
|
||||
]);
|
||||
}
|
||||
}
|
||||
// check for changed data and cache just for TTL checking
|
||||
$checksum = hash('sha256', json_encode($profileValues));
|
||||
$this->connection->writeToCache($cacheKey, $checksum // write array to cache. is waste of cache space
|
||||
|
||||
@@ -132,6 +132,7 @@ style('user_ldap', 'settings');
|
||||
<p><label for="ldap_attr_role"> <?php p($l->t('Role Field')); ?></label><input type="text" id="ldap_attr_role" name="ldap_attr_role" title="<?php p($l->t('User profile Role will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_role_default']); ?>"></p>
|
||||
<p><label for="ldap_attr_headline"> <?php p($l->t('Headline Field')); ?></label><input type="text" id="ldap_attr_headline" name="ldap_attr_headline" title="<?php p($l->t('User profile Headline will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_headline_default']); ?>"></p>
|
||||
<p><label for="ldap_attr_biography"> <?php p($l->t('Biography Field')); ?></label><input type="text" id="ldap_attr_biography" name="ldap_attr_biography" title="<?php p($l->t('User profile Biography will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_biography_default']); ?>"></p>
|
||||
<p><label for="ldap_attr_birthdate"> <?php p($l->t('Birthdate Field')); ?></label><input type="text" id="ldap_attr_birthdate" name="ldap_attr_birthdate" title="<?php p($l->t('User profile Date of birth will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_birthdate_default']); ?>"></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php print_unescaped($_['settingControls']); ?>
|
||||
|
||||
52
apps/user_ldap/tests/Service/BirthdateParserServiceTest.php
Normal file
52
apps/user_ldap/tests/Service/BirthdateParserServiceTest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\user_ldap\tests\Service;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use OCA\User_LDAP\Service\BirthdateParserService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BirthdateParserServiceTest extends TestCase {
|
||||
private BirthdateParserService $service;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->service = new BirthdateParserService();
|
||||
}
|
||||
|
||||
public function parseBirthdateDataProvider(): array {
|
||||
return [
|
||||
['2024-01-01', new DateTimeImmutable('2024-01-01'), false],
|
||||
['20240101', new DateTimeImmutable('2024-01-01'), false],
|
||||
['199412161032Z', new DateTimeImmutable('1994-12-16'), false], // LDAP generalized time
|
||||
['199412160532-0500', new DateTimeImmutable('1994-12-16'), false], // LDAP generalized time
|
||||
['2023-07-31T00:60:59.000Z', null, true],
|
||||
['01.01.2024', null, true],
|
||||
['01/01/2024', null, true],
|
||||
['01 01 2024', null, true],
|
||||
['foobar', null, true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider parseBirthdateDataProvider
|
||||
*/
|
||||
public function testParseBirthdate(
|
||||
string $value,
|
||||
?DateTimeImmutable $expected,
|
||||
bool $shouldThrow,
|
||||
): void {
|
||||
if ($shouldThrow) {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
}
|
||||
|
||||
$actual = $this->service->parseBirthdate($value);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
}
|
||||
6
dist/5455-5455.js
vendored
6
dist/5455-5455.js
vendored
File diff suppressed because one or more lines are too long
2
dist/5455-5455.js.map
vendored
2
dist/5455-5455.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/comments-comments-app.js
vendored
6
dist/comments-comments-app.js
vendored
File diff suppressed because one or more lines are too long
2
dist/comments-comments-app.js.map
vendored
2
dist/comments-comments-app.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/comments-comments-tab.js
vendored
6
dist/comments-comments-tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/comments-comments-tab.js.map
vendored
2
dist/comments-comments-tab.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/core-common.js
vendored
6
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/core-legacy-unified-search.js
vendored
6
dist/core-legacy-unified-search.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-legacy-unified-search.js.map
vendored
2
dist/core-legacy-unified-search.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/core-login.js
vendored
6
dist/core-login.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-login.js.map
vendored
2
dist/core-login.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/core-main.js
vendored
6
dist/core-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-main.js.map
vendored
2
dist/core-main.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/core-profile.js
vendored
6
dist/core-profile.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-profile.js.map
vendored
2
dist/core-profile.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/core-unified-search.js
vendored
6
dist/core-unified-search.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-unified-search.js.map
vendored
2
dist/core-unified-search.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/dav-settings-personal-availability.js
vendored
6
dist/dav-settings-personal-availability.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/files-init.js
vendored
6
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files-main.js
vendored
6
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files-personal-settings.js
vendored
6
dist/files-personal-settings.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-personal-settings.js.map
vendored
2
dist/files-personal-settings.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files-reference-files.js
vendored
6
dist/files-reference-files.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-reference-files.js.map
vendored
2
dist/files-reference-files.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files-search.js
vendored
6
dist/files-search.js
vendored
@@ -1,3 +1,3 @@
|
||||
/*! For license information please see files-search.js.license?v=f5cdaebd5e694da75d57 */
|
||||
(()=>{"use strict";var e,r,t,i={66747:(e,r,t)=>{var i=t(61338),o=t(85168),n=t(63814),a=t(53334);const l=(0,t(53529).YK)().setApp("files").detectUser().build();document.addEventListener("DOMContentLoaded",(function(){const e=window.OCA;e.UnifiedSearch&&(l.info("Initializing unified search plugin: folder search from files app"),e.UnifiedSearch.registerFilterAction({id:"files",appId:"files",label:(0,a.Tl)("files","In folder"),icon:(0,n.d0)("files","app.svg"),callback:()=>{(0,o.a1)("Pick plain text files").addMimeTypeFilter("httpd/unix-directory").allowDirectories(!0).addButton({label:"Pick",callback:e=>{l.info("Folder picked",{folder:e[0]});const r=e[0];(0,i.Ic)("nextcloud:unified-search:add-filter",{id:"files",payload:r,filterUpdateText:(0,a.Tl)("files","Search in folder: {folder}",{folder:r.basename}),filterParams:{path:r.path}})}}).build().pick()}}))}))}},o={};function n(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return i[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}n.m=i,e=[],n.O=(r,t,i,o)=>{if(!t){var a=1/0;for(s=0;s<e.length;s++){t=e[s][0],i=e[s][1],o=e[s][2];for(var l=!0,d=0;d<t.length;d++)(!1&o||a>=o)&&Object.keys(n.O).every((e=>n.O[e](t[d])))?t.splice(d--,1):(l=!1,o<a&&(a=o));if(l){e.splice(s--,1);var c=i();void 0!==c&&(r=c)}}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,i,o]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((r,t)=>(n.f[t](e,r),r)),[])),n.u=e=>e+"-"+e+".js?v="+{1110:"a5d6e6f59aa058840a1e",5455:"8915a218db5b7bc90f34"}[e],n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",n.l=(e,i,o,a)=>{if(r[e])r[e].push(i);else{var l,d;if(void 0!==o)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var f=c[s];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==t+o){l=f;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,n.nc&&l.setAttribute("nonce",n.nc),l.setAttribute("data-webpack",t+o),l.src=e),r[e]=[i];var u=(t,i)=>{l.onerror=l.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(i))),t)return t(i)},p=setTimeout(u.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=u.bind(null,l.onerror),l.onload=u.bind(null,l.onload),d&&document.head.appendChild(l)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.j=2277,(()=>{var e;n.g.importScripts&&(e=n.g.location+"");var r=n.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var i=t.length-1;i>-1&&(!e||!/^http(s?):/.test(e));)e=t[i--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),n.p=e})(),(()=>{n.b=document.baseURI||self.location.href;var e={2277:0};n.f.j=(r,t)=>{var i=n.o(e,r)?e[r]:void 0;if(0!==i)if(i)t.push(i[2]);else{var o=new Promise(((t,o)=>i=e[r]=[t,o]));t.push(i[2]=o);var a=n.p+n.u(r),l=new Error;n.l(a,(t=>{if(n.o(e,r)&&(0!==(i=e[r])&&(e[r]=void 0),i)){var o=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+a+")",l.name="ChunkLoadError",l.type=o,l.request=a,i[1](l)}}),"chunk-"+r,r)}},n.O.j=r=>0===e[r];var r=(r,t)=>{var i,o,a=t[0],l=t[1],d=t[2],c=0;if(a.some((r=>0!==e[r]))){for(i in l)n.o(l,i)&&(n.m[i]=l[i]);if(d)var s=d(n)}for(r&&r(t);c<a.length;c++)o=a[c],n.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return n.O(s)},t=self.webpackChunknextcloud=self.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),n.nc=void 0;var a=n.O(void 0,[4208],(()=>n(66747)));a=n.O(a)})();
|
||||
//# sourceMappingURL=files-search.js.map?v=f5cdaebd5e694da75d57
|
||||
/*! For license information please see files-search.js.license?v=9859549c4d9082ff7d42 */
|
||||
(()=>{"use strict";var e,r,t,i={66747:(e,r,t)=>{var i=t(61338),o=t(85168),a=t(63814),n=t(53334);const l=(0,t(53529).YK)().setApp("files").detectUser().build();document.addEventListener("DOMContentLoaded",(function(){const e=window.OCA;e.UnifiedSearch&&(l.info("Initializing unified search plugin: folder search from files app"),e.UnifiedSearch.registerFilterAction({id:"files",appId:"files",label:(0,n.Tl)("files","In folder"),icon:(0,a.d0)("files","app.svg"),callback:()=>{(0,o.a1)("Pick plain text files").addMimeTypeFilter("httpd/unix-directory").allowDirectories(!0).addButton({label:"Pick",callback:e=>{l.info("Folder picked",{folder:e[0]});const r=e[0];(0,i.Ic)("nextcloud:unified-search:add-filter",{id:"files",payload:r,filterUpdateText:(0,n.Tl)("files","Search in folder: {folder}",{folder:r.basename}),filterParams:{path:r.path}})}}).build().pick()}}))}))}},o={};function a(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return i[e].call(t.exports,t,t.exports,a),t.loaded=!0,t.exports}a.m=i,e=[],a.O=(r,t,i,o)=>{if(!t){var n=1/0;for(s=0;s<e.length;s++){t=e[s][0],i=e[s][1],o=e[s][2];for(var l=!0,d=0;d<t.length;d++)(!1&o||n>=o)&&Object.keys(a.O).every((e=>a.O[e](t[d])))?t.splice(d--,1):(l=!1,o<n&&(n=o));if(l){e.splice(s--,1);var c=i();void 0!==c&&(r=c)}}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,i,o]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var t in r)a.o(r,t)&&!a.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,t)=>(a.f[t](e,r),r)),[])),a.u=e=>e+"-"+e+".js?v="+{1110:"a5d6e6f59aa058840a1e",5455:"70389af1c0aeb085252a"}[e],a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",a.l=(e,i,o,n)=>{if(r[e])r[e].push(i);else{var l,d;if(void 0!==o)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var f=c[s];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==t+o){l=f;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,a.nc&&l.setAttribute("nonce",a.nc),l.setAttribute("data-webpack",t+o),l.src=e),r[e]=[i];var u=(t,i)=>{l.onerror=l.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(i))),t)return t(i)},p=setTimeout(u.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=u.bind(null,l.onerror),l.onload=u.bind(null,l.onload),d&&document.head.appendChild(l)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=2277,(()=>{var e;a.g.importScripts&&(e=a.g.location+"");var r=a.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var i=t.length-1;i>-1&&(!e||!/^http(s?):/.test(e));)e=t[i--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b=document.baseURI||self.location.href;var e={2277:0};a.f.j=(r,t)=>{var i=a.o(e,r)?e[r]:void 0;if(0!==i)if(i)t.push(i[2]);else{var o=new Promise(((t,o)=>i=e[r]=[t,o]));t.push(i[2]=o);var n=a.p+a.u(r),l=new Error;a.l(n,(t=>{if(a.o(e,r)&&(0!==(i=e[r])&&(e[r]=void 0),i)){var o=t&&("load"===t.type?"missing":t.type),n=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+n+")",l.name="ChunkLoadError",l.type=o,l.request=n,i[1](l)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,t)=>{var i,o,n=t[0],l=t[1],d=t[2],c=0;if(n.some((r=>0!==e[r]))){for(i in l)a.o(l,i)&&(a.m[i]=l[i]);if(d)var s=d(a)}for(r&&r(t);c<n.length;c++)o=n[c],a.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return a.O(s)},t=self.webpackChunknextcloud=self.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),a.nc=void 0;var n=a.O(void 0,[4208],(()=>a(66747)));n=a.O(n)})();
|
||||
//# sourceMappingURL=files-search.js.map?v=9859549c4d9082ff7d42
|
||||
2
dist/files-search.js.map
vendored
2
dist/files-search.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files-sidebar.js
vendored
6
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files_external-init.js
vendored
6
dist/files_external-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_external-init.js.map
vendored
2
dist/files_external-init.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files_reminders-init.js
vendored
6
dist/files_reminders-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_reminders-init.js.map
vendored
2
dist/files_reminders-init.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files_sharing-files_sharing_tab.js
vendored
6
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files_sharing-personal-settings.js
vendored
6
dist/files_sharing-personal-settings.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-personal-settings.js.map
vendored
2
dist/files_sharing-personal-settings.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files_versions-files_versions.js
vendored
6
dist/files_versions-files_versions.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_versions-files_versions.js.map
vendored
2
dist/files_versions-files_versions.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/settings-declarative-settings-forms.js
vendored
4
dist/settings-declarative-settings-forms.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/settings-vue-settings-admin-security.js
vendored
6
dist/settings-vue-settings-admin-security.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/settings-vue-settings-admin-sharing.js
vendored
6
dist/settings-vue-settings-admin-sharing.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/settings-vue-settings-personal-info.js
vendored
6
dist/settings-vue-settings-personal-info.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/systemtags-admin.js
vendored
6
dist/systemtags-admin.js
vendored
File diff suppressed because one or more lines are too long
2
dist/systemtags-admin.js.map
vendored
2
dist/systemtags-admin.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/theming-admin-theming.js
vendored
6
dist/theming-admin-theming.js
vendored
File diff suppressed because one or more lines are too long
2
dist/theming-admin-theming.js.map
vendored
2
dist/theming-admin-theming.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/theming-personal-theming.js
vendored
6
dist/theming-personal-theming.js
vendored
File diff suppressed because one or more lines are too long
2
dist/theming-personal-theming.js.map
vendored
2
dist/theming-personal-theming.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/user_status-menu.js
vendored
6
dist/user_status-menu.js
vendored
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user