55d5c4cfc7bdb0b428e107c984e5dffca8bbd071
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / poll / PollAction.class.php
1 <?php
2
3 namespace wcf\data\poll;
4
5 use wcf\data\AbstractDatabaseObjectAction;
6 use wcf\data\IGroupedUserListAction;
7 use wcf\data\object\type\ObjectTypeCache;
8 use wcf\data\poll\option\PollOptionEditor;
9 use wcf\data\poll\option\PollOptionList;
10 use wcf\system\exception\PermissionDeniedException;
11 use wcf\system\exception\UserInputException;
12 use wcf\system\poll\PollManager;
13 use wcf\system\user\GroupedUserList;
14 use wcf\system\WCF;
15
16 /**
17 * Executes poll-related actions.
18 *
19 * @author Alexander Ebert
20 * @copyright 2001-2019 WoltLab GmbH
21 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22 * @package WoltLabSuite\Core\Data\Poll
23 *
24 * @method PollEditor[] getObjects()
25 * @method PollEditor getSingleObject()
26 */
27 class PollAction extends AbstractDatabaseObjectAction implements IGroupedUserListAction
28 {
29 /**
30 * @inheritDoc
31 */
32 protected $allowGuestAccess = ['getGroupedUserList'];
33
34 /**
35 * @inheritDoc
36 */
37 protected $className = PollEditor::class;
38
39 /**
40 * poll object
41 * @var Poll
42 */
43 protected $poll;
44
45 /**
46 * @inheritDoc
47 * @return Poll
48 */
49 public function create()
50 {
51 if (!isset($this->parameters['data']['time'])) {
52 $this->parameters['data']['time'] = TIME_NOW;
53 }
54
55 /** @var Poll $poll */
56 $poll = parent::create();
57
58 // create options
59 $sql = "INSERT INTO wcf" . WCF_N . "_poll_option
60 (pollID, optionValue, showOrder)
61 VALUES (?, ?, ?)";
62 $statement = WCF::getDB()->prepareStatement($sql);
63
64 WCF::getDB()->beginTransaction();
65 foreach ($this->parameters['options'] as $showOrder => $option) {
66 $statement->execute([
67 $poll->pollID,
68 $option['optionValue'],
69 $showOrder,
70 ]);
71 }
72 WCF::getDB()->commitTransaction();
73
74 return $poll;
75 }
76
77 /**
78 * @inheritDoc
79 */
80 public function update()
81 {
82 parent::update();
83
84 // read current poll
85 $pollEditor = \reset($this->objects);
86
87 // get current options
88 $optionList = new PollOptionList();
89 $optionList->getConditionBuilder()->add("poll_option.pollID = ?", [$pollEditor->pollID]);
90 $optionList->sqlOrderBy = "poll_option.showOrder ASC";
91 $optionList->readObjects();
92 $options = $optionList->getObjects();
93
94 $newOptions = $updateOptions = [];
95 foreach ($this->parameters['options'] as $showOrder => $option) {
96 // check if editing an existing option
97 if ($option['optionID']) {
98 // check if an update is required
99 if ($options[$option['optionID']]->showOrder != $showOrder || $options[$option['optionID']]->optionValue != $option['optionValue']) {
100 $updateOptions[$option['optionID']] = [
101 'optionValue' => $option['optionValue'],
102 'showOrder' => $showOrder,
103 ];
104 }
105
106 // remove option
107 unset($options[$option['optionID']]);
108 } else {
109 $newOptions[] = [
110 'optionValue' => $option['optionValue'],
111 'showOrder' => $showOrder,
112 ];
113 }
114 }
115
116 if (!empty($newOptions) || !empty($updateOptions) || !empty($options)) {
117 WCF::getDB()->beginTransaction();
118
119 // check if new options should be created
120 if (!empty($newOptions)) {
121 $sql = "INSERT INTO wcf" . WCF_N . "_poll_option
122 (pollID, optionValue, showOrder)
123 VALUES (?, ?, ?)";
124 $statement = WCF::getDB()->prepareStatement($sql);
125 foreach ($newOptions as $option) {
126 $statement->execute([
127 $pollEditor->pollID,
128 $option['optionValue'],
129 $option['showOrder'],
130 ]);
131 }
132 }
133
134 // check if existing options should be updated
135 if (!empty($updateOptions)) {
136 $sql = "UPDATE wcf" . WCF_N . "_poll_option
137 SET optionValue = ?,
138 showOrder = ?
139 WHERE optionID = ?";
140 $statement = WCF::getDB()->prepareStatement($sql);
141 foreach ($updateOptions as $optionID => $option) {
142 $statement->execute([
143 $option['optionValue'],
144 $option['showOrder'],
145 $optionID,
146 ]);
147 }
148 }
149
150 // check if options should be removed
151 if (!empty($options)) {
152 $sql = "DELETE FROM wcf" . WCF_N . "_poll_option
153 WHERE optionID = ?";
154 $statement = WCF::getDB()->prepareStatement($sql);
155 foreach ($options as $option) {
156 $statement->execute([$option->optionID]);
157 }
158 }
159
160 // force recalculation of poll stats
161 $pollEditor->calculateVotes();
162
163 WCF::getDB()->commitTransaction();
164 }
165 }
166
167 /**
168 * Executes a user's vote.
169 */
170 public function vote()
171 {
172 $poll = \current($this->objects);
173
174 // get previous vote
175 $sql = "SELECT optionID
176 FROM wcf" . WCF_N . "_poll_option_vote
177 WHERE pollID = ?
178 AND userID = ?";
179 $statement = WCF::getDB()->prepareStatement($sql);
180 $statement->execute([
181 $poll->pollID,
182 WCF::getUser()->userID,
183 ]);
184 $optionIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
185 $alreadyVoted = !empty($optionIDs);
186
187 // calculate the difference
188 foreach ($this->parameters['optionIDs'] as $index => $optionID) {
189 $optionsIndex = \array_search($optionID, $optionIDs);
190 if ($optionsIndex !== false) {
191 // ignore this option
192 unset($this->parameters['optionIDs'][$index]);
193 unset($optionIDs[$optionsIndex]);
194 }
195 }
196
197 // insert new vote options
198 if (!empty($this->parameters['optionIDs'])) {
199 $sql = "INSERT INTO wcf" . WCF_N . "_poll_option_vote
200 (pollID, optionID, userID)
201 VALUES (?, ?, ?)";
202 $statement = WCF::getDB()->prepareStatement($sql);
203 foreach ($this->parameters['optionIDs'] as $optionID) {
204 $statement->execute([
205 $poll->pollID,
206 $optionID,
207 WCF::getUser()->userID,
208 ]);
209 }
210
211 // increase votes per option
212 $sql = "UPDATE wcf" . WCF_N . "_poll_option
213 SET votes = votes + 1
214 WHERE optionID = ?";
215 $statement = WCF::getDB()->prepareStatement($sql);
216 foreach ($this->parameters['optionIDs'] as $optionID) {
217 $statement->execute([$optionID]);
218 }
219 }
220
221 // remove previous options
222 if (!empty($optionIDs)) {
223 $sql = "DELETE FROM wcf" . WCF_N . "_poll_option_vote
224 WHERE optionID = ?
225 AND userID = ?";
226 $statement = WCF::getDB()->prepareStatement($sql);
227 foreach ($optionIDs as $optionID) {
228 $statement->execute([
229 $optionID,
230 WCF::getUser()->userID,
231 ]);
232 }
233
234 // decrease votes per option
235 $sql = "UPDATE wcf" . WCF_N . "_poll_option
236 SET votes = votes - 1
237 WHERE optionID = ?";
238 $statement = WCF::getDB()->prepareStatement($sql);
239 foreach ($optionIDs as $optionID) {
240 $statement->execute([$optionID]);
241 }
242 }
243
244 // increase poll votes
245 if (!$alreadyVoted) {
246 $poll->increaseVotes();
247 }
248 }
249
250 /**
251 * @inheritDoc
252 */
253 public function validateGetGroupedUserList()
254 {
255 $this->readInteger('pollID');
256
257 // read poll
258 $this->poll = new Poll($this->parameters['pollID']);
259 if (!$this->poll->pollID) {
260 throw new UserInputException('pollID');
261 } elseif (!$this->poll->canViewParticipants()) {
262 throw new PermissionDeniedException();
263 }
264 }
265
266 /**
267 * Validates the 'getResultTemplate' method.
268 *
269 * @throws UserInputException If not exactly one valid poll is given.
270 * @throws PermissionDeniedException If the current user cannot see the result of the poll.
271 * @since 5.5
272 */
273 public function validateGetResultTemplate(): void
274 {
275 $this->poll = $this->getSingleObject();
276 $this->loadRelatedObject();
277
278 if (!$this->poll->pollID) {
279 throw new UserInputException('pollID');
280 }
281
282 if (!$this->poll->canSeeResult()) {
283 throw new PermissionDeniedException();
284 }
285 }
286
287 /**
288 * Returns the result template for a specific poll.
289 *
290 * @since 5.5
291 */
292 public function getResultTemplate(): array
293 {
294 \assert($this->poll instanceof PollEditor);
295
296 return [
297 'template' => WCF::getTPL()->fetch('pollResult', 'wcf', [
298 'poll' => $this->poll->getDecoratedObject(),
299 ]),
300 ];
301 }
302
303 /**
304 * Validates the 'getVoteTemplate' method.
305 *
306 * @throws UserInputException If not exactly one valid poll is given.
307 * @throws PermissionDeniedException If the current user cannot vote the poll.
308 * @since 5.5
309 */
310 public function validateGetVoteTemplate(): void
311 {
312 $this->poll = $this->getSingleObject();
313 $this->loadRelatedObject();
314
315 if (!$this->poll->pollID) {
316 throw new UserInputException('pollID');
317 }
318
319 if (!$this->poll->canVote()) {
320 throw new PermissionDeniedException();
321 }
322 }
323
324 /**
325 * Returns the result template for a specific poll.
326 *
327 * @since 5.5
328 */
329 public function getVoteTemplate(): array
330 {
331 return [
332 'template' => WCF::getTPL()->fetch('pollVote', 'wcf', [
333 'poll' => $this->poll,
334 ]),
335 ];
336 }
337
338 private function loadRelatedObject(): void
339 {
340 \assert($this->poll instanceof PollEditor);
341
342 $relatedObject = PollManager::getInstance()->getRelatedObject($this->poll->getDecoratedObject());
343
344 $this->poll->setRelatedObject($relatedObject);
345 }
346
347 /**
348 * @inheritDoc
349 */
350 public function getGroupedUserList()
351 {
352 // get options
353 $sql = "SELECT optionID, optionValue
354 FROM wcf" . WCF_N . "_poll_option
355 WHERE pollID = ?
356 ORDER BY " . ($this->poll->sortByVotes ? "votes DESC" : "showOrder ASC");
357 $statement = WCF::getDB()->prepareStatement($sql);
358 $statement->execute([$this->poll->pollID]);
359 $options = [];
360 while ($row = $statement->fetchArray()) {
361 $options[$row['optionID']] = new GroupedUserList($row['optionValue'], 'wcf.poll.noVotes');
362 }
363
364 // get votes
365 $sql = "SELECT userID, optionID
366 FROM wcf" . WCF_N . "_poll_option_vote
367 WHERE pollID = ?";
368 $statement = WCF::getDB()->prepareStatement($sql);
369 $statement->execute([$this->poll->pollID]);
370 $voteData = $statement->fetchMap('optionID', 'userID', false);
371
372 // assign user ids
373 foreach ($voteData as $optionID => $userIDs) {
374 $options[$optionID]->addUserIDs($userIDs);
375 }
376
377 // load user profiles
378 GroupedUserList::loadUsers();
379
380 WCF::getTPL()->assign([
381 'groupedUsers' => $options,
382 ]);
383
384 return [
385 'pageCount' => 1,
386 'template' => WCF::getTPL()->fetch('groupedUserList'),
387 ];
388 }
389
390 /**
391 * Copies a poll from one object id to another.
392 */
393 public function copy()
394 {
395 $sourceObjectType = ObjectTypeCache::getInstance()->getObjectTypeByName(
396 'com.woltlab.wcf.poll',
397 $this->parameters['sourceObjectType']
398 );
399 $targetObjectType = ObjectTypeCache::getInstance()->getObjectTypeByName(
400 'com.woltlab.wcf.poll',
401 $this->parameters['targetObjectType']
402 );
403
404 //
405 // step 1) get data
406 //
407
408 // get poll
409 $sql = "SELECT *
410 FROM wcf" . WCF_N . "_poll
411 WHERE objectTypeID = ?
412 AND objectID = ?";
413 $statement = WCF::getDB()->prepareStatement($sql);
414 $statement->execute([
415 $sourceObjectType->objectTypeID,
416 $this->parameters['sourceObjectID'],
417 ]);
418 $row = $statement->fetchArray();
419
420 if ($row === false) {
421 return [
422 'pollID' => null,
423 ];
424 }
425
426 // get options
427 $pollOptionList = new PollOptionList();
428 $pollOptionList->getConditionBuilder()->add("poll_option.pollID = ?", [$row['pollID']]);
429 $pollOptionList->readObjects();
430
431 //
432 // step 2) copy
433 //
434
435 // create poll
436 $pollData = $row;
437 $pollData['objectTypeID'] = $targetObjectType->objectTypeID;
438 $pollData['objectID'] = $this->parameters['targetObjectID'];
439 unset($pollData['pollID']);
440
441 $newPoll = PollEditor::create($pollData);
442
443 // create options
444 $newOptionIDs = [];
445 foreach ($pollOptionList as $pollOption) {
446 $newOption = PollOptionEditor::create([
447 'pollID' => $newPoll->pollID,
448 'optionValue' => $pollOption->optionValue,
449 'votes' => $pollOption->votes,
450 'showOrder' => $pollOption->showOrder,
451 ]);
452
453 $newOptionIDs[$pollOption->optionID] = $newOption->optionID;
454 }
455
456 // copy votes
457 WCF::getDB()->beginTransaction();
458 foreach ($newOptionIDs as $oldOptionID => $newOptionID) {
459 $sql = "INSERT INTO wcf" . WCF_N . "_poll_option_vote
460 (pollID, optionID, userID)
461 SELECT " . $newPoll->pollID . ", " . $newOptionID . ", userID
462 FROM wcf" . WCF_N . "_poll_option_vote
463 WHERE optionID = ?";
464 $statement = WCF::getDB()->prepareStatement($sql);
465 $statement->execute([$oldOptionID]);
466 }
467 WCF::getDB()->commitTransaction();
468
469 return [
470 'pollID' => $newPoll->pollID,
471 ];
472 }
473 }