Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

join votes for participation check #3373

Merged
merged 16 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@

use OCA\Polls\AppConstants;
use OCA\Polls\Dashboard\PollWidget;
use OCA\Polls\Db\CommentMapper;
use OCA\Polls\Db\LogMapper;
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\SubscriptionMapper;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Db\VoteMapper;
use OCA\Polls\Event\CommentAddEvent;
use OCA\Polls\Event\CommentDeleteEvent;
use OCA\Polls\Event\CommentEvent;
Expand Down Expand Up @@ -64,14 +71,23 @@
use OCA\Polls\Listener\UserDeletedListener;
use OCA\Polls\Listener\VoteListener;
use OCA\Polls\Middleware\RequestAttributesMiddleware;
use OCA\Polls\Model\Settings\AppSettings;
use OCA\Polls\Notification\Notifier;
use OCA\Polls\Provider\SearchProvider;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\ISession;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\User\Events\UserDeletedEvent;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

/**
* @psalm-api
Expand All @@ -90,6 +106,7 @@ public function boot(IBootContext $context): void {

public function register(IRegistrationContext $context): void {
include_once __DIR__ . '/../../vendor/autoload.php';
$this->registerServices($context);

$context->registerMiddleWare(RequestAttributesMiddleware::class);
$context->registerNotifierService(Notifier::class);
Expand Down Expand Up @@ -132,4 +149,66 @@ public function register(IRegistrationContext $context): void {
$context->registerSearchProvider(SearchProvider::class);
$context->registerDashboardWidget(PollWidget::class);
}

/**
* Register some Services
*/
private function registerServices(IRegistrationContext $context) {
$context->registerService(UserMapper::class, function (ContainerInterface $c): UserMapper {
return new UserMapper(
$c->get(IDBConnection::class),
$c->get(ISession::class),
$c->get(IUserSession::class),
$c->get(IUserManager::class),
$c->get(LoggerInterface::class),
);
});
Comment on lines +157 to +165
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is all this needed? This is the default DI behavior to my knowledge.

Copy link
Collaborator Author

@dartcafe dartcafe Mar 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was annoyed by the unit tests, where every dependency needed to be instantiated.
If there is a DI possibility for the tests, this is indeed unnecessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually you use Mocks for the tests

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Tests. See #3147 😉


$context->registerService(AppSettings::class, function (ContainerInterface $c): AppSettings {
return new AppSettings(
$c->get(IConfig::class),
$c->get(IGroupManager::class),
$c->get(IUserSession::class),
);
});

$context->registerService(PollMapper::class, function (ContainerInterface $c): PollMapper {
return new PollMapper(
$c->get(IDBConnection::class),
$c->get(UserMapper::class)
);
});

$context->registerService(CommentMapper::class, function (ContainerInterface $c): CommentMapper {
return new CommentMapper(
$c->get(IDBConnection::class),
);
});

$context->registerService(VoteMapper::class, function (ContainerInterface $c): VoteMapper {
return new VoteMapper(
$c->get(IDBConnection::class),
$c->get(LoggerInterface::class),
);
});

$context->registerService(OptionMapper::class, function (ContainerInterface $c): OptionMapper {
return new OptionMapper(
$c->get(IDBConnection::class),
$c->get(UserMapper::class),
);
});

$context->registerService(SubscriptionMapper::class, function (ContainerInterface $c): SubscriptionMapper {
return new SubscriptionMapper(
$c->get(IDBConnection::class),
);
});

$context->registerService(LogMapper::class, function (ContainerInterface $c): LogMapper {
return new LogMapper(
$c->get(IDBConnection::class),
);
});
}
}
3 changes: 2 additions & 1 deletion lib/Controller/PollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\Server;

/**
* @psalm-api
Expand All @@ -56,7 +57,7 @@ public function __construct(
#[NoAdminRequired]
public function list(): JSONResponse {
return $this->response(function () {
$appSettings = new AppSettings;
$appSettings = Server::get(AppSettings::class);
return [
'list' => $this->pollService->list(),
'pollCreationAllowed' => $appSettings->getPollCreationAllowed(),
Expand Down
3 changes: 2 additions & 1 deletion lib/Cron/JanitorCron.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use OCA\Polls\Model\Settings\AppSettings;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Server;

/**
* @psalm-api
Expand All @@ -52,7 +53,7 @@ public function __construct(
) {
parent::__construct($time);
parent::setInterval(86400); // run once a day
$this->appSettings = new AppSettings;
$this->appSettings = Server::get(AppSettings::class);
}

/**
Expand Down
7 changes: 7 additions & 0 deletions lib/Db/Poll.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class Poll extends EntityWithUser implements JsonSerializable {
protected bool $hasOrphanedVotes = false;
protected int $maxDate = 0;
protected int $minDate = 0;
protected int $currentUserVotes = 0;

public function __construct() {
$this->addType('created', 'int');
Expand All @@ -153,6 +154,7 @@ public function __construct() {
$this->addType('lastInteraction', 'int');
$this->addType('maxDate', 'int');
$this->addType('minDate', 'int');
$this->addType('currentUserVotes', 'int');
$this->urlGenerator = Container::queryClass(IURLGenerator::class);
$this->userMapper = Container::queryClass(UserMapper::class);
$this->voteMapper = Container::queryClass(VoteMapper::class);
Expand Down Expand Up @@ -190,6 +192,7 @@ public function jsonSerialize(): array {
'summary' => [
'orphanedVotes' => count($this->voteMapper->findOrphanedByPollandUser($this->id, $this->userMapper->getCurrentUserCached()->getId())),
'yesByCurrentUser' => count($this->voteMapper->getYesVotesByParticipant($this->getPollId(), $this->userMapper->getCurrentUserCached()->getId())),
'countVotes' => $this->getCurrentUserCountVotes(),
],
];
}
Expand Down Expand Up @@ -275,6 +278,10 @@ public function getProposalsExpired(): bool {
);
}

public function getCurrentUserCountVotes(): int {
return $this->currentUserVotes;
}

public function getDescription(): string {
return $this->description ?? '';
}
Expand Down
29 changes: 28 additions & 1 deletion lib/Db/PollMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ class PollMapper extends QBMapper {
/**
* @psalm-suppress PossiblyUnusedMethod
*/
public function __construct(IDBConnection $db) {
public function __construct(
IDBConnection $db,
private UserMapper $userMapper,
) {
parent::__construct($db, Poll::TABLE, Poll::class);
}

Expand Down Expand Up @@ -167,13 +170,15 @@ public function deleteByUserId(string $userId): void {
* Build the enhanced query with joined tables
*/
protected function buildQuery(): IQueryBuilder {
$currentUserId = $this->userMapper->getCurrentUser()->getId();
$qb = $this->db->getQueryBuilder();

$qb->select(self::TABLE . '.*')
// TODO: check if this is necessary, in case of empty table to avoid possibly nulled columns
// ->groupBy(self::TABLE . '.id')
->from($this->getTableName(), self::TABLE);
$this->joinOptionsForMaxDate($qb, self::TABLE);
$this->joinCurrentUserVotes($qb, self::TABLE, $currentUserId);
$qb->groupBy(self::TABLE . '.id');
return $qb;
}
Expand Down Expand Up @@ -201,4 +206,26 @@ protected function joinOptionsForMaxDate(IQueryBuilder &$qb, string $fromAlias):
);
}

/**
* Joins options to evaluate min and max option date for date polls
* if text poll or no options are set,
* the min value is the current time,
* the max value is null
*/
protected function joinCurrentUserVotes(IQueryBuilder &$qb, string $fromAlias, $currentUserId): void {
$joinAlias = 'user_vote';
// force value into a MIN function to avoid grouping errors
$qb->selectAlias($qb->func()->count($joinAlias . '.vote_answer'), 'current_user_votes');

$qb->leftJoin(
$fromAlias,
Vote::TABLE,
$joinAlias,
$qb->expr()->andX(
$qb->expr()->eq($joinAlias . '.poll_id', $fromAlias . '.id'),
$qb->expr()->eq($joinAlias . '.user_id', $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR)),
)
);
}

}
6 changes: 3 additions & 3 deletions lib/Db/Share.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@

use JsonSerializable;
use OCA\Polls\AppConstants;
use OCA\Polls\Helper\Container;
use OCA\Polls\Model\Settings\AppSettings;
use OCP\IURLGenerator;
use OCP\Server;

/**
* @psalm-suppress UnusedProperty
Expand Down Expand Up @@ -146,8 +146,8 @@ public function __construct() {
$this->addType('locked', 'int');
$this->addType('reminderSent', 'int');
$this->addType('deleted', 'int');
$this->urlGenerator = Container::queryClass(IURLGenerator::class);
$this->appSettings = new AppSettings;
$this->urlGenerator = Server::get(IURLGenerator::class);
$this->appSettings = Server::get(AppSettings::class);
}

/**
Expand Down
13 changes: 0 additions & 13 deletions lib/Db/VoteMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,6 @@ public function findParticipantsByPoll(int $pollId): array {
return $this->findEntities($qb);
}


/**
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @return Vote[]
* @psalm-return array<array-key, Vote>
*/
public function findParticipantsVotes(int $pollId, string $userId): array {
$qb = $this->buildQuery();
$qb->andWhere($qb->expr()->eq(self::TABLE . '.poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq(self::TABLE . '.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
return $this->findEntities($qb);
}

public function deleteByPollAndUserId(int $pollId, string $userId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
Expand Down
19 changes: 6 additions & 13 deletions lib/Helper/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,27 @@
use OCA\Polls\Db\Share;
use OCA\Polls\Db\ShareMapper;
use OCP\App\IAppManager;
use OCP\AppFramework\App;
use OCP\IL10N;
use OCP\L10N\IFactory;
use Psr\Container\ContainerInterface;
use OCP\Server;

abstract class Container {
public static function getContainer(): ContainerInterface {
$app = new App(AppConstants::APP_ID);
return $app->getContainer();
}

public static function queryClass(string $class): mixed {
return self::getContainer()->get($class);
return Server::get($class);
}

public static function queryPoll(int $pollId): Poll {
return self::queryClass(PollMapper::class)->find($pollId);
return Server::get(PollMapper::class)->find($pollId);
}

public static function findShare(int $pollId, string $userId): Share {
return self::queryClass(ShareMapper::class)
->findByPollAndUser($pollId, $userId);
return Server::get(ShareMapper::class)->findByPollAndUser($pollId, $userId);
}

public static function getL10N(?string $lang = null): IL10N {
return self::queryClass(IFactory::class)->get(AppConstants::APP_ID, $lang);
return Server::get(IFactory::class)->get(AppConstants::APP_ID, $lang);
}
public static function isAppEnabled(string $app): bool {
return self::queryClass(IAppManager::class)->isEnabledForUser($app);
return Server::get(IAppManager::class)->isEnabledForUser($app);
}
}
7 changes: 1 addition & 6 deletions lib/Model/Acl.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
use OCA\Polls\Db\Share;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Db\VoteMapper;
use OCA\Polls\Exceptions\ForbiddenException;
use OCA\Polls\Exceptions\InsufficientAttributesException;
use OCA\Polls\Exceptions\InvalidPollIdException;
Expand Down Expand Up @@ -79,12 +78,10 @@ public function __construct(
private ISession $session,
private ShareMapper $shareMapper,
private UserMapper $userMapper,
private VoteMapper $voteMapper,
private Poll $poll,
private Share $share,
) {
$this->pollId = null;
$this->appSettings = new AppSettings;
}

/**
Expand Down Expand Up @@ -323,9 +320,7 @@ private function getIsOpenPoll(): bool {
* @return bool Returns true, if the current user is already a particitipant of the current poll.
*/
private function getIsParticipant(): bool {
return count(
$this->voteMapper->findParticipantsVotes($this->getPollId(), $this->getUserId())
) > 0;
return $this->getPoll()->getCurrentUserCountVotes() > 0;
}

/**
Expand Down
16 changes: 7 additions & 9 deletions lib/Model/Settings/AppSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

use JsonSerializable;
use OCA\Polls\AppConstants;
use OCA\Polls\Helper\Container;
use OCA\Polls\Model\Group\Group;
use OCP\IConfig;
use OCP\IGroupManager;
Expand Down Expand Up @@ -64,16 +63,15 @@ class AppSettings implements JsonSerializable {
public const SETTING_UPDATE_TYPE_PERIODIC_POLLING = 'periodicPolling';
public const SETTING_UPDATE_TYPE_DEFAULT = self::SETTING_UPDATE_TYPE_NO_POLLING;

private IConfig $config;
private IGroupManager $groupManager;
private IUserSession $session;
private string $userId = '';

public function __construct(
private IConfig $config,
private IGroupManager $groupManager,
private IUserSession $session,

public function __construct() {
$this->config = Container::queryClass(IConfig::class);
$this->session = Container::queryClass(IUserSession::class);
$this->userId = Container::queryClass(IUserSession::class)->getUser()?->getUId() ?? '';
$this->groupManager = Container::queryClass(IGroupManager::class);
) {
$this->userId = $this->session->getUser()?->getUId() ?? '';
}

// Getters
Expand Down
Loading
Loading