ILIAS  trunk Revision v11.0_alpha-1731-gff9cd7e2bd3
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
Importer.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
28 
29 class Importer
30 {
31  protected const string PATH_TO_SCHEMA = __DIR__ . '/../../../../VocabValidation/controlled_vocabulary.xsd';
32 
34  protected ControlledVocabsRepository $vocab_repo;
35  protected SlotHandler $slot_handler;
36 
37  public function __construct(
38  PathFactory $path_factory,
39  ControlledVocabsRepository $vocab_repo,
40  SlotHandler $slot_handler
41  ) {
42  $this->path_factory = $path_factory;
43  $this->vocab_repo = $vocab_repo;
44  $this->slot_handler = $slot_handler;
45  }
46 
47  public function import(string $xml_string): Result
48  {
49  $errors_or_xml = $this->loadXML($xml_string);
50  if (is_array($errors_or_xml)) {
51  return new Result(...$errors_or_xml);
52  }
53  $xml_path = new \DOMXPath($errors_or_xml);
54  $errors = [];
55 
56  try {
57  $slot = $this->extractVocabularySlot($xml_path);
58  } catch (\ilMDPathException $e) {
59  $errors[] = $e->getMessage();
60  }
61  if (isset($slot) && $slot === SlotIdentifier::NULL) {
62  $errors[] = 'Cannot add vocabulary, invalid element or condition.';
63  }
64 
65  $duplicates = $this->findDuplicateValues($xml_path);
66  if (!empty($duplicates)) {
67  $errors[] = 'The following values are not unique: ' . implode(', ', $duplicates);
68  }
69  if (empty($errors) && isset($slot)) {
70  $already_exist = $this->findAlreadyExistingValues($xml_path, $slot);
71  if (!empty($already_exist)) {
72  $errors[] = 'The following values already exist in other vocabularies of the element: ' .
73  implode(', ', $already_exist);
74  }
75  }
76 
77  if (empty($errors) && isset($slot)) {
78  try {
79  $vocab_id = $this->createVocabulary(
80  $slot,
81  $this->extractSource($xml_path)
82  );
83  } catch (\ilMDVocabulariesException $e) {
84  $errors[] = $e->getMessage();
85  }
86  }
87  if (empty($errors) && isset($vocab_id)) {
88  $this->addValuesToVocabulary($xml_path, $vocab_id);
89  }
90 
91  return new Result(...$errors);
92  }
93 
98  protected function loadXML(string $xml_string): \DOMDocument|array
99  {
100  $use_internal_errors = libxml_use_internal_errors(true);
101 
102  $xml = new \DOMDocument('1.0', 'utf-8');
103  $xml->loadXML($xml_string);
104 
105  if (!$xml->schemaValidate(self::PATH_TO_SCHEMA)) {
106  $errors = [];
107  foreach (libxml_get_errors() as $error) {
108  $errors[] = $error->message;
109  }
110  }
111 
112  libxml_clear_errors();
113  libxml_use_internal_errors($use_internal_errors);
114 
115  return empty($errors) ? $xml : $errors;
116  }
117 
118  protected function extractPathToElement(\DOMXPath $xml_path): PathInterface
119  {
120  $node = $xml_path->query('//vocabulary/appliesTo/pathToElement')->item(0);
121  return $this->writeToPath($node);
122  }
123 
124  protected function extractPathToCondition(\DOMXPath $xml_path): ?PathInterface
125  {
126  $node = $xml_path->query('//vocabulary/appliesTo/condition/pathToElement')->item(0);
127  return is_null($node) ? null : $this->writeToPath($node, true);
128  }
129 
130  protected function extractConditionValue(\DOMXPath $xml_path): ?string
131  {
132  $node = $xml_path->query('//vocabulary/appliesTo/condition/@value')->item(0);
133  return $node?->nodeValue;
134  }
135 
136  protected function extractVocabularySlot(\DOMXPath $xml_path): SlotIdentifier
137  {
138  $path_to_element = $this->extractPathToElement($xml_path);
139  $path_to_condition = $this->extractPathToCondition($xml_path);
140  $condition_value = $this->extractConditionValue($xml_path);
141 
142  return $this->slot_handler->identiferFromPathAndCondition(
143  $path_to_element,
144  $path_to_condition,
145  $condition_value
146  );
147  }
148 
149  protected function extractSource(\DOMXPath $xml_path): string
150  {
151  $node = $xml_path->query('//vocabulary/source')->item(0);
152  return (string) $node?->nodeValue;
153  }
154 
158  protected function extractValuesAndLabels(\DOMXPath $xml_path): \Generator
159  {
160  $nodes = $xml_path->query('//vocabulary/values/value');
161  foreach ($nodes as $node) {
162  $label = $node->hasAttribute('label') ? $node->getAttribute('label') : '';
163  $value = $node->nodeValue;
164  yield $value => $label;
165  }
166  }
167 
171  protected function findDuplicateValues(\DOMXPath $xml_path): array
172  {
173  $values = [];
174  $duplicates = [];
175  foreach ($this->extractValuesAndLabels($xml_path) as $value => $label) {
176  if (in_array($value, $values) && !in_array($value, $duplicates)) {
177  $duplicates[] = $value;
178  }
179  $values[] = $value;
180  }
181 
182  return $duplicates;
183  }
184 
188  protected function findAlreadyExistingValues(
189  \DOMXPath $xml_path,
190  SlotIdentifier $slot
191  ): array {
192  $values = [];
193  foreach ($this->extractValuesAndLabels($xml_path) as $value => $label) {
194  $values[] = $value;
195  }
196  return iterator_to_array($this->vocab_repo->findAlreadyExistingValues(
197  $slot,
198  ...$values
199  ));
200  }
201 
205  protected function createVocabulary(
206  SlotIdentifier $slot,
207  string $source
208  ): string {
209  return $this->vocab_repo->create($slot, $source);
210  }
211 
212  protected function addValuesToVocabulary(
213  \DOMXPath $xml_path,
214  string $vocab_id
215  ): void {
216  foreach ($this->extractValuesAndLabels($xml_path) as $value => $label) {
217  $this->vocab_repo->addValueToVocabulary(
218  $vocab_id,
219  $value,
220  $label
221  );
222  }
223  }
224 
225  protected function writeToPath(\DOMElement $path_in_xml, bool $relative = false): PathInterface
226  {
227  $builder = $this->path_factory->custom();
228  foreach ($path_in_xml->childNodes as $step) {
229  if (!($step instanceof \DOMElement)) {
230  continue;
231  }
232  if ($step->nodeName === 'step') {
233  $builder = $builder->withNextStep($step->nodeValue);
234  } elseif ($step->nodeName === 'stepToSuper') {
235  $builder = $builder->withNextStepToSuperElement();
236  }
237  }
238  return $builder->withRelative($relative)->get();
239  }
240 }
writeToPath(\DOMElement $path_in_xml, bool $relative=false)
Definition: Importer.php:225
addValuesToVocabulary(\DOMXPath $xml_path, string $vocab_id)
Definition: Importer.php:212
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
__construct(PathFactory $path_factory, ControlledVocabsRepository $vocab_repo, SlotHandler $slot_handler)
Definition: Importer.php:37
loadXML(string $xml_string)
Returns the xml or errors.
Definition: Importer.php:98
extractValuesAndLabels(\DOMXPath $xml_path)
Yields value => label as strings.
Definition: Importer.php:158
ilErrorHandling $error
Definition: class.ilias.php:69
findAlreadyExistingValues(\DOMXPath $xml_path, SlotIdentifier $slot)
Definition: Importer.php:188
createVocabulary(SlotIdentifier $slot, string $source)
Returns vocab ID.
Definition: Importer.php:205