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