ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
BasicPersistence.php
Go to the documentation of this file.
1 <?php
2 
20 
31 
32 class BasicPersistence implements Persistence
33 {
34  protected static BasicPersistence $instance;
35  protected static array $buckets = [];
36  protected \ilDBInterface $db;
37  protected \SplObjectStorage $bucketHashToObserverContainerId;
38  protected \SplObjectStorage $taskHashToTaskContainerId;
39  protected \SplObjectStorage $valueHashToValueContainerId;
40  protected ?\arConnector $connector = null;
41  protected static array $tasks = [];
42 
44  {
45  if (!isset(self::$instance)) {
46  self::$instance = new BasicPersistence($db);
47  }
48 
49  return self::$instance;
50  }
51 
52  public function __construct(\ilDBInterface $db)
53  {
54  $this->db = $db;
55  $this->valueHashToValueContainerId = new \SplObjectStorage();
56  $this->bucketHashToObserverContainerId = new \SplObjectStorage();
57  $this->taskHashToTaskContainerId = new \SplObjectStorage();
58  }
59 
60  protected function gc(): void
61  {
62  $atom = $this->db->buildAtomQuery();
63 
64  $atom->addTableLock('il_bt_bucket');
65  $atom->addTableLock('il_bt_task');
66  $atom->addTableLock('il_bt_value');
67  $atom->addTableLock('il_bt_value_to_task');
68  $atom->addQueryCallable(function (\ilDBInterface $db): void {
69  $this->db->manipulateF(
70  "DELETE FROM il_bt_bucket WHERE user_id = %s AND (state = %s OR state = %s) AND last_heartbeat < %s AND last_heartbeat > 0",
71  ['integer', 'integer', 'integer', 'integer'],
72  [
73  defined('ANONYMOUS_USER_ID') ? \ANONYMOUS_USER_ID : 13,
76  time() - 1 * 60 * 24 * 30
77  ]
78  );
79 
80  // remove old finished buckets
81  $this->db->manipulateF(
82  "DELETE FROM il_bt_bucket WHERE state = %s AND last_heartbeat < %s AND last_heartbeat > 0",
83  ['integer', 'integer'],
84  [State::FINISHED, time() - 60 * 60 * 24 * 30] // older than 30 days
85  );
86 
87  // remove old buckets with other states
88  $this->db->manipulateF(
89  "DELETE FROM il_bt_bucket WHERE state != %s AND last_heartbeat < %s AND last_heartbeat > 0",
90  ['integer', 'integer'],
91  [State::FINISHED, time() - 60 * 60 * 24 * 180] // older than 180 days
92  );
93 
94  // remove tasks without a bucket
95  $this->db->manipulate(
96  "DELETE il_bt_task FROM il_bt_task LEFT JOIN il_bt_bucket ON il_bt_bucket.id = il_bt_task.bucket_id WHERE il_bt_bucket.id IS NULL;"
97  );
98 
99  // remove value to bucket links without a bucket
100  $this->db->manipulate(
101  "DELETE il_bt_value_to_task FROM il_bt_value_to_task LEFT JOIN il_bt_bucket ON il_bt_bucket.id = il_bt_value_to_task.bucket_id WHERE il_bt_bucket.id IS NULL;"
102  );
103 
104  // remove value to bucket links without a task
105  $this->db->manipulate(
106  "DELETE il_bt_value_to_task FROM il_bt_value_to_task LEFT JOIN il_bt_task ON il_bt_task.id = il_bt_value_to_task.task_id WHERE il_bt_task.id IS NULL;"
107  );
108 
109  // remove values without a task
110  $this->db->manipulate(
111  "DELETE il_bt_value FROM il_bt_value LEFT JOIN il_bt_value_to_task ON il_bt_value_to_task.value_id = il_bt_value.id WHERE il_bt_value_to_task.id IS NULL;"
112  );
113  });
114  $atom->run();
115  }
116 
117  public function setConnector(\arConnector $c): void
118  {
119  $this->connector = $c;
120  }
121 
126  public function saveBucketAndItsTasks(Bucket $bucket): void
127  {
128  $bucket->checkIntegrity();
129 
130  $this->saveObserver($bucket);
131  }
132 
136  public function updateBucket(Bucket $bucket): void
137  {
138  $bucketContainer = new BucketContainer($this->getBucketContainerId($bucket), $this->connector);
139 
140  // The basic information about the task.
141  $bucketContainer->setUserId($bucket->getUserId());
142  $bucketContainer->setState($bucket->getState());
143  $bucketContainer->setTotalNumberoftasks(count($bucket->getTask()->unfoldTask()));
144  $bucketContainer->setPercentage($bucket->getOverallPercentage());
145  $bucketContainer->setTitle($bucket->getTitle());
146  $bucketContainer->setLastHeartbeat($bucket->getLastHeartbeat());
147  $bucketContainer->setDescription($bucket->getDescription());
148  $bucketContainer->setCurrentTaskid($this->getTaskContainerId($bucket->getCurrentTask()));
149  $bucketContainer->setRootTaskid($this->getTaskContainerId($bucket->getTask()));
150 
151  // Save and store the container to bucket instance.
152  $bucketContainer->update();
153  }
154 
158  public function getBucketIdsOfUser(int $user_id, string $order_by = "id", string $order_direction = "ASC"): array
159  {
160  // Garbage Collection
161  $random = new \ilRandom();
162 
163  if($random->int(1, 100) === 1) {
164  $this->gc();
165  }
166 
167  return BucketContainer::where(['user_id' => $user_id])
168  ->orderBy($order_by, $order_direction)
169  ->getArray(null, 'id');
170  }
171 
175  public function getBucketMetaOfUser(int $user_id): array
176  {
177  $buckets = BucketContainer::where(['user_id' => $user_id])->get();
178 
179  return array_map(function (BucketContainer $bucketContainer): \ILIAS\BackgroundTasks\Implementation\Bucket\BasicBucketMeta {
180  $bucketMeta = new BasicBucketMeta();
181 
182  $bucketMeta->setUserId($bucketContainer->getUserId());
183  $bucketMeta->setState($bucketContainer->getState());
184  $bucketMeta->setTitle($bucketContainer->getTitle());
185  $bucketMeta->setDescription($bucketContainer->getDescription());
186  $bucketMeta->setOverallPercentage($bucketContainer->getPercentage());
187 
188  return $bucketMeta;
189  }, $buckets);
190  }
191 
195  public function getBucketIdsByState(int $state): array
196  {
197  $buckets = BucketContainer::where(['state' => $state])->get();
198 
199  return array_map(fn (BucketContainer $bucket_container): int => $bucket_container->getId(), $buckets);
200  }
201 
206  protected function saveObserver(Bucket $bucket): void
207  {
208  // If the instance has a known container we use it, otherwise we create a new container.
209  if ($this->bucketHashToObserverContainerId->contains($bucket)) {
210  $bucketContainer = new BucketContainer($this->bucketHashToObserverContainerId[$bucket], $this->connector);
211  } else {
212  $bucketContainer = new BucketContainer(0, $this->connector);
213  }
214 
215  // The basic information about the task.
216  $bucketContainer->setUserId($bucket->getUserId());
217  $bucketContainer->setState($bucket->getState());
218  $bucketContainer->setTitle($bucket->getTitle());
219  $bucketContainer->setDescription($bucket->getDescription());
220  $bucketContainer->setTotalNumberoftasks(count($bucket->getTask()->unfoldTask()));
221  $bucketContainer->setPercentage($bucket->getOverallPercentage());
222 
223  // We want to store the bucket ID in every sub task and value. Thus we need to create an id if not available yet.
224  if (!$bucketContainer->getId()) {
225  $bucketContainer->create();
226  }
227 
228  // The recursive part.
229  $this->saveTask($bucket->getTask(), $bucketContainer->getId());
230  if (!$bucket->hasCurrentTask()) {
231  $bucket->setCurrentTask($bucket->getTask());
232  }
233  $bucketContainer->setCurrentTaskid($this->getTaskContainerId($bucket->getCurrentTask()));
234  $bucketContainer->setRootTaskid($this->getTaskContainerId($bucket->getTask()));
235 
236  // Save and store the container to bucket instance.
237  $bucketContainer->save();
238  $this->bucketHashToObserverContainerId[$bucket] = $bucketContainer->getId();
239  }
240 
247  protected function saveTask(Task $task, int $bucketId): void
248  {
249  // If the instance has a known container we use it, otherwise we create a new container.
250  if ($this->taskHashToTaskContainerId->contains($task)) {
251  $taskContainer = new TaskContainer($this->taskHashToTaskContainerId[$task]);
252  } else {
253  $taskContainer = new TaskContainer(0);
254  }
255 
256  // The basic information about the task.
257  $taskContainer->setType($task->getType());
258  $taskContainer->setBucketId($bucketId);
259  $reflection = new \ReflectionClass(get_class($task));
260  $taskContainer->setClassName(get_class($task));
261 
262  // Recursivly save the inputs and link them to this task.
263  foreach ($task->getInput() as $k => $input) {
264  $this->saveValue($input, $bucketId, $k);
265  }
266  $this->saveValueToTask($task, $taskContainer, $bucketId);
267 
268  // Save and store the container to the task instance.
269  $taskContainer->save();
270  $this->taskHashToTaskContainerId[$task] = $taskContainer->getId();
271  }
272 
279  protected function saveValueToTask(Task $task, TaskContainer $taskContainer, int $bucketId): void
280  {
281  // If we have previous values to task associations we delete them.
282  if ($taskContainer->getId() !== 0) {
284  $olds = ValueToTaskContainer::where(['task_id' => $taskContainer->getId()])->get();
285  foreach ($olds as $old) {
286  $old->delete();
287  }
288  } else {
289  // We need a valid ID to link the inputs
290  $taskContainer->save();
291  }
292 
293  // We create the new 1 to n relation.
294  foreach ($task->getInput() as $k => $inputValue) {
295  $v = new ValueToTaskContainer(0, $this->connector);
296  $v->setTaskId($taskContainer->getId());
297  $v->setPosition($k);
298  $v->setBucketId($bucketId);
299  $v->setValueId($this->getValueContainerId($inputValue));
300  $v->save();
301  }
302  }
303 
310  protected function saveValue(Value $value, int $bucketId, int $position): void
311  {
312  // If we have previous values to task associations we delete them.
313  if ($this->valueHashToValueContainerId->contains($value)) {
314  $valueContainer = new ValueContainer($this->valueHashToValueContainerId[$value], $this->connector);
315  } else {
316  $valueContainer = new ValueContainer(0, $this->connector);
317  }
318  $valueContainer->setClassName(get_class($value));
319  // bugfix mantis 23503
320  // $absolute_class_path = $reflection->getFileName();
321  // $relative_class_path = str_replace(ILIAS_ABSOLUTE_PATH,".",$absolute_class_path);
322  // $valueContainer->setClassPath($relative_class_path);
323  $valueContainer->setType($value->getType());
324  $valueContainer->setHasParenttask($value->hasParentTask());
325  $valueContainer->setBucketId($bucketId);
326  $valueContainer->setPosition($position);
327  $valueContainer->setHash($value->getHash());
328  $valueContainer->setSerialized($value->serialize());
329 
330  // If the value is a thunk value we also store its parent.
331  if ($value->hasParentTask()) {
332  $this->saveTask($value->getParentTask(), $bucketId);
333  $valueContainer->setParentTaskid($this->getTaskContainerId($value->getParentTask()));
334  }
335 
336  // We save the container and store the instance to container association.
337  $valueContainer->save();
338  $this->valueHashToValueContainerId[$value] = $valueContainer->getId();
339  }
340 
344  public function getBucketContainerId(Bucket $bucket): int
345  {
346  if (!$this->bucketHashToObserverContainerId->contains($bucket)) {
347  throw new SerializationException("Could not resolve container id of task: "
348  . print_r($bucket, true));
349  }
350 
351  return (int) $this->bucketHashToObserverContainerId[$bucket];
352  }
353 
358  protected function getTaskContainerId(Task $task): int
359  {
360  if (!$this->taskHashToTaskContainerId->contains($task)) {
361  throw new SerializationException("Could not resolve container id of task: "
362  . print_r($task, true));
363  }
364 
365  return (int) $this->taskHashToTaskContainerId[$task];
366  }
367 
371  protected function getValueContainerId(Value $value): int
372  {
373  if (!$this->valueHashToValueContainerId->contains($value)) {
374  throw new SerializationException("Could not resolve container id of value: "
375  . print_r($value, true));
376  }
377 
378  return (int )$this->valueHashToValueContainerId[$value];
379  }
380 
384  public function loadBucket(int $bucket_container_id): Bucket
385  {
386  if (isset(self::$buckets[$bucket_container_id])) {
387  return self::$buckets[$bucket_container_id];
388  }
390  $bucketContainer = BucketContainer::find($bucket_container_id);
391  if (!$bucketContainer) {
392  throw new BucketNotFoundException("The requested bucket with container id $bucket_container_id could not be found in the database.");
393  }
394  $bucket = new BasicBucket();
395 
396  $bucket->setUserId($bucketContainer->getUserId());
397  $bucket->setState($bucketContainer->getState());
398  $bucket->setTitle($bucketContainer->getTitle());
399  $bucket->setDescription($bucketContainer->getDescription());
400  $bucket->setOverallPercentage($bucketContainer->getPercentage());
401  $bucket->setLastHeartbeat($bucketContainer->getLastHeartbeat());
402  $bucket->setTask($this->loadTask($bucketContainer->getRootTaskid(), $bucket, $bucketContainer));
403 
404  $this->bucketHashToObserverContainerId[$bucket] = $bucket_container_id;
405 
406  return $bucket;
407  }
408 
417  private function loadTask(int $taskContainerId, Bucket $bucket, BucketContainer $bucketContainer): \ILIAS\BackgroundTasks\Task
418  {
419  global $DIC;
420  $factory = $DIC->backgroundTasks()->taskFactory();
422  $taskContainer = TaskContainer::find($taskContainerId);
425  $task = $factory->createTask($taskContainer->getClassName());
426 
427  // Added additional orderBy for the id to ensure that the items are returned in the right order.
428  $valueToTasks = ValueToTaskContainer::where(['task_id' => $taskContainerId])
429  ->orderBy('task_id')
430  ->orderBy('position')
431  ->orderBy('id')
432  ->get();
433  $inputs = [];
434  foreach ($valueToTasks as $valueToTask) {
435  $inputs[] = $this->loadValue($valueToTask->getValueId(), $bucket, $bucketContainer);
436  }
437  $task->setInput($inputs);
438 
439  if ($taskContainerId === $bucketContainer->getCurrentTaskid()) {
440  $bucket->setCurrentTask($task);
441  }
442 
443  $this->taskHashToTaskContainerId[$task] = $taskContainerId;
444 
445  return $task;
446  }
447 
448  private function loadValue($valueContainerId, Bucket $bucket, BucketContainer $bucketContainer): \ILIAS\BackgroundTasks\Value
449  {
450  global $DIC;
451  $factory = $DIC->backgroundTasks()->injector();
452 
454  $valueContainer = ValueContainer::find($valueContainerId);
458  $value = $factory->createInstance($valueContainer->getClassName());
459 
460  $value->unserialize($valueContainer->getSerialized());
461  if ($valueContainer->getHasParenttask() !== 0) {
462  $value->setParentTask($this->loadTask($valueContainer->getParentTaskid(), $bucket, $bucketContainer));
463  }
464 
465  $this->valueHashToValueContainerId[$value] = $valueContainerId;
466 
467  return $value;
468  }
469 
470  public function deleteBucketById(int $bucket_id): void
471  {
472  $buckets = BucketContainer::where(['id' => $bucket_id])->get();
473  array_map(function (BucketContainer $item): void {
474  $item->delete();
475  }, $buckets);
476 
478  $tasks = TaskContainer::where(['bucket_id' => $bucket_id])->get();
479  array_map(function (TaskContainer $item): void {
480  $item->delete();
481  }, $tasks);
482 
484  $values = ValueContainer::where(['bucket_id' => $bucket_id])->get();
485  array_map(function (ValueContainer $item): void {
486  $item->delete();
487  }, $values);
488 
490  $valueToTasks = ValueToTaskContainer::where(['bucket_id' => $bucket_id])->get();
491  array_map(function (ValueToTaskContainer $item): void {
492  $item->delete();
493  }, $valueToTasks);
494  }
495 
499  public function deleteBucket(Bucket $bucket): void
500  {
501  $id = $this->getBucketContainerId($bucket);
502  $this->deleteBucketById($id);
503  $this->bucketHashToObserverContainerId->detach($bucket);
504  }
505 
509  public function loadBuckets(array $bucket_container_ids): array
510  {
511  $buckets = [];
512  foreach ($bucket_container_ids as $bucket_id) {
513  try {
514  $buckets[] = $this->loadBucket($bucket_id);
515  } catch (\Throwable $t) {
516  // there seem to be a problem with this container, we must delete it
517  $this->deleteBucketById($bucket_id);
518  }
519  }
520 
521  return $buckets;
522  }
523 }
const ANONYMOUS_USER_ID
Definition: constants.php:27
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
loadBucket(int $bucket_container_id)
Class ChatMainBarProvider .
getBucketIdsOfUser(int $user_id, string $order_by="id", string $order_direction="ASC")
[] Returns an array of bucket ids for the given user Id.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static where($where, $operator=null)
global $DIC
Definition: feed.php:28
updateBucket(Bucket $bucket)
Updates only the bucket! Use this if e.g.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getLastHeartbeat()
When was the last time that something happened on this bucket?
getBucketIdsByState(int $state)
int[] Returns a list of bucket ids for the given Observer State
saveBucketAndItsTasks(Bucket $bucket)
Fully updates or creates an Observer and all its tasks into the database.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Job.php:19
deleteBucket(Bucket $bucket)
Delete the bucket and all its stuff.
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
deleteBucketById(int $bucket_id)
Deletes the Observer AND all its tasks and values.