ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assMatchingQuestion.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
7 require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
8 require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
9 require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
10 
25 {
43 
49  protected $terms;
50 
51  protected $definitions;
57  var $thumb_geometry = 100;
58 
65 
66  const MATCHING_MODE_1_ON_1 = '1:1';
67  const MATCHING_MODE_N_ON_N = 'n:n';
68 
70 
85  public function __construct(
86  $title = "",
87  $comment = "",
88  $author = "",
89  $owner = -1,
90  $question = "",
92  )
93  {
95  $this->matchingpairs = array();
96  $this->matching_type = $matching_type;
97  $this->terms = array();
98  $this->definitions = array();
99  }
100 
106  public function isComplete()
107  {
108  if (strlen($this->title)
109  && $this->author
110  && $this->question
111  && count($this->matchingpairs)
112  && $this->getMaximumPoints() > 0
113  )
114  {
115  return true;
116  }
117  return false;
118  }
119 
126  public function saveToDb($original_id = "")
127  {
128  global $ilDB;
129 
132  $this->saveAnswerSpecificDataToDb( $ilDB );
133 
134 
136  }
137 
138  public function saveAnswerSpecificDataToDb()
139  {
140  global $ilDB;
141  // delete old terms
142  $ilDB->manipulateF( "DELETE FROM qpl_a_mterm WHERE question_fi = %s",
143  array( 'integer' ),
144  array( $this->getId() )
145  );
146 
147  // delete old definitions
148  $ilDB->manipulateF( "DELETE FROM qpl_a_mdef WHERE question_fi = %s",
149  array( 'integer' ),
150  array( $this->getId() )
151  );
152 
153  $termids = array();
154  // write terms
155  foreach ($this->terms as $key => $term)
156  {
157  $next_id = $ilDB->nextId( 'qpl_a_mterm' );
158  $ilDB->manipulateF( "INSERT INTO qpl_a_mterm (term_id, question_fi, picture, term) VALUES (%s, %s, %s, %s)",
159  array( 'integer', 'integer', 'text', 'text' ),
160  array( $next_id, $this->getId(), $term->picture, $term->text )
161  );
162  $termids[$term->identifier] = $next_id;
163  }
164 
165  $definitionids = array();
166  // write definitions
167  foreach ($this->definitions as $key => $definition)
168  {
169  $next_id = $ilDB->nextId( 'qpl_a_mdef' );
170  $ilDB->manipulateF( "INSERT INTO qpl_a_mdef (def_id, question_fi, picture, definition, morder) VALUES (%s, %s, %s, %s, %s)",
171  array( 'integer', 'integer', 'text', 'text', 'integer' ),
172  array( $next_id, $this->getId(
173  ), $definition->picture, $definition->text, $definition->identifier )
174  );
175  $definitionids[$definition->identifier] = $next_id;
176  }
177 
178  $ilDB->manipulateF( "DELETE FROM qpl_a_matching WHERE question_fi = %s",
179  array( 'integer' ),
180  array( $this->getId() )
181  );
182  $matchingpairs = $this->getMatchingPairs();
183  foreach ($matchingpairs as $key => $pair)
184  {
185  $next_id = $ilDB->nextId( 'qpl_a_matching' );
186  $ilDB->manipulateF( "INSERT INTO qpl_a_matching (answer_id, question_fi, points, term_fi, definition_fi) VALUES (%s, %s, %s, %s, %s)",
187  array( 'integer', 'integer', 'float', 'integer', 'integer' ),
188  array(
189  $next_id,
190  $this->getId(),
191  $pair->points,
192  $termids[$pair->term->identifier],
193  $definitionids[$pair->definition->identifier]
194  )
195  );
196  }
197 
198  $this->rebuildThumbnails();
199  }
200 
202  {
203  global $ilDB;
204 
205  // save additional data
206 
207  $ilDB->manipulateF(
208  "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
209  array( "integer" ), array( $this->getId() )
210  );
211 
212  $ilDB->insert($this->getAdditionalTableName(), array(
213  'question_fi' => array('integer', $this->getId()),
214  'shuffle' => array('text', $this->shuffle),
215  'matching_type' => array('text', $this->matching_type),
216  'thumb_geometry' => array('integer', $this->getThumbGeometry()),
217  'matching_mode' => array('text', $this->getMatchingMode())
218  ));
219  }
220 
227  public function loadFromDb($question_id)
228  {
229  global $ilDB;
230 
231  $query = "
232  SELECT qpl_questions.*,
233  {$this->getAdditionalTableName()}.*
234  FROM qpl_questions
235  LEFT JOIN {$this->getAdditionalTableName()}
236  ON {$this->getAdditionalTableName()}.question_fi = qpl_questions.question_id
237  WHERE qpl_questions.question_id = %s
238  ";
239 
240  $result = $ilDB->queryF(
241  $query, array('integer'), array($question_id)
242  );
243 
244  if ($result->numRows() == 1)
245  {
246  $data = $ilDB->fetchAssoc($result);
247  $this->setId($question_id);
248  $this->setObjId($data["obj_fi"]);
249  $this->setTitle($data["title"]);
250  $this->setComment($data["description"]);
251  $this->setOriginalId($data["original_id"]);
252  $this->setNrOfTries($data['nr_of_tries']);
253  $this->setAuthor($data["author"]);
254  $this->setPoints($data["points"]);
255  $this->setOwner($data["owner"]);
256  include_once("./Services/RTE/classes/class.ilRTE.php");
257  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1));
258  $this->setThumbGeometry($data["thumb_geometry"]);
259  $this->setShuffle($data["shuffle"]);
260  $this->setMatchingMode($data['matching_mode'] === null ? self::MATCHING_MODE_1_ON_1 : $data['matching_mode']);
261  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
262 
263  try
264  {
265  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
266  }
268  {
269  }
270  }
271 
272  $termids = array();
273  $result = $ilDB->queryF("SELECT * FROM qpl_a_mterm WHERE question_fi = %s ORDER BY term_id ASC",
274  array('integer'),
275  array($question_id)
276  );
277  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingTerm.php";
278  $this->terms = array();
279  if ($result->numRows() > 0)
280  {
281  while ($data = $ilDB->fetchAssoc($result))
282  {
283  $term = new assAnswerMatchingTerm($data['term'], $data['picture'], $data['term_id']);
284  array_push($this->terms, $term);
285  $termids[$data['term_id']] = $term;
286  }
287  }
288 
289  $definitionids = array();
290  $result = $ilDB->queryF("SELECT * FROM qpl_a_mdef WHERE question_fi = %s ORDER BY def_id ASC",
291  array('integer'),
292  array($question_id)
293  );
294  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingDefinition.php";
295  $this->definitions = array();
296  if ($result->numRows() > 0)
297  {
298  while ($data = $ilDB->fetchAssoc($result))
299  {
300  $definition = new assAnswerMatchingDefinition($data['definition'], $data['picture'], $data['morder']);
301  array_push($this->definitions, $definition);
302  $definitionids[$data['def_id']] = $definition;
303  }
304  }
305 
306  $this->matchingpairs = array();
307  $result = $ilDB->queryF("SELECT * FROM qpl_a_matching WHERE question_fi = %s ORDER BY answer_id",
308  array('integer'),
309  array($question_id)
310  );
311  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingPair.php";
312  if ($result->numRows() > 0)
313  {
314  while ($data = $ilDB->fetchAssoc($result))
315  {
316  array_push($this->matchingpairs, new assAnswerMatchingPair($termids[$data['term_fi']], $definitionids[$data['definition_fi']], $data['points']));
317  }
318  }
319  parent::loadFromDb($question_id);
320  }
321 
322 
326  public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
327  {
328  if ($this->id <= 0)
329  {
330  // The question has not been saved. It cannot be duplicated
331  return;
332  }
333  // duplicate the question in database
334  $this_id = $this->getId();
335  $thisObjId = $this->getObjId();
336 
337  $clone = $this;
338  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
340  $clone->id = -1;
341 
342  if( (int)$testObjId > 0 )
343  {
344  $clone->setObjId($testObjId);
345  }
346 
347  if ($title)
348  {
349  $clone->setTitle($title);
350  }
351  if ($author)
352  {
353  $clone->setAuthor($author);
354  }
355  if ($owner)
356  {
357  $clone->setOwner($owner);
358  }
359  if ($for_test)
360  {
361  $clone->saveToDb($original_id);
362  }
363  else
364  {
365  $clone->saveToDb();
366  }
367 
368  // copy question page content
369  $clone->copyPageOfQuestion($this_id);
370  // copy XHTML media objects
371  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
372  // duplicate the image
373  $clone->duplicateImages($this_id, $thisObjId);
374 
375  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
376 
377  return $clone->id;
378  }
379 
383  public function copyObject($target_questionpool_id, $title = "")
384  {
385  if ($this->id <= 0)
386  {
387  // The question has not been saved. It cannot be duplicated
388  return;
389  }
390  // duplicate the question in database
391  $clone = $this;
392  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
394  $clone->id = -1;
395  $source_questionpool_id = $this->getObjId();
396  $clone->setObjId($target_questionpool_id);
397  if ($title)
398  {
399  $clone->setTitle($title);
400  }
401  $clone->saveToDb();
402  // copy question page content
403  $clone->copyPageOfQuestion($original_id);
404  // copy XHTML media objects
405  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
406  // duplicate the image
407  $clone->copyImages($original_id, $source_questionpool_id);
408 
409  $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
410 
411  return $clone->id;
412  }
413 
414  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
415  {
416  if ($this->id <= 0)
417  {
418  // The question has not been saved. It cannot be duplicated
419  return;
420  }
421 
422  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
423 
424  $sourceQuestionId = $this->id;
425  $sourceParentId = $this->getObjId();
426 
427  // duplicate the question in database
428  $clone = $this;
429  $clone->id = -1;
430 
431  $clone->setObjId($targetParentId);
432 
433  if ($targetQuestionTitle)
434  {
435  $clone->setTitle($targetQuestionTitle);
436  }
437 
438  $clone->saveToDb();
439  // copy question page content
440  $clone->copyPageOfQuestion($sourceQuestionId);
441  // copy XHTML media objects
442  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
443  // duplicate the image
444  $clone->copyImages($sourceQuestionId, $sourceParentId);
445 
446  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
447 
448  return $clone->id;
449  }
450 
451  public function duplicateImages($question_id, $objectId = null)
452  {
453  global $ilLog;
454  $imagepath = $this->getImagePath();
455  $imagepath_original = str_replace("/$this->id/images", "/$question_id/images", $imagepath);
456 
457  if( (int)$objectId > 0 )
458  {
459  $imagepath_original = str_replace("/$this->obj_id/", "/$objectId/", $imagepath_original);
460  }
461 
462  foreach ($this->terms as $term)
463  {
464  if (strlen($term->picture))
465  {
466  $filename = $term->picture;
467  if (!file_exists($imagepath))
468  {
469  ilUtil::makeDirParents($imagepath);
470  }
471  if (!@copy($imagepath_original . $filename, $imagepath . $filename))
472  {
473  $ilLog->write("matching question image could not be duplicated: $imagepath_original$filename");
474  }
475  if (@file_exists($imagepath_original . $this->getThumbPrefix() . $filename))
476  {
477  if (!@copy($imagepath_original . $this->getThumbPrefix() . $filename, $imagepath . $this->getThumbPrefix() . $filename))
478  {
479  $ilLog->write("matching question image thumbnail could not be duplicated: $imagepath_original" . $this->getThumbPrefix() . $filename);
480  }
481  }
482  }
483  }
484  foreach ($this->definitions as $definition)
485  {
486  if (strlen($definition->picture))
487  {
488  $filename = $definition->picture;
489  if (!file_exists($imagepath))
490  {
491  ilUtil::makeDirParents($imagepath);
492  }
493  if (!@copy($imagepath_original . $filename, $imagepath . $filename))
494  {
495  $ilLog->write("matching question image could not be duplicated: $imagepath_original$filename");
496  }
497  if (@file_exists($imagepath_original . $this->getThumbPrefix() . $filename))
498  {
499  if (!@copy($imagepath_original . $this->getThumbPrefix() . $filename, $imagepath . $this->getThumbPrefix() . $filename))
500  {
501  $ilLog->write("matching question image thumbnail could not be duplicated: $imagepath_original" . $this->getThumbPrefix() . $filename);
502  }
503  }
504  }
505  }
506  }
507 
508  public function copyImages($question_id, $source_questionpool)
509  {
510  global $ilLog;
511 
512  $imagepath = $this->getImagePath();
513  $imagepath_original = str_replace("/$this->id/images", "/$question_id/images", $imagepath);
514  $imagepath_original = str_replace("/$this->obj_id/", "/$source_questionpool/", $imagepath_original);
515  foreach ($this->terms as $term)
516  {
517  if (strlen($term->picture))
518  {
519  if (!file_exists($imagepath))
520  {
521  ilUtil::makeDirParents($imagepath);
522  }
523  $filename = $term->picture;
524  if (!@copy($imagepath_original . $filename, $imagepath . $filename))
525  {
526  $ilLog->write("matching question image could not be copied: $imagepath_original$filename");
527  }
528  if (!@copy($imagepath_original . $this->getThumbPrefix() . $filename, $imagepath . $this->getThumbPrefix() . $filename))
529  {
530  $ilLog->write("matching question image thumbnail could not be copied: $imagepath_original" . $this->getThumbPrefix() . $filename);
531  }
532  }
533  }
534  foreach ($this->definitions as $definition)
535  {
536  if (strlen($definition->picture))
537  {
538  $filename = $definition->picture;
539  if (!file_exists($imagepath))
540  {
541  ilUtil::makeDirParents($imagepath);
542  }
543  if (!copy($imagepath_original . $filename, $imagepath . $filename))
544  {
545  $ilLog->write("matching question image could not be copied: $imagepath_original$filename");
546  }
547  if (!copy($imagepath_original . $this->getThumbPrefix() . $filename, $imagepath . $this->getThumbPrefix() . $filename))
548  {
549  $ilLog->write("matching question image thumbnail could not be copied: $imagepath_original" . $this->getThumbPrefix() . $filename);
550  }
551  }
552  }
553  }
554 
565  public function insertMatchingPair($position, $term = null, $definition = null, $points = 0.0)
566  {
567  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingPair.php";
568  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingTerm.php";
569  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingDefinition.php";
570  if (is_null($term)) $term = new assAnswerMatchingTerm();
571  if (is_null($definition)) $definition = new assAnswerMatchingDefinition();
572  $pair = new assAnswerMatchingPair($term, $definition, $points);
573  if ($position < count($this->matchingpairs))
574  {
575  $part1 = array_slice($this->matchingpairs, 0, $position);
576  $part2 = array_slice($this->matchingpairs, $position);
577  $this->matchingpairs = array_merge($part1, array($pair), $part2);
578  }
579  else
580  {
581  array_push($this->matchingpairs, $pair);
582  }
583  }
584 
596  public function addMatchingPair($term = null, $definition = null, $points = 0.0)
597  {
598  require_once './Modules/TestQuestionPool/classes/class.assAnswerMatchingPair.php';
599  require_once './Modules/TestQuestionPool/classes/class.assAnswerMatchingTerm.php';
600  require_once './Modules/TestQuestionPool/classes/class.assAnswerMatchingDefinition.php';
601  if (is_null($term))
602  {
603  $term = new assAnswerMatchingTerm();
604  }
605  if (is_null($definition))
606  {
607  $definition = new assAnswerMatchingDefinition();
608  }
609  $pair = new assAnswerMatchingPair($term, $definition, $points);
610  array_push($this->matchingpairs, $pair);
611  }
612 
616  public function getTermWithIdentifier($a_identifier)
617  {
618  foreach ($this->terms as $term)
619  {
620  if ($term->identifier == $a_identifier) return $term;
621  }
622  return null;
623  }
624 
628  public function getDefinitionWithIdentifier($a_identifier)
629  {
630  foreach ($this->definitions as $definition)
631  {
632  if ($definition->identifier == $a_identifier) return $definition;
633  }
634  return null;
635  }
636 
645  public function getMatchingPair($index = 0)
646  {
647  if ($index < 0)
648  {
649  return NULL;
650  }
651  if (count($this->matchingpairs) < 1)
652  {
653  return NULL;
654  }
655  if ($index >= count($this->matchingpairs))
656  {
657  return NULL;
658  }
659  return $this->matchingpairs[$index];
660  }
661 
669  public function deleteMatchingPair($index = 0)
670  {
671  if ($index < 0)
672  {
673  return;
674  }
675  if (count($this->matchingpairs) < 1)
676  {
677  return;
678  }
679  if ($index >= count($this->matchingpairs))
680  {
681  return;
682  }
683  unset($this->matchingpairs[$index]);
684  $this->matchingpairs = array_values($this->matchingpairs);
685  }
686 
691  public function flushMatchingPairs()
692  {
693  $this->matchingpairs = array();
694  }
695 
702  public function getMatchingPairCount()
703  {
704  return count($this->matchingpairs);
705  }
706 
713  public function getTerms()
714  {
715  return $this->terms;
716  }
717 
724  public function getDefinitions()
725  {
726  return $this->definitions;
727  }
728 
735  public function getTermCount()
736  {
737  return count($this->terms);
738  }
739 
746  public function getDefinitionCount()
747  {
748  return count($this->definitions);
749  }
750 
757  public function addTerm($term)
758  {
759  array_push($this->terms, $term);
760  }
761 
768  public function addDefinition($definition)
769  {
770  array_push($this->definitions, $definition);
771  }
772 
779  public function insertTerm($position, $term = null)
780  {
781  if (is_null($term))
782  {
783  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingTerm.php";
784  $term = new assAnswerMatchingTerm();
785  }
786  if ($position < count($this->terms))
787  {
788  $part1 = array_slice($this->terms, 0, $position);
789  $part2 = array_slice($this->terms, $position);
790  $this->terms = array_merge($part1, array($term), $part2);
791  }
792  else
793  {
794  array_push($this->terms, $term);
795  }
796  }
797 
804  public function insertDefinition($position, $definition = null)
805  {
806  if (is_null($definition))
807  {
808  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMatchingDefinition.php";
809  $definition = new assAnswerMatchingDefinition();
810  }
811  if ($position < count($this->definitions))
812  {
813  $part1 = array_slice($this->definitions, 0, $position);
814  $part2 = array_slice($this->definitions, $position);
815  $this->definitions = array_merge($part1, array($definition), $part2);
816  }
817  else
818  {
819  array_push($this->definitions, $definition);
820  }
821  }
822 
827  public function flushTerms()
828  {
829  $this->terms = array();
830  }
831 
836  public function flushDefinitions()
837  {
838  $this->definitions = array();
839  }
840 
847  public function deleteTerm($position)
848  {
849  unset($this->terms[$position]);
850  $this->terms = array_values($this->terms);
851  }
852 
859  public function deleteDefinition($position)
860  {
861  unset($this->definitions[$position]);
862  $this->definitions = array_values($this->definitions);
863  }
864 
872  public function setTerm($term, $index)
873  {
874  $this->terms[$index] = $term;
875  }
876 
887  public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
888  {
889  if( $returndetails )
890  {
891  throw new ilTestException('return details not implemented for '.__METHOD__);
892  }
893 
894  global $ilDB;
895 
896  $found_values = array();
897  if (is_null($pass))
898  {
899  $pass = $this->getSolutionMaxPass($active_id);
900  }
901  $result = $this->getCurrentSolutionResultSet($active_id, $pass);
902  while ($data = $ilDB->fetchAssoc($result))
903  {
904  if (strcmp($data["value1"], "") != 0)
905  {
906  if( !isset($found_values[$data['value2']]) )
907  {
908  $found_values[$data['value2']] = array();
909  }
910 
911  $found_values[$data['value2']][] = $data['value1'];
912  }
913  }
914 
915  $points = $this->calculateReachedPointsForSolution($found_values);
916 
917  return $points;
918  }
919 
923  function getMaximumPoints()
924  {
925  $points = 0;
926 
927  foreach( $this->getMaximumScoringMatchingPairs() as $pair )
928  {
929  $points += $pair->points;
930  }
931 
932  return $points;
933  }
934 
936  {
937  if( $this->getMatchingMode() == self::MATCHING_MODE_N_ON_N )
938  {
939  return $this->getPositiveScoredMatchingPairs();
940  }
941  elseif( $this->getMatchingMode() == self::MATCHING_MODE_1_ON_1 )
942  {
944  }
945 
946  return array();
947  }
948 
950  {
951  $matchingPairs = array();
952 
953  foreach( $this->matchingpairs as $pair )
954  {
955  if( $pair->points <= 0 )
956  {
957  continue;
958  }
959 
960  $matchingPairs[] = $pair;
961  }
962 
963  return $matchingPairs;
964  }
965 
967  {
968  $matchingPairsByDefinition = array();
969 
970  foreach( $this->matchingpairs as $pair )
971  {
972  if( $pair->points <= 0 )
973  {
974  continue;
975  }
976 
977  $defId = $pair->definition->identifier;
978 
979  if( !isset($matchingPairsByDefinition[$defId]) )
980  {
981  $matchingPairsByDefinition[$defId] = $pair;
982  }
983  elseif( $pair->points > $matchingPairsByDefinition[$defId]->points )
984  {
985  $matchingPairsByDefinition[$defId] = $pair;
986  }
987  }
988 
989  return $matchingPairsByDefinition;
990  }
991 
1001  {
1002  $extension = "";
1003  if (preg_match("/.*\\.(\\w+)$/", $filename, $matches))
1004  {
1005  $extension = $matches[1];
1006  }
1007  return md5($filename) . "." . $extension;
1008  }
1009 
1010  public function removeTermImage($index)
1011  {
1012  $term = $this->terms[$index];
1013  if (is_object($term))
1014  {
1015  $this->deleteImagefile($term->picture);
1016  $term->picture = null;
1017  }
1018  }
1019 
1020  public function removeDefinitionImage($index)
1021  {
1022  $definition = $this->definitions[$index];
1023  if (is_object($definition))
1024  {
1025  $this->deleteImagefile($definition->picture);
1026  $definition->picture = null;
1027  }
1028  }
1029 
1030 
1037  public function deleteImagefile($filename)
1038  {
1039  $deletename = $filename;
1040  $result = @unlink($this->getImagePath().$deletename);
1041  $result = $result & @unlink($this->getImagePath().$this->getThumbPrefix() . $deletename);
1042  return $result;
1043  }
1044 
1053  function setImageFile($image_tempfilename, $image_filename, $previous_filename = '')
1054  {
1055  $result = TRUE;
1056  if (strlen($image_tempfilename))
1057  {
1058  $image_filename = str_replace(" ", "_", $image_filename);
1059  $imagepath = $this->getImagePath();
1060  if (!file_exists($imagepath))
1061  {
1062  ilUtil::makeDirParents($imagepath);
1063  }
1064  $savename = $image_filename;
1065  if (!ilUtil::moveUploadedFile($image_tempfilename, $savename, $imagepath.$savename))
1066  {
1067  $result = FALSE;
1068  }
1069  else
1070  {
1071  // create thumbnail file
1072  $thumbpath = $imagepath . $this->getThumbPrefix() . $savename;
1073  ilUtil::convertImage($imagepath.$savename, $thumbpath, "JPEG", $this->getThumbGeometry());
1074  }
1075  if ($result && (strcmp($image_filename, $previous_filename) != 0) && (strlen($previous_filename)))
1076  {
1077  $this->deleteImagefile($previous_filename);
1078  }
1079  }
1080  return $result;
1081  }
1082 
1084  {
1085  $postData = $_POST['matching'][$this->getId()];
1086 
1087  $matchings = array();
1088 
1089  foreach( $this->getDefinitions() as $definition )
1090  {
1091  if( isset($postData[$definition->identifier]) )
1092  {
1093  foreach( $this->getTerms() as $term )
1094  {
1095  if( isset($postData[$definition->identifier][$term->identifier]) )
1096  {
1097  if( !is_array($postData[$definition->identifier]) )
1098  {
1099  $postData[$definition->identifier] = array();
1100  }
1101 
1102  $matchings[$definition->identifier][] = $term->identifier;
1103  }
1104  }
1105  }
1106  }
1107 
1108  return $matchings;
1109  }
1110 
1111  private function checkSubmittedMatchings($submittedMatchings)
1112  {
1113  if( $this->getMatchingMode() == self::MATCHING_MODE_N_ON_N )
1114  {
1115  return true;
1116  }
1117 
1118  $handledTerms = array();
1119 
1120  foreach( $submittedMatchings as $definition => $terms )
1121  {
1122  if( count($terms) > 1 )
1123  {
1124  ilUtil::sendFailure($this->lng->txt("multiple_matching_values_selected"), true);
1125  return false;
1126  }
1127 
1128  foreach( $terms as $i => $term )
1129  {
1130  if( isset($handledTerms[$term]) )
1131  {
1132  ilUtil::sendFailure($this->lng->txt("duplicate_matching_values_selected"), true);
1133  return false;
1134  }
1135 
1136  $handledTerms[$term] = $term;
1137  }
1138  }
1139 
1140  return true;
1141  }
1142 
1151  public function saveWorkingData($active_id, $pass = NULL)
1152  {
1153  global $ilDB;
1154 
1155  $submittedMatchings = $this->fetchSubmittedMatchingsFromPost();
1156  $submittedMatchingsValid = $this->checkSubmittedMatchings($submittedMatchings);
1157 
1158  $matchingsExist = false;
1159 
1160  if ($submittedMatchingsValid)
1161  {
1162  if (is_null($pass))
1163  {
1164  include_once "./Modules/Test/classes/class.ilObjTest.php";
1165  $pass = ilObjTest::_getPass($active_id);
1166  }
1167 
1168  $this->getProcessLocker()->requestUserSolutionUpdateLock();
1169 
1170  $affectedRows = $this->removeCurrentSolution($active_id, $pass);
1171 
1172  foreach( $submittedMatchings as $definition => $terms )
1173  {
1174  foreach( $terms as $i => $term )
1175  {
1176  $affectedRows = $this->saveCurrentSolution($active_id, $pass, $term, $definition);
1177 
1178  $matchingsExist = true;
1179  }
1180  }
1181 
1182  $this->getProcessLocker()->releaseUserSolutionUpdateLock();
1183 
1184  $saveWorkingDataResult = true;
1185  }
1186  else
1187  {
1188  $saveWorkingDataResult = false;
1189  }
1190 
1191  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1193  {
1194  if( $matchingsExist )
1195  {
1196  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1197  }
1198  else
1199  {
1200  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1201  }
1202  }
1203 
1204  return $saveWorkingDataResult;
1205  }
1206 
1207  protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1208  {
1209  $submittedMatchings = $this->fetchSubmittedMatchingsFromPost();
1210 
1211  if( $this->checkSubmittedMatchings($submittedMatchings) )
1212  {
1213  $previewSession->setParticipantsSolution($submittedMatchings);
1214  }
1215  }
1216 
1225  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered)
1226  {
1227  // nothing to rework!
1228  }
1229 
1230  public function getRandomId()
1231  {
1232  mt_srand((double)microtime()*1000000);
1233  $random_number = mt_rand(1, 100000);
1234  $found = FALSE;
1235  while ($found)
1236  {
1237  $found = FALSE;
1238  foreach ($this->matchingpairs as $key => $pair)
1239  {
1240  if (($pair->term->identifier == $random_number) || ($pair->definition->identifier == $random_number))
1241  {
1242  $found = TRUE;
1243  $random_number++;
1244  }
1245  }
1246  }
1247  return $random_number;
1248  }
1249 
1256  public function setShuffle($shuffle)
1257  {
1258  switch ($shuffle)
1259  {
1260  case 0:
1261  case 1:
1262  case 2:
1263  case 3:
1264  $this->shuffle = $shuffle;
1265  break;
1266  default:
1267  $this->shuffle = 1;
1268  break;
1269  }
1270  }
1271 
1277  public function getQuestionType()
1278  {
1279  return "assMatchingQuestion";
1280  }
1281 
1287  public function getAdditionalTableName()
1288  {
1289  return "qpl_qst_matching";
1290  }
1291 
1297  public function getAnswerTableName()
1298  {
1299  return array("qpl_a_matching", "qpl_a_mterm");
1300  }
1301 
1306  public function getRTETextWithMediaObjects()
1307  {
1309  }
1310 
1314  public function &getMatchingPairs()
1315  {
1316  return $this->matchingpairs;
1317  }
1318 
1331  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
1332  {
1333  include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
1334  $solutions = $this->getSolutionValues($active_id, $pass);
1335  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
1336  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
1337  $imagepath = $this->getImagePath();
1338  $i = 1;
1339  foreach ($solutions as $solution)
1340  {
1341  $matches_written = FALSE;
1342  foreach ($this->getMatchingPairs() as $idx => $pair)
1343  {
1344  if (!$matches_written) $worksheet->writeString($startrow + $i, 1, ilExcelUtils::_convert_text($this->lng->txt("matches")));
1345  $matches_written = TRUE;
1346  if ($pair->definition->identifier == $solution["value2"])
1347  {
1348  if (strlen($pair->definition->text))
1349  {
1350  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($pair->definition->text));
1351  }
1352  else
1353  {
1354  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($pair->definition->picture));
1355  }
1356  }
1357  if ($pair->term->identifier == $solution["value1"])
1358  {
1359  if (strlen($pair->term->text))
1360  {
1361  $worksheet->writeString($startrow + $i, 2, ilExcelUtils::_convert_text($pair->term->text));
1362  }
1363  else
1364  {
1365  $worksheet->writeString($startrow + $i, 2, ilExcelUtils::_convert_text($pair->term->picture));
1366  }
1367  }
1368  }
1369  $i++;
1370  }
1371  return $startrow + $i + 1;
1372  }
1373 
1379  public function getThumbGeometry()
1380  {
1381  return $this->thumb_geometry;
1382  }
1383 
1389  public function getThumbSize()
1390  {
1391  return $this->getThumbGeometry();
1392  }
1393 
1399  public function setThumbGeometry($a_geometry)
1400  {
1401  $this->thumb_geometry = ($a_geometry < 1) ? 100 : $a_geometry;
1402  }
1403 
1407  public function rebuildThumbnails()
1408  {
1409  foreach ($this->terms as $term)
1410  {
1411  if (strlen($term->picture)) $this->generateThumbForFile($this->getImagePath(), $term->picture);
1412  }
1413  foreach ($this->definitions as $definition)
1414  {
1415  if (strlen($definition->picture)) $this->generateThumbForFile($this->getImagePath(), $definition->picture);
1416  }
1417  }
1418 
1419  public function getThumbPrefix()
1420  {
1421  return "thumb.";
1422  }
1423 
1424  protected function generateThumbForFile($path, $file)
1425  {
1426  $filename = $path . $file;
1427  if (@file_exists($filename))
1428  {
1429  $thumbpath = $path . $this->getThumbPrefix() . $file;
1430  $path_info = @pathinfo($filename);
1431  $ext = "";
1432  switch (strtoupper($path_info['extension']))
1433  {
1434  case 'PNG':
1435  $ext = 'PNG';
1436  break;
1437  case 'GIF':
1438  $ext = 'GIF';
1439  break;
1440  default:
1441  $ext = 'JPEG';
1442  break;
1443  }
1444  ilUtil::convertImage($filename, $thumbpath, $ext, $this->getThumbGeometry());
1445  }
1446  }
1447 
1452  public function toJSON()
1453  {
1454  $result = array();
1455 
1456  $result['id'] = (int) $this->getId();
1457  $result['type'] = (string) $this->getQuestionType();
1458  $result['title'] = (string) $this->getTitle();
1459  $result['question'] = $this->formatSAQuestion($this->getQuestion());
1460  $result['nr_of_tries'] = (int) $this->getNrOfTries();
1461  $result['matching_mode'] = $this->getMatchingMode();
1462  $result['shuffle'] = true;
1463  $result['feedback'] = array(
1464  'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1465  'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1466  );
1467 
1468  $terms = array();
1469  foreach ($this->getTerms() as $term)
1470  {
1471  $terms[] = array(
1472  "text" => $term->text,
1473  "id" =>(int)$term->identifier
1474  );
1475  }
1476  $result['terms'] = $terms;
1477 
1478  // alex 9.9.2010 as a fix for bug 6513 I added the question id
1479  // to the "def_id" in the array. The $pair->definition->identifier is not
1480  // unique, since it gets it value from the morder table field
1481  // this value is not changed, when a question is copied.
1482  // thus copying the same question on a page results in problems
1483  // when the second one (the copy) is answered.
1484 
1485  $definitions = array();
1486  foreach ($this->getDefinitions() as $def)
1487  {
1488  $definitions[] = array(
1489  "text" => (string) $def->text,
1490  "id" => (int) $this->getId().$def->identifier
1491  );
1492  }
1493  $result['definitions'] = $definitions;
1494 
1495  // #10353
1496  $matchings = array();
1497  foreach ($this->getMatchingPairs() as $pair)
1498  {
1499  $pid = $pair->definition->identifier;
1500  if( $this->getMatchingMode() == self::MATCHING_MODE_N_ON_N )
1501  {
1502  $pid .= '::'.$pair->term->identifier;
1503  }
1504 
1505  if( !isset($matchings[$pid]) || $matchings[$pid]["points"] < $pair->points )
1506  {
1507  $matchings[$pid] = array(
1508  "term_id" => (int) $pair->term->identifier,
1509  "def_id" => (int) $this->getId().$pair->definition->identifier,
1510  "points" => (int) $pair->points
1511  );
1512  }
1513  }
1514 
1515  $result['matchingPairs'] = array_values($matchings);
1516 
1517  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1518  $result['mobs'] = $mobs;
1519 
1520  global $lng;
1521  $lng->loadLanguageModule('assessment');
1522  $result['reset_button_label'] = $lng->txt("reset_terms");
1523 
1524  return json_encode($result);
1525  }
1526 
1527  public function supportsJavascriptOutput()
1528  {
1529  return true;
1530  }
1531 
1532  public function supportsNonJsOutput()
1533  {
1534  return false;
1535  }
1536 
1538  {
1539  $this->matchingMode = $matchingMode;
1540  }
1541 
1542  public function getMatchingMode()
1543  {
1544  return $this->matchingMode;
1545  }
1546 
1551  protected function calculateReachedPointsForSolution($found_values)
1552  {
1553  $points = 0;
1554  foreach($found_values as $definition => $terms)
1555  {
1556  foreach($terms as $term)
1557  {
1558  foreach($this->matchingpairs as $pair)
1559  {
1560  if($pair->definition->identifier == $definition && $pair->term->identifier == $term)
1561  {
1562  $points += $pair->points;
1563  }
1564  }
1565  }
1566  }
1567  return $points;
1568  }
1569 
1578  public function getOperators($expression)
1579  {
1580  require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1582  }
1583 
1588  public function getExpressionTypes()
1589  {
1590  return array(
1595  );
1596  }
1605  public function getUserQuestionResult($active_id, $pass)
1606  {
1608  global $ilDB;
1609  $result = new ilUserQuestionResult($this, $active_id, $pass);
1610 
1611  $data = $ilDB->queryF(
1612  "SELECT morder FROM qpl_a_mdef WHERE question_fi = %s ORDER BY def_id",
1613  array("integer"),
1614  array($this->getId())
1615  );
1616 
1617  $definitions = array();
1618  for($index=1; $index <= $ilDB->numRows($data); ++$index)
1619  {
1620  $row = $ilDB->fetchAssoc($data);
1621  $definitions[$row["morder"]] = $index;
1622  }
1623 
1624  $data = $ilDB->queryF(
1625  "SELECT term_id FROM qpl_a_mterm WHERE question_fi = %s ORDER BY term_id",
1626  array("integer"),
1627  array($this->getId())
1628  );
1629 
1630  $terms = array();
1631  for($index=1; $index <= $ilDB->numRows($data); ++$index)
1632  {
1633  $row = $ilDB->fetchAssoc($data);
1634  $terms[$row["term_id"]] = $index;
1635  }
1636 
1637  $data = $ilDB->queryF(
1638  "SELECT value1, value2 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = (
1639  SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s
1640  )",
1641  array("integer", "integer", "integer","integer", "integer", "integer"),
1642  array($active_id, $pass, $this->getId(), $active_id, $pass, $this->getId())
1643  );
1644 
1645  while($row = $ilDB->fetchAssoc($data))
1646  {
1647  if($row["value1"] > 0)
1648  {
1649  $result->addKeyValue($definitions[$row["value2"]],$terms[$row["value1"]]);
1650  }
1651  }
1652 
1653  $points = $this->calculateReachedPoints($active_id, $pass);
1654  $max_points = $this->getMaximumPoints();
1655 
1656  $result->setReachedPercentage(($points/$max_points) * 100);
1657 
1658  return $result;
1659  }
1660 
1669  public function getAvailableAnswerOptions($index = null)
1670  {
1671  if($index !== null)
1672  {
1673  return $this->getMatchingPair($index);
1674  }
1675  else
1676  {
1677  return $this->getMatchingPairs();
1678  }
1679  }
1680 }