Merge branch 'master_MDL-28728' of git://github.com/danmarsden/moodle
authorAparup Banerjee <aparup@moodle.com>
Fri, 16 Dec 2011 03:36:47 +0000 (11:36 +0800)
committerAparup Banerjee <aparup@moodle.com>
Fri, 16 Dec 2011 03:36:47 +0000 (11:36 +0800)
60 files changed:
admin/environment.xml
admin/tool/health/index.php
admin/tool/qeupgradehelper/locallib.php
admin/webservice/forms.php
backup/controller/backup_controller.class.php
backup/controller/restore_controller.class.php
backup/moodle2/restore_stepslib.php
backup/util/plan/backup_plan.class.php
course/format/topics/format.php
course/format/weeks/format.php
course/moodleform_mod.php
course/user.php
enrol/database/lib.php
enrol/imsenterprise/lib.php
enrol/ldap/lib.php
enrol/ldap/settings.php
grade/report/grader/lib.php
grade/report/grader/styles.css
lang/en/condition.php
lang/en/webservice.php
lib/blocklib.php
lib/db/install.php
lib/db/upgrade.php
lib/environmentlib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrequirementslib.php
lib/pagelib.php
lib/phpmailer/class.phpmailer.php
lib/questionlib.php
lib/setup.php
lib/setuplib.php
mod/glossary/import_form.php
mod/imscp/locallib.php
mod/lesson/renderer.php
mod/lti/locallib.php
mod/quiz/lib.php
mod/quiz/report/attemptsreport.php
mod/quiz/report/default.php
mod/quiz/report/grading/report.php
mod/quiz/report/overview/report.php
mod/quiz/report/responses/report.php
mod/quiz/report/statistics/lib.php [new file with mode: 0644]
mod/quiz/report/statistics/report.php
mod/scorm/api.php
mod/scorm/datamodels/scorm_13.js.php
mod/scorm/datamodels/scormlib.php
notes/index.php
question/behaviour/adaptive/lang/en/qbehaviour_adaptive.php
question/behaviour/adaptive/renderer.php
question/behaviour/adaptive/simpletest/testwalkthrough.php
question/category_class.php
question/editlib.php
question/type/questiontypebase.php
question/type/shortanswer/questiontype.php
question/type/upgrade.txt
user/index.php
user/lib.php
version.php

index a064ba8..c43b94e 100644 (file)
       </PHP_SETTING>
     </PHP_SETTINGS>
   </MOODLE>
+  <MOODLE version="2.3" requires="2.2">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mysql" version="5.0.25">
+        <FEEDBACK>
+          <ON_ERROR message="mysql416required" />
+        </FEEDBACK>
+      </VENDOR>
+      <VENDOR name="postgres" version="8.3" />
+      <VENDOR name="mssql" version="9.0" />
+      <VENDOR name="odbc_mssql" version="9.0" />
+      <VENDOR name="mssql_n" version="9.0" />
+      <VENDOR name="oracle" version="10.2" />
+      <VENDOR name="sqlite" version="2.0" />
+    </DATABASE>
+    <PHP version="5.3.2" level="required">
+    </PHP>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opensslrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="gdrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="intlrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="40M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="safe_mode" value="0" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingsafemode" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index bb63e3c..cc17830 100644 (file)
 
     ob_start(); //for whitespace test
     require('../../../config.php');
-
-    // extra whitespace test - intentionally breaks cookieless mode
-    $extraws = '';
-    while (ob_get_level()) {
-        $extraws .= ob_get_contents();
-        ob_end_clean();
-    }
+    $extraws = ob_get_clean();
 
     require_once($CFG->libdir.'/adminlib.php');
 
index 7c001d4..62cfca6 100644 (file)
@@ -178,7 +178,7 @@ abstract class tool_qeupgradehelper_quiz_list {
             html_writer::link(new moodle_url('/course/view.php',
                     array('id' => $quizinfo->courseid)), format_string($quizinfo->shortname)),
             html_writer::link(new moodle_url('/mod/quiz/view.php',
-                    array('id' => $quizinfo->name)), format_string($quizinfo->name)),
+                    array('q' => $quizinfo->id)), format_string($quizinfo->name)),
             $quizinfo->attemptcount,
             $quizinfo->questionattempts ? $quizinfo->questionattempts : 0,
         );
index 7520228..b95935c 100644 (file)
@@ -187,16 +187,25 @@ class web_service_token_form extends moodleform {
         $mform->addElement('header', 'token', get_string('token', 'webservice'));
 
         if (empty($data->nouserselection)) {
-            //user searchable selector - get all users (admin and guest included)
-            $sql = "SELECT u.id, u.firstname, u.lastname
-            FROM {user} u
-            ORDER BY u.lastname";
-            $users = $DB->get_records_sql($sql, array());
-            $options = array();
-            foreach ($users as $userid => $user) {
-                $options[$userid] = $user->firstname . " " . $user->lastname;
+
+            //check if the number of user is reasonable to be displayed in a select box
+            $usertotal = $DB->count_records('user',
+                    array('deleted' => 0, 'suspended' => 0, 'confirmed' => 1));
+
+            if ($usertotal < 500) {
+                //user searchable selector - get all users (admin and guest included)
+                $users = $DB->get_records('user',
+                        array('deleted' => 0, 'suspended' => 0, 'confirmed' => 1), 'lastname',
+                        'id, firstname, lastname');
+                $options = array();
+                foreach ($users as $userid => $user) {
+                    $options[$userid] = $user->firstname . " " . $user->lastname;
+                }
+                $mform->addElement('searchableselector', 'user', get_string('user'), $options);
+            } else {
+                //simple text box for username or user id (if two username exists, a form error is displayed)
+                $mform->addElement('text', 'user', get_string('usernameorid', 'webservice'));
             }
-            $mform->addElement('searchableselector', 'user', get_string('user'), $options);
             $mform->addRule('user', get_string('required'), 'required', null, 'client');
         }
 
@@ -230,8 +239,44 @@ class web_service_token_form extends moodleform {
         $this->set_data($data);
     }
 
-    function validation($data, $files) {
+    function get_data() {
+        global $DB;
+        $data = parent::get_data();
+
+        if (!empty($data) && !is_numeric($data->user)) {
+            //retrieve username
+            $user = $DB->get_record('user', array('username' => $data->user), 'id');
+            $data->user = $user->id;
+        }
+        return $data;
+    }
+
+    function validation(&$data, $files) {
+        global $DB;
+
         $errors = parent::validation($data, $files);
+
+        if (is_numeric($data['user'])) {
+            $searchtype = 'id';
+        } else {
+            $searchtype = 'username';
+            //check the username is valid
+            if (clean_param($data['user'], PARAM_USERNAME) != $data['user']) {
+                $errors['user'] = get_string('invalidusername');
+            }
+        }
+
+        if (!isset($errors['user'])) {
+            $users = $DB->get_records('user', array($searchtype => $data['user']), '', 'id');
+
+            //check that the user exists in the database
+            if (count($users) == 0) {
+                $errors['user'] = get_string('usernameoridnousererror', 'webservice');
+            } else if (count($users) > 1) { //can only be a username search as id are unique
+                $errors['user'] = get_string('usernameoridoccurenceerror', 'webservice');
+            }
+        }
+
         return $errors;
     }
 
index 1bab62b..d6d7bd1 100644 (file)
@@ -293,6 +293,14 @@ class backup_controller extends backup implements loggable {
         // Basic/initial prevention against time/memory limits
         set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
         raise_memory_limit(MEMORY_EXTRA);
+        // If this is not a course backup, inform the plan we are not
+        // including all the activities for sure. This will affect any
+        // task/step executed conditionally to stop including information
+        // for section and activity backup. MDL-28180.
+        if ($this->get_type() !== backup::TYPE_1COURSE) {
+            $this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
+            $this->plan->set_excluding_activities();
+        }
         return $this->plan->execute();
     }
 
index 50ec102..680e6e9 100644 (file)
@@ -299,6 +299,14 @@ class restore_controller extends backup implements loggable {
         // Basic/initial prevention against time/memory limits
         set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
         raise_memory_limit(MEMORY_EXTRA);
+        // If this is not a course restore, inform the plan we are not
+        // including all the activities for sure. This will affect any
+        // task/step executed conditionally to stop processing information
+        // for section and activity restore. MDL-28180.
+        if ($this->get_type() !== backup::TYPE_1COURSE) {
+            $this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
+            $this->plan->set_excluding_activities();
+        }
         return $this->plan->execute();
     }
 
index f83b7c3..14d130a 100644 (file)
@@ -1805,15 +1805,20 @@ class restore_course_completion_structure_step extends restore_structure_step {
 
         $data->course = $this->get_courseid();
 
-        $params = array(
-            'course' => $data->course,
-            'criteriatype' => $data->criteriatype,
-            'method' => $data->method,
-            'value' => $data->value,
-        );
-        $DB->insert_record('course_completion_aggr_methd', $params);
+        // Only create the course_completion_aggr_methd records if
+        // the target course has not them defined. MDL-28180
+        if (!$DB->record_exists('course_completion_aggr_methd', array(
+                    'course' => $data->course,
+                    'criteriatype' => $data->criteriatype))) {
+            $params = array(
+                'course' => $data->course,
+                'criteriatype' => $data->criteriatype,
+                'method' => $data->method,
+                'value' => $data->value,
+            );
+            $DB->insert_record('course_completion_aggr_methd', $params);
+        }
     }
-
 }
 
 
index f21385c..bf217fd 100644 (file)
@@ -44,6 +44,7 @@ class backup_plan extends base_plan implements loggable {
         }
         $this->controller = $controller;
         $this->basepath   = $CFG->tempdir . '/backup/' . $controller->get_backupid();
+        $this->excludingdactivities = false;
         parent::__construct('backup_plan');
     }
 
index fe8fb3e..7e24305 100644 (file)
@@ -110,7 +110,7 @@ if ($thissection->summary or $thissection->sequence or $PAGE->user_is_editing())
     if ($PAGE->user_is_editing() && has_capability('moodle/course:update', $coursecontext)) {
         echo '<a title="'.$streditsummary.'" '.
              ' href="editsection.php?id='.$thissection->id.'"><img src="'.$OUTPUT->pix_url('t/edit') . '" '.
-             ' class="icon edit" alt="'.$streditsummary.'" /></a>';
+             ' class="iconsmall edit" alt="'.$streditsummary.'" /></a>';
     }
     echo '</div>';
 
@@ -234,7 +234,7 @@ while ($section <= $course->numsections) {
 
             if ($PAGE->user_is_editing() && has_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $course->id))) {
                 echo ' <a title="'.$streditsummary.'" href="editsection.php?id='.$thissection->id.'">'.
-                     '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="icon edit" alt="'.$streditsummary.'" /></a><br /><br />';
+                     '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall edit" alt="'.$streditsummary.'" /></a><br /><br />';
             }
             echo '</div>';
 
index cf48505..74fb964 100644 (file)
@@ -103,7 +103,7 @@ defined('MOODLE_INTERNAL') || die();
         if ($PAGE->user_is_editing() && has_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $course->id))) {
             echo '<p><a title="'.$streditsummary.'" '.
                  ' href="editsection.php?id='.$thissection->id.'"><img src="'.$OUTPUT->pix_url('t/edit') . '" '.
-                 ' class="icon edit" alt="'.$streditsummary.'" /></a></p>';
+                 ' class="iconsmall edit" alt="'.$streditsummary.'" /></a></p>';
         }
         echo '</div>';
 
@@ -234,7 +234,7 @@ defined('MOODLE_INTERNAL') || die();
 
                 if ($PAGE->user_is_editing() && has_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $course->id))) {
                     echo ' <a title="'.$streditsummary.'" href="editsection.php?id='.$thissection->id.'">'.
-                         '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="icon edit" alt="'.$streditsummary.'" /></a><br /><br />';
+                         '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall edit" alt="'.$streditsummary.'" /></a><br /><br />';
                 }
                 echo '</div>';
 
index a531061..75e0874 100644 (file)
@@ -313,6 +313,35 @@ abstract class moodleform_mod extends moodleform {
             $errors['availablefrom'] = get_string('badavailabledates', 'condition');
         }
 
+        // Conditions: Verify that the grade conditions are numbers, and make sense.
+        if (array_key_exists('conditiongradegroup', $data)) {
+            foreach ($data['conditiongradegroup'] as $i => $gradedata) {
+                if ($gradedata['conditiongrademin'] !== '' && !is_numeric($gradedata['conditiongrademin'])) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradesmustbenumeric', 'condition');
+                    continue;
+                }
+                if ($gradedata['conditiongrademax'] !== '' && !is_numeric($gradedata['conditiongrademax'])) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradesmustbenumeric', 'condition');
+                    continue;
+                }
+                if ($gradedata['conditiongrademin'] !== '' && $gradedata['conditiongrademax'] !== '' &&
+                        $gradedata['conditiongrademax'] < $gradedata['conditiongrademin']) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('badgradelimits', 'condition');
+                    continue;
+                }
+                if ($gradedata['conditiongrademin'] === '' && $gradedata['conditiongrademax'] === '' &&
+                        $gradedata['conditiongradeitemid']) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradeitembutnolimits', 'condition');
+                    continue;
+                }
+                if (($gradedata['conditiongrademin'] !== '' || $gradedata['conditiongrademax'] !== '') &&
+                        !$gradedata['conditiongradeitemid']) {
+                    $errors["conditiongradegroup[{$i}]"] = get_string('gradelimitsbutnoitem', 'condition');
+                    continue;
+                }
+            }
+        }
+
         return $errors;
     }
 
@@ -471,8 +500,6 @@ abstract class moodleform_mod extends moodleform {
             $grouparray[] =& $mform->createElement('static', '', '','% '.get_string('grade_upto','condition').' ');
             $grouparray[] =& $mform->createElement('text', 'conditiongrademax','',array('size'=>3));
             $grouparray[] =& $mform->createElement('static', '', '','%');
-            $mform->setType('conditiongrademin',PARAM_FLOAT);
-            $mform->setType('conditiongrademax',PARAM_FLOAT);
             $group = $mform->createElement('group','conditiongradegroup',
                 get_string('gradecondition', 'condition'),$grouparray);
 
index c7b9e30..7f7535e 100644 (file)
@@ -55,6 +55,8 @@ if ($mode === 'coursecompletions' or $mode === 'coursecompletion') {
 $coursecontext   = get_context_instance(CONTEXT_COURSE, $course->id);
 $personalcontext = get_context_instance(CONTEXT_USER, $user->id);
 
+$PAGE->set_url('/course/user.php', array('id'=>$id, 'user'=>$user->id, 'mode'=>$mode));
+
 require_login();
 $PAGE->set_pagelayout('admin');
 if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext) and !is_enrolled($coursecontext)) {
index 33ddad4..08b86f9 100644 (file)
@@ -639,22 +639,44 @@ class enrol_database_plugin extends enrol_plugin {
         if ($createcourses) {
             require_once("$CFG->dirroot/course/lib.php");
 
-            $template        = $this->get_config('templatecourse');
+            $templatecourse = $this->get_config('templatecourse');
             $defaultcategory = $this->get_config('defaultcategory');
 
-            if ($template) {
-                if ($template = $DB->get_record('course', array('shortname'=>$template))) {
+            $template = false;
+            if ($templatecourse) {
+                if ($template = $DB->get_record('course', array('shortname'=>$templatecourse))) {
                     unset($template->id);
                     unset($template->fullname);
                     unset($template->shortname);
                     unset($template->idnumber);
                 } else {
-                    $template = new stdClass();
+                    if ($verbose) {
+                        mtrace("  can not find template for new course!");
+                    }
                 }
-            } else {
+            }
+            if (!$template) {
+                $courseconfig = get_config('moodlecourse');
                 $template = new stdClass();
+                $template->summary        = '';
+                $template->summaryformat  = FORMAT_HTML;
+                $template->format         = $courseconfig->format;
+                $template->numsections    = $courseconfig->numsections;
+                $template->hiddensections = $courseconfig->hiddensections;
+                $template->newsitems      = $courseconfig->newsitems;
+                $template->showgrades     = $courseconfig->showgrades;
+                $template->showreports    = $courseconfig->showreports;
+                $template->maxbytes       = $courseconfig->maxbytes;
+                $template->groupmode      = $courseconfig->groupmode;
+                $template->groupmodeforce = $courseconfig->groupmodeforce;
+                $template->visible        = $courseconfig->visible;
+                $template->lang           = $courseconfig->lang;
+                $template->groupmodeforce = $courseconfig->groupmodeforce;
             }
             if (!$DB->record_exists('course_categories', array('id'=>$defaultcategory))) {
+                if ($verbose) {
+                    mtrace("  default course category does not exist!");
+                }
                 $categories = $DB->get_records('course_categories', array(), 'sortorder', 'id', 0, 1);
                 $first = reset($categories);
                 $defaultcategory = $first->id;
index e9e90fa..ace7435 100644 (file)
@@ -315,9 +315,12 @@ function process_group_tag($tagcontents) {
     if (preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)) {
         $group->coursecode = trim($matches[1]);
     }
-    if (preg_match('{<description>.*?<short>(.*?)</short>.*?</description>}is', $tagcontents, $matches)) {
+    if (preg_match('{<description>.*?<long>(.*?)</long>.*?</description>}is', $tagcontents, $matches)){
         $group->description = trim($matches[1]);
     }
+    if (preg_match('{<description>.*?<short>(.*?)</short>.*?</description>}is', $tagcontents, $matches)) {
+        $group->shortName = trim($matches[1]);
+    }
     if (preg_match('{<org>.*?<orgunit>(.*?)</orgunit>.*?</org>}is', $tagcontents, $matches)) {
         $group->category = trim($matches[1]);
     }
@@ -355,11 +358,25 @@ function process_group_tag($tagcontents) {
                 if (!$createnewcourses) {
                     $this->log_line("Course $coursecode not found in Moodle's course idnumbers.");
                 } else {
+                    // Set shortname to description or description to shortname if one is set but not the other.
+                    $nodescription = !isset($group->description);
+                    $noshortname = !isset($group->shortName);
+                    if ( $nodescription && $noshortname) {
+                        // If neither short nor long description are set let if fail
+                        $this->log_line("Neither long nor short name are set for $coursecode");
+                    } else if ($nodescription) {
+                        // If short and ID exist, then give the long short's value, then give short the ID's value
+                        $group->description = $group->shortName;
+                        $group->shortName = $coursecode;
+                    } else if ($noshortname) {
+                        // If long and ID exist, then map long to long, then give short the ID's value.
+                        $group->shortName = $coursecode;
+                    }
                     // Create the (hidden) course(s) if not found
                     $courseconfig = get_config('moodlecourse'); // Load Moodle Course shell defaults
                     $course = new stdClass();
                     $course->fullname = $group->description;
-                    $course->shortname = $coursecode;
+                    $course->shortname = $group->shortName;;
                     $course->idnumber = $coursecode;
                     $course->format = $courseconfig->format;
                     $course->visible = $courseconfig->visible;
index 90c0830..730ec5a 100644 (file)
@@ -885,16 +885,34 @@ class enrol_ldap_plugin extends enrol_plugin {
         require_once("$CFG->dirroot/course/lib.php");
 
         // Override defaults with template course
-        $course = new stdClass();
+        $template = false;
         if ($this->get_config('template')) {
-            if($template = $DB->get_record('course', array('shortname'=>$this->get_config('template')))) {
+            if ($template = $DB->get_record('course', array('shortname'=>$this->get_config('template')))) {
                 unset($template->id); // So we are clear to reinsert the record
                 unset($template->fullname);
                 unset($template->shortname);
                 unset($template->idnumber);
-                $course = $template;
             }
         }
+        if (!$template) {
+            $courseconfig = get_config('moodlecourse');
+            $template = new stdClass();
+            $template->summary        = '';
+            $template->summaryformat  = FORMAT_HTML;
+            $template->format         = $courseconfig->format;
+            $template->numsections    = $courseconfig->numsections;
+            $template->hiddensections = $courseconfig->hiddensections;
+            $template->newsitems      = $courseconfig->newsitems;
+            $template->showgrades     = $courseconfig->showgrades;
+            $template->showreports    = $courseconfig->showreports;
+            $template->maxbytes       = $courseconfig->maxbytes;
+            $template->groupmode      = $courseconfig->groupmode;
+            $template->groupmodeforce = $courseconfig->groupmodeforce;
+            $template->visible        = $courseconfig->visible;
+            $template->lang           = $courseconfig->lang;
+            $template->groupmodeforce = $courseconfig->groupmodeforce;
+        }
+        $course = $template;
 
         $course->category = $this->get_config('category');
         if (!$DB->record_exists('course_categories', array('id'=>$this->get_config('category')))) {
index edd0217..fbc8c35 100644 (file)
@@ -56,7 +56,7 @@ if ($ADMIN->fulltree) {
         //--- role mapping settings ---
         $settings->add(new admin_setting_heading('enrol_ldap_roles', get_string('roles', 'enrol_ldap'), ''));
         if (!during_initial_install()) {
-            $settings->add(new admin_setting_ldap_rolemapping('enrol_ldap/role_mapping', get_string ('role_mapping_key', 'enrol_ldap', $role->name), get_string ('role_mapping', 'enrol_ldap'), ''));
+            $settings->add(new admin_setting_ldap_rolemapping('enrol_ldap/role_mapping', get_string ('role_mapping_key', 'enrol_ldap'), get_string ('role_mapping', 'enrol_ldap'), ''));
         }
         $options = $yesno;
         $settings->add(new admin_setting_configselect('enrol_ldap/course_search_sub', get_string('course_search_sub_key', 'enrol_ldap'), get_string('course_search_sub', 'enrol_ldap'), 0, $options));
index 8ca6b1e..bcacf6d 100644 (file)
@@ -627,7 +627,7 @@ class grade_report_grader extends grade_report {
             $usercell->scope = 'row';
 
             if ($showuserimage) {
-                $usercell->text = $OUTPUT->container($OUTPUT->user_picture($user), 'userpic');
+                $usercell->text = $OUTPUT->user_picture($user);
             }
 
             $usercell->text .= html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $this->course->id)), fullname($user));
index b5a3f95..20c78ed 100644 (file)
@@ -269,16 +269,12 @@ table#user-grades td.topleft {
 background-color:#fff;
 }
 
-.path-grade-report-grader div.userpic {
-margin-right:10px;
-float:left;
-}
-
-.path-grade-report-grader div.userpic img {
+.path-grade-report-grader th.user img {
 border:3px double #cecece;
-vertical-align:middle;
+vertical-align:top;
 width:2.7em;
 height:2.7em;
+margin-right:10px;
 }
 
 .path-grade-report-grader a.quickedit {
index ed05e32..9a1c764 100644 (file)
@@ -32,6 +32,7 @@ $string['availablefrom_help'] = 'Access from/to dates determine when students ca
 The difference between access from/to dates and availability settings for the activity is that outside the set dates the latter allows students to view the activity description, whereas access from/to dates prevent access completely.';
 $string['availableuntil'] = 'Allow access until';
 $string['badavailabledates'] = 'Invalid dates. If you set both dates, the \'Allow access from\' date should be before the \'until\' date.';
+$string['badgradelimits'] = 'If you set both an upper and lower grade limit, the upper limit must be higher than the lower limit.';
 $string['completion_complete'] = 'must be marked complete';
 $string['completioncondition'] = 'Activity completion condition';
 $string['completioncondition_help'] = 'This setting determines any activity completion conditions which must be met in order to access the activity. Note that completion tracking must first be set before an activity completion condition can be set.
@@ -48,6 +49,9 @@ $string['gradecondition_help'] = 'This setting determines any grade conditions w
 
 Multiple grade conditions may be set if desired. If so, the activity will only allow access when ALL grade conditions are met.';
 $string['grade_upto'] = 'and less than';
+$string['gradeitembutnolimits'] = 'You must enter an upper or lower limit, or both.';
+$string['gradelimitsbutnoitem'] = 'You must choose a grade item.';
+$string['gradesmustbenumeric'] = 'The minimum and maximum grades must be numeric (or blank).';
 $string['none'] = '(none)';
 $string['notavailableyet'] = 'Not available yet';
 $string['requires_completion_0'] = 'Not available unless the activity <strong>{$a}</strong> is incomplete.';
index 74f84c8..6bc4e03 100644 (file)
@@ -186,6 +186,10 @@ $string['updateusersettings'] = 'Update';
 $string['userasclients'] = 'Users as clients with token';
 $string['userasclientsdescription'] = 'The following steps help you to set up the Moodle web service for users as clients. These steps also help to set up the recommended token (security keys) authentication method. In this use case, the user will generate his token from the security keys page via My profile settings.';
 $string['usermissingcaps'] = 'Missing capabilities: {$a}';
+$string['usernameorid'] = 'Username / User id';
+$string['usernameorid_help'] = 'Enter a username or a user id.';
+$string['usernameoridnousererror'] = 'No users were found with this username/user id.';
+$string['usernameoridoccurenceerror'] = 'More than one user was found with this username. Please enter the user id.';
 $string['usernotallowed'] = 'The user is not allowed for this service. First you need to allow this user on the {$a}\'s allowed users administration page.';
 $string['usersettingssaved'] = 'User settings saved';
 $string['validuntil'] = 'Valid until';
index d5d1c3b..508b94c 100644 (file)
@@ -1467,8 +1467,10 @@ class block_manager {
         } else {
             $newweight = ceil($newweight);
             for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
-                foreach ($usedweights[$weight] as $biid) {
-                    $this->reposition_block($biid, $newregion, $weight + 1);
+                if (array_key_exists($weight, $usedweights)) {
+                    foreach ($usedweights[$weight] as $biid) {
+                        $this->reposition_block($biid, $newregion, $weight + 1);
+                    }
                 }
             }
             $this->reposition_block($block->instance->id, $newregion, $newweight);
index de4c45b..8ad8f1a 100644 (file)
@@ -27,7 +27,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 function xmldb_main_install() {
-    global $CFG, $DB, $SITE;
+    global $CFG, $DB, $SITE, $OUTPUT;
 
     /// Make sure system context exists
     $syscontext = context_system::instance(0, MUST_EXIST, false);
@@ -178,7 +178,7 @@ function xmldb_main_install() {
     $guest->timemodified= time();
     $guest->id = $DB->insert_record('user', $guest);
     if ($guest->id != 1) {
-        throw new moodle_exception('generalexceptionmessage', 'error', '', 'Unexpected new guest user id!');
+        echo $OUTPUT->notification('Unexpected id generated for the Guest account. Your database configuration or clustering setup may not be fully supported', 'notifyproblem');
     }
     // Store guest id
     set_config('siteguest', $guest->id);
@@ -201,10 +201,15 @@ function xmldb_main_install() {
     $admin->timemodified = time();
     $admin->lastip       = CLI_SCRIPT ? '0.0.0.0' : getremoteaddr(); // installation hijacking prevention
     $admin->id = $DB->insert_record('user', $admin);
+
     if ($admin->id != 2) {
-        throw new moodle_exception('generalexceptionmessage', 'error', '', 'Unexpected new admin user id!');
+        echo $OUTPUT->notification('Unexpected id generated for the Admin account. Your database configuration or clustering setup may not be fully supported', 'notifyproblem');
+    }
+    if ($admin->id != ($guest->id + 1)) {
+        echo $OUTPUT->notification('Nonconsecutive id generated for the Admin account. Your database configuration or clustering setup may not be fully supported.', 'notifyproblem');
     }
-    // Store list of admins
+
+    /// Store list of admins
     set_config('siteadmins', $admin->id);
     // Make sure user context exists
     context_user::instance($admin->id);
index e540801..a8a8f51 100644 (file)
@@ -6951,6 +6951,34 @@ FROM
     // Moodle v2.2.0 release upgrade line
     // Put any upgrade step following this
 
+    if ($oldversion < 2011120500.02) {
+
+        upgrade_set_timeout(60*20); // This may take a while
+        // MDL-28180. Some missing restrictions in certain backup & restore operations
+        // were causing incorrect duplicates in the course_completion_aggr_methd table.
+        // This upgrade step takes rid of them.
+        $sql = 'SELECT course, criteriatype, MIN(id) AS minid
+                  FROM {course_completion_aggr_methd}
+              GROUP BY course, criteriatype
+                HAVING COUNT(*) > 1';
+        $duprs = $DB->get_recordset_sql($sql);
+        foreach ($duprs as $duprec) {
+            // We need to handle NULLs in criteriatype diferently
+            if (is_null($duprec->criteriatype)) {
+                $where = 'course = ? AND criteriatype IS NULL AND id > ?';
+                $params = array($duprec->course, $duprec->minid);
+            } else {
+                $where = 'course = ? AND criteriatype = ? AND id > ?';
+                $params = array($duprec->course, $duprec->criteriatype, $duprec->minid);
+            }
+            $DB->delete_records_select('course_completion_aggr_methd', $where, $params);
+        }
+        $duprs->close();
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011120500.02);
+    }
+
     return true;
 }
 
index ee925af..607bcfb 100644 (file)
@@ -715,7 +715,12 @@ function environment_check_moodle($version, $env_select) {
     }
 
 /// Now search the version we are using
-    $current_version = normalize_version(get_config('', 'release'));
+    $release = get_config('', 'release');
+    $current_version = normalize_version($release);
+    if (strpos($release, 'dev') !== false) {
+        // when final version is required, dev is NOT enough!
+        $current_version = $current_version - 0.1;
+    }
 
 /// And finally compare them, saving results
     if (version_compare($current_version, $needed_version, '>=')) {
@@ -724,7 +729,7 @@ function environment_check_moodle($version, $env_select) {
         $result->setStatus(false);
     }
     $result->setLevel('required');
-    $result->setCurrentVersion($current_version);
+    $result->setCurrentVersion($release);
     $result->setNeededVersion($needed_version);
 
     return $result;
index d6bba11..527c3be 100644 (file)
@@ -2760,7 +2760,11 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
 
         $access = false;
 
-        if (is_viewing($coursecontext, $USER)) {
+        if (is_role_switched($course->id)) {
+            // ok, user had to be inside this course before the switch
+            $access = true;
+
+        } else if (is_viewing($coursecontext, $USER)) {
             // ok, no need to mess with enrol
             $access = true;
 
@@ -7766,35 +7770,50 @@ function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
     return $plugins;
 }
 
+/**
+* Invoke plugin's callback functions
+*
+* @param string $type plugin type e.g. 'mod'
+* @param string $name plugin name
+* @param string $feature feature name
+* @param string $action feature's action
+* @param array $params parameters of callback function, should be an array
+* @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
+* @return mixed
+*
+* @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
+*/
+function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
+    return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
+}
 
 /**
- * invoke plugin's callback functions
+ * Invoke component's callback functions
  *
- * @param string $type Plugin type e.g. 'mod'
- * @param string $name Plugin name
- * @param string $feature Feature name
- * @param string $action Feature's action
- * @param string $options parameters of callback function, should be an array
- * @param mixed $default default value if callback function hasn't been defined
+ * @param string $component frankenstyle component name, e.g. 'mod_quiz'
+ * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
+ * @param array $params parameters of callback function
+ * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
  * @return mixed
  */
-function plugin_callback($type, $name, $feature, $action, $options = null, $default=null) {
+function component_callback($component, $function, array $params = array(), $default = null) {
     global $CFG; // this is needed for require_once() bellow
 
-    $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
-    if (empty($component)) {
-        throw new coding_exception('Invalid component used in plugin_callback():' . $type . '_' . $name);
+    $cleancomponent = clean_param($component, PARAM_COMPONENT);
+    if (empty($cleancomponent)) {
+        throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
     }
+    $component = $cleancomponent;
 
     list($type, $name) = normalize_component($component);
     $component = $type . '_' . $name;
 
-    $function = $component.'_'.$feature.'_'.$action;
-    $oldfunction = $name.'_'.$feature.'_'.$action;
+    $oldfunction = $name.'_'.$function;
+    $function = $component.'_'.$function;
 
     $dir = get_component_directory($component);
     if (empty($dir)) {
-        throw new coding_exception('Invalid component used in plugin_callback():' . $type . '_' . $name);
+        throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
     }
 
     // Load library and look for function
@@ -7811,7 +7830,7 @@ function plugin_callback($type, $name, $feature, $action, $options = null, $defa
 
     if (function_exists($function)) {
         // Function exists, so just return function result
-        $ret = call_user_func_array($function, (array)$options);
+        $ret = call_user_func_array($function, $params);
         if (is_null($ret)) {
             return $default;
         } else {
index 8645e27..121b432 100644 (file)
@@ -2014,7 +2014,7 @@ class global_navigation extends navigation_node {
                 }
             }
             if ($gradeaccess) {
-                $reporttab->add(get_string('grade'), new moodle_url('/course/user.php', array('mode'=>'grade', 'id'=>$course->id)));
+                $reporttab->add(get_string('grade'), new moodle_url('/course/user.php', array('mode'=>'grade', 'id'=>$course->id, 'user'=>$usercontext->instanceid)));
             }
         }
         // Check the number of nodes in the report node... if there are none remove the node
index c5c2046..16f1fe2 100644 (file)
@@ -270,7 +270,7 @@ class user_picture implements renderable {
      * @return moodle_url
      */
     public function get_url(moodle_page $page, renderer_base $renderer = null) {
-        global $CFG, $FULLME;
+        global $CFG;
 
         if (is_null($renderer)) {
             $renderer = $page->get_renderer('core');
@@ -329,7 +329,7 @@ class user_picture implements renderable {
             // Build a gravatar URL with what we know.
             // If the currently requested page is https then we'll return an
             // https gravatar page.
-            if (strpos($FULLME, 'https://') === 0) {
+            if (strpos($CFG->httpswwwroot, 'https:') === 0) {
                 $imageurl = new moodle_url("https://secure.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $imageurl->out(false)));
             } else {
                 $imageurl = new moodle_url("http://www.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $imageurl->out(false)));
index 93c5cbe..f216297 100644 (file)
@@ -127,6 +127,7 @@ class page_requirements_manager {
         if (debugging('', DEBUG_DEVELOPER)) {
             $this->yui3loader->filter = YUI_RAW; // for more detailed logging info use YUI_DEBUG here
             $this->yui2loader->filter = YUI_RAW; // for more detailed logging info use YUI_DEBUG here
+            $this->yui2loader->allowRollups = false;
         } else {
             $this->yui3loader->filter = null;
             $this->yui2loader->filter = null;
@@ -160,7 +161,7 @@ class page_requirements_manager {
         $this->M_yui_loader->base         = $this->yui3loader->base;
         $this->M_yui_loader->comboBase    = $this->yui3loader->comboBase;
         $this->M_yui_loader->combine      = $this->yui3loader->combine;
-        $this->M_yui_loader->filter       = ($this->yui3loader->filter == YUI_DEBUG) ? 'debug' : '';
+        $this->M_yui_loader->filter       = (string)$this->yui3loader->filter;
         $this->M_yui_loader->insertBefore = 'firstthemesheet';
         $this->M_yui_loader->modules      = array();
         $this->M_yui_loader->groups       = array(
@@ -267,7 +268,7 @@ class page_requirements_manager {
      * Initialise with the bits of JavaScript that every Moodle page should have.
      *
      * @param moodle_page $page
-     * @param core_renderer $output
+     * @param core_renderer $renderer
      */
     protected function init_requirements_data(moodle_page $page, core_renderer $renderer) {
         global $CFG;
@@ -547,7 +548,7 @@ class page_requirements_manager {
     /**
      * Returns true if the module has already been loaded.
      *
-     * @param string|array $modulename
+     * @param string|array $module
      * @return bool True if the module has already been loaded
      */
     protected function js_module_loaded($module) {
@@ -818,7 +819,7 @@ class page_requirements_manager {
      * (e.g. and array) that you pass to JavaScript with {@link data_for_js()}.
      *
      * @param string $identifier the desired string.
-     * @param string $module the language file to look in.
+     * @param string $component the language file to look in.
      * @param mixed $a any extra data to add into the string (optional).
      */
     public function string_for_js($identifier, $component, $a = NULL) {
@@ -918,7 +919,8 @@ class page_requirements_manager {
 
     /**
      * Get the inline JavaScript code that need to appear in a particular place.
-     * @return bool $ondomready
+     * @param bool $ondomready
+     * @return string
      */
     protected function get_javascript_code($ondomready) {
         $where = $ondomready ? 'ondomready' : 'normal';
@@ -972,10 +974,14 @@ class page_requirements_manager {
             $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssbase/base-min.css" />';
         }
 
-        if (debugging('', DEBUG_DEVELOPER)) {
-            $code .= '<script type="text/javascript" src="'.$this->yui3loader->base.'yui/yui-debug.js"></script>';
-        } else {
-            $code .= '<script type="text/javascript" src="'.$this->yui3loader->base.'yui/yui-min.js"></script>';
+        $code .= '<script type="text/javascript" src="'.$this->yui3loader->base.'yui/yui-min.js"></script>';
+
+        if ($this->yui3loader->filter === YUI_RAW) {
+            $code = str_replace('-min.css', '.css', $code);
+            $code = str_replace('-min.js', '.js', $code);
+        } else if ($this->yui3loader->filter === YUI_DEBUG) {
+            $code = str_replace('-min.css', '.css', $code);
+            $code = str_replace('-min.js', '-debug.js', $code);
         }
 
         return $code;
@@ -1045,6 +1051,7 @@ class page_requirements_manager {
 
     /**
      * Adds extra modules specified after printing of page header
+     * @return string
      */
     protected function get_extra_modules_code() {
         if (empty($this->extramodules)) {
@@ -1059,6 +1066,8 @@ class page_requirements_manager {
      * Normally, this method is called automatically by the code that prints the
      * <head> tag. You should not normally need to call it in your own code.
      *
+     * @param moodle_page $page
+     * @param core_renderer $renderer
      * @return string the HTML code to to inside the <head> tag.
      */
     public function get_head_code(moodle_page $page, core_renderer $renderer) {
index f01e74c..aafe23a 100644 (file)
@@ -1185,6 +1185,8 @@ class moodle_page {
         }
 
         // now the real test and redirect!
+        // NOTE: do NOT use this test for detection of https on current page because this code is not compatible with SSL proxies,
+        //       instead use strpos($CFG->httpswwwroot, 'https:') === 0
         if (strpos($FULLME, 'https:') !== 0) {
             // this may lead to infinite redirect on misconfigured sites, in that case use $CFG->loginhttps=0; in /config.php
             redirect($this->_url);
index 094f6be..430cbc9 100644 (file)
@@ -1080,9 +1080,9 @@ class PHPMailer {
 
     $result .= $this->HeaderLine('Date', self::RFCDate());
     if($this->Sender == '') {
-      $result .= $this->HeaderLine('Return-Path', trim($this->From));
+      $result .= $this->HeaderLine('Return-Path', trim($this->SecureHeader($this->From))); // Moodle modification
     } else {
-      $result .= $this->HeaderLine('Return-Path', trim($this->Sender));
+      $result .= $this->HeaderLine('Return-Path', trim($this->SecureHeader($this->Sender))); // Moodle modification
     }
 
     // To be created automatically by mail()
index 6e605db..06be371 100644 (file)
@@ -484,7 +484,7 @@ function question_delete_course_category($category, $newcategory, $feedback=true
 /**
  * Enter description here...
  *
- * @param string $questionids list of questionids
+ * @param array $questionids of question ids
  * @param object $newcontext the context to create the saved category in.
  * @param string $oldplace a textual description of the think being deleted,
  *      e.g. from get_context_name
@@ -571,7 +571,7 @@ function question_delete_activity($cm, $feedback=true) {
  * function also have to do other work, which is why you should not call this method
  * directly from outside the questionbank.
  *
- * @param string $questionids a comma-separated list of question ids.
+ * @param array $questionids of question ids.
  * @param integer $newcategoryid the id of the category to move to.
  */
 function question_move_questions_to_category($questionids, $newcategoryid) {
@@ -1636,10 +1636,7 @@ class question_edit_contexts {
 }
 
 /**
- * Rewrite question url, file_rewrite_pluginfile_urls always build url by
- * $file/$contextid/$component/$filearea/$itemid/$pathname_in_text, so we cannot add
- * extra questionid and attempted in url by it, so we create quiz_rewrite_question_urls
- * to build url here
+ * Helps call file_rewrite_pluginfile_urls with the right parameters.
  *
  * @param string $text text being processed
  * @param string $file the php script used to serve files
@@ -1653,32 +1650,59 @@ class question_edit_contexts {
  */
 function question_rewrite_question_urls($text, $file, $contextid, $component,
         $filearea, array $ids, $itemid, array $options=null) {
-    global $CFG;
 
-    $options = (array)$options;
-    if (!isset($options['forcehttps'])) {
-        $options['forcehttps'] = false;
+    $idsstr = '';
+    if (!empty($ids)) {
+        $idsstr .= implode('/', $ids);
     }
-
-    if (!$CFG->slasharguments) {
-        $file = $file . '?file=';
+    if ($itemid !== null) {
+        $idsstr .= '/' . $itemid;
     }
+    return file_rewrite_pluginfile_urls($text, $file, $contextid, $component,
+            $filearea, $idsstr, $options);
+}
+
+/**
+ * Rewrite the PLUGINFILE urls in the questiontext, when viewing the question
+ * text outside and attempt (for example, in the question bank listing or in the
+ * quiz statistics report).
+ *
+ * @param string $questiontext the question text.
+ * @param int $contextid the context the text is being displayed in.
+ * @param string $component component
+ * @param array $ids other IDs will be used to check file permission
+ * @param array $options
+ * @return string $questiontext with URLs rewritten.
+ */
+function question_rewrite_questiontext_preview_urls($questiontext, $contextid,
+        $component, $questionid, $options=null) {
 
-    $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
+    return file_rewrite_pluginfile_urls($questiontext, 'pluginfile.php', $contextid,
+            'question', 'questiontext_preview', "$component/$questionid", $options);
+}
 
-    if (!empty($ids)) {
-        $baseurl .= (implode('/', $ids) . '/');
-    }
+/**
+ * Send a file from the question text of a question.
+ * @param int $questionid the question id
+ * @param array $args the remaining file arguments (file path).
+ * @param bool $forcedownload whether the user must be forced to download the file.
+ */
+function question_send_questiontext_file($questionid, $args, $forcedownload) {
+    global $DB;
 
-    if ($itemid !== null) {
-        $baseurl .= "$itemid/";
-    }
+    $question = $DB->get_record_sql('
+            SELECT q.id, qc.contextid
+              FROM {question} q
+              JOIN {question_categories} qc ON qc.id = q.category
+             WHERE q.id = :id', array('id' => $questionid), MUST_EXIST);
 
-    if ($options['forcehttps']) {
-        $baseurl = str_replace('http://', 'https://', $baseurl);
+    $fs = get_file_storage();
+    $fullpath = "/$question->contextid/question/questiontext/$question->id/" . implode('/', $args);
+    if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
+        send_file_not_found();
     }
 
-    return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
+    send_stored_file($file, 0, 0, $forcedownload);
 }
 
 /**
@@ -1704,6 +1728,16 @@ function question_rewrite_question_urls($text, $file, $contextid, $component,
 function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload) {
     global $DB, $CFG;
 
+    if ($filearea === 'questiontext_preview') {
+        $component = array_shift($args);
+        $questionid = array_shift($args);
+
+        component_callback($component, 'questiontext_preview_pluginfile', array(
+                $context, $questionid, $args, $forcedownload));
+
+        send_file_not_found();
+    }
+
     list($context, $course, $cm) = get_context_info_array($context->id);
     require_login($course, false, $cm);
 
@@ -1793,6 +1827,33 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f
     }
 }
 
+/**
+ * Serve questiontext files in the question text when they are displayed in this report.
+ * @param context $context the context
+ * @param int $questionid the question id
+ * @param array $args remaining file args
+ * @param bool $forcedownload
+ */
+function core_question_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) {
+    global $DB;
+
+    // Verify that contextid matches the question.
+    $question = $DB->get_record_sql('
+            SELECT q.*, qc.contextid
+              FROM {question} q
+              JOIN {question_categories} qc ON qc.id = q.category
+             WHERE q.id = :id AND qc.contextid = :contextid',
+            array('id' => $questionid, 'contextid' => $context->id), MUST_EXIST);
+
+    // Check the capability.
+    list($context, $course, $cm) = get_context_info_array($context->id);
+    require_login($course, false, $cm);
+
+    question_require_capability_on($question, 'use');
+
+    question_send_questiontext_file($questionid, $args, $forcedownload);
+}
+
 /**
  * Create url for question export
  *
index 2b51b1b..2f0f1e4 100644 (file)
@@ -341,6 +341,10 @@ global $MCACHE;
 
 /**
  * Full script path including all params, slash arguments, scheme and host.
+ *
+ * Note: Do NOT use for getting of current page URL or detection of https,
+ * instead use $PAGE->url or strpos($CFG->httpswwwroot, 'https:') === 0
+ *
  * @global string $FULLME
  * @name $FULLME
  */
index c206d10..cc1cd12 100644 (file)
@@ -1193,7 +1193,12 @@ function make_upload_directory($directory, $exceptiononerror = true) {
  */
 function make_temp_directory($directory, $exceptiononerror = true) {
     global $CFG;
-    protect_directory($CFG->tempdir);
+    if ($CFG->tempdir !== "$CFG->dataroot/temp") {
+        check_dir_exists($CFG->tempdir, true, true);
+        protect_directory($CFG->tempdir);
+    } else {
+        protect_directory($CFG->dataroot);
+    }
     return make_writable_directory("$CFG->tempdir/$directory", $exceptiononerror);
 }
 
@@ -1206,7 +1211,12 @@ function make_temp_directory($directory, $exceptiononerror = true) {
  */
 function make_cache_directory($directory, $exceptiononerror = true) {
     global $CFG;
-    protect_directory($CFG->cachedir);
+    if ($CFG->cachedir !== "$CFG->dataroot/cache") {
+        check_dir_exists($CFG->cachedir, true, true);
+        protect_directory($CFG->cachedir);
+    } else {
+        protect_directory($CFG->dataroot);
+    }
     return make_writable_directory("$CFG->cachedir/$directory", $exceptiononerror);
 }
 
index 475644b..2f37a14 100644 (file)
@@ -17,7 +17,7 @@ class mod_glossary_import_form extends moodleform {
         $options = array();
         $options['current'] = get_string('currentglossary', 'glossary');
         $options['newglossary'] = get_string('newglossary', 'glossary');
-        $mform->addElement('select', 'dest', get_string('currentglossary', 'glossary'), $options);
+        $mform->addElement('select', 'dest', get_string('destination', 'glossary'), $options);
         $mform->addHelpButton('dest', 'destination', 'glossary');
         $mform->addElement('checkbox', 'catsincl', get_string('importcategories', 'glossary'));
         $submit_string = get_string('submit');
index 6936d4d..6eb6ebb 100644 (file)
@@ -153,9 +153,17 @@ function imscp_parse_manifestfile($manifestfilecontents) {
             $xmlbase = '';
         }
         if (!$href = $res->attributes->getNamedItem('href')) {
-            continue;
+            // If href not found look for <file href="help.htm"/>
+            $fileresources = $res->getElementsByTagName('file');
+            foreach ($fileresources as $file) {
+                $href = $file->getAttribute('href');
+            }
+            if (empty($href)) {
+                continue;
+            }
+        } else {
+            $href = $href->nodeValue;
         }
-        $href = $href->nodeValue;
         if (strpos($href, 'http://') !== 0) {
             $href = $xmlbase.$href;
         }
index 696b8b9..ff32232 100644 (file)
@@ -166,6 +166,7 @@ class mod_lesson_renderer extends plugin_renderer_base {
 
         $yeslink = html_writer::link(new moodle_url('/mod/lesson/view.php', array('id'=>$this->page->cm->id, 'pageid'=>$lastpageseenid, 'startlastseen'=>'yes')), get_string('yes'));
         $output .= html_writer::tag('span', $yeslink, array('class'=>'lessonbutton standardbutton'));
+        $output .= '&nbsp;';
 
         $nolink = html_writer::link(new moodle_url('/mod/lesson/view.php', array('id'=>$this->page->cm->id, 'pageid'=>$lesson->firstpageid, 'startlastseen'=>'no')), get_string('no'));
         $output .= html_writer::tag('span', $nolink, array('class'=>'lessonbutton standardbutton'));
index 53425a3..74f0276 100644 (file)
@@ -1127,8 +1127,8 @@ function lti_get_launch_container($lti, $toolconfig) {
 }
 
 function lti_request_is_using_ssl() {
-    global $FULLME;
-    return (stripos($FULLME, 'https://') === 0);
+    global $CFG;
+    return (stripos($CFG->httpswwwroot, 'https://') === 0);
 }
 
 function lti_ensure_url_is_https($url) {
index 1a45b15..9e2bf23 100644 (file)
@@ -1447,7 +1447,7 @@ function quiz_num_attempt_summary($quiz, $cm, $returnzero = false, $currentgroup
         if (groups_get_activity_groupmode($cm)) {
             $a->total = $numattempts;
             if ($currentgroup) {
-                $a->group = $DB->count_records_sql('SELECT count(1) FROM ' .
+                $a->group = $DB->count_records_sql('SELECT COUNT(DISTINCT qa.id) FROM ' .
                         '{quiz_attempts} qa JOIN ' .
                         '{groups_members} gm ON qa.userid = gm.userid ' .
                         'WHERE quiz = ? AND preview = 0 AND groupid = ?',
@@ -1455,7 +1455,7 @@ function quiz_num_attempt_summary($quiz, $cm, $returnzero = false, $currentgroup
                 return get_string('attemptsnumthisgroup', 'quiz', $a);
             } else if ($groups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid)) {
                 list($usql, $params) = $DB->get_in_or_equal(array_keys($groups));
-                $a->group = $DB->count_records_sql('SELECT count(1) FROM ' .
+                $a->group = $DB->count_records_sql('SELECT COUNT(DISTINCT qa.id) FROM ' .
                         '{quiz_attempts} qa JOIN ' .
                         '{groups_members} gm ON qa.userid = gm.userid ' .
                         'WHERE quiz = ? AND preview = 0 AND ' .
index edc4ea4..e075eff 100644 (file)
@@ -72,6 +72,7 @@ abstract class quiz_attempt_report extends quiz_default_report {
     /**
      * Get information about which students to show in the report.
      * @param object $cm the coures module.
+     * @param object $course the course settings.
      * @return an array with four elements:
      *      0 => integer the current group id (0 for none).
      *      1 => array ids of all the students in this course.
@@ -79,8 +80,12 @@ abstract class quiz_attempt_report extends quiz_default_report {
      *      3 => array ids of all the students to show in the report. Will be the
      *              same as either element 1 or 2.
      */
-    protected function load_relevant_students($cm) {
-        $currentgroup = groups_get_activity_group($cm, true);
+    protected function load_relevant_students($cm, $course = null) {
+        $currentgroup = $this->get_current_group($cm, $course, $this->context);
+
+        if ($currentgroup == self::NO_GROUPS_ALLOWED) {
+            return array($currentgroup, array(), array(), array());
+        }
 
         if (!$students = get_users_by_capability($this->context,
                 array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
index ad80e2e..1cbd44a 100644 (file)
@@ -43,6 +43,8 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 abstract class quiz_default_report {
+    const NO_GROUPS_ALLOWED = -2;
+
     /**
      * Override this function to displays the report.
      * @param $cm the course-module for this quiz.
@@ -51,6 +53,14 @@ abstract class quiz_default_report {
      */
     public abstract function display($cm, $course, $quiz);
 
+    /**
+     * Initialise some parts of $PAGE and start output.
+     *
+     * @param object $cm the course_module information.
+     * @param object $coures the course settings.
+     * @param object $quiz the quiz settings.
+     * @param string $reportmode the report name.
+     */
     public function print_header_and_tabs($cm, $course, $quiz, $reportmode = 'overview') {
         global $PAGE, $OUTPUT;
 
@@ -59,4 +69,24 @@ abstract class quiz_default_report {
         $PAGE->set_heading($course->fullname);
         echo $OUTPUT->header();
     }
+
+    /**
+     * Get the current group for the user user looking at the report.
+     *
+     * @param object $cm the course_module information.
+     * @param object $coures the course settings.
+     * @param context $context the quiz context.
+     * @return int the current group id, if applicable. 0 for all users,
+     *      NO_GROUPS_ALLOWED if the user cannot see any group.
+     */
+    public function get_current_group($cm, $course, $context) {
+        $groupmode = groups_get_activity_groupmode($cm, $course);
+        $currentgroup = groups_get_activity_group($cm, true);
+
+        if ($groupmode == SEPARATEGROUPS && !$currentgroup && !has_capability('moodle/site:accessallgroups', $context)) {
+            $currentgroup = self::NO_GROUPS_ALLOWED;
+        }
+
+        return $currentgroup;
+    }
 }
index 05affcd..0ee9c33 100644 (file)
@@ -117,10 +117,14 @@ class quiz_grading_report extends quiz_default_report {
         }
 
         // Get the group, and the list of significant users.
-        $this->currentgroup = groups_get_activity_group($this->cm, true);
-        $this->users = get_users_by_capability($this->context,
-                array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '',
-                $this->currentgroup, '', false);
+        $this->currentgroup = $this->get_current_group($cm, $course, $this->context);
+        if ($this->currentgroup == self::NO_GROUPS_ALLOWED) {
+            $this->users = array();
+        } else {
+            $this->users = get_users_by_capability($this->context,
+                    array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '',
+                    $this->currentgroup, '', false);
+        }
 
         // Start output.
         $this->print_header_and_tabs($cm, $course, $quiz, 'grading');
index e863e45..a8cf7bc 100644 (file)
@@ -47,7 +47,7 @@ class quiz_overview_report extends quiz_attempt_report {
         $download = optional_param('download', '', PARAM_ALPHA);
 
         list($currentgroup, $students, $groupstudents, $allowed) =
-                $this->load_relevant_students($cm);
+                $this->load_relevant_students($cm, $course);
 
         $pageoptions = array();
         $pageoptions['id'] = $cm->id;
index 9f9857e..8da9e64 100644 (file)
@@ -55,7 +55,7 @@ class quiz_responses_report extends quiz_attempt_report {
         $download = optional_param('download', '', PARAM_ALPHA);
 
         list($currentgroup, $students, $groupstudents, $allowed) =
-                $this->load_relevant_students($cm);
+                $this->load_relevant_students($cm, $course);
 
         $pageoptions = array();
         $pageoptions['id'] = $cm->id;
diff --git a/mod/quiz/report/statistics/lib.php b/mod/quiz/report/statistics/lib.php
new file mode 100644 (file)
index 0000000..06d7fd9
--- /dev/null
@@ -0,0 +1,46 @@
+<?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/>.
+
+/**
+ * Standard plugin entry points of the quiz statistics report.
+ *
+ * @package    quiz
+ * @subpackage statistics
+ * @copyright  2011 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Serve questiontext files in the question text when they are displayed in this report.
+ * @param context $context the context
+ * @param int $questionid the question id
+ * @param array $args remaining file args
+ * @param bool $forcedownload
+ */
+function quiz_statistics_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) {
+    global $CFG;
+    require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+
+    list($context, $course, $cm) = get_context_info_array($context->id);
+    require_login($course, false, $cm);
+
+    // Assume only trusted people can see this report. There is no real way to
+    // validate questionid, becuase of the complexity of random quetsions.
+    require_capability('quiz/statistics:view', $context);
+
+    question_send_questiontext_file($questionid, $args, $forcedownload);
+}
index f29d220..c2e431b 100644 (file)
@@ -54,7 +54,7 @@ class quiz_statistics_report extends quiz_default_report {
     public function display($quiz, $cm, $course) {
         global $CFG, $DB, $OUTPUT, $PAGE;
 
-        $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+        $this->context = get_context_instance(CONTEXT_MODULE, $cm->id);
 
         // Work out the display options.
         $download = optional_param('download', '', PARAM_ALPHA);
@@ -85,16 +85,19 @@ class quiz_statistics_report extends quiz_default_report {
         }
 
         // Find out current groups mode
-        $groupmode = groups_get_activity_groupmode($cm);
-        $currentgroup = groups_get_activity_group($cm, true);
+        $currentgroup = $this->get_current_group($cm, $course, $this->context);
         $nostudentsingroup = false; // True if a group is selected and there is no one in it.
         if (empty($currentgroup)) {
             $currentgroup = 0;
             $groupstudents = array();
 
+        } else if ($currentgroup == self::NO_GROUPS_ALLOWED) {
+            $groupstudents = array();
+            $nostudentsingroup = true;
+
         } else {
             // All users who can attempt quizzes and who are in the currently selected group
-            $groupstudents = get_users_by_capability($context,
+            $groupstudents = get_users_by_capability($this->context,
                     array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
                     '', '', '', '', $currentgroup, '', false);
             if (!$groupstudents) {
@@ -152,7 +155,7 @@ class quiz_statistics_report extends quiz_default_report {
         if (!$this->table->is_downloading()) {
             $this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
 
-            if ($groupmode) {
+            if (groups_get_activity_groupmode($cm)) {
                 groups_print_activity_menu($cm, $reporturl->out());
                 if ($currentgroup && !$groupstudents) {
                     $OUTPUT->notification(get_string('nostudentsingroup', 'quiz_statistics'));
@@ -160,7 +163,7 @@ class quiz_statistics_report extends quiz_default_report {
             }
 
             if (!quiz_questions_in_quiz($quiz->questions)) {
-                echo quiz_no_questions_message($quiz, $cm, $context);
+                echo quiz_no_questions_message($quiz, $cm, $this->context);
             } else if (!$this->table->is_downloading() && $s == 0) {
                 echo $OUTPUT->notification(get_string('noattempts', 'quiz'));
             }
@@ -317,15 +320,33 @@ class quiz_statistics_report extends quiz_default_report {
         echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'));
         echo html_writer::table($questionstatstable);
     }
+    public function format_text($text, $format, $qa, $component, $filearea, $itemid,
+            $clean = false) {
+        $formatoptions = new stdClass();
+        $formatoptions->noclean = !$clean;
+        $formatoptions->para = false;
+        $text = $qa->rewrite_pluginfile_urls($text, $component, $filearea, $itemid);
+        return format_text($text, $format, $formatoptions);
+    }
+
+    /** @return the result of applying {@link format_text()} to the question text. */
+    public function format_questiontext($qa) {
+        return $this->format_text($this->questiontext, $this->questiontextformat,
+        $qa, 'question', 'questiontext', $this->id);
+    }
 
     /**
      * @param object $question question data.
      * @return string HTML of question text, ready for display.
      */
-    protected function render_question_text($question){
+    protected function render_question_text($question) {
         global $OUTPUT;
-        return $OUTPUT->box(format_text($question->questiontext, $question->questiontextformat,
-                array('overflowdiv' => true)),
+
+        $text = question_rewrite_questiontext_preview_urls($question->questiontext,
+                $this->context->id, 'quiz_statistics', $question->id);
+
+        return $OUTPUT->box(format_text($text, $question->questiontextformat,
+                array('noclean' => true, 'para' => false, 'overflowdiv' => true)),
                 'questiontext boxaligncenter generalbox boxwidthnormal mdl-align');
     }
 
index 88875ce..1acc5ef 100644 (file)
@@ -75,7 +75,7 @@ if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) {
 $userdata->student_id = addslashes_js($USER->username);
 $userdata->student_name = addslashes_js($USER->lastname .', '. $USER->firstname);
 $userdata->mode = 'normal';
-if (isset($mode)) {
+if (!empty($mode)) {
     $userdata->mode = $mode;
 }
 if ($userdata->mode == 'normal') {
index d6e0b03..8349cb1 100644 (file)
@@ -200,10 +200,10 @@ function SCORMapi1_3() {
         'cmi.learner_id':{'defaultvalue':'<?php echo $userdata->student_id ?>', 'mod':'r'},
         'cmi.learner_name':{'defaultvalue':'<?php echo $userdata->student_name ?>', 'mod':'r'},
         'cmi.learner_preference._children':{'defaultvalue':student_preference_children, 'mod':'r'},
-        'cmi.learner_preference.audio_level':{'defaultvalue':'1', 'format':CMIDecimal, 'range':audio_range, 'mod':'rw'},
-        'cmi.learner_preference.language':{'defaultvalue':'', 'format':CMILang, 'mod':'rw'},
-        'cmi.learner_preference.delivery_speed':{'defaultvalue':'1', 'format':CMIDecimal, 'range':speed_range, 'mod':'rw'},
-        'cmi.learner_preference.audio_captioning':{'defaultvalue':'0', 'format':CMISInteger, 'range':text_range, 'mod':'rw'},
+        'cmi.learner_preference.audio_level':{'defaultvalue':<?php echo !empty($userdata->{'cmi.learner_preference.audio_level'})?'\''.$userdata->{'cmi.learner_preference.audio_level'}.'\'':'\'1\'' ?>, 'format':CMIDecimal, 'range':audio_range, 'mod':'rw'},
+        'cmi.learner_preference.language':{'defaultvalue':<?php echo !empty($userdata->{'cmi.learner_preference.language'})?'\''.$userdata->{'cmi.learner_preference.language'}.'\'':'\'\'' ?>, 'format':CMILang, 'mod':'rw'},
+        'cmi.learner_preference.delivery_speed':{'defaultvalue':<?php echo !empty($userdata->{'cmi.learner_preference.delivery_speed'})?'\''.$userdata->{'cmi.learner_preference.delivery_speed'}.'\'':'\'1\'' ?>, 'format':CMIDecimal, 'range':speed_range, 'mod':'rw'},
+        'cmi.learner_preference.audio_captioning':{'defaultvalue':<?php echo !empty($userdata->{'cmi.learner_preference.audio_captioning'})?'\''.$userdata->{'cmi.learner_preference.audio_captioning'}.'\'':'\'0\'' ?>, 'format':CMISInteger, 'range':text_range, 'mod':'rw'},
         'cmi.location':{'defaultvalue':<?php echo !empty($userdata->{'cmi.location'})?'\''.$userdata->{'cmi.location'}.'\'':'null' ?>, 'format':CMIString1000, 'mod':'rw'},
         'cmi.max_time_allowed':{'defaultvalue':<?php echo !empty($userdata->attemptAbsoluteDurationLimit)?'\''.$userdata->attemptAbsoluteDurationLimit.'\'':'null' ?>, 'mod':'r'},
         'cmi.mode':{'defaultvalue':'<?php echo $userdata->mode ?>', 'mod':'r'},
@@ -219,7 +219,7 @@ function SCORMapi1_3() {
         'cmi.objectives.n.completion_status':{'defaultvalue':'unknown', 'pattern':CMIIndex, 'format':CMICStatus, 'mod':'rw'},
         'cmi.objectives.n.progress_measure':{'defaultvalue':null, 'format':CMIDecimal, 'range':progress_range, 'mod':'rw'},
         'cmi.objectives.n.description':{'pattern':CMIIndex, 'format':CMILangString250, 'mod':'rw'},
-        'cmi.progress_measure':{'defaultvalue':<?php echo !empty($userdata->{'cmi.progess_measure'})?'\''.$userdata->{'cmi.progress_measure'}.'\'':'null' ?>, 'format':CMIDecimal, 'range':progress_range, 'mod':'rw'},
+        'cmi.progress_measure':{'defaultvalue':<?php echo !empty($userdata->{'cmi.progress_measure'})?'\''.$userdata->{'cmi.progress_measure'}.'\'':'null' ?>, 'format':CMIDecimal, 'range':progress_range, 'mod':'rw'},
         'cmi.scaled_passing_score':{'defaultvalue':<?php echo !empty($userdata->{'cmi.scaled_passing_score'})?'\''.$userdata->{'cmi.scaled_passing_score'}.'\'':'null' ?>, 'format':CMIDecimal, 'range':scaled_range, 'mod':'r'},
         'cmi.score._children':{'defaultvalue':score_children, 'mod':'r'},
         'cmi.score.scaled':{'defaultvalue':<?php echo !empty($userdata->{'cmi.score.scaled'})?'\''.$userdata->{'cmi.score.scaled'}.'\'':'null' ?>, 'format':CMIDecimal, 'range':scaled_range, 'mod':'rw'},
index cc77fa7..d3a1ab9 100644 (file)
@@ -208,10 +208,10 @@ function scorm_get_manifest($blocks, $scoes) {
                 case 'ADLCP:COMPLETIONTHRESHOLD':
                     $parent = array_pop($parents);
                     array_push($parents, $parent);
-                    if (!isset($block['tagData'])) {
-                        $block['tagData'] = '';
+                    if (!isset($block['attrs']['MINPROGRESSMEASURE'])) {
+                        $block['attrs']['MINPROGRESSMEASURE'] = '1.0';
                     }
-                    $scoes->elements[$manifest][$parent->organization][$parent->identifier]->threshold = $block['tagData'];
+                    $scoes->elements[$manifest][$parent->organization][$parent->identifier]->threshold = $block['attrs']['MINPROGRESSMEASURE'];
                 break;
                 case 'ADLNAV:PRESENTATION':
                     $parent = array_pop($parents);
index f1371d3..54fdfc4 100644 (file)
@@ -84,8 +84,6 @@ if ($userid) {
     if (has_capability('moodle/course:viewparticipants', $coursecontext) || has_capability('moodle/site:viewparticipants', $systemcontext)) {
         $link = new moodle_url('/user/index.php',array('id'=>$course->id));
     }
-    $PAGE->navbar->add(get_string('participants'), $link);
-    $PAGE->navbar->add($strnotes);
 }
 
 $PAGE->set_pagelayout('course');
index ca97f54..aa64f5b 100644 (file)
@@ -24,7 +24,8 @@
  */
 
 $string['gradingdetails'] = 'Marks for this submission: {$a->raw}/{$a->max}.';
-$string['gradingdetailsadjustment'] = 'With previous penalties this gives <strong>{$a->cur}/{$a->max}</strong>.';
+$string['gradingdetailsadjustment'] = 'Accounting for previous tries, this gives <strong>{$a->cur}/{$a->max}</strong>.';
 $string['gradingdetailspenalty'] = 'This submission attracted a penalty of {$a}.';
+$string['gradingdetailspenaltytotal'] = 'Total penalties so far: {$a}.';
 $string['notcomplete'] = 'Not complete';
 $string['pluginname'] = 'Adaptive mode';
index f5a984e..2bc4fdb 100644 (file)
@@ -88,7 +88,11 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
      */
     protected function penalty_info(question_attempt $qa, $mark,
             question_display_options $options) {
-        if (!$qa->get_question()->penalty) {
+
+        $currentpenalty = $qa->get_question()->penalty * $qa->get_max_mark();
+        $totalpenalty = $currentpenalty * $qa->get_last_behaviour_var('_try', 0);
+
+        if ($currentpenalty == 0) {
             return '';
         }
         $output = '';
@@ -101,7 +105,13 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
         // Print information about any new penalty, only relevant if the answer can be improved.
         if ($qa->get_behaviour()->is_state_improvable($qa->get_state())) {
             $output .= ' ' . get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
-                    format_float($qa->get_question()->penalty, $options->markdp));
+                    format_float($currentpenalty, $options->markdp));
+
+            // Print information about total penalties so far, if larger than current penalty.
+            if ($totalpenalty > $currentpenalty) {
+                $output .= ' ' . get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive',
+                        format_float($totalpenalty, $options->markdp));
+            }
         }
 
         return $output;
index 6a6e27c..99727d4 100644 (file)
@@ -51,6 +51,18 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         return new NoPatternExpectation($penaltypattern);
     }
 
+    protected function get_contains_total_penalty_expectation($penalty) {
+        $penaltyinfo = get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive',
+                                  format_float($penalty, $this->displayoptions->markdp));
+        return new PatternExpectation('/'.preg_quote($penaltyinfo).'/');
+    }
+
+    protected function get_does_not_contain_total_penalty_expectation() {
+        $penaltyinfo = get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive', 'XXXXX');
+        $penaltypattern = '/'.str_replace('XXXXX', '\\w*', preg_quote($penaltyinfo)).'/';
+        return new NoPatternExpectation($penaltypattern);
+    }
+
     public function test_adaptive_multichoice() {
 
         // Create a multiple choice, single response question.
@@ -85,7 +97,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false),
                 $this->get_contains_mc_radio_expectation(($wrongindex + 2) % 3, true, false),
                 $this->get_contains_incorrect_expectation(),
-                $this->get_contains_penalty_info_expectation(0.33));
+                $this->get_contains_penalty_info_expectation(1.00),
+                $this->get_does_not_contain_total_penalty_expectation());
         $this->assertPattern('/B|C/',
                 $this->quba->get_response_summary($this->slot));
 
@@ -115,7 +128,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),
                 $this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, true, false),
                 $this->get_contains_correct_expectation(),
-                $this->get_does_not_contain_penalty_info_expectation());
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation());
         $this->assertEqual('A',
                 $this->quba->get_response_summary($this->slot));
 
@@ -186,7 +200,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mark_summary(2),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_correct_expectation(),
-                $this->get_does_not_contain_penalty_info_expectation());
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation());
 
         // Save the same correct answer again. Should not do anything.
         $numsteps = $this->get_step_count();
@@ -235,6 +250,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_partcorrect_expectation(),
                 $this->get_contains_penalty_info_expectation(0.33),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit an incorrect answer.
@@ -248,6 +264,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_contains_penalty_info_expectation(0.33),
+                $this->get_contains_total_penalty_expectation(0.67),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit a correct answer.
@@ -261,6 +278,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Finish the attempt.
@@ -280,7 +298,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
 
         // Create a short answer question
         $sa = test_question_maker::make_a_shortanswer_question();
-        $this->start_attempt_at_question($sa, 'adaptive');
+        $this->start_attempt_at_question($sa, 'adaptive', 6);
 
         // Check the initial state.
         $this->check_current_state(question_state::$todo);
@@ -300,7 +318,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mark_summary(0),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_incorrect_expectation(),
-                $this->get_contains_penalty_info_expectation(0.33),
+                $this->get_contains_penalty_info_expectation(2.00),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit the same wrong answer again. Nothing should change.
@@ -313,7 +332,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mark_summary(0),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_incorrect_expectation(),
-                $this->get_contains_penalty_info_expectation(0.33),
+                $this->get_contains_penalty_info_expectation(2.00),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit a correct answer.
@@ -321,12 +341,13 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
 
         // Verify.
         $this->check_current_state(question_state::$complete);
-        $this->check_current_mark(0.66666667);
+        $this->check_current_mark(4.00);
         $this->check_current_output(
-                $this->get_contains_mark_summary(0.67),
+                $this->get_contains_mark_summary(4.00),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit another incorrect answer.
@@ -334,12 +355,13 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
 
         // Verify.
         $this->check_current_state(question_state::$complete);
-        $this->check_current_mark(0.66666667);
+        $this->check_current_mark(4.00);
         $this->check_current_output(
-                $this->get_contains_mark_summary(0.67),
+                $this->get_contains_mark_summary(4.00),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Finish the attempt.
@@ -347,9 +369,9 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
 
         // Verify.
         $this->check_current_state(question_state::$gradedwrong);
-        $this->check_current_mark(0.66666667);
+        $this->check_current_mark(4.00);
         $this->check_current_output(
-                $this->get_contains_mark_summary(0.67),
+                $this->get_contains_mark_summary(4.00),
                 $this->get_contains_submit_button_expectation(false),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
@@ -380,6 +402,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_contains_penalty_info_expectation(0.33),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit a correct answer.
@@ -393,6 +416,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit an empty answer.
@@ -405,6 +429,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mark_summary(0.67),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_contains_validation_error_expectation());
 
         // Submit another wrong answer.
@@ -418,6 +443,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Finish the attempt.
@@ -433,6 +459,63 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_does_not_contain_validation_error_expectation());
     }
 
+    public function test_adaptive_shortanswer_zero_penalty() {
+
+        // Create a short answer question
+        $sa = test_question_maker::make_a_shortanswer_question();
+        // Disable penalties for this question
+        $sa->penalty = 0;
+        $this->start_attempt_at_question($sa, 'adaptive');
+
+        // Check the initial state.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(null);
+        $this->check_current_output(
+                $this->get_contains_marked_out_of_summary(),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_feedback_expectation());
+
+        // Submit a wrong answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
+
+        // Verify.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_contains_incorrect_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+
+        // Submit a correct answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
+
+        // Verify.
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(1.0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1.0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_contains_correct_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+
+        // Finish the attempt.
+        $this->quba->finish_all_questions();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedright);
+        $this->check_current_mark(1.0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1.0),
+                $this->get_contains_submit_button_expectation(false),
+                $this->get_contains_correct_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+    }
+
     public function test_adaptive_shortanswer_try_to_submit_blank() {
 
         // Create a short answer question with correct answer true.
@@ -458,6 +541,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_contains_validation_error_expectation());
         $this->assertNull($this->quba->get_response_summary($this->slot));
 
@@ -472,6 +556,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_partcorrect_expectation(),
                 $this->get_contains_penalty_info_expectation(0.33),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Now submit blank again.
@@ -485,6 +570,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_partcorrect_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_contains_validation_error_expectation());
     }
 
@@ -513,6 +599,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Submit an incorrect answer.
@@ -526,6 +613,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
 
         // Finish the attempt.
index 8881ce4..07c2d91 100644 (file)
@@ -360,7 +360,7 @@ class question_category_object {
         global $DB;
         $questionids = $DB->get_records_select_menu('question',
                 'category = ? AND (parent = 0 OR parent = id)', array($oldcat), '', 'id,1');
-        question_move_questions_to_category($questionids, $newcat);
+        question_move_questions_to_category(array_keys($questionids), $newcat);
     }
 
     /**
index 2b541a6..5f2487e 100644 (file)
@@ -791,16 +791,22 @@ class question_bank_question_text_row extends question_bank_row_base {
     }
 
     protected function display_content($question, $rowclasses) {
-        $text = format_text($question->questiontext, $question->questiontextformat,
-                $this->formatoptions, $this->qbank->get_courseid());
+        $text = question_rewrite_questiontext_preview_urls($question->questiontext,
+                $question->contextid, 'question', $question->id);
+        $text = format_text($text, $question->questiontextformat,
+                $this->formatoptions);
         if ($text == '') {
             $text = '&#160;';
         }
         echo $text;
     }
 
+    public function get_extra_joins() {
+        return array('qc' => 'JOIN {question_categories} qc ON qc.id = q.category');
+    }
+
     public function get_required_fields() {
-        return array('q.questiontext', 'q.questiontextformat');
+        return array('q.id', 'q.questiontext', 'q.questiontextformat', 'qc.contextid');
     }
 }
 
@@ -1100,10 +1106,10 @@ class question_bank_view {
         }
 
     /// Build the where clause.
-        $tests = array('parent = 0');
+        $tests = array('q.parent = 0');
 
         if (!$showhidden) {
-            $tests[] = 'hidden = 0';
+            $tests[] = 'q.hidden = 0';
         }
 
         if ($recurse) {
index 5e76deb..ba8858d 100644 (file)
@@ -165,7 +165,7 @@ class question_type {
      * If you use extra_question_fields, overload this function to return question id field name
      *  in case you table use another name for this column
      */
-    protected function questionid_column_name() {
+    public function questionid_column_name() {
         return 'questionid';
     }
 
@@ -176,7 +176,7 @@ class question_type {
      *
      * @return mixed array as above, or null to tell the base class to do nothing.
      */
-    protected function extra_answer_fields() {
+    public function extra_answer_fields() {
         return null;
     }
 
index b43e3ee..ff9df64 100644 (file)
@@ -42,7 +42,7 @@ class qtype_shortanswer extends question_type {
         return array('question_shortanswer', 'answers', 'usecase');
     }
 
-    protected function questionid_column_name() {
+    public function questionid_column_name() {
         return 'question';
     }
 
index aaf2213..acaacd3 100644 (file)
@@ -38,3 +38,8 @@ $string['pluginnameediting'] = 'Editing a Description';
 $string['pluginnamesummary'] = 'This is not actually a question. Instead it is a way to add some instructions, rubric or other content to the activity. This is similar to the way that labels can be used to add content to the course page.';
 
 The old strings will continue to work, but only until Moodle 2.3 is released.
+
+* If you are using the facilities provided by overriding the extra_answer_fields
+  or questionid_column_name methods, then you must change these to be public
+  methods. (This is required so that backup and restore can be made to work
+  automatically. MDL-24408, MDL-25617, MDL-30562)
index 972364d..362d2be 100644 (file)
 
     $isseparategroups = ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context));
 
-    if ($course->id===SITEID) {
-        $PAGE->navbar->ignore_active();
-    }
-
-    $PAGE->navbar->add(get_string('participants'));
     $PAGE->set_title("$course->shortname: ".get_string('participants'));
     $PAGE->set_heading($course->fullname);
     $PAGE->set_pagetype('course-view-' . $course->format);
 
     if (!isset($hiddenfields['lastaccess'])) {
         $table->sortable(true, 'lastaccess', SORT_DESC);
+    } else {
+        $table->sortable(true, 'firstname', SORT_ASC);
     }
 
     $table->no_sorting('roles');
index 66f8404..7e0f27d 100644 (file)
@@ -47,6 +47,10 @@ function user_create_user($user) {
 /// insert the user into the database
     $newuserid = $DB->insert_record('user', $user);
 
+/// trigger user_created event on the full database user row
+    $newuser = $DB->get_record('user', array('id' => $newuserid));
+    events_trigger('user_created', $newuser);
+
 /// create USER context for this user
     get_context_instance(CONTEXT_USER, $newuserid);
 
@@ -71,6 +75,11 @@ function user_update_user($user) {
 
     $user->timemodified = time();
     $DB->update_record('user', $user);
+
+    /// trigger user_updated event on the full database user row
+    $updateduser = $DB->get_record('user', array('id' => $user->id));
+    events_trigger('user_updated', $updateduser);
+
 }
 
 
index 868b083..ce0a35c 100644 (file)
@@ -30,7 +30,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2011120500.01;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2011120500.02;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes