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