MDL-44630: Several additions to Common Cartridge Import/Export

Changes provided by Darko Miletic and Sam Chaffee.

* Fix problem with CC import not detecting a CC package. (Sam)
* Fixed Common Cartridge not importing FIB answers. (Sam)
* Fix to CC export so that short answer pattern matching question
  in a quiz does not prevent the entire quiz from exporting. (Sam)
* Fix notice about missing index when there is no answer-specific
  feedback in short answer question during CC export. (Sam)
* Fixed Japanese characters being incorrectly encoded (garbled)
  on Common Cartridge import. (Sam)
* Changed import of CC discussions to create 'Standard General'
  forums in Moodle. (Sam)
* Fix problem with question name being replaced with question
  text on CC import of exported Moodle questions. (Sam)
* Fix CC 1.1 multiple choice question import having no correct
  answers. (Sam)
* Fixed problem with multiple choice, multiple answers allowed
  questions not exporting correct answers to CC. (Sam)
* Removed duplicate method. (Darko)
* Implemented fix that skips quiz export if it contains
  non-exportable question. (Darko)
* Fixed: Common cartridge export is extremely slow when dealing
  with lot of more than 200 files. (Darko)
* Added support for basic roles support in common cartridge. (Darko)
* Implemented folder resource export. (Darko)
* Minor fixes to adjust to the standard. (Darko)
* Added support for exporting simple fill in the blank
  (shortanswer without wildcards in Moodle). (Darko)
* Added support for exporting essay question. (Darko)
* Added support for true/false export. (Darko)
* Added support for exporting multiple response questions,
  added also category information into question metadata
  during export (Darko)
* Added support for correctly importing multiple response
  questions. (Darko)
This commit is contained in:
Mark Nielsen 2014-03-13 15:42:13 -07:00
parent 0a489777fc
commit c3d245cff3
24 changed files with 1091 additions and 225 deletions

View File

@ -40,4 +40,5 @@ require_once($CFG->dirroot .'/backup/cc/cc_lib/cc_converter_resource.php');
require_once($CFG->dirroot .'/backup/cc/cc_lib/cc_converter_quiz.php');
require_once($CFG->dirroot .'/backup/cc/cc_lib/cc_converter_page.php');
require_once($CFG->dirroot .'/backup/cc/cc_lib/cc_converter_label.php');
require_once($CFG->dirroot .'/backup/cc/cc_lib/cc_converter_folder.php');
require_once($CFG->dirroot .'/backup/cc/cc_lib/cc_convert_moodle2.php');

View File

@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package backup-convert
* @copyright 2012 Darko Miletic <dmiletic@moodlerooms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
class cc_assesment_question_essay extends cc_assesment_question_proc_base {
public function __construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir) {
parent::__construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$this->qtype = cc_qti_profiletype::essay;
$maximum_quiz_grade = (int)$this->quiz->nodeValue('/activity/quiz/grade');
$this->total_grade_value = ($maximum_quiz_grade + 1).'.0000000';
}
public function on_generate_metadata() {
parent::on_generate_metadata();
//Mark essay for manual grading
$this->qmetadata->enable_scoringpermitted();
$this->qmetadata->enable_computerscored(false);
}
public function on_generate_presentation() {
parent::on_generate_presentation();
$response_str = new cc_assesment_response_strtype();
$response_fib = new cc_assesment_render_fibtype();
$row_value = (int)$this->questions->nodeValue('plugin_qtype_essay_question//responsefieldlines', $this->question_node);
$response_fib->set_rows($row_value);
$response_str->set_render_fib($response_fib);
$this->qpresentation->set_response_str($response_str);
}
public function on_generate_response_processing() {
parent::on_generate_response_processing();
//respconditions
if (!empty($this->general_feedback)) {
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->set_title('General feedback');
$this->qresprocessing->add_respcondition($qrespcondition);
//define the condition for success
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$qother = new cc_assignment_conditionvar_othertype();
$qconditionvar->set_other($qother);
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid('general_fb');
}
}
}

View File

@ -0,0 +1,183 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package backup-convert
* @copyright 2012 Darko Miletic <dmiletic@moodlerooms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
require_once 'cc_asssesment.php';
class cc_assesment_question_sfib extends cc_assesment_question_proc_base {
public function __construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir) {
parent::__construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$this->qtype = cc_qti_profiletype::field_entry;
$this->correct_answer_node_id = $this->questions->nodeValue('plugin_qtype_truefalse_question/truefalse/trueanswer', $this->question_node);
$maximum_quiz_grade = (int)$this->quiz->nodeValue('/activity/quiz/grade');
$this->total_grade_value = ($maximum_quiz_grade + 1).'.0000000';
}
public function on_generate_metadata() {
parent::on_generate_metadata();
$category = $this->questions->nodeValue('../../name', $this->question_node);
if (!empty($category)) {
$this->qmetadata->set_category($category);
}
}
public function on_generate_presentation() {
parent::on_generate_presentation();
$response_str = new cc_assesment_response_strtype();
$response_fib = new cc_assesment_render_fibtype();
/**
* The standard requires that only rows attribute must be set
* The rest may or may not be configured. For the sake of brevity we leave it empty.
*/
$response_fib->set_rows(1);
$response_str->set_render_fib($response_fib);
$this->qpresentation->set_response_str($response_str);
}
public function on_generate_feedbacks() {
parent::on_generate_feedbacks();
//Question combined feedbacks
$responsenodes = $this->questions->nodeList('plugin_qtype_shortanswer_question//answer', $this->question_node);
$count = 0;
foreach ($responsenodes as $respnode) {
$content = $this->questions->nodeValue('feedback', $respnode);
if (empty($content)) {
continue;
}
$correct = (int)$this->questions->nodeValue('fraction', $respnode) == 1;
$answerid = (int)$this->questions->nodeValue('@id', $respnode);
$result = cc_helpers::process_linked_files( $content,
$this->manifest,
$this->rootpath,
$this->contextid,
$this->outdir);
$ident = $correct ? 'correct' : 'incorrect';
$ident .= '_'.$count.'_fb';
cc_assesment_helper::add_feedback( $this->qitem,
$result[0],
cc_qti_values::htmltype,
$ident);
pkg_resource_dependencies::instance()->add($result[1]);
if ($correct) {
$this->correct_feedbacks[$answerid] = $ident;
} else {
$this->incorrect_feedbacks[$answerid] = $ident;
}
++$count;
}
}
public function on_generate_response_processing() {
parent::on_generate_response_processing();
//respconditions
/**
* General unconditional feedback must be added as a first respcondition
* without any condition and just displayfeedback (if exists)
*/
if (!empty($this->general_feedback)) {
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->set_title('General feedback');
$this->qresprocessing->add_respcondition($qrespcondition);
$qrespcondition->enable_continue();
//define the condition for success
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$qother = new cc_assignment_conditionvar_othertype();
$qconditionvar->set_other($qother);
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid('general_fb');
}
//Answer separate conditions
$correct_responses = $this->questions->nodeList('plugin_qtype_shortanswer_question//answer[fraction=1]', $this->question_node);
$incorrect_responses = $this->questions->nodeList('plugin_qtype_shortanswer_question//answer[fraction<1]', $this->question_node);
$items = array(array($correct_responses ,$this->correct_feedbacks), array($incorrect_responses, $this->incorrect_feedbacks));
foreach ($items as $respfeed) {
foreach ($respfeed[0] as $coresponse) {
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->enable_continue();
$this->qresprocessing->add_respcondition($qrespcondition);
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$respc = $this->questions->nodeValue('answertext', $coresponse);
$resid = $this->questions->nodeValue('@id', $coresponse);
$qvarequal = new cc_assignment_conditionvar_varequaltype($respc);
$qconditionvar->set_varequal($qvarequal);
$qvarequal->set_respident('response');
$qvarequal->enable_case(false);
if (!empty($respfeed[1][$resid])) {
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid($respfeed[1][$resid]);
}
}
}
//success condition
/**
* For all question types outside of the Essay question, scoring is done in a
* single <respcondition> with a continue flag set to No. The outcome is always
* a variable named SCORE which value must be set to 100 in case of correct answer.
* Partial scores (not 0 or 100) are not supported.
*/
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->set_title('Correct');
$this->qresprocessing->add_respcondition($qrespcondition);
$qrespcondition->enable_continue(false);
$qsetvar = new cc_assignment_setvartype(100);
$qrespcondition->add_setvar($qsetvar);
//define the condition for success
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
//No case sensitivity as stated in documentation
//$case_sensitive = $this->questions->nodeValue('plugin_qtype_shortanswer_question//shortanswer/usecase', $this->question_node)==1;
foreach ($correct_responses as $coresponse) {
$respc = $this->questions->nodeValue('answertext', $coresponse);
$qvarequal = new cc_assignment_conditionvar_varequaltype($respc);
$qconditionvar->set_varequal($qvarequal);
$qvarequal->set_respident('response');
$qvarequal->enable_case(false);
}
//Add incorrect handling
$qrespcondition = new cc_assesment_respconditiontype();
//$qrespcondition->set_title('General feedback');
$this->qresprocessing->add_respcondition($qrespcondition);
$qrespcondition->enable_continue(false);
//define the condition for faliure
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$qother = new cc_assignment_conditionvar_othertype();
$qconditionvar->set_other($qother);
$qsetvar = new cc_assignment_setvartype(0);
$qrespcondition->add_setvar($qsetvar);
}
}

View File

@ -0,0 +1,176 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package backup-convert
* @copyright 2012 Darko Miletic <dmiletic@moodlerooms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
require_once 'cc_asssesment.php';
class cc_assesment_question_truefalse extends cc_assesment_question_proc_base {
public function __construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir) {
parent::__construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$this->qtype = cc_qti_profiletype::true_false;
$this->correct_answer_node_id = $this->questions->nodeValue('plugin_qtype_truefalse_question/truefalse/trueanswer', $this->question_node);
$maximum_quiz_grade = (int)$this->quiz->nodeValue('/activity/quiz/grade');
$this->total_grade_value = ($maximum_quiz_grade + 1).'.0000000';
}
public function on_generate_answers() {
//add responses holder
$qresponse_lid = new cc_response_lidtype();
$this->qresponse_lid = $qresponse_lid;
$this->qpresentation->set_response_lid($qresponse_lid);
$qresponse_choice = new cc_assesment_render_choicetype();
$qresponse_lid->set_render_choice($qresponse_choice);
//Mark that question has only one correct answer -
//which applies for multiple choice and yes/no questions
$qresponse_lid->set_rcardinality(cc_qti_values::Single);
//are we to shuffle the responses?
$shuffle_answers = (int)$this->quiz->nodeValue('/activity/quiz/shuffleanswers') > 0;
$qresponse_choice->enable_shuffle($shuffle_answers);
$answerlist = array();
$qa_responses = $this->questions->nodeList('plugin_qtype_truefalse_question/answers/answer', $this->question_node);
foreach ($qa_responses as $node) {
$answer_content = $this->questions->nodeValue('answertext', $node);
$id = ((int)$this->questions->nodeValue('@id', $node) == $this->correct_answer_node_id);
$qresponse_label = cc_assesment_helper::add_answer( $qresponse_choice,
$answer_content,
cc_qti_values::htmltype);
$answer_ident = strtolower(trim($answer_content));
$qresponse_label->set_ident($answer_ident);
$feedback_ident = ($id) ? 'correct_fb' : 'incorrect_fb';
if (empty($this->correct_answer_ident) && $id) {
$this->correct_answer_ident = $answer_ident;
}
//add answer specific feedbacks if not empty
$content = $this->questions->nodeValue('feedback', $node);
if (!empty($content)) {
$result = cc_helpers::process_linked_files( $content,
$this->manifest,
$this->rootpath,
$this->contextid,
$this->outdir);
cc_assesment_helper::add_feedback( $this->qitem,
$result[0],
cc_qti_values::htmltype,
$feedback_ident);
pkg_resource_dependencies::instance()->add($result[1]);
$answerlist[$answer_ident] = $feedback_ident;
}
}
$this->answerlist = $answerlist;
}
public function on_generate_response_processing() {
parent::on_generate_response_processing();
//respconditions
/**
* General unconditional feedback must be added as a first respcondition
* without any condition and just displayfeedback (if exists)
*/
if (!empty($this->general_feedback)) {
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->set_title('General feedback');
$this->qresprocessing->add_respcondition($qrespcondition);
$qrespcondition->enable_continue();
//define the condition for success
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$qother = new cc_assignment_conditionvar_othertype();
$qconditionvar->set_other($qother);
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid('general_fb');
}
//success condition
/**
* For all question types outside of the Essay question, scoring is done in a
* single <respcondition> with a continue flag set to No. The outcome is always
* a variable named SCORE which value must be set to 100 in case of correct answer.
* Partial scores (not 0 or 100) are not supported.
*/
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->set_title('Correct');
$this->qresprocessing->add_respcondition($qrespcondition);
$qrespcondition->enable_continue(false);
$qsetvar = new cc_assignment_setvartype(100);
$qrespcondition->add_setvar($qsetvar);
//define the condition for success
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
//TODO: recheck this
$qvarequal = new cc_assignment_conditionvar_varequaltype($this->correct_answer_ident);
$qconditionvar->set_varequal($qvarequal);
$qvarequal->set_respident($this->qresponse_lid->get_ident());
if (array_key_exists($this->correct_answer_ident, $this->answerlist)) {
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid($this->answerlist[$this->correct_answer_ident]);
}
foreach ($this->correct_feedbacks as $ident) {
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid($ident);
}
//rest of the conditions
foreach ($this->answerlist as $ident => $refid) {
if ($ident == $this->correct_answer_ident) {
continue;
}
$qrespcondition = new cc_assesment_respconditiontype();
$this->qresprocessing->add_respcondition($qrespcondition);
$qsetvar = new cc_assignment_setvartype(0);
$qrespcondition->add_setvar($qsetvar);
//define the condition for fail
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$qvarequal = new cc_assignment_conditionvar_varequaltype($ident);
$qconditionvar->set_varequal($qvarequal);
$qvarequal->set_respident($this->qresponse_lid->get_ident());
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid($refid);
foreach ($this->incorrect_feedbacks as $ident) {
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid($ident);
}
}
}
}

View File

@ -698,26 +698,30 @@ class cc_assignment_conditionvar_varsubstringtype extends cc_assignment_conditio
class cc_assignment_conditionvar_andtype extends cc_question_metadata_base {
protected $not = null;
protected $varequal = null;
protected $nots = array();
protected $varequals = array();
public function set_not(cc_assignment_conditionvar_varequaltype $object) {
$this->not = $object;
$this->nots[] = $object;
}
public function set_varequal(cc_assignment_conditionvar_varequaltype $object) {
$this->varequal = $object;
$this->varequals[] = $object;
}
public function generate(XMLGenericDocument &$doc, DOMNode &$item, $namespace) {
$node = $doc->append_new_element_ns($item, $namespace, cc_qti_tags::and_);
if (!empty($this->not)) {
$not = $doc->append_new_element_ns($node, $namespace, cc_qti_tags::not_);
$this->not->generate($doc, $not, $namespace);
if (!empty($this->nots)) {
foreach ($this->nots as $notv) {
$not = $doc->append_new_element_ns($node, $namespace, cc_qti_tags::not_);
$notv->generate($doc, $not, $namespace);
}
}
if (!empty($this->varequal)) {
$this->varequal->generate($doc, $node, $namespace);
if (!empty($this->varequals)) {
foreach ($this->varequals as $varequal) {
$varequal->generate($doc, $node, $namespace);
}
}
}
}
@ -733,9 +737,9 @@ class cc_assignment_conditionvar extends cc_question_metadata_base {
*/
protected $other = null;
/**
* @var cc_assignment_conditionvar_varequaltype
* @var array
*/
protected $varequal = null;
protected $varequal = array();
/**
* @var cc_assignment_conditionvar_varsubstringtype
*/
@ -750,7 +754,7 @@ class cc_assignment_conditionvar extends cc_question_metadata_base {
}
public function set_varequal(cc_assignment_conditionvar_varequaltype $object) {
$this->varequal = $object;
$this->varequal[] = $object;
}
public function set_varsubstring(cc_assignment_conditionvar_varsubstringtype $object) {
@ -769,7 +773,9 @@ class cc_assignment_conditionvar extends cc_question_metadata_base {
}
if (!empty($this->varequal)) {
$this->varequal->generate($doc, $node, $namespace);
foreach ($this->varequal as $varequal) {
$varequal->generate($doc, $node, $namespace);
}
}
if (!empty($this->varsubstring)) {
@ -1895,8 +1901,20 @@ abstract class cc_assesment_helper {
return $qresponse_label;
}
public static function add_response_condition() {
public static function add_response_condition($node, $title, $ident, $feedback_refid, $respident) {
$qrespcondition = new cc_assesment_respconditiontype();
$node->add_respcondition($qrespcondition);
//define rest of the conditions
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$qvarequal = new cc_assignment_conditionvar_varequaltype($ident);
$qvarequal->enable_case();
$qconditionvar->set_varequal($qvarequal);
$qvarequal->set_respident($respident);
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid($feedback_refid);
}
public static function add_assesment_description($rt, $content, $contenttype) {
@ -1913,6 +1931,27 @@ abstract class cc_assesment_helper {
$rt->set_rubric($activity_rubric);
}
public static function add_respcondition($node, $title, $feedback_refid, $grade_value = null, $continue = false ) {
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->set_title($title);
$node->add_respcondition($qrespcondition);
$qrespcondition->enable_continue($continue);
//Add setvar if grade present
if ($grade_value !== null) {
$qsetvar = new cc_assignment_setvartype($grade_value);
$qrespcondition->add_setvar($qsetvar);
}
//define the condition for success
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
$qother = new cc_assignment_conditionvar_othertype();
$qconditionvar->set_other($qother);
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
$qdisplayfeedback->set_linkrefid($feedback_refid);
}
/**
*
* Enter description here ...
@ -1932,7 +1971,8 @@ abstract class cc_assesment_helper {
}
pkg_resource_dependencies::instance()->reset();
$questioncount = 0;
$questionforexport = 0;
$qids = explode(',', $qdoc->nodeValue('/activity/quiz/questions'));
foreach ($qids as $value) {
if (intval($value) == 0) {
@ -1942,6 +1982,7 @@ abstract class cc_assesment_helper {
if (empty($question_node)) {
continue;
}
++$questionforexport;
//process question
//question type
$qtype = $questions->nodeValue('qtype', $question_node);
@ -1949,15 +1990,43 @@ abstract class cc_assesment_helper {
switch ($qtype) {
case 'multichoice':
$single_correct_answer = (int)$questions->nodeValue('plugin_qtype_multichoice_question/multichoice/single', $question_node) > 0;
//TODO: Add checking for the nunmber of valid responses
//If question is marked as multi response but contains only one valid answer it
//should be handle as single response - classic multichoice
if ($single_correct_answer) {
$question_processor = new cc_assesment_question_multichoice($qdoc, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$question_processor->generate();
} else {
//TODO: implement
$question_processor = new cc_assesment_question_multichoice_multiresponse($qdoc, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
}
$question_processor->generate();
++$questioncount;
break;
case 'truefalse':
$question_processor = new cc_assesment_question_truefalse($qdoc, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$question_processor->generate();
++$questioncount;
break;
case 'essay':
$question_processor = new cc_assesment_question_essay($qdoc, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$question_processor->generate();
++$questioncount;
break;
case 'shortanswer':
//This is rather ambiguos since shortanswer supports partial pattern match
//In order to detect pattern match we need to scan for all the responses
//if at least one of the responses uses wildcards it should be treated as
//pattern match, otherwise it should be simple fill in the blank
if (self::has_matching_element($questions, $question_node)) {
//$question_processor = new cc_assesment_question_patternmatch($qdoc, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$questionforexport--;
} else {
$question_processor = new cc_assesment_question_sfib($qdoc, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
}
if (!empty($question_processor)) {
$question_processor->generate();
++$questioncount;
}
;
break;
default:
;
break;
@ -1966,7 +2035,28 @@ abstract class cc_assesment_helper {
}
//return dependencies
return pkg_resource_dependencies::instance()->get_deps();
return ($questioncount == 0) || ($questioncount != $questionforexport)?
false: pkg_resource_dependencies::instance()->get_deps();
}
/**
*
* Checks if question has matching element
* @param XMLGenericDocument $questions
* @param object $question_node
* @return bool
*/
public static function has_matching_element(XMLGenericDocument $questions, $question_node) {
$answers = $questions->nodeList('plugin_qtype_shortanswer_question//answertext', $question_node);
$result = false;
foreach ($answers as $answer) {
$prepare = str_replace('\*', '\#', $answer->nodeValue);
$result = (strpos($prepare, '*') !== false);
if ($result) {
break;
}
}
return $result;
}
}
@ -2085,6 +2175,11 @@ class cc_assesment_question_proc_base {
if ($weighting_value > 1) {
$this->qmetadata->set_weighting($weighting_value);
}
//Get category
$question_category = $this->questions->nodeValue('../../name', $this->question_node);
if (!empty($question_category)) {
$this->qmetadata->set_category($question_category);
}
$rts = new cc_assesment_itemmetadata();
$rts->add_metadata($this->qmetadata);
$this->qitem->set_itemmetadata($rts);
@ -2169,14 +2264,14 @@ class cc_assesment_question_multichoice extends cc_assesment_question_proc_base
$this->qtype = cc_qti_profiletype::multiple_choice;
/**
*
* What is needed is a maximum grade value taken from the answer fraction
* It is supposed to always be between 1 and 0 in decimal representation,
* however that is not always the case so a change in test was needed
* but since we support here one correct answer type
* correct answer would always have to be 1
*/
$correct_answer_node = $this->questions->node("plugin_qtype_multichoice_question/answers/answer[fraction!=0.0000000]", $this->question_node);
*
* What is needed is a maximum grade value taken from the answer fraction
* It is supposed to always be between 1 and 0 in decimal representation,
* however that is not always the case so a change in test was needed
* but since we support here one correct answer type
* correct answer would always have to be 1
*/
$correct_answer_node = $this->questions->node("plugin_qtype_multichoice_question/answers/answer[fraction > 0]", $this->question_node);
if (empty($correct_answer_node)) {
throw new RuntimeException('No correct answer!');
}
@ -2362,3 +2457,193 @@ class cc_assesment_question_multichoice extends cc_assesment_question_proc_base
}
}
}
class cc_assesment_question_multichoice_multiresponse extends cc_assesment_question_proc_base {
/**
* @var DOMNodeList
*/
protected $correct_answers = null;
public function __construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir) {
parent::__construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
$this->qtype = cc_qti_profiletype::multiple_response;
$correct_answer_nodes = $this->questions->nodeList("plugin_qtype_multichoice_question/answers/answer[fraction > 0]", $this->question_node);
if ($correct_answer_nodes->length == 0) {
throw new RuntimeException('No correct answer!');
}
$this->correct_answers = $correct_answer_nodes;
//$this->correct_answer_node_id = $this->questions->nodeValue('@id', $correct_answer_node);
$maximum_quiz_grade = (int)$this->quiz->nodeValue('/activity/quiz/grade');
$this->total_grade_value = ($maximum_quiz_grade + 1).'.0000000';
}
public function on_generate_answers() {
//add responses holder
$qresponse_lid = new cc_response_lidtype();
$this->qresponse_lid = $qresponse_lid;
$this->qpresentation->set_response_lid($qresponse_lid);
$qresponse_choice = new cc_assesment_render_choicetype();
$qresponse_lid->set_render_choice($qresponse_choice);
//Mark that question has more than one correct answer
$qresponse_lid->set_rcardinality(cc_qti_values::Multiple);
//are we to shuffle the responses?
$shuffle_answers = (int)$this->quiz->nodeValue('/activity/quiz/shuffleanswers') > 0;
$qresponse_choice->enable_shuffle($shuffle_answers);
$answerlist = array();
$qa_responses = $this->questions->nodeList('plugin_qtype_multichoice_question/answers/answer', $this->question_node);
foreach ($qa_responses as $node) {
$answer_content = $this->questions->nodeValue('answertext', $node);
$answer_grade_fraction = (float)$this->questions->nodeValue('fraction', $node);
$result = cc_helpers::process_linked_files( $answer_content,
$this->manifest,
$this->rootpath,
$this->contextid,
$this->outdir);
$qresponse_label = cc_assesment_helper::add_answer( $qresponse_choice,
$result[0],
cc_qti_values::htmltype);
pkg_resource_dependencies::instance()->add($result[1]);
$answer_ident = $qresponse_label->get_ident();
$feedback_ident = $answer_ident.'_fb';
//add answer specific feedbacks if not empty
$content = $this->questions->nodeValue('feedback', $node);
if (!empty($content)) {
$result = cc_helpers::process_linked_files( $content,
$this->manifest,
$this->rootpath,
$this->contextid,
$this->outdir);
cc_assesment_helper::add_feedback( $this->qitem,
$result[0],
cc_qti_values::htmltype,
$feedback_ident);
pkg_resource_dependencies::instance()->add($result[1]);
}
$answerlist[$answer_ident] = array($feedback_ident, ($answer_grade_fraction > 0));
}
$this->answerlist = $answerlist;
}
public function on_generate_feedbacks() {
parent::on_generate_feedbacks();
//Question combined feedbacks
$correct_question_fb = $this->questions->nodeValue('plugin_qtype_multichoice_question/multichoice/correctfeedback', $this->question_node);
$incorrect_question_fb = $this->questions->nodeValue('plugin_qtype_multichoice_question/multichoice/incorrectfeedback', $this->question_node);
if (empty($correct_question_fb)) {
//Hardcode some text for now
$correct_question_fb = 'Well done!';
}
if (empty($incorrect_question_fb)) {
//Hardcode some text for now
$incorrect_question_fb = 'Better luck next time!';
}
$proc = array('correct_fb' => $correct_question_fb, 'incorrect_fb' => $incorrect_question_fb);
foreach ($proc as $ident => $content) {
if (empty($content)) {
continue;
}
$result = cc_helpers::process_linked_files( $content,
$this->manifest,
$this->rootpath,
$this->contextid,
$this->outdir);
cc_assesment_helper::add_feedback( $this->qitem,
$result[0],
cc_qti_values::htmltype,
$ident);
pkg_resource_dependencies::instance()->add($result[1]);
if ($ident == 'correct_fb') {
$this->correct_feedbacks[$ident] = $ident;
} else {
$this->incorrect_feedbacks[$ident] = $ident;
}
}
}
public function on_generate_response_processing() {
parent::on_generate_response_processing();
//respconditions
/**
* General unconditional feedback must be added as a first respcondition
* without any condition and just displayfeedback (if exists)
*/
cc_assesment_helper::add_respcondition( $this->qresprocessing,
'General feedback',
$this->general_feedback,
null,
true
);
//success condition
/**
* For all question types outside of the Essay question, scoring is done in a
* single <respcondition> with a continue flag set to No. The outcome is always
* a variable named SCORE which value must be set to 100 in case of correct answer.
* Partial scores (not 0 or 100) are not supported.
*/
$qrespcondition = new cc_assesment_respconditiontype();
$qrespcondition->set_title('Correct');
$this->qresprocessing->add_respcondition($qrespcondition);
$qrespcondition->enable_continue(false);
$qsetvar = new cc_assignment_setvartype(100);
$qrespcondition->add_setvar($qsetvar);
//define the condition for success
$qconditionvar = new cc_assignment_conditionvar();
$qrespcondition->set_conditionvar($qconditionvar);
//create root and condition
$qandcondition = new cc_assignment_conditionvar_andtype();
$qconditionvar->set_and($qandcondition);
foreach ($this->answerlist as $ident => $refid) {
$qvarequal = new cc_assignment_conditionvar_varequaltype($ident);
$qvarequal->enable_case();
if ($refid[1]) {
$qandcondition->set_varequal($qvarequal);
} else {
$qandcondition->set_not($qvarequal);
}
$qvarequal->set_respident($this->qresponse_lid->get_ident());
}
$qdisplayfeedback = new cc_assignment_displayfeedbacktype();
$qrespcondition->add_displayfeedback($qdisplayfeedback);
$qdisplayfeedback->set_feedbacktype(cc_qti_values::Response);
//TODO: this needs to be fixed
reset($this->correct_feedbacks);
$ident = key($this->correct_feedbacks);
$qdisplayfeedback->set_linkrefid($ident);
//rest of the conditions
foreach ($this->answerlist as $ident => $refid) {
cc_assesment_helper::add_response_condition( $this->qresprocessing,
'Incorrect feedback',
$refid[0],
$this->general_feedback,
$this->qresponse_lid->get_ident()
);
}
//Final element for incorrect feedback
reset($this->incorrect_feedbacks);
$ident = key($this->incorrect_feedbacks);
cc_assesment_helper::add_respcondition( $this->qresprocessing,
'Incorrect feedback',
$ident,
0
);
}
}

View File

@ -0,0 +1,48 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package backup-convert
* @subpackage cc-library
* @copyright 2012 Darko Miletic <dmiletic@moodlerooms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once 'cc_converters.php';
require_once 'cc_general.php';
class cc_converter_folder extends cc_converter {
public function __construct(cc_i_item &$item, cc_i_manifest &$manifest, $rootpath, $path){
$this->defaultfile = 'folder.xml';
parent::__construct($item, $manifest, $rootpath, $path);
}
public function convert($outdir) {
$resitem = new cc_item();
$resitem->title = $this->doc->nodeValue('/activity/folder/name');
$this->item->add_child_item($resitem);
$contextid = $this->doc->nodeValue('/activity/@contextid');
cc_helpers::handle_static_content($this->manifest,
$this->rootpath,
$contextid,
$outdir);
return true;
}
}

View File

@ -3,6 +3,9 @@
require_once 'cc_converters.php';
require_once 'cc_general.php';
require_once 'cc_asssesment.php';
require_once 'cc_assesment_truefalse.php';
require_once 'cc_assesment_essay.php';
require_once 'cc_assesment_sfib.php';
class cc_converter_quiz extends cc_converter {
@ -59,6 +62,11 @@ class cc_converter_quiz extends cc_converter {
$this->rootpath,
$contextid,
$outdir);
if ($ndeps === false) {
//No exportable questions in quizz or quizz has no questions
//so just skip it
return true;
}
//store any additional dependencies
$deps = array_merge($result[1], $ndeps);

View File

@ -52,6 +52,9 @@ class cc_converter_resource extends cc_converter {
$resitem->title = $title;
$this->item->add_child_item($resitem);
//checking the visibility
$this->manifest->update_instructoronly($resvalue, !$this->is_visible());
return true;
}

View File

@ -110,6 +110,21 @@ abstract class cc_converter {
*/
abstract public function convert($outdir);
/**
*
* Is the element visible in the course?
* @throws RuntimeException
* @return bool
*/
protected function is_visible() {
$tdoc = new XMLGenericDocument();
if (!$tdoc->load($this->path . DIRECTORY_SEPARATOR . 'module.xml')) {
throw new RuntimeException('File does not exist!');
}
$visible = (int)$tdoc->nodeValue('/module/visible');
return ($visible > 0);
}
/**
*
* Stores any files that need to be stored
@ -120,6 +135,7 @@ abstract class cc_converter {
if ( $doc->saveTo($rtp) ) {
$resource = new cc_resource($rdir->rootdir(), $this->defaultname, $rdir->dirname(true));
$resource->dependency = empty($deps) ? array() : $deps;
$resource->instructoronly = !$this->is_visible();
$res = $this->manifest->add_resource($resource, null, $this->cc_type);
$resitem = new cc_item();
$resitem->attach_resource($res[0]);

View File

@ -118,11 +118,9 @@ class cc_manifest extends XMLGenericDocument implements cc_i_manifest {
"[@identifier='".
$identifier.
"']/imscc:file");
$dnode = $this->doc->createElementNS($this->ccnamespaces['imscc'], "metadata");
$metanode->insertBefore($dnode,$metanode2);
$this->activemanifest->create_metadata_resource_node($met,$this->doc,$dnode);
$nspaces = $this->activemanifest->get_cc_namespaces();
$dnode = $this->append_new_element_ns($metanode2, $nspaces['imscc'], 'metadata');
$this->activemanifest->create_metadata_resource_node($met, $this->doc, $dnode);
}
@ -147,7 +145,8 @@ class cc_manifest extends XMLGenericDocument implements cc_i_manifest {
$filename.
"']");
$dnode = $this->doc->createElementNS($this->ccnamespaces['imscc'], "metadata");
$nspaces = $this->activemanifest->get_cc_namespaces();
$dnode = $this->doc->createElementNS($nspaces['imscc'], "metadata");
$metanode->appendChild($dnode);
@ -240,24 +239,15 @@ class cc_manifest extends XMLGenericDocument implements cc_i_manifest {
throw new Exception("Type invalid...");
}
if (is_null($res)){
if ($res == null){
throw new Exception('Invalid Resource or dont give it');
}
$rst = null;
if (is_string($res)){
$rst = new cc_resource($this->filePath(), $res);
if (is_string($identifier)){
$rst->identifier = $identifier;
}
} else {
$rst = $res;
}
$rst = $res;
//TODO: This has to be reviewed since it does not handle properly mutiple file
// dependencies
if (is_object($identifier)) {
$this->activemanifest->create_resource_node($rst,$this->doc,$identifier);
$this->activemanifest->create_resource_node($rst, $this->doc, $identifier);
} else {
$nresnode = null;
@ -265,50 +255,32 @@ class cc_manifest extends XMLGenericDocument implements cc_i_manifest {
if (!cc_helpers::is_html($rst->filename)) {
$rst->href = null;
}
$this->activemanifest->create_resource_node($rst,$this->doc,$nresnode);
for ($i = 1 ; $i < count ($rst->files); $i++){
$ident = $this->get_identifier_by_filename($rst->files[$i]);
if(empty($ident)){
$newres = new cc_resource($rst->manifestroot,$rst->files[$i],false);
if (!empty($newres)) {
if (!cc_helpers::is_html($rst->files[$i])) {
$newres->href = null;
}
$newres->type = 'webcontent';
$this->activemanifest->create_resource_node($newres,$this->doc,$nresnode);
$this->activemanifest->create_resource_node($rst, $this->doc, $nresnode);
foreach ($rst->files as $file) {
$ident = $this->get_identifier_by_filename($file);
if($ident == null){
$newres = new cc_resource($rst->manifestroot, $file);
if (!cc_helpers::is_html($file)) {
$newres->href = null;
}
}
}
foreach ($this->activemanifest->resources as $k => $v){
($k);
$depen = $this->check_if_exist_in_other($v->files[0]);
if (!empty($depen)){
$this->replace_file_x_dependency($depen,$v->files[0]);
// coloca aca como type = webcontent porque son archivos dependientes
// quizas aqui habria q ver de que type es el que vino y segun eso, ponerlo
// en associatedcontent o en webcontent
$v->type = 'webcontent';
$newres->type = 'webcontent';
$this->activemanifest->create_resource_node($newres, $this->doc, $nresnode);
}
}
}
$tmparray = array($rst->identifier,$rst->files[0]);
$tmparray = array($rst->identifier, $rst->files[0]);
return $tmparray;
}
private function check_if_exist_in_other($name){
private function check_if_exist_in_other($name, $identifier){
$status = array();
foreach ($this->activemanifest->resources as $key => $value){
($key);
for ($i=1; $i< count($value->files); $i++){
if ($name == $value->files[$i]){
array_push($status,$value->identifier);
}
foreach ($this->activemanifest->resources as $value){
if (($value->identifier != $identifier) && isset($value->files[$name])) {
$status[] = $value->identifier;
}
}
return $status;
@ -332,11 +304,8 @@ class cc_manifest extends XMLGenericDocument implements cc_i_manifest {
private function get_identifier_by_filename($name){
$result = null;
foreach ($this->activemanifest->resources as $key => $value) {
if ($name == $value->files[0]){
$result = $key;
break;
}
if (isset($this->activemanifest->resources_ind[$name])) {
$result = $this->activemanifest->resources_ind[$name];
}
return $result;
}
@ -353,6 +322,12 @@ class cc_manifest extends XMLGenericDocument implements cc_i_manifest {
}
public function update_instructoronly($identifier, $value = false) {
if (isset($this->activemanifest->resources[$identifier])) {
$resource = $this->activemanifest->resources[$identifier];
$resource->instructoronly = $value;
}
}
/**
* Append the resources nodes in the Manifest
@ -365,12 +340,31 @@ class cc_manifest extends XMLGenericDocument implements cc_i_manifest {
"']/imscc:resources";
$resnode = $this->node($resnodestr);
foreach ($this->activemanifest->resources as $key => $node) {
($key);
$resnode->appendChild($this->activemanifest->create_resource_node($node,$this->doc,null));
foreach ($this->activemanifest->resources as $k => $v){
($k);
$depen = $this->check_if_exist_in_other($v->files[0], $v->identifier);
if (!empty($depen)){
$this->replace_file_x_dependency($depen,$v->files[0]);
// coloca aca como type = webcontent porque son archivos dependientes
// quizas aqui habria q ver de que type es el que vino y segun eso, ponerlo
// en associatedcontent o en webcontent
$v->type = 'webcontent';
}
}
return $resnode;
foreach ($this->activemanifest->resources as $node) {
$rnode = $this->activemanifest->create_resource_node($node, $this->doc, null);
$resnode->appendChild($rnode);
if ($node->instructoronly) {
$metafileceduc = new cc_metadata_resouce_educational();
$metafileceduc->set_value(intended_user_role::INSTRUCTOR);
$metafile = new cc_metadata_resouce();
$metafile->add_metadata_resource_educational($metafileceduc);
$this->activemanifest->create_metadata_educational($metafile, $this->doc, $rnode);
}
}
return $resnode;
}
}

View File

@ -31,6 +31,7 @@ class page11_resurce_file extends general_cc_file {
protected $intro = null;
public function set_content($value) {
//we are not cleaning up this one on purporse.
$this->content = $value;
}

View File

@ -94,6 +94,7 @@ class cc_resource implements cc_i_resource {
public $isempty = null;
public $manifestroot = null;
public $folder = null;
public $instructoronly = false;
private $throwonerror = true;

View File

@ -271,7 +271,7 @@ abstract class cc_helpers {
$outdir);
$replaceprefix = $webcontent ? '' : '$IMS-CC-FILEBASE$';
foreach ($lfiles as $lfile) {
if (array_key_exists($lfile, $files)) {
if (isset($files[$lfile])) {
$filename = str_replace('%2F', '/',rawurlencode($lfile));
$content = str_replace('@@PLUGINFILE@@'.$filename,
$replaceprefix.'../'.$files[$lfile][1],
@ -413,11 +413,7 @@ class pkg_static_resources {
}
public function get_identifier($location) {
$result = false;
if (array_key_exists($location, $this->values)) {
$result = $this->values[$location];
}
return $result;
return isset($this->values[$location]) ? $this->values[$location] : false;
}
public function reset() {

View File

@ -96,10 +96,8 @@ class cc_version1 extends cc_version_base {
//add all namespaces
foreach ($this->ccnamespaces as $key => $value) {
if ($key != 'lom' ){
$dummy_attr = $key.":dummy";
$doc->createAttributeNS($value,$dummy_attr);
}
$dummy_attr = $key.":dummy";
$doc->createAttributeNS($value,$dummy_attr);
}
// add location of schemas
@ -237,6 +235,7 @@ class cc_version1 extends cc_version_base {
$dnode->appendChild($nd);
}
$this->resources[$res->identifier] = $res;
$this->resources_ind[$res->files[0]] = $res->identifier;
foreach ($res->dependency as $dependency){
$nd = $doc->createElementNS($this->ccnamespaces['imscc'],'dependency');
@ -543,7 +542,7 @@ class cc_version1 extends cc_version_base {
* @param object $xmlnode
* @return DOMNode
*/
protected function create_metadata_educational ($met,DOMDocument &$doc, $xmlnode){
public function create_metadata_educational ($met,DOMDocument &$doc, $xmlnode){
$nd = $doc->createElementNS($this->ccnamespaces['lom'],'educational');
$nd2 = $doc->createElementNS($this->ccnamespaces['lom'],'intendedEndUserRole');
$nd3 = $doc->createElementNS($this->ccnamespaces['voc'],'vocabulary');

View File

@ -90,4 +90,33 @@ class cc_version11 extends cc_version1 {
}
}
/**
* Create Education Metadata (How To)
*
* @param object $met
* @param DOMDocument $doc
* @param object $xmlnode
* @return DOMNode
*/
public function create_metadata_educational ($met, DOMDocument &$doc, $xmlnode){
$metadata = $doc->createElementNS($this->ccnamespaces['imscc'] ,'metadata');
$xmlnode->insertBefore($metadata, $xmlnode->firstChild);
$lom = $doc->createElementNS($this->ccnamespaces['lom'] ,'lom');
$metadata->appendChild($lom);
$educational = $doc->createElementNS($this->ccnamespaces['lom'] ,'educational');
$lom->appendChild($educational);
foreach ($met->arrayeducational as $name => $value) {
!is_array($value)?$value =array($value):null;
foreach ($value as $v){
$userrole = $doc->createElementNS($this->ccnamespaces['lom'],'intendedEndUserRole');
$educational->appendChild($userrole);
$nd4 = $doc->createElementNS($this->ccnamespaces['lom'], 'source', 'IMSGLC_CC_Rolesv1p1');
$nd5 = $doc->createElementNS($this->ccnamespaces['lom'], 'value', $v[0]);
$userrole->appendChild($nd4);
$userrole->appendChild($nd5);
}
}
return $metadata;
}
}

View File

@ -35,6 +35,7 @@ abstract class cc_version_base {
protected $manifestID = null;
protected $organizationid = null;
public $resources = null;
public $resources_ind = null;
protected $metadata = null;
public $organizations = null;
protected $base = null;

View File

@ -51,62 +51,25 @@ function is_url($url) {
return $result;
}
function GetDepFiles($manifestroot, $fname,$folder,&$filenames) {
$extension = pathinfo($fname, PATHINFO_EXTENSION);
$filenames = array();
$dcx = new XMLGenericDocument();
$result = true;
switch ($extension){
case 'xml':
$result = @$dcx->loadXMLFile($manifestroot.$folder.$fname);
if (!$result) {
$result = @$dcx->loadXMLFile($manifestroot.DIRECTORY_SEPARATOR.$folder.DIRECTORY_SEPARATOR.$fname);
}
GetDepFilesXML($manifestroot, $fname,$filenames,$dcx, $folder);
break;
case 'html':
case 'htm':
$result = @$dcx->loadHTMLFile($manifestroot.$folder.$fname);
if (!$result) {
$result = @$dcx->loadHTMLFile($manifestroot.DIRECTORY_SEPARATOR.$folder.DIRECTORY_SEPARATOR.$fname);
}
GetDepFilesHTML($manifestroot, $fname,$filenames,$dcx, $folder);
break;
function GetDepFiles($manifestroot, $fname, $folder, &$filenames) {
static $types = array('xhtml' => true, 'html' => true, 'htm' => true);
$extension = strtolower(trim(pathinfo($fname, PATHINFO_EXTENSION)));
$filenames = array();
if (isset($types[$extension])) {
$dcx = new XMLGenericDocument();
$filename = $manifestroot.$folder.$fname;
if (!file_exists($filename)) {
$filename = $manifestroot.DIRECTORY_SEPARATOR.$folder.DIRECTORY_SEPARATOR.$fname;
}
if (file_exists($filename)) {
$res = $dcx->loadHTMLFile($filename);
if ($res) {
GetDepFilesHTML($manifestroot, $fname, $filenames, $dcx, $folder);
}
}
}
return $result;
}
function GetDepFilesXML ($manifestroot, $fname,&$filenames,&$dcx, $folder){
$nlist = $dcx->nodeList("//img/@src | //attachments/attachment/@href | //link/@href | //script/@src");
$css_obj_array = array();
foreach ($nlist as $nl) {
$item = $folder.$nl->nodeValue;
$path_parts = pathinfo($item);
$fname = $path_parts['basename'];
$ext = array_key_exists('extension',$path_parts) ? $path_parts['extension'] : '';
if (!is_url($nl->nodeValue)) {
//$file = $folder.$nl->nodeValue; // DEPENDERA SI SE QUIERE Q SEA RELATIVO O ABSOLUTO
$file = $nl->nodeValue;
toNativePath($file);
$filenames[]=$file;
}
}
$dcx->registerNS('qti','http://www.imsglobal.org/xsd/imscc/ims_qtiasiv1p2.xsd');
$dcx->resetXpath();
$nlist = $dcx->nodeList("//qti:mattext | //text");
$dcx2 = new XMLGenericDocument();
foreach ($nlist as $nl) {
if ($dcx2->loadString($nl->nodeValue)){
GetDepFilesHTML($manifestroot,$fname,$filenames,$dcx2,$folder);
}
}
}
function GetDepFilesHTML ($manifestroot, $fname, &$filenames, &$dcx, $folder){
$dcx->resetXpath();
$nlist = $dcx->nodeList("//img/@src | //link/@href | //script/@src | //a[not(starts-with(@href,'#'))]/@href");
@ -121,7 +84,7 @@ function GetDepFilesHTML ($manifestroot, $fname, &$filenames, &$dcx, $folder){
$file = fullPath($path,"/");
toNativePath($file);
if (file_exists($manifestroot.DIRECTORY_SEPARATOR.$file)) {
$filenames[]= $file;
$filenames[$file]= $file;
}
}
if ($ext == 'css') {
@ -138,10 +101,12 @@ function GetDepFilesHTML ($manifestroot, $fname, &$filenames, &$dcx, $folder){
$limg = $cssobj->Get($item,"list-style-image");
$npath = pathinfo($csskey);
if ((!empty($bimg))&& ($bimg != 'none')) {
$filenames[] = stripUrl($bimg,$npath['dirname'].'/');
$value = stripUrl($bimg,$npath['dirname'].'/');
$filenames[$value] = $value;
} else
if ((!empty($limg))&& ($limg != 'none')) {
$filenames[] = stripUrl($limg,$npath['dirname'].'/');
$value = stripUrl($limg,$npath['dirname'].'/');
$filenames[$value] = $value;
}
}
}
@ -157,10 +122,12 @@ function GetDepFilesHTML ($manifestroot, $fname, &$filenames, &$dcx, $folder){
$sbl = $cssobj->Get($elem,"list-style-image");
$npath = pathinfo($csskey);
if ((!empty($sb)) && ($sb != 'none')) {
$filenames[] = stripUrl($sb,$npath['dirname'].'/');
$value = stripUrl($sb,$npath['dirname'].'/');
$filenames[$value] = $value;
} else
if ((!empty($sbl)) && ($sbl != 'none')) {
$filenames[] = stripUrl($sbl,$npath['dirname'].'/');
$value = stripUrl($sbl,$npath['dirname'].'/');
$filenames[$value] = $value;
}
}
}

View File

@ -40,7 +40,6 @@ class XMLGenericDocument {
private $arrayPrefixNS = array();
private $is_html = false;
/**
* @param string $value
* @return string

View File

@ -83,9 +83,7 @@ class entities {
public function update_sources ($html, $root_path = '') {
$document = new DOMDocument();
@$document->loadHTML($html);
$document = $this->load_html($html);
$tags = array('img' => 'src' , 'a' => 'href');
@ -108,7 +106,7 @@ class entities {
}
}
$html = $this->clear_doctype($document->saveHTML());
$html = $this->html_insidebody($document);
return $html;
}
@ -159,8 +157,7 @@ class entities {
public function include_titles ($html) {
$document = new DOMDocument();
@$document->loadHTML($html);
$document = $this->load_html($html);
$images = $document->getElementsByTagName('img');
@ -180,7 +177,7 @@ class entities {
$image->setAttribute('title', $title);
}
$html = $this->clear_doctype($document->saveHTML());
$html = $this->html_insidebody($document);
return $html;
}
@ -289,13 +286,36 @@ class entities {
}
private function clear_doctype ($html) {
/**
* @param string $html
* @return DOMDocument
*/
private function load_html($html) {
// Need to make sure that the html passed has charset meta tag.
$metatag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
if (strpos($html, $metatag) === false) {
$html = '<html><head>'.$metatag.'</head><body>'.$html.'</body></html>';
}
return preg_replace('/^<!DOCTYPE.+?>/',
'',
str_replace(array('<html>' , '</html>' , '<body>' , '</body>'),
array('' , '' , '' , ''),
$html));
$document = new DOMDocument();
@$document->loadHTML($html);
return $document;
}
/**
* @param DOMDocument $domdocument
* @return string
*/
private function html_insidebody($domdocument) {
$html = '';
$bodyitems = $domdocument->getElementsByTagName('body');
if ($bodyitems->length > 0) {
$body = $bodyitems->item(0);
$html = str_ireplace(array('<body>', '</body>'), '', $body->C14N());
}
return $html;
}
public function generate_random_string ($length = 6) {

View File

@ -653,8 +653,9 @@ class cc_quiz extends entities {
$question_cc_type = $this->get_question_type($identifier, $assessment);
$question_cc_type = $question_cc_type['cc'];
$is_multiresponse = ($question_cc_type == CC_QUIZ_MULTIPLE_RESPONSE);
if ($question_cc_type == CC_QUIZ_MULTIPLE_CHOICE || $question_cc_type == CC_QUIZ_MULTIPLE_RESPONSE || $question_cc_type == CC_QUIZ_TRUE_FALSE) {
if ($question_cc_type == CC_QUIZ_MULTIPLE_CHOICE || $is_multiresponse || $question_cc_type == CC_QUIZ_TRUE_FALSE) {
$query_answers = '//xmlns:item[@ident="' . $identifier . '"]/xmlns:presentation/xmlns:response_lid/xmlns:render_choice/xmlns:response_label';
$query_answers_with_flow = '//xmlns:item[@ident="' . $identifier . '"]/xmlns:presentation/xmlns:flow/xmlns:response_lid/xmlns:render_choice/xmlns:response_label';
@ -704,7 +705,19 @@ class cc_quiz extends entities {
}
if (!empty($response_items)) {
if ($is_multiresponse) {
$correct_answer_score = 0;
//get the correct answers count
$canswers_query = "//xmlns:item[@ident='{$identifier}']//xmlns:setvar[@varname='SCORE'][.=100]/../xmlns:conditionvar//xmlns:varequal[@case='Yes'][not(parent::xmlns:not)]";
$canswers = $xpath->query($canswers_query);
if ($canswers->length > 0) {
$correct_answer_score = round(1.0 / (float)$canswers->length, 7); //weird
$correct_answers_ident = array();
foreach ($canswers as $cnode) {
$correct_answers_ident[$cnode->nodeValue] = true;
}
}
}
foreach ($response_items as $response_item) {
$last_answer_id++;
@ -719,6 +732,10 @@ class cc_quiz extends entities {
$answer_score = $this->get_score($assessment, $answer_identifier, $identifier);
if ($is_multiresponse && isset($correct_answers_ident[$answer_identifier])) {
$answer_score = $correct_answer_score;
}
$answers[] = array('id' => $last_answer_id,
'title' => $answer_title,
'score' => $answer_score,
@ -757,7 +774,7 @@ class cc_quiz extends entities {
}
}
$score = empty($score) ? 0 : $score;
$score = empty($score) ? 0 : sprintf("%.7F", $score);
return $score;
}

View File

@ -304,8 +304,9 @@ class cc11_quiz extends entities11 {
$question_type_node = ($question_moodle_type == MOODLE_QUIZ_ESSAY) ? $this->create_node_course_question_categories_question_category_question_eesay($question) : $question_type_node;
$question_type_node = ($question_moodle_type == MOODLE_QUIZ_SHORTANSWER) ? $this->create_node_course_question_categories_question_category_question_shortanswer($question) : $question_type_node;
$questionname = !empty($question['name']) ? self::safexml($question['name']) : self::safexml($this->truncate_text($question['title'], 255, true));
$replace_values = array($question['id'],
self::safexml($this->truncate_text($question['title'], 255, true)),
$questionname,
self::safexml($question['title']),
$question_moodle_type,
self::safexml($question['feedback']),
@ -365,7 +366,12 @@ class cc11_quiz extends entities11 {
$question_title = $this->update_sources($question_title, $root_path);
$question_title = !empty($question_title) ? str_replace("%24", "\$", $this->include_titles($question_title)) : '';
// This attribute is not IMSCC spec, but it is included in Moodle 2.x export of IMS1.1
$questionname = $xpath->query('@title', $question_item);
$questionname = !empty($questionname->item(0)->nodeValue) ? $questionname->item(0)->nodeValue : '';
$questions[$question_identifier]['title'] = $question_title;
$questions[$question_identifier]['name'] = $questionname;
$questions[$question_identifier]['identifier'] = $question_identifier;
$questions[$question_identifier]['moodle_type'] = $question_type['moodle'];
$questions[$question_identifier]['cc_type'] = $question_type['cc'];
@ -507,63 +513,90 @@ class cc11_quiz extends entities11 {
$xpath = cc112moodle::newx_path($assessment, cc112moodle::getquizns());
$answers_fib = array();
$correctanswersfib = array();
$incorrectanswersfib = array();
$response_items = $xpath->query('//xmlns:item[@ident="' . $question_identifier . '"]/xmlns:resprocessing/xmlns:respcondition');
$correctrespcond = $xpath->query('//xmlns:item[@ident="' . $question_identifier . '"]/xmlns:resprocessing/xmlns:respcondition/xmlns:setvar[text()="100"]/..');
$correctanswers = $xpath->query('xmlns:conditionvar/xmlns:varequal', $correctrespcond->item(0));
// Correct answers.
foreach ($correctanswers as $correctans) {
$answertitle = !empty($correctans->nodeValue) ? $correctans->nodeValue : '';
if (empty($answertitle)) {
continue;
}
$last_answer_id++;
$correctanswersfib[$answertitle] = array(
'id' => $last_answer_id,
'title' => $answertitle,
'score' => 1,
'feedback' => '',
'case' => 0);
}
// Handle incorrect answers and feedback for all items.
foreach ($response_items as $response_item) {
$setvar = $xpath->query('xmlns:setvar', $response_item);
$setvar = is_object($setvar->item(0)) ? $setvar->item(0)->nodeValue : '';
if (!empty($setvar->length) && $setvar->item(0)->nodeValue == '100') {
// Skip the correct answer responsecondition.
continue;
}
if ($setvar != '') {
$varequal = $xpath->query('xmlns:conditionvar/xmlns:varequal', $response_item);
if (empty($varequal->length)) {
// Skip respcondition elements that don't have varequal containing an answer
continue;
}
$answer_title = !empty($varequal->item(0)->nodeValue) ? $varequal->item(0)->nodeValue : '';
$display_feedback = $xpath->query('xmlns:displayfeedback', $response_item);
unset($feedbacks_identifiers);
if (!empty($display_feedback)) {
foreach ($display_feedback as $feedback) {
$feedback_identifier = $feedback->getAttributeNode('linkrefid');
$feedback_identifier = !empty($feedback_identifier->nodeValue) ? $feedback_identifier->nodeValue : '';
if (!empty($feedback_identifier)) {
$feedbacks_identifiers[] = $feedback_identifier;
}
}
}
$feedback = '';
$feedbacks_identifiers = empty($feedbacks_identifiers) ? '' : $feedbacks_identifiers;
if (!empty($feedbacks_identifiers)) {
foreach ($feedbacks_identifiers as $feedback_identifier) {
$feedbacks = $xpath->query('//xmlns:item[@ident="' . $question_identifier . '"]/xmlns:itemfeedback[@ident="' . $feedback_identifier . '"]/xmlns:flow_mat/xmlns:material/xmlns:mattext');
$feedback .= !empty($feedbacks->item(0)->nodeValue) ? $feedbacks->item(0)->nodeValue . ' ' : '';
}
}
if (array_key_exists($answer_title, $correctanswersfib)) {
// Already a correct answer, just need the feedback for the correct answer.
$correctanswerfib[$answer_title]['feedback'] = $feedback;
} else {
// Need to add an incorrect answer.
$last_answer_id++;
$answer_title = $xpath->query('xmlns:conditionvar/xmlns:varequal[@respident="' . $identifier . '"]', $response_item);
$answer_title = !empty($answer_title->item(0)->nodeValue) ? $answer_title->item(0)->nodeValue : '';
$case = $xpath->query('xmlns:conditionvar/xmlns:varequal/@case', $response_item);
$case = is_object($case->item(0)) ? $case->item(0)->nodeValue : 'no'
;
$case = strtolower($case) == 'yes' ? 1 :
0;
$display_feedback = $xpath->query('xmlns:displayfeedback', $response_item);
unset($feedbacks_identifiers);
if (!empty($display_feedback)) {
foreach ($display_feedback as $feedback) {
$feedback_identifier = $feedback->getAttributeNode('linkrefid');
$feedback_identifier = !empty($feedback_identifier->nodeValue) ? $feedback_identifier->nodeValue : '';
if (!empty($feedback_identifier)) {
$feedbacks_identifiers[] = $feedback_identifier;
}
}
}
$feedback = '';
$feedbacks_identifiers = empty($feedbacks_identifiers) ? '' : $feedbacks_identifiers;
if (!empty($feedbacks_identifiers)) {
foreach ($feedbacks_identifiers as $feedback_identifier) {
$feedbacks = $xpath->query('//xmlns:item[@ident="' . $question_identifier . '"]/xmlns:itemfeedback[@ident="' . $feedback_identifier . '"]/xmlns:flow_mat/xmlns:material/xmlns:mattext');
$feedback .= !empty($feedbacks->item(0)->nodeValue) ? $feedbacks->item(0)->nodeValue . ' ' : '';
}
}
$answers_fib[] = array('id' => $last_answer_id,
'title' => $answer_title,
'score' => $setvar,
'feedback' => $feedback,
'case' => $case);
$incorrectanswersfib[] = array(
'id' => $last_answer_id,
'title' => $answer_title,
'score' => 0,
'feedback' => $feedback,
'case' => 0);
}
}
$answers_fib = array_merge($correctanswersfib, $incorrectanswersfib);
$answers_fib = empty($answers_fib) ? '' : $answers_fib;
return $answers_fib;
@ -653,8 +686,9 @@ class cc11_quiz extends entities11 {
$question_cc_type = $this->get_question_type($identifier, $assessment);
$question_cc_type = $question_cc_type['cc'];
$is_multiresponse = ($question_cc_type == CC_QUIZ_MULTIPLE_RESPONSE);
if ($question_cc_type == CC_QUIZ_MULTIPLE_CHOICE || $question_cc_type == CC_QUIZ_MULTIPLE_RESPONSE || $question_cc_type == CC_QUIZ_TRUE_FALSE) {
if ($question_cc_type == CC_QUIZ_MULTIPLE_CHOICE || $is_multiresponse || $question_cc_type == CC_QUIZ_TRUE_FALSE) {
$query_answers = '//xmlns:item[@ident="' . $identifier . '"]/xmlns:presentation/xmlns:response_lid/xmlns:render_choice/xmlns:response_label';
$query_answers_with_flow = '//xmlns:item[@ident="' . $identifier . '"]/xmlns:presentation/xmlns:flow/xmlns:response_lid/xmlns:render_choice/xmlns:response_label';
@ -705,6 +739,20 @@ class cc11_quiz extends entities11 {
if (!empty($response_items)) {
if ($is_multiresponse) {
$correct_answer_score = 0;
//get the correct answers count
$canswers_query = "//xmlns:item[@ident='{$identifier}']//xmlns:setvar[@varname='SCORE'][.=100]/../xmlns:conditionvar//xmlns:varequal[@case='Yes'][not(parent::xmlns:not)]";
$canswers = $xpath->query($canswers_query);
if ($canswers->length > 0) {
$correct_answer_score = round(1.0 / (float)$canswers->length, 7); //weird
$correct_answers_ident = array();
foreach ($canswers as $cnode) {
$correct_answers_ident[$cnode->nodeValue] = true;
}
}
}
foreach ($response_items as $response_item) {
$last_answer_id++;
@ -719,6 +767,10 @@ class cc11_quiz extends entities11 {
$answer_score = $this->get_score($assessment, $answer_identifier, $identifier);
if ($is_multiresponse && isset($correct_answers_ident[$answer_identifier])) {
$answer_score = $correct_answer_score;
}
$answers[] = array('id' => $last_answer_id,
'title' => $answer_title,
'score' => $answer_score,
@ -757,7 +809,8 @@ class cc11_quiz extends entities11 {
}
}
$score = empty($score) ? 0 : $score;
// This method (get_score) is only used by T/F & M/C questions in CC, therefore it's either 0 or 1 in Moodle.
$score = empty($score) ? "0.0000000" : '1.0000000';
return $score;
}

View File

@ -1,7 +1,7 @@
<MOD>
<ID>[#mod_instance#]</ID>
<MODTYPE>forum</MODTYPE>
<TYPE>news</TYPE>
<TYPE>general</TYPE>
<NAME>[#mod_forum_title#]</NAME>
<INTRO>[#mod_forum_intro#]</INTRO>
<ASSESSED>0</ASSESSED>

View File

@ -44,7 +44,7 @@ class imscc1_converter extends base_converter {
if (!empty($manifest)) {
// looks promising, lets load some information
$handle = fopen($manifest, 'r');
$xml_snippet = fread($handle, 500);
$xml_snippet = fread($handle, 1024);
fclose($handle);
// check if it has the required strings

View File

@ -45,7 +45,7 @@ class imscc11_converter extends base_converter {
if (file_exists($manifest)) {
// looks promising, lets load some information
$handle = fopen($manifest, 'r');
$xml_snippet = fread($handle, 500);
$xml_snippet = fread($handle, 1024);
fclose($handle);
// check if it has the required strings