Merge branch '5.2' into 5.3
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / worker / UserRebuildDataWorker.class.php
1 <?php
2 namespace wcf\system\worker;
3 use wcf\data\reaction\type\ReactionTypeCache;
4 use wcf\data\user\avatar\UserAvatar;
5 use wcf\data\user\avatar\UserAvatarEditor;
6 use wcf\data\user\avatar\UserAvatarList;
7 use wcf\data\user\User;
8 use wcf\data\user\UserEditor;
9 use wcf\data\user\UserList;
10 use wcf\data\user\UserProfileAction;
11 use wcf\system\bbcode\BBCodeHandler;
12 use wcf\system\database\util\PreparedStatementConditionBuilder;
13 use wcf\system\exception\SystemException;
14 use wcf\system\html\input\HtmlInputProcessor;
15 use wcf\system\image\ImageHandler;
16 use wcf\system\WCF;
17
18 /**
19 * Worker implementation for updating users.
20 *
21 * @author Marcel Werk
22 * @copyright 2001-2019 WoltLab GmbH
23 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
24 * @package WoltLabSuite\Core\System\Worker
25 *
26 * @method UserList getObjectList()
27 */
28 class UserRebuildDataWorker extends AbstractRebuildDataWorker {
29 /**
30 * @inheritDoc
31 */
32 protected $objectListClassName = UserList::class;
33
34 /**
35 * @inheritDoc
36 */
37 protected $limit = 50;
38
39 /**
40 * @inheritDoc
41 */
42 protected function initObjectList() {
43 parent::initObjectList();
44
45 $this->objectList->sqlSelects = 'user_option_value.userOption' . User::getUserOptionID('aboutMe') . ' AS aboutMe';
46 $this->objectList->sqlJoins = "LEFT JOIN wcf".WCF_N."_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)";
47 $this->objectList->sqlOrderBy = 'user_table.userID';
48 }
49
50 /**
51 * @inheritDoc
52 */
53 public function execute() {
54 parent::execute();
55
56 $users = $userIDs = [];
57 foreach ($this->getObjectList() as $user) {
58 $users[] = new UserEditor($user);
59 $userIDs[] = $user->userID;
60 }
61
62 // update user ranks
63 if (!empty($users)) {
64 $action = new UserProfileAction($users, 'updateUserOnlineMarking');
65 $action->executeAction();
66 }
67
68 if (!empty($userIDs)) {
69 // update article counter
70 $conditionBuilder = new PreparedStatementConditionBuilder();
71 $conditionBuilder->add('user_table.userID IN (?)', [$userIDs]);
72 $sql = "UPDATE wcf".WCF_N."_user user_table
73 SET articles = (
74 SELECT COUNT(*)
75 FROM wcf".WCF_N."_article
76 WHERE userID = user_table.userID
77 )
78 ".$conditionBuilder;
79 $statement = WCF::getDB()->prepareStatement($sql);
80 $statement->execute($conditionBuilder->getParameters());
81
82 // update like counter
83 if (MODULE_LIKE) {
84 $sql = "UPDATE wcf".WCF_N."_user user_table SET";
85
86 $reactionTypeIDs = array_keys(ReactionTypeCache::getInstance()->getReactionTypes());
87 if (!empty($reactionTypeIDs)) {
88 $sql .= " likesReceived = (
89 SELECT COUNT(*)
90 FROM wcf".WCF_N."_like
91 WHERE objectUserID = user_table.userID
92 AND reactionTypeID IN (". implode(',', $reactionTypeIDs) .")
93 )";
94 }
95 else {
96 $sql .= " likesReceived = 0";
97 }
98
99 $sql .= " ".$conditionBuilder;
100 $statement = WCF::getDB()->prepareStatement($sql);
101 $statement->execute($conditionBuilder->getParameters());
102 }
103
104 // update trophy points
105 if (MODULE_TROPHY) {
106 $sql = "UPDATE wcf".WCF_N."_user user_table
107 SET trophyPoints = (
108 SELECT COUNT(*)
109 FROM wcf".WCF_N."_user_trophy user_trophy
110 LEFT JOIN wcf".WCF_N."_trophy trophy ON user_trophy.trophyID = trophy.trophyID
111 LEFT JOIN wcf".WCF_N."_category trophy_category ON trophy.categoryID = trophy_category.categoryID
112 WHERE user_trophy.userID = user_table.userID
113 AND trophy.isDisabled = 0
114 AND trophy_category.isDisabled = 0
115 )
116 ".$conditionBuilder;
117 $statement = WCF::getDB()->prepareStatement($sql);
118 $statement->execute($conditionBuilder->getParameters());
119 }
120
121 // update signatures and about me
122 $sql = "UPDATE wcf".WCF_N."_user_option_value
123 SET userOption" . User::getUserOptionID('aboutMe') . " = ?
124 WHERE userID = ?";
125 $statement = WCF::getDB()->prepareStatement($sql);
126
127 // retrieve permissions
128 $userIDs = [];
129 foreach ($users as $user) {
130 $userIDs[] = $user->userID;
131 }
132 $userPermissions = $this->getBulkUserPermissions($userIDs, ['user.message.disallowedBBCodes', 'user.signature.disallowedBBCodes']);
133
134 $htmlInputProcessor = new HtmlInputProcessor();
135 WCF::getDB()->beginTransaction();
136 /** @var UserEditor $user */
137 foreach ($users as $user) {
138 BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', $this->getBulkUserPermissionValue($userPermissions, $user->userID, 'user.signature.disallowedBBCodes')));
139
140 if (!$user->signatureEnableHtml) {
141 $htmlInputProcessor->process($user->signature, 'com.woltlab.wcf.user.signature', $user->userID, true);
142
143 $user->update([
144 'signature' => $htmlInputProcessor->getHtml(),
145 'signatureEnableHtml' => 1
146 ]);
147 }
148 else {
149 $htmlInputProcessor->reprocess($user->signature, 'com.woltlab.wcf.user.signature', $user->userID);
150 $user->update(['signature' => $htmlInputProcessor->getHtml()]);
151 }
152
153 if ($user->aboutMe) {
154 BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', $this->getBulkUserPermissionValue($userPermissions, $user->userID, 'user.message.disallowedBBCodes')));
155
156 if (!$user->signatureEnableHtml) {
157 $htmlInputProcessor->process($user->aboutMe, 'com.woltlab.wcf.user.aboutMe', $user->userID, true);
158 }
159 else {
160 $htmlInputProcessor->reprocess($user->aboutMe, 'com.woltlab.wcf.user.aboutMe', $user->userID);
161 }
162
163 $html = $htmlInputProcessor->getHtml();
164 // MySQL's TEXT type allows for 65,535 bytes, hence we need to count
165 // the bytes rather than the actual amount of characters
166 if (strlen($html) > 65535) {
167 // content does not fit the available space, and any
168 // attempts to truncate it will yield awkward results
169 $html = '';
170 }
171
172 $statement->execute([$html, $user->userID]);
173 }
174 }
175 WCF::getDB()->commitTransaction();
176
177 // update old/imported avatars
178 $avatarList = new UserAvatarList();
179 $avatarList->getConditionBuilder()->add('user_avatar.userID IN (?)', [$userIDs]);
180 $avatarList->getConditionBuilder()->add('(user_avatar.width <> ? OR user_avatar.height <> ?)', [UserAvatar::AVATAR_SIZE, UserAvatar::AVATAR_SIZE]);
181 $avatarList->readObjects();
182 foreach ($avatarList as $avatar) {
183 $editor = new UserAvatarEditor($avatar);
184 if (!file_exists($avatar->getLocation()) || @getimagesize($avatar->getLocation()) === false) {
185 // delete avatars that are missing or broken
186 $editor->delete();
187 continue;
188 }
189
190 $width = $avatar->width;
191 $height = $avatar->height;
192 if ($width != $height) {
193 // make avatar quadratic
194 $width = $height = min($width, $height, UserAvatar::AVATAR_SIZE);
195 $adapter = ImageHandler::getInstance()->getAdapter();
196
197 try {
198 $adapter->loadFile($avatar->getLocation());
199 }
200 catch (SystemException $e) {
201 // broken image
202 $editor->delete();
203 continue;
204 }
205
206 $thumbnail = $adapter->createThumbnail($width, $height, false);
207 $adapter->writeImage($thumbnail, $avatar->getLocation());
208 // Clear thumbnail as soon as possible to free up the memory.
209 $thumbnail = null;
210 }
211
212 if ($width != UserAvatar::AVATAR_SIZE || $height != UserAvatar::AVATAR_SIZE) {
213 // resize avatar
214 $adapter = ImageHandler::getInstance()->getAdapter();
215
216 try {
217 $adapter->loadFile($avatar->getLocation());
218 }
219 catch (SystemException $e) {
220 // broken image
221 $editor->delete();
222 continue;
223 }
224
225 $adapter->resize(0, 0, $width, $height, UserAvatar::AVATAR_SIZE, UserAvatar::AVATAR_SIZE);
226 $adapter->writeImage($adapter->getImage(), $avatar->getLocation());
227 $width = $height = UserAvatar::AVATAR_SIZE;
228 }
229
230 $editor->update([
231 'width' => $width,
232 'height' => $height
233 ]);
234 }
235 }
236 }
237 }