diff --git a/composer.json b/composer.json index 32f379e..aea018c 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "compucie/database", "description": "Library of database managers used by Newton's systems.", - "version": "6.0.1", + "version": "6.2.0", "type": "library", "license": "MIT", "autoload": { diff --git a/src/Event/EventDatabaseManager.php b/src/Event/EventDatabaseManager.php index 9796a6a..e5deb1e 100644 --- a/src/Event/EventDatabaseManager.php +++ b/src/Event/EventDatabaseManager.php @@ -2,10 +2,13 @@ namespace Compucie\Database\Event; +use Exception; use Compucie\Database\DatabaseManager; +use Compucie\Database\Event\Model\PinnedEvent; use DateTime; use InvalidArgumentException; use mysqli_sql_exception; +use function Compucie\Database\safeDateTime; class EventDatabaseManager extends DatabaseManager { @@ -46,12 +49,89 @@ public function getCurrentlyPinnedEventIds(): array ); } + /** + * @return array + * @throws mysqli_sql_exception + */ + public function getPinnedEventIds(): array + { + $rows = $this->executeReadAll( + "SELECT `event_id` + FROM `pins`" + ); + + return array_map( + static fn(array $row): int => (int)$row['event_id'], + $rows + ); + } + + /** + * @return array + * @throws mysqli_sql_exception + */ + public function getPinnedEvents(): array + { + $rows = $this->executeReadAll( + "SELECT `pin_id`, `event_id`, `start_at`, `end_at` + FROM `pins`" + ); + + $pinnedEvents = []; + + foreach ($rows as $row) { + $pinnedEvents[] = new PinnedEvent( + (int) $row['pin_id'], + (int) $row['event_id'], + safeDateTime((string)$row['start_at']), + safeDateTime((string)$row['end_at']) + ); + } + return $pinnedEvents; + } + + /** + * @param int $pinId + * @return PinnedEvent|null + * @throws Exception + * @throws mysqli_sql_exception + */ + public function getPinnedEvent(int $pinId): ?PinnedEvent + { + if ($pinId <= 0){ + return null; + } + + $row = $this->executeReadOne( + "SELECT `pin_id`, `event_id`, `start_at`, `end_at` + FROM `pins` + WHERE `pin_id` = ?", + [$pinId], + "i" + ); + + if ($row === null){ + throw new Exception("Pinned event $pinId not found"); + } + + return new PinnedEvent( + $pinId, + (int) $row['event_id'], + safeDateTime((string)$row['start_at']), + safeDateTime((string)$row['end_at']) + ); + } + /** * Insert an event pin. + * @param int $eventId + * @param DateTime|null $startAt + * @param DateTime|null $endAt + * @return int * @throws mysqli_sql_exception * @throws InvalidArgumentException */ - public function insertPin(int $eventId, DateTime $startAt = null, DateTime $endAt = null): int + public function insertPin(int $eventId, ?DateTime $startAt = null, ?DateTime $endAt = null): int { if ($endAt !== null && $endAt < ($startAt ?? new DateTime())) { throw new InvalidArgumentException('endAt must be after startAt'); @@ -72,6 +152,7 @@ public function insertPin(int $eventId, DateTime $startAt = null, DateTime $endA /** * Update an event pin. + * @throws InvalidArgumentException * @throws mysqli_sql_exception */ public function updatePin(int $eventId, ?DateTime $startAt = null, ?DateTime $endAt = null): bool @@ -97,4 +178,14 @@ public function updatePin(int $eventId, ?DateTime $startAt = null, ?DateTime $en 'ss' ); } + + /** + * @param int $pinId + * @return bool + * @throws mysqli_sql_exception + */ + public function deletePin(int $pinId): bool + { + return $this->executeDelete('pins', 'pin_id', $pinId); + } } diff --git a/src/Event/Model/PinnedEvent.php b/src/Event/Model/PinnedEvent.php new file mode 100644 index 0000000..2853fc7 --- /dev/null +++ b/src/Event/Model/PinnedEvent.php @@ -0,0 +1,42 @@ +id; + } + + public function getEventId(): int + { + return $this->eventId; + } + + public function getStartAt(): DateTime + { + return $this->startAt; + } + + public function getEndAt(): DateTime + { + return $this->endAt; + } +} \ No newline at end of file diff --git a/src/Member/Model/MemberAccess.php b/src/Member/Model/MemberAccess.php new file mode 100644 index 0000000..da0ebda --- /dev/null +++ b/src/Member/Model/MemberAccess.php @@ -0,0 +1,33 @@ +congressusMemberId; + } + + public function getReason(): string + { + return $this->reason; + } + + public function getHasAccess(): bool + { + return $this->hasAccess; + } +} \ No newline at end of file diff --git a/src/Member/RfidTableManager.php b/src/Member/RfidTableManager.php index 59d3355..1f68da3 100644 --- a/src/Member/RfidTableManager.php +++ b/src/Member/RfidTableManager.php @@ -4,6 +4,7 @@ use Compucie\Database\Member\Exceptions\ActivationTokenNotFoundException; use Compucie\Database\Member\Exceptions\CardNotRegisteredException; +use Compucie\Database\Member\Model\MemberAccess; use DateTime; use Exception; use mysqli; @@ -30,6 +31,19 @@ protected function createRfidTable(): void $statement->execute(); $statement->close(); } + + $statement = $this->getClient()->prepare( + "CREATE TABLE IF NOT EXISTS `access` ( + `congressus_member_id` INT NOT NULL, + `reason` VARCHAR(255) DEFAULT NULL, + `has_access` BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (`congressus_member_id`) + );" + ); + if ($statement){ + $statement->execute(); + $statement->close(); + } } /** @@ -176,4 +190,154 @@ public function deleteMembersActivatedRegistrations(int $congressusMemberId): bo ['`is_email_confirmed` = TRUE'] ); } + + /** + * @param int $congressusMemberId + * @param string $reason + * @param bool $hasAccess + * @return int + * @throws mysqli_sql_exception + */ + public function addMemberAccess(int $congressusMemberId, string $reason, bool $hasAccess = false): int + { + return $this->executeCreate( + "access", + ["`congressus_member_id`", "`reason`", "`has_access`"], + [ + $congressusMemberId, + $reason, + $hasAccess, + ], + "isi" + ); + } + + /** + * @return array + * @throws mysqli_sql_exception + */ + public function getAllMemberAccesses(): array + { + $rows = $this->executeReadAll( + "SELECT `congressus_member_id`, `reason`, `has_access` + FROM `access`" + ); + + $memberAccesses = []; + + foreach ($rows as $row) { + $memberAccesses[] = new MemberAccess( + (int) $row['congressus_member_id'], + $row['reason'], + (bool)$row['has_access'] + ); + } + return $memberAccesses; + } + + /** + * @param int $congressusMemberId + * @return MemberAccess|null + * @throws Exception + * @throws mysqli_sql_exception + */ + public function getMemberAccesses(int $congressusMemberId): ?MemberAccess + { + if ($congressusMemberId <= 0){ + return null; + } + + $row = $this->executeReadOne( + "SELECT `congressus_member_id`, `reason`, `has_access` + FROM `access` + WHERE `congressus_member_id` = ?", + [$congressusMemberId], + "i" + ); + + if ($row === null){ + throw new Exception("Member access for $congressusMemberId not found"); + } + + return new MemberAccess( + $congressusMemberId, + $row['reason'], + (bool)$row['has_access'] + ); + } + + /** + * @param int $congressusMemberId + * @param string|null $reason + * @param bool|null $hasAccess + * @param bool $clearReason + * @return bool + * @throws mysqli_sql_exception + */ + public function updateMemberAccess( + int $congressusMemberId, + ?string $reason = null, + ?bool $hasAccess = null, + bool $clearReason = false + ): bool { + $fields = []; + $params = []; + $types = ''; + + if ($clearReason) { + $fields[] = 'reason = NULL'; + } elseif ($reason !== null) { + $fields[] = 'reason = ?'; + $params[] = $reason; + $types .= 's'; + } + + if ($hasAccess !== null) { + $fields[] = 'has_access = ?'; + $params[] = $hasAccess; + $types .= 'i'; + } + + return $this->executeUpdate('access', 'congressus_member_id', $congressusMemberId, $fields, $params, $types); + } + + /** + * @param int $congressusMemberId + * @param string $reason + * @return bool + * @throws mysqli_sql_exception + * @throws Exception + */ + public function revokeMemberAccess(int $congressusMemberId, string $reason): bool + { + if ($reason === "") { + throw new Exception("Revoke member access reason cannot be empty"); + } + return $this->updateMemberAccess($congressusMemberId, $reason, false); + } + + /** + * @param int $congressusMemberId + * @param string $reason + * @return bool + * @throws mysqli_sql_exception + * @throws Exception + */ + public function approveMemberAccess(int $congressusMemberId, string $reason): bool + { + if ($reason === "") { + throw new Exception("Approve member access reason cannot be empty"); + } + return $this->updateMemberAccess($congressusMemberId, $reason, true); + } + + /** + * @param int $congressusMemberId + * @return bool + * @throws mysqli_sql_exception + */ + public function deleteMemberAccess(int $congressusMemberId): bool + { + return $this->executeDelete("access", "congressus_member_id", $congressusMemberId); + } } diff --git a/src/Poll/Model/Options.php b/src/Poll/Model/Options.php deleted file mode 100644 index 2671033..0000000 --- a/src/Poll/Model/Options.php +++ /dev/null @@ -1,83 +0,0 @@ - $ids - */ - private array $ids; - /** - * @var array $texts - */ - private array $texts; - /** - * @var array $voteCounts - */ - private array $voteCounts; - - /** - * @param array $ids - * @param array $texts - * @param array $voteCounts - */ - public function __construct( - array $ids = array(), - array $texts = array(), - array $voteCounts = array(), - ) { - $this->ids = array(); - $this->texts = array(); - $this->voteCounts = array(); - - foreach ($ids as $id) { - $this->registerId($id); - } - - foreach ($texts as $id => $text) { - $this->setText((int)$id, (string)$text); - } - - foreach ($voteCounts as $id => $count) { - $this->setVoteCount((int)$id, (int)$count); - } - } - - public function getText(int $id): string - { - return $this->texts[$id] ?? ''; - } - - public function getVoteCount(int $id): int - { - return $this->voteCounts[$id] ?? 0; - } - - public function setText(int $id, string $text): void - { - $this->texts[$id] = $text; - $this->registerId($id); - } - - public function setVoteCount(int $id, int $voteCount): void - { - $this->voteCounts[$id] = $voteCount; - $this->registerId($id); - } - - /** - * @return array - */ - public function getIds(): array - { - return $this->ids; - } - - private function registerId(int $id): void - { - if (!in_array($id, $this->ids)) { - $this->ids[] = $id; - } - } -} diff --git a/src/Poll/Model/Poll.php b/src/Poll/Model/Poll.php index 2631e29..7d8b632 100644 --- a/src/Poll/Model/Poll.php +++ b/src/Poll/Model/Poll.php @@ -9,12 +9,20 @@ */ readonly class Poll { + /** + * @param int $id + * @param string $question + * @param DateTime $publishedAt + * @param DateTime $expiresAt + * @param array