{/if}>
{if MODULE_USER_COVER_PHOTO}
<div class="userProfileCoverPhoto" style="background-image: url({$user->getCoverPhoto()->getURL()})">
- {if $user->userID == $__wcf->user->userID}<a href="#" class="button small jsButtonEditCoverPhoto"><span class="icon icon16 fa-pencil"></span> {lang}wcf.user.coverPhoto.edit{/lang}</a>{/if}
+ {if $user->userID == $__wcf->user->userID && ($__wcf->getSession()->getPermission('user.profile.coverPhoto.canUploadCoverPhoto') || $user->coverPhotoHash)}
+ <div class="userProfileManageCoverPhoto dropdown jsOnly">
+ <a href="#" class="button small dropdownToggle"><span class="icon icon16 fa-pencil"></span> {lang}wcf.user.coverPhoto.edit{/lang}</a>
+ <ul class="dropdownMenu">
+ {if $__wcf->getSession()->getPermission('user.profile.coverPhoto.canUploadCoverPhoto')}
+ <li><a href="#" class="jsButtonUploadCoverPhoto jsStaticDialog" data-dialog-id="userProfileCoverPhotoUpload">{lang}wcf.user.coverPhoto.upload{/lang}</a></li>
+ {/if}
+ <li><a href="#" class="jsButtonDeleteCoverPhoto"{if !$user->coverPhotoHash} style="display:none;"{/if}>{lang}wcf.user.coverPhoto.delete{/lang}</a></li>
+ </ul>
+ </div>
+ {/if}
</div>
{/if}
<div class="contentHeaderIcon">
<p class="info">{lang}wcf.user.profile.protected{/lang}</p>
{/if}
+{if MODULE_USER_COVER_PHOTO && $user->userID == $__wcf->user->userID}
+ {if $__wcf->getSession()->getPermission('user.profile.coverPhoto.canUploadCoverPhoto')}
+ <div id="userProfileCoverPhotoUpload" class="jsStaticDialogContent" data-title="{lang}wcf.user.coverPhoto.upload{/lang}">
+ {lang}wcf.user.coverPhoto.description{/lang}
+
+ {if $__wcf->user->disableCoverPhoto}
+ <p class="error">{lang}wcf.user.coverPhoto.error.disabled{/lang}</p>
+ {else}
+ <div id="coverPhotoUploadPreview"></div>
+
+ {* placeholder for the upload button *}
+ <div id="coverPhotoUploadButtonContainer"></div>
+ {/if}
+ </div>
+ <script data-relocate="true">
+ require(['Language', 'WoltLabSuite/Core/Ui/User/CoverPhoto/Upload'], function (Language, UiUserCoverPhotoUpload) {
+ Language.addObject({
+ 'wcf.user.coverPhoto.upload.error.invalidExtension': '{lang}wcf.user.coverPhoto.upload.error.invalidExtension{/lang}',
+ 'wcf.user.coverPhoto.upload.error.tooSmall': '{lang}wcf.user.coverPhoto.upload.error.tooSmall{/lang}',
+ 'wcf.user.coverPhoto.upload.error.tooLarge': '{lang}wcf.user.coverPhoto.upload.error.tooLarge{/lang}',
+ 'wcf.user.coverPhoto.upload.error.uploadFailed': '{lang}wcf.user.coverPhoto.upload.error.uploadFailed{/lang}',
+ 'wcf.user.coverPhoto.upload.error.badImage': '{lang}wcf.user.coverPhoto.upload.error.badImage{/lang}',
+ 'wcf.user.coverPhoto.upload.success': '{lang}wcf.user.coverPhoto.upload.success{/lang}'
+ });
+
+ {if !$__wcf->user->disableCoverPhoto}
+ new UiUserCoverPhotoUpload();
+ {/if}
+ });
+ </script>
+ {/if}
+ <script data-relocate="true">
+ require(['WoltLabSuite/Core/Ui/User/CoverPhoto/Delete'], function (UiUserCoverPhotoDelete) {
+ UiUserCoverPhotoDelete.init();
+ })
+ </script>
+{/if}
+
{include file='footer'}
<defaultvalue>1</defaultvalue>
<usersonly>1</usersonly>
</option>
+ <option name="user.profile.coverPhoto.maxSize">
+ <categoryname>user.profile.coverPhoto</categoryname>
+ <optiontype>fileSize</optiontype>
+ <defaultvalue>500000</defaultvalue>
+ <minvalue>100000</minvalue>
+ <usersonly>1</usersonly>
+ </option>
<option name="user.profile.trophy.canSeeTrophies">
<categoryname>user.profile.trophy</categoryname>
--- /dev/null
+/**
+ * Deletes the current user cover photo.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
+ */
+define(['Ajax', 'EventHandler', 'Language', 'Ui/Confirmation', 'Ui/Notification'], function (Ajax, EventHandler, Language, UiConfirmation, UiNotification) {
+ "use strict";
+
+ var _button;
+
+ /**
+ * @exports WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
+ */
+ return {
+ /**
+ * Initializes the delete handler and enables the delete button on upload.
+ */
+ init: function () {
+ _button = elBySel('.jsButtonDeleteCoverPhoto');
+ _button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+
+ EventHandler.add('com.woltlab.wcf.user', 'coverPhoto', function (data) {
+ if (typeof data.url === 'string' && data.url.length > 0) {
+ elShow(_button.parentNode);
+ }
+ });
+ },
+
+ /**
+ * Handles clicks on the delete button.
+ *
+ * @protected
+ */
+ _click: function () {
+ UiConfirmation.show({
+ confirm: Ajax.api.bind(Ajax, this),
+ message: Language.get('wcf.user.coverPhoto.delete.confirmMessage')
+ });
+ },
+
+ _ajaxSuccess: function (data) {
+ elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
+
+ elHide(_button.parentNode);
+
+ UiNotification.show();
+ },
+
+ _ajaxSetup: function () {
+ return {
+ data: {
+ actionName: 'deleteCoverPhoto',
+ className: 'wcf\\data\\user\\UserProfileAction'
+ }
+ };
+ }
+ };
+});
--- /dev/null
+/**
+ * Uploads the user cover photo via AJAX.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/User/CoverPhoto/Upload
+ */
+define(['Core', 'EventHandler', 'Upload', 'Ui/Notification', 'Ui/Dialog'], function(Core, EventHandler, Upload, UiNotification, UiDialog) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function UiUserCoverPhotoUpload() {
+ Upload.call(this, 'coverPhotoUploadButtonContainer', 'coverPhotoUploadPreview', {
+ action: 'uploadCoverPhoto',
+ className: 'wcf\\data\\user\\UserProfileAction'
+ });
+ }
+ Core.inherit(UiUserCoverPhotoUpload, Upload, {
+ /**
+ * @see WoltLabSuite/Core/Upload#_success
+ */
+ _success: function(uploadId, data) {
+ // remove or display the error message
+ elInnerError(this._button, data.returnValues.errorMessage);
+
+ // remove the upload progress
+ this._target.innerHTML = '';
+
+ if (data.returnValues.url) {
+ elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
+
+ UiDialog.close('userProfileCoverPhotoUpload');
+ UiNotification.show();
+
+ EventHandler.fire('com.woltlab.wcf.user', 'coverPhoto', {
+ url: data.returnValues.url
+ });
+ }
+ }
+ });
+
+ return UiUserCoverPhotoUpload;
+});
use wcf\system\html\output\HtmlOutputProcessor;
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\UserCoverPhotoUploadFileValidationStrategy;
use wcf\system\user\group\assignment\UserGroupAssignmentHandler;
use wcf\system\user\storage\UserStorageHandler;
use wcf\system\WCF;
use wcf\util\ArrayUtil;
+use wcf\util\FileUtil;
use wcf\util\MessageUtil;
use wcf\util\StringUtil;
* user profile object
* @var UserProfile
*/
- public $userProfile = null;
+ public $userProfile;
+
+ /**
+ * uploaded file
+ * @var UploadFile
+ */
+ public $uploadFile;
/**
* Validates parameters for signature preview.
}
}
+ /**
+ * Validates the 'uploadCoverPhoto' method.
+ *
+ * @throws PermissionDeniedException
+ * @throws UserInputException
+ * @since 3.1
+ */
+ public function validateUploadCoverPhoto() {
+ if (!MODULE_USER_COVER_PHOTO) {
+ throw new PermissionDeniedException();
+ }
+
+ WCF::getSession()->checkPermissions(['user.profile.coverPhoto.canUploadCoverPhoto']);
+
+ // 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() {
+ 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()
+ ];
+ }
+
+ // delete old cover photo
+ if (WCF::getUser()->coverPhotoHash) {
+ UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID)->getCoverPhoto()->delete();
+ }
+
+ // update user
+ (new UserEditor(WCF::getUser()))->update([
+ // always generate a new hash to invalidate the browser cache and to avoid filename guessing
+ 'coverPhotoHash' => StringUtil::getRandomID(),
+ 'coverPhotoExtension' => $this->uploadFile->getFileExtension()
+ ]);
+
+ // force-reload the user profile to use a predictable code-path to fetch the cover photo
+ UserProfileRuntimeCache::getInstance()->removeObject(WCF::getUser()->userID);
+ $userProfile = UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID);
+ $coverPhoto = $userProfile->getCoverPhoto();
+
+ // check images directory and create subdirectory if necessary
+ $dir = dirname($coverPhoto->getLocation());
+ if (!@file_exists($dir)) {
+ FileUtil::makePath($dir);
+ }
+
+ if (@move_uploaded_file($this->uploadFile->getLocation(), $coverPhoto->getLocation())) {
+ return [
+ 'url' => $coverPhoto->getURL()
+ ];
+ }
+ else {
+ return [
+ 'filesize' => $this->uploadFile->getFilesize(),
+ 'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.user.coverPhoto.upload.error.uploadFailed', [
+ 'file' => $this->uploadFile
+ ]),
+ 'errorType' => 'uploadFailed'
+ ];
+ }
+ }
+
+ public function validateDeleteCoverPhoto() {
+ if (!MODULE_USER_COVER_PHOTO) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ public function deleteCoverPhoto() {
+ if (WCF::getUser()->coverPhotoHash) {
+ UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID)->getCoverPhoto()->delete();
+
+ (new UserEditor(WCF::getUser()))->update([
+ 'coverPhotoHash' => null,
+ 'coverPhotoExtension' => ''
+ ]);
+ }
+
+ // force-reload the user profile to use a predictable code-path to fetch the cover photo
+ UserProfileRuntimeCache::getInstance()->removeObject(WCF::getUser()->userID);
+
+ return [
+ 'url' => UserProfileRuntimeCache::getInstance()->getObject(WCF::getUser()->userID)->getCoverPhoto()->getURL()
+ ];
+ }
+
/**
* Returns the user option handler object.
*
* @package WoltLabSuite\Core\Data\User\Cover\Photo
*/
class DefaultUserCoverPhoto implements IUserCoverPhoto {
+ /**
+ * @inheritDoc
+ */
+ public function delete() {
+ /* NOP */
+ }
+
/**
* @inheritDoc
*/
* @package WoltLabSuite\Core\Data\User\Cover\Photo
*/
interface IUserCoverPhoto {
+ /**
+ * Deletes this cover photo.
+ */
+ public function delete();
/**
* Returns the physical location of this cover photo.
$this->coverPhotoExtension = $coverPhotoExtension;
}
+ /**
+ * @inheritDoc
+ */
+ public function delete() {
+ if (file_exists($this->getLocation())) {
+ @unlink($this->getLocation());
+ }
+ }
+
/**
* @inheritDoc
*/
--- /dev/null
+<?php
+namespace wcf\system\upload;
+use wcf\data\user\cover\photo\UserCoverPhoto;
+use wcf\system\WCF;
+use wcf\util\ExifUtil;
+use wcf\util\FileUtil;
+
+/**
+ * Upload file validation strategy implementation for user cover photos.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Gallery\System\Upload
+ * @since 3.1
+ */
+class UserCoverPhotoUploadFileValidationStrategy implements IUploadFileValidationStrategy {
+ /**
+ * list of allowed file extensions
+ * @var string[]
+ */
+ public static $allowedExtensions = ['gif', 'jpg', 'jpeg', 'png'];
+
+ /**
+ * @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('noImage');
+
+ 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 = FileUtil::checkMemoryLimit($width * $height * ($uploadFile->getFileExtension() == 'png' ? 4 : 3) * 2.1);
+
+ // check width
+ if ($width < UserCoverPhoto::MIN_WIDTH) {
+ $uploadFile->setValidationErrorType('minWidth');
+
+ return false;
+ }
+ else if (!$sufficientMemory && $width > UserCoverPhoto::MAX_WIDTH) {
+ $uploadFile->setValidationErrorType('maxWidth');
+
+ return false;
+ }
+
+ // check height
+ if ($height < UserCoverPhoto::MIN_HEIGHT) {
+ $uploadFile->setValidationErrorType('minHeight');
+
+ return false;
+ }
+ else if (!$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;
+ }
+}
top: 0;
}
- .jsButtonEditCoverPhoto {
+ .userProfileManageCoverPhoto {
position: absolute;
right: 10px;
top: 10px;
</category>
<category name="wcf.user.coverPhoto">
+ <item name="wcf.user.coverPhoto.delete"><![CDATA[Profilbild löschen]]></item>
+ <item name="wcf.user.coverPhoto.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du dein{else}Wollen Sie Ihr{/if} Profilbild wirklich löschen? Nach dem Löschen wird wieder das Standard-Bild angezeigt.]]></item>
<item name="wcf.user.coverPhoto.edit"><![CDATA[Profilbild bearbeiten]]></item>
+ <item name="wcf.user.coverPhoto.upload"><![CDATA[Profilbild hochladen]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.badImage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} kein gültiges Bild hochgeladen.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.invalidExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.maxHeight"><![CDATA[Das Bild ist zu hoch.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.maxSize"><![CDATA[Die Datei ist zu groß.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.maxWidth"><![CDATA[Das Bild ist zu breit.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.minHeight"><![CDATA[Das Bild ist zu niedrig.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.minWidth"><![CDATA[Das Bild ist zu schmal.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.uploadFailed"><![CDATA[Beim Hochladen der Datei ist ein unbekannter Fehler aufgetreten.]]></item>
</category>
<category name="wcf.user.notification">
</category>
<category name="wcf.user.coverPhoto">
+ <item name="wcf.user.coverPhoto.delete"><![CDATA[Delete Cover Photo]]></item>
+ <item name="wcf.user.coverPhoto.delete.confirmMessage"><![CDATA[Do you really want to delete your cover photo? This will replace your current photo with the default image.]]></item>
<item name="wcf.user.coverPhoto.edit"><![CDATA[Edit Cover Photo]]></item>
+ <item name="wcf.user.coverPhoto.upload"><![CDATA[Upload Cover Photo]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.badImage"><![CDATA[The uploaded file is not an image.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.invalidExtension"><![CDATA[The file has an invalid extension.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.maxHeight"><![CDATA[The image is too tall.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.maxSize"><![CDATA[The file is too big.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.maxWidth"><![CDATA[The image is too wide.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.minHeight"><![CDATA[The image is too small.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.minWidth"><![CDATA[The image is too small.]]></item>
+ <item name="wcf.user.coverPhoto.upload.error.uploadFailed"><![CDATA[An unknown error occurred during the upload.]]></item>
</category>
<category name="wcf.user.notification">
authData VARCHAR(191) NOT NULL DEFAULT '',
likesReceived MEDIUMINT(7) NOT NULL DEFAULT 0,
trophyPoints INT(10) NOT NULL DEFAULT 0,
+ coverPhotoHash CHAR(40) DEFAULT NULL,
+ coverPhotoExtension VARCHAR(4) NOT NULL DEFAULT '',
+ disableCoverPhoto TINYINT(1) NOT NULL DEFAULT 0,
+ disableCoverPhotoExpires INT(10) NOT NULL DEFAULT 0,
KEY username (username),
KEY email (email),