Merge pull request #6006 from WoltLab/file-processor-can-adopt
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / reaction / ReactionAction.class.php
CommitLineData
15aeb641 1<?php
a9229942 2
15aeb641 3namespace wcf\data\reaction;
a9229942
TD
4
5use wcf\data\AbstractDatabaseObjectAction;
a5f117ba 6use wcf\data\like\ILikeObjectTypeProvider;
15aeb641 7use wcf\data\like\IRestrictedLikeObjectTypeProvider;
a5f117ba 8use wcf\data\like\Like;
15aeb641 9use wcf\data\like\LikeEditor;
a5f117ba 10use wcf\data\like\object\ILikeObject;
9b1ccf5c 11use wcf\data\like\object\LikeObjectEditor;
15aeb641 12use wcf\data\like\ViewableLikeList;
a5f117ba 13use wcf\data\object\type\ObjectType;
9b1ccf5c 14use wcf\data\object\type\ObjectTypeCache;
15aeb641
JR
15use wcf\data\reaction\type\ReactionType;
16use wcf\data\reaction\type\ReactionTypeCache;
9b1ccf5c
JR
17use wcf\data\user\User;
18use wcf\data\user\UserEditor;
9f6f1885 19use wcf\system\cache\runtime\UserProfileRuntimeCache;
15aeb641
JR
20use wcf\system\exception\IllegalLinkException;
21use wcf\system\exception\PermissionDeniedException;
22use wcf\system\exception\UserInputException;
23use wcf\system\reaction\ReactionHandler;
9b1ccf5c 24use wcf\system\user\activity\point\UserActivityPointHandler;
5df438d6 25use wcf\system\user\GroupedUserList;
15aeb641 26use wcf\system\WCF;
e768ff98 27use 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
41class 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}