ILIAS  release_10 Revision v10.1-43-ga1241a92c2f
Manipulator.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
40 
42 {
48 
49  public function __construct(
50  ScaffoldProviderInterface $scaffold_provider,
51  MarkerFactoryInterface $marker_factory,
52  NavigatorFactoryInterface $navigator_factory,
53  PathFactoryInterface $path_factory,
54  PathUtilitiesFactoryInterface $path_utilities_factory
55  ) {
56  $this->scaffold_provider = $scaffold_provider;
57  $this->marker_factory = $marker_factory;
58  $this->navigator_factory = $navigator_factory;
59  $this->path_factory = $path_factory;
60  $this->path_utilities_factory = $path_utilities_factory;
61  }
62 
66  public function prepareDelete(
67  SetInterface $set,
69  ): SetInterface {
73  $my_set = clone $set;
74  $elements = $this->navigator_factory->navigator($path, $my_set->getRoot())->elementsAtFinalStep();
75  $target_elements = [];
76  foreach ($elements as $element) {
77  if ($element instanceof MarkableInterface) {
78  $target_elements[] = $element;
79  }
80  }
81  $this->markElementsDelete($target_elements);
82  return $my_set;
83  }
84 
88  public function prepareCreateOrUpdate(
89  SetInterface $set,
90  PathInterface $path,
91  string ...$values
92  ): SetInterface {
93  $my_set = clone $set;
94  $elements_to_update = $this->getElementsToUpdate($my_set->getRoot(), $path, ...$values);
95  $remaining_values = array_slice($values, 0, count($values) - count($elements_to_update));
96  $elements_to_create = $this->getElementsToCreate($my_set->getRoot(), $path, ...$remaining_values);
97  $target_elements = array_merge($elements_to_update, $elements_to_create);
98  $this->markElementsCreateOrUpdate($target_elements, $values);
99  return $my_set;
100  }
101 
105  public function prepareForceCreate(
106  SetInterface $set,
107  PathInterface $path,
108  string ...$values
109  ): SetInterface {
110  $my_set = clone $set;
111  $target_elements = $this->getElementsToCreate($my_set->getRoot(), $path, ...$values);
112  $this->markElementsCreateOrUpdate($target_elements, $values);
113  return $my_set;
114  }
115 
116  protected function getElementsToUpdate(
117  ElementInterface $set_root,
118  PathInterface $path,
119  string ...$values
120  ): array {
125  $path_conditions = $this->path_utilities_factory->pathConditionsCollection($path);
126  $path_checker = $this->path_utilities_factory->pathConditionChecker($path_conditions);
127  $target_elements = [];
128  $navigators = [$this->navigator_factory->navigator($path_conditions->getPathWithoutConditions(), $set_root)];
129 
130  if (count($values) <= 0) {
131  return [];
132  }
133 
134  // Search for existing elements to update
135  while (count($navigators) > 0) {
136  $curr_navi = array_shift($navigators)->nextStep();
137  $at_least_one_path_condition_met = $path_checker->atLeastOnePathConditionIsMet(
138  $curr_navi->currentStep(),
139  ...$curr_navi->elements()
140  );
141 
142  // Complete Path: Target elements at end of path found
143  if ($at_least_one_path_condition_met && $curr_navi->hasElements() && !$curr_navi->hasNextStep()) {
144  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
145  $curr_navi->currentStep(),
146  ...$curr_navi->elements()
147  ));
148  // Slice array to select only as many elements as needed
149  $missing_target_count = max(0, count($values) - count($target_elements));
150  $targets = array_slice($roots, 0, $missing_target_count);
151  array_push($target_elements, ...$targets);
152  continue;
153  }
154 
155  // Path Continues: Add navigators with the elements that meet the path conditions as roots
156  if ($at_least_one_path_condition_met && $curr_navi->hasElements() && $curr_navi->hasNextStep()) {
157  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
158  $curr_navi->currentStep(),
159  ...$curr_navi->elements()
160  ));
161  foreach ($roots as $root) {
162  $navigators[] = $this->navigator_factory->navigator(
163  $this->remainingPathOfNavigator($curr_navi->nextStep()),
164  $root
165  );
166  }
167  continue;
168  }
169  }
170 
171  return $target_elements;
172  }
173 
174  protected function getElementsToCreate(
175  ElementInterface $set_root,
176  PathInterface $path,
177  string ...$values
178  ): array {
185  $path_conditions = $this->path_utilities_factory->pathConditionsCollection($path);
186  $path_checker = $this->path_utilities_factory->pathConditionChecker($path_conditions);
187  $target_elements = [];
188  $loose_end_roots = [];
189  $loose_end_paths = [];
190  $navigators = [$this->navigator_factory->navigator($path_conditions->getPathWithoutConditions(), $set_root)];
191 
192  if (count($values) <= 0) {
193  return [];
194  }
195 
196  // Elements to create
197  while (count($navigators) > 0) {
198  $orig_navi = array_shift($navigators);
199  $curr_navi = $orig_navi->nextStep();
200  $at_least_one_path_condition_met = $path_checker->atLeastOnePathConditionIsMet(
201  $curr_navi->currentStep(),
202  ...$curr_navi->elements()
203  );
204 
205  // Path Conditions not met
206  if (!$at_least_one_path_condition_met) {
207  $roots = iterator_to_array($orig_navi->elements());
208  $paths = array_fill(0, count($roots), $this->remainingPathOfNavigator($orig_navi));
209  array_push($loose_end_roots, ...$roots);
210  array_push($loose_end_paths, ...$paths);
211  continue;
212  }
213 
214  // Incomplete Path: At end of path but target elements are missing
215  if ($at_least_one_path_condition_met && !$curr_navi->hasElements() && !$curr_navi->hasNextStep()) {
216  $roots = iterator_to_array($orig_navi->elements());
217  $paths = array_fill(0, count($roots), $this->remainingPathOfNavigator($orig_navi->nextStep()));
218  array_push($loose_end_roots, ...$roots);
219  array_push($loose_end_paths, ...$paths);
220  continue;
221  }
222 
223  // Incomplete Path: Complete the paths as needed
224  if ($at_least_one_path_condition_met && !$curr_navi->hasElements() && $curr_navi->hasNextStep()) {
225  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
226  $curr_navi->currentStep(),
227  ...$curr_navi->elements()
228  ));
229  $paths = array_fill(0, count($roots), $this->remainingPathOfNavigator($curr_navi->nextStep()));
230  array_push($loose_end_roots, ...$roots);
231  array_push($loose_end_paths, ...$paths);
232  continue;
233  }
234 
235  // Path Continues: Add navigators with the elements that meet the path conditions as roots
236  if ($at_least_one_path_condition_met && $curr_navi->hasElements() && $curr_navi->hasNextStep()) {
237  $roots = iterator_to_array($path_checker->getRootsThatMeetPathCondition(
238  $curr_navi->currentStep(),
239  ...$curr_navi->elements()
240  ));
241  foreach ($roots as $root) {
242  $navigators[] = $this->navigator_factory->navigator(
243  $this->remainingPathOfNavigator($curr_navi->nextStep()),
244  $root
245  );
246  }
247  continue;
248  }
249  }
250 
251  // Create the path if an incomplete path was found
252  $missing_target_count = max(0, count($values) - count($target_elements));
253  if (count($loose_end_roots) > 0 && $missing_target_count > 0) {
254  $target_elements[] = $this->insertPathElementsAsScaffolds(
255  array_shift($loose_end_paths),
256  array_shift($loose_end_roots),
257  $path_conditions
258  );
259  }
260 
261  // Move Navigator to path end
262  $navigator = $this->getLastNavigatorWithElements(
263  $path_conditions->getPathWithoutConditions(),
264  $set_root
265  );
266 
267  // Move Navigator backwards to insert point: parent of first non unique element
268  $navigator = $this->moveNavigatorBackwardsToFirstNonUnique($navigator);
269  if ($navigator->hasPreviousStep()) {
270  $navigator = $navigator->previousStep();
271  }
272 
273  // Add targets of loose ends
274  $missing_target_count = max(0, count($values) - count($target_elements));
275  $loose_end_paths = array_slice($loose_end_paths, 0, $missing_target_count);
276  $loose_end_roots = array_slice($loose_end_roots, 0, $missing_target_count);
277  array_push($target_elements, ...$this->createTargetElements($loose_end_roots, $loose_end_paths, $path_conditions));
278 
279  // Add new targets
280  $missing_target_count = max(0, count($values) - count($target_elements));
281  $remaining_path = $this->remainingPathOfNavigator($navigator->nextStep());
282  $root = $navigator->lastElement();
283  $target_paths = array_fill(0, $missing_target_count, $remaining_path);
284  $target_roots = array_fill(0, $missing_target_count, $root);
285  array_push($target_elements, ...$this->createTargetElements($target_roots, $target_paths, $path_conditions));
286 
287  return $target_elements;
288  }
289 
294  {
295  while ($navigator->hasPreviousStep() && $navigator->lastElement()->getDefinition()->unique()) {
296  $navigator = $navigator->previousStep();
297  }
298  return $navigator;
299  }
300 
302  {
303  $builder = $this->path_factory->custom()
304  ->withRelative(true)
305  ->withLeadsToExactlyOneElement(false);
306  while (!is_null($navigator)) {
307  if (is_null($navigator->currentStep())) {
308  $navigator = $navigator->nextStep();
309  continue;
310  }
311  $builder = $builder->withNextStepFromStep($navigator->currentStep(), false);
312  $navigator = $navigator->nextStep();
313  }
314  return $builder->get();
315  }
316 
317  protected function getLastNavigatorWithElements(
318  PathInterface $path,
319  ElementInterface $root
320  ): NavigatorInterface {
321  $navigator = $this->navigator_factory->navigator($path, $root);
322  while ($navigator->hasNextStep() && $navigator->nextStep()->hasElements()) {
323  $navigator = $navigator->nextStep();
324  }
325  return $navigator;
326  }
327 
331  protected function insertPathElementsAsScaffolds(
332  PathInterface $path,
333  ElementInterface $root,
334  PathConditionsCollectionInterface $path_conditions_collection
335  ): ElementInterface {
336  $navigator = $this->navigator_factory->navigator($path, $root);
337  $navigator = $navigator->nextStep();
338  $scaffold = $root;
339  while (!is_null($navigator)) {
340  $scaffold = $this->addAndMarkScaffoldByStep($scaffold, $navigator->currentStep());
341  if (is_null($scaffold)) {
342  throw new ilMDPathException(
343  'Cannot create scaffold at step: ' . $navigator->currentStep()->name() . ', invalid path.'
344  );
345  }
346  $condition_path = $path_conditions_collection->getConditionPathByStepName($navigator->currentStep()->name());
347  if ($condition_path->steps()->valid()) {
348  $this->insertConditionPathElementsAsScaffolds($condition_path, $scaffold);
349  }
350  $navigator = $navigator->nextStep();
351  }
352  return $scaffold;
353  }
354 
361  protected function createTargetElements(
362  array $roots,
363  array $paths,
364  PathConditionsCollectionInterface $path_conditions
365  ): array {
366  $target_elements = [];
367  while (0 < count($paths)) {
368  $target_elements[] = $this->insertPathElementsAsScaffolds(
369  array_shift($paths),
370  array_shift($roots),
371  $path_conditions
372  );
373  }
374  return $target_elements;
375  }
376 
381  PathInterface $condition_path,
382  ElementInterface $root
383  ): void {
384  $navigator = $this->navigator_factory->navigator($condition_path, $root);
385  $navigator = $navigator->nextStep();
386  $scaffold = $root;
387  while (!is_null($navigator)) {
388  $scaffold = $this->addAndMarkScaffoldByStep($scaffold, $navigator->currentStep());
389  if (is_null($scaffold)) {
390  throw new ilMDPathException('Cannot create scaffold, invalid path.');
391  }
392  $navigator = $navigator->nextStep();
393  }
394  }
395 
399  protected function addAndMarkScaffoldByStep(
400  ElementInterface $element,
401  StepInterface $step
402  ): ?ElementInterface {
403  if ($step->name() === StepToken::SUPER) {
404  return $element->getSuperElement();
405  }
406  $scaffold = $element->addScaffoldToSubElements(
407  $this->scaffold_provider,
408  $step->name()
409  );
410  if (!isset($scaffold)) {
411  return null;
412  }
413  $data = '';
414  foreach ($step->filters() as $filter) {
415  if ($filter->type() === FilterType::DATA) {
416  $data = $filter->values()->current();
417  break;
418  }
419  }
420  $scaffold->mark(
421  $this->marker_factory,
422  Action::CREATE_OR_UPDATE,
423  $data
424  );
425  return $scaffold;
426  }
427 
432  protected function markElementsCreateOrUpdate(array $target_elements, array $values): void
433  {
434  array_map(function (ElementInterface $element, ?string $value) {
435  $element->mark($this->marker_factory, Action::CREATE_OR_UPDATE, $value);
436  }, $target_elements, $values);
437  }
438 
439  protected function markElementsDelete(array $target_elements): void
440  {
441  array_map(function (ElementInterface $element) {
442  $element->mark($this->marker_factory, Action::DELETE);
443  }, $target_elements);
444  }
445 }
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)
ScaffoldProviderInterface $scaffold_provider
Definition: Manipulator.php:43
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:30
__construct(ScaffoldProviderInterface $scaffold_provider, MarkerFactoryInterface $marker_factory, NavigatorFactoryInterface $navigator_factory, PathFactoryInterface $path_factory, PathUtilitiesFactoryInterface $path_utilities_factory)
Definition: Manipulator.php:49
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:45
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:47
MarkerFactoryInterface $marker_factory
Definition: Manipulator.php:44
prepareCreateOrUpdate(SetInterface $set, PathInterface $path, string ... $values)
Definition: Manipulator.php:88
insertPathElementsAsScaffolds(PathInterface $path, ElementInterface $root, PathConditionsCollectionInterface $path_conditions_collection)