diff --git a/lib/db/install.xml b/lib/db/install.xml index 04d4fb9024bf0..a3a969749d183 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -1246,8 +1246,9 @@ - - + + + @@ -1266,10 +1267,10 @@ - - - - + + + + @@ -1293,9 +1294,11 @@ - - - + + + + + @@ -1338,8 +1341,9 @@ - - + + + @@ -1575,7 +1579,7 @@ - +
@@ -2709,4 +2713,4 @@
-
\ No newline at end of file + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 0881eeaf8d879..a7b8d79b839f4 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -4911,7 +4911,170 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2010080305); } + if ($oldversion < 2010080900) { + /// Define field generalfeedbackformat to be added to question + $table = new xmldb_table('question'); + $field = new xmldb_field('generalfeedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'generalfeedback'); + + /// Conditionally launch add field generalfeedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + /// Define field infoformat to be added to question_categories + $table = new xmldb_table('question_categories'); + $field = new xmldb_field('infoformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'info'); + + /// Conditionally launch add field infoformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + /// Define field answerformat to be added to question_answers + $table = new xmldb_table('question_answers'); + $field = new xmldb_field('answerformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'answer'); + + /// Conditionally launch add field answerformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + /// Define field feedbackformat to be added to question_answers + $field = new xmldb_field('feedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'feedback'); + + /// Conditionally launch add field feedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + /// Define field manualcommentformat to be added to question_sessions + $table = new xmldb_table('question_sessions'); + $field = new xmldb_field('manualcommentformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'manualcomment'); + + /// Conditionally launch add field manualcommentformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + /// Main savepoint reached + upgrade_main_savepoint(true, 2010080900); + } + + /// updating question image + if ($oldversion < 2010080901) { + $fs = get_file_storage(); + $rs = $DB->get_recordset('question'); + $textlib = textlib_get_instance(); + foreach ($rs as $question) { + if (empty($question->image)) { + continue; + } + if (!$category = $DB->get_record('question_categories', array('id'=>$question->category))) { + continue; + } + $categorycontext = get_context_instance_by_id($category->contextid); + // question files are stored in course level + // so we have to find course context + switch ($categorycontext->contextlevel){ + case CONTEXT_COURSE : + $context = $categorycontext; + break; + case CONTEXT_MODULE : + $courseid = $DB->get_field('course_modules', 'course', array('id'=>$categorycontext->instanceid)); + $context = get_context_instance(CONTEXT_COURSE, $courseid); + break; + case CONTEXT_COURSECAT : + case CONTEXT_SYSTEM : + $context = get_system_context(); + break; + default : + continue; + } + if ($textlib->substr($textlib->strtolower($question->image), 0, 7) == 'http://') { + // it is a link, appending to existing question text + $question->questiontext .= ' '; + // update question record + $DB->update_record('question', $question); + } else { + $filename = basename($question->image); + $filepath = dirname($question->image); + if (empty($filepath) or $filepath == '.' or $filepath == '/') { + $filepath = '/'; + } else { + // append / + $filepath = '/'.trim($filepath, './@#$ ').'/'; + } + + // course files already moved to file pool by previous upgrade block + // so we just create copy from course_legacy area + if ($image = $fs->get_file($context->id, 'course', 'legacy', 0, $filepath, $filename)) { + // move files to file pool + $file_record = array( + 'contextid'=>$category->contextid, + 'component'=>'question', + 'filearea'=>'questiontext', + 'itemid'=>$question->id + ); + $fs->create_file_from_storedfile($file_record, $image); + $question->questiontext .= ' '; + // update question record + $DB->update_record('question', $question); + } + } + } + $rs->close(); + + // Define field image to be dropped from question + $table = new xmldb_table('question'); + $field = new xmldb_field('image'); + + // Conditionally launch drop field image + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // fix fieldformat + $sql = 'SELECT a.*, q.qtype FROM {question_answers} a, {question} q WHERE a.question = q.id'; + $rs = $DB->get_recordset_sql($sql); + foreach ($rs as $record) { + // generalfeedback should use questiontext format + if ($CFG->texteditors !== 'textarea') { + if (!empty($record->feedback)) { + $record->feedback = text_to_html($record->feedback); + } + $record->feedbackformat = FORMAT_HTML; + } else { + $record->feedbackformat = FORMAT_MOODLE; + $record->answerformat = FORMAT_MOODLE; + } + unset($record->qtype); + $DB->update_record('question_answers', $record); + } + $rs->close(); + + $rs = $DB->get_recordset('question'); + foreach ($rs as $record) { + if ($CFG->texteditors !== 'textarea') { + if (!empty($record->questiontext)) { + $record->questiontext = text_to_html($record->questiontext); + } + $record->questiontextformat = FORMAT_HTML; + // conver generalfeedback text to html + if (!empty($record->generalfeedback)) { + $record->generalfeedback = text_to_html($record->generalfeedback); + } + } else { + $record->questiontextformat = FORMAT_MOODLE; + } + // generalfeedbackformat should be the save as questiontext format + $record->generalfeedbackformat = $record->questiontextformat; + $DB->update_record('question', $record); + } + $rs->close(); + // Main savepoint reached + upgrade_main_savepoint(true, 2010080901); + } return true; } diff --git a/lib/questionlib.php b/lib/questionlib.php index fbecc814fc8c3..c05efd2f44a9e 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -851,18 +851,31 @@ function question_delete_activity($cm, $feedback=true) { * * @global object * @param string $questionids a comma-separated list of question ids. - * @param integer $newcategory the id of the category to move to. + * @param integer $newcategoryid the id of the category to move to. */ -function question_move_questions_to_category($questionids, $newcategory) { - global $DB; - +function question_move_questions_to_category($questionids, $newcategoryid) { + global $DB, $QTYPES; $result = true; + $ids = explode(',', $questionids); + foreach ($ids as $questionid) { + $questionid = (int)$questionid; + $params = array(); + $params[] = $questionid; + $sql = 'SELECT q.*, c.id AS contextid, c.contextlevel, c.instanceid, c.path, c.depth + FROM {question} q, {question_categories} qc, {context} c + WHERE q.category=qc.id AND q.id=? AND qc.contextid=c.id'; + $question = $DB->get_record_sql($sql, $params); + $category = $DB->get_record('question_categories', array('id'=>$newcategoryid)); + // process files + $QTYPES[$question->qtype]->move_files($question, $category); + } + // Move the questions themselves. - $result = $result && $DB->set_field_select('question', 'category', $newcategory, "id IN ($questionids)"); + $result = $result && $DB->set_field_select('question', 'category', $newcategoryid, "id IN ($questionids)"); // Move any subquestions belonging to them. - $result = $result && $DB->set_field_select('question', 'category', $newcategory, "parent IN ($questionids)"); + $result = $result && $DB->set_field_select('question', 'category', $newcategoryid, "parent IN ($questionids)"); // TODO Deal with datasets. @@ -1080,6 +1093,7 @@ function question_load_states(&$questions, &$states, $cmoptions, $attempt, $last $states[$qid]->last_graded = clone($states[$qid]); } } else { + if ($lastattemptid) { // If the new attempt is to be based on this previous attempt. // Find the responses from the previous attempt and save them to the new session @@ -2091,7 +2105,7 @@ function question_init_qengine_js() { $module = array( 'name' => 'core_question_flags', 'fullpath' => '/question/flags.js', - 'requires' => array('base', 'dom', 'event-delegate', 'io-base'), + 'requires' => array('base', 'dom', 'event-delegate', 'io-base'), ); $actionurl = $CFG->wwwroot . '/question/toggleflag.php'; $flagattributes = array( @@ -2162,9 +2176,9 @@ function question_get_editing_head_contributions($question) { * @param object $cmoptions The options specified by the course module * @param object $options An object specifying the rendering options. */ -function print_question(&$question, &$state, $number, $cmoptions, $options=null) { +function print_question(&$question, &$state, $number, $cmoptions, $options=null, $context=null) { global $QTYPES; - $QTYPES[$question->qtype]->print_question($question, $state, $number, $cmoptions, $options); + $QTYPES[$question->qtype]->print_question($question, $state, $number, $cmoptions, $options, $context); } /** * Saves question options @@ -3191,4 +3205,124 @@ public function require_one_edit_tab_cap($tabname){ print_error('nopermissions', '', '', 'access question edit tab '.$tabname); } } -} \ No newline at end of file +} + +/** + * Rewrite question url, file_rewrite_pluginfile_urls always build url by + * $file/$contextid/$component/$filearea/$itemid/$pathname_in_text, so we cannot add + * extra questionid and attempted in url by it, so we create quiz_rewrite_question_urls + * to build url here + * + * @param string $text text being processed + * @param string $file the php script used to serve files + * @param int $contextid + * @param string $component component + * @param string $filearea filearea + * @param array $ids other IDs will be used to check file permission + * @param int $itemid + * @param array $options + * @return string + */ +function quiz_rewrite_question_urls($text, $file, $contextid, $component, $filearea, array $ids, $itemid, array $options=null) { + global $CFG; + + $options = (array)$options; + if (!isset($options['forcehttps'])) { + $options['forcehttps'] = false; + } + + if (!$CFG->slasharguments) { + $file = $file . '?file='; + } + + $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/"; + + if (!empty($ids)) { + $baseurl .= (implode('/', $ids) . '/'); + } + + if ($itemid !== null) { + $baseurl .= "$itemid/"; + } + + if ($options['forcehttps']) { + $baseurl = str_replace('http://', 'https://', $baseurl); + } + + return str_replace('@@PLUGINFILE@@/', $baseurl, $text); +} + +/** + * Called by pluginfile.php to serve files related to the 'question' core + * component and for files belonging to qtypes. + * + * For files that relate to questions in a question_attempt, then we delegate to + * a function in the component that owns the attempt (for example in the quiz, + * or in core question preview) to get necessary inforation. + * + * (Note that, at the moment, all question file areas relate to questions in + * attempts, so the If at the start of the last paragraph is always true.) + * + * Does not return, either calls send_file_not_found(); or serves the file. + * + * @param object $course course settings object + * @param object $context context object + * @param string $component the name of the component we are serving files for. + * @param string $filearea the name of the file area. + * @param array $args the remaining bits of the file path. + * @param bool $forcedownload whether the user must be forced to download the file. + */ +function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload) { + global $DB, $CFG; + + $attemptid = (int)array_shift($args); + $questionid = (int)array_shift($args); + + require_login($course, false); + + if ($attemptid === 0) { + // preview + require_once($CFG->dirroot . '/question/previewlib.php'); + return question_preview_question_pluginfile($course, $context, + $component, $filearea, $attemptid, $questionid, $args, $forcedownload); + + } else { + $module = $DB->get_field('question_attempts', 'modulename', + array('id' => $attemptid)); + + $dir = get_component_directory($module); + if (!file_exists("$dir/lib.php")) { + send_file_not_found(); + } + include_once("$dir/lib.php"); + + $filefunction = $module . '_question_pluginfile'; + if (!function_exists($filefunction)) { + send_file_not_found(); + } + + $filefunction($course, $context, $component, $filearea, $attemptid, $questionid, + $args, $forcedownload); + + send_file_not_found(); + } +} + +/** + * Final test for whether a studnet should be allowed to see a particular file. + * This delegates the decision to the question type plugin. + * + * @param object $question The question to be rendered. + * @param object $state The state to render the question in. + * @param object $options An object specifying the rendering options. + * @param string $component the name of the component we are serving files for. + * @param string $filearea the name of the file area. + * @param array $args the remaining bits of the file path. + * @param bool $forcedownload whether the user must be forced to download the file. + */ +function question_check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args, $forcedownload) { + global $QTYPES; + return $QTYPES[$question->qtype]->check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args, $forcedownload); +} diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 3d4fae405967a..51ce60a775ade 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -283,6 +283,10 @@ public function get_access_manager($timenow) { return $this->accessmanager; } + public function get_overall_feedback($grade) { + return quiz_feedback_for_grade($grade, $this->quiz, $this->context, $this->cm); + } + /** * Wrapper round the has_capability funciton that automatically passes in the quiz context. */ @@ -571,7 +575,7 @@ public function is_finished() { return $this->attempt->timefinish != 0; } - /** @return boolean whether this attemp is a preview attempt. */ + /** @return boolean whether this attempt is a preview attempt. */ public function is_preview() { return $this->attempt->preview; } @@ -629,10 +633,12 @@ public function get_review_options() { /** * Wrapper that calls get_render_options with the appropriate arguments. * + * @param integer questionid the quetsion to get the render options for. * @return object the render options for this user on this attempt. */ - public function get_render_options($state) { - return quiz_get_renderoptions($this->quiz, $this->attempt, $this->context, $state); + public function get_render_options($questionid) { + return quiz_get_renderoptions($this->quiz, $this->attempt, $this->context, + $this->get_question_state($questionid)); } /** @@ -669,7 +675,7 @@ public function get_question_status($questionid) { case QUESTION_EVENTCLOSEANDGRADE: case QUESTION_EVENTCLOSE: case QUESTION_EVENTMANUALGRADE: - $options = $this->get_render_options($this->states[$questionid]); + $options = $this->get_render_options($questionid); if ($options->scores && $this->questions[$questionid]->maxgrade > 0) { return question_get_feedback_class($state->last_graded->raw_grade / $this->questions[$questionid]->maxgrade); @@ -703,7 +709,7 @@ public function is_question_flagged($questionid) { * @return string the formatted grade, to the number of decimal places specified by the quiz. */ public function get_question_score($questionid) { - $options = $this->get_render_options($this->states[$questionid]); + $options = $this->get_render_options($questionid); if ($options->scores) { return quiz_format_question_grade($this->quiz, $this->states[$questionid]->last_graded->grade); } else { @@ -805,7 +811,7 @@ public function print_question($id, $reviewing, $thispageurl = '') { if ($reviewing) { $options = $this->get_review_options(); } else { - $options = $this->get_render_options($this->states[$id]); + $options = $this->get_render_options($id); } if ($thispageurl) { $this->quiz->thispageurl = $thispageurl; @@ -816,6 +822,20 @@ public function print_question($id, $reviewing, $thispageurl = '') { $this->quiz, $options); } + public function check_file_access($questionid, $isreviewing, $contextid, $component, + $filearea, $args, $forcedownload) { + if ($isreviewing) { + $options = $this->get_review_options(); + } else { + $options = $this->get_render_options($questionid); + } + // XXX: mulitichoice type needs quiz id to get maxgrade + $options->quizid = $this->attempt->quiz; + return question_check_file_access($this->questions[$questionid], + $this->get_question_state($questionid), $options, $contextid, + $component, $filearea, $args, $forcedownload); + } + /** * Triggers the sending of the notification emails at the end of this attempt. */ diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml index d85a3fec5b89f..2196e070142c3 100755 --- a/mod/quiz/db/install.xml +++ b/mod/quiz/db/install.xml @@ -1,5 +1,5 @@ - @@ -100,8 +100,9 @@ - - + + + diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php index 80667d33e4f7d..b528afa02a2c8 100644 --- a/mod/quiz/db/upgrade.php +++ b/mod/quiz/db/upgrade.php @@ -341,6 +341,22 @@ function xmldb_quiz_upgrade($oldversion) { upgrade_mod_savepoint(true, 2010051800, 'quiz'); } + if ($oldversion < 2010080600) { + + // Define field feedbacktextformat to be added to quiz_feedback + $table = new xmldb_table('quiz_feedback'); + $field = new xmldb_field('feedbacktextformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'feedbacktext'); + + // Conditionally launch add field feedbacktextformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // quiz savepoint reached + upgrade_mod_savepoint(true, 2010080600, 'quiz'); + } + + return true; } diff --git a/mod/quiz/index.php b/mod/quiz/index.php index 351b505159687..631f95bf1ff52 100644 --- a/mod/quiz/index.php +++ b/mod/quiz/index.php @@ -155,7 +155,7 @@ $grade = get_string('outofshort', 'quiz', $a); } if ($alloptions->overallfeedback) { - $feedback = quiz_feedback_for_grade($scores[$quiz->id], $quiz->id); + $feedback = quiz_feedback_for_grade($scores[$quiz->id], $quiz, $context, $cm); } } $data[] = $grade; diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 2d373a3173e40..3d0737f0d6371 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -104,6 +104,7 @@ */ function quiz_add_instance($quiz) { global $DB; + $cmid = $quiz->coursemodule; // Process the options from the form. $quiz->created = time(); @@ -982,10 +983,10 @@ function quiz_process_options(&$quiz) { if (isset($quiz->feedbacktext)) { // Clean up the boundary text. for ($i = 0; $i < count($quiz->feedbacktext); $i += 1) { - if (empty($quiz->feedbacktext[$i])) { - $quiz->feedbacktext[$i] = ''; + if (empty($quiz->feedbacktext[$i]['text'])) { + $quiz->feedbacktext[$i]['text'] = ''; } else { - $quiz->feedbacktext[$i] = trim($quiz->feedbacktext[$i]); + $quiz->feedbacktext[$i]['text'] = trim($quiz->feedbacktext[$i]['text']); } } @@ -1023,7 +1024,7 @@ function quiz_process_options(&$quiz) { } } for ($i = $numboundaries + 1; $i < count($quiz->feedbacktext); $i += 1) { - if (!empty($quiz->feedbacktext[$i]) && trim($quiz->feedbacktext[$i]) != '') { + if (!empty($quiz->feedbacktext[$i]['text']) && trim($quiz->feedbacktext[$i]['text']) != '') { return get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1); } } @@ -1145,17 +1146,25 @@ function quiz_process_options(&$quiz) { */ function quiz_after_add_or_update($quiz) { global $DB; + $cmid = $quiz->coursemodule; + + // we need to use context now, so we need to make sure all needed info is already in db + $DB->set_field('course_modules', 'instance', $quiz->id, array('id'=>$cmid)); + $context = get_context_instance(CONTEXT_MODULE, $cmid); // Save the feedback $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); - for ($i = 0; $i <= $quiz->feedbackboundarycount; $i += 1) { + for ($i = 0; $i <= $quiz->feedbackboundarycount; $i++) { $feedback = new stdClass; $feedback->quizid = $quiz->id; - $feedback->feedbacktext = $quiz->feedbacktext[$i]; + $feedback->feedbacktext = $quiz->feedbacktext[$i]['text']; + $feedback->feedbacktextformat = $quiz->feedbacktext[$i]['format']; $feedback->mingrade = $quiz->feedbackboundaries[$i]; $feedback->maxgrade = $quiz->feedbackboundaries[$i - 1]; - $DB->insert_record('quiz_feedback', $feedback, false); + $feedback->id = $DB->insert_record('quiz_feedback', $feedback); + $feedbacktext = file_save_draft_area_files((int)$quiz->feedbacktext[$i]['itemid'], $context->id, 'mod_quiz', 'feedback', $feedback->id, array('subdirs'=>false, 'maxfiles'=>-1, 'maxbytes'=>0), $quiz->feedbacktext[$i]['text']); + $DB->set_field('quiz_feedback', 'feedbacktext', $feedbacktext, array('id'=>$feedback->id)); } // Update the events relating to this quiz. @@ -1421,23 +1430,44 @@ function quiz_reset_userdata($data) { * @param int $questionid int question id * @return boolean to indicate access granted or denied */ -function quiz_check_file_access($attemptuniqueid, $questionid) { - global $USER, $DB; +function quiz_check_file_access($attemptuniqueid, $questionid, $context = null) { + global $USER, $DB, $CFG; + require_once(dirname(__FILE__).'/attemptlib.php'); + require_once(dirname(__FILE__).'/locallib.php'); $attempt = $DB->get_record('quiz_attempts', array('uniqueid' => $attemptuniqueid)); - $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz)); - $context = get_context_instance(CONTEXT_COURSE, $quiz->course); + $attemptobj = quiz_attempt::create($attempt->id); + + // does question exist? + if (!$question = $DB->get_record('question', array('id' => $questionid))) { + return false; + } + + if ($context === null) { + $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz)); + $cm = get_coursemodule_from_id('quiz', $quiz->id); + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + } + + // Load those questions and the associated states. + $attemptobj->load_questions(array($questionid)); + $attemptobj->load_question_states(array($questionid)); + + // obtain state + $state = $attemptobj->get_question_state($questionid); + // obtain questoin + $question = $attemptobj->get_question($questionid); // access granted if the current user submitted this file - if ($attempt->userid == $USER->id) { - return true; + if ($attempt->userid != $USER->id) { + return false; // access granted if the current user has permission to grade quizzes in this course - } else if (has_capability('mod/quiz:viewreports', $context) || has_capability('mod/quiz:grade', $context)) { - return true; + } + if (!(has_capability('mod/quiz:viewreports', $context) || has_capability('mod/quiz:grade', $context))) { + return false; } - // otherwise, this user does not have permission - return false; + return array($question, $state, array()); } /** @@ -1682,3 +1712,98 @@ function quiz_extend_settings_navigation($settings, $quiznode) { question_extend_settings_navigation($quiznode, $PAGE->cm->context)->trim_if_empty(); } + +/** + * Serves the quiz files. + * + * @param object $course + * @param object $cm + * @param object $context + * @param string $filearea + * @param array $args + * @param bool $forcedownload + * @return bool false if file not found, does not return if found - justsend the file + */ +function quiz_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $CFG, $DB; + + if ($context->contextlevel != CONTEXT_MODULE) { + return false; + } + + require_login($course, false, $cm); + + if (!$quiz = $DB->get_record('quiz', array('id'=>$cm->instance))) { + return false; + } + + // 'intro' area is served by pluginfile.php + $fileareas = array('feedback'); + if (!in_array($filearea, $fileareas)) { + return false; + } + + $feedbackid = (int)array_shift($args); + if (!$feedback = $DB->get_record('quiz_feedback', array('id'=>$feedbackid))) { + return false; + } + + $fs = get_file_storage(); + $relativepath = implode('/', $args); + $fullpath = "/$context->id/mod_quiz/$filearea/$feedbackid/$relativepath"; + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + return false; + } + send_stored_file($file, 0, 0, true); +} + +/** + * Called via pluginfile.php -> question_pluginfile to serve files belonging to + * a question in a question_attempt when that attempt is a quiz attempt. + * + * @param object $course course settings object + * @param object $context context object + * @param string $component the name of the component we are serving files for. + * @param string $filearea the name of the file area. + * @param array $args the remaining bits of the file path. + * @param bool $forcedownload whether the user must be forced to download the file. + * @return bool false if file not found, does not return if found - justsend the file + */ +function quiz_question_pluginfile($course, $context, $component, + $filearea, $attemptid, $questionid, $args, $forcedownload) { + global $USER, $CFG; + require_once($CFG->dirroot . '/mod/quiz/locallib.php'); + + $attemptobj = quiz_attempt::create($attemptid); + require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); + $questionids = array($questionid); + $attemptobj->load_questions($questionids); + $attemptobj->load_question_states($questionids); + + if ($attemptobj->is_own_attempt() && !$attemptobj->is_finished()) { + // In the middle of an attempt. + if (!$attemptobj->is_preview_user()) { + $attemptobj->require_capability('mod/quiz:attempt'); + } + $isreviewing = false; + + } else { + // Reviewing an attempt. + $attemptobj->check_review_capability(); + $isreviewing = true; + } + + if (!$attemptobj->check_file_access($questionid, $isreviewing, $context->id, + $component, $filearea, $args, $forcedownload)) { + send_file_not_found(); + } + + $fs = get_file_storage(); + $relativepath = implode('/', $args); + $fullpath = "/$context->id/$component/$filearea/$relativepath"; + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + send_stored_file($file, 0, 0, $forcedownload); +} diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index a4b247a9085d5..04051a1185a1e 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -456,21 +456,22 @@ function quiz_rescale_grade($rawgrade, $quiz, $round = true) { * @param integer $quizid the id of the quiz object. * @return string the comment that corresponds to this grade (empty string if there is not one. */ -function quiz_feedback_for_grade($grade, $quizid) { +function quiz_feedback_for_grade($grade, $quiz, $context, $cm=null) { global $DB; - $feedback = $DB->get_field_select('quiz_feedback', 'feedbacktext', - "quizid = ? AND mingrade <= ? AND $grade < maxgrade", array($quizid, $grade)); - if (empty($feedback)) { - $feedback = ''; + $feedback = $DB->get_record_select('quiz_feedback', "quizid = ? AND mingrade <= ? AND $grade < maxgrade", array($quiz->id, $grade)); + + if (empty($feedback->feedbacktext)) { + $feedback->feedbacktext = ''; } // Clean the text, ready for display. $formatoptions = new stdClass; $formatoptions->noclean = true; - $feedback = format_text($feedback, FORMAT_MOODLE, $formatoptions); + $feedbacktext = file_rewrite_pluginfile_urls($feedback->feedbacktext, 'pluginfile.php', $context->id, 'mod_quiz', 'feedback', $feedback->id); + $feedbacktext = format_text($feedbacktext, $feedback->feedbacktextformat, $formatoptions); - return $feedback; + return $feedbacktext; } /** @@ -1263,4 +1264,4 @@ function quiz_get_js_module() { array('flagged', 'question'), ), ); -} \ No newline at end of file +} diff --git a/mod/quiz/mod_form.php b/mod/quiz/mod_form.php index e51b1e25bfbda..e7f5dba71f5c5 100644 --- a/mod/quiz/mod_form.php +++ b/mod/quiz/mod_form.php @@ -296,7 +296,7 @@ function definition() { $mform->addElement('static', 'gradeboundarystatic1', get_string('gradeboundary', 'quiz'), '100%'); $repeatarray = array(); - $repeatarray[] = &MoodleQuickForm::createElement('text', 'feedbacktext', get_string('feedback', 'quiz'), array('size' => 50)); + $repeatarray[] = &MoodleQuickForm::createElement('editor', 'feedbacktext', get_string('feedback', 'quiz'), null, array('maxfiles'=>EDITOR_UNLIMITED_FILES, 'noclean'=>true, 'context'=>$this->context)); $mform->setType('feedbacktext', PARAM_RAW); $repeatarray[] = &MoodleQuickForm::createElement('text', 'feedbackboundaries', get_string('gradeboundary', 'quiz'), array('size' => 10)); $mform->setType('feedbackboundaries', PARAM_NOTAGS); @@ -313,7 +313,7 @@ function definition() { get_string('addmoreoverallfeedbacks', 'quiz'), true); // Put some extra elements in before the button - $insertEl = &MoodleQuickForm::createElement('text', "feedbacktext[$nextel]", get_string('feedback', 'quiz'), array('size' => 50)); + $insertEl = &MoodleQuickForm::createElement('editor', "feedbacktext[$nextel]", get_string('feedback', 'quiz'), null, array('maxfiles'=>EDITOR_UNLIMITED_FILES, 'noclean'=>true, 'context'=>$this->context)); $mform->insertElementBefore($insertEl, 'boundary_add_fields'); $insertEl = &MoodleQuickForm::createElement('static', 'gradeboundarystatic2', get_string('gradeboundary', 'quiz'), '0%'); @@ -342,7 +342,19 @@ function data_preprocessing(&$default_values){ if (count($this->_feedbacks)) { $key = 0; foreach ($this->_feedbacks as $feedback){ - $default_values['feedbacktext['.$key.']'] = $feedback->feedbacktext; + $draftid = file_get_submitted_draft_itemid('feedbacktext['.$key.']'); + $default_values['feedbacktext['.$key.']']['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'mod_quiz', // component + 'feedback', // filarea + !empty($feedback->id)?(int)$feedback->id:null, // itemid + null, + $feedback->feedbacktext // text + ); + $default_values['feedbacktext['.$key.']']['format'] = $feedback->feedbacktextformat; + $default_values['feedbacktext['.$key.']']['itemid'] = $draftid; + if ($feedback->mingrade > 0) { $default_values['feedbackboundaries['.$key.']'] = (100.0 * $feedback->mingrade / $default_values['grade']) . '%'; } @@ -433,7 +445,7 @@ function validation($data, $files) { } } for ($i = $numboundaries + 1; $i < count($data['feedbacktext']); $i += 1) { - if (!empty($data['feedbacktext'][$i] ) && trim($data['feedbacktext'][$i] ) != '') { + if (!empty($data['feedbacktext'][$i]['text']) && trim($data['feedbacktext'][$i]['text'] ) != '') { $errors["feedbacktext[$i]"] = get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1); } } diff --git a/mod/quiz/review.php b/mod/quiz/review.php index 446bba961ee50..2ae4fac02971d 100644 --- a/mod/quiz/review.php +++ b/mod/quiz/review.php @@ -200,7 +200,7 @@ } /// Feedback if there is any, and the user is allowed to see it now. - $feedback = quiz_feedback_for_grade($grade, $attempt->quiz); + $feedback = $attemptobj->get_overall_feedback($grade); if ($options->overallfeedback && $feedback) { $rows[] = '' . get_string('feedback', 'quiz') . '' . $feedback . ''; diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 42c59baa3ac5d..9cc23d75beab1 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -5,7 +5,7 @@ // This fragment is called by moodle_needs_upgrading() and /admin/index.php //////////////////////////////////////////////////////////////////////////////// -$module->version = 2010051801; // The (date) version of this module +$module->version = 2010080600; // The (date) version of this module $module->requires = 2010080300; // Requires this Moodle version $module->cron = 0; // How often should cron check this module (seconds)? diff --git a/mod/quiz/view.php b/mod/quiz/view.php index e5579164a5b63..f402cbe9af9e9 100644 --- a/mod/quiz/view.php +++ b/mod/quiz/view.php @@ -277,7 +277,7 @@ if ($feedbackcolumn && $attempt->timefinish > 0) { if ($attemptoptions->overallfeedback) { - $row[] = quiz_feedback_for_grade($attemptgrade, $quiz->id); + $row[] = quiz_feedback_for_grade($attemptgrade, $quiz, $context, $cm); } else { $row[] = ''; } @@ -330,7 +330,7 @@ } if ($feedbackcolumn) { $resultinfo .= $OUTPUT->heading(get_string('overallfeedback', 'quiz'), 3, 'main'); - $resultinfo .= '

'.quiz_feedback_for_grade($mygrade, $quiz->id)."

\n"; + $resultinfo .= '

'.quiz_feedback_for_grade($mygrade, $quiz, $context, $cm)."

\n"; } if ($resultinfo) { diff --git a/pluginfile.php b/pluginfile.php index bcc5291f93790..de9730363b5b7 100644 --- a/pluginfile.php +++ b/pluginfile.php @@ -630,6 +630,12 @@ send_file_not_found(); } +// ======================================================================================================================== +} else if ($component === 'question') { + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'question', $filearea, $args, $forcedownload); + send_file_not_found(); + // ======================================================================================================================== } else if (strpos($component, 'mod_') === 0) { $modname = substr($component, 4); diff --git a/question/contextmoveq.php b/question/contextmoveq.php index a3a3acd05bd24..9f0be647f6080 100644 --- a/question/contextmoveq.php +++ b/question/contextmoveq.php @@ -12,7 +12,7 @@ require_once(dirname(__FILE__) . '/editlib.php'); require_once($CFG->dirroot.'/question/contextmoveq_form.php'); -$ids = required_param('ids',PARAM_SEQUENCE); // question ids +$ids = required_param('ids', PARAM_SEQUENCE); // question ids if (!$cmid = optional_param('cmid', 0, PARAM_INT)){ $courseid = required_param('courseid', PARAM_INT); @@ -165,6 +165,9 @@ if (!question_move_questions_to_category($ids, $tocat->id)) { print_error('errormovingquestions', 'question', $returnurl, $ids); } + if ($returnurl) { + $returnurl = new moodle_url('/' . $returnurl); + } redirect($returnurl); } diff --git a/question/edit.php b/question/edit.php index 9c594877e57c2..f19bb1c3444b1 100644 --- a/question/edit.php +++ b/question/edit.php @@ -24,11 +24,11 @@ /////////////////////////////////////////////////////////////////////////// /** -* Page to edit the question bank -* -* @license http://www.gnu.org/copyleft/gpl.html GNU Public License -* @package questionbank -*//** */ + * Page to edit the question bank + * + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + * @package questionbank + */ require_once("../config.php"); require_once("editlib.php"); @@ -67,7 +67,7 @@ $context = $contexts->lowest(); $streditingquestions = get_string('editquestions', "quiz"); - $PAGE->set_title($streditingquestions); + $PAGE->set_title($streditingquestions); $PAGE->set_heading($COURSE->fullname); echo $OUTPUT->header(); diff --git a/question/editlib.php b/question/editlib.php index 1daca7aa09f01..33bab06baf63f 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -1402,7 +1402,7 @@ public function process_actions() { $questionids[] = $key; } } - if ($questionids){ + if ($questionids) { list($usql, $params) = $DB->get_in_or_equal($questionids); $sql = "SELECT q.*, c.contextid FROM {question} q, {question_categories} c WHERE q.id $usql AND c.id = q.category"; if (!$questions = $DB->get_records_sql($sql, $params)){ @@ -1426,8 +1426,9 @@ public function process_actions() { } else { $returnurl = str_replace($CFG->wwwroot . '/', '', $returnurl); $movecontexturl = new moodle_url('/question/contextmoveq.php', - array('returnurl' => $returnurl, 'ids' => $questionids, - 'tocatid' => $tocategoryid)); + array('returnurl' => $returnurl, + 'ids' => implode(',', $questionids), + 'tocatid' => $tocategoryid)); if (!empty($cm->id)){ $movecontexturl->param('cmid', $cm->id); } else { diff --git a/question/format.php b/question/format.php index fdbcc1f3a6b72..cc0203f3f0e93 100644 --- a/question/format.php +++ b/question/format.php @@ -862,6 +862,7 @@ function question_get_export_dir() { * performs the conversion. */ function format_question_text($question) { + global $DB; $formatoptions = new stdClass; $formatoptions->noclean = true; $formatoptions->para = false; @@ -870,10 +871,7 @@ function format_question_text($question) { } else { $format = $question->questiontextformat; } - return format_text($question->questiontext, $format, $formatoptions); + $text = $question->questiontext; + return format_text(html_to_text($text), $format, $formatoptions); } - - } - - diff --git a/question/preview.php b/question/preview.php index 7e6064143babf..daad31b4fbca8 100644 --- a/question/preview.php +++ b/question/preview.php @@ -211,7 +211,7 @@ $PAGE->set_title($strpreview); $PAGE->set_heading($COURSE->fullname); echo $OUTPUT->header(); - + if (!empty($quizid)) { echo '

'.get_string('modulename', 'quiz') . ': '; p(format_string($quiz->name)); @@ -219,7 +219,7 @@ } $number = 1; echo '

', "\n"; - print_question($questions[$id], $curstate, $number, $quiz, $options); + print_question($questions[$id], $curstate, $number, $quiz, $options, $context); echo '
'; echo html_writer::input_hidden_params($url); diff --git a/question/previewlib.php b/question/previewlib.php new file mode 100644 index 0000000000000..c3dfbfb69b70c --- /dev/null +++ b/question/previewlib.php @@ -0,0 +1,108 @@ +. + +/** + * Library functions used by question/preview.php. + * + * @package core + * @subpackage questionengine + * @copyright 2010 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Called via pluginfile.php -> question_pluginfile to serve files belonging to + * a question in a question_attempt when that attempt is a preview. + * + * @param object $course course settings object + * @param object $context context object + * @param string $component the name of the component we are serving files for. + * @param string $filearea the name of the file area. + * @param array $args the remaining bits of the file path. + * @param bool $forcedownload whether the user must be forced to download the file. + * @return bool false if file not found, does not return if found - justsend the file + */ +function question_preview_question_pluginfile($course, $context, $component, + $filearea, $attemptid, $questionid, $args, $forcedownload) { + global $USER, $SESSION, $DB, $CFG; + require_once($CFG->dirroot . '/mod/quiz/locallib.php'); + + if (!$question = $DB->get_record('question', array('id' => $questionid))) { + return send_file_not_found(); + } + + if (!question_has_capability_on($question, 'use', $question->category)) { + send_file_not_found(); + } + + if (!isset($SESSION->quizpreview->states) || $SESSION->quizpreview->questionid != $questionid) { + send_file_not_found(); + } + + $states = end($SESSION->quizpreview->states); + if (!array_key_exists($question->id, $states)) { + send_file_not_found(); + } + $state = $states[$question->id]; + + // Build fake cmoptions + $quiz = new cmoptions; + $quiz->id = 0; + $quiz->review = get_config('quiz', 'review'); + if (empty($course->id)) { + $quiz->course = SITEID; + } else { + $quiz->course = $course->id; + } + $quiz->decimalpoints = get_config('quiz', 'decimalpoints'); + + $questions[$question->id] = $question; + get_question_options($questions); + + // Build fake attempt + $timenow = time(); + $attempt = new stdclass; + $attempt->quiz = $quiz->id; + $attempt->userid = $USER->id; + $attempt->attempt = 0; + $attempt->sumgrades = 0; + $attempt->timestart = $timenow; + $attempt->timefinish = 0; + $attempt->timemodified = $timenow; + $attempt->uniqueid = 0; + $attempt->id = 0; + $attempt->layout = $question->id; + + $options = quiz_get_renderoptions($quiz, $attempt, $context, $state); + $options->noeditlink = true; + // XXX: mulitichoice type needs quiz id to get maxgrade + $options->quizid = 0; + + if (!question_check_file_access($question, $state, $options, $context->id, $component, + $filearea, $args, $forcedownload)) { + send_file_not_found(); + } + + $fs = get_file_storage(); + $relativepath = implode('/', $args); + $fullpath = "/$context->id/$component/$filearea/$relativepath"; + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + send_stored_file($file, 0, 0, $forcedownload); +} diff --git a/question/question.php b/question/question.php index 52ec0bb8e595b..e63c603b63e2f 100644 --- a/question/question.php +++ b/question/question.php @@ -75,6 +75,7 @@ $thiscontext = get_context_instance(CONTEXT_MODULE, $cmid); } elseif ($courseid) { require_login($courseid, false); + $PAGE->set_pagelayout('course'); $thiscontext = get_context_instance(CONTEXT_COURSE, $courseid); $module = null; $cm = null; @@ -106,7 +107,7 @@ } else if ($categoryid) { // Category, but no qtype. They probably came from the addquestion.php - // script without choosing a question type. Send them back. + // script without choosing a question type. Send them back. $addurl = new moodle_url('/question/addquestion.php', $url->params()); $addurl->param('validationerror', 1); redirect($addurl); @@ -151,6 +152,8 @@ } else { // creating a new question require_capability('moodle/question:add', $categorycontext); $formeditable = true; + $question->formoptions->canedit = question_has_capability_on($question, 'edit'); + $question->formoptions->canmove = (question_has_capability_on($question, 'move') && $addpermission); $question->formoptions->repeatelements = true; $question->formoptions->movecontext = false; } @@ -189,7 +192,9 @@ } else { $toform->courseid = $COURSE->id; } + $toform->inpopup = $inpopup; + $mform->set_data($toform); if ($mform->is_cancelled()){ diff --git a/question/type/calculated/db/install.xml b/question/type/calculated/db/install.xml index 9304024767907..7a2426324f5f4 100644 --- a/question/type/calculated/db/install.xml +++ b/question/type/calculated/db/install.xml @@ -1,5 +1,5 @@ - @@ -29,10 +29,13 @@ - - - - + + + + + + + diff --git a/question/type/calculated/db/upgrade.php b/question/type/calculated/db/upgrade.php index c6131e31f1803..ce05a3ac65782 100644 --- a/question/type/calculated/db/upgrade.php +++ b/question/type/calculated/db/upgrade.php @@ -138,6 +138,62 @@ function xmldb_qtype_calculated_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2010020800, 'qtype', 'calculated'); } + if ($oldversion < 2010020801) { + + // Define field correctfeedbackformat to be added to question_calculated_options + $table = new xmldb_table('question_calculated_options'); + $field = new xmldb_field('correctfeedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'correctfeedback'); + + // Conditionally launch add field correctfeedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field partiallycorrectfeedbackformat to be added to question_calculated_options + $field = new xmldb_field('partiallycorrectfeedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'partiallycorrectfeedback'); + + // Conditionally launch add field partiallycorrectfeedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field incorrectfeedbackformat to be added to question_calculated_options + $field = new xmldb_field('incorrectfeedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'incorrectfeedback'); + + // Conditionally launch add field incorrectfeedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // fix fieldformat + $rs = $DB->get_recordset('question_calculated_options'); + foreach ($rs as $record) { + if ($CFG->texteditors !== 'textarea') { + if (!empty($record->correctfeedback)) { + $record->correctfeedback = text_to_html($record->correctfeedback); + } + $record->correctfeedbackformat = FORMAT_HTML; + if (!empty($record->partiallycorrectfeedback)) { + $record->partiallycorrectfeedback = text_to_html($record->partiallycorrectfeedback); + } + $record->partiallycorrectfeedbackformat = FORMAT_HTML; + if (!empty($record->incorrectfeedback)) { + $record->incorrectfeedback = text_to_html($record->incorrectfeedback); + } + $record->incorrectfeedbackformat = FORMAT_HTML; + } else { + $record->correctfeedbackformat = FORMAT_MOODLE; + $record->partiallycorrectfeedbackformat = FORMAT_MOODLE; + $record->incorrectfeedbackformat = FORMAT_MOODLE; + } + $DB->update_record('question_calculated_options', $record); + } + $rs->close(); + + // calculated savepoint reached + upgrade_plugin_savepoint(true, 2010020801, 'qtype', 'calculated'); + } + return true; } diff --git a/question/type/calculated/edit_calculated_form.php b/question/type/calculated/edit_calculated_form.php index 7c187f42c63fc..1e52ec3392c57 100644 --- a/question/type/calculated/edit_calculated_form.php +++ b/question/type/calculated/edit_calculated_form.php @@ -1,4 +1,20 @@ . + /** * Defines the editing form for the calculated question type. * @@ -19,12 +35,12 @@ class question_edit_calculated_form extends question_edit_form { * @var question_calculated_qtype */ public $qtypeobj; - public $questiondisplay ; - public $activecategory ; - public $categorychanged = false ; + public $questiondisplay; + public $activecategory; + public $categorychanged = false; public $initialname = ''; - public $reload = false ; - + public $reload = false; + function question_edit_calculated_form(&$submiturl, &$question, &$category, &$contexts, $formeditable = true){ global $QTYPES, $SESSION, $CFG, $DB; $this->question = $question; @@ -42,20 +58,20 @@ function question_edit_calculated_form(&$submiturl, &$question, &$category, &$co $regs= array(); if(preg_match('~#\{([^[:space:]]*)#~',$question->name , $regs)){ $question->name = str_replace($regs[0], '', $question->name); - }; + }; } }else { - } + } parent::question_edit_form($submiturl, $question, $category, $contexts, $formeditable); } + function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) { - // $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption); - $repeated = array(); + // $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption); + $repeated = array(); $repeated[] =& $mform->createElement('header', 'answerhdr', $label); $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50)); $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions); - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), - array('course' => $this->coursefilesid)); + $repeated[] =& $mform->createElement('editor', 'feedback', get_string('feedback', 'quiz'), null, $this->editoroptions); $repeatedoptions['answer']['type'] = PARAM_RAW; $repeatedoptions['fraction']['default'] = 0; $answersoption = 'answers'; @@ -63,8 +79,8 @@ function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions $mform->setType('answer', PARAM_NOTAGS); $addrepeated = array(); - $addrepeated[] =& $mform->createElement('text', 'tolerance', get_string('tolerance', 'qtype_calculated')); - $addrepeated[] =& $mform->createElement('select', 'tolerancetype', get_string('tolerancetype', 'quiz'), $this->qtypeobj->tolerance_types()); + $addrepeated[] =& $mform->createElement('text', 'tolerance', get_string('tolerance', 'qtype_calculated')); + $addrepeated[] =& $mform->createElement('select', 'tolerancetype', get_string('tolerancetype', 'quiz'), $this->qtypeobj->tolerance_types()); $repeatedoptions['tolerance']['type'] = PARAM_NUMBER; $repeatedoptions['tolerance']['default'] = 0.01; @@ -74,7 +90,7 @@ function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions $answerlengthformats = array('1' => get_string('decimalformat', 'quiz'), '2' => get_string('significantfiguresformat', 'quiz')); $addrepeated[] =& $mform->createElement('select', 'correctanswerformat', get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats); array_splice($repeated, 3, 0, $addrepeated); - $repeated[1]->setLabel(get_string('correctanswerformula', 'quiz').'='); + $repeated[1]->setLabel(get_string('correctanswerformula', 'quiz').'='); return $repeated; } @@ -87,8 +103,8 @@ function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions function definition_inner(&$mform) { global $QTYPES; $this->qtypeobj =& $QTYPES[$this->qtype()]; - // echo code left for testing period - // echo "

question ".optional_param('multichoice', '', PARAM_RAW)." optional

";print_r($this->question);echo "

"; + // echo code left for testing period + // echo "

question ".optional_param('multichoice', '', PARAM_RAW)." optional

";print_r($this->question);echo "

"; $label = get_string('sharedwildcards', 'qtype_calculated'); $mform->addElement('hidden', 'initialcategory', 1); $mform->addElement('hidden', 'reload', 1); @@ -100,43 +116,42 @@ function definition_inner(&$mform) { }; $addfieldsname='updatecategory'; $addstring=get_string("updatecategory", "qtype_calculated"); - $mform->registerNoSubmitButton($addfieldsname); + $mform->registerNoSubmitButton($addfieldsname); $mform->insertElementBefore( $mform->createElement('submit', $addfieldsname, $addstring),'listcategory'); $mform->registerNoSubmitButton('createoptionbutton'); //editing as regular - $mform->setType('single', PARAM_INT); + $mform->setType('single', PARAM_INT); - $mform->addElement('hidden','shuffleanswers', '1'); - $mform->setType('shuffleanswers', PARAM_INT); - $mform->addElement('hidden','answernumbering', 'abc'); - $mform->setType('answernumbering', PARAM_SAFEDIR); + $mform->addElement('hidden','shuffleanswers', '1'); + $mform->setType('shuffleanswers', PARAM_INT); + $mform->addElement('hidden','answernumbering', 'abc'); + $mform->setType('answernumbering', PARAM_SAFEDIR); $creategrades = get_grade_options(); - $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'), - $creategrades->gradeoptions, 1, 1); - + $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'), $creategrades->gradeoptions, 1, 1); $repeated = array(); $QTYPES['numerical']->add_units_options($mform,$this); $QTYPES['numerical']->add_units_elements($mform,$this); + + $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice')); foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { - $mform->addElement('hidden', $feedbackname); + $mform->addElement('editor', $feedbackname, get_string($feedbackname, 'qtype_calculated'), null, $this->editoroptions); $mform->setType($feedbackname, PARAM_RAW); } + //hidden elements $mform->addElement('hidden', 'synchronize', ''); $mform->setType('synchronize', PARAM_INT); $mform->addElement('hidden', 'wizard', 'datasetdefinitions'); $mform->setType('wizard', PARAM_ALPHA); - - } - function set_data($question) { + function data_preprocessing($question) { global $QTYPES; $default_values = array(); @@ -145,26 +160,58 @@ function set_data($question) { if (count($answers)) { $key = 0; foreach ($answers as $answer){ + $draftid = file_get_submitted_draft_itemid('feedback['.$key.']'); $default_values['answer['.$key.']'] = $answer->answer; $default_values['fraction['.$key.']'] = $answer->fraction; $default_values['tolerance['.$key.']'] = $answer->tolerance; $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype; $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength; $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat; - $default_values['feedback['.$key.']'] = $answer->feedback; + $default_values['feedback['.$key.']'] = array(); + $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'question', // component + 'answerfeedback', // filarea + !empty($answer->id)?(int)$answer->id:null, // itemid + $this->fileoptions, // options + $answer->feedback // text + ); + $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat; + $default_values['feedback['.$key.']']['itemid'] = $draftid; $key++; } } - $default_values['synchronize'] = $question->options->synchronize ; - $QTYPES['numerical']->set_numerical_unit_data($question,$default_values); + $default_values['synchronize'] = $question->options->synchronize ; + // set unit data, prepare files in instruction area + $QTYPES['numerical']->set_numerical_unit_data($this, $question, $default_values); } if (isset($question->options->single)){ $default_values['single'] = $question->options->single; $default_values['answernumbering'] = $question->options->answernumbering; $default_values['shuffleanswers'] = $question->options->shuffleanswers; - $default_values['correctfeedback'] = $question->options->correctfeedback; - $default_values['partiallycorrectfeedback'] = $question->options->partiallycorrectfeedback; - $default_values['incorrectfeedback'] = $question->options->incorrectfeedback; + //$default_values['correctfeedback'] = $question->options->correctfeedback; + //$default_values['partiallycorrectfeedback'] = $question->options->partiallycorrectfeedback; + //$default_values['incorrectfeedback'] = $question->options->incorrectfeedback; + // prepare feedback editor to display files in draft area + foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { + $draftid = file_get_submitted_draft_itemid($feedbackname); + $text = $question->options->$feedbackname; + $feedbackformat = $feedbackname . 'format'; + $format = $question->options->$feedbackformat; + $default_values[$feedbackname] = array(); + $default_values[$feedbackname]['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'qtype_calculated', // component + $feedbackname, // filarea + !empty($question->id)?(int)$question->id:null, // itemid + $this->fileoptions, // options + $text // text + ); + $default_values[$feedbackname]['format'] = $format; + $default_values[$feedbackname]['itemid'] = $draftid; + } } $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated'); $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated'); @@ -173,16 +220,16 @@ function set_data($question) { update category button. The value can be obtain by $qu->category =$this->_form->_elements[$this->_form->_elementIndex['category']]->_values[0]; but is coded using existing functions - */ - $qu = new stdClass; - $el = new stdClass; - /* no need to call elementExists() here */ - if ($this->_form->elementExists('category')){ + */ + $qu = new stdClass; + $el = new stdClass; + /* no need to call elementExists() here */ + if ($this->_form->elementExists('category')){ $el=$this->_form->getElement('category'); - } else { + } else { $el=$this->_form->getElement('categorymoveto'); - } - if($value =$el->getSelected()) { + } + if($value =$el->getSelected()) { $qu->category =$value[0]; }else { $qu->category=$question->category;// on load $question->category is set by question.php @@ -191,7 +238,7 @@ function set_data($question) { $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2 ; $question = (object)((array)$question + $default_values); - parent::set_data($question); + return $question; } function qtype() { @@ -200,21 +247,21 @@ function qtype() { function validation($data, $files) { global $QTYPES; - // echo code left for testing period + // echo code left for testing period - // echo "

question

";print_r($this->question);echo "

"; - // echo "

data

";print_r($data);echo "

"; + // echo "

question

";print_r($this->question);echo "

"; + // echo "

data

";print_r($data);echo "

"; $errors = parent::validation($data, $files); //verifying for errors in {=...} in question text; $qtext = ""; - $qtextremaining = $data['questiontext'] ; - $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']); - foreach ($possibledatasets as $name => $value) { + $qtextremaining = $data['questiontext']['text']; + $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']); + foreach ($possibledatasets as $name => $value) { $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining); } - // echo "numericalquestion qtextremaining
";print_r($possibledatasets);
-        while  (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
+        // echo "numericalquestion qtextremaining 
";print_r($possibledatasets);
+        while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
             $qtext =$qtext.$qtextsplits[0];
             $qtextremaining = $qtextsplits[1];
@@ -229,45 +276,45 @@ function validation($data, $files) {
         $answers = $data['answer'];
         $answercount = 0;
         $maxgrade = false;
-        $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']);
+        $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
         $mandatorydatasets = array();
         foreach ($answers as $key => $answer){
             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
         }
         if ( count($mandatorydatasets )==0){
-          //  $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent');
+            //  $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent');
             foreach ($answers as $key => $answer){
                 $errors['answer['.$key.']'] = get_string('atleastonewildcard', 'qtype_datasetdependent');
             }
         }
- // regular calculated
-            foreach ($answers as $key => $answer){
-                //check no of choices
-                // the * for everykind of answer not actually implemented
-                $trimmedanswer = trim($answer);
-                if (($trimmedanswer!='')||$answercount==0){
-                    $eqerror = qtype_calculated_find_formula_errors($trimmedanswer);
-                    if (FALSE !== $eqerror){
-                        $errors['answer['.$key.']'] = $eqerror;
-                    }
+        // regular calculated
+        foreach ($answers as $key => $answer){
+            //check no of choices
+            // the * for everykind of answer not actually implemented
+            $trimmedanswer = trim($answer);
+            if (($trimmedanswer!='')||$answercount==0){
+                $eqerror = qtype_calculated_find_formula_errors($trimmedanswer);
+                if (FALSE !== $eqerror){
+                    $errors['answer['.$key.']'] = $eqerror;
                 }
-                if ($trimmedanswer!=''){
-                    if ('2' == $data['correctanswerformat'][$key]
-                            && '0' == $data['correctanswerlength'][$key]) {
+            }
+            if ($trimmedanswer!=''){
+                if ('2' == $data['correctanswerformat'][$key]
+                    && '0' == $data['correctanswerlength'][$key]) {
                         $errors['correctanswerlength['.$key.']'] = get_string('zerosignificantfiguresnotallowed','quiz');
                     }
-                    if (!is_numeric($data['tolerance'][$key])){
-                        $errors['tolerance['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
-                    }
-                    if ($data['fraction'][$key] == 1) {
-                       $maxgrade = true;
-                    }
-
-                    $answercount++;
+                if (!is_numeric($data['tolerance'][$key])){
+                    $errors['tolerance['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
                 }
-                //check grades
+                if ($data['fraction'][$key] == 1) {
+                    $maxgrade = true;
+                }
+
+                $answercount++;
+            }
+            //check grades
 
-                //TODO how should grade checking work here??
+            //TODO how should grade checking work here??
                 /*if ($answer != '') {
                     if ($data['fraction'][$key] > 0) {
                         $totalfraction += $data['fraction'][$key];
@@ -276,10 +323,10 @@ function validation($data, $files) {
                         $maxfraction = $data['fraction'][$key];
                     }
                 }*/
-            }
+        }
 
-            //grade checking :
-            /// Perform sanity checks on fractional grades
+        //grade checking :
+        /// Perform sanity checks on fractional grades
             /*if ( ) {
                 if ($maxfraction != 1) {
                     $maxfraction = $maxfraction * 100;
@@ -292,7 +339,7 @@ function validation($data, $files) {
                     $errors['fraction[0]'] = get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
                 }
             }
-            $units  = $data['unit'];
+            $units = $data['unit'];
             if (count($units)) {
                 foreach ($units as $key => $unit){
                     if (is_numeric($unit)){
@@ -309,18 +356,17 @@ function validation($data, $files) {
                         }
 
                     }
-                }                
+                }
             }*/
-            $QTYPES['numerical']->validate_numerical_options($data, $errors) ;
-            if ($answercount==0){
-                $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
-            }
-            if ($maxgrade == false) {
-                $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
-            }
-        
+        $QTYPES['numerical']->validate_numerical_options($data, $errors) ;
+        if ($answercount==0){
+            $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
+        }
+        if ($maxgrade == false) {
+            $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
+        }
+
 
         return $errors;
     }
 }
-
diff --git a/question/type/calculated/lang/en/qtype_calculated.php b/question/type/calculated/lang/en/qtype_calculated.php
index 11def2d37c0d5..bd895b1da6534 100644
--- a/question/type/calculated/lang/en/qtype_calculated.php
+++ b/question/type/calculated/lang/en/qtype_calculated.php
@@ -45,6 +45,7 @@
 $string['choosedatasetproperties_help'] = 'A dataset is a set of values inserted in place of a wildcard. You can create a private dataset for a specific question, or a shared dataset that can be used for other calculated questions within the category.';
 $string['correctanswershows'] = 'Correct answer shows';
 $string['correctanswershowsformat'] = 'Format';
+$string['correctfeedback'] = 'For any correct response';
 $string['dataitemdefined']='with {$a} numerical values already defined is available';
 $string['datasetrole']= ' The wild cards {x..} will be substituted by a numerical value from their dataset';
 $string['deleteitem'] = 'Delete Item';
@@ -61,6 +62,7 @@
 $string['forceregenerationshared'] = 'forceregeneration of only non-shared wildcards';
 $string['getnextnow'] = 'Get New \'Item to Add\' Now';
 $string['hexanotallowed'] = 'Dataset {$a->name} hexadecimal format value $a->value is not allowed' ;
+$string['incorrectfeedback'] = 'For any incorrect response';
 $string['item(s)'] = 'item(s)';
 $string['itemno'] = 'Item {$a}';
 $string['itemscount']='Items
Count'; @@ -91,8 +93,9 @@ $string['nodataset'] = 'nothing - it is not a wild card'; $string['nosharedwildcard'] = 'No shared wild card in this category'; $string['notvalidnumber'] = 'Wild card value is not a valid number ' ; -$string['oneanswertrueansweroutsidelimits'] = 'At least one correct answer outside the true value limits.
Modify the answers tolerance settings available as Advanced parameters'; +$string['oneanswertrueansweroutsidelimits'] = 'At least one correct answer outside the true value limits.
Modify the answers tolerance settings available as Advanced parameters'; $string['param'] = 'Param {{$a}}'; +$string['partiallycorrectfeedback'] = 'For any partially correct response'; $string['possiblehdr'] = 'Possible wild cards present only in the question text'; $string['questiondatasets'] = 'Question datasets'; $string['questiondatasets_help'] = 'Question datasets of wild cards that will be used in each individual question'; diff --git a/question/type/calculated/lib.php b/question/type/calculated/lib.php new file mode 100644 index 0000000000000..7cd62081db228 --- /dev/null +++ b/question/type/calculated/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package questionbank + * @subpackage questiontypes + * @author Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_calculated_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_calculated', $filearea, $args, $forcedownload); +} diff --git a/question/type/calculated/questiontype.php b/question/type/calculated/questiontype.php index ea43f1ebaec31..794b949438f2d 100644 --- a/question/type/calculated/questiontype.php +++ b/question/type/calculated/questiontype.php @@ -1,17 +1,31 @@ . + + ///////////////// // CALCULATED /// ///////////////// /// QUESTION TYPE CLASS ////////////////// - - class question_calculated_qtype extends default_questiontype { // Used by the function custom_generator_tools: - var $calcgenerateidhasbeenadded = false; + public $calcgenerateidhasbeenadded = false; public $virtualqtype = false; function name() { @@ -29,10 +43,10 @@ function requires_qtypes() { function get_question_options(&$question) { // First get the datasets and default options // the code is used for calculated, calculatedsimple and calculatedmulti qtypes - global $CFG, $DB, $OUTPUT, $QTYPES; + global $CFG, $DB, $OUTPUT, $QTYPES; if (!$question->options = $DB->get_record('question_calculated_options', array('question' => $question->id))) { - // echo $OUTPUT->notification('Error: Missing question options for calculated question'.$question->id.'!'); - // return false; + // echo $OUTPUT->notification('Error: Missing question options for calculated question'.$question->id.'!'); + // return false; $question->options->synchronize = 0; $question->options->single = 0; //$question->single; $question->options->answernumbering = 'abc'; @@ -40,19 +54,18 @@ function get_question_options(&$question) { $question->options->correctfeedback = ''; $question->options->partiallycorrectfeedback = ''; $question->options->incorrectfeedback = ''; - } if (!$question->options->answers = $DB->get_records_sql( - "SELECT a.*, c.tolerance, c.tolerancetype, c.correctanswerlength, c.correctanswerformat " . - "FROM {question_answers} a, " . - " {question_calculated} c " . - "WHERE a.question = ? " . - "AND a.id = c.answer ". - "ORDER BY a.id ASC", array($question->id))) { - echo $OUTPUT->notification('Error: Missing question answer for calculated question ' . $question->id . '!'); - return false; - } + "SELECT a.*, c.tolerance, c.tolerancetype, c.correctanswerlength, c.correctanswerformat " . + "FROM {question_answers} a, " . + " {question_calculated} c " . + "WHERE a.question = ? " . + "AND a.id = c.answer ". + "ORDER BY a.id ASC", array($question->id))) { + echo $OUTPUT->notification('Error: Missing question answer for calculated question ' . $question->id . '!'); + return false; + } if ( $this->get_virtual_qtype() == $QTYPES['numerical']){ $QTYPES['numerical']->get_numerical_units($question); @@ -66,16 +79,12 @@ function get_question_options(&$question) { } function get_datasets_for_export(&$question){ - global $DB; + global $DB, $CFG; $datasetdefs = array(); if (!empty($question->id)) { - global $CFG; $sql = "SELECT i.* - FROM {question_datasets} d, - {question_dataset_definitions} i - WHERE d.question = ? - AND d.datasetdefinition = i.id - "; + FROM {question_datasets} d, {question_dataset_definitions} i + WHERE d.question = ? AND d.datasetdefinition = i.id"; if ($records = $DB->get_records_sql($sql, array($question->id))) { foreach ($records as $r) { $def = $r ; @@ -90,7 +99,7 @@ function get_datasets_for_export(&$question){ $def->minimum=$min; $def->maximum=$max; $def->decimals=$dec ; - if ($def->itemcount > 0 ) { + if ($def->itemcount > 0 ) { // get the datasetitems $def->items = array(); if ($items = $this->get_database_dataset_items($def->id)){ @@ -100,8 +109,8 @@ function get_datasets_for_export(&$question){ $def->items[$n] = new stdClass; $def->items[$n]->itemnumber=$ii->itemnumber; $def->items[$n]->value=$ii->value; - } - $def->number_of_items=$n ; + } + $def->number_of_items=$n ; } } $datasetdefs["1-$r->category-$r->name"] = $def; @@ -112,10 +121,9 @@ function get_datasets_for_export(&$question){ } function save_question_options($question) { - //$options = $question->subtypeoptions; - // Get old answers: - // the code is used for calculated, calculatedsimple and calculatedmulti qtypes global $CFG, $DB, $QTYPES ; + // the code is used for calculated, calculatedsimple and calculatedmulti qtypes + $context = $question->context; if (isset($question->answer) && !isset($question->answers)) { $question->answers = $question->answer; } @@ -129,26 +137,26 @@ function save_question_options($question) { } // as used only by calculated if(isset($question->synchronize)){ - $options->synchronize = $question->synchronize; + $options->synchronize = $question->synchronize; }else { $options->synchronize = 0 ; } $options->single = 0; //$question->single; $options->answernumbering = $question->answernumbering; $options->shuffleanswers = $question->shuffleanswers; - $options->correctfeedback = trim($question->correctfeedback); - $options->partiallycorrectfeedback = trim($question->partiallycorrectfeedback); - $options->incorrectfeedback = trim($question->incorrectfeedback); + + foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { + $feedback = $question->$feedbackname; + $options->$feedbackname = trim($feedback['text']); + $feedbackformat = $feedbackname . 'format'; + $options->$feedbackformat = trim($feedback['format']); + $options->$feedbackname = file_save_draft_area_files($feedback['itemid'], $context->id, 'qtype_calculated', $feedbackname, $question->id, self::$fileoptions, trim($feedback['text'])); + } + if ($update) { - if (!$DB->update_record("question_calculated_options", $options)) { - $result->error = "Could not update calculated question options! (id=$options->id)"; - return $result; - } + $DB->update_record("question_calculated_options", $options); } else { - if (!$DB->insert_record("question_calculated_options", $options)) { - $result->error = "Could not insert calculated question options!"; - return $result; - } + $DB->insert_record("question_calculated_options", $options); } // Get old versions of the objects @@ -173,18 +181,22 @@ function save_question_options($question) { $question->answers=$question->answer; } foreach ($question->answers as $key => $dataanswer) { - if ( trim($dataanswer) != '' ) { + if ( trim($dataanswer) != '' ) { $answer = new stdClass; $answer->question = $question->id; $answer->answer = trim($dataanswer); $answer->fraction = $question->fraction[$key]; - $answer->feedback = trim($question->feedback[$key]); + $answer->feedbackformat = $question->feedback[$key]['format']; if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it $answer->id = $oldanswer->id; + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $oldanswer->id, self::$fileoptions, trim($question->feedback[$key]['text'])); $DB->update_record("question_answers", $answer); } else { // This is a completely new answer + $answer->feedback = ''; $answer->id = $DB->insert_record("question_answers", $answer); + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, trim($question->feedback[$key]['text'])); + $DB->set_field('question_answers', 'feedback', $answer->feedback, array('id'=>$answer->id)); } // Set up the options object @@ -219,15 +231,15 @@ function save_question_options($question) { $DB->delete_records('question_calculated', array('id' => $oo->id)); } } + $result = $QTYPES['numerical']->save_numerical_options($question); if (isset($result->error)) { return $result; } - if( isset($question->import_process)&&$question->import_process){ $this->import_datasets($question); - } + } // Report any problems. if (!empty($result->notice)) { return $result; @@ -250,12 +262,12 @@ function import_datasets($question){ $todo='create' ; }else if ($dataset->status =='shared' ){ if ($sharedatasetdefs = $DB->get_records_select( - 'question_dataset_definitions', - "type = '1' - AND name = ? - AND category = ? - ORDER BY id DESC ", array($dataset->name, $question->category) - )) { // so there is at least one + 'question_dataset_definitions', + "type = '1' + AND name = ? + AND category = ? + ORDER BY id DESC ", array($dataset->name, $question->category) + )) { // so there is at least one $sharedatasetdef = array_shift($sharedatasetdefs); if ( $sharedatasetdef->options == $datasetdef->options ){// identical so use it $todo='useit' ; @@ -267,15 +279,15 @@ function import_datasets($question){ }else { // no so create one $datasetdef->category =$question->category ; $todo='create' ; - } + } } if ( $todo=='create'){ $datasetdef->id = $DB->insert_record( 'question_dataset_definitions', $datasetdef); - } - // Create relation to the dataset: - $questiondataset = new stdClass; - $questiondataset->question = $question->id; - $questiondataset->datasetdefinition = $datasetdef->id; + } + // Create relation to the dataset: + $questiondataset = new stdClass; + $questiondataset->question = $question->id; + $questiondataset->datasetdefinition = $datasetdef->id; $DB->insert_record('question_datasets', $questiondataset); if ($todo=='create'){ // add the items foreach ($dataset->datasetitem as $dataitem ){ @@ -292,20 +304,20 @@ function import_datasets($question){ function restore_session_and_responses(&$question, &$state) { global $OUTPUT; if (!preg_match('~^dataset([0-9]+)[^-]*-(.*)$~', - $state->responses[''], $regs)) { - echo $OUTPUT->notification("Wrongly formatted raw response answer " . - "{$state->responses['']}! Could not restore session for " . - " question #{$question->id}."); - $state->options->datasetitem = 1; - $state->options->dataset = array(); - $state->responses = array('' => ''); - return false; - } + $state->responses[''], $regs)) { + echo $OUTPUT->notification("Wrongly formatted raw response answer " . + "{$state->responses['']}! Could not restore session for " . + " question #{$question->id}."); + $state->options->datasetitem = 1; + $state->options->dataset = array(); + $state->responses = array('' => ''); + return false; + } // Restore the chosen dataset $state->options->datasetitem = $regs[1]; $state->options->dataset = - $this->pick_question_dataset($question,$state->options->datasetitem); + $this->pick_question_dataset($question,$state->options->datasetitem); $state->responses = array('' => $regs[2]); $virtualqtype = $this->get_virtual_qtype(); return $virtualqtype->restore_session_and_responses($question, $state); @@ -315,29 +327,25 @@ function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) // Find out how many datasets are available global $CFG, $DB, $QTYPES, $OUTPUT; if(!$maxnumber = (int)$DB->get_field_sql( - "SELECT MIN(a.itemcount) - FROM {question_dataset_definitions} a, - {question_datasets} b - WHERE b.question = ? - AND a.id = b.datasetdefinition", array($question->id))) { + "SELECT MIN(a.itemcount) + FROM {question_dataset_definitions} a, {question_datasets} b + WHERE b.question = ? AND a.id = b.datasetdefinition", array($question->id))) { print_error('cannotgetdsforquestion', 'question', '', $question->id); } - $sql = "SELECT i.* - FROM {question_datasets} d, - {question_dataset_definitions} i - WHERE d.question = ? - AND d.datasetdefinition = i.id - AND i.category != 0 - "; + + $sql = "SELECT i.* + FROM {question_datasets} d, {question_dataset_definitions} i + WHERE d.question = ? AND d.datasetdefinition = i.id AND i.category != 0"; + if (!$question->options->synchronize || !$records = $DB->get_records_sql($sql, array($question->id))) { $synchronize_calculated = false ; }else { - // i.e records is true so test coherence - $coherence = true ; - $a = new stdClass ; - $a->qid = $question->id ; - $a->qcat = $question->category ; - foreach($records as $def ){ + // i.e records is true so test coherence + $coherence = true ; + $a = new stdClass ; + $a->qid = $question->id ; + $a->qcat = $question->category ; + foreach($records as $def ){ if ($def->category != $question->category){ $a->name = $def->name; $a->sharedcat = $def->category ; @@ -346,8 +354,8 @@ function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) } } if(!$coherence){ - echo $OUTPUT->notification(get_string('nocoherencequestionsdatyasetcategory','qtype_calculated',$a)); - } + echo $OUTPUT->notification(get_string('nocoherencequestionsdatyasetcategory','qtype_calculated',$a)); + } $synchronize_calculated = true ; } @@ -368,18 +376,16 @@ function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) }; $state->options->dataset = - $this->pick_question_dataset($question,$state->options->datasetitem); - $virtualqtype = $this->get_virtual_qtype( ); - return $virtualqtype->create_session_and_responses($question, $state, $cmoptions, $attempt); - } - - + $this->pick_question_dataset($question,$state->options->datasetitem); + $virtualqtype = $this->get_virtual_qtype( ); + return $virtualqtype->create_session_and_responses($question, $state, $cmoptions, $attempt); + } function save_session_and_responses(&$question, &$state) { global $DB; $responses = 'dataset'.$state->options->datasetitem.'-' ; // regular numeric type - if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){ + if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){ $responses .= $state->responses['answer'].'|||||'.$question->options->units[$state->responses['unit']]->unit; }else if(isset($state->responses['unit'])){ $responses .= $state->responses['answer'].'|||||'.$state->responses['unit'] ; @@ -400,7 +406,7 @@ function create_runtime_question($question, $form) { foreach ($form->answers as $key => $answer) { $a->answer = trim($form->answer[$key]); $a->fraction = $form->fraction[$key];//new - $a->tolerance = $form->tolerance[$key]; + $a->tolerance = $form->tolerance[$key]; $a->tolerancetype = $form->tolerancetype[$key]; $a->correctanswerlength = $form->correctanswerlength[$key]; $a->correctanswerformat = $form->correctanswerformat[$key]; @@ -412,45 +418,45 @@ function create_runtime_question($question, $form) { function validate_form($form) { switch($form->wizardpage) { - case 'question': - $calculatedmessages = array(); - if (empty($form->name)) { - $calculatedmessages[] = get_string('missingname', 'quiz'); - } - if (empty($form->questiontext)) { - $calculatedmessages[] = get_string('missingquestiontext', 'quiz'); + case 'question': + $calculatedmessages = array(); + if (empty($form->name)) { + $calculatedmessages[] = get_string('missingname', 'quiz'); + } + if (empty($form->questiontext)) { + $calculatedmessages[] = get_string('missingquestiontext', 'quiz'); + } + // Verify formulas + foreach ($form->answers as $key => $answer) { + if ('' === trim($answer)) { + $calculatedmessages[] = + get_string('missingformula', 'quiz'); } - // Verify formulas - foreach ($form->answers as $key => $answer) { - if ('' === trim($answer)) { - $calculatedmessages[] = - get_string('missingformula', 'quiz'); - } - if ($formulaerrors = - qtype_calculated_find_formula_errors($answer)) { + if ($formulaerrors = + qtype_calculated_find_formula_errors($answer)) { $calculatedmessages[] = $formulaerrors; } - if (! isset($form->tolerance[$key])) { - $form->tolerance[$key] = 0.0; - } - if (! is_numeric($form->tolerance[$key])) { - $calculatedmessages[] = - get_string('tolerancemustbenumeric', 'quiz'); - } + if (! isset($form->tolerance[$key])) { + $form->tolerance[$key] = 0.0; } + if (! is_numeric($form->tolerance[$key])) { + $calculatedmessages[] = + get_string('tolerancemustbenumeric', 'quiz'); + } + } - if (!empty($calculatedmessages)) { - $errorstring = "The following errors were found:
"; - foreach ($calculatedmessages as $msg) { - $errorstring .= $msg . '
'; - } - print_error($errorstring); + if (!empty($calculatedmessages)) { + $errorstring = "The following errors were found:
"; + foreach ($calculatedmessages as $msg) { + $errorstring .= $msg . '
'; } + print_error($errorstring); + } - break; - default: - return parent::validate_form($form); - break; + break; + default: + return parent::validate_form($form); + break; } return true; } @@ -468,16 +474,16 @@ function print_next_wizard_page(&$question, &$form, $course) { // See where we're coming from switch($form->wizardpage) { - case 'question': - require("$CFG->dirroot/question/type/calculated/datasetdefinitions.php"); - break; - case 'datasetdefinitions': - case 'datasetitems': - require("$CFG->dirroot/question/type/calculated/datasetitems.php"); - break; - default: - print_error('invalidwizardpage', 'question'); - break; + case 'question': + require("$CFG->dirroot/question/type/calculated/datasetdefinitions.php"); + break; + case 'datasetdefinitions': + case 'datasetitems': + require("$CFG->dirroot/question/type/calculated/datasetitems.php"); + break; + default: + print_error('invalidwizardpage', 'question'); + break; } } @@ -495,18 +501,18 @@ function &next_wizard_form($submiturl, $question, $wizardnow){ // See where we're coming from switch($wizardnow) { - case 'datasetdefinitions': - require("$CFG->dirroot/question/type/calculated/datasetdefinitions_form.php"); - $mform = new question_dataset_dependent_definitions_form("$submiturl?wizardnow=datasetdefinitions", $question); - break; - case 'datasetitems': - require("$CFG->dirroot/question/type/calculated/datasetitems_form.php"); - $regenerate = optional_param('forceregeneration', 0, PARAM_BOOL); - $mform = new question_dataset_dependent_items_form("$submiturl?wizardnow=datasetitems", $question, $regenerate); - break; - default: - print_error('invalidwizardpage', 'question'); - break; + case 'datasetdefinitions': + require("$CFG->dirroot/question/type/calculated/datasetdefinitions_form.php"); + $mform = new question_dataset_dependent_definitions_form("$submiturl?wizardnow=datasetdefinitions", $question); + break; + case 'datasetitems': + require("$CFG->dirroot/question/type/calculated/datasetitems_form.php"); + $regenerate = optional_param('forceregeneration', 0, PARAM_BOOL); + $mform = new question_dataset_dependent_items_form("$submiturl?wizardnow=datasetitems", $question, $regenerate); + break; + default: + print_error('invalidwizardpage', 'question'); + break; } return $mform; @@ -523,25 +529,23 @@ function &next_wizard_form($submiturl, $question, $wizardnow){ function display_question_editing_page(&$mform, $question, $wizardnow){ global $OUTPUT ; switch ($wizardnow){ - case '': - //on first page default display is fine - parent::display_question_editing_page($mform, $question, $wizardnow); - return; - break; - case 'datasetdefinitions': - echo $OUTPUT->heading_with_help(get_string("choosedatasetproperties", "qtype_calculated"), 'questiondatasets', 'qtype_calculated'); - break; - case 'datasetitems': - echo $OUTPUT->heading_with_help(get_string("editdatasets", "qtype_calculated"), 'questiondatasets', 'qtype_calculated'); - break; + case '': + //on first page default display is fine + parent::display_question_editing_page($mform, $question, $wizardnow); + return; + break; + case 'datasetdefinitions': + echo $OUTPUT->heading_with_help(get_string("choosedatasetproperties", "qtype_calculated"), 'questiondatasets', 'qtype_calculated'); + break; + case 'datasetitems': + echo $OUTPUT->heading_with_help(get_string("editdatasets", "qtype_calculated"), 'questiondatasets', 'qtype_calculated'); + break; } - $mform->display(); - } - /** + /** * This method prepare the $datasets in a format similar to dadatesetdefinitions_form.php * so that they can be saved * using the function save_dataset_definitions($form) @@ -557,9 +561,9 @@ function preparedatasets(&$form , $questionfromid='0'){ // the dataset names present in the edit_question_form and edit_calculated_form are retrieved $possibledatasets = $this->find_dataset_names($form->questiontext); $mandatorydatasets = array(); - foreach ($form->answers as $answer) { - $mandatorydatasets += $this->find_dataset_names($answer); - } + foreach ($form->answers as $answer) { + //$mandatorydatasets += $this->find_dataset_names($answer); + } // if there are identical datasetdefs already saved in the original question. // either when editing a question or saving as new // they are retrieved using $questionfromid @@ -573,9 +577,9 @@ function preparedatasets(&$form , $questionfromid='0'){ foreach ($mandatorydatasets as $datasetname) { if (!isset($datasets[$datasetname])) { list($options, $selected) = - $this->dataset_options($form, $datasetname); + $this->dataset_options($form, $datasetname); $datasets[$datasetname]=''; - $form->dataset[$key]=$selected ; + $form->dataset[$key]=$selected ; $key++; } } @@ -584,32 +588,29 @@ function preparedatasets(&$form , $questionfromid='0'){ // the $options are not used here if ($questionfromid!='0'){ - foreach ($possibledatasets as $datasetname) { - if (!isset($datasets[$datasetname])) { - list($options, $selected) = + foreach ($possibledatasets as $datasetname) { + if (!isset($datasets[$datasetname])) { + list($options, $selected) = $this->dataset_options($form, $datasetname,false); - $datasets[$datasetname]=''; - $form->dataset[$key]=$selected ; - $key++; + $datasets[$datasetname]=''; + $form->dataset[$key]=$selected ; + $key++; + } } } - } - return $datasets ; - } + return $datasets ; + } function addnamecategory(&$question){ global $DB; - $categorydatasetdefs = $DB->get_records_sql( - "SELECT a.* - FROM {question_datasets} b, - {question_dataset_definitions} a - WHERE a.id = b.datasetdefinition - AND a.type = '1' - AND a.category != 0 - AND b.question = ? ORDER BY a.name ",array($question->id)); + $categorydatasetdefs = $DB->get_records_sql( + "SELECT a.* + FROM {question_datasets} b, {question_dataset_definitions} a + WHERE a.id = b.datasetdefinition AND a.type = '1' AND a.category != 0 AND b.question = ? + ORDER BY a.name ", array($question->id)); $questionname = $question->name ; $regs= array(); if(preg_match('~#\{([^[:space:]]*)#~',$questionname , $regs)){ - $questionname = str_replace($regs[0], '', $questionname); + $questionname = str_replace($regs[0], '', $questionname); }; if (!empty($categorydatasetdefs)){ // there is at least one with the same name @@ -617,35 +618,35 @@ function addnamecategory(&$question){ foreach($categorydatasetdefs as $def) { if(strlen("{$def->name}")+strlen($questionname) < 250 ){ $questionname = '{'.$def->name.'}' - .$questionname; + .$questionname; } } $questionname ="#".$questionname; } - if (!$DB->set_field('question', 'name', $questionname, array("id" => $question->id))) { + if (!$DB->set_field('question', 'name', $questionname, array("id" => $question->id))) { return false ; } } /** - * this version save the available data at the different steps of the question editing process - * without using global $SESSION as storage between steps - * at the first step $wizardnow = 'question' - * when creating a new question - * when modifying a question - * when copying as a new question - * the general parameters and answers are saved using parent::save_question - * then the datasets are prepared and saved - * at the second step $wizardnow = 'datasetdefinitions' - * the datadefs final type are defined as private, category or not a datadef - * at the third step $wizardnow = 'datasetitems' - * the datadefs parameters and the data items are created or defined - * - * @param object question - * @param object $form - * @param int $course - * @param PARAM_ALPHA $wizardnow should be added as we are coming from question2.php - */ + * this version save the available data at the different steps of the question editing process + * without using global $SESSION as storage between steps + * at the first step $wizardnow = 'question' + * when creating a new question + * when modifying a question + * when copying as a new question + * the general parameters and answers are saved using parent::save_question + * then the datasets are prepared and saved + * at the second step $wizardnow = 'datasetdefinitions' + * the datadefs final type are defined as private, category or not a datadef + * at the third step $wizardnow = 'datasetitems' + * the datadefs parameters and the data items are created or defined + * + * @param object question + * @param object $form + * @param int $course + * @param PARAM_ALPHA $wizardnow should be added as we are coming from question2.php + */ function save_question($question, $form, $course) { global $DB; $wizardnow = optional_param('wizardnow', '', PARAM_ALPHA); @@ -660,73 +661,73 @@ function save_question($question, $form, $course) { // See where we're coming from switch($wizardnow) { - case '' : - case 'question': // coming from the first page, creating the second - if (empty($form->id)) { // for a new question $form->id is empty - $question = parent::save_question($question, $form, $course); - //prepare the datasets using default $questionfromid - $this->preparedatasets($form); - $form->id = $question->id; - $this->save_dataset_definitions($form); - if(isset($form->synchronize) && $form->synchronize == 2 ){ - $this->addnamecategory($question); - } - } else if (!empty($form->makecopy)){ - $questionfromid = $form->id ; - $question = parent::save_question($question, $form, $course); - //prepare the datasets - $this->preparedatasets($form,$questionfromid); - $form->id = $question->id; - $this->save_as_new_dataset_definitions($form,$questionfromid ); - if(isset($form->synchronize) && $form->synchronize == 2 ){ - $this->addnamecategory($question); - } - } else {// editing a question - $question = parent::save_question($question, $form, $course); - //prepare the datasets - $this->preparedatasets($form,$question->id); - $form->id = $question->id; - $this->save_dataset_definitions($form); - if(isset($form->synchronize) && $form->synchronize == 2 ){ - $this->addnamecategory($question); - } + case '' : + case 'question': // coming from the first page, creating the second + if (empty($form->id)) { // for a new question $form->id is empty + $question = parent::save_question($question, $form, $course); + //prepare the datasets using default $questionfromid + $this->preparedatasets($form); + $form->id = $question->id; + $this->save_dataset_definitions($form); + if(isset($form->synchronize) && $form->synchronize == 2 ){ + $this->addnamecategory($question); } - break; - case 'datasetdefinitions': - // calculated options - // it cannot go here without having done the first page - // so the question_calculated_options should exist - // only need to update the synchronize field - if(isset($form->synchronize) ){ - $options_synchronize = $form->synchronize ; - }else { - $options_synchronize = 0 ; + } else if (!empty($form->makecopy)){ + $questionfromid = $form->id ; + $question = parent::save_question($question, $form, $course); + //prepare the datasets + $this->preparedatasets($form,$questionfromid); + $form->id = $question->id; + $this->save_as_new_dataset_definitions($form,$questionfromid ); + if(isset($form->synchronize) && $form->synchronize == 2 ){ + $this->addnamecategory($question); } - if (!$DB->set_field('question_calculated_options', 'synchronize', $options_synchronize, array("question" => $question->id))) { - return false; + } else {// editing a question + $question = parent::save_question($question, $form, $course); + //prepare the datasets + $this->preparedatasets($form,$question->id); + $form->id = $question->id; + $this->save_dataset_definitions($form); + if(isset($form->synchronize) && $form->synchronize == 2 ){ + $this->addnamecategory($question); } - if(isset($form->synchronize) && $form->synchronize == 2 ){ - $this->addnamecategory($question); - } + } + break; + case 'datasetdefinitions': + // calculated options + // it cannot go here without having done the first page + // so the question_calculated_options should exist + // only need to update the synchronize field + if(isset($form->synchronize) ){ + $options_synchronize = $form->synchronize ; + }else { + $options_synchronize = 0 ; + } + if (!$DB->set_field('question_calculated_options', 'synchronize', $options_synchronize, array("question" => $question->id))) { + return false; + } + if(isset($form->synchronize) && $form->synchronize == 2 ){ + $this->addnamecategory($question); + } - $this->save_dataset_definitions($form); - break; - case 'datasetitems': - $this->save_dataset_items($question, $form); - $this->save_question_calculated($question, $form); - break; - default: - print_error('invalidwizardpage', 'question'); - break; + $this->save_dataset_definitions($form); + break; + case 'datasetitems': + $this->save_dataset_items($question, $form); + $this->save_question_calculated($question, $form); + break; + default: + print_error('invalidwizardpage', 'question'); + break; } return $question; } /** - * Deletes question from the question-type specific tables - * - * @return boolean Success/Failure - * @param object $question The question being deleted - */ + * Deletes question from the question-type specific tables + * + * @return boolean Success/Failure + * @param object $question The question being deleted + */ function delete_question($questionid) { global $DB; @@ -736,43 +737,43 @@ function delete_question($questionid) { if ($datasets = $DB->get_records('question_datasets', array('question' => $questionid))) { foreach ($datasets as $dataset) { if (!$DB->get_records_select( - 'question_datasets', - "question != ? - AND datasetdefinition = ? ", array($questionid, $dataset->datasetdefinition))){ - $DB->delete_records('question_dataset_definitions', array('id' => $dataset->datasetdefinition)); - $DB->delete_records('question_dataset_items', array('definition' => $dataset->datasetdefinition)); - } + 'question_datasets', + "question != ? + AND datasetdefinition = ? ", array($questionid, $dataset->datasetdefinition))){ + $DB->delete_records('question_dataset_definitions', array('id' => $dataset->datasetdefinition)); + $DB->delete_records('question_dataset_items', array('definition' => $dataset->datasetdefinition)); + } } } $DB->delete_records("question_datasets", array("question" => $questionid)); return true; } function test_response(&$question, &$state, $answer) { - $virtualqtype = $this->get_virtual_qtype(); + $virtualqtype = $this->get_virtual_qtype(); return $virtualqtype->test_response($question, $state, $answer); } function compare_responses(&$question, $state, $teststate) { - $virtualqtype = $this->get_virtual_qtype(); + $virtualqtype = $this->get_virtual_qtype(); return $virtualqtype->compare_responses($question, $state, $teststate); } function convert_answers (&$question, &$state){ - foreach ($question->options->answers as $key => $answer) { - $answer = fullclone($question->options->answers[$key]); - $question->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer->answer, + foreach ($question->options->answers as $key => $answer) { + $answer = fullclone($question->options->answers[$key]); + $question->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer->answer, $state->options->dataset); - } } + } function convert_questiontext (&$question, &$state){ $tolerancemax =0.01; - $tolerancetypemax = 1 ; - $correctanswerlengthmax = 2 ; - $correctanswerformatmax = 1 ; - $tolerancemaxset = false ; + $tolerancetypemax = 1 ; + $correctanswerlengthmax = 2 ; + $correctanswerformatmax = 1 ; + $tolerancemaxset = false ; foreach ($question->options->answers as $key => $answer) { - if($answer->fraction == 1.0 && !$tolerancemaxset){ + if($answer->fraction == 1.0 && !$tolerancemaxset){ $tolerancemax = $answer->tolerance; $tolerancetypemax = $answer->tolerancetype ; $correctanswerlengthmax = $answer->correctanswerlength; @@ -780,44 +781,43 @@ function convert_questiontext (&$question, &$state){ $tolerancemaxset = true ; } } - $question->questiontext = $this->substitute_variables( - $question->questiontext, $state->options->dataset); + $question->questiontext = $this->substitute_variables($question->questiontext, $state->options->dataset); //evaluate the equations i.e {=5+4) $qtext = ""; $qtextremaining = $question->questiontext ; while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { - // while (preg_match('~\{=|%=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { + // while (preg_match('~\{=|%=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { $qtextsplits = explode($regs1[0], $qtextremaining, 2); $qtext =$qtext.$qtextsplits[0]; $qtextremaining = $qtextsplits[1]; if (empty($regs1[1])) { - $str = ''; - } else { - if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){ - $str=$formulaerrors ; - }else { - eval('$str = '.$regs1[1].';'); - $texteval= qtype_calculated_calculate_answer( - $str, $state->options->dataset, $tolerancemax, - $tolerancetypemax, $correctanswerlengthmax, + $str = ''; + } else { + if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){ + $str=$formulaerrors ; + }else { + eval('$str = '.$regs1[1].';'); + $texteval= qtype_calculated_calculate_answer( + $str, $state->options->dataset, $tolerancemax, + $tolerancetypemax, $correctanswerlengthmax, $correctanswerformatmax, ''); - $str = $texteval->answer; + $str = $texteval->answer; - ; - } + ; } - $qtext = $qtext.$str ; + } + $qtext = $qtext.$str ; } $question->questiontext = $qtext.$qtextremaining ; // end replace equations - } + } function get_default_numerical_unit($question,$virtualqtype){ - if($unit = $virtualqtype->get_default_numerical_unit($question)){ - $unit = $unit->unit; - } else { - $unit = ''; - } - return $unit ; + if($unit = $virtualqtype->get_default_numerical_unit($question)){ + $unit = $unit->unit; + } else { + $unit = ''; + } + return $unit ; } function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { @@ -828,8 +828,8 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions $unit = $this-> get_default_numerical_unit($question,$virtualqtype); // We modify the question to look like a numerical question $numericalquestion = fullclone($question); - $this->convert_answers($numericalquestion, $state); - $this->convert_questiontext($numericalquestion, $state); + $this->convert_answers($numericalquestion, $state); + $this->convert_questiontext($numericalquestion, $state); /* $tolerancemax =0.01; $tolerancetypemax = 1 ; $correctanswerlengthmax = 2 ; @@ -874,7 +874,7 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions $qtext = $qtext.$str ; } $numericalquestion->questiontext = $qtext.$qtextremaining ; // end replace equations - */ + */ $virtualqtype->print_question_formulation_and_controls($numericalquestion, $state, $cmoptions, $options); } @@ -882,12 +882,12 @@ function grade_responses(&$question, &$state, $cmoptions) { // Forward the grading to the virtual qtype // We modify the question to look like a numerical question $numericalquestion = fullclone($question); - foreach ($numericalquestion->options->answers as $key => $answer) { + foreach ($numericalquestion->options->answers as $key => $answer) { $answer = $numericalquestion->options->answers[$key]->answer; // for PHP 4.x - $numericalquestion->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer, - $state->options->dataset); - } - $virtualqtype = $this->get_virtual_qtype(); + $numericalquestion->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer, + $state->options->dataset); + } + $virtualqtype = $this->get_virtual_qtype(); return $virtualqtype->grade_responses($numericalquestion, $state, $cmoptions) ; } @@ -904,7 +904,7 @@ function check_response(&$question, &$state) { foreach ($numericalquestion->options->answers as $key => $answer) { $answer = &$numericalquestion->options->answers[$key]; // for PHP 4.x $answer->answer = $this->substitute_variables_and_eval($answer->answer, - $state->options->dataset); + $state->options->dataset); } $virtualqtype = $this->get_virtual_qtype(); return $virtualqtype->check_response($numericalquestion, $state) ; @@ -926,11 +926,11 @@ function get_actual_response(&$question, &$state) { foreach ($numericalquestion->options->answers as $key => $answer) { $answer = &$numericalquestion->options->answers[$key]; // for PHP 4.x $answer->answer = $this->substitute_variables_and_eval($answer->answer, - $state->options->dataset); + $state->options->dataset); // apply_unit } $numericalquestion->questiontext = $this->substitute_variables_and_eval( - $numericalquestion->questiontext, $state->options->dataset); + $numericalquestion->questiontext, $state->options->dataset); $responses = $virtualqtype->get_all_responses($numericalquestion, $state); $response = reset($responses->responses); $correct = $response->answer.' : '; @@ -946,12 +946,12 @@ function get_actual_response(&$question, &$state) { function create_virtual_qtype() { global $CFG; - require_once("$CFG->dirroot/question/type/numerical/questiontype.php"); - return new question_numerical_qtype(); + require_once("$CFG->dirroot/question/type/numerical/questiontype.php"); + return new question_numerical_qtype(); } function supports_dataset_item_generation() { - // Calcualted support generation of randomly distributed number data + // Calcualted support generation of randomly distributed number data return true; } function custom_generator_tools_part(&$mform, $idx, $j){ @@ -968,8 +968,6 @@ function custom_generator_tools_part(&$mform, $idx, $j){ $distriboptions = array('uniform' => get_string('uniform', 'qtype_calculated'), 'loguniform' => get_string('loguniform', 'qtype_calculated')); $mform->addElement('select', "calcdistribution[$idx]", get_string('calcdistribution', 'qtype_calculated'), $distriboptions); - - } function custom_generator_set_data($datasetdefs, $formdata){ @@ -990,18 +988,18 @@ function custom_generator_set_data($datasetdefs, $formdata){ function custom_generator_tools($datasetdef) { global $OUTPUT; if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~', - $datasetdef->options, $regs)) { - $defid = "$datasetdef->type-$datasetdef->category-$datasetdef->name"; - for ($i = 0 ; $i<10 ; ++$i) { - $lengthoptions[$i] = get_string(($regs[1] == 'uniform' - ? 'decimals' - : 'significantfigures'), 'quiz', $i); - } - $menu1 = html_writer::select($lengthoptions, 'calclength[]', $regs[4], null); - - $options = array('uniform' => get_string('uniform', 'quiz'), 'loguniform' => get_string('loguniform', 'quiz')); - $menu2 = html_writer::select($options, 'calcdistribution[]', $regs[1], null); - return 'type-$datasetdef->category-$datasetdef->name"; + for ($i = 0 ; $i<10 ; ++$i) { + $lengthoptions[$i] = get_string(($regs[1] == 'uniform' + ? 'decimals' + : 'significantfigures'), 'quiz', $i); + } + $menu1 = html_writer::select($lengthoptions, 'calclength[]', $regs[4], null); + + $options = array('uniform' => get_string('uniform', 'quiz'), 'loguniform' => get_string('loguniform', 'quiz')); + $menu2 = html_writer::select($options, 'calcdistribution[]', $regs[1], null); + return '
' . ' ' . $menu1 . '
' . $menu2; - } else { - return ''; - } + } else { + return ''; + } } function update_dataset_options($datasetdefs, $form) { // Do we have informatin about new options??? if (empty($form->definition) || empty($form->calcmin) - || empty($form->calcmax) || empty($form->calclength) - || empty($form->calcdistribution)) { - // I guess not + || empty($form->calcmax) || empty($form->calclength) + || empty($form->calcdistribution)) { + // I guess not - } else { - // Looks like we just could have some new information here - $uniquedefs = array_values(array_unique($form->definition)); - foreach ($uniquedefs as $key => $defid) { - if (isset($datasetdefs[$defid]) + } else { + // Looks like we just could have some new information here + $uniquedefs = array_values(array_unique($form->definition)); + foreach ($uniquedefs as $key => $defid) { + if (isset($datasetdefs[$defid]) && is_numeric($form->calcmin[$key+1]) && is_numeric($form->calcmax[$key+1]) && is_numeric($form->calclength[$key+1])) { - switch ($form->calcdistribution[$key+1]) { - case 'uniform': case 'loguniform': - $datasetdefs[$defid]->options = + switch ($form->calcdistribution[$key+1]) { + case 'uniform': case 'loguniform': + $datasetdefs[$defid]->options = $form->calcdistribution[$key+1] . ':' . $form->calcmin[$key+1] . ':' . $form->calcmax[$key+1] . ':' . $form->calclength[$key+1]; - break; - default: - echo $OUTPUT->notification("Unexpected distribution ".$form->calcdistribution[$key+1]); - } + break; + default: + echo $OUTPUT->notification("Unexpected distribution ".$form->calcdistribution[$key+1]); + } + } } } - } // Look for empty options, on which we set default values foreach ($datasetdefs as $defid => $def) { @@ -1068,29 +1066,29 @@ function save_question_calculated($question, $fromform){ } } - /** - * This function get the dataset items using id as unique parameter and return an - * array with itemnumber as index sorted ascendant - * If the multiple records with the same itemnumber exist, only the newest one - * i.e with the greatest id is used, the others are ignored but not deleted. - * MDL-19210 - */ + /** + * This function get the dataset items using id as unique parameter and return an + * array with itemnumber as index sorted ascendant + * If the multiple records with the same itemnumber exist, only the newest one + * i.e with the greatest id is used, the others are ignored but not deleted. + * MDL-19210 + */ function get_database_dataset_items($definition){ - global $CFG, $DB; - $databasedataitems = $DB->get_records_sql( // Use number as key!! - " SELECT id , itemnumber, definition, value - FROM {question_dataset_items} - WHERE definition = $definition order by id DESC ", array($definition)); - $dataitems = Array(); - foreach($databasedataitems as $id => $dataitem ){ - if (!isset($dataitems[$dataitem->itemnumber])){ - $dataitems[$dataitem->itemnumber] = $dataitem ; - }else { - // deleting the unused records could be added here - } - } - ksort($dataitems); - return $dataitems ; + global $CFG, $DB; + $databasedataitems = $DB->get_records_sql( // Use number as key!! + " SELECT id , itemnumber, definition, value + FROM {question_dataset_items} + WHERE definition = $definition order by id DESC ", array($definition)); + $dataitems = Array(); + foreach($databasedataitems as $id => $dataitem ){ + if (!isset($dataitems[$dataitem->itemnumber])){ + $dataitems[$dataitem->itemnumber] = $dataitem ; + }else { + // deleting the unused records could be added here + } + } + ksort($dataitems); + return $dataitems ; } function save_dataset_items($question, $fromform){ @@ -1119,11 +1117,11 @@ function save_dataset_items($question, $fromform){ $maxnumber = -1; foreach ($datasetdefs as $defid => $datasetdef) { if (isset($datasetdef->id) - && $datasetdef->options != $olddatasetdefs[$defid]->options) { - // Save the new value for options - $DB->update_record('question_dataset_definitions', $datasetdef); + && $datasetdef->options != $olddatasetdefs[$defid]->options) { + // Save the new value for options + $DB->update_record('question_dataset_definitions', $datasetdef); - } + } // Get maxnumber if ($maxnumber == -1 || $datasetdef->itemcount < $maxnumber) { $maxnumber = $datasetdef->itemcount; @@ -1158,16 +1156,16 @@ function save_dataset_items($question, $fromform){ $i++; } if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber - && $addeditem->itemnumber < CALCULATEDQUESTIONMAXITEMNUMBER ){ - $maxnumber = $addeditem->itemnumber; - foreach ($datasetdefs as $key => $newdef) { - if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) { - $newdef->itemcount = $maxnumber; - // Save the new value for options - $DB->update_record('question_dataset_definitions', $newdef); + && $addeditem->itemnumber < CALCULATEDQUESTIONMAXITEMNUMBER ){ + $maxnumber = $addeditem->itemnumber; + foreach ($datasetdefs as $key => $newdef) { + if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) { + $newdef->itemcount = $maxnumber; + // Save the new value for options + $DB->update_record('question_dataset_definitions', $newdef); + } } } - } // adding supplementary items $numbertoadd =0; if (isset($fromform->addbutton) && $fromform->selectadd > 1 && $maxnumber < CALCULATEDQUESTIONMAXITEMNUMBER ) { @@ -1178,30 +1176,30 @@ function save_dataset_items($question, $fromform){ //add the other items. // Generate a new dataset item (or reuse an old one) foreach ($datasetdefs as $defid => $datasetdef) { - // in case that for category datasets some new items has been added - // get actual values - // fix regenerate for this datadefs - $defregenerate = 0 ; - if($synchronize && !empty ($fromform->nextpageparam["datasetregenerate[$datasetdef->name"])) { - $defregenerate = 1 ; - }else if(!$synchronize && (($regenerate == 1 && $datasetdef->category == 0) ||$regenerate == 2 )){ - $defregenerate = 1 ; - } + // in case that for category datasets some new items has been added + // get actual values + // fix regenerate for this datadefs + $defregenerate = 0 ; + if($synchronize && !empty ($fromform->nextpageparam["datasetregenerate[$datasetdef->name"])) { + $defregenerate = 1 ; + }else if(!$synchronize && (($regenerate == 1 && $datasetdef->category == 0) ||$regenerate == 2 )){ + $defregenerate = 1 ; + } if (isset($datasetdef->id)) { - $datasetdefs[$defid]->items = $this->get_database_dataset_items($datasetdef->id); + $datasetdefs[$defid]->items = $this->get_database_dataset_items($datasetdef->id); } for ($numberadded =$maxnumber+1 ; $numberadded <= $maxnumber+$numbertoadd ; $numberadded++){ if (isset($datasetdefs[$defid]->items[$numberadded]) ){ - // in case of regenerate it modifies the already existing record - if ( $defregenerate ) { - $datasetitem = new stdClass; - $datasetitem->id = $datasetdefs[$defid]->items[$numberadded]->id; - $datasetitem->definition = $datasetdef->id ; - $datasetitem->itemnumber = $numberadded; - $datasetitem->value = $this->generate_dataset_item($datasetdef->options); - $DB->update_record('question_dataset_items', $datasetitem); - } - //if not regenerate do nothing as there is already a record + // in case of regenerate it modifies the already existing record + if ( $defregenerate ) { + $datasetitem = new stdClass; + $datasetitem->id = $datasetdefs[$defid]->items[$numberadded]->id; + $datasetitem->definition = $datasetdef->id ; + $datasetitem->itemnumber = $numberadded; + $datasetitem->value = $this->generate_dataset_item($datasetdef->options); + $DB->update_record('question_dataset_items', $datasetitem); + } + //if not regenerate do nothing as there is already a record } else { $datasetitem = new stdClass; $datasetitem->definition = $datasetdef->id ; @@ -1235,14 +1233,14 @@ function save_dataset_items($question, $fromform){ $DB->update_record('question_dataset_definitions', $datasetdef); } } - } + } } function generate_dataset_item($options) { if (!preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~', - $options, $regs)) { - // Unknown options... - return false; - } + $options, $regs)) { + // Unknown options... + return false; + } if ($regs[1] == 'uniform') { $nbr = $regs[2] + ($regs[3]-$regs[2])*mt_rand()/mt_getrandmax(); return sprintf("%.".$regs[4]."f",$nbr); @@ -1271,12 +1269,12 @@ function comment_header($question) { } else { $strheader .= $delimiter.$answer->answer; } - $delimiter = '


'; + $delimiter = '


'; } return $strheader; } - function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$data, $number) { + function comment_on_datasetitems($qtypeobj, $questionid, $questiontext, $answers, $data, $number) { global $DB, $QTYPES; $comment = new stdClass; $comment->stranswers = array(); @@ -1298,16 +1296,16 @@ function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$ foreach ($answers as $key => $answer) { $formula = $this->substitute_variables($answer->answer,$data); $formattedanswer = qtype_calculated_calculate_answer( - $answer->answer, $data, $answer->tolerance, - $answer->tolerancetype, $answer->correctanswerlength, - $answer->correctanswerformat, $unit); - if ( $formula === '*'){ - $answer->min = ' '; - $formattedanswer->answer = $answer->answer ; - }else { - eval('$answer->answer = '.$formula.';') ; - $virtualqtype->get_tolerance_interval($answer); - } + $answer->answer, $data, $answer->tolerance, + $answer->tolerancetype, $answer->correctanswerlength, + $answer->correctanswerformat, $unit); + if ( $formula === '*'){ + $answer->min = ' '; + $formattedanswer->answer = $answer->answer ; + }else { + eval('$answer->answer = '.$formula.';') ; + $virtualqtype->get_tolerance_interval($answer); + } if ($answer->min === '') { // This should mean that something is wrong $comment->stranswers[$key] = " $formattedanswer->answer".'

'; @@ -1332,7 +1330,7 @@ function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$ } return fullclone($comment); } - function multichoice_comment_on_datasetitems($questionid,$questiontext, $answers,$data, $number) { + function multichoice_comment_on_datasetitems($questionid, $questiontext, $answers,$data, $number) { global $DB; $comment = new stdClass; $comment->stranswers = array(); @@ -1351,34 +1349,34 @@ function multichoice_comment_on_datasetitems($questionid,$questiontext, $answers $errors = ''; $delimiter = ': '; foreach ($answers as $key => $answer) { - $answer->answer = $this->substitute_variables($answer->answer, $data); - //evaluate the equations i.e {=5+4) - $qtext = ""; - $qtextremaining = $answer->answer ; - while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { - $qtextsplits = explode($regs1[0], $qtextremaining, 2); - $qtext =$qtext.$qtextsplits[0]; - $qtextremaining = $qtextsplits[1]; - if (empty($regs1[1])) { - $str = ''; - } else { - if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){ - $str=$formulaerrors ; - }else { - eval('$str = '.$regs1[1].';'); + $answer->answer = $this->substitute_variables($answer->answer, $data); + //evaluate the equations i.e {=5+4) + $qtext = ""; + $qtextremaining = $answer->answer ; + while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { + $qtextsplits = explode($regs1[0], $qtextremaining, 2); + $qtext =$qtext.$qtextsplits[0]; + $qtextremaining = $qtextsplits[1]; + if (empty($regs1[1])) { + $str = ''; + } else { + if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){ + $str=$formulaerrors ; + }else { + eval('$str = '.$regs1[1].';'); - $texteval= qtype_calculated_calculate_answer( - $str, $data, $answer->tolerance, - $answer->tolerancetype, $answer->correctanswerlength, - $answer->correctanswerformat, ''); + $texteval= qtype_calculated_calculate_answer( + $str, $data, $answer->tolerance, + $answer->tolerancetype, $answer->correctanswerlength, + $answer->correctanswerformat, ''); $str = $texteval->answer; - } - } - $qtext = $qtext.$str ; + } } - $answer->answer = $qtext.$qtextremaining ; ; - $comment->stranswers[$key]= $answer->answer ; + $qtext = $qtext.$str ; + } + $answer->answer = $qtext.$qtextremaining ; ; + $comment->stranswers[$key]= $answer->answer ; /* $formula = $this->substitute_variables($answer->answer,$data); @@ -1413,24 +1411,24 @@ function multichoice_comment_on_datasetitems($questionid,$questiontext, $answers $comment->stranswers[$key] .=get_string('trueanswerinsidelimits','qtype_calculated',$correcttrue);//' True answer :'.$calculated->trueanswer.' inside limits'; } $comment->stranswers[$key] .=''; - }*/ + }*/ } return fullclone($comment); } function tolerance_types() { return array('1' => get_string('relative', 'quiz'), - '2' => get_string('nominal', 'quiz'), - '3' => get_string('geometric', 'quiz')); + '2' => get_string('nominal', 'quiz'), + '3' => get_string('geometric', 'quiz')); } function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false) { - // Takes datasets from the parent implementation but - // filters options that are currently not accepted by calculated - // It also determines a default selection... - //$renameabledatasets not implemented anmywhere + // Takes datasets from the parent implementation but + // filters options that are currently not accepted by calculated + // It also determines a default selection... + //$renameabledatasets not implemented anmywhere list($options, $selected) = $this->dataset_options_from_database($form, $name,'','qtype_calculated'); - // list($options, $selected) = $this->dataset_optionsa($form, $name); + // list($options, $selected) = $this->dataset_optionsa($form, $name); foreach ($options as $key => $whatever) { if (!preg_match('~^1-~', $key) && $key != '0') { @@ -1439,7 +1437,7 @@ function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false } if (!$selected) { if ($mandatory){ - $selected = "1-0-$name"; // Default + $selected = "1-0-$name"; // Default }else { $selected = "0"; // Default } @@ -1448,26 +1446,26 @@ function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false } function construct_dataset_menus($form, $mandatorydatasets, - $optionaldatasets) { - global $OUTPUT; - $datasetmenus = array(); - foreach ($mandatorydatasets as $datasetname) { - if (!isset($datasetmenus[$datasetname])) { - list($options, $selected) = + $optionaldatasets) { + global $OUTPUT; + $datasetmenus = array(); + foreach ($mandatorydatasets as $datasetname) { + if (!isset($datasetmenus[$datasetname])) { + list($options, $selected) = $this->dataset_options($form, $datasetname); - unset($options['0']); // Mandatory... - $datasetmenus[$datasetname] = html_writer::select($options, 'dataset[]', $selected, null); + unset($options['0']); // Mandatory... + $datasetmenus[$datasetname] = html_writer::select($options, 'dataset[]', $selected, null); + } } - } - foreach ($optionaldatasets as $datasetname) { - if (!isset($datasetmenus[$datasetname])) { - list($options, $selected) = + foreach ($optionaldatasets as $datasetname) { + if (!isset($datasetmenus[$datasetname])) { + list($options, $selected) = $this->dataset_options($form, $datasetname); - $datasetmenus[$datasetname] = html_writer::select($options, 'dataset[]', $selected, null); + $datasetmenus[$datasetname] = html_writer::select($options, 'dataset[]', $selected, null); + } } + return $datasetmenus; } - return $datasetmenus; - } function print_question_grading_details(&$question, &$state, &$cmoptions, &$options) { $virtualqtype = $this->get_virtual_qtype(); @@ -1479,14 +1477,14 @@ function get_correct_responses(&$question, &$state) { $virtualqtype = $this->get_virtual_qtype(); $unit = $this->get_default_numerical_unit($question,$virtualqtype); // We modify the question to look like a numerical question - $this->convert_answers($question, $state); - return $virtualqtype->get_correct_responses($question, $state) ; + $this->convert_answers($question, $state); + return $virtualqtype->get_correct_responses($question, $state) ; } function substitute_variables($str, $dataset) { global $OUTPUT ; - // testing for wrong numerical values - // all calculations used this function so testing here should be OK + // testing for wrong numerical values + // all calculations used this function so testing here should be OK foreach ($dataset as $name => $value) { $val = $value ; @@ -1494,8 +1492,8 @@ function substitute_variables($str, $dataset) { $a = new stdClass; $a->name = '{'.$name.'}' ; $a->value = $value ; - echo $OUTPUT->notification(get_string('notvalidnumber','qtype_calculated',$a)); - $val = 1.0 ; + echo $OUTPUT->notification(get_string('notvalidnumber','qtype_calculated',$a)); + $val = 1.0 ; } if($val < 0 ){ $str = str_replace('{'.$name.'}', '('.$val.')', $str); @@ -1507,7 +1505,7 @@ function substitute_variables($str, $dataset) { } function evaluate_equations($str, $dataset){ $formula = $this->substitute_variables($str, $dataset) ; - if ($error = qtype_calculated_find_formula_errors($formula)) { + if ($error = qtype_calculated_find_formula_errors($formula)) { return $error; } return $str; @@ -1516,7 +1514,7 @@ function evaluate_equations($str, $dataset){ function substitute_variables_and_eval($str, $dataset) { $formula = $this->substitute_variables($str, $dataset) ; - if ($error = qtype_calculated_find_formula_errors($formula)) { + if ($error = qtype_calculated_find_formula_errors($formula)) { return $error; } /// Calculate the correct answer @@ -1537,11 +1535,8 @@ function get_dataset_definitions($questionid, $newdatasets) { if (!empty($questionid)) { global $CFG; $sql = "SELECT i.* - FROM {question_datasets} d, - {question_dataset_definitions} i - WHERE d.question = ? - AND d.datasetdefinition = i.id - "; + FROM {question_datasets} d, {question_dataset_definitions} i + WHERE d.question = ? AND d.datasetdefinition = i.id"; if ($records = $DB->get_records_sql($sql, array($questionid))) { foreach ($records as $r) { $datasetdefs["$r->type-$r->category-$r->name"] = $r; @@ -1573,6 +1568,9 @@ function save_dataset_definitions($form) { global $DB; // save synchronize + if (empty($form->dataset)) { + $form->dataset = array(); + } // Save datasets $datasetdefinitions = $this->get_dataset_definitions($form->id, $form->dataset); $tmpdatasets = array_flip($form->dataset); @@ -1581,7 +1579,7 @@ function save_dataset_definitions($form) { $datasetdef = &$datasetdefinitions[$defid]; if (isset($datasetdef->id)) { if (!isset($tmpdatasets[$defid])) { - // This dataset is not used any more, delete it + // This dataset is not used any more, delete it $DB->delete_records('question_datasets', array('question' => $form->id, 'datasetdefinition' => $datasetdef->id)); if ($datasetdef->category == 0) { // Question local dataset $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id)); @@ -1601,12 +1599,12 @@ function save_dataset_definitions($form) { // By first creating the datasetdefinition above we // can manage to automatically take care of // some possible realtime concurrence - if ($olderdatasetdefs = $DB->get_records_select( 'question_dataset_definitions', - "type = ? - AND name = ? - AND category = ? - AND id < ? - ORDER BY id DESC", array($datasetdef->type, $datasetdef->name, $datasetdef->category, $datasetdef->id))) { + if ($olderdatasetdefs = $DB->get_records_select('question_dataset_definitions', + "type = ? + AND name = ? + AND category = ? + AND id < ? + ORDER BY id DESC", array($datasetdef->type, $datasetdef->name, $datasetdef->category, $datasetdef->id))) { while ($olderdatasetdef = array_shift($olderdatasetdefs)) { $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id)); @@ -1637,12 +1635,12 @@ function save_dataset_definitions($form) { } } /** This function create a copy of the datasets ( definition and dataitems) - * from the preceding question if they remain in the new question - * otherwise its create the datasets that have been added as in the - * save_dataset_definitions() - */ + * from the preceding question if they remain in the new question + * otherwise its create the datasets that have been added as in the + * save_dataset_definitions() + */ function save_as_new_dataset_definitions($form, $initialid) { - global $CFG, $DB; + global $CFG, $DB; // Get the datasets from the intial question $datasetdefinitions = $this->get_dataset_definitions($initialid, $form->dataset); // $tmpdatasets contains those of the new question @@ -1659,13 +1657,13 @@ function save_as_new_dataset_definitions($form, $initialid) { } // create a copy but not for category one if (0 == $datasetdef->category) { - $olddatasetid = $datasetdef->id ; - $olditemcount = $datasetdef->itemcount ; - $datasetdef->itemcount =0; - $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef); - //copy the dataitems - $olditems = $this->get_database_dataset_items($olddatasetid); - if (count($olditems) > 0 ) { + $olddatasetid = $datasetdef->id ; + $olditemcount = $datasetdef->itemcount ; + $datasetdef->itemcount =0; + $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef); + //copy the dataitems + $olditems = $this->get_database_dataset_items($olddatasetid); + if (count($olditems) > 0 ) { $itemcount = 0; foreach($olditems as $item ){ $item->definition = $datasetdef->id; @@ -1701,18 +1699,18 @@ function save_as_new_dataset_definitions($form, $initialid) { // can manage to automatically take care of // some possible realtime concurrence if ($olderdatasetdefs = $DB->get_records_select( - 'question_dataset_definitions', - "type = ? - AND name = ? - AND category = ? - AND id < ? - ORDER BY id DESC", array($datasetdef->type, $datasetdef->name, $datasetdef->category, $datasetdef->id))) { - - while ($olderdatasetdef = array_shift($olderdatasetdefs)) { - $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id)); - $datasetdef = $olderdatasetdef; + 'question_dataset_definitions', + "type = ? + AND name = ? + AND category = ? + AND id < ? + ORDER BY id DESC", array($datasetdef->type, $datasetdef->name, $datasetdef->category, $datasetdef->id))) { + + while ($olderdatasetdef = array_shift($olderdatasetdefs)) { + $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id)); + $datasetdef = $olderdatasetdef; + } } - } } // Create relation to this dataset: @@ -1737,31 +1735,27 @@ function save_as_new_dataset_definitions($form, $initialid) { } } -/// Dataset functionality + /// Dataset functionality function pick_question_dataset($question, $datasetitem) { // Select a dataset in the following format: // An array indexed by the variable names (d.name) pointing to the value // to be substituted global $CFG, $DB; if (!$dataitems = $DB->get_records_sql( - "SELECT i.id, d.name, i.value - FROM {question_dataset_definitions} d, - {question_dataset_items} i, - {question_datasets} q - WHERE q.question = ? - AND q.datasetdefinition = d.id - AND d.id = i.definition - AND i.itemnumber = ? ORDER by i.id DESC ", array($question->id, $datasetitem))) { + "SELECT i.id, d.name, i.value + FROM {question_dataset_definitions} d, {question_dataset_items} i, {question_datasets} q + WHERE q.question = ? AND q.datasetdefinition = d.id AND d.id = i.definition AND i.itemnumber = ? + ORDER by i.id DESC ", array($question->id, $datasetitem))) { print_error('cannotgetdsfordependent', 'question', '', array($question->id, $datasetitem)); } $dataset = Array(); - foreach($dataitems as $id => $dataitem ){ - if (!isset($dataset[$dataitem->name])){ - $dataset[$dataitem->name] = $dataitem->value ; - }else { - // deleting the unused records could be added here - } - } + foreach($dataitems as $id => $dataitem ){ + if (!isset($dataset[$dataitem->name])){ + $dataset[$dataitem->name] = $dataitem->value ; + }else { + // deleting the unused records could be added here + } + } return $dataset; } @@ -1779,54 +1773,51 @@ function dataset_options_from_database($form, $name,$prefix='',$langfile='quiz') $currentdatasetdef->type = '0'; }else { - // Construct question local options - if ( ! $currentdatasetdef = $DB->get_record_sql( - "SELECT a.* - FROM {question_dataset_definitions} a, - {question_datasets} b - WHERE a.id = b.datasetdefinition - AND a.type = '1' - AND b.question = ? - AND a.name = ?", array($form->id, $name))){ - $currentdatasetdef->type = '0'; - }; - $key = "$type-0-$name"; - if ($currentdatasetdef->type == $type + // Construct question local options + $sql = "SELECT a.* + FROM {question_dataset_definitions} a, {question_datasets} b + WHERE a.id = b.datasetdefinition AND a.type = '1' AND b.question = ? AND a.name = ?"; + $currentdatasetdef = $DB->get_record_sql($sql, array($form->id, $name)); + if (!$currentdatasetdef) { + $currentdatasetdef->type = '0'; + } + $key = "$type-0-$name"; + if ($currentdatasetdef->type == $type and $currentdatasetdef->category == 0) { - $options[$key] = get_string($prefix."keptlocal$type", $langfile); - } else { - $options[$key] = get_string($prefix."newlocal$type", $langfile); - } + $options[$key] = get_string($prefix."keptlocal$type", $langfile); + } else { + $options[$key] = get_string($prefix."newlocal$type", $langfile); + } } // Construct question category options $categorydatasetdefs = $DB->get_records_sql( - "SELECT b.question, a.* - FROM {question_datasets} b, - {question_dataset_definitions} a - WHERE a.id = b.datasetdefinition - AND a.type = '1' - AND a.category = ? - AND a.name = ?", array($form->category, $name)); + "SELECT b.question, a.* + FROM {question_datasets} b, + {question_dataset_definitions} a + WHERE a.id = b.datasetdefinition + AND a.type = '1' + AND a.category = ? + AND a.name = ?", array($form->category, $name)); $type = 1 ; $key = "$type-$form->category-$name"; if (!empty($categorydatasetdefs)){ // there is at least one with the same name if (isset($form->id) && isset($categorydatasetdefs[$form->id])) {// it is already used by this question - $options[$key] = get_string($prefix."keptcategory$type", $langfile); - } else { - $options[$key] = get_string($prefix."existingcategory$type", $langfile); - } + $options[$key] = get_string($prefix."keptcategory$type", $langfile); + } else { + $options[$key] = get_string($prefix."existingcategory$type", $langfile); + } } else { $options[$key] = get_string($prefix."newcategory$type", $langfile); } // All done! return array($options, $currentdatasetdef->type - ? "$currentdatasetdef->type-$currentdatasetdef->category-$name" - : ''); + ? "$currentdatasetdef->type-$currentdatasetdef->category-$name" + : ''); } function find_dataset_names($text) { - /// Returns the possible dataset names found in the text as an array - /// The array has the dataset name for both key and value + /// Returns the possible dataset names found in the text as an array + /// The array has the dataset name for both key and value $datasetnames = array(); while (preg_match('~\\{([[:alpha:]][^>} <{"\']*)\\}~', $text, $regs)) { $datasetnames[$regs[1]] = $regs[1]; @@ -1836,37 +1827,34 @@ function find_dataset_names($text) { } /** - * This function retrieve the item count of the available category shareable - * wild cards that is added as a comment displayed when a wild card with - * the same name is displayed in datasetdefinitions_form.php - */ + * This function retrieve the item count of the available category shareable + * wild cards that is added as a comment displayed when a wild card with + * the same name is displayed in datasetdefinitions_form.php + */ function get_dataset_definitions_category($form) { global $CFG, $DB; $datasetdefs = array(); $lnamemax = 30; if (!empty($form->category)) { $sql = "SELECT i.*,d.* - FROM {question_datasets} d, - {question_dataset_definitions} i - WHERE i.id = d.datasetdefinition - AND i.category = ?"; - if ($records = $DB->get_records_sql($sql, array($form->category))) { - foreach ($records as $r) { - if ( !isset ($datasetdefs["$r->name"])) $datasetdefs["$r->name"] = $r->itemcount; - } + FROM {question_datasets} d, {question_dataset_definitions} i + WHERE i.id = d.datasetdefinition AND i.category = ?"; + if ($records = $DB->get_records_sql($sql, array($form->category))) { + foreach ($records as $r) { + if ( !isset ($datasetdefs["$r->name"])) $datasetdefs["$r->name"] = $r->itemcount; } + } } return $datasetdefs ; } /** - * This function build a table showing the available category shareable - * wild cards, their name, their definition (Min, Max, Decimal) , the item count - * and the name of the question where they are used. - * This table is intended to be add before the question text to help the user use - * these wild cards - */ - + * This function build a table showing the available category shareable + * wild cards, their name, their definition (Min, Max, Decimal) , the item count + * and the name of the question where they are used. + * This table is intended to be add before the question text to help the user use + * these wild cards + */ function print_dataset_definitions_category($form) { global $CFG, $DB; $datasetdefs = array(); @@ -1877,19 +1865,19 @@ function print_dataset_definitions_category($form) { $rangeofvaluestr=get_string('minmax','qtype_calculated'); $questionusingstr = get_string('usedinquestion','qtype_calculated'); $itemscountstr = get_string('itemscount','qtype_calculated'); - $text =''; + $text =''; if (!empty($form->category)) { list($category) = explode(',', $form->category); $sql = "SELECT i.*,d.* - FROM {question_datasets} d, - {question_dataset_definitions} i - WHERE i.id = d.datasetdefinition - AND i.category = ?"; + FROM {question_datasets} d, + {question_dataset_definitions} i + WHERE i.id = d.datasetdefinition + AND i.category = ?"; if ($records = $DB->get_records_sql($sql, array($category))) { foreach ($records as $r) { $sql1 = "SELECT q.* - FROM {question} q - WHERE q.id = ?"; + FROM {question} q + WHERE q.id = ?"; if ( !isset ($datasetdefs["$r->type-$r->category-$r->name"])){ $datasetdefs["$r->type-$r->category-$r->name"]= $r; } @@ -1909,7 +1897,7 @@ function print_dataset_definitions_category($form) { //limit the name length displayed if (!empty($qu->name)) { $qu->name = (strlen($qu->name) > $lnamemax) ? - substr($qu->name, 0, $lnamemax).'...' : $qu->name; + substr($qu->name, 0, $lnamemax).'...' : $qu->name; } else { $qu->name = ''; } @@ -1919,18 +1907,18 @@ function print_dataset_definitions_category($form) { } $text .=""; }else{ - $text .=get_string('nosharedwildcard', 'qtype_calculated'); + $text .=get_string('nosharedwildcard', 'qtype_calculated'); } return $text ; } /** - * This function build a table showing the available category shareable - * wild cards, their name, their definition (Min, Max, Decimal) , the item count - * and the name of the question where they are used. - * This table is intended to be add before the question text to help the user use - * these wild cards - */ + * This function build a table showing the available category shareable + * wild cards, their name, their definition (Min, Max, Decimal) , the item count + * and the name of the question where they are used. + * This table is intended to be add before the question text to help the user use + * these wild cards + */ function print_dataset_definitions_category_shared($question,$datasetdefsq) { global $CFG, $DB; @@ -1942,19 +1930,17 @@ function print_dataset_definitions_category_shared($question,$datasetdefsq) { $rangeofvaluestr=get_string('minmax','qtype_calculated'); $questionusingstr = get_string('usedinquestion','qtype_calculated'); $itemscountstr = get_string('itemscount','qtype_calculated'); - $text =''; + $text =''; if (!empty($question->category)) { list($category) = explode(',', $question->category); $sql = "SELECT i.*,d.* - FROM {question_datasets} d, - {question_dataset_definitions} i - WHERE i.id = d.datasetdefinition - AND i.category = ?"; + FROM {question_datasets} d, {question_dataset_definitions} i + WHERE i.id = d.datasetdefinition AND i.category = ?"; if ($records = $DB->get_records_sql($sql, array($category))) { foreach ($records as $r) { $sql1 = "SELECT q.* - FROM {question} q - WHERE q.id = ?"; + FROM {question} q + WHERE q.id = ?"; if ( !isset ($datasetdefs["$r->type-$r->category-$r->name"])){ $datasetdefs["$r->type-$r->category-$r->name"]= $r; } @@ -1982,7 +1968,7 @@ function print_dataset_definitions_category_shared($question,$datasetdefsq) { //limit the name length displayed if (!empty($qu->name)) { $qu->name = (strlen($qu->name) > $lnamemax) ? - substr($qu->name, 0, $lnamemax).'...' : $qu->name; + substr($qu->name, 0, $lnamemax).'...' : $qu->name; } else { $qu->name = ''; } @@ -1991,42 +1977,42 @@ function print_dataset_definitions_category_shared($question,$datasetdefsq) { } $line++; $text .="$qu->name"; - $nb_of_quiz = 0; - $nb_of_attempts=0; - $used_in_quiz = false ; - if ($list = $DB->get_records('quiz_question_instances', array( 'question'=> $qu->id))){ - $used_in_quiz = true; - foreach($list as $key => $li){ - $nb_of_quiz ++; - if($att = $DB->get_records('quiz_attempts',array( 'quiz'=> $li->quiz, 'preview'=> '0'))){ - $nb_of_attempts+= count($att); + $nb_of_quiz = 0; + $nb_of_attempts=0; + $used_in_quiz = false ; + if ($list = $DB->get_records('quiz_question_instances', array( 'question'=> $qu->id))){ + $used_in_quiz = true; + foreach($list as $key => $li){ + $nb_of_quiz ++; + if($att = $DB->get_records('quiz_attempts',array( 'quiz'=> $li->quiz, 'preview'=> '0'))){ + $nb_of_attempts+= count($att); + } } } - } - if($used_in_quiz){ - $text .="$nb_of_quiz"; - }else { - $text .="0"; - } - if($used_in_quiz){ - $text .="$nb_of_attempts"; - }else { - $text .="
"; - } + if($used_in_quiz){ + $text .="$nb_of_quiz"; + }else { + $text .="0"; + } + if($used_in_quiz){ + $text .="$nb_of_attempts"; + }else { + $text .="
"; + } - $text .=""; + $text .=""; } } $text .=""; }else{ - $text .=get_string('nosharedwildcard', 'qtype_calculated'); + $text .=get_string('nosharedwildcard', 'qtype_calculated'); } return $text ; } function find_math_equations($text) { - /// Returns the possible dataset names found in the text as an array - /// The array has the dataset name for both key and value + /// Returns the possible dataset names found in the text as an array + /// The array has the dataset name for both key and value $equations = array(); /* $qtext = ""; $qtextremaining = $numericalquestion->questiontext ; @@ -2038,7 +2024,7 @@ function find_math_equations($text) { if (empty($regs1[1])) { $str = ''; } else { -*/ + */ while (preg_match('~\{=([^[:space:]}]*)}~', $text, $regs)) { $equations[] = $regs[1]; $text = str_replace($regs[0], '', $text); @@ -2048,12 +2034,12 @@ function find_math_equations($text) { function get_virtual_qtype() { global $QTYPES; - $this->virtualqtype =& $QTYPES['numerical']; + $this->virtualqtype =& $QTYPES['numerical']; return $this->virtualqtype; } -/// BACKUP FUNCTIONS //////////////////////////// + /// BACKUP FUNCTIONS //////////////////////////// /* * Backup the data in the question @@ -2105,7 +2091,7 @@ function backup($bf,$preferences,$question,$level=6) { return $status; } -/// RESTORE FUNCTIONS ///////////////// + /// RESTORE FUNCTIONS ///////////////// /* * Restores the data in the question @@ -2154,43 +2140,43 @@ function restore($old_question_id,$new_question_id,$info,$restore) { } backup_flush(300); } - //Get the calculated_options array - // need to check as old questions don't have calculated_options record - if(isset($info['#']['CALCULATED_OPTIONS'])){ - $calculatedoptions = $info['#']['CALCULATED_OPTIONS']; - - //Iterate over calculated_options - for($i = 0; $i < sizeof($calculatedoptions); $i++){ - $cal_info = $calculatedoptions[$i]; - //traverse_xmlize($cal_info); //Debug - //print_object ($GLOBALS['traverse_array']); //Debug - //$GLOBALS['traverse_array']=""; //Debug - - //Now, build the question_calculated_options record structure - $calculated_options->questionid = $new_question_id; - $calculated_options->synchronize = backup_todb($cal_info['#']['SYNCHRONIZE']['0']['#']); - $calculated_options->single = backup_todb($cal_info['#']['SINGLE']['0']['#']); - $calculated_options->shuffleanswers = isset($cal_info['#']['SHUFFLEANSWERS']['0']['#'])?backup_todb($mul_info['#']['SHUFFLEANSWERS']['0']['#']):''; - $calculated_options->correctfeedback = backup_todb($cal_info['#']['CORRECTFEEDBACK']['0']['#']); - $calculated_options->partiallycorrectfeedback = backup_todb($cal_info['#']['PARTIALLYCORRECTFEEDBACK']['0']['#']); - $calculated_options->incorrectfeedback = backup_todb($cal_info['#']['INCORRECTFEEDBACK']['0']['#']); - $calculated_options->answernumbering = backup_todb($cal_info['#']['ANSWERNUMBERING']['0']['#']); - - //The structure is equal to the db, so insert the question_calculated_options - $newid = $DB->insert_record ("question_calculated_options",$calculated_options); - - //Do some output - if (($i+1) % 50 == 0) { - if (!defined('RESTORE_SILENTLY')) { - echo "."; - if (($i+1) % 1000 == 0) { - echo "
"; + //Get the calculated_options array + // need to check as old questions don't have calculated_options record + if(isset($info['#']['CALCULATED_OPTIONS'])){ + $calculatedoptions = $info['#']['CALCULATED_OPTIONS']; + + //Iterate over calculated_options + for($i = 0; $i < sizeof($calculatedoptions); $i++){ + $cal_info = $calculatedoptions[$i]; + //traverse_xmlize($cal_info); //Debug + //print_object ($GLOBALS['traverse_array']); //Debug + //$GLOBALS['traverse_array']=""; //Debug + + //Now, build the question_calculated_options record structure + $calculated_options->questionid = $new_question_id; + $calculated_options->synchronize = backup_todb($cal_info['#']['SYNCHRONIZE']['0']['#']); + $calculated_options->single = backup_todb($cal_info['#']['SINGLE']['0']['#']); + $calculated_options->shuffleanswers = isset($cal_info['#']['SHUFFLEANSWERS']['0']['#'])?backup_todb($mul_info['#']['SHUFFLEANSWERS']['0']['#']):''; + $calculated_options->correctfeedback = backup_todb($cal_info['#']['CORRECTFEEDBACK']['0']['#']); + $calculated_options->partiallycorrectfeedback = backup_todb($cal_info['#']['PARTIALLYCORRECTFEEDBACK']['0']['#']); + $calculated_options->incorrectfeedback = backup_todb($cal_info['#']['INCORRECTFEEDBACK']['0']['#']); + $calculated_options->answernumbering = backup_todb($cal_info['#']['ANSWERNUMBERING']['0']['#']); + + //The structure is equal to the db, so insert the question_calculated_options + $newid = $DB->insert_record ("question_calculated_options",$calculated_options); + + //Do some output + if (($i+1) % 50 == 0) { + if (!defined('RESTORE_SILENTLY')) { + echo "."; + if (($i+1) % 1000 == 0) { + echo "
"; + } } + backup_flush(300); } - backup_flush(300); } } - } //Now restore numerical_units $status = question_restore_numerical_units ($old_question_id,$new_question_id,$cal_info,$restore); $status = question_restore_numerical_options($old_question_id,$new_question_id,$info,$restore); @@ -2207,53 +2193,125 @@ function restore($old_question_id,$new_question_id,$info,$restore) { return $status; } - /** - * Runs all the code required to set up and save an essay question for testing purposes. - * Alternate DB table prefix may be used to facilitate data deletion. - */ - function generate_test($name, $courseid = null) { - global $DB; - list($form, $question) = parent::generate_test($name, $courseid); - $form->feedback = 1; - $form->multiplier = array(1, 1); - $form->shuffleanswers = 1; - $form->noanswers = 1; - $form->qtype ='calculated'; - $question->qtype ='calculated'; - $form->answers = array('{a} + {b}'); - $form->fraction = array(1); - $form->tolerance = array(0.01); - $form->tolerancetype = array(1); - $form->correctanswerlength = array(2); - $form->correctanswerformat = array(1); - $form->questiontext = "What is {a} + {b}?"; - - if ($courseid) { - $course = $DB->get_record('course', array('id'=> $courseid)); - } - - $new_question = $this->save_question($question, $form, $course); - - $dataset_form = new stdClass(); - $dataset_form->nextpageparam["forceregeneration"]= 1; - $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0); - $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0); - $dataset_form->calclength = array(1 => 1, 2 => 1); - $dataset_form->number = array(1 => 5.4 , 2 => 4.9); - $dataset_form->itemid = array(1 => '' , 2 => ''); - $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform'); - $dataset_form->definition = array(1 => "1-0-a", - 2 => "1-0-b"); - $dataset_form->nextpageparam = array('forceregeneration' => false); - $dataset_form->addbutton = 1; - $dataset_form->selectadd = 1; - $dataset_form->courseid = $courseid; - $dataset_form->cmid = 0; - $dataset_form->id = $new_question->id; - $this->save_dataset_items($new_question, $dataset_form); - - return $new_question; - } + /** + * Runs all the code required to set up and save an essay question for testing purposes. + * Alternate DB table prefix may be used to facilitate data deletion. + */ + function generate_test($name, $courseid = null) { + global $DB; + list($form, $question) = parent::generate_test($name, $courseid); + $form->feedback = 1; + $form->multiplier = array(1, 1); + $form->shuffleanswers = 1; + $form->noanswers = 1; + $form->qtype ='calculated'; + $question->qtype ='calculated'; + $form->answers = array('{a} + {b}'); + $form->fraction = array(1); + $form->tolerance = array(0.01); + $form->tolerancetype = array(1); + $form->correctanswerlength = array(2); + $form->correctanswerformat = array(1); + $form->questiontext = "What is {a} + {b}?"; + + if ($courseid) { + $course = $DB->get_record('course', array('id'=> $courseid)); + } + + $new_question = $this->save_question($question, $form, $course); + + $dataset_form = new stdClass(); + $dataset_form->nextpageparam["forceregeneration"]= 1; + $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0); + $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0); + $dataset_form->calclength = array(1 => 1, 2 => 1); + $dataset_form->number = array(1 => 5.4 , 2 => 4.9); + $dataset_form->itemid = array(1 => '' , 2 => ''); + $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform'); + $dataset_form->definition = array(1 => "1-0-a", + 2 => "1-0-b"); + $dataset_form->nextpageparam = array('forceregeneration' => false); + $dataset_form->addbutton = 1; + $dataset_form->selectadd = 1; + $dataset_form->courseid = $courseid; + $dataset_form->cmid = 0; + $dataset_form->id = $new_question->id; + $this->save_dataset_items($new_question, $dataset_form); + + return $new_question; + } + + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + parent::move_files($question, $newcategory); + + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'answerfeedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + $component = 'qtype_numerical'; + $filearea = 'instruction'; + $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + $itemid = reset($args); + if ($component == 'question' && $filearea == 'answerfeedback') { + + // check if answer id exists + $result = $options->feedback && array_key_exists($itemid, $question->options->answers); + if (!$result) { + return false; + } + // check response + if (!$this->check_response($question, $state)) { + return false; + } + return true; + } else if ($filearea == 'instruction') { + // TODO: should it be display all the time like questiontext? + // check if question id exists + if ($itemid != $question->id) { + return false; + } else { + return true; + } + } else if (in_array($filearea, array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) { + // TODO: calculated type doesn't display question feedback yet + return false; + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } } //// END OF CLASS //// @@ -2267,11 +2325,11 @@ function generate_test($name, $courseid = null) { } function qtype_calculated_calculate_answer($formula, $individualdata, - $tolerance, $tolerancetype, $answerlength, $answerformat='1', $unit='') { -/// The return value has these properties: -/// ->answer the correct answer -/// ->min the lower bound for an acceptable response -/// ->max the upper bound for an accetpable response + $tolerance, $tolerancetype, $answerlength, $answerformat='1', $unit='') { + /// The return value has these properties: + /// ->answer the correct answer + /// ->min the lower bound for an acceptable response + /// ->max the upper bound for an accetpable response /// Exchange formula variables with the correct values... global $QTYPES; @@ -2290,12 +2348,12 @@ function qtype_calculated_calculate_answer($formula, $individualdata, if (preg_match('~^(.*\\.)(.*)$~', $calculated->answer, $regs)) { $calculated->answer = $regs[1] . substr( - $regs[2] . '00000000000000000000000000000000000000000x', - 0, $answerlength) - . $unit; + $regs[2] . '00000000000000000000000000000000000000000x', + 0, $answerlength) + . $unit; } else { $calculated->answer .= - substr('.00000000000000000000000000000000000000000x', + substr('.00000000000000000000000000000000000000000x', 0, $answerlength + 1) . $unit; } } else { @@ -2337,9 +2395,9 @@ function qtype_calculated_calculate_answer($formula, $individualdata, } else { // Attach additional zeros at the end of $answer, $answer .= (1==strlen($answer) ? '.' : '') - . '00000000000000000000000000000000000000000x'; + . '00000000000000000000000000000000000000000x'; $calculated->answer = $sign - .substr($answer, 0, $answerlength +1).$eX.$unit; + .substr($answer, 0, $answerlength +1).$eX.$unit; } } else { // Stick to plain numeric format @@ -2349,7 +2407,7 @@ function qtype_calculated_calculate_answer($formula, $individualdata, } else { // Could be an idea to add some zeros here $answer .= (preg_match('~^[0-9]*$~', $answer) ? '.' : '') - . '00000000000000000000000000000000000000000x'; + . '00000000000000000000000000000000000000000x'; $oklen = $answerlength + ($p10 < 1 ? 2-$p10 : 1); $calculated->answer = $sign.substr($answer, 0, $oklen).$unit; } @@ -2365,9 +2423,9 @@ function qtype_calculated_calculate_answer($formula, $individualdata, function qtype_calculated_find_formula_errors($formula) { -/// Validates the formula submitted from the question edit page. -/// Returns false if everything is alright. -/// Otherwise it constructs an error message + /// Validates the formula submitted from the question edit page. + /// Returns false if everything is alright. + /// Otherwise it constructs an error message // Strip away dataset names while (preg_match('~\\{[[:alpha:]][^>} <{"\']*\\}~', $formula, $regs)) { $formula = str_replace($regs[0], '1', $formula); @@ -2380,59 +2438,58 @@ function qtype_calculated_find_formula_errors($formula) { $operatorornumber = "[$safeoperatorchar.0-9eE]"; while ( preg_match("~(^|[$safeoperatorchar,(])([a-z0-9_]*)\\(($operatorornumber+(,$operatorornumber+((,$operatorornumber+)+)?)?)?\\)~", - $formula, $regs)) { - + $formula, $regs)) { switch ($regs[2]) { // Simple parenthesis - case '': - if ($regs[4] || strlen($regs[3])==0) { - return get_string('illegalformulasyntax', 'quiz', $regs[0]); - } - break; + case '': + if ($regs[4] || strlen($regs[3])==0) { + return get_string('illegalformulasyntax', 'quiz', $regs[0]); + } + break; // Zero argument functions - case 'pi': - if ($regs[3]) { - return get_string('functiontakesnoargs', 'quiz', $regs[2]); - } - break; + case 'pi': + if ($regs[3]) { + return get_string('functiontakesnoargs', 'quiz', $regs[2]); + } + break; // Single argument functions (the most common case) - case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh': - case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos': - case 'cosh': case 'decbin': case 'decoct': case 'deg2rad': - case 'exp': case 'expm1': case 'floor': case 'is_finite': - case 'is_infinite': case 'is_nan': case 'log10': case 'log1p': - case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt': - case 'tan': case 'tanh': - if (!empty($regs[4]) || empty($regs[3])) { - return get_string('functiontakesonearg','quiz',$regs[2]); - } - break; + case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh': + case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos': + case 'cosh': case 'decbin': case 'decoct': case 'deg2rad': + case 'exp': case 'expm1': case 'floor': case 'is_finite': + case 'is_infinite': case 'is_nan': case 'log10': case 'log1p': + case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt': + case 'tan': case 'tanh': + if (!empty($regs[4]) || empty($regs[3])) { + return get_string('functiontakesonearg','quiz',$regs[2]); + } + break; // Functions that take one or two arguments - case 'log': case 'round': - if (!empty($regs[5]) || empty($regs[3])) { - return get_string('functiontakesoneortwoargs','quiz',$regs[2]); - } - break; + case 'log': case 'round': + if (!empty($regs[5]) || empty($regs[3])) { + return get_string('functiontakesoneortwoargs','quiz',$regs[2]); + } + break; // Functions that must have two arguments - case 'atan2': case 'fmod': case 'pow': - if (!empty($regs[5]) || empty($regs[4])) { - return get_string('functiontakestwoargs', 'quiz', $regs[2]); - } - break; + case 'atan2': case 'fmod': case 'pow': + if (!empty($regs[5]) || empty($regs[4])) { + return get_string('functiontakestwoargs', 'quiz', $regs[2]); + } + break; // Functions that take two or more arguments - case 'min': case 'max': - if (empty($regs[4])) { - return get_string('functiontakesatleasttwo','quiz',$regs[2]); - } - break; + case 'min': case 'max': + if (empty($regs[4])) { + return get_string('functiontakesatleasttwo','quiz',$regs[2]); + } + break; - default: - return get_string('unsupportedformulafunction','quiz',$regs[2]); + default: + return get_string('unsupportedformulafunction','quiz',$regs[2]); } // Exchange the function call with '1' and then chack for @@ -2452,13 +2509,4 @@ function qtype_calculated_find_formula_errors($formula) { // Formula just might be valid return false; } - } - -function dump($obj) { - echo "
\n";
-    var_dump($obj);
-    echo "

\n"; -} - - diff --git a/question/type/calculated/version.php b/question/type/calculated/version.php index aa56eb9470c80..6b690e75cf360 100644 --- a/question/type/calculated/version.php +++ b/question/type/calculated/version.php @@ -1,5 +1,5 @@ version = 2010020800; +$plugin->version = 2010020801; $plugin->requires = 2007101000; diff --git a/question/type/calculatedmulti/edit_calculatedmulti_form.php b/question/type/calculatedmulti/edit_calculatedmulti_form.php index 3b81121d8cb0e..a2e736f21ae0f 100644 --- a/question/type/calculatedmulti/edit_calculatedmulti_form.php +++ b/question/type/calculatedmulti/edit_calculatedmulti_form.php @@ -18,11 +18,11 @@ class question_edit_calculatedmulti_form extends question_edit_form { * * @var question_calculatedmulti_qtype */ - var $qtypeobj; + public $qtypeobj; public $questiondisplay ; public $initialname = ''; public $reload = false ; - function question_edit_calculatedmulti_form(&$submiturl, &$question, &$category, &$contexts, $formeditable = true){ + function question_edit_calculatedmulti_form(&$submiturl, &$question, &$category, &$contexts, $formeditable = true) { global $QTYPES, $SESSION, $CFG, $DB; $this->question = $question; $this->qtypeobj =& $QTYPES[$this->question->qtype]; @@ -38,22 +38,21 @@ function question_edit_calculatedmulti_form(&$submiturl, &$question, &$category, $regs= array(); if(preg_match('~#\{([^[:space:]]*)#~',$question->name , $regs)){ $question->name = str_replace($regs[0], '', $question->name); - }; + }; } }else { - } + } parent::question_edit_form($submiturl, $question, $category, $contexts, $formeditable); } function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) { - // $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption); - $repeated = array(); + // $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption); + $repeated = array(); $repeated[] =& $mform->createElement('header', 'answerhdr', $label); - // if ($this->editasmultichoice == 1){ + // if ($this->editasmultichoice == 1){ $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50)); $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions); - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), - array('course' => $this->coursefilesid)); + $repeated[] =& $mform->createElement('editor', 'feedback', get_string('feedback', 'quiz'), null, $this->editoroptions); $repeatedoptions['answer']['type'] = PARAM_RAW; $repeatedoptions['fraction']['default'] = 0; $answersoption = 'answers'; @@ -61,8 +60,8 @@ function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions $mform->setType('answer', PARAM_NOTAGS); $addrepeated = array(); - $addrepeated[] =& $mform->createElement('hidden', 'tolerance'); - $addrepeated[] =& $mform->createElement('hidden', 'tolerancetype',1); + $addrepeated[] =& $mform->createElement('hidden', 'tolerance'); + $addrepeated[] =& $mform->createElement('hidden', 'tolerancetype',1); $repeatedoptions['tolerance']['type'] = PARAM_NUMBER; $repeatedoptions['tolerance']['default'] = 0.01; @@ -72,7 +71,7 @@ function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions $answerlengthformats = array('1' => get_string('decimalformat', 'quiz'), '2' => get_string('significantfiguresformat', 'quiz')); $addrepeated[] =& $mform->createElement('select', 'correctanswerformat', get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats); array_splice($repeated, 3, 0, $addrepeated); - $repeated[1]->setLabel('...{={x}+..}...'); + $repeated[1]->setLabel('...{={x}+..}...'); return $repeated; } @@ -85,79 +84,78 @@ function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions function definition_inner(&$mform) { global $QTYPES; $this->qtypeobj =& $QTYPES[$this->qtype()]; - // echo code left for testing period - // echo "

question ".optional_param('multichoice', '', PARAM_RAW)." optional

";print_r($this->question);echo "

"; + // echo code left for testing period + // echo "

question ".optional_param('multichoice', '', PARAM_RAW)." optional

";print_r($this->question);echo "

"; $label = get_string("sharedwildcards", "qtype_calculated"); $mform->addElement('hidden', 'initialcategory', 1); $mform->addElement('hidden', 'reload', 1); $mform->setType('initialcategory', PARAM_INT); - // $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question); - $html2 =""; + // $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question); + $html2 =""; $mform->insertElementBefore($mform->createElement('static','listcategory',$label,$html2),'name'); if(isset($this->question->id )){ $mform->insertElementBefore($mform->createElement('static','initialname',get_string('questionstoredname','qtype_calculated'),$this->initialname),'name'); }; $addfieldsname='updatecategory'; $addstring=get_string("updatecategory", "qtype_calculated"); - $mform->registerNoSubmitButton($addfieldsname); + $mform->registerNoSubmitButton($addfieldsname); $this->editasmultichoice = 1 ; - + $mform->insertElementBefore( $mform->createElement('submit', $addfieldsname, $addstring),'listcategory'); $mform->registerNoSubmitButton('createoptionbutton'); - $mform->addElement('hidden', 'multichoice',$this->editasmultichoice); - $mform->setType('multichoice', PARAM_INT); - + $mform->addElement('hidden', 'multichoice',$this->editasmultichoice); + $mform->setType('multichoice', PARAM_INT); -// $mform->addElement('header', 'choicehdr',get_string('multichoicecalculatedquestion', 'qtype_calculated')); - $menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice')); - $mform->addElement('select', 'single', get_string('answerhowmany', 'qtype_multichoice'), $menu); - $mform->setDefault('single', 1); - - $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0,1)); - $mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice'); - $mform->setDefault('shuffleanswers', 1); - - $numberingoptions = $QTYPES['multichoice']->get_numbering_styles(); - $menu = array(); - foreach ($numberingoptions as $numberingoption) { - $menu[$numberingoption] = get_string('answernumbering' . $numberingoption, 'qtype_multichoice'); - } - $mform->addElement('select', 'answernumbering', get_string('answernumbering', 'qtype_multichoice'), $menu); - $mform->setDefault('answernumbering', 'abc'); + + // $mform->addElement('header', 'choicehdr',get_string('multichoicecalculatedquestion', 'qtype_calculated')); + $menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice')); + $mform->addElement('select', 'single', get_string('answerhowmany', 'qtype_multichoice'), $menu); + $mform->setDefault('single', 1); + + $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0,1)); + $mform->setHelpButton('shuffleanswers', array('multichoiceshuffle', get_string('shuffleanswers','qtype_multichoice'), 'qtype_multichoice')); + $mform->setDefault('shuffleanswers', 1); + + $numberingoptions = $QTYPES['multichoice']->get_numbering_styles(); + $menu = array(); + foreach ($numberingoptions as $numberingoption) { + $menu[$numberingoption] = get_string('answernumbering' . $numberingoption, 'qtype_multichoice'); + } + $mform->addElement('select', 'answernumbering', get_string('answernumbering', 'qtype_multichoice'), $menu); + $mform->setDefault('answernumbering', 'abc'); $creategrades = get_grade_options(); - $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'), - $creategrades->gradeoptionsfull, max(5, QUESTION_NUMANS_START)); - + $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'), + $creategrades->gradeoptionsfull, max(5, QUESTION_NUMANS_START)); + $repeated = array(); - // if ($this->editasmultichoice == 1){ - $nounits = optional_param('nounits', 1, PARAM_INT); - $mform->addElement('hidden', 'nounits', $nounits); - $mform->setType('nounits', PARAM_INT); - $mform->setConstants(array('nounits'=>$nounits)); - for ($i=0; $i< $nounits; $i++) { - $mform->addElement('hidden','unit'."[$i]", optional_param('unit'."[$i]", '', PARAM_NOTAGS)); - $mform->setType('unit'."[$i]", PARAM_NOTAGS); - $mform->addElement('hidden', 'multiplier'."[$i]", optional_param('multiplier'."[$i]", '', PARAM_NUMBER)); - $mform->setType('multiplier'."[$i]", PARAM_NUMBER); - } - $mform->addElement('hidden','unitgradingtype',optional_param('unitgradingtype', '', PARAM_INT)) ; - $mform->addElement('hidden','unitpenalty',optional_param('unitpenalty', '', PARAM_NUMBER)) ; - $mform->addElement('hidden','showunits',optional_param('showunits', '', PARAM_INT)) ; - $mform->addElement('hidden','unitsleft',optional_param('unitsleft', '', PARAM_INT)) ; - $mform->addElement('hidden','instructions',optional_param('instructions', '', PARAM_RAW)) ; + // if ($this->editasmultichoice == 1){ + $nounits = optional_param('nounits', 1, PARAM_INT); + $mform->addElement('hidden', 'nounits', $nounits); + $mform->setType('nounits', PARAM_INT); + $mform->setConstants(array('nounits'=>$nounits)); + for ($i=0; $i< $nounits; $i++) { + $mform->addElement('hidden','unit'."[$i]", optional_param('unit'."[$i]", '', PARAM_NOTAGS)); + $mform->setType('unit'."[$i]", PARAM_NOTAGS); + $mform->addElement('hidden', 'multiplier'."[$i]", optional_param('multiplier'."[$i]", '', PARAM_NUMBER)); + $mform->setType('multiplier'."[$i]", PARAM_NUMBER); + } + $mform->addElement('hidden','unitgradingtype',optional_param('unitgradingtype', '', PARAM_INT)) ; + $mform->addElement('hidden','unitpenalty',optional_param('unitpenalty', '', PARAM_NUMBER)) ; + $mform->addElement('hidden','showunits',optional_param('showunits', '', PARAM_INT)) ; + $mform->addElement('hidden','unitsleft',optional_param('unitsleft', '', PARAM_INT)) ; + $mform->addElement('hidden','instructions',optional_param('instructions', '', PARAM_RAW)) ; - $mform->setType('addunits','hidden'); - $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice')); + $mform->setType('addunits','hidden'); + $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice')); - foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { - $mform->addElement('htmleditor', $feedbackname, get_string($feedbackname, 'qtype_multichoice'), - array('course' => $this->coursefilesid)); - $mform->setType($feedbackname, PARAM_RAW); - } + foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { + $mform->addElement('editor', $feedbackname, get_string($feedbackname, 'qtype_multichoice'), null, $this->editoroptions); + $mform->setType($feedbackname, PARAM_RAW); + } //hidden elements $mform->addElement('hidden', 'synchronize', ''); $mform->setType('synchronize', PARAM_INT); @@ -168,40 +166,43 @@ function definition_inner(&$mform) { } $mform->addElement('hidden', 'wizard', 'datasetdefinitions'); $mform->setType('wizard', PARAM_ALPHA); - - } - function set_data($question) { - $default_values['multichoice']= $this->editasmultichoice ; //$this->editasmultichoice ; + function data_preprocessing($question) { + $default_values['multichoice']= $this->editasmultichoice ; //$this->editasmultichoice ; if (isset($question->options)){ $answers = $question->options->answers; if (count($answers)) { $key = 0; foreach ($answers as $answer){ + $draftid = file_get_submitted_draft_itemid('feedback['.$key.']'); $default_values['answer['.$key.']'] = $answer->answer; $default_values['fraction['.$key.']'] = $answer->fraction; $default_values['tolerance['.$key.']'] = $answer->tolerance; $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype; $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength; $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat; - $default_values['feedback['.$key.']'] = $answer->feedback; + $default_values['feedback['.$key.']'] = array(); + // prepare draftarea + $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area($draftid, $this->context->id, 'question', 'answerfeedback', empty($answer->id)?null:(int)$answer->id, null, $answer->feedback); + $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat; + $default_values['feedback['.$key.']']['itemid'] = $draftid; $key++; } } - // $default_values['unitgradingtype'] = $question->options->unitgradingtype ; - // $default_values['unitpenalty'] = $question->options->unitpenalty ; - // $default_values['showunits'] = $question->options->showunits ; - // $default_values['unitsleft'] = $question->options->unitsleft ; - // $default_values['instructions'] = $question->options->instructions ; - $default_values['synchronize'] = $question->options->synchronize ; + // $default_values['unitgradingtype'] = $question->options->unitgradingtype ; + // $default_values['unitpenalty'] = $question->options->unitpenalty ; + // $default_values['showunits'] = $question->options->showunits ; + // $default_values['unitsleft'] = $question->options->unitsleft ; + // $default_values['instructions'] = $question->options->instructions ; + $default_values['synchronize'] = $question->options->synchronize ; if (isset($question->options->units)){ $units = array_values($question->options->units); // make sure the default unit is at index 0 usort($units, create_function('$a, $b', - 'if (1.0 === (float)$a->multiplier) { return -1; } else '. - 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); + 'if (1.0 === (float)$a->multiplier) { return -1; } else '. + 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); if (count($units)) { $key = 0; foreach ($units as $unit){ @@ -213,30 +214,51 @@ function set_data($question) { } } if (isset($question->options->single)){ - $default_values['single'] = $question->options->single; - $default_values['answernumbering'] = $question->options->answernumbering; - $default_values['shuffleanswers'] = $question->options->shuffleanswers; - $default_values['correctfeedback'] = $question->options->correctfeedback; - $default_values['partiallycorrectfeedback'] = $question->options->partiallycorrectfeedback; - $default_values['incorrectfeedback'] = $question->options->incorrectfeedback; - } + $default_values['single'] = $question->options->single; + $default_values['answernumbering'] = $question->options->answernumbering; + $default_values['shuffleanswers'] = $question->options->shuffleanswers; + } $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated'); $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated'); - /* set the wild cards category display given that on loading the category element is - unselected when processing this function but have a valid value when processing the - update category button. The value can be obtain by - $qu->category =$this->_form->_elements[$this->_form->_elementIndex['category']]->_values[0]; - but is coded using existing functions - */ - $qu = new stdClass; - $el = new stdClass; - /* no need to call elementExists() here */ - if ($this->_form->elementExists('category')){ + + // prepare draft files + foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { + if (!isset($question->options->$feedbackname)) { + continue; + } + $text = $question->options->$feedbackname; + $draftid = file_get_submitted_draft_itemid($feedbackname); + $feedbackformat = $feedbackname . 'format'; + $format = $question->options->$feedbackformat; + $default_values[$feedbackname] = array(); + $default_values[$feedbackname]['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'qtype_calculatedmulti', // component + $feedbackname, // filarea + !empty($question->id)?(int)$question->id:null, // itemid + $this->fileoptions, // options + $text // text + ); + $default_values[$feedbackname]['format'] = $format; + $default_values[$feedbackname]['itemid'] = $draftid; + } + /** + * set the wild cards category display given that on loading the category element is + * unselected when processing this function but have a valid value when processing the + * update category button. The value can be obtain by + * $qu->category =$this->_form->_elements[$this->_form->_elementIndex['category']]->_values[0]; + * but is coded using existing functions + */ + $qu = new stdClass; + $el = new stdClass; + /* no need to call elementExists() here */ + if ($this->_form->elementExists('category')){ $el=$this->_form->getElement('category'); - } else { + } else { $el=$this->_form->getElement('categorymoveto'); - } - if($value =$el->getSelected()) { + } + if($value =$el->getSelected()) { $qu->category =$value[0]; }else { $qu->category=$question->category;// on load $question->category is set by question.php @@ -244,8 +266,7 @@ function set_data($question) { $html2 = $this->qtypeobj->print_dataset_definitions_category($qu); $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2 ; $question = (object)((array)$question + $default_values); - - parent::set_data($question); + return $question; } function qtype() { @@ -253,21 +274,20 @@ function qtype() { } function validation($data, $files) { - // echo code left for testing period - - // echo "

question

";print_r($this->question);echo "

"; - // echo "

data

";print_r($data);echo "

"; + // echo code left for testing period + // echo "

question

";print_r($this->question);echo "

"; + // echo "

data

";print_r($data);echo "

"; $errors = parent::validation($data, $files); //verifying for errors in {=...} in question text; $qtext = ""; - $qtextremaining = $data['questiontext'] ; - $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']); - foreach ($possibledatasets as $name => $value) { + $qtextremaining = $data['questiontext']['text']; + $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']); + foreach ($possibledatasets as $name => $value) { $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining); } - // echo "numericalquestion qtextremaining
";print_r($possibledatasets);
-        while  (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
+        // echo "numericalquestion qtextremaining 
";print_r($possibledatasets);
+        while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
             $qtext =$qtext.$qtextsplits[0];
             $qtextremaining = $qtextsplits[1];
@@ -282,13 +302,13 @@ function validation($data, $files) {
         $answers = $data['answer'];
         $answercount = 0;
         $maxgrade = false;
-        $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']);
+        $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
         $mandatorydatasets = array();
         foreach ($answers as $key => $answer){
             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
         }
         if ( count($mandatorydatasets )==0){
-          //  $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent');
+            //  $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent');
             foreach ($answers as $key => $answer){
                 $errors['answer['.$key.']'] = get_string('atleastonewildcard', 'qtype_datasetdependent');
             }
@@ -296,15 +316,15 @@ function validation($data, $files) {
         if ($data['multichoice']== 1 ){
             foreach ($answers as $key => $answer){
                 $trimmedanswer = trim($answer);
-                if (($trimmedanswer!='')||$answercount==0){    
+                if (($trimmedanswer!='')||$answercount==0){
                     //verifying for errors in {=...} in answer text;
                     $qanswer = "";
                     $qanswerremaining =  $trimmedanswer ;
                     $possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
-                        foreach ($possibledatasets as $name => $value) {
+                    foreach ($possibledatasets as $name => $value) {
                         $qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
                     }
-                //     echo "numericalquestion qanswerremaining 
";print_r($possibledatasets);
+                    //     echo "numericalquestion qanswerremaining 
";print_r($possibledatasets);
                     while  (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
                         $qanswersplits = explode($regs1[0], $qanswerremaining, 2);
                         $qanswer =$qanswer.$qanswersplits[0];
@@ -320,16 +340,16 @@ function validation($data, $files) {
                 }
                 if ($trimmedanswer!=''){
                     if ('2' == $data['correctanswerformat'][$key]
-                            && '0' == $data['correctanswerlength'][$key]) {
-                        $errors['correctanswerlength['.$key.']'] = get_string('zerosignificantfiguresnotallowed','quiz');
-                    }
+                        && '0' == $data['correctanswerlength'][$key]) {
+                            $errors['correctanswerlength['.$key.']'] = get_string('zerosignificantfiguresnotallowed','quiz');
+                        }
                     if (!is_numeric($data['tolerance'][$key])){
                         $errors['tolerance['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
                     }
                     if ($data['fraction'][$key] == 1) {
-                       $maxgrade = true;
+                        $maxgrade = true;
                     }
-    
+
                     $answercount++;
                 }
                 //check grades
@@ -342,14 +362,14 @@ function validation($data, $files) {
                     if ($data['fraction'][$key] > $maxfraction) {
                         $maxfraction = $data['fraction'][$key];
                     }
-                }        
+                }
             }
             if ($answercount==0){
                 $errors['answer[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
                 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
             } elseif ($answercount==1){
                 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
-    
+
             }
 
             /// Perform sanity checks on fractional grades
@@ -365,17 +385,15 @@ function validation($data, $files) {
                     $errors['fraction[0]'] = get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
                 }
             }
-            
-        
+
             if ($answercount==0){
                 $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
             }
             if ($maxgrade == false) {
                 $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
             }
-        
+
         }
         return $errors;
     }
 }
-
diff --git a/question/type/calculatedmulti/lib.php b/question/type/calculatedmulti/lib.php
new file mode 100644
index 0000000000000..220f828fa011a
--- /dev/null
+++ b/question/type/calculatedmulti/lib.php
@@ -0,0 +1,31 @@
+.
+
+/**
+ * Serve question type files
+ *
+ * @since 2.0
+ * @package questionbank
+ * @subpackage questiontypes
+ * @author Dongsheng Cai 
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+function qtype_calculatedmulti_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
+    global $DB, $CFG;
+    require_once($CFG->libdir . '/questionlib.php');
+    question_pluginfile($course, $context, 'qtype_calculatedmulti', $filearea, $args, $forcedownload);
+}
diff --git a/question/type/calculatedmulti/questiontype.php b/question/type/calculatedmulti/questiontype.php
index 0d878988a062e..c648899e3abb1 100644
--- a/question/type/calculatedmulti/questiontype.php
+++ b/question/type/calculatedmulti/questiontype.php
@@ -1,17 +1,30 @@
 .
+
 /////////////////
 // CALCULATED ///
 /////////////////
 
 /// QUESTION TYPE CLASS //////////////////
 
-
-
 class question_calculatedmulti_qtype extends question_calculated_qtype {
 
     // Used by the function custom_generator_tools:
-    var $calcgenerateidhasbeenadded = false;
+    public $calcgenerateidhasbeenadded = false;
     public $virtualqtype = false;
 
     function name() {
@@ -28,14 +41,13 @@ function requires_qtypes() {
 
 
     function save_question_options($question) {
-        //$options = $question->subtypeoptions;
-        // Get old answers:
         global $CFG, $DB, $QTYPES ;
+        $context = $question->context;
         if (isset($question->answer) && !isset($question->answers)) {
             $question->answers = $question->answer;
         }
         // calculated options
-        $update = true ; 
+        $update = true ;
         $options = $DB->get_record("question_calculated_options", array("question" => $question->id));
         if (!$options) {
             $update = false;
@@ -46,19 +58,20 @@ function save_question_options($question) {
         $options->single = $question->single;
         $options->answernumbering = $question->answernumbering;
         $options->shuffleanswers = $question->shuffleanswers;
-        $options->correctfeedback = trim($question->correctfeedback);
-        $options->partiallycorrectfeedback = trim($question->partiallycorrectfeedback);
-        $options->incorrectfeedback = trim($question->incorrectfeedback);
+
+        // save question feedback files
+        foreach (array('correct', 'partiallycorrect', 'incorrect') as $feedbacktype) {
+            $feedbackname = $feedbacktype . 'feedback';
+            $feedbackformat = $feedbackname . 'format';
+            $feedback = $question->$feedbackname;
+            $options->$feedbackformat = $feedback['format'];
+            $options->$feedbackname = file_save_draft_area_files($feedback['itemid'], $context->id, 'qtype_calculatedmulti', $feedbackname, $question->id, self::$fileoptions, trim($feedback['text']));
+        }
+
         if ($update) {
-            if (!$DB->update_record("question_calculated_options", $options)) {
-                $result->error = "Could not update calculated question options! (id=$options->id)";
-                return $result;
-            }
+            $DB->update_record("question_calculated_options", $options);
         } else {
-            if (!$DB->insert_record("question_calculated_options", $options)) {
-                $result->error = "Could not insert calculated question options!";
-                return $result;
-            }
+            $DB->insert_record("question_calculated_options", $options);
         }
 
         // Get old versions of the objects
@@ -71,8 +84,9 @@ function save_question_options($question) {
         }
 
         // Save the units.
-        $virtualqtype = $this->get_virtual_qtype( $question);
-       // $result = $virtualqtype->save_numerical_units($question);
+        $virtualqtype = $this->get_virtual_qtype($question);
+        // TODO: What is this?
+        // $result = $virtualqtype->save_numerical_units($question);
         if (isset($result->error)) {
             return $result;
         } else {
@@ -80,21 +94,25 @@ function save_question_options($question) {
         }
         // Insert all the new answers
         if (isset($question->answer) && !isset($question->answers)) {
-            $question->answers=$question->answer;
+            $question->answers = $question->answer;
         }
         foreach ($question->answers as $key => $dataanswer) {
-            if (  trim($dataanswer) != '' ) {
+            if ( trim($dataanswer) != '' ) {
                 $answer = new stdClass;
                 $answer->question = $question->id;
                 $answer->answer = trim($dataanswer);
                 $answer->fraction = $question->fraction[$key];
-                $answer->feedback = trim($question->feedback[$key]);
+                $answer->feedback = trim($question->feedback[$key]['text']);
+                $answer->feedbackformat = $question->feedback[$key]['format'];
 
                 if ($oldanswer = array_shift($oldanswers)) {  // Existing answer, so reuse it
                     $answer->id = $oldanswer->id;
+                    $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, $answer->feedback);
                     $DB->update_record("question_answers", $answer);
                 } else { // This is a completely new answer
                     $answer->id = $DB->insert_record("question_answers", $answer);
+                    $feedbacktext = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, $answer->feedback);
+                    $DB->set_field('question_answers', 'feedback', $feedbacktext, array('id'=>$answer->id));
                 }
 
                 // Set up the options object
@@ -129,15 +147,15 @@ function save_question_options($question) {
                 $DB->delete_records('question_calculated', array('id' => $oo->id));
             }
         }
-      //  $result = $QTYPES['numerical']->save_numerical_options($question);
-      //  if (isset($result->error)) {
-      //      return $result;
-      //  }
+        //  $result = $QTYPES['numerical']->save_numerical_options($question);
+        //  if (isset($result->error)) {
+        //      return $result;
+        //  }
 
 
         if( isset($question->import_process)&&$question->import_process){
             $this->import_datasets($question);
-         }
+        }
         // Report any problems.
         if (!empty($result->notice)) {
             return $result;
@@ -148,30 +166,25 @@ function save_question_options($question) {
     function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
         // Find out how many datasets are available
         global $CFG, $DB, $QTYPES, $OUTPUT ;
-        if(!$maxnumber = (int)$DB->get_field_sql(
-                            "SELECT MIN(a.itemcount)
-                            FROM {question_dataset_definitions} a,
-                                 {question_datasets} b
-                            WHERE b.question = ?
-                            AND   a.id = b.datasetdefinition", array($question->id))) {
+        $maxnumber = (int)$DB->get_field_sql(
+            "SELECT MIN(a.itemcount)
+               FROM {question_dataset_definitions} a, {question_datasets} b
+              WHERE b.question = ? AND a.id = b.datasetdefinition", array($question->id));
+        if (!$maxnumber) {
             print_error('cannotgetdsforquestion', 'question', '', $question->id);
         }
-                    $sql = "SELECT i.*
-                    FROM {question_datasets} d,
-                         {question_dataset_definitions} i
-                    WHERE d.question = ?
-                    AND   d.datasetdefinition = i.id  
-                    AND   i.category != 0 
-                   ";
+        $sql = "SELECT i.*
+                  FROM {question_datasets} d, {question_dataset_definitions} i
+                 WHERE d.question = ? AND d.datasetdefinition = i.id AND i.category != 0";
         if (!$question->options->synchronize || !$records = $DB->get_records_sql($sql, array($question->id))) {
-            $synchronize_calculated  =  false ; 
-        }else {
-           // i.e records is true so test coherence
-           $coherence = true ;
-                $a = new stdClass ;
-                $a->qid = $question->id ;
-                $a->qcat = $question->category ;
-           foreach($records as $def ){
+            $synchronize_calculated  =  false ;
+        } else {
+            // i.e records is true so test coherence
+            $coherence = true ;
+            $a = new stdClass ;
+            $a->qid = $question->id ;
+            $a->qcat = $question->category ;
+            foreach($records as $def ){
                 if ($def->category != $question->category){
                     $a->name = $def->name;
                     $a->sharedcat = $def->category ;
@@ -180,11 +193,11 @@ function create_session_and_responses(&$question, &$state, $cmoptions, $attempt)
                 }
             }
             if(!$coherence){
-                         echo $OUTPUT->notification(get_string('nocoherencequestionsdatyasetcategory','qtype_calculated',$a));
-          } 
-            
-            $synchronize_calculated  = true ; 
-        }    
+                echo $OUTPUT->notification(get_string('nocoherencequestionsdatyasetcategory','qtype_calculated',$a));
+            }
+
+            $synchronize_calculated  = true ;
+        }
 
         // Choose a random dataset
         // maxnumber sould not be breater than 100
@@ -194,41 +207,40 @@ function create_session_and_responses(&$question, &$state, $cmoptions, $attempt)
         if ( $synchronize_calculated === false ) {
             $state->options->datasetitem = rand(1, $maxnumber);
         }else{
-            $state->options->datasetitem = intval( $maxnumber * substr($attempt->timestart,-2) /100 ) ;            
+            $state->options->datasetitem = intval( $maxnumber * substr($attempt->timestart,-2) /100 ) ;
             if ($state->options->datasetitem < 1) {
                 $state->options->datasetitem =1 ;
             } else if ($state->options->datasetitem > $maxnumber){
                 $state->options->datasetitem = $maxnumber ;
             }
-           
-        };  
+
+        };
         $state->options->dataset =
-         $this->pick_question_dataset($question,$state->options->datasetitem);
-                    // create an array of answerids ??? why so complicated ???
-            $answerids = array_values(array_map(create_function('$val',
-             'return $val->id;'), $question->options->answers));
-            // Shuffle the answers if required
-            if (!empty($cmoptions->shuffleanswers) and !empty($question->options->shuffleanswers)) {
-               $answerids = swapshuffle($answerids);
-            }
-            $state->options->order = $answerids;
-            // Create empty responses
-            if ($question->options->single) {
-                $state->responses = array('' => '');
-            } else {
-                $state->responses = array();
-            }
-            return true;
-        
+            $this->pick_question_dataset($question,$state->options->datasetitem);
+        // create an array of answerids ??? why so complicated ???
+        $answerids = array_values(array_map(create_function('$val',
+            'return $val->id;'), $question->options->answers));
+        // Shuffle the answers if required
+        if (!empty($cmoptions->shuffleanswers) and !empty($question->options->shuffleanswers)) {
+            $answerids = swapshuffle($answerids);
+        }
+        $state->options->order = $answerids;
+        // Create empty responses
+        if ($question->options->single) {
+            $state->responses = array('' => '');
+        } else {
+            $state->responses = array();
+        }
+        return true;
     }
-    
+
     function save_session_and_responses(&$question, &$state) {
         global $DB;
-        $responses = 'dataset'.$state->options->datasetitem.'-' ;       
+        $responses = 'dataset'.$state->options->datasetitem.'-' ;
         $responses .= implode(',', $state->options->order) . ':';
         $responses .= implode(',', $state->responses);
-         
-        // Set the legacy answer field        
+
+        // Set the legacy answer field
         if (!$DB->set_field('question_states', 'answer', $responses, array('id'=> $state->id))) {
             return false;
         }
@@ -241,7 +253,7 @@ function create_runtime_question($question, $form) {
         foreach ($form->answers as $key => $answer) {
             $a->answer              = trim($form->answer[$key]);
             $a->fraction              = $form->fraction[$key];//new
-           $a->tolerance           = $form->tolerance[$key];
+            $a->tolerance           = $form->tolerance[$key];
             $a->tolerancetype       = $form->tolerancetype[$key];
             $a->correctanswerlength = $form->correctanswerlength[$key];
             $a->correctanswerformat = $form->correctanswerformat[$key];
@@ -251,57 +263,53 @@ function create_runtime_question($question, $form) {
         return $question;
     }
 
-
-
-
-
     function convert_answers (&$question, &$state){
-            foreach ($question->options->answers as $key => $answer) {
-                $answer->answer = $this->substitute_variables($answer->answer, $state->options->dataset);
-                //evaluate the equations i.e {=5+4)
-                $qtext = "";
-                $qtextremaining = $answer->answer ;
-             //   while  (preg_match('~\{(=)|%[[:digit]]\.=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
-                while  (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
-
-                    $qtextsplits = explode($regs1[0], $qtextremaining, 2);
-                    $qtext =$qtext.$qtextsplits[0];
-                    $qtextremaining = $qtextsplits[1];
-                    if (empty($regs1[1])) {
-                            $str = '';
-                        } else {
-                            if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
-                                $str=$formulaerrors ;
-                            }else {
-                                eval('$str = '.$regs1[1].';');
-                       $texteval= qtype_calculated_calculate_answer(
-                     $str, $state->options->dataset, $answer->tolerance,
-                     $answer->tolerancetype, $answer->correctanswerlength,
-                        $answer->correctanswerformat, '');
+        foreach ($question->options->answers as $key => $answer) {
+            $answer->answer = $this->substitute_variables($answer->answer, $state->options->dataset);
+            //evaluate the equations i.e {=5+4)
+            $qtext = "";
+            $qtextremaining = $answer->answer ;
+            //   while  (preg_match('~\{(=)|%[[:digit]]\.=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
+            while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
+
+                $qtextsplits = explode($regs1[0], $qtextremaining, 2);
+                $qtext = $qtext.$qtextsplits[0];
+                $qtextremaining = $qtextsplits[1];
+                if (empty($regs1[1])) {
+                    $str = '';
+                } else {
+                    if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
+                        $str=$formulaerrors ;
+                    }else {
+                        eval('$str = '.$regs1[1].';');
+                        $texteval= qtype_calculated_calculate_answer(
+                            $str, $state->options->dataset, $answer->tolerance,
+                            $answer->tolerancetype, $answer->correctanswerlength,
+                            $answer->correctanswerformat, '');
                         $str = $texteval->answer;
-                            }
-                        }
-                        $qtext = $qtext.$str ;
+                    }
                 }
-                $answer->answer = $qtext.$qtextremaining ; ;
+                $qtext = $qtext.$str ;
             }
+            $answer->answer = $qtext.$qtextremaining ; ;
         }
+    }
 
-    function get_default_numerical_unit($question,$virtualqtype){
-                $unit = '';
-            return $unit ;        
-    }    
+    function get_default_numerical_unit($question, $virtualqtype){
+        $unit = '';
+        return $unit ;
+    }
     function grade_responses(&$question, &$state, $cmoptions) {
         // Forward the grading to the virtual qtype
         // We modify the question to look like a multichoice question
-        // for grading nothing to do 
+        // for grading nothing to do
 /*        $numericalquestion = fullclone($question);
        foreach ($numericalquestion->options->answers as $key => $answer) {
             $answer = $numericalquestion->options->answers[$key]->answer; // for PHP 4.x
           $numericalquestion->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer,
              $state->options->dataset);
-       }*/
-         $virtualqtype = $this->get_virtual_qtype( $question);
+}*/
+        $virtualqtype = $this->get_virtual_qtype( $question);
         return $virtualqtype->grade_responses($question, $state, $cmoptions) ;
     }
 
@@ -319,7 +327,7 @@ function get_actual_response(&$question, &$state) {
         $this->convert_answers ($numericalquestion, $state);
         $this->convert_questiontext ($numericalquestion, $state);
      /*   $numericalquestion->questiontext = $this->substitute_variables_and_eval(
-                                  $numericalquestion->questiontext, $state->options->dataset);*/
+     $numericalquestion->questiontext, $state->options->dataset);*/
         $responses = $virtualqtype->get_all_responses($numericalquestion, $state);
         $response = reset($responses->responses);
         $correct = $response->answer.' : ';
@@ -335,8 +343,8 @@ function get_actual_response(&$question, &$state) {
 
     function create_virtual_qtype() {
         global $CFG;
-            require_once("$CFG->dirroot/question/type/multichoice/questiontype.php");
-            return new question_multichoice_qtype();
+        require_once("$CFG->dirroot/question/type/multichoice/questiontype.php");
+        return new question_multichoice_qtype();
     }
 
 
@@ -353,7 +361,7 @@ function comment_header($question) {
             } else {
                 $strheader .= $delimiter.$answer->answer;
             }
-                $delimiter = '
'; + $delimiter = '
'; } return $strheader; } @@ -369,7 +377,7 @@ function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$ $unit = $unit->unit; } else { $unit = ''; - }*/ + }*/ $answers = fullclone($answers); $strmin = get_string('min', 'quiz'); @@ -377,29 +385,29 @@ function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$ $errors = ''; $delimiter = ': '; foreach ($answers as $key => $answer) { - $answer->answer = $this->substitute_variables($answer->answer, $data); - //evaluate the equations i.e {=5+4) - $qtext = ""; - $qtextremaining = $answer->answer ; - while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { - $qtextsplits = explode($regs1[0], $qtextremaining, 2); - $qtext =$qtext.$qtextsplits[0]; - $qtextremaining = $qtextsplits[1]; - if (empty($regs1[1])) { - $str = ''; - } else { - if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){ - $str=$formulaerrors ; - }else { - eval('$str = '.$regs1[1].';'); - } - } - $qtext = $qtext.$str ; + $answer->answer = $this->substitute_variables($answer->answer, $data); + //evaluate the equations i.e {=5+4) + $qtext = ""; + $qtextremaining = $answer->answer ; + while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { + $qtextsplits = explode($regs1[0], $qtextremaining, 2); + $qtext =$qtext.$qtextsplits[0]; + $qtextremaining = $qtextsplits[1]; + if (empty($regs1[1])) { + $str = ''; + } else { + if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){ + $str=$formulaerrors ; + }else { + eval('$str = '.$regs1[1].';'); + } } - $answer->answer = $qtext.$qtextremaining ; ; - $comment->stranswers[$key]= $answer->answer ; - - + $qtext = $qtext.$str ; + } + $answer->answer = $qtext.$qtextremaining; + $comment->stranswers[$key] = $answer->answer; + + /* $formula = $this->substitute_variables($answer->answer,$data); $formattedanswer = qtype_calculated_calculate_answer( $answer->answer, $data, $answer->tolerance, @@ -411,7 +419,7 @@ function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$ }else { eval('$answer->answer = '.$formula.';') ; $virtualqtype->get_tolerance_interval($answer); - } + } if ($answer->min === '') { // This should mean that something is wrong $comment->stranswers[$key] = " $formattedanswer->answer".'

'; @@ -432,15 +440,11 @@ function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$ $comment->stranswers[$key] .=get_string('trueanswerinsidelimits','qtype_calculated',$correcttrue);//' True answer :'.$calculated->trueanswer.' inside limits'; } $comment->stranswers[$key] .=''; - }*/ + }*/ } return fullclone($comment); } - - - - function get_correct_responses1(&$question, &$state) { $virtualqtype = $this->get_virtual_qtype( $question); /* if ($question->options->multichoice != 1 ) { @@ -464,71 +468,148 @@ function get_correct_responses1(&$question, &$state) { return $correct; } } - }else{**/ - return $virtualqtype->get_correct_responses($question, $state) ; - // } + }else{**/ + return $virtualqtype->get_correct_responses($question, $state) ; + // } return null; } function get_virtual_qtype() { global $QTYPES; - // if ( isset($question->options->multichoice) && $question->options->multichoice == '1'){ - $this->virtualqtype =& $QTYPES['multichoice']; - // }else { - // $this->virtualqtype =& $QTYPES['numerical']; - // } + // if ( isset($question->options->multichoice) && $question->options->multichoice == '1'){ + $this->virtualqtype =& $QTYPES['multichoice']; + // }else { + // $this->virtualqtype =& $QTYPES['numerical']; + // } return $this->virtualqtype; } - /** - * Runs all the code required to set up and save an essay question for testing purposes. - * Alternate DB table prefix may be used to facilitate data deletion. - */ - function generate_test($name, $courseid = null) { - global $DB; - list($form, $question) = parent::generate_test($name, $courseid); - $form->feedback = 1; - $form->multiplier = array(1, 1); - $form->shuffleanswers = 1; - $form->noanswers = 1; - $form->qtype ='calculatedmulti'; - $question->qtype ='calculatedmulti'; - $form->answers = array('{a} + {b}'); - $form->fraction = array(1); - $form->tolerance = array(0.01); - $form->tolerancetype = array(1); - $form->correctanswerlength = array(2); - $form->correctanswerformat = array(1); - $form->questiontext = "What is {a} + {b}?"; - - if ($courseid) { - $course = $DB->get_record('course', array('id'=> $courseid)); - } - - $new_question = $this->save_question($question, $form, $course); - - $dataset_form = new stdClass(); - $dataset_form->nextpageparam["forceregeneration"]= 1; - $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0); - $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0); - $dataset_form->calclength = array(1 => 1, 2 => 1); - $dataset_form->number = array(1 => 5.4 , 2 => 4.9); - $dataset_form->itemid = array(1 => '' , 2 => ''); - $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform'); - $dataset_form->definition = array(1 => "1-0-a", - 2 => "1-0-b"); - $dataset_form->nextpageparam = array('forceregeneration' => false); - $dataset_form->addbutton = 1; - $dataset_form->selectadd = 1; - $dataset_form->courseid = $courseid; - $dataset_form->cmid = 0; - $dataset_form->id = $new_question->id; - $this->save_dataset_items($new_question, $dataset_form); - - return $new_question; - } + /** + * Runs all the code required to set up and save an essay question for testing purposes. + * Alternate DB table prefix may be used to facilitate data deletion. + */ + function generate_test($name, $courseid = null) { + global $DB; + list($form, $question) = parent::generate_test($name, $courseid); + $form->feedback = 1; + $form->multiplier = array(1, 1); + $form->shuffleanswers = 1; + $form->noanswers = 1; + $form->qtype ='calculatedmulti'; + $question->qtype ='calculatedmulti'; + $form->answers = array('{a} + {b}'); + $form->fraction = array(1); + $form->tolerance = array(0.01); + $form->tolerancetype = array(1); + $form->correctanswerlength = array(2); + $form->correctanswerformat = array(1); + $form->questiontext = "What is {a} + {b}?"; + + if ($courseid) { + $course = $DB->get_record('course', array('id'=> $courseid)); + } + + $new_question = $this->save_question($question, $form, $course); + + $dataset_form = new stdClass(); + $dataset_form->nextpageparam["forceregeneration"]= 1; + $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0); + $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0); + $dataset_form->calclength = array(1 => 1, 2 => 1); + $dataset_form->number = array(1 => 5.4 , 2 => 4.9); + $dataset_form->itemid = array(1 => '' , 2 => ''); + $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform'); + $dataset_form->definition = array(1 => "1-0-a", + 2 => "1-0-b"); + $dataset_form->nextpageparam = array('forceregeneration' => false); + $dataset_form->addbutton = 1; + $dataset_form->selectadd = 1; + $dataset_form->courseid = $courseid; + $dataset_form->cmid = 0; + $dataset_form->id = $new_question->id; + $this->save_dataset_items($new_question, $dataset_form); + + return $new_question; + } + + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + // move files belonging to question component + parent::move_files($question, $newcategory); + + // move files belonging to qtype_calculatedmulti + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'answerfeedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + + $component = 'qtype_calculatedmulti'; + foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $filearea) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + } + + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + $itemid = reset($args); + + if (empty($question->maxgrade)) { + $question->maxgrade = $question->defaultgrade; + } + + if (in_array($filearea, array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) { + $result = $options->feedback && ($itemid == $question->id); + if (!$result) { + return false; + } + if ($state->raw_grade >= $question->maxgrade/1.01) { + $feedbacktype = 'correctfeedback'; + } else if ($state->raw_grade > 0) { + $feedbacktype = 'partiallycorrectfeedback'; + } else { + $feedbacktype = 'incorrectfeedback'; + } + if ($feedbacktype != $filearea) { + return false; + } + return true; + } else if ($component == 'question' && $filearea == 'answerfeedback') { + return $options->feedback && (array_key_exists($itemid, $question->options->answers)); + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } } + //// END OF CLASS //// ////////////////////////////////////////////////////////////////////////// diff --git a/question/type/calculatedsimple/edit_calculatedsimple_form.php b/question/type/calculatedsimple/edit_calculatedsimple_form.php index f63b72cdc9de5..8c84517e4b7a3 100644 --- a/question/type/calculatedsimple/edit_calculatedsimple_form.php +++ b/question/type/calculatedsimple/edit_calculatedsimple_form.php @@ -1,4 +1,20 @@ . + /** * Defines the editing form for the calculated simplequestion type. * @@ -8,21 +24,17 @@ * @package questionbank * @subpackage questiontypes */ - -/** - * calculatedsimple editing form definition. - */ class question_edit_calculatedsimple_form extends question_edit_form { /** * Handle to the question type for this question. * * @var question_calculatedsimple_qtype */ - var $qtypeobj; + public $qtypeobj; - var $wildcarddisplay ; + public $wildcarddisplay ; - var $questiondisplay ; + public $questiondisplay ; public $datasetdefs; @@ -46,8 +58,6 @@ class question_edit_calculatedsimple_form extends question_edit_form { public $formdata = array(); - - function question_edit_calculatedsimple_form(&$submiturl, &$question, &$category, &$contexts, $formeditable = true){ global $QTYPES, $SESSION, $CFG, $DB; $this->regenerate = true; @@ -60,17 +70,17 @@ function question_edit_calculatedsimple_form(&$submiturl, &$question, &$category //so this should be removed from here // get priority to paramdatasets - if ( "1" == optional_param('reload','', PARAM_INT )) { - $this->reload = true ; + if ("1" == optional_param('reload','', PARAM_INT )) { + $this->reload = true; }else { - $this->reload = false ; + $this->reload = false; } - if(!$this->reload ){ // use database data as this is first pass + if (!$this->reload) { // use database data as this is first pass // question->id == 0 so no stored datasets // else get datasets // echo "

question

";print_r($question);echo "

"; - if ( !empty($question->id)) { - + if (!empty($question->id)) { + /* if (empty($question->options)) { $this->get_question_options($question); }*/ @@ -127,7 +137,7 @@ function question_edit_calculatedsimple_form(&$submiturl, &$question, &$category $mandatorydatasets = array(); // should not test on adding a new answer // should test if there are already olddatasets or if the 'analyzequestion' submit button has been clicked - if ('' != optional_param('datasetdef', '', PARAM_RAW) || '' != optional_param('analyzequestion', '', PARAM_RAW)){ + if ('' != optional_param('datasetdef', '', PARAM_RAW) || '' != optional_param('analyzequestion', '', PARAM_RAW)){ if ( $dummyform->answer = optional_param('answer', '', PARAM_NOTAGS)) { // there is always at least one answer... $fraction = optional_param('fraction', '', PARAM_NUMBER); @@ -297,12 +307,12 @@ function definition_inner(&$mform) { $addfieldsname='updatequestion value'; $addstring=get_string("updatecategory", "qtype_calculated"); $mform->registerNoSubmitButton($addfieldsname); -//put a submit button to stop supplementary answers on update answers parameters -// $mform->insertElementBefore( $mform->createElement('submit', $addfieldsname, $addstring),'listcategory'); + // put a submit button to stop supplementary answers on update answers parameters + // $mform->insertElementBefore($mform->createElement('submit', $addfieldsname, $addstring), 'listcategory'); $creategrades = get_grade_options(); $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'), - $creategrades->gradeoptions, 1, 1); + $creategrades->gradeoptions, 1, 1); $QTYPES['numerical']->add_units_options($mform,$this); @@ -327,7 +337,7 @@ function definition_inner(&$mform) { $this->noofitems = 0; } if(!empty($this->datasetdefs)){//So there are some datadefs - // we put them on the page + // we put them on the page $key = 0; $mform->addElement('header', 'additemhdr', get_string('wildcardparam', 'qtype_calculatedsimple')); $idx = 1; @@ -347,180 +357,183 @@ function definition_inner(&$mform) { } //this should be done before the elements are created and stored as $this->formdata ; //fill out all data sets and also the fields for the next item to add. - /*Here we do already the values error analysis so that - * we could force all wild cards values display if there is an error in values. - * as using a , in a number */ - $this->numbererrors = array(); + /*Here we do already the values error analysis so that + * we could force all wild cards values display if there is an error in values. + * as using a , in a number */ + $this->numbererrors = array(); if(!empty($this->datasetdefs)){ - $j = $this->noofitems * count($this->datasetdefs); - for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--){ - $data = array(); - $numbererrors = array() ; - $comment = new stdClass; - $comment->stranswers = array(); - $comment->outsidelimit = false ; - $comment->answers = array(); - - foreach ($this->datasetdefs as $defid => $datasetdef){ - if (isset($datasetdef->items[$itemnumber])){ - $this->formdata["definition[$j]"] = $defid; - $this->formdata["itemid[$j]"] = $datasetdef->items[$itemnumber]->id; - $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value; - $this->formdata["number[$j]"] = $number = $datasetdef->items[$itemnumber]->value; - if(! is_numeric($number)){ - $a = new stdClass; - $a->name = '{'.$datasetdef->name.'}' ; - $a->value = $datasetdef->items[$itemnumber]->value ; - if (stristr($number,',')){ + $j = $this->noofitems * count($this->datasetdefs); + for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--){ + $data = array(); + $numbererrors = array() ; + $comment = new stdClass; + $comment->stranswers = array(); + $comment->outsidelimit = false ; + $comment->answers = array(); + + foreach ($this->datasetdefs as $defid => $datasetdef){ + if (isset($datasetdef->items[$itemnumber])){ + $this->formdata["definition[$j]"] = $defid; + $this->formdata["itemid[$j]"] = $datasetdef->items[$itemnumber]->id; + $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value; + $this->formdata["number[$j]"] = $number = $datasetdef->items[$itemnumber]->value; + if(! is_numeric($number)){ + $a = new stdClass; + $a->name = '{'.$datasetdef->name.'}' ; + $a->value = $datasetdef->items[$itemnumber]->value ; + if (stristr($number,',')){ $this->numbererrors["number[$j]"]=get_string('nocommaallowed', 'qtype_calculated'); - $numbererrors .= $this->numbererrors['number['.$j.']']."
"; + $numbererrors .= $this->numbererrors['number['.$j.']']."
"; - }else { + }else { $this->numbererrors["number[$j]"]= get_string('notvalidnumber','qtype_calculated',$a); $numbererrors .= $this->numbererrors['number['.$j.']']."
"; //$comment->outsidelimit = false ; - } - }else if( stristr($number,'x')){ // hexa will pass the test - $a = new stdClass; - $a->name = '{'.$datasetdef->name.'}' ; - $a->value = $datasetdef->items[$itemnumber]->value ; - $this->numbererrors['number['.$j.']']= get_string('hexanotallowed','qtype_calculated',$a); - $numbererrors .= $this->numbererrors['number['.$j.']']."
"; - } else if( is_nan($number)){ - $a = new stdClass; - $a->name = '{'.$datasetdef->name.'}' ; - $a->value = $datasetdef->items[$itemnumber]->value ; - $this->numbererrors["number[$j]"]= get_string('notvalidnumber','qtype_calculated',$a); - $numbererrors .= $this->numbererrors['number['.$j.']']."
"; - // $val = 1.0 ; - } + } + }else if( stristr($number,'x')){ // hexa will pass the test + $a = new stdClass; + $a->name = '{'.$datasetdef->name.'}' ; + $a->value = $datasetdef->items[$itemnumber]->value ; + $this->numbererrors['number['.$j.']']= get_string('hexanotallowed','qtype_calculated',$a); + $numbererrors .= $this->numbererrors['number['.$j.']']."
"; + } else if( is_nan($number)){ + $a = new stdClass; + $a->name = '{'.$datasetdef->name.'}' ; + $a->value = $datasetdef->items[$itemnumber]->value ; + $this->numbererrors["number[$j]"]= get_string('notvalidnumber','qtype_calculated',$a); + $numbererrors .= $this->numbererrors['number['.$j.']']."
"; + // $val = 1.0 ; } - $j--; } - if($this->noofitems != 0 ) { - if (empty($numbererrors )){ - if(!isset($this->question->id)) $this->question->id = 0 ; - $comment = $this->qtypeobj->comment_on_datasetitems($this->qtypeobj,$this->question->id,$this->question->questiontext,$this->nonemptyanswer, $data, $itemnumber);//$this-> - if ($comment->outsidelimit) { - $this->outsidelimit=$comment->outsidelimit ; - } - $totalcomment=''; - - foreach ($this->nonemptyanswer as $key => $answer) { - $totalcomment .= $comment->stranswers[$key].'
'; - } - - $this->formdata['answercomment['.$itemnumber.']'] = $totalcomment ; - } + $j--; + } + if($this->noofitems != 0 ) { + if (empty($numbererrors)) { + if (!isset($this->question->id)) { + $this->question->id = 0 ; + } + $this->question->questiontext = !empty($this->question->questiontext)?$this->question->questiontext:''; + $comment = $this->qtypeobj->comment_on_datasetitems($this->qtypeobj, $this->question->id, $this->question->questiontext, $this->nonemptyanswer, $data, $itemnumber); + if ($comment->outsidelimit) { + $this->outsidelimit=$comment->outsidelimit ; } + $totalcomment=''; + + foreach ($this->nonemptyanswer as $key => $answer) { + $totalcomment .= $comment->stranswers[$key].'
'; + } + + $this->formdata['answercomment['.$itemnumber.']'] = $totalcomment ; + } } - $this->formdata['selectdelete'] = '1'; - $this->formdata['selectadd'] = '1'; - $j = $this->noofitems * count($this->datasetdefs)+1; - $data = array(); // data for comment_on_datasetitems later + } + $this->formdata['selectdelete'] = '1'; + $this->formdata['selectadd'] = '1'; + $j = $this->noofitems * count($this->datasetdefs)+1; + $data = array(); // data for comment_on_datasetitems later $idx =1 ; foreach ($this->datasetdefs as $defid => $datasetdef){ $this->formdata["datasetdef[$idx]"] = $defid; $idx++; } - $this->formdata = $this->qtypeobj->custom_generator_set_data($this->datasetdefs, $this->formdata); + $this->formdata = $this->qtypeobj->custom_generator_set_data($this->datasetdefs, $this->formdata); + } + + + $addoptions = Array(); + $addoptions['1']='1'; + for ($i=10; $i<=100 ; $i+=10){ + $addoptions["$i"]="$i"; + } + $showoptions = Array(); + $showoptions['1']='1'; + $showoptions['2']='2'; + $showoptions['5']='5'; + for ($i=10; $i<=100 ; $i+=10){ + $showoptions["$i"]="$i"; + } + $mform->closeHeaderBefore('additemhdr'); + $addgrp = array(); + $addgrp[] =& $mform->createElement('submit', 'addbutton', get_string('generatenewitemsset', 'qtype_calculatedsimple')); + $addgrp[] =& $mform->createElement('select', "selectadd", '', $addoptions); + $addgrp[] = & $mform->createElement('static',"stat",'',get_string('newsetwildcardvalues', 'qtype_calculatedsimple')); + $mform->addGroup($addgrp, 'addgrp', '', ' ', false); + $mform->registerNoSubmitButton('addbutton'); + $mform->closeHeaderBefore('addgrp'); + $addgrp1 = array(); + $addgrp1[] =& $mform->createElement('submit', 'showbutton', get_string('showitems', 'qtype_calculatedsimple')); + $addgrp1[] =& $mform->createElement('select', "selectshow",'' , $showoptions); + $addgrp1[] = & $mform->createElement('static',"stat",'',get_string('setwildcardvalues', 'qtype_calculatedsimple')); + $mform->addGroup($addgrp1, 'addgrp1', '', ' ', false); + $mform->registerNoSubmitButton('showbutton'); + $mform->closeHeaderBefore('addgrp1'); + $mform->addElement('static', "divideradd", '', ''); + if ($this->noofitems == 0) { + $mform->addElement('static','warningnoitems','',''.get_string('youmustaddatleastonevalue', 'qtype_calculatedsimple').''); + $mform->closeHeaderBefore('warningnoitems'); + }else { + $mform->addElement('header', 'additemhdr1', get_string('wildcardvalues', 'qtype_calculatedsimple')); + $mform->closeHeaderBefore('additemhdr1'); + // $mform->addElement('header', '', get_string('itemno', 'qtype_calculated', "")); + if( !empty($this->numbererrors) || $this->outsidelimit) { + $mform->addElement('static', "alert", '', ''.get_string('useadvance', 'qtype_calculatedsimple').''); } + $mform->addElement('submit', 'updatedatasets', get_string('updatewildcardvalues', 'qtype_calculatedsimple')); + $mform->registerNoSubmitButton('updatedatasets'); + $mform->setAdvanced("updatedatasets",true); - $addoptions = Array(); - $addoptions['1']='1'; - for ($i=10; $i<=100 ; $i+=10){ - $addoptions["$i"]="$i"; - } - $showoptions = Array(); - $showoptions['1']='1'; - $showoptions['2']='2'; - $showoptions['5']='5'; - for ($i=10; $i<=100 ; $i+=10){ - $showoptions["$i"]="$i"; - } - $mform->closeHeaderBefore('additemhdr'); - $addgrp = array(); - $addgrp[] =& $mform->createElement('submit', 'addbutton', get_string('generatenewitemsset', 'qtype_calculatedsimple')); - $addgrp[] =& $mform->createElement('select', "selectadd", '', $addoptions); - $addgrp[] = & $mform->createElement('static',"stat",'',get_string('newsetwildcardvalues', 'qtype_calculatedsimple')); - $mform->addGroup($addgrp, 'addgrp', '', ' ', false); - $mform->registerNoSubmitButton('addbutton'); - $mform->closeHeaderBefore('addgrp'); - $addgrp1 = array(); - $addgrp1[] =& $mform->createElement('submit', 'showbutton', get_string('showitems', 'qtype_calculatedsimple')); - $addgrp1[] =& $mform->createElement('select', "selectshow",'' , $showoptions); - $addgrp1[] = & $mform->createElement('static',"stat",'',get_string('setwildcardvalues', 'qtype_calculatedsimple')); - $mform->addGroup($addgrp1, 'addgrp1', '', ' ', false); - $mform->registerNoSubmitButton('showbutton'); - $mform->closeHeaderBefore('addgrp1'); - $mform->addElement('static', "divideradd", '', ''); - if ($this->noofitems == 0) { - $mform->addElement('static','warningnoitems','',''.get_string('youmustaddatleastonevalue', 'qtype_calculatedsimple').''); - $mform->closeHeaderBefore('warningnoitems'); - }else { - $mform->addElement('header', 'additemhdr1', get_string('wildcardvalues', 'qtype_calculatedsimple')); - $mform->closeHeaderBefore('additemhdr1'); - // $mform->addElement('header', '', get_string('itemno', 'qtype_calculated', "")); - if( !empty($this->numbererrors) || $this->outsidelimit) { - $mform->addElement('static', "alert", '', ''.get_string('useadvance', 'qtype_calculatedsimple').''); - } + //------------------------------------------------------------------------------------------------------------------------------ + $j = $this->noofitems * count($this->datasetdefs); + $k = 1 ; + if ("" != optional_param('selectshow')){ + $k = optional_param('selectshow', '', PARAM_INT); + } - $mform->addElement('submit', 'updatedatasets', get_string('updatewildcardvalues', 'qtype_calculatedsimple')); - $mform->registerNoSubmitButton('updatedatasets'); - $mform->setAdvanced("updatedatasets",true); + for ($i = $this->noofitems; $i >= 1 ; $i--){ + foreach ($this->datasetdefs as $defkey => $datasetdef){ + if($k > 0 || $this->outsidelimit || !empty($this->numbererrors ) ){ + $mform->addElement('text',"number[$j]" , get_string('wildcard', 'qtype_calculatedsimple', $datasetdef->name)); + $mform->setAdvanced("number[$j]",true); + if(!empty($this->numbererrors['number['.$j.']']) ){ + $mform->addElement('static', "numbercomment[$j]",'',''.$this->numbererrors['number['.$j.']'].''); + $mform->setAdvanced("numbercomment[$j]",true); + } + }else { + $mform->addElement('hidden',"number[$j]" , get_string('wildcard', 'qtype_calculatedsimple', $datasetdef->name)); + } + $mform->setType("number[$j]", PARAM_NUMBER); -//------------------------------------------------------------------------------------------------------------------------------ - $j = $this->noofitems * count($this->datasetdefs); - $k = 1 ; - if ("" != optional_param('selectshow')){ - $k = optional_param('selectshow', '', PARAM_INT); - } + $mform->addElement('hidden', "itemid[$j]"); + $mform->setType("itemid[$j]", PARAM_INT); - for ($i = $this->noofitems; $i >= 1 ; $i--){ - foreach ($this->datasetdefs as $defkey => $datasetdef){ - if($k > 0 || $this->outsidelimit || !empty($this->numbererrors ) ){ - $mform->addElement('text',"number[$j]" , get_string('wildcard', 'qtype_calculatedsimple', $datasetdef->name)); - $mform->setAdvanced("number[$j]",true); - if(!empty($this->numbererrors['number['.$j.']']) ){ - $mform->addElement('static', "numbercomment[$j]",'',''.$this->numbererrors['number['.$j.']'].''); - $mform->setAdvanced("numbercomment[$j]",true); - } - }else { - $mform->addElement('hidden',"number[$j]" , get_string('wildcard', 'qtype_calculatedsimple', $datasetdef->name)); - } - $mform->setType("number[$j]", PARAM_NUMBER); - - $mform->addElement('hidden', "itemid[$j]"); - $mform->setType("itemid[$j]", PARAM_INT); - - $mform->addElement('hidden', "definition[$j]"); - $mform->setType("definition[$j]", PARAM_NOTAGS); + $mform->addElement('hidden', "definition[$j]"); + $mform->setType("definition[$j]", PARAM_NOTAGS); - $j--; - } - if (!empty( $strquestionlabel) && ($k > 0 || $this->outsidelimit || !empty($this->numbererrors ) ) ){ - // $repeated[] =& $mform->addElement('static', "answercomment[$i]", $strquestionlabel); - $mform->addElement('static', "answercomment[$i]", "".get_string('setno', 'qtype_calculatedsimple', $i)."  ".$strquestionlabel); + $j--; + } + if (!empty( $strquestionlabel) && ($k > 0 || $this->outsidelimit || !empty($this->numbererrors ) ) ){ + // $repeated[] =& $mform->addElement('static', "answercomment[$i]", $strquestionlabel); + $mform->addElement('static', "answercomment[$i]", "".get_string('setno', 'qtype_calculatedsimple', $i)."  ".$strquestionlabel); - } - if($k > 0 || $this->outsidelimit || !empty($this->numbererrors )){ - $mform->addElement('static', "divider1[$j]", '', '
'); + } + if($k > 0 || $this->outsidelimit || !empty($this->numbererrors )){ + $mform->addElement('static', "divider1[$j]", '', '
'); - } - $k-- ; + } + $k-- ; + } + } + // if ($this->outsidelimit){ + // $mform->addElement('static','outsidelimit','',''); + // } + }else { + $mform->addElement('static','warningnowildcards','',''.get_string('atleastonewildcard', 'qtype_calculatedsimple').''); + $mform->closeHeaderBefore('warningnowildcards'); } - } - // if ($this->outsidelimit){ - // $mform->addElement('static','outsidelimit','',''); - // } - }else { - $mform->addElement('static','warningnowildcards','',''.get_string('atleastonewildcard', 'qtype_calculatedsimple').''); - $mform->closeHeaderBefore('warningnowildcards'); - } -//------------------------------------------------------------------------------------------------------------------------------ + //------------------------------------------------------------------------------------------------------------------------------ //non standard name for button element needed so not using add_action_buttons //hidden elements @@ -536,50 +549,64 @@ function definition_inner(&$mform) { $mform->setDefault('cmid', 0); if (!empty($this->question->id)){ if ($this->question->formoptions->cansaveasnew){ - $mform->addElement('header', 'additemhdr', get_string('converttocalculated', 'qtype_calculatedsimple')); - $mform->closeHeaderBefore('additemhdr'); + $mform->addElement('header', 'additemhdr', get_string('converttocalculated', 'qtype_calculatedsimple')); + $mform->closeHeaderBefore('additemhdr'); $mform->addElement('checkbox', 'convert','' ,get_string('willconverttocalculated', 'qtype_calculatedsimple')); - $mform->setDefault('convert', 0); + $mform->setDefault('convert', 0); - } } + } } - function set_data($question) { + function data_preprocessing($question) { global $QTYPES; - - $answer = $this->answer; - $default_values = array(); - if (count($answer)) { - $key = 0; - foreach ($answer as $answer){ - $default_values['answer['.$key.']'] = $answer->answer; // is necessary ? to-do test it - $default_values['fraction['.$key.']'] = $answer->fraction; - $default_values['tolerance['.$key.']'] = $answer->tolerance; - $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype; - $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength; - $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat; - $key++; - } + $answer = $this->answer; + $default_values = array(); + if (count($answer)) { + $key = 0; + foreach ($answer as $answer){ + $default_values['answer['.$key.']'] = $answer->answer; // is necessary ? to-do test it + $default_values['fraction['.$key.']'] = $answer->fraction; + $default_values['tolerance['.$key.']'] = $answer->tolerance; + $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype; + $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength; + $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat; + + // prepare draft files + $draftid = file_get_submitted_draft_itemid('feedback['.$key.']'); + $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'question', // component + 'answerfeedback', // filarea + !empty($answer->id)?(int)$answer->id:null, // itemid + $this->fileoptions, // options + !empty($answer->feedback)?$answer->feedback:'' // text + ); + $default_values['feedback['.$key.']']['format'] = !empty($answer->feedbackformat)?$answer->feedbackformat:editors_get_preferred_format(); + $default_values['feedback['.$key.']']['itemid'] = $draftid; + + $key++; } - $default_values['synchronize'] = 0 ; - $QTYPES['numerical']->set_numerical_unit_data($question,$default_values); - /* if (isset($question->options)){ + } + $default_values['synchronize'] = 0 ; + $QTYPES['numerical']->set_numerical_unit_data($this, $question, $default_values); + /* if (isset($question->options)){ $default_values['unitgradingtype'] = $question->options->unitgradingtype ; $default_values['unitpenalty'] = $question->options->unitpenalty ; switch ($question->options->showunits){ case 'O' : - case '1' : + case '1' : $default_values['showunits0'] = $question->options->showunits ; $default_values['unitrole'] = 0 ; break; case '2' : - case '3' : + case '3' : $default_values['showunits1'] = $question->options->showunits ; $default_values['unitrole'] = 1 ; break; - } + } $default_values['unitsleft'] = $question->options->unitsleft ; $default_values['instructions'] = $question->options->instructions ; @@ -593,16 +620,16 @@ function set_data($question) { } } } - */ - $key = 0 ; + */ + $key = 0 ; $formdata = array(); $fromform = new stdClass(); //this should be done before the elements are created and stored as $this->formdata ; //fill out all data sets and also the fields for the next item to add. - /* if(!empty($this->datasetdefs)){ + /* if(!empty($this->datasetdefs)){ $j = $this->noofitems * count($this->datasetdefs); - for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--){ + for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--){ $data = array(); foreach ($this->datasetdefs as $defid => $datasetdef){ if (isset($datasetdef->items[$itemnumber])){ @@ -613,27 +640,27 @@ function set_data($question) { } $j--; } - // echo "

answers avant comment

";print_r($answer);echo"

"; - // echo "

data avant comment

";print_r($data);echo"

"; + // echo "

answers avant comment

";print_r($answer);echo"

"; + // echo "

data avant comment

";print_r($data);echo"

"; if($this->noofitems != 0 ) { if(!isset($question->id)) $question->id = 0 ; - $comment = $this->qtypeobj->comment_on_datasetitems($question->id,$this->nonemptyanswer, $data, $itemnumber);//$this-> - if ($comment->outsidelimit) { - $this->outsidelimit=$comment->outsidelimit ; - } - $totalcomment=''; - // echo "

comment

";print_r($comment);echo"

"; + $comment = $this->qtypeobj->comment_on_datasetitems($question->id,$this->nonemptyanswer, $data, $itemnumber);//$this-> + if ($comment->outsidelimit) { + $this->outsidelimit=$comment->outsidelimit ; + } + $totalcomment=''; + // echo "

comment

";print_r($comment);echo"

"; - foreach ($this->nonemptyanswer as $key => $answer) { - $totalcomment .= $comment->stranswers[$key].'
'; - } + foreach ($this->nonemptyanswer as $key => $answer) { + $totalcomment .= $comment->stranswers[$key].'
'; + } - $formdata['answercomment['.$itemnumber.']'] = $totalcomment ; - } + $formdata['answercomment['.$itemnumber.']'] = $totalcomment ; + } } - // $formdata['reload'] = '1'; - // $formdata['nextpageparam[forceregeneration]'] = $this->regenerate; + // $formdata['reload'] = '1'; + // $formdata['nextpageparam[forceregeneration]'] = $this->regenerate; $formdata['selectdelete'] = '1'; $formdata['selectadd'] = '1'; $j = $this->noofitems * count($this->datasetdefs)+1; @@ -644,10 +671,10 @@ function set_data($question) { $idx++; } $formdata = $this->qtypeobj->custom_generator_set_data($this->datasetdefs, $formdata); - }*/ + }*/ $question = (object)((array)$question + $default_values+$this->formdata ); - parent::set_data($question); + return $question; } function qtype() { @@ -659,8 +686,8 @@ function validation($data, $files) { $errors = parent::validation($data, $files); //verifying for errors in {=...} in question text; $qtext = ""; - $qtextremaining = $data['questiontext'] ; - $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']); + $qtextremaining = $data['questiontext']['text']; + $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']); foreach ($possibledatasets as $name => $value) { $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining); } @@ -679,7 +706,7 @@ function validation($data, $files) { $answers = $data['answer']; $answercount = 0; $maxgrade = false; - $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']); + $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']); $mandatorydatasets = array(); foreach ($answers as $key => $answer){ $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer); @@ -797,4 +824,3 @@ function validation($data, $files) { return $errors; } } - diff --git a/question/type/calculatedsimple/lib.php b/question/type/calculatedsimple/lib.php new file mode 100644 index 0000000000000..23a1770a58346 --- /dev/null +++ b/question/type/calculatedsimple/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package questionbank + * @subpackage questiontypes + * @author Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_calculatedsimple_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_calculatedsimple', $filearea, $args, $forcedownload); +} diff --git a/question/type/calculatedsimple/questiontype.php b/question/type/calculatedsimple/questiontype.php index 91bcd9a40bf6f..bb6845e8ebb9a 100644 --- a/question/type/calculatedsimple/questiontype.php +++ b/question/type/calculatedsimple/questiontype.php @@ -1,30 +1,41 @@ . + ///////////////// // CALCULATED /// ///////////////// /// QUESTION TYPE CLASS ////////////////// - - class question_calculatedsimple_qtype extends question_calculated_qtype { // Used by the function custom_generator_tools: - var $calcgenerateidhasbeenadded = false; + public $calcgenerateidhasbeenadded = false; public $virtualqtype = false; function name() { return 'calculatedsimple'; } - - - function save_question_options($question) { + global $CFG, $DB , $QTYPES; + $context = $question->context; //$options = $question->subtypeoptions; // Get old answers: - global $CFG, $DB , $QTYPES; if (isset($question->answer) && !isset($question->answers)) { $question->answers = $question->answer; @@ -52,18 +63,22 @@ function save_question_options($question) { $question->answers=$question->answer; } foreach ($question->answers as $key => $dataanswer) { - if ( trim($dataanswer) != '' ) { + if ( trim($dataanswer) != '' ) { $answer = new stdClass; $answer->question = $question->id; $answer->answer = trim($dataanswer); $answer->fraction = $question->fraction[$key]; - $answer->feedback = trim($question->feedback[$key]); + $answer->feedbackformat = $question->feedback[$key]['format']; if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it $answer->id = $oldanswer->id; + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, trim($question->feedback[$key]['text'])); $DB->update_record("question_answers", $answer); } else { // This is a completely new answer + $answer->feedback = ''; $answer->id = $DB->insert_record("question_answers", $answer); + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, trim($question->feedback[$key]['text'])); + $DB->set_field('question_answers', 'feedback', $answer->feedback, array('id'=>$answer->id)); } // Set up the options object @@ -99,82 +114,82 @@ function save_question_options($question) { } } - - if( isset($question->import_process)&&$question->import_process){ + if(isset($question->import_process)&&$question->import_process) { $this->import_datasets($question); - }else { + } else { //save datasets and datatitems from form i.e in question - // $datasetdefs = $this->get_dataset_definitions($question->id, array()); - $question->dataset = $question->datasetdef ; - // $this->save_dataset_definitions($question); - // Save datasets - $datasetdefinitions = $this->get_dataset_definitions($question->id, $question->dataset); - $tmpdatasets = array_flip($question->dataset); - $defids = array_keys($datasetdefinitions); - $datasetdefs = array(); - foreach ($defids as $defid) { - $datasetdef = &$datasetdefinitions[$defid]; - if (isset($datasetdef->id)) { - if (!isset($tmpdatasets[$defid])) { - // This dataset is not used any more, delete it - $DB->delete_records('question_datasets', array('question' => $question->id, 'datasetdefinition' => $datasetdef->id)); - // if ($datasetdef->category == 0) { // Question local dataset + // $datasetdefs = $this->get_dataset_definitions($question->id, array()); + $question->dataset = $question->datasetdef ; + // $this->save_dataset_definitions($question); + // Save datasets + $datasetdefinitions = $this->get_dataset_definitions($question->id, $question->dataset); + $tmpdatasets = array_flip($question->dataset); + $defids = array_keys($datasetdefinitions); + $datasetdefs = array(); + foreach ($defids as $defid) { + $datasetdef = &$datasetdefinitions[$defid]; + if (isset($datasetdef->id)) { + if (!isset($tmpdatasets[$defid])) { + // This dataset is not used any more, delete it + $DB->delete_records('question_datasets', array('question' => $question->id, 'datasetdefinition' => $datasetdef->id)); + // if ($datasetdef->category == 0) { // Question local dataset $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id)); $DB->delete_records('question_dataset_items', array('definition' => $datasetdef->id)); - // } - } - // This has already been saved or just got deleted + // } + } + // This has already been saved or just got deleted + unset($datasetdefinitions[$defid]); + continue; + } + $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef); + $datasetdefs[]= clone($datasetdef); + $questiondataset = new stdClass; + $questiondataset->question = $question->id; + $questiondataset->datasetdefinition = $datasetdef->id; + $DB->insert_record('question_datasets', $questiondataset); unset($datasetdefinitions[$defid]); - continue; } - $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef); - $datasetdefs[]= clone($datasetdef); - $questiondataset = new stdClass; - $questiondataset->question = $question->id; - $questiondataset->datasetdefinition = $datasetdef->id; - $DB->insert_record('question_datasets', $questiondataset); - unset($datasetdefinitions[$defid]); - } - // Remove local obsolete datasets as well as relations - // to datasets in other categories: - if (!empty($datasetdefinitions)) { - foreach ($datasetdefinitions as $def) { - $DB->delete_records('question_datasets', array('question' => $question->id, 'datasetdefinition' => $def->id)); - if ($def->category == 0) { // Question local dataset - $DB->delete_records('question_dataset_definitions', array('id' => $def->id)); - $DB->delete_records('question_dataset_items', array('definition' => $def->id)); + // Remove local obsolete datasets as well as relations + // to datasets in other categories: + if (!empty($datasetdefinitions)) { + foreach ($datasetdefinitions as $def) { + $DB->delete_records('question_datasets', array('question' => $question->id, 'datasetdefinition' => $def->id)); + if ($def->category == 0) { // Question local dataset + $DB->delete_records('question_dataset_definitions', array('id' => $def->id)); + $DB->delete_records('question_dataset_items', array('definition' => $def->id)); + } } } - } - $datasetdefs = $this->get_dataset_definitions($question->id, $question->dataset); - // Handle adding and removing of dataset items - $i = 1; - ksort($question->definition); - foreach ($question->definition as $key => $defid) { - $addeditem = new stdClass(); - $addeditem->definition = $datasetdefs[$defid]->id; - $addeditem->value = $question->number[$i]; - $addeditem->itemnumber = ceil($i / count($datasetdefs)); - if (empty($question->makecopy) && $question->itemid[$i]) { - // Reuse any previously used record - $addeditem->id = $question->itemid[$i]; - $DB->update_record('question_dataset_items', $addeditem); - } else { - $DB->insert_record('question_dataset_items', $addeditem); + $datasetdefs = $this->get_dataset_definitions($question->id, $question->dataset); + // Handle adding and removing of dataset items + $i = 1; + ksort($question->definition); + foreach ($question->definition as $key => $defid) { + $addeditem = new stdClass(); + $addeditem->definition = $datasetdefs[$defid]->id; + $addeditem->value = $question->number[$i]; + $addeditem->itemnumber = ceil($i / count($datasetdefs)); + if (empty($question->makecopy) && $question->itemid[$i]) { + // Reuse any previously used record + $addeditem->id = $question->itemid[$i]; + $DB->update_record('question_dataset_items', $addeditem); + } else { + $DB->insert_record('question_dataset_items', $addeditem); + } + $i++; } - $i++; - } - if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber){ - $maxnumber = $addeditem->itemnumber; - foreach ($datasetdefs as $key => $newdef) { - if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) { - $newdef->itemcount = $maxnumber; - // Save the new value for options - $DB->update_record('question_dataset_definitions', $newdef); + $maxnumber = -1; + if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber){ + $maxnumber = $addeditem->itemnumber; + foreach ($datasetdefs as $key => $newdef) { + if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) { + $newdef->itemcount = $maxnumber; + // Save the new value for options + $DB->update_record('question_dataset_definitions', $newdef); + } } } } - } // Report any problems. //convert to calculated if(!empty($question->makecopy) && !empty($question->convert)) { @@ -190,39 +205,34 @@ function save_question_options($question) { } return true; } - function finished_edit_wizard(&$form) { + function finished_edit_wizard(&$form) { return true ; //isset($form->backtoquiz); } - - - /** - * this version save the available data at the different steps of the question editing process - * without using global $SESSION as storage between steps - * at the first step $wizardnow = 'question' - * when creating a new question - * when modifying a question - * when copying as a new question - * the general parameters and answers are saved using parent::save_question - * then the datasets are prepared and saved - * at the second step $wizardnow = 'datasetdefinitions' - * the datadefs final type are defined as private, category or not a datadef - * at the third step $wizardnow = 'datasetitems' - * the datadefs parameters and the data items are created or defined - * - * @param object question - * @param object $form - * @param int $course - * @param PARAM_ALPHA $wizardnow should be added as we are coming from question2.php - */ + * this version save the available data at the different steps of the question editing process + * without using global $SESSION as storage between steps + * at the first step $wizardnow = 'question' + * when creating a new question + * when modifying a question + * when copying as a new question + * the general parameters and answers are saved using parent::save_question + * then the datasets are prepared and saved + * at the second step $wizardnow = 'datasetdefinitions' + * the datadefs final type are defined as private, category or not a datadef + * at the third step $wizardnow = 'datasetitems' + * the datadefs parameters and the data items are created or defined + * + * @param object question + * @param object $form + * @param int $course + * @param PARAM_ALPHA $wizardnow should be added as we are coming from question2.php + */ function save_question($question, $form, $course) { $question = default_questiontype::save_question($question, $form, $course); return $question; } - - function custom_generator_tools_part(&$mform, $idx, $j){ $minmaxgrp = array(); @@ -247,14 +257,14 @@ function comment_header($answers) { $strheader = ""; $delimiter = ''; - // $answers = $question->options->answers; + // $answers = $question->options->answers; foreach ($answers as $key => $answer) { /* if (is_string($answer)) { $strheader .= $delimiter.$answer; - } else {*/ - $strheader .= $delimiter.$answer->answer; - // } + } else {*/ + $strheader .= $delimiter.$answer->answer; + // } $delimiter = '


'; } return $strheader; @@ -262,18 +272,18 @@ function comment_header($answers) { function tolerance_types() { return array('1' => get_string('relative', 'quiz'), - '2' => get_string('nominal', 'quiz'), - // '3' => get_string('geometric', 'quiz') - ); + '2' => get_string('nominal', 'quiz'), + // '3' => get_string('geometric', 'quiz') + ); } function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false) { - // Takes datasets from the parent implementation but - // filters options that are currently not accepted by calculated - // It also determines a default selection... - //$renameabledatasets not implemented anmywhere + // Takes datasets from the parent implementation but + // filters options that are currently not accepted by calculated + // It also determines a default selection... + //$renameabledatasets not implemented anmywhere list($options, $selected) = $this->dataset_options_from_database($form, $name,'','qtype_calculated'); - // list($options, $selected) = $this->dataset_optionsa($form, $name); + // list($options, $selected) = $this->dataset_optionsa($form, $name); foreach ($options as $key => $whatever) { if (!preg_match('~^1-~', $key) && $key != '0') { @@ -282,7 +292,7 @@ function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false } if (!$selected) { if ($mandatory){ - $selected = "1-0-$name"; // Default + $selected = "1-0-$name"; // Default }else { $selected = "0"; // Default } @@ -291,53 +301,123 @@ function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false } -/** - * Runs all the code required to set up and save an essay question for testing purposes. - * Alternate DB table prefix may be used to facilitate data deletion. - */ - function generate_test($name, $courseid = null) { - global $DB; - list($form, $question) = parent::generate_test($name, $courseid); - $form->feedback = 1; - $form->multiplier = array(1, 1); - $form->shuffleanswers = 1; - $form->noanswers = 1; - $form->qtype ='calculatedsimple'; - $question->qtype ='calculatedsimple'; - $form->answers = array('{a} + {b}'); - $form->fraction = array(1); - $form->tolerance = array(0.01); - $form->tolerancetype = array(1); - $form->correctanswerlength = array(2); - $form->correctanswerformat = array(1); - $form->questiontext = "What is {a} + {b}?"; - - if ($courseid) { - $course = $DB->get_record('course', array('id'=> $courseid)); - } - - $new_question = $this->save_question($question, $form, $course); - - $dataset_form = new stdClass(); - $dataset_form->nextpageparam["forceregeneration"]= 1; - $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0); - $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0); - $dataset_form->calclength = array(1 => 1, 2 => 1); - $dataset_form->number = array(1 => 5.4 , 2 => 4.9); - $dataset_form->itemid = array(1 => '' , 2 => ''); - $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform'); - $dataset_form->definition = array(1 => "1-0-a", - 2 => "1-0-b"); - $dataset_form->nextpageparam = array('forceregeneration' => false); - $dataset_form->addbutton = 1; - $dataset_form->selectadd = 1; - $dataset_form->courseid = $courseid; - $dataset_form->cmid = 0; - $dataset_form->id = $new_question->id; - $this->save_dataset_items($new_question, $dataset_form); - - return $new_question; - } + /** + * Runs all the code required to set up and save an essay question for testing purposes. + * Alternate DB table prefix may be used to facilitate data deletion. + */ + function generate_test($name, $courseid = null) { + global $DB; + list($form, $question) = parent::generate_test($name, $courseid); + $form->feedback = 1; + $form->multiplier = array(1, 1); + $form->shuffleanswers = 1; + $form->noanswers = 1; + $form->qtype ='calculatedsimple'; + $question->qtype ='calculatedsimple'; + $form->answers = array('{a} + {b}'); + $form->fraction = array(1); + $form->tolerance = array(0.01); + $form->tolerancetype = array(1); + $form->correctanswerlength = array(2); + $form->correctanswerformat = array(1); + $form->questiontext = "What is {a} + {b}?"; + + if ($courseid) { + $course = $DB->get_record('course', array('id'=> $courseid)); + } + + $new_question = $this->save_question($question, $form, $course); + + $dataset_form = new stdClass(); + $dataset_form->nextpageparam["forceregeneration"]= 1; + $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0); + $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0); + $dataset_form->calclength = array(1 => 1, 2 => 1); + $dataset_form->number = array(1 => 5.4 , 2 => 4.9); + $dataset_form->itemid = array(1 => '' , 2 => ''); + $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform'); + $dataset_form->definition = array(1 => "1-0-a", + 2 => "1-0-b"); + $dataset_form->nextpageparam = array('forceregeneration' => false); + $dataset_form->addbutton = 1; + $dataset_form->selectadd = 1; + $dataset_form->courseid = $courseid; + $dataset_form->cmid = 0; + $dataset_form->id = $new_question->id; + $this->save_dataset_items($new_question, $dataset_form); + + return $new_question; + } + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + parent::move_files($question, $newcategory); + + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'feedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + $component = 'qtype_calculatedsimple'; + $filearea = 'feedback'; + $filearea = 'instruction'; + $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + $itemid = reset($args); + if ($component == 'question' && $filearea == 'answerfeedback') { + + // check if answer id exists + $result = $options->feedback && array_key_exists($itemid, $question->options->answers); + if (!$result) { + return false; + } + // check response + if (!$this->check_response($question, $state)) { + return false; + } + return true; + } else if ($filearea == 'instruction') { + // TODO: should it be display all the time like questiontext? + // check if question id exists + if ($itemid != $question->id) { + return false; + } else { + return true; + } + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + return true; + } } //// END OF CLASS //// @@ -349,5 +429,3 @@ function generate_test($name, $courseid = null) { if ( ! defined ("CALCULATEDSIMPLE")) { define("CALCULATEDSIMPLE", "calculatedsimple"); } - - diff --git a/question/type/description/edit_description_form.php b/question/type/description/edit_description_form.php index 3170740f00f72..b13858f80596a 100644 --- a/question/type/description/edit_description_form.php +++ b/question/type/description/edit_description_form.php @@ -1,4 +1,20 @@ . + /** * Defines the editing form for the description question type. * diff --git a/question/type/description/question.html b/question/type/description/question.html index c55db3daf6e32..78f525e8ba292 100644 --- a/question/type/description/question.html +++ b/question/type/description/question.html @@ -6,10 +6,6 @@
- - - -
diff --git a/question/type/description/questiontype.php b/question/type/description/questiontype.php index 2d4221e70ee58..c3e8fb812c02f 100644 --- a/question/type/description/questiontype.php +++ b/question/type/description/questiontype.php @@ -68,7 +68,6 @@ function print_question(&$question, &$state, $number, $cmoptions, $options) { $editlink = $this->get_question_edit_link($question, $cmoptions, $options); $questiontext = $this->format_text($question->questiontext, $question->questiontextformat, $cmoptions); - $image = get_question_image($question); $generalfeedback = ''; if ($isfinished && $options->generalfeedback) { @@ -91,4 +90,3 @@ function grade_responses(&$question, &$state, $cmoptions) { } // Register this question type with questionlib.php. question_register_questiontype(new description_qtype()); - diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php index 21b0ed260341f..a0a5977070426 100644 --- a/question/type/edit_question_form.php +++ b/question/type/edit_question_form.php @@ -1,4 +1,20 @@ . + /** * A base class for question editing forms. * @@ -7,7 +23,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package questionbank * @subpackage questiontypes - *//** */ + */ /** * Form definition base class. This defines the common fields that @@ -27,24 +43,44 @@ class question_edit_form extends moodleform { * @var object */ public $question; - public $contexts; public $category; public $categorycontext; public $coursefilesid; + /** @var object current context */ + public $context; + /** @var array html editor options */ + public $editoroptions; + /** @var array options to preapre draft area */ + public $fileoptions; + /** @var object instance of question type */ + public $instance; + function question_edit_form($submiturl, $question, $category, $contexts, $formeditable = true){ + global $DB; $this->question = $question; $this->contexts = $contexts; + $record = $DB->get_record('question_categories', array('id'=>$question->category), 'contextid'); + $this->context = get_context_instance_by_id($record->contextid); + + $this->editoroptions = array('maxfiles' => EDITOR_UNLIMITED_FILES, 'context'=>$this->context); + $this->fileoptions = array('subdir'=>true, 'maxfiles'=>-1, 'maxbytes'=>-1); + $this->category = $category; $this->categorycontext = get_context_instance_by_id($category->contextid); - + //** * //course id or site id depending on question cat context $this->coursefilesid = get_filesdir_from_context(get_context_instance_by_id($category->contextid)); + if (!empty($question->id)) { + $question->id = (int)$question->id; + //$this->instance = new + } + parent::moodleform($submiturl, null, 'post', '', null, $formeditable); } @@ -106,26 +142,9 @@ function definition() { $mform->setType('name', PARAM_TEXT); $mform->addRule('name', null, 'required', null, 'client'); - //TODO: MDL-16094 convert to new editor element - $mform->addElement('htmleditor', 'questiontext', get_string('questiontext', 'quiz'), - array('rows' => 15, 'course' => $this->coursefilesid)); + $mform->addElement('editor', 'questiontext', get_string('questiontext', 'quiz'), + array('rows' => 15, 'course' => $this->coursefilesid), $this->editoroptions); $mform->setType('questiontext', PARAM_RAW); - //$mform->addElement('format', 'questiontextformat', get_string('format')); - $mform->addElement('hidden', 'questiontextformat'); - $mform->setType('questiontextformat', PARAM_INT); - - make_upload_directory($this->coursefilesid); // Just in case - $coursefiles = get_directory_list("$CFG->dataroot/$this->coursefilesid", $CFG->moddata); - foreach ($coursefiles as $filename) { - if (mimeinfo("icon", $filename) == "image.gif") { - $images["$filename"] = $filename; - } - } - if (empty($images)) { - $mform->addElement('static', 'image', get_string('imagedisplay', 'quiz'), get_string('noimagesyet')); - } else { - $mform->addElement('select', 'image', get_string('imagedisplay', 'quiz'), array_merge(array(''=>get_string('none')), $images)); - } $mform->addElement('text', 'defaultgrade', get_string('defaultgrade', 'quiz'), array('size' => 3)); @@ -140,8 +159,8 @@ function definition() { $mform->addHelpButton('penalty', 'penaltyfactor', 'question'); $mform->setDefault('penalty', 0.1); - $mform->addElement('htmleditor', 'generalfeedback', get_string('generalfeedback', 'quiz'), - array('rows' => 10, 'course' => $this->coursefilesid)); + $mform->addElement('editor', 'generalfeedback', get_string('generalfeedback', 'quiz'), + array('rows' => 10, 'course' => $this->coursefilesid), $this->editoroptions); $mform->setType('generalfeedback', PARAM_RAW); $mform->addHelpButton('generalfeedback', 'generalfeedback', 'quiz'); @@ -235,7 +254,7 @@ function validation($fromform, $files) { $errors= parent::validation($fromform, $files); if (empty($fromform->makecopy) && isset($this->question->id) && ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew) - && empty($fromform->usecurrentcat) && !$this->question->formoptions->canmove){ + && empty($fromform->usecurrentcat) && !$this->question->formoptions->canmove) { $errors['currentgrp'] = get_string('nopermissionmove', 'question'); } return $errors; @@ -264,8 +283,8 @@ function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions $repeated[] =& $mform->createElement('header', 'answerhdr', $label); $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50)); $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions); - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), - array('course' => $this->coursefilesid)); + $repeated[] =& $mform->createElement('editor', 'feedback', get_string('feedback', 'quiz'), + array('course' => $this->coursefilesid), $this->editoroptions); $repeatedoptions['answer']['type'] = PARAM_RAW; $repeatedoptions['fraction']['default'] = 0; $answersoption = 'answers'; @@ -302,9 +321,33 @@ function add_per_answer_fields(&$mform, $label, $gradeoptions, $minoptions = QUE function set_data($question) { global $QTYPES; - if (empty($question->image)){ - unset($question->image); + // prepare question text + $draftid = file_get_submitted_draft_itemid('questiontext'); + + if (!empty($question->questiontext)) { + $questiontext = $question->questiontext; + } else { + $questiontext = ''; } + $questiontext = file_prepare_draft_area($draftid, $this->context->id, 'question', 'questiontext', empty($question->id)?null:(int)$question->id, null, $questiontext); + + $question->questiontext = array(); + $question->questiontext['text'] = $questiontext; + $question->questiontext['format'] = empty($question->questiontextformat) ? editors_get_preferred_format() : $question->questiontextformat; + $question->questiontext['itemid'] = $draftid; + + // prepare general feedback + $draftid = file_get_submitted_draft_itemid('generalfeedback'); + + if (empty($question->generalfeedback)) { + $question->generalfeedback = ''; + } + + $feedback = file_prepare_draft_area($draftid, $this->context->id, 'question', 'generalfeedback', empty($question->id)?null:(int)$question->id, null, $question->generalfeedback); + $question->generalfeedback = array(); + $question->generalfeedback['text'] = $feedback; + $question->generalfeedback['format'] = empty($question->generalfeedbackformat) ? editors_get_preferred_format() : $question->generalfeedbackformat; + $question->generalfeedback['itemid'] = $draftid; // Remove unnecessary trailing 0s form grade fields. if (isset($question->defaultgrade)) { @@ -324,10 +367,20 @@ function set_data($question) { } } } - + // subclass adds data_preprocessing code here + $question = $this->data_preprocessing($question); parent::set_data($question); } + /** + * Any preprocessing needed for the settings form for the question type + * + * @param array $question - array to fill in with the default values + */ + function data_preprocessing($question) { + return $question; + } + /** * Override this in the subclass to question type name. * @return the question type name, should be the same as the name() method in the question type class. @@ -336,5 +389,3 @@ function qtype() { return ''; } } - - diff --git a/question/type/essay/display.html b/question/type/essay/display.html index 2acb0a2f5b032..89c91d8014cfc 100644 --- a/question/type/essay/display.html +++ b/question/type/essay/display.html @@ -2,10 +2,6 @@
- - - -
diff --git a/question/type/essay/edit_essay_form.php b/question/type/essay/edit_essay_form.php index 5b90371d72d74..b238842892747 100644 --- a/question/type/essay/edit_essay_form.php +++ b/question/type/essay/edit_essay_form.php @@ -1,4 +1,20 @@ . + /** * Defines the editing form for the essay question type. * @@ -19,8 +35,7 @@ class question_edit_essay_form extends question_edit_form { * @param MoodleQuickForm $mform the form being built. */ function definition_inner(&$mform) { - $mform->addElement('htmleditor', 'feedback', get_string("feedback", "quiz"), - array('course' => $this->coursefilesid)); + $mform->addElement('editor', 'feedback', get_string('feedback', 'quiz'), null, $this->editoroptions); $mform->setType('feedback', PARAM_RAW); $mform->addElement('hidden', 'fraction', 0); @@ -32,17 +47,29 @@ function definition_inner(&$mform) { $mform->setType('penalty', PARAM_RAW); } - function set_data($question) { + function data_preprocessing($question) { if (!empty($question->options) && !empty($question->options->answers)) { $answer = reset($question->options->answers); - $question->feedback = $answer->feedback; + $question->feedback = array(); + $draftid = file_get_submitted_draft_itemid('feedback'); + $question->feedback['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'question', // component + 'answerfeedback', // filarea + !empty($answer->id)?(int)$answer->id:null, // itemid + $this->fileoptions, // options + $answer->feedback // text + ); + $question->feedback['text'] = $answer->feedback; + $question->feedback['format'] = $answer->feedbackformat; + $question->feedback['itemid'] = $draftid; } $question->penalty = 0; - parent::set_data($question); + return $question; } function qtype() { return 'essay'; } } - diff --git a/question/type/essay/lib.php b/question/type/essay/lib.php new file mode 100644 index 0000000000000..dc1fe09d5caa9 --- /dev/null +++ b/question/type/essay/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package questionbank + * @subpackage questiontypes + * @author Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_essay_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_essay', $filearea, $args, $forcedownload); +} diff --git a/question/type/essay/questiontype.php b/question/type/essay/questiontype.php index 12acc8d629ef7..67bafcd99f142 100644 --- a/question/type/essay/questiontype.php +++ b/question/type/essay/questiontype.php @@ -1,5 +1,20 @@ . + ////////////////// /// ESSAY /// ///////////////// @@ -21,6 +36,7 @@ function is_manual_graded() { function save_question_options($question) { global $DB; + $context = $question->context; $result = true; $update = true; $answer = $DB->get_record("question_answers", array("question" => $question->id)); @@ -29,13 +45,20 @@ function save_question_options($question) { $answer->question = $question->id; $update = false; } - $answer->answer = $question->feedback; - $answer->feedback = $question->feedback; + $answer->feedbackformat = $question->feedback['format']; + $answer->answerformat = $question->feedback['format']; $answer->fraction = $question->fraction; if ($update) { + $answer->feedback = file_save_draft_area_files($question->feedback['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, trim($question->feedback['text'])); + $answer->answer = $answer->feedback; $DB->update_record("question_answers", $answer); } else { - $answer->id = $DB->insert_record("question_answers", $answer); + $answer->feedback = $question->feedback['text']; + $answer->answer = $answer->feedback; + $answer->id = $DB->insert_record('question_answers', $answer); + $answer->feedback = file_save_draft_area_files($question->feedback['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, trim($question->feedback['text'])); + $answer->answer = $answer->feedback; + $DB->update_record('question_answers', $answer); } return $result; } @@ -43,12 +66,14 @@ function save_question_options($question) { function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { global $CFG; - $answers = &$question->options->answers; - $readonly = empty($options->readonly) ? '' : 'disabled="disabled"'; + $context = $this->get_context_by_category_id($question->category); + + $answers = &$question->options->answers; + $readonly = empty($options->readonly) ? '' : 'disabled="disabled"'; // Only use the rich text editor for the first essay question on a page. - $formatoptions = new stdClass; + $formatoptions = new stdClass; $formatoptions->noclean = true; $formatoptions->para = false; @@ -60,13 +85,12 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions $question->questiontextformat, $formatoptions, $cmoptions->course); - $image = get_question_image($question); - // feedback handling $feedback = ''; if ($options->feedback && !empty($answers)) { foreach ($answers as $answer) { - $feedback = format_text($answer->feedback, '', $formatoptions, $cmoptions->course); + $feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'qtype_essay', 'feedback', array($state->attempt, $state->question), $answer->id); + $feedback = format_text($feedback, $answer->feedbackformat, $formatoptions, $cmoptions->course); } } @@ -139,6 +163,54 @@ function generate_test($name, $courseid = null) { return $this->save_question($question, $form, $course); } + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + parent::move_files($question, $newcategory); + + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'answerfeedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + } + + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + if ($component == 'question' && $filearea == 'answerfeedback') { + + $answerid = reset($args); // itemid is answer id. + $answers = &$question->options->answers; + if (isset($state->responses[''])) { + $response = $state->responses['']; + } else { + $response = ''; + } + + return $options->feedback && !empty($response); + + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } // Restore method not needed. } //// END OF CLASS //// @@ -147,4 +219,3 @@ function generate_test($name, $courseid = null) { //// INITIATION - Without this line the question type is not in use... /// ////////////////////////////////////////////////////////////////////////// question_register_questiontype(new question_essay_qtype()); - diff --git a/question/type/match/db/install.xml b/question/type/match/db/install.xml index 78cb64986a08d..cb0ba92ac319f 100644 --- a/question/type/match/db/install.xml +++ b/question/type/match/db/install.xml @@ -1,5 +1,8 @@ - + @@ -18,8 +21,9 @@ - - + + + @@ -27,4 +31,4 @@
-
+
\ No newline at end of file diff --git a/question/type/match/db/upgrade.php b/question/type/match/db/upgrade.php new file mode 100644 index 0000000000000..f634514b4555c --- /dev/null +++ b/question/type/match/db/upgrade.php @@ -0,0 +1,58 @@ +get_manager(); + + if ($oldversion < 2009072100) { + + // Define field questiontextformat to be added to question_match_sub + $table = new xmldb_table('question_match_sub'); + $field = new xmldb_field('questiontextformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'questiontext'); + + // Conditionally launch add field questiontextformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $rs = $DB->get_recordset('question_match_sub'); + foreach ($rs as $record) { + if ($CFG->texteditors !== 'textarea') { + if (!empty($record->questiontext)) { + $record->questiontext = text_to_html($record->questiontext); + } + $record->questiontextformat = FORMAT_HTML; + } else { + $record->questiontextformat = FORMAT_MOODLE; + } + $DB->update_record('question_match_sub', $record); + } + $rs->close(); + + // match savepoint reached + upgrade_plugin_savepoint(true, 2009072100, 'qtype', 'match'); + } + + return true; +} diff --git a/question/type/match/display.html b/question/type/match/display.html index 828262a740649..c55cfd60c8e96 100644 --- a/question/type/match/display.html +++ b/question/type/match/display.html @@ -2,10 +2,6 @@
- - - -
diff --git a/question/type/match/edit_match_form.php b/question/type/match/edit_match_form.php index 880bc5966d6a6..75f27739dbdcb 100644 --- a/question/type/match/edit_match_form.php +++ b/question/type/match/edit_match_form.php @@ -17,7 +17,7 @@ class question_edit_match_form extends question_edit_form { function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) { $repeated = array(); $repeated[] =& $mform->createElement('header', 'answerhdr', $label); - $repeated[] =& $mform->createElement('textarea', 'subquestions', get_string('question', 'quiz'), array('cols'=>40, 'rows'=>3)); + $repeated[] =& $mform->createElement('editor', 'subquestions', get_string('question', 'quiz'), null, $this->editoroptions); $repeated[] =& $mform->createElement('text', 'subanswers', get_string('answer', 'quiz'), array('size'=>50)); $repeatedoptions['subquestions']['type'] = PARAM_RAW; $repeatedoptions['subanswers']['type'] = PARAM_TEXT; @@ -41,21 +41,35 @@ function definition_inner(&$mform) { $this->add_per_answer_fields($mform, get_string('questionno', 'quiz', '{no}'), 0); } - function set_data($question) { - if (isset($question->options)){ + function data_preprocessing($question) { + if (isset($question->options)) { $subquestions = $question->options->subquestions; if (count($subquestions)) { $key = 0; foreach ($subquestions as $subquestion){ $default_values['subanswers['.$key.']'] = $subquestion->answertext; - $default_values['subquestions['.$key.']'] = $subquestion->questiontext; + + $draftid = file_get_submitted_draft_itemid('subquestions['.$key.']'); + $default_values['subquestions['.$key.']'] = array(); + $default_values['subquestions['.$key.']']['format'] = $subquestion->questiontextformat; + $default_values['subquestions['.$key.']']['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'qtype_match', // component + 'subquestion', // filarea + !empty($subquestion->id)?(int)$subquestion->id:null, // itemid + $this->fileoptions, // options + $subquestion->questiontext // text + ); + $default_values['subquestions['.$key.']']['itemid'] = $draftid; + $key++; } } $default_values['shuffleanswers'] = $question->options->shuffleanswers; $question = (object)((array)$question + $default_values); } - parent::set_data($question); + return $question; } function qtype() { @@ -69,7 +83,7 @@ function validation($data, $files) { $questioncount = 0; $answercount = 0; foreach ($questions as $key => $question){ - $trimmedquestion = trim($question); + $trimmedquestion = trim($question['text']); $trimmedanswer = trim($answers[$key]); if ($trimmedquestion != ''){ $questioncount++; diff --git a/question/type/match/lib.php b/question/type/match/lib.php new file mode 100644 index 0000000000000..e51349e159c6f --- /dev/null +++ b/question/type/match/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package questionbank + * @subpackage questiontypes + * @author Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_match_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $DB, $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_match', $filearea, $args, $forcedownload); +} diff --git a/question/type/match/questiontype.php b/question/type/match/questiontype.php index f1bbe5ed38587..be01f1c539ebd 100644 --- a/question/type/match/questiontype.php +++ b/question/type/match/questiontype.php @@ -1,5 +1,20 @@ . + ///////////// /// MATCH /// ///////////// @@ -24,6 +39,7 @@ function get_question_options(&$question) { function save_question_options($question) { global $DB; + $context = $question->context; $result = new stdClass; if (!$oldsubquestions = $DB->get_records("question_match_sub", array("question" => $question->id), "id ASC")) { @@ -35,24 +51,30 @@ function save_question_options($question) { // Insert all the new question+answer pairs foreach ($question->subquestions as $key => $questiontext) { - $questiontext = trim($questiontext); + $itemid = $questiontext['itemid']; + $format = $questiontext['format']; + $questiontext = trim($questiontext['text']); $answertext = trim($question->subanswers[$key]); if ($questiontext != '' || $answertext != '') { if ($subquestion = array_shift($oldsubquestions)) { // Existing answer, so reuse it - $subquestion->questiontext = $questiontext; $subquestion->answertext = $answertext; + $subquestion->questiontext = file_save_draft_area_files($itemid, $context->id, 'qtype_match', 'subquestion', $subquestion->id, self::$fileoptions, $questiontext); + $subquestion->questiontextformat = $format; $DB->update_record("question_match_sub", $subquestion); } else { $subquestion = new stdClass; // Determine a unique random code - $subquestion->code = rand(1,999999999); + $subquestion->code = rand(1, 999999999); while ($DB->record_exists('question_match_sub', array('code' => $subquestion->code, 'question' => $question->id))) { $subquestion->code = rand(); } $subquestion->question = $question->id; $subquestion->questiontext = $questiontext; - $subquestion->answertext = $answertext; + $subquestion->questiontextformat = $format; + $subquestion->answertext = $answertext; $subquestion->id = $DB->insert_record("question_match_sub", $subquestion); + $questiontext = file_save_draft_area_files($itemid, $context->id, 'qtype_match', 'subquestion', $subquestion->id, self::$fileoptions, $questiontext); + $DB->set_field('question_match_sub', 'questiontext', $questiontext, array('id'=>$subquestion->id)); } $subquestions[] = $subquestion->id; } @@ -228,6 +250,7 @@ function get_correct_responses(&$question, &$state) { function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { global $CFG, $OUTPUT; + $context = $this->get_context_by_category_id($question->category); $subquestions = $state->options->subquestions; $correctanswers = $this->get_correct_responses($question, $state); $nameprefix = $question->name_prefix; @@ -263,15 +286,14 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions // Print formulation $questiontext = $this->format_text($question->questiontext, $question->questiontextformat, $cmoptions); - $image = get_question_image($question); // Print the input controls foreach ($subquestions as $key => $subquestion) { if ($subquestion->questiontext !== '' && !is_null($subquestion->questiontext)) { // Subquestion text: $a = new stdClass; - $a->text = $this->format_text($subquestion->questiontext, - $question->questiontextformat, $cmoptions); + $text = quiz_rewrite_question_urls($subquestion->questiontext, 'pluginfile.php', $context->id, 'qtype_match', 'subquestion', array($state->attempt, $state->question), $subquestion->id); + $a->text = $this->format_text($text, $subquestion->questiontextformat, $cmoptions); // Drop-down list: $menuname = $nameprefix.$subquestion->id; @@ -757,6 +779,53 @@ function generate_test($name, $courseid = null) { return $this->save_question($question, $form, $course); } + + function move_files($question, $newcategory) { + global $DB; + // move files belonging to question component + parent::move_files($question, $newcategory); + + // move files belonging to qtype_multichoice + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + + // process files in sub questions + if (!$subquestions = $DB->get_records('question_match_sub', array('question' => $question->id), 'id ASC')) { + $subquestions = array(); + } + $component = 'qtype_match'; + $filearea = 'subquestion'; + foreach ($subquestions as $sub) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $sub->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + } + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + + $itemid = reset($args); + if ($filearea == 'subquestion') { + // always display quetion images + // itemid is sub question id + if ($itemid != $question->id) { + return false; + } + return true; + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } } //// END OF CLASS //// @@ -764,4 +833,3 @@ function generate_test($name, $courseid = null) { //// INITIATION - Without this line the question type is not in use... /// ////////////////////////////////////////////////////////////////////////// question_register_questiontype(new question_match_qtype()); - diff --git a/question/type/match/version.php b/question/type/match/version.php index 1950094efc88b..5b5cd5e043746 100644 --- a/question/type/match/version.php +++ b/question/type/match/version.php @@ -1,6 +1,6 @@ version = 2006032200; +$plugin->version = 2009072100; $plugin->requires = 2007101000; diff --git a/question/type/missingtype/display.html b/question/type/missingtype/display.html index e4ebf65893508..01d65085e2e52 100644 --- a/question/type/missingtype/display.html +++ b/question/type/missingtype/display.html @@ -2,10 +2,6 @@ - - - -
@@ -21,4 +17,4 @@
- \ No newline at end of file + diff --git a/question/type/missingtype/questiontype.php b/question/type/missingtype/questiontype.php index e0422d474bac4..d0d450a153862 100644 --- a/question/type/missingtype/questiontype.php +++ b/question/type/missingtype/questiontype.php @@ -42,7 +42,6 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions $questiontext = format_text($question->questiontext, $question->questiontextformat, $formatoptions, $cmoptions->course); - $image = get_question_image($question); // Print each answer in a separate row if there are any $anss = array(); diff --git a/question/type/multianswer/edit_multianswer_form.php b/question/type/multianswer/edit_multianswer_form.php index 151f745dc4dd2..fd88cb74c1674 100644 --- a/question/type/multianswer/edit_multianswer_form.php +++ b/question/type/multianswer/edit_multianswer_form.php @@ -15,15 +15,15 @@ class question_edit_multianswer_form extends question_edit_form { // $questiondisplay will contain the qtype_multianswer_extract_question from the questiontext - var $questiondisplay ; + public $questiondisplay ; // $savedquestiondisplay will contain the qtype_multianswer_extract_question from the questiontext in database - var $savedquestion ; - var $savedquestiondisplay ; - var $used_in_quiz = false ; - var $qtype_change = false ; - var $negative_diff = 0 ; - var $nb_of_quiz = 0; - var $nb_of_attempts = 0; + public $savedquestion ; + public $savedquestiondisplay ; + public $used_in_quiz = false ; + public $qtype_change = false ; + public $negative_diff = 0 ; + public $nb_of_quiz = 0; + public $nb_of_attempts = 0; public $confirm = 0 ; public $reload = false ; @@ -35,9 +35,9 @@ function question_edit_multianswer_form(&$submiturl, &$question, &$category, &$c }else { $this->reload = false ; } - // $this->question = $question; - $this->used_in_quiz =false; - // echo "

question

";print_r($question);echo "

"; + // $this->question = $question; + $this->used_in_quiz =false; + // echo "

question

";print_r($question);echo "

"; if(isset($question->id) && $question->id != 0 ){ $this->savedquestiondisplay =fullclone($question ) ; if ($list = $DB->get_records('quiz_question_instances', array( 'question'=> $question->id))){ @@ -51,15 +51,9 @@ function question_edit_multianswer_form(&$submiturl, &$question, &$category, &$c } } - - - - parent::question_edit_form($submiturl, $question, $category, $contexts, $formeditable); } - - function definition_inner(&$mform) { $mform->addElement('hidden', 'reload', 1); $mform->setType('reload', PARAM_INT); @@ -69,7 +63,7 @@ function definition_inner(&$mform) { $mform->removeElement('defaultgrade'); $this->confirm = optional_param('confirm','0', PARAM_RAW); - // display the questions from questiontext; + // display the questions from questiontext; if ( "" != optional_param('questiontext','', PARAM_RAW)) { $this->questiondisplay = fullclone(qtype_multianswer_extract_question(optional_param('questiontext','', PARAM_RAW))) ; @@ -80,14 +74,14 @@ function definition_inner(&$mform) { // question->id == 0 so no stored datasets $this->questiondisplay = fullclone($this->savedquestiondisplay); foreach($this->questiondisplay->options->questions as $subquestion){ - if (!empty($subquestion)){ - $subquestion->answer = array(''); - foreach($subquestion->options->answers as $ans){ - $subquestion->answer[]=$ans->answer ; + if (!empty($subquestion)){ + $subquestion->answer = array(''); + foreach($subquestion->options->answers as $ans){ + $subquestion->answer[]=$ans->answer ; + } + // $subquestion->answer = fullclone($subquestion->options->answers); } - // $subquestion->answer = fullclone($subquestion->options->answers); } - } }else { $this->questiondisplay = ""; } @@ -97,7 +91,7 @@ function definition_inner(&$mform) { $countsavedsubquestions =0; foreach($this->savedquestiondisplay->options->questions as $subquestion){ if (!empty($subquestion)){ - $countsavedsubquestions++; + $countsavedsubquestions++; } } } else { @@ -108,24 +102,24 @@ function definition_inner(&$mform) { $countsubquestions =0; foreach($this->questiondisplay->options->questions as $subquestion){ if (!empty($subquestion)){ - $countsubquestions++; + $countsubquestions++; } } - } else { - $countsubquestions =0; - } - }else{ - $countsubquestions =$countsavedsubquestions ; + } else { + $countsubquestions =0; } - // echo "

saved question $countsavedsubquestions

";print_r($this->savedquestiondisplay);echo "

"; - // echo "

saved question $countsubquestions

";print_r($this->questiondisplay);echo "

"; + }else{ + $countsubquestions =$countsavedsubquestions ; + } + // echo "

saved question $countsavedsubquestions

";print_r($this->savedquestiondisplay);echo "

"; + // echo "

saved question $countsubquestions

";print_r($this->questiondisplay);echo "

"; - $mform->addElement('submit', 'analyzequestion', get_string('decodeverifyquestiontext','qtype_multianswer')); - $mform->registerNoSubmitButton('analyzequestion'); - echo '
'; - echo '
'; - if ( $this->reload ){ + $mform->addElement('submit', 'analyzequestion', get_string('decodeverifyquestiontext','qtype_multianswer')); + $mform->registerNoSubmitButton('analyzequestion'); + echo '
'; + echo '
'; + if ( $this->reload ){ for ($sub =1;$sub <=$countsubquestions ;$sub++) { $this->editas[$sub] = 'unknown type'; @@ -135,14 +129,14 @@ function definition_inner(&$mform) { $this->editas[$sub] = optional_param('sub_'.$sub."_".'qtype', '', PARAM_RAW); } $storemess = ''; - if(isset($this->savedquestiondisplay->options->questions[$sub]->qtype) && - $this->savedquestiondisplay->options->questions[$sub]->qtype != $this->questiondisplay->options->questions[$sub]->qtype ){ - $this->qtype_change = true ; - $storemess = " STORED QTYPE ".$question_type_names[$this->savedquestiondisplay->options->questions[$sub]->qtype].""; - } + if(isset($this->savedquestiondisplay->options->questions[$sub]->qtype) && + $this->savedquestiondisplay->options->questions[$sub]->qtype != $this->questiondisplay->options->questions[$sub]->qtype ){ + $this->qtype_change = true ; + $storemess = " STORED QTYPE ".$question_type_names[$this->savedquestiondisplay->options->questions[$sub]->qtype].""; + } $mform->addElement('header', 'subhdr'.$sub, get_string('questionno', 'quiz', - '{#'.$sub.'}').' '.$question_type_names[$this->questiondisplay->options->questions[$sub]->qtype].$storemess); + '{#'.$sub.'}').' '.$question_type_names[$this->questiondisplay->options->questions[$sub]->qtype].$storemess); $mform->addElement('static', 'sub_'.$sub."_".'questiontext', get_string('questiondefinition','qtype_multianswer'),array('cols'=>60, 'rows'=>3)); @@ -153,16 +147,16 @@ function definition_inner(&$mform) { $mform->addElement('static', 'sub_'.$sub."_".'defaultgrade', get_string('defaultgrade', 'quiz')); $mform->setDefault('sub_'.$sub."_".'defaultgrade',$this->questiondisplay->options->questions[$sub]->defaultgrade); - if ($this->questiondisplay->options->questions[$sub]->qtype =='shortanswer' ) { - $mform->addElement('static', 'sub_'.$sub."_".'usecase', get_string('casesensitive', 'quiz')); - } + if ($this->questiondisplay->options->questions[$sub]->qtype =='shortanswer' ) { + $mform->addElement('static', 'sub_'.$sub."_".'usecase', get_string('casesensitive', 'quiz')); + } - if ($this->questiondisplay->options->questions[$sub]->qtype =='multichoice' ) { - $mform->addElement('static', 'sub_'.$sub."_".'layout', get_string('layout', 'qtype_multianswer'),array('cols'=>60, 'rows'=>1)) ;//, $gradeoptions); - } + if ($this->questiondisplay->options->questions[$sub]->qtype =='multichoice' ) { + $mform->addElement('static', 'sub_'.$sub."_".'layout', get_string('layout', 'qtype_multianswer'),array('cols'=>60, 'rows'=>1)) ;//, $gradeoptions); + } foreach ($this->questiondisplay->options->questions[$sub]->answer as $key =>$ans) { - $mform->addElement('static', 'sub_'.$sub."_".'answer['.$key.']', get_string('answer', 'quiz'), array('cols'=>60, 'rows'=>1)); + $mform->addElement('static', 'sub_'.$sub."_".'answer['.$key.']', get_string('answer', 'quiz'), array('cols'=>60, 'rows'=>1)); if ($this->questiondisplay->options->questions[$sub]->qtype =='numerical' && $key == 0 ) { $mform->addElement('static', 'sub_'.$sub."_".'tolerance['.$key.']', get_string('acceptederror', 'quiz')) ;//, $gradeoptions); @@ -177,16 +171,14 @@ function definition_inner(&$mform) { echo '
'; $this->negative_diff =$countsavedsubquestions - $countsubquestions ; if ( ($this->negative_diff > 0 ) ||$this->qtype_change || ($this->used_in_quiz && $this->negative_diff != 0)){ - $mform->addElement('header', 'additemhdr', get_string('warningquestionmodified','qtype_multianswer')); - } + $mform->addElement('header', 'additemhdr', get_string('warningquestionmodified','qtype_multianswer')); + } if($this->negative_diff > 0) { //$this->used_in_quiz - - $mform->addElement('static', 'alert1', "".get_string('questiondeleted','qtype_multianswer')."",get_string('questionsless','qtype_multianswer',$this->negative_diff)); + $mform->addElement('static', 'alert1', "".get_string('questiondeleted','qtype_multianswer')."",get_string('questionsless','qtype_multianswer',$this->negative_diff)); } - if($this->qtype_change ) - { - $mform->addElement('static', 'alert1', "".get_string('questiontypechanged','qtype_multianswer')."",get_string('questiontypechangedcomment','qtype_multianswer')); + if($this->qtype_change ) { + $mform->addElement('static', 'alert1', "".get_string('questiontypechanged','qtype_multianswer')."",get_string('questiontypechangedcomment','qtype_multianswer')); } echo '
'; } @@ -219,11 +211,11 @@ function set_data($question) { foreach ($question->options->questions as $key => $wrapped) { if(!empty($wrapped)){ - // The old way of restoring the definitions is kept to gradually - // update all multianswer questions - if (empty($wrapped->questiontext)) { - $parsableanswerdef = '{' . $wrapped->defaultgrade . ':'; - switch ($wrapped->qtype) { + // The old way of restoring the definitions is kept to gradually + // update all multianswer questions + if (empty($wrapped->questiontext)) { + $parsableanswerdef = '{' . $wrapped->defaultgrade . ':'; + switch ($wrapped->qtype) { case 'multichoice': $parsableanswerdef .= 'MULTICHOICE:'; break; @@ -235,62 +227,62 @@ function set_data($question) { break; default: print_error('unknownquestiontype', 'question', '', $wrapped->qtype); - } - $separator= ''; - foreach ($wrapped->options->answers as $subanswer) { - $parsableanswerdef .= $separator - . '%' . round(100*$subanswer->fraction) . '%'; - $parsableanswerdef .= $subanswer->answer; - if (!empty($wrapped->options->tolerance)) { - // Special for numerical answers: - $parsableanswerdef .= ":{$wrapped->options->tolerance}"; - // We only want tolerance for the first alternative, it will - // be applied to all of the alternatives. - unset($wrapped->options->tolerance); } - if ($subanswer->feedback) { - $parsableanswerdef .= "#$subanswer->feedback"; + $separator= ''; + foreach ($wrapped->options->answers as $subanswer) { + $parsableanswerdef .= $separator + . '%' . round(100*$subanswer->fraction) . '%'; + $parsableanswerdef .= $subanswer->answer; + if (!empty($wrapped->options->tolerance)) { + // Special for numerical answers: + $parsableanswerdef .= ":{$wrapped->options->tolerance}"; + // We only want tolerance for the first alternative, it will + // be applied to all of the alternatives. + unset($wrapped->options->tolerance); + } + if ($subanswer->feedback) { + $parsableanswerdef .= "#$subanswer->feedback"; + } + $separator = '~'; } - $separator = '~'; + $parsableanswerdef .= '}'; + // Fix the questiontext fields of old questions + $DB->set_field('question', 'questiontext', $parsableanswerdef, array('id' => $wrapped->id)); + } else { + $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext); } - $parsableanswerdef .= '}'; - // Fix the questiontext fields of old questions - $DB->set_field('question', 'questiontext', $parsableanswerdef, array('id' => $wrapped->id)); - } else { - $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext); + $question->questiontext = str_replace("{#$key}", $parsableanswerdef, $question->questiontext); } - $question->questiontext = str_replace("{#$key}", $parsableanswerdef, $question->questiontext); } } - } // set default to $questiondisplay questions elements if ( $this->reload ){ - if (isset($this->questiondisplay->options->questions)) { - $subquestions = fullclone($this->questiondisplay->options->questions) ; - if (count($subquestions)) { - $sub =1; - foreach ($subquestions as $subquestion) { - $prefix = 'sub_'.$sub.'_' ; - - // validate parameters - $answercount = 0; - $maxgrade = false; - $maxfraction = -1; - if ($subquestion->qtype =='shortanswer' ) { - switch ($subquestion->usecase) { + if (isset($this->questiondisplay->options->questions)) { + $subquestions = fullclone($this->questiondisplay->options->questions) ; + if (count($subquestions)) { + $sub =1; + foreach ($subquestions as $subquestion) { + $prefix = 'sub_'.$sub.'_' ; + + // validate parameters + $answercount = 0; + $maxgrade = false; + $maxfraction = -1; + if ($subquestion->qtype =='shortanswer' ) { + switch ($subquestion->usecase) { case '1': $default_values[$prefix.'usecase']= get_string('caseyes', 'quiz'); break; case '0': default : $default_values[$prefix.'usecase']= get_string('caseno', 'quiz'); + } } - } - if ($subquestion->qtype == 'multichoice' ) { - $default_values[$prefix.'layout'] = $subquestion->layout ; - switch ($subquestion->layout) { + if ($subquestion->qtype == 'multichoice' ) { + $default_values[$prefix.'layout'] = $subquestion->layout ; + switch ($subquestion->layout) { case '0': $default_values[$prefix.'layout']= get_string('layoutselectinline', 'qtype_multianswer'); break; @@ -302,53 +294,53 @@ function set_data($question) { break; default: $default_values[$prefix.'layout']= get_string('layoutundefined', 'qtype_multianswer'); + } } - } - foreach ($subquestion->answer as $key=>$answer) { - if ( $subquestion->qtype == 'numerical' && $key == 0 ) { - $default_values[$prefix.'tolerance['.$key.']'] = $subquestion->tolerance[0] ; - } - $trimmedanswer = trim($answer); - if ($trimmedanswer !== '') { - $answercount++; - if ($subquestion->qtype == 'numerical' && !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) { - $this->_form->setElementError($prefix.'answer['.$key.']' , get_string('answermustbenumberorstar', 'qtype_numerical')); + foreach ($subquestion->answer as $key=>$answer) { + if ( $subquestion->qtype == 'numerical' && $key == 0 ) { + $default_values[$prefix.'tolerance['.$key.']'] = $subquestion->tolerance[0] ; } - if ($subquestion->fraction[$key] == 1) { - $maxgrade = true; + $trimmedanswer = trim($answer); + if ($trimmedanswer !== '') { + $answercount++; + if ($subquestion->qtype == 'numerical' && !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) { + $this->_form->setElementError($prefix.'answer['.$key.']' , get_string('answermustbenumberorstar', 'qtype_numerical')); + } + if ($subquestion->fraction[$key] == 1) { + $maxgrade = true; + } + if ($subquestion->fraction[$key] > $maxfraction) { + $maxfraction = $subquestion->fraction[$key] ; + } } - if ($subquestion->fraction[$key] > $maxfraction) { - $maxfraction = $subquestion->fraction[$key] ; + + $default_values[$prefix.'answer['.$key.']'] = htmlspecialchars ($answer); + } + if ($answercount == 0) { + if ($subquestion->qtype == 'multichoice' ) { + $this->_form->setElementError($prefix.'answer[0]' , get_string('notenoughanswers', 'qtype_multichoice', 2)); + } else { + $this->_form->setElementError($prefix.'answer[0]' , get_string('notenoughanswers', 'quiz', 1)); } } - - $default_values[$prefix.'answer['.$key.']'] = htmlspecialchars ($answer); - } - if ($answercount == 0) { - if ($subquestion->qtype == 'multichoice' ) { - $this->_form->setElementError($prefix.'answer[0]' , get_string('notenoughanswers', 'qtype_multichoice', 2)); - } else { - $this->_form->setElementError($prefix.'answer[0]' , get_string('notenoughanswers', 'quiz', 1)); + if ($maxgrade == false) { + $this->_form->setElementError($prefix.'fraction[0]' ,get_string('fractionsnomax', 'question')); } - } - if ($maxgrade == false) { - $this->_form->setElementError($prefix.'fraction[0]' ,get_string('fractionsnomax', 'question')); - } - foreach ($subquestion->feedback as $key=>$answer) { + foreach ($subquestion->feedback as $key=>$answer) { - $default_values[$prefix.'feedback['.$key.']'] = htmlspecialchars ($answer); - } - foreach ( $subquestion->fraction as $key=>$answer) { - $default_values[$prefix.'fraction['.$key.']'] = $answer; - } + $default_values[$prefix.'feedback['.$key.']'] = htmlspecialchars ($answer); + } + foreach ( $subquestion->fraction as $key=>$answer) { + $default_values[$prefix.'fraction['.$key.']'] = $answer; + } - $sub++; + $sub++; + } } } } - } - $default_values['alertas']= "".get_string('questioninquiz','qtype_multianswer').""; + $default_values['alertas']= "".get_string('questioninquiz','qtype_multianswer').""; if( $default_values != "") { $question = (object)((array)$question + $default_values); @@ -358,14 +350,11 @@ function set_data($question) { function validation($data, $files) { $errors = parent::validation($data, $files); - - $questiondisplay = qtype_multianswer_extract_question($data['questiontext']) ; - + $questiondisplay = qtype_multianswer_extract_question($data['questiontext']['text']); if (isset($questiondisplay->options->questions)) { - - $subquestions = fullclone($questiondisplay->options->questions) ; + $subquestions = fullclone($questiondisplay->options->questions) ; if (count($subquestions)) { $sub =1; foreach ($subquestions as $subquestion) { @@ -373,17 +362,17 @@ function validation($data, $files) { $answercount = 0; $maxgrade = false; $maxfraction = -1; - if(isset($this->savedquestiondisplay->options->questions[$sub]->qtype) && - $this->savedquestiondisplay->options->questions[$sub]->qtype != $questiondisplay->options->questions[$sub]->qtype ){ - $storemess = " STORED QTYPE ".$question_type_names[$this->savedquestiondisplay->options->questions[$sub]->qtype]; - } + if(isset($this->savedquestiondisplay->options->questions[$sub]->qtype) && + $this->savedquestiondisplay->options->questions[$sub]->qtype != $questiondisplay->options->questions[$sub]->qtype ){ + $storemess = " STORED QTYPE ".$question_type_names[$this->savedquestiondisplay->options->questions[$sub]->qtype]; + } foreach ( $subquestion->answer as $key=>$answer) { $trimmedanswer = trim($answer); if ($trimmedanswer !== '') { $answercount++; if ($subquestion->qtype =='numerical' && !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) { $errors[$prefix.'answer['.$key.']']= get_string('answermustbenumberorstar', 'qtype_numerical'); - } + } if ($subquestion->fraction[$key] == 1) { $maxgrade = true; } @@ -408,18 +397,17 @@ function validation($data, $files) { $errors['questiontext']=get_string('questionsmissing', 'qtype_multianswer'); } } - // $question = qtype_multianswer_extract_question($data['questiontext']); - // if (isset $question->options->questions + // $question = qtype_multianswer_extract_question($data['questiontext']); + // if (isset $question->options->questions if (( $this->negative_diff > 0 || $this->used_in_quiz && ($this->negative_diff > 0 ||$this->negative_diff < 0 || $this->qtype_change ))&& $this->confirm == 0 ){ - $errors['confirm']=get_string('confirmsave', 'qtype_multianswer',$this->negative_diff); + $errors['confirm']=get_string('confirmsave', 'qtype_multianswer',$this->negative_diff); } - - return $errors; + + return $errors; } function qtype() { return 'multianswer'; } } - diff --git a/question/type/multianswer/questiontype.php b/question/type/multianswer/questiontype.php index e2786b4f39f2d..35f51e88d0937 100644 --- a/question/type/multianswer/questiontype.php +++ b/question/type/multianswer/questiontype.php @@ -307,10 +307,6 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions } echo '
'; - // For this question type, we better print the image on top: - if ($image = get_question_image($question)) { - echo('
'); - } $qtextremaining = format_text($question->questiontext, $question->questiontextformat, $formatoptions, $cmoptions->course); @@ -986,9 +982,7 @@ function qtype_multianswer_extract_question($text) { $question->options->questions = array(); $question->defaultgrade = 0; // Will be increased for each answer norm - for ($positionkey=1 - ; preg_match('/'.ANSWER_REGEX.'/', $question->questiontext, $answerregs) - ; ++$positionkey ) { + for ($positionkey=1; preg_match('/'.ANSWER_REGEX.'/', $question->questiontext['text'], $answerregs); ++$positionkey ) { $wrapped = new stdClass; if (isset($answerregs[ANSWER_REGEX_NORM])&& $answerregs[ANSWER_REGEX_NORM]!== ''){ $wrapped->defaultgrade = $answerregs[ANSWER_REGEX_NORM]; @@ -1087,4 +1081,3 @@ function qtype_multianswer_extract_question($text) { $question->questiontext = $question->questiontext; return $question; } -?> diff --git a/question/type/multichoice/db/install.xml b/question/type/multichoice/db/install.xml index 8a50ff9d03637..da93831b65c7f 100644 --- a/question/type/multichoice/db/install.xml +++ b/question/type/multichoice/db/install.xml @@ -1,5 +1,5 @@ - @@ -12,10 +12,13 @@ - - - - + + + + + + + @@ -23,4 +26,4 @@ - + \ No newline at end of file diff --git a/question/type/multichoice/db/upgrade.php b/question/type/multichoice/db/upgrade.php index bbd4dcfea6316..04e31965c86aa 100644 --- a/question/type/multichoice/db/upgrade.php +++ b/question/type/multichoice/db/upgrade.php @@ -52,6 +52,61 @@ function xmldb_qtype_multichoice_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2008021800, 'qtype', 'multichoice'); } + if ($oldversion < 2009021801) { + + /// Define field correctfeedbackformat to be added to question_multichoice + $table = new xmldb_table('question_multichoice'); + $field = new xmldb_field('correctfeedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'correctfeedback'); + + /// Conditionally launch add field correctfeedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + /// Define field partiallycorrectfeedbackformat to be added to question_multichoice + $field = new xmldb_field('partiallycorrectfeedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'partiallycorrectfeedback'); + + /// Conditionally launch add field partiallycorrectfeedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + /// Define field incorrectfeedbackformat to be added to question_multichoice + $field = new xmldb_field('incorrectfeedbackformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'incorrectfeedback'); + + /// Conditionally launch add field incorrectfeedbackformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $rs = $DB->get_recordset('question_multichoice'); + foreach ($rs as $record) { + if ($CFG->texteditors !== 'textarea') { + if (!empty($record->correctfeedback)) { + $record->correctfeedback = text_to_html($record->correctfeedback); + } + $record->correctfeedbackformat = FORMAT_HTML; + if (!empty($record->partiallycorrectfeedback)) { + $record->partiallycorrectfeedback = text_to_html($record->partiallycorrectfeedback); + } + $record->partiallycorrectfeedbackformat = FORMAT_HTML; + if (!empty($record->incorrectfeedback)) { + $record->incorrectfeedback = text_to_html($record->incorrectfeedback); + } + $record->incorrectfeedbackformat = FORMAT_HTML; + } else { + $record->correctfeedbackformat = FORMAT_MOODLE; + $record->partiallycorrectfeedbackformat = FORMAT_MOODLE; + $record->incorrectfeedbackformat = FORMAT_MOODLE; + } + $DB->update_record('question_multichoice', $record); + } + $rs->close(); + + /// multichoice savepoint reached + upgrade_plugin_savepoint(true, 2009021801, 'qtype', 'multichoice'); + } + return true; } diff --git a/question/type/multichoice/display.html b/question/type/multichoice/display.html index 5e4f66d6cd9ff..9acf11fd91489 100644 --- a/question/type/multichoice/display.html +++ b/question/type/multichoice/display.html @@ -2,10 +2,6 @@
- - - -
diff --git a/question/type/multichoice/edit_multichoice_form.php b/question/type/multichoice/edit_multichoice_form.php index f25a4eeec9e71..e062fc6812698 100644 --- a/question/type/multichoice/edit_multichoice_form.php +++ b/question/type/multichoice/edit_multichoice_form.php @@ -1,4 +1,22 @@ . + +defined('MOODLE_INTERNAL') || die(); + /** * Defines the editing form for the multichoice question type. * @@ -47,14 +65,14 @@ function definition_inner(&$mform) { $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice')); foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { - $mform->addElement('htmleditor', $feedbackname, get_string($feedbackname, 'qtype_multichoice'), - array('course' => $this->coursefilesid)); + $mform->addElement('editor', $feedbackname, get_string($feedbackname, 'qtype_multichoice'), + array('course' => $this->coursefilesid), $this->editoroptions); $mform->setType($feedbackname, PARAM_RAW); } } - function set_data($question) { + function data_preprocessing($question) { if (isset($question->options)){ $answers = $question->options->answers; if (count($answers)) { @@ -62,19 +80,44 @@ function set_data($question) { foreach ($answers as $answer){ $default_values['answer['.$key.']'] = $answer->answer; $default_values['fraction['.$key.']'] = $answer->fraction; - $default_values['feedback['.$key.']'] = $answer->feedback; + + // prepare question text + $draftid = file_get_submitted_draft_itemid('feedback['.$key.']'); + $default_values['feedback['.$key.']'] = array(); + $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area($draftid, $this->context->id, 'question', 'answerfeedback', empty($answer->id)?null:(int)$answer->id, null, $answer->feedback); + $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat; + $default_values['feedback['.$key.']']['itemid'] = $draftid; $key++; } } $default_values['single'] = $question->options->single; $default_values['answernumbering'] = $question->options->answernumbering; $default_values['shuffleanswers'] = $question->options->shuffleanswers; - $default_values['correctfeedback'] = $question->options->correctfeedback; - $default_values['partiallycorrectfeedback'] = $question->options->partiallycorrectfeedback; - $default_values['incorrectfeedback'] = $question->options->incorrectfeedback; + + // prepare feedback editor to display files in draft area + foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) { + $draftid = file_get_submitted_draft_itemid($feedbackname); + $text = $question->options->$feedbackname; + $feedbackformat = $feedbackname . 'format'; + $format = $question->options->$feedbackformat; + $default_values[$feedbackname] = array(); + $default_values[$feedbackname]['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'qtype_multichoice', // component + $feedbackname, // filarea + !empty($question->id)?(int)$question->id:null, // itemid + $this->fileoptions, // options + $text // text + ); + $default_values[$feedbackname]['format'] = $format; + $default_values[$feedbackname]['itemid'] = $draftid; + } + // prepare files code block ends + $question = (object)((array)$question + $default_values); } - parent::set_data($question); + return $question; } function qtype() { diff --git a/question/type/multichoice/lib.php b/question/type/multichoice/lib.php new file mode 100644 index 0000000000000..5852ecaeef8ff --- /dev/null +++ b/question/type/multichoice/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package questionbank + * @subpackage questiontypes + * @author Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_multichoice_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_multichoice', $filearea, $args, $forcedownload); +} diff --git a/question/type/multichoice/questiontype.php b/question/type/multichoice/questiontype.php index 856b59af9a8a8..c38437769878f 100644 --- a/question/type/multichoice/questiontype.php +++ b/question/type/multichoice/questiontype.php @@ -29,8 +29,8 @@ function get_question_options(&$question) { list ($usql, $params) = $DB->get_in_or_equal(explode(',', $question->options->answers)); if (!$question->options->answers = $DB->get_records_select('question_answers', "id $usql", $params, 'id')) { - echo $OUTPUT->notification('Error: Missing question answers for multichoice question'.$question->id.'!'); - return false; + echo $OUTPUT->notification('Error: Missing question answers for multichoice question'.$question->id.'!'); + return false; } return true; @@ -38,6 +38,7 @@ function get_question_options(&$question) { function save_question_options($question) { global $DB; + $context = $question->context; $result = new stdClass; if (!$oldanswers = $DB->get_records("question_answers", array("question" => $question->id), "id ASC")) { $oldanswers = array(); @@ -65,18 +66,23 @@ function save_question_options($question) { foreach ($question->answer as $key => $dataanswer) { if ($dataanswer != "") { + $feedbackformat = $question->feedback[$key]['format']; if ($answer = array_shift($oldanswers)) { // Existing answer, so reuse it $answer->answer = $dataanswer; $answer->fraction = $question->fraction[$key]; - $answer->feedback = $question->feedback[$key]; + $answer->feedbackformat = $feedbackformat; + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, $question->feedback[$key]['text']); $DB->update_record("question_answers", $answer); } else { unset($answer); $answer->answer = $dataanswer; $answer->question = $question->id; $answer->fraction = $question->fraction[$key]; - $answer->feedback = $question->feedback[$key]; + $answer->feedback = ''; + $answer->feedbackformat = $feedbackformat; $answer->id = $DB->insert_record("question_answers", $answer); + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, $question->feedback[$key]['text']); + $DB->set_field('question_answers', 'feedback', $answer->feedback, array('id'=>$answer->id)); } $answers[] = $answer->id; @@ -100,13 +106,19 @@ function save_question_options($question) { $options->answers = implode(",",$answers); $options->single = $question->single; if(isset($question->layout)){ - $options->layout = $question->layout; + $options->layout = $question->layout; } $options->answernumbering = $question->answernumbering; $options->shuffleanswers = $question->shuffleanswers; - $options->correctfeedback = trim($question->correctfeedback); - $options->partiallycorrectfeedback = trim($question->partiallycorrectfeedback); - $options->incorrectfeedback = trim($question->incorrectfeedback); + + foreach (array('correct', 'partiallycorrect', 'incorrect') as $feedbacktype) { + $feedbackname = $feedbacktype . 'feedback'; + $feedbackformat = $feedbackname . 'format'; + $feedback = $question->$feedbackname; + $options->$feedbackformat = trim($feedback['format']); + $options->$feedbackname = file_save_draft_area_files($feedback['itemid'], $context->id, 'qtype_multichoice', $feedbackname, $question->id, self::$fileoptions, trim($feedback['text'])); + } + if ($update) { $DB->update_record("question_multichoice", $options); } else { @@ -139,11 +151,11 @@ function save_question_options($question) { } /** - * Deletes question from the question-type specific tables - * - * @return boolean Success/Failure - * @param object $question The question being deleted - */ + * Deletes question from the question-type specific tables + * + * @return boolean Success/Failure + * @param object $question The question being deleted + */ function delete_question($questionid) { global $DB; $DB->delete_records("question_multichoice", array("question" => $questionid)); @@ -153,10 +165,10 @@ function delete_question($questionid) { function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) { // create an array of answerids ??? why so complicated ??? $answerids = array_values(array_map(create_function('$val', - 'return $val->id;'), $question->options->answers)); + 'return $val->id;'), $question->options->answers)); // Shuffle the answers if required if (!empty($cmoptions->shuffleanswers) and !empty($question->options->shuffleanswers)) { - $answerids = swapshuffle($answerids); + $answerids = swapshuffle($answerids); } $state->options->order = $answerids; // Create empty responses @@ -203,7 +215,7 @@ function restore_session_and_responses(&$question, &$state) { $state->responses = array_flip($state->responses); // Set the value of each element to be equal to the index array_walk($state->responses, create_function('&$a, $b', - '$a = $b;')); + '$a = $b;')); } } return true; @@ -252,6 +264,10 @@ function get_correct_responses(&$question, &$state) { function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { global $CFG; + // required by file api + $context = $this->get_context_by_category_id($question->category); + $component = 'qtype_' . $question->qtype; + $answers = &$question->options->answers; $correctanswers = $this->get_correct_responses($question, $state); $readonly = empty($options->readonly) ? '' : 'disabled="disabled"'; @@ -261,10 +277,8 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions $formatoptions->para = false; // Print formulation - $questiontext = format_text($question->questiontext, - $question->questiontextformat, - $formatoptions, $cmoptions->course); - $image = get_question_image($question); + $questiontext = format_text($question->questiontext, $question->questiontextformat, + $formatoptions, $cmoptions->course); $answerprompt = ($question->options->single) ? get_string('singleanswer', 'quiz') : get_string('multipleanswers', 'quiz'); @@ -311,11 +325,13 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions // Print the answer text $a->text = $this->number_in_style($key, $question->options->answernumbering) . - format_text($answer->answer, FORMAT_MOODLE, $formatoptions, $cmoptions->course); + format_text($answer->answer, $answer->answerformat, $formatoptions, $cmoptions->course); // Print feedback if feedback is on if (($options->feedback || $options->correct_responses) && $checked) { - $a->feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course); + // feedback for each answer + $a->feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id); + $a->feedback = format_text($a->feedback, $answer->feedbackformat, $formatoptions, $cmoptions->course); } else { $a->feedback = ''; } @@ -327,14 +343,18 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions if ($options->feedback) { if ($state->raw_grade >= $question->maxgrade/1.01) { $feedback = $question->options->correctfeedback; + $feedbacktype = 'correctfeedback'; } else if ($state->raw_grade > 0) { $feedback = $question->options->partiallycorrectfeedback; + $feedbacktype = 'partiallycorrectfeedback'; } else { $feedback = $question->options->incorrectfeedback; + $feedbacktype = 'incorrectfeedback'; } - $feedback = format_text($feedback, - $question->questiontextformat, - $formatoptions, $cmoptions->course); + + $feedback = quiz_rewrite_question_urls($feedback, 'pluginfile.php', $context->id, $component, $feedbacktype, array($state->attempt, $state->question), $question->id); + $feedbackformat = $feedbacktype . 'format'; + $feedback = format_text($feedback, $question->options->$feedbackformat, $formatoptions, $cmoptions->course); } include("$CFG->dirroot/question/type/multichoice/display.html"); @@ -357,7 +377,7 @@ function grade_responses(&$question, &$state, $cmoptions) { // Make sure we don't assign negative or too high marks $state->raw_grade = min(max((float) $state->raw_grade, - 0.0), 1.0) * $question->maxgrade; + 0.0), 1.0) * $question->maxgrade; // Apply the penalty for this attempt $state->penalty = $question->penalty * $question->maxgrade; @@ -401,7 +421,7 @@ function get_random_guess_score($question) { } return $totalfraction / count($question->options->answers); } -/// BACKUP FUNCTIONS //////////////////////////// + /// BACKUP FUNCTIONS //////////////////////////// /* * Backup the data in the question @@ -436,7 +456,7 @@ function backup($bf,$preferences,$question,$level=6) { return $status; } -/// RESTORE FUNCTIONS ///////////////// + /// RESTORE FUNCTIONS ///////////////// /* * Restores the data in the question @@ -575,31 +595,31 @@ function decode_content_links_caller($questionids, $restore, &$i) { // Decode links in the question_multichoice table. if ($multichoices = $DB->get_records_list('question_multichoice', 'question', - $questionids, '', 'id, correctfeedback, partiallycorrectfeedback, incorrectfeedback')) { + $questionids, '', 'id, correctfeedback, partiallycorrectfeedback, incorrectfeedback')) { - foreach ($multichoices as $multichoice) { - $correctfeedback = restore_decode_content_links_worker($multichoice->correctfeedback, $restore); - $partiallycorrectfeedback = restore_decode_content_links_worker($multichoice->partiallycorrectfeedback, $restore); - $incorrectfeedback = restore_decode_content_links_worker($multichoice->incorrectfeedback, $restore); - if ($correctfeedback != $multichoice->correctfeedback || + foreach ($multichoices as $multichoice) { + $correctfeedback = restore_decode_content_links_worker($multichoice->correctfeedback, $restore); + $partiallycorrectfeedback = restore_decode_content_links_worker($multichoice->partiallycorrectfeedback, $restore); + $incorrectfeedback = restore_decode_content_links_worker($multichoice->incorrectfeedback, $restore); + if ($correctfeedback != $multichoice->correctfeedback || $partiallycorrectfeedback != $multichoice->partiallycorrectfeedback || $incorrectfeedback != $multichoice->incorrectfeedback) { - $subquestion->correctfeedback = $correctfeedback; - $subquestion->partiallycorrectfeedback = $partiallycorrectfeedback; - $subquestion->incorrectfeedback = $incorrectfeedback; - $DB->update_record('question_multichoice', $multichoice); - } - - // Do some output. - if (++$i % 5 == 0 && !defined('RESTORE_SILENTLY')) { - echo "."; - if ($i % 100 == 0) { - echo "
"; + $subquestion->correctfeedback = $correctfeedback; + $subquestion->partiallycorrectfeedback = $partiallycorrectfeedback; + $subquestion->incorrectfeedback = $incorrectfeedback; + $DB->update_record('question_multichoice', $multichoice); + } + + // Do some output. + if (++$i % 5 == 0 && !defined('RESTORE_SILENTLY')) { + echo "."; + if ($i % 100 == 0) { + echo "
"; + } + backup_flush(300); } - backup_flush(300); } } - } return $status; } @@ -625,16 +645,16 @@ function number_html($qnum) { */ function number_in_style($num, $style) { switch($style) { - case 'abc': - return $this->number_html(chr(ord('a') + $num)); - case 'ABCD': - return $this->number_html(chr(ord('A') + $num)); - case '123': - return $this->number_html(($num + 1)); - case 'none': - return ''; - default: - return 'ERR'; + case 'abc': + return $this->number_html(chr(ord('a') + $num)); + case 'ABCD': + return $this->number_html(chr(ord('A') + $num)); + case '123': + return $this->number_html(($num + 1)); + case 'none': + return ''; + default: + return 'ERR'; } } @@ -708,8 +728,82 @@ function generate_test($name, $courseid = null) { return $this->save_question($question, $form, $course); } + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + // move files belonging to question component + parent::move_files($question, $newcategory); + + // move files belonging to qtype_multichoice + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'answerfeedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + + $component = 'qtype_multichoice'; + foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $filearea) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + } + + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + $itemid = reset($args); + + if (empty($question->maxgrade)) { + $question->maxgrade = $question->defaultgrade; + } + + if (in_array($filearea, array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) { + $result = $options->feedback && ($itemid == $question->id); + if (!$result) { + return false; + } + if ($state->raw_grade >= $question->maxgrade/1.01) { + $feedbacktype = 'correctfeedback'; + } else if ($state->raw_grade > 0) { + $feedbacktype = 'partiallycorrectfeedback'; + } else { + $feedbacktype = 'incorrectfeedback'; + } + if ($feedbacktype != $filearea) { + return false; + } + return true; + } else if ($component == 'question' && $filearea == 'answerfeedback') { + return $options->feedback && (array_key_exists($itemid, $question->options->answers)); + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } } // Register this question type with the question bank. question_register_questiontype(new question_multichoice_qtype()); - diff --git a/question/type/multichoice/version.php b/question/type/multichoice/version.php index 0d354e821d2bd..c0b4d8672c8d6 100644 --- a/question/type/multichoice/version.php +++ b/question/type/multichoice/version.php @@ -1,6 +1,6 @@ version = 2009021800; +$plugin->version = 2009021801; $plugin->requires = 2007101000; diff --git a/question/type/numerical/db/install.xml b/question/type/numerical/db/install.xml index 735fc4b466068..c4f3ee44a0521 100644 --- a/question/type/numerical/db/install.xml +++ b/question/type/numerical/db/install.xml @@ -1,5 +1,5 @@ - @@ -23,8 +23,9 @@ - - + + + @@ -50,4 +51,4 @@ - + \ No newline at end of file diff --git a/question/type/numerical/db/upgrade.php b/question/type/numerical/db/upgrade.php index d2ff825cbd55a..96b547a5993ba 100644 --- a/question/type/numerical/db/upgrade.php +++ b/question/type/numerical/db/upgrade.php @@ -51,6 +51,35 @@ function xmldb_qtype_numerical_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2009100100, 'qtype', 'numerical'); } + if ($oldversion < 2009100101) { + + // Define field instructionsformat to be added to question_numerical_options + $table = new xmldb_table('question_numerical_options'); + $field = new xmldb_field('instructionsformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'instructions'); + + // Conditionally launch add field instructionsformat + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $rs = $DB->get_recordset('question_numerical_options'); + foreach ($rs as $record) { + if ($CFG->texteditors !== 'textarea') { + if (!empty($record->instructions)) { + $record->instructions = text_to_html($record->instructions); + } + $record->instructionsformat = FORMAT_HTML; + } else { + $record->instructionsformat = FORMAT_MOODLE; + } + $DB->update_record('question_numerical_options', $record); + } + $rs->close(); + + // numerical savepoint reached + upgrade_plugin_savepoint(true, 2009100101, 'qtype', 'numerical'); + } + return true; } diff --git a/question/type/numerical/display.html b/question/type/numerical/display.html index 4262283926d39..c81bd1e7a64a5 100644 --- a/question/type/numerical/display.html +++ b/question/type/numerical/display.html @@ -2,10 +2,6 @@
- - - -
@@ -237,7 +233,7 @@
options->instructions)){?>
- options->instructions, true, $formatoptions, $cmoptions->course);?> + options->instructions, $question->options->instructionsformat, $formatoptions, $cmoptions->course);?>
@@ -250,5 +246,3 @@ print_question_submit_buttons($question, $state, $cmoptions, $options); ?>
- - diff --git a/question/type/numerical/edit_numerical_form.php b/question/type/numerical/edit_numerical_form.php index 598e0c79b01bc..8ed3aff89eabb 100644 --- a/question/type/numerical/edit_numerical_form.php +++ b/question/type/numerical/edit_numerical_form.php @@ -1,4 +1,20 @@ . + /** * Defines the editing form for the numerical question type. * @@ -39,30 +55,36 @@ function definition_inner(&$mform) { $creategrades->gradeoptions); //------------------------------------------------------------------------------------------ $QTYPES['numerical']->add_units_options($mform,$this); - $QTYPES['numerical']->add_units_elements($mform,$this); + $QTYPES['numerical']->add_units_elements($mform,$this); } - function set_data($question) { - global $QTYPES ; + function data_preprocessing($question) { + global $QTYPES ; if (isset($question->options)){ - /* $default_values['unitgradingtype'] = $question->options->unitgradingtype ; - $default_values['unitpenalty'] = $question->options->unitpenalty ; - $default_values['showunits'] = $question->options->showunits ; - $default_values['unitsleft'] = $question->options->unitsleft ; - $default_values['instructions'] = $question->options->instructions ; -*/ $answers = $question->options->answers; if (count($answers)) { $key = 0; foreach ($answers as $answer){ + $draftid = file_get_submitted_draft_itemid('feedback['.$key.']'); $default_values['answer['.$key.']'] = $answer->answer; $default_values['fraction['.$key.']'] = $answer->fraction; $default_values['tolerance['.$key.']'] = $answer->tolerance; - $default_values['feedback['.$key.']'] = $answer->feedback; + $default_values['feedback['.$key.']'] = array(); + $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat; + $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'question', // component + 'answerfeedback', // filarea + !empty($answer->id)?(int)$answer->id:null, // itemid + $this->fileoptions, // options + $answer->feedback // text + ); + $default_values['feedback['.$key.']']['itemid'] = $draftid; $key++; } } - $QTYPES['numerical']->set_numerical_unit_data($question,$default_values); + $QTYPES['numerical']->set_numerical_unit_data($this, $question, $default_values); /* if (isset($question->options->units)){ $units = array_values($question->options->units); @@ -75,8 +97,9 @@ function set_data($question) { }*/ $question = (object)((array)$question + $default_values); } - parent::set_data($question); + return $question; } + function validation($data, $files) { global $QTYPES; $errors = parent::validation($data, $files); @@ -95,7 +118,7 @@ function validation($data, $files) { if ($data['fraction'][$key] == 1) { $maxgrade = true; } - } else if ($data['fraction'][$key] != 0 || !html_is_blank($data['feedback'][$key])) { + } else if ($data['fraction'][$key] != 0 || !html_is_blank($data['feedback'][$key]['text'])) { $errors["answer[$key]"] = get_string('answermustbenumberorstar', 'qtype_numerical'); $answercount++; } @@ -110,6 +133,7 @@ function validation($data, $files) { return $errors; } + function qtype() { return 'numerical'; } diff --git a/question/type/numerical/lib.php b/question/type/numerical/lib.php new file mode 100644 index 0000000000000..0b83d90cba108 --- /dev/null +++ b/question/type/numerical/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package questionbank + * @subpackage questiontypes + * @author Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_numerical_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_numerical', $filearea, $args, $forcedownload); +} diff --git a/question/type/numerical/questiontype.php b/question/type/numerical/questiontype.php index 33062593a5624..acb91008030e6 100644 --- a/question/type/numerical/questiontype.php +++ b/question/type/numerical/questiontype.php @@ -1,10 +1,26 @@ . + /** * @author Martin Dougiamas and many others. Tim Hunt. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package questionbank * @subpackage questiontypes - *//** */ + */ require_once("$CFG->dirroot/question/type/shortanswer/questiontype.php"); @@ -19,6 +35,7 @@ * @package questionbank * @subpackage questiontypes */ + class question_numerical_qtype extends question_shortanswer_qtype { public $virtualqtype = false; @@ -52,7 +69,7 @@ function get_question_options(&$question) { } $this->get_numerical_units($question); //get_numerical_options() need to know if there are units - // to set correctly default values + // to set correctly default values $this->get_numerical_options($question); // If units are defined we strip off the default unit from the answer, if @@ -75,7 +92,7 @@ function get_numerical_options(&$question) { if (!$options = $DB->get_record('question_numerical_options', array('question' => $question->id))) { $question->options->unitgradingtype = 0; // total grade $question->options->unitpenalty = 0; - // the default + // the default if ($defaultunit = $this->get_default_numerical_unit($question)) { // so units can be graded $question->options->showunits = NUMERICALQUESTIONUNITTEXTINPUTDISPLAY ; @@ -85,18 +102,20 @@ function get_numerical_options(&$question) { $question->options->showunits = NUMERICALQUESTIONUNITNODISPLAY ; } $question->options->unitsleft = 0 ; - $question->options->instructions = '' ; + $question->options->instructions = ''; + $question->options->instructionsformat = editors_get_preferred_format(); } else { $question->options->unitgradingtype = $options->unitgradingtype; $question->options->unitpenalty = $options->unitpenalty; - $question->options->showunits = $options->showunits ; - $question->options->unitsleft = $options->unitsleft ; - $question->options->instructions = $options->instructions ; + $question->options->showunits = $options->showunits; + $question->options->unitsleft = $options->unitsleft; + $question->options->instructions = $options->instructions; + $question->options->instructionsformat = $options->instructionsformat; } - return true; } + function get_numerical_units(&$question) { global $DB; if ($units = $DB->get_records('question_numerical_units', array('question' => $question->id), 'id ASC')) { @@ -127,6 +146,8 @@ function get_default_numerical_unit(&$question) { */ function save_question_options($question) { global $DB; + $context = $question->context; + // Get old versions of the objects if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { $oldanswers = array(); @@ -148,7 +169,7 @@ function save_question_options($question) { foreach ($question->answer as $key => $dataanswer) { // Check for, and ingore, completely blank answer from the form. if (trim($dataanswer) == '' && $question->fraction[$key] == 0 && - html_is_blank($question->feedback[$key])) { + html_is_blank($question->feedback[$key]['text'])) { continue; } @@ -163,13 +184,23 @@ function save_question_options($question) { } } $answer->fraction = $question->fraction[$key]; - $answer->feedback = trim($question->feedback[$key]); + + $feedbacktext = trim($question->feedback[$key]['text']); + $draftid = $question->feedback[$key]['itemid']; + + + $answer->feedbackformat = $question->feedback[$key]['format']; if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it + $feedbacktext = file_save_draft_area_files($draftid, $context->id, 'question', 'answerfeedback', $oldanswer->id, self::$fileoptions, $feedbacktext); + $answer->feedback = $feedbacktext; $answer->id = $oldanswer->id; $DB->update_record("question_answers", $answer); } else { // This is a completely new answer + $answer->feedback = $feedbacktext; $answer->id = $DB->insert_record("question_answers", $answer); + $feedbacktext = file_save_draft_area_files($draftid, $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, $feedbacktext); + $DB->set_field('question_answers', 'feedback', $feedbacktext, array('id'=>$answer->id)); } // Set up the options object @@ -220,10 +251,11 @@ function save_question_options($question) { function save_numerical_options(&$question) { global $DB; + $result = new stdClass; // numerical options $update = true ; - $options = $DB->get_record("question_numerical_options", array("question" => $question->id)); + $options = $DB->get_record('question_numerical_options', array('question' => $question->id)); if (!$options) { $update = false; $options = new stdClass; @@ -246,7 +278,7 @@ function save_numerical_options(&$question) { }else { $options->showunits = $question->showunits1; } - }else { + }else { if(isset($question->showunits)){ $options->showunits = $question->showunits; }else { @@ -264,22 +296,27 @@ function save_numerical_options(&$question) { }else { $options->unitsleft = 0 ; } + $options->instructionsformat = $question->instructions['format']; if(isset($question->instructions)){ - $options->instructions = trim($question->instructions); + $options->instructions = trim($question->instructions['text']); }else { $options->instructions = '' ; } + $component = 'qtype_' . $question->qtype; + $options->instructions = file_save_draft_area_files($question->instructions['itemid'], + $question->context->id, // context + $component, // component + 'instruction', // filearea + $question->id, // itemid + self::$fileoptions, // options + $question->instructions['text'] // text + ); if ($update) { - if (!$DB->update_record("question_numerical_options", $options)) { - $result->error = "Could not update numerical question options! (id=$options->id)"; - return $result; - } + $DB->update_record("question_numerical_options", $options); } else { - if (!$DB->insert_record("question_numerical_options", $options)) { - $result->error = "Could not insert numerical question options!"; - return $result; - } + $id = $DB->insert_record("question_numerical_options", $options); } + return $result; } @@ -320,7 +357,7 @@ function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) $state->responses = array(); $state->responses['answer'] = ''; $state->responses['unit'] = ''; - + return true; } function restore_session_and_responses(&$question, &$state) { @@ -356,10 +393,10 @@ function split_old_answer($rawresponse, $units, &$answer ,&$unit ) { $rawresponse = str_replace($search, $replace, trim($rawresponse)); if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~', $rawresponse, $responseparts)) { - if(isset($responseparts[5]) ){ + if(isset($responseparts[5]) ){ $unit = $responseparts[5] ; } - if(isset($responseparts[1]) ){ + if(isset($responseparts[1]) ){ $answer = $responseparts[1] ; } } @@ -404,30 +441,33 @@ function delete_question($questionid) { */ function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { global $CFG; + $context = $this->get_context_by_category_id($question->category); $readonly = empty($options->readonly) ? '' : 'readonly="readonly"'; $formatoptions = new stdClass; $formatoptions->noclean = true; $formatoptions->para = false; $nameprefix = $question->name_prefix; + $component = 'qtype_' . $question->qtype; + // rewrite instructions text + $question->options->instructions = quiz_rewrite_question_urls($question->options->instructions, 'pluginfile.php', $context->id, $component, 'instruction', array($state->attempt, $state->question), $question->id); /// Print question text and media $questiontext = format_text($question->questiontext, $question->questiontextformat, $formatoptions, $cmoptions->course); - $image = get_question_image($question); /// Print input controls // as the entry is controlled the question type here is numerical // In all cases there is a text input for the number - // If $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY + // If $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY // there is an additional text input for the unit // If $question->options->showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY" // radio elements display the defined unit // The code allows the input number elememt to be displayed // before i.e. at left or after at rigth of the unit variants. + $nameanswer = "name=\"".$question->name_prefix."answer\""; $nameunit = "name=\"".$question->name_prefix."unit\""; - $nameanswer = "name=\"".$question->name_prefix."answer\""; if (isset($state->responses['']) && $state->responses[''] != '' && !isset($state->responses['answer'])){ $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] ); } @@ -438,7 +478,7 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions } if (isset($state->responses['unit']) && $state->responses['unit']!='') { $valueunit = ' value="'.s($state->responses['unit']).'" '; - } else { + } else { $valueunit = ' value="" '; if ($question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ){ $valueunit = ' value="'.s($question->options->units[0]->unit).'" '; @@ -464,72 +504,75 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions //this is OK for the first answer with a good response // having to test for * so response as long as not empty $response = $this->extract_numerical_response($state->responses['answer']); - $break = 0 ; + $break = 0 ; foreach($question->options->answers as $answer) { - // if * then everything has the $answer->fraction value + // if * then everything has the $answer->fraction value if ($answer->answer !== '*' ) { $this->get_tolerance_interval($answer); } - + + $answer->feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id); if ($answer->answer === '*') { $answerasterisk = true ; - $rawgrade = $answer->fraction ; + $rawgrade = $answer->fraction ; $class = question_get_feedback_class($answer->fraction); $feedbackimg = question_get_feedback_image($answer->fraction); $classunitvalue = $class ; - $classunit = question_get_feedback_class($answer->fraction); + $classunit = question_get_feedback_class($answer->fraction); $feedbackimgunit = question_get_feedback_image($answer->fraction, $options->feedback); if ($answer->feedback) { $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course); } - if ( isset($question->options->units)) - { + if ( isset($question->options->units)) + { $valid_numerical_unit = true ; } $break = 1 ; } else if ($response !== false && isset($question->options->units) && count($question->options->units) > 0) { $hasunits = 1 ; - foreach($question->options->units as $key => $unit){ + foreach($question->options->units as $key => $unit){ // The student did type a number, so check it with tolerances. - $testresponse = $response /$unit->multiplier ; + $testresponse = $response /$unit->multiplier ; if($answer->min <= $testresponse && $testresponse <= $answer->max) { $unittested = $unit->unit ; - $rawgrade = $answer->fraction ; + $rawgrade = $answer->fraction ; $class = question_get_feedback_class($answer->fraction); $feedbackimg = question_get_feedback_image($answer->fraction); if ($answer->feedback) { $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course); } - if($state->responses['unit'] == $unit->unit){ + if($state->responses['unit'] == $unit->unit){ $classunitvalue = $answer->fraction ; - }else { + }else { $classunitvalue == 0 ; } - $classunit = question_get_feedback_class($classunitvalue); + $classunit = question_get_feedback_class($classunitvalue); $feedbackimgunit = question_get_feedback_image($classunitvalue, $options->feedback); - $break = 1 ; + $break = 1 ; break; } } - }else if($response !== false && ($answer->min <= $response && $response <= $answer->max) ) { - $rawgrade = $answer->fraction ; + } else if($response !== false && ($answer->min <= $response && $response <= $answer->max) ) { + $rawgrade = $answer->fraction ; $class = question_get_feedback_class($answer->fraction); $feedbackimg = question_get_feedback_image($answer->fraction); if ($answer->feedback) { - $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course); + $feedback = format_text($answer->feedback, $answer->feedbackformat, $formatoptions, $cmoptions->course); } $break = 1 ; } - if ($break) break; + if ($break) { + break; + } } } $state->options->raw_unitpenalty = 0 ; $raw_unitpenalty = 0 ; - if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY || + if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY || $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ) { $classunitvalue = 1 ; } - + if($classunitvalue == 0){ if($question->options->unitgradingtype == 1){ @@ -537,10 +580,9 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions }else { $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade; } - $state->options->raw_unitpenalty = $raw_unitpenalty ; + $state->options->raw_unitpenalty = $raw_unitpenalty ; } - /// Removed correct answer, to be displayed later MDL-7496 include("$CFG->dirroot/question/type/numerical/display.html"); } @@ -580,7 +622,7 @@ function compare_responses(&$question, $state, $teststate) { * and but NOT the unit into account. Returns a true for if a response matches the * answer or in one of the unit , false if it doesn't. * the total grading will see if the unit match. - * if unit != -1 then the test is done only on this unit + * if unit != -1 then the test is done only on this unit */ function test_response(&$question, &$state, $answer ) { // Deal with the match anything answer. @@ -589,17 +631,17 @@ function test_response(&$question, &$state, $answer ) { } /* To be able to test (old) questions that do not have an unit * input element the test is done using the $state->responses[''] - * which contains the response which is analyzed by extract_numerical_response() + * which contains the response which is analyzed by extract_numerical_response() * If the data comes from the numerical or calculated display - * the $state->responses['unit'] comes from either + * the $state->responses['unit'] comes from either * a multichoice radio element NUMERICALQUESTIONUNITMULTICHOICEDISPLAY * where the $state->responses['unit'] value is the key => unit object * in the the $question->options->units array * or an input text element NUMERICALUNITTEXTINPUTDISPLAY * which contains the student response * for NUMERICALQUESTIONUNITTEXTDISPLAY and NUMERICALQUESTIONUNITNODISPLAY - * - */ + * + */ if (!isset($state->responses['answer']) && isset($state->responses[''])){ $state->responses['answer'] = $state->responses['']; @@ -611,14 +653,14 @@ function test_response(&$question, &$state, $answer ) { // The student did type a number, so check it with tolerances. $this->get_tolerance_interval($answer); if ($answer->min <= $response && $response <= $answer->max){ - return true; + return true; } - // testing for other units + // testing for other units if ( isset($question->options->units) && count($question->options->units) > 0) { - foreach($question->options->units as $key =>$unit){ + foreach($question->options->units as $key =>$unit){ $testresponse = $response /$unit->multiplier ; if($answer->min <= $testresponse && $testresponse<= $answer->max) { - return true; + return true; } } } @@ -626,11 +668,11 @@ function test_response(&$question, &$state, $answer ) { } /** - * Performs response processing and grading + * Performs response processing and grading * The function was redefined for handling correctly the two parts - * number and unit of numerical or calculated questions - * The code handles also the case when there no unit defined by the user or - * when used in a multianswer (Cloze) question. + * number and unit of numerical or calculated questions + * The code handles also the case when there no unit defined by the user or + * when used in a multianswer (Cloze) question. * This function performs response processing and grading and updates * the state accordingly. * @return boolean Indicates success or failure. @@ -652,7 +694,7 @@ function grade_responses(&$question, &$state, $cmoptions) { if (!isset($state->responses['answer']) && isset($state->responses[''])){ $state->responses['answer'] = $state->responses['']; } - + //to apply the unit penalty we need to analyse the response in a more complex way //the apply_unit() function analysis could be used to obtain the infos // however it is used to detect good or bad numbers but also @@ -664,33 +706,33 @@ function grade_responses(&$question, &$state, $cmoptions) { $hasunits = 0 ; $response = $this->extract_numerical_response($state->responses['answer']); $answerasterisk = false ; - - $break = 0 ; + + $break = 0 ; foreach($question->options->answers as $answer) { if ($answer->answer !== '*' ) { // The student did type a number, so check it with tolerances. $this->get_tolerance_interval($answer); } - // if * then everything is OK even unit + // if * then everything is OK even unit if ($answer->answer === '*') { $state->raw_grade = $answer->fraction; - if ( isset($question->options->units)){ + if ( isset($question->options->units)){ $valid_numerical_unit = true ; } - $answerasterisk = true ; + $answerasterisk = true ; $break = 1 ; }else if ($response !== false && isset($question->options->units) && count($question->options->units) > 0) { $hasunits = 1 ; - foreach($question->options->units as $key => $unit){ - $testresponse = $response /$unit->multiplier ; - + foreach($question->options->units as $key => $unit){ + $testresponse = $response /$unit->multiplier ; + if($answer->min <= $testresponse && $testresponse <= $answer->max) { $state->raw_grade = $answer->fraction; $unittested = $unit->unit ; - $break = 1 ; + $break = 1 ; break; - } + } } }else if ($response !== false) { if($this->test_response($question, $state, $answer)) { @@ -700,9 +742,9 @@ function grade_responses(&$question, &$state, $cmoptions) { } if ($break) break; } //foreach($question->options - + // in all cases the unit should be tested - if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY || + if( $question->options->showunits == NUMERICALQUESTIONUNITNODISPLAY || $question->options->showunits == NUMERICALQUESTIONUNITTEXTDISPLAY ) { $valid_numerical_unit = true ; }else { @@ -714,7 +756,7 @@ function grade_responses(&$question, &$state, $cmoptions) { $valid_numerical_unit = true ; } } - + // apply unit penalty $raw_unitpenalty = 0 ; if(!empty($question->options->unitpenalty)&& $valid_numerical_unit != true ){ @@ -725,7 +767,7 @@ function grade_responses(&$question, &$state, $cmoptions) { } $state->raw_grade -= $raw_unitpenalty ; } - + // Make sure we don't assign negative or too high marks. $state->raw_grade = min(max((float) $state->raw_grade, 0.0), 1.0) * $question->maxgrade; @@ -857,7 +899,7 @@ function extract_numerical_response($rawresponse) { // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and , if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) { $replace = array('', ''); - }else { // remove spaces and normalise , to a . . + }else { // remove spaces and normalise , to a . . $replace = array('', '.'); } $rawresponse = str_replace($search, $replace, $rawresponse); @@ -890,7 +932,7 @@ function apply_unit_old($rawresponse, $units) { // test if a . is present or there are multiple , (i.e. 2,456,789 ) so that we don't need spaces and , if ( strpos($rawresponse,'.' ) !== false || substr_count($rawresponse,',') > 1 ) { $replace = array('', ''); - }else { // remove spaces and normalise , to a . . + }else { // remove spaces and normalise , to a . . $replace = array('', '.'); } $rawresponse = str_replace($search, $replace, $rawresponse); @@ -920,10 +962,10 @@ function apply_unit_old($rawresponse, $units) { } /** - * function used in function definition_inner() - * of edit_..._form.php for - * numerical, calculated, calculatedsimple - */ + * function used in function definition_inner() + * of edit_..._form.php for + * numerical, calculated, calculatedsimple + */ function add_units_options(&$mform, &$that){ $mform->addElement('header', 'unithandling', get_string('unitshandling', 'qtype_numerical')); // Units are graded @@ -938,8 +980,7 @@ function add_units_options(&$mform, &$that){ $showunits0grp[] =& $mform->createElement('radio', 'showunits0', get_string('unitedit', 'qtype_numerical'), get_string('editableunittext', 'qtype_numerical'),0); $showunits0grp[] =& $mform->createElement('radio', 'showunits0', get_string('selectunits', 'qtype_numerical') , get_string('unitchoice', 'qtype_numerical'),1); $mform->addGroup($showunits0grp, 'showunits0grp', get_string('studentunitanswer', 'qtype_numerical'),' OR ' , false); - $mform->addElement('htmleditor', 'instructions', get_string('instructions', 'qtype_numerical'), - array('rows' => 10, 'course' => $that->coursefilesid)); + $mform->addElement('editor', 'instructions', get_string('instructions', 'qtype_numerical'), null, $that->editoroptions); $mform->addElement('static', 'separator1', '
', '
'); // Units are not graded $mform->addElement('radio', 'unitrole', get_string('unitnotgraded', 'qtype_numerical'), get_string('onlynumerical', 'qtype_numerical'),1); @@ -967,6 +1008,8 @@ function add_units_options(&$mform, &$that){ $mform->disabledIf('unitsleft', 'showunits1','eq','3'); $mform->disabledIf('showunits1','unitrole','eq','0'); $mform->disabledIf('showunits0','unitrole','eq','1'); + + } /** @@ -974,7 +1017,7 @@ function add_units_options(&$mform, &$that){ * of edit_..._form.php for * numerical, calculated, calculatedsimple */ - function add_units_elements(& $mform,& $that) { + function add_units_elements(& $mform,& $that) { $repeated = array(); $repeated[] =& $mform->createElement('header', 'unithdr', get_string('unithdr', 'qtype_numerical', '{no}')); @@ -1006,31 +1049,48 @@ function add_units_elements(& $mform,& $that) { } /** - * function used in in function setdata () - * of edit_..._form.php for - * numerical, calculated, calculatedsimple - */ - function set_numerical_unit_data(&$question,&$default_values){ + * function used in in function data_preprocessing() of edit_numerical_form.php for + * numerical, calculated, calculatedsimple + */ + function set_numerical_unit_data($mform, &$question, &$default_values){ + + list($categoryid) = explode(',', $question->category); + $context = $this->get_context_by_category_id($categoryid); if (isset($question->options)){ $default_values['unitgradingtype'] = $question->options->unitgradingtype ; $default_values['unitpenalty'] = $question->options->unitpenalty ; switch ($question->options->showunits){ case 'O' : - case '1' : + case '1' : $default_values['showunits0'] = $question->options->showunits ; $default_values['unitrole'] = 0 ; break; case '2' : - case '3' : + case '3' : $default_values['showunits1'] = $question->options->showunits ; $default_values['unitrole'] = 1 ; break; - } + } $default_values['unitsleft'] = $question->options->unitsleft ; - $default_values['instructions'] = $question->options->instructions ; - - if (isset($question->options->units)){ + + // processing files + $component = 'qtype_' . $question->qtype; + $draftid = file_get_submitted_draft_itemid('instructions'); + $default_values['instructions'] = array(); + $default_values['instructions']['format'] = $question->options->instructionsformat; + $default_values['instructions']['text'] = file_prepare_draft_area( + $draftid, // draftid + $context->id, // context + $component, // component + 'instruction', // filarea + !empty($question->id)?(int)$question->id:null, // itemid + $mform->fileoptions, // options + $question->options->instructions // text + ); + $default_values['instructions']['itemid'] = $draftid; + + if (isset($question->options->units)) { $units = array_values($question->options->units); if (!empty($units)) { foreach ($units as $key => $unit){ @@ -1042,11 +1102,11 @@ function set_numerical_unit_data(&$question,&$default_values){ } } -/** - * function use in in function validation() - * of edit_..._form.php for - * numerical, calculated, calculatedsimple - */ + /** + * function use in in function validation() + * of edit_..._form.php for + * numerical, calculated, calculatedsimple + */ function validate_numerical_options(& $data, & $errors){ $units = $data['unit']; @@ -1055,9 +1115,9 @@ function validate_numerical_options(& $data, & $errors){ }else { $showunits = $data['showunits1']; } - - if (($showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY) || - ($showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY ) || + + if (($showunits == NUMERICALQUESTIONUNITTEXTINPUTDISPLAY) || + ($showunits == NUMERICALQUESTIONUNITMULTICHOICEDISPLAY ) || ($showunits == NUMERICALQUESTIONUNITTEXTDISPLAY )){ if (trim($units[0]) == ''){ $errors['unit[0]'] = 'You must set a valid unit name' ; @@ -1072,8 +1132,8 @@ function validate_numerical_options(& $data, & $errors){ } } } - - + + // Check double units. $alreadyseenunits = array(); if (isset($data['unit'])) { @@ -1106,7 +1166,7 @@ function validate_numerical_options(& $data, & $errors){ } } - } + } } } @@ -1284,6 +1344,71 @@ function generate_test($name, $courseid = null) { return $this->save_question($question, $form, $course); } + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + parent::move_files($question, $newcategory); + + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'answerfeedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + $component = 'qtype_numerical'; + $filearea = 'instruction'; + $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + $itemid = reset($args); + if ($component == 'question' && $filearea == 'answerfeedback') { + $result = $options->feedback && array_key_exists($itemid, $question->options->answers); + if (!$result) { + return false; + } + foreach($question->options->answers as $answer) { + if ($this->test_response($question, $state, $answer)) { + return true; + } + } + return false; + } else if ($filearea == 'instruction') { + if ($itemid != $question->id) { + return false; + } else { + return true; + } + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } } // INITIATION - Without this line the question type is not in use. diff --git a/question/type/numerical/version.php b/question/type/numerical/version.php index 168f4ff99cc71..ca04978a12415 100644 --- a/question/type/numerical/version.php +++ b/question/type/numerical/version.php @@ -1,6 +1,6 @@ version = 2009100100; +$plugin->version = 2009100101; $plugin->requires = 2007101000; diff --git a/question/type/questiontype.php b/question/type/questiontype.php index 54e44956e42fe..e5767afa6b59c 100644 --- a/question/type/questiontype.php +++ b/question/type/questiontype.php @@ -1,4 +1,20 @@ . + /** * The default questiontype class. * @@ -30,6 +46,7 @@ * @subpackage questiontypes */ class default_questiontype { + public static $fileoptions = array('subdirs'=>false, 'maxfiles'=>-1, 'maxbytes'=>0); /** * Name of the question type @@ -294,31 +311,35 @@ function get_heading($adding = false){ */ function save_question($question, $form, $course) { global $USER, $DB, $OUTPUT; + + list($question->category) = explode(',', $form->category); + $context = $this->get_context_by_category_id($question->category); + // This default implementation is suitable for most // question types. // First, save the basic question itself $question->name = trim($form->name); - $question->questiontext = trim($form->questiontext); - $question->questiontextformat = $form->questiontextformat; $question->parent = isset($form->parent) ? $form->parent : 0; $question->length = $this->actual_number_of_questions($question); $question->penalty = isset($form->penalty) ? $form->penalty : 0; - if (empty($form->image)) { - $question->image = ''; + if (empty($form->questiontext['text'])) { + $question->questiontext = ''; } else { - $question->image = $form->image; + $question->questiontext = trim($form->questiontext['text']);; } + $question->questiontextformat = !empty($form->questiontext['format'])?$form->questiontext['format']:0; - if (empty($form->generalfeedback)) { + if (empty($form->generalfeedback['text'])) { $question->generalfeedback = ''; } else { - $question->generalfeedback = trim($form->generalfeedback); + $question->generalfeedback = trim($form->generalfeedback['text']); } + $question->generalfeedbackformat = !empty($form->generalfeedback['format'])?$form->generalfeedback['format']:0; if (empty($question->name)) { - $question->name = shorten_text(strip_tags($question->questiontext), 15); + $question->name = shorten_text(strip_tags($form->questiontext['text']), 15); if (empty($question->name)) { $question->name = '-'; } @@ -332,14 +353,16 @@ function save_question($question, $form, $course) { $question->defaultgrade = $form->defaultgrade; } - list($question->category) = explode(',', $form->category); - if (!empty($question->id)) { /// Question already exists, update. $question->modifiedby = $USER->id; $question->timemodified = time(); - $DB->update_record('question', $question); + // process queston text + $question->questiontext = file_save_draft_area_files($form->questiontext['itemid'], $context->id, 'question', 'questiontext', (int)$question->id, self::$fileoptions, $question->questiontext); + // process feedback text + $question->generalfeedback = file_save_draft_area_files($form->generalfeedback['itemid'], $context->id, 'question', 'generalfeedback', (int)$question->id, self::$fileoptions, $question->generalfeedback); + $DB->update_record('question', $question); } else { /// New question. // Set the unique code @@ -349,6 +372,12 @@ function save_question($question, $form, $course) { $question->timecreated = time(); $question->timemodified = time(); $question->id = $DB->insert_record('question', $question); + // process queston text + $question->questiontext = file_save_draft_area_files($form->questiontext['itemid'], $context->id, 'question', 'questiontext', (int)$question->id, self::$fileoptions, $question->questiontext); + // process feedback text + $question->generalfeedback = file_save_draft_area_files($form->generalfeedback['itemid'], $context->id, 'question', 'generalfeedback', (int)$question->id, self::$fileoptions, $question->generalfeedback); + + $DB->update_record('question', $question); } // Now to save all the answers and type-specific options @@ -356,11 +385,14 @@ function save_question($question, $form, $course) { $form->qtype = $question->qtype; $form->category = $question->category; $form->questiontext = $question->questiontext; + $form->questiontextformat = $question->questiontextformat; + // current context + $form->context = $context; $result = $this->save_question_options($form); if (!empty($result->error)) { - print_error('questionsaveerror', 'question', '', $result->error); + print_error($result->error); } if (!empty($result->notice)) { @@ -888,12 +920,18 @@ protected function find_standard_scripts() { * @param object $cmoptions * @param object $options An object describing the rendering options. */ - function print_question(&$question, &$state, $number, $cmoptions, $options) { + function print_question(&$question, &$state, $number, $cmoptions, $options, $context=null) { /* The default implementation should work for most question types provided the member functions it calls are overridden where required. The layout is determined by the template question.html */ global $CFG, $OUTPUT; + + $context = $this->get_context_by_category_id($question->category); + $question->questiontext = quiz_rewrite_question_urls($question->questiontext, 'pluginfile.php', $context->id, 'question', 'questiontext', array($state->attempt, $state->question), $question->id); + + $question->generalfeedback = quiz_rewrite_question_urls($question->generalfeedback, 'pluginfile.php', $context->id, 'question', 'generalfeedback', array($state->attempt, $state->question), $question->id); + $isgraded = question_state_is_graded($state->last_graded); if (isset($question->randomquestionid)) { @@ -908,7 +946,7 @@ function print_question(&$question, &$state, $number, $cmoptions, $options) { $generalfeedback = ''; if ($isgraded && $options->generalfeedback) { $generalfeedback = $this->format_text($question->generalfeedback, - $question->questiontextformat, $cmoptions); + $question->generalfeedbackformat, $cmoptions); } $grade = ''; @@ -1246,6 +1284,22 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions .' been implemented for question type '.$this->name()); } + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + + if ($component == 'question' && $filearea == 'questiontext') { + // Question text always visible. + return true; + + } else if ($component == 'question' && $filearea = 'generalfeedback') { + return $options->generalfeedback && question_state_is_graded($state->last_graded); + + } else { + // Unrecognised component or filearea. + return false; + } + } + /** * Prints the submit button(s) for the question in the given state * @@ -1494,29 +1548,6 @@ function format_text($text, $textformat, $cmoptions = NULL) { */ function find_file_links($question, $courseid){ $urls = array(); - - /// Question image - if ($question->image != ''){ - if (substr(strtolower($question->image), 0, 7) == 'http://') { - $matches = array(); - - //support for older questions where we have a complete url in image field - if (preg_match('!^'.question_file_links_base_url($courseid).'(.*)!i', $question->image, $matches)){ - if ($cleanedurl = question_url_check($urls[$matches[2]])){ - $urls[$cleanedurl] = null; - } - } - } else { - if ($question->image != ''){ - if ($cleanedurl = question_url_check($question->image)){ - $urls[$cleanedurl] = null;//will be set later - } - } - - } - - } - /// Questiontext and general feedback. $urls += question_find_file_links_from_html($question->questiontext, $courseid); $urls += question_find_file_links_from_html($question->generalfeedback, $courseid); @@ -1557,21 +1588,6 @@ function find_file_links($question, $courseid){ function replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination){ global $CFG, $DB; $updateqrec = false; - - /// Question image - if (!empty($question->image)){ - //support for older questions where we have a complete url in image field - if (substr(strtolower($question->image), 0, 7) == 'http://') { - $questionimage = preg_replace('!^'.question_file_links_base_url($fromcourseid).preg_quote($url, '!').'$!i', $destination, $question->image, 1); - } else { - $questionimage = preg_replace('!^'.preg_quote($url, '!').'$!i', $destination, $question->image, 1); - } - if ($questionimage != $question->image){ - $question->image = $questionimage; - $updateqrec = true; - } - } - /// Questiontext and general feedback. $question->questiontext = question_replace_file_links_in_html($question->questiontext, $fromcourseid, $tocourseid, $url, $destination, $updateqrec); $question->generalfeedback = question_replace_file_links_in_html($question->generalfeedback, $fromcourseid, $tocourseid, $url, $destination, $updateqrec); @@ -1802,5 +1818,45 @@ function generate_test($name, $courseid=null) { $question->qtype = $this->qtype; return array($form, $question); } -} + /** + * Get question context by category id + * @param int $category + * @return object $context + */ + function get_context_by_category_id($category) { + global $DB; + $contextid = $DB->get_field('question_categories', 'contextid', array('id'=>$category)); + $context = get_context_instance_by_id($contextid); + return $context; + } + + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + $fs = get_file_storage(); + $component = 'question'; + // process general question files + // Currently we have questiontext and generalfeedback areas + foreach (array('questiontext', 'generalfeedback') as $filearea) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $question->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + if ($newcategory->contextid == $question->contextid) { + continue; + } + $newfile = new object(); + // only contextid changed + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + // delete old files + $storedfile->delete(); + } + } + } + } +} diff --git a/question/type/random/edit_random_form.php b/question/type/random/edit_random_form.php index 35eddb91b71fd..0572d14f1f95d 100644 --- a/question/type/random/edit_random_form.php +++ b/question/type/random/edit_random_form.php @@ -79,11 +79,13 @@ function definition() { $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); $mform->closeHeaderBefore('buttonar'); } + function validation($fromform, $files) { //validation of category //is not relevant for this question type return array(); } + function qtype() { return 'random'; } diff --git a/question/type/random/questiontype.php b/question/type/random/questiontype.php index 958501fbcd4e5..f1acc34c2f822 100644 --- a/question/type/random/questiontype.php +++ b/question/type/random/questiontype.php @@ -88,6 +88,7 @@ function init_qtype_lists() { } function display_question_editing_page(&$mform, $question, $wizardnow){ + global $OUTPUT; $heading = $this->get_heading(empty($question->id)); echo $OUTPUT->heading_with_help($heading, $this->name(), $this->plugin_name()); $mform->display(); @@ -424,5 +425,3 @@ function get_random_guess_score($question) { //// INITIATION - Without this line the question type is not in use... /// ////////////////////////////////////////////////////////////////////////// question_register_questiontype(new random_qtype()); - - diff --git a/question/type/randomsamatch/edit_randomsamatch_form.php b/question/type/randomsamatch/edit_randomsamatch_form.php index 24b9502e175aa..4cdf576850132 100644 --- a/question/type/randomsamatch/edit_randomsamatch_form.php +++ b/question/type/randomsamatch/edit_randomsamatch_form.php @@ -19,8 +19,6 @@ class question_edit_randomsamatch_form extends question_edit_form { * @param MoodleQuickForm $mform the form being built. */ function definition_inner(&$mform) { - $mform->removeElement('image'); - $questionstoselect = array(); for ($i=2; $i<=QUESTION_NUMANS; $i++){ $questionstoselect[$i] = $i; @@ -33,15 +31,15 @@ function definition_inner(&$mform) { $mform->setType('fraction', PARAM_RAW); } - function set_data($question) { + function data_preprocessing($question) { if (empty($question->name)) { $question->name = get_string("randomsamatch", "quiz"); } if (empty($question->questiontext)) { - $question->questiontext = get_string("randomsamatchintro", "quiz"); + $question->questiontext = get_string("randomsamatchintro", "quiz"); } - parent::set_data($question); + return $question; } function qtype() { @@ -70,7 +68,5 @@ function validation($data, $files) { $errors['choose'] = get_string('notenoughsaincategory', 'qtype_randomsamatch', $a); } return $errors; - } } - diff --git a/question/type/shortanswer/display.html b/question/type/shortanswer/display.html index ad263640a8b34..d29fe5d97c4a1 100644 --- a/question/type/shortanswer/display.html +++ b/question/type/shortanswer/display.html @@ -2,10 +2,6 @@
- - - -
diff --git a/question/type/shortanswer/edit_shortanswer_form.php b/question/type/shortanswer/edit_shortanswer_form.php index fae996e4642fe..a43f3c2fac64a 100644 --- a/question/type/shortanswer/edit_shortanswer_form.php +++ b/question/type/shortanswer/edit_shortanswer_form.php @@ -1,4 +1,22 @@ . + +defined('MOODLE_INTERNAL') || die(); + /** * Defines the editing form for the shortanswer question type. * @@ -30,22 +48,40 @@ function definition_inner(&$mform) { $creategrades->gradeoptions); } - function set_data($question) { + function data_preprocessing($question) { if (isset($question->options)){ $answers = $question->options->answers; + $answers_ids = array(); if (count($answers)) { $key = 0; foreach ($answers as $answer){ + $answers_ids[] = $answer->id; $default_values['answer['.$key.']'] = $answer->answer; $default_values['fraction['.$key.']'] = $answer->fraction; - $default_values['feedback['.$key.']'] = $answer->feedback; + $default_values['feedback['.$key.']'] = array(); + + // prepare feedback editor to display files in draft area + $draftid_editor = file_get_submitted_draft_itemid('feedback['.$key.']'); + $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area( + $draftid_editor, // draftid + $this->context->id, // context + 'question', // component + 'answerfeedback', // filarea + !empty($answer->id)?(int)$answer->id:null, // itemid + $this->fileoptions, // options + $answer->feedback // text + ); + $default_values['feedback['.$key.']']['itemid'] = $draftid_editor; + // prepare files code block ends + + $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat; $key++; } } - $default_values['usecase'] = $question->options->usecase; + $default_values['usecase'] = $question->options->usecase; $question = (object)((array)$question + $default_values); } - parent::set_data($question); + return $question; } function validation($data, $files) { $errors = parent::validation($data, $files); @@ -59,7 +95,7 @@ function validation($data, $files) { if ($data['fraction'][$key] == 1) { $maxgrade = true; } - } else if ($data['fraction'][$key] != 0 || !html_is_blank($data['feedback'][$key])) { + } else if ($data['fraction'][$key] != 0 || !html_is_blank($data['feedback'][$key]['text'])) { $errors["answer[$key]"] = get_string('answermustbegiven', 'qtype_shortanswer'); $answercount++; } diff --git a/question/type/shortanswer/lib.php b/question/type/shortanswer/lib.php new file mode 100644 index 0000000000000..82757e70d7383 --- /dev/null +++ b/question/type/shortanswer/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package questionbank + * @subpackage questiontypes + * @author Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_shortanswer_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $DB, $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_shortanswer', $filearea, $args, $forcedownload); +} diff --git a/question/type/shortanswer/questiontype.php b/question/type/shortanswer/questiontype.php index a9a0d22eb4d2f..d285f45e93eae 100644 --- a/question/type/shortanswer/questiontype.php +++ b/question/type/shortanswer/questiontype.php @@ -1,5 +1,20 @@ . + /////////////////// /// SHORTANSWER /// /////////////////// @@ -27,17 +42,48 @@ function has_wildcards_in_responses($question, $subqid) { } function extra_question_fields() { - return array('question_shortanswer','answers','usecase'); + return array('question_shortanswer', 'answers', 'usecase'); } function questionid_column_name() { return 'question'; } + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + parent::move_files($question, $newcategory); + + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'answerfeedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + } + function save_question_options($question) { global $DB; $result = new stdClass; + $context = $question->context; + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { $oldanswers = array(); } @@ -49,23 +95,36 @@ function save_question_options($question) { foreach ($question->answer as $key => $dataanswer) { // Check for, and ingore, completely blank answer from the form. if (trim($dataanswer) == '' && $question->fraction[$key] == 0 && - html_is_blank($question->feedback[$key])) { + html_is_blank($question->feedback[$key]['text'])) { continue; } + $feedbackformat = $question->feedback[$key]['format']; + if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it $answer = $oldanswer; $answer->answer = trim($dataanswer); $answer->fraction = $question->fraction[$key]; - $answer->feedback = $question->feedback[$key]; + + // save draft file and rewrite text in feedback + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $oldanswer->id, self::$fileoptions, $question->feedback[$key]['text']); + $answer->feedbackformat = $feedbackformat; + $DB->update_record("question_answers", $answer); } else { // This is a completely new answer $answer = new stdClass; $answer->answer = trim($dataanswer); $answer->question = $question->id; $answer->fraction = $question->fraction[$key]; - $answer->feedback = $question->feedback[$key]; + // feedback content needs to be rewriten + $answer->feedback = ''; + $answer->feedbackformat = $feedbackformat; $answer->id = $DB->insert_record("question_answers", $answer); + + // save draft file and rewrite text in feedback + $answer->feedback = file_save_draft_area_files($question->feedback[$key]['itemid'], $context->id, 'question', 'answerfeedback', $answer->id, self::$fileoptions, $question->feedback[$key]['text']); + // update feedback content + $DB->set_field('question_answers', 'feedback', $answer->feedback, array('id'=>$answer->id)); } $answers[] = $answer->id; if ($question->fraction[$key] > $maxfraction) { @@ -97,6 +156,8 @@ function save_question_options($question) { } function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { + global $CFG; + $context = $this->get_context_by_category_id($question->category); /// This implementation is also used by question type 'numerical' $readonly = empty($options->readonly) ? '' : 'readonly="readonly"'; $formatoptions = new stdClass; @@ -109,7 +170,6 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions $questiontext = format_text($question->questiontext, $question->questiontextformat, $formatoptions, $cmoptions->course); - $image = get_question_image($question); /// Print input controls @@ -135,6 +195,7 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions $class = question_get_feedback_class($answer->fraction); $feedbackimg = question_get_feedback_image($answer->fraction); if ($answer->feedback) { + $answer->feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id); $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course); } break; @@ -145,7 +206,7 @@ function print_question_formulation_and_controls(&$question, &$state, $cmoptions /// Removed correct answer, to be displayed later MDL-7496 include($this->get_display_html_path()); } - + function get_display_html_path() { global $CFG; return $CFG->dirroot.'/question/type/shortanswer/display.html'; @@ -330,12 +391,12 @@ function print_question_grading_details(&$question, &$state, $cmoptions, $option // print grade for this submission print_string('gradingdetails', 'quiz', $grade) ; // A unit penalty for numerical was applied so display it - // a temporary solution for unit rendering in numerical + // a temporary solution for unit rendering in numerical // waiting for the new question engine code for a permanent one if(isset($state->options->raw_unitpenalty) && $state->options->raw_unitpenalty > 0.0 ){ echo ' '; print_string('unitappliedpenalty','qtype_numerical',question_format_grade($cmoptions, $state->options->raw_unitpenalty )); - } + } if ($cmoptions->penaltyscheme) { // print details of grade adjustment due to penalties if ($state->last_graded->raw_grade > $state->last_graded->grade){ @@ -390,6 +451,33 @@ function generate_test($name, $courseid = null) { return $this->save_question($question, $form, $course); } + + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + if ($component == 'question' && $filearea == 'answerfeedback') { + $answers = &$question->options->answers; + if (isset($state->responses[''])) { + $response = $state->responses['']; + } else { + $response = ''; + } + $answerid = reset($args); // itemid is answer id. + if (empty($options->feedback)) { + return false; + } + foreach($answers as $answer) { + if ($this->test_response($question, $state, $answer)) { + return true; + } + } + return false; + + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } + } //// END OF CLASS //// diff --git a/question/type/truefalse/display.html b/question/type/truefalse/display.html index 2bd99a5702c42..344f3d830db43 100644 --- a/question/type/truefalse/display.html +++ b/question/type/truefalse/display.html @@ -2,10 +2,6 @@
- - - -
: @@ -27,4 +23,4 @@
print_question_submit_buttons($question, $state, $cmoptions, $options); ?> -
\ No newline at end of file +
diff --git a/question/type/truefalse/edit_truefalse_form.php b/question/type/truefalse/edit_truefalse_form.php index ad70e66a838b9..4057d886fca29 100644 --- a/question/type/truefalse/edit_truefalse_form.php +++ b/question/type/truefalse/edit_truefalse_form.php @@ -1,9 +1,26 @@ . + if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page } require_once($CFG->dirroot.'/question/type/edit_question_form.php'); + /** * Defines the editing form for the thruefalse question type. * @@ -12,7 +29,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package questionbank * @subpackage questiontypes - *//** */ + */ /** * truefalse editing form definition. @@ -27,12 +44,10 @@ function definition_inner(&$mform) { $mform->addElement('select', 'correctanswer', get_string('correctanswer', 'qtype_truefalse'), array(0 => get_string('false', 'qtype_truefalse'), 1 => get_string('true', 'qtype_truefalse'))); - $mform->addElement('htmleditor', 'feedbacktrue', get_string('feedbacktrue', 'qtype_truefalse'), - array('course' => $this->coursefilesid));; + $mform->addElement('editor', 'feedbacktrue', get_string('feedbacktrue', 'qtype_truefalse'), null, $this->editoroptions);; $mform->setType('feedbacktrue', PARAM_RAW); - $mform->addElement('htmleditor', 'feedbackfalse', get_string('feedbackfalse', 'qtype_truefalse'), - array('course' => $this->coursefilesid)); + $mform->addElement('editor', 'feedbackfalse', get_string('feedbackfalse', 'qtype_truefalse'), null, $this->editoroptions); $mform->setType('feedbackfalse', PARAM_RAW); // Fix penalty factor at 1. @@ -43,9 +58,45 @@ function definition_inner(&$mform) { function set_data($question) { if (!empty($question->options->trueanswer)) { $trueanswer = $question->options->answers[$question->options->trueanswer]; + $draftid = file_get_submitted_draft_itemid('trueanswer'); + $answerid = $question->options->trueanswer; + $text = $trueanswer->feedback; + $question->correctanswer = ($trueanswer->fraction != 0); - $question->feedbacktrue = $trueanswer->feedback; - $question->feedbackfalse = $question->options->answers[$question->options->falseanswer]->feedback; + $question->feedbacktrue = array(); + $question->feedbacktrue['text'] = $trueanswer->feedback; + $question->feedbacktrue['format'] = $trueanswer->feedbackformat; + $question->feedbacktrue['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'question', // component + 'answerfeedback', // filarea + !empty($answerid)?(int)$answerid:null, // itemid + $this->fileoptions, // options + $text // text + ); + $question->feedbacktrue['itemid'] = $draftid; + } + if (!empty($question->options->falseanswer)) { + $falseanswer = $question->options->answers[$question->options->falseanswer]; + $draftid = file_get_submitted_draft_itemid('falseanswer'); + $answerid = $question->options->falseanswer; + $text = $falseanswer->feedback; + + $question->correctanswer = ($falseanswer->fraction != 0); + $question->feedbackfalse = array(); + $question->feedbackfalse['text'] = $falseanswer->feedback; + $question->feedbackfalse['format'] = $falseanswer->feedbackformat; + $question->feedbackfalse['text'] = file_prepare_draft_area( + $draftid, // draftid + $this->context->id, // context + 'question', // component + 'answerfeedback', // filarea + !empty($answerid)?(int)$answerid:null, // itemid + $this->fileoptions, // options + $text // text + ); + $question->feedbackfalse['itemid'] = $draftid; } parent::set_data($question); } diff --git a/question/type/truefalse/lib.php b/question/type/truefalse/lib.php new file mode 100644 index 0000000000000..6b8e005719530 --- /dev/null +++ b/question/type/truefalse/lib.php @@ -0,0 +1,31 @@ +. + +/** + * Serve question type files + * + * @since 2.0 + * @package qtype + * @subpackage qtype_truefalse + * @copyright The Open Unviersity + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function qtype_truefalse_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { + global $CFG; + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'qtype_truefalse', $filearea, $args, $forcedownload); +} diff --git a/question/type/truefalse/questiontype.php b/question/type/truefalse/questiontype.php index 9c2b895e0c072..58c78a1330235 100644 --- a/question/type/truefalse/questiontype.php +++ b/question/type/truefalse/questiontype.php @@ -1,5 +1,22 @@ . + +defined('MOODLE_INTERNAL') || die(); + ///////////////// /// TRUEFALSE /// ///////////////// @@ -24,34 +41,52 @@ function save_question_options($question) { $oldanswers = array(); } + $feedbacktext = $question->feedbacktrue['text']; + $feedbackitemid = $question->feedbacktrue['itemid']; + $feedbackformat = $question->feedbacktrue['format']; // Save answer 'True' if ($true = array_shift($oldanswers)) { // Existing answer, so reuse it $true->answer = get_string("true", "quiz"); $true->fraction = $question->correctanswer; $true->feedback = $question->feedbacktrue; + $true->feedback = file_save_draft_area_files($feedbackitemid, $question->context->id, 'question', 'answerfeedback', $true->id, self::$fileoptions, $feedbacktext); + $true->feedbackformat = $feedbackformat; $DB->update_record("question_answers", $true); } else { unset($true); + $true = new stdclass; $true->answer = get_string("true", "quiz"); $true->question = $question->id; $true->fraction = $question->correctanswer; - $true->feedback = $question->feedbacktrue; + $true->feedback = ''; + $true->feedbackformat = $feedbackformat; $true->id = $DB->insert_record("question_answers", $true); + $feedbacktext = file_save_draft_area_files($feedbackitemid, $question->context->id, 'question', 'answerfeedback', $true->id, self::$fileoptions, $feedbacktext); + $DB->set_field('question_answers', 'feedback', $feedbacktext, array('id'=>$true->id)); } + $feedbacktext = $question->feedbackfalse['text']; + $feedbackitemid = $question->feedbackfalse['itemid']; + $feedbackformat = $question->feedbackfalse['format']; + // Save answer 'False' if ($false = array_shift($oldanswers)) { // Existing answer, so reuse it $false->answer = get_string("false", "quiz"); $false->fraction = 1 - (int)$question->correctanswer; - $false->feedback = $question->feedbackfalse; + $false->feedback = file_save_draft_area_files($feedbackitemid, $question->context->id, 'question', 'answerfeedback', $false->id, self::$fileoptions, $feedbacktext); + $false->feedbackformat = $feedbackformat; $DB->update_record("question_answers", $false); } else { unset($false); + $false = new stdclass; $false->answer = get_string("false", "quiz"); $false->question = $question->id; $false->fraction = 1 - (int)$question->correctanswer; - $false->feedback = $question->feedbackfalse; + $false->feedback = ''; + $false->feedbackformat = $feedbackformat; $false->id = $DB->insert_record("question_answers", $false); + $feedbacktext = file_save_draft_area_files($feedbackitemid, $question->context->id, 'question', 'answerfeedback', $false->id, self::$fileoptions, $feedbacktext); + $DB->set_field('question_answers', 'feedback', $feedbacktext, array('id'=>$false->id)); } // delete any leftover old answer records (there couldn't really be any, but who knows) @@ -134,9 +169,9 @@ function get_correct_responses(&$question, &$state) { /** * Prints the main content of the question including any interactions */ - function print_question_formulation_and_controls(&$question, &$state, - $cmoptions, $options) { + function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { global $CFG; + $context = $this->get_context_by_category_id($question->category); $readonly = $options->readonly ? ' disabled="disabled"' : ''; @@ -148,7 +183,6 @@ function print_question_formulation_and_controls(&$question, &$state, $questiontext = format_text($question->questiontext, $question->questiontextformat, $formatoptions, $cmoptions->course); - $image = get_question_image($question); $answers = &$question->options->answers; $trueanswer = &$answers[$question->options->trueanswer]; @@ -198,12 +232,33 @@ function print_question_formulation_and_controls(&$question, &$state, $feedback = ''; if ($options->feedback and isset($answers[$response])) { $chosenanswer = $answers[$response]; + $chosenanswer->feedback = quiz_rewrite_question_urls($chosenanswer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $chosenanswer->id); $feedback = format_text($chosenanswer->feedback, true, $formatoptions, $cmoptions->course); } include("$CFG->dirroot/question/type/truefalse/display.html"); } + function check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args) { + if ($component == 'question' && $filearea == 'answerfeedback') { + + $answerid = reset($args); // itemid is answer id. + $answers = &$question->options->answers; + if (isset($state->responses[''])) { + $response = $state->responses['']; + } else { + $response = ''; + } + + return $options->feedback && isset($answers[$response]) && $answerid == $response; + + } else { + return parent::check_file_access($question, $state, $options, $contextid, $component, + $filearea, $args); + } + } + function grade_responses(&$question, &$state, $cmoptions) { if (isset($state->responses['']) && isset($question->options->answers[$state->responses['']])) { $state->raw_grade = $question->options->answers[$state->responses['']]->fraction * $question->maxgrade; @@ -363,6 +418,34 @@ function generate_test($name, $courseid = null) { return $this->save_question($question, $form, $course); } + /** + * When move the category of questions, the belonging files should be moved as well + * @param object $question, question information + * @param object $newcategory, target category information + */ + function move_files($question, $newcategory) { + global $DB; + parent::move_files($question, $newcategory); + + $fs = get_file_storage(); + // process files in answer + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + $component = 'question'; + $filearea = 'answerfeedback'; + foreach ($oldanswers as $answer) { + $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id); + foreach ($files as $storedfile) { + if (!$storedfile->is_directory()) { + $newfile = new object(); + $newfile->contextid = (int)$newcategory->contextid; + $fs->create_file_from_storedfile($newfile, $storedfile); + $storedfile->delete(); + } + } + } + } } //// END OF CLASS //// @@ -370,4 +453,3 @@ function generate_test($name, $courseid = null) { //// INITIATION - Without this line the question type is not in use... /// ////////////////////////////////////////////////////////////////////////// question_register_questiontype(new question_truefalse_qtype()); - diff --git a/version.php b/version.php index c07041033937f..ae20a02f0c55d 100644 --- a/version.php +++ b/version.php @@ -6,7 +6,7 @@ // This is compared against the values stored in the database to determine // whether upgrades should be performed (see lib/db/*.php) - $version = 2010080307; // YYYYMMDD = date of the last version bump + $version = 2010080901; // YYYYMMDD = date of the last version bump // XX = daily increments $release = '2.0 Preview 4+ (Build: 20100810)'; // Human-friendly version name