3 namespace wcf\system\worker
;
5 use wcf\data\reaction\type\ReactionTypeCache
;
6 use wcf\data\user\avatar\UserAvatar
;
7 use wcf\data\user\avatar\UserAvatarEditor
;
8 use wcf\data\user\avatar\UserAvatarList
;
9 use wcf\data\user\cover\photo\DefaultUserCoverPhoto
;
10 use wcf\data\user\cover\photo\IWebpUserCoverPhoto
;
11 use wcf\data\user\User
;
12 use wcf\data\user\UserEditor
;
13 use wcf\data\user\UserList
;
14 use wcf\data\user\UserProfileAction
;
15 use wcf\data\user\UserProfileList
;
16 use wcf\system\bbcode\BBCodeHandler
;
17 use wcf\system\database\util\PreparedStatementConditionBuilder
;
18 use wcf\system\exception\SystemException
;
19 use wcf\system\html\input\HtmlInputProcessor
;
20 use wcf\system\image\ImageHandler
;
21 use wcf\system\user\storage\UserStorageHandler
;
25 * Worker implementation for updating users.
28 * @copyright 2001-2019 WoltLab GmbH
29 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
30 * @package WoltLabSuite\Core\System\Worker
32 * @method UserList getObjectList()
34 class UserRebuildDataWorker
extends AbstractRebuildDataWorker
39 protected $objectListClassName = UserList
::class;
44 protected $limit = 50;
49 protected function initObjectList()
51 parent
::initObjectList();
53 $this->objectList
->sqlSelects
= 'user_option_value.userOption' . User
::getUserOptionID('aboutMe') . ' AS aboutMe';
54 $this->objectList
->sqlJoins
= "
55 LEFT JOIN wcf" . WCF_N
. "_user_option_value user_option_value
56 ON user_option_value.userID = user_table.userID";
57 $this->objectList
->sqlOrderBy
= 'user_table.userID';
63 public function execute()
67 $users = $userIDs = [];
68 foreach ($this->getObjectList() as $user) {
69 $users[] = new UserEditor($user);
70 $userIDs[] = $user->userID
;
75 $action = new UserProfileAction($users, 'updateUserOnlineMarking');
76 $action->executeAction();
79 if (!empty($userIDs)) {
80 // update article counter
81 $conditionBuilder = new PreparedStatementConditionBuilder();
82 $conditionBuilder->add('user_table.userID IN (?)', [$userIDs]);
83 $sql = "UPDATE wcf" . WCF_N
. "_user user_table
86 FROM wcf" . WCF_N
. "_article
87 WHERE userID = user_table.userID
89 " . $conditionBuilder;
90 $statement = WCF
::getDB()->prepareStatement($sql);
91 $statement->execute($conditionBuilder->getParameters());
93 // update like counter
95 $sql = "UPDATE wcf" . WCF_N
. "_user user_table
98 $reactionTypeIDs = \array_keys
(ReactionTypeCache
::getInstance()->getReactionTypes());
99 if (!empty($reactionTypeIDs)) {
103 FROM wcf" . WCF_N
. "_like
104 WHERE objectUserID = user_table.userID
105 AND reactionTypeID IN (" . \
implode(',', $reactionTypeIDs) . ")
108 $sql .= " likesReceived = 0";
111 $sql .= " " . $conditionBuilder;
112 $statement = WCF
::getDB()->prepareStatement($sql);
113 $statement->execute($conditionBuilder->getParameters());
116 // update trophy points
118 $sql = "UPDATE wcf" . WCF_N
. "_user user_table
121 FROM wcf" . WCF_N
. "_user_trophy user_trophy
122 LEFT JOIN wcf" . WCF_N
. "_trophy trophy
123 ON user_trophy.trophyID = trophy.trophyID
124 LEFT JOIN wcf" . WCF_N
. "_category trophy_category
125 ON trophy.categoryID = trophy_category.categoryID
126 WHERE user_trophy.userID = user_table.userID
127 AND trophy.isDisabled = 0
128 AND trophy_category.isDisabled = 0
130 " . $conditionBuilder;
131 $statement = WCF
::getDB()->prepareStatement($sql);
132 $statement->execute($conditionBuilder->getParameters());
135 // update signatures and about me
136 $sql = "UPDATE wcf" . WCF_N
. "_user_option_value
137 SET userOption" . User
::getUserOptionID('aboutMe') . " = ?
139 $statement = WCF
::getDB()->prepareStatement($sql);
141 // retrieve permissions
143 foreach ($users as $user) {
144 $userIDs[] = $user->userID
;
146 $userPermissions = $this->getBulkUserPermissions(
148 ['user.message.disallowedBBCodes', 'user.signature.disallowedBBCodes']
151 $htmlInputProcessor = new HtmlInputProcessor();
152 WCF
::getDB()->beginTransaction();
153 /** @var UserEditor $user */
154 foreach ($users as $user) {
155 BBCodeHandler
::getInstance()->setDisallowedBBCodes(\
explode(
157 $this->getBulkUserPermissionValue(
160 'user.signature.disallowedBBCodes'
164 if (!$user->signatureEnableHtml
) {
165 $htmlInputProcessor->process(
167 'com.woltlab.wcf.user.signature',
173 'signature' => $htmlInputProcessor->getHtml(),
174 'signatureEnableHtml' => 1,
177 $htmlInputProcessor->reprocess($user->signature
, 'com.woltlab.wcf.user.signature', $user->userID
);
178 $user->update(['signature' => $htmlInputProcessor->getHtml()]);
181 if ($user->aboutMe
) {
182 BBCodeHandler
::getInstance()->setDisallowedBBCodes(\
explode(
184 $this->getBulkUserPermissionValue(
187 'user.message.disallowedBBCodes'
191 if (!$user->signatureEnableHtml
) {
192 $htmlInputProcessor->process(
194 'com.woltlab.wcf.user.aboutMe',
199 $htmlInputProcessor->reprocess($user->aboutMe
, 'com.woltlab.wcf.user.aboutMe', $user->userID
);
202 $html = $htmlInputProcessor->getHtml();
203 // MySQL's TEXT type allows for 65,535 bytes, hence we need to count
204 // the bytes rather than the actual amount of characters
205 if (\
strlen($html) > 65535) {
206 // content does not fit the available space, and any
207 // attempts to truncate it will yield awkward results
211 $statement->execute([$html, $user->userID
]);
214 WCF
::getDB()->commitTransaction();
216 // update old/imported avatars
217 $avatarList = new UserAvatarList();
218 $avatarList->getConditionBuilder()->add('user_avatar.userID IN (?)', [$userIDs]);
219 $avatarList->getConditionBuilder()->add(
221 (user_avatar.width <> ? OR user_avatar.height <> ?)
222 OR (user_avatar.hasWebP = ? AND user_avatar.avatarExtension <> ?)
225 UserAvatar
::AVATAR_SIZE
,
226 UserAvatar
::AVATAR_SIZE
,
231 $avatarList->readObjects();
232 $resetAvatarCache = [];
233 foreach ($avatarList as $avatar) {
234 $resetAvatarCache[] = $avatar->userID
;
236 $editor = new UserAvatarEditor($avatar);
237 if (!\file_exists
($avatar->getLocation()) ||
@\
getimagesize($avatar->getLocation()) === false) {
238 // delete avatars that are missing or broken
243 $width = $avatar->width
;
244 $height = $avatar->height
;
245 if ($width != $height) {
246 // make avatar quadratic
247 $width = $height = \
min($width, $height, UserAvatar
::AVATAR_SIZE
);
248 $adapter = ImageHandler
::getInstance()->getAdapter();
251 $adapter->loadFile($avatar->getLocation());
252 } catch (SystemException
$e) {
258 $thumbnail = $adapter->createThumbnail($width, $height, false);
259 $adapter->writeImage($thumbnail, $avatar->getLocation());
260 // Clear thumbnail as soon as possible to free up the memory.
264 if ($width != UserAvatar
::AVATAR_SIZE ||
$height != UserAvatar
::AVATAR_SIZE
) {
266 $adapter = ImageHandler
::getInstance()->getAdapter();
269 $adapter->loadFile($avatar->getLocation());
270 } catch (SystemException
$e) {
276 $adapter->resize(0, 0, $width, $height, UserAvatar
::AVATAR_SIZE
, UserAvatar
::AVATAR_SIZE
);
277 $adapter->writeImage($adapter->getImage(), $avatar->getLocation());
278 $width = $height = UserAvatar
::AVATAR_SIZE
;
281 $editor->deleteLegacyThumbnails();
282 $editor->createAvatarVariant();
290 // Reset the avatar cache for all avatars that had been processed.
291 if (!empty($resetAvatarCache)) {
292 UserStorageHandler
::getInstance()->reset($resetAvatarCache, 'avatar');
295 // Create WebP variants of existing cover photos.
296 $userProfiles = new UserProfileList();
297 $userProfiles->getConditionBuilder()->add("user_table.userID IN (?)", [$userIDs]);
298 $userProfiles->getConditionBuilder()->add("user_table.coverPhotoHash IS NOT NULL");
299 $userProfiles->getConditionBuilder()->add("user_table.coverPhotoHasWebP = ?", [0]);
300 $userProfiles->readObjects();
301 foreach ($userProfiles as $userProfile) {
302 $editor = new UserEditor($userProfile->getDecoratedObject());
303 $coverPhoto = $userProfile->getCoverPhoto(true);
304 if ($coverPhoto instanceof DefaultUserCoverPhoto
) {
305 // The default cover photo can be returned if the user has a
306 // cover photo, but it has been disabled by an administrator.
310 // If neither the regular, nor the WebP variant is readable then the
311 // cover photo is missing and we must clear the database information.
313 !\
is_readable($coverPhoto->getLocation(false))
314 && !\
is_readable($coverPhoto->getLocation(true))
317 'coverPhotoHash' => null,
318 'coverPhotoExtension' => '',
324 if ($coverPhoto instanceof IWebpUserCoverPhoto
) {
325 $result = $coverPhoto->createWebpVariant();
326 if ($result !== null) {
327 $data['coverPhotoHasWebP'] = 1;
329 // A fallback jpeg image was just created.
330 if ($result === false) {
331 $data['coverPhotoExtension'] = 'jpg';
334 $editor->update($data);