ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilXapiStatementEvaluation.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2019 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
5 
16 {
22  protected $resultStatusByXapiVerbMap = array(
23  ilCmiXapiVerbList::COMPLETED => "completed",
24  ilCmiXapiVerbList::PASSED => "passed",
25  ilCmiXapiVerbList::FAILED => "failed",
27  );
28 
29  protected $resultProgressByXapiVerbMap = array(
30  ilCmiXapiVerbList::PROGRESSED => "progressed",
31  ilCmiXapiVerbList::EXPERIENCED => "experienced"
32  );
33 
37  protected $object;
38 
42  protected $log;
43 
50  {
51  $this->log = $log;
52  $this->object = $object;
53 
54  $objLP = ilObjectLP::getInstance($this->object->getId());
55  $this->lpMode = $objLP->getCurrentMode();
56  }
57 
58  public function evaluateReport(ilCmiXapiStatementsReport $report)
59  {
60  foreach ($report->getStatements() as $xapiStatement) {
61  #$this->log->debug(
62  # "handle statement:\n".json_encode($xapiStatement, JSON_PRETTY_PRINT)
63  #);
64 
65  // ensure json decoded non assoc
66  $xapiStatement = json_decode(json_encode($xapiStatement));
67  $cmixUser = $this->getCmixUser($xapiStatement);
68 
69  $this->evaluateStatement($xapiStatement, $cmixUser->getUsrId());
70 
71  $this->log->debug('update lp for object (' . $this->object->getId() . ')');
72  if ($cmixUser->getUsrId() != ANONYMOUS_USER_ID) {
73  ilLPStatusWrapper::_updateStatus($this->object->getId(), $cmixUser->getUsrId());
74  }
75  }
76  }
77 
78  public function getCmixUser($xapiStatement)
79  {
80  $cmixUser = null;
81  if ($this->object->getContentType() == ilObjCmiXapi::CONT_TYPE_CMI5) {
83  $this->object->getId(),
84  $xapiStatement->actor->account->name
85  );
86  } else {
88  $this->object->getId(),
89  str_replace('mailto:', '', $xapiStatement->actor->mbox)
90  );
91  }
92  return $cmixUser;
93  }
94 
95  public function evaluateStatement($xapiStatement, $usrId)
96  {
97  global $DIC;
98  $xapiVerb = $this->getXapiVerb($xapiStatement);
99 
100  if ($this->isValidXapiStatement($xapiStatement)) {
101  // result status and if exists scaled score
102  if ($this->hasResultStatusRelevantXapiVerb($xapiVerb)) {
103  if (!$this->isValidObject($xapiStatement)) {
104  return;
105  }
106  $userResult = $this->getUserResult($usrId);
107 
108  $oldResultStatus = $userResult->getStatus();
109  $newResultStatus = $this->getResultStatusForXapiVerb($xapiVerb);
110 
111  // this is for both xapi and cmi5
112  if ($this->isResultStatusToBeReplaced($oldResultStatus, $newResultStatus)) {
113  $this->log->debug("isResultStatusToBeReplaced: true");
114  $userResult->setStatus($newResultStatus);
115  }
116 
117  if ($this->hasXapiScore($xapiStatement)) {
118  $xapiScore = $this->getXapiScore($xapiStatement);
119  $this->log->debug("Score: " . $xapiScore);
120  $userResult->setScore((float) $xapiScore);
121  }
122  $userResult->save();
123 
124  // only cmi5
125  if ($this->object->getContentType() == ilObjCmiXapi::CONT_TYPE_CMI5) {
126  if (($xapiVerb == ilCmiXapiVerbList::COMPLETED || $xapiVerb == ilCmiXapiVerbList::PASSED) && $this->isLpModeInterestedInResultStatus($newResultStatus, false)) {
127  // it is possible to check against authToken usrId!
128  $cmixUser = $this->getCmixUser($xapiStatement);
129  $cmixUser->setSatisfied(true);
130  $cmixUser->save();
131  $this->sendSatisfiedStatement($cmixUser);
132  }
133  }
134  }
135  // result progress (i think only cmi5 relevant)
136  if ($this->hasResultProgressRelevantXapiVerb($xapiVerb)) {
137  $userResult = $this->getUserResult($usrId);
138  $progressedScore = $this->getProgressedScore($xapiStatement);
139  if ($progressedScore !== false && (float) $progressedScore > 0) {
140  $userResult->setScore((float) ($progressedScore / 100));
141  $userResult->save();
142  }
143  }
144  }
145  }
146 
147  protected function isValidXapiStatement($xapiStatement)
148  {
149  if (!isset($xapiStatement->actor)) {
150  return false;
151  }
152 
153  if (!isset($xapiStatement->verb) || !isset($xapiStatement->verb->id)) {
154  return false;
155  }
156 
157  if (!isset($xapiStatement->object) || !isset($xapiStatement->object->id)) {
158  return false;
159  }
160 
161  return true;
162  }
163 
167  protected function isValidObject($xapiStatement)
168  {
169  if ($xapiStatement->object->id != $this->object->getActivityId()) {
170  $this->log->debug($xapiStatement->object->id . " != " . $this->object->getActivityId());
171  return false;
172  }
173  return true;
174  }
175 
176 
177  protected function getXapiVerb($xapiStatement)
178  {
179  return $xapiStatement->verb->id;
180  }
181 
182  protected function getResultStatusForXapiVerb($xapiVerb)
183  {
184  return $this->resultStatusByXapiVerbMap[$xapiVerb];
185  }
186 
187  protected function hasResultStatusRelevantXapiVerb($xapiVerb)
188  {
189  return isset($this->resultStatusByXapiVerbMap[$xapiVerb]);
190  }
191 
192  protected function getResultProgressForXapiVerb($xapiVerb)
193  {
194  return $this->resultProgressByXapiVerbMap[$xapiVerb];
195  }
196 
197  protected function hasResultProgressRelevantXapiVerb($xapiVerb)
198  {
199  return isset($this->resultProgressByXapiVerbMap[$xapiVerb]);
200  }
201 
202  protected function hasXapiScore($xapiStatement)
203  {
204  if (!isset($xapiStatement->result)) {
205  return false;
206  }
207 
208  if (!isset($xapiStatement->result->score)) {
209  return false;
210  }
211 
212  if (!isset($xapiStatement->result->score->scaled)) {
213  return false;
214  }
215 
216  return true;
217  }
218 
219  protected function getXapiScore($xapiStatement)
220  {
221  return $xapiStatement->result->score->scaled;
222  }
223 
224  protected function getProgressedScore($xapiStatement)
225  {
226  if (!isset($xapiStatement->result)) {
227  return false;
228  }
229 
230  if (!isset($xapiStatement->result->extensions)) {
231  return false;
232  }
233 
234  if (!isset($xapiStatement->result->extensions->{'https://w3id.org/xapi/cmi5/result/extensions/progress'})) {
235  return false;
236  }
237  return $xapiStatement->result->extensions->{'https://w3id.org/xapi/cmi5/result/extensions/progress'};
238  }
239 
240  protected function getUserResult($usrId)
241  {
242  try {
243  $result = ilCmiXapiResult::getInstanceByObjIdAndUsrId($this->object->getId(), $usrId);
244  } catch (ilCmiXapiException $e) {
246  $result->setObjId($this->object->getId());
247  $result->setUsrId($usrId);
248  }
249 
250  return $result;
251  }
252 
253  protected function isResultStatusToBeReplaced($oldResultStatus, $newResultStatus)
254  {
255  if (!$this->isLpModeInterestedInResultStatus($newResultStatus)) {
256  $this->log->debug("isLpModeInterestedInResultStatus: false");
257  return false;
258  }
259 
260  if (!$this->doesNewResultStatusDominateOldOne($oldResultStatus, $newResultStatus)) {
261  $this->log->debug("doesNewResultStatusDominateOldOne: false");
262  return false;
263  }
264 
265  if ($this->needsAvoidFailedEvaluation($oldResultStatus, $newResultStatus)) {
266  $this->log->debug("needsAvoidFailedEvaluation: false");
267  return false;
268  }
269 
270  return true;
271  }
272 
273  protected function isLpModeInterestedInResultStatus($resultStatus, $deactivated = true)
274  {
275  if ($this->lpMode == ilLPObjSettings::LP_MODE_DEACTIVATED) {
276  return $deactivated;
277  }
278 
279  switch ($resultStatus) {
280  case 'failed':
281 
282  return in_array($this->lpMode, [
286  ]);
287 
288  case 'passed':
289 
290  return in_array($this->lpMode, [
295  ]);
296 
297  case 'completed':
298 
299  return in_array($this->lpMode, [
304  ]);
305  }
306 
307  return false;
308  }
309 
310  protected function doesNewResultStatusDominateOldOne($oldResultStatus, $newResultStatus)
311  {
312  if ($oldResultStatus == '') {
313  return true;
314  }
315 
316  if (in_array($newResultStatus, ['passed', 'failed'])) {
317  return true;
318  }
319 
320  if (!in_array($oldResultStatus, ['passed', 'failed'])) {
321  return true;
322  }
323 
324  return false;
325  }
326 
327  protected function needsAvoidFailedEvaluation($oldResultStatus, $newResultStatus)
328  {
329  if (!$this->object->isKeepLpStatusEnabled()) {
330  return false;
331  }
332 
333  if ($newResultStatus != 'failed') {
334  return false;
335  }
336 
337  return $oldResultStatus == 'completed' || $oldResultStatus == 'passed';
338  }
339 
340  protected function sendSatisfiedStatement($cmixUser)
341  {
342  global $DIC;
343 
344  $lrsType = $this->object->getLrsType();
345  $defaultLrs = $lrsType->getLrsEndpoint();
346  //$fallbackLrs = $lrsType->getLrsFallbackEndpoint();
347  $defaultBasicAuth = $lrsType->getBasicAuth();
348  //$fallbackBasicAuth = $lrsType->getFallbackBasicAuth();
349  $defaultHeaders = [
350  'X-Experience-API-Version' => '1.0.3',
351  'Authorization' => $defaultBasicAuth,
352  'Content-Type' => 'application/json;charset=utf-8',
353  'Cache-Control' => 'no-cache, no-store, must-revalidate'
354  ];
355  /*
356  $fallbackHeaders = [
357  'X-Experience-API-Version' => '1.0.3',
358  'Authorization' => $fallbackBasicAuth,
359  'Content-Type' => 'application/json;charset=utf-8',
360  'Cache-Control' => 'no-cache, no-store, must-revalidate'
361  ];
362  */
363  $satisfiedStatement = $this->object->getSatisfiedStatement($cmixUser);
364  $satisfiedStatementParams = [];
365  $satisfiedStatementParams['statementId'] = $satisfiedStatement['id'];
366  $defaultStatementsUrl = $defaultLrs . "/statements";
367  $defaultSatisfiedStatementUrl = $defaultStatementsUrl . '?' . ilCmiXapiAbstractRequest::buildQuery($satisfiedStatementParams);
368 
369  $client = new GuzzleHttp\Client();
370  $req_opts = array(
371  GuzzleHttp\RequestOptions::VERIFY => true,
372  GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 10,
373  GuzzleHttp\RequestOptions::HTTP_ERRORS => false
374  );
375 
376  $defaultSatisfiedStatementRequest = new GuzzleHttp\Psr7\Request(
377  'PUT',
378  $defaultSatisfiedStatementUrl,
379  $defaultHeaders,
380  json_encode($satisfiedStatement)
381  );
382  $promises = array();
383  $promises['defaultSatisfiedStatement'] = $client->sendAsync($defaultSatisfiedStatementRequest, $req_opts);
384  try {
385  $responses = GuzzleHttp\Promise\Utils::settle($promises)->wait();
386  $body = '';
387  ilCmiXapiAbstractRequest::checkResponse($responses['defaultSatisfiedStatement'], $body, [204]);
388  } catch (Exception $e) {
389  $this->log->error('error:' . $e->getMessage());
390  }
391  }
392 }
isResultStatusToBeReplaced($oldResultStatus, $newResultStatus)
const ANONYMOUS_USER_ID
Definition: constants.php:25
$result
static _updateStatus($a_obj_id, $a_usr_id, $a_obj=null, $a_percentage=false, $a_force_raise=false)
Update status.
if($_SERVER['argc']< 4) $client
Definition: cron.php:12
isLpModeInterestedInResultStatus($resultStatus, $deactivated=true)
global $DIC
Definition: goto.php:24
doesNewResultStatusDominateOldOne($oldResultStatus, $newResultStatus)
__construct(ilLogger $log, ilObjCmiXapi $object)
ilXapiStatementEvaluation constructor.
static getInstanceByObjIdAndUsrId($objId, $usrId)
evaluateReport(ilCmiXapiStatementsReport $report)
static getInstanceByObjectIdAndUsrIdent($objId, $usrIdent)
static checkResponse($response, &$body, $allowedStatus=[200, 204])
const LP_MODE_CMIX_COMPL_OR_PASSED_WITH_FAILED
static buildQuery(array $params, $encoding=PHP_QUERY_RFC3986)
Component logger with individual log levels by component id.
needsAvoidFailedEvaluation($oldResultStatus, $newResultStatus)
static getInstance($a_obj_id)