ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilAssOrderingElementList.php
Go to the documentation of this file.
1 <?php
2 
26 {
27  public static $objectInstanceCounter = 0;
29 
33 
34  public const RANDOM_IDENTIFIER_BUILD_MAX_TRIES = 1000;
36  public const RANDOM_IDENTIFIER_RANGE_UPPER_BOUND = 100000;
37 
41 
42  public const IDENTIFIER_TYPE_SOLUTION = 'SolutionIds';
43  public const IDENTIFIER_TYPE_RANDOM = 'RandomIds';
44 
48  protected static $identifierRegistry = array(
49  self::IDENTIFIER_TYPE_SOLUTION => array(),
50  self::IDENTIFIER_TYPE_RANDOM => array()
51  );
52 
56  protected $question_id;
57 
61  protected $elements = array();
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 = array();
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 = array();
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 
230  public function getSolutionIdentifierIndexedElements(): array
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 = array();
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 
273  public function getElementByPosition($position): ?ilAssOrderingElement
274  {
275  if (isset($this->elements[$position])) {
276  return $this->elements[$position];
277  }
278 
279  return null;
280  }
281 
286  public function elementExistByPosition($position): bool
287  {
288  return ($this->getElementByPosition($position) !== null);
289  }
290 
295  public function getElementByRandomIdentifier($randomIdentifier): ?ilAssOrderingElement
296  {
297  foreach ($this as $element) {
298  if ($element->getRandomIdentifier() === intval($randomIdentifier)) {
299  return $element;
300  }
301  }
302 
303  return null;
304  }
305 
310  public function elementExistByRandomIdentifier($randomIdentifier): bool
311  {
312  return ($this->getElementByRandomIdentifier($randomIdentifier) !== null);
313  }
314 
319  public function getElementBySolutionIdentifier($solutionIdentifier): ?ilAssOrderingElement
320  {
321  foreach ($this as $element) {
322  if ($element->getSolutionIdentifier() != $solutionIdentifier) {
323  continue;
324  }
325 
326  return $element;
327  }
328  return null;
329  }
330 
335  public function elementExistBySolutionIdentifier($solutionIdentifier): bool
336  {
337  return ($this->getElementBySolutionIdentifier($solutionIdentifier) !== null);
338  }
339 
343  protected function getRegisteredSolutionIdentifiers(): array
344  {
345  return $this->getRegisteredIdentifiers(self::IDENTIFIER_TYPE_SOLUTION);
346  }
347 
351  protected function getRegisteredRandomIdentifiers(): array
352  {
353  return $this->getRegisteredIdentifiers(self::IDENTIFIER_TYPE_RANDOM);
354  }
355 
360  protected function getRegisteredIdentifiers($identifierType): array
361  {
362  if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
363  return array();
364  }
365 
366  return self::$identifierRegistry[$identifierType][$this->getQuestionId()];
367  }
368 
373  protected function hasValidIdentifiers(ilAssOrderingElement $element): bool
374  {
375  $identifier = $this->fetchIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
376 
377  if (!$this->isValidIdentifier(self::IDENTIFIER_TYPE_SOLUTION, $identifier)) {
378  return false;
379  }
380 
381  $identifier = $this->fetchIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
382 
383  if (!$this->isValidIdentifier(self::IDENTIFIER_TYPE_RANDOM, $identifier)) {
384  return false;
385  }
386 
387  return true;
388  }
389 
394  {
395  //TODO: remove!
396  $this->ensureValidIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
397  $this->ensureValidIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
398  return $element;
399  }
400 
405  protected function ensureValidIdentifier(ilAssOrderingElement $element, $identifierType): void
406  {
407  $identifier = $this->fetchIdentifier($element, $identifierType);
408 
409  if (!$this->isValidIdentifier($identifierType, $identifier)) {
410  $identifier = $this->buildIdentifier($identifierType);
411  $this->populateIdentifier($element, $identifierType, $identifier);
412  $this->registerIdentifier($element, $identifierType);
413  }
414  }
415 
419  protected function registerIdentifiers(ilAssOrderingElement $element): void
420  {
421  $this->registerIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
422  $this->registerIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
423  }
424 
430  protected function registerIdentifier(ilAssOrderingElement $element, $identifierType): void
431  {
432  if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
433  self::$identifierRegistry[$identifierType][$this->getQuestionId()] = array();
434  }
435 
436  $identifier = $this->fetchIdentifier($element, $identifierType);
437 
438  if (!in_array($identifier, self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
439  self::$identifierRegistry[$identifierType][$this->getQuestionId()][] = $identifier;
440  }
441  }
442 
449  protected function isIdentifierRegistered(ilAssOrderingElement $element, $identifierType): bool
450  {
451  if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
452  return false;
453  }
454 
455  $identifier = $this->fetchIdentifier($element, $identifierType);
456 
457  if (!in_array($identifier, self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
458  return false;
459  }
460 
461  return true;
462  }
463 
467  protected function fetchIdentifier(ilAssOrderingElement $element, string $identifierType): ?int
468  {
469  if ($identifierType == self::IDENTIFIER_TYPE_SOLUTION) {
470  return $element->getSolutionIdentifier();
471  } else {
472  return $element->getRandomIdentifier();
473  }
474 
475  $this->throwUnknownIdentifierTypeException($identifierType);
476  }
477 
484  protected function populateIdentifier(ilAssOrderingElement $element, $identifierType, $identifier): void
485  {
486  switch ($identifierType) {
487  case self::IDENTIFIER_TYPE_SOLUTION: $element->setSolutionIdentifier($identifier);
488  break;
489  case self::IDENTIFIER_TYPE_RANDOM: $element->setRandomIdentifier($identifier);
490  break;
491  default: $this->throwUnknownIdentifierTypeException($identifierType);
492  }
493  }
494 
496  protected function isValidIdentifier($identifierType, $identifier)
497  {
498  switch ($identifierType) {
499  case self::IDENTIFIER_TYPE_SOLUTION:
500  return self::isValidSolutionIdentifier($identifier);
501 
502  case self::IDENTIFIER_TYPE_RANDOM:
503  return self::isValidRandomIdentifier($identifier);
504  }
505 
506  $this->throwUnknownIdentifierTypeException($identifierType);
507  }
508 
509  protected function buildIdentifier($identifierType): int
510  {
511  switch ($identifierType) {
512  case self::IDENTIFIER_TYPE_SOLUTION:
513  return $this->buildSolutionIdentifier();
514  default:
515  case self::IDENTIFIER_TYPE_RANDOM:
516  return $this->buildRandomIdentifier();
517  }
518 
519  $this->throwUnknownIdentifierTypeException($identifierType);
520  }
521 
526  protected function throwUnknownIdentifierTypeException($identifierType): void
527  {
528  throw new ilTestQuestionPoolException(
529  "unknown identifier type given (type: $identifierType)"
530  );
531  }
532 
537  protected function throwCouldNotBuildRandomIdentifierException($maxTries): void
538  {
539  throw new ilTestQuestionPoolException(
540  "could not build random identifier (max tries: $maxTries)"
541  );
542  }
543 
548  protected function throwMissingReorderPositionException($randomIdentifier): void
549  {
550  throw new ilTestQuestionPoolException(
551  "cannot reorder element due to missing position (random identifier: $randomIdentifier)"
552  );
553  }
554 
559  protected function throwUnknownRandomIdentifiersException($randomIdentifiers): void
560  {
561  throw new ilTestQuestionPoolException(
562  'cannot reorder element due to one or more unknown random identifiers ' .
563  '(' . implode(', ', $randomIdentifiers) . ')'
564  );
565  }
566 
570  protected function getLastSolutionIdentifier(): ?int
571  {
572  $lastSolutionIdentifier = null;
573 
574  foreach ($this->getRegisteredSolutionIdentifiers() as $registeredIdentifier) {
575  if ($lastSolutionIdentifier > $registeredIdentifier) {
576  continue;
577  }
578 
579  $lastSolutionIdentifier = $registeredIdentifier;
580  }
581 
582  return $lastSolutionIdentifier;
583  }
584 
588  protected function buildSolutionIdentifier(): ?int
589  {
590  $lastSolutionIdentifier = $this->getLastSolutionIdentifier();
591 
592  if ($lastSolutionIdentifier === null) {
593  return 0;
594  }
595 
596  $nextSolutionIdentifier = $lastSolutionIdentifier + self::SOLUTION_IDENTIFIER_VALUE_INTERVAL;
597 
598  return $nextSolutionIdentifier;
599  }
600 
605  protected function buildRandomIdentifier(): int
606  {
607  $usedTriesCounter = 0;
608 
609  do {
610  if ($usedTriesCounter >= self::RANDOM_IDENTIFIER_BUILD_MAX_TRIES) {
611  $this->throwCouldNotBuildRandomIdentifierException(self::RANDOM_IDENTIFIER_BUILD_MAX_TRIES);
612  }
613 
614  $usedTriesCounter++;
615 
616  $lowerBound = self::RANDOM_IDENTIFIER_RANGE_LOWER_BOUND;
617  $upperBound = self::RANDOM_IDENTIFIER_RANGE_UPPER_BOUND;
618  $randomIdentifier = mt_rand($lowerBound, $upperBound);
619 
620  $testElement = new ilAssOrderingElement();
621  $testElement->setRandomIdentifier($randomIdentifier);
622  } while ($this->isIdentifierRegistered($testElement, self::IDENTIFIER_TYPE_RANDOM));
623 
624  return $randomIdentifier;
625  }
626 
627  public static function isValidSolutionIdentifier($identifier): bool
628  {
629  return is_numeric($identifier)
630  && $identifier == (int) $identifier
631  && (int) $identifier >= 0;
632  }
633 
634  public static function isValidRandomIdentifier($identifier): bool
635  {
636  return is_numeric($identifier)
637  && $identifier == (int) $identifier
638  && (int) $identifier >= self::RANDOM_IDENTIFIER_RANGE_LOWER_BOUND
639  && (int) $identifier <= self::RANDOM_IDENTIFIER_RANGE_UPPER_BOUND;
640  }
641 
642  public static function isValidPosition($position): bool
643  {
644  return self::isValidSolutionIdentifier($position); // this was the position earlier
645  }
646 
647  public static function isValidIndentation($indentation): bool
648  {
649  return self::isValidPosition($indentation); // horizontal position ^^
650  }
651 
655  public function distributeNewRandomIdentifiers(): void
656  {
657  foreach ($this as $element) {
658  $element->setRandomIdentifier($this->buildRandomIdentifier());
659  }
660  }
661 
665  public function hasSameElementSetByRandomIdentifiers(self $otherList): bool
666  {
667  $numIntersectingElements = count(array_intersect(
668  $otherList->getRandomIdentifierIndex(),
669  $this->getRandomIdentifierIndex()
670  ));
671 
672  return $numIntersectingElements == $this->countElements()
673  && $numIntersectingElements == $otherList->countElements();
674  }
675 
676  public function getParityTrueElementList(self $otherList): ilAssOrderingElementList
677  {
678  if (!$this->hasSameElementSetByRandomIdentifiers($otherList)) {
679  throw new ilTestQuestionPoolException('cannot compare lists having different element sets');
680  }
681 
682  $parityTrueElementList = new self();
683  $parityTrueElementList->setQuestionId($this->getQuestionId());
684 
685  foreach ($this as $thisElement) {
686  $otherElement = $otherList->getElementByRandomIdentifier(
687  $thisElement->getRandomIdentifier()
688  );
689 
690  if ($otherElement->getPosition() != $thisElement->getPosition()) {
691  continue;
692  }
693 
694  if ($otherElement->getIndentation() != $thisElement->getIndentation()) {
695  continue;
696  }
697 
698  $parityTrueElementList->addElement($thisElement);
699  }
700 
701  return $parityTrueElementList;
702  }
703 
708  public function reorderByRandomIdentifiers($randomIdentifiers): void
709  {
710  $positionsMap = array_flip(array_values($randomIdentifiers));
711 
712  $orderedElements = array();
713 
714  foreach ($this as $element) {
715  if (!isset($positionsMap[$element->getRandomIdentifier()])) {
716  $this->throwMissingReorderPositionException($element->getRandomIdentifier());
717  }
718 
719  $position = $positionsMap[$element->getRandomIdentifier()];
720  unset($positionsMap[$element->getRandomIdentifier()]);
721 
722  $element->setPosition($position);
723  $orderedElements[$position] = $element;
724  }
725 
726  if (count($positionsMap)) {
727  $this->throwUnknownRandomIdentifiersException(array_keys($positionsMap));
728  }
729 
730  ksort($orderedElements);
731 
732  $this->setElements(array_values($orderedElements));
733  }
734 
738  public function resetElementsIndentations(): void
739  {
740  foreach ($this as $element) {
741  $element->setIndentation(0);
742  }
743  }
744 
749  public function getDifferenceElementList(self $otherElementList): ilAssOrderingElementList
750  {
751  $differenceRandomIdentifierIndex = $this->getDifferenceRandomIdentifierIndex($otherElementList);
752 
753  $differenceElementList = new self();
754  $differenceElementList->setQuestionId($this->getQuestionId());
755 
756  foreach ($differenceRandomIdentifierIndex as $randomIdentifier) {
757  $element = $this->getElementByRandomIdentifier($randomIdentifier);
758  $differenceElementList->addElement($element);
759  }
760 
761  return $differenceElementList;
762  }
763 
768  protected function getDifferenceRandomIdentifierIndex(self $otherElementList): array
769  {
770  $differenceRandomIdentifierIndex = array_diff(
771  $this->getRandomIdentifierIndex(),
772  $otherElementList->getRandomIdentifierIndex()
773  );
774 
775  return $differenceRandomIdentifierIndex;
776  }
777 
781  public function completeContentsFromElementList(self $otherList): void
782  {
783  foreach ($this as $thisElement) {
784  if (!$otherList->elementExistByRandomIdentifier($thisElement->getRandomIdentifier())) {
785  continue;
786  }
787 
788  $otherElement = $otherList->getElementByRandomIdentifier(
789  $thisElement->getRandomIdentifier()
790  );
791 
792  $thisElement->setContent($otherElement->getContent());
793  }
794  }
795 
799  public function current(): ilAssOrderingElement
800  {
801  return current($this->elements);
802  }
803 
807  public function next()
808  {
809  return next($this->elements);
810  }
811 
815  public function key()
816  {
817  return key($this->elements);
818  }
819 
823  public function valid(): bool
824  {
825  return ($this->key() !== null);
826  }
827 
831  public function rewind()
832  {
833  return reset($this->elements);
834  }
835 
840  {
841  $element = new ilAssOrderingElement();
842  $element->setRandomIdentifier(self::FALLBACK_DEFAULT_ELEMENT_RANDOM_IDENTIFIER);
843 
844  return $element;
845  }
846 
847  //TODO: remove this (it's __construct, actually...)
848  public static function buildInstance(int $question_id, array $elements = []): ilAssOrderingElementList
849  {
850  $elementList = new self();
851 
852  $elementList->setQuestionId($question_id);
853  $elementList->setElements($elements);
854 
855  return $elementList;
856  }
857 
858  public function getHash(): string
859  {
860  $items = array();
861 
862  foreach ($this as $element) {
863  $items[] = implode(':', array(
864  $element->getSolutionIdentifier(),
865  $element->getRandomIdentifier(),
866  $element->getIndentation()
867  ));
868  }
869 
870  return md5(serialize($items));
871  }
872 
876  public function withElements(array $elements): self
877  {
878  $clone = clone $this;
879  $clone->elements = $elements;
880  return $clone;
881  }
882 
883  public function withQuestionId(int $question_id): self
884  {
885  $clone = clone $this;
886  $clone->question_id = $question_id;
887  return $clone;
888  }
889 }
__clone()
clone list by additionally cloning the element objects
fetchIdentifier(ilAssOrderingElement $element, string $identifierType)
registerIdentifiers(ilAssOrderingElement $element)
elementExistBySolutionIdentifier($solutionIdentifier)
setRandomIdentifier(int $random_identifier)
isValidIdentifier($identifierType, $identifier)
PhpInconsistentReturnPointsInspection
populateIdentifier(ilAssOrderingElement $element, $identifierType, $identifier)
removeElement(ilAssOrderingElement $removeElement)
moveElementByPositions($currentPosition, $targetPosition)
static buildInstance(int $question_id, array $elements=[])
throwMissingReorderPositionException($randomIdentifier)
throwUnknownRandomIdentifiersException($randomIdentifiers)
getElementBySolutionIdentifier($solutionIdentifier)
ensureValidIdentifier(ilAssOrderingElement $element, $identifierType)
addElement(ilAssOrderingElement $element)
getDifferenceRandomIdentifierIndex(self $otherElementList)
string $key
Consumer key/client ID value.
Definition: System.php:193
isIdentifierRegistered(ilAssOrderingElement $element, $identifierType)
resetElementsIndentations()
resets the indentation to level 0 for all elements in list
setSolutionIdentifier(int $solution_identifier)
__construct(int $question_id=null, array $elements=[])
ilAssOrderingElementList constructor.
hasValidIdentifiers(ilAssOrderingElement $element)
ensureValidIdentifiers(ilAssOrderingElement $element)
registerIdentifier(ilAssOrderingElement $element, $identifierType)
getDifferenceElementList(self $otherElementList)