ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
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  ilLPStatusWrapper::_updateStatus($this->object->getId(), $cmixUser->getUsrId());
73  }
74  }
75 
76  public function getCmixUser($xapiStatement)
77  {
78  $cmixUser = null;
79  if ($this->object->getContentType() == ilObjCmiXapi::CONT_TYPE_CMI5)
80  {
82  $this->object->getId(),
83  $xapiStatement->actor->account->name
84  );
85  }
86  else
87  {
89  $this->object->getId(),
90  str_replace('mailto:', '', $xapiStatement->actor->mbox)
91  );
92  }
93  return $cmixUser;
94  }
95 
96  public function evaluateStatement($xapiStatement, $usrId)
97  {
98  global $DIC;
99  $xapiVerb = $this->getXapiVerb($xapiStatement);
100 
101  if ($this->isValidXapiStatement($xapiStatement))
102  {
103  // result status and if exists scaled score
104  if ($this->hasResultStatusRelevantXapiVerb($xapiVerb))
105  {
106  if (!$this->isValidObject($xapiStatement))
107  {
108  return;
109  }
110  $userResult = $this->getUserResult($usrId);
111 
112  $oldResultStatus = $userResult->getStatus();
113  $newResultStatus = $this->getResultStatusForXapiVerb($xapiVerb);
114 
115  // this is for both xapi and cmi5
116  if ($this->isResultStatusToBeReplaced($oldResultStatus, $newResultStatus)) {
117  $this->log->debug("isResultStatusToBeReplaced: true");
118  $userResult->setStatus($newResultStatus);
119  }
120 
121  if ($this->hasXapiScore($xapiStatement)) {
122  $xapiScore = $this->getXapiScore($xapiStatement);
123  $this->log->debug("Score: " . $xapiScore);
124  $userResult->setScore((float) $xapiScore);
125  }
126  $userResult->save();
127 
128  // only cmi5
129  if ($this->object->getContentType() == ilObjCmiXapi::CONT_TYPE_CMI5)
130  {
131  if (($xapiVerb == ilCmiXapiVerbList::COMPLETED || $xapiVerb == ilCmiXapiVerbList::PASSED) && $this->isLpModeInterestedInResultStatus($newResultStatus,false))
132  {
133  // it is possible to check against authToken usrId!
134  $cmixUser = $this->getCmixUser($xapiStatement);
135  $cmixUser->setSatisfied(true);
136  $cmixUser->save();
137  $this->sendSatisfiedStatement($cmixUser);
138  }
139  }
140  }
141  // result progress (i think only cmi5 relevant)
142  if ($this->hasResultProgressRelevantXapiVerb($xapiVerb))
143  {
144  $userResult = $this->getUserResult($usrId);
145  $progressedScore = $this->getProgressedScore($xapiStatement);
146  if ($progressedScore !== false && (float) $progressedScore > 0)
147  {
148  $userResult->setScore((float) ($progressedScore / 100));
149  $userResult->save();
150  }
151  }
152  }
153  }
154 
155  protected function isValidXapiStatement($xapiStatement)
156  {
157  if (!isset($xapiStatement->actor)) {
158  return false;
159  }
160 
161  if (!isset($xapiStatement->verb) || !isset($xapiStatement->verb->id)) {
162  return false;
163  }
164 
165  if (!isset($xapiStatement->object) || !isset($xapiStatement->object->id)) {
166  return false;
167  }
168 
169  return true;
170  }
171 
175  protected function isValidObject($xapiStatement)
176  {
177  if ($xapiStatement->object->id != $this->object->getActivityId())
178  {
179  $this->log->debug($xapiStatement->object->id . " != " . $this->object->getActivityId());
180  return false;
181  }
182  return true;
183  }
184 
185 
186  protected function getXapiVerb($xapiStatement)
187  {
188  return $xapiStatement->verb->id;
189  }
190 
191  protected function getResultStatusForXapiVerb($xapiVerb)
192  {
193  return $this->resultStatusByXapiVerbMap[$xapiVerb];
194  }
195 
196  protected function hasResultStatusRelevantXapiVerb($xapiVerb)
197  {
198  return isset($this->resultStatusByXapiVerbMap[$xapiVerb]);
199  }
200 
201  protected function getResultProgressForXapiVerb($xapiVerb)
202  {
203  return $this->resultProgressByXapiVerbMap[$xapiVerb];
204  }
205 
206  protected function hasResultProgressRelevantXapiVerb($xapiVerb)
207  {
208  return isset($this->resultProgressByXapiVerbMap[$xapiVerb]);
209  }
210 
211  protected function hasXapiScore($xapiStatement)
212  {
213  if (!isset($xapiStatement->result)) {
214  return false;
215  }
216 
217  if (!isset($xapiStatement->result->score)) {
218  return false;
219  }
220 
221  if (!isset($xapiStatement->result->score->scaled)) {
222  return false;
223  }
224 
225  return true;
226  }
227 
228  protected function getXapiScore($xapiStatement)
229  {
230  return $xapiStatement->result->score->scaled;
231  }
232 
233  protected function getProgressedScore($xapiStatement)
234  {
235  if (!isset($xapiStatement->result)) {
236  return false;
237  }
238 
239  if (!isset($xapiStatement->result->extensions)) {
240  return false;
241  }
242 
243  if (!isset($xapiStatement->result->extensions->{'https://w3id.org/xapi/cmi5/result/extensions/progress'})) {
244  return false;
245  }
246  return $xapiStatement->result->extensions->{'https://w3id.org/xapi/cmi5/result/extensions/progress'};
247  }
248 
249  protected function getUserResult($usrId)
250  {
251  try {
252  $result = ilCmiXapiResult::getInstanceByObjIdAndUsrId($this->object->getId(), $usrId);
253  } catch (ilCmiXapiException $e) {
255  $result->setObjId($this->object->getId());
256  $result->setUsrId($usrId);
257  }
258 
259  return $result;
260  }
261 
262  protected function isResultStatusToBeReplaced($oldResultStatus, $newResultStatus)
263  {
264  if (!$this->isLpModeInterestedInResultStatus($newResultStatus)) {
265  $this->log->debug("isLpModeInterestedInResultStatus: false");
266  return false;
267  }
268 
269  if (!$this->doesNewResultStatusDominateOldOne($oldResultStatus, $newResultStatus)) {
270  $this->log->debug("doesNewResultStatusDominateOldOne: false");
271  return false;
272  }
273 
274  if ($this->needsAvoidFailedEvaluation($oldResultStatus, $newResultStatus)) {
275  $this->log->debug("needsAvoidFailedEvaluation: false");
276  return false;
277  }
278 
279  return true;
280  }
281 
282  protected function isLpModeInterestedInResultStatus($resultStatus, $deactivated=true)
283  {
284  if ($this->lpMode == ilLPObjSettings::LP_MODE_DEACTIVATED) {
285  return $deactivated;
286  }
287 
288  switch ($resultStatus) {
289  case 'failed':
290 
291  return in_array($this->lpMode, [
295  ]);
296 
297  case 'passed':
298 
299  return in_array($this->lpMode, [
304  ]);
305 
306  case 'completed':
307 
308  return in_array($this->lpMode, [
313  ]);
314  }
315 
316  return false;
317  }
318 
319  protected function doesNewResultStatusDominateOldOne($oldResultStatus, $newResultStatus)
320  {
321  if ($oldResultStatus == '' ) {
322  return true;
323  }
324 
325  if (in_array($newResultStatus, ['passed', 'failed'])) {
326  return true;
327  }
328 
329  if (!in_array($oldResultStatus, ['passed', 'failed'])) {
330  return true;
331  }
332 
333  return false;
334  }
335 
336  protected function needsAvoidFailedEvaluation($oldResultStatus, $newResultStatus)
337  {
338  if (!$this->object->isKeepLpStatusEnabled()) {
339  return false;
340  }
341 
342  if ($newResultStatus != 'failed') {
343  return false;
344  }
345 
346  return $oldResultStatus == 'completed' || $oldResultStatus == 'passed';
347  }
348 
349  protected function sendSatisfiedStatement($cmixUser)
350  {
351  global $DIC;
352 
353  $lrsType = $this->object->getLrsType();
354  $defaultLrs = $lrsType->getLrsEndpoint();
355  //$fallbackLrs = $lrsType->getLrsFallbackEndpoint();
356  $defaultBasicAuth = $lrsType->getBasicAuth();
357  //$fallbackBasicAuth = $lrsType->getFallbackBasicAuth();
358  $defaultHeaders = [
359  'X-Experience-API-Version' => '1.0.3',
360  'Authorization' => $defaultBasicAuth,
361  'Content-Type' => 'application/json;charset=utf-8',
362  'Cache-Control' => 'no-cache, no-store, must-revalidate'
363  ];
364  /*
365  $fallbackHeaders = [
366  'X-Experience-API-Version' => '1.0.3',
367  'Authorization' => $fallbackBasicAuth,
368  'Content-Type' => 'application/json;charset=utf-8',
369  'Cache-Control' => 'no-cache, no-store, must-revalidate'
370  ];
371  */
372  $satisfiedStatement = $this->object->getSatisfiedStatement($cmixUser);
373  $satisfiedStatementParams = [];
374  $satisfiedStatementParams['statementId'] = $satisfiedStatement['id'];
375  $defaultStatementsUrl = $defaultLrs . "/statements";
376  $defaultSatisfiedStatementUrl = $defaultStatementsUrl . '?' . ilCmiXapiAbstractRequest::buildQuery($satisfiedStatementParams);
377 
378  $client = new GuzzleHttp\Client();
379  $req_opts = array(
380  GuzzleHttp\RequestOptions::VERIFY => true,
381  GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 10,
382  GuzzleHttp\RequestOptions::HTTP_ERRORS => false
383  );
384 
385  $defaultSatisfiedStatementRequest = new GuzzleHttp\Psr7\Request(
386  'PUT',
387  $defaultSatisfiedStatementUrl,
388  $defaultHeaders,
389  json_encode($satisfiedStatement)
390  );
391  $promises = array();
392  $promises['defaultSatisfiedStatement'] = $client->sendAsync($defaultSatisfiedStatementRequest, $req_opts);
393  try
394  {
395  $responses = GuzzleHttp\Promise\Utils::settle($promises)->wait();
396  $body = '';
397  ilCmiXapiAbstractRequest::checkResponse($responses['defaultSatisfiedStatement'],$body,[204]);
398  }
399  catch(Exception $e)
400  {
401  $this->log->error('error:' . $e->getMessage());
402  }
403  }
404 }
isResultStatusToBeReplaced($oldResultStatus, $newResultStatus)
$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)
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
$DIC
Definition: xapitoken.php:46
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)