diff --git a/backup/cc/cc_includes.php b/backup/cc/cc_includes.php index 9b1a9bd9349..9505614ed07 100644 --- a/backup/cc/cc_includes.php +++ b/backup/cc/cc_includes.php @@ -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'); diff --git a/backup/cc/cc_lib/cc_assesment_essay.php b/backup/cc/cc_lib/cc_assesment_essay.php new file mode 100644 index 00000000000..51f31f15b12 --- /dev/null +++ b/backup/cc/cc_lib/cc_assesment_essay.php @@ -0,0 +1,69 @@ +. +/** + * @package backup-convert + * @copyright 2012 Darko Miletic + * @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'); + } + } +} + diff --git a/backup/cc/cc_lib/cc_assesment_sfib.php b/backup/cc/cc_lib/cc_assesment_sfib.php new file mode 100644 index 00000000000..8f196755276 --- /dev/null +++ b/backup/cc/cc_lib/cc_assesment_sfib.php @@ -0,0 +1,183 @@ +. +/** + * @package backup-convert + * @copyright 2012 Darko Miletic + * @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 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); + } +} diff --git a/backup/cc/cc_lib/cc_assesment_truefalse.php b/backup/cc/cc_lib/cc_assesment_truefalse.php new file mode 100644 index 00000000000..3ce84e6f729 --- /dev/null +++ b/backup/cc/cc_lib/cc_assesment_truefalse.php @@ -0,0 +1,176 @@ +. +/** + * @package backup-convert + * @copyright 2012 Darko Miletic + * @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 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); + } + } + } +} diff --git a/backup/cc/cc_lib/cc_asssesment.php b/backup/cc/cc_lib/cc_asssesment.php index 3e3af90f296..56d6f904ce7 100644 --- a/backup/cc/cc_lib/cc_asssesment.php +++ b/backup/cc/cc_lib/cc_asssesment.php @@ -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 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 + ); + + } + +} diff --git a/backup/cc/cc_lib/cc_converter_folder.php b/backup/cc/cc_lib/cc_converter_folder.php new file mode 100644 index 00000000000..3498417607b --- /dev/null +++ b/backup/cc/cc_lib/cc_converter_folder.php @@ -0,0 +1,48 @@ +. +/** +* @package backup-convert +* @subpackage cc-library +* @copyright 2012 Darko Miletic +* @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; + } + +} + diff --git a/backup/cc/cc_lib/cc_converter_quiz.php b/backup/cc/cc_lib/cc_converter_quiz.php index 343f3a43e86..28bbd2dddd9 100644 --- a/backup/cc/cc_lib/cc_converter_quiz.php +++ b/backup/cc/cc_lib/cc_converter_quiz.php @@ -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); diff --git a/backup/cc/cc_lib/cc_converter_resource.php b/backup/cc/cc_lib/cc_converter_resource.php index 6293c953b44..b007c3a27e2 100644 --- a/backup/cc/cc_lib/cc_converter_resource.php +++ b/backup/cc/cc_lib/cc_converter_resource.php @@ -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; } diff --git a/backup/cc/cc_lib/cc_converters.php b/backup/cc/cc_lib/cc_converters.php index 26e6d3739bf..9958df41c73 100644 --- a/backup/cc/cc_lib/cc_converters.php +++ b/backup/cc/cc_lib/cc_converters.php @@ -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]); diff --git a/backup/cc/cc_lib/cc_manifest.php b/backup/cc/cc_lib/cc_manifest.php index 01dbb1f1bd8..315dd8b3421 100644 --- a/backup/cc/cc_lib/cc_manifest.php +++ b/backup/cc/cc_lib/cc_manifest.php @@ -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; } } diff --git a/backup/cc/cc_lib/cc_page.php b/backup/cc/cc_lib/cc_page.php index af9fd98bcd2..48c30e4c4a6 100644 --- a/backup/cc/cc_lib/cc_page.php +++ b/backup/cc/cc_lib/cc_page.php @@ -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; } diff --git a/backup/cc/cc_lib/cc_resources.php b/backup/cc/cc_lib/cc_resources.php index eb34779523d..565fbcb1dfc 100644 --- a/backup/cc/cc_lib/cc_resources.php +++ b/backup/cc/cc_lib/cc_resources.php @@ -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; diff --git a/backup/cc/cc_lib/cc_utils.php b/backup/cc/cc_lib/cc_utils.php index 39e70622562..142dc17c4e4 100644 --- a/backup/cc/cc_lib/cc_utils.php +++ b/backup/cc/cc_lib/cc_utils.php @@ -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() { diff --git a/backup/cc/cc_lib/cc_version1.php b/backup/cc/cc_lib/cc_version1.php index 30f382a8479..f7854d33c58 100644 --- a/backup/cc/cc_lib/cc_version1.php +++ b/backup/cc/cc_lib/cc_version1.php @@ -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'); diff --git a/backup/cc/cc_lib/cc_version11.php b/backup/cc/cc_lib/cc_version11.php index 6df8ab715bd..d07c6ea34ed 100644 --- a/backup/cc/cc_lib/cc_version11.php +++ b/backup/cc/cc_lib/cc_version11.php @@ -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; + } } diff --git a/backup/cc/cc_lib/cc_version_base.php b/backup/cc/cc_lib/cc_version_base.php index 9fc230e9d64..0c689a5e59b 100644 --- a/backup/cc/cc_lib/cc_version_base.php +++ b/backup/cc/cc_lib/cc_version_base.php @@ -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; diff --git a/backup/cc/cc_lib/gral_lib/ccdependencyparser.php b/backup/cc/cc_lib/gral_lib/ccdependencyparser.php index e01557cc85a..e91558c3fe6 100644 --- a/backup/cc/cc_lib/gral_lib/ccdependencyparser.php +++ b/backup/cc/cc_lib/gral_lib/ccdependencyparser.php @@ -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; } } } diff --git a/backup/cc/cc_lib/xmlbase.php b/backup/cc/cc_lib/xmlbase.php index c85cbe906bf..f336c1f27a6 100644 --- a/backup/cc/cc_lib/xmlbase.php +++ b/backup/cc/cc_lib/xmlbase.php @@ -40,7 +40,6 @@ class XMLGenericDocument { private $arrayPrefixNS = array(); private $is_html = false; - /** * @param string $value * @return string diff --git a/backup/cc/entities.class.php b/backup/cc/entities.class.php index 4f441ba4a3a..106fcb39fa9 100644 --- a/backup/cc/entities.class.php +++ b/backup/cc/entities.class.php @@ -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 = ''; + if (strpos($html, $metatag) === false) { + $html = ''.$metatag.''.$html.''; + } - return preg_replace('/^/', - '', - str_replace(array('' , '' , '' , ''), - 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->C14N()); + } + + return $html; } public function generate_random_string ($length = 6) { diff --git a/backup/cc/entity.quiz.class.php b/backup/cc/entity.quiz.class.php index 4bb8c32b839..eabd85404ac 100644 --- a/backup/cc/entity.quiz.class.php +++ b/backup/cc/entity.quiz.class.php @@ -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; } diff --git a/backup/cc/entity11.quiz.class.php b/backup/cc/entity11.quiz.class.php index 9472607ead9..5410caa9ea9 100644 --- a/backup/cc/entity11.quiz.class.php +++ b/backup/cc/entity11.quiz.class.php @@ -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; } diff --git a/backup/cc/sheets/course_modules_mod_forum.xml b/backup/cc/sheets/course_modules_mod_forum.xml index 78b21b1e412..4d8e372fb05 100644 --- a/backup/cc/sheets/course_modules_mod_forum.xml +++ b/backup/cc/sheets/course_modules_mod_forum.xml @@ -1,7 +1,7 @@ [#mod_instance#] forum - news + general [#mod_forum_title#] [#mod_forum_intro#] 0 diff --git a/backup/converter/imscc1/lib.php b/backup/converter/imscc1/lib.php index 5cfae39bad2..ec413cb8a21 100644 --- a/backup/converter/imscc1/lib.php +++ b/backup/converter/imscc1/lib.php @@ -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 diff --git a/backup/converter/imscc11/lib.php b/backup/converter/imscc11/lib.php index 7e885f0fca9..72d9c8b2cd3 100644 --- a/backup/converter/imscc11/lib.php +++ b/backup/converter/imscc11/lib.php @@ -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