ILIAS  trunk Revision v12.0_alpha-1227-g7ff6d300864
class.ilExPeerReview.php
Go to the documentation of this file.
1<?php
2
21
29{
31 protected ilDBInterface $db;
32 protected ilObjUser $user;
34 protected int $assignment_id;
35 protected ilLogger $log;
36
37 public function __construct(
38 ilExAssignment $a_assignment
39 ) {
40 global $DIC;
41
42 $this->db = $DIC->database();
43 $this->user = $DIC->user();
44 $this->assignment = $a_assignment;
45 $this->assignment_id = $a_assignment->getId();
46 $this->log = ilLoggerFactory::getLogger("exc");
47 $this->domain = $DIC->exercise()->internal()->domain();
48 }
49
50 public function hasPeerReviewGroups(): bool
51 {
53
54 $set = $ilDB->query("SELECT count(*) cnt" .
55 " FROM exc_assignment_peer" .
56 " WHERE ass_id = " . $ilDB->quote($this->assignment_id, "integer"));
57 $cnt = $ilDB->fetchAssoc($set);
58 return (bool) $cnt["cnt"];
59 }
60
61 public function getReviewId(int $giver_id, int $peer_id): int
62 {
63 $set = $this->db->queryF(
64 "SELECT id FROM exc_assignment_peer " .
65 " WHERE ass_id = %s AND giver_id = %s AND peer_id = %s",
66 ["integer","integer","integer"],
67 [$this->assignment_id, $giver_id, $peer_id]
68 );
69 $rec = $this->db->fetchAssoc($set);
70 return $rec["id"] ?? 0;
71 }
72
76 protected function getValidPeerReviewUsers(): array
77 {
78 return $this->domain->submission($this->assignment_id)->getUsersWithSubmission();
79 }
80
81 public function initPeerReviews(): bool
82 {
84
85 // see #22246
86 if (!$this->assignment->afterDeadlineStrict()) {
87 return false;
88 }
89
90 if (!$this->hasPeerReviewGroups()) {
91 $user_ids = $this->getValidPeerReviewUsers();
92
93 $distribution = new ExcPeerReviewDistribution($user_ids, $this->assignment->getPeerReviewMin());
94
95 foreach ($user_ids as $rater_id) {
96 foreach ($distribution->getPeersOfRater($rater_id) as $peer_id) {
97 $next_id = $ilDB->nextId("exc_assignment_peer");
98 $ilDB->manipulate("INSERT INTO exc_assignment_peer" .
99 " (id, ass_id, giver_id, peer_id, migrated)" .
100 " VALUES (" . $ilDB->quote($next_id, "integer") .
101 ", " . $ilDB->quote($this->assignment_id, "integer") .
102 ", " . $ilDB->quote($rater_id, "integer") .
103 ", " . $ilDB->quote($peer_id, "integer") .
104 ", " . $ilDB->quote(1, "integer") . ")");
105 }
106 }
107 }
108 return true;
109 }
110
114 public function resetPeerReviews(): array
115 {
117
118 $all = array();
119
120 if ($this->hasPeerReviewGroups()) {
121 foreach ($this->getAllPeerReviews(false) as $peer_id => $reviews) {
122 foreach (array_keys($reviews) as $giver_id) {
123 $all[] = $giver_id;
124
125 foreach ($this->assignment->getPeerReviewCriteriaCatalogueItems() as $crit) {
126 $crit->setPeerReviewContext($this->assignment, $giver_id, $peer_id);
127 $crit->resetReview();
128 }
129 }
130 }
131
132 // peer groups
133 $ilDB->manipulate("DELETE FROM exc_assignment_peer" .
134 " WHERE ass_id = " . $ilDB->quote($this->assignment_id, "integer"));
135 }
136
137 return $all;
138 }
139
140 public function validatePeerReviewGroups(): ?array
141 {
142 if ($this->hasPeerReviewGroups()) {
143 $all_exc = ilExerciseMembers::_getMembers($this->assignment->getExerciseId());
144 $all_valid = $this->getValidPeerReviewUsers(); // only returned
145
146 $peer_ids = $invalid_peer_ids = $invalid_giver_ids = $all_reviews = array();
147 foreach ($this->getAllPeerReviews(false) as $peer_id => $reviews) {
148 $peer_ids[] = $peer_id;
149
150 if (!in_array($peer_id, $all_valid) ||
151 !in_array($peer_id, $all_exc)) {
152 $invalid_peer_ids[] = $peer_id;
153 }
154 foreach ($reviews as $giver_id => $valid) {
155 if (!in_array($giver_id, $all_valid) ||
156 !in_array($peer_id, $all_exc)) {
157 $invalid_giver_ids[] = $giver_id;
158 } else {
159 $all_reviews[$peer_id][$giver_id] = $valid;
160 }
161 }
162 }
163 $invalid_giver_ids = array_unique($invalid_giver_ids);
164
165 $missing_user_ids = array();
166 foreach ($all_valid as $user_id) {
167 // a missing peer is also a missing giver
168 if (!in_array($user_id, $peer_ids)) {
169 $missing_user_ids[] = $user_id;
170 }
171 }
172
173 $not_returned_ids = array();
174 foreach ($all_exc as $user_id) {
175 if (!in_array($user_id, $all_valid)) {
176 $not_returned_ids[] = $user_id;
177 }
178 }
179
180 return array(
181 "invalid" => (count($missing_user_ids) ||
182 count($invalid_peer_ids) ||
183 count($invalid_giver_ids)),
184 "missing_user_ids" => $missing_user_ids,
185 "not_returned_ids" => $not_returned_ids,
186 "invalid_peer_ids" => $invalid_peer_ids,
187 "invalid_giver_ids" => $invalid_giver_ids,
188 "reviews" => $all_reviews);
189 }
190
191 return null;
192 }
193
194 public function getPeerReviewValues(
195 int $a_giver_id,
196 int $a_peer_id
197 ): array {
198 $peer = null;
199 foreach ($this->getPeerReviewsByGiver($a_giver_id) as $item) {
200 if ($item["peer_id"] == $a_peer_id) {
201 $peer = $item;
202 }
203 }
204 if (!$peer) {
205 return [];
206 }
207 $data = $peer["pcomment"];
208 if ($data) {
209 $items = unserialize($data, ['allowed_classes' => false]);
210 if (!is_array($items)) {
211 // v1 - pcomment == text
212 $items = array("text" => $data);
213 }
214 return $items;
215 }
216 return [];
217 }
218
219 public function getPeerReviewsByGiver(int $a_user_id): array
220 {
221 $ilDB = $this->db;
222
223 $res = array();
224
225 if ($this->initPeerReviews()) {
226 $idx = 0;
227 $set = $ilDB->query("SELECT *" .
228 " FROM exc_assignment_peer" .
229 " WHERE giver_id = " . $ilDB->quote($a_user_id, "integer") .
230 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
231 " ORDER BY peer_id");
232 while ($row = $ilDB->fetchAssoc($set)) {
233 $row["seq"] = ++$idx;
234 $res[] = $row;
235 }
236 }
237
238 return $res;
239 }
240
241 public function getPeerMaskedId(
242 int $a_giver_id,
243 int $a_peer_id
244 ): int {
245 foreach ($this->getPeerReviewsByGiver($a_giver_id) as $peer) {
246 if ($peer["peer_id"] == $a_peer_id) {
247 return (int) $peer["seq"];
248 }
249 }
250 return 0;
251 }
252
253 protected function validatePeerReview(array $a_data): bool
254 {
255 $all_empty = true;
256 // see getPeerReviewValues()
257 $values = null;
258 $data = $a_data["pcomment"];
259 if ($data) {
260 try {
261 $values = unserialize($data, ['allowed_classes' => false]);
262 } catch (Exception $e) {
263 }
264 if (!is_array($values)) {
265 // v1 - pcomment == text
266 $values = array("text" => $data);
267 }
268 }
269
270 foreach ($this->assignment->getPeerReviewCriteriaCatalogueItems() as $crit) {
271 $crit_id = $crit->getId()
272 ? $crit->getId()
273 : $crit->getType();
274 $crit->setPeerReviewContext(
275 $this->assignment,
276 $a_data["giver_id"],
277 $a_data["peer_id"]
278 );
279 if (!$crit->validate($values[$crit_id] ?? null)) {
280 return false;
281 }
282 if ($crit->hasValue($values[$crit_id] ?? null)) {
283 $all_empty = false;
284 }
285 }
286
287 return !$all_empty;
288 }
289
290 public function getPeerReviewsByPeerId(
291 int $a_user_id,
292 bool $a_only_valid = false
293 ): array {
294 $ilDB = $this->db;
295
296 $res = array();
297
298 $idx = 0;
299 $set = $ilDB->query("SELECT *" .
300 " FROM exc_assignment_peer" .
301 " WHERE peer_id = " . $ilDB->quote($a_user_id, "integer") .
302 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
303 " ORDER BY peer_id");
304 while ($row = $ilDB->fetchAssoc($set)) {
305 if (!$a_only_valid ||
306 $this->validatePeerReview($row)) {
307 // this would be correct but rather senseless
308 // $row["seq"] = $this->getPeerMaskedId($row["giver_id"], $a_user_id);
309 $row["seq"] = ++$idx;
310 $res[] = $row;
311 }
312 }
313
314 return $res;
315 }
316
317 public function countReceivedFeedbacks(
318 int $user_id,
319 bool $only_valid = true
320 ): int {
321 return count($this->getPeerReviewsByPeerId($user_id, $only_valid));
322 }
323
324 public function getAllPeerReviews(
325 bool $a_only_valid = true
326 ): array {
327 $ilDB = $this->db;
328
329 $res = array();
330
331 $set = $ilDB->query("SELECT *" .
332 " FROM exc_assignment_peer" .
333 " WHERE ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
334 " ORDER BY peer_id");
335 while ($row = $ilDB->fetchAssoc($set)) {
336 $valid = $this->validatePeerReview($row);
337 if (!$a_only_valid ||
338 $valid) {
339 $res[$row["peer_id"]][$row["giver_id"]] = $valid;
340 }
341 }
342
343 return $res;
344 }
345
346 public function hasPeerReviewAccess(
347 int $a_peer_id
348 ): bool {
349 $ilDB = $this->db;
350 $ilUser = $this->user;
351
352 $set = $ilDB->query("SELECT ass_id" .
353 " FROM exc_assignment_peer" .
354 " WHERE giver_id = " . $ilDB->quote($ilUser->getId(), "integer") .
355 " AND peer_id = " . $ilDB->quote($a_peer_id, "integer") .
356 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer"));
357 $row = $ilDB->fetchAssoc($set);
358 return ((int) ($row["ass_id"] ?? 0) > 0);
359 }
360
362 int $a_peer_id
363 ): void {
364 $ilDB = $this->db;
365 $ilUser = $this->user;
366
367 $ilDB->manipulate("UPDATE exc_assignment_peer" .
368 " SET tstamp = " . $ilDB->quote(ilUtil::now(), "timestamp") .
369 " WHERE giver_id = " . $ilDB->quote($ilUser->getId(), "integer") .
370 " AND peer_id = " . $ilDB->quote($a_peer_id, "integer") .
371 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer"));
372 }
373
374 public function updatePeerReview(
375 int $a_peer_id,
376 array $a_values
377 ): void {
378 $ilDB = $this->db;
379 $ilUser = $this->user;
380
381 $data = [
382 "pcomment" => serialize($a_values),
383 "peer_id" => $a_peer_id,
384 "giver_id" => $ilUser->getId()
385 ];
386 $valid = $this->validatePeerReview($data);
387
388 $sql = "UPDATE exc_assignment_peer" .
389 " SET tstamp = " . $ilDB->quote(ilUtil::now(), "timestamp") .
390 ",pcomment = " . $ilDB->quote(serialize($a_values), "text") .
391 ",is_valid = " . $ilDB->quote((int) $valid, "integer") .
392 " WHERE giver_id = " . $ilDB->quote($ilUser->getId(), "integer") .
393 " AND peer_id = " . $ilDB->quote($a_peer_id, "integer") .
394 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer");
395
396 $ilDB->manipulate($sql);
397 }
398
399 public function countGivenFeedback(
400 bool $a_validate = true,
401 ?int $a_user_id = null
402 ): int {
403 $ilDB = $this->db;
404 $ilUser = $this->user;
405
406 if (!$a_user_id) {
407 $a_user_id = $ilUser->getId();
408 }
409
410 $cnt = 0;
411
412 $set = $ilDB->query("SELECT *" .
413 " FROM exc_assignment_peer" .
414 " WHERE ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
415 " AND giver_id = " . $ilDB->quote($a_user_id, "integer"));
416 while ($row = $ilDB->fetchAssoc($set)) {
417 if (!$a_validate ||
418 $this->validatePeerReview($row)) {
419 $cnt++;
420 }
421 }
422
423 return $cnt;
424 }
425
426 protected function getMaxPossibleFeedbacks(): int
427 {
428 return (count($this->domain->submission($this->assignment_id)->getUsersWithSubmission()) - 1);
429 }
430
432 {
433 $max = $this->getMaxPossibleFeedbacks();
434
435 // #16160 - forever alone
436 if ($max === 0) {
437 return 0;
438 }
439
440 // are all required or just 1?
441 if ($this->assignment->getPeerReviewSimpleUnlock() == 2) {
442 $needed = 0;
443 } elseif ($this->assignment->getPeerReviewSimpleUnlock() == 0) {
444 $needed = $this->assignment->getPeerReviewMin();
445 } else {
446 $needed = 1;
447 }
448 // there could be less participants than stated in the min required setting
449 $min = min($max, $needed);
450 return max(0, $min - $this->countGivenFeedback());
451 }
452
453 public function isFeedbackValidForPassed(int $a_user_id): bool
454 {
455 // peer feedback is not required for passing
456 if ($this->assignment->getPeerReviewValid() == ilExAssignment::PEER_REVIEW_VALID_NONE) {
457 return true;
458 }
459
460 // #16227 - no processing before reaching the peer review period
461 if (!$this->assignment->afterDeadlineStrict()) {
462 return false;
463 }
464
465 // forever alone - should be valid
466 $max = $this->getMaxPossibleFeedbacks();
467 if ($max === 0) {
468 return true;
469 }
470
471 $no_of_feedbacks = $this->countGivenFeedback(true, $a_user_id);
472
473 switch ($this->assignment->getPeerReviewValid()) {
475 return (bool) $no_of_feedbacks;
476
478 // there could be less participants than stated in the min required setting
479 $min = min($max, $this->assignment->getPeerReviewMin());
480
481 return (($min - $no_of_feedbacks) < 1);
482 }
483 return false;
484 }
485
486 public static function lookupGiversWithPendingFeedback(int $a_ass_id): array
487 {
488 global $DIC;
489
490 $ilDB = $DIC->database();
491 $user_ids = array();
492
493 $set = $ilDB->query(
494 "SELECT DISTINCT(giver_id) FROM exc_assignment_peer " .
495 " WHERE ass_id = " . $ilDB->quote($a_ass_id, "integer") .
496 " AND tstamp is NULL"
497 );
498
499 while ($row = $ilDB->fetchAssoc($set)) {
500 $user_ids[] = $row["giver_id"];
501 }
502
503 return $user_ids;
504 }
505}
Calculates peer review distribution (rater to peer assignments)
Exercise assignment.
Exercise peer review.
countGivenFeedback(bool $a_validate=true, ?int $a_user_id=null)
getPeerMaskedId(int $a_giver_id, int $a_peer_id)
updatePeerReviewTimestamp(int $a_peer_id)
countReceivedFeedbacks(int $user_id, bool $only_valid=true)
getPeerReviewsByGiver(int $a_user_id)
static lookupGiversWithPendingFeedback(int $a_ass_id)
getPeerReviewsByPeerId(int $a_user_id, bool $a_only_valid=false)
__construct(ilExAssignment $a_assignment)
getAllPeerReviews(bool $a_only_valid=true)
validatePeerReview(array $a_data)
hasPeerReviewAccess(int $a_peer_id)
getPeerReviewValues(int $a_giver_id, int $a_peer_id)
isFeedbackValidForPassed(int $a_user_id)
InternalDomainService $domain
updatePeerReview(int $a_peer_id, array $a_values)
ilExAssignment $assignment
getReviewId(int $giver_id, int $peer_id)
static _getMembers(int $a_obj_id)
static getLogger(string $a_component_id)
Get component logger.
Component logger with individual log levels by component id.
User class.
static now()
Return current timestamp in Y-m-d H:i:s format.
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$valid
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
global $DIC
Definition: shib_login.php:26