<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>
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)
);
}
}
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;
];
}
- /**
- * 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.
*
*/
interface IUserCoverPhoto
{
- /**
- * Deletes this cover photo.
- */
- public function delete();
-
/**
* Returns the physical location of this cover photo.
*/
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.
* @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' => [
+++ /dev/null
-<?php
-
-namespace wcf\system\upload;
-
-use wcf\data\user\cover\photo\IUserCoverPhoto;
-use wcf\data\user\cover\photo\UserCoverPhoto;
-use wcf\data\user\User;
-use wcf\data\user\UserEditor;
-use wcf\system\cache\runtime\UserProfileRuntimeCache;
-use wcf\system\exception\SystemException;
-use wcf\system\WCF;
-use wcf\util\FileUtil;
-use wcf\util\ImageUtil;
-use wcf\util\StringUtil;
-
-/**
- * Save strategy for user cover photos.
- *
- * @author Marcel Werk
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @since 5.2
- */
-class UserCoverPhotoUploadFileSaveStrategy implements IUploadFileSaveStrategy
-{
- /**
- * @var int
- */
- protected $userID = 0;
-
- /**
- * @var User
- */
- protected $user;
-
- /**
- * @var IUserCoverPhoto
- */
- protected $coverPhoto;
-
- /**
- * Creates a new instance of UserCoverPhotoUploadFileSaveStrategy.
- *
- * @param int $userID
- */
- public function __construct($userID = null)
- {
- $this->userID = ($userID ?: WCF::getUser()->userID);
- $this->user = ($this->userID != WCF::getUser()->userID ? new User($userID) : WCF::getUser());
- }
-
- /**
- * @return IUserCoverPhoto
- */
- public function getCoverPhoto()
- {
- return $this->coverPhoto;
- }
-
- /**
- * @inheritDoc
- */
- public function save(UploadFile $uploadFile)
- {
- if (!$uploadFile->getValidationErrorType()) {
- // rotate image if necessary
- /** @noinspection PhpUnusedLocalVariableInspection */
- $fileLocation = ImageUtil::fixOrientation($uploadFile->getLocation());
-
- // shrink cover photo if necessary
- try {
- $newFileLocation = ImageUtil::enforceDimensions(
- $fileLocation,
- UserCoverPhoto::MAX_WIDTH,
- UserCoverPhoto::MAX_HEIGHT
- );
- }
- /** @noinspection PhpRedundantCatchClauseInspection */
- catch (SystemException $e) {
- $uploadFile->setValidationErrorType('maxSize');
-
- return;
- }
-
- if ($newFileLocation != $fileLocation) {
- // check dimensions (after shrink)
- $imageData = \getimagesize($newFileLocation);
- if ($imageData[0] < UserCoverPhoto::MIN_WIDTH || $imageData[1] < UserCoverPhoto::MIN_HEIGHT) {
- $uploadFile->setValidationErrorType('tooLarge');
-
- return;
- }
-
- // check filesize (after shrink)
- if (@\filesize($newFileLocation) > WCF::getSession()->getPermission('user.profile.coverPhoto.maxSize')) {
- $uploadFile->setValidationErrorType('tooLarge');
-
- return;
- }
- }
- $fileLocation = $newFileLocation;
-
- // delete old cover photo
- if ($this->user->coverPhotoHash) {
- UserProfileRuntimeCache::getInstance()->getObject($this->user->userID)->getCoverPhoto()->delete();
- }
-
- // update user
- (new UserEditor($this->user))->update([
- // always generate a new hash to invalidate the browser cache and to avoid filename guessing
- 'coverPhotoHash' => StringUtil::getRandomID(),
- 'coverPhotoExtension' => $uploadFile->getFileExtension(),
- 'coverPhotoHasWebP' => 0,
- ]);
-
- // force-reload the user profile to use a predictable code-path to fetch the cover photo
- UserProfileRuntimeCache::getInstance()->removeObject($this->user->userID);
- $userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->user->userID);
- $this->coverPhoto = $userProfile->getCoverPhoto();
-
- // check images directory and create subdirectory if necessary
- $coverPhoto = $this->coverPhoto->getLocation(false);
- $dir = \dirname($coverPhoto);
- if (!@\file_exists($dir)) {
- FileUtil::makePath($dir);
- }
-
- // move uploaded file
- if (!@\copy($fileLocation, $coverPhoto)) {
- // copy failed
- @\unlink($fileLocation);
- (new UserEditor($this->user))->update([
- 'coverPhotoHash' => '',
- 'coverPhotoExtension' => '',
- ]);
- $uploadFile->setValidationErrorType('uploadFailed');
- }
-
- $outputFilenameWithoutExtension = \preg_replace('~\.[a-z]+$~', '', $coverPhoto);
- $result = ImageUtil::createWebpVariant($coverPhoto, $outputFilenameWithoutExtension);
- if ($result !== null) {
- $data = ['coverPhotoHasWebP' => 1];
-
- // A fallback jpeg image was just created.
- if ($result === false) {
- $data['coverPhotoExtension'] = 'jpg';
- }
-
- (new UserEditor($this->user))->update($data);
-
- // force-reload the user profile to use a predictable code-path to fetch the cover photo
- UserProfileRuntimeCache::getInstance()->removeObject($this->user->userID);
- $userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->user->userID);
- $this->coverPhoto = $userProfile->getCoverPhoto();
- }
-
- @\unlink($fileLocation);
- }
- }
-}
+++ /dev/null
-<?php
-
-namespace wcf\system\upload;
-
-use wcf\data\user\cover\photo\UserCoverPhoto;
-use wcf\system\image\ImageHandler;
-use wcf\system\WCF;
-use wcf\util\ExifUtil;
-
-/**
- * Upload file validation strategy implementation for user cover photos.
- *
- * @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @since 3.1
- */
-class UserCoverPhotoUploadFileValidationStrategy implements IUploadFileValidationStrategy
-{
- /**
- * list of allowed file extensions
- * @var string[]
- */
- public static $allowedExtensions = ['gif', 'jpg', 'jpeg', 'png', 'webp'];
-
- /**
- * @inheritDoc
- */
- public function validate(UploadFile $uploadFile)
- {
- if ($uploadFile->getErrorCode() != 0) {
- $uploadFile->setValidationErrorType('uploadFailed');
-
- return false;
- }
-
- // validate file extension
- if (!\in_array($uploadFile->getFileExtension(), self::$allowedExtensions)) {
- $uploadFile->setValidationErrorType('fileExtension');
-
- return false;
- }
-
- // check image data
- $imageData = $uploadFile->getImageData();
- if ($imageData === null) {
- $uploadFile->setValidationErrorType('uploadFailed');
-
- return false;
- }
-
- $height = $imageData['height'];
- $width = $imageData['width'];
- $orientation = ExifUtil::getOrientation(ExifUtil::getExifData($uploadFile->getLocation()));
-
- // flip height and width if image is rotated 90 or 270 degrees
- if ($orientation == ExifUtil::ORIENTATION_90_ROTATE || $orientation == ExifUtil::ORIENTATION_270_ROTATE) {
- $height = $imageData['width'];
- $width = $imageData['height'];
- }
-
- // estimate if there is enough memory for a resize, if there is,
- // we do not need to mark an image which is too high or too wide
- // as invalid
- $sufficientMemory = ImageHandler::getInstance()
- ->getAdapter()
- ->checkMemoryLimit($width, $height, $imageData['mimeType']);
-
- // check width
- if ($width < UserCoverPhoto::MIN_WIDTH) {
- $uploadFile->setValidationErrorType('minWidth');
-
- return false;
- } elseif (!$sufficientMemory && $width > UserCoverPhoto::MAX_WIDTH) {
- $uploadFile->setValidationErrorType('maxWidth');
-
- return false;
- }
-
- // check height
- if ($height < UserCoverPhoto::MIN_HEIGHT) {
- $uploadFile->setValidationErrorType('minHeight');
-
- return false;
- } elseif (!$sufficientMemory && $height > UserCoverPhoto::MAX_HEIGHT) {
- $uploadFile->setValidationErrorType('maxHeight');
-
- return false;
- }
-
- // check file size if image will not be resized automatically
- // the file size of resized images is checked in ImageAction::processImages()
- $filesize = $uploadFile->getFilesize();
- if ($width <= UserCoverPhoto::MAX_WIDTH && $height <= UserCoverPhoto::MAX_HEIGHT) {
- if ($filesize > WCF::getSession()->getPermission('user.profile.coverPhoto.maxSize')) {
- $uploadFile->setValidationErrorType('maxSize');
-
- return false;
- }
- }
-
- return true;
- }
-}