ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilAssOrderingElementList.php
Go to the documentation of this file.
1<?php
2
25class ilAssOrderingElementList implements Iterator
26{
27 public static $objectInstanceCounter = 0;
29
33
37
41
42 public const IDENTIFIER_TYPE_SOLUTION = 'SolutionIds';
43 public const IDENTIFIER_TYPE_RANDOM = 'RandomIds';
44
48 protected static $identifierRegistry = [
49 self::IDENTIFIER_TYPE_SOLUTION => [],
50 self::IDENTIFIER_TYPE_RANDOM => []
51 ];
52
56 protected $question_id;
57
61 protected $elements = [];
62
67 public function __construct(
68 ?int $question_id = null,
69 array $elements = []
70 ) {
71 $this->objectInstanceId = ++self::$objectInstanceCounter;
72
73 $this->question_id = $question_id;
74 $this->elements = $elements;
75 }
76
80 public function __clone()
81 {
82 $this->objectInstanceId = ++self::$objectInstanceCounter;
83
84 $elements = [];
85
86 foreach ($this as $key => $element) {
87 $elements[$key] = clone $element;
88 }
89
90 $this->elements = $elements;
91 }
92
97 {
98 $that = clone $this;
99 return $that;
100 }
101
105 public function getQuestionId(): ?int
106 {
107 return $this->question_id;
108 }
109
113 public function setQuestionId($question_id): void
114 {
115 $this->question_id = $question_id;
116 }
117
118 public function countElements(): int
119 {
120 return count($this->elements);
121 }
122
123 public function hasElements(): bool
124 {
125 return (bool) $this->countElements();
126 }
127
128 public function isFirstElementPosition($position): bool
129 {
130 return $position == 0;
131 }
132
133 public function isLastElementPosition($position): bool
134 {
135 return $position == ($this->countElements() - 1);
136 }
137
138 public function moveElementByPositions($currentPosition, $targetPosition): void
139 {
140 $movingElement = $this->getElementByPosition($currentPosition);
141 $dodgingElement = $this->getElementByPosition($targetPosition);
142
143 $elementList = new self();
144 $elementList->setQuestionId($this->getQuestionId());
145
146 foreach ($this as $element) {
147 if ($element->getPosition() == $currentPosition) {
148 $elementList->addElement($dodgingElement);
149 continue;
150 }
151
152 if ($element->getPosition() == $targetPosition) {
153 $elementList->addElement($movingElement);
154 continue;
155 }
156
157 $elementList->addElement($element);
158 }
159
160 $dodgingElement->setPosition($currentPosition);
161 $movingElement->setPosition($targetPosition);
162
163 $this->setElements($elementList->getElements());
164 }
165
166 public function removeElement(ilAssOrderingElement $removeElement): void
167 {
168 $elementList = new self();
169 $elementList->setQuestionId($this->getQuestionId());
170
171 $positionCounter = 0;
172
173 foreach ($this as $element) {
174 if ($element->isSameElement($removeElement)) {
175 continue;
176 }
177
178 $element->setPosition($positionCounter++);
179 $elementList->addElement($element);
180 }
181 }
182
186 public function resetElements(): void
187 {
188 $this->elements = [];
189 }
190
194 public function setElements($elements): void
195 {
196 $this->resetElements();
197
198 foreach ($elements as $element) {
199 $this->addElement($element);
200 }
201 }
202
206 public function getElements(): array
207 {
208 return $this->elements;
209 }
210
214 public function getRandomIdentifierIndexedElements(): array
215 {
216 return $this->getIndexedElements(self::IDENTIFIER_TYPE_RANDOM);
217 }
218
222 public function getRandomIdentifierIndex(): array
223 {
224 return array_keys($this->getRandomIdentifierIndexedElements());
225 }
226
231 {
232 return $this->getIndexedElements(self::IDENTIFIER_TYPE_SOLUTION);
233 }
234
238 public function getSolutionIdentifierIndex(): array
239 {
240 return array_keys($this->getSolutionIdentifierIndexedElements());
241 }
242
246 protected function getIndexedElements($identifierType): array
247 {
248 $elements = [];
249
250 foreach ($this as $element) {
251 $elements[$this->fetchIdentifier($element, $identifierType)] = $element;
252 }
253
254 return $elements;
255 }
256
260 public function addElement(ilAssOrderingElement $element): void
261 {
262 if ($this->hasValidIdentifiers($element)) {
263 $this->registerIdentifiers($element);
264 }
265
266 $this->elements[] = $element;
267 }
268
269 public function getElementByPosition(int $position): ?ilAssOrderingElement
270 {
271 if (isset($this->elements[$position])) {
272 return $this->elements[$position];
273 }
274
275 return null;
276 }
277
278 public function elementExistByPosition(int $position): bool
279 {
280 return ($this->getElementByPosition($position) !== null);
281 }
282
283 public function getElementByRandomIdentifier(int $random_identifier): ?ilAssOrderingElement
284 {
285 foreach ($this as $element) {
286 if ($element->getRandomIdentifier() === intval($random_identifier)) {
287 return $element;
288 }
289 }
290
291 return null;
292 }
293
298 public function elementExistByRandomIdentifier($randomIdentifier): bool
299 {
300 return ($this->getElementByRandomIdentifier($randomIdentifier) !== null);
301 }
302
307 public function getElementBySolutionIdentifier($solutionIdentifier): ?ilAssOrderingElement
308 {
309 foreach ($this as $element) {
310 if ($element->getSolutionIdentifier() != $solutionIdentifier) {
311 continue;
312 }
313
314 return $element;
315 }
316 return null;
317 }
318
323 public function elementExistBySolutionIdentifier($solutionIdentifier): bool
324 {
325 return ($this->getElementBySolutionIdentifier($solutionIdentifier) !== null);
326 }
327
331 protected function getRegisteredSolutionIdentifiers(): array
332 {
333 return $this->getRegisteredIdentifiers(self::IDENTIFIER_TYPE_SOLUTION);
334 }
335
339 protected function getRegisteredRandomIdentifiers(): array
340 {
341 return $this->getRegisteredIdentifiers(self::IDENTIFIER_TYPE_RANDOM);
342 }
343
348 protected function getRegisteredIdentifiers($identifierType): array
349 {
350 if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
351 return [];
352 }
353
354 return self::$identifierRegistry[$identifierType][$this->getQuestionId()];
355 }
356
361 protected function hasValidIdentifiers(ilAssOrderingElement $element): bool
362 {
363 $identifier = $this->fetchIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
364
365 if (!$this->isValidIdentifier(self::IDENTIFIER_TYPE_SOLUTION, $identifier)) {
366 return false;
367 }
368
369 $identifier = $this->fetchIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
370
371 if (!$this->isValidIdentifier(self::IDENTIFIER_TYPE_RANDOM, $identifier)) {
372 return false;
373 }
374
375 return true;
376 }
377
382 {
383 //TODO: remove!
384 $this->ensureValidIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
385 $this->ensureValidIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
386 return $element;
387 }
388
393 protected function ensureValidIdentifier(ilAssOrderingElement $element, $identifierType): void
394 {
395 $identifier = $this->fetchIdentifier($element, $identifierType);
396
397 if (!$this->isValidIdentifier($identifierType, $identifier)) {
398 $identifier = $this->buildIdentifier($identifierType);
399 $this->populateIdentifier($element, $identifierType, $identifier);
400 $this->registerIdentifier($element, $identifierType);
401 }
402 }
403
407 protected function registerIdentifiers(ilAssOrderingElement $element): void
408 {
409 $this->registerIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
410 $this->registerIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
411 }
412
418 protected function registerIdentifier(ilAssOrderingElement $element, $identifierType): void
419 {
420 if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
421 self::$identifierRegistry[$identifierType][$this->getQuestionId()] = [];
422 }
423
424 $identifier = $this->fetchIdentifier($element, $identifierType);
425
426 if (!in_array($identifier, self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
427 self::$identifierRegistry[$identifierType][$this->getQuestionId()][] = $identifier;
428 }
429 }
430
437 protected function isIdentifierRegistered(ilAssOrderingElement $element, $identifierType): bool
438 {
439 if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
440 return false;
441 }
442
443 $identifier = $this->fetchIdentifier($element, $identifierType);
444
445 if (!in_array($identifier, self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
446 return false;
447 }
448
449 return true;
450 }
451
455 protected function fetchIdentifier(ilAssOrderingElement $element, string $identifierType): ?int
456 {
457 if ($identifierType == self::IDENTIFIER_TYPE_SOLUTION) {
458 return $element->getSolutionIdentifier();
459 } else {
460 return $element->getRandomIdentifier();
461 }
462
463 $this->throwUnknownIdentifierTypeException($identifierType);
464 }
465
472 protected function populateIdentifier(ilAssOrderingElement $element, $identifierType, $identifier): void
473 {
474 switch ($identifierType) {
475 case self::IDENTIFIER_TYPE_SOLUTION: $element->setSolutionIdentifier($identifier);
476 break;
477 case self::IDENTIFIER_TYPE_RANDOM: $element->setRandomIdentifier($identifier);
478 break;
479 default: $this->throwUnknownIdentifierTypeException($identifierType);
480 }
481 }
482
484 protected function isValidIdentifier($identifierType, $identifier)
485 {
486 switch ($identifierType) {
488 return self::isValidSolutionIdentifier($identifier);
489
491 return self::isValidRandomIdentifier($identifier);
492 }
493
494 $this->throwUnknownIdentifierTypeException($identifierType);
495 }
496
497 protected function buildIdentifier($identifierType): int
498 {
499 switch ($identifierType) {
501 return $this->buildSolutionIdentifier();
502 default:
504 return $this->buildRandomIdentifier();
505 }
506
507 $this->throwUnknownIdentifierTypeException($identifierType);
508 }
509
514 protected function throwUnknownIdentifierTypeException($identifierType): void
515 {
517 "unknown identifier type given (type: $identifierType)"
518 );
519 }
520
525 protected function throwCouldNotBuildRandomIdentifierException($maxTries): void
526 {
528 "could not build random identifier (max tries: $maxTries)"
529 );
530 }
531
536 protected function throwMissingReorderPositionException($randomIdentifier): void
537 {
539 "cannot reorder element due to missing position (random identifier: $randomIdentifier)"
540 );
541 }
542
547 protected function throwUnknownRandomIdentifiersException($randomIdentifiers): void
548 {
550 'cannot reorder element due to one or more unknown random identifiers ' .
551 '(' . implode(', ', $randomIdentifiers) . ')'
552 );
553 }
554
558 protected function getLastSolutionIdentifier(): ?int
559 {
560 $lastSolutionIdentifier = null;
561
562 foreach ($this->getRegisteredSolutionIdentifiers() as $registeredIdentifier) {
563 if ($lastSolutionIdentifier > $registeredIdentifier) {
564 continue;
565 }
566
567 $lastSolutionIdentifier = $registeredIdentifier;
568 }
569
570 return $lastSolutionIdentifier;
571 }
572
576 protected function buildSolutionIdentifier(): ?int
577 {
578 $lastSolutionIdentifier = $this->getLastSolutionIdentifier();
579
580 if ($lastSolutionIdentifier === null) {
581 return 0;
582 }
583
584 $nextSolutionIdentifier = $lastSolutionIdentifier + self::SOLUTION_IDENTIFIER_VALUE_INTERVAL;
585
586 return $nextSolutionIdentifier;
587 }
588
593 protected function buildRandomIdentifier(): int
594 {
595 $usedTriesCounter = 0;
596
597 do {
598 if ($usedTriesCounter >= self::RANDOM_IDENTIFIER_BUILD_MAX_TRIES) {
599 $this->throwCouldNotBuildRandomIdentifierException(self::RANDOM_IDENTIFIER_BUILD_MAX_TRIES);
600 }
601
602 $usedTriesCounter++;
603
606 $randomIdentifier = mt_rand($lowerBound, $upperBound);
607
608 $testElement = new ilAssOrderingElement();
609 $testElement->setRandomIdentifier($randomIdentifier);
610 } while ($this->isIdentifierRegistered($testElement, self::IDENTIFIER_TYPE_RANDOM));
611
612 return $randomIdentifier;
613 }
614
615 public static function isValidSolutionIdentifier($identifier): bool
616 {
617 return is_numeric($identifier)
618 && $identifier == (int) $identifier
619 && (int) $identifier >= 0;
620 }
621
622 public static function isValidRandomIdentifier($identifier): bool
623 {
624 return is_numeric($identifier)
625 && $identifier == (int) $identifier
626 && (int) $identifier >= self::RANDOM_IDENTIFIER_RANGE_LOWER_BOUND
627 && (int) $identifier <= self::RANDOM_IDENTIFIER_RANGE_UPPER_BOUND;
628 }
629
630 public static function isValidPosition($position): bool
631 {
632 return self::isValidSolutionIdentifier($position); // this was the position earlier
633 }
634
635 public static function isValidIndentation($indentation): bool
636 {
637 return self::isValidPosition($indentation); // horizontal position ^^
638 }
639
643 public function distributeNewRandomIdentifiers(): void
644 {
645 foreach ($this as $element) {
646 $element->setRandomIdentifier($this->buildRandomIdentifier());
647 }
648 }
649
653 public function hasSameElementSetByRandomIdentifiers(self $otherList): bool
654 {
655 $numIntersectingElements = count(array_intersect(
656 $otherList->getRandomIdentifierIndex(),
657 $this->getRandomIdentifierIndex()
658 ));
659
660 return $numIntersectingElements == $this->countElements()
661 && $numIntersectingElements == $otherList->countElements();
662 }
663
664 public function getParityTrueElementList(self $otherList): ilAssOrderingElementList
665 {
666 if (!$this->hasSameElementSetByRandomIdentifiers($otherList)) {
667 throw new ilTestQuestionPoolException('cannot compare lists having different element sets');
668 }
669
670 $parityTrueElementList = new self();
671 $parityTrueElementList->setQuestionId($this->getQuestionId());
672
673 foreach ($this as $thisElement) {
674 $otherElement = $otherList->getElementByRandomIdentifier(
675 $thisElement->getRandomIdentifier()
676 );
677
678 if ($otherElement->getPosition() != $thisElement->getPosition()) {
679 continue;
680 }
681
682 if ($otherElement->getIndentation() != $thisElement->getIndentation()) {
683 continue;
684 }
685
686 $parityTrueElementList->addElement($thisElement);
687 }
688
689 return $parityTrueElementList;
690 }
691
696 public function reorderByRandomIdentifiers($randomIdentifiers): void
697 {
698 $positionsMap = array_flip(array_values($randomIdentifiers));
699
700 $orderedElements = [];
701
702 foreach ($this as $element) {
703 if (!isset($positionsMap[$element->getRandomIdentifier()])) {
704 $this->throwMissingReorderPositionException($element->getRandomIdentifier());
705 }
706
707 $position = $positionsMap[$element->getRandomIdentifier()];
708 unset($positionsMap[$element->getRandomIdentifier()]);
709
710 $element->setPosition($position);
711 $orderedElements[$position] = $element;
712 }
713
714 if (count($positionsMap)) {
715 $this->throwUnknownRandomIdentifiersException(array_keys($positionsMap));
716 }
717
718 ksort($orderedElements);
719
720 $this->setElements(array_values($orderedElements));
721 }
722
726 public function resetElementsIndentations(): void
727 {
728 foreach ($this as $element) {
729 $element->setIndentation(0);
730 }
731 }
732
737 public function getDifferenceElementList(self $otherElementList): ilAssOrderingElementList
738 {
739 $differenceRandomIdentifierIndex = $this->getDifferenceRandomIdentifierIndex($otherElementList);
740
741 $differenceElementList = new self();
742 $differenceElementList->setQuestionId($this->getQuestionId());
743
744 foreach ($differenceRandomIdentifierIndex as $randomIdentifier) {
745 $element = $this->getElementByRandomIdentifier($randomIdentifier);
746 $differenceElementList->addElement($element);
747 }
748
749 return $differenceElementList;
750 }
751
756 protected function getDifferenceRandomIdentifierIndex(self $otherElementList): array
757 {
758 $differenceRandomIdentifierIndex = array_diff(
760 $otherElementList->getRandomIdentifierIndex()
761 );
762
763 return $differenceRandomIdentifierIndex;
764 }
765
769 public function completeContentsFromElementList(self $otherList): void
770 {
771 foreach ($this as $thisElement) {
772 if (!$otherList->elementExistByRandomIdentifier($thisElement->getRandomIdentifier())) {
773 continue;
774 }
775
776 $otherElement = $otherList->getElementByRandomIdentifier(
777 $thisElement->getRandomIdentifier()
778 );
779
780 $thisElement->setContent($otherElement->getContent());
781 }
782 }
783
787 public function current(): ilAssOrderingElement
788 {
789 return current($this->elements);
790 }
791
795 public function next(): void
796 {
797 next($this->elements);
798 }
799
803 public function key(): ?int
804 {
805 return key($this->elements);
806 }
807
811 public function valid(): bool
812 {
813 return ($this->key() !== null);
814 }
815
816 public function rewind(): void
817 {
818 reset($this->elements);
819 }
820
825 {
826 $element = new ilAssOrderingElement();
827 $element->setRandomIdentifier(self::FALLBACK_DEFAULT_ELEMENT_RANDOM_IDENTIFIER);
828
829 return $element;
830 }
831
832 //TODO: remove this (it's __construct, actually...)
833 public static function buildInstance(int $question_id, array $elements = []): ilAssOrderingElementList
834 {
835 $elementList = new self();
836
837 $elementList->setQuestionId($question_id);
838 $elementList->setElements($elements);
839
840 return $elementList;
841 }
842
843 public function getHash(): string
844 {
845 $items = [];
846
847 foreach ($this as $element) {
848 $items[] = implode(':', [
849 $element->getSolutionIdentifier(),
850 $element->getRandomIdentifier(),
851 $element->getIndentation()
852 ]);
853 }
854
855 return md5(serialize($items));
856 }
857
861 public function withElements(array $elements): self
862 {
863 $clone = clone $this;
864 $clone->elements = $elements;
865 return $clone;
866 }
867
868 public function withQuestionId(int $question_id): self
869 {
870 $clone = clone $this;
871 $clone->question_id = $question_id;
872 return $clone;
873 }
874}
isIdentifierRegistered(ilAssOrderingElement $element, $identifierType)
fetchIdentifier(ilAssOrderingElement $element, string $identifierType)
registerIdentifiers(ilAssOrderingElement $element)
elementExistBySolutionIdentifier($solutionIdentifier)
getDifferenceElementList(self $otherElementList)
static buildInstance(int $question_id, array $elements=[])
getElementBySolutionIdentifier($solutionIdentifier)
registerIdentifier(ilAssOrderingElement $element, $identifierType)
getDifferenceRandomIdentifierIndex(self $otherElementList)
throwMissingReorderPositionException($randomIdentifier)
populateIdentifier(ilAssOrderingElement $element, $identifierType, $identifier)
getElementByRandomIdentifier(int $random_identifier)
__construct(?int $question_id=null, array $elements=[])
ilAssOrderingElementList constructor.
removeElement(ilAssOrderingElement $removeElement)
isValidIdentifier($identifierType, $identifier)
@noinspection PhpInconsistentReturnPointsInspection
hasValidIdentifiers(ilAssOrderingElement $element)
ensureValidIdentifiers(ilAssOrderingElement $element)
moveElementByPositions($currentPosition, $targetPosition)
addElement(ilAssOrderingElement $element)
__clone()
clone list by additionally cloning the element objects
ensureValidIdentifier(ilAssOrderingElement $element, $identifierType)
throwUnknownRandomIdentifiersException($randomIdentifiers)
resetElementsIndentations()
resets the indentation to level 0 for all elements in list
setRandomIdentifier(int $random_identifier)
setSolutionIdentifier(int $solution_identifier)