protected function buildQTI($data = null) { if (!is_null($data)) { $this->data = $data; } if (empty($this->data)) { return false; } // container element and other metadata $ai = $this->initialXML(); // response declaration $rd = $ai->addChild("responseDeclaration"); $rd->addAttribute("identifier", "RESPONSE"); $rd->addAttribute("cardinality", $this->itemType() == "multipleChoice" ? "single" : "multiple"); $rd->addAttribute("baseType", "identifier"); // correct response if ($this->itemType() == "multipleResponse") { // build array of correct responses $correct = array(); if ($this->data["scoring"] == "custom") { // collect scores, sort $scores = array(); for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) { $scores[$i] = floatval($this->data["option_{$i}_score"]); } arsort($scores, SORT_NUMERIC); $keys = array_keys($scores); // using max and min choices find the optimal response for ($i = 0; $i < (isset($this->data["minchoices"]) ? $this->data["minchoices"] : 0); $i++) { $correct[] = $keys[$i]; } for (; $i < count($scores); $i++) { if (isset($this->data["maxchoices"]) && $this->data["maxchoices"] > 0 && $i >= $this->data["maxchoices"]) { break; } //stop if we've used all the choices we're allowed if ($scores[$keys[$i]] <= 0) { break; } //stop if the next best choice doesn't improve the score $correct[] = $keys[$i]; } } else { for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) { if (isset($this->data["option_{$i}_correct"])) { $correct[] = $i; } } } // add correctResponse node only if any options are correct if (!empty($correct)) { $rd->addChild("correctResponse"); foreach ($correct as $o) { $rd->correctResponse->addChild("value", "option_{$o}"); } } } else { $rd->addChild("correctResponse"); $rd->correctResponse->addChild("value", $this->data["correct"]); } // feedback outcome declaration if (isset($this->data["feedback"])) { $od = $ai->addChild("outcomeDeclaration"); $od->addAttribute("identifier", "FEEDBACK"); $od->addAttribute("cardinality", "multiple"); $od->addAttribute("baseType", "identifier"); } // score outcome declaration $od = $ai->addChild("outcomeDeclaration"); $od->addAttribute("identifier", "SCORE"); $od->addAttribute("cardinality", "single"); $od->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer"); $od->addChild("defaultValue"); $od->defaultValue->addChild("value", "0"); // item body $ib = $ai->addChild("itemBody"); // get stimulus and add to the XML tree if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) { $this->data["stimulus"] = wrapindiv($this->data["stimulus"]); // parse it as XML $stimulus = stringtoxml($this->data["stimulus"], "stimulus"); if (is_array($stimulus)) { // errors $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:"; $this->errors = array_merge($this->errors, $stimulus); } else { simplexml_append($ib, $stimulus); } } // div with class eqiat-mcr $d = $ib->addChild("div"); $d->addAttribute("class", "eqiat-mcr"); // choices $ci = $d->addChild("choiceInteraction"); $ci->addAttribute("responseIdentifier", "RESPONSE"); $ci->addAttribute("shuffle", isset($this->data["shuffle"]) ? "true" : "false"); if ($this->itemType() == "multipleChoice") { $ci->addAttribute("maxChoices", "1"); } else { if (isset($this->data["maxchoices"])) { $ci->addAttribute("maxChoices", $this->data["maxchoices"]); } if (isset($this->data["minchoices"])) { $ci->addAttribute("minChoices", $this->data["minchoices"]); } } if (isset($this->data["prompt"])) { $ci->addChild("prompt", $this->data["prompt"]); } for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) { $sc = $ci->addChild("simpleChoice", $this->data["option_{$i}_optiontext"]); $sc->addAttribute("identifier", "option_{$i}"); if (isset($this->data["shuffle"])) { $sc->addAttribute("fixed", isset($this->data["option_{$i}_fixed"]) ? "true" : "false"); } } // response processing $rp = $ai->addChild("responseProcessing"); // scoring logic if ($this->itemType() == "multipleChoice" || $this->data["scoring"] == "exact") { $rc = $rp->addChild("responseCondition"); $ri = $rc->addChild("responseIf"); if ($this->itemType() == "multipleResponse" && empty($correct)) { // multiple response in which the correct response is to tick no // boxes -- check number of responses is equal to zero $e = $ri->addChild("equal"); $e->addAttribute("toleranceMode", "exact"); $e->addChild("containerSize")->addChild("variable")->addAttribute("identifier", "RESPONSE"); $e->addChild("baseValue", "0")->addAttribute("baseType", "integer"); } else { // otherwise, we match responses to the correctResponse above $m = $ri->addChild("match"); $m->addChild("variable")->addAttribute("identifier", "RESPONSE"); $m->addChild("correct")->addAttribute("identifier", "RESPONSE"); } $sov = $ri->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", "1")->addAttribute("baseType", "integer"); $re = $rc->addChild("responseElse"); $sov = $re->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", "0")->addAttribute("baseType", "integer"); } else { // start with zero score $sov = $rp->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", "0")->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer"); // add the option's score if it was chosen for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) { if ($this->data["scoring"] == "custom" && $scores[$i] == 0) { continue; } $rc = $rp->addChild("responseCondition"); $ri = $rc->addChild("responseIf"); $c = $ri->addChild("member"); $c->addChild("baseValue", "option_{$i}")->addAttribute("baseType", "identifier"); //if this $c->addChild("variable")->addAttribute("identifier", "RESPONSE"); //is a member of this $sov = $ri->addChild("setOutcomeValue"); //then do this $sov->addAttribute("identifier", "SCORE"); $s = $sov->addChild("sum"); $s->addChild("variable")->addAttribute("identifier", "SCORE"); if ($this->data["scoring"] == "custom") { $val = $scores[$i]; } else { if (isset($this->data["option_{$i}_correct"])) { $val = 1; } else { $val = -1; } } $s->addChild("baseValue", $val)->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer"); } // minimum score if ($this->data["scoring"] == "cumulative" || $this->data["scoring"] == "custom" && is_numeric($this->data["minscore"])) { $rc = $rp->addChild("responseCondition"); $ri = $rc->addChild("responseIf"); $lt = $ri->addChild("lt"); $lt->addChild("variable")->addAttribute("identifier", "SCORE"); $lt->addChild("baseValue", $this->data["scoring"] == "custom" ? $this->data["minscore"] : 0)->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer"); $sov = $ri->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", $this->data["scoring"] == "custom" ? $this->data["minscore"] : 0)->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer"); } // maximum score if ($this->data["scoring"] == "custom" && is_numeric($this->data["maxscore"])) { $rc = $rp->addChild("responseCondition"); $ri = $rc->addChild("responseIf"); $gt = $ri->addChild("gt"); $gt->addChild("variable")->addAttribute("identifier", "SCORE"); $gt->addChild("baseValue", $this->data["maxscore"])->addAttribute("baseType", "float"); $sov = $ri->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", $this->data["maxscore"])->addAttribute("baseType", "float"); } } if (isset($this->data["feedback"])) { // initialize feedback var $sov = $rp->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "FEEDBACK"); $sov->addChild("null"); // feedback logic for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) { $rc = $rp->addChild("responseCondition"); $ri = $rc->addChild("responseIf"); if ($this->itemType() == "multipleResponse") { $c = $ri->addChild("member"); $c->addChild("baseValue", "option_{$i}")->addAttribute("baseType", "identifier"); //if this $c->addChild("variable")->addAttribute("identifier", "RESPONSE"); //is a member of this } else { $m = $ri->addChild("match"); $m->addChild("baseValue", "option_{$i}")->addAttribute("baseType", "identifier"); //if this $m->addChild("variable")->addAttribute("identifier", "RESPONSE"); //is equal to this } $sov = $ri->addChild("setOutcomeValue"); //then do this $sov->addAttribute("identifier", "FEEDBACK"); $m = $sov->addChild("multiple"); $m->addChild("variable")->addAttribute("identifier", "FEEDBACK"); $m->addChild("baseValue", "feedback_option_{$i}")->addAttribute("baseType", "identifier"); } // the feedback itself for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) { if (isset($this->data["option_{$i}_feedback_chosen"]) && !empty($this->data["option_{$i}_feedback_chosen"])) { $this->data["option_{$i}_feedback_chosen"] = wrapindiv($this->data["option_{$i}_feedback_chosen"]); $mf = $ai->addChild("modalFeedback"); $mf->addAttribute("outcomeIdentifier", "FEEDBACK"); $mf->addAttribute("identifier", "feedback_option_{$i}"); $mf->addAttribute("showHide", "show"); // parse it as XML $feedback = stringtoxml($this->data["option_{$i}_feedback_chosen"], "feedback"); if (is_array($feedback)) { // errors $this->errors[] = "Feedback is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:"; $this->errors = array_merge($this->errors, $feedback); } else { simplexml_append($mf, $feedback); } } if (isset($this->data["option_{$i}_feedback_unchosen"]) && !empty($this->data["option_{$i}_feedback_unchosen"])) { $this->data["option_{$i}_feedback_unchosen"] = wrapindiv($this->data["option_{$i}_feedback_unchosen"]); $mf = $ai->addChild("modalFeedback"); $mf->addAttribute("outcomeIdentifier", "FEEDBACK"); $mf->addAttribute("identifier", "feedback_option_{$i}"); $mf->addAttribute("showHide", "hide"); // parse it as XML $feedback = stringtoxml($this->data["option_{$i}_feedback_unchosen"], "feedback"); if (is_array($feedback)) { // errors $this->errors[] = "Feedback is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:"; $this->errors = array_merge($this->errors, $feedback); } else { simplexml_append($mf, $feedback); } } } } if (!empty($this->errors)) { return false; } // validate the QTI validateQTI($ai, $this->errors, $this->warnings, $this->messages); if (!empty($this->errors)) { return false; } return $ai; }
public function buildQTI($data = null) { if (!is_null($data)) { $this->data = $data; } if (empty($this->data)) { return false; } // container element and other metadata $ai = $this->initialXML(); // response declarations for ($g = 0; array_key_exists("gap_{$g}_response_0", $this->data); $g++) { $rd = $ai->addChild("responseDeclaration"); $rd->addAttribute("identifier", "RESPONSE_gap_{$g}"); $rd->addAttribute("cardinality", "single"); $rd->addAttribute("baseType", "string"); $m = $rd->addChild("mapping"); $m->addAttribute("defaultValue", "0"); for ($r = 0; array_key_exists("gap_{$g}_response_{$r}", $this->data); $r++) { $me = $m->addChild("mapEntry"); $me->addAttribute("mapKey", $this->data["gap_{$g}_response_{$r}"]); $me->addAttribute("mappedValue", $this->data["gap_{$g}_response_{$r}_score"]); } } // outcome declaration $od = $ai->addChild("outcomeDeclaration"); $od->addAttribute("identifier", "SCORE"); $od->addAttribute("cardinality", "single"); $od->addAttribute("baseType", "float"); $od->addChild("defaultValue"); $od->defaultValue->addChild("value", "0"); // item body $ib = $ai->addChild("itemBody"); // get stimulus and add to the XML tree if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) { $this->data["stimulus"] = wrapindiv($this->data["stimulus"]); // parse it as XML $stimulus = stringtoxml($this->data["stimulus"], "stimulus"); if (is_array($stimulus)) { // errors $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:"; $this->errors = array_merge($this->errors, $stimulus); } else { simplexml_append($ib, $stimulus); } } // div with class eqiat-te $d = $ib->addChild("div"); $d->addAttribute("class", "eqiat-te"); // body text $bt = $d->addChild("div"); $bt->addAttribute("class", "textentrytextbody"); $text = xmlspecialchars($this->data["textbody"]); $text = preg_replace('%\\n\\n+%', "</p><p>", $text); $text = preg_replace('%\\n%', "<br/>", $text); $text = "<p>" . $text . "</p>"; $g = 0; $start = 0; while (($start = strpos($text, "[", $start)) !== false) { $start = strpos($text, "["); $end = strpos($text, "]", $start); // base expected length on the longest answer plus 10% $el = 0; for ($r = 0; array_key_exists("gap_{$g}_response_{$r}", $this->data); $r++) { $el = max($el, strlen($this->data["gap_{$g}_response_{$r}"])); } $el = ceil($el * 1.1); $text = substr($text, 0, $start) . '<textEntryInteraction responseIdentifier="RESPONSE_gap_' . $g++ . '" expectedLength="' . $el . '"/>' . substr($text, $end + 1); } // parse it as XML libxml_use_internal_errors(true); $textxml = simplexml_load_string($text); if ($textxml === false) { $this->errors[] = "Text body did not convert to valid XML"; foreach (libxml_get_errors() as $error) { $this->errors[] = "Text body line " . $error->line . ", column " . $error->column . ": " . $error->message; } libxml_clear_errors(); } else { simplexml_append($bt, $textxml); } libxml_use_internal_errors(false); // response processing $rp = $ai->addChild("responseProcessing"); // set score = 0 $sov = $rp->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", "0.0")->addAttribute("baseType", "float"); for ($g = 0; array_key_exists("gap_{$g}_response_0", $this->data); $g++) { $rc = $rp->addChild("responseCondition"); // if $ri = $rc->addChild("responseIf"); // not null $ri->addChild("not")->addChild("isNull")->addChild("variable")->addAttribute("identifier", "RESPONSE_gap_{$g}"); // increment score $sov = $ri->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $s = $sov->addChild("sum"); $s->addChild("variable")->addAttribute("identifier", "SCORE"); $s->addChild("mapResponse")->addAttribute("identifier", "RESPONSE_gap_{$g}"); } if (!empty($this->errors)) { return false; } // validate the QTI validateQTI($ai, $this->errors, $this->warnings, $this->messages); if (!empty($this->errors)) { return false; } $this->qti = $ai; return $this->qti; }
/* * Question Bank */ /*------------------------------------------------------------------------------ (c) 2010 JISC-funded EASiHE project, University of Southampton Licensed under the Creative Commons 'Attribution non-commercial share alike' licence -- see the LICENCE file for more details ------------------------------------------------------------------------------*/ $errors = array(); $warnings = array(); $messages = array(); if (isset($_POST["uploaditem"])) { $output = handleupload($errors, $warnings, $messages); if ($output !== false) { list($xml, $metadata) = $output; if (validateQTI($xml, $errors, $warnings, $messages)) { $xml = simplexml_load_string($xml); // get data from QTI $identifier = (string) $xml["identifier"]; $title = (string) $xml["title"]; // see if an item with this identifier already exists in the // database $exists = itemexists($identifier); if ($exists && itemowner($identifier) != username()) { $errors[] = "The item you are trying to upload was already uploaded by a different user. You should clone it so it gets a new identifier and then try again."; } else { deposititem($xml, $metadata); if (!authoredineqiat($xml)) { $warnings[] = "This item was not authored in Eqiat. The item will still be playable but Eqiat may not be able to import it for editing."; } // collect any warnings and messages
function xmltoqtiobject($xml, &$errors, &$warnings, &$messages, $metadata = array(), $newidentifier = false) { // make sure it's valid QTI if (!validateQTI($xml, $errors, $warnings, $messages)) { $errors[] = "The assessment item found is not valid QTI"; return false; } // load to SimpleXML object $xml = simplexml_load_string($xml); // test against supported item types $items = item_types(); $scores = array(); $ai = null; foreach ($items as $item) { $score = $item->fromXML($xml); $scores[] = $score; if ($score == 255) { $ai = $item; break; } } if (is_null($ai)) { arsort($scores); $keys = array_keys($scores); if ($scores[$keys[0]] == 0) { $errors[] = "The uploaded item was not of a recognized type"; return false; } else { $ai = $items[$keys[0]]; $ai->addWarning("Item did not exactly match any of the implemented types. The closest match ({$ai->itemTypePrint()}, " . round($scores[$keys[0]] / 2.55) . "% match) was chosen."); } } // give it a new identifier if appropriate, restore manifest identifier if // no new identifier is wanted if ($newidentifier) { $ai->setQTIID(null); } else { if (array_key_exists("midentifier", $metadata)) { $ai->setMID($metadata["midentifier"]); } } // restore the metadata taken from the manifest if (array_key_exists("description", $metadata)) { $ai->data("description", $metadata["description"]); } if (array_key_exists("keywords", $metadata)) { $ai->data("keywords", implode(", ", $metadata["keywords"])); } // take the user to the main menu with the uploaded item highlighted return $ai; }
public function buildQTI($data = null) { if (!is_null($data)) { $this->data = $data; } if (empty($this->data)) { return false; } // container element and other metadata $ai = $this->initialXML(); // response declarations for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) { $rd = $ai->addChild("responseDeclaration"); $rd->addAttribute("identifier", "RESPONSE_question_{$q}"); $rd->addAttribute("cardinality", "single"); $rd->addAttribute("baseType", "identifier"); if (isset($this->data["question_{$q}_answer"])) { $rd->addChild("correctResponse")->addChild("value", "question_{$q}_" . $this->data["question_{$q}_answer"]); } } // outcome declaration $od = $ai->addChild("outcomeDeclaration"); $od->addAttribute("identifier", "SCORE"); $od->addAttribute("cardinality", "single"); $od->addAttribute("baseType", "integer"); $od->addChild("defaultValue"); $od->defaultValue->addChild("value", "0"); // item body $ib = $ai->addChild("itemBody"); // get stimulus and add to the XML tree if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) { $this->data["stimulus"] = wrapindiv($this->data["stimulus"]); // parse it as XML $stimulus = stringtoxml($this->data["stimulus"], "stimulus"); if (is_array($stimulus)) { // errors $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:"; $this->errors = array_merge($this->errors, $stimulus); } else { simplexml_append($ib, $stimulus); } } // div with class eqiat-qm $d = $ib->addChild("div"); $d->addAttribute("class", "eqiat-qm"); // questions for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) { $ci = $d->addChild("choiceInteraction"); $ci->addAttribute("maxChoices", "1"); $ci->addAttribute("shuffle", "false"); $ci->addAttribute("responseIdentifier", "RESPONSE_question_{$q}"); $ci->addChild("prompt", $this->data["question_{$q}_prompt"]); foreach (array("true", "false") as $o) { $sc = $ci->addChild("simpleChoice", $o); $sc->addAttribute("identifier", "question_{$q}_{$o}"); } } // response processing $rp = $ai->addChild("responseProcessing"); // set score = 0 $sov = $rp->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", "0")->addAttribute("baseType", "integer"); for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) { $rc = $rp->addChild("responseCondition"); // if $ri = $rc->addChild("responseIf"); // criteria for a correct answer $m = $ri->addChild("match"); $m->addChild("variable")->addAttribute("identifier", "RESPONSE_question_{$q}"); $m->addChild("correct")->addAttribute("identifier", "RESPONSE_question_{$q}"); // increment score $sov = $ri->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $s = $sov->addChild("sum"); $s->addChild("variable")->addAttribute("identifier", "SCORE"); $s->addChild("baseValue", "1")->addAttribute("baseType", "integer"); } if (!empty($this->errors)) { return false; } // validate the QTI validateQTI($ai, $this->errors, $this->warnings, $this->messages); if (!empty($this->errors)) { return false; } $this->qti = $ai; return $this->qti; }
public function buildQTI($data = null) { if (!is_null($data)) { $this->data = $data; } if (empty($this->data)) { return false; } // container element and other metadata $ai = $this->initialXML(); // response declarations for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) { $rd = $ai->addChild("responseDeclaration"); $rd->addAttribute("identifier", "RESPONSE_question_{$q}"); $rd->addAttribute("cardinality", "multiple"); $rd->addAttribute("baseType", "identifier"); // build array of correct responses $correct = array(); for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) { if (isset($this->data["question_{$q}_option_{$o}_correct"])) { $correct[] = $o; } } // add correctResponse node only if any options are correct if (!empty($correct)) { $rd->addChild("correctResponse"); foreach ($correct as $o) { $rd->correctResponse->addChild("value", "question_{$q}_option_{$o}"); } } } // outcome declaration $od = $ai->addChild("outcomeDeclaration"); $od->addAttribute("identifier", "SCORE"); $od->addAttribute("cardinality", "single"); $od->addAttribute("baseType", "integer"); $od->addChild("defaultValue"); $od->defaultValue->addChild("value", "0"); // item body $ib = $ai->addChild("itemBody"); // get stimulus and add to the XML tree if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) { $this->data["stimulus"] = wrapindiv($this->data["stimulus"]); // parse it as XML $stimulus = stringtoxml($this->data["stimulus"], "stimulus"); if (is_array($stimulus)) { // errors $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:"; $this->errors = array_merge($this->errors, $stimulus); } else { simplexml_append($ib, $stimulus); } } // div with class eqiat-emi $d = $ib->addChild("div"); $d->addAttribute("class", "eqiat-emi"); // list the options $options = ""; for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) { $options .= "<li>" . xmlspecialchars($this->data["option_{$o}_optiontext"]) . "</li>"; } simplexml_append($d, simplexml_load_string('<ol class="emioptions">' . $options . '</ol>')); // questions for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) { $ci = $d->addChild("choiceInteraction"); $ci->addAttribute("maxChoices", "0"); $ci->addAttribute("minChoices", "0"); $ci->addAttribute("shuffle", "false"); $ci->addAttribute("responseIdentifier", "RESPONSE_question_{$q}"); $ci->addChild("prompt", $this->data["question_{$q}_prompt"]); for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) { $sc = $ci->addChild("simpleChoice", chr(ord("A") + $o)); $sc->addAttribute("identifier", "question_{$q}_option_{$o}"); } } // response processing $rp = $ai->addChild("responseProcessing"); // set score = 0 $sov = $rp->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $sov->addChild("baseValue", "0")->addAttribute("baseType", "integer"); for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) { $rc = $rp->addChild("responseCondition"); // if $ri = $rc->addChild("responseIf"); // build array of correct responses $correct = array(); for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) { if (isset($this->data["question_{$q}_option_{$o}_correct"])) { $correct[] = $o; } } // criteria for a correct answer if (empty($correct)) { // multiple response in which the correct response is to tick no // boxes -- check number of responses is equal to zero $e = $ri->addChild("equal"); $e->addAttribute("toleranceMode", "exact"); $e->addChild("containerSize")->addChild("variable")->addAttribute("identifier", "RESPONSE_question_{$q}"); $e->addChild("baseValue", "0")->addAttribute("baseType", "integer"); } else { // otherwise, we match responses to the correctResponse above $m = $ri->addChild("match"); $m->addChild("variable")->addAttribute("identifier", "RESPONSE_question_{$q}"); $m->addChild("correct")->addAttribute("identifier", "RESPONSE_question_{$q}"); } // increment score $sov = $ri->addChild("setOutcomeValue"); $sov->addAttribute("identifier", "SCORE"); $s = $sov->addChild("sum"); $s->addChild("variable")->addAttribute("identifier", "SCORE"); $s->addChild("baseValue", "1")->addAttribute("baseType", "integer"); } if (!empty($this->errors)) { return false; } // validate the QTI validateQTI($ai, $this->errors, $this->warnings, $this->messages); if (!empty($this->errors)) { return false; } $this->qti = $ai; return $this->qti; }