Commit | Line | Data |
---|---|---|
15aeb641 | 1 | <?php |
a9229942 | 2 | |
15aeb641 | 3 | namespace wcf\data\reaction; |
a9229942 TD |
4 | |
5 | use wcf\data\AbstractDatabaseObjectAction; | |
a5f117ba | 6 | use wcf\data\like\ILikeObjectTypeProvider; |
15aeb641 | 7 | use wcf\data\like\IRestrictedLikeObjectTypeProvider; |
a5f117ba | 8 | use wcf\data\like\Like; |
15aeb641 | 9 | use wcf\data\like\LikeEditor; |
a5f117ba | 10 | use wcf\data\like\object\ILikeObject; |
9b1ccf5c | 11 | use wcf\data\like\object\LikeObjectEditor; |
15aeb641 | 12 | use wcf\data\like\ViewableLikeList; |
a5f117ba | 13 | use wcf\data\object\type\ObjectType; |
9b1ccf5c | 14 | use wcf\data\object\type\ObjectTypeCache; |
15aeb641 JR |
15 | use wcf\data\reaction\type\ReactionType; |
16 | use wcf\data\reaction\type\ReactionTypeCache; | |
9b1ccf5c JR |
17 | use wcf\data\user\User; |
18 | use wcf\data\user\UserEditor; | |
9f6f1885 | 19 | use wcf\system\cache\runtime\UserProfileRuntimeCache; |
15aeb641 JR |
20 | use wcf\system\exception\IllegalLinkException; |
21 | use wcf\system\exception\PermissionDeniedException; | |
22 | use wcf\system\exception\UserInputException; | |
23 | use wcf\system\reaction\ReactionHandler; | |
9b1ccf5c | 24 | use wcf\system\user\activity\point\UserActivityPointHandler; |
5df438d6 | 25 | use wcf\system\user\GroupedUserList; |
15aeb641 | 26 | use wcf\system\WCF; |
e768ff98 | 27 | use wcf\util\StringUtil; |
15aeb641 JR |
28 | |
29 | /** | |
30 | * Executes reaction-related actions. | |
31 | * | |
a9229942 TD |
32 | * @author Joshua Ruesweg |
33 | * @copyright 2001-2019 WoltLab GmbH | |
34 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
a9229942 TD |
35 | * @since 5.2 |
36 | * | |
37 | * @method Like create() | |
38 | * @method LikeEditor[] getObjects() | |
39 | * @method LikeEditor getSingleObject() | |
15aeb641 | 40 | */ |
a9229942 TD |
41 | class ReactionAction extends AbstractDatabaseObjectAction |
42 | { | |
43 | /** | |
44 | * @inheritDoc | |
45 | */ | |
46 | protected $allowGuestAccess = ['getReactionDetails', 'load']; | |
47 | ||
48 | /** | |
49 | * @inheritDoc | |
50 | */ | |
51 | protected $className = LikeEditor::class; | |
52 | ||
53 | /** | |
54 | * likeable object | |
55 | * @var ILikeObject | |
56 | */ | |
57 | public $likeableObject; | |
58 | ||
59 | /** | |
60 | * object type object | |
61 | * @var ObjectType | |
62 | */ | |
63 | public $objectType; | |
64 | ||
65 | /** | |
66 | * like object type provider object | |
67 | * @var ILikeObjectTypeProvider | |
68 | */ | |
69 | public $objectTypeProvider; | |
70 | ||
71 | /** | |
72 | * reaction type for the reaction | |
73 | * @var ReactionType | |
74 | */ | |
75 | public $reactionType; | |
76 | ||
77 | /** | |
78 | * Validates parameters to fetch like details. | |
79 | */ | |
80 | public function validateGetReactionDetails() | |
81 | { | |
82 | $this->validateObjectParameters(); | |
83 | ||
84 | if (!WCF::getSession()->getPermission('user.like.canViewLike')) { | |
85 | throw new PermissionDeniedException(); | |
86 | } | |
87 | } | |
88 | ||
89 | /** | |
90 | * Returns like details. | |
91 | * | |
92 | * @return string[] | |
93 | */ | |
94 | public function getReactionDetails() | |
95 | { | |
96 | $likeList = new ViewableLikeList(); | |
97 | $likeList->getConditionBuilder()->add('objectID = ?', [$this->parameters['data']['objectID']]); | |
98 | $likeList->getConditionBuilder()->add('objectTypeID = ?', [$this->objectType->objectTypeID]); | |
99 | $likeList->sqlOrderBy = 'time DESC'; | |
100 | $likeList->sqlLimit = 0; | |
101 | $likeList->readObjects(); | |
102 | ||
103 | $data = []; | |
104 | foreach ($likeList as $item) { | |
105 | // we cast the reactionTypeID to a string, so that we can sort the array | |
106 | if (!isset($data[(string)$item->getReactionType()->reactionTypeID])) { | |
107 | $data[(string)$item->getReactionType()->reactionTypeID] = new GroupedUserList($item->getReactionType()->renderIcon() . ' ' . StringUtil::encodeHTML($item->getReactionType()->getTitle())); | |
108 | } | |
109 | ||
110 | $data[(string)$item->getReactionType()->reactionTypeID]->addUserIDs([$item->userID]); | |
111 | } | |
112 | ||
113 | GroupedUserList::loadUsers(); | |
114 | ||
115 | \uksort($data, static function ($a, $b) { | |
116 | $sortOrderA = ReactionTypeCache::getInstance()->getReactionTypeByID($a)->showOrder; | |
117 | $sortOrderB = ReactionTypeCache::getInstance()->getReactionTypeByID($b)->showOrder; | |
118 | ||
119 | return ($sortOrderA < $sortOrderB) ? -1 : 1; | |
120 | }); | |
121 | ||
122 | return [ | |
123 | 'template' => WCF::getTPL()->fetch('groupedUserReactionList', 'wcf', ['groupedUsers' => $data]), | |
124 | 'title' => WCF::getLanguage()->get('wcf.reactions.summary.title'), | |
125 | ]; | |
126 | } | |
127 | ||
128 | /** | |
129 | * Validates permissions for given object. | |
130 | */ | |
131 | protected function validateObjectParameters() | |
132 | { | |
133 | if (!MODULE_LIKE) { | |
134 | throw new IllegalLinkException(); | |
135 | } | |
136 | ||
137 | $this->readInteger('objectID', false, 'data'); | |
138 | $this->readString('objectType', false, 'data'); | |
139 | ||
140 | $this->objectType = ReactionHandler::getInstance()->getObjectType($this->parameters['data']['objectType']); | |
141 | if ($this->objectType === null) { | |
142 | throw new UserInputException('objectType'); | |
143 | } | |
144 | ||
145 | $this->objectTypeProvider = $this->objectType->getProcessor(); | |
146 | $this->likeableObject = $this->objectTypeProvider->getObjectByID($this->parameters['data']['objectID']); | |
147 | $this->likeableObject->setObjectType($this->objectType); | |
148 | if ($this->objectTypeProvider instanceof IRestrictedLikeObjectTypeProvider) { | |
149 | if (!$this->objectTypeProvider->canViewLikes($this->likeableObject)) { | |
150 | throw new PermissionDeniedException(); | |
151 | } | |
152 | } elseif (!$this->objectTypeProvider->checkPermissions($this->likeableObject)) { | |
153 | throw new PermissionDeniedException(); | |
154 | } | |
155 | } | |
156 | ||
157 | /** | |
158 | * React on an given object with the given reactionType. | |
159 | * | |
160 | * @return array | |
161 | */ | |
162 | public function react() | |
163 | { | |
164 | $reactionData = ReactionHandler::getInstance()->react( | |
165 | $this->likeableObject, | |
166 | WCF::getUser(), | |
167 | $this->reactionType->reactionTypeID | |
168 | ); | |
169 | ||
170 | // get stats | |
171 | return [ | |
172 | 'reactions' => $reactionData['cachedReactions'], | |
173 | 'objectID' => $this->likeableObject->getObjectID(), | |
174 | 'objectType' => $this->parameters['data']['objectType'], | |
175 | 'reactionTypeID' => $reactionData['reactionTypeID'], | |
176 | 'reputationCount' => $reactionData['cumulativeLikes'], | |
177 | ]; | |
178 | } | |
179 | ||
180 | /** | |
181 | * Validates the 'react' method. | |
182 | */ | |
183 | public function validateReact() | |
184 | { | |
185 | $this->validateObjectParameters(); | |
186 | ||
187 | $this->readInteger('reactionTypeID', false); | |
188 | ||
189 | $this->reactionType = ReactionTypeCache::getInstance()->getReactionTypeByID($this->parameters['reactionTypeID']); | |
190 | ||
191 | if (!$this->reactionType->reactionTypeID) { | |
192 | throw new IllegalLinkException(); | |
193 | } | |
194 | ||
195 | // check permissions | |
196 | if (!WCF::getUser()->userID || !WCF::getSession()->getPermission('user.like.canLike')) { | |
197 | throw new PermissionDeniedException(); | |
198 | } | |
199 | ||
200 | // check if liking own content but forbidden by configuration | |
201 | $this->likeableObject = $this->objectTypeProvider->getObjectByID($this->parameters['data']['objectID']); | |
202 | $this->likeableObject->setObjectType($this->objectType); | |
203 | if ($this->likeableObject->getUserID() == WCF::getUser()->userID) { | |
204 | throw new PermissionDeniedException(); | |
205 | } | |
206 | ||
207 | if ($this->objectTypeProvider instanceof IRestrictedLikeObjectTypeProvider) { | |
208 | if (!$this->objectTypeProvider->canLike($this->likeableObject)) { | |
209 | throw new PermissionDeniedException(); | |
210 | } | |
211 | } | |
212 | ||
213 | if (!$this->reactionType->isAssignable) { | |
214 | // check, if the reaction is reverted | |
215 | $like = Like::getLike( | |
216 | $this->likeableObject->getObjectType()->objectTypeID, | |
217 | $this->likeableObject->getObjectID(), | |
218 | WCF::getUser()->userID | |
219 | ); | |
220 | ||
221 | if (!$like->likeID || $like->reactionTypeID !== $this->reactionType->reactionTypeID) { | |
222 | throw new IllegalLinkException(); | |
223 | } | |
224 | } | |
225 | } | |
226 | ||
227 | /** | |
228 | * Validates parameters to load reactions. | |
229 | */ | |
230 | public function validateLoad() | |
231 | { | |
232 | if (!MODULE_LIKE) { | |
233 | throw new IllegalLinkException(); | |
234 | } | |
235 | ||
236 | $this->readInteger('lastLikeTime', true); | |
237 | $this->readInteger('userID'); | |
238 | $this->readInteger('reactionTypeID', true); | |
239 | $this->readString('targetType'); | |
240 | ||
241 | $user = UserProfileRuntimeCache::getInstance()->getObject($this->parameters['userID']); | |
242 | ||
243 | if ($user === null) { | |
244 | throw new IllegalLinkException(); | |
245 | } | |
246 | ||
247 | if ($user->isProtected()) { | |
248 | throw new PermissionDeniedException(); | |
249 | } | |
250 | } | |
251 | ||
252 | /** | |
253 | * Loads a list of reactions. | |
254 | * | |
255 | * @return array | |
256 | */ | |
257 | public function load() | |
258 | { | |
259 | $likeList = new ViewableLikeList(); | |
260 | if ($this->parameters['lastLikeTime']) { | |
261 | $likeList->getConditionBuilder()->add("like_table.time < ?", [$this->parameters['lastLikeTime']]); | |
262 | } | |
263 | if ($this->parameters['targetType'] == 'received') { | |
264 | $likeList->getConditionBuilder()->add("like_table.objectUserID = ?", [$this->parameters['userID']]); | |
265 | } else { | |
266 | $likeList->getConditionBuilder()->add("like_table.userID = ?", [$this->parameters['userID']]); | |
267 | } | |
268 | if ($this->parameters['reactionTypeID']) { | |
269 | $likeList->getConditionBuilder()->add( | |
270 | "like_table.reactionTypeID = ?", | |
271 | [$this->parameters['reactionTypeID']] | |
272 | ); | |
273 | } | |
274 | $likeList->readObjects(); | |
275 | ||
276 | if (empty($likeList)) { | |
277 | return []; | |
278 | } | |
279 | ||
280 | // parse template | |
281 | WCF::getTPL()->assign([ | |
282 | 'likeList' => $likeList, | |
283 | ]); | |
284 | ||
285 | return [ | |
286 | 'lastLikeTime' => $likeList->getLastLikeTime(), | |
287 | 'template' => WCF::getTPL()->fetch('userProfileLikeItem'), | |
288 | ]; | |
289 | } | |
290 | ||
291 | /** | |
292 | * Copies likes from one object id to another. | |
293 | */ | |
294 | public function copy() | |
295 | { | |
296 | $sourceObjectType = ObjectTypeCache::getInstance()->getObjectTypeByName( | |
297 | 'com.woltlab.wcf.like.likeableObject', | |
298 | $this->parameters['sourceObjectType'] | |
299 | ); | |
300 | $targetObjectType = ObjectTypeCache::getInstance()->getObjectTypeByName( | |
301 | 'com.woltlab.wcf.like.likeableObject', | |
302 | $this->parameters['targetObjectType'] | |
303 | ); | |
304 | ||
305 | // | |
306 | // step 1) get data | |
307 | // | |
308 | ||
309 | // get like object | |
310 | $sql = "SELECT * | |
311 | FROM wcf" . WCF_N . "_like_object | |
312 | WHERE objectTypeID = ? | |
313 | AND objectID = ?"; | |
314 | $statement = WCF::getDB()->prepareStatement($sql); | |
315 | $statement->execute([ | |
316 | $sourceObjectType->objectTypeID, | |
317 | $this->parameters['sourceObjectID'], | |
318 | ]); | |
319 | $row = $statement->fetchArray(); | |
320 | ||
321 | // no reactions at all | |
322 | if ($row === false) { | |
323 | return; | |
324 | } | |
325 | ||
326 | unset($row['likeObjectID']); | |
327 | $row['objectTypeID'] = $targetObjectType->objectTypeID; | |
328 | $row['objectID'] = $this->parameters['targetObjectID']; | |
329 | $newLikeObject = LikeObjectEditor::create($row); | |
330 | ||
331 | // | |
332 | // step 2) copy | |
333 | // | |
334 | ||
335 | $sql = "INSERT INTO wcf" . WCF_N . "_like | |
336 | (objectID, objectTypeID, objectUserID, userID, time, likeValue, reactionTypeID) | |
337 | SELECT " . $this->parameters['targetObjectID'] . ", " . $targetObjectType->objectTypeID . ", objectUserID, userID, time, likeValue, reactionTypeID | |
338 | FROM wcf" . WCF_N . "_like | |
339 | WHERE objectTypeID = ? | |
340 | AND objectID = ?"; | |
341 | $statement = WCF::getDB()->prepareStatement($sql); | |
342 | $statement->execute([ | |
343 | $sourceObjectType->objectTypeID, | |
344 | $this->parameters['sourceObjectID'], | |
345 | ]); | |
346 | ||
347 | // | |
348 | // step 3) update owner | |
349 | // | |
350 | ||
351 | if ($newLikeObject->objectUserID) { | |
352 | $sql = "SELECT COUNT(*) as count | |
353 | FROM wcf" . WCF_N . "_like | |
354 | WHERE objectTypeID = ? | |
355 | AND objectID = ?"; | |
356 | $statement = WCF::getDB()->prepareStatement($sql); | |
357 | $statement->execute([ | |
358 | $targetObjectType->objectTypeID, | |
359 | $this->parameters['targetObjectID'], | |
360 | ]); | |
361 | $count = $statement->fetchSingleColumn(); | |
362 | ||
363 | // update received likes | |
364 | $userEditor = new UserEditor(new User($newLikeObject->objectUserID)); | |
365 | $userEditor->updateCounters([ | |
366 | 'likesReceived' => $count, | |
367 | ]); | |
368 | ||
369 | // add activity points | |
370 | UserActivityPointHandler::getInstance()->fireEvents( | |
371 | 'com.woltlab.wcf.like.activityPointEvent.receivedLikes', | |
372 | [$newLikeObject->objectUserID => $count] | |
373 | ); | |
374 | } | |
375 | } | |
15aeb641 | 376 | } |