2020-06-14 23:42:17 +03:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
|
|
|
*
|
|
|
|
* @author René Gieling <github@dartcafe.de>
|
|
|
|
*
|
|
|
|
* @license GNU AGPL version 3 or any later version
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace OCA\Polls\Service;
|
|
|
|
|
2020-08-22 08:09:01 +03:00
|
|
|
use DateTime;
|
2021-05-24 20:48:35 +03:00
|
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
|
|
|
use Psr\Log\LoggerInterface;
|
2020-06-22 21:43:42 +03:00
|
|
|
use OCP\AppFramework\Db\DoesNotExistException;
|
2021-05-23 10:33:05 +03:00
|
|
|
use OCP\DB\Exception;
|
2021-05-24 20:48:35 +03:00
|
|
|
use OCP\EventDispatcher\IEventDispatcher;
|
|
|
|
|
2020-08-20 20:27:58 +03:00
|
|
|
use OCA\Polls\Exceptions\DuplicateEntryException;
|
2021-03-07 13:47:18 +03:00
|
|
|
use OCA\Polls\Exceptions\InvalidPollTypeException;
|
2021-05-08 23:13:52 +03:00
|
|
|
use OCA\Polls\Exceptions\InvalidOptionPropertyException;
|
2021-05-24 20:48:35 +03:00
|
|
|
|
2020-06-14 23:42:17 +03:00
|
|
|
use OCA\Polls\Db\OptionMapper;
|
2021-03-02 16:41:39 +03:00
|
|
|
use OCA\Polls\Db\VoteMapper;
|
|
|
|
use OCA\Polls\Db\Vote;
|
2020-07-11 17:54:09 +03:00
|
|
|
use OCA\Polls\Db\Option;
|
|
|
|
use OCA\Polls\Db\Poll;
|
2021-12-29 02:58:24 +03:00
|
|
|
use OCA\Polls\Event\OptionUpdatedEvent;
|
2021-05-24 20:48:35 +03:00
|
|
|
use OCA\Polls\Event\OptionConfirmedEvent;
|
|
|
|
use OCA\Polls\Event\OptionCreatedEvent;
|
|
|
|
use OCA\Polls\Event\OptionDeletedEvent;
|
2021-12-29 02:58:24 +03:00
|
|
|
use OCA\Polls\Event\OptionUnconfirmedEvent;
|
|
|
|
use OCA\Polls\Event\PollOptionReorderedEvent;
|
2020-06-14 23:42:17 +03:00
|
|
|
use OCA\Polls\Model\Acl;
|
|
|
|
|
2020-07-11 18:32:57 +03:00
|
|
|
class OptionService {
|
2020-06-14 23:42:17 +03:00
|
|
|
|
2021-05-24 20:48:35 +03:00
|
|
|
/** @var IEventDispatcher */
|
|
|
|
private $eventDispatcher;
|
|
|
|
|
2021-03-24 01:18:47 +03:00
|
|
|
/** @var LoggerInterface */
|
|
|
|
private $logger;
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
private $appName;
|
|
|
|
|
2021-01-31 00:59:40 +03:00
|
|
|
/** @var Acl */
|
|
|
|
private $acl;
|
2020-07-11 17:54:09 +03:00
|
|
|
|
2021-12-13 22:17:40 +03:00
|
|
|
/** @var AnonymizeService */
|
|
|
|
private $anonymizer;
|
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
/** @var Option */
|
2020-06-14 23:42:17 +03:00
|
|
|
private $option;
|
2020-07-11 17:54:09 +03:00
|
|
|
|
2021-03-02 16:41:39 +03:00
|
|
|
/** @var Poll */
|
|
|
|
private $poll;
|
|
|
|
|
|
|
|
/** @var Option[] */
|
|
|
|
private $options;
|
|
|
|
|
|
|
|
/** @var Vote[] */
|
|
|
|
private $votes;
|
|
|
|
|
2021-01-31 00:59:40 +03:00
|
|
|
/** @var OptionMapper */
|
|
|
|
private $optionMapper;
|
|
|
|
|
2021-03-02 16:41:39 +03:00
|
|
|
/** @var VoteMapper */
|
|
|
|
private $voteMapper;
|
|
|
|
|
2020-06-14 23:42:17 +03:00
|
|
|
public function __construct(
|
2021-03-24 01:18:47 +03:00
|
|
|
string $AppName,
|
2021-01-31 00:59:40 +03:00
|
|
|
Acl $acl,
|
2021-12-13 22:17:40 +03:00
|
|
|
AnonymizeService $anonymizer,
|
2021-05-24 20:48:35 +03:00
|
|
|
IEventDispatcher $eventDispatcher,
|
|
|
|
LoggerInterface $logger,
|
2020-06-14 23:42:17 +03:00
|
|
|
Option $option,
|
2021-01-31 00:59:40 +03:00
|
|
|
OptionMapper $optionMapper,
|
2021-05-24 20:48:35 +03:00
|
|
|
VoteMapper $voteMapper
|
2020-06-14 23:42:17 +03:00
|
|
|
) {
|
2021-03-24 01:18:47 +03:00
|
|
|
$this->appName = $AppName;
|
2021-01-31 00:59:40 +03:00
|
|
|
$this->acl = $acl;
|
2021-12-13 22:17:40 +03:00
|
|
|
$this->anonymizer = $anonymizer;
|
2021-05-24 20:48:35 +03:00
|
|
|
$this->eventDispatcher = $eventDispatcher;
|
|
|
|
$this->logger = $logger;
|
2020-06-14 23:42:17 +03:00
|
|
|
$this->option = $option;
|
2021-01-31 00:59:40 +03:00
|
|
|
$this->optionMapper = $optionMapper;
|
2021-03-02 16:41:39 +03:00
|
|
|
$this->voteMapper = $voteMapper;
|
2021-10-25 22:07:59 +03:00
|
|
|
$this->options = [];
|
|
|
|
$this->poll = new Poll;
|
|
|
|
$this->votes = [];
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Get all options of given poll
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return Option[]
|
|
|
|
*
|
|
|
|
* @psalm-return array<array-key, Option>
|
2020-06-14 23:42:17 +03:00
|
|
|
*/
|
2022-05-19 23:53:52 +03:00
|
|
|
public function list(?int $pollId, ?string $token = null): array {
|
2020-11-30 22:29:24 +03:00
|
|
|
if ($token) {
|
2021-05-10 18:29:11 +03:00
|
|
|
$this->acl->setToken($token);
|
2020-11-30 22:29:24 +03:00
|
|
|
} else {
|
2021-05-10 18:29:11 +03:00
|
|
|
$this->acl->setPollId($pollId);
|
2020-11-30 22:29:24 +03:00
|
|
|
}
|
2020-06-14 23:42:17 +03:00
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
try {
|
2022-05-19 23:53:52 +03:00
|
|
|
$this->options = $this->optionMapper->findByPoll($this->acl->getPollId());
|
|
|
|
|
2021-12-13 22:17:40 +03:00
|
|
|
if (!$this->acl->getIsAllowed(Acl::PERMISSION_POLL_USERNAMES_VIEW)) {
|
|
|
|
$this->anonymizer->set($this->acl->getpollId(), $this->acl->getUserId());
|
2022-05-19 23:53:52 +03:00
|
|
|
$this->anonymizer->anonymize($this->options);
|
|
|
|
} elseif (!$this->acl->getIsLoggedIn()) {
|
2022-05-17 21:19:05 +03:00
|
|
|
// if participant is not logged in avoid leaking user ids
|
2022-05-18 16:26:46 +03:00
|
|
|
AnonymizeService::replaceUserId($this->options, $this->acl->getUserId());
|
2022-05-17 21:19:05 +03:00
|
|
|
}
|
|
|
|
|
2022-05-19 23:53:52 +03:00
|
|
|
$this->votes = $this->voteMapper->findByPoll($this->acl->getPollId());
|
2021-12-13 22:17:40 +03:00
|
|
|
|
2021-03-02 16:41:39 +03:00
|
|
|
$this->calculateVotes();
|
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
if ($this->acl->getPoll()->getHideBookedUp() && !$this->acl->getIsAllowed(Acl::PERMISSION_POLL_EDIT)) {
|
2021-03-02 16:41:39 +03:00
|
|
|
// hide booked up options except the user has edit permission
|
|
|
|
$this->filterBookedUp();
|
2021-11-14 21:09:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->acl->getIsAllowed(Acl::PERMISSION_POLL_RESULTS_VIEW)) {
|
2021-03-02 16:41:39 +03:00
|
|
|
$this->calculateRanks();
|
|
|
|
}
|
2020-07-11 17:54:09 +03:00
|
|
|
} catch (DoesNotExistException $e) {
|
2022-05-19 23:53:52 +03:00
|
|
|
$this->options = [];
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
2022-05-19 23:53:52 +03:00
|
|
|
|
|
|
|
return array_values($this->options);
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
|
|
|
|
2020-08-17 11:35:43 +03:00
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Get option
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
2020-08-17 11:35:43 +03:00
|
|
|
* @return Option
|
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
public function get(int $optionId): Option {
|
2021-05-10 18:29:11 +03:00
|
|
|
$option = $this->optionMapper->find($optionId);
|
|
|
|
$this->acl->setPollId($option->getPollId());
|
|
|
|
return $option;
|
2020-08-17 11:35:43 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
|
2020-06-14 23:42:17 +03:00
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Add a new option
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
2020-07-11 17:54:09 +03:00
|
|
|
* @return Option
|
2020-06-14 23:42:17 +03:00
|
|
|
*/
|
2022-05-21 00:46:35 +03:00
|
|
|
public function add(?int $pollId, int $timestamp = 0, string $pollOptionText = '', ?int $duration = 0, string $token = ''): Option {
|
2021-04-18 23:10:53 +03:00
|
|
|
if ($token) {
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setToken($token, Acl::PERMISSION_OPTIONS_ADD);
|
2021-04-18 23:10:53 +03:00
|
|
|
} else {
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setPollId($pollId, Acl::PERMISSION_OPTIONS_ADD);
|
2021-04-18 23:10:53 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
$this->option = new Option();
|
2021-05-12 00:46:47 +03:00
|
|
|
$this->option->setPollId($this->acl->getPollId());
|
|
|
|
$this->option->setOrder($this->getHighestOrder($this->acl->getPollId()) + 1);
|
2021-01-24 15:16:16 +03:00
|
|
|
$this->setOption($timestamp, $pollOptionText, $duration);
|
2021-03-20 00:33:47 +03:00
|
|
|
|
2021-03-21 12:03:38 +03:00
|
|
|
if (!$this->acl->getIsOwner()) {
|
|
|
|
$this->option->setOwner($this->acl->getUserId());
|
|
|
|
}
|
2021-03-20 00:33:47 +03:00
|
|
|
|
2020-08-20 20:27:58 +03:00
|
|
|
try {
|
2021-01-31 00:59:40 +03:00
|
|
|
$this->option = $this->optionMapper->insert($this->option);
|
2021-07-08 00:54:53 +03:00
|
|
|
} catch (UniqueConstraintViolationException $e) {
|
|
|
|
// deprecated NC22
|
|
|
|
throw new DuplicateEntryException('This option already exists');
|
2021-06-15 12:43:17 +03:00
|
|
|
} catch (Exception $e) {
|
2021-05-23 10:33:05 +03:00
|
|
|
if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
|
|
|
|
throw new DuplicateEntryException('This option already exists');
|
|
|
|
}
|
|
|
|
throw $e;
|
2020-08-20 20:27:58 +03:00
|
|
|
}
|
2021-05-23 10:33:05 +03:00
|
|
|
|
2021-05-24 20:48:35 +03:00
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionCreatedEvent($this->option));
|
|
|
|
|
2021-01-31 00:59:40 +03:00
|
|
|
return $this->option;
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
2022-03-26 15:18:29 +03:00
|
|
|
/**
|
|
|
|
* Add a new option
|
|
|
|
*
|
|
|
|
* @return Option[]
|
|
|
|
*/
|
|
|
|
public function addBulk(int $pollId, string $pollOptionText = ''): array {
|
|
|
|
$this->acl->setPollId($pollId, Acl::PERMISSION_OPTIONS_ADD);
|
|
|
|
|
|
|
|
$newOptions = array_unique(explode(PHP_EOL, $pollOptionText));
|
|
|
|
foreach ($newOptions as $option) {
|
|
|
|
if ($option) {
|
|
|
|
try {
|
|
|
|
$this->add($pollId, 0, $option);
|
|
|
|
} catch (DuplicateEntryException $e) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $this->list($pollId);
|
|
|
|
}
|
2020-06-14 23:42:17 +03:00
|
|
|
|
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Update option
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
2020-06-14 23:42:17 +03:00
|
|
|
* @return Option
|
|
|
|
*/
|
2021-01-24 15:16:16 +03:00
|
|
|
public function update(int $optionId, int $timestamp = 0, ?string $pollOptionText = '', ?int $duration = 0): Option {
|
2020-07-11 17:54:09 +03:00
|
|
|
$this->option = $this->optionMapper->find($optionId);
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setPollId($this->option->getPollId(), Acl::PERMISSION_POLL_EDIT);
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2021-01-24 15:16:16 +03:00
|
|
|
$this->setOption($timestamp, $pollOptionText, $duration);
|
2020-06-21 19:55:23 +03:00
|
|
|
|
2021-01-31 00:59:40 +03:00
|
|
|
$this->option = $this->optionMapper->update($this->option);
|
2021-12-29 02:58:24 +03:00
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionUpdatedEvent($this->option));
|
2021-05-24 20:48:35 +03:00
|
|
|
|
2021-01-31 00:59:40 +03:00
|
|
|
return $this->option;
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Delete option
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return Option
|
2020-06-14 23:42:17 +03:00
|
|
|
*/
|
2021-04-18 23:10:53 +03:00
|
|
|
public function delete(int $optionId, string $token = ''): Option {
|
2020-06-14 23:42:17 +03:00
|
|
|
$this->option = $this->optionMapper->find($optionId);
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2021-04-18 23:10:53 +03:00
|
|
|
if ($token) {
|
2021-11-16 11:12:18 +03:00
|
|
|
$this->acl->setToken($token, Acl::PERMISSION_POLL_VIEW, $this->option->getPollId());
|
2021-04-18 23:10:53 +03:00
|
|
|
} else {
|
|
|
|
$this->acl->setPollId($this->option->getPollId());
|
|
|
|
}
|
|
|
|
|
2021-05-10 18:33:53 +03:00
|
|
|
if ($this->option->getOwner() !== $this->acl->getUserId()) {
|
|
|
|
$this->acl->request(Acl::PERMISSION_POLL_EDIT);
|
2021-03-20 00:33:47 +03:00
|
|
|
}
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2020-06-14 23:42:17 +03:00
|
|
|
$this->optionMapper->delete($this->option);
|
2021-05-24 20:48:35 +03:00
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionDeletedEvent($this->option));
|
2020-06-14 23:42:17 +03:00
|
|
|
|
|
|
|
return $this->option;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Switch option confirmation
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return Option
|
2020-06-14 23:42:17 +03:00
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
public function confirm(int $optionId): Option {
|
2020-07-11 17:54:09 +03:00
|
|
|
$this->option = $this->optionMapper->find($optionId);
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setPollId($this->option->getPollId(), Acl::PERMISSION_POLL_EDIT);
|
2020-06-14 23:42:17 +03:00
|
|
|
|
2021-01-24 15:16:16 +03:00
|
|
|
$this->option->setConfirmed($this->option->getConfirmed() ? 0 : time());
|
2021-01-31 00:59:40 +03:00
|
|
|
$this->option = $this->optionMapper->update($this->option);
|
2021-05-12 00:46:47 +03:00
|
|
|
|
2021-12-29 02:58:24 +03:00
|
|
|
if ($this->option->getConfirmed()) {
|
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionConfirmedEvent($this->option));
|
|
|
|
} else {
|
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionUnconfirmedEvent($this->option));
|
|
|
|
}
|
2021-05-12 00:46:47 +03:00
|
|
|
|
2021-01-31 00:59:40 +03:00
|
|
|
return $this->option;
|
2020-07-11 17:54:09 +03:00
|
|
|
}
|
|
|
|
|
2020-08-22 08:09:01 +03:00
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Make a sequence of date poll options
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return Option[]
|
|
|
|
*
|
|
|
|
* @psalm-return array<array-key, Option>
|
2020-08-22 08:09:01 +03:00
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
public function sequence(int $optionId, int $step, string $unit, int $amount): array {
|
2020-12-06 00:43:20 +03:00
|
|
|
$this->option = $this->optionMapper->find($optionId);
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setPollId($this->option->getPollId(), Acl::PERMISSION_POLL_EDIT);
|
2020-08-22 08:09:01 +03:00
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
if ($this->acl->getPoll()->getType() !== Poll::TYPE_DATE) {
|
2021-05-10 18:33:53 +03:00
|
|
|
throw new InvalidPollTypeException('Sequences are only available in date polls');
|
|
|
|
}
|
2021-03-07 13:47:18 +03:00
|
|
|
|
2020-08-22 08:09:01 +03:00
|
|
|
if ($step === 0) {
|
2021-05-12 00:46:47 +03:00
|
|
|
return $this->optionMapper->findByPoll($this->acl->getPollId());
|
2020-08-22 08:09:01 +03:00
|
|
|
}
|
|
|
|
|
2021-05-10 18:33:53 +03:00
|
|
|
$baseDate = new DateTime;
|
2020-12-06 00:43:20 +03:00
|
|
|
$baseDate->setTimestamp($this->option->getTimestamp());
|
2020-08-22 08:09:01 +03:00
|
|
|
|
2020-09-01 00:57:18 +03:00
|
|
|
for ($i = 0; $i < $amount; $i++) {
|
2020-12-06 00:43:20 +03:00
|
|
|
$clonedOption = new Option();
|
2021-05-12 00:46:47 +03:00
|
|
|
$clonedOption->setPollId($this->acl->getPollId());
|
2021-01-24 15:16:16 +03:00
|
|
|
$clonedOption->setDuration($this->option->getDuration());
|
2020-12-06 00:43:20 +03:00
|
|
|
$clonedOption->setConfirmed(0);
|
|
|
|
$clonedOption->setTimestamp($baseDate->modify($step . ' ' . $unit)->getTimestamp());
|
2021-01-17 15:59:44 +03:00
|
|
|
$clonedOption->setOrder($clonedOption->getTimestamp());
|
2020-12-06 00:43:20 +03:00
|
|
|
$clonedOption->setPollOptionText($baseDate->format('c'));
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2020-08-22 08:09:01 +03:00
|
|
|
try {
|
2020-12-06 00:43:20 +03:00
|
|
|
$this->optionMapper->insert($clonedOption);
|
2021-07-08 00:54:53 +03:00
|
|
|
} catch (UniqueConstraintViolationException $e) {
|
|
|
|
// deprecated NC22
|
|
|
|
$this->logger->warning('skip adding ' . $baseDate->format('c') . 'for pollId' . $this->option->getPollId() . '. Option already exists.');
|
2021-05-23 10:33:05 +03:00
|
|
|
} catch (Exception $e) {
|
|
|
|
if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
|
|
|
|
$this->logger->warning('skip adding ' . $baseDate->format('c') . 'for pollId' . $this->option->getPollId() . '. Option already exists.');
|
|
|
|
}
|
|
|
|
throw $e;
|
2020-08-22 08:09:01 +03:00
|
|
|
}
|
|
|
|
}
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2021-05-24 20:48:35 +03:00
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionCreatedEvent($this->option));
|
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
return $this->optionMapper->findByPoll($this->acl->getPollId());
|
2020-08-22 08:09:01 +03:00
|
|
|
}
|
|
|
|
|
2021-03-07 13:47:18 +03:00
|
|
|
/**
|
|
|
|
* Shift all date options
|
|
|
|
*
|
|
|
|
* @return Option[]
|
|
|
|
*
|
|
|
|
* @psalm-return array<array-key, Option>
|
|
|
|
*/
|
|
|
|
public function shift(int $pollId, int $step, string $unit): array {
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT);
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
if ($this->acl->getPoll()->getType() !== Poll::TYPE_DATE) {
|
2021-05-10 18:33:53 +03:00
|
|
|
throw new InvalidPollTypeException('Shifting is only available in date polls');
|
2021-03-07 13:47:18 +03:00
|
|
|
}
|
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
$this->options = $this->optionMapper->findByPoll($this->acl->getPollId());
|
2021-03-07 13:47:18 +03:00
|
|
|
|
|
|
|
if ($step > 0) {
|
2021-05-10 18:33:53 +03:00
|
|
|
// avoid UniqueConstraintViolationException
|
2021-05-23 10:33:05 +03:00
|
|
|
// start from last item
|
2021-03-07 13:47:18 +03:00
|
|
|
$this->options = array_reverse($this->options);
|
|
|
|
}
|
|
|
|
|
2021-05-10 18:33:53 +03:00
|
|
|
$shiftedDate = new DateTime;
|
2021-03-07 13:47:18 +03:00
|
|
|
foreach ($this->options as $option) {
|
|
|
|
$shiftedDate->setTimestamp($option->getTimestamp());
|
|
|
|
$option->setTimestamp($shiftedDate->modify($step . ' ' . $unit)->getTimestamp());
|
|
|
|
$this->optionMapper->update($option);
|
|
|
|
}
|
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
return $this->optionMapper->findByPoll($this->acl->getPollId());
|
2021-03-07 13:47:18 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Copy options from $fromPoll to $toPoll
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return Option[]
|
|
|
|
*
|
|
|
|
* @psalm-return array<array-key, Option>
|
2020-07-11 17:54:09 +03:00
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
public function clone(int $fromPollId, int $toPollId): array {
|
2020-11-30 22:29:24 +03:00
|
|
|
$this->acl->setPollId($fromPollId);
|
2020-06-21 19:55:23 +03:00
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
foreach ($this->optionMapper->findByPoll($fromPollId) as $origin) {
|
|
|
|
$option = new Option();
|
|
|
|
$option->setPollId($toPollId);
|
|
|
|
$option->setConfirmed(0);
|
|
|
|
$option->setPollOptionText($origin->getPollOptionText());
|
|
|
|
$option->setTimestamp($origin->getTimestamp());
|
2021-01-24 15:16:16 +03:00
|
|
|
$option->setDuration($origin->getDuration());
|
2021-05-10 18:33:53 +03:00
|
|
|
$option->setOrder($origin->getOrder());
|
2020-07-11 17:54:09 +03:00
|
|
|
$this->optionMapper->insert($option);
|
2021-12-29 02:58:24 +03:00
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionCreatedEvent($option));
|
2020-07-11 17:54:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->optionMapper->findByPoll($toPollId);
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-02 16:41:39 +03:00
|
|
|
* Reorder options with the order specified by $options
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return Option[]
|
|
|
|
*
|
|
|
|
* @psalm-return array<array-key, Option>
|
2020-06-14 23:42:17 +03:00
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
public function reorder(int $pollId, array $options): array {
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT);
|
2020-07-11 17:54:09 +03:00
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
if ($this->acl->getPoll()->getType() === Poll::TYPE_DATE) {
|
2021-05-10 18:33:53 +03:00
|
|
|
throw new InvalidPollTypeException('Not allowed in date polls');
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
$i = 0;
|
|
|
|
foreach ($options as $option) {
|
|
|
|
$this->option = $this->optionMapper->find($option['id']);
|
2021-05-12 00:46:47 +03:00
|
|
|
if ($this->acl->getPollId() === intval($this->option->getPollId())) {
|
2020-06-14 23:42:17 +03:00
|
|
|
$this->option->setOrder(++$i);
|
|
|
|
$this->optionMapper->update($this->option);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-29 02:58:24 +03:00
|
|
|
$this->eventDispatcher->dispatchTyped(new OptionUpdatedEvent($this->option));
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
return $this->optionMapper->findByPoll($this->acl->getPollId());
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|
2020-06-20 12:38:34 +03:00
|
|
|
|
|
|
|
/**
|
2021-03-02 16:41:39 +03:00
|
|
|
* Change order for $optionId and reorder the options
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return Option[]
|
|
|
|
*
|
|
|
|
* @psalm-return array<array-key, Option>
|
2020-06-20 12:38:34 +03:00
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
public function setOrder(int $optionId, int $newOrder): array {
|
2021-05-10 18:33:53 +03:00
|
|
|
$this->option = $this->optionMapper->find($optionId);
|
2021-05-11 22:45:48 +03:00
|
|
|
$this->acl->setPollId($this->option->getPollId(), Acl::PERMISSION_POLL_EDIT);
|
2021-05-10 18:33:53 +03:00
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
if ($this->acl->getPoll()->getType() === Poll::TYPE_DATE) {
|
2021-05-10 18:33:53 +03:00
|
|
|
throw new InvalidPollTypeException('Not allowed in date polls');
|
2020-06-21 19:55:23 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
if ($newOrder < 1) {
|
|
|
|
$newOrder = 1;
|
2021-05-12 00:46:47 +03:00
|
|
|
} elseif ($newOrder > $this->getHighestOrder($this->acl->getPollId())) {
|
|
|
|
$newOrder = $this->getHighestOrder($this->acl->getPollId());
|
2020-07-11 17:54:09 +03:00
|
|
|
}
|
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
foreach ($this->optionMapper->findByPoll($this->acl->getPollId()) as $option) {
|
2020-11-08 21:08:48 +03:00
|
|
|
$option->setOrder($this->moveModifier($this->option->getOrder(), $newOrder, $option->getOrder()));
|
2020-11-08 20:09:13 +03:00
|
|
|
$this->optionMapper->update($option);
|
2020-07-11 17:54:09 +03:00
|
|
|
}
|
|
|
|
|
2021-12-29 02:58:24 +03:00
|
|
|
$this->eventDispatcher->dispatchTyped(new PollOptionReorderedEvent($this->acl->getPoll()));
|
2021-05-24 20:48:35 +03:00
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
return $this->optionMapper->findByPoll($this->acl->getPollId());
|
2020-07-11 17:54:09 +03:00
|
|
|
}
|
|
|
|
|
2020-11-08 20:09:13 +03:00
|
|
|
/**
|
2021-03-02 16:41:39 +03:00
|
|
|
* moveModifier - evaluate new order depending on the old and
|
|
|
|
* the new position of a moved array item
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
2021-03-02 16:41:39 +03:00
|
|
|
* @return int - The modified new new position of the current item
|
2020-11-08 20:09:13 +03:00
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
private function moveModifier(int $moveFrom, int $moveTo, int $currentPosition): int {
|
2020-11-08 20:09:13 +03:00
|
|
|
$moveModifier = 0;
|
|
|
|
if ($moveFrom < $currentPosition && $currentPosition <= $moveTo) {
|
|
|
|
// moving forward
|
|
|
|
$moveModifier = -1;
|
|
|
|
} elseif ($moveTo <= $currentPosition && $currentPosition < $moveFrom) {
|
|
|
|
//moving backwards
|
|
|
|
$moveModifier = 1;
|
|
|
|
} elseif ($moveFrom === $currentPosition) {
|
|
|
|
return $moveTo;
|
|
|
|
}
|
|
|
|
return $currentPosition + $moveModifier;
|
|
|
|
}
|
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
/**
|
|
|
|
* Set option entities validated
|
2021-03-02 16:41:39 +03:00
|
|
|
*
|
|
|
|
* @return void
|
2020-07-11 17:54:09 +03:00
|
|
|
*/
|
2021-01-24 15:16:16 +03:00
|
|
|
private function setOption(int $timestamp = 0, ?string $pollOptionText = '', ?int $duration = 0): void {
|
2021-05-12 00:46:47 +03:00
|
|
|
if ($timestamp) {
|
2020-11-08 11:55:29 +03:00
|
|
|
$this->option->setTimestamp($timestamp);
|
|
|
|
$this->option->setOrder($timestamp);
|
2021-05-08 23:13:52 +03:00
|
|
|
$this->option->setDuration($duration ?? 0);
|
2021-03-06 18:22:45 +03:00
|
|
|
if ($duration > 0) {
|
2021-03-02 16:41:39 +03:00
|
|
|
$this->option->setPollOptionText(date('c', $timestamp) . ' - ' . date('c', $timestamp + $duration));
|
2021-01-24 15:16:16 +03:00
|
|
|
} else {
|
2021-03-06 18:22:45 +03:00
|
|
|
$this->option->setPollOptionText(date('c', $timestamp));
|
2021-01-24 15:16:16 +03:00
|
|
|
}
|
2021-05-08 23:45:46 +03:00
|
|
|
} elseif ($pollOptionText) {
|
2020-11-08 20:09:13 +03:00
|
|
|
$this->option->setPollOptionText($pollOptionText);
|
2021-05-08 23:13:52 +03:00
|
|
|
} else {
|
|
|
|
throw new InvalidOptionPropertyException('Option must have a value');
|
2020-07-11 17:54:09 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-02 16:41:39 +03:00
|
|
|
/**
|
|
|
|
* Get all voteOptionTexts of the options, the user opted in
|
|
|
|
* with yes or maybe
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getUsersVotes() {
|
2021-05-14 21:26:47 +03:00
|
|
|
$userVotes = [];
|
|
|
|
foreach ($this->votes as $vote) {
|
|
|
|
if ($vote->getUserId() === $this->acl->getUserId() && in_array($vote->getVoteAnswer(), ['yes', 'maybe'])) {
|
|
|
|
$userVotes[] = $vote->getVoteOptionText();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $userVotes;
|
2021-03-02 16:41:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove booked up options, because they are not votable
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function filterBookedUp() {
|
|
|
|
$exceptVotes = $this->getUsersVotes();
|
|
|
|
$this->options = array_filter($this->options, function ($option) use ($exceptVotes) {
|
2021-05-08 23:13:52 +03:00
|
|
|
return (!$option->isBookedUp || in_array($option->getPollOptionText(), $exceptVotes));
|
2021-03-02 16:41:39 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the votes of each option and determines if the option is booked up
|
|
|
|
* - unvoted counts as no
|
|
|
|
* - realNo reports the actually opted out votes
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function calculateVotes() {
|
|
|
|
foreach ($this->options as $option) {
|
|
|
|
$option->yes = count(
|
|
|
|
array_filter($this->votes, function ($vote) use ($option) {
|
|
|
|
return ($vote->getVoteOptionText() === $option->getPollOptionText()
|
|
|
|
&& $vote->getVoteAnswer() === 'yes') ;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
$option->realNo = count(
|
|
|
|
array_filter($this->votes, function ($vote) use ($option) {
|
|
|
|
return ($vote->getVoteOptionText() === $option->getPollOptionText()
|
|
|
|
&& $vote->getVoteAnswer() === 'no');
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
$option->maybe = count(
|
|
|
|
array_filter($this->votes, function ($vote) use ($option) {
|
|
|
|
return ($vote->getVoteOptionText() === $option->getPollOptionText()
|
|
|
|
&& $vote->getVoteAnswer() === 'maybe');
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2021-05-12 00:46:47 +03:00
|
|
|
$option->isBookedUp = $this->acl->getPoll()->getOptionLimit() ? $this->acl->getPoll()->getOptionLimit() <= $option->yes : false;
|
2021-03-02 16:41:39 +03:00
|
|
|
|
2021-03-06 18:22:45 +03:00
|
|
|
// remove details, if the results shall be hidden
|
2021-05-11 22:45:48 +03:00
|
|
|
if (!$this->acl->getIsAllowed(Acl::PERMISSION_POLL_RESULTS_VIEW)) {
|
2021-03-02 16:41:39 +03:00
|
|
|
$option->yes = 0;
|
|
|
|
$option->no = 0;
|
|
|
|
$option->maybe = 0;
|
|
|
|
$option->realNo = 0;
|
|
|
|
} else {
|
2021-05-12 00:46:47 +03:00
|
|
|
$option->no = count($this->voteMapper->findParticipantsByPoll($this->acl->getPollId())) - $option->maybe - $option->yes;
|
2021-05-10 18:33:53 +03:00
|
|
|
$option->no = $this->countParticipants() - $option->maybe - $option->yes;
|
2021-03-02 16:41:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-10 18:33:53 +03:00
|
|
|
private function countParticipants(): int {
|
2021-05-12 00:46:47 +03:00
|
|
|
return count($this->voteMapper->findParticipantsByPoll($this->acl->getPollId()));
|
2021-05-10 18:33:53 +03:00
|
|
|
}
|
|
|
|
|
2021-03-02 16:41:39 +03:00
|
|
|
/**
|
|
|
|
* Calculate the rank of each option based on the
|
|
|
|
* yes and maybe votes and recognize equal ranked options
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function calculateRanks() {
|
|
|
|
// sort array by yes and maybe votes
|
2021-05-08 23:13:52 +03:00
|
|
|
usort($this->options, function (Option $a, Option $b):int {
|
2021-03-02 16:41:39 +03:00
|
|
|
$diff = $b->yes - $a->yes;
|
|
|
|
return ($diff !== 0) ? $diff : $b->maybe - $a->maybe;
|
|
|
|
});
|
|
|
|
|
|
|
|
// calculate the rank
|
|
|
|
$count = count($this->options);
|
|
|
|
for ($i = 0; $i < $count; $i++) {
|
|
|
|
if ($i > 0 && $this->options[$i]->yes === $this->options[$i - 1]->yes && $this->options[$i]->maybe === $this->options[$i - 1]->maybe) {
|
|
|
|
$this->options[$i]->rank = $this->options[$i - 1]->rank;
|
|
|
|
} else {
|
|
|
|
$this->options[$i]->rank = $i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// restore original order
|
2021-05-08 23:13:52 +03:00
|
|
|
usort($this->options, function (Option $a, Option $b):int {
|
2021-03-02 16:41:39 +03:00
|
|
|
return $a->getOrder() - $b->getOrder();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-11 17:54:09 +03:00
|
|
|
/**
|
2021-03-06 18:22:45 +03:00
|
|
|
* Get the highest order number in $pollId
|
|
|
|
* Return Highest order number
|
2020-12-05 22:44:00 +03:00
|
|
|
*
|
|
|
|
* @return int
|
2020-07-11 17:54:09 +03:00
|
|
|
*/
|
2020-12-05 22:44:00 +03:00
|
|
|
private function getHighestOrder(int $pollId): int {
|
2020-11-08 20:09:13 +03:00
|
|
|
$highestOrder = 0;
|
2020-07-11 17:54:09 +03:00
|
|
|
foreach ($this->optionMapper->findByPoll($pollId) as $option) {
|
2021-01-24 15:16:16 +03:00
|
|
|
$highestOrder = ($option->getOrder() > $highestOrder) ? $option->getOrder() : $highestOrder;
|
2020-07-11 17:54:09 +03:00
|
|
|
}
|
2020-11-08 20:09:13 +03:00
|
|
|
return $highestOrder;
|
2020-06-20 12:38:34 +03:00
|
|
|
}
|
2020-06-14 23:42:17 +03:00
|
|
|
}
|