ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
Manipulator.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
40 
42 
44 {
50 
51  public function __construct(
52  RepositoryInterface $repository,
53  MarkerFactoryInterface $marker_factory,
54  NavigatorFactoryInterface $navigator_factory,
55  PathFactoryInterface $path_factory,
56  PathUtilitiesFactoryInterface $path_utilities_factory
57  ) {
58  $this->repository = $repository;
59  $this->marker_factory = $marker_factory;
60  $this->navigator_factory = $navigator_factory;
61  $this->path_factory = $path_factory;
62  $this->path_utilities_factory = $path_utilities_factory;
63  }
64 
68  public function prepareDelete(
69  SetInterface $set,
71  ): SetInterface {
75  $my_set = clone $set;
76  $elements = $this->navigator_factory->navigator($path, $my_set->getRoot())->elementsAtFinalStep();
77  $target_elements = [];
78  foreach ($elements as $element) {
79  if ($element instanceof MarkableInterface) {
80  $target_elements[] = $element;
81  }
82  }
83  $this->markElementsDelete($target_elements);
84  return $my_set;
85  }
86 
87  public function execute(SetInterface $set): void
88  {
89  $this->repository->manipulateMD($set);
90  }
91 
95  public function prepareCreateOrUpdate(
96  SetInterface $set,
97  PathInterface $path,
98  string ...$values
99  ): SetInterface {
100  $my_set = clone $set;
101  $elements_to_update = $this->getElementsToUpdate($my_set->getRoot(), $path, ...$values);
102  $remaining_values = array_slice($values, 0, count($values) - count($elements_to_update));
103  $elements_to_create = $this->getElementsToCreate($my_set->getRoot(), $path, ...$remaining_values);
104  $target_elements = array_merge($elements_to_update, $elements_to_create);
105  $this->markElementsCreateOrUpdate($target_elements, $values);
106  return $my_set;
107  }
108 
112  public function prepareForceCreate(
113  SetInterface $set,
114  PathInterface $path,
115  string ...$values
116  ): SetInterface {
117  $my_set = clone $set;
118  $target_elements = $this->getElementsToCreate($my_set->getRoot(), $path, ...$values);
119  $this->markElementsCreateOrUpdate($target_elements, $values);
120  return $my_set;
121  }
122 
123  protected function getElementsToUpdate(
124  ElementInterface $set_root,
125  PathInterface $path,
126  string ...$values
127  ): array {
132  $path_conditions = $this->path_utilities_factory->pathConditionsCollection($path);
133  $path_checker = $this->path_utilities_factory->pathConditionChecker($path_conditions);
134  $target_elements = [];
135  $navigators = [$this->navigator_factory->navigator($path_conditions->getPathWithoutConditions(), $set_root)];
136 
137  if (count($values) <= 0) {
138  return [];
139  }
140 
141  // Search for existing elements to update
142  while (count($navigators) > 0) {
143  $curr_navi = array_shift($navigators)->nextStep();
144  $at_least_one_path_condition_met = $path_checker->atLeastOnePathConditionIsMet(
145  $curr_navi->currentStep(),
146  ...$curr_navi->elements()
147  );
148 
149  // Complete Path: Target elements at end of path found
150  if ($at_least_one_path_condition_met && $curr_navi->hasElements() && !$curr_navi->hasNextStep()) {
151  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
152  $curr_navi->currentStep(),
153  ...$curr_navi->elements()
154  ));
155  // Slice array to select only as many elements as needed
156  $missing_target_count = max(0, count($values) - count($target_elements));
157  $targets = array_slice($roots, 0, $missing_target_count);
158  array_push($target_elements, ...$targets);
159  continue;
160  }
161 
162  // Path Continues: Add navigators with the elements that meet the path conditions as roots
163  if ($at_least_one_path_condition_met && $curr_navi->hasElements() && $curr_navi->hasNextStep()) {
164  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
165  $curr_navi->currentStep(),
166  ...$curr_navi->elements()
167  ));
168  foreach ($roots as $root) {
169  $navigators[] = $this->navigator_factory->navigator(
170  $this->remainingPathOfNavigator($curr_navi->nextStep()),
171  $root
172  );
173  }
174  continue;
175  }
176  }
177 
178  return $target_elements;
179  }
180 
181  protected function getElementsToCreate(
182  ElementInterface $set_root,
183  PathInterface $path,
184  string ...$values
185  ): array {
192  $path_conditions = $this->path_utilities_factory->pathConditionsCollection($path);
193  $path_checker = $this->path_utilities_factory->pathConditionChecker($path_conditions);
194  $target_elements = [];
195  $loose_end_roots = [];
196  $loose_end_paths = [];
197  $navigators = [$this->navigator_factory->navigator($path_conditions->getPathWithoutConditions(), $set_root)];
198 
199  if (count($values) <= 0) {
200  return [];
201  }
202 
203  // Elements to create
204  while (count($navigators) > 0) {
205  $orig_navi = array_shift($navigators);
206  $curr_navi = $orig_navi->nextStep();
207  $at_least_one_path_condition_met = $path_checker->atLeastOnePathConditionIsMet(
208  $curr_navi->currentStep(),
209  ...$curr_navi->elements()
210  );
211 
212  // Path Conditions not met
213  if (!$at_least_one_path_condition_met) {
214  $roots = iterator_to_array($orig_navi->elements());
215  $paths = array_fill(0, count($roots), $this->remainingPathOfNavigator($orig_navi));
216  array_push($loose_end_roots, ...$roots);
217  array_push($loose_end_paths, ...$paths);
218  continue;
219  }
220 
221  // Incomplete Path: At end of path but target elements are missing
222  if ($at_least_one_path_condition_met && !$curr_navi->hasElements() && !$curr_navi->hasNextStep()) {
223  $roots = iterator_to_array($orig_navi->elements());
224  $paths = array_fill(0, count($roots), $this->remainingPathOfNavigator($orig_navi->nextStep()));
225  array_push($loose_end_roots, ...$roots);
226  array_push($loose_end_paths, ...$paths);
227  continue;
228  }
229 
230  // Incomplete Path: Complete the paths as needed
231  if ($at_least_one_path_condition_met && !$curr_navi->hasElements() && $curr_navi->hasNextStep()) {
232  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
233  $curr_navi->currentStep(),
234  ...$curr_navi->elements()
235  ));
236  $paths = array_fill(0, count($roots), $this->remainingPathOfNavigator($curr_navi->nextStep()));
237  array_push($loose_end_roots, ...$roots);
238  array_push($loose_end_paths, ...$paths);
239  continue;
240  }
241 
242  // Path Continues: Add navigators with the elements that meet the path conditions as roots
243  if ($at_least_one_path_condition_met && $curr_navi->hasElements() && $curr_navi->hasNextStep()) {
244  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
245  $curr_navi->currentStep(),
246  ...$curr_navi->elements()
247  ));
248  foreach ($roots as $root) {
249  $navigators[] = $this->navigator_factory->navigator(
250  $this->remainingPathOfNavigator($curr_navi->nextStep()),
251  $root
252  );
253  }
254  continue;
255  }
256  }
257 
258  // Create the path if an incomplete path was found
259  $missing_target_count = max(0, count($values) - count($target_elements));
260  if (count($loose_end_roots) > 0 && $missing_target_count > 0) {
261  $target_elements[] = $this->insertPathElementsAsScaffolds(
262  array_shift($loose_end_paths),
263  array_shift($loose_end_roots),
264  $path_conditions
265  );
266  }
267 
268  // Move Navigator to path end
269  $navigator = $this->getLastNavigatorWithElements(
270  $path_conditions->getPathWithoutConditions(),
271  $set_root
272  );
273 
274  // Move Navigator backwards to insert point: parent of first non unique element
275  $navigator = $this->moveNavigatorBackwardsToFirstNonUnique($navigator);
276  if ($navigator->hasPreviousStep()) {
277  $navigator = $navigator->previousStep();
278  }
279 
280  // Add targets of loose ends
281  $missing_target_count = max(0, count($values) - count($target_elements));
282  $loose_end_paths = array_slice($loose_end_paths, 0, $missing_target_count);
283  $loose_end_roots = array_slice($loose_end_roots, 0, $missing_target_count);
284  array_push($target_elements, ...$this->createTargetElements($loose_end_roots, $loose_end_paths, $path_conditions));
285 
286  // Add new targets
287  $missing_target_count = max(0, count($values) - count($target_elements));
288  $remaining_path = $this->remainingPathOfNavigator($navigator->nextStep());
289  $root = $navigator->lastElement();
290  $target_paths = array_fill(0, $missing_target_count, $remaining_path);
291  $target_roots = array_fill(0, $missing_target_count, $root);
292  array_push($target_elements, ...$this->createTargetElements($target_roots, $target_paths, $path_conditions));
293 
294  return $target_elements;
295  }
296 
301  {
302  while ($navigator->hasPreviousStep() && $navigator->lastElement()->getDefinition()->unique()) {
303  $navigator = $navigator->previousStep();
304  }
305  return $navigator;
306  }
307 
309  {
310  $builder = $this->path_factory->custom()
311  ->withRelative(true)
312  ->withLeadsToExactlyOneElement(false);
313  while (!is_null($navigator)) {
314  if (is_null($navigator->currentStep())) {
315  $navigator = $navigator->nextStep();
316  continue;
317  }
318  $builder = $builder->withNextStepFromStep($navigator->currentStep(), false);
319  $navigator = $navigator->nextStep();
320  }
321  return $builder->get();
322  }
323 
324  protected function getLastNavigatorWithElements(
325  PathInterface $path,
326  ElementInterface $root
327  ): NavigatorInterface {
328  $navigator = $this->navigator_factory->navigator($path, $root);
329  while ($navigator->hasNextStep() && $navigator->nextStep()->hasElements()) {
330  $navigator = $navigator->nextStep();
331  }
332  return $navigator;
333  }
334 
338  protected function insertPathElementsAsScaffolds(
339  PathInterface $path,
340  ElementInterface $root,
341  PathConditionsCollectionInterface $path_conditions_collection
342  ): ElementInterface {
343  $navigator = $this->navigator_factory->navigator($path, $root);
344  $navigator = $navigator->nextStep();
345  $scaffold = $root;
346  while (!is_null($navigator)) {
347  $scaffold = $this->addAndMarkScaffoldByStep($scaffold, $navigator->currentStep());
348  if (is_null($scaffold)) {
349  throw new ilMDPathException(
350  'Cannot create scaffold at step: ' . $navigator->currentStep()->name() . ', invalid path.'
351  );
352  }
353  $condition_path = $path_conditions_collection->getConditionPathByStepName($navigator->currentStep()->name());
354  if ($condition_path->steps()->valid()) {
355  $this->insertConditionPathElementsAsScaffolds($condition_path, $scaffold);
356  }
357  $navigator = $navigator->nextStep();
358  }
359  return $scaffold;
360  }
361 
368  protected function createTargetElements(
369  array $roots,
370  array $paths,
371  PathConditionsCollectionInterface $path_conditions
372  ): array {
373  $target_elements = [];
374  while (0 < count($paths)) {
375  $target_elements[] = $this->insertPathElementsAsScaffolds(
376  array_shift($paths),
377  array_shift($roots),
378  $path_conditions
379  );
380  }
381  return $target_elements;
382  }
383 
388  PathInterface $condition_path,
389  ElementInterface $root
390  ): void {
391  $navigator = $this->navigator_factory->navigator($condition_path, $root);
392  $navigator = $navigator->nextStep();
393  $scaffold = $root;
394  while (!is_null($navigator)) {
395  $scaffold = $this->addAndMarkScaffoldByStep($scaffold, $navigator->currentStep());
396  if (is_null($scaffold)) {
397  throw new ilMDPathException('Cannot create scaffold, invalid path.');
398  }
399  $navigator = $navigator->nextStep();
400  }
401  }
402 
406  protected function addAndMarkScaffoldByStep(
407  ElementInterface $element,
408  StepInterface $step
409  ): ?ElementInterface {
410  if ($step->name() === StepToken::SUPER) {
411  return $element->getSuperElement();
412  }
413  $scaffold = $element->addScaffoldToSubElements(
414  $this->repository->scaffolds(),
415  $step->name()
416  );
417  if (!isset($scaffold)) {
418  return null;
419  }
420  $data = '';
421  foreach ($step->filters() as $filter) {
422  if ($filter->type() === FilterType::DATA) {
423  $data = $filter->values()->current();
424  break;
425  }
426  }
427  $scaffold->mark(
428  $this->marker_factory,
429  Action::CREATE_OR_UPDATE,
430  $data
431  );
432  return $scaffold;
433  }
434 
439  protected function markElementsCreateOrUpdate(array $target_elements, array $values): void
440  {
441  array_map(function (ElementInterface $element, ?string $value) {
442  $element->mark($this->marker_factory, Action::CREATE_OR_UPDATE, $value);
443  }, $target_elements, $values);
444  }
445 
446  protected function markElementsDelete(array $target_elements): void
447  {
448  array_map(function (ElementInterface $element) {
449  $element->mark($this->marker_factory, Action::DELETE);
450  }, $target_elements);
451  }
452 }
lastElement()
Returns only the first element at the current step, if one exists.
moveNavigatorBackwardsToFirstNonUnique(NavigatorInterface $navigator)
nextStep()
Returns null if there is no next step.
prepareDelete(SetInterface $set, PathInterface $path)
markElementsDelete(array $target_elements)
FilterType
Values should always be all lowercase.
Definition: FilterType.php:26
filters()
Filters restrict the elements a step leads to.
remainingPathOfNavigator(NavigatorInterface $navigator)
mark(MarkerFactoryInterface $factory, Action $action, string $data_value='')
Leaves a trail of markers from this element up to the root element, or up to the first already marked...
insertConditionPathElementsAsScaffolds(PathInterface $condition_path, ElementInterface $root)
$path
Definition: ltiservices.php:32
__construct(RepositoryInterface $repository, MarkerFactoryInterface $marker_factory, NavigatorFactoryInterface $navigator_factory, PathFactoryInterface $path_factory, PathUtilitiesFactoryInterface $path_utilities_factory)
Definition: Manipulator.php:51
getLastNavigatorWithElements(PathInterface $path, ElementInterface $root)
addScaffoldToSubElements(ScaffoldProviderInterface $scaffold_provider, string $name)
If possible, adds a scaffold with the given name to this element&#39;s sub-elements, and returns it...
markElementsCreateOrUpdate(array $target_elements, array $values)
createTargetElements(array $roots, array $paths, PathConditionsCollectionInterface $path_conditions)
name()
Steps are identified by the names of LOM elements, or a token to specify a step to the super-element...
StepToken
The string representation of these tokens must not occur as names of metadata elements.
Definition: StepToken.php:27
NavigatorFactoryInterface $navigator_factory
Definition: Manipulator.php:47
prepareForceCreate(SetInterface $set, PathInterface $path, string ... $values)
addAndMarkScaffoldByStep(ElementInterface $element, StepInterface $step)
also returns the added scaffold, if valid
PathUtilitiesFactoryInterface $path_utilities_factory
Definition: Manipulator.php:49
MarkerFactoryInterface $marker_factory
Definition: Manipulator.php:46
prepareCreateOrUpdate(SetInterface $set, PathInterface $path, string ... $values)
Definition: Manipulator.php:95
insertPathElementsAsScaffolds(PathInterface $path, ElementInterface $root, PathConditionsCollectionInterface $path_conditions_collection)