3 namespace wcf\system\trophy\condition
;
5 use wcf\data\
object\type\ObjectType
;
6 use wcf\data\
object\type\ObjectTypeCache
;
7 use wcf\data\trophy\Trophy
;
8 use wcf\data\trophy\TrophyList
;
9 use wcf\data\user\trophy\UserTrophyAction
;
10 use wcf\data\user\UserList
;
11 use wcf\system\SingletonFactory
;
14 * Handles trophy conditions.
16 * @author Joshua Ruesweg
17 * @copyright 2001-2019 WoltLab GmbH
18 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
19 * @package WoltLabSuite\Core\System\Trophy\Condition
22 class TrophyConditionHandler
extends SingletonFactory
25 * definition name for trophy conditions
28 const CONDITION_DEFINITION_NAME
= 'com.woltlab.wcf.condition.trophy';
31 * list of grouped trophy condition object types
34 protected $groupedObjectTypes = [];
39 protected function init()
41 $objectTypes = ObjectTypeCache
::getInstance()->getObjectTypes(self
::CONDITION_DEFINITION_NAME
);
43 foreach ($objectTypes as $objectType) {
44 if (!$objectType->conditiongroup
) {
48 if (!isset($this->groupedObjectTypes
[$objectType->conditiongroup
])) {
49 $this->groupedObjectTypes
[$objectType->conditiongroup
] = [];
52 $this->groupedObjectTypes
[$objectType->conditiongroup
][$objectType->objectTypeID
] = $objectType;
57 * Returns the list of grouped trophy condition object types.
59 * @return ObjectType[][]
61 public function getGroupedObjectTypes()
63 return $this->groupedObjectTypes
;
67 * Assign trophies based on rules.
69 * @param int $maxAssigns
71 public function assignTrophies($maxAssigns = 500)
73 $trophyList = new TrophyList();
74 $trophyList->getConditionBuilder()->add('awardAutomatically = ?', [1]);
75 $trophyList->getConditionBuilder()->add('isDisabled = ?', [0]);
76 $trophyList->readObjects();
79 foreach ($trophyList as $trophy) {
80 $userIDs = $this->getUserIDs($trophy);
82 foreach ($userIDs as $userID) {
83 (new UserTrophyAction([], 'create', [
85 'trophyID' => $trophy->trophyID
,
91 if (++
$i >= $maxAssigns) {
99 * Revoke user trophies which are not longer fulfills the conditions.
101 * @param int $maxRevokes
104 public function revokeTrophies($maxRevokes = 500)
106 $trophyList = new TrophyList();
107 $trophyList->getConditionBuilder()->add('awardAutomatically = ?', [1]);
108 $trophyList->getConditionBuilder()->add('revokeAutomatically = ?', [1]);
109 $trophyList->getConditionBuilder()->add('isDisabled = ?', [0]);
110 $trophyList->readObjects();
113 foreach ($trophyList as $trophy) {
114 $userTrophyIDs = $this->getRevocableUserTrophyIDs($trophy, $maxRevokes - $i);
116 $i +
= \
count($userTrophyIDs);
118 (new UserTrophyAction($userTrophyIDs, 'delete'))->executeAction();
120 if ($i >= $maxRevokes) {
127 * Returns the users who fulfill all conditions of the given trophy.
129 * @param Trophy $trophy
133 private function getUserIDs(Trophy
$trophy)
135 $userList = new UserList();
136 $userList->sqlConditionJoins
.= " LEFT JOIN wcf" . WCF_N
. "_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)";
138 $conditions = $trophy->getConditions();
139 foreach ($conditions as $condition) {
140 $condition->getObjectType()->getProcessor()->addUserCondition($condition, $userList);
143 // prevent multiple awards from a trophy for a user
144 $userList->getConditionBuilder()->add(
145 'user_table.userID NOT IN (SELECT userID FROM wcf' . WCF_N
. '_user_trophy WHERE trophyID IN (?))',
148 $userList->readObjectIDs();
150 return $userList->getObjectIDs();
154 * Returns the userTrophyIDs of the users, which no longer fulfills the trophy conditions.
156 * @param Trophy $trophy
157 * @param int $maxTrophyIDs maximum number of trophies that are processed
161 private function getRevocableUserTrophyIDs(Trophy
$trophy, $maxTrophyIDs)
163 // Unfortunately, the condition system does not support negated conditions.
164 // Therefore, we need to build our own SQL query. To get to the conditions
165 // that must be fulfilled for earning a specific trophy, we first create
166 // a pseudo DBOList element to pass them to the condition handler. Then we
167 // extract the condition builder from the object.
168 $pseudoUserList = new UserList();
170 $conditions = $trophy->getConditions();
172 // Check if there are conditions for the award of the trophy for the given trophy.
173 // If there are no conditions, we simply return an empty list and do not remove any trophy.
174 // A trophy without conditions that is awarded automatically cannot be created by default.
175 if (empty($conditions)) {
179 // Assign the condition to the pseudo DBOList object
180 foreach ($conditions as $condition) {
181 $condition->getObjectType()->getProcessor()->addUserCondition($condition, $pseudoUserList);
184 // Now we create our own query to find out which users no longer meet the conditions.
185 // For this we use a UserList object again and transfer basic data from the pseudo object.
186 $userList = new UserList();
187 $userList->sqlOrderBy
= $pseudoUserList->sqlOrderBy
;
188 $userList->sqlLimit
= $maxTrophyIDs;
190 // Now we copy the sql joins from the pseudo object to the new one if a condition
191 // has joined a table.
192 $userList->sqlJoins
= $pseudoUserList->sqlJoins
;
194 // We joining the user_trophy table to receive the userTrophyID, which should be deleted.
195 $userList->sqlJoins
.= " LEFT JOIN wcf" . WCF_N
. "_user_trophy user_trophy ON (user_table.userID = user_trophy.userID)";
197 // We do not need the complete user object, but only the userTrophyID.
198 // So that the UserList object can also assign the users (which is used
199 // as an array index), we also get the userID.
200 $userList->useQualifiedShorthand
= false;
201 $userList->sqlSelects
= "user_trophy.userTrophyID, user_table.userID";
203 // Now we transfer the old conditions to our new object. To avoid having two WHERE keywords,
204 // we deactivate it in the pseudo-object.
205 $pseudoUserList->getConditionBuilder()->enableWhereKeyword(false);
206 $userList->getConditionBuilder()->add(
207 'NOT(' . $pseudoUserList->getConditionBuilder() . ')',
208 $pseudoUserList->getConditionBuilder()->getParameters()
211 // In order not to get all users who do not fulfill the conditions (in case of
212 // doubt there can be many), we filter for users who have received the trophy.
213 $userList->getConditionBuilder()->add(
214 'user_table.userID IN (SELECT userID FROM wcf' . WCF_N
. '_user_trophy WHERE trophyID IN (?))',
218 // Prevents us from getting faulty UserTrophyIDs.
219 $userList->getConditionBuilder()->add('user_trophy.trophyID = ?', [$trophy->trophyID
]);
220 $userList->readObjects();
222 // We now return an array of userTrophyIDs instead of user objects to remove them directly via DBOAction.
223 return \array_map
(static function ($object) {
224 return $object->userTrophyID
;
225 }, $userList->getObjects());