Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Use file processor for user cover photos #6127

Merged
merged 53 commits into from
Dec 23, 2024
Merged
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
2fdd0e2
Add cover photo management
Cyperghost Nov 28, 2024
7cb6012
Only GET for this form is required
Cyperghost Nov 28, 2024
0d4751f
Remove cover photo file strategy classes
Cyperghost Nov 28, 2024
f0ed3f0
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Nov 29, 2024
a18e1d1
Use `FileRuntimeCache`
Cyperghost Nov 29, 2024
6c6cdc3
Manage cover photo in acp
Cyperghost Nov 29, 2024
67396c9
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Nov 29, 2024
f400bb6
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Nov 29, 2024
b5d1837
Dont use `coverPhotoHash` or `coverPhotoExtension` anymore
Cyperghost Nov 29, 2024
bacb446
Insert back `IUserCoverPhoto::delete()`
Cyperghost Nov 29, 2024
e130828
Migrate old cover photos into the new file system
Cyperghost Nov 29, 2024
8cd6ef5
Check if `coverPhotoHash` or `coverPhotoExtension` is set
Cyperghost Nov 29, 2024
23c7618
Add phpdoc to `getCoverPhotoLocation()`
Cyperghost Nov 29, 2024
a9f68bc
Delete old user cover photo upload/delete
Cyperghost Nov 29, 2024
45f8e6d
Add foreign key for `wcf1_user.coverPhotoFileID` to `wcf1_file.fileID`
Cyperghost Dec 2, 2024
4e1bb40
Mark `IWebpUserCoverPhoto` as deprecated
Cyperghost Dec 2, 2024
b7d4b5f
Cache cover photos
Cyperghost Dec 2, 2024
7b84b13
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 4, 2024
195992d
`createMinMax` renamed to `forMinMax`
Cyperghost Dec 4, 2024
e9c7e57
Rename `getCoverPhotoLocation()` to `getLegacyLocation()` and move to…
Cyperghost Dec 4, 2024
3d432b8
Remove `defaultCoverPhoto` from bootstrap
Cyperghost Dec 4, 2024
3d0983e
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 5, 2024
205b325
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 5, 2024
1c47b52
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 5, 2024
3b02de8
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 5, 2024
592b293
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 6, 2024
dab00bf
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 6, 2024
663c9a6
`coverPhotoStyle` need to be encoded
Cyperghost Dec 6, 2024
89143be
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 9, 2024
b1250d9
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 10, 2024
e5e64cd
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 12, 2024
ef27055
Use the user profile header template
Cyperghost Dec 12, 2024
38fd2a5
Use `File::getFullSizeImageSource()` instead of `File::getLink()`
Cyperghost Dec 12, 2024
f8deb1a
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 16, 2024
40d1a6c
Merge branch 'file-upload-image-crop' into 6.2-user-coverphoto
Cyperghost Dec 18, 2024
6c1cf08
Add `simpleReplace` and `hideDeleteButton` to the `FileProcessorFormF…
Cyperghost Dec 12, 2024
c665eca
Use `simpleReplace()`
Cyperghost Dec 18, 2024
a7ed9ad
Display thumbnail size by file processor
Cyperghost Dec 12, 2024
bbdbcc1
Unset `uploadResolve`
Cyperghost Dec 16, 2024
4ce87d5
Add a callback that is triggered when the value of a FileProcessorFor…
Cyperghost Dec 18, 2024
83da2e1
Notify only when the value has really changed.
Cyperghost Dec 18, 2024
5d9b9a4
Fix crop cancel event handling
Cyperghost Dec 18, 2024
2e02d69
Change cover photo instantly when a new file is uploaded or deleted
Cyperghost Dec 18, 2024
eea4e06
Add thumbnail
Cyperghost Dec 18, 2024
98f0eb0
Add `IUserCoverPhoto::getThumbnailURL()`
Cyperghost Dec 18, 2024
9a3942b
Use `IUserCoverPhoto::getThumbnailURL()` in `userCard` template
Cyperghost Dec 18, 2024
8cfb42e
Set small thumbnail size to 800x200
Cyperghost Dec 18, 2024
fb8cdd0
Clamp the values when the selection violates the boundaries
dtdesign Dec 19, 2024
3285e2e
Reverse the event logic
dtdesign Dec 19, 2024
3e73655
use `$change` instead of changing all values individually, for each v…
Cyperghost Dec 20, 2024
e8afc7c
Do not use `precise` anymore so that we only calculate with integers
Cyperghost Dec 20, 2024
6f56543
Merge branch '6.2' into 6.2-user-coverphoto
Cyperghost Dec 23, 2024
4360ace
Run `tsc`
Cyperghost Dec 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions com.woltlab.wcf/fileDelete.xml
Original file line number Diff line number Diff line change
@@ -2997,6 +2997,8 @@
<file>lib/system/template/plugin/TemplatePluginPrefilterIcon.class.php</file>
<file>lib/system/template/plugin/TemplatePluginPrefilterLang.class.php</file>
<file>lib/system/template/plugin/WordwrapModifierTemplatePlugin.class.php</file>
<file>lib/system/upload/UserCoverPhotoUploadFileSaveStrategy.class.php</file>
<file>lib/system/upload/UserCoverPhotoUploadFileValidationStrategy.class.php</file>
<file>lib/system/user/UserCollapsibleContentHandler.class.php</file>
<file>lib/system/user/activity/point/AbstractUserActivityPointObjectProcessor.class.php</file>
<file>lib/system/user/activity/point/DefaultUserActivityPointObjectProcessor.class.php</file>
6 changes: 2 additions & 4 deletions wcfsetup/install/files/lib/data/user/UserProfile.class.php
Original file line number Diff line number Diff line change
@@ -415,14 +415,12 @@ public function canSeeAvatar()
public function getCoverPhoto($isACP = false)
{
if ($this->coverPhoto === null) {
if ($this->coverPhotoHash) {
if ($this->coverPhotoFileID) {
if ($isACP || !$this->disableCoverPhoto) {
if ($this->canSeeCoverPhoto()) {
$this->coverPhoto = new UserCoverPhoto(
$this->userID,
$this->coverPhotoHash,
$this->coverPhotoExtension,
$this->coverPhotoHasWebP
FileRuntimeCache::getInstance()->getObject($this->coverPhotoFileID)
);
}
}
125 changes: 0 additions & 125 deletions wcfsetup/install/files/lib/data/user/UserProfileAction.class.php
Original file line number Diff line number Diff line change
@@ -17,9 +17,6 @@
use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
use wcf\system\option\user\UserOptionHandler;
use wcf\system\upload\UploadFile;
use wcf\system\upload\UploadHandler;
use wcf\system\upload\UserCoverPhotoUploadFileSaveStrategy;
use wcf\system\upload\UserCoverPhotoUploadFileValidationStrategy;
use wcf\system\user\group\assignment\UserGroupAssignmentHandler;
use wcf\system\user\storage\UserStorageHandler;
use wcf\system\WCF;
@@ -599,128 +596,6 @@ public function setAvatar(): array
];
}

/**
* Validates the 'uploadCoverPhoto' method.
*
* @throws PermissionDeniedException
* @throws UserInputException
* @since 3.1
*/
public function validateUploadCoverPhoto()
{
WCF::getSession()->checkPermissions(['user.profile.coverPhoto.canUploadCoverPhoto']);

$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;
}

$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 PermissionDeniedException();
}

// validate uploaded file
if (!isset($this->parameters['__files']) || \count($this->parameters['__files']->getFiles()) != 1) {
throw new UserInputException('files');
}

/** @var UploadHandler $uploadHandler */
$uploadHandler = $this->parameters['__files'];

$this->uploadFile = $uploadHandler->getFiles()[0];

$uploadHandler->validateFiles(new UserCoverPhotoUploadFileValidationStrategy());
}

/**
* 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);

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(),
];
}
}

/**
* 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;
}

$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();
}
}

/**
* 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' => '',
]);
}

// force-reload the user profile to use a predictable code-path to fetch the cover photo
UserProfileRuntimeCache::getInstance()->removeObject($this->user->userID);

return [
'url' => UserProfileRuntimeCache::getInstance()->getObject($this->user->userID)->getCoverPhoto()->getURL(),
];
}

/**
* Returns the user option handler object.
*
Original file line number Diff line number Diff line change
@@ -11,11 +11,6 @@
*/
interface IUserCoverPhoto
{
/**
* Deletes this cover photo.
*/
public function delete();

/**
* Returns the physical location of this cover photo.
*/
Original file line number Diff line number Diff line change
@@ -2,8 +2,7 @@

namespace wcf\data\user\cover\photo;

use wcf\system\WCF;
use wcf\util\ImageUtil;
use wcf\data\file\File;

/**
* Represents a user's cover photo.
@@ -12,119 +11,47 @@
* @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/
class UserCoverPhoto implements IWebpUserCoverPhoto
final class UserCoverPhoto implements IUserCoverPhoto
{
/**
* file extension
* @var string
*/
protected $coverPhotoExtension;
public const MAX_HEIGHT = 800;

/**
* file hash
* @var string
*/
protected $coverPhotoHash;
public const MAX_WIDTH = 2000;

/**
* @var int
*/
protected $coverPhotoHasWebP = 0;

/**
* user id
* @var int
*/
protected $userID;
public const MIN_HEIGHT = 200;

const MAX_HEIGHT = 800;

const MAX_WIDTH = 2000;

const MIN_HEIGHT = 200;

const MIN_WIDTH = 500;
public const MIN_WIDTH = 500;

/**
* UserCoverPhoto constructor.
*
* @param int $userID
* @param string $coverPhotoHash
* @param string $coverPhotoExtension
*/
public function __construct($userID, $coverPhotoHash, $coverPhotoExtension, int $coverPhotoHasWebP)
{
$this->userID = $userID;
$this->coverPhotoHash = $coverPhotoHash;
$this->coverPhotoExtension = $coverPhotoExtension;
$this->coverPhotoHasWebP = $coverPhotoHasWebP;
public function __construct(
protected readonly int $userID,
protected readonly File $file
) {
}

/**
* @inheritDoc
*/
public function delete()
{
if (\file_exists($this->getLocation(false))) {
@\unlink($this->getLocation(false));
}

if (\file_exists($this->getLocation(true))) {
@\unlink($this->getLocation(true));
}
}

/**
* @inheritDoc
*/
#[\Override]
public function getLocation(?bool $forceWebP = null): string
{
return WCF_DIR . 'images/coverPhotos/' . $this->getFilename($forceWebP);
return $this->file->getPath();
}

/**
* @inheritDoc
*/
#[\Override]
public function getURL(?bool $forceWebP = null): string
{
return WCF::getPath() . 'images/coverPhotos/' . $this->getFilename($forceWebP);
return $this->file->getLink();
}

/**
* @inheritDoc
*/
#[\Override]
public function getFilename(?bool $forceWebP = null): string
{
$useWebP = $forceWebP || ($this->coverPhotoHasWebP && $forceWebP === null && ImageUtil::browserSupportsWebp());

return \substr(
$this->coverPhotoHash,
0,
2
) . '/' . $this->userID . '-' . $this->coverPhotoHash . '.' . ($useWebP ? 'webp' : $this->coverPhotoExtension);
}

/**
* @inheritDoc
*/
public function createWebpVariant()
{
if ($this->coverPhotoHasWebP) {
return;
}

$sourceLocation = $this->getLocation($this->coverPhotoExtension === 'webp');
$outputFilenameWithoutExtension = \preg_replace('~\.[a-z]+$~', '', $sourceLocation);

return ImageUtil::createWebpVariant($sourceLocation, $outputFilenameWithoutExtension);
return $this->file->filename;
}

/**
* Returns the minimum and maximum dimensions for cover photos.
*
* @return array
*/
public static function getCoverPhotoDimensions()
public static function getCoverPhotoDimensions(): array
{
return [
'max' => [
Loading