From: Cyperghost Date: Mon, 23 Dec 2024 08:02:45 +0000 (+0100) Subject: Merge branch '6.2' into 6.2-user-coverphoto X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=6f56543e0314e89cb796fb18e158f0b0b3b58a13;p=GitHub%2FWoltLab%2FWCF.git Merge branch '6.2' into 6.2-user-coverphoto # Conflicts: # com.woltlab.wcf/fileDelete.xml # com.woltlab.wcf/objectType.xml # wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2.php # wcfsetup/install/files/lib/data/user/UserProfile.class.php # wcfsetup/install/files/lib/data/user/UserProfileAction.class.php # wcfsetup/install/files/lib/data/user/UserProfileList.class.php # wcfsetup/setup/db/install.sql --- 6f56543e0314e89cb796fb18e158f0b0b3b58a13 diff --cc com.woltlab.wcf/fileDelete.xml index 302f95860d,0bf2d4c5bb..437e8ddf7d --- a/com.woltlab.wcf/fileDelete.xml +++ b/com.woltlab.wcf/fileDelete.xml @@@ -3001,8 -3000,8 +3002,10 @@@ lib/system/template/plugin/TemplatePluginPrefilterIcon.class.php lib/system/template/plugin/TemplatePluginPrefilterLang.class.php lib/system/template/plugin/WordwrapModifierTemplatePlugin.class.php + lib/system/upload/AvatarUploadFileSaveStrategy.class.php + lib/system/upload/AvatarUploadFileValidationStrategy.class.php + lib/system/upload/UserCoverPhotoUploadFileSaveStrategy.class.php + lib/system/upload/UserCoverPhotoUploadFileValidationStrategy.class.php lib/system/user/UserCollapsibleContentHandler.class.php lib/system/user/activity/point/AbstractUserActivityPointObjectProcessor.class.php lib/system/user/activity/point/DefaultUserActivityPointObjectProcessor.class.php diff --cc com.woltlab.wcf/objectType.xml index c522744e96,567b539407..654ccf917d --- a/com.woltlab.wcf/objectType.xml +++ b/com.woltlab.wcf/objectType.xml @@@ -1744,11 -1744,11 +1744,16 @@@ com.woltlab.wcf.file wcf\system\file\processor\AttachmentFileProcessor + + com.woltlab.wcf.user.avatar + com.woltlab.wcf.file + wcf\system\file\processor\UserAvatarFileProcessor + + + com.woltlab.wcf.user.coverPhoto + com.woltlab.wcf.file + wcf\system\file\processor\UserCoverPhotoFileProcessor + com.woltlab.wcf.page.controller diff --cc com.woltlab.wcf/templates/userProfileHeader.tpl index cdc58d0d80,3a9fe00d0b..399042ad8c --- a/com.woltlab.wcf/templates/userProfileHeader.tpl +++ b/com.woltlab.wcf/templates/userProfileHeader.tpl @@@ -28,15 -28,23 +28,21 @@@ {event name='beforeManageButtons'} {if $view->canEditCoverPhoto()} - + {/if} + {if $view->user->canEditAvatar()} + + {/if} + {if $view->canEditUser()} {/if} diff --cc wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2.php index edc887ef15,c275d354e1..991913e597 --- a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2.php +++ b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2.php @@@ -15,15 -15,15 +15,23 @@@ use wcf\system\database\table\PartialDa return [ PartialDatabaseTable::create('wcf1_user') ->columns([ + IntDatabaseTableColumn::create('avatarFileID') + ->length(10) + ->defaultValue(null), + IntDatabaseTableColumn::create('coverPhotoFileID') + ->length(10) + ->defaultValue(null), ]) ->foreignKeys([ + DatabaseTableForeignKey::create() + ->columns(['avatarFileID']) + ->referencedTable('wcf1_file') + ->referencedColumns(['fileID']) + ->onDelete('SET NULL'), + DatabaseTableForeignKey::create() + ->columns(['coverPhotoFileID']) + ->referencedTable('wcf1_file') + ->referencedColumns(['fileID']) + ->onDelete('SET NULL'), ]), ]; diff --cc wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js index f123518cb4,bdcd0c0fef..6f11659bf1 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js @@@ -151,14 -154,11 +154,14 @@@ define(["require", "exports", "tslib", }); (0, LazyLoader_1.whenFirstSeen)(".jsImageViewer", () => { console.warn("The class `jsImageViewer` is deprecated. Use the attribute `data-fancybox` instead."); - void new Promise((resolve_12, reject_12) => { require(["./Component/Image/Viewer"], resolve_12, reject_12); }).then(tslib_1.__importStar).then(({ setupLegacy }) => setupLegacy()); + void new Promise((resolve_13, reject_13) => { require(["./Component/Image/Viewer"], resolve_13, reject_13); }).then(tslib_1.__importStar).then(({ setupLegacy }) => setupLegacy()); }); (0, LazyLoader_1.whenFirstSeen)(".jsEnablesOptions", () => { - void new Promise((resolve_13, reject_13) => { require(["./Component/Option/Enable"], resolve_13, reject_13); }).then(tslib_1.__importStar).then(({ setup }) => setup()); + void new Promise((resolve_14, reject_14) => { require(["./Component/Option/Enable"], resolve_14, reject_14); }).then(tslib_1.__importStar).then(({ setup }) => setup()); }); + (0, LazyLoader_1.whenFirstSeen)("[data-edit-cover-photo]", () => { + void new Promise((resolve_14, reject_14) => { require(["./Component/User/CoverPhoto"], resolve_14, reject_14); }).then(tslib_1.__importStar).then(({ setup }) => setup()); + }); // Move the reCAPTCHA widget overlay to the `pageOverlayContainer` // when widget form elements are placed in a dialog. const observer = new MutationObserver((mutations) => { diff --cc wcfsetup/install/files/lib/data/user/UserProfile.class.php index 8093a07d02,0b2083a4d3..a8aef1554e --- a/wcfsetup/install/files/lib/data/user/UserProfile.class.php +++ b/wcfsetup/install/files/lib/data/user/UserProfile.class.php @@@ -1190,23 -1199,26 +1197,46 @@@ class UserProfile extends DatabaseObjec && ($this->isAccessible('canViewTrophies') || $this->userID == WCF::getSession()->userID); } + /** + * @since 6.2 + */ + public function canEditAvatar(): bool + { + if ( + WCF::getSession()->getPermission('admin.user.canEditUser') + && UserGroup::isAccessibleGroup($this->getGroupIDs()) + ) { + return true; + } + + if ($this->userID !== WCF::getUser()->userID) { + return false; + } + + if ($this->disableAvatar) { + return false; + } + + return WCF::getSession()->getPermission('user.profile.avatar.canUploadAvatar'); + } ++ + /** + * @since 6.2 + */ + public function canEditCoverPhoto(): bool + { + if ($this->canEdit() && WCF::getSession()->getPermission('admin.user.canDisableCoverPhoto')) { + return true; + } + + if ($this->userID !== WCF::getUser()->userID) { + return false; + } + + if ($this->disableCoverPhoto) { + return false; + } + + return WCF::getSession()->getPermission('user.profile.coverPhoto.canUploadCoverPhoto'); + } } diff --cc wcfsetup/install/files/lib/data/user/UserProfileAction.class.php index 250af1f74f,b4a56b6b2d..b6d5940d85 --- a/wcfsetup/install/files/lib/data/user/UserProfileAction.class.php +++ b/wcfsetup/install/files/lib/data/user/UserProfileAction.class.php @@@ -508,94 -506,128 +503,6 @@@ class UserProfileAction extends UserAct } } -- /** - * Sets an avatar for a given user. The given file will be renamed and is gone after this method call. - * Validates the 'uploadCoverPhoto' method. -- * - * @throws UserInputException If none or more than one user is given. - * @throws \InvalidArgumentException If the given file is not an image or is incorrectly sized. - * @since 5.5 - * @throws PermissionDeniedException - * @throws UserInputException - * @since 3.1 -- */ - public function setAvatar(): array - public function validateUploadCoverPhoto() -- { - $user = $this->getSingleObject(); - - $imageData = \getimagesize($this->parameters['fileLocation']); - WCF::getSession()->checkPermissions(['user.profile.coverPhoto.canUploadCoverPhoto']); -- - if (!$imageData) { - throw new \InvalidArgumentException("The given file is not an image."); - $this->readInteger('userID', true); - // The `userID` parameter did not exist in 3.1, defaulting to the own user for backwards compatibility. - if (!$this->parameters['userID']) { - $this->parameters['userID'] = WCF::getUser()->userID; -- } -- - if ( - ($imageData[0] != UserAvatar::AVATAR_SIZE || $imageData[1] != UserAvatar::AVATAR_SIZE) - && ($imageData[0] != UserAvatar::AVATAR_SIZE_2X || $imageData[1] != UserAvatar::AVATAR_SIZE_2X) - $this->user = new User($this->parameters['userID']); - if (!$this->user->userID) { - throw new UserInputException('userID'); - } elseif ($this->user->userID == WCF::getUser()->userID && WCF::getUser()->disableCoverPhoto) { - throw new PermissionDeniedException(); - } elseif ( - $this->user->userID != WCF::getUser()->userID - && (!$this->user->canEdit() || !WCF::getSession()->getPermission('admin.user.canDisableCoverPhoto')) -- ) { - throw new \InvalidArgumentException( - \sprintf( - "The given file does not have the size of %dx%d", - UserAvatar::AVATAR_SIZE, - UserAvatar::AVATAR_SIZE - ) - ); - throw new PermissionDeniedException(); -- } -- - $data = [ - 'avatarName' => $this->parameters['filename'] ?? \basename($this->parameters['fileLocation']), - 'avatarExtension' => ImageUtil::getExtensionByMimeType($imageData['mime']), - 'width' => $imageData[0], - 'height' => $imageData[1], - 'userID' => $user->userID, - 'fileHash' => \sha1_file($this->parameters['fileLocation']), - ]; - // validate uploaded file - if (!isset($this->parameters['__files']) || \count($this->parameters['__files']->getFiles()) != 1) { - throw new UserInputException('files'); - } -- - // create avatar - $avatar = UserAvatarEditor::create($data); - /** @var UploadHandler $uploadHandler */ - $uploadHandler = $this->parameters['__files']; -- - try { - // check avatar directory - // and create subdirectory if necessary - $dir = \dirname($avatar->getLocation(null, false)); - if (!\file_exists($dir)) { - FileUtil::makePath($dir); - } - $this->uploadFile = $uploadHandler->getFiles()[0]; -- - \rename($this->parameters['fileLocation'], $avatar->getLocation(null, false)); - $uploadHandler->validateFiles(new UserCoverPhotoUploadFileValidationStrategy()); - } -- - // Fix the permissions of the file in case the source file was created with restricted - // permissions (e.g. 0600 instead of 0644). Without this the file might not be readable - // for the web server if it runs with a different system user. - FileUtil::makeWritable($avatar->getLocation(null, false)); - /** - * Uploads a cover photo. - * - * @since 3.1 - */ - public function uploadCoverPhoto() - { - $saveStrategy = new UserCoverPhotoUploadFileSaveStrategy( - (!empty($this->parameters['userID']) ? \intval($this->parameters['userID']) : WCF::getUser()->userID) - ); - /** @noinspection PhpUndefinedMethodInspection */ - $this->parameters['__files']->saveFiles($saveStrategy); -- - // Create the WebP variant or the JPEG fallback of the avatar. - $avatarEditor = new UserAvatarEditor($avatar); - if ($avatarEditor->createAvatarVariant()) { - $avatar = new UserAvatar($avatar->avatarID); - } - if ($this->uploadFile->getValidationErrorType()) { - return [ - 'filesize' => $this->uploadFile->getFilesize(), - 'errorMessage' => WCF::getLanguage()->getDynamicVariable( - 'wcf.user.coverPhoto.upload.error.' . $this->uploadFile->getValidationErrorType(), - [ - 'file' => $this->uploadFile, - ] - ), - 'errorType' => $this->uploadFile->getValidationErrorType(), - ]; - } else { - return [ - 'url' => $saveStrategy->getCoverPhoto()->getURL(), - ]; - } - } -- - // update user - $userEditor = new UserEditor($user->getDecoratedObject()); - $userEditor->update([ - 'avatarID' => $avatar->avatarID, - ]); - } catch (\Exception $e) { - $editor = new UserAvatarEditor($avatar); - $editor->delete(); - /** - * Validates the `deleteCoverPhoto` action. - * - * @throws PermissionDeniedException - * @throws UserInputException - */ - public function validateDeleteCoverPhoto() - { - $this->readInteger('userID', true); - // The `userID` parameter did not exist in 3.1, defaulting to the own user for backwards compatibility. - if (!$this->parameters['userID']) { - $this->parameters['userID'] = WCF::getUser()->userID; - } -- - throw $e; - $this->user = new User($this->parameters['userID']); - if (!$this->user->userID) { - throw new UserInputException('userID'); - } elseif ( - $this->user->userID != WCF::getUser()->userID - && (!$this->user->canEdit() || !WCF::getSession()->getPermission('admin.user.canDisableCoverPhoto')) - ) { - throw new PermissionDeniedException(); -- } - } -- - // delete old avatar - if ($user->avatarID) { - (new UserAvatarAction([$user->avatarID], 'delete'))->executeAction(); - /** - * Deletes the cover photo of the active user. - * - * @return string[] link to the new cover photo - */ - public function deleteCoverPhoto() - { - if ($this->user->coverPhotoHash) { - UserProfileRuntimeCache::getInstance()->getObject($this->user->userID)->getCoverPhoto()->delete(); - - (new UserEditor($this->user))->update([ - 'coverPhotoHash' => null, - 'coverPhotoExtension' => '', - ]); -- } -- - // reset user storage - UserStorageHandler::getInstance()->reset([$user->userID], 'avatar'); - // force-reload the user profile to use a predictable code-path to fetch the cover photo - UserProfileRuntimeCache::getInstance()->removeObject($this->user->userID); -- -- return [ - 'avatar' => $avatar, - 'url' => UserProfileRuntimeCache::getInstance()->getObject($this->user->userID)->getCoverPhoto()->getURL(), -- ]; -- } -- /** * Returns the user option handler object. * diff --cc wcfsetup/install/files/lib/data/user/UserProfileList.class.php index ad997ba686,bb362da65e..f54c72556a --- a/wcfsetup/install/files/lib/data/user/UserProfileList.class.php +++ b/wcfsetup/install/files/lib/data/user/UserProfileList.class.php @@@ -62,12 -58,6 +60,14 @@@ class UserProfileList extends UserLis parent::readObjects(); + $this->cacheAvatarFiles(); ++ + $coverPhotoFileIDs = []; + foreach ($this->objects as $object) { + if ($object->coverPhotoFileID) { + $coverPhotoFileIDs[] = $object->coverPhotoFileID; + } + } + FileRuntimeCache::getInstance()->cacheObjectIDs($coverPhotoFileIDs); } } diff --cc wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php index 521e59496d,9409784bfe..d766b7f620 --- a/wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php +++ b/wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php @@@ -4,10 -4,10 +4,9 @@@ namespace wcf\system\worker use wcf\data\file\FileEditor; use wcf\data\reaction\type\ReactionTypeCache; - use wcf\data\user\avatar\UserAvatar; use wcf\data\user\avatar\UserAvatarEditor; use wcf\data\user\avatar\UserAvatarList; -use wcf\data\user\cover\photo\DefaultUserCoverPhoto; -use wcf\data\user\cover\photo\IWebpUserCoverPhoto; +use wcf\data\user\cover\photo\UserCoverPhoto; use wcf\data\user\User; use wcf\data\user\UserEditor; use wcf\data\user\UserList; @@@ -15,9 -15,10 +14,10 @@@ use wcf\data\user\UserProfileAction use wcf\system\bbcode\BBCodeHandler; use wcf\system\database\util\PreparedStatementConditionBuilder; use wcf\system\exception\SystemException; + use wcf\system\file\processor\UserAvatarFileProcessor; use wcf\system\html\input\HtmlInputProcessor; use wcf\system\image\ImageHandler; +use wcf\system\user\command\SetCoverPhoto; use wcf\system\user\storage\UserStorageHandler; use wcf\system\WCF; diff --cc wcfsetup/setup/db/install.sql index 956151aaad,b2b8d4ff2b..e3a9aba8bb --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@@ -2257,7 -2257,7 +2258,8 @@@ ALTER TABLE wcf1_tracked_visit_type AD ALTER TABLE wcf1_unfurl_url ADD FOREIGN KEY (imageID) REFERENCES wcf1_unfurl_url_image (imageID) ON DELETE SET NULL; ALTER TABLE wcf1_user ADD FOREIGN KEY (avatarID) REFERENCES wcf1_user_avatar (avatarID) ON DELETE SET NULL; + ALTER TABLE wcf1_user ADD FOREIGN KEY (avatarFileID) REFERENCES wcf1_file (fileID) ON DELETE SET NULL; +ALTER TABLE wcf1_user ADD FOREIGN KEY (coverPhotoFileID) REFERENCES wcf1_file (fileID) ON DELETE SET NULL; ALTER TABLE wcf1_user ADD FOREIGN KEY (rankID) REFERENCES wcf1_user_rank (rankID) ON DELETE SET NULL; ALTER TABLE wcf1_user ADD FOREIGN KEY (userOnlineGroupID) REFERENCES wcf1_user_group (groupID) ON DELETE SET NULL;