Skip to content

Commit afa9346

Browse files
committed
Replacing lock-strict flag with lock-type enum for exam groups.
1 parent f145053 commit afa9346

7 files changed

Lines changed: 216 additions & 43 deletions

File tree

app/model/entity/Group.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace App\Model\Entity;
44

5-
use DateTime;
5+
use App\Model\GroupExamLockType;
66
use Doctrine\Common\Collections\Collection;
77
use Doctrine\Common\Collections\ReadableCollection;
88
use Doctrine\Common\Collections\ArrayCollection;
@@ -11,6 +11,7 @@
1111
use Gedmo\Mapping\Annotation as Gedmo;
1212
use LogicException;
1313
use InvalidArgumentException;
14+
use DateTime;
1415

1516
/**
1617
* @ORM\Entity
@@ -231,22 +232,25 @@ public function isDirectlyArchived(): bool
231232
* @ORM\Column(type="datetime", nullable=true)
232233
* When an exam in this groups begins. In the exam period, a user must lock in a group to be allowed
233234
* submitting solutions. This is completely independent of the isExam flag.
235+
* @var DateTime|null
234236
*/
235237
protected $examBegin = null;
236238

237239
/**
238240
* @ORM\Column(type="datetime", nullable=true)
239241
* When an exam in this groups ends. In the exam period, a user must lock in a group to be allowed
240242
* submitting solutions. This is completely independent of the isExam flag.
243+
* @var DateTime|null
241244
*/
242245
protected $examEnd = null;
243246

244247
/**
245-
* @ORM\Column(type="boolean")
246-
* Whether the group-lock for the exam should be strict
248+
* @ORM\Column(type="string")
249+
* The type of lock for the exam.
247250
* (under strict lock, the user cannot read data from other groups).
251+
* @var string
248252
*/
249-
protected $examLockStrict = false;
253+
protected $examLockType = GroupExamLockType::Visible->value;
250254

251255
/**
252256
* @var Collection
@@ -260,9 +264,9 @@ public function isDirectlyArchived(): bool
260264
* Switch the group into an exam group by setting the begin and end dates of the exam.
261265
* @param DateTime $begin when the exam starts
262266
* @param DateTime $end when the exam ends
263-
* @param bool $strict if true, locked users cannot access other groups (for reading)
267+
* @param GroupExamLockType $type the type of lock for the exam
264268
*/
265-
public function setExamPeriod(DateTime $begin, DateTime $end, bool $strict = false): void
269+
public function setExamPeriod(DateTime $begin, DateTime $end, GroupExamLockType $type): void
266270
{
267271
// asserts
268272
if ($begin >= $end) {
@@ -275,7 +279,7 @@ public function setExamPeriod(DateTime $begin, DateTime $end, bool $strict = fal
275279

276280
$this->examBegin = $begin;
277281
$this->examEnd = $end;
278-
$this->examLockStrict = $strict;
282+
$this->examLockType = $type->value;
279283
$this->isOrganizational = false;
280284
}
281285

@@ -286,7 +290,7 @@ public function removeExamPeriod(): void
286290
{
287291
$this->examBegin = null;
288292
$this->examEnd = null;
289-
$this->examLockStrict = false;
293+
$this->examLockType = GroupExamLockType::Visible->value;
290294
}
291295

292296
/**
@@ -301,7 +305,12 @@ public function hasExamPeriodSet(?DateTime $at = null): bool
301305

302306
public function isExamLockStrict(): bool
303307
{
304-
return $this->examLockStrict;
308+
return $this->examLockType === GroupExamLockType::Restricted->value;
309+
}
310+
311+
public function getExamLockType(): GroupExamLockType
312+
{
313+
return GroupExamLockType::from($this->examLockType);
305314
}
306315

307316
/**

app/model/entity/GroupExam.php

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Model\Entity;
44

5+
use App\Model\GroupExamLockType;
56
use Doctrine\ORM\Mapping as ORM;
67
use DateTime;
78
use JsonSerializable;
@@ -11,7 +12,7 @@
1112
* @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(columns={"group_id", "begin"})})
1213
* Holds history record of an exam that took place in a group.
1314
* The `examBegin`, `examEnd` fields are copied from group to `begin`, `end` fields here,
14-
* `examLockStrict` is copied to `lockStrict` field.
15+
* `examLockType` is copied to `lockType` field.
1516
* This entity is created when the first user locks in (i.e., only exams with users are recorded in history).
1617
*/
1718
class GroupExam implements JsonSerializable
@@ -20,58 +21,61 @@ class GroupExam implements JsonSerializable
2021
* @ORM\Id
2122
* @ORM\Column(type="integer")
2223
* @ORM\GeneratedValue(strategy="AUTO")
24+
* @var int|null
2325
*/
2426
protected $id;
2527

2628
/**
2729
* @ORM\ManyToOne(targetEntity="Group", inversedBy="exams")
30+
* @var Group
2831
*/
2932
protected $group;
3033

3134
/**
3235
* @ORM\Column(type="datetime")
33-
* @var DateTime
36+
* @var DateTime|null
3437
*/
3538
protected $begin = null;
3639

3740
/**
3841
* @ORM\Column(type="datetime")
39-
* @var DateTime
42+
* @var DateTime|null
4043
*/
4144
protected $end = null;
4245

4346
/**
44-
* @ORM\Column(type="boolean")
45-
* Saved value from examLockStrict flag.
47+
* @ORM\Column(type="string")
48+
* Saved value from examLockType flag.
49+
* @var string
4650
*/
47-
protected $lockStrict = false;
51+
protected $lockType = GroupExamLockType::Visible->value;
4852

4953
/**
5054
* Constructor
5155
* @param Group $group
5256
* @param DateTime $begin
5357
* @param DateTime $end
54-
* @param bool $strict
58+
* @param GroupExamLockType $type
5559
*/
56-
public function __construct(Group $group, DateTime $begin, DateTime $end, bool $strict)
60+
public function __construct(Group $group, DateTime $begin, DateTime $end, GroupExamLockType $type)
5761
{
5862
$this->group = $group;
5963
$this->begin = $begin;
6064
$this->end = $end;
61-
$this->lockStrict = $strict;
65+
$this->lockType = $type->value;
6266
}
6367

6468
/**
6569
* Update the parameters (happens if pending exam is cut short, for instance).
6670
* @param DateTime $begin
6771
* @param DateTime $end
68-
* @param bool $strict
72+
* @param GroupExamLockType $type
6973
*/
70-
public function update(DateTime $begin, DateTime $end, bool $strict): void
74+
public function update(DateTime $begin, DateTime $end, GroupExamLockType $type): void
7175
{
7276
$this->begin = $begin;
7377
$this->end = $end;
74-
$this->lockStrict = $strict;
78+
$this->lockType = $type->value;
7579
}
7680

7781
public function jsonSerialize(): mixed
@@ -82,7 +86,9 @@ public function jsonSerialize(): mixed
8286
"groupId" => $group ? $group->getId() : null,
8387
"begin" => $this->getBegin()->getTimestamp(),
8488
"end" => $this->getEnd()->getTimestamp(),
85-
"strict" => $this->lockStrict,
89+
"type" => $this->lockType,
90+
// BC only, DEPRECATED
91+
"strict" => $this->lockType === GroupExamLockType::Restricted->value,
8692
];
8793
}
8894

@@ -113,6 +119,11 @@ public function getEnd(): DateTime
113119

114120
public function isLockStrict(): bool
115121
{
116-
return $this->lockStrict;
122+
return $this->lockType === GroupExamLockType::Restricted->value;
123+
}
124+
125+
public function getLockType(): GroupExamLockType
126+
{
127+
return GroupExamLockType::from($this->lockType);
117128
}
118129
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace App\Model;
4+
5+
/**
6+
* Defines the type of lock for a group exam.
7+
* The lock type defines the level of access to the student's solutions previously submitted in other groups.
8+
*/
9+
enum GroupExamLockType: string
10+
{
11+
/**
12+
* No access to other groups.
13+
*/
14+
case Restricted = 'restricted';
15+
16+
/**
17+
* Solutions that were marked as accepted in other groups are visible.
18+
*/
19+
case Accepted = 'accepted';
20+
21+
/**
22+
* Solutions that were marked as accepted or reviewed in other groups are visible.
23+
*/
24+
case Reviewed = 'reviewed';
25+
26+
/**
27+
* All solutions from other groups are visible.
28+
*/
29+
case Visible = 'visible';
30+
31+
32+
public static function values(): array
33+
{
34+
return array_map(fn($case) => $case->value, self::cases());
35+
}
36+
}

app/model/entity/User.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Model\Entity;
44

5+
use App\Model\GroupExamLockType;
56
use App\Security\Roles;
67
use Doctrine\Common\Collections\Collection;
78
use Doctrine\ORM\Mapping as ORM;
@@ -420,6 +421,7 @@ public function updateLastAuthenticationAt(?DateTime $time = null)
420421
* @ORM\Column(type="string", nullable=true)
421422
* IP address (either IPv4 or IPv6) the user is locked to. API requests from different addresses
422423
* will be treated as unauthorized.
424+
* @var string|null
423425
*/
424426
protected $ipLock = null;
425427

@@ -499,13 +501,16 @@ public function verifyIpLock(string $currentIp): bool
499501
* @ORM\ManyToOne(targetEntity="Group")
500502
* If set, any user actions will be restricted to this group only.
501503
* (except for fundamental operations like listing groups or getting group name)
504+
* @var Group|null
502505
*/
503506
protected $groupLock = null;
504507

505508
/**
506-
* @ORM\Column(type="boolean")
509+
* @ORM\Column(type="string")
510+
* String representation of the GroupExamLockType enum (copied from the group exam).
511+
* @var string
507512
*/
508-
protected $groupLockStrict = false;
513+
protected $groupLockType = GroupExamLockType::Visible->value;
509514

510515
/**
511516
* @ORM\Column(type="datetime", nullable=true)
@@ -528,7 +533,7 @@ public function isGroupLocked(): bool
528533
*/
529534
public function isGroupLockStrict(): bool
530535
{
531-
return $this->groupLockStrict;
536+
return $this->groupLockType === GroupExamLockType::Restricted->value;
532537
}
533538

534539
/**
@@ -548,9 +553,9 @@ public function getGroupLockExpiration(): ?DateTimeInterface
548553
* Lock the user within a group.
549554
* @param Group $group
550555
* @param DateTime|null $expiration of the lock, if null, the lock will never expire
551-
* @param bool $strict if true, the user may not even access other groups for reading
556+
* @param GroupExamLockType $type if true, the user may not even access other groups for reading
552557
*/
553-
public function setGroupLock(Group $group, ?DateTime $expiration = null, bool $strict = false): void
558+
public function setGroupLock(Group $group, ?DateTime $expiration, GroupExamLockType $type): void
554559
{
555560
// basic asserts to be on the safe side
556561
if (!$group->hasExamPeriodSet()) {
@@ -562,14 +567,14 @@ public function setGroupLock(Group $group, ?DateTime $expiration = null, bool $s
562567

563568
$this->groupLock = $group;
564569
$this->groupLockExpiration = $expiration;
565-
$this->groupLockStrict = $strict;
570+
$this->groupLockType = $type->value;
566571
}
567572

568573
public function removeGroupLock(): void
569574
{
570575
$this->groupLock = null;
571576
$this->groupLockExpiration = null;
572-
$this->groupLockStrict = false;
577+
$this->groupLockType = GroupExamLockType::Visible->value;
573578
}
574579

575580

app/model/repository/GroupExams.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\Model\Entity\Group;
66
use App\Model\Entity\GroupExam;
7+
use App\Model\GroupExamLockType;
78
use Doctrine\ORM\EntityManagerInterface;
89
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
910
use DateTime;
@@ -45,10 +46,10 @@ public function findPendingForGroup(Group $group): ?GroupExam
4546
* @param Group $group
4647
* @param DateTime $begin
4748
* @param DateTime $end
48-
* @param bool $strict
49+
* @param GroupExamLockType $type
4950
* @return GroupExam|null
5051
*/
51-
private function tryFindOrCreate(Group $group, DateTime $begin, DateTime $end, bool $strict): ?GroupExam
52+
private function tryFindOrCreate(Group $group, DateTime $begin, DateTime $end, GroupExamLockType $type): ?GroupExam
5253
{
5354
$exam = $this->findBy(["group" => $group, "begin" => $begin]);
5455
if (count($exam) > 1) {
@@ -58,12 +59,12 @@ private function tryFindOrCreate(Group $group, DateTime $begin, DateTime $end, b
5859
if (!$exam) {
5960
try {
6061
$this->em->getConnection()->executeQuery(
61-
"INSERT INTO group_exam (group_id, begin, end, lock_strict) VALUES (:gid, :begin, :end, :strict)",
62+
"INSERT INTO group_exam (group_id, begin, end, lock_type) VALUES (:gid, :begin, :end, :type)",
6263
[
6364
'gid' => $group->getId(),
6465
'begin' => $begin->format('Y-m-d H:i:s'),
6566
'end' => $end->format('Y-m-d H:i:s'),
66-
'strict' => $strict ? 1 : 0
67+
'type' => $type->value
6768
]
6869
);
6970
} catch (UniqueConstraintViolationException) {
@@ -82,21 +83,21 @@ private function tryFindOrCreate(Group $group, DateTime $begin, DateTime $end, b
8283
* @param Group $group
8384
* @param DateTime|null $begin if null, exam begin from the group is taken
8485
* @param DateTime|null $end if null, exam end from the group is taken
85-
* @param bool|null $strict if null, examLockStrict value is taken
86+
* @param GroupExamLockType|null $type if null, examLockType value is taken
8687
* @return GroupExam
8788
*/
8889
public function findOrCreate(
8990
Group $group,
9091
?DateTime $begin = null,
9192
?DateTime $end = null,
92-
?bool $strict = null
93+
?GroupExamLockType $type = null
9394
): GroupExam {
9495
$begin = $begin ?? $group->getExamBegin();
9596
$end = $end ?? $group->getExamEnd();
96-
$strict = $strict === null ? $group->isExamLockStrict() : $strict;
97+
$type = $type ?? $group->getExamLockType();
9798

9899
for ($retries = 0; $retries < 3; $retries++) {
99-
$exam = $this->tryFindOrCreate($group, $begin, $end, $strict);
100+
$exam = $this->tryFindOrCreate($group, $begin, $end, $type);
100101
if ($exam !== null) {
101102
return $exam;
102103
}

0 commit comments

Comments
 (0)