ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
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)" .
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 }
105 }
106 }
107 return true;
108 }
109
113 public function resetPeerReviews(): array
114 {
116
117 $all = array();
118
119 if ($this->hasPeerReviewGroups()) {
120 foreach ($this->getAllPeerReviews(false) as $peer_id => $reviews) {
121 foreach (array_keys($reviews) as $giver_id) {
122 $all[] = $giver_id;
123
124 foreach ($this->assignment->getPeerReviewCriteriaCatalogueItems() as $crit) {
125 $crit->setPeerReviewContext($this->assignment, $giver_id, $peer_id);
126 $crit->resetReview();
127 }
128 }
129 }
130
131 // peer groups
132 $ilDB->manipulate("DELETE FROM exc_assignment_peer" .
133 " WHERE ass_id = " . $ilDB->quote($this->assignment_id, "integer"));
134 }
135
136 return $all;
137 }
138
139 public function validatePeerReviewGroups(): ?array
140 {
141 if ($this->hasPeerReviewGroups()) {
142 $all_exc = ilExerciseMembers::_getMembers($this->assignment->getExerciseId());
143 $all_valid = $this->getValidPeerReviewUsers(); // only returned
144
145 $peer_ids = $invalid_peer_ids = $invalid_giver_ids = $all_reviews = array();
146 foreach ($this->getAllPeerReviews(false) as $peer_id => $reviews) {
147 $peer_ids[] = $peer_id;
148
149 if (!in_array($peer_id, $all_valid) ||
150 !in_array($peer_id, $all_exc)) {
151 $invalid_peer_ids[] = $peer_id;
152 }
153 foreach ($reviews as $giver_id => $valid) {
154 if (!in_array($giver_id, $all_valid) ||
155 !in_array($peer_id, $all_exc)) {
156 $invalid_giver_ids[] = $giver_id;
157 } else {
158 $all_reviews[$peer_id][$giver_id] = $valid;
159 }
160 }
161 }
162 $invalid_giver_ids = array_unique($invalid_giver_ids);
163
164 $missing_user_ids = array();
165 foreach ($all_valid as $user_id) {
166 // a missing peer is also a missing giver
167 if (!in_array($user_id, $peer_ids)) {
168 $missing_user_ids[] = $user_id;
169 }
170 }
171
172 $not_returned_ids = array();
173 foreach ($all_exc as $user_id) {
174 if (!in_array($user_id, $all_valid)) {
175 $not_returned_ids[] = $user_id;
176 }
177 }
178
179 return array(
180 "invalid" => (count($missing_user_ids) ||
181 count($invalid_peer_ids) ||
182 count($invalid_giver_ids)),
183 "missing_user_ids" => $missing_user_ids,
184 "not_returned_ids" => $not_returned_ids,
185 "invalid_peer_ids" => $invalid_peer_ids,
186 "invalid_giver_ids" => $invalid_giver_ids,
187 "reviews" => $all_reviews);
188 }
189
190 return null;
191 }
192
193 public function getPeerReviewValues(
194 int $a_giver_id,
195 int $a_peer_id
196 ): array {
197 $peer = null;
198 foreach ($this->getPeerReviewsByGiver($a_giver_id) as $item) {
199 if ($item["peer_id"] == $a_peer_id) {
200 $peer = $item;
201 }
202 }
203 if (!$peer) {
204 return [];
205 }
206 $data = $peer["pcomment"];
207 if ($data) {
208 $items = unserialize($data, ['allowed_classes' => false]);
209 if (!is_array($items)) {
210 // v1 - pcomment == text
211 $items = array("text" => $data);
212 }
213 return $items;
214 }
215 return [];
216 }
217
218 public function getPeerReviewsByGiver(int $a_user_id): array
219 {
220 $ilDB = $this->db;
221
222 $res = array();
223
224 if ($this->initPeerReviews()) {
225 $idx = 0;
226 $set = $ilDB->query("SELECT *" .
227 " FROM exc_assignment_peer" .
228 " WHERE giver_id = " . $ilDB->quote($a_user_id, "integer") .
229 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
230 " ORDER BY peer_id");
231 while ($row = $ilDB->fetchAssoc($set)) {
232 $row["seq"] = ++$idx;
233 $res[] = $row;
234 }
235 }
236
237 return $res;
238 }
239
240 public function getPeerMaskedId(
241 int $a_giver_id,
242 int $a_peer_id
243 ): int {
244 foreach ($this->getPeerReviewsByGiver($a_giver_id) as $peer) {
245 if ($peer["peer_id"] == $a_peer_id) {
246 return (int) $peer["seq"];
247 }
248 }
249 return 0;
250 }
251
252 protected function validatePeerReview(array $a_data): bool
253 {
254 $all_empty = true;
255 // see getPeerReviewValues()
256 $values = null;
257 $data = $a_data["pcomment"];
258 if ($data) {
259 try {
260 $values = unserialize($data, ['allowed_classes' => false]);
261 } catch (Exception $e) {
262 }
263 if (!is_array($values)) {
264 // v1 - pcomment == text
265 $values = array("text" => $data);
266 }
267 }
268
269 foreach ($this->assignment->getPeerReviewCriteriaCatalogueItems() as $crit) {
270 $crit_id = $crit->getId()
271 ? $crit->getId()
272 : $crit->getType();
273 $crit->setPeerReviewContext(
274 $this->assignment,
275 $a_data["giver_id"],
276 $a_data["peer_id"]
277 );
278 if (!$crit->validate($values[$crit_id] ?? null)) {
279 return false;
280 }
281 if ($crit->hasValue($values[$crit_id] ?? null)) {
282 $all_empty = false;
283 }
284 }
285
286 return !$all_empty;
287 }
288
289 public function getPeerReviewsByPeerId(
290 int $a_user_id,
291 bool $a_only_valid = false
292 ): array {
293 $ilDB = $this->db;
294
295 $res = array();
296
297 $idx = 0;
298 $set = $ilDB->query("SELECT *" .
299 " FROM exc_assignment_peer" .
300 " WHERE peer_id = " . $ilDB->quote($a_user_id, "integer") .
301 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
302 " ORDER BY peer_id");
303 while ($row = $ilDB->fetchAssoc($set)) {
304 if (!$a_only_valid ||
305 $this->validatePeerReview($row)) {
306 // this would be correct but rather senseless
307 // $row["seq"] = $this->getPeerMaskedId($row["giver_id"], $a_user_id);
308 $row["seq"] = ++$idx;
309 $res[] = $row;
310 }
311 }
312
313 return $res;
314 }
315
316 public function countReceivedFeedbacks(
317 int $user_id,
318 bool $only_valid = true
319 ): int {
320 return count($this->getPeerReviewsByPeerId($user_id, $only_valid));
321 }
322
323 public function getAllPeerReviews(
324 bool $a_only_valid = true
325 ): array {
326 $ilDB = $this->db;
327
328 $res = array();
329
330 $set = $ilDB->query("SELECT *" .
331 " FROM exc_assignment_peer" .
332 " WHERE ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
333 " ORDER BY peer_id");
334 while ($row = $ilDB->fetchAssoc($set)) {
335 $valid = $this->validatePeerReview($row);
336 if (!$a_only_valid ||
337 $valid) {
338 $res[$row["peer_id"]][$row["giver_id"]] = $valid;
339 }
340 }
341
342 return $res;
343 }
344
345 public function hasPeerReviewAccess(
346 int $a_peer_id
347 ): bool {
348 $ilDB = $this->db;
349 $ilUser = $this->user;
350
351 $set = $ilDB->query("SELECT ass_id" .
352 " FROM exc_assignment_peer" .
353 " WHERE giver_id = " . $ilDB->quote($ilUser->getId(), "integer") .
354 " AND peer_id = " . $ilDB->quote($a_peer_id, "integer") .
355 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer"));
356 $row = $ilDB->fetchAssoc($set);
357 return ((int) ($row["ass_id"] ?? 0) > 0);
358 }
359
361 int $a_peer_id
362 ): void {
363 $ilDB = $this->db;
364 $ilUser = $this->user;
365
366 $ilDB->manipulate("UPDATE exc_assignment_peer" .
367 " SET tstamp = " . $ilDB->quote(ilUtil::now(), "timestamp") .
368 " WHERE giver_id = " . $ilDB->quote($ilUser->getId(), "integer") .
369 " AND peer_id = " . $ilDB->quote($a_peer_id, "integer") .
370 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer"));
371 }
372
373 public function updatePeerReview(
374 int $a_peer_id,
375 array $a_values
376 ): void {
377 $ilDB = $this->db;
378 $ilUser = $this->user;
379
380 $data = [
381 "pcomment" => serialize($a_values),
382 "peer_id" => $a_peer_id,
383 "giver_id" => $ilUser->getId()
384 ];
385 $valid = $this->validatePeerReview($data);
386
387 $sql = "UPDATE exc_assignment_peer" .
388 " SET tstamp = " . $ilDB->quote(ilUtil::now(), "timestamp") .
389 ",pcomment = " . $ilDB->quote(serialize($a_values), "text") .
390 ",is_valid = " . $ilDB->quote((int) $valid, "integer") .
391 " WHERE giver_id = " . $ilDB->quote($ilUser->getId(), "integer") .
392 " AND peer_id = " . $ilDB->quote($a_peer_id, "integer") .
393 " AND ass_id = " . $ilDB->quote($this->assignment_id, "integer");
394
395 $ilDB->manipulate($sql);
396 }
397
398 public function countGivenFeedback(
399 bool $a_validate = true,
400 ?int $a_user_id = null
401 ): int {
402 $ilDB = $this->db;
403 $ilUser = $this->user;
404
405 if (!$a_user_id) {
406 $a_user_id = $ilUser->getId();
407 }
408
409 $cnt = 0;
410
411 $set = $ilDB->query("SELECT *" .
412 " FROM exc_assignment_peer" .
413 " WHERE ass_id = " . $ilDB->quote($this->assignment_id, "integer") .
414 " AND giver_id = " . $ilDB->quote($a_user_id, "integer"));
415 while ($row = $ilDB->fetchAssoc($set)) {
416 if (!$a_validate ||
417 $this->validatePeerReview($row)) {
418 $cnt++;
419 }
420 }
421
422 return $cnt;
423 }
424
425 protected function getMaxPossibleFeedbacks(): int
426 {
427 return (count($this->domain->submission($this->assignment_id)->getUsersWithSubmission()) - 1);
428 }
429
431 {
432 $max = $this->getMaxPossibleFeedbacks();
433
434 // #16160 - forever alone
435 if ($max === 0) {
436 return 0;
437 }
438
439 // are all required or just 1?
440 if ($this->assignment->getPeerReviewSimpleUnlock() == 2) {
441 $needed = 0;
442 } elseif ($this->assignment->getPeerReviewSimpleUnlock() == 0) {
443 $needed = $this->assignment->getPeerReviewMin();
444 } else {
445 $needed = 1;
446 }
447 // there could be less participants than stated in the min required setting
448 $min = min($max, $needed);
449 return max(0, $min - $this->countGivenFeedback());
450 }
451
452 public function isFeedbackValidForPassed(int $a_user_id): bool
453 {
454 // peer feedback is not required for passing
455 if ($this->assignment->getPeerReviewValid() == ilExAssignment::PEER_REVIEW_VALID_NONE) {
456 return true;
457 }
458
459 // #16227 - no processing before reaching the peer review period
460 if (!$this->assignment->afterDeadlineStrict()) {
461 return false;
462 }
463
464 // forever alone - should be valid
465 $max = $this->getMaxPossibleFeedbacks();
466 if ($max === 0) {
467 return true;
468 }
469
470 $no_of_feedbacks = $this->countGivenFeedback(true, $a_user_id);
471
472 switch ($this->assignment->getPeerReviewValid()) {
474 return (bool) $no_of_feedbacks;
475
477 // there could be less participants than stated in the min required setting
478 $min = min($max, $this->assignment->getPeerReviewMin());
479
480 return (($min - $no_of_feedbacks) < 1);
481 }
482 return false;
483 }
484
485 public static function lookupGiversWithPendingFeedback(int $a_ass_id): array
486 {
487 global $DIC;
488
489 $ilDB = $DIC->database();
490 $user_ids = array();
491
492 $set = $ilDB->query(
493 "SELECT DISTINCT(giver_id) FROM exc_assignment_peer " .
494 " WHERE ass_id = " . $ilDB->quote($a_ass_id, "integer") .
495 " AND tstamp is NULL"
496 );
497
498 while ($row = $ilDB->fetchAssoc($set)) {
499 $user_ids[] = $row["giver_id"];
500 }
501
502 return $user_ids;
503 }
504}
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