ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilCmiXapiStatementsDeleteRequest.php
Go to the documentation of this file.
1<?php
2
19declare(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) {
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)) {
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}";
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()];
424 }
425 if ($this->scope === self::DELETE_SCOPE_FILTERED) {
426 if ($this->checkDeleteState() && $this->filter) {
427 $usrId = [$this->filter->getActor()->getUsrId()];
429 }
430 }
431 }
432
433}
__construct(int $obj_id, int $type_id, string $activity_id, ?int $usr_id=null, ?string $scope=self::DELETE_SCOPE_FILTERED, ?ilCmiXapiStatementsReportFilter $filter=null)
static deleteUsersForObject(int $objId, ?array $users=[])
static getInstancesByObjectIdAndUsrId(int $objId, int $usrId)
static getUsersForObject(int $objId, bool $asUsrId=false)
static getLogger(string $a_component_id)
Get component logger.
Component logger with individual log levels by component id.
static appendUrlParameterString(string $a_url, string $a_par, bool $xml_style=false)
const ILIAS_VERSION_NUMERIC
$res
Definition: ltiservices.php:69
filter(string $filter_id, $class_path, string $cmd, bool $activated=true, bool $expanded=true)
global $DIC
Definition: shib_login.php:26
$url
Definition: shib_logout.php:68
$response
Definition: xapitoken.php:93