ILIAS  trunk Revision v11.0_alpha-1715-g7fc467680fb
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
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  {
52  $ilDB = $this->db;
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  {
83  $ilDB = $this->db;
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  {
115  $ilDB = $this->db;
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 
360  public function updatePeerReviewTimestamp(
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 }
$res
Definition: ltiservices.php:66
Exercise assignment.
static getLogger(string $a_component_id)
Get component logger.
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.
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
updatePeerReviewTimestamp(int $a_peer_id)
Exercise peer review.
getPeerMaskedId(int $a_giver_id, int $a_peer_id)
InternalDomainService $domain
global $DIC
Definition: shib_login.php:22
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)
countGivenFeedback(bool $a_validate=true, ?int $a_user_id=null)
getAllPeerReviews(bool $a_only_valid=true)
countReceivedFeedbacks(int $user_id, bool $only_valid=true)
static _getMembers(int $a_obj_id)
__construct(ilExAssignment $a_assignment)