<div class="userRank">{@$userProfile->getRank()->getImage()}</div>
{/if}
{/if}
+
+ {if MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && ($userProfile->isAccessible('canViewTrophies') || $userProfile->userID == $__wcf->session->userID) && $userProfile->getSpecialTrophies()|count}
+ <div class="specialTrophyContainer">
+ <ul>
+ {foreach from=$userProfile->getSpecialTrophies() item=trophy}
+ <li><a href="{@$trophy->getLink()}">{@$trophy->renderTrophy(32)}</a></li>
+ {/foreach}
+ </ul>
+ </div>
+ {/if}
{else}
<div class="messageAuthorContainer">
{if $userProfile->username}
{event name='styleFields'}
</section>
{/if}
+
+ {if MODULE_TROPHY && $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') > 0 && $availableTrophies|count}
+ <section class="section">
+ <h2 class="sectionTitle">{lang}wcf.user.trophy.trophies{/lang}</h2>
+ <dl{if $errorField == 'specialTrophies'} class="formError"{/if}>
+ <dt>{lang}wcf.user.trophy.specialTrophies{/lang}</dt>
+ <dd>
+ <ul class="specialTrophiesUl">
+ {if $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') == 1}
+ {foreach from=$availableTrophies item=trophy}
+ <li><label><input type="radio" name="specialTrophies[]" value="{$trophy->getObjectID()}"{if $trophy->getObjectID()|in_array:$specialTrophies} checked{/if}> {@$trophy->renderTrophy(32)} <span>{$trophy->getTitle()}</span></label></li>
+ {/foreach}
+ {else}
+ {foreach from=$availableTrophies item=trophy}
+ <li><label><input type="checkbox" name="specialTrophies[]" value="{$trophy->getObjectID()}"{if $trophy->getObjectID()|in_array:$specialTrophies} checked{/if}> {@$trophy->renderTrophy(32)} <span>{$trophy->getTitle()}</span></label></li>
+ {/foreach}
+ {/if}
+ </ul>
+ {if $errorField == 'specialTrophies'}
+ <small class="innerError">
+ {lang}wcf.user.trophy.specialTrophies.error.{$errorType}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.trophy.specialTrophies.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='trophyFields'}
+ </section>
+ {/if}
{/if}
{if !$optionTree|empty}
</h1>
<div class="contentHeaderDescription">
+ {if MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && ($user->isAccessible('canViewTrophies') || $user->userID == $__wcf->session->userID) && $user->getSpecialTrophies()|count}
+ <div class="specialTrophyUserContainer">
+ <ul>
+ {foreach from=$user->getSpecialTrophies() item=trophy}
+ <li><a href="{@$trophy->getLink()}">{@$trophy->renderTrophy(32)}</a></li>
+ {/foreach}
+ </ul>
+ </div>
+ {/if}
<ul class="inlineList commaSeparated">
{if !$user->isProtected()}
{if $user->isVisibleOption('gender') && $user->gender}<li>{lang}wcf.user.gender.{if $user->gender == 1}male{else}female{/if}{/lang}</li>{/if}
<div class="userInformation">
{include file='userInformation'}
+
+ {if MODULE_TROPHY && $__wcf->session->getPermission('user.profile.trophy.canSeeTrophies') && ($user->isAccessible('canViewTrophies') || $user->userID == $__wcf->session->userID) && $user->getSpecialTrophies()|count}
+ <div class="specialTrophyUserContainer">
+ <ul>
+ {foreach from=$user->getSpecialTrophies() item=trophy}
+ <li><a href="{@$trophy->getLink()}">{@$trophy->renderTrophy(32)}</a></li>
+ {/foreach}
+ </ul>
+ </div>
+ {/if}
{if $user->canViewOnlineStatus() && $user->getLastActivityTime()}
<dl class="plain inlineDataList">
<defaultvalue>1</defaultvalue>
<options>module_trophy</options>
</option>
+ <option name="user.profile.trophy.maxUserSpecialTrophies">
+ <categoryname>user.profile.trophy</categoryname>
+ <optiontype>integer</optiontype>
+ <minvalue>0</minvalue>
+ <maxvalue>5</maxvalue>
+ <defaultvalue>3</defaultvalue>
+ <usersonly>1</usersonly>
+ <options>module_trophy</options>
+ </option>
<!-- /user.profile -->
<option name="user.page.canAddComment">
ConditionHandler::getInstance()->deleteConditions(TrophyConditionHandler::CONDITION_DEFINITION_NAME, [$this->trophy->trophyID]);
}
+ // reset special trophies, if trophy is disabled
+ if ($this->isDisabled) {
+ WCF::getDB()->prepareStatement("DELETE FROM wcf". WCF_N ."_user_special_trophy WHERE trophyID = ?")->execute([$this->trophyID]);
+ UserStorageHandler::getInstance()->resetAll('specialTrophies');
+ }
+
$this->saved();
// show success message
use wcf\system\image\ImageHandler;
use wcf\system\upload\TrophyImageUploadFileValidationStrategy;
use wcf\system\upload\UploadFile;
+use wcf\system\user\storage\UserStorageHandler;
use wcf\system\WCF;
/**
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Trophy
* @since 3.1
+ *
+ * @method TrophyEditor[] getObjects()
+ * @method TrophyEditor getSingleObject()
*/
class TrophyAction extends AbstractDatabaseObjectAction implements IToggleAction, IUploadAction {
/**
return $trophy;
}
+ /**
+ * @inheritDoc
+ */
+ public function delete() {
+ $returnValues = parent::delete();
+
+ UserStorageHandler::getInstance()->resetAll('specialTrophies');
+
+ return $returnValues;
+ }
+
/**
* @inheritDoc
*/
*/
public function toggle() {
foreach ($this->getObjects() as $trophy) {
- /** @var TrophyEditor $trophy */
$trophy->update(['isDisabled' => $trophy->isDisabled ? 0 : 1]);
+
+ if (!$trophy->isDisabled) {
+ WCF::getDB()->prepareStatement("DELETE FROM wcf". WCF_N ."_user_special_trophy WHERE trophyID = ?")->execute([$trophy->trophyID]);
+ }
}
+
+ UserStorageHandler::getInstance()->resetAll('specialTrophies');
}
/**
<?php
namespace wcf\data\user;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
use wcf\data\user\avatar\DefaultAvatar;
use wcf\data\user\avatar\Gravatar;
use wcf\data\user\avatar\IUserAvatar;
use wcf\data\ITitledLinkObject;
use wcf\system\cache\builder\UserGroupPermissionCacheBuilder;
use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\event\EventHandler;
use wcf\system\user\signature\SignatureCache;
use wcf\system\user\storage\UserStorageHandler;
return $this->currentLocation;
}
+ /**
+ * Returns the special trophies for the user.
+ *
+ * @return Trophy[]
+ */
+ public function getSpecialTrophies() {
+ $specialTrophies = UserStorageHandler::getInstance()->getField('specialTrophies', $this->userID);
+
+ if ($specialTrophies === null) {
+ // load special trophies for the user
+ $specialTrophies = [];
+
+ $statement = WCF::getDB()->prepareStatement("SELECT trophyID FROM wcf".WCF_N."_user_special_trophy WHERE userID = ?");
+ $statement->execute([$this->userID]);
+
+ while ($trophyID = $statement->fetchColumn()) {
+ $specialTrophies[] = $trophyID;
+ }
+
+ UserStorageHandler::getInstance()->update($this->userID, 'specialTrophies', serialize($specialTrophies));
+ }
+ else {
+ $specialTrophies = unserialize($specialTrophies);
+ }
+
+ // check if the user has the permission to store these number of trophies,
+ // otherwise, delete the last trophies
+ if (count($specialTrophies) > $this->getPermission('user.profile.trophy.maxUserSpecialTrophies')) {
+ $trophyDeleteIDs = [];
+ while (count($specialTrophies) > $this->getPermission('user.profile.trophy.maxUserSpecialTrophies')) {
+ $trophyDeleteIDs[] = array_pop($specialTrophies);
+ }
+
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('userID = ?', [$this->userID]);
+ $conditionBuilder->add('trophyID IN (?)', [$trophyDeleteIDs]);
+
+ // reset some special trophies
+ WCF::getDB()->prepareStatement("DELETE FROM wcf".WCF_N."_user_special_trophy ".$conditionBuilder)->execute($conditionBuilder->getParameters());
+
+ UserStorageHandler::getInstance()->update($this->userID, 'specialTrophies', serialize($specialTrophies));
+ }
+
+ return TrophyCache::getInstance()->getTrophiesByID($specialTrophies);
+ }
+
/**
* Returns the last activity time.
*
}
}
+ /**
+ * Updates the special trophies.
+ */
+ public function updateSpecialTrophies() {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ foreach ($this->getObjects() as $user) {
+ WCF::getDB()->beginTransaction();
+
+ WCF::getDB()->prepareStatement("DELETE FROM wcf".WCF_N."_user_special_trophy WHERE userID = ?")->execute([$user->userID]);
+
+ if (!empty($this->parameters['trophyIDs'])) {
+ $statement = WCF::getDB()->prepareStatement("INSERT INTO wcf".WCF_N."_user_special_trophy (userID, trophyID) VALUES (?, ?)");
+
+ foreach ($this->parameters['trophyIDs'] as $trophyID) {
+ $statement->execute([
+ $user->userID,
+ $trophyID
+ ]);
+ }
+ }
+
+ WCF::getDB()->commitTransaction();
+
+ UserStorageHandler::getInstance()->reset([$user->userID], 'specialTrophies');
+ }
+ }
+
/**
* Returns the user option handler object.
*
use wcf\data\user\UserAction;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\user\notification\object\UserTrophyNotificationObject;
use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\user\storage\UserStorageHandler;
use wcf\system\WCF;
/**
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\User\Trophy
* @since 3.1
+ *
+ * @method UserTrophyEditor[] getObjects()
+ * @method UserTrophyEditor getSingleObject()
*/
class UserTrophyAction extends AbstractDatabaseObjectAction {
/**
* @inheritDoc
*/
public function delete() {
+ $trophyIDs = $userIDs = [];
+ foreach ($this->getObjects() as $object) {
+ $trophyIDs[] = $object->trophyID;
+ $userIDs[] = $object->userID;
+ }
+
$returnValues = parent::delete();
- $updateUserTrophies = [];
+ // update user special trophies trophies
+ $userTrophies = UserTrophyList::getUserTrophies($userIDs);
+
+ foreach ($userTrophies as $userID => $trophies) {
+ $userTrophyIDs = [];
+ foreach ($trophies as $trophy) {
+ $userTrophyIDs[] = $trophy->trophyID;
+ }
+
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('trophyID NOT IN (?)', [array_unique($userTrophyIDs)]);
+ $conditionBuilder->add('userID = ?', [$userID]);
+ WCF::getDB()->prepareStatement("DELETE FROM wcf". WCF_N ."_user_special_trophy ". $conditionBuilder)->execute($conditionBuilder->getParameters());
+
+ UserStorageHandler::getInstance()->reset([$userID], 'specialTrophies');
+ }
/** @var $object UserTrophyEditor */
- foreach ($this->objects as $object) {
+ foreach ($this->getObjects() as $object) {
if (!isset($updateUserTrophies[$object->userID])) $updateUserTrophies[$object->userID] = 0;
$updateUserTrophies[$object->userID]--;
}
namespace wcf\form;
use wcf\data\language\Language;
use wcf\data\style\Style;
+use wcf\data\trophy\Trophy;
+use wcf\data\trophy\TrophyCache;
use wcf\data\user\option\category\UserOptionCategory;
+use wcf\data\user\trophy\UserTrophyList;
use wcf\data\user\UserAction;
+use wcf\data\user\UserProfile;
+use wcf\data\user\UserProfileAction;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\UserInputException;
use wcf\system\language\LanguageFactory;
*/
public $availableStyles = [];
+ /**
+ * list of available trophies
+ * @var Trophy[]
+ */
+ public $availableTrophies = [];
+
/**
* list of content language ids
* @var integer[]
*/
public $styleID = 0;
+ /**
+ * special trophies
+ * @var integer[]
+ */
+ public $specialTrophies = [];
+
/**
* @inheritDoc
*/
$this->availableContentLanguages = LanguageFactory::getInstance()->getContentLanguages();
$this->availableLanguages = LanguageFactory::getInstance()->getLanguages();
$this->availableStyles = StyleHandler::getInstance()->getAvailableStyles();
+
+ // read available trophies
+ $trophyIDs = array_unique(array_map(function ($userTrophy) {
+ return $userTrophy->trophyID;
+ }, UserTrophyList::getUserTrophies([WCF::getUser()->userID])[WCF::getUser()->userID]));
+
+ $this->availableTrophies = TrophyCache::getInstance()->getTrophiesByID($trophyIDs);
}
}
if (isset($_POST['contentLanguageIDs']) && is_array($_POST['contentLanguageIDs'])) $this->contentLanguageIDs = ArrayUtil::toIntegerArray($_POST['contentLanguageIDs']);
if (isset($_POST['languageID'])) $this->languageID = intval($_POST['languageID']);
if (isset($_POST['styleID'])) $this->styleID = intval($_POST['styleID']);
+ if (isset($_POST['specialTrophies'])) $this->specialTrophies = ArrayUtil::toIntegerArray($_POST['specialTrophies']);
}
}
if (!isset($this->availableStyles[$this->styleID])) {
$this->styleID = 0;
}
+
+ // validate special trophies
+ if (count($this->specialTrophies) > WCF::getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies')) {
+ throw new UserInputException('specialTrophies', 'tooMuch');
+ }
+
+ foreach ($this->specialTrophies as $trophyID) {
+ if (!in_array($trophyID, array_map(function ($trophy) {
+ return $trophy->trophyID;
+ }, $this->availableTrophies))) {
+ throw new UserInputException('specialTrophies' , 'invalid');
+ }
+ }
}
}
$this->languageID = WCF::getUser()->languageID;
}
$this->styleID = WCF::getUser()->styleID;
+
+ $this->specialTrophies = array_unique(array_map(function ($trophy) {
+ return $trophy->trophyID;
+ }, (new UserProfile(WCF::getUser()))->getSpecialTrophies()));
}
}
}
if ($this->category == 'general') {
// reset user language ids cache
UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'languageIDs');
+
+ $userProfileAction = new UserProfileAction([WCF::getUser()->userID], 'updateSpecialTrophies', [
+ 'trophyIDs' => $this->specialTrophies
+ ]);
+ $userProfileAction->executeAction();
}
$this->saved();
'availableContentLanguages' => $this->availableContentLanguages,
'availableLanguages' => $this->availableLanguages,
'availableStyles' => $this->availableStyles,
+ 'availableTrophies' => $this->availableTrophies,
'contentLanguageIDs' => $this->contentLanguageIDs,
'languageID' => $this->languageID,
- 'styleID' => $this->styleID
+ 'styleID' => $this->styleID,
+ 'specialTrophies' => $this->specialTrophies
]);
}
}
font-size: 81px;
}
}
+
+.specialTrophiesUl {
+ display: flex;
+ flex-wrap: wrap;
+
+ > li {
+ width: 300px;
+ padding-bottom: 5px;
+
+ > label {
+ display: flex;
+ align-items: center;
+
+ > span:last-child {
+ margin-left: 5px;
+ }
+ }
+ }
+}
+
+.specialTrophyContainer > ul {
+ display: flex;
+ margin-top: 5px;
+ justify-content: center;
+
+ > li:not(:last-child) {
+ margin-right: 5px;
+ }
+}
+
+
+@include screen-sm-down {
+ .specialTrophyContainer {
+ display: none;
+ }
+}
+
+.specialTrophyUserContainer > ul {
+ display: flex;
+ margin-top: -15px;
+ margin-bottom: 5px;
+
+ > li:not(:last-child) {
+ margin-right: 5px;
+ }
+}
+
+
+.userProfilePreview .specialTrophyUserContainer > ul {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
<item name="wcf.acp.group.option.admin.trophy.canAwardTrophy"><![CDATA[Kann Trophäen verleihen]]></item>
<item name="wcf.acp.group.option.category.user.profile.trophy"><![CDATA[Trophäen]]></item>
<item name="wcf.acp.group.option.user.profile.trophy.canSeeTrophies"><![CDATA[Kann Trophäen sehen]]></item>
+ <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies"><![CDATA[Maximale Anzahl an besonderen Trophäen]]></item> <!-- @TODO: find a better name? -->
+ <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies.description"><![CDATA[Besondere Trophäen können vom Benutzer individuell ausgewählt werden und werden in der Message-Sidebar und im Benutzerprofil angezeigt.]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.user.trophy.showTrophies"><![CDATA[Trophäen von {$user->username} anzeigen]]></item>
<item name="wcf.user.trophy.noTrophies"><![CDATA[Der Benutzer hat noch keine Trophäen]]></item>
<item name="wcf.user.trophy.dialogTitle"><![CDATA[Trophäen von {$username}]]></item>
+ <item name="wcf.user.trophy.trophies"><![CDATA[Trophäen]]></item>
+ <item name="wcf.user.trophy.specialTrophies"><![CDATA[Besondere Trophäen]]></item>
+ <item name="wcf.user.trophy.specialTrophies.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier ihre besonderen Trophäen aus, welche im Profil und in der Nachrichten-Seitenleiste angezeigt werden.]]></item>
+ <item name="wcf.user.trophy.specialTrophies.error.tooMuch"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} maximal {#$__wcf->session->getPermission('user.profile.trophy.maxUserSpecialTrophies')} Trophäen auswählen.]]></item>
+ <item name="wcf.user.trophy.specialTrophies.error.invalid"><![CDATA[Die angegebenen Trophäen sind invalid.]]></item>
</category>
<category name="wcf.acp.trophy">
<item name="wcf.acp.group.option.admin.trophy.canAwardTrophy"><![CDATA[Can award trophies]]></item>
<item name="wcf.acp.group.option.category.user.profile.trophy"><![CDATA[Trophies]]></item>
<item name="wcf.acp.group.option.user.profile.trophy.canSeeTrophies"><![CDATA[Can see trophies]]></item>
+ <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies"><![CDATA[Maximum number of special trophies]]></item> <!-- @TODO: find a better name? -->
+ <item name="wcf.acp.group.option.user.profile.trophy.maxUserSpecialTrophies.description"><![CDATA[Special trophies can be individually selected by the user and displayed in the message sidebar and the user profile.]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.user.trophy.showTrophies"><![CDATA[Display Trophies of {$user->username}]]></item>
<item name="wcf.user.trophy.noTrophies"><![CDATA[The user has no trophies]]></item>
<item name="wcf.user.trophy.dialogTitle"><![CDATA[Trophies of {$username}]]></item>
+ <item name="wcf.user.trophy.trophies"><![CDATA[Trophies]]></item>
+ <item name="wcf.user.trophy.specialTrophies"><![CDATA[Special Trophies]]></item>
+ <item name="wcf.user.trophy.specialTrophies.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier ihre besonderen Trophäen aus, welche im Profil und in der Nachrichten-Seitenleiste angezeigt werden.]]></item>
+ <item name="wcf.user.trophy.specialTrophies.error.tooMuch"><![CDATA[You can choose a maximum of {#$__wcf->session->getPermission('user.profile.trophy.maxUserSpecialTrophies')} trophies.]]></item>
+ <item name="wcf.user.trophy.specialTrophies.error.invalid"><![CDATA[The selected trophies are invalid.]]></item>
</category>
<category name="wcf.acp.trophy">
UNIQUE KEY (userID, ignoreUserID)
);
+DROP TABLE IF EXISTS wcf1_user_special_trophy;
+CREATE TABLE wcf1_user_special_trophy(
+ trophyID INT(10) NOT NULL,
+ userID INT(10) NOT NULL,
+ UNIQUE KEY (trophyID, userID)
+);
+
DROP TABLE IF EXISTS wcf1_user_trophy;
CREATE TABLE wcf1_user_trophy(
userTrophyID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_special_trophy ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_special_trophy ADD FOREIGN KEY (trophyID) REFERENCES wcf1_trophy (trophyID) ON DELETE CASCADE;
+
ALTER TABLE wcf1_message_embedded_object ADD FOREIGN KEY (messageObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
ALTER TABLE wcf1_message_embedded_object ADD FOREIGN KEY (embeddedObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;