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