Issues (1686)

sources/ElkArte/Controller/Poll.php (3 issues)

1
<?php
2
3
/**
4
 * This receives requests for voting, locking, removing editing polls
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte\Controller;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Errors\ErrorContext;
21
use ElkArte\Exceptions\Exception;
22
use ElkArte\Helper\Util;
23
use ElkArte\Languages\Txt;
24
25
/**
26
 * This receives requests for voting, locking, removing and editing polls.
27
 * Note that that posting polls is done in Post.controller.php.
28
 */
29
class Poll extends AbstractController
30
{
31
	/**
32
	 * Forward to the right action.
33
	 *
34
	 * @see AbstractController::action_index
35
	 */
36
	public function action_index()
37
	{
38
		// Figure out the right action to do.
39
	}
40
41
	/**
42
	 * Allow the user to vote.
43
	 *
44
	 * What it does:
45
	 *
46
	 * - It is called to register a vote in a poll.
47
	 * - Must be called with a topic and option specified.
48
	 * - Requires the poll_vote permission.
49
	 * - Upon successful completion of action will direct user back to topic.
50
	 * - Accessed via ?action=poll;sa=vote.
51
	 *
52
	 * @uses Post language file.
53
	 */
54
	public function action_vote()
55
	{
56
		global $topic, $modSettings;
57
58
		require_once(SUBSDIR . '/Poll.subs.php');
59
60
		// Make sure you can vote.
61
		isAllowedTo('poll_vote');
62
63
		Txt::load('Post');
64
65
		// Check if they have already voted, or voting is locked.
66
		$row = checkVote($topic);
67
68
		if (empty($row))
69
		{
70
			throw new Exception('poll_error', false);
71
		}
72
73
		// If this is a guest can they vote?
74
		if ($this->user->is_guest)
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
75
		{
76
			// Guest voting disabled?
77
			if (!$row['guest_vote'])
78
			{
79
				throw new Exception('guest_vote_disabled');
80
			}
81
			// Guest already voted?
82
			if (!empty($_COOKIE['guest_poll_vote']) && preg_match('~^[0-9,;]+$~', $_COOKIE['guest_poll_vote']) && strpos($_COOKIE['guest_poll_vote'], ';' . $row['id_poll'] . ',') !== false)
83
			{
84
				// ;id,timestamp,[vote,vote...]; etc
85
				$guestinfo = explode(';', $_COOKIE['guest_poll_vote']);
86
				// Find the poll we're after.
87
				foreach ($guestinfo as $i => $guestvoted)
88
				{
89
					$guestvoted = explode(',', $guestvoted);
90
					if ($guestvoted[0] == $row['id_poll'])
91
					{
92
						break;
93
					}
94
				}
95
				// Has the poll been reset since guest voted?
96
				if (isset($guestvoted[1]) && $row['reset_poll'] > $guestvoted[1])
97
				{
98
					// Remove the poll info from the cookie to allow guest to vote again
99
					unset($guestinfo[$i]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i seems to be defined by a foreach iteration on line 87. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
100
					if (!empty($guestinfo))
101
					{
102
						$_COOKIE['guest_poll_vote'] = ';' . implode(';', $guestinfo);
103
					}
104
					else
105
					{
106
						unset($_COOKIE['guest_poll_vote']);
107
					}
108
				}
109
				else
110
				{
111
					throw new Exception('poll_error', false);
112
				}
113
114
				unset($guestinfo, $guestvoted, $i);
115
			}
116
		}
117
118
		// Is voting locked or has it expired?
119
		if (!empty($row['voting_locked']) || (!empty($row['expire_time']) && time() > $row['expire_time']))
120
		{
121
			throw new Exception('poll_error', false);
122
		}
123
		// If they have already voted and aren't allowed to change their vote - hence they are outta here!
124
		if ($this->user->is_guest === false && $row['selected'] != -1 && empty($row['change_vote']))
125
		{
126
			throw new Exception('poll_error', false);
127
		}
128
129
		// Otherwise if they can change their vote yet they haven't sent any options... remove their vote and redirect.
130
		if (!empty($row['change_vote']) && $this->user->is_guest === false && empty($this->_req->post->options))
131
		{
132
			checkSession('request');
133
134
			// Find out what they voted for before.
135
			$pollOptions = determineVote($this->user->id, $row['id_poll']);
136
137
			// Just skip it if they had voted for nothing before.
138
			if (!empty($pollOptions))
139
			{
140
				// Update the poll totals.
141
				decreaseVoteCounter($row['id_poll'], $pollOptions);
142
143
				// Delete off the log.
144
				removeVote($this->user->id, $row['id_poll']);
145
			}
146
			// Redirect back to the topic so the user can vote again!
147
			if (empty($this->_req->post->options))
148
			{
149
				redirectexit('topic=' . $topic . '.' . $this->_req->post->start);
150
			}
151
		}
152
153
		checkSession('request');
154
155
		// Make sure the option(s) are valid.
156
		if (empty($this->_req->post->options))
157
		{
158
			throw new Exception('didnt_select_vote', false);
159
		}
160
161
		// Too many options checked!
162
		if (count($this->_req->post->options) > $row['max_votes'])
163
		{
164
			throw new Exception('poll_too_many_votes', false, array($row['max_votes']));
165
		}
166
167
		$pollOptions = array();
168
		$inserts = array();
169
		foreach ($this->_req->post->options as $id)
170
		{
171
			$id = (int) $id;
172
173
			$pollOptions[] = $id;
174
			$inserts[] = array($row['id_poll'], $this->user->id, $id);
175
		}
176
177
		// Add their vote to the tally.
178
		addVote($inserts);
179
		increaseVoteCounter($row['id_poll'], $pollOptions);
180
181
		// If it's a guest don't let them vote again.
182
		if ($this->user->is_guest && $pollOptions !== [])
183
		{
184
			// Time is stored in case the poll is reset later, plus what they voted for.
185
			$_COOKIE['guest_poll_vote'] = empty($_COOKIE['guest_poll_vote']) ? '' : $_COOKIE['guest_poll_vote'];
186
187
			// ;id,timestamp,[vote,vote...]; etc
188
			$_COOKIE['guest_poll_vote'] .= ';' . $row['id_poll'] . ',' . time() . ',' . (count($pollOptions) > 1 ? implode(',', $pollOptions) : $pollOptions[0]);
189
190
			// Increase num guest voters count by 1
191
			increaseGuestVote($row['id_poll']);
192
193
			// Set the guest cookie so we can track the voting
194
			require_once(SUBSDIR . '/Auth.subs.php');
195
			$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
196
			elk_setcookie('guest_poll_vote', $_COOKIE['guest_poll_vote'], time() + 2500000, $cookie_url[1], $cookie_url[0], false, false);
197
		}
198
199
		// Maybe let a social networking mod log this, or something?
200
		call_integration_hook('integrate_poll_vote', array(&$row['id_poll'], &$pollOptions));
201
202
		// Return to the post...
203
		redirectexit('topic=' . $topic . '.' . $this->_req->post->start);
204
	}
205
206
	/**
207
	 * Lock the voting for a poll.
208
	 *
209
	 * What it does:
210
	 *
211
	 * - Must be called with a topic specified in the URL.
212
	 * - An admin always has over riding permission to lock a poll.
213
	 * - If not an admin must have poll_lock_any permission, otherwise must
214
	 * be poll starter with poll_lock_own permission.
215
	 * - Upon successful completion of action will direct user back to topic.
216
	 * - Accessed via ?action=lockvoting.
217
	 */
218
	public function action_lockvoting()
219
	{
220
		global $topic;
221
222
		require_once(SUBSDIR . '/Poll.subs.php');
223
224
		checkSession('get');
225
226
		// Get the poll starter, ID, and whether or not it is locked.
227
		$poll = pollInfoForTopic($topic);
228
229
		// If the user _can_ modify the poll....
230
		if (!allowedTo('poll_lock_any'))
231
		{
232
			isAllowedTo('poll_lock_' . ($this->user->id == $poll['id_member_started'] ? 'own' : 'any'));
233
		}
234
235
		// It's been locked by a non-moderator.
236
		if ($poll['locked'] == '1')
237
		{
238
			$poll['locked'] = '0';
239
		}
240
		// Locked by a moderator, and this is a moderator.
241
		elseif ($poll['locked'] == '2' && allowedTo('moderate_board'))
242
		{
243
			$poll['locked'] = '0';
244
		}
245
		// Sorry, a moderator locked it.
246
		elseif ($poll['locked'] == '2' && !allowedTo('moderate_board'))
247
		{
248
			throw new Exception('locked_by_admin', 'user');
249
		}
250
		// A moderator *is* locking it.
251
		elseif ($poll['locked'] == '0' && allowedTo('moderate_board'))
252
		{
253
			$poll['locked'] = '2';
254
		}
255
		// Well, it's gonna be locked one way or another otherwise...
256
		else
257
		{
258
			$poll['locked'] = '1';
259
		}
260
261
		// Lock!  *Poof* - no one can vote.
262
		lockPoll($poll['id_poll'], $poll['locked']);
263
264
		redirectexit('topic=' . $topic . '.' . $this->_req->post->start);
265
	}
266
267
	/**
268
	 * Update the settings for a poll, or add a new one.
269
	 *
270
	 * What it does:
271
	 *
272
	 * - Must be called with a topic specified in the URL.
273
	 * - The user must have poll_edit_any/poll_add_any permission for the relevant action. Otherwise
274
	 * they must be poll starter with poll_edit_own permission for editing, or be topic starter
275
	 * with poll_add_any permission for adding.
276
	 * - In the case of an error, this function will redirect back to action_editpoll and
277
	 * display the relevant error message.
278
	 * - Upon successful completion of action will direct user back to topic.
279
	 * - Accessed via ?action=editpoll2.
280
	 */
281
	public function action_editpoll2()
282
	{
283
		global $topic, $board;
284
285
		// Sneaking off, are we?
286
		if (empty($this->_req->post))
287
		{
288
			redirectexit('action=editpoll;topic=' . $topic . '.0');
289
		}
290
291
		$poll_errors = ErrorContext::context('poll');
292
293
		if (checkSession('post', '', false) !== '')
294
		{
295
			$poll_errors->addError('session_timeout');
296
		}
297
298
		// HACKERS (!!) can't edit :P.
299
		if (empty($topic))
300
		{
301
			throw new Exception('no_access', false);
302
		}
303
304
		// Is this a new poll, or editing an existing?
305
		$isEdit = isset($this->_req->post->add) ? 0 : 1;
306
307
		// Make sure we have our stuff.
308
		require_once(SUBSDIR . '/Poll.subs.php');
309
310
		// Get the starter and the poll's ID - if it's an edit.
311
		$bcinfo = getPollStarter($topic);
312
		// Check their adding/editing is valid.
313
		if (!$isEdit && !empty($bcinfo['id_poll']))
314
		{
315
			throw new Exception('poll_already_exists');
316
		}
317
318
		// Are we editing a poll which doesn't exist?
319
		if ($isEdit && empty($bcinfo['id_poll']))
320
		{
321
			throw new Exception('poll_not_found');
322
		}
323
324
		// Check if they have the power to add or edit the poll.
325
		if ($isEdit && !allowedTo('poll_edit_any'))
326
		{
327
			isAllowedTo('poll_edit_' . ($this->user->id == $bcinfo['id_member_started'] || ($bcinfo['poll_starter'] != 0 && $this->user->id == $bcinfo['poll_starter']) ? 'own' : 'any'));
328
		}
329
		elseif (!$isEdit && !allowedTo('poll_add_any'))
330
		{
331
			isAllowedTo('poll_add_' . ($this->user->id == $bcinfo['id_member_started'] ? 'own' : 'any'));
332
		}
333
334
		$optionCount = 0;
335
		$idCount = 0;
336
337
		// Ensure the user is leaving a valid amount of options - there must be at least two.
338
		foreach ($this->_req->post->options as $k => $option)
339
		{
340
			if (trim($option) !== '')
341
			{
342
				$optionCount++;
343
				$idCount = max($idCount, $k);
344
			}
345
		}
346
347
		if ($optionCount < 2)
348
		{
349
			$poll_errors->addError('poll_few');
350
		}
351
		elseif ($optionCount > 256 || $idCount > 255)
352
		{
353
			$poll_errors->addError('poll_many');
354
		}
355
356
		// Also - ensure they are not removing the question.
357
		if ($this->_req->getPost('question', 'trim') === '')
358
		{
359
			$poll_errors->addError('no_question');
360
		}
361
362
		// Got any errors to report?
363
		if ($poll_errors->hasErrors())
364
		{
365
			$this->action_editpoll();
366
		}
367
368
		// Prevent double submission of this form.
369
		checkSubmitOnce('check');
370
371
		// Now we've done all our error checking, let's get the core poll information cleaned... question first.
372
		$question = Util::htmlspecialchars($this->_req->getPost('question', 'trim'));
373
		$question = Util::substr($question, 0, 255);
374
375
		$poll_hide = $this->_req->getPost('poll_hide', 'intval', 0);
376
		$poll_expire = $this->_req->getPost('poll_expire', 'intval', 0);
377
		$poll_change_vote = isset($this->_req->post->poll_change_vote) ? 1 : 0;
378
		$poll_guest_vote = isset($this->_req->post->poll_guest_vote) ? 1 : 0;
379
		$poll_max_votes = 0;
380
381
		// Make sure guests are actually allowed to vote generally.
382
		if ($poll_guest_vote !== 0)
383
		{
384
			require_once(SUBSDIR . '/Members.subs.php');
385
			$allowedGroups = groupsAllowedTo('poll_vote', $board);
386
			if (!in_array(-1, $allowedGroups['allowed']))
387
			{
388
				$poll_guest_vote = 0;
389
			}
390
		}
391
392
		// Ensure that the number options allowed makes sense, and the expiration date is valid.
393
		if (!$isEdit || allowedTo('moderate_board'))
394
		{
395
			$poll_expire = $poll_expire > 9999 ? 9999 : ($poll_expire < 0 ? 0 : $poll_expire);
396
397
			if (empty($poll_expire) && $poll_hide == 2)
398
			{
399
				$poll_hide = 1;
400
			}
401
			elseif (!$isEdit || $poll_expire != ceil($bcinfo['expire_time'] <= time() ? -1 : ($bcinfo['expire_time'] - time()) / (3600 * 24)))
402
			{
403
				$poll_expire = empty($poll_expire) ? 0 : time() + $_POST['poll_expire'] * 3600 * 24;
404
			}
405
			else
406
			{
407
				$poll_expire = $bcinfo['expire_time'];
408
			}
409
410
			if (empty($this->_req->post->poll_max_votes) || $this->_req->post->poll_max_votes <= 0)
411
			{
412
				$poll_max_votes = 1;
413
			}
414
			else
415
			{
416
				$poll_max_votes = $this->_req->getPost('poll_max_votes', 'intval', 0);
417
			}
418
		}
419
420
		// If we're editing, let's commit the changes.
421
		if ($isEdit !== 0)
422
		{
423
			modifyPoll($bcinfo['id_poll'], $question,
424
				empty($poll_max_votes) ? 0 : $poll_max_votes,
425
				$poll_hide,
426
				empty($poll_expire) ? 0 : $poll_expire,
427
				$poll_change_vote, $poll_guest_vote
428
			);
429
		}
430
		// Otherwise, let's get our poll going!
431
		else
432
		{
433
			// Create the poll.
434
			$bcinfo['id_poll'] = createPoll($question, $this->user->id, $this->user->username,
435
				$poll_max_votes, $poll_hide, $poll_expire,
436
				$poll_change_vote, $poll_guest_vote
437
			);
438
439
			// Link the poll to the topic.
440
			associatedPoll($topic, $bcinfo['id_poll']);
441
		}
442
443
		// Get all the choices.  (no better way to remove all emptied and add previously non-existent ones.)
444
		$choices = array_keys(pollOptions($bcinfo['id_poll']));
445
446
		$add_options = array();
447
		$update_options = array();
448
		$delete_options = array();
449
		foreach ($this->_req->post->options as $k => $option)
450
		{
451
			// Make sure the key is numeric for sanity's sake.
452
			$k = (int) $k;
453
454
			// They've cleared the box.  Either they want it deleted, or it never existed.
455
			if (trim($option) === '')
456
			{
457
				// They want it deleted.  Bye.
458
				if (in_array($k, $choices))
459
				{
460
					$delete_options[] = $k;
461
				}
462
463
				// Skip the rest...
464
				continue;
465
			}
466
467
			// Dress the option up for its big date with the database.
468
			$option = Util::htmlspecialchars($option);
469
470
			// If it's already there, update it.  If it's not... add it.
471
			if (in_array($k, $choices))
472
			{
473
				$update_options[] = array($bcinfo['id_poll'], $k, $option);
474
			}
475
			else
476
			{
477
				$add_options[] = array($bcinfo['id_poll'], $k, $option, 0);
478
			}
479
		}
480
481
		if (!empty($update_options))
482
		{
483
			modifyPollOption($update_options);
484
		}
485
486
		if (!empty($add_options))
487
		{
488
			insertPollOptions($add_options);
489
		}
490
491
		// I'm sorry, but... well, no one was choosing you. Poor options, I'll put you out of your misery.
492
		if (!empty($delete_options))
493
		{
494
			deletePollOptions($bcinfo['id_poll'], $delete_options);
495
		}
496
497
		// Shall I reset the vote count, sir?
498
		if (isset($this->_req->post->resetVoteCount))
499
		{
500
			resetVotes($bcinfo['id_poll']);
501
		}
502
503
		call_integration_hook('integrate_poll_add_edit', array($bcinfo['id_poll'], $isEdit));
504
505
		// Off we go.
506
		redirectexit('topic=' . $topic . '.' . $this->_req->post->start);
507
	}
508
509
	/**
510
	 * Display screen for editing or adding a poll.
511
	 *
512
	 * What it does:
513
	 *
514
	 * - Must be called with a topic specified in the URL.
515
	 * - If the user is adding a poll to a topic, must contain the variable
516
	 * 'add' in the url.
517
	 * - User must have poll_edit_any/poll_add_any permission for the relevant action,
518
	 * otherwise must be poll starter with poll_edit_own permission for editing, or
519
	 * be topic starter with poll_add_any permission for adding.
520
	 * - Accessed via ?action=editpoll.
521
	 *
522
	 * @uses Post language file.
523
	 * @uses template_poll_edit() sub-template in Poll.template,
524
	 */
525
	public function action_editpoll()
526
	{
527
		global $txt, $context, $topic, $board;
528
529
		// No topic, means you can't edit the poll
530
		if (empty($topic))
531
		{
532
			throw new Exception('no_access', false);
533
		}
534
535
		// We work hard with polls.
536
		require_once(SUBSDIR . '/Poll.subs.php');
537
538
		Txt::load('Post');
539
		theme()->getTemplates()->load('Poll');
540
		loadJavascriptFile('post.js', array(), 'post_scripts');
541
542
		$context['sub_template'] = 'poll_edit';
543
		$context['start'] = $this->_req->getQuery('start', 'intval');
544
		$context['is_edit'] = isset($this->_req->post->add) ? 0 : 1;
545
546
		$poll_errors = ErrorContext::context('poll');
547
		$pollinfo = pollInfoForTopic($topic);
548
549
		// Assume it all exists, right?
550
		if (empty($pollinfo))
551
		{
552
			throw new Exception('no_board');
553
		}
554
555
		// If we are adding a new poll - make sure that there isn't already a poll there.
556
		if (!$context['is_edit'] && !empty($pollinfo['id_poll']))
557
		{
558
			throw new Exception('poll_already_exists');
559
		}
560
561
		// Otherwise, if we're editing it, it does exist I assume?
562
		if ($context['is_edit'] && empty($pollinfo['id_poll']))
563
		{
564
			throw new Exception('poll_not_found');
565
		}
566
567
		// Can you do this?
568
		if ($context['is_edit'] && !allowedTo('poll_edit_any'))
569
		{
570
			isAllowedTo('poll_edit_' . ($this->user->id == $pollinfo['id_member_started'] || ($pollinfo['poll_starter'] != 0 && $this->user->id == $pollinfo['poll_starter']) ? 'own' : 'any'));
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
571
		}
572
		elseif (!$context['is_edit'] && !allowedTo('poll_add_any'))
573
		{
574
			isAllowedTo('poll_add_' . ($this->user->id == $pollinfo['id_member_started'] ? 'own' : 'any'));
575
		}
576
577
		$context['can_moderate_poll'] = isset($this->_req->post->add) ? true : allowedTo('poll_edit_' . ($this->user->id == $pollinfo['id_member_started'] || ($pollinfo['poll_starter'] != 0 && $this->user->id == $pollinfo['poll_starter']) ? 'own' : 'any'));
578
579
		// Do we enable guest voting?
580
		require_once(SUBSDIR . '/Members.subs.php');
581
		$groupsAllowedVote = groupsAllowedTo('poll_vote', $board);
582
583
		// Want to make sure before you actually submit?  Must be a lot of options, or something.
584
		if ($poll_errors->hasErrors())
585
		{
586
			$question = Util::htmlspecialchars($this->_req->post->question);
587
588
			// Basic theme info...
589
			$context['poll'] = array(
590
				'id' => $pollinfo['id_poll'],
591
				'question' => $question,
592
				'hide_results' => empty($this->_req->post->poll_hide) ? 0 : $this->_req->post->poll_hide,
593
				'change_vote' => isset($this->_req->post->poll_change_vote),
594
				'guest_vote' => isset($this->_req->post->poll_guest_vote),
595
				'guest_vote_allowed' => in_array(-1, $groupsAllowedVote['allowed']),
596
				'max_votes' => empty($this->_req->post->poll_max_votes) ? '1' : max(1, $this->_req->post->poll_max_votes),
597
			);
598
599
			// Start at number one with no last id to speak of.
600
			$number = 1;
601
			$last_id = 0;
602
603
			// Get all the choices - if this is an edit.
604
			if ($context['is_edit'])
605
			{
606
				$pollOptions = pollOptions($pollinfo['id_poll']);
607
				$context['poll']['choices'] = array();
608
609
				foreach ($pollOptions as $option)
610
				{
611
					// Get the highest id so we can add more without reusing.
612
					if ($option['id_choice'] >= $last_id)
613
					{
614
						$last_id = $option['id_choice'] + 1;
615
					}
616
617
					// They cleared this by either omitting it or emptying it.
618
					if (!isset($this->_req->post->options[$option['id_choice']]) || $this->_req->post->options[$option['id_choice']] == '')
619
					{
620
						continue;
621
					}
622
623
					// Add the choice!
624
					$context['poll']['choices'][$option['id_choice']] = array(
625
						'id' => $option['id_choice'],
626
						'number' => $number++,
627
						'votes' => $option['votes'],
628
						'label' => $option['label'],
629
						'is_last' => false
630
					);
631
				}
632
			}
633
634
			// Work out how many options we have, so we get the 'is_last' field right...
635
			$totalPostOptions = 0;
636
			foreach ($this->_req->post->options as $id => $label)
637
			{
638
				if ($label !== '')
639
				{
640
					$totalPostOptions++;
641
				}
642
			}
643
644
			$count = 1;
645
646
			// If an option exists, update it.  If it is new, add it - but don't reuse ids!
647
			foreach ($this->_req->post->options as $id => $label)
648
			{
649
				$label = censor(Util::htmlspecialchars($label));
650
651
				if (isset($context['poll']['choices'][$id]))
652
				{
653
					$context['poll']['choices'][$id]['label'] = $label;
654
				}
655
				elseif ($label !== '')
656
				{
657
					$context['poll']['choices'][] = array(
658
						'id' => $last_id++,
659
						'number' => $number++,
660
						'label' => $label,
661
						'votes' => -1,
662
						'is_last' => $count++ === $totalPostOptions && $totalPostOptions > 1,
663
					);
664
				}
665
			}
666
667
			// Make sure we have two choices for sure!
668
			if ($totalPostOptions < 2)
669
			{
670
				// Need two?
671
				if ($totalPostOptions === 0)
672
				{
673
					$context['poll']['choices'][] = array(
674
						'id' => $last_id++,
675
						'number' => $number++,
676
						'label' => '',
677
						'votes' => -1,
678
						'is_last' => false
679
					);
680
				}
681
682
				$poll_errors->addError('poll_few');
683
			}
684
685
			// Always show one extra box...
686
			$context['poll']['choices'][] = array(
687
				'id' => $last_id++,
688
				'number' => $number,
689
				'label' => '',
690
				'votes' => -1,
691
				'is_last' => true
692
			);
693
694
			$context['last_choice_id'] = $last_id;
695
696
			if ($context['can_moderate_poll'])
697
			{
698
				$context['poll']['expiration'] = (int) $this->_req->post->poll_expire;
699
			}
700
701
			// Check the question/option count for errors.
702
			// @todo: why !$poll_errors->hasErrors()?
703
			if (trim($this->_req->post->question) === '' && !$poll_errors->hasErrors())
704
			{
705
				$poll_errors->addError('no_question');
706
			}
707
708
			// No check is needed, since nothing is really posted.
709
			checkSubmitOnce('free');
710
711
			// Take a check for any errors... assuming we haven't already done so!
712
			$context['poll_error'] = array(
713
				'errors' => $poll_errors->prepareErrors(),
714
				'type' => $poll_errors->getErrorType() == 0 ? 'minor' : 'serious',
715
				'title' => $context['is_edit'] ? $txt['error_while_editing_poll'] : $txt['error_while_adding_poll'],
716
			);
717
		}
718
		else
719
		{
720
			// Basic theme info...
721
			$context['poll'] = array(
722
				'id' => $pollinfo['id_poll'],
723
				'question' => $pollinfo['question'],
724
				'hide_results' => $pollinfo['hide_results'],
725
				'max_votes' => $pollinfo['max_votes'],
726
				'change_vote' => !empty($pollinfo['change_vote']),
727
				'guest_vote' => !empty($pollinfo['guest_vote']),
728
				'guest_vote_allowed' => in_array(-1, $groupsAllowedVote['allowed']),
729
			);
730
731
			// Poll expiration time?
732
			$context['poll']['expiration'] = empty($pollinfo['expire_time']) || !$context['can_moderate_poll'] ? '' : ceil($pollinfo['expire_time'] <= time() ? -1 : ($pollinfo['expire_time'] - time()) / (3600 * 24));
733
734
			// Get all the choices - if this is an edit.
735
			if ($context['is_edit'])
736
			{
737
				$context['poll']['choices'] = getPollChoices($pollinfo['id_poll']);
738
739
				$last_id = max(array_keys($context['poll']['choices'])) + 1;
740
741
				// Add an extra choice...
742
				$context['poll']['choices'][] = array(
743
					'id' => $last_id,
744
					'number' => $context['poll']['choices'][$last_id - 1]['number'] + 1,
745
					'votes' => -1,
746
					'label' => '',
747
					'is_last' => true
748
				);
749
				$context['last_choice_id'] = $last_id;
750
			}
751
			// New poll?
752
			else
753
			{
754
				// Setup the default poll options.
755
				$context['poll'] = array(
756
					'id' => 0,
757
					'question' => '',
758
					'hide_results' => 0,
759
					'max_votes' => 1,
760
					'change_vote' => 0,
761
					'guest_vote' => 0,
762
					'guest_vote_allowed' => in_array(-1, $groupsAllowedVote['allowed']),
763
					'expiration' => '',
764
				);
765
766
				// Make all five poll choices empty.
767
				$context['poll']['choices'] = array(
768
					array('id' => 0, 'number' => 1, 'votes' => -1, 'label' => '', 'is_last' => false),
769
					array('id' => 1, 'number' => 2, 'votes' => -1, 'label' => '', 'is_last' => false),
770
					array('id' => 2, 'number' => 3, 'votes' => -1, 'label' => '', 'is_last' => false),
771
					array('id' => 3, 'number' => 4, 'votes' => -1, 'label' => '', 'is_last' => false),
772
					array('id' => 4, 'number' => 5, 'votes' => -1, 'label' => '', 'is_last' => true)
773
				);
774
				$context['last_choice_id'] = 4;
775
			}
776
		}
777
778
		$context['page_title'] = $context['is_edit'] ? $txt['poll_edit'] : $txt['add_poll'];
779
		$context['form_url'] = getUrl('action', ['action' => 'editpoll2'] + ($context['is_edit'] ? [] : ['add']) + ['topic' => $context['current_topic'] . '.' . $context['start']]);
780
781
		// Build the link tree.
782
		$pollinfo['subject'] = censor($pollinfo['subject']);
783
		$context['breadcrumbs'][] = array(
784
			'url' => getUrl('topic', ['topic' => $topic, 'start' => '0', 'subject' => $pollinfo['subject']]),
785
			'name' => $pollinfo['subject'],
786
		);
787
		$context['breadcrumbs'][] = [
788
			'name' => $context['page_title'],
789
		];
790
791
		// Register this form in the session variables.
792
		checkSubmitOnce('register');
793
	}
794
795
	/**
796
	 * Remove a poll from a topic without removing the topic.
797
	 *
798
	 * What it does:
799
	 *
800
	 * - Must be called with a topic specified in the URL.
801
	 * - Requires poll_remove_any permission, unless it's the poll starter
802
	 * with poll_remove_own permission.
803
	 * - Upon successful completion of action will direct user back to topic.
804
	 * - Accessed via ?action=poll;sa=remove.
805
	 */
806
	public function action_remove()
807
	{
808
		global $topic;
809
810
		// Make sure the topic is not empty.
811
		if (empty($topic))
812
		{
813
			throw new Exception('no_access', false);
814
		}
815
816
		// Verify the session.
817
		checkSession('get');
818
819
		// We need to work with them polls.
820
		require_once(SUBSDIR . '/Poll.subs.php');
821
822
		// Check permissions.
823
		if (!allowedTo('poll_remove_any'))
824
		{
825
			$pollStarters = pollStarters($topic);
826
			if (empty($pollStarters))
827
			{
828
				throw new Exception('no_access', false);
829
			}
830
831
			[$topicStarter, $pollStarter] = $pollStarters;
832
			if ($topicStarter == $this->user->id || ($pollStarter != 0 && $pollStarter == $this->user->id))
833
			{
834
				isAllowedTo('poll_remove_own');
835
			}
836
		}
837
838
		// Retrieve the poll ID.
839
		$pollID = associatedPoll($topic);
840
841
		// Remove the poll!
842
		removePoll($pollID);
843
844
		// Finally set the topic poll ID back to 0!
845
		associatedPoll($topic, 0);
846
847
		// A mod might have logged this (social network?), so let them remove, it too
848
		call_integration_hook('integrate_poll_remove', array($pollID));
849
850
		// Take the moderator back to the topic.
851
		redirectexit('topic=' . $topic . '.' . $this->_req->post->start);
852
	}
853
854
	/**
855
	 * The only reason of this function is to build the poll UI and send it back in an XML form
856
	 */
857
	public function action_interface()
858
	{
859
		global $context, $board, $db_show_debug;
860
861
		theme()->getLayers()->removeAll();
862
		theme()->getTemplates()->load('Poll');
863
		Txt::load('Post');
864
865
		$db_show_debug = false;
866
867
		$context['sub_template'] = 'poll_edit';
868
869
		require_once(SUBSDIR . '/Members.subs.php');
870
		$allowedVoteGroups = groupsAllowedTo('poll_vote', $board);
871
872
		// Set up the poll options.
873
		$context['poll'] = array(
874
			'max_votes' => 1,
875
			'hide_results' => 0,
876
			'expiration' => '',
877
			'change_vote' => false,
878
			'guest_vote' => false,
879
			'guest_vote_allowed' => in_array(-1, $allowedVoteGroups['allowed']),
880
		);
881
882
		$context['can_moderate_poll'] = true;
883
884
		// Make all five poll choices empty.
885
		$context['poll']['choices'] = array(
886
			array('id' => 0, 'number' => 1, 'label' => '', 'is_last' => false),
887
			array('id' => 1, 'number' => 2, 'label' => '', 'is_last' => false),
888
			array('id' => 2, 'number' => 3, 'label' => '', 'is_last' => false),
889
			array('id' => 3, 'number' => 4, 'label' => '', 'is_last' => false),
890
			array('id' => 4, 'number' => 5, 'label' => '', 'is_last' => true)
891
		);
892
		$context['last_choice_id'] = 4;
893
	}
894
}
895