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