1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssOrderingElement.php';
5
6/**
7 * @author        Björn Heyser <bheyser@databay.de>
8 * @version        $Id$
9 *
10 * @package        Modules/Test(QuestionPool)
11 */
12class ilAssOrderingElementList implements Iterator
13{
14    public static $objectInstanceCounter = 0;
15    public $objectInstanceId;
16
17    const SOLUTION_IDENTIFIER_BUILD_MAX_TRIES = 1000;
18    const SOLUTION_IDENTIFIER_VALUE_INTERVAL = 1;
19    const SOLUTION_IDENTIFIER_START_VALUE = 0;
20
21    const RANDOM_IDENTIFIER_BUILD_MAX_TRIES = 1000;
22    const RANDOM_IDENTIFIER_RANGE_LOWER_BOUND = 1;
23    const RANDOM_IDENTIFIER_RANGE_UPPER_BOUND = 100000;
24
25    const FALLBACK_DEFAULT_ELEMENT_RANDOM_IDENTIFIER = 0;
26    const JS_ADDED_ELEMENTS_RANDOM_IDENTIFIER_START_VALUE = -1;
27    const JS_ADDED_ELEMENTS_RANDOM_IDENTIFIER_VALUE_INTERVAL = -1;
28
29    const IDENTIFIER_TYPE_SOLUTION = 'SolutionIds';
30    const IDENTIFIER_TYPE_RANDOM = 'RandomIds';
31
32    /**
33     * @var array[integer]
34     */
35    protected static $identifierRegistry = array(
36        self::IDENTIFIER_TYPE_SOLUTION => array(),
37        self::IDENTIFIER_TYPE_RANDOM => array()
38    );
39
40    /**
41     * @var integer
42     */
43    protected $questionId;
44
45    /**
46     * @var array
47     */
48    protected $elements;
49
50    /**
51     * ilAssOrderingElementList constructor.
52     */
53    public function __construct()
54    {
55        $this->objectInstanceId = ++self::$objectInstanceCounter;
56
57        $this->questionId = null;
58        $this->resetElements();
59    }
60
61    /**
62     * clone list by additionally cloning the element objects
63     */
64    public function __clone()
65    {
66        $this->objectInstanceId = ++self::$objectInstanceCounter;
67
68        $elements = array();
69
70        foreach ($this as $key => $element) {
71            $elements[$key] = clone $element;
72        }
73
74        $this->elements = $elements;
75    }
76
77    /**
78     * @return ilAssOrderingElementList
79     */
80    public function getClone()
81    {
82        $that = clone $this;
83        return $that;
84    }
85
86    /**
87     * @return integer
88     */
89    public function getQuestionId()
90    {
91        return $this->questionId;
92    }
93
94    /**
95     * @param integer $questionId
96     */
97    public function setQuestionId($questionId)
98    {
99        $this->questionId = $questionId;
100    }
101
102    /**
103     * load elements from database
104     */
105    public function loadFromDb()
106    {
107        global $DIC; /* @var ILIAS\DI\Container $DIC */
108        $ilDB = $DIC['ilDB'];
109
110        $result = $ilDB->queryF(
111            "SELECT * FROM qpl_a_ordering WHERE question_fi = %s ORDER BY position ASC",
112            array('integer'),
113            array($this->getQuestionId())
114        );
115
116        while ($row = $ilDB->fetchAssoc($result)) {
117            $element = new ilAssOrderingElement();
118
119            $element->setRandomIdentifier($row['random_id']);
120            $element->setSolutionIdentifier($row['solution_key']);
121
122            $element->setPosition($row['position']);
123            $element->setIndentation($row["depth"]);
124
125            $element->setContent($row['answertext']);
126
127            $this->addElement($element);
128            $this->registerIdentifiers($element);
129        }
130    }
131
132    /**
133     * TODO: refactor to a select/update/insert strategy incl. deleting all except existing
134     */
135    public function saveToDb()
136    {
137        /** @var ilDBInterface $ilDB */
138        $ilDB = isset($GLOBALS['DIC']) ? $GLOBALS['DIC']['ilDB'] : $GLOBALS['DIC']['ilDB'];
139
140        $ilDB->manipulateF(
141            "DELETE FROM qpl_a_ordering WHERE question_fi = %s",
142            array( 'integer' ),
143            array( $this->getQuestionId() )
144        );
145
146        foreach ($this as $orderElement) {
147            $this->ensureValidIdentifiers($orderElement);
148
149            $ilDB->insert('qpl_a_ordering', array(
150                'answer_id' => array( 'integer', $ilDB->nextId('qpl_a_ordering') ),
151                'question_fi' => array( 'integer', $this->getQuestionId() ),
152                'answertext' => array( 'text', $orderElement->getContent()),
153                'solution_key' => array( 'integer', $orderElement->getSolutionIdentifier() ),
154                'random_id' => array( 'integer', $orderElement->getRandomIdentifier() ),
155                'position' => array( 'integer', $orderElement->getPosition() ),
156                'depth' => array( 'integer', $orderElement->getIndentation() ),
157                'tstamp' => array( 'integer', time() )
158            ));
159        }
160    }
161
162    /**
163     * clears the contents of all elements
164     */
165    public function clearElementContents()
166    {
167        foreach ($this as $orderingElement) {
168            $orderingElement->setContent('');
169        }
170    }
171
172    public function countElements()
173    {
174        return count($this->elements);
175    }
176
177    public function hasElements()
178    {
179        return (bool) $this->countElements();
180    }
181
182    public function isFirstElementPosition($position)
183    {
184        return $position == 0;
185    }
186
187    public function isLastElementPosition($position)
188    {
189        return $position == ($this->countElements() - 1);
190    }
191
192    public function moveElementByPositions($currentPosition, $targetPosition)
193    {
194        $movingElement = $this->getElementByPosition($currentPosition);
195        $dodgingElement = $this->getElementByPosition($targetPosition);
196
197        $elementList = new self();
198        $elementList->setQuestionId($this->getQuestionId());
199
200        foreach ($this as $element) {
201            if ($element->getPosition() == $currentPosition) {
202                $elementList->addElement($dodgingElement);
203                continue;
204            }
205
206            if ($element->getPosition() == $targetPosition) {
207                $elementList->addElement($movingElement);
208                continue;
209            }
210
211            $elementList->addElement($element);
212        }
213
214        $dodgingElement->setPosition($currentPosition);
215        $movingElement->setPosition($targetPosition);
216
217        $this->setElements($elementList->getElements());
218    }
219
220    public function removeElement(ilAssOrderingElement $removeElement)
221    {
222        $elementList = new self();
223        $elementList->setQuestionId($this->getQuestionId());
224
225        $positionCounter = 0;
226
227        foreach ($this as $element) {
228            if ($element->isSameElement($removeElement)) {
229                continue;
230            }
231
232            $element->setPosition($positionCounter++);
233            $elementList->addElement($element);
234        }
235    }
236
237    /**
238     * resets elements
239     */
240    public function resetElements()
241    {
242        $this->elements = array();
243    }
244
245    /**
246     * @param $elements
247     */
248    public function setElements($elements)
249    {
250        $this->resetElements();
251
252        foreach ($elements as $element) {
253            $this->addElement($element);
254        }
255    }
256
257    /**
258     * @return array[ilAssOrderingElement]
259     */
260    public function getElements()
261    {
262        return $this->elements;
263    }
264
265    /**
266     * @return array
267     */
268    public function getRandomIdentifierIndexedElements()
269    {
270        return $this->getIndexedElements(self::IDENTIFIER_TYPE_RANDOM);
271    }
272
273    /**
274     * @return array
275     */
276    public function getRandomIdentifierIndex()
277    {
278        return array_keys($this->getRandomIdentifierIndexedElements());
279    }
280
281    /**
282     * @return array
283     */
284    public function getSolutionIdentifierIndexedElements()
285    {
286        return $this->getIndexedElements(self::IDENTIFIER_TYPE_SOLUTION);
287    }
288
289    /**
290     * @return array
291     */
292    public function getSolutionIdentifierIndex()
293    {
294        return array_keys($this->getSolutionIdentifierIndexedElements());
295    }
296
297    /**
298     * @return array
299     */
300    protected function getIndexedElements($identifierType)
301    {
302        $elements = array();
303
304        foreach ($this as $element) {
305            $elements[$this->fetchIdentifier($element, $identifierType)] = $element;
306        }
307
308        return $elements;
309    }
310
311    /**
312     * @param ilAssOrderingElement $element
313     */
314    public function addElement(ilAssOrderingElement $element)
315    {
316        if ($this->hasValidIdentifiers($element)) {
317            $this->registerIdentifiers($element);
318        }
319
320        $this->elements[] = $element;
321    }
322
323    /**
324     * @param $randomIdentifier
325     * @return ilAssOrderingElement
326     */
327    public function getElementByPosition($position)
328    {
329        if (isset($this->elements[$position])) {
330            return $this->elements[$position];
331        }
332
333        return null;
334    }
335
336    /**
337     * @param $position
338     * @return bool
339     */
340    public function elementExistByPosition($position)
341    {
342        return ($this->getElementByPosition($position) !== null);
343    }
344
345    /**
346     * @param $randomIdentifier
347     * @return ilAssOrderingElement
348     */
349    public function getElementByRandomIdentifier($randomIdentifier)
350    {
351        foreach ($this as $element) {
352            if ($element->getRandomIdentifier() != $randomIdentifier) {
353                continue;
354            }
355
356            return $element;
357        }
358
359        return null;
360    }
361
362    /**
363     * @param $randomIdentifier
364     * @return bool
365     */
366    public function elementExistByRandomIdentifier($randomIdentifier)
367    {
368        return ($this->getElementByRandomIdentifier($randomIdentifier) !== null);
369    }
370
371    /**
372     * @param $randomIdentifier
373     * @return ilAssOrderingElement
374     */
375    public function getElementBySolutionIdentifier($solutionIdentifier)
376    {
377        foreach ($this as $element) {
378            if ($element->getSolutionIdentifier() != $solutionIdentifier) {
379                continue;
380            }
381
382            return $element;
383        }
384        return null;
385    }
386
387    /**
388     * @param $solutionIdentifier
389     * @return bool
390     */
391    public function elementExistBySolutionIdentifier($solutionIdentifier)
392    {
393        return ($this->getElementBySolutionIdentifier($solutionIdentifier) !== null);
394    }
395
396    /**
397     * @return array
398     */
399    protected function getRegisteredSolutionIdentifiers()
400    {
401        return $this->getRegisteredIdentifiers(self::IDENTIFIER_TYPE_SOLUTION);
402    }
403
404    /**
405     * @return array
406     */
407    protected function getRegisteredRandomIdentifiers()
408    {
409        return $this->getRegisteredIdentifiers(self::IDENTIFIER_TYPE_RANDOM);
410    }
411
412    /**
413     * @param string $identifierType
414     * @return array
415     */
416    protected function getRegisteredIdentifiers($identifierType)
417    {
418        if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
419            return array();
420        }
421
422        return self::$identifierRegistry[$identifierType][$this->getQuestionId()];
423    }
424
425    /**
426     * @param ilAssOrderingElement $element
427     * @return bool
428     */
429    protected function hasValidIdentifiers(ilAssOrderingElement $element)
430    {
431        $identifier = $this->fetchIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
432
433        if (!$this->isValidIdentifier(self::IDENTIFIER_TYPE_SOLUTION, $identifier)) {
434            return false;
435        }
436
437        $identifier = $this->fetchIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
438
439        if (!$this->isValidIdentifier(self::IDENTIFIER_TYPE_RANDOM, $identifier)) {
440            return false;
441        }
442
443        return true;
444    }
445
446    /**
447     * @param ilAssOrderingElement $element
448     */
449    protected function ensureValidIdentifiers(ilAssOrderingElement $element)
450    {
451        $this->ensureValidIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
452        $this->ensureValidIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
453    }
454
455    /**
456     * @param ilAssOrderingElement $element
457     * @param string $identifierType
458     */
459    protected function ensureValidIdentifier(ilAssOrderingElement $element, $identifierType)
460    {
461        $identifier = $this->fetchIdentifier($element, $identifierType);
462
463        if (!$this->isValidIdentifier($identifierType, $identifier)) {
464            $identifier = $this->buildIdentifier($identifierType);
465            $this->populateIdentifier($element, $identifierType, $identifier);
466            $this->registerIdentifier($element, $identifierType);
467        }
468    }
469
470    /**
471     * @param ilAssOrderingElement $element
472     */
473    protected function registerIdentifiers(ilAssOrderingElement $element)
474    {
475        $this->registerIdentifier($element, self::IDENTIFIER_TYPE_SOLUTION);
476        $this->registerIdentifier($element, self::IDENTIFIER_TYPE_RANDOM);
477    }
478
479    /**
480     * @param ilAssOrderingElement $element
481     * @param string $identifierType
482     * @throws ilTestQuestionPoolException
483     */
484    protected function registerIdentifier(ilAssOrderingElement $element, $identifierType)
485    {
486        if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
487            self::$identifierRegistry[$identifierType][$this->getQuestionId()] = array();
488        }
489
490        $identifier = $this->fetchIdentifier($element, $identifierType);
491
492        if (!in_array($identifier, self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
493            self::$identifierRegistry[$identifierType][$this->getQuestionId()][] = $identifier;
494        }
495    }
496
497    /**
498     * @param ilAssOrderingElement $element
499     * @param string $identifierType
500     * @return bool
501     * @throws ilTestQuestionPoolException
502     */
503    protected function isIdentifierRegistered(ilAssOrderingElement $element, $identifierType)
504    {
505        if (!isset(self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
506            return false;
507        }
508
509        $identifier = $this->fetchIdentifier($element, $identifierType);
510
511        if (!in_array($identifier, self::$identifierRegistry[$identifierType][$this->getQuestionId()])) {
512            return false;
513        }
514
515        return true;
516    }
517
518    /**
519     * @param ilAssOrderingElement $element
520     * @param string $identifierType
521     * @return int
522     * @throws ilTestQuestionPoolException
523     */
524    protected function fetchIdentifier(ilAssOrderingElement $element, $identifierType)
525    {
526        switch ($identifierType) {
527            case self::IDENTIFIER_TYPE_SOLUTION: return $element->getSolutionIdentifier();
528            case self::IDENTIFIER_TYPE_RANDOM: return $element->getRandomIdentifier();
529        }
530
531        $this->throwUnknownIdentifierTypeException($identifierType);
532    }
533
534    /**
535     * @param ilAssOrderingElement $element
536     * @param string $identifierType
537     * @param $identifier
538     * @throws ilTestQuestionPoolException
539     */
540    protected function populateIdentifier(ilAssOrderingElement $element, $identifierType, $identifier)
541    {
542        switch ($identifierType) {
543            case self::IDENTIFIER_TYPE_SOLUTION: $element->setSolutionIdentifier($identifier); break;
544            case self::IDENTIFIER_TYPE_RANDOM: $element->setRandomIdentifier($identifier); break;
545            default: $this->throwUnknownIdentifierTypeException($identifierType);
546        }
547    }
548
549    /**
550     * @param string $identifierType
551     * @param $identifier
552     * @return mixed
553     * @throws ilTestQuestionPoolException
554     */
555    protected function isValidIdentifier($identifierType, $identifier)
556    {
557        switch ($identifierType) {
558            case self::IDENTIFIER_TYPE_SOLUTION:
559                return self::isValidSolutionIdentifier($identifier);
560
561            case self::IDENTIFIER_TYPE_RANDOM:
562                return self::isValidRandomIdentifier($identifier);
563        }
564
565        $this->throwUnknownIdentifierTypeException($identifierType);
566    }
567
568    /**
569     * @param string $identifierType
570     * @return integer
571     * @throws ilTestQuestionPoolException
572     */
573    protected function buildIdentifier($identifierType)
574    {
575        switch ($identifierType) {
576            case self::IDENTIFIER_TYPE_SOLUTION: return $this->buildSolutionIdentifier();
577            case self::IDENTIFIER_TYPE_RANDOM: return $this->buildRandomIdentifier();
578        }
579
580        $this->throwUnknownIdentifierTypeException($identifierType);
581    }
582
583    /**
584     * @param string $identifierType
585     * @throws ilTestQuestionPoolException
586     */
587    protected function throwUnknownIdentifierTypeException($identifierType)
588    {
589        throw new ilTestQuestionPoolException(
590            "unknown identifier type given (type: $identifierType)"
591        );
592    }
593
594    /**
595     * @param string $identifierType
596     * @throws ilTestQuestionPoolException
597     */
598    protected function throwCouldNotBuildRandomIdentifierException($maxTries)
599    {
600        throw new ilTestQuestionPoolException(
601            "could not build random identifier (max tries: $maxTries)"
602        );
603    }
604
605    /**
606     * @param string $randomIdentifier
607     * @throws ilTestQuestionPoolException
608     */
609    protected function throwMissingReorderPositionException($randomIdentifier)
610    {
611        throw new ilTestQuestionPoolException(
612            "cannot reorder element due to missing position (random identifier: $randomIdentifier)"
613        );
614    }
615
616    /**
617     * @param array $randomIdentifiers
618     * @throws ilTestQuestionPoolException
619     */
620    protected function throwUnknownRandomIdentifiersException($randomIdentifiers)
621    {
622        throw new ilTestQuestionPoolException(
623            'cannot reorder element due to one or more unknown random identifiers ' .
624            '(' . implode(', ', $randomIdentifiers) . ')'
625        );
626    }
627
628    /**
629     * @return integer|null $lastSolutionIdentifier
630     */
631    protected function getLastSolutionIdentifier()
632    {
633        $lastSolutionIdentifier = null;
634
635        foreach ($this->getRegisteredSolutionIdentifiers() as $registeredIdentifier) {
636            if ($lastSolutionIdentifier > $registeredIdentifier) {
637                continue;
638            }
639
640            $lastSolutionIdentifier = $registeredIdentifier;
641        }
642
643        return $lastSolutionIdentifier;
644    }
645
646    /**
647     * @return integer|null $nextSolutionIdentifier
648     */
649    protected function buildSolutionIdentifier()
650    {
651        $lastSolutionIdentifier = $this->getLastSolutionIdentifier();
652
653        if ($lastSolutionIdentifier === null) {
654            return 0;
655        }
656
657        $nextSolutionIdentifier = $lastSolutionIdentifier + self::SOLUTION_IDENTIFIER_VALUE_INTERVAL;
658
659        return $nextSolutionIdentifier;
660    }
661
662    /**
663     * @return int $randomIdentifier
664     * @throws ilTestQuestionPoolException
665     */
666    protected function buildRandomIdentifier()
667    {
668        $usedTriesCounter = 0;
669
670        do {
671            if ($usedTriesCounter >= self::RANDOM_IDENTIFIER_BUILD_MAX_TRIES) {
672                $this->throwCouldNotBuildRandomIdentifierException(self::RANDOM_IDENTIFIER_BUILD_MAX_TRIES);
673            }
674
675            $usedTriesCounter++;
676
677            $lowerBound = self::RANDOM_IDENTIFIER_RANGE_LOWER_BOUND;
678            $upperBound = self::RANDOM_IDENTIFIER_RANGE_UPPER_BOUND;
679            $randomIdentifier = mt_rand($lowerBound, $upperBound);
680
681            $testElement = new ilAssOrderingElement();
682            $testElement->setRandomIdentifier($randomIdentifier);
683        } while ($this->isIdentifierRegistered($testElement, self::IDENTIFIER_TYPE_RANDOM));
684
685        return $randomIdentifier;
686    }
687
688    public static function isValidSolutionIdentifier($identifier)
689    {
690        if (!is_numeric($identifier)) {
691            return false;
692        }
693
694        if ($identifier != (int) $identifier) {
695            return false;
696        }
697
698        if ($identifier < 0) {
699            return false;
700        }
701
702        return true;
703    }
704
705    public static function isValidRandomIdentifier($identifier)
706    {
707        if (!is_numeric($identifier)) {
708            return false;
709        }
710
711        if ($identifier != (int) $identifier) {
712            return false;
713        }
714
715        if ($identifier < self::RANDOM_IDENTIFIER_RANGE_LOWER_BOUND) {
716            return false;
717        }
718
719        if ($identifier > self::RANDOM_IDENTIFIER_RANGE_UPPER_BOUND) {
720            return false;
721        }
722
723        return true;
724    }
725
726    public static function isValidPosition($position)
727    {
728        return self::isValidSolutionIdentifier($position); // this was the position earlier
729    }
730
731    public static function isValidIndentation($indentation)
732    {
733        return self::isValidPosition($indentation); // horizontal position ^^
734    }
735
736    /**
737     *
738     */
739    public function distributeNewRandomIdentifiers()
740    {
741        foreach ($this as $element) {
742            $element->setRandomIdentifier($this->buildRandomIdentifier());
743        }
744    }
745
746    /**
747     * @param ilAssOrderingElementList $otherList
748     * @return bool $hasSameElements
749     */
750    public function hasSameElementSetByRandomIdentifiers(self $otherList)
751    {
752        $numIntersectingElements = count(array_intersect(
753            $otherList->getRandomIdentifierIndex(),
754            $this->getRandomIdentifierIndex()
755        ));
756
757        if ($numIntersectingElements != $this->countElements()) {
758            return false;
759        }
760
761        if ($numIntersectingElements != $otherList->countElements()) {
762            return false;
763        }
764
765        return true; // faster ;-)
766
767        $otherListRandomIdentifierIndex = $otherList->getRandomIdentifierIndex();
768
769        foreach ($this as $orderingElement) {
770            if (!in_array($orderingElement->getRandomIdentifier(), $otherListRandomIdentifierIndex)) {
771                return false;
772            }
773
774            $randomIdentifierIndexMatchingsCount = count(array_keys(
775                $otherListRandomIdentifierIndex,
776                $orderingElement->getRandomIdentifier(),
777                false
778            ));
779
780            if ($randomIdentifierIndexMatchingsCount != 1) {
781                return false;
782            }
783        }
784
785        return $this->countElements() == $otherList->countElements();
786    }
787
788    public function getParityTrueElementList(self $otherList)
789    {
790        if (!$this->hasSameElementSetByRandomIdentifiers($otherList)) {
791            throw new ilTestQuestionPoolException('cannot compare lists having different element sets');
792        }
793
794        $parityTrueElementList = new self();
795        $parityTrueElementList->setQuestionId($this->getQuestionId());
796
797        foreach ($this as $thisElement) {
798            $otherElement = $otherList->getElementByRandomIdentifier(
799                $thisElement->getRandomIdentifier()
800            );
801
802            if ($otherElement->getPosition() != $thisElement->getPosition()) {
803                continue;
804            }
805
806            if ($otherElement->getIndentation() != $thisElement->getIndentation()) {
807                continue;
808            }
809
810            $parityTrueElementList->addElement($thisElement);
811        }
812
813        return $parityTrueElementList;
814    }
815
816    /**
817     * @param $randomIdentifiers
818     * @return ilAssOrderingElementList
819     * @throws ilTestQuestionPoolException
820     */
821    public function reorderByRandomIdentifiers($randomIdentifiers)
822    {
823        $positionsMap = array_flip(array_values($randomIdentifiers));
824
825        $orderedElements = array();
826
827        foreach ($this as $element) {
828            if (!isset($positionsMap[$element->getRandomIdentifier()])) {
829                $this->throwMissingReorderPositionException($element->getRandomIdentifier());
830            }
831
832            $position = $positionsMap[$element->getRandomIdentifier()];
833            unset($positionsMap[$element->getRandomIdentifier()]);
834
835            $element->setPosition($position);
836            $orderedElements[$position] = $element;
837        }
838
839        if (count($positionsMap)) {
840            $this->throwUnknownRandomIdentifiersException(array_keys($positionsMap));
841        }
842
843        ksort($orderedElements);
844
845        $this->setElements(array_values($orderedElements));
846    }
847
848    /**
849     * resets the indentation to level 0 for all elements in list
850     */
851    public function resetElementsIndentations()
852    {
853        foreach ($this as $element) {
854            $element->setIndentation(0);
855        }
856    }
857
858    /**
859     * @param ilAssOrderingElementList $otherElementList
860     * @return $differenceElementList ilAssOrderingElementList
861     */
862    public function getDifferenceElementList(self $otherElementList)
863    {
864        $differenceRandomIdentifierIndex = $this->getDifferenceRandomIdentifierIndex($otherElementList);
865
866        $differenceElementList = new self();
867        $differenceElementList->setQuestionId($this->getQuestionId());
868
869        foreach ($differenceRandomIdentifierIndex as $randomIdentifier) {
870            $element = $this->getElementByRandomIdentifier($randomIdentifier);
871            $differenceElementList->addElement($element);
872        }
873
874        return $differenceElementList;
875    }
876
877    /**
878     * @param ilAssOrderingElementList $other
879     * @return array
880     */
881    protected function getDifferenceRandomIdentifierIndex(self $otherElementList)
882    {
883        $differenceRandomIdentifierIndex = array_diff(
884            $this->getRandomIdentifierIndex(),
885            $otherElementList->getRandomIdentifierIndex()
886        );
887
888        return $differenceRandomIdentifierIndex;
889    }
890
891    /**
892     * @param ilAssOrderingElementList $otherList
893     */
894    public function completeContentsFromElementList(self $otherList)
895    {
896        foreach ($this as $thisElement) {
897            if (!$otherList->elementExistByRandomIdentifier($thisElement->getRandomIdentifier())) {
898                continue;
899            }
900
901            $otherElement = $otherList->getElementByRandomIdentifier(
902                $thisElement->getRandomIdentifier()
903            );
904
905            $thisElement->setContent($otherElement->getContent());
906        }
907    }
908
909    /**
910     * @return ilAssOrderingElement
911     */
912    public function current()
913    {
914        return current($this->elements);
915    }
916
917    /**
918     * @return ilAssOrderingElement
919     */
920    public function next()
921    {
922        return next($this->elements);
923    }
924
925    /**
926     * @return integer|bool
927     */
928    public function key()
929    {
930        return key($this->elements);
931    }
932
933    /**
934     * @return bool
935     */
936    public function valid()
937    {
938        return ($this->key() !== null);
939    }
940
941    /**
942     * @return ilAssOrderingElement
943     */
944    public function rewind()
945    {
946        return reset($this->elements);
947    }
948
949    /**
950     * @return ilAssOrderingElement
951     */
952    public static function getFallbackDefaultElement()
953    {
954        $element = new ilAssOrderingElement();
955        $element->setRandomIdentifier(self::FALLBACK_DEFAULT_ELEMENT_RANDOM_IDENTIFIER);
956
957        return $element;
958    }
959
960    /**
961     * @param integer $questionId
962     * @param array[ilAssOrderingElement] $orderingElements
963     * @return ilAssOrderingElementList
964     */
965    public static function buildInstance($questionId, $orderingElements = array())
966    {
967        $elementList = new self();
968
969        $elementList->setQuestionId($questionId);
970        $elementList->setElements($orderingElements);
971
972        return $elementList;
973    }
974
975    public function getHash()
976    {
977        $items = array();
978
979        foreach ($this as $element) {
980            $items[] = implode(':', array(
981                $element->getSolutionIdentifier(),
982                $element->getRandomIdentifier(),
983                $element->getIndentation()
984            ));
985        }
986
987        return md5(serialize($items));
988    }
989}
990