aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorskjnldsv <skjnldsv@protonmail.com>2024-11-13 09:42:26 +0100
committerskjnldsv <skjnldsv@protonmail.com>2024-11-14 10:25:02 +0100
commitb15fdfd40e4bf96b53f7afcf0e4dce359158cf1c (patch)
tree0c5cfe660bcdb04338661687b3733ae3cbc88ced /core
parentdfa7e7edea66c37a7b33965ad9e93648d44243b0 (diff)
downloadnextcloud-server-b15fdfd40e4bf96b53f7afcf0e4dce359158cf1c.tar.gz
nextcloud-server-b15fdfd40e4bf96b53f7afcf0e4dce359158cf1c.zip
chore(profile): move profile app from core to apps
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Diffstat (limited to 'core')
-rw-r--r--core/Controller/ProfilePageController.php115
-rw-r--r--core/shipped.json2
-rw-r--r--core/src/profile.ts27
-rw-r--r--core/src/profile/ProfileSections.js25
-rw-r--r--core/src/views/Profile.vue489
-rw-r--r--core/templates/404-profile.php30
-rw-r--r--core/templates/profile.php7
7 files changed, 2 insertions, 693 deletions
diff --git a/core/Controller/ProfilePageController.php b/core/Controller/ProfilePageController.php
deleted file mode 100644
index 7463173e906..00000000000
--- a/core/Controller/ProfilePageController.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-namespace OC\Core\Controller;
-
-use OC\Profile\ProfileManager;
-use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http\Attribute\AnonRateLimit;
-use OCP\AppFramework\Http\Attribute\BruteForceProtection;
-use OCP\AppFramework\Http\Attribute\FrontpageRoute;
-use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
-use OCP\AppFramework\Http\Attribute\OpenAPI;
-use OCP\AppFramework\Http\Attribute\PublicPage;
-use OCP\AppFramework\Http\Attribute\UserRateLimit;
-use OCP\AppFramework\Http\TemplateResponse;
-use OCP\AppFramework\Services\IInitialState;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\INavigationManager;
-use OCP\IRequest;
-use OCP\IUserManager;
-use OCP\IUserSession;
-use OCP\Profile\BeforeTemplateRenderedEvent;
-use OCP\Share\IManager as IShareManager;
-use OCP\UserStatus\IManager as IUserStatusManager;
-
-#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
-class ProfilePageController extends Controller {
- public function __construct(
- string $appName,
- IRequest $request,
- private IInitialState $initialStateService,
- private ProfileManager $profileManager,
- private IShareManager $shareManager,
- private IUserManager $userManager,
- private IUserSession $userSession,
- private IUserStatusManager $userStatusManager,
- private INavigationManager $navigationManager,
- private IEventDispatcher $eventDispatcher,
- ) {
- parent::__construct($appName, $request);
- }
-
- #[PublicPage]
- #[NoCSRFRequired]
- #[FrontpageRoute(verb: 'GET', url: '/u/{targetUserId}')]
- #[BruteForceProtection(action: 'user')]
- #[UserRateLimit(limit: 30, period: 120)]
- #[AnonRateLimit(limit: 30, period: 120)]
- public function index(string $targetUserId): TemplateResponse {
- $profileNotFoundTemplate = new TemplateResponse(
- 'core',
- '404-profile',
- [],
- TemplateResponse::RENDER_AS_GUEST,
- );
-
- $targetUser = $this->userManager->get($targetUserId);
- if ($targetUser === null) {
- $profileNotFoundTemplate->throttle();
- return $profileNotFoundTemplate;
- }
- if (!$targetUser->isEnabled()) {
- return $profileNotFoundTemplate;
- }
- $visitingUser = $this->userSession->getUser();
-
- if (!$this->profileManager->isProfileEnabled($targetUser)) {
- return $profileNotFoundTemplate;
- }
-
- // Run user enumeration checks only if viewing another user's profile
- if ($targetUser !== $visitingUser) {
- if (!$this->shareManager->currentUserCanEnumerateTargetUser($visitingUser, $targetUser)) {
- return $profileNotFoundTemplate;
- }
- }
-
- if ($visitingUser !== null) {
- $userStatuses = $this->userStatusManager->getUserStatuses([$targetUserId]);
- $status = $userStatuses[$targetUserId] ?? null;
- if ($status !== null) {
- $this->initialStateService->provideInitialState('status', [
- 'icon' => $status->getIcon(),
- 'message' => $status->getMessage(),
- ]);
- }
- }
-
- $this->initialStateService->provideInitialState(
- 'profileParameters',
- $this->profileManager->getProfileFields($targetUser, $visitingUser),
- );
-
- if ($targetUser === $visitingUser) {
- $this->navigationManager->setActiveEntry('profile');
- }
-
- $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($targetUserId));
-
- \OCP\Util::addScript('core', 'profile');
-
- return new TemplateResponse(
- 'core',
- 'profile',
- [],
- $this->userSession->isLoggedIn() ? TemplateResponse::RENDER_AS_USER : TemplateResponse::RENDER_AS_PUBLIC,
- );
- }
-}
diff --git a/core/shipped.json b/core/shipped.json
index 6eb6a9879c2..3583ce68546 100644
--- a/core/shipped.json
+++ b/core/shipped.json
@@ -30,6 +30,7 @@
"password_policy",
"photos",
"privacy",
+ "profile",
"provisioning_api",
"recommendations",
"related_resources",
@@ -79,6 +80,7 @@
"password_policy",
"photos",
"privacy",
+ "profile",
"provisioning_api",
"recommendations",
"related_resources",
diff --git a/core/src/profile.ts b/core/src/profile.ts
deleted file mode 100644
index 454562edb05..00000000000
--- a/core/src/profile.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-import { getCSPNonce } from '@nextcloud/auth'
-import Vue from 'vue'
-
-import Profile from './views/Profile.vue'
-import ProfileSections from './profile/ProfileSections.js'
-
-__webpack_nonce__ = getCSPNonce()
-
-if (!window.OCA) {
- window.OCA = {}
-}
-
-if (!window.OCA.Core) {
- window.OCA.Core = {}
-}
-Object.assign(window.OCA.Core, { ProfileSections: new ProfileSections() })
-
-const View = Vue.extend(Profile)
-
-window.addEventListener('DOMContentLoaded', () => {
- new View().$mount('#content')
-})
diff --git a/core/src/profile/ProfileSections.js b/core/src/profile/ProfileSections.js
deleted file mode 100644
index 9c6ca08e33f..00000000000
--- a/core/src/profile/ProfileSections.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-export default class ProfileSections {
-
- _sections
-
- constructor() {
- this._sections = []
- }
-
- /**
- * @param {registerSectionCallback} section To be called to mount the section to the profile page
- */
- registerSection(section) {
- this._sections.push(section)
- }
-
- getSections() {
- return this._sections
- }
-
-}
diff --git a/core/src/views/Profile.vue b/core/src/views/Profile.vue
deleted file mode 100644
index 661e9a7fa0b..00000000000
--- a/core/src/views/Profile.vue
+++ /dev/null
@@ -1,489 +0,0 @@
-<!--
- - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- - SPDX-License-Identifier: AGPL-3.0-or-later
--->
-
-<template>
- <NcContent app-name="profile">
- <NcAppContent>
- <div class="profile__header">
- <div class="profile__header__container">
- <div class="profile__header__container__placeholder" />
- <div class="profile__header__container__displayname">
- <h2>{{ displayname || userId }}</h2>
- <span v-if="pronouns">·</span>
- <span v-if="pronouns" class="profile__header__container__pronouns">{{ pronouns }}</span>
- <NcButton v-if="isCurrentUser"
- type="primary"
- :href="settingsUrl">
- <template #icon>
- <PencilIcon :size="20" />
- </template>
- {{ t('core', 'Edit Profile') }}
- </NcButton>
- </div>
- <NcButton v-if="status.icon || status.message"
- :disabled="!isCurrentUser"
- :type="isCurrentUser ? 'tertiary' : 'tertiary-no-background'"
- @click="openStatusModal">
- {{ status.icon }} {{ status.message }}
- </NcButton>
- </div>
- </div>
-
- <div class="profile__wrapper">
- <div class="profile__content">
- <div class="profile__sidebar">
- <NcAvatar class="avatar"
- :class="{ interactive: isCurrentUser }"
- :user="userId"
- :size="180"
- :show-user-status="true"
- :show-user-status-compact="false"
- :disable-menu="true"
- :disable-tooltip="true"
- :is-no-user="!isUserAvatarVisible"
- @click.native.prevent.stop="openStatusModal" />
-
- <div class="user-actions">
- <!-- When a tel: URL is opened with target="_blank", a blank new tab is opened which is inconsistent with the handling of other URLs so we set target="_self" for the phone action -->
- <NcButton v-if="primaryAction"
- type="primary"
- class="user-actions__primary"
- :href="primaryAction.target"
- :icon="primaryAction.icon"
- :target="primaryAction.id === 'phone' ? '_self' :'_blank'">
- <template #icon>
- <!-- Fix for https://github.com/nextcloud-libraries/nextcloud-vue/issues/2315 -->
- <img :src="primaryAction.icon" alt="" class="user-actions__primary__icon">
- </template>
- {{ primaryAction.title }}
- </NcButton>
- <NcActions class="user-actions__other" :inline="4">
- <NcActionLink v-for="action in otherActions"
- :key="action.id"
- :close-after-click="true"
- :href="action.target"
- :target="action.id === 'phone' ? '_self' :'_blank'">
- <template #icon>
- <!-- Fix for https://github.com/nextcloud-libraries/nextcloud-vue/issues/2315 -->
- <img :src="action.icon" alt="" class="user-actions__other__icon">
- </template>
- {{ action.title }}
- </NcActionLink>
- </NcActions>
- </div>
- </div>
-
- <div class="profile__blocks">
- <div v-if="organisation || role || address" class="profile__blocks-details">
- <div v-if="organisation || role" class="detail">
- <p>{{ organisation }} <span v-if="organisation && role">•</span> {{ role }}</p>
- </div>
- <div v-if="address" class="detail">
- <p>
- <MapMarkerIcon class="map-icon"
- :size="16" />
- {{ address }}
- </p>
- </div>
- </div>
- <template v-if="headline || biography || sections.length > 0">
- <h3 v-if="headline" class="profile__blocks-headline">
- {{ headline }}
- </h3>
- <p v-if="biography" class="profile__blocks-biography">
- {{ biography }}
- </p>
-
- <!-- additional entries, use it with cautious -->
- <div v-for="(section, index) in sections"
- :ref="'section-' + index"
- :key="index"
- class="profile__additionalContent">
- <component :is="section($refs['section-'+index], userId)" :user-id="userId" />
- </div>
- </template>
- <NcEmptyContent v-else
- class="profile__blocks-empty-info"
- :name="emptyProfileMessage"
- :description="t('core', 'The headline and about sections will show up here')">
- <template #icon>
- <AccountIcon :size="60" />
- </template>
- </NcEmptyContent>
- </div>
- </div>
- </div>
- </NcAppContent>
- </NcContent>
-</template>
-
-<script lang="ts">
-import { getCurrentUser } from '@nextcloud/auth'
-import { showError } from '@nextcloud/dialogs'
-import { subscribe, unsubscribe } from '@nextcloud/event-bus'
-import { loadState } from '@nextcloud/initial-state'
-import { translate as t } from '@nextcloud/l10n'
-import { generateUrl } from '@nextcloud/router'
-import { defineComponent } from 'vue'
-
-import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
-import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
-import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
-import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
-import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
-import NcContent from '@nextcloud/vue/dist/Components/NcContent.js'
-import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
-import AccountIcon from 'vue-material-design-icons/Account.vue'
-import MapMarkerIcon from 'vue-material-design-icons/MapMarker.vue'
-import PencilIcon from 'vue-material-design-icons/Pencil.vue'
-
-interface IProfileAction {
- target: string
- icon: string
- id: string
- title: string
-}
-
-interface IStatus {
- icon: string,
- message: string,
- userId: string,
-}
-
-export default defineComponent({
- name: 'Profile',
-
- components: {
- AccountIcon,
- MapMarkerIcon,
- NcActionLink,
- NcActions,
- NcAppContent,
- NcAvatar,
- NcButton,
- NcContent,
- NcEmptyContent,
- PencilIcon,
- },
-
- data() {
- const profileParameters = loadState('core', 'profileParameters', {
- userId: null as string|null,
- displayname: null as string|null,
- address: null as string|null,
- organisation: null as string|null,
- role: null as string|null,
- headline: null as string|null,
- biography: null as string|null,
- actions: [] as IProfileAction[],
- isUserAvatarVisible: false,
- pronouns: null as string|null,
- })
-
- return {
- ...profileParameters,
- status: loadState<Partial<IStatus>>('core', 'status', {}),
- sections: window.OCA.Core.ProfileSections.getSections(),
- }
- },
-
- computed: {
- isCurrentUser() {
- return getCurrentUser()?.uid === this.userId
- },
-
- allActions() {
- return this.actions
- },
-
- primaryAction() {
- if (this.allActions.length) {
- return this.allActions[0]
- }
- return null
- },
-
- otherActions() {
- console.warn(this.allActions)
- if (this.allActions.length > 1) {
- return this.allActions.slice(1)
- }
- return []
- },
-
- settingsUrl() {
- return generateUrl('/settings/user')
- },
-
- emptyProfileMessage() {
- return this.isCurrentUser
- ? t('core', 'You have not added any info yet')
- : t('core', '{user} has not added any info yet', { user: (this.displayname || this.userId!) })
- },
- },
-
- mounted() {
- // Set the user's displayname or userId in the page title and preserve the default title of "Nextcloud" at the end
- document.title = `${this.displayname || this.userId} - ${document.title}`
- subscribe('user_status:status.updated', this.handleStatusUpdate)
- },
-
- beforeDestroy() {
- unsubscribe('user_status:status.updated', this.handleStatusUpdate)
- },
-
- methods: {
- t,
-
- handleStatusUpdate(status: IStatus) {
- if (this.isCurrentUser && status.userId === this.userId) {
- this.status = status
- }
- },
-
- openStatusModal() {
- const statusMenuItem = document.querySelector<HTMLButtonElement>('.user-status-menu-item')
- // Changing the user status is only enabled if you are the current user
- if (this.isCurrentUser) {
- if (statusMenuItem) {
- statusMenuItem.click()
- } else {
- showError(t('core', 'Error opening the user status modal, try hard refreshing the page'))
- }
- }
- },
- },
-})
-</script>
-
-<style lang="scss" scoped>
-$profile-max-width: 1024px;
-$content-max-width: 640px;
-
-:deep(#app-content-vue) {
- background-color: unset;
-}
-
-.profile {
- width: 100%;
- overflow-y: auto;
-
- &__header {
- display: flex;
- position: sticky;
- height: 190px;
- top: -40px;
- background-color: var(--color-main-background-blur);
- backdrop-filter: var(--filter-background-blur);
- -webkit-backdrop-filter: var(--filter-background-blur);
-
- &__container {
- align-self: flex-end;
- width: 100%;
- max-width: $profile-max-width;
- margin: 8px auto;
- row-gap: 8px;
- display: grid;
- grid-template-rows: max-content max-content;
- grid-template-columns: 240px 1fr;
- justify-content: center;
-
- &__placeholder {
- grid-row: 1 / 3;
- }
-
- &__displayname {
- padding-inline: 16px; // same as the status text button, see NcButton
- width: $content-max-width;
- height: 45px;
- margin-block: 125px 0;
- display: flex;
- align-items: center;
- gap: 18px;
-
- h2 {
- font-size: 30px;
- margin: 0;
- }
-
- span {
- font-size: 20px;
- }
- }
- }
- }
-
- &__sidebar {
- position: sticky;
- top: 0;
- align-self: flex-start;
- padding-top: 20px;
- min-width: 220px;
- margin-block: -150px 0;
- margin-inline: 0 20px;
-
- // Specificity hack is needed to override Avatar component styles
- :deep(.avatar.avatardiv) {
- text-align: center;
- margin: auto;
- display: block;
- padding: 8px;
-
- &.interactive {
- .avatardiv__user-status {
- // Show that the status is interactive
- cursor: pointer;
- }
- }
-
- .avatardiv__user-status {
- inset-inline-end: 14px;
- bottom: 14px;
- width: 34px;
- height: 34px;
- background-size: 28px;
- border: none;
- // Styles when custom status icon and status text are set
- background-color: var(--color-main-background);
- line-height: 34px;
- font-size: 20px;
- }
- }
- }
-
- &__wrapper {
- background-color: var(--color-main-background);
- min-height: 100%;
- }
-
- &__content {
- max-width: $profile-max-width;
- margin: 0 auto;
- display: flex;
- width: 100%;
- }
-
- &__blocks {
- margin: 18px 0 80px 0;
- display: grid;
- gap: 16px 0;
- width: $content-max-width;
-
- p, h3 {
- cursor: text;
- overflow-wrap: anywhere;
- }
-
- &-details {
- display: flex;
- flex-direction: column;
- gap: 2px 0;
-
- .detail {
- display: inline-block;
- color: var(--color-text-maxcontrast);
-
- p .map-icon {
- display: inline-block;
- vertical-align: middle;
- }
- }
- }
-
- &-headline {
- margin-inline: 0;
- margin-block: 10px 0;
- font-weight: bold;
- font-size: 20px;
- }
-
- &-biography {
- white-space: pre-line;
- }
- }
-}
-
-@media only screen and (max-width: 1024px) {
- .profile {
- &__header {
- height: 250px;
- position: unset;
-
- &__container {
- grid-template-columns: unset;
- margin-bottom: 110px;
-
- &__displayname {
- margin: 80px 20px 0px 0px!important;
- width: unset;
- text-align: center;
- padding-inline: 12px;
- }
-
- &__edit-button {
- width: fit-content;
- display: block;
- margin: 60px auto;
- }
-
- &__status-text {
- margin: 4px auto;
- }
- }
- }
-
- &__content {
- display: block;
-
- .avatar {
- // Overlap avatar to top header
- margin-top: -110px !important;
- }
- }
-
- &__blocks {
- width: unset;
- max-width: 600px;
- margin: 0 auto;
- padding: 20px 50px 50px 50px;
- }
-
- &__sidebar {
- margin: unset;
- position: unset;
- }
- }
-}
-
-.user-actions {
- display: flex;
- flex-direction: column;
- gap: 8px 0;
- margin-top: 20px;
-
- &__primary {
- margin: 0 auto;
-
- &__icon {
- filter: var(--primary-invert-if-dark);
- }
- }
-
- &__other {
- display: flex;
- justify-content: center;
- gap: 0 4px;
-
- &__icon {
- height: 20px;
- width: 20px;
- object-fit: contain;
- filter: var(--background-invert-if-dark);
- align-self: center;
- margin: 12px; // so we get 44px x 44px
- }
- }
-}
-</style>
diff --git a/core/templates/404-profile.php b/core/templates/404-profile.php
deleted file mode 100644
index 3f2d8731347..00000000000
--- a/core/templates/404-profile.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-/**
- * SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-/** @var array $_ */
-/** @var \OCP\IL10N $l */
-/** @var \OCP\Defaults $theme */
-// @codeCoverageIgnoreStart
-if (!isset($_)) { //standalone page is not supported anymore - redirect to /
- require_once '../../lib/base.php';
-
- $urlGenerator = \OC::$server->getURLGenerator();
- header('Location: ' . $urlGenerator->getAbsoluteURL('/'));
- exit;
-}
-// @codeCoverageIgnoreEnd
-?>
-<?php if (isset($_['content'])) : ?>
- <?php print_unescaped($_['content']) ?>
-<?php else : ?>
- <div class="body-login-container update">
- <div class="icon-big icon-error"></div>
- <h2><?php p($l->t('Profile not found')); ?></h2>
- <p class="infogroup"><?php p($l->t('The profile does not exist.')); ?></p>
- <p><a class="button primary" href="<?php p(\OC::$server->getURLGenerator()->linkTo('', 'index.php')) ?>">
- <?php p($l->t('Back to %s', [$theme->getName()])); ?>
- </a></p>
- </div>
-<?php endif; ?>
diff --git a/core/templates/profile.php b/core/templates/profile.php
deleted file mode 100644
index 460bfcc4221..00000000000
--- a/core/templates/profile.php
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-/**
- * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-?>
-<div id="content"></div>