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