ILIAS  trunk Revision v11.0_alpha-1713-gd8962da2f67
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.ilCmiXapiStatementsDeleteRequest.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
29 {
30  public const DELETE_SCOPE_FILTERED = "filtered";
31  public const DELETE_SCOPE_ALL = "all";
32  public const DELETE_SCOPE_OWN = "own";
33 
34  private \GuzzleHttp\Client $client;
35 
36  private ?int $usrId;
37 
38  private string $activityId;
39 
40  protected string $scope;
41 
43 
44  protected int $objId;
45 
47 
48  protected string $endpointDefault = '';
49 
50  protected string $endpointFallback = '';
51 
52  protected array $headers;
53 
54  protected array $defaultHeaders;
55 
56  protected ilLogger $log;
57 
58  public function __construct(
59  int $obj_id,
60  int $type_id,
61  string $activity_id,
62  ?int $usr_id = null,
63  ?string $scope = self::DELETE_SCOPE_FILTERED,
65  ) {
66  if ((int)ILIAS_VERSION_NUMERIC < 6) { // only in plugin
67  require_once __DIR__ . '/../XapiProxy/vendor/autoload.php';
68  }
69  $this->objId = $obj_id;
70  $this->lrsType = new ilCmiXapiLrsType($type_id);
71  $this->activityId = $activity_id;
72  $this->usrId = $usr_id;
73  $this->scope = $scope;
74  $this->filter = $filter;
75 
76  $this->endpointDefault = $this->lrsType->getLrsEndpoint();
77  $this->client = new GuzzleHttp\Client();
78  $this->headers = [
79  'X-Experience-API-Version' => '1.0.3'
80  ];
81  $this->defaultHeaders = $this->headers;
82  $this->defaultHeaders['Authorization'] = $this->lrsType->getBasicAuth();
83 
84  $this->log = ilLoggerFactory::getLogger('cmix');
85  }
86 
90  public function delete(): bool
91  {
92  global $DIC;
93  $allResponses = $this->deleteData();
94  $resStatements = $allResponses['statements'];
95  $resStates = $allResponses['states'];
96  $defaultRejected = isset($resStatements['default']) && isset($resStatements['default']['state']) && $resStatements['default']['state'] === 'rejected';
97  $resArr = array();
98  // ToDo: fullfilled and status code handling
99  if (isset($resStatements['default']) && isset($resStatements['default']['value'])) {
100  $res = $resStatements['default']['value'];
101  $resBody = json_decode((string) $res->getBody(), true);
102  $resArr[] = $resBody['_id'];
103  }
104  if (count($resArr) == 0) {
105  $this->log->debug("No data deleted");
106  return !$defaultRejected;
107  }
108 
109  $maxtime = 240; // should be some minutes!
110  $t = 0;
111  $done = false;
112  while ($t < $maxtime) {
113  // get batch done
114  sleep(1);
115  $response = $this->queryBatch($resArr);
116  if (isset($response['default']) && isset($response['default']['value'])) {
117  $res = $response['default']['value'];
118  $resBody = json_decode((string) $res->getBody(), true);
119  if ($resBody && $resBody['edges'] && count($resBody['edges']) == 1) {
120  $doneDefault = $resBody['edges'][0]['node']['done'];
121  $this->log->debug("doneDefault: " . $doneDefault);
122  }
123  }
124  if ($doneDefault) {
125  $done = true;
126  break;
127  }
128  $t++;
129  }
130  if ($done) {
131  $this->checkDeleteUsersForObject();
132  }
133  return $done;
134  }
135 
136  public function deleteData(): array
137  {
138  global $DIC;
139 
140  $deleteState = true;
141 
142  $f = null;
143  if ($this->scope === self::DELETE_SCOPE_FILTERED) {
144  $deleteState = $this->checkDeleteState();
145  $f = $this->buildDeleteFiltered();
146  }
147  if ($this->scope === self::DELETE_SCOPE_ALL) {
148  $f = $this->buildDeleteAll();
149  }
150  if ($this->scope === self::DELETE_SCOPE_OWN) {
151  $f = $this->buildDeleteOwn();
152  }
153  if ($f === false) {
154  $this->log->debug('error: could not build filter');
155  return array();
156  }
157  $cf = array('filter' => $f);
158  $body = json_encode($cf);
159  $this->defaultHeaders['Content-Type'] = 'application/json; charset=utf-8';
160  $defaultUrl = $this->lrsType->getLrsEndpointDeleteLink();
161  $defaultRequest = new GuzzleHttp\Psr7\Request('POST', $defaultUrl, $this->defaultHeaders, $body);
162  $promisesStatements = [
163  'default' => $this->client->sendAsync($defaultRequest)
164  ];
165  $promisesStates = array();
166  if ($deleteState) {
167  $urls = $this->getDeleteStateUrls($this->lrsType->getLrsEndpointStateLink());
168  foreach ($urls as $i => $v) {
169  $r = new GuzzleHttp\Psr7\Request('DELETE', $v, $this->defaultHeaders);
170  $promisesStates['default' . $i] = $this->client->sendAsync($r);
171  }
172  }
173  $response = array();
174  $response['statements'] = array();
175  $response['states'] = array();
176 
177  try { // maybe everything into one promise?
178  $response['statements'] = GuzzleHttp\Promise\Utils::settle($promisesStatements)->wait();
179  if ($deleteState && count($promisesStates) > 0) {
180  $response['states'] = GuzzleHttp\Promise\Utils::settle($promisesStates)->wait();
181  }
182  } catch (Exception $e) {
183  $this->log->debug('error:' . $e->getMessage());
184  }
185  return $response;
186  }
187 
188  public function _lookUpDataCount($scope = null)
189  {
190  global $DIC;
191  $pipeline = array();
192  if (is_null($scope)) {
193  $scope = $this->scope;
194  }
195  if ($scope === self::DELETE_SCOPE_OWN) {
196  $f = $this->buildDeleteOwn();
197  if (count($f) == 0) {
198  return 0;
199  }
200  }
201  if ($scope === self::DELETE_SCOPE_FILTERED) {
202  $f = $this->buildDeleteFiltered();
203  }
204  if ($scope === self::DELETE_SCOPE_ALL) {
205  $f = $this->buildDeleteAll();
206  }
207  $pipeline[] = array('$match' => $f);
208  $pipeline[] = array('$count' => 'count');
209  $pquery = urlencode(json_encode($pipeline));
210  $query = "pipeline={$pquery}";
211  $purl = $this->lrsType->getLrsEndpointStatementsAggregationLink();
212  $url = ilUtil::appendUrlParameterString($purl, $query);
213  $request = new GuzzleHttp\Psr7\Request('GET', $url, $this->defaultHeaders);
214  try {
215  $response = $this->client->sendAsync($request)->wait();
216  $cnt = json_decode($response->getBody());
217  return (int) $cnt[0]->count;
218  } catch(Exception $e) {
219  throw new Exception("LRS Connection Problems");
220  return 0;
221  }
222  }
223 
228  public function queryBatch(array $batchId): array
229  {
230  global $DIC;
231  $defaultUrl = $this->getBatchUrl($this->lrsType->getLrsEndpointBatchLink(), $batchId[0]);
232  $defaultRequest = new GuzzleHttp\Psr7\Request('GET', $defaultUrl, $this->defaultHeaders);
233  $promises = [
234  'default' => $this->client->sendAsync($defaultRequest)
235  ];
236  $response = [];
237  try {
238  $response = GuzzleHttp\Promise\Utils::settle($promises)->wait();
239  } catch (Exception $e) {
240  $this->log->debug('error:' . $e->getMessage());
241  }
242  return $response;
243  }
244 
245  private function getBatchUrl(string $url, string $batchId): string
246  {
247  $f = array();
248  $f['_id'] = [
249  '$oid' => $batchId
250  ];
251  $f = urlencode(json_encode($f));
252  $f = "filter={$f}";
253  return ilUtil::appendUrlParameterString($url, $f);
254  }
255 
256  private function getDeleteStateUrls($url): array
257  {
258  $ret = array();
259  $states = $this->buildDeleteStates();
260  foreach($states as $i => $v) {
262  }
263  return $ret;
264  }
265 
266  private function buildDeleteAll(): array
267  {
268  global $DIC;
269  $f = array();
270 
271  $f['statement.object.objectType'] = 'Activity';
272  $f['statement.object.id'] = [
273  '$regex' => '^' . preg_quote($this->activityId) . ''
274  ];
275 
276  $f['statement.actor.objectType'] = 'Agent';
277 
278  $f['$or'] = [];
279  // foreach (ilXapiCmi5User::getUsersForObjectPlugin($this->getObjId()) as $usr_id) {
280  // $f['$or'][] = ['statement.actor.mbox' => "mailto:".ilXapiCmi5User::getUsrIdentPlugin($usr_id,$this->getObjId())];
281  foreach (ilCmiXapiUser::getUsersForObject($this->objId) as $cmixUser) {
282  $f['$or'][] = ['statement.actor.mbox' => "mailto:{$cmixUser->getUsrIdent()}"];
283  }
284  if (count($f['$or']) == 0) {
285  // Exception Handling!
286  return [];
287  } else {
288  return $f;
289  }
290  }
291 
292  private function buildDeleteFiltered(): array
293  {
294  global $DIC;
295  $f = array();
296 
297  $f['statement.object.objectType'] = 'Activity';
298  $f['statement.object.id'] = [
299  '$regex' => '^' . preg_quote($this->activityId) . ''
300  ];
301 
302  $f['statement.actor.objectType'] = 'Agent';
303  $f['$or'] = [];
304  if ($this->filter->getActor()) {
305  foreach (ilCmiXapiUser::getUsersForObject($this->objId) as $cmixUser) {
306  if ($cmixUser->getUsrId() == $this->filter->getActor()->getUsrId()) {
307  $f['$or'][] = ['statement.actor.mbox' => "mailto:{$cmixUser->getUsrIdent()}"];
308  }
309  }
310  } else { // check hasOutcomes Access?
311  foreach (ilCmiXapiUser::getUsersForObject($this->objId) as $cmixUser) {
312  $f['$or'][] = ['statement.actor.mbox' => "mailto:{$cmixUser->getUsrIdent()}"];
313  }
314  }
315 
316  if ($this->filter->getVerb()) {
317  $f['statement.verb.id'] = $this->filter->getVerb();
318  }
319 
320  if ($this->filter->getStartDate() || $this->filter->getEndDate()) {
321  $f['statement.timestamp'] = array();
322 
323  if ($this->filter->getStartDate()) {
324  $f['statement.timestamp']['$gt'] = $this->filter->getStartDate()->toXapiTimestamp();
325  }
326 
327  if ($this->filter->getEndDate()) {
328  $f['statement.timestamp']['$lt'] = $this->filter->getEndDate()->toXapiTimestamp();
329  }
330  }
331 
332  if (count($f['$or']) == 0) {
333  // Exception Handling!
334  return [];
335  } else {
336  return $f;
337  }
338  }
339 
340  private function buildDeleteOwn(): array
341  {
342  global $DIC;
343  $f = array();
344  $f['statement.object.objectType'] = 'Activity';
345  $f['statement.object.id'] = [
346  '$regex' => '^' . preg_quote($this->activityId) . ''
347  ];
348  $f['statement.actor.objectType'] = 'Agent';
349 
350  $usrId = ($this->usrId !== null) ? $this->usrId : $DIC->user()->getId();
351  $cmixUsers = ilCmiXapiUser::getInstancesByObjectIdAndUsrId($this->objId, $usrId);
352  $f['$or'] = [];
353  foreach ($cmixUsers as $cmixUser) {
354  $f['$or'][] = ['statement.actor.mbox' => "mailto:{$cmixUser->getUsrIdent()}"];
355  }
356  if (count($f['$or']) == 0) {
357  return [];
358  } else {
359  return $f;
360  }
361  }
362 
363  private function buildDeleteStates(): array
364  {
365  global $DIC;
366  $ret = array();
367  $user = "";
368  if ($this->scope === self::DELETE_SCOPE_FILTERED && $this->filter->getActor()) {
369  foreach (ilCmiXapiUser::getUsersForObject($this->objId) as $cmixUser) {
370  if ($cmixUser->getUsrId() == $this->filter->getActor()->getUsrId()) {
371  $user = $cmixUser->getUsrIdent();
372  $ret[] = 'activityId=' . urlencode($this->activityId) . '&agent=' . urlencode('{"mbox":"mailto:' . $user . '"}');
373  }
374  }
375  }
376 
377  if ($this->scope === self::DELETE_SCOPE_OWN) {
378  $usrId = ($this->usrId !== null) ? $this->usrId : $DIC->user()->getId();
379  foreach (ilCmiXapiUser::getUsersForObject($this->objId) as $cmixUser) {
380  if ((int) $cmixUser->getUsrId() === $usrId) {
381  $user = $cmixUser->getUsrIdent();
382  $ret[] = 'activityId=' . urlencode($this->activityId) . '&agent=' . urlencode('{"mbox":"mailto:' . $user . '"}');
383  }
384  }
385  }
386 
387  if ($this->scope === self::DELETE_SCOPE_ALL) {
388  //todo check cmix_del_object
389  foreach (ilCmiXapiUser::getUsersForObject($this->objId) as $cmixUser) {
390  $user = $cmixUser->getUsrIdent();
391  $ret[] = 'activityId=' . urlencode($this->activityId) . '&agent=' . urlencode('{"mbox":"mailto:' . $user . '"}');
392  }
393  }
394  return $ret;
395  }
396 
397  private function checkDeleteState(): bool
398  {
399  global $DIC;
400  if ($this->scope === self::DELETE_SCOPE_ALL || $this->scope === self::DELETE_SCOPE_OWN) {
401  return true;
402  }
403  if ($this->filter->getActor()) { // ToDo: only in Multicactor Mode?
404  if ($this->filter->getVerb() || $this->filter->getStartDate() || $this->filter->getEndDate()) {
405  return false;
406  } else {
407  return true;
408  }
409  }
410  return false;
411  }
412 
413  private function checkDeleteUsersForObject()
414  {
415  global $DIC;
416  if ($this->scope === self::DELETE_SCOPE_ALL) {
418  // $model = ilCmiXapiDelModel::init();
419  // $model->deleteXapiObjectEntry($this->objId);
420  }
421  if ($this->scope === self::DELETE_SCOPE_OWN) {
422  $usrId = ($this->usrId !== null) ? [$this->usrId] : [$DIC->user()->getId()];
423  ilCmiXapiUser::deleteUsersForObject($this->objId, $usrId);
424  }
425  if ($this->scope === self::DELETE_SCOPE_FILTERED) {
426  if ($this->checkDeleteState() && $this->filter) {
427  $usrId = [$this->filter->getActor()->getUsrId()];
428  ilCmiXapiUser::deleteUsersForObject($this->objId, $usrId);
429  }
430  }
431  }
432 
433 }
$res
Definition: ltiservices.php:66
static appendUrlParameterString(string $a_url, string $a_par, bool $xml_style=false)
static getLogger(string $a_component_id)
Get component logger.
$response
Definition: xapitoken.php:93
$url
Definition: shib_logout.php:66
Class ilCmiXapiStatementsDeleteRequest.
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
const ILIAS_VERSION_NUMERIC
static deleteUsersForObject(int $objId, ?array $users=[])
global $DIC
Definition: shib_login.php:22
static getInstancesByObjectIdAndUsrId(int $objId, int $usrId)
client()
description: > This example shows how a Progress Bar can be rendered and used on the client...
Definition: client.php:37
__construct(int $obj_id, int $type_id, string $activity_id, ?int $usr_id=null, ?string $scope=self::DELETE_SCOPE_FILTERED, ?ilCmiXapiStatementsReportFilter $filter=null)
filter(string $filter_id, $class_path, string $cmd, bool $activated=true, bool $expanded=true)
static getUsersForObject(int $objId, bool $asUsrId=false)
$r