ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
DatabaseDocumentRepository.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
37 use ilDBConstants;
38 use ilDBInterface;
39 
41 {
42  use DeriveFieldTypes;
43 
44  public function __construct(
45  private readonly string $id,
46  private readonly ilDBInterface $database,
47  private readonly UserAction $action,
48  ) {
49  }
50 
51  public function createDocument(string $title, DocumentContent $content): void
52  {
53  $creation = $this->action->modifiedNow();
54  $this->insert($this->documentTable(), [
55  'title' => $title,
56  'creation_ts' => $creation->time(),
57  'owner_usr_id' => $creation->user(),
58  'text' => $content->value(),
59  'type' => $content->type(),
60  'sorting' => $this->nextSorting(),
61  'provider' => $this->id,
62  ]);
63  }
64 
65  public function createCriterion(Document $document, CriterionContent $content): void
66  {
67  $creation = $this->action->modifiedNow();
68  $this->insert($this->criterionTable(), [
69  'doc_id' => $document->id(),
70  'assigned_ts' => $creation->time(),
71  'owner_usr_id' => $creation->user(),
72  ...$this->criterionFields($content),
73  ]);
74  }
75 
76  public function deleteDocument(Document $document): void
77  {
78  $this->deleteEntry($this->documentTable(), $document->id(), 'id', true);
79  }
80 
81  public function deleteCriterion(int $criterion_id): void
82  {
83  $this->deleteEntry($this->criterionTable(), $criterion_id, 'doc_id');
84  }
85 
86  public function updateDocumentTitle(DocumentId $document_id, string $title): void
87  {
88  $this->updateDocument($document_id, ['title' => $title]);
89  }
90 
91  public function updateDocumentContent(DocumentId $document_id, DocumentContent $content): void
92  {
93  $this->updateDocument($document_id, ['text' => $content->value(), 'type' => $content->type()]);
94  }
95 
96  public function updateDocumentOrder(DocumentId $document_id, int $order): void
97  {
98  $this->updateDocument($document_id, ['sorting' => $order], true);
99  }
100 
101  public function updateCriterionContent(int $criterion_id, CriterionContent $content): void
102  {
103  $modification = $this->action->modifiedNow();
104 
105  $this->database->update($this->criterionTable(), $this->deriveFieldTypes([
106  'modification_ts' => $modification->time(),
107  'last_modified_usr_id' => $modification->user(),
108  ...$this->criterionFields($content),
109  ]), $this->deriveFieldTypes([
110  'id' => $criterion_id,
111  ]));
112  }
113 
117  private function updateDocument(DocumentId $document_id, array $fields_and_values, bool $silent = false): void
118  {
119  match (get_class($document_id)) {
120  HashId::class => $this->lazyDocFields($fields_and_values, $document_id->hash(), $silent),
121  NumberId::class => $this->setDocFields($fields_and_values, $document_id->number(), $silent),
122  };
123  }
124 
125  public function countAll(): int
126  {
127  return (int) current($this->queryF('SELECT COUNT(1) as c FROM ' . $this->documentTable() . ' WHERE provider = %s', [$this->id]))['c'];
128  }
129 
133  public function all(int $offset = 0, ?int $limit = null): array
134  {
135  return $this->queryDocuments('1', $limit === null ? '' : ' LIMIT ' . $offset . ', ' . $limit);
136  }
137 
142  public function select(array $ids): array
143  {
144  if ([] === $ids) {
145  return [];
146  }
147  return $this->queryDocuments($this->database->in('id', $ids, false, ilDBConstants::T_INTEGER));
148  }
149 
153  public function find(int $id): Result
154  {
155  return $this->first(
156  $this->select([$id]),
157  'Document with ID ' . $id . ' not found.'
158  );
159  }
160 
161  public function findId(DocumentId $document_id): Result
162  {
163  return match (get_class($document_id)) {
164  HashId::class => $this->findHash($document_id->hash()),
165  NumberId::class => $this->find($document_id->number()),
166  };
167  }
168 
182  public function documentFromRow(array $row, array $criteria): Document
183  {
184  return new Document((int) $row['id'], new Meta(
185  (int) $row['sorting'],
186  new Edit((int) $row['last_modified_usr_id'], new DateTimeImmutable('@' . $row['modification_ts'])),
187  new Edit((int) $row['owner_usr_id'], new DateTimeImmutable('@' . $row['creation_ts']))
188  ), new DocumentContent($row['type'], $row['title'] ?? '', $row['text'] ?? ''), $criteria);
189  }
190 
191  public function documentTable(): string
192  {
193  return 'ldoc_documents';
194  }
195 
196  public function exists(string $doc_id_name): string
197  {
198  $documents = $this->documentTable();
199  $provider = $this->database->quote($this->id, ilDBConstants::T_TEXT);
200  $table = 't' . random_int(0, 100);
201  return "EXISTS (SELECT 1 FROM $documents AS $table WHERE $table.id = $doc_id_name AND $table.provider = $provider)";
202  }
203 
207  private function setDocFields(array $fields_and_values, int $doc_id, bool $silent): void
208  {
209  $modification = $this->action->modifiedNow();
210  $this->database->update($this->documentTable(), $this->deriveFieldTypes([
211  ...$fields_and_values,
212  ...($silent ? [] : [
213  'modification_ts' => $modification->time(),
214  'last_modified_usr_id' => $modification->user(),
215  ]),
216  ]), $this->deriveFieldTypes([
217  'id' => $doc_id,
218  'provider' => $this->id,
219  ]));
220  }
221 
225  private function lazyDocFields(array $fields_and_values, string $hash, bool $silent): void
226  {
227  $modification = $this->action->modifiedNow();
228  $affected_rows = $this->database->update($this->documentTable(), $this->deriveFieldTypes([
229  ...$fields_and_values,
230  ...($silent ? [] : [
231  'modification_ts' => $modification->time(),
232  'last_modified_usr_id' => $modification->user(),
233  ]),
234  ]), $this->deriveFieldTypes([
235  'hash' => $hash,
236  'provider' => $this->id,
237  ]));
238 
239  if (0 === $affected_rows) {
240  $this->database->insert($this->documentTable(), $this->deriveFieldTypes([
241  'id' => $this->database->nextId($this->documentTable()),
242  'creation_ts' => $modification->time(),
243  'owner_usr_id' => $modification->user(),
244  'sorting' => $this->nextSorting(),
245  'provider' => $this->id,
246  'title' => 'Unnamed document',
247  'hash' => $hash,
248  ...$fields_and_values,
249  ]));
250  }
251  }
252 
253  private function criterionFields(CriterionContent $content): array
254  {
255  return [
256  'criterion_id' => $content->type(),
257  'criterion_value' => json_encode($content->arguments()),
258  ];
259  }
260 
261  private function queryDocuments(string $where = '1', string $limit = ''): array
262  {
263  $doc_table = $this->documentTable();
264  $provider = $this->database->quote($this->id, ilDBConstants::T_TEXT);
265  $documents = $this->query('SELECT * FROM ' . $doc_table . ' WHERE ' . $where . ' AND provider = ' . $provider . ' ORDER BY sorting ' . $limit);
266  $doc_ids = array_map(fn($doc) => (int) $doc['id'], $documents);
267  $array = $this->query(join(' ', [
268  'SELECT * FROM',
269  $this->criterionTable(),
270  'WHERE',
271  $this->database->in('doc_id', $doc_ids, false, ilDBConstants::T_INTEGER),
272  'AND',
273  $this->exists('doc_id')
274  ]));
275 
276  $assignments = [];
277  foreach ($array as $row) {
278  $document_id = (int) $row['doc_id'];
279  $assignments[$document_id] ??= [];
280  $assignments[$document_id][] = $this->criterionFromRow($row);
281  }
282 
283 
284  return array_map(
285  fn($doc) => $this->documentFromRow($doc, $assignments[(int) $doc['id']] ?? []),
286  $documents
287  );
288  }
289 
290  private function criterionFromRow(array $row): Criterion
291  {
292  return new Criterion(
293  (int) $row['id'],
294  new CriterionContent(
295  $row['criterion_id'],
296  json_decode($row['criterion_value'], true)
297  ),
298  new Edit((int) $row['last_modified_usr_id'], new DateTimeImmutable('@' . $row['modification_ts'])),
299  new Edit((int) $row['owner_usr_id'], new DateTimeImmutable('@' . $row['assigned_ts']))
300  );
301  }
302 
303  private function criterionTable(): string
304  {
305  return 'ldoc_criteria';
306  }
307 
311  private function insert(string $table, array $fields_and_values): void
312  {
313  $id = $this->database->nextId($table);
314 
315  $this->database->insert($table, $this->deriveFieldTypes([...$fields_and_values, 'id' => $id]));
316  }
317 
321  private function update(int $id, string $table, array $fields_and_values): void
322  {
323  $this->database->update($table, $this->deriveFieldTypes($fields_and_values), $this->deriveFieldTypes([
324  'id' => $id,
325  ]));
326  }
327 
328  private function deleteEntry(string $table, int $id, string $doc_field, bool $cleanup = false): void
329  {
330  $id = $this->database->quote($id, ilDBConstants::T_INTEGER);
331  $this->database->manipulate("DELETE FROM $table WHERE id = $id AND " . $this->exists($table . '.' . $doc_field));
332  if ($cleanup) {
333  $this->cleanupCriteria();
334  }
335  }
336 
337  private function cleanupCriteria(): void
338  {
339  $criteria = $this->criterionTable();
340  $documents = $this->documentTable();
341  // The provider is not specified because this statement cleans up dead criteria independent of the provider.
342  $this->database->manipulate("DELETE FROM $criteria WHERE doc_id NOT IN (SELECT id FROM $documents)");
343  }
344 
345  private function nextSorting(): int
346  {
347  $documents = $this->documentTable();
348  $sorting = (int) ($this->database->fetchAssoc($this->database->query(
349  "SELECT MAX(sorting) as s FROM $documents WHERE " . $this->exists($documents . '.id')
350  ))['s'] ?? 0);
351 
352  return $sorting + 10;
353  }
354 
355  private function findHash(string $hash): Result
356  {
357  return $this->first(
358  $this->queryDocuments($this->database->in('hash', [$hash], false, ilDBConstants::T_TEXT)),
359  'Document with hash . ' . json_encode($hash) . ' not found.'
360  );
361  }
362 
363  private function first(array $array, string $message): Result
364  {
365  $document = current($array) ?: null;
366  return $document ? new Ok($document) : new Error($message);
367  }
368 }
updateDocumentContent(DocumentId $document_id, DocumentContent $content)
update(int $id, string $table, array $fields_and_values)
__construct(private readonly string $id, private readonly ilDBInterface $database, private readonly UserAction $action,)
updateDocument(DocumentId $document_id, array $fields_and_values, bool $silent=false)
setDocFields(array $fields_and_values, int $doc_id, bool $silent)
A result encapsulates a value or an error and simplifies the handling of those.
Definition: Result.php:14
createCriterion(Document $document, CriterionContent $content)
lazyDocFields(array $fields_and_values, string $hash, bool $silent)
$provider
Definition: ltitoken.php:83
updateCriterionContent(int $criterion_id, CriterionContent $content)
A result encapsulates a value or an error and simplifies the handling of those.
Definition: Ok.php:16
deleteEntry(string $table, int $id, string $doc_field, bool $cleanup=false)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$message
Definition: xapiexit.php:32