ILIAS  release_8 Revision v8.23
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 
61  protected function getValidPeerReviewUsers(): array
62  {
63  $ilDB = $this->db;
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  {
81  $ilDB = $this->db;
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  {
111  $ilDB = $this->db;
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;
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 
349  public function updatePeerReviewTimestamp(
350  int $a_peer_id
351  ): void {
352  $ilDB = $this->db;
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;
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;
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 }
$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)
hasPeerReviewAccess(int $a_peer_id)
updatePeerReview(int $a_peer_id, array $a_values)
Calculates peer review distribution (rater to peer assignments)
$ilUser
Definition: imgupload.php:34
getAllPeerReviews(bool $a_only_valid=true)
static _getMembers(int $a_obj_id)
__construct(ilExAssignment $a_assignment)