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