</div>
{/if}
{if $__wcf->getSession()->getPermission('user.profile.avatar.canUploadAvatar') && !$__wcf->getUserProfileHandler()->disableAvatar}
- <div class="userMenuItem userMenuItemNarrow userMenuItemSingleLine">
+ <div class="userMenuItem userMenuItemNarrow userMenuItemSingleLine userAvatarManagement" data-edit-avatar="{link controller="UserAvatar"}{/link}">
<div class="userMenuItemImage">
{icon size=16 name='user-pen'}
</div>
<div class="userMenuItemContent">
- <button type="button" class="userMenuItemLink userAvatarManagement">
+ <button type="button" class="userMenuItemLink">
{lang}wcf.user.avatarManagement{/lang}
</button>
</div>
whenFirstSeen("[data-ignore-user]", () => {
void import("./Component/User/Ignore").then(({ setup }) => setup());
});
+ whenFirstSeen("[data-edit-avatar]", () => {
+ void import("./Component/User/Avatar").then(({ setup }) => setup());
+ });
}
--- /dev/null
+/**
+ * Handles the user avatar edit buttons.
+ *
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+
+import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex";
+import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector";
+import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog";
+import { close as closeControlPanel } from "WoltLabSuite/Core/Ui/User/Menu/ControlPanel";
+
+async function editAvatar(button: HTMLElement): Promise<void> {
+ // If the user is editing their own avatar, the control panel is open and can overlay the dialog.
+ closeControlPanel();
+
+ const { ok } = await dialogFactory().usingFormBuilder().fromEndpoint(button.dataset.editAvatar!);
+
+ if (ok) {
+ // TODO can we simple replace all avatar images?
+ window.location.reload();
+ }
+}
+
+export function setup(): void {
+ wheneverFirstSeen("[data-edit-avatar]", (button) => {
+ button.addEventListener(
+ "click",
+ promiseMutex(() => editAvatar(button)),
+ );
+ });
+}
}
}
-function close(): void {
+export function close(): void {
focusTrap.deactivate();
element.hidden = true;
(0, LazyLoader_1.whenFirstSeen)("[data-ignore-user]", () => {
void new Promise((resolve_8, reject_8) => { require(["./Component/User/Ignore"], resolve_8, reject_8); }).then(tslib_1.__importStar).then(({ setup }) => setup());
});
+ (0, LazyLoader_1.whenFirstSeen)("[data-edit-avatar]", () => {
+ void new Promise((resolve_9, reject_9) => { require(["./Component/User/Avatar"], resolve_9, reject_9); }).then(tslib_1.__importStar).then(({ setup }) => setup());
+ });
}
});
--- /dev/null
+/**
+ * Handles the user avatar edit buttons.
+ *
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Helper/Selector", "WoltLabSuite/Core/Component/Dialog", "WoltLabSuite/Core/Ui/User/Menu/ControlPanel"], function (require, exports, PromiseMutex_1, Selector_1, Dialog_1, ControlPanel_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.setup = setup;
+ async function editAvatar(button) {
+ // If the user is editing their own avatar, the control panel is open and can overlay the dialog.
+ (0, ControlPanel_1.close)();
+ const { ok } = await (0, Dialog_1.dialogFactory)().usingFormBuilder().fromEndpoint(button.dataset.editAvatar);
+ if (ok) {
+ // TODO can we simple replace all avatar images?
+ window.location.reload();
+ }
+ }
+ function setup() {
+ (0, Selector_1.wheneverFirstSeen)("[data-edit-avatar]", (button) => {
+ button.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(() => editAvatar(button)));
+ });
+ }
+});
define(["require", "exports", "tslib", "../../CloseOverlay", "./Manager", "focus-trap", "../../Alignment", "../../../Dom/Util"], function (require, exports, tslib_1, CloseOverlay_1, Manager_1, focus_trap_1, Alignment, Util_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+ exports.close = close;
exports.getElement = getElement;
exports.setup = setup;
CloseOverlay_1 = tslib_1.__importDefault(CloseOverlay_1);
--- /dev/null
+<?php
+
+namespace wcf\action;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\data\file\FileAction;
+use wcf\data\IStorableObject;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfile;
+use wcf\http\Helper;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\file\processor\UserAvatarFileProcessor;
+use wcf\system\form\builder\data\processor\CustomFormDataProcessor;
+use wcf\system\form\builder\field\dependency\ValueFormFieldDependency;
+use wcf\system\form\builder\field\FileProcessorFormField;
+use wcf\system\form\builder\field\RadioButtonFormField;
+use wcf\system\form\builder\IFormDocument;
+use wcf\system\form\builder\Psr15DialogForm;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\user\UserProfileHandler;
+use wcf\system\WCF;
+
+/**
+ * Handles user avatars editing.
+ *
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+final class UserAvatarAction implements RequestHandlerInterface
+{
+ #[\Override]
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $parameters = Helper::mapQueryParameters(
+ $request->getQueryParams(),
+ <<<'EOT'
+ array {
+ id?: positive-int
+ }
+ EOT
+ );
+
+ if (!WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+
+ if (isset($parameters['id'])) {
+ $user = UserProfileRuntimeCache::getInstance()->getObject($parameters['id']);
+ } else {
+ $user = UserProfileHandler::getInstance()->getUserProfile();
+ }
+
+ if (!UserAvatarFileProcessor::canEditAvatar($user)) {
+ throw new PermissionDeniedException();
+ }
+
+ $form = $this->getForm($user);
+
+ if ($request->getMethod() === 'GET') {
+ return $form->toResponse();
+ } elseif ($request->getMethod() === 'POST') {
+ $response = $form->validateRequest($request);
+ if ($response !== null) {
+ return $response;
+ }
+
+ $data = $form->getData()['data'];
+
+ // If the user has already uploaded and optionally cropped an image,
+ // this is already assigned to the `$user` and does not need to be saved again.
+ // However, if the user wants to delete their avatar and use a standard avatar,
+ // this must be saved and the cache reset
+ if ($data['avatarType'] === 'none') {
+ if ($user->avatarFileID !== null) {
+ (new FileAction([$user->avatarFileID], 'delete'))->executeAction();
+ }
+
+ (new UserEditor($user->getDecoratedObject()))->update([
+ 'avatarFileID' => null,
+ ]);
+
+ UserStorageHandler::getInstance()->reset([$user->userID], 'avatar');
+ }
+
+ return new JsonResponse([
+ // TODO did we need the avatar url?
+ 'result' => [],
+ ]);
+ } else {
+ throw new \LogicException('Unreachable');
+ }
+ }
+
+ private function getForm(UserProfile $user): Psr15DialogForm
+ {
+ $form = new Psr15DialogForm(
+ UserAvatarAction::class,
+ WCF::getLanguage()->get('wcf.user.avatarManagement')
+ );
+ $form->appendChildren([
+ RadioButtonFormField::create('avatarType')
+ ->value("none")
+ ->required()
+ ->options([
+ "none" => WCF::getLanguage()->get('wcf.user.avatar.type.none'),
+ "custom" => WCF::getLanguage()->get('wcf.user.avatar.type.custom'),
+ ]),
+ FileProcessorFormField::create('avatarFileID')
+ ->objectType("com.woltlab.wcf.user.avatar")
+ ->required()
+ ->singleFileUpload()
+ ->bigPreview()
+ ->addDependency(
+ ValueFormFieldDependency::create('avatarType')
+ ->fieldId('avatarType')
+ ->values(['custom'])
+ ),
+ ]);
+ $form->getDataHandler()->addProcessor(
+ new CustomFormDataProcessor(
+ 'avatarType',
+ null,
+ function (IFormDocument $document, array $data, IStorableObject $object) {
+ \assert($object instanceof UserProfile);
+ if ($object->avatarFileID === null) {
+ $data['avatarType'] = 'none';
+ } else {
+ $data['avatarType'] = 'custom';
+ }
+
+ return $data;
+ }
+ )
+ );
+
+ $form->markRequiredFields(false);
+ $form->updatedObject($user);
+ $form->build();
+
+ return $form;
+ }
+}
$userFromContext = $this->getUser($context);
$userFromCoreFile = $this->getUserByFile($file);
- if ($userFromContext === null) {
+ if ($userFromCoreFile === null) {
return true;
}
// Save the `fileID` in the session variable so that the current user can delete it the old avatar
if ($user->avatarFileID !== null) {
- WCF::getSession()->register(\sprintf(self::SESSION_VARIABLE, $$user->avatarFileID), TIME_NOW);
+ WCF::getSession()->register(\sprintf(self::SESSION_VARIABLE, $user->avatarFileID), TIME_NOW);
WCF::getSession()->update();
}
- (new UserEditor($user))->update([
+ (new UserEditor($user->getDecoratedObject()))->update([
'avatarFileID' => $file->fileID,
]);
// reset user storage
return FileProcessorPreflightResult::InvalidContext;
}
- if (!$this->canEditAvatar($user)) {
+ if (!UserAvatarFileProcessor::canEditAvatar($user)) {
return FileProcessorPreflightResult::InsufficientPermissions;
}
) !== null;
}
- return $this->canEditAvatar($user);
+ return UserAvatarFileProcessor::canEditAvatar($user);
}
#[\Override]
return UserProfileRuntimeCache::getInstance()->getObject($userID);
}
- private function canEditAvatar(UserProfile $user): bool
+ public static function canEditAvatar(UserProfile $user): bool
{
if (WCF::getSession()->getPermission('admin.user.canEditUser')) {
return true;