<optiontype>boolean</optiontype>
<defaultvalue>1</defaultvalue>
</option>
+ <option name="module_user_cover_photo">
+ <categoryname>module.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
<option name="module_like">
<categoryname>module.community</categoryname>
{/capture}
{capture assign='contentHeader'}
- <header class="contentHeader userProfileUser"{if $isAccessible}
+ <header class="contentHeader userProfileUser{if MODULE_USER_COVER_PHOTO} userProfileUserWithCoverPhoto{/if}"{if $isAccessible}
data-object-id="{@$user->userID}"
{if $__wcf->session->getPermission('admin.user.canBanUser')}
data-banned="{@$user->banned}"
data-is-disabled="{if $user->activationCode}true{else}false{/if}"
{/if}
{/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}
+ </div>
+ {/if}
<div class="contentHeaderIcon">
{if $user->userID == $__wcf->user->userID}
<a href="{link controller='AvatarEdit'}{/link}" class="jsTooltip" title="{lang}wcf.user.avatar.edit{/lang}">{@$user->getAvatar()->getImageTag(128)}</a>
<div class="contentHeaderTitle">
<h1 class="contentTitle">
- {$user->username}
+ <span class="userProfileUsername">{if $user->username === 'root'}Alexander Ebert{else}{$user->username}{/if}</span>
{if $user->banned}<span class="icon icon24 fa-lock jsTooltip jsUserBanned" title="{lang}wcf.user.banned{/lang}"></span>{/if}
{if MODULE_USER_RANK}
{if $user->getUserTitle()}
<category name="user.profile.avatar">
<parent>user.profile</parent>
</category>
+ <category name="user.profile.coverPhoto">
+ <parent>user.profile</parent>
+ </category>
<category name="user.profile.trophy">
<parent>user.profile</parent>
</category>
png</defaultvalue>
<usersonly>1</usersonly>
</option>
+
+ <option name="user.profile.coverPhoto.canSeeCoverPhotos">
+ <categoryname>user.profile.coverPhoto</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.profile.coverPhoto.canUploadCoverPhoto">
+ <categoryname>user.profile.coverPhoto</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ <usersonly>1</usersonly>
+ </option>
+
<option name="user.profile.trophy.canSeeTrophies">
<categoryname>user.profile.trophy</categoryname>
<optiontype>boolean</optiontype>
define('FORCE_LOGIN', 0);
define('DESKTOP_NOTIFICATION_PACKAGE_ID', 1);
define('PAGE_LOGO_LINK_TO_APP_DEFAULT', 1);
+define('MODULE_USER_COVER_PHOTO', 1);
* @property-read string $notificationMailToken token used for authenticating requests by the user to disable notification emails
* @property-read string $authData data of the third party used for authentication
* @property-read integer $likesReceived cumulative result of likes (counting +1) and dislikes (counting -1) the user's contents have received
+ * @property-read string $coverPhotoHash hash of the user's cover photo
+ * @property-read string $coverPhotoExtension extension of the user's cover photo file
+ * @property-read integer $disableCoverPhoto is `1` if the user's cover photo has been disabled, otherwise `0`
+ * @property-read integer $disableCoverPhotoExpires timestamp at which the user's cover photo will automatically be enabled again
*/
final class User extends DatabaseObject implements IRouteController, IUserContent {
/**
use wcf\data\user\avatar\Gravatar;
use wcf\data\user\avatar\IUserAvatar;
use wcf\data\user\avatar\UserAvatar;
+use wcf\data\user\cover\photo\DefaultUserCoverPhoto;
+use wcf\data\user\cover\photo\IUserCoverPhoto;
+use wcf\data\user\cover\photo\UserCoverPhoto;
use wcf\data\user\group\UserGroup;
use wcf\data\user\online\UserOnline;
use wcf\data\user\option\ViewableUserOption;
* list of ignored user ids
* @var integer[]
*/
- protected $ignoredUserIDs = null;
+ protected $ignoredUserIDs;
/**
* list of follower user ids
* @var integer[]
*/
- protected $followerUserIDs = null;
+ protected $followerUserIDs;
/**
* list of following user ids
* @var integer[]
*/
- protected $followingUserIDs = null;
+ protected $followingUserIDs;
/**
* user avatar
* @var IUserAvatar
*/
- protected $avatar = null;
+ protected $avatar;
/**
* user rank object
* @var UserRank
*/
- protected $rank = null;
+ protected $rank;
/**
* age of this user
* @var integer
*/
- protected $__age = null;
+ protected $__age;
/**
* group data and permissions
* @var mixed[][]
*/
- protected $groupData = null;
+ protected $groupData;
/**
* current location of this user.
* @var string
*/
- protected $currentLocation = null;
+ protected $currentLocation;
+
+ /**
+ * user cover photo
+ * @var UserCoverPhoto
+ */
+ protected $coverPhoto;
const GENDER_MALE = 1;
const GENDER_FEMALE = 2;
return (WCF::getUser()->userID == $this->userID || WCF::getSession()->getPermission('user.profile.avatar.canSeeAvatars'));
}
+ /**
+ * Returns the user's cover photo.
+ *
+ * @return IUserCoverPhoto
+ */
+ public function getCoverPhoto() {
+ if ($this->coverPhoto === null) {
+ if (!$this->disableCoverPhoto && $this->coverPhotoHash) {
+ if ($this->canSeeCoverPhoto()) {
+ $this->coverPhoto = new UserCoverPhoto($this->userID, $this->coverPhotoHash, $this->coverPhotoExtension);
+ }
+ }
+
+ // use default cover photo
+ if ($this->coverPhoto === null) {
+ $this->coverPhoto = new DefaultUserCoverPhoto();
+ }
+ }
+
+ return $this->coverPhoto;
+ }
+
+ /**
+ * Returns true if the active user can view the cover photo of this user.
+ *
+ * @return boolean
+ */
+ public function canSeeCoverPhoto() {
+ return (WCF::getUser()->userID == $this->userID || WCF::getSession()->getPermission('user.profile.coverPhoto.canSeeCoverPhotos'));
+ }
+
/**
* Returns true if this user is currently online.
*
--- /dev/null
+<?php
+namespace wcf\data\user\cover\photo;
+use wcf\system\style\StyleHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a default cover photo.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\User\Cover\Photo
+ */
+class DefaultUserCoverPhoto implements IUserCoverPhoto {
+ /**
+ * @inheritDoc
+ */
+ public function getLocation() {
+ return WCF_DIR . 'images/coverPhotos/' . $this->getFilename();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getURL() {
+ return WCF::getPath() . 'images/coverPhotos/' . $this->getFilename();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFilename() {
+ /*if ($coverPhoto = StyleHandler::getInstance()->getStyle()->getDefaultCoverPhoto()) {
+ return $coverPhoto;
+ }*/
+
+ return 'default.png';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\cover\photo;
+
+/**
+ * Any displayable cover photo type should implement this class.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\User\Cover\Photo
+ */
+interface IUserCoverPhoto {
+
+ /**
+ * Returns the physical location of this cover photo.
+ *
+ * @return string
+ */
+ public function getLocation();
+
+ /**
+ * Returns the url to this cover photo.
+ *
+ * @return string
+ */
+ public function getURL();
+
+ /**
+ * Returns the file name of this cover photo.
+ *
+ * @return string
+ */
+ public function getFilename();
+}
--- /dev/null
+<?php
+namespace wcf\data\user\cover\photo;
+use wcf\system\WCF;
+
+/**
+ * Represents a user's cover photo.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\User\Cover\Photo
+ */
+class UserCoverPhoto implements IUserCoverPhoto {
+ /**
+ * file extension
+ * @var string
+ */
+ protected $coverPhotoExtension;
+
+ /**
+ * file hash
+ * @var string
+ */
+ protected $coverPhotoHash;
+
+ /**
+ * user id
+ * @var integer
+ */
+ protected $userID;
+
+ const MAX_HEIGHT = 400;
+ const MAX_WIDTH = 1600;
+ const MIN_HEIGHT = 200;
+ const MIN_WIDTH = 800;
+
+ /**
+ * UserCoverPhoto constructor.
+ *
+ * @param integer $userID
+ * @param string $coverPhotoHash
+ * @param string $coverPhotoExtension
+ */
+ public function __construct($userID, $coverPhotoHash, $coverPhotoExtension) {
+ $this->userID = $userID;
+ $this->coverPhotoHash = $coverPhotoHash;
+ $this->coverPhotoExtension = $coverPhotoExtension;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getLocation() {
+ return WCF_DIR . 'images/coverPhotos/' . $this->getFilename();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getURL() {
+ return WCF::getPath() . 'images/coverPhotos/' . $this->getFilename();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFilename() {
+ return substr($this->coverPhotoHash, 0, 2) . '/' . $this->userID . '-' . $this->coverPhotoHash . '.' . $this->coverPhotoExtension;
+ }
+}
display: flex;
flex-wrap: wrap;
+ .contentHeaderNavigation {
+ flex: 0 0 100%;
+ }
+ }
+
+ @include screen-sm {
+ .contentHeaderIcon {
+ display: block;
+ flex: 0 0 96px;
+ margin-right: 15px;
+
+ img {
+ width: 96px !important;
+ height: 96px !important;
+ }
+ }
+
+ .contentHeaderTitle {
+ flex: 0 0 calc(100% - 111px);
+ max-width: calc(100% - 11px);
+ }
+ }
+
+ @include screen-xs {
.contentHeaderIcon {
display: block;
flex: 0 0 48px;
max-width: calc(100% - 58px);
}
- .contentHeaderNavigation {
- flex: 0 0 100%;
- }
- }
-
- @include screen-xs {
.contentHeaderNavigation {
.userProfileButtonContainer {
display: flex;
}
}
+/* user profile cover photo */
+.userProfileUserWithCoverPhoto {
+ padding-top: 165px;
+ position: relative;
+
+ .userProfileCoverPhoto {
+ background: no-repeat center;
+ background-size: cover;
+ border-radius: 3px;
+ height: 200px;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+
+ &::after {
+ background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 70%, rgba(0, 0, 0, .5) 100%);
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+ bottom: 0;
+ content: "";
+ display: block;
+ left: 0;
+ pointer-events: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
+
+ .jsButtonEditCoverPhoto {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ }
+ }
+
+ .contentHeaderTitle {
+ /* avoid being covered by the photo */
+ z-index: 10;
+ }
+
+ .userProfileUsername {
+ color: #fff;
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
+ }
+
+ @include screen-md-up {
+ .contentHeaderIcon {
+ flex: 0 0 138px;
+ margin-top: -29px; /* 35px photo overlap - (128px height / 2) */
+ padding-left: 10px;
+ }
+
+ .contentHeaderDescription {
+ margin-top: 15px !important;
+ }
+
+ .contentHeaderNavigation {
+ padding-top: 45px;
+ }
+ }
+
+ @include screen-sm-up {
+ .contentHeaderIcon .badgeOnline {
+ left: 10px !important;
+ }
+
+ .userProfileUsername + .badge {
+ margin-left: 5px;
+ }
+ }
+
+ @include screen-sm {
+ padding-top: 170px;
+
+ .contentHeaderIcon {
+ margin-top: -18px; /* 30px photo overlap - (96px height / 2) */
+ padding-left: 5px;
+ }
+
+ .contentHeaderDescription {
+ margin-top: 10px !important;
+ }
+ }
+
+ @include screen-xs {
+ padding-top: 120px;
+
+ .userProfileCoverPhoto {
+ height: 150px;
+ }
+
+ .contentHeaderIcon {
+ margin-top: 6px; /* 30px photo overlap - (48px height / 2) */
+ padding-left: 5px;
+ }
+
+ .contentTitle {
+ margin-bottom: 35px;
+ position: relative;
+ }
+
+ .userProfileUsername {
+ display: block;
+ }
+
+ .userProfileUsername + .badge {
+ margin-left: 0;
+ position: absolute;
+ top: 35px !important;
+ }
+
+ .contentHeaderDescription {
+ margin-left: -58px;
+ }
+ }
+}
+
.userTitleBadge {
max-width: 154px;
overflow: hidden;
<item name="wcf.user.condition.trophyPoints"><![CDATA[Trophäen]]></item>
</category>
+ <category name="wcf.user.coverPhoto">
+ <item name="wcf.user.coverPhoto.edit"><![CDATA[Profilbild bearbeiten]]></item>
+ </category>
+
<category name="wcf.user.notification">
<item name="wcf.user.notification.button.confirmed"><![CDATA[OK]]></item>
<item name="wcf.user.notification.count"><![CDATA[if (data.returnValues.count == 0) { "Keine Benachrichtigungen" } else if (data.returnValues.count == 1) { "Eine Benachrichtigung" } else { data.returnValues.count + " Benachrichtigungen" }]]></item>
<item name="wcf.user.condition.trophyPoints"><![CDATA[Trophies]]></item>
</category>
+ <category name="wcf.user.coverPhoto">
+ <item name="wcf.user.coverPhoto.edit"><![CDATA[Edit Cover Photo]]></item>
+ </category>
+
<category name="wcf.user.notification">
<item name="wcf.user.notification.button.confirmed"><![CDATA[OK]]></item>
<item name="wcf.user.notification.count"><![CDATA[if (data.returnValues.count == 0) { "No Notifications" } else if (data.returnValues.count == 1) { "1 Notification" } else { data.returnValues.count + " Notifications" }]]></item>