Merge branch 'MDL-28108' of git://github.com/timhunt/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 29 Jun 2011 22:00:12 +0000 (00:00 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 29 Jun 2011 22:00:12 +0000 (00:00 +0200)
91 files changed:
backup/moodle2/restore_stepslib.php
blog/lib.php
course/lib.php
course/report/completion/lang/en/coursereport_completion.php
course/report/completion/lib.php
course/report/lib.php [new file with mode: 0644]
course/report/log/lang/en/coursereport_log.php
course/report/log/lib.php
course/report/outline/index.php
course/report/outline/lang/en/coursereport_outline.php
course/report/outline/lib.php
course/report/participation/lang/en/coursereport_participation.php
course/report/participation/lib.php
course/report/progress/lang/en/coursereport_progress.php
course/report/progress/lib.php
course/report/stats/lang/en/coursereport_stats.php
course/report/stats/lib.php
lang/en/pagetype.php
lib/blocklib.php
lib/db/services.php
lib/db/upgrade.php
lib/messagelib.php
lib/pagelib.php
lib/questionlib.php
local/qeupgradehelper/locallib.php
message/externallib.php
message/lib.php
mod/assignment/lang/en/assignment.php
mod/assignment/lib.php
mod/chat/lib.php
mod/choice/lib.php
mod/data/lib.php
mod/feedback/item/multichoice/lib.php
mod/feedback/item/multichoicerated/lib.php
mod/feedback/lib.php
mod/folder/lib.php
mod/forum/lib.php
mod/glossary/lib.php
mod/imscp/lib.php
mod/lesson/continue.php
mod/lesson/format.php
mod/lesson/import.php
mod/lesson/import_form.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lesson/pagetypes/essay.php
mod/lesson/pagetypes/matching.php
mod/lesson/pagetypes/multichoice.php
mod/lesson/pagetypes/shortanswer.php
mod/lesson/pagetypes/truefalse.php
mod/lesson/styles.css
mod/lesson/view.php
mod/page/lib.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/resource/lib.php
mod/scorm/lib.php
mod/survey/lib.php
mod/url/lib.php
mod/wiki/lib.php
mod/workshop/lib.php
notes/lib.php
pluginfile.php
question/editlib.php
question/engine/upgrade/behaviourconverters.php
question/preview.php
question/previewlib.php
question/type/calculated/db/simpletest/testupgradelibnewqe.php
question/type/calculated/db/upgradelib.php
question/type/calculated/question.php
question/type/calculatedmulti/db/simpletest/testupgradelibnewqe.php
question/type/calculatedmulti/db/upgradelib.php
question/type/calculatedsimple/db/simpletest/testupgradelibnewqe.php
question/type/multianswer/db/simpletest/testupgradelibnewqe.php
question/type/multianswer/renderer.php
question/type/numerical/edit_numerical_form.php
question/type/numerical/lang/en/qtype_numerical.php
question/type/numerical/question.php
question/type/numerical/questiontype.php
question/type/numerical/renderer.php
question/type/numerical/simpletest/testanswerprocessor.php
question/type/numerical/simpletest/testform.php [new file with mode: 0755]
question/type/numerical/simpletest/testquestion.php
question/type/truefalse/db/simpletest/testupgradelibnewqe.php
question/type/truefalse/version.php
tag/lib.php
user/externallib.php
user/lib.php
version.php

index 6178a04..26f33c0 100644 (file)
@@ -273,7 +273,9 @@ class restore_gradebook_structure_step extends restore_structure_step {
         //$this->set_mapping('grade_setting', $oldid, $newitemid);
     }
 
-    //put all activity grade items in the correct grade category and mark all for recalculation
+    /**
+     * put all activity grade items in the correct grade category and mark all for recalculation
+     */
     protected function after_execute() {
         global $DB;
 
@@ -284,8 +286,15 @@ class restore_gradebook_structure_step extends restore_structure_step {
         );
         $rs = $DB->get_recordset('backup_ids_temp', $conditions);
 
+        // We need this for calculation magic later on.
+        $mappings = array();
+
         if (!empty($rs)) {
             foreach($rs as $grade_item_backup) {
+
+                // Store the oldid with the new id.
+                $mappings[$grade_item_backup->itemid] = $grade_item_backup->newitemid;
+
                 $updateobj = new stdclass();
                 $updateobj->id = $grade_item_backup->newitemid;
 
@@ -301,6 +310,55 @@ class restore_gradebook_structure_step extends restore_structure_step {
         }
         $rs->close();
 
+        // We need to update the calculations for calculated grade items that may reference old
+        // grade item ids using ##gi\d+##.
+        list($sql, $params) = $DB->get_in_or_equal(array_values($mappings), SQL_PARAMS_NAMED);
+        $sql = "SELECT gi.id, gi.calculation
+                  FROM {grade_items} gi
+                 WHERE gi.id {$sql} AND
+                       calculation IS NOT NULL";
+        $rs = $DB->get_recordset_sql($sql, $params);
+        foreach ($rs as $gradeitem) {
+            // Collect all of the used grade item id references
+            if (preg_match_all('/##gi(\d+)##/', $gradeitem->calculation, $matches) < 1) {
+                // This calculation doesn't reference any other grade items... EASY!
+                continue;
+            }
+            // For this next bit we are going to do the replacement of id's in two steps:
+            // 1. We will replace all old id references with a special mapping reference.
+            // 2. We will replace all mapping references with id's
+            // Why do we do this?
+            // Because there potentially there will be an overlap of ids within the query and we
+            // we substitute the wrong id.. safest way around this is the two step system
+            $calculationmap = array();
+            $mapcount = 0;
+            foreach ($matches[1] as $match) {
+                // Check that the old id is known to us, if not it was broken to begin with and will
+                // continue to be broken.
+                if (!array_key_exists($match, $mappings)) {
+                    continue;
+                }
+                // Our special mapping key
+                $mapping = '##MAPPING'.$mapcount.'##';
+                // The old id that exists within the calculation now
+                $oldid = '##gi'.$match.'##';
+                // The new id that we want to replace the old one with.
+                $newid = '##gi'.$mappings[$match].'##';
+                // Replace in the special mapping key
+                $gradeitem->calculation = str_replace($oldid, $mapping, $gradeitem->calculation);
+                // And record the mapping
+                $calculationmap[$mapping] = $newid;
+                $mapcount++;
+            }
+            // Iterate all special mappings for this calculation and replace in the new id's
+            foreach ($calculationmap as $mapping => $newid) {
+                $gradeitem->calculation = str_replace($mapping, $newid, $gradeitem->calculation);
+            }
+            // Update the calculation now that its being remapped
+            $DB->update_record('grade_items', $gradeitem);
+        }
+        $rs->close();
+
         //need to correct the grade category path and parent
         $conditions = array(
             'courseid' => $this->get_courseid()
@@ -1800,28 +1858,47 @@ class restore_activity_grades_structure_step extends restore_structure_step {
     }
 
     protected function process_grade_item($data) {
+        global $DB;
 
         $data = (object)($data);
         $oldid       = $data->id;        // We'll need these later
         $oldparentid = $data->categoryid;
+        $courseid = $this->get_courseid();
 
         // make sure top course category exists, all grade items will be associated
         // to it. Later, if restoring the whole gradebook, categories will be introduced
-        $coursecat = grade_category::fetch_course_category($this->get_courseid());
+        $coursecat = grade_category::fetch_course_category($courseid);
         $coursecatid = $coursecat->id; // Get the categoryid to be used
 
+        $idnumber = null;
+        if (!empty($data->idnumber)) {
+            // Don't get any idnumber from course module. Keep them as they are in grade_item->idnumber
+            // Reason: it's not clear what happens with outcomes->idnumber or activities with multiple items (workshop)
+            // so the best is to keep the ones already in the gradebook
+            // Potential problem: duplicates if same items are restored more than once. :-(
+            // This needs to be fixed in some way (outcomes & activities with multiple items)
+            // $data->idnumber     = get_coursemodule_from_instance($data->itemmodule, $data->iteminstance)->idnumber;
+            // In any case, verify always for uniqueness
+            $sql = "SELECT cm.id
+                      FROM {course_modules} cm
+                     WHERE cm.course = :courseid AND
+                           cm.idnumber = :idnumber AND
+                           cm.id <> :cmid";
+            $params = array(
+                'courseid' => $courseid,
+                'idnumber' => $data->idnumber,
+                'cmid' => $this->task->get_moduleid()
+            );
+            if (!$DB->record_exists_sql($sql, $params) && !$DB->record_exists('grade_items', array('courseid' => $courseid, 'idnumber' => $data->idnumber))) {
+                $idnumber = $data->idnumber;
+            }
+        }
+
         unset($data->id);
         $data->categoryid   = $coursecatid;
         $data->courseid     = $this->get_courseid();
         $data->iteminstance = $this->task->get_activityid();
-        // Don't get any idnumber from course module. Keep them as they are in grade_item->idnumber
-        // Reason: it's not clear what happens with outcomes->idnumber or activities with multiple items (workshop)
-        // so the best is to keep the ones already in the gradebook
-        // Potential problem: duplicates if same items are restored more than once. :-(
-        // This needs to be fixed in some way (outcomes & activities with multiple items)
-        // $data->idnumber     = get_coursemodule_from_instance($data->itemmodule, $data->iteminstance)->idnumber;
-        // In any case, verify always for uniqueness
-        $data->idnumber = grade_verify_idnumber($data->idnumber, $this->get_courseid()) ? $data->idnumber : null;
+        $data->idnumber     = $idnumber;
         $data->scaleid      = $this->get_mappingid('scale', $data->scaleid);
         $data->outcomeid    = $this->get_mappingid('outcome', $data->outcomeid);
         $data->timecreated  = $this->apply_date_offset($data->timecreated);
@@ -2827,8 +2904,7 @@ abstract class restore_questions_activity_structure_step extends restore_activit
         $usage->preferredbehaviour = $quiz->preferredbehaviour;
         $usage->id = $DB->insert_record('question_usages', $usage);
 
-        $DB->set_field('quiz_attempts', 'uniqueid', $usage->id,
-                array('id' => $this->get_mappingid('quiz_attempt', $data->id)));
+        $this->inform_new_usage_id($usage->id);
 
         $data->uniqueid = $usage->id;
         $upgrader->save_usage($quiz->preferredbehaviour, $data, $qas, $quiz->questions);
@@ -2846,10 +2922,15 @@ abstract class restore_questions_activity_structure_step extends restore_activit
         $qstates = array();
         foreach ($data->states['state'] as $state) {
             if ($state['question'] == $questionid) {
-                $qstates[$state['seq_number']] = (object) $state;
+                // It would be natural to use $state['seq_number'] as the array-key
+                // here, but it seems that buggy behaviour in 2.0 and early can
+                // mean that that is not unique, so we use id, which is guaranteed
+                // to be unique.
+                $qstates[$state['id']] = (object) $state;
             }
         }
         ksort($qstates);
+        $qstates = array_values($qstates);
 
         return array($qsession, $qstates);
     }
index 0f25fb4..c37af10 100644 (file)
@@ -1035,7 +1035,7 @@ function blog_comment_validate($comment_param) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function blog_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function blog_page_type_list($pagetype, $parentcontext, $currentcontext) {
     return array(
         '*'=>get_string('page-x', 'pagetype'),
         'blog-*'=>get_string('page-blog-x', 'blog'),
index 1a9dbd9..8517148 100644 (file)
@@ -4218,7 +4218,7 @@ class course_request {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function course_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
     // if above course context ,display all course fomats
     list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
     if ($course->id == SITEID) {
@@ -4230,4 +4230,4 @@ function course_pagetypelist($pagetype, $parentcontext, $currentcontext) {
             'mod-*'=>get_string('page-mod-x', 'pagetype')
         );
     }
-}
+}
\ No newline at end of file
index 3e67898..2ec123a 100644 (file)
@@ -2,4 +2,5 @@
 
 $string['completion:view'] = 'View course completion report';
 $string['completiondate']='Completion date';
+$string['pluginpagetype'] = 'Completion course report';
 $string['pluginname']='Course completion';
index a84f3a2..7bf087e 100644 (file)
@@ -44,3 +44,18 @@ function completion_report_extend_navigation($navigation, $course, $context) {
         }
     }
 }
+
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function completion_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype'),
+        'course-report-completion-*' => get_string('pluginpagetype',  'coursereport_completion')
+    );
+    return $array;
+}
diff --git a/course/report/lib.php b/course/report/lib.php
new file mode 100644 (file)
index 0000000..3ed43f7
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains functions used by course reports
+ *
+ * @since 2.1
+ * @package course-report
+ * @copyright 2011 Andrew Davis
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function coursereport_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype')
+    );
+    return $array;
+}
\ No newline at end of file
index dc37408..431e260 100644 (file)
@@ -27,4 +27,5 @@ $string['loglive'] = 'Live logs';
 $string['log:view'] = 'View course logs';
 $string['log:viewlive'] = 'View live logs';
 $string['log:viewtoday'] = 'View today\'s logs';
+$string['pluginpagetype'] = 'Log course report';
 $string['pluginname'] = 'Logs';
index 96cbdc7..de27692 100644 (file)
@@ -537,3 +537,18 @@ function log_report_extend_navigation($navigation, $course, $context) {
         $navigation->add(get_string('pluginname', 'coursereport_log'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
     }
 }
+
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function log_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype'),
+        'course-report-log-*' => get_string('pluginpagetype',  'coursereport_log')
+    );
+    return $array;
+}
\ No newline at end of file
index ad4ef64..dc1dd10 100644 (file)
             $reportrow->cells[] = $numviewscell;
 
             if ($CFG->useblogassociations) {
+                require_once($CFG->dirroot.'/blog/lib.php');
                 $blogcell = new html_table_cell();
                 $blogcell->attributes['class'] = 'blog';
                 if ($blogcount = blog_get_associated_count($course->id, $cm->id)) {
index eaf1886..6bce7b8 100644 (file)
@@ -24,4 +24,5 @@
  */
 
 $string['outline:view'] = 'View course activity report';
+$string['pluginpagetype'] = 'Course activity report';
 $string['pluginname'] = 'Course activity';
index c020dcc..f3632b3 100644 (file)
@@ -37,4 +37,19 @@ function outline_report_extend_navigation($navigation, $course, $context) {
         $url = new moodle_url('/course/report/outline/index.php', array('id'=>$course->id));
         $navigation->add(get_string( 'activityreport' ), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
     }
+}
+
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function outline_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype'),
+        'course-report-outline-*' => get_string('pluginpagetype',  'coursereport_outline')
+    );
+    return $array;
 }
\ No newline at end of file
index 82e9864..16e5eaa 100644 (file)
@@ -24,4 +24,5 @@
  */
 
 $string['participation:view'] = 'View course participation report';
+$string['pluginpagetype'] = 'Participation course report';
 $string['pluginname'] = 'Course participation';
index 7071876..62429c9 100644 (file)
@@ -37,4 +37,19 @@ function participation_report_extend_navigation($navigation, $course, $context)
         $url = new moodle_url('/course/report/participation/index.php', array('id'=>$course->id));
         $navigation->add(get_string('participationreport'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
     }
+}
+
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function participation_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype'),
+        'course-report-participation-*' => get_string('pluginpagetype',  'coursereport_participation')
+    );
+    return $array;
 }
\ No newline at end of file
index 7cb9005..dd27c66 100644 (file)
@@ -24,4 +24,5 @@
  */
 
 $string['pluginname'] = 'Activity completion';
+$string['pluginpagetype'] = 'Progress course report';
 $string['progress:view'] = 'View activity completion reports';
index f4c74f6..23a9686 100644 (file)
@@ -49,3 +49,18 @@ function progress_report_extend_navigation($navigation, $course, $context) {
         $navigation->add(get_string('pluginname','coursereport_progress'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
     }
 }
+
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function progress_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype'),
+        'course-report-progress-*' => get_string('pluginpagetype',  'coursereport_progress')
+    );
+    return $array;
+}
\ No newline at end of file
index d4491a9..38815c8 100644 (file)
@@ -24,4 +24,5 @@
  */
 
 $string['pluginname'] = 'Course statistics';
+$string['pluginpagetype'] = 'Statistics course report';
 $string['stats:view'] = 'View course statistics report';
index ed31e78..220e8df 100644 (file)
@@ -91,3 +91,18 @@ function stats_report_extend_navigation($navigation, $course, $context) {
         }
     }
 }
+
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function stats_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype'),
+        'course-report-stats-*' => get_string('pluginpagetype',  'coursereport_stats')
+    );
+    return $array;
+}
\ No newline at end of file
index bc540f0..7a073f4 100644 (file)
@@ -25,6 +25,7 @@
 
 $string['page-course-view-x'] = 'Any type of course main page';
 $string['page-course-x'] = 'Any course page';
+$string['page-course-report-x'] = 'Any course report';
 $string['page-mod-x'] = 'Any activity module page';
 $string['page-mod-x-view'] = 'Any main activity module page';
 $string['page-my-index'] = 'My home page';
index 608a3d5..991b34d 100644 (file)
@@ -1217,13 +1217,14 @@ class block_manager {
                 // If the block wants to be system-wide, then explicitly set that
                 if ($data->bui_contexts == BUI_CONTEXTS_ENTIRE_SITE) {   // Only possible on a frontpage or system page
                     $bi->parentcontextid = $systemcontext->id;
-                    $bi->showinsubcontexts = 1;
+                    $bi->showinsubcontexts = BUI_CONTEXTS_CURRENT_SUBS; //show in current and sub contexts
+                    $bi->pagetypepattern = '*';
 
                 } else { // The block doesn't want to be system-wide, so let's ensure that
                     if ($parentcontext->id == $systemcontext->id) {  // We need to move it to the front page
                         $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
                         $bi->parentcontextid = $frontpagecontext->id;
-                        $bi->pagetypepattern = '*';  // Just in case
+                        $bi->pagetypepattern = 'site-index';
                     }
                 }
             }
@@ -1563,34 +1564,72 @@ function generate_page_type_patterns($pagetype, $parentcontext = null, $currentc
 
     $bits = explode('-', $pagetype);
 
-    $component = clean_param(reset($bits), PARAM_ALPHANUMEXT);
-    $function = 'default_pagetypelist';
-
     $core = get_core_subsystems();
     $plugins = get_plugin_types();
 
-    // First check to see if the initial component is a core component
-    // if its not check to see if it is a plugin component.
-    if (array_key_exists($component, $core) && !empty($core[$component])) {
-        $libfile = $CFG->dirroot.'/'.$core[$component].'/lib.php';
-        if (file_exists($libfile)) {
-            require_once($libfile);
-            if (function_exists($component.'_pagetypelist')) {
-                $function = $component.'_pagetypelist';
+    //progressively strip pieces off the page type looking for a match
+    $componentarray = null;
+    for ($i = count($bits); $i > 0; $i--) {
+        $possiblecomponentarray = array_slice($bits, 0, $i);
+        $possiblecomponent = implode('', $possiblecomponentarray);
+
+        // Check to see if the component is a core component
+        if (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {
+            $libfile = $CFG->dirroot.'/'.$core[$possiblecomponent].'/lib.php';
+            if (file_exists($libfile)) {
+                require_once($libfile);
+                $function = $possiblecomponent.'_page_type_list';
+                if (function_exists($function)) {
+                    if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
+                        break;
+                    }
+                }
             }
         }
-    } else if (array_key_exists($component, $plugins) && !empty($plugins[$component])) {
-        $function = 'plugin_pagetypelist';
-        if (function_exists($component.'_pagetypelist')) {
-            $function = $component.'_pagetypelist';
+
+        //check the plugin directory and look for a callback
+        if (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {
+
+            //We've found a plugin type. Look for a plugin name by getting the next section of page type
+            if (count($bits) > $i) {
+                $pluginname = $bits[$i];
+                $directory = get_plugin_directory($possiblecomponent, $pluginname);
+                if (!empty($directory)){
+                    $libfile = $directory.'/lib.php';
+                    if (file_exists($libfile)) {
+                        require_once($libfile);
+                        $function = $pluginname.'_page_type_list';
+                        if (function_exists($function)) {
+                            if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            //we'll only get to here if we still don't have any patterns
+            //the plugin type may have a callback
+            $directory = get_plugin_directory($possiblecomponent, null);
+            if (!empty($directory)){
+                $libfile = $directory.'/lib.php';
+                if (file_exists($libfile)) {
+                    require_once($libfile);
+                    $function = $possiblecomponent.'_page_type_list';
+                    if (function_exists($function)) {
+                        if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
+                            break;
+                        }
+                    }
+                }
+            }
         }
     }
-    // Call the most appropriate function we could determine
-    $patterns = $function($pagetype, $parentcontext, $currentcontext);
+
     if (empty($patterns)) {
-        // If there are no patterns default to just the current pattern.
-        $patterns = array($pagetype => $pagetype);
+        $patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);
     }
+
     return $patterns;
 }
 
@@ -1602,7 +1641,7 @@ function generate_page_type_patterns($pagetype, $parentcontext = null, $currentc
  * @param stdClass $currentcontext
  * @return array
  */
-function default_pagetypelist($pagetype, $parentcontext = null, $currentcontext = null) {
+function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
     // Generate page type patterns based on current page type if
     // callbacks haven't been defined
     $patterns = array($pagetype => $pagetype);
@@ -1622,40 +1661,6 @@ function default_pagetypelist($pagetype, $parentcontext = null, $currentcontext
     return $patterns;
 }
 
-/**
- * Generates a page type list for plugins
- *
- * @param string $pagetype
- * @param stdClass $parentcontext
- * @param stdClass $currentcontext
- * @return array
- */
-function plugin_pagetypelist($pagetype, $parentcontext = null, $currentcontext = null) {
-    global $CFG;
-
-    // for modules
-    $bits = explode('-', $pagetype);
-    $plugintype = $bits[0];
-    $pluginname = $bits[1];
-    $directory = get_plugin_directory($plugintype, $pluginname);
-    if (empty($directory)) {
-        return array();
-    }
-    $libfile = $directory.'/lib.php';
-    require_once($libfile);
-    $function = $pluginname.'_pagetypelist';
-    if (!function_exists($function)) {
-        return array();
-    }
-    $patterns = $function($pagetype, $parentcontext, $currentcontext);
-    if ($parentcontext->contextlevel == CONTEXT_COURSE) {
-        // including course page type
-        require_once("$CFG->dirroot/course/lib.php");
-        $patterns = array_merge(course_pagetypelist($pagetype, $parentcontext, $currentcontext), $patterns);
-    }
-    return $patterns;
-}
-
 /**
  * Generates the page type list for the my moodle page
  *
@@ -1664,7 +1669,7 @@ function plugin_pagetypelist($pagetype, $parentcontext = null, $currentcontext =
  * @param stdClass $currentcontext
  * @return array
  */
-function my_pagetypelist($pagetype, $parentcontext = null, $currentcontext = null) {
+function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
     return array('my-index' => 'my-index');
 }
 
@@ -1677,8 +1682,8 @@ function my_pagetypelist($pagetype, $parentcontext = null, $currentcontext = nul
  * @param stdClass $currentcontext
  * @return array
  */
-function mod_pagetypelist($pagetype, $parentcontext = null, $currentcontext = null) {
-    $patterns = plugin_pagetypelist($pagetype, $parentcontext, $currentcontext);
+function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
+    $patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);
     if (empty($patterns)) {
         // if modules don't have callbacks
         // generate two default page type patterns for modules only
index 53c1309..117bf9d 100644 (file)
@@ -129,6 +129,15 @@ $functions = array(
         'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update',
     ),
 
+    'moodle_user_get_users_by_courseid' => array(
+        'classname'   => 'moodle_user_external',
+        'methodname'  => 'get_users_by_courseid',
+        'classpath'   => 'user/externallib.php',
+        'description' => 'Get enrolled users by course id.',
+        'type'        => 'read',
+        'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update, moodle/site:accessallgroups',
+    ),
+
     'moodle_user_get_course_participants_by_id' => array(
         'classname'   => 'moodle_user_external',
         'methodname'  => 'get_course_participants_by_id',
@@ -217,11 +226,11 @@ $functions = array(
 
     // === message related functions ===
 
-    'moodle_message_send_messages' => array(
+    'moodle_message_send_instantmessages' => array(
         'classname'   => 'moodle_message_external',
-        'methodname'  => 'send_messages',
+        'methodname'  => 'send_instantmessages',
         'classpath'   => 'message/externallib.php',
-        'description' => 'Send messages',
+        'description' => 'Send instant messages',
         'type'        => 'write',
         'capabilities'=> 'moodle/site:sendmessage',
     ),
@@ -258,7 +267,8 @@ $services = array(
             'moodle_webservice_get_siteinfo',
             'moodle_notes_create_notes',
             'moodle_user_get_course_participants_by_id',
-            'moodle_message_send_messages'),
+            'moodle_user_get_users_by_courseid',
+            'moodle_message_send_instantmessages'),
         'enabled' => 0,
         'restrictedusers' => 0,
         'shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE
index b12db58..271fb12 100644 (file)
@@ -6191,6 +6191,13 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
                 )
             ");
 
+            // It seems that it is possible, in old versions of Moodle, for a
+            // quiz_attempt to be deleted while the question_attempt remains.
+            // In that situation we still get NULLs left in the table, which
+            // causes the upgrade to break at the next step. To avoid breakage,
+            // without risking dataloss, we just replace all NULLs with 0 here.
+            $DB->set_field_select('question_usages', 'contextid', 0, 'contextid IS NULL');
+
             // Then make it NOT NULL.
             $field = new xmldb_field('contextid');
             $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED,
index a45afa2..5fbc20b 100644 (file)
@@ -133,7 +133,7 @@ function message_send($eventdata) {
         // Find out if user has configured this output
         $userisconfigured = $processor->object->is_user_configured($eventdata->userto);
 
-        // DEBUG: noify if we are forcing unconfigured output
+        // DEBUG: notify if we are forcing unconfigured output
         if ($permitted == 'forced' && !$userisconfigured) {
             debugging('Attempt to force message delivery to user who has "'.$processor->name.'" output unconfigured', DEBUG_NORMAL);
         }
index b6c9cf0..34c1c91 100644 (file)
@@ -340,7 +340,9 @@ class moodle_page {
                 // cli scripts work in system context, do not annoy devs with debug info
                 // very few scripts do not use cookies, we can safely use system as default context there
             } else {
-                debugging('Coding problem: this page does not set $PAGE->context properly.');
+                debugging('Coding problem: $PAGE->context was not set. You may have forgotten '
+                    .'to call require_login() or $PAGE->set_context(). The page may not display '
+                    .'correctly as a result');
             }
             $this->_context = get_context_instance(CONTEXT_SYSTEM);
         }
index 5c7d89f..fe52dfe 100644 (file)
@@ -640,13 +640,25 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte
  * @param question_display_options $displayoptions the display options to use.
  * @param int $variant the variant of the question to preview. If null, one will
  *      be picked randomly.
+ * @param object $context context to run the preview in (affects things like
+ *      filter settings, theme, lang, etc.) Defaults to $PAGE->context.
  * @return string the URL.
  */
 function question_preview_url($questionid, $preferredbehaviour = null,
-        $maxmark = null, $displayoptions = null, $variant = null) {
+        $maxmark = null, $displayoptions = null, $variant = null, $context = null) {
 
     $params = array('id' => $questionid);
 
+    if (is_null($context)) {
+        global $PAGE;
+        $context = $PAGE->context;
+    }
+    if ($context->contextlevel == CONTEXT_MODULE) {
+        $params['cmid'] = $context->instanceid;
+    } else if ($context->contextlevel == CONTEXT_COURSE) {
+        $params['courseid'] = $context->instanceid;
+    }
+
     if (!is_null($preferredbehaviour)) {
         $params['behaviour'] = $preferredbehaviour;
     }
@@ -1826,7 +1838,7 @@ function question_make_export_url($contextid, $categoryid, $format, $withcategor
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function question_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function question_page_type_list($pagetype, $parentcontext, $currentcontext) {
     global $CFG;
     $types = array(
         'question-*'=>get_string('page-question-x', 'question'),
@@ -1837,7 +1849,7 @@ function question_pagetypelist($pagetype, $parentcontext, $currentcontext) {
     );
     if ($currentcontext->contextlevel == CONTEXT_COURSE) {
         require_once($CFG->dirroot . '/course/lib.php');
-        return array_merge(course_pagetypelist($pagetype, $parentcontext, $currentcontext), $types);
+        return array_merge(course_page_type_list($pagetype, $parentcontext, $currentcontext), $types);
     } else {
         return $types;
     }
index dfb7d1a..e27388d 100644 (file)
@@ -531,7 +531,7 @@ function local_qeupgradehelper_generate_unit_test($questionsessionid, $namesuffi
     if (!local_qeupgradehelper_is_upgraded()) {
         if (!$quiz->optionflags) {
             $quiz->preferredbehaviour = 'deferredfeedback';
-        } else if (!$quiz->penaltyscheme) {
+        } else if ($quiz->penaltyscheme) {
             $quiz->preferredbehaviour = 'adaptive';
         } else {
             $quiz->preferredbehaviour = 'adaptivenopenalty';
index 00f24a8..1a7a160 100644 (file)
@@ -31,7 +31,7 @@ class moodle_message_external extends external_api {
      * Returns description of method parameters
      * @return external_function_parameters
      */
-    public static function send_messages_parameters() {
+    public static function send_instantmessages_parameters() {
         return new external_function_parameters(
             array(
                 'messages' => new external_multiple_structure(
@@ -53,7 +53,7 @@ class moodle_message_external extends external_api {
      * @param $messages  An array of message to send.
      * @return boolean
      */
-    public static function send_messages($messages = array()) {
+    public static function send_instantmessages($messages = array()) {
         global $CFG, $USER, $DB;
         require_once($CFG->dirroot . "/message/lib.php");
 
@@ -67,24 +67,32 @@ class moodle_message_external extends external_api {
         self::validate_context($context);
         require_capability('moodle/site:sendmessage', $context);
 
-        $params = self::validate_parameters(self::send_messages_parameters(), array('messages' => $messages));
+        $params = self::validate_parameters(self::send_instantmessages_parameters(), array('messages' => $messages));
 
         //retrieve all tousers of the messages
-        $touserids = array();
+        $receivers = array();
         foreach($params['messages'] as $message) {
-            $touserids[] = $message['touserid'];
+            $receivers[] = $message['touserid'];
         }
-        list($sqluserids, $sqlparams) = $DB->get_in_or_equal($touserids, SQL_PARAMS_NAMED, 'userid_');
+        list($sqluserids, $sqlparams) = $DB->get_in_or_equal($receivers, SQL_PARAMS_NAMED, 'userid_');
         $tousers = $DB->get_records_select("user", "id " . $sqluserids . " AND deleted = 0", $sqlparams);
-
-        //retrieve the tousers who are blocking the $USER
+        $blocklist   = array();
+        $contactlist = array();
         $sqlparams['contactid'] = $USER->id;
-        $sqlparams['blocked'] = 1;
-        //Note: return userid field should be unique for the below request,
-        //so we'll use this field as key of $blockingcontacts
-        $blockingcontacts = $DB->get_records_select("message_contacts",
-                "userid " . $sqluserids . " AND contactid = :contactid AND blocked = :blocked",
-                $sqlparams, '', "userid");
+        $rs = $DB->get_recordset_sql("SELECT *
+                                        FROM {message_contacts}
+                                       WHERE userid $sqluserids
+                                             AND contactid = :contactid", $sqlparams);
+        foreach ($rs as $record) {
+            if ($record->blocked) {
+                // $record->userid is blocking current user
+                $blocklist[$record->userid] = true;
+            } else {
+                // $record->userid have current user as contact
+                $contactlist[$record->userid] = true;
+            }
+        }
+        $rs->close();
 
         $canreadallmessages = has_capability('moodle/site:readallmessages', $context);
 
@@ -104,14 +112,16 @@ class moodle_message_external extends external_api {
             }
 
             //check that the touser is not blocking the current user
-            if ($success and isset($blockingcontacts[$message['touserid']]) and !$canreadallmessages) {
+            if ($success and !empty($blocklist[$message['touserid']]) and !$canreadallmessages) {
                 $success = false;
                 $errormessage = get_string('userisblockingyou', 'message');
             }
 
             // Check if the user is a contact
             //TODO: performance improvement - edit the function so we can pass an array instead userid
-            if ($success && empty($contact) && get_user_preferences('message_blocknoncontacts', NULL, $message['touserid']) == null) {
+            $blocknoncontacts = get_user_preferences('message_blocknoncontacts', NULL, $message['touserid']);
+            // message_blocknoncontacts option is on and current user is not in contact list
+            if ($success && empty($contactlist[$message['touserid']]) && !empty($blocknoncontacts)) {
                 // The user isn't a contact and they have selected to block non contacts so this message won't be sent.
                 $success = false;
                 $errormessage = get_string('userisblockingyounoncontact', 'message');
@@ -144,12 +154,12 @@ class moodle_message_external extends external_api {
      * Returns description of method result value
      * @return external_description
      */
-    public static function send_messages_returns() {
+    public static function send_instantmessages_returns() {
         return new external_multiple_structure(
             new external_single_structure(
                 array(
-                    'clientmsgid' => new external_value(PARAM_ALPHANUMEXT, 'your own id for the message', VALUE_OPTIONAL),
                     'msgid' => new external_value(PARAM_INT, 'test this to know if it succeeds:  id of the created message if it succeeded, -1 when failed'),
+                    'clientmsgid' => new external_value(PARAM_ALPHANUMEXT, 'your own id for the message', VALUE_OPTIONAL),
                     'errormessage' => new external_value(PARAM_TEXT, 'error message - if it failed', VALUE_OPTIONAL)
                 )
             )
index 8020453..9de50e0 100644 (file)
@@ -2369,6 +2369,6 @@ function translate_message_default_setting($plugindefault, $processorname) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function message_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function message_page_type_list($pagetype, $parentcontext, $currentcontext) {
     return array('messages-*'=>get_string('page-message-x', 'message'));
 }
index aec18b6..b7f559e 100644 (file)
@@ -45,6 +45,8 @@ You can see it appended to your assignment submission:
 $string['assignmentmailhtml'] = '{$a->teacher} has posted some feedback on your
 assignment submission for \'<i>{$a->assignment}</i>\'<br /><br />
 You can see it appended to your <a href="{$a->url}">assignment submission</a>.';
+$string['assignmentmailsmall'] = '{$a->teacher} has posted some feedback on your
+assignment submission for \'{$a->assignment}\' You can see it appended to your submission';
 $string['assignmentname'] = 'Assignment name';
 $string['assignment:submit'] = 'Submit assignment';
 $string['assignmentsubmission'] = 'Assignment submissions';
@@ -90,8 +92,8 @@ $string['emailteachers_help'] = 'If enabled, teachers receive email notification
 
 Only teachers who are able to grade the particular assignment are notified. So, for example, if the course uses separate groups, teachers restricted to particular groups won\'t receive notification about students in other groups.';
 $string['emptysubmission'] = 'You have not submitted anything yet';
-$string['enableemailnotification'] = 'Send notification emails';
-$string['enableemailnotification_help'] = 'If enabled, students will receive email notification when their assignment submissions are graded.';
+$string['enablenotification'] = 'Send notifications';
+$string['enablenotification_help'] = 'If enabled, students will be notified when their assignment submissions are graded.';
 $string['errornosubmissions'] = 'There are no submissions to download';
 $string['existingfiledeleted'] = 'Existing file has been deleted: {$a}';
 $string['failedupdatefeedback'] = 'Failed to update submission feedback for user {$a}';
index 13cb271..84665f8 100644 (file)
@@ -1021,6 +1021,7 @@ class assignment_base {
         $mformdata->submissioncommentformat= FORMAT_HTML;
         $mformdata->submission_content= $this->print_user_files($user->id,true);
         $mformdata->filter = $filter;
+        $mformdata->mailinfo = get_user_preferences('assignment_mailinfo', 0);
          if ($assignment->assignmenttype == 'upload') {
             $mformdata->fileui_options = array('subdirs'=>1, 'maxbytes'=>$assignment->maxbytes, 'maxfiles'=>$assignment->var1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
         } elseif ($assignment->assignmenttype == 'uploadsingle') {
@@ -1470,9 +1471,9 @@ class assignment_base {
             if (get_user_preferences('assignment_mailinfo', 1)) {
                 $mailinfopref = true;
             }
-            $emailnotification =  html_writer::checkbox('mailinfo', 1, $mailinfopref, get_string('enableemailnotification','assignment'));
+            $emailnotification =  html_writer::checkbox('mailinfo', 1, $mailinfopref, get_string('enablenotification','assignment'));
 
-            $emailnotification .= $OUTPUT->help_icon('enableemailnotification', 'assignment');
+            $emailnotification .= $OUTPUT->help_icon('enablenotification', 'assignment');
             echo html_writer::tag('div', $emailnotification, array('class'=>'emailnotification'));
 
             $savefeedback = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'fastg', 'value'=>get_string('saveallfeedback', 'assignment')));
@@ -1746,7 +1747,7 @@ class assignment_base {
                 $eventdata->fullmessage      = $posttext;
                 $eventdata->fullmessageformat = FORMAT_PLAIN;
                 $eventdata->fullmessagehtml  = $posthtml;
-                $eventdata->smallmessage     = '';
+                $eventdata->smallmessage     = $postsubject;
 
                 $eventdata->name            = 'assignment_updates';
                 $eventdata->component       = 'mod_assignment';
@@ -2303,12 +2304,10 @@ class mod_assignment_grading_form extends moodleform {
                 default :
                     break;
             }
-            $lastmailinfo = get_user_preferences('assignment_mailinfo', 1) ? array('checked'=>'checked') : array();
             $mform->addElement('hidden', 'mailinfo_h', "0");
             $mform->setType('mailinfo_h', PARAM_INT);
-            $mform->addElement('checkbox', 'mailinfo',get_string('enableemailnotification','assignment').
-            $OUTPUT->help_icon('enableemailnotification', 'assignment') .':' );
-            $mform->updateElementAttr('mailinfo', $lastmailinfo);
+            $mform->addElement('checkbox', 'mailinfo',get_string('enablenotification','assignment').
+            $OUTPUT->help_icon('enablenotification', 'assignment') .':' );
             $mform->setType('mailinfo', PARAM_INT);
         }
     }
@@ -2616,11 +2615,13 @@ function assignment_cron () {
             $eventdata->fullmessage      = $posttext;
             $eventdata->fullmessageformat = FORMAT_PLAIN;
             $eventdata->fullmessagehtml  = $posthtml;
-            $eventdata->smallmessage     = '';
+            $eventdata->smallmessage     = get_string('assignmentmailsmall', 'assignment', $assignmentinfo);
 
             $eventdata->name            = 'assignment_updates';
             $eventdata->component       = 'mod_assignment';
             $eventdata->notification    = 1;
+            $eventdata->contexturl      = $assignmentinfo->url;
+            $eventdata->contexturlname  = $assignmentinfo->assignment;
 
             message_send($eventdata);
         }
@@ -3722,7 +3723,7 @@ function assignment_get_file_areas($course, $cm, $context) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function assignment_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function assignment_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array(
         'mod-assignment-*'=>get_string('page-mod-assignment-x', 'assignment'),
         'mod-assignment-view'=>get_string('page-mod-assignment-view', 'assignment'),
index c40b833..a59a5e2 100644 (file)
@@ -1312,7 +1312,7 @@ function chat_user_logout($user) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function chat_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function chat_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-chat-*'=>get_string('page-mod-chat-x', 'chat'));
     return $module_pagetype;
 }
index b9b1fa0..a03db26 100644 (file)
@@ -863,7 +863,7 @@ function choice_get_completion_state($course, $cm, $userid, $type) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function choice_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function choice_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-choice-*'=>get_string('page-mod-choice-x', 'choice'));
     return $module_pagetype;
 }
index ff954dd..8758bfc 100644 (file)
@@ -3344,7 +3344,7 @@ function data_comment_validate($comment_param) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function data_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function data_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-data-*'=>get_string('page-mod-data-x', 'data'));
     return $module_pagetype;
 }
index af2dfcb..30402b2 100644 (file)
@@ -213,11 +213,7 @@ class feedback_item_multichoice extends feedback_item_base {
             $analysedVals = $analysedItem[2];
             $pixnr = 0;
             foreach($analysedVals as $val) {
-                if( function_exists("bcmod")) {
-                    $intvalue = bcmod($pixnr, 10);
-                }else {
-                    $intvalue = 0;
-                }
+                $intvalue = $pixnr % 10;
                 $pix = "pics/$intvalue.gif";
                 $pixnr++;
                 $pixwidth = intval($val->quotient * FEEDBACK_MAX_PIX_LENGTH);
index 77a2b04..41764e9 100644 (file)
@@ -184,11 +184,7 @@ class feedback_item_multichoicerated extends feedback_item_base {
             $pixnr = 0;
             $avg = 0.0;
             foreach($analysedVals as $val) {
-                if( function_exists("bcmod")) {
-                    $intvalue = bcmod($pixnr, 10);
-                }else {
-                    $intvalue = 0;
-                }
+                $intvalue = $pixnr % 10;
                 $pix = "pics/$intvalue.gif";
                 $pixnr++;
                 $pixwidth = intval($val->quotient * FEEDBACK_MAX_PIX_LENGTH);
index b777e7e..59f574f 100644 (file)
@@ -2788,7 +2788,7 @@ function feedback_init_feedback_session() {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function feedback_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function feedback_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-feedback-*'=>get_string('page-mod-feedback-x', 'feedback'));
     return $module_pagetype;
 }
index 20775d5..491e5ed 100644 (file)
@@ -343,7 +343,7 @@ function folder_extend_navigation($navigation, $course, $module, $cm) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function folder_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function folder_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-folder-*'=>get_string('page-mod-folder-x', 'folder'));
     return $module_pagetype;
 }
index 03ad341..208e008 100644 (file)
@@ -7964,7 +7964,7 @@ function forum_cm_info_view(cm_info $cm) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function forum_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $forum_pagetype = array(
         'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
         'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
index d488752..8df1014 100644 (file)
@@ -2861,7 +2861,7 @@ function glossary_comment_validate($comment_param) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function glossary_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function glossary_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-glossary-*'=>get_string('page-mod-glossary-x', 'glossary'));
     return $module_pagetype;
 }
index 697c293..af804cf 100644 (file)
@@ -407,7 +407,7 @@ function imscp_extend_navigation($navigation, $course, $module, $cm) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function imscp_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function imscp_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-imscp-*'=>get_string('page-mod-imscp-x', 'imscp'));
     return $module_pagetype;
 }
index d207aae..384cb47 100644 (file)
@@ -67,6 +67,13 @@ if (!$canmanage) {
 
 // record answer (if necessary) and show response (if none say if answer is correct or not)
 $page = $lesson->load_page(required_param('pageid', PARAM_INT));
+
+$userhasgrade = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
+$reviewmode = false;
+if ($userhasgrade && !$lesson->retake) {
+    $reviewmode = true;
+}
+
 // Check the page has answers [MDL-25632]
 if (count($page->answers) > 0) {
     $result = $page->record_attempt($context);
@@ -80,7 +87,7 @@ if (count($page->answers) > 0) {
 
 if (isset($USER->modattempts[$lesson->id])) {
     // make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time
-    if ($USER->modattempts[$lesson->id] == $page->id && $page->nextpageid == 0) {  // remember, this session variable holds the pageid of the last page that the user saw
+    if ($USER->modattempts[$lesson->id]->pageid == $page->id && $page->nextpageid == 0) {  // remember, this session variable holds the pageid of the last page that the user saw
         $result->newpageid = LESSON_EOL;
     } else {
         $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
@@ -153,11 +160,11 @@ if ($canmanage) {
     }
 }
 // Report attempts remaining
-if ($result->attemptsremaining != 0 && !$lesson->review) {
+if ($result->attemptsremaining != 0 && !$lesson->review && !$reviewmode) {
     $lesson->add_message(get_string('attemptsremaining', 'lesson', $result->attemptsremaining));
 }
 // Report if max attempts reached
-if ($result->maxattemptsreached != 0 && !$lesson->review) {
+if ($result->maxattemptsreached != 0 && !$lesson->review && !$reviewmode) {
     $lesson->add_message('('.get_string("maximumnumberofattemptsreached", "lesson").')');
 }
 
@@ -172,7 +179,7 @@ if ($lesson->displayleft) {
     echo '<a name="maincontent" id="maincontent" title="'.get_string('anchortitle', 'lesson').'"></a>';
 }
 // This calculates and prints the ongoing score message
-if ($lesson->ongoing) {
+if ($lesson->ongoing && !$reviewmode) {
     echo $lessonoutput->ongoing_score($lesson);
 }
 echo $result->feedback;
@@ -180,17 +187,17 @@ echo $result->feedback;
 // User is modifying attempts - save button and some instructions
 if (isset($USER->modattempts[$lesson->id])) {
     $url = $CFG->wwwroot.'/mod/lesson/view.php';
-    $content = $OUTPUT->box(get_string("savechangesandeol", "lesson"), 'center');
+    $content = $OUTPUT->box(get_string("gotoendoflesson", "lesson"), 'center');
     $content .= $OUTPUT->box(get_string("or", "lesson"), 'center');
-    $content .= $OUTPUT->box(get_string("continuetoanswer", "lesson"), 'center');
+    $content .= $OUTPUT->box(get_string("continuetonextpage", "lesson"), 'center');
     $content .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'id', 'value'=>$cm->id));
     $content .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'pageid', 'value'=>LESSON_EOL));
-    $content .= html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'submit', 'value'=>get_string('savechanges', 'lesson')));
+    $content .= html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'submit', 'value'=>get_string('finish', 'lesson')));
     echo html_writer::tag('form', "<div>$content</div>", array('method'=>'post', 'action'=>$url));
 }
 
 // Review button back
-if ($lesson->review && !$result->correctanswer && !$result->noanswer && !$result->isessayquestion) {
+if (!$result->correctanswer && !$result->noanswer && !$result->isessayquestion && !$reviewmode) {
     $url = $CFG->wwwroot.'/mod/lesson/view.php';
     $content = html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'id', 'value'=>$cm->id));
     $content .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'pageid', 'value'=>$page->id));
index 6f131ad..11fbd4a 100644 (file)
@@ -97,7 +97,8 @@ function lesson_save_question_options($question, $lesson) {
                     $answer->timecreated   = $timenow;
                     $answer->grade = $question->fraction[$key] * 100;
                     $answer->answer   = $dataanswer;
-                    $answer->response = $question->feedback[$key];
+                    $answer->response = $question->feedback[$key]['text'];
+                    $answer->responseformat = $question->feedback[$key]['format'];
                     $answer->id = $DB->insert_record("lesson_answers", $answer);
                     $answers[] = $answer->id;
                     if ($question->fraction[$key] > $maxfraction) {
@@ -134,7 +135,8 @@ function lesson_save_question_options($question, $lesson) {
                     $max = $question->answer[$key] + $question->tolerance[$key];
                     $answer->answer   = $min.":".$max;
                     // $answer->answer   = $question->min[$key].":".$question->max[$key]; original line for min/max
-                    $answer->response = $question->feedback[$key];
+                    $answer->response = $question->feedback[$key]['text'];
+                    $answer->responseformat = $question->feedback[$key]['format'];
                     $answer->id = $DB->insert_record("lesson_answers", $answer);
 
                     $answers[] = $answer->id;
@@ -160,12 +162,13 @@ function lesson_save_question_options($question, $lesson) {
             $answer->pageid = $question->id;
             $answer->timecreated   = $timenow;
             $answer->answer = get_string("true", "quiz");
-            $answer->grade = $question->answer * 100;
+            $answer->grade = $question->correctanswer * 100;
             if ($answer->grade > 50 ) {
                 $answer->jumpto = LESSON_NEXTPAGE;
             }
             if (isset($question->feedbacktrue)) {
-                $answer->response = $question->feedbacktrue;
+                $answer->response = $question->feedbacktrue['text'];
+                $answer->responseformat = $question->feedbacktrue['format'];
             }
             $DB->insert_record("lesson_answers", $answer);
 
@@ -175,12 +178,13 @@ function lesson_save_question_options($question, $lesson) {
             $answer->pageid = $question->id;
             $answer->timecreated   = $timenow;
             $answer->answer = get_string("false", "quiz");
-            $answer->grade = (1 - (int)$question->answer) * 100;
+            $answer->grade = (1 - (int)$question->correctanswer) * 100;
             if ($answer->grade > 50 ) {
                 $answer->jumpto = LESSON_NEXTPAGE;
             }
             if (isset($question->feedbackfalse)) {
-                $answer->response = $question->feedbackfalse;
+                $answer->response = $question->feedbackfalse['text'];
+                $answer->responseformat = $question->feedbackfalse['format'];
             }
             $DB->insert_record("lesson_answers", $answer);
 
@@ -212,8 +216,10 @@ function lesson_save_question_options($question, $lesson) {
                         $answer->score = 1;
                     }
                     // end Replace
-                    $answer->answer   = $dataanswer;
-                    $answer->response = $question->feedback[$key];
+                    $answer->answer   = $dataanswer['text'];
+                    $answer->answerformat   = $dataanswer['format'];
+                    $answer->response = $question->feedback[$key]['text'];
+                    $answer->responseformat = $question->feedback[$key]['format'];
                     $answer->id = $DB->insert_record("lesson_answers", $answer);
                     // for Sanity checks
                     if ($question->fraction[$key] > 0) {
@@ -268,7 +274,8 @@ function lesson_save_question_options($question, $lesson) {
                 $answertext = $question->subanswers[$key];
                 if (!empty($questiontext) and !empty($answertext)) {
                     $answer = clone($defaultanswer);
-                    $answer->answer = $questiontext;
+                    $answer->answer = $questiontext['text'];
+                    $answer->answerformat   = $questiontext['format'];
                     $answer->response   = $answertext;
                     if ($i == 0) {
                         // first answer contains the correct answer jump
@@ -330,7 +337,9 @@ class qformat_default {
             return false;
         }
 
-        echo $OUTPUT->notification(get_string('importcount', 'lesson', sizeof($questions)));
+        //Avoid category as question type
+        echo $OUTPUT->notification(get_string('importcount', 'lesson',
+                $this->count_questions($questions)), 'notifysuccess');
 
         $count = 0;
 
@@ -338,6 +347,9 @@ class qformat_default {
 
         foreach ($questions as $question) {   // Process and store each question
             switch ($question->qtype) {
+                //TODO: Bad way to bypass category in data... Quickfix for MDL-27964
+                case 'category':
+                    break;
                 // the good ones
                 case SHORTANSWER :
                 case NUMERICAL :
@@ -346,7 +358,9 @@ class qformat_default {
                 case MATCH :
                     $count++;
 
-                    echo "<hr><p><b>$count</b>. ".$question->questiontext."</p>";
+                    //Show nice formated question in one line.
+                    echo "<hr><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
+
                     $newpage = new stdClass;
                     $newpage->lessonid = $lesson->id;
                     $newpage->qtype = $this->qtypeconvert[$question->qtype];
@@ -436,6 +450,27 @@ class qformat_default {
         return true;
     }
 
+    /**
+     * Count all non-category questions in the questions array.
+     *
+     * @param array questions An array of question objects.
+     * @return int The count.
+     *
+     */
+    protected function count_questions($questions) {
+        $count = 0;
+        if (!is_array($questions)) {
+            return $count;
+        }
+        foreach ($questions as $question) {
+            if (!is_object($question) || !isset($question->qtype) ||
+                    ($question->qtype == 'category')) {
+                continue;
+            }
+            $count++;
+        }
+        return $count;
+    }
 
     function readdata($filename) {
     /// Returns complete file with an array, one item per line
@@ -505,7 +540,7 @@ class qformat_default {
 
         $question = new stdClass();
         $question->shuffleanswers = get_config('quiz', 'shuffleanswers');
-        $question->defaultgrade = 1;
+        $question->defaultmark = 1;
         $question->image = "";
         $question->usecase = 0;
         $question->multiplier = array();
@@ -519,6 +554,11 @@ class qformat_default {
         $question->qoption = 0;
         $question->layout = 1;
 
+        // this option in case the questiontypes class wants
+        // to know where the data came from
+        $question->export_process = true;
+        $question->import_process = true;
+
         return $question;
     }
 
@@ -529,6 +569,16 @@ class qformat_default {
         return true;
     }
 
+    /**
+     * Convert the question text to plain text, so it can safely be displayed
+     * during import to let the user see roughly what is going on.
+     */
+    protected function format_question_text($question) {
+        $formatoptions = new stdClass();
+        $formatoptions->noclean = true;
+        return html_to_text(format_text($question->questiontext,
+                $question->questiontextformat, $formatoptions), 0, false);
+    }
 }
 
 
index 6d2921f..0b00e20 100644 (file)
@@ -66,9 +66,13 @@ if ($data = $mform->get_data()) {
 
     require_sesskey();
 
-    if (!$importfile = $mform->get_importfile_name()) {
-        print_error('uploadproblem', 'moodle');
-        }
+    $realfilename = $mform->get_new_filename('questionfile');
+    //TODO: Leave all imported questions in Questionimport for now.
+    $importfile = "{$CFG->dataroot}/temp/questionimport/{$realfilename}";
+    make_upload_directory('temp/questionimport');
+    if (!$result = $mform->save_file('questionfile', $importfile, true)) {
+        throw new moodle_exception('uploadproblem');
+    }
 
     $formatclass = 'qformat_'.$data->format;
     $formatclassfile = $CFG->dirroot.'/question/format/'.$data->format.'/format.php';
@@ -102,4 +106,4 @@ if ($data = $mform->get_data()) {
     $mform->display();
 }
 
-echo $OUTPUT->footer();
+echo $OUTPUT->footer();
\ No newline at end of file
index 4bd93d4..765d565 100644 (file)
@@ -48,30 +48,49 @@ class lesson_import_form extends moodleform {
         $mform->setType('format', 'text');
         $mform->addRule('format', null, 'required');
 
-        $mform->addElement('file', 'newfile', get_string('upload'), array('size'=>'50'));
-        $mform->addRule('newfile', null, 'required');
-
-        $this->add_action_buttons(null, get_string("uploadthisfile"));
+        //Using filemanager as filepicker
+        $mform->addElement('filepicker', 'questionfile', get_string('upload'));
+        $mform->addRule('questionfile', null, 'required', null, 'client');
 
+        $this->add_action_buttons(null, get_string("import"));
     }
 
-    public function get_importfile_name(){
-        if ($this->is_submitted() and $this->is_validated()) {
-            // return the temporary filename to process
-            return $_FILES['newfile']['tmp_name'];
-        }else{
-            return  NULL;
+    /**
+     * Checks that a file has been uploaded, and that it is of a plausible type.
+     * @param array $data the submitted data.
+     * @param array $errors the errors so far.
+     * @return array the updated errors.
+     */
+    protected function validate_uploaded_file($data, $errors) {
+        global $CFG;
+
+        if (empty($data['questionfile'])) {
+            $errors['questionfile'] = get_string('required');
+            return $errors;
+        }
+
+        $files = $this->get_draft_files('questionfile');
+        if (count($files) < 1) {
+            $errors['questionfile'] = get_string('required');
+            return $errors;
         }
-    }
 
-    public function get_importfile_realname(){
-        if ($this->is_submitted() and $this->is_validated()) {
-            // return the temporary filename to process
-            // TODO change this to use the files API properly.
-            return $_FILES['newfile']['name'];
-        }else{
-            return  NULL;
+        $formatfile = $CFG->dirroot.'/question/format/'.$data['format'].'/format.php';
+        if (!is_readable($formatfile)) {
+            throw new moodle_exception('formatnotfound', 'lesson', '', $data['format']);
         }
+
+        require_once($formatfile);
+
+        $classname = 'qformat_' . $data['format'];
+        $qformat = new $classname();
+
+        return $errors;
     }
 
+    public function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+        $errors = $this->validate_uploaded_file($data, $errors);
+        return $errors;
+    }
 }
\ No newline at end of file
index abc853a..372cf8b 100644 (file)
@@ -109,6 +109,7 @@ $string['confirmdeletionofthispage'] = 'Confirm deletion of this page';
 $string['congratulations'] = 'Congratulations - end of lesson reached';
 $string['continue'] = 'Continue';
 $string['continuetoanswer'] = 'Continue to change answers.';
+$string['continuetonextpage'] = 'Continue to next page.';
 $string['correctanswerjump'] = 'Correct answer jump';
 $string['correctanswerscore'] = 'Correct answer score';
 $string['correctresponse'] = 'Correct response';
@@ -166,11 +167,13 @@ $string['essayemailsubject'] = 'Your grade for {$a} question';
 $string['essays'] = 'Essays';
 $string['essayscore'] = 'Essay score';
 $string['fileformat'] = 'File format';
+$string['finish'] = 'Finish';
 $string['firstanswershould'] = 'First answer should jump to the "Correct" page';
 $string['firstwrong'] = 'Unfortunately you cannot earn this one point, because your response was not correct.  Would you like to keep guessing, just for the sheer joy of learning (but for no point credit)?';
 $string['flowcontrol'] = 'Flow control';
 $string['full'] = 'Expanded';
 $string['general'] = 'General';
+$string['gotoendoflesson'] = 'Go to the end of the lesson';
 $string['grade'] = 'Grade';
 $string['gradebetterthan'] = 'Grade better than (&#37;)';
 $string['gradebetterthanerror'] = 'Earn a grade better than {$a} percent';
@@ -378,6 +381,7 @@ $string['studentattemptlesson'] = '{$a->lastname}, {$a->firstname}\'s attempt nu
 $string['studentname'] = '{$a} Name';
 $string['studentoneminwarning'] = 'Warning: You have 1 minute or less to finish the lesson.';
 $string['studentresponse'] = '{$a}\'s response';
+$string['submit'] = 'Submit';
 $string['submitname'] = 'Submit name';
 $string['teacherjumpwarning'] = 'An {$a->cluster} jump or an {$a->unseen} jump is being used in this lesson.  The next page jump will be used instead.  Login as a student to test these jumps.';
 $string['teacherongoingwarning'] = 'Ongoing score is only displayed for student.  Login as a student to test ongoing score';
index cfb1bbc..1b4906e 100644 (file)
@@ -989,7 +989,7 @@ function lesson_get_file_info($browser, $areas, $course, $cm, $context, $fileare
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function lesson_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function lesson_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-lesson-*'=>get_string('page-mod-lesson-x', 'lesson'));
     return $module_pagetype;
 }
index 84a8661..f26f1be 100644 (file)
@@ -1961,13 +1961,15 @@ abstract class lesson_page extends lesson_base {
                     $attempt->retry = $nretakes - 1; // they are going through on review, $nretakes will be too high
                 }
 
-                $DB->insert_record("lesson_attempts", $attempt);
+                if ($this->lesson->retake || (!$this->lesson->retake && $nretakes == 0)) {
+                    $DB->insert_record("lesson_attempts", $attempt);
+                }
                 // "number of attempts remaining" message if $this->lesson->maxattempts > 1
                 // displaying of message(s) is at the end of page for more ergonomic display
                 if (!$result->correctanswer && ($result->newpageid == 0)) {
                     // wrong answer and student is stuck on this page - check how many attempts
                     // the student has had at this page/question
-                    $nattempts = $DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id, "retry" => $nretakes));
+                    $nattempts = $DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id, "retry" => $attempt->retry));
                     // retreive the number of attempts left counter for displaying at bottom of feedback page
                     if ($nattempts >= $this->lesson->maxattempts) {
                         if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
index 2f7d4f9..b1c8136 100644 (file)
@@ -51,14 +51,14 @@ class lesson_page_type_essay extends lesson_page {
     public function display($renderer, $attempt) {
         global $PAGE, $CFG, $USER;
 
-        $mform = new lesson_display_answer_form_essay($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents()));
+        $mform = new lesson_display_answer_form_essay($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents(), 'lessonid'=>$this->lesson->id));
 
         $data = new stdClass;
         $data->id = $PAGE->cm->id;
         $data->pageid = $this->properties->id;
         if (isset($USER->modattempts[$this->lesson->id])) {
             $essayinfo = unserialize($attempt->useranswer);
-            $data->answer = array('text'=>$essayinfo->answer, 'format'=>FORMAT_HTML);
+            $data->answer = $essayinfo->answer;
         }
         $mform->set_data($data);
         return $mform->display();
@@ -252,6 +252,20 @@ class lesson_display_answer_form_essay extends moodleform {
         $mform = $this->_form;
         $contents = $this->_customdata['contents'];
 
+        $hasattempt = false;
+        $attrs = '';
+        $useranswer = '';
+        $useranswerraw = '';
+        if (isset($this->_customdata['lessonid'])) {
+            $lessonid = $this->_customdata['lessonid'];
+            if (isset($USER->modattempts[$lessonid]->useranswer) && !empty($USER->modattempts[$lessonid]->useranswer)) {
+                $attrs = array('disabled' => 'disabled');
+                $hasattempt = true;
+                $useranswer = unserialize($USER->modattempts[$lessonid]->useranswer);
+                $useranswer = htmlspecialchars_decode($useranswer->answer, ENT_QUOTES);
+            }
+        }
+
         $mform->addElement('header', 'pageheader');
 
         $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
@@ -266,10 +280,16 @@ class lesson_display_answer_form_essay extends moodleform {
         $mform->addElement('hidden', 'pageid');
         $mform->setType('pageid', PARAM_INT);
 
-        $mform->addElement('editor', 'answer', get_string('youranswer', 'lesson'), null, null);
-        $mform->setType('answer', PARAM_RAW);
-
-        $this->add_action_buttons(null, get_string("pleaseenteryouranswerinthebox", "lesson"));
+        if ($hasattempt) {
+            $mform->addElement('hidden', 'answer', $useranswerraw);
+            $mform->setType('answer', PARAM_CLEANHTML);
+            $mform->addElement('html', $OUTPUT->container(get_string('youranswer', 'lesson'), 'youranswer'));
+            $mform->addElement('html', $OUTPUT->container($useranswer, 'reviewessay'));
+            $this->add_action_buttons(null, get_string("nextpage", "lesson"));
+        } else {
+            $mform->addElement('editor', 'answer', get_string('youranswer', 'lesson'), null, null);
+            $mform->setType('answer', PARAM_RAW);
+            $this->add_action_buttons(null, get_string("submit", "lesson"));
+        }
     }
-
 }
index 3e40c63..e9ffa09 100644 (file)
@@ -61,7 +61,13 @@ class lesson_page_type_matching extends lesson_page {
     protected function make_answer_form($attempt=null) {
         global $USER, $CFG;
         // don't shuffle answers (could be an option??)
-        $answers = array_slice($this->get_answers(), 2);
+        $getanswers = array_slice($this->get_answers(), 2);
+
+        $answers = array();
+        foreach ($getanswers as $getanswer) {
+            $answers[$getanswer->id] = $getanswer;
+        }
+
         $responses = array();
         foreach ($answers as $answer) {
             // get all the response
@@ -487,6 +493,13 @@ class lesson_display_answer_form_matching extends moodleform {
 
         $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
 
+        $hasattempt = false;
+        $disabled = '';
+        if (isset($useranswers) && !empty($useranswers)) {
+            $hasattempt = true;
+            $disabled = array('disabled' => 'disabled');
+        }
+
         $options = new stdClass;
         $options->para = false;
         $options->noclean = true;
@@ -501,19 +514,28 @@ class lesson_display_answer_form_matching extends moodleform {
         foreach ($answers as $answer) {
             $mform->addElement('html', '<div class="answeroption">');
             if ($answer->response != NULL) {
-                $mform->addElement('select', 'response['.$answer->id.']', format_text($answer->answer,$answer->answerformat,$options), $responseoptions);
-                $mform->setType('response['.$answer->id.']', PARAM_TEXT);
-                if (isset($USER->modattempts[$lessonid])) {
-                    $mform->setDefault('response['.$answer->id.']', htmlspecialchars(trim($answers[$useranswers[$i]]->response))); //TODO: this is suspicious
+                $responseid = 'response['.$answer->id.']';
+                if ($hasattempt) {
+                    $responseid = 'response_'.$answer->id;
+                    $mform->addElement('hidden', 'response['.$answer->id.']', htmlspecialchars(trim($answers[$useranswers[$i]]->response)));
+                    $mform->setType('response['.$answer->id.']', PARAM_TEXT);
+                }
+                $mform->addElement('select', $responseid, format_text($answer->answer,$answer->answerformat,$options), $responseoptions, $disabled);
+                $mform->setType($responseid, PARAM_TEXT);
+                if ($hasattempt) {
+                    $mform->setDefault($responseid, htmlspecialchars(trim($answers[$useranswers[$i]]->response))); //TODO: this is suspicious
                 } else {
-                    $mform->setDefault('response['.$answer->id.']', 'answeroption');
+                    $mform->setDefault($responseid, 'answeroption');
                 }
             }
             $mform->addElement('html', '</div>');
             $i++;
         }
-
-        $this->add_action_buttons(null, get_string("pleasematchtheabovepairs", "lesson"));
+        if ($hasattempt) {
+            $this->add_action_buttons(null, get_string("nextpage", "lesson"));
+        } else {
+            $this->add_action_buttons(null, get_string("submit", "lesson"));
+        }
     }
 
 }
index f29268f..078af89 100644 (file)
@@ -490,6 +490,13 @@ class lesson_display_answer_form_multichoice_singleanswer extends moodleform {
 
         $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
 
+        $hasattempt = false;
+        $disabled = '';
+        if (isset($USER->modattempts[$lessonid]) && !empty($USER->modattempts[$lessonid])) {
+            $hasattempt = true;
+            $disabled = array('disabled' => 'disabled');
+        }
+
         $options = new stdClass;
         $options->para = false;
         $options->noclean = true;
@@ -503,16 +510,20 @@ class lesson_display_answer_form_multichoice_singleanswer extends moodleform {
         $i = 0;
         foreach ($answers as $answer) {
             $mform->addElement('html', '<div class="answeroption">');
-            $mform->addElement('radio','answerid',null,format_text($answer->answer, $answer->answerformat, $options),$answer->id);
+            $mform->addElement('radio','answerid',null,format_text($answer->answer, $answer->answerformat, $options),$answer->id, $disabled);
             $mform->setType('answer'.$i, PARAM_INT);
-            if (isset($USER->modattempts[$lessonid]) && $answer->id == $USER->modattempts[$lessonid]->answerid) {
-                $mform->setDefault('answerid', true);
+            if ($hasattempt && $answer->id == $USER->modattempts[$lessonid]->answerid) {
+                $mform->setDefault('answerid', $USER->modattempts[$lessonid]->answerid);
             }
             $mform->addElement('html', '</div>');
             $i++;
         }
 
-        $this->add_action_buttons(null, get_string("pleasecheckoneanswer", "lesson"));
+        if ($hasattempt) {
+            $this->add_action_buttons(null, get_string("nextpage", "lesson"));
+        } else {
+            $this->add_action_buttons(null, get_string("submit", "lesson"));
+        }
     }
 
 }
index 9e5c0f5..8681fdf 100644 (file)
@@ -50,7 +50,7 @@ class lesson_page_type_shortanswer extends lesson_page {
     }
     public function display($renderer, $attempt) {
         global $USER, $CFG, $PAGE;
-        $mform = new lesson_display_answer_form_shortanswer($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents()));
+        $mform = new lesson_display_answer_form_shortanswer($CFG->wwwroot.'/mod/lesson/continue.php', array('contents'=>$this->get_contents(), 'lessonid'=>$this->lesson->id));
         $data = new stdClass;
         $data->id = $PAGE->cm->id;
         $data->pageid = $this->properties->id;
@@ -328,10 +328,20 @@ class lesson_add_page_form_shortanswer extends lesson_add_page_form_base {
 class lesson_display_answer_form_shortanswer extends moodleform {
 
     public function definition() {
-        global $OUTPUT;
+        global $OUTPUT, $USER;
         $mform = $this->_form;
         $contents = $this->_customdata['contents'];
 
+        $hasattempt = false;
+        $attrs = array('size'=>'50', 'maxlength'=>'200');
+        if (isset($this->_customdata['lessonid'])) {
+            $lessonid = $this->_customdata['lessonid'];
+            if (isset($USER->modattempts[$lessonid]->useranswer)) {
+                $attrs['readonly'] = 'readonly';
+                $hasattempt = true;
+            }
+        }
+
         $mform->addElement('header', 'pageheader');
 
         $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
@@ -346,10 +356,14 @@ class lesson_display_answer_form_shortanswer extends moodleform {
         $mform->addElement('hidden', 'pageid');
         $mform->setType('pageid', PARAM_INT);
 
-        $mform->addElement('text', 'answer', get_string('youranswer', 'lesson'), array('size'=>'50', 'maxlength'=>'200'));
+        $mform->addElement('text', 'answer', get_string('youranswer', 'lesson'), $attrs);
         $mform->setType('answer', PARAM_TEXT);
 
-        $this->add_action_buttons(null, get_string("pleaseenteryouranswerinthebox", "lesson"));
+        if ($hasattempt) {
+            $this->add_action_buttons(null, get_string("nextpage", "lesson"));
+        } else {
+            $this->add_action_buttons(null, get_string("submit", "lesson"));
+        }
     }
 
 }
index 61a1a7e..6b8977b 100644 (file)
@@ -303,6 +303,13 @@ class lesson_display_answer_form_truefalse extends moodleform {
 
         $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
 
+        $hasattempt = false;
+        $disabled = '';
+        if (isset($USER->modattempts[$lessonid]) && !empty($USER->modattempts[$lessonid])) {
+            $hasattempt = true;
+            $disabled = array('disabled' => 'disabled');
+        }
+
         $options = new stdClass();
         $options->para = false;
         $options->noclean = true;
@@ -316,16 +323,28 @@ class lesson_display_answer_form_truefalse extends moodleform {
         $i = 0;
         foreach ($answers as $answer) {
             $mform->addElement('html', '<div class="answeroption">');
-            $mform->addElement('radio', 'answerid', null, format_text($answer->answer, $answer->answerformat, $options), $answer->id);
-            $mform->setType('answerid', PARAM_INT);
-            if (isset($USER->modattempts[$lessonid]) && $answer->id == $attempt->answerid) {
-                $mform->setDefault('answerid', true);
+            $ansid = 'answerid';
+            if ($hasattempt) {
+                $ansid = 'answer_id';
+            }
+
+            $mform->addElement('radio', $ansid, null, format_text($answer->answer, $answer->answerformat, $options), $answer->id, $disabled);
+            $mform->setType($ansid, PARAM_INT);
+            if ($hasattempt && $answer->id == $USER->modattempts[$lessonid]->answerid) {
+                $mform->setDefault($ansid, $attempt->answerid);
+                $mform->addElement('hidden', 'answerid', $answer->id);
+                $mform->setType('answerid', PARAM_INT);
             }
             $mform->addElement('html', '</div>');
             $i++;
         }
 
-        $this->add_action_buttons(null, get_string("pleasecheckoneanswer", "lesson"));
+        if ($hasattempt) {
+            $this->add_action_buttons(null, get_string("nextpage", "lesson"));
+        } else {
+            $this->add_action_buttons(null, get_string("submit", "lesson"));
+        }
+
     }
 
 }
index 99028da..fd4541a 100644 (file)
@@ -28,4 +28,5 @@
 /**
  * Style for view.php
  **/
-#page-mod-lesson-view .password-form .submitbutton {display: inline;}
\ No newline at end of file
+#page-mod-lesson-view .password-form .submitbutton {display: inline;}
+.path-mod-lesson .reviewessay {width:40%; border:1px solid #DDDDDD; background-color: #EEEEEE;}
index c82d589..855b755 100644 (file)
@@ -60,6 +60,12 @@ $canmanage = has_capability('mod/lesson:manage', $context);
 
 $lessonoutput = $PAGE->get_renderer('mod_lesson');
 
+$reviewmode = false;
+$userhasgrade = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
+if ($userhasgrade && !$lesson->retake) {
+    $reviewmode = true;
+}
+
 /// Check these for students only TODO: Find a better method for doing this!
 ///     Check lesson availability
 ///     Check for password
@@ -316,11 +322,14 @@ if ($pageid != LESSON_EOL) {
                     $a->minquestions = $lesson->minquestions;
                     $lesson->add_message(get_string('numberofpagesviewednotice', 'lesson', $a));
                 }
-                $lesson->add_message(get_string("numberofcorrectanswers", "lesson", $gradeinfo->earned), 'notify');
+
                 $a = new stdClass;
                 $a->grade = number_format($gradeinfo->grade * $lesson->grade / 100, 1);
                 $a->total = $lesson->grade;
-                $lesson->add_message(get_string('yourcurrentgradeisoutof', 'lesson', $a), 'notify');
+                if (!$reviewmode && !$lesson->retake){
+                    $lesson->add_message(get_string("numberofcorrectanswers", "lesson", $gradeinfo->earned), 'notify');
+                    $lesson->add_message(get_string('yourcurrentgradeisoutof', 'lesson', $a), 'notify');
+                }
             }
         }
     } else {
@@ -356,6 +365,7 @@ if ($pageid != LESSON_EOL) {
                 print_error('cannotfindpreattempt', 'lesson');
             }
             $attempt = end($attempts);
+            $USER->modattempts[$lesson->id] = $attempt;
         } else {
             $attempt = false;
         }
@@ -387,7 +397,7 @@ if ($pageid != LESSON_EOL) {
         echo $OUTPUT->heading(get_string('attempt', 'lesson', $retries));
     }
     /// This calculates and prints the ongoing score
-    if ($lesson->ongoing && !empty($pageid)) {
+    if ($lesson->ongoing && !empty($pageid) && !$reviewmode) {
         echo $lessonoutput->ongoing_score($lesson);
     }
     if ($lesson->displayleft) {
@@ -538,17 +548,18 @@ if ($pageid != LESSON_EOL) {
         // $ntries is decremented above
         if (!$attempts = $lesson->get_attempts($ntries)) {
             $attempts = array();
+            $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id));
+        } else {
+            $firstattempt = current($attempts);
+            $pageid = $firstattempt->pageid;
+            // IF the student wishes to review, need to know the last question page that the student answered.  This will help to make
+            // sure that the student can leave the lesson via pushing the continue button.
+            $lastattempt = end($attempts);
+            $USER->modattempts[$lesson->id] = $lastattempt->pageid;
+
+            $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$pageid));
         }
-        $firstattempt = current($attempts);
-        $pageid = $firstattempt->pageid;
-        // IF the student wishes to review, need to know the last question page that the student answered.  This will help to make
-        // sure that the student can leave the lesson via pushing the continue button.
-        $lastattempt = end($attempts);
-        $USER->modattempts[$lesson->id] = $lastattempt->pageid;
-
-        $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$pageid));
         $lessoncontent .= html_writer::link($url, get_string('reviewlesson', 'lesson'), array('class' => 'centerpadded lessonbutton standardbutton'));
-
     } elseif ($lesson->modattempts && $canmanage) {
         $lessoncontent .= $lessonoutput->paragraph(get_string("modattemptsnoteacher", "lesson"), 'centerpadded');
     }
index 300051b..2fcd026 100644 (file)
@@ -411,7 +411,7 @@ function page_extend_navigation($navigation, $course, $module, $cm) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function page_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function page_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-page-*'=>get_string('page-mod-page-x', 'page'));
     return $module_pagetype;
 }
index b2ed15b..a80f87c 100644 (file)
@@ -271,27 +271,19 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
     }
 
     protected function process_quiz_attempt($data) {
-        global $DB;
-
         $data = (object)$data;
-        $oldid = $data->id;
-        $olduniqueid = $data->uniqueid;
 
         $data->quiz = $this->get_new_parentid('quiz');
         $data->attempt = $data->attemptnum;
 
-        $data->uniqueid = 0; // filled in later by {@link inform_new_usage_id()}
-
         $data->userid = $this->get_mappingid('user', $data->userid);
 
         $data->timestart = $this->apply_date_offset($data->timestart);
         $data->timefinish = $this->apply_date_offset($data->timefinish);
         $data->timemodified = $this->apply_date_offset($data->timemodified);
 
-        $newitemid = $DB->insert_record('quiz_attempts', $data);
-
-        // Save quiz_attempt->id mapping, because logs use it
-        $this->set_mapping('quiz_attempt', $oldid, $newitemid, false);
+        // The data is actually inserted into the database later in inform_new_usage_id.
+        $this->currentquizattempt = clone($data);
     }
 
     protected function process_quiz_attempt_legacy($data) {
@@ -306,8 +298,16 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
 
     protected function inform_new_usage_id($newusageid) {
         global $DB;
-        $DB->set_field('quiz_attempts', 'uniqueid', $newusageid, array('id' =>
-                $this->get_new_parentid('quiz_attempt')));
+
+        $data = $this->currentquizattempt;
+
+        $oldid = $data->id;
+        $data->uniqueid = $newusageid;
+
+        $newitemid = $DB->insert_record('quiz_attempts', $data);
+
+        // Save quiz_attempt->id mapping, because logs use it
+        $this->set_mapping('quiz_attempt', $oldid, $newitemid, false);
     }
 
     protected function after_execute() {
index 4cf6fcf..3eadb0b 100644 (file)
@@ -1710,7 +1710,7 @@ function mod_quiz_question_pluginfile($course, $context, $component,
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function quiz_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function quiz_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-quiz-*'=>get_string('page-mod-quiz-x', 'quiz'));
     return $module_pagetype;
 }
index 3efca32..0e57182 100644 (file)
@@ -880,7 +880,7 @@ function quiz_question_preview_url($quiz, $question) {
  * @return the HTML for a preview question icon.
  */
 function quiz_question_preview_button($quiz, $question, $label = false) {
-    global $CFG, $COURSE, $OUTPUT;
+    global $CFG, $OUTPUT;
     if (!question_has_capability_on($question, 'use', $question->category)) {
         return '';
     }
index 803c89b..be79a32 100644 (file)
@@ -436,7 +436,7 @@ function resource_pluginfile($course, $cm, $context, $filearea, $args, $forcedow
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function resource_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function resource_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-resource-*'=>get_string('page-mod-resource-x', 'resource'));
     return $module_pagetype;
 }
index bbddc92..b799907 100644 (file)
@@ -1078,7 +1078,7 @@ function scorm_print_overview($courses, &$htmlarray) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function scorm_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function scorm_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-scorm-*'=>get_string('page-mod-scorm-x', 'scorm'));
     return $module_pagetype;
 }
index 9c3d380..bed1fa1 100644 (file)
@@ -871,7 +871,7 @@ function survey_extend_settings_navigation($settings, $surveynode) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function survey_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function survey_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-survey-*'=>get_string('page-mod-survey-x', 'survey'));
     return $module_pagetype;
 }
index 9e43a41..e354511 100644 (file)
@@ -318,7 +318,7 @@ function url_extend_navigation($navigation, $course, $module, $cm) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function url_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function url_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-url-*'=>get_string('page-mod-url-x', 'url'));
     return $module_pagetype;
 }
index 42c8429..7f9a3d6 100644 (file)
@@ -665,7 +665,7 @@ function wiki_comment_validate($comment_param) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function wiki_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function wiki_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array(
         'mod-wiki-*'=>get_string('page-mod-wiki-x', 'wiki'),
         'mod-wiki-view'=>get_string('page-mod-wiki-view', 'wiki'),
index a4d86a3..26880ed 100644 (file)
@@ -1382,7 +1382,7 @@ function workshop_extend_settings_navigation(settings_navigation $settingsnav, n
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function workshop_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
     return $module_pagetype;
 }
index f01d870..0744211 100644 (file)
@@ -284,6 +284,6 @@ function note_delete_all($courseid) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function note_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function note_page_type_list($pagetype, $parentcontext, $currentcontext) {
     return array('notes-*'=>get_string('page-notes-x', 'notes'));
 }
index e749921..cc855cc 100644 (file)
@@ -294,6 +294,9 @@ if ($component === 'blog') {
 // ========================================================================================================================
 } else if ($component === 'user') {
     if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) {
+        // XXX: pix_url will initialize $PAGE, so we have to set up context here
+        // this temp hack should be fixed by better solution
+        $PAGE->set_context(get_system_context());
         if (!empty($CFG->forcelogin) and !isloggedin()) {
             // protect images if login required and not logged in;
             // do not use require_login() because it is expensive and not suitable here anyway
index afb8891..d7c082a 100644 (file)
@@ -1152,7 +1152,8 @@ class question_bank_view {
     }
 
     public function preview_question_url($question) {
-        return question_preview_url($question->id);
+        return question_preview_url($question->id, null, null, null, null,
+                $this->contexts->lowest());
     }
 
     /**
index 3a9f50a..0553b4e 100644 (file)
@@ -435,6 +435,7 @@ class qbehaviour_informationitem_converter extends question_behaviour_attempt_up
 
 class qbehaviour_adaptive_converter extends question_behaviour_attempt_updater {
     protected $try;
+    protected $laststepwasatry = false;
     protected $finished = false;
     protected $bestrawgrade = 0;
 
@@ -455,7 +456,7 @@ class qbehaviour_adaptive_converter extends question_behaviour_attempt_updater {
 
     protected function process0($step, $state) {
         $this->try = 1;
-        $step->data['-_try'] = $this->try;
+        $this->laststepwasatry = false;
         parent::process0($step, $state);
     }
 
@@ -471,6 +472,7 @@ class qbehaviour_adaptive_converter extends question_behaviour_attempt_updater {
             $step->fraction = $state->grade / $this->question->maxmark;
         }
 
+        $this->laststepwasatry = false;
         parent::process2($step, $state);
     }
 
@@ -488,8 +490,9 @@ class qbehaviour_adaptive_converter extends question_behaviour_attempt_updater {
 
         $this->bestrawgrade = max($state->raw_grade, $this->bestrawgrade);
 
-        $this->try += 1;
         $step->data['-_try'] = $this->try;
+        $this->try += 1;
+        $this->laststepwasatry = true;
         if ($this->question->maxmark > 0) {
             $step->data['-_rawfraction'] = $state->raw_grade / $this->question->maxmark;
         } else {
@@ -524,6 +527,9 @@ class qbehaviour_adaptive_converter extends question_behaviour_attempt_updater {
         }
 
         $step->data['-finish'] = 1;
+        if ($this->laststepwasatry) {
+            $this->try -= 1;
+        }
         $step->data['-_try'] = $this->try;
         if ($this->question->maxmark > 0) {
             $step->data['-_rawfraction'] = $state->raw_grade / $this->question->maxmark;
index e6085a6..ea24399 100644 (file)
@@ -37,19 +37,36 @@ require_once(dirname(__FILE__) . '/previewlib.php');
 // Get and validate question id.
 $id = required_param('id', PARAM_INT);
 $question = question_bank::load_question($id);
-require_login();
-$category = $DB->get_record('question_categories',
-        array('id' => $question->category), '*', MUST_EXIST);
-question_require_capability_on($question, 'use');
 $PAGE->set_pagelayout('popup');
-$PAGE->set_context(get_context_instance_by_id($category->contextid));
+
+// Were we given a particular context to run the question in?
+// This affects things like filter settings, or forced theme or language.
+if ($cmid = optional_param('cmid', 0, PARAM_INT)) {
+    $cm = get_coursemodule_from_id(false, $cmid);
+    require_login($cm->course, false, $cm);
+    $context = get_context_instance(CONTEXT_MODULE, $cmid);
+
+} else if ($courseid = optional_param('courseid', 0, PARAM_INT)) {
+    require_login($courseid);
+    $context = get_context_instance(CONTEXT_COURSE, $courseid);
+
+} else {
+    require_login();
+    $category = $DB->get_record('question_categories',
+            array('id' => $question->category), '*', MUST_EXIST);
+    $context = get_context_instance_by_id($category->contextid);
+    $PAGE->set_context($context);
+    // Note that in the other cases, require_login will set the correct page context.
+}
+question_require_capability_on($question, 'use');
 
 // Get and validate display options.
 $maxvariant = $question->get_num_variants();
 $options = new question_preview_options($question);
 $options->load_user_defaults();
 $options->set_from_request();
-$PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, $options));
+$PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark,
+        $options, $options->variant, $context));
 
 // Get and validate exitsing preview, or start a new one.
 $previewid = optional_param('previewid', 0, PARAM_INT);
@@ -63,7 +80,7 @@ if ($previewid) {
     } catch (Exception $e) {
         print_error('submissionoutofsequencefriendlymessage', 'question',
                 question_preview_url($question->id, $options->behaviour,
-                $options->maxmark, $options), null, $e);
+                $options->maxmark, $options, $options->variant, $context), null, $e);
     }
     $slot = $quba->get_first_question_number();
     $usedquestion = $quba->get_question($slot);
@@ -74,8 +91,8 @@ if ($previewid) {
     $options->variant = $quba->get_variant($slot);
 
 } else {
-    $quba = question_engine::make_questions_usage_by_activity('core_question_preview',
-            get_context_instance_by_id($category->contextid));
+    $quba = question_engine::make_questions_usage_by_activity(
+            'core_question_preview', $context);
     $quba->set_preferred_behaviour($options->behaviour);
     $slot = $quba->add_question($question, $options->maxmark);
 
@@ -97,8 +114,8 @@ $options->behaviour = $quba->get_preferred_behaviour();
 $options->maxmark = $quba->get_question_max_mark($slot);
 
 // Create the settings form, and initialise the fields.
-$optionsform = new preview_options_form(new moodle_url('/question/preview.php',
-        array('id' => $question->id)), array('quba' => $quba, 'maxvariant' => $maxvariant));
+$optionsform = new preview_options_form(question_preview_form_url($question->id, $context),
+        array('quba' => $quba, 'maxvariant' => $maxvariant));
 $optionsform->set_data($options);
 
 // Process change of settings, if that was requested.
@@ -108,16 +125,16 @@ if ($newoptions = $optionsform->get_submitted_data()) {
     if (!isset($newoptions->variant)) {
         $newoptions->variant = $options->variant;
     }
-    restart_preview($previewid, $question->id, $newoptions);
+    restart_preview($previewid, $question->id, $newoptions, $context);
 }
 
 // Prepare a URL that is used in various places.
-$actionurl = question_preview_action_url($question->id, $quba->get_id(), $options);
+$actionurl = question_preview_action_url($question->id, $quba->get_id(), $options, $context);
 
 // Process any actions from the buttons at the bottom of the form.
 if (data_submitted() && confirm_sesskey()) {
     if (optional_param('restart', false, PARAM_BOOL)) {
-        restart_preview($previewid, $question->id, $options);
+        restart_preview($previewid, $question->id, $options, $context);
 
     } else if (optional_param('fill', null, PARAM_BOOL)) {
         $correctresponse = $quba->get_correct_response($slot);
index 6e34333..0978e87 100644 (file)
@@ -230,10 +230,6 @@ function question_preview_question_pluginfile($course, $context, $component,
 
     $quba = question_engine::load_questions_usage_by_activity($qubaid);
 
-    if ($quba->get_owning_context()->id != $context->id) {
-        send_file_not_found();
-    }
-
     if (!question_has_capability_on($quba->get_question($slot), 'use')) {
         send_file_not_found();
     }
@@ -267,22 +263,46 @@ function question_preview_question_pluginfile($course, $context, $component,
  * @param question_preview_options $options the options in use.
  */
 function question_preview_action_url($questionid, $qubaid,
-        question_preview_options $options) {
+        question_preview_options $options, $context) {
     $params = array(
         'id' => $questionid,
         'previewid' => $qubaid,
     );
+    if ($context->contextlevel == CONTEXT_MODULE) {
+        $params['cmid'] = $context->instanceid;
+    } else if ($context->contextlevel == CONTEXT_COURSE) {
+        $params['courseid'] = $context->instanceid;
+    }
     $params = array_merge($params, $options->get_url_params());
     return new moodle_url('/question/preview.php', $params);
 }
 
+/**
+ * The the URL to use for actions relating to this preview.
+ * @param int $questionid the question being previewed.
+ * @param int $qubaid the id of the question usage for this preview.
+ * @param question_preview_options $options the options in use.
+ */
+function question_preview_form_url($questionid, $context) {
+    $params = array(
+        'id' => $questionid,
+    );
+    if ($context->contextlevel == CONTEXT_MODULE) {
+        $params['cmid'] = $context->instanceid;
+    } else if ($context->contextlevel == CONTEXT_COURSE) {
+        $params['courseid'] = $context->instanceid;
+    }
+    return new moodle_url('/question/preview.php', $params);
+}
+
 /**
  * Delete the current preview, if any, and redirect to start a new preview.
  * @param int $previewid
  * @param int $questionid
  * @param object $displayoptions
+ * @param object $context
  */
-function restart_preview($previewid, $questionid, $displayoptions) {
+function restart_preview($previewid, $questionid, $displayoptions, $context) {
     global $DB;
 
     if ($previewid) {
@@ -291,5 +311,5 @@ function restart_preview($previewid, $questionid, $displayoptions) {
         $transaction->allow_commit();
     }
     redirect(question_preview_url($questionid, $displayoptions->behaviour,
-            $displayoptions->maxmark, $displayoptions, $displayoptions->variant));
+            $displayoptions->maxmark, $displayoptions, $displayoptions->variant, $context));
 }
index 315dd3c..6a3f315 100644 (file)
@@ -219,7 +219,7 @@ Remember to type a unit.',
                     'fraction' => null,
                     'timecreated' => 1305830650,
                     'userid' => 4,
-                    'data' => array('-_try' => 1, '_separators' => '.$,',
+                    'data' => array('_separators' => '.$,',
                             '_var_a' => '7.5', '_var_b' => '4.9'),
                 ),
                 1 => (object) array(
@@ -442,7 +442,7 @@ Remember to type a unit.',
                     'fraction' => null,
                     'timecreated' => 1305830661,
                     'userid' => 4,
-                    'data' => array('-_try' => 1, '_separators' => '.$,',
+                    'data' => array('_separators' => '.$,',
                             '_var_a' => '5.1', '_var_b' => '4.5'),
                 ),
                 1 => (object) array(
@@ -451,7 +451,7 @@ Remember to type a unit.',
                     'fraction' => 0.5,
                     'timecreated' => 1305830714,
                     'userid' => 4,
-                    'data' => array('answer' => 9.6, '-_try' => 2,
+                    'data' => array('answer' => 9.6, '-_try' => 1,
                             '-_rawfraction' => 0.5, '-submit' => 1),
                 ),
                 2 => (object) array(
@@ -460,7 +460,7 @@ Remember to type a unit.',
                     'fraction' => 0.9,
                     'timecreated' => 1305830722,
                     'userid' => 4,
-                    'data' => array('answer' => '9.6 m', '-_try' => 3,
+                    'data' => array('answer' => '9.6 m', '-_try' => 2,
                             '-_rawfraction' => 1, '-submit' => 1),
                 ),
                 3 => (object) array(
@@ -469,7 +469,7 @@ Remember to type a unit.',
                     'fraction' => 0.9,
                     'timecreated' => 1305830722,
                     'userid' => 4,
-                    'data' => array('answer' => '9.6 m', '-_try' => 3,
+                    'data' => array('answer' => '9.6 m', '-_try' => 2,
                             '-_rawfraction' => 1, '-finish' => 1),
                 ),
             ),
@@ -672,7 +672,7 @@ Remember to type a unit.',
                     'fraction' => null,
                     'timecreated' => 1305830744,
                     'userid' => 3,
-                    'data' => array('-_try' => 1, '_separators' => '.$,',
+                    'data' => array('_separators' => '.$,',
                             '_var_a' => '9.9', '_var_b' => '2.5'),
                 ),
                 1 => (object) array(
@@ -681,7 +681,7 @@ Remember to type a unit.',
                     'fraction' => 0,
                     'timecreated' => 1305830775,
                     'userid' => 3,
-                    'data' => array('answer' => '123 cm', '-_try' => 2,
+                    'data' => array('answer' => '123 cm', '-_try' => 1,
                             '-_rawfraction' => 0, '-submit' => 1),
                 ),
                 2 => (object) array(
index 36fe371..c3db14c 100644 (file)
@@ -207,7 +207,7 @@ class qtype_calculated_qe2_attempt_updater extends question_qtype_attempt_update
      * @param $x
      */
     public function format_float($x, $length = null, $format = null) {
-        if (!is_null($format) && !is_null($format)) {
+        if (!is_null($length) && !is_null($format)) {
             if ($format == 1) {
                 // Decimal places.
                 $x = sprintf('%.' . $length . 'F', $x);
index 66f2e9a..b89b9c8 100644 (file)
@@ -310,7 +310,7 @@ class qtype_calculated_variable_substituter {
      * @param $x
      */
     public function format_float($x, $length = null, $format = null) {
-        if (!is_null($format) && !is_null($format)) {
+        if (!is_null($length) && !is_null($format)) {
             if ($format == 1) {
                 // Decimal places.
                 $x = sprintf('%.' . $length . 'F', $x);
index ba7a442..50b506d 100644 (file)
@@ -242,8 +242,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => null,
                     'timecreated' => 1305830650,
                     'userid' => 4,
-                    'data' => array('-_try' => 1,
-                            '_order' => '24,26,27,25', '_var_a' => '4.3', '_var_b' => '5.4'),
+                    'data' => array('_order' => '24,26,27,25', '_var_a' => '4.3', '_var_b' => '5.4'),
                 ),
                 1 => (object) array(
                     'sequencenumber' => 1,
@@ -476,8 +475,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => null,
                     'timecreated' => 1305830661,
                     'userid' => 4,
-                    'data' => array('-_try' => 1,
-                            '_order' => '25,24,27,26', '_var_a' => '3.7', '_var_b' => '6.0'),
+                    'data' => array('_order' => '25,24,27,26', '_var_a' => '3.7', '_var_b' => '6.0'),
                 ),
                 1 => (object) array(
                     'sequencenumber' => 1,
@@ -485,7 +483,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => 1,
                     'timecreated' => 1305830699,
                     'userid' => 4,
-                    'data' => array('answer' => '0', '-submit' => 1, '-_try' => 2, '-_rawfraction' => 1),
+                    'data' => array('answer' => '0', '-submit' => 1, '-_try' => 1, '-_rawfraction' => 1),
                 ),
                 2 => (object) array(
                     'sequencenumber' => 2,
@@ -493,7 +491,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => 1,
                     'timecreated' => 1305830699,
                     'userid' => 4,
-                    'data' => array('answer' => '0', '-finish' => 1, '-_try' => 2, '-_rawfraction' => 1),
+                    'data' => array('answer' => '0', '-finish' => 1, '-_try' => 1, '-_rawfraction' => 1),
                 ),
             ),
         );
@@ -754,8 +752,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => null,
                     'timecreated' => 1305830744,
                     'userid' => 3,
-                    'data' => array('-_try' => 1,
-                            '_order' => '26,24,25,27', '_var_a' => '4.4', '_var_b' => '8.2'),
+                    'data' => array('_order' => '26,24,25,27', '_var_a' => '4.4', '_var_b' => '8.2'),
                 ),
                 1 => (object) array(
                     'sequencenumber' => 1,
@@ -763,7 +760,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => 0,
                     'timecreated' => 1305830759,
                     'userid' => 3,
-                    'data' => array('answer' => '3', '-submit' => 1, '-_try' => 2, '-_rawfraction' => 0),
+                    'data' => array('answer' => '3', '-submit' => 1, '-_try' => 1, '-_rawfraction' => 0),
                 ),
                 2 => (object) array(
                     'sequencenumber' => 2,
@@ -771,7 +768,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => 0,
                     'timecreated' => 1305830761,
                     'userid' => 3,
-                    'data' => array('answer' => '1', '-submit' => 1, '-_try' => 3, '-_rawfraction' => 0),
+                    'data' => array('answer' => '1', '-submit' => 1, '-_try' => 2, '-_rawfraction' => 0),
                 ),
                 3 => (object) array(
                     'sequencenumber' => 3,
@@ -779,7 +776,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => 0,
                     'timecreated' => 1305830764,
                     'userid' => 3,
-                    'data' => array('answer' => '0', '-submit' => 1, '-_try' => 4, '-_rawfraction' => 0),
+                    'data' => array('answer' => '0', '-submit' => 1, '-_try' => 3, '-_rawfraction' => 0),
                 ),
                 4 => (object) array(
                     'sequencenumber' => 4,
@@ -787,7 +784,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => 0.7,
                     'timecreated' => 1305830766,
                     'userid' => 3,
-                    'data' => array('answer' => '2', '-submit' => 1, '-_try' => 5, '-_rawfraction' => 1),
+                    'data' => array('answer' => '2', '-submit' => 1, '-_try' => 4, '-_rawfraction' => 1),
                 ),
                 5 => (object) array(
                     'sequencenumber' => 5,
@@ -795,7 +792,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
                     'fraction' => 0.7,
                     'timecreated' => 1305830768,
                     'userid' => 3,
-                    'data' => array('answer' => '1', '-submit' => 1, '-_try' => 6, '-_rawfraction' => 0),
+                    'data' => array('answer' => '1', '-submit' => 1, '-_try' => 5, '-_rawfraction' => 0),
                 ),
             ),
         );
index 5bd7579..8dd51ba 100644 (file)
@@ -231,7 +231,7 @@ class qtype_calculatedmulti_qe2_attempt_updater extends question_qtype_attempt_u
      * @param $x
      */
     public function format_float($x, $length = null, $format = null) {
-        if (!is_null($format) && !is_null($format)) {
+        if (!is_null($length) && !is_null($format)) {
             if ($format == 1) {
                 // Decimal places.
                 $x = sprintf('%.' . $length . 'F', $x);
index a98b7ce..885ed19 100644 (file)
@@ -206,7 +206,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
                     'fraction' => null,
                     'timecreated' => 1305830650,
                     'userid' => 4,
-                    'data' => array('-_try' => 1, '_separators' => '.$,',
+                    'data' => array('_separators' => '.$,',
                             '_var_a' => '3', '_var_b' => '6'),
                 ),
                 1 => (object) array(
@@ -416,7 +416,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
                     'fraction' => null,
                     'timecreated' => 1305830661,
                     'userid' => 4,
-                    'data' => array('-_try' => 1, '_separators' => '.$,',
+                    'data' => array('_separators' => '.$,',
                             '_var_a' => '6.4', '_var_b' => '9'),
                 ),
                 1 => (object) array(
@@ -425,7 +425,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
                     'fraction' => 0,
                     'timecreated' => 1305830668,
                     'userid' => 4,
-                    'data' => array('answer' => '9.00', '-submit' => 1, '-_try' => 2, '-_rawfraction' => 0),
+                    'data' => array('answer' => '9.00', '-submit' => 1, '-_try' => 1, '-_rawfraction' => 0),
                 ),
                 2 => (object) array(
                     'sequencenumber' => 2,
@@ -433,7 +433,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
                     'fraction' => 0.9,
                     'timecreated' => 1305830679,
                     'userid' => 4,
-                    'data' => array('answer' => '15.40', '-submit' => 1, '-_try' => 3, '-_rawfraction' => 1),
+                    'data' => array('answer' => '15.40', '-submit' => 1, '-_try' => 2, '-_rawfraction' => 1),
                 ),
                 3 => (object) array(
                     'sequencenumber' => 3,
@@ -441,7 +441,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
                     'fraction' => 0.9,
                     'timecreated' => 1305830679,
                     'userid' => 4,
-                    'data' => array('answer' => '15.40', '-finish' => 1, '-_try' => 3, '-_rawfraction' => 1),
+                    'data' => array('answer' => '15.40', '-finish' => 1, '-_try' => 2, '-_rawfraction' => 1),
                 ),
             ),
         );
@@ -618,7 +618,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
                     'fraction' => null,
                     'timecreated' => 1305830744,
                     'userid' => 3,
-                    'data' => array('-_try' => 1, '_separators' => '.$,',
+                    'data' => array('_separators' => '.$,',
                             '_var_a' => '6.1', '_var_b' => '7'),
                 ),
                 1 => (object) array(
index 2b38422..2d5db91 100644 (file)
@@ -216,7 +216,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
                     'fraction' => null,
                     'timecreated' => 1306425691,
                     'userid' => 4,
-                    'data' => array('-_try' => 1),
+                    'data' => array(),
                 ),
                 1 => (object) array(
                     'sequencenumber' => 1,
@@ -408,7 +408,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
                     'fraction' => null,
                     'timecreated' => 1306425757,
                     'userid' => 4,
-                    'data' => array('-_try' => '1'),
+                    'data' => array(),
                 ),
                 1 => (object) array(
                     'sequencenumber' => 1,
@@ -615,7 +615,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
                     'fraction' => null,
                     'timecreated' => 1306425784,
                     'userid' => 3,
-                    'data' => array('-_try' => '1'),
+                    'data' => array(),
                 ),
                 1 => (object) array(
                     'sequencenumber' => 1,
@@ -631,7 +631,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
                     'fraction' => 1,
                     'timecreated' => 1306425917,
                     'userid' => 3,
-                    'data' => array('sub1_answer' => 'frog', '-_try' => 2,
+                    'data' => array('sub1_answer' => 'frog', '-_try' => 1,
                             '-_rawfraction' => 1.0, '-submit' => 1),
                 ),
                 3 => (object) array(
@@ -640,7 +640,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
                     'fraction' => 1,
                     'timecreated' => 1306425917,
                     'userid' => 3,
-                    'data' => array('sub1_answer' => 'frog', '-_try' => 2,
+                    'data' => array('sub1_answer' => 'frog', '-_try' => 1,
                             '-_rawfraction' => 1.0, '-finish' => 1),
                 ),
             ),
@@ -1300,7 +1300,7 @@ b) What grade would you give it? _____',
                     'fraction' => null,
                     'timecreated' => 1306425691,
                     'userid' => 4,
-                    'data' => array('-_try' => '1', '_sub1_order' => '29,30,31,32',
+                    'data' => array('_sub1_order' => '29,30,31,32',
                             '_sub3_separators' => '.$,', '_sub4_order' => '38,39,40,41',
                             '_sub5_order' => '42,43,44,45', '_sub7_order' => '49,50',
                             '_sub8_separators' => '.$,'),
@@ -1314,7 +1314,7 @@ b) What grade would you give it? _____',
                     'data' => array('sub1_answer' => '2', 'sub2_answer' => 'dsf',
                             'sub3_answer' => 'sadf', 'sub4_answer' => '1',
                             'sub5_answer' => '2', 'sub6_answer' => 'MOODLE',
-                            'sub7_answer' => 'sadf', 'sub8_answer' => '100%', '-_try' => '2',
+                            'sub7_answer' => 'sadf', 'sub8_answer' => '100%', '-_try' => '1',
                             '-_rawfraction' => '0.38461538461538466', '-submit' => '1'),
                 ),
                 2 => (object) array(
@@ -1326,7 +1326,7 @@ b) What grade would you give it? _____',
                     'data' => array('sub1_answer' => '2', 'sub2_answer' => 'dsf',
                             'sub3_answer' => 'sadf', 'sub4_answer' => '1',
                             'sub5_answer' => '2', 'sub6_answer' => 'MOODLE',
-                            'sub7_answer' => 'sadf', 'sub8_answer' => '100%', '-_try' => '2',
+                            'sub7_answer' => 'sadf', 'sub8_answer' => '100%', '-_try' => '1',
                             '-_rawfraction' => '0.38461538461538466', '-finish' => '1'),
                 ),
             ),
@@ -1974,7 +1974,7 @@ b) What grade would you give it? _____',
                     'fraction' => null,
                     'timecreated' => 1306425757,
                     'userid' => 4,
-                    'data' => array('-_try' => '1', '_sub1_order' => '29,30,31,32',
+                    'data' => array('_sub1_order' => '29,30,31,32',
                             '_sub3_separators' => '.$,', '_sub4_order' => '38,39,40,41',
                             '_sub5_order' => '42,43,44,45', '_sub7_order' => '49,50',
                             '_sub8_separators' => '.$,'),
@@ -2668,7 +2668,7 @@ b) What grade would you give it? _____',
                     'fraction' => null,
                     'timecreated' => 1306425784,
                     'userid' => 3,
-                    'data' => array('-_try' => '1', '_sub1_order' => '29,30,31,32',
+                    'data' => array('_sub1_order' => '29,30,31,32',
                             '_sub3_separators' => '.$,', '_sub4_order' => '38,39,40,41',
                             '_sub5_order' => '42,43,44,45', '_sub7_order' => '49,50',
                             '_sub8_separators' => '.$,'),
@@ -2682,7 +2682,7 @@ b) What grade would you give it? _____',
                     'data' => array('sub1_answer' => '3', 'sub2_answer' => 'asdgf',
                             'sub3_answer' => 'saedf', 'sub4_answer' => '1',
                             'sub5_answer' => '1', 'sub6_answer' => 'sadf',
-                            '-_try' => '2',
+                            '-_try' => '1',
                             '-_rawfraction' => '0.038461538461538464', '-submit' => '1'),
                 ),
                 2 => (object) array(
@@ -2694,7 +2694,7 @@ b) What grade would you give it? _____',
                     'data' => array('sub1_answer' => '2', 'sub2_answer' => 'asdgf',
                             'sub3_answer' => '28.3', 'sub4_answer' => '2',
                             'sub5_answer' => '2', 'sub6_answer' => 'MOODLE',
-                            'sub7_answer' => '0', 'sub8_answer' => '13', '-_try' => '3',
+                            'sub7_answer' => '0', 'sub8_answer' => '13', '-_try' => '2',
                             '-_rawfraction' => '0.53846153846153845', '-submit' => '1'),
                 ),
                 3 => (object) array(
@@ -2706,7 +2706,7 @@ b) What grade would you give it? _____',
                     'data' => array('sub1_answer' => '2', 'sub2_answer' => 'Correct answer',
                             'sub3_answer' => '23.8', 'sub4_answer' => '2',
                             'sub5_answer' => '2', 'sub6_answer' => 'MOODLE',
-                            'sub7_answer' => '0', 'sub8_answer' => '3', '-_try' => '4',
+                            'sub7_answer' => '0', 'sub8_answer' => '3', '-_try' => '3',
                             '-_rawfraction' => '0.92307692307692311', '-submit' => '1'),
                 ),
                 4 => (object) array(
@@ -2718,7 +2718,7 @@ b) What grade would you give it? _____',
                     'data' => array('sub1_answer' => '2', 'sub2_answer' => 'Correct answer',
                             'sub3_answer' => '23.8', 'sub4_answer' => '2',
                             'sub5_answer' => '2', 'sub6_answer' => 'MOODLE',
-                            'sub7_answer' => '0', 'sub8_answer' => '3', '-_try' => '4',
+                            'sub7_answer' => '0', 'sub8_answer' => '3', '-_try' => '3',
                             '-_rawfraction' => '0.92307692307692311', '-finish' => '1'),
                 ),
             ),
index 03d8b38..36e1c08 100644 (file)
@@ -168,6 +168,8 @@ class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_render
         $response = $qa->get_last_qt_var($fieldname);
         if ($subq->qtype->name() == 'shortanswer') {
             $matchinganswer = $subq->get_matching_answer(array('answer' => $response));
+        } else if ($subq->qtype->name() == 'numerical') {
+            $matchinganswer = $subq->get_matching_answer($response, 1);
         } else {
             $matchinganswer = $subq->get_matching_answer($response);
         }
index 72cf994..d57e157 100644 (file)
@@ -26,6 +26,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+require_once($CFG->dirroot . '/question/type/edit_question_form.php');
 require_once($CFG->dirroot . '/question/type/numerical/questiontype.php');
 
 
@@ -36,6 +37,7 @@ require_once($CFG->dirroot . '/question/type/numerical/questiontype.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qtype_numerical_edit_form extends question_edit_form {
+    protected $ap = null;
 
     protected function definition_inner($mform) {
         $this->add_per_answer_fields($mform, get_string('answerno', 'qtype_numerical', '{no}'),
@@ -54,6 +56,7 @@ class qtype_numerical_edit_form extends question_edit_form {
         $tolerance = $mform->createElement('text', 'tolerance',
                 get_string('acceptederror', 'qtype_numerical'));
         $repeatedoptions['tolerance']['type'] = PARAM_NUMBER;
+        $repeatedoptions['tolerance']['default'] = 0;
         array_splice($repeated, 3, 0, array($tolerance));
         $repeated[1]->setSize(10);
 
@@ -98,7 +101,8 @@ class qtype_numerical_edit_form extends question_edit_form {
 
         $unitinputoptions = array(
             qtype_numerical::UNITINPUT => get_string('editableunittext', 'qtype_numerical'),
-            qtype_numerical::UNITSELECT => get_string('unitchoice', 'qtype_numerical'),
+            qtype_numerical::UNITRADIO => get_string('unitchoice', 'qtype_numerical'),
+            qtype_numerical::UNITSELECT => get_string('unitselect', 'qtype_numerical'),
         );
         $mform->addElement('select', 'multichoicedisplay',
                 get_string('studentunitanswer', 'qtype_numerical'), $unitinputoptions);
@@ -255,7 +259,7 @@ class qtype_numerical_edit_form extends question_edit_form {
             if ($trimmedanswer != '') {
                 $answercount++;
                 if (!$this->is_valid_answer($trimmedanswer, $data)) {
-                    $errors['answer[' . $key . ']'] = $this->valid_answer_message();
+                    $errors['answer[' . $key . ']'] = $this->valid_answer_message($trimmedanswer);
                 }
                 if ($data['fraction'][$key] == 1) {
                     $maxgrade = true;
@@ -266,7 +270,7 @@ class qtype_numerical_edit_form extends question_edit_form {
                 }
             } else if ($data['fraction'][$key] != 0 ||
                     !html_is_blank($data['feedback'][$key]['text'])) {
-                $errors['answer[' . $key . ']'] = $this->valid_answer_message();
+                $errors['answer[' . $key . ']'] = $this->valid_answer_message($trimmedanswer);
                 $answercount++;
             }
         }
@@ -276,6 +280,8 @@ class qtype_numerical_edit_form extends question_edit_form {
         if ($maxgrade == false) {
             $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
         }
+
+        return $errors;
     }
 
     /**
@@ -285,7 +291,22 @@ class qtype_numerical_edit_form extends question_edit_form {
      * @return bool whether this is a valid answer.
      */
     protected function is_valid_answer($answer, $data) {
-        return $answer == '*' || is_numeric($answer);
+        return $answer == '*' || $this->is_valid_number($x);
+    }
+
+    /**
+     * Validate that a string is a nubmer formatted correctly for the current locale.
+     * @param string $x a string
+     * @return bool whether $x is a number that the numerical question type can interpret.
+     */
+    protected function is_valid_number($x) {
+        if (is_null($this->ap)) {
+            $this->ap = new qtype_numerical_answer_processor(array());
+        }
+
+        list($value, $unit) = $this->ap->apply_units($x);
+
+        return !is_null($value) && !$unit;
     }
 
     /**
index 94426bc..a2c42c3 100644 (file)
@@ -27,7 +27,7 @@ $string['acceptederror'] = 'Accepted error';
 $string['addingnumerical'] = 'Adding a Numerical question';
 $string['addmoreanswerblanks'] = 'Blanks for {no} More Answers';
 $string['addmoreunitblanks'] = 'Blanks for {no} More Units';
-$string['answermustbenumberorstar'] = 'The answer must be a number, or \'*\'.';
+$string['answermustbenumberorstar'] = 'The answer must be a number, for example -1.234 or 3e8, or \'*\'.';
 $string['answerno'] = 'Answer {$a}';
 $string['decfractionofquestiongrade'] = 'as a fraction (0-1) of the question grade';
 $string['decfractionofresponsegrade'] = 'as a fraction (0-1) of the response grade';
@@ -64,6 +64,7 @@ $string['nominal'] = 'Nominal';
 $string['onlynumerical'] = 'Units are not used at all. Only the numerical value is graded.';
 $string['oneunitshown'] = 'Unit 1 is automatically displayed beside the answer box.';
 $string['pleaseenterananswer'] = 'Please enter an answer.';
+$string['pleaseenteranswerwithoutthousandssep'] = 'Please enter your answer without using the thousand separator ({$a}).';
 $string['relative'] = 'Relative';
 $string['rightexample'] = 'on the right, for example 1.00cm or 1.00km';
 $string['selectunits'] = 'Select units';
@@ -71,9 +72,11 @@ $string['selectunit'] = 'Select one unit';
 $string['studentunitanswer'] = 'Units are input using';
 $string['tolerancetype'] = 'Tolerance type';
 $string['unit'] = 'Unit';
+$string['unitappliedpenalty'] = 'These marks include a penalty of {$a} for bad unit.';
 $string['unitchoice'] = 'a multiple choice selection';
 $string['unitedit'] = 'Edit unit';
 $string['unitgraded'] = 'The unit must be given, and will be graded.';
+$string['unithandling'] = 'Unit handling';
 $string['unithdr'] = 'Unit {$a}';
 $string['unitincorrect'] = 'You did not give the correct unit.';
 $string['unitmandatory'] = 'Mandatory';
@@ -84,6 +87,7 @@ $string['unitmandatory_help'] = '
 * The unit penalty will be applied if the unit field is empty
 
 ';
+$string['unitnotselected'] = 'You must select a unit.';
 $string['unitonerequired'] = 'You must enter at least one unit';
 $string['unitoptional'] = 'Optional unit';
 $string['unitoptional_help'] = '
@@ -91,15 +95,13 @@ $string['unitoptional_help'] = '
 
 * If the unit is badly written or unknown, the response will be considered as non valid.
 ';
-$string['unitnotselected'] = 'You must select a unit.';
 $string['unitpenalty'] = 'Unit penalty';
 $string['unitpenalty_help'] = 'The penalty is applied if
 
 * the wrong unit name is entered into the unit input, or
 * a unit is entered into the value input box';
-$string['unitappliedpenalty'] = 'These marks include a penalty of {$a} for bad unit.';
 $string['unitposition'] = 'Units go';
-$string['unithandling'] = 'Unit handling';
+$string['unitselect'] = 'a drop-down menu';
 $string['validnumberformats'] = 'Valid number formats';
 $string['validnumberformats_help'] = '
 * regular numbers 13500.67, 13 500.67, 13500,67 or 13 500,67
index 7645528..0e43a84 100644 (file)
@@ -39,7 +39,7 @@ class qtype_numerical_question extends question_graded_automatically {
     /** @var array of question_answer. */
     public $answers = array();
 
-    /** @var int one of the constants UNITNONE, UNITSELECT or UNITINPUT. */
+    /** @var int one of the constants UNITNONE, UNITRADIO, UNITSELECT or UNITINPUT. */
     public $unitdisplay;
     /** @var int one of the constants UNITGRADEDOUTOFMARK or UNITGRADEDOUTOFMAX. */
     public $unitgradingtype;
@@ -51,12 +51,17 @@ class qtype_numerical_question extends question_graded_automatically {
 
     public function get_expected_data() {
         $expected = array('answer' => PARAM_RAW_TRIMMED);
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($this->has_separate_unit_field()) {
             $expected['unit'] = PARAM_RAW_TRIMMED;
         }
         return $expected;
     }
 
+    public function has_separate_unit_field() {
+        return $this->unitdisplay == qtype_numerical::UNITRADIO ||
+                $this->unitdisplay == qtype_numerical::UNITSELECT;
+    }
+
     public function start_attempt(question_attempt_step $step, $variant) {
         $step->set_qt_var('_separators',
                 $this->ap->get_point() . '$' . $this->ap->get_separator());
@@ -74,7 +79,7 @@ class qtype_numerical_question extends question_graded_automatically {
             $resp = null;
         }
 
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT && !empty($response['unit'])) {
+        if ($this->has_separate_unit_field() && !empty($response['unit'])) {
             $resp = $this->ap->add_unit($resp, $response['unit']);
         }
 
@@ -100,7 +105,11 @@ class qtype_numerical_question extends question_graded_automatically {
             return false;
         }
 
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT && empty($response['unit'])) {
+        if ($this->has_separate_unit_field() && empty($response['unit'])) {
+            return false;
+        }
+
+        if ($this->ap->contains_thousands_seaparator($response['answer'])) {
             return false;
         }
 
@@ -121,10 +130,15 @@ class qtype_numerical_question extends question_graded_automatically {
             return get_string('invalidnumbernounit', 'qtype_numerical');
         }
 
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT && empty($response['unit'])) {
+        if ($this->has_separate_unit_field() && empty($response['unit'])) {
             return get_string('unitnotselected', 'qtype_numerical');
         }
 
+        if ($this->ap->contains_thousands_seaparator($response['answer'])) {
+            return get_string('pleaseenteranswerwithoutthousandssep', 'qtype_numerical',
+                    $this->ap->get_separator());
+        }
+
         return '';
     }
 
@@ -134,7 +148,7 @@ class qtype_numerical_question extends question_graded_automatically {
             return false;
         }
 
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($this->has_separate_unit_field()) {
             return question_utils::arrays_same_at_key_missing_is_blank(
                 $prevresponse, $newresponse, 'unit');
         }
@@ -148,9 +162,9 @@ class qtype_numerical_question extends question_graded_automatically {
             return array();
         }
 
-        $response = array('answer' => $answer->answer);
+        $response = array('answer' => str_replace('.', $this->ap->get_point(), $answer->answer));
 
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($this->has_separate_unit_field()) {
             $response['unit'] = $this->ap->get_default_unit();
         } else if ($this->unitdisplay == qtype_numerical::UNITINPUT) {
             $response['answer'] = $this->ap->add_unit($answer->answer);
@@ -163,12 +177,22 @@ class qtype_numerical_question extends question_graded_automatically {
      * Get an answer that contains the feedback and fraction that should be
      * awarded for this resonse.
      * @param number $value the numerical value of a response.
+     * @param number $multiplier for the unit the student gave, if any. When no
+     *      unit was given, or an unrecognised unit was given, $multiplier will be null.
      * @return question_answer the matching answer.
      */
-    public function get_matching_answer($value) {
+    public function get_matching_answer($value, $multiplier) {
+        if (!is_null($multiplier)) {
+            $scaledvalue = $value * $multiplier;
+        } else {
+            $scaledvalue = $value;
+        }
         foreach ($this->answers as $aid => $answer) {
-            if ($answer->within_tolerance($value)) {
-                $answer->id = $aid;
+            if ($answer->within_tolerance($scaledvalue)) {
+                $answer->unitisright = !is_null($multiplier);
+                return $answer;
+            } else if ($answer->within_tolerance($value)) {
+                $answer->unitisright = false;
                 return $answer;
             }
         }
@@ -185,8 +209,14 @@ class qtype_numerical_question extends question_graded_automatically {
         return null;
     }
 
-    public function apply_unit_penalty($fraction, $unit) {
-        if (!empty($unit) && $this->ap->is_known_unit($unit)) {
+    /**
+     * Adjust the fraction based on whether the unit was correct.
+     * @param number $fraction
+     * @param bool $unitisright
+     * @return number
+     */
+    public function apply_unit_penalty($fraction, $unitisright) {
+        if ($unitisright) {
             return $fraction;
         }
 
@@ -199,18 +229,20 @@ class qtype_numerical_question extends question_graded_automatically {
     }
 
     public function grade_response(array $response) {
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($this->has_separate_unit_field()) {
             $selectedunit = $response['unit'];
         } else {
             $selectedunit = null;
         }
-        list($value, $unit) = $this->ap->apply_units($response['answer'], $selectedunit);
-        $answer = $this->get_matching_answer($value);
+        list($value, $unit, $multiplier) = $this->ap->apply_units(
+                $response['answer'], $selectedunit);
+
+        $answer = $this->get_matching_answer($value, $multiplier);
         if (!$answer) {
             return array(0, question_state::$gradedwrong);
         }
 
-        $fraction = $this->apply_unit_penalty($answer->fraction, $unit);
+        $fraction = $this->apply_unit_penalty($answer->fraction, $answer->unitisright);
         return array($fraction, question_state::graded_state_for_fraction($fraction));
     }
 
@@ -219,32 +251,40 @@ class qtype_numerical_question extends question_graded_automatically {
             return array($this->id => question_classified_response::no_response());
         }
 
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($this->has_separate_unit_field()) {
             $selectedunit = $response['unit'];
         } else {
             $selectedunit = null;
         }
-        list($value, $unit) = $this->ap->apply_units($response['answer'], $selectedunit);
-        $ans = $this->get_matching_answer($value);
+        list($value, $unit, $multiplier) = $this->ap->apply_units($response['answer'], $selectedunit);
+        $ans = $this->get_matching_answer($value, $multiplier);
         if (!$ans) {
             return array($this->id => question_classified_response::no_response());
         }
 
         $resp = $response['answer'];
-        if ($this->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($this->has_separate_unit_field()) {
             $resp = $this->ap->add_unit($resp, $unit);
         }
 
         return array($this->id => new question_classified_response($ans->id,
                 $resp,
-                $this->apply_unit_penalty($ans->fraction, $unit)));
+                $this->apply_unit_penalty($ans->fraction, $ans->unitisright)));
     }
 
     public function check_file_access($qa, $options, $component, $filearea, $args,
             $forcedownload) {
         if ($component == 'question' && $filearea == 'answerfeedback') {
+            $question = $qa->get_question();
             $currentanswer = $qa->get_last_qt_var('answer');
-            $answer = $qa->get_question()->get_matching_answer(array('answer' => $currentanswer));
+            if ($this->has_separate_unit_field()) {
+                $selectedunit = $qa->get_last_qt_var('unit');
+            } else {
+                $selectedunit = null;
+            }
+            list($value, $unit, $multiplier) = $question->ap->apply_units(
+                    $currentanswer, $selectedunit);
+            $answer = $question->get_matching_answer($value, $multiplier);
             $answerid = reset($args); // itemid is answer id.
             return $options->feedback && $answerid == $answer->id;
 
index b8c8b38..5090b00 100644 (file)
@@ -41,7 +41,8 @@ require_once($CFG->dirroot . '/question/type/numerical/question.php');
  */
 class qtype_numerical extends question_type {
     const UNITINPUT = 0;
-    const UNITSELECT = 1;
+    const UNITRADIO = 1;
+    const UNITSELECT = 2;
 
     const UNITNONE = 3;
     const UNITGRADED = 1;
@@ -448,7 +449,10 @@ class qtype_numerical extends question_type {
      */
     public function apply_unit($rawresponse, $units, $unitsleft) {
         $ap = $this->make_answer_processor($units, $unitsleft);
-        list($value, $unit) = $ap->apply_units($rawresponse);
+        list($value, $unit, $multiplier) = $ap->apply_units($rawresponse);
+        if (!is_null($multiplier)) {
+            $value *= $multiplier;
+        }
         return $value;
     }
 
@@ -523,6 +527,19 @@ class qtype_numerical_answer_processor {
         return $this->thousandssep;
     }
 
+    /**
+     * @return book If the student's response contains a '.' or a ',' that
+     * matches the thousands separator in the current locale. In this case, the
+     * parsing in apply_unit can give a result that the student did not expect.
+     */
+    public function contains_thousands_seaparator($value) {
+        if (!in_array($this->thousandssep, array('.', ','))) {
+            return false;
+        }
+
+        return strpos($value, $this->thousandssep) !== false;
+    }
+
     /**
      * Create the regular expression that {@link parse_response()} requires.
      * @return string
@@ -549,6 +566,11 @@ class qtype_numerical_answer_processor {
     }
 
     /**
+     * This method can be used for more locale-strict parsing of repsonses. At the
+     * moment we don't use it, and instead use the more lax parsing in apply_units.
+     * This is just a note that this funciton was used in the past, so if you are
+     * intersted, look through version control history.
+     *
      * Take a string which is a number with or without a decimal point and exponent,
      * and possibly followed by one of the units, and split it into bits.
      * @param string $response a value, optionally with a unit.
@@ -584,25 +606,44 @@ class qtype_numerical_answer_processor {
     }
 
     /**
-     * Takes a number in localised form, that is, using the decsep and thousandssep
-     * defined in the lanuage pack, and possibly with a unit after it. It separates
-     * off the unit, if present, and converts to the default unit, by using the
-     * given unit multiplier.
+     * Takes a number in almost any localised form, and possibly with a unit
+     * after it. It separates off the unit, if present, and converts to the
+     * default unit, by using the given unit multiplier.
      *
      * @param string $response a value, optionally with a unit.
      * @return array(numeric, sting) the value with the unit stripped, and normalised
      *      by the unit multiplier, if any, and the unit string, for reference.
      */
     public function apply_units($response, $separateunit = null) {
-        list($beforepoint, $decimals, $exponent, $unit) = $this->parse_response($response);
+        // Strip spaces (which may be thousands separators) and change other forms
+        // of writing e to e.
+        $response = str_replace(' ', '', $response);
+        $response = preg_replace('~(?:e|E|(?:x|\*|×)10(?:\^|\*\*))([+-]?\d+)~', 'e$1', $response);
+
+        // If a . is present or there are multiple , (i.e. 2,456,789 ) assume ,
+        // is a thouseands separator, and strip it, else assume it is a decimal
+        // separator, and change it to ..
+        if (strpos($response, '.') !== false || substr_count($response, ',') > 1) {
+            $response = str_replace(',', '', $response);
+        } else {
+            $response = str_replace(',', '.', $response);
+        }
 
-        if (is_null($beforepoint)) {
-            return array(null, null);
+        $regex = '[+-]?(?:\d+(?:\\.\d*)?|\\.\d+)(?:e[-+]?\d+)?';
+        if ($this->unitsbefore) {
+            $regex = "/$regex$/";
+        } else {
+            $regex = "/^$regex/";
+        }
+        if (!preg_match($regex, $response, $matches)) {
+            return array(null, null, null);
         }
 
-        $numberstring = $beforepoint . '.' . $decimals;
-        if ($exponent) {
-            $numberstring .= 'e' . $exponent;
+        $numberstring = $matches[0];
+        if ($this->unitsbefore) {
+            $unit = substr($response, 0, -strlen($numberstring));
+        } else {
+            $unit = substr($response, strlen($numberstring));
         }
 
         if (!is_null($separateunit)) {
@@ -610,12 +651,12 @@ class qtype_numerical_answer_processor {
         }
 
         if ($unit && $this->is_known_unit($unit)) {
-            $value = $numberstring / $this->units[$unit];
+            $multiplier = 1 / $this->units[$unit];
         } else {
-            $value = $numberstring * 1;
+            $multiplier = null;
         }
 
-        return array($value, $unit);
+        return array($numberstring + 0, $unit, $multiplier); // + 0 to convert to number.
     }
 
     /**
index b707479..cc334c4 100644 (file)
@@ -36,7 +36,7 @@ class qtype_numerical_renderer extends qtype_renderer {
 
         $question = $qa->get_question();
         $currentanswer = $qa->get_last_qt_var('answer');
-        if ($question->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($question->has_separate_unit_field()) {
             $selectedunit = $qa->get_last_qt_var('unit');
         } else {
             $selectedunit = null;
@@ -57,10 +57,11 @@ class qtype_numerical_renderer extends qtype_renderer {
 
         $feedbackimg = '';
         if ($options->correctness) {
-            list($value, $unit) = $question->ap->apply_units($currentanswer, $selectedunit);
-            $answer = $question->get_matching_answer($value);
+            list($value, $unit, $multiplier) = $question->ap->apply_units(
+                    $currentanswer, $selectedunit);
+            $answer = $question->get_matching_answer($value, $multiplier);
             if ($answer) {
-                $fraction = $question->apply_unit_penalty($answer->fraction, $unit);
+                $fraction = $question->apply_unit_penalty($answer->fraction, $answer->unitisright);
             } else {
                 $fraction = 0;
             }
@@ -77,14 +78,35 @@ class qtype_numerical_renderer extends qtype_renderer {
 
         $input = html_writer::empty_tag('input', $inputattributes) . $feedbackimg;
 
-        if ($question->unitdisplay == qtype_numerical::UNITSELECT) {
-            $unitselect = html_writer::select($question->ap->get_unit_options(),
-                    $qa->get_qt_field_name('unit'), $selectedunit, array(''=>'choosedots'),
-                    array('disabled' => $options->readonly));
+        if ($question->has_separate_unit_field()) {
+            if ($question->unitdisplay == qtype_numerical::UNITRADIO) {
+                $choices = array();
+                $i = 1;
+                foreach ($question->ap->get_unit_options() as $unit) {
+                    $id = $qa->get_qt_field_name('unit') . '_' . $i++;
+                    $radioattrs = array('type' => 'radio', 'id' => $id, 'value' => $unit,
+                            'name' => $qa->get_qt_field_name('unit'));
+                    if ($unit == $selectedunit) {
+                        $radioattrs['checked'] = 'checked';
+                    }
+                    $choices[] = html_writer::tag('label',
+                            html_writer::empty_tag('input', $radioattrs) . $unit,
+                            array('for' => $id, 'class' => 'unitchoice'));
+                }
+
+                $unitchoice = html_writer::tag('span', implode(' ', $choices),
+                        array('class' => 'unitchoices'));
+
+            } else if ($question->unitdisplay == qtype_numerical::UNITSELECT) {
+                $unitchoice = html_writer::select($question->ap->get_unit_options(),
+                        $qa->get_qt_field_name('unit'), $selectedunit, array(''=>'choosedots'),
+                        array('disabled' => $options->readonly));
+            }
+
             if ($question->ap->are_units_before()) {
-                $input = $unitselect . ' ' . $input;
+                $input = $unitchoice . ' ' . $input;
             } else {
-                $input = $input . ' ' . $unitselect;
+                $input = $input . ' ' . $unitchoice;
             }
         }
 
@@ -114,14 +136,14 @@ class qtype_numerical_renderer extends qtype_renderer {
     public function specific_feedback(question_attempt $qa) {
         $question = $qa->get_question();
 
-        if ($question->unitdisplay == qtype_numerical::UNITSELECT) {
+        if ($question->has_separate_unit_field()) {
             $selectedunit = $qa->get_last_qt_var('unit');
         } else {
             $selectedunit = null;
         }
-        list($value, $unit) = $question->ap->apply_units(
+        list($value, $unit, $multiplier) = $question->ap->apply_units(
                 $qa->get_last_qt_var('answer'), $selectedunit);
-        $answer = $question->get_matching_answer($value);
+        $answer = $question->get_matching_answer($value, $multiplier);
 
         if ($answer && $answer->feedback) {
             $feedback = $question->format_text($answer->feedback, $answer->feedbackformat,
@@ -144,11 +166,11 @@ class qtype_numerical_renderer extends qtype_renderer {
             return '';
         }
 
-        $response = $answer->answer;
+        $response = str_replace('.', $question->ap->get_point(), $answer->answer);
         if ($question->unitdisplay != qtype_numerical::UNITNONE) {
             $response = $question->ap->add_unit($response);
         }
 
-        return get_string('correctansweris', 'qtype_shortanswer', s($response));
+        return get_string('correctansweris', 'qtype_shortanswer', $response);
     }
 }
index 40e988a..f0681c1 100644 (file)
@@ -82,15 +82,20 @@ class qtype_numerical_answer_processor_test extends UnitTestCase {
         $this->assertEqual(array(null, null, null, null), $ap->parse_response(','));
     }
 
-    protected function verify_value_and_unit($exectedval, $expectedunit,
+    protected function verify_value_and_unit($exectedval, $expectedunit, $expectedmultiplier,
             qtype_numerical_answer_processor $ap, $input, $separateunit = null) {
-        list($val, $unit) = $ap->apply_units($input, $separateunit);
+        list($val, $unit, $multiplier) = $ap->apply_units($input, $separateunit);
         if (is_null($exectedval)) {
             $this->assertNull($val);
         } else {
             $this->assertWithinMargin($exectedval, $val, 0.0001);
         }
         $this->assertEqual($expectedunit, $unit);
+        if (is_null($expectedmultiplier)) {
+            $this->assertNull($multiplier);
+        } else {
+            $this->assertWithinMargin($expectedmultiplier, $multiplier, 0.0001);
+        }
     }
 
     public function test_apply_units() {
@@ -98,13 +103,13 @@ class qtype_numerical_answer_processor_test extends UnitTestCase {
                 array('m/s' => 1, 'c' => 3.3356409519815E-9,
                         'mph' => 2.2369362920544), false, '.', ',');
 
-        $this->verify_value_and_unit(3e8, 'm/s', $ap, '3x10^8 m/s');
-        $this->verify_value_and_unit(3e8, '', $ap, '3x10^8');
-        $this->verify_value_and_unit(299792458, 'c', $ap, '1c');
-        $this->verify_value_and_unit(0.44704, 'mph', $ap, '0001.000 mph');
+        $this->verify_value_and_unit(3e8, 'm/s', 1, $ap, '3x10^8 m/s');
+        $this->verify_value_and_unit(3e8, '', null, $ap, '3x10^8');
+        $this->verify_value_and_unit(1, 'c', 299792458, $ap, '1c');
+        $this->verify_value_and_unit(1, 'mph', 0.44704, $ap, '0001.000 mph');
 
-        $this->verify_value_and_unit(1, 'frogs', $ap, '1 frogs');
-        $this->verify_value_and_unit(null, null, $ap, '. m/s');
+        $this->verify_value_and_unit(1, 'frogs', null, $ap, '1 frogs');
+        $this->verify_value_and_unit(null, null, null, $ap, '. m/s');
     }
 
     public function test_apply_units_separate_unit() {
@@ -112,39 +117,39 @@ class qtype_numerical_answer_processor_test extends UnitTestCase {
                 array('m/s' => 1, 'c' => 3.3356409519815E-9,
                         'mph' => 2.2369362920544), false, '.', ',');
 
-        $this->verify_value_and_unit(3e8, 'm/s', $ap, '3x10^8', 'm/s');
-        $this->verify_value_and_unit(3e8, '', $ap, '3x10^8', '');
-        $this->verify_value_and_unit(299792458, 'c', $ap, '1', 'c');
-        $this->verify_value_and_unit(0.44704, 'mph', $ap, '0001.000', 'mph');
+        $this->verify_value_and_unit(3e8, 'm/s', 1, $ap, '3x10^8', 'm/s');
+        $this->verify_value_and_unit(3e8, '', null, $ap, '3x10^8', '');
+        $this->verify_value_and_unit(1, 'c', 299792458, $ap, '1', 'c');
+        $this->verify_value_and_unit(1, 'mph', 0.44704, $ap, '0001.000', 'mph');
 
-        $this->verify_value_and_unit(1, 'frogs', $ap, '1', 'frogs');
-        $this->verify_value_and_unit(null, null, $ap, '.', 'm/s');
+        $this->verify_value_and_unit(1, 'frogs', null, $ap, '1', 'frogs');
+        $this->verify_value_and_unit(null, null, null, $ap, '.', 'm/s');
     }
 
     public function test_euro_style() {
         $ap = new qtype_numerical_answer_processor(array(), false, ',', ' ');
 
-        $this->assertEqual(array(-1000, ''), $ap->apply_units('-1 000'));
-        $this->assertEqual(array(3.14159, ''), $ap->apply_units('3,14159'));
+        $this->assertEqual(array(-1000, '', null), $ap->apply_units('-1 000'));
+        $this->assertEqual(array(3.14159, '', null), $ap->apply_units('3,14159'));
     }
 
     public function test_percent() {
         $ap = new qtype_numerical_answer_processor(array('%' => 100), false, '.', ',');
 
-        $this->assertEqual(array('0.03', '%'), $ap->apply_units('3%'));
-        $this->assertEqual(array('1e-8', '%'), $ap->apply_units('1e-6 %'));
-        $this->assertEqual(array('100', ''), $ap->apply_units('100'));
+        $this->assertEqual(array('3', '%', 0.01), $ap->apply_units('3%'));
+        $this->assertEqual(array('1e-6', '%', 0.01), $ap->apply_units('1e-6 %'));
+        $this->assertEqual(array('100', '', null), $ap->apply_units('100'));
     }
 
 
     public function test_currency() {
         $ap = new qtype_numerical_answer_processor(array('$' => 1, '£' => 1), true, '.', ',');
 
-        $this->assertEqual(array('1234.56', '£'), $ap->apply_units('£1,234.56'));
-        $this->assertEqual(array('100', '$'), $ap->apply_units('$100'));
-        $this->assertEqual(array('100', '$'), $ap->apply_units('$100.'));
-        $this->assertEqual(array('100.00', '$'), $ap->apply_units('$100.00'));
-        $this->assertEqual(array('100', ''), $ap->apply_units('100'));
-        $this->assertEqual(array('100', 'frog'), $ap->apply_units('frog 100'));
+        $this->assertEqual(array('1234.56', '£', 1), $ap->apply_units('£1,234.56'));
+        $this->assertEqual(array('100', '$', 1), $ap->apply_units('$100'));
+        $this->assertEqual(array('100', '$', 1), $ap->apply_units('$100.'));
+        $this->assertEqual(array('100.00', '$', 1), $ap->apply_units('$100.00'));
+        $this->assertEqual(array('100', '', null), $ap->apply_units('100'));
+        $this->assertEqual(array('100', 'frog', null), $ap->apply_units('frog 100'));
     }
 }
diff --git a/question/type/numerical/simpletest/testform.php b/question/type/numerical/simpletest/testform.php
new file mode 100755 (executable)
index 0000000..af293f0
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for (some of) question/type/numerical/edit_numerical_form.php.
+ *
+ * @package    qtype
+ * @subpackage numerical
+ * @copyright  2011 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/question/type/numerical/edit_numerical_form.php');
+
+
+/**
+ * Test sub-class, so we can force the locale.
+ *
+ * @copyright  2011 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_qtype_numerical_edit_form extends qtype_numerical_edit_form {
+    public function __construct() {
+        // Warning, avoid running the parent constructor. That means the form is
+        // not properly tested but for now that is OK, we are only testing a few
+        // methods.
+        $this->ap = new qtype_numerical_answer_processor(array(), false, ',', ' ');
+    }
+    public function is_valid_number($x) {
+        return parent::is_valid_number($x);
+    }
+}
+
+
+/**
+ * Unit tests for question/type/numerical/edit_numerical_form.php.
+ *
+ * @copyright  2011 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_numerical_form_test extends UnitTestCase {
+    public static $includecoverage = array(
+        'question/type/numerical/edit_numerical_form.php'
+    );
+
+    protected $form;
+
+    public function setUp() {
+        $this->form = new test_qtype_numerical_edit_form();
+    }
+
+    public function tearDown() {
+        $this->form = null;
+    }
+
+    public function test_is_valid_number() {
+        $this->assertTrue($this->form->is_valid_number('1,001'));
+        $this->assertTrue($this->form->is_valid_number('1.001'));
+        $this->assertTrue($this->form->is_valid_number('1'));
+        $this->assertTrue($this->form->is_valid_number('1,e8'));
+        $this->assertFalse($this->form->is_valid_number('1001 xxx'));
+        $this->assertTrue($this->form->is_valid_number('1.e8'));
+    }
+}
index 2a1fe39..090f85d 100644 (file)
@@ -64,6 +64,7 @@ class qtype_numerical_question_test extends UnitTestCase {
 
     public function test_grading_with_units() {
         $question = test_question_maker::make_question('numerical');
+        $question->unitgradingtype = qtype_numerical::UNITOPTIONAL;
         $question->ap = new qtype_numerical_answer_processor(
                 array('m' => 1, 'cm' => 100), false, '.', ',');
 
@@ -79,6 +80,28 @@ class qtype_numerical_question_test extends UnitTestCase {
                 $question->grade_response(array('answer' => '314000000x10^-8m')));
     }
 
+    public function test_grading_with_units_graded() {
+        $question = test_question_maker::make_question('numerical');
+        $question->unitgradingtype = qtype_numerical::UNITGRADED;
+        $question->ap = new qtype_numerical_answer_processor(
+                array('m' => 1, 'cm' => 100), false, '.', ',');
+
+        $this->assertEqual(array(0.8, question_state::$gradedpartial),
+                $question->grade_response(array('answer' => '3.14 frogs')));
+        $this->assertEqual(array(0.8, question_state::$gradedpartial),
+                $question->grade_response(array('answer' => '3.14')));
+        $this->assertEqual(array(1, question_state::$gradedright),
+                $question->grade_response(array('answer' => '3.14 m')));
+        $this->assertEqual(array(1, question_state::$gradedright),
+                $question->grade_response(array('answer' => '314cm')));
+        $this->assertEqual(array(1, question_state::$gradedright),
+                $question->grade_response(array('answer' => '314000000x10^-8m')));
+        $this->assertEqual(array(0.8, question_state::$gradedpartial),
+                $question->grade_response(array('answer' => '3.14 cm')));
+        $this->assertEqual(array(0, question_state::$gradedwrong),
+                $question->grade_response(array('answer' => '314 m')));
+    }
+
     public function test_grading_unit() {
         $question = test_question_maker::make_question('numerical', 'unit');
 
@@ -110,17 +133,17 @@ class qtype_numerical_question_test extends UnitTestCase {
         $this->assertEqual(array(1, question_state::$gradedright),
                 $question->grade_response(array('answer' => '$1332')));
         $this->assertEqual(array(1, question_state::$gradedright),
-                $question->grade_response(array('answer' => '$ 1,332')));
+                $question->grade_response(array('answer' => '$ 1332')));
         $this->assertEqual(array(0.8, question_state::$gradedpartial),
                 $question->grade_response(array('answer' => 'frog 1332')));
         $this->assertEqual(array(0.8, question_state::$gradedpartial),
                 $question->grade_response(array('answer' => '1332')));
         $this->assertEqual(array(0.8, question_state::$gradedpartial),
-                $question->grade_response(array('answer' => ' 1,332')));
+                $question->grade_response(array('answer' => ' 1332')));
         $this->assertEqual(array(0, question_state::$gradedwrong),
                 $question->grade_response(array('answer' => '1332 $')));
         $this->assertEqual(array(0, question_state::$gradedwrong),
-                $question->grade_response(array('answer' => '1,332 frogs')));
+                $question->grade_response(array('answer' => '1332 frogs')));
         $this->assertEqual(array(0, question_state::$gradedwrong),
                 $question->grade_response(array('answer' => '$1')));
     }
@@ -225,7 +248,7 @@ class qtype_numerical_question_test extends UnitTestCase {
                 new question_classified_response(14, '$100', 0)),
                 $num->classify_response(array('answer' => '$100')));
         $this->assertEqual(array(
-                new question_classified_response(13, '1,332', 0.8)),
-                $num->classify_response(array('answer' => '1,332')));
+                new question_classified_response(13, '1 332', 0.8)),
+                $num->classify_response(array('answer' => '1 332')));
     }
 }
\ No newline at end of file
index 22d5868..78db2ed 100644 (file)
@@ -583,4 +583,385 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
 
         $this->compare_qas($expectedqa, $qa);
     }
+
+    public function test_truefalse_adaptive_qsession119() {
+        $quiz = (object) array(
+            'id' => '6',
+            'course' => '3',
+            'name' => 'Simply quiz',
+            'intro' => '<p>One quiz with 1 true/false Q</p>',
+            'introformat' => '1',
+            'timeopen' => '0',
+            'timeclose' => '0',
+            'attempts' => '0',
+            'attemptonlast' => '0',
+            'grademethod' => '1',
+            'decimalpoints' => '2',
+            'questiondecimalpoints' => '-1',
+            'review' => '4459503',
+            'questionsperpage' => '1',
+            'shufflequestions' => '0',
+            'shuffleanswers' => '1',
+            'questions' => '30,0',
+            'sumgrades' => '10.00000',
+            'grade' => '10.00000',
+            'timecreated' => '0',
+            'timemodified' => '1309103209',
+            'timelimit' => '0',
+            'password' => '',
+            'subnet' => '',
+            'popup' => '0',
+            'delay1' => '0',
+            'delay2' => '0',
+            'showuserpicture' => '0',
+            'showblocks' => '0',
+            'preferredbehaviour' => 'adaptive',
+        );
+        $attempt = (object) array(
+            'id' => '20',
+            'uniqueid' => '20',
+            'quiz' => '6',
+            'userid' => '7',
+            'attempt' => '1',
+            'sumgrades' => '10.00000',
+            'timestart' => '1309103112',
+            'timefinish' => '1309103120',
+            'timemodified' => '1309103120',
+            'layout' => '30,0',
+            'preview' => '0',
+            'needsupgradetonewqe' => 1,
+        );
+        $question = (object) array(
+            'id' => '30',
+            'category' => '10',
+            'parent' => '0',
+            'name' => '1 + 1 = 2 ?',
+            'questiontext' => '<p>1 +1 = 2 ?</p>',
+            'questiontextformat' => '1',
+            'generalfeedback' => '<p>this is general feedback</p>',
+            'generalfeedbackformat' => '1',
+            'penalty' => '1.0000000',
+            'qtype' => 'truefalse',
+            'length' => '1',
+            'stamp' => '127.0.0.1+110626154410+wFrWwP',
+            'version' => '127.0.0.1+110626154410+u7CoaA',
+            'hidden' => '0',
+            'timecreated' => '1309103050',
+            'timemodified' => '1309103050',
+            'createdby' => '6',
+            'modifiedby' => '6',
+            'maxmark' => '10.0000000',
+            'options' => (object) array(
+                'id' => '4',
+                'question' => '30',
+                'trueanswer' => '53',
+                'falseanswer' => '54',
+                'answers' => array(
+                    53 => (object) array(
+                        'id' => '53',
+                        'question' => '30',
+                        'answer' => 'True',
+                        'answerformat' => '0',
+                        'fraction' => '1.0000000',
+                        'feedback' => '<p>this is correct (for true) feedback</p>',
+                        'feedbackformat' => '1',
+                    ),
+                    54 => (object) array(
+                        'id' => '54',
+                        'question' => '30',
+                        'answer' => 'False',
+                        'answerformat' => '0',
+                        'fraction' => '0.0000000',
+                        'feedback' => '<p>this is incorrect (for false) feedback</p>',
+                        'feedbackformat' => '1',
+                    ),
+                ),
+            ),
+            'defaultmark' => '1.0000000',
+        );
+        $qsession = (object) array(
+            'id' => '119',
+            'attemptid' => '20',
+            'questionid' => '30',
+            'newest' => '312',
+            'newgraded' => '312',
+            'sumpenalty' => '10.0000000',
+            'manualcomment' => '',
+            'manualcommentformat' => '1',
+            'flagged' => '0',
+        );
+        $qstates = array(
+            310 => (object) array(
+                'id' => '310',
+                'attempt' => '20',
+                'question' => '30',
+                'seq_number' => '0',
+                'answer' => '',
+                'timestamp' => '1309103112',
+                'event' => '0',
+                'grade' => '0.0000000',
+                'raw_grade' => '0.0000000',
+                'penalty' => '0.0000000',
+            ),
+            311 => (object) array(
+                'id' => '311',
+                'attempt' => '20',
+                'question' => '30',
+                'seq_number' => '1',
+                'answer' => '53',
+                'timestamp' => '1309103115',
+                'event' => '3',
+                'grade' => '10.0000000',
+                'raw_grade' => '10.0000000',
+                'penalty' => '10.0000000',
+            ),
+            312 => (object) array(
+                'id' => '312',
+                'attempt' => '20',
+                'question' => '30',
+                'seq_number' => '1',
+                'answer' => '53',
+                'timestamp' => '1309103115',
+                'event' => '6',
+                'grade' => '10.0000000',
+                'raw_grade' => '10.0000000',
+                'penalty' => '10.0000000',
+            ),
+        );
+
+        $qa = $this->updater->convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates);
+
+        $expectedqa = (object) array(
+            'behaviour' => 'adaptive',
+            'questionid' => 30,
+            'variant' => 1,
+            'maxmark' => 10.0000000,
+            'minfraction' => 0,
+            'flagged' => 0,
+            'questionsummary' => '1 +1 = 2 ?',
+            'rightanswer' => 'True',
+            'responsesummary' => 'True',
+            'timemodified' => 1309103115,
+            'steps' => array(
+                0 => (object) array(
+                    'sequencenumber' => 0,
+                    'state' => 'todo',
+                    'fraction' => null,
+                    'timecreated' => 1309103112,
+                    'userid' => 7,
+                    'data' => array(),
+                ),
+                1 => (object) array(
+                    'sequencenumber' => 1,
+                    'state' => 'complete',
+                    'fraction' => 1.0,
+                    'timecreated' => 1309103115,
+                    'userid' => 7,
+                    'data' => array('answer' => '1', '-_try' => '1',
+                            '-_rawfraction' => '1', '-submit' => '1'),
+                ),
+                2 => (object) array(
+                    'sequencenumber' => 2,
+                    'state' => 'gradedright',
+                    'fraction' => 1.0,
+                    'timecreated' => 1309103115,
+                    'userid' => 7,
+                    'data' => array('answer' => '1', '-_try' => '1',
+                            '-_rawfraction' => '1', '-finish' => '1'),
+                ),
+            ),
+        );
+
+        $this->compare_qas($expectedqa, $qa);
+    }
+
+    public function test_truefalse_adaptive_qsession120() {
+        $quiz = (object) array(
+            'id' => '6',
+            'course' => '3',
+            'name' => 'Simply quiz',
+            'intro' => '<p>One quiz with 1 true/false Q</p>',
+            'introformat' => '1',
+            'timeopen' => '0',
+            'timeclose' => '0',
+            'attempts' => '0',
+            'attemptonlast' => '0',
+            'grademethod' => '1',
+            'decimalpoints' => '2',
+            'questiondecimalpoints' => '-1',
+            'review' => '4459503',
+            'questionsperpage' => '1',
+            'shufflequestions' => '0',
+            'shuffleanswers' => '1',
+            'questions' => '30,0',
+            'sumgrades' => '10.00000',
+            'grade' => '10.00000',
+            'timecreated' => '0',
+            'timemodified' => '1309103209',
+            'timelimit' => '0',
+            'password' => '',
+            'subnet' => '',
+            'popup' => '0',
+            'delay1' => '0',
+            'delay2' => '0',
+            'showuserpicture' => '0',
+            'showblocks' => '0',
+            'preferredbehaviour' => 'adaptive',
+        );
+        $attempt = (object) array(
+            'id' => '21',
+            'uniqueid' => '21',
+            'quiz' => '6',
+            'userid' => '7',
+            'attempt' => '2',
+            'sumgrades' => '0.00000',
+            'timestart' => '1309103130',
+            'timefinish' => '1309103136',
+            'timemodified' => '1309103136',
+            'layout' => '30,0',
+            'preview' => '0',
+            'needsupgradetonewqe' => 1,
+        );
+        $question = (object) array(
+            'id' => '30',
+            'category' => '10',
+            'parent' => '0',
+            'name' => '1 + 1 = 2 ?',
+            'questiontext' => '<p>1 +1 = 2 ?</p>',
+            'questiontextformat' => '1',
+            'generalfeedback' => '<p>this is general feedback</p>',
+            'generalfeedbackformat' => '1',
+            'penalty' => '1.0000000',
+            'qtype' => 'truefalse',
+            'length' => '1',
+            'stamp' => '127.0.0.1+110626154410+wFrWwP',
+            'version' => '127.0.0.1+110626154410+u7CoaA',
+            'hidden' => '0',
+            'timecreated' => '1309103050',
+            'timemodified' => '1309103050',
+            'createdby' => '6',
+            'modifiedby' => '6',
+            'maxmark' => '10.0000000',
+            'options' => (object) array(
+                'id' => '4',
+                'question' => '30',
+                'trueanswer' => '53',
+                'falseanswer' => '54',
+                'answers' => array(
+                    53 => (object) array(
+                        'id' => '53',
+                        'question' => '30',
+                        'answer' => 'True',
+                        'answerformat' => '0',
+                        'fraction' => '1.0000000',
+                        'feedback' => '<p>this is correct (for true) feedback</p>',
+                        'feedbackformat' => '1',
+                    ),
+                    54 => (object) array(
+                        'id' => '54',
+                        'question' => '30',
+                        'answer' => 'False',
+                        'answerformat' => '0',
+                        'fraction' => '0.0000000',
+                        'feedback' => '<p>this is incorrect (for false) feedback</p>',
+                        'feedbackformat' => '1',
+                    ),
+                ),
+            ),
+            'defaultmark' => '1.0000000',
+        );
+        $qsession = (object) array(
+            'id' => '120',
+            'attemptid' => '21',
+            'questionid' => '30',
+            'newest' => '315',
+            'newgraded' => '315',
+            'sumpenalty' => '10.0000000',
+            'manualcomment' => '',
+            'manualcommentformat' => '1',
+            'flagged' => '0',
+        );
+        $qstates = array(
+            313 => (object) array(
+                'id' => '313',
+                'attempt' => '21',
+                'question' => '30',
+                'seq_number' => '0',
+                'answer' => '',
+                'timestamp' => '1309103130',
+                'event' => '0',
+                'grade' => '0.0000000',
+                'raw_grade' => '0.0000000',
+                'penalty' => '0.0000000',
+            ),
+            314 => (object) array(
+                'id' => '314',
+                'attempt' => '21',
+                'question' => '30',
+                'seq_number' => '1',
+                'answer' => '54',
+                'timestamp' => '1309103132',
+                'event' => '2',
+                'grade' => '0.0000000',
+                'raw_grade' => '0.0000000',
+                'penalty' => '10.0000000',
+            ),
+            315 => (object) array(
+                'id' => '315',
+                'attempt' => '21',
+                'question' => '30',
+                'seq_number' => '2',
+                'answer' => '54',
+                'timestamp' => '1309103132',
+                'event' => '6',
+                'grade' => '0.0000000',
+                'raw_grade' => '0.0000000',
+                'penalty' => '10.0000000',
+            ),
+        );
+
+        $qa = $this->updater->convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates);
+
+        $expectedqa = (object) array(
+            'behaviour' => 'adaptive',
+            'questionid' => 30,
+            'variant' => 1,
+            'maxmark' => 10.0000000,
+            'minfraction' => 0,
+            'flagged' => 0,
+            'questionsummary' => '1 +1 = 2 ?',
+            'rightanswer' => 'True',
+            'responsesummary' => 'False',
+            'timemodified' => 1309103132,
+            'steps' => array(
+                0 => (object) array(
+                    'sequencenumber' => 0,
+                    'state' => 'todo',
+                    'fraction' => null,
+                    'timecreated' => 1309103130,
+                    'userid' => 7,
+                    'data' => array(),
+                ),
+                1 => (object) array(
+                    'sequencenumber' => 1,
+                    'state' => 'complete',
+                    'fraction' => null,
+                    'timecreated' => 1309103132,
+                    'userid' => 7,
+                    'data' => array('answer' => '0'),
+                ),
+                2 => (object) array(
+                    'sequencenumber' => 2,
+                    'state' => 'gradedwrong',
+                    'fraction' => 0.0,
+                    'timecreated' => 1309103132,
+                    'userid' => 7,
+                    'data' => array('answer' => 0, '-finish' => 1,
+                            '-_try' => 1, '-_rawfraction' => 0),
+                ),
+            ),
+        );
+
+        $this->compare_qas($expectedqa, $qa);
+    }
 }
index 0e1dc78..5f19896 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Version information for the true/fales question type.
+ * Version information for the true/false question type.
  *
  * @package    qtype
  * @subpackage truefalse
index 7d33ccb..2284a41 100644 (file)
@@ -1124,7 +1124,7 @@ function tag_unset_flag($tagids) {
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function tag_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function tag_page_type_list($pagetype, $parentcontext, $currentcontext) {
     return array(
         'tag-*'=>get_string('page-tag-x', 'tag'),
         'tag-index'=>get_string('page-tag-index', 'tag'),
index fdc28bd..ac8cd74 100644 (file)
@@ -355,10 +355,6 @@ class moodle_user_external extends external_api {
     public static function get_users_by_id($userids) {
         global $CFG, $USER, $DB;
         require_once($CFG->dirroot . "/user/lib.php");
-        require_once($CFG->dirroot . "/user/profile/lib.php"); //custom field library
-        require_once($CFG->dirroot . "/lib/filelib.php"); // file handling on description and friends
-
-        $isadmin = is_siteadmin($USER);
 
         $params = self::validate_parameters(self::get_users_by_id_parameters(),
                 array('userids'=>$userids));
@@ -371,205 +367,29 @@ class moodle_user_external extends external_api {
         $users = $DB->get_recordset_sql($usersql, $params);
 
         $result = array();
+        $hasuserupdatecap = has_capability('moodle/user:update', get_system_context());
         foreach ($users as $user) {
             if (!empty($user->deleted)) {
                 continue;
             }
             context_instance_preload($user);
-            // cached
-            $context = get_context_instance(CONTEXT_USER, $user->id);
-            $hasviewdetailscap = has_capability('moodle/user:viewdetails', $context);
-            $hasuserupdatecap = has_capability('moodle/user:update', get_system_context());
-
-            self::validate_context($context);
-
+            $usercontext = get_context_instance(CONTEXT_USER, $user->id);
+            self::validate_context($usercontext);
             $currentuser = ($user->id == $USER->id);
 
-            if (!$currentuser && !$hasviewdetailscap && !has_coursecontact_role($user->id)) {
-                throw new moodle_exception('usernotavailable', 'error');
-            }
-
-            $userarray = array();
-
-            //basic fields
-            $userarray['id'] = $user->id;
-            if ($isadmin) {
-                $userarray['username'] = $user->username;
-            }
-            if ($isadmin or has_capability('moodle/site:viewfullnames', $context)) {
-                $userarray['firstname'] = $user->firstname;
-                $userarray['lastname'] = $user->lastname;
-            }
-            $userarray['fullname'] = fullname($user);
-
-            //fields matching permissions from /user/editadvanced.php
-            if ($currentuser or $hasuserupdatecap) {
-                $userarray['auth'] = $user->auth;
-                $userarray['confirmed'] = $user->confirmed;
-                $userarray['idnumber'] = $user->idnumber;
-                $userarray['lang'] = $user->lang;
-                $userarray['theme'] = $user->theme;
-                $userarray['timezone'] = $user->timezone;
-                $userarray['mailformat'] = $user->mailformat;
-            }
-
-            //Custom fields (matching /user/profil/lib.php - profile_display_fields code logic)
-            $fields = $DB->get_recordset_sql("SELECT f.*
-                                                FROM {user_info_field} f
-                                                JOIN {user_info_category} c
-                                                     ON f.categoryid=c.id
-                                            ORDER BY c.sortorder ASC, f.sortorder ASC");
-            foreach ($fields as $field) {
-                require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
-                $newfield = 'profile_field_'.$field->datatype;
-                $formfield = new $newfield($field->id, $user->id);
-                if ($formfield->is_visible() and !$formfield->is_empty()) {
-                    $userarray['customfields'][] =
-                        array('name' => $formfield->field->name, 'value' => $formfield->data,
-                            'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
-                }
-            }
-            $fields->close();
-
-            //image profiles urls (public, no permission required in fact)
-            $profileimageurl = moodle_url::make_pluginfile_url($context->id, 'user', 'icon', NULL, '/', 'f1');
-            $userarray['profileimageurl'] = $profileimageurl->out(false);
-            $profileimageurlsmall = moodle_url::make_pluginfile_url($context->id, 'user', 'icon', NULL, '/', 'f2');
-            $userarray['profileimageurlsmall'] = $profileimageurlsmall->out(false);
-
-            //hidden user field
-            if (has_capability('moodle/user:viewhiddendetails', $context)) {
-                $hiddenfields = array();
-            } else {
-                $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
-            }
-
-            if (isset($user->description) && (!isset($hiddenfields['description']) or $isadmin)) {
-                if (empty($CFG->profilesforenrolledusersonly) || $currentuser) {
-                    $user->description = file_rewrite_pluginfile_urls($user->description, 'pluginfile.php', $context->id, 'user', 'profile', null);
-                    $userarray['description'] = $user->description;
-                    $userarray['descriptionformat'] = $user->descriptionformat;
-                }
-            }
-
-            if ((! isset($hiddenfields['country']) or $isadmin) && $user->country) {
-                $userarray['country'] = $user->country;
-            }
-
-            if ((! isset($hiddenfields['city']) or $isadmin) && $user->city) {
-                $userarray['city'] = $user->city;
-            }
-
-            if (has_capability('moodle/user:viewhiddendetails', $context)) {
-                if ($user->address) {
-                    $userarray['address'] = $user->address;
-                }
-                if ($user->phone1) {
-                    $userarray['phone1'] = $user->phone1;
-                }
-                if ($user->phone2) {
-                    $userarray['phone2'] = $user->phone2;
-                }
-            }
-
-            if ($currentuser
-              or $user->maildisplay == 1
-              or has_capability('moodle/course:useremail', $context)
-              or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER))) {
-                $userarray['email'] = $user->email;;
-            }
-
-            if ($user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
-                $url = $user->url;
-                if (strpos($user->url, '://') === false) {
-                    $url = 'http://'. $url;
-                }
-                $user->url = clean_param($user->url, PARAM_URL);
-                $userarray['url'] = $user->url;
-            }
-
-            if ($user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
-                $userarray['icq'] = $user->icq;
-            }
-
-            if ($user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
-                $userarray['skype'] = $user->skype;
-            }
-            if ($user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
-                $userarray['yahoo'] = $user->yahoo;
-            }
-            if ($user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
-                $userarray['aim'] = $user->aim;
-            }
-            if ($user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
-                $userarray['msn'] = $user->msn;
-            }
-
-            if ((!isset($hiddenfields['firstaccess'])) or $isadmin) {
-                if ($user->firstaccess) {
-                    $userarray['firstaccess'] = $user->firstaccess;
-                } else {
-                    $userarray['firstaccess'] = 0;
-                }
-            }
-            if ((!isset($hiddenfields['lastaccess'])) or $isadmin) {
-                if ($user->lastaccess) {
-                    $userarray['lastaccess'] = $user->lastaccess;
-                } else {
-                    $userarray['lastaccess'] = 0;
+            if ($userarray  = user_get_user_details($user)) {
+                //fields matching permissions from /user/editadvanced.php
+                if ($currentuser or $hasuserupdatecap) {
+                    $userarray['auth']       = $user->auth;
+                    $userarray['confirmed']  = $user->confirmed;
+                    $userarray['idnumber']   = $user->idnumber;
+                    $userarray['lang']       = $user->lang;
+                    $userarray['theme']      = $user->theme;
+                    $userarray['timezone']   = $user->timezone;
+                    $userarray['mailformat'] = $user->mailformat;
                 }
+                $result[] = $userarray;
             }
-            /// Printing tagged interests
-            if (!empty($CFG->usetags)) {
-                require_once($CFG->dirroot . '/tag/lib.php');
-                if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
-                    $userarray['interests'] = $interests;
-                }
-            }
-
-            //Departement/Institution are not displayed on any profile, however you can get them from editing profile.
-            if ($isadmin or $currentuser) {
-                if ($user->institution) {
-                    $userarray['institution'] = $user->institution;
-                }
-                if (isset($user->department)) { //isset because it's ok to have department 0
-                    $userarray['department'] = $user->department;
-                }
-            }
-
-            //list of courses where the user is enrolled
-            $enrolledcourses = array();
-            if (!isset($hiddenfields['mycourses'])) {
-                if ($mycourses = enrol_get_users_courses($user->id, true, NULL, 'visible DESC,sortorder ASC')) {
-                    $courselisting = '';
-                    foreach ($mycourses as $mycourse) {
-                        if ($mycourse->category) {
-                            if ($mycourse->visible == 0) {
-                                $ccontext = get_context_instance(CONTEXT_COURSE, $mycourse->id);
-                                if (!has_capability('moodle/course:viewhiddencourses', $ccontext)) {
-                                    continue;
-                                }
-                            }
-                            $enrolledcourse = array();
-                            $enrolledcourse['id'] = $mycourse->id;
-                            $enrolledcourse['fullname'] = $mycourse->fullname;
-                            $enrolledcourses[] = $enrolledcourse;
-                        }
-                    }
-                    $userarray['enrolledcourses'] = $enrolledcourses;
-                }
-            }
-
-            //user preferences
-            if ($currentuser) {
-                $preferences = array();
-                $userpreferences = get_user_preferences();
-                 foreach($userpreferences as $prefname => $prefvalue) {
-                    $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
-                 }
-                 $userarray['preferences'] = $preferences;
-            }
-            $result[] = $userarray;
         }
         $users->close();
 
@@ -637,7 +457,8 @@ class moodle_user_external extends external_api {
                         new external_single_structure(
                             array(
                                 'id'  => new external_value(PARAM_INT, 'Id of the course'),
-                                'fullname' => new external_value(PARAM_RAW, 'Fullname of the course')
+                                'fullname'  => new external_value(PARAM_RAW, 'Fullname of the course'),
+                                'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
                             )
                     ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL)
                 )
@@ -671,11 +492,6 @@ class moodle_user_external extends external_api {
     public static function get_course_participants_by_id($userlist) {
         global $CFG, $USER, $DB;
         require_once($CFG->dirroot . "/user/lib.php");
-        require_once($CFG->dirroot . "/user/profile/lib.php"); //custom field library
-        require_once($CFG->dirroot . "/lib/filelib.php");      // file handling on description and friends
-
-        $isadmin = is_siteadmin($USER);
-
         $params = self::validate_parameters(self::get_course_participants_by_id_parameters(), array('userlist'=>$userlist));
 
         $userids = array();
@@ -713,190 +529,217 @@ class moodle_user_external extends external_api {
                 continue;
             }
             context_instance_preload($user);
-            $usercontext = get_context_instance(CONTEXT_USER, $user->id);
             $course = $courses[$courseids[$user->id]];
             $context = get_context_instance(CONTEXT_COURSE, $courseids[$user->id]);
-            $hasviewdetailscap = has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext);
-
             self::validate_context($context);
-
-            $currentuser = ($user->id == $USER->id);
-
-            if (!$currentuser && !$hasviewdetailscap && !has_coursecontact_role($user->id)) {
-                throw new moodle_exception('usernotavailable', 'error');
-            }
-            $userarray = array();
-
-            //basic fields
-            $userarray['id'] = $user->id;
-            if ($isadmin) {
-                $userarray['username'] = $user->username;
-            }
-            if ($isadmin or has_capability('moodle/site:viewfullnames', $context)) {
-                $userarray['firstname'] = $user->firstname;
-                $userarray['lastname'] = $user->lastname;
-            }
-            $userarray['fullname'] = fullname($user);
-
-            //Custom fields (matching /user/profile/lib.php - profile_display_fields code logic)
-            $userarray['customfields'] = array();
-
-            $fields = $DB->get_recordset_sql("SELECT f.*
-                                                FROM {user_info_field} f
-                                                JOIN {user_info_category} c
-                                                     ON f.categoryid=c.id
-                                            ORDER BY c.sortorder ASC, f.sortorder ASC");
-            foreach ($fields as $field) {
-                require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
-                $newfield = 'profile_field_'.$field->datatype;
-                $formfield = new $newfield($field->id, $user->id);
-                if ($formfield->is_visible() and !$formfield->is_empty()) {
-                    $userarray['customfields'][] =
-                        array('name' => $formfield->field->name, 'value' => $formfield->data,
-                            'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
-                }
-            }
-            $fields->close();
-
-            //image profiles urls (public, no permission required in fact)
-            $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', 'f1');
-            $userarray['profileimageurl'] = $profileimageurl->out(false);
-            $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', 'f2');
-            $userarray['profileimageurlsmall'] = $profileimageurlsmall->out(false);
-
-            //hidden user field
-            if (has_capability('moodle/course:viewhiddenuserfields', $context)) {
-                $hiddenfields = array();
-            } else {
-                $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
-            }
-
-            if (isset($user->description) && (!isset($hiddenfields['description']) or $isadmin)) {
-                if (empty($CFG->profilesforenrolledusersonly) || $currentuser) {
-                    $user->description = file_rewrite_pluginfile_urls($user->description, 'pluginfile.php', $context->id, 'user', 'profile', null);
-                    $userarray['description'] = $user->description;
-                    $userarray['descriptionformat'] = $user->descriptionformat;
-                }
-            }
-
-            if ((! isset($hiddenfields['country']) or $isadmin) && $user->country) {
-                $userarray['country'] = $user->country;
+            if ($userarray = user_get_user_details($user, $course)) {
+                $result[] = $userarray;
             }
+        }
 
-            if ((! isset($hiddenfields['city']) or $isadmin) && $user->city) {
-                $userarray['city'] = $user->city;
-            }
+        $users->close();
 
-            if (has_capability('moodle/course:viewhiddenuserfields', $context)) {
-                if ($user->address) {
-                    $userarray['address'] = $user->address;
-                }
-                if ($user->phone1) {
-                    $userarray['phone1'] = $user->phone1;
-                }
-                if ($user->phone2) {
-                    $userarray['phone2'] = $user->phone2;
-                }
-            }
+        return $result;
+    }
 
-            if ($currentuser
-              or $user->maildisplay == 1
-              or has_capability('moodle/course:useremail', $context)
-              or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER))) {
-                $userarray['email'] = $user->email;;
-            }
+    /**
+     * Returns description of method result value
+     * @return external_description
+     */
+    public static function get_course_participants_by_id_returns() {
+        return new external_multiple_structure(
+            new external_single_structure(
+                array(
+                    'id'    => new external_value(PARAM_NUMBER, 'ID of the user'),
+                    'username'    => new external_value(PARAM_RAW, 'Username policy is defined in Moodle security config', VALUE_OPTIONAL),
+                    'firstname'   => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL),
+                    'lastname'    => new external_value(PARAM_NOTAGS, 'The family name of the user', VALUE_OPTIONAL),
+                    'fullname'    => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
+                    'email'       => new external_value(PARAM_TEXT, 'An email address - allow email as root@localhost', VALUE_OPTIONAL),
+                    'address'     => new external_value(PARAM_MULTILANG, 'Postal address', VALUE_OPTIONAL),
+                    'phone1'      => new external_value(PARAM_NOTAGS, 'Phone 1', VALUE_OPTIONAL),
+                    'phone2'      => new external_value(PARAM_NOTAGS, 'Phone 2', VALUE_OPTIONAL),
+                    'icq'         => new external_value(PARAM_NOTAGS, 'icq number', VALUE_OPTIONAL),
+                    'skype'       => new external_value(PARAM_NOTAGS, 'skype id', VALUE_OPTIONAL),
+                    'yahoo'       => new external_value(PARAM_NOTAGS, 'yahoo id', VALUE_OPTIONAL),
+                    'aim'         => new external_value(PARAM_NOTAGS, 'aim id', VALUE_OPTIONAL),
+                    'msn'         => new external_value(PARAM_NOTAGS, 'msn number', VALUE_OPTIONAL),
+                    'department'  => new external_value(PARAM_TEXT, 'department', VALUE_OPTIONAL),
+                    'institution' => new external_value(PARAM_TEXT, 'institution', VALUE_OPTIONAL),
+                    'interests'   => new external_value(PARAM_TEXT, 'user interests (separated by commas)', VALUE_OPTIONAL),
+                    'firstaccess' => new external_value(PARAM_INT, 'first access to the site (0 if never)', VALUE_OPTIONAL),
+                    'lastaccess'  => new external_value(PARAM_INT, 'last access to the site (0 if never)', VALUE_OPTIONAL),
+                    'description' => new external_value(PARAM_RAW, 'User profile description', VALUE_OPTIONAL),
+                    'descriptionformat' => new external_value(PARAM_INT, 'User profile description format', VALUE_OPTIONAL),
+                    'city'        => new external_value(PARAM_NOTAGS, 'Home city of the user', VALUE_OPTIONAL),
+                    'url'         => new external_value(PARAM_URL, 'URL of the user', VALUE_OPTIONAL),
+                    'country'     => new external_value(PARAM_ALPHA, 'Home country code of the user, such as AU or CZ', VALUE_OPTIONAL),
+                    'profileimageurlsmall' => new external_value(PARAM_URL, 'User image profile URL - small version'),
+                    'profileimageurl' => new external_value(PARAM_URL, 'User image profile URL - big version'),
+                    'customfields' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'type'  => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field - text field, checkbox...'),
+                                'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
+                                'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
+                                'shortname' => new external_value(PARAM_RAW, 'The shortname of the custom field - to be able to build the field class in the code'),
+                            )
+                        ), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
+                    'groups' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'id'  => new external_value(PARAM_INT, 'group id'),
+                                'name' => new external_value(PARAM_RAW, 'group name'),
+                                'description' => new external_value(PARAM_RAW, 'group description'),
+                            )
+                        ), 'user groups', VALUE_OPTIONAL),
+                    'roles' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'roleid'       => new external_value(PARAM_INT, 'role id'),
+                                'name'         => new external_value(PARAM_RAW, 'role name'),
+                                'shortname'    => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
+                                'sortorder'    => new external_value(PARAM_INT, 'role sortorder')
+                            )
+                        ), 'user roles', VALUE_OPTIONAL),
+                    'preferences' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'name'  => new external_value(PARAM_ALPHANUMEXT, 'The name of the preferences'),
+                                'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
+                            )
+                    ), 'User preferences', VALUE_OPTIONAL),
+                    'enrolledcourses' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'id'  => new external_value(PARAM_INT, 'Id of the course'),
+                                'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
+                                'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
+                            )
+                    ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL)
+                )
+            )
+        );
+    }
 
-            if ($user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
-                $url = $user->url;
-                if (strpos($user->url, '://') === false) {
-                    $url = 'http://'. $url;
-                }
-                $user->url = clean_param($user->url, PARAM_URL);
-                $userarray['url'] = $user->url;
-            }
+    /**
+     * Returns description of method parameters
+     * @return external_function_parameters
+     */
+    public static function get_users_by_courseid_parameters() {
+        return new external_function_parameters(
+            array(
+                'courseid' => new external_value(PARAM_INT, 'course id'),
+                'options'  => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name'  => new external_value(PARAM_ALPHANUMEXT, 'option name'),
+                            'value' => new external_value(PARAM_RAW, 'option value')
+                        )
+                    ), 'method options'),
+            )
+        );
+    }
 
-            if ($user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
-                $userarray['icq'] = $user->icq;
-            }
+    /**
+     * Get course participants details
+     * @param int $courseid  course id
+     * @param array $options options {
+     *          'name' => option name
+     *          'value' => option value
+     * }
+     * @return array An array of users
+     */
+    public static function get_users_by_courseid($courseid, $options) {
+        global $CFG, $USER, $DB;
+        require_once($CFG->dirroot . "/user/lib.php");
 
-            if ($user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
-                $userarray['skype'] = $user->skype;
-            }
-            if ($user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
-                $userarray['yahoo'] = $user->yahoo;
-            }
-            if ($user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
-                $userarray['aim'] = $user->aim;
-            }
-            if ($user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
-                $userarray['msn'] = $user->msn;
+        $params = self::validate_parameters(
+            self::get_users_by_courseid_parameters(),
+            array(
+                'courseid'=>$courseid,
+                'options'=>$options
+            )
+        );
+        $withcapability = '';
+        $groupid        = 0;
+        $onlyactive     = false;
+        foreach ($options as $option) {
+            switch ($option['name']) {
+            case 'withcapability':
+                $withcapability = $option['value'];
+                break;
+            case 'groupid':
+                $groupid = (int)$option['value'];
+                break;
+            case 'onlyactive':
+                $onlyactive = !empty($option['value']);
+                break;
             }
+        }
 
-            if ((!isset($hiddenfields['firstaccess'])) or $isadmin) {
-                if ($user->firstaccess) {
-                    $userarray['firstaccess'] = $user->firstaccess;
-                } else {
-                    $userarray['firstaccess'] = 0;
-                }
-            }
-            if ((!isset($hiddenfields['lastaccess'])) or $isadmin) {
-                if ($user->lastaccess) {
-                    $userarray['lastaccess'] = $user->lastaccess;
-                } else {
-                    $userarray['lastaccess'] = 0;
-                }
-            }
-            /// Printing tagged interests
-            if (!empty($CFG->usetags)) {
-                require_once($CFG->dirroot . '/tag/lib.php');
-                if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
-                    $userarray['interests'] = $interests;
-                }
-            }
+        // to overwrite this parameter, you need role:review capability
+        if ($withcapability) {
+            require_capability('moodle/role:review', $coursecontext);
+        }
+        // need accessallgroups capability if you want to overwrite this option
+        if (!empty($groupid) && groups_is_member($groupid)) {
+            require_capability('moodle/site:accessallgroups', $context);
+        }
+        // to overwrite this option, you need course:enrolereview permission
+        if ($onlyactive) {
+            require_capability('moodle/course:enrolreview', $coursecontext);
+        }
 
-            //Departement/Institution are not displayed on any profile, however you can get them from editing profile.
-            if ($isadmin or $currentuser) {
-                if ($user->institution) {
-                    $userarray['institution'] = $user->institution;
-                }
-                if (isset($user->department)) { //isset because it's ok to have department 0
-                    $userarray['department'] = $user->department;
-                }
-            }
+        list($coursectxselect, $coursectxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
+        $coursesql = "SELECT c.* $coursectxselect
+                        FROM {course} c $coursectxjoin
+                       WHERE c.id = $courseid";
+        $course = $DB->get_record_sql($coursesql);
+        context_instance_preload($course);
+        $coursecontext = get_context_instance(CONTEXT_COURSE, $params['courseid']);
+        if ($courseid == SITEID) {
+            $context = get_system_context();
+        } else {
+            $context = $coursecontext;
+        }
+        try {
+            self::validate_context($context);
+        } catch (Exception $e) {
+            $exceptionparam = new stdClass();
+            $exceptionparam->message = $e->getMessage();
+            $exceptionparam->courseid = $params['courseid'];
+            throw new moodle_exception(get_string('errorcoursecontextnotvalid' , 'webservice', $exceptionparam));
+        }
 
-            // not a big secret
-            $userarray['roles'] = array();
-            $roles = get_user_roles($context, $user->id, false);
-            foreach ($roles as $role) {
-                $userarray['roles'][] = array(
-                    'roleid'       => $role->roleid,
-                    'name'         => $role->name,
-                    'shortname'    => $role->shortname,
-                    'sortorder'    => $role->sortorder
-                );
+        list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, $withcapability, $groupid, $onlyactive);
+        list($ctxselect, $ctxjoin) = context_instance_preload_sql('u.id', CONTEXT_USER, 'ctx');
+        $records = $DB->get_records_sql($enrolledsql, $enrolledparams);
+        $sqlparams['courseid'] = $courseid;
+        $sql = "SELECT u.* $ctxselect
+                  FROM {user} u $ctxjoin
+                 WHERE u.id IN ($enrolledsql)
+                 ORDER BY u.id ASC";
+        $enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams);
+        $users = array();
+        foreach ($enrolledusers as $user) {
+            if (!empty($user->deleted)) {
+                continue;
             }
-
-            // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group
-            if (has_capability('moodle/site:accessallgroups', $context)) {
-                $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name,g.description');
-                foreach ($usergroups as $group) {
-                    $group->description = file_rewrite_pluginfile_urls($group->description, 'pluginfile.php', $context->id, 'group', 'description', $group->id);
-                    $userarray['groups'][] = array('id'=>$group->id, 'name'=>$group->name, 'description'=>$group->description);
-                }
+            context_instance_preload($user);
+            if ($userdetails = user_get_user_details($user, $course)) {
+                $users[] = $userdetails;
             }
-            $result[] = $userarray;
         }
+        $enrolledusers->close();
 
-        $users->close();
-
-        return $result;
+        return $users;
     }
-
     /**
      * Returns description of method result value
      * @return external_description
      */
-    public static function get_course_participants_by_id_returns() {
+    public static function get_users_by_courseid_returns() {
         return new external_multiple_structure(
             new external_single_structure(
                 array(
@@ -952,6 +795,21 @@ class moodle_user_external extends external_api {
                                 'sortorder'    => new external_value(PARAM_INT, 'role sortorder')
                             )
                         ), 'user roles', VALUE_OPTIONAL),
+                    'preferences' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'name'  => new external_value(PARAM_ALPHANUMEXT, 'The name of the preferences'),
+                                'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
+                            )
+                    ), 'User preferences', VALUE_OPTIONAL),
+                    'enrolledcourses' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'id'  => new external_value(PARAM_INT, 'Id of the course'),
+                                'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
+                                'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
+                            )
+                    ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL)
                 )
             )
         );
index b5df18c..668e7a8 100644 (file)
@@ -96,13 +96,256 @@ function user_get_users_by_id($userids) {
     return $DB->get_records_list('user', 'id', $userids);
 }
 
+
+/**
+ *
+ * Give user record from mdl_user, build an array conntains
+ * all user details
+ * @param stdClass $user user record from mdl_user
+ * @param stdClass $context context object
+ * @param stdClass $course moodle course
+ * @return array
+ */
+function user_get_user_details($user, $course = null) {
+    global $USER, $DB, $CFG;
+    require_once($CFG->dirroot . "/user/profile/lib.php"); //custom field library
+    require_once($CFG->dirroot . "/lib/filelib.php");      // file handling on description and friends
+
+    if (!empty($course)) {
+        $context = get_context_instance(CONTEXT_COURSE, $course->id);
+        $usercontext = get_context_instance(CONTEXT_USER, $user->id);
+        $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
+    } else {
+        $context = get_context_instance(CONTEXT_USER, $user->id);
+        $usercontext = $context;
+    }
+
+    $currentuser = ($user->id == $USER->id);
+    $isadmin = is_siteadmin($USER);
+
+    if (!empty($course)) {
+        $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
+    } else {
+        $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
+    }
+    $canviewdetailscap       = ($canviewdetailscap || has_capability('moodle/user:viewdetails', $context));
+    $canviewfullnames        = has_capability('moodle/site:viewfullnames', $context);
+    if (!empty($course)) {
+        $canviewuseremail = has_capability('moodle/course:useremail', $context);
+    } else {
+        $canviewuseremail = false;
+    }
+    $cannotviewdescription   = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid'=>$user->id));
+    if (!empty($course)) {
+        $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
+    } else {
+        $canaccessallgroups = false;
+    }
+
+    if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
+        // skip this user details
+        return null;
+    }
+
+    $userdetails = array();
+    $userdetails['id'] = $user->id;
+
+    if ($isadmin or $currentuser) {
+        $userdetails['username'] = $user->username;
+    }
+    if ($isadmin or $canviewfullnames) {
+        $userdetails['firstname'] = $user->firstname;
+        $userdetails['lastname'] = $user->lastname;
+    }
+    $userdetails['fullname'] = fullname($user);
+
+    $fields = $DB->get_recordset_sql("SELECT f.*
+                                        FROM {user_info_field} f
+                                        JOIN {user_info_category} c
+                                             ON f.categoryid=c.id
+                                    ORDER BY c.sortorder ASC, f.sortorder ASC");
+    $userdetails['customfields'] = array();
+    foreach ($fields as $field) {
+        require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
+        $newfield = 'profile_field_'.$field->datatype;
+        $formfield = new $newfield($field->id, $user->id);
+        if ($formfield->is_visible() and !$formfield->is_empty()) {
+            $userdetails['customfields'][] =
+                array('name' => $formfield->field->name, 'value' => $formfield->data,
+                    'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
+        }
+    }
+    $fields->close();
+    // unset customfields if it's empty
+    if (empty($userdetails['customfields'])) {
+        unset($userdetails['customfields']);
+    }
+
+    // profile image
+    $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', 'f1');
+    $userdetails['profileimageurl'] = $profileimageurl->out(false);
+    $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', 'f2');
+    $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
+
+    //hidden user field
+    if ($canviewhiddenuserfields) {
+        $hiddenfields = array();
+        // address, phone1 and phone2 not appears in hidden fields list
+        // but require viewhiddenfields capability
+        // according to user/profile.php
+        if ($user->address) {
+            $userdetails['address'] = $user->address;
+        }
+        if ($user->phone1) {
+            $userdetails['phone1'] = $user->phone1;
+        }
+        if ($user->phone2) {
+            $userdetails['phone2'] = $user->phone2;
+        }
+    } else {
+        $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
+    }
+
+    if (isset($user->description) && (!isset($hiddenfields['description']) or $isadmin)) {
+        if (!$cannotviewdescription) {
+            $user->description = file_rewrite_pluginfile_urls($user->description, 'pluginfile.php', $usercontext->id, 'user', 'profile', null);
+            $userdetails['description'] = $user->description;
+            $userdetails['descriptionformat'] = $user->descriptionformat;
+        }
+    }
+
+    if ((!isset($hiddenfields['country']) or $isadmin) && $user->country) {
+        $userdetails['country'] = $user->country;
+    }
+
+    if ((!isset($hiddenfields['city']) or $isadmin) && $user->city) {
+        $userdetails['city'] = $user->city;
+    }
+
+    if ($user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
+        $url = $user->url;
+        if (strpos($user->url, '://') === false) {
+            $url = 'http://'. $url;
+        }
+        $user->url = clean_param($user->url, PARAM_URL);
+        $userdetails['url'] = $user->url;
+    }
+
+    if ($user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
+        $userdetails['icq'] = $user->icq;
+    }
+
+    if ($user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
+        $userdetails['skype'] = $user->skype;
+    }
+    if ($user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
+        $userdetails['yahoo'] = $user->yahoo;
+    }
+    if ($user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
+        $userdetails['aim'] = $user->aim;
+    }
+    if ($user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
+        $userdetails['msn'] = $user->msn;
+    }
+
+    if (!isset($hiddenfields['firstaccess']) or $isadmin) {
+        if ($user->firstaccess) {
+            $userdetails['firstaccess'] = $user->firstaccess;
+        } else {
+            $userdetails['firstaccess'] = 0;
+        }
+    }
+    if (!isset($hiddenfields['lastaccess']) or $isadmin) {
+        if ($user->lastaccess) {
+            $userdetails['lastaccess'] = $user->lastaccess;
+        } else {
+            $userdetails['lastaccess'] = 0;
+        }
+    }
+
+    if ($currentuser
+      or $canviewuseremail  // this is a capability in course context, it will be false in usercontext
+      or $user->maildisplay == 1
+      or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER))) {
+        $userdetails['email'] = $user->email;;
+    }
+
+    if (!empty($CFG->usetags)) {
+        require_once($CFG->dirroot . '/tag/lib.php');
+        if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
+            $userdetails['interests'] = $interests;
+        }
+    }
+
+    //Departement/Institution are not displayed on any profile, however you can get them from editing profile.
+    if ($isadmin or $currentuser) {
+        if ($user->institution) {
+            $userdetails['institution'] = $user->institution;
+        }
+        if (isset($user->department)) { //isset because it's ok to have department 0
+            $userdetails['department'] = $user->department;
+        }
+    }
+
+    if (!empty($course)) {
+        // not a big secret
+        $roles = get_user_roles($context, $user->id, false);
+        $userdetails['roles'] = array();
+        foreach ($roles as $role) {
+            $userdetails['roles'][] = array(
+                'roleid'       => $role->roleid,
+                'name'         => $role->name,
+                'shortname'    => $role->shortname,
+                'sortorder'    => $role->sortorder
+            );
+        }
+    }
+
+    // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group
+    if (!empty($course) && $canaccessallgroups) {
+        $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name,g.description');
+        $userdetails['groups'] = array();
+        foreach ($usergroups as $group) {
+            $group->description = file_rewrite_pluginfile_urls($group->description, 'pluginfile.php', $context->id, 'group', 'description', $group->id);
+            $userdetails['groups'][] = array('id'=>$group->id, 'name'=>$group->name, 'description'=>$group->description);
+        }
+    }
+    //list of courses where the user is enrolled
+    if (!isset($hiddenfields['mycourses'])) {
+        $enrolledcourses = array();
+        if ($mycourses = enrol_get_users_courses($user->id, true)) {
+            foreach ($mycourses as $mycourse) {
+                if ($mycourse->category) {
+                    $enrolledcourse = array();
+                    $enrolledcourse['id'] = $mycourse->id;
+                    $enrolledcourse['fullname'] = $mycourse->fullname;
+                    $enrolledcourse['shortname'] = $mycourse->shortname;
+                    $enrolledcourses[] = $enrolledcourse;
+                }
+            }
+            $userdetails['enrolledcourses'] = $enrolledcourses;
+        }
+    }
+
+    //user preferences
+    if ($currentuser) {
+        $preferences = array();
+        $userpreferences = get_user_preferences();
+         foreach($userpreferences as $prefname => $prefvalue) {
+            $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
+         }
+         $userdetails['preferences'] = $preferences;
+    }
+    return $userdetails;
+}
+
 /**
  * Return a list of page types
  * @param string $pagetype current page type
  * @param stdClass $parentcontext Block's parent context
  * @param stdClass $currentcontext Current context of block
  */
-function user_pagetypelist($pagetype, $parentcontext, $currentcontext) {
+function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
     return array(
         'user-profile'=>get_string('page-user-profile', 'pagetype'),
         'my-index'=>get_string('page-my-index', 'pagetype')
index 2504d70..fa70a8b 100644 (file)
@@ -31,7 +31,7 @@ defined('MOODLE_INTERNAL') || die();
 
 
 
-$version  = 2011062700.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2011062700.04;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes