Add support for revocable trophies
authorJoshua Rüsweg <josh@bastelstu.be>
Tue, 7 Aug 2018 13:12:54 +0000 (15:12 +0200)
committerJoshua Rüsweg <josh@bastelstu.be>
Tue, 7 Aug 2018 13:12:54 +0000 (15:12 +0200)
See #2665

wcfsetup/install/files/acp/templates/trophyAdd.tpl
wcfsetup/install/files/lib/acp/form/TrophyAddForm.class.php
wcfsetup/install/files/lib/acp/form/TrophyEditForm.class.php
wcfsetup/install/files/lib/data/trophy/Trophy.class.php
wcfsetup/install/files/lib/data/user/trophy/UserTrophyAction.class.php
wcfsetup/install/files/lib/system/cronjob/AssignTrophiesCronjob.class.php
wcfsetup/install/files/lib/system/trophy/condition/TrophyConditionHandler.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index c62a82ce1f651866fcc0b27a6c3483ff7328bd01..64a9b883bef02c59dc646957f393f60bf658ccd9 100644 (file)
                elBySel('input[name=awardAutomatically]').addEventListener('change', function () {
                        var awardAutomatically = elBySel('input[name=awardAutomatically]').checked;
                        elBySelAll('.conditionSection', null, window[(awardAutomatically ? 'elShow' : 'elHide')]);
+                       
+                       if (awardAutomatically) {
+                               elShow(elById('revokeAutomaticallyDL'));
+                       }
+                       else {
+                               elHide(elById('revokeAutomaticallyDL'));
+                       }
                });
                
                BadgeHandler.init(); 
                                </dd>
                        </dl>
                        
+                       <dl id="revokeAutomaticallyDL"{if !$awardAutomatically} style="display: none;"{/if}>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="revokeAutomatically" value="1"{if $revokeAutomatically} checked{/if}> {lang}wcf.acp.trophy.revokeAutomatically{/lang}</label>
+                               </dd>
+                       </dl>
+                       
                        <dl>
                                <dt><label for="type">{lang}wcf.acp.trophy.type{/lang}</label></dt>
                                <dd>
index 04c1a46ee4741730c5957d7e4d236ee123ae0380..bef34411825d0b32dff19323f1ae703824e82a1e 100644 (file)
@@ -119,6 +119,12 @@ class TrophyAddForm extends AbstractAcpForm {
         */
        public $awardAutomatically = 0;
        
+       /**
+        * `1` if the trophy is automatically revoked, if the conditions are not longer fulfilled
+        * @var int
+        */
+       public $revokeAutomatically = 0;
+       
        /**
         * `1` if the trophy contains html in the description
         * @var int
@@ -183,6 +189,7 @@ class TrophyAddForm extends AbstractAcpForm {
                if (isset($_POST['iconColor'])) $this->iconColor = $_POST['iconColor'];
                if (isset($_POST['badgeColor'])) $this->badgeColor = $_POST['badgeColor'];
                if (isset($_POST['awardAutomatically'])) $this->awardAutomatically = 1;
+               if (isset($_POST['revokeAutomatically']) && $this->awardAutomatically) $this->revokeAutomatically = 1;
                if (isset($_POST['trophyUseHtml'])) $this->trophyUseHtml = 1;
                if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
                
@@ -295,6 +302,7 @@ class TrophyAddForm extends AbstractAcpForm {
                                'type' => $this->type,
                                'isDisabled' => $this->isDisabled,
                                'awardAutomatically' => $this->awardAutomatically,
+                               'revokeAutomatically' => $this->revokeAutomatically,
                                'trophyUseHtml' => $this->trophyUseHtml,
                                'showOrder' => $this->showOrder
                        ]),
@@ -328,7 +336,7 @@ class TrophyAddForm extends AbstractAcpForm {
        public function reset() {
                parent::reset();
                
-               $this->isDisabled = $this->awardAutomatically = $this->categoryID = $this->trophyUseHtml = $this->showOrder = 0;
+               $this->isDisabled = $this->awardAutomatically = $this->categoryID = $this->trophyUseHtml = $this->showOrder = $this->revokeAutomatically = 0;
                $this->type = Trophy::TYPE_BADGE;
                $this->iconName = $this->uploadedImageURL = '';
                $this->iconColor = 'rgba(255, 255, 255, 1)';
@@ -359,6 +367,7 @@ class TrophyAddForm extends AbstractAcpForm {
                        'trophyCategories' => TrophyCategoryCache::getInstance()->getCategories(),
                        'groupedObjectTypes' => $this->conditions, 
                        'awardAutomatically' => $this->awardAutomatically,
+                       'revokeAutomatically' => $this->revokeAutomatically,
                        'availableTypes' => $this->availableTypes, 
                        'tmpHash' => $this->tmpHash,
                        'uploadedImageURL' => $this->uploadedImageURL,
index 69bb1d0e6e292954c3d704c6dbd56b8cef7ea948..55a456ed5aeae5c33296f928e478147c20f69b65 100644 (file)
@@ -74,6 +74,7 @@ class TrophyEditForm extends TrophyAddForm {
                        $this->iconColor = $this->trophy->iconColor;
                        $this->badgeColor = $this->trophy->badgeColor;
                        $this->awardAutomatically = $this->trophy->awardAutomatically;
+                       $this->revokeAutomatically = $this->trophy->revokeAutomatically;
                        $this->trophyUseHtml = $this->trophy->trophyUseHtml;
                        $this->showOrder = $this->trophy->showOrder;
                        
@@ -167,6 +168,7 @@ class TrophyEditForm extends TrophyAddForm {
                        'type' => $this->type,
                        'isDisabled' => $this->isDisabled,
                        'awardAutomatically' => $this->awardAutomatically,
+                       'revokeAutomatically' => $this->revokeAutomatically,
                        'trophyUseHtml' => $this->trophyUseHtml,
                        'showOrder' => $this->showOrder
                ])]);
index 5d5310fe6b41153bd097cf641ba16bce6f6f3321..5bcde024c4088024cf462f0bb2a3c2d5ffc220d9 100644 (file)
@@ -32,7 +32,8 @@ use wcf\util\StringUtil;
  * @property-read      string          $badgeColor                     the icon badge color
  * @property-read      integer         $isDisabled                     `1` if the trophy is disabled
  * @property-read      integer         $awardAutomatically             `1` if the trophy is awarded automatically
- * @property-read      integer         $trophyUseHtml                  `1`, if the trophy use a html description
+ * @property-read      integer         $revokeAutomatically            `1` if the trophy is automatically revoked, if the conditions are not longer fulfilled
+ * @property-read      integer         $trophyUseHtml                  `1` if the trophy use a html description
  * @property-read      integer         $showOrder                      position of the trophy in relation to the other trophies at the same location
  */
 class Trophy extends DatabaseObject implements ITitledLinkObject, IRouteController {
index ad87909eec55b909a9980b1fea22bdba99e78805..7c4b18b8eedc8b40af01ea13f38cb6284bf4f5e8 100644 (file)
@@ -99,6 +99,10 @@ class UserTrophyAction extends AbstractDatabaseObjectAction {
         * @inheritDoc
         */
        public function delete() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
                $trophyIDs = $userIDs = []; 
                foreach ($this->getObjects() as $object) {
                        $trophyIDs[] = $object->trophyID; 
index 93031400dbf48d2481164d2fbc54031d18e503bd..27f1495fd1f1d0fce1daca550ce966eaff681f02 100644 (file)
@@ -21,6 +21,7 @@ class AssignTrophiesCronjob extends AbstractCronjob {
                
                if (MODULE_TROPHY) {
                        TrophyConditionHandler::getInstance()->assignTrophies(100);
+                       TrophyConditionHandler::getInstance()->revokeTrophies(100);
                }
        }
 }
index f3237a07f4b6804431f3725f3e48b8987ef67534..ac4dd048aa5a368cec5c837f793b464815342d5f 100644 (file)
@@ -85,6 +85,30 @@ class TrophyConditionHandler extends SingletonFactory {
                }
        }
        
+       /**
+        * Revoke user trophies which are not longer fulfills the conditions. 
+        * 
+        * @param       integer         $maxRevokes
+        */
+       public function revokeTrophies($maxRevokes = 500) {
+               $trophyList = new TrophyList();
+               $trophyList->getConditionBuilder()->add('awardAutomatically = ?', [1]);
+               $trophyList->getConditionBuilder()->add('revokeAutomatically = ?', [1]);
+               $trophyList->getConditionBuilder()->add('isDisabled = ?', [0]);
+               $trophyList->readObjects();
+               
+               $i = 0;
+               foreach ($trophyList as $trophy) {
+                       $userTrophyIDs = $this->getRevocableUserTrophyIDs($trophy);
+                       
+                       $i += count($userTrophyIDs);
+                       
+                       (new UserTrophyAction($userTrophyIDs, 'delete'))->executeAction();
+                       
+                       if ($i >= $maxRevokes) return;
+               }
+       }
+       
        /**
         * Returns the users who fulfill all conditions of the given trophy.
         *
@@ -106,4 +130,35 @@ class TrophyConditionHandler extends SingletonFactory {
                
                return $userList->getObjectIDs();
        }
+       
+       /**
+        * Returns the userTrophyIDs of the users, which no longer fulfills the trophy conditions. 
+        * 
+        * @param       Trophy          $trophy
+        * @return      integer[]
+        */
+       private function getRevocableUserTrophyIDs(Trophy $trophy) {
+               $pseudoUserList = new UserList();
+               $pseudoUserList->sqlConditionJoins .= " LEFT JOIN wcf".WCF_N."_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)";
+               
+               $conditions = $trophy->getConditions();
+               foreach ($conditions as $condition) {
+                       $condition->getObjectType()->getProcessor()->addUserCondition($condition, $pseudoUserList);
+               }
+               
+               $userList = new UserList();
+               $userList->sqlConditionJoins = $pseudoUserList->sqlConditionJoins;
+               $userList->sqlOrderBy = $pseudoUserList->sqlOrderBy;
+               $userList->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_trophy user_trophy ON (user_table.userID = user_trophy.userID)";
+               $userList->useQualifiedShorthand = false;
+               $userList->sqlSelects = "user_trophy.userTrophyID, user_table.userID";
+               $pseudoUserList->getConditionBuilder()->enableWhereKeyword(false);
+               $userList->getConditionBuilder()->add('NOT('. $pseudoUserList->getConditionBuilder() .')', $pseudoUserList->getConditionBuilder()->getParameters());
+               $userList->getConditionBuilder()->add('user_table.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_trophy WHERE trophyID IN (?))', [$trophy->trophyID]);
+               $userList->readObjects();
+               
+               return array_map(function($object) {
+                       return $object->userTrophyID; 
+               }, $userList->getObjects());
+       }
 }
index cd0854353e3c949db1dde2bc9cd94330dd3d83b8..b70afd3bb620ef62fb8d758a83ffc10cce1474a4 100644 (file)
@@ -3857,6 +3857,7 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.acp.trophy.category"><![CDATA[Kategorie]]></item>
                <item name="wcf.acp.trophy.isDisabled"><![CDATA[Trophäe deaktivieren]]></item>
                <item name="wcf.acp.trophy.awardAutomatically"><![CDATA[Trophäe automatisch vergeben]]></item>
+               <item name="wcf.acp.trophy.revokeAutomatically"><![CDATA[Trophäe automatisch entziehen, wenn die Bedingungen nicht mehr erfüllt sind]]></item>
                <item name="wcf.acp.trophy.type"><![CDATA[Trophäen-Typ]]></item>
                <item name="wcf.acp.trophy.type.badge"><![CDATA[Badge]]></item>
                <item name="wcf.acp.trophy.type.imageUpload"><![CDATA[Bild-Datei]]></item>
index a850c5b920282894188202572d748744a03f6901..153cd6802197f4f92378cd5cd229a13b46bcc98b 100644 (file)
@@ -3850,6 +3850,7 @@ Open the link below to access the user profile:
                <item name="wcf.acp.trophy.category"><![CDATA[Category]]></item>
                <item name="wcf.acp.trophy.isDisabled"><![CDATA[Disable Trophy]]></item>
                <item name="wcf.acp.trophy.awardAutomatically"><![CDATA[Award trophy automatically]]></item>
+               <item name="wcf.acp.trophy.revokeAutomatically"><![CDATA[Revoke trophy automatically if the conditions are not longer fulfilled]]></item>
                <item name="wcf.acp.trophy.type"><![CDATA[Trophy Type]]></item>
                <item name="wcf.acp.trophy.type.badge"><![CDATA[Badge]]></item>
                <item name="wcf.acp.trophy.type.imageUpload"><![CDATA[Image]]></item>
index 572e29fb2644a3f3bbbf84672628f382b7d4f4ab..4f07945112a408a3fd7ec6d9ea2df39e58d5d334 100644 (file)
@@ -1382,6 +1382,7 @@ CREATE TABLE wcf1_trophy(
        badgeColor VARCHAR(255),
        isDisabled TINYINT(1) NOT NULL DEFAULT 0,
        awardAutomatically TINYINT(1) NOT NULL DEFAULT 0,
+       revokeAutomatically TINYINT(1) NOT NULL DEFAULT 0,
        trophyUseHtml TINYINT(1) NOT NULL DEFAULT 0,
        showOrder INT(10) NOT NULL DEFAULT 0,
        KEY(categoryID)