Merge branch 'install_master' of git://github.com/amosbot/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Fri, 23 Dec 2011 01:52:54 +0000 (02:52 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Fri, 23 Dec 2011 01:52:54 +0000 (02:52 +0100)
135 files changed:
admin/environment.xml
admin/mnet/peer_forms.php
admin/mnet/peers.php
admin/qbehaviours.php
admin/renderer.php
admin/settings.php
admin/settings/mnet.php
admin/tool/dbtransfer/database_transfer_form.php
admin/tool/dbtransfer/index.php
admin/tool/health/index.php
admin/tool/qeupgradehelper/locallib.php
admin/tool/spamcleaner/index.php
admin/tool/spamcleaner/lang/en/tool_spamcleaner.php
admin/webservice/forms.php
admin/webservice/tokens.php
backup/cc/entities.class.php
backup/cc/entities11.class.php
backup/cc/entity.quiz.class.php
backup/cc/entity11.quiz.class.php
backup/cc/sheets/course_question_categories_question_category_question.xml
backup/controller/backup_controller.class.php
backup/controller/restore_controller.class.php
backup/converter/imscc1/lib.php
backup/converter/imscc11/lib.php
backup/moodle2/restore_stepslib.php
backup/util/plan/backup_plan.class.php
blocks/community/yui/comments/comments.js
blog/index.php
course/format/topics/format.php
course/format/weeks/format.php
course/lib.php
course/mod.php
course/modedit.php
course/moodleform_mod.php
course/rest.php
course/user.php
course/view.php
enrol/database/lib.php
enrol/imsenterprise/lib.php
enrol/instances.php
enrol/ldap/lib.php
enrol/ldap/settings.php
enrol/yui/notification/notification.js
grade/lib.php
grade/report/grader/lib.php
grade/report/grader/styles.css
grade/report/user/index.php
lang/en/condition.php
lang/en/error.php
lang/en/install.php
lang/en/mnet.php
lang/en/webservice.php
lib/accesslib.php
lib/ajax/section_classes.js
lib/blocklib.php
lib/db/access.php
lib/db/install.php
lib/db/install.xml
lib/db/upgrade.php
lib/dml/simpletest/testdml.php
lib/enrollib.php
lib/environmentlib.php
lib/form/editor.php
lib/html2text.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
lib/simpletest/fulltestaccesslib.php
lib/simpletest/testmoodlelib.php
lib/simpletest/testweblib.php
lib/zend/Zend/Validate/Barcode/Upca.php [moved from lib/zend/Zend/Validate/Barcode/UpcA.php with 95% similarity]
lib/zend/readme_moodle.txt
mnet/lib.php
mod/assignment/type/upload/assignment.class.php
mod/assignment/type/uploadsingle/assignment.class.php
mod/forum/post.php
mod/forum/post_form.php
mod/glossary/backup/moodle2/restore_glossary_stepslib.php
mod/glossary/import_form.php
mod/imscp/locallib.php
mod/lesson/pagetypes/essay.php
mod/lesson/renderer.php
mod/lesson/report.php
mod/lti/locallib.php
mod/quiz/edit.php
mod/quiz/editlib.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/overview_table.php
mod/quiz/report/overview/report.php
mod/quiz/report/responses/report.php
mod/quiz/report/responses/responses_table.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
mod/scorm/db/access.php
mod/scorm/lang/en/scorm.php
mod/scorm/locallib.php
mod/scorm/version.php
mod/scorm/view.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/engine/bank.php
question/engine/datalib.php
question/engine/upgrade/upgradelib.php
question/type/multianswer/questiontype.php
question/type/multichoice/backup/moodle1/lib.php
question/type/multichoice/backup/moodle2/restore_qtype_multichoice_plugin.class.php
question/type/numerical/db/upgrade.php
question/type/questiontypebase.php
question/type/shortanswer/questiontype.php
question/type/upgrade.txt
report/outline/index.php
repository/filepicker.js
theme/base/style/admin.css
theme/sky_high/config.php
theme/sky_high/style/pagelayout.css
theme/sky_high/style/report.css [new file with mode: 0644]
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 2000ef5..4e2e173 100644 (file)
@@ -38,9 +38,10 @@ class mnet_simple_host_form extends moodleform {
 
         $mform = $this->_form;
 
-        $mform->addElement('text', 'wwwroot', get_string('hostname', 'mnet'));
+        $mform->addElement('text', 'wwwroot', get_string('hostname', 'mnet'), array('maxlength' => 255, 'size' => 50));
         $mform->setType('wwwroot', PARAM_URL);
         $mform->addRule('wwwroot', null, 'required', null, 'client');
+        $mform->addRule('wwwroot', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
 
         $mform->addElement('select', 'applicationid', get_string('applicationtype', 'mnet'),
                            $DB->get_records_menu('mnet_application', array(), 'id,display_name'));
@@ -81,11 +82,13 @@ class mnet_review_host_form extends moodleform {
         $mform->addElement('hidden', 'applicationid');
         $mform->addElement('hidden', 'oldpublickey');
 
-        $mform->addElement('text', 'name', get_string('site'));
+        $mform->addElement('text', 'name', get_string('site'), array('maxlength' => 80, 'size' => 50));
         $mform->setType('name', PARAM_NOTAGS);
+        $mform->addRule('name', get_string('maximumchars', '', 80), 'maxlength', 80, 'client');
 
-        $mform->addElement('text', 'wwwroot', get_string('hostname', 'mnet'));
+        $mform->addElement('text', 'wwwroot', get_string('hostname', 'mnet'), array('maxlength' => 255, 'size' => 50));
         $mform->setType('wwwroot', PARAM_URL);
+        $mform->addRule('wwwroot', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
 
         $themes = array('' => get_string('forceno'));
         foreach (array_keys(get_plugin_list('theme')) as $themename) {
@@ -96,15 +99,6 @@ class mnet_review_host_form extends moodleform {
         $mform->addElement('textarea', 'public_key', get_string('publickey', 'mnet'), array('rows' => 17, 'cols' => 100, 'class' => 'smalltext'));
         $mform->setType('public_key', PARAM_PEM);
 
-        if ($mnet_peer && !empty($mnet_peer->deleted)) {
-            $radioarray = array();
-            $radioarray[] = MoodleQuickForm::createElement('radio', 'deleted', '', get_string('yes'), 1);
-            $radioarray[] = MoodleQuickForm::createElement('radio', 'deleted', '', get_string('no'), 0);
-            $mform->addGroup($radioarray, 'radioar', get_string('deleted'), array(' '), false);
-        } else {
-            $mform->addElement('hidden', 'deleted');
-        }
-
         // finished with form controls, now the static informational stuff
         if ($mnet_peer && !empty($mnet_peer->bootstrapped)) {
             $expires = '';
@@ -140,7 +134,19 @@ class mnet_review_host_form extends moodleform {
                 }
             }
 
-            $mform->addElement('static', 'certdetails', get_string('certdetails', 'mnet'), $OUTPUT->box('<pre>' . $credstr . '</pre>'));
+            $mform->addElement('static', 'certdetails', get_string('certdetails', 'mnet'),
+                $OUTPUT->box('<pre>' . $credstr . '</pre>', 'generalbox certdetails'));
+        }
+
+        if ($mnet_peer && !empty($mnet_peer->deleted)) {
+            $radioarray = array();
+            $radioarray[] = MoodleQuickForm::createElement('static', 'deletedinfo', '',
+                $OUTPUT->container(get_string('deletedhostinfo', 'mnet'), 'deletedhostinfo'));
+            $radioarray[] = MoodleQuickForm::createElement('radio', 'deleted', '', get_string('yes'), 1);
+            $radioarray[] = MoodleQuickForm::createElement('radio', 'deleted', '', get_string('no'), 0);
+            $mform->addGroup($radioarray, 'radioar', get_string('deleted'), array(' ', ' '), false);
+        } else {
+            $mform->addElement('hidden', 'deleted');
         }
 
         // finished with static stuff, print save button
index 0c334d0..4d402ed 100644 (file)
@@ -190,7 +190,7 @@ if ($formdata = $reviewform->get_data()) {
 
 // normal flow - just display all hosts with links
 echo $OUTPUT->header();
-$hosts = mnet_get_hosts();
+$hosts = mnet_get_hosts(true);
 
 // print the table to display the register all hosts setting
 $table = new html_table();
@@ -231,28 +231,43 @@ $table->head = array(
 );
 $table->wrap = array('nowrap', 'nowrap', 'nowrap', 'nowrap');
 $baseurl = new moodle_url('/admin/mnet/peers.php');
+$deleted = array();
 foreach($hosts as $host) {
     $hosturl = new moodle_url($baseurl, array('hostid' => $host->id));
+    if (trim($host->name) === '') {
+        // should not happen but...
+        $host->name = '???';
+    }
     // process all hosts first since it's the easiest
     if ($host->id == $CFG->mnet_all_hosts_id) {
-        $table->data[] = array(html_writer::tag('a', $host->name, array('href'=>$hosturl)), '', '', '');
+        $table->data[] = array(html_writer::link($hosturl, get_string('allhosts', 'core_mnet')), '*', '', '');
+        continue;
+    }
+
+    // populate the list of deleted hosts
+    if ($host->deleted) {
+        $deleted[] = html_writer::link($hosturl, $host->name);
         continue;
     }
 
     if ($host->last_connect_time == 0) {
         $last_connect = get_string('never');
     } else {
-        $last_connect = date('H:i:s d/m/Y', $host->last_connect_time);
+        $last_connect = userdate($host->last_connect_time, get_string('strftimedatetime', 'core_langconfig'));
     }
     $table->data[] = array(
         html_writer::link($hosturl, $host->name),
-        html_writer::link($hosturl, $host->wwwroot),
+        html_writer::link($host->wwwroot, $host->wwwroot),
         $last_connect,
         $OUTPUT->single_button(new moodle_url('/admin/mnet/delete.php', array('hostid' => $host->id)), get_string('delete'))
     );
 }
 echo html_writer::table($table);
 
+if ($deleted) {
+    echo $OUTPUT->box(get_string('deletedhosts', 'core_mnet', join(', ', $deleted)), 'deletedhosts');
+}
+
 // finally, print the initial form to add a new host
 echo $OUTPUT->box_start();
 echo $OUTPUT->heading(get_string('addnewhost', 'mnet'), 3);
index 5c6524b..2ae5f08 100644 (file)
@@ -52,16 +52,16 @@ foreach ($behaviours as $behaviour => $notused) {
     if (!array_key_exists($behaviour, $counts)) {
         $counts[$behaviour] = 0;
     }
-    $needed[$behaviour] = ($counts[$behaviour] > 0) &&
+    $needed[$behaviour] = ($counts[$behaviour] > 0) ||
             $pluginmanager->other_plugins_that_require('qbehaviour_' . $behaviour);
     $archetypal[$behaviour] = question_engine::is_behaviour_archetypal($behaviour);
 }
-
 foreach ($counts as $behaviour => $count) {
     if (!array_key_exists($behaviour, $behaviours)) {
-        $counts['missingtype'] += $count;
+        $counts['missing'] += $count;
     }
 }
+$needed['missing'] = true;
 
 // Work of the correct sort order.
 $config = get_config('question');
@@ -179,7 +179,7 @@ if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey())
         unset($disabledbehaviours[$key]);
         set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question');
     }
-    $behaviourorder = explode(',', $config->behavioursortorder);
+    $behaviourorder = array_keys($sortedbehaviours);
     if (($key = array_search($delete, $behaviourorder)) !== false) {
         unset($behaviourorder[$key]);
         set_config('behavioursortorder', implode(',', $behaviourorder), 'question');
@@ -248,7 +248,7 @@ foreach ($sortedbehaviours as $behaviour => $behaviourname) {
         $row[] = '';
     }
 
-    // Are people allowed to create new questions of this type?
+    // Are people allowed to select this behaviour?
     $rowclass = '';
     if ($archetypal[$behaviour]) {
         $enabled = array_search($behaviour, $disabledbehaviours) === false;
index 8c56d26..d823e0e 100644 (file)
@@ -192,7 +192,7 @@ class core_admin_renderer extends plugin_renderer_base {
         $output .= $this->maturity_info($maturity);
         $output .= $this->insecure_dataroot_warning($insecuredataroot);
         $output .= $this->display_errors_warning($errorsdisplayed);
-        $output .= $this->cron_overdue_warning($errorsdisplayed);
+        $output .= $this->cron_overdue_warning($cronoverdue);
         $output .= $this->db_problems($dbproblems);
         $output .= $this->maintenance_mode_warning($maintenancemode);
 
index 596d618..ddccf31 100644 (file)
@@ -98,13 +98,13 @@ if (empty($SITE->fullname)) {
             $url->param('adminedit', 'on');
         }
         $buttons = $OUTPUT->single_button($url, $caption, 'get');
+        $PAGE->set_button($buttons);
     }
 
     $visiblepathtosection = array_reverse($settingspage->visiblepath);
 
     $PAGE->set_title("$SITE->shortname: " . implode(": ",$visiblepathtosection));
     $PAGE->set_heading($SITE->fullname);
-    $PAGE->set_button($buttons);
     echo $OUTPUT->header();
 
     if ($errormsg !== '') {
index c8adb6a..dfbc57f 100644 (file)
@@ -22,6 +22,9 @@ if (isset($CFG->mnet_dispatcher_mode) and $CFG->mnet_dispatcher_mode !== 'off')
 
     $hosts = mnet_get_hosts();
     foreach ($hosts as $host) {
+        if ($host->id == $CFG->mnet_all_hosts_id) {
+            $host->name = get_string('allhosts', 'core_mnet');
+        }
         $ADMIN->add('mnetpeercat',
             new admin_externalpage(
                 'mnetpeer' . $host->id,
index 241d572..5b32c0f 100644 (file)
@@ -57,6 +57,8 @@ class database_transfer_form extends moodleform {
         $mform->addElement('text', 'dbuser', get_string('user'));
         $mform->addElement('text', 'dbpass', get_string('password'));
         $mform->addElement('text', 'prefix', get_string('dbprefix', 'install'));
+        $mform->addElement('text', 'dbport', get_string('dbport', 'install'));
+        $mform->addElement('text', 'dbsocket', get_string('databasesocket', 'install'));
 
         $mform->addRule('dbhost', get_string('required'), 'required', null);
         $mform->addRule('dbname', get_string('required'), 'required', null);
index 57791da..2b1a2c5 100644 (file)
@@ -40,7 +40,14 @@ if ($data = $form->get_data()) {
     // Connect to the other database.
     list($dbtype, $dblibrary) = explode('/', $data->driver);
     $targetdb = moodle_database::get_driver_instance($dbtype, $dblibrary);
-    if (!$targetdb->connect($data->dbhost, $data->dbuser, $data->dbpass, $data->dbname, $data->prefix, null)) {
+    $dboptions = array();
+    if ($data->dbport) {
+        $dboptions['dbport'] = $data->dbport;
+    }
+    if ($data->dbsocket) {
+        $dboptions['dbsocket'] = $data->dbsocket;
+    }
+    if (!$targetdb->connect($data->dbhost, $data->dbuser, $data->dbpass, $data->dbname, $data->prefix, $dboptions)) {
         throw new dbtransfer_exception('notargetconectexception', null, "$CFG->wwwroot/$CFG->admin/tool/dbtransfer/");
     }
     if ($targetdb->get_tables()) {
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 c2d1272..443f5cd 100644 (file)
@@ -157,21 +157,54 @@ function search_spammers($keywords) {
         $params['descpat'.$i] = "%$keyword%";
         $keywordfull2[] = $DB->sql_like('p.summary', ':sumpat'.$i, false);
         $params['sumpat'.$i] = "%$keyword%";
+        $keywordfull3[] = $DB->sql_like('p.subject', ':subpat'.$i, false);
+        $params['subpat'.$i] = "%$keyword%";
+        $keywordfull4[] = $DB->sql_like('c.content', ':contpat'.$i, false);
+        $params['contpat'.$i] = "%$keyword%";
+        $keywordfull5[] = $DB->sql_like('m.fullmessage', ':msgpat'.$i, false);
+        $params['msgpat'.$i] = "%$keyword%";
+        $keywordfull6[] = $DB->sql_like('fp.message', ':forumpostpat'.$i, false);
+        $params['forumpostpat'.$i] = "%$keyword%";
+        $keywordfull7[] = $DB->sql_like('fp.subject', ':forumpostsubpat'.$i, false);
+        $params['forumpostsubpat'.$i] = "%$keyword%";
         $i++;
     }
     $conditions = '( '.implode(' OR ', $keywordfull).' )';
     $conditions2 = '( '.implode(' OR ', $keywordfull2).' )';
+    $conditions3 = '( '.implode(' OR ', $keywordfull3).' )';
+    $conditions4 = '( '.implode(' OR ', $keywordfull4).' )';
+    $conditions5 = '( '.implode(' OR ', $keywordfull5).' )';
+    $conditions6 = '( '.implode(' OR ', $keywordfull6).' )';
+    $conditions7 = '( '.implode(' OR ', $keywordfull7).' )';
 
     $sql  = "SELECT * FROM {user} WHERE deleted = 0 AND id <> :userid AND $conditions";  // Exclude oneself
     $sql2 = "SELECT u.*, p.summary FROM {user} AS u, {post} AS p WHERE $conditions2 AND u.deleted = 0 AND u.id=p.userid AND u.id <> :userid";
+    $sql3 = "SELECT u.*, p.subject as postsubject FROM {user} AS u, {post} AS p WHERE $conditions3 AND u.deleted = 0 AND u.id=p.userid AND u.id <> :userid";
+    $sql4 = "SELECT u.*, c.content FROM {user} AS u, {comments} AS c WHERE $conditions4 AND u.deleted = 0 AND u.id=c.userid AND u.id <> :userid";
+    $sql5 = "SELECT u.*, m.fullmessage FROM {user} AS u, {message} AS m WHERE $conditions5 AND u.deleted = 0 AND u.id=m.useridfrom AND u.id <> :userid";
+    $sql6 = "SELECT u.*, fp.message FROM {user} AS u, {forum_posts} AS fp WHERE $conditions6 AND u.deleted = 0 AND u.id=fp.userid AND u.id <> :userid";
+    $sql7 = "SELECT u.*, fp.subject FROM {user} AS u, {forum_posts} AS fp WHERE $conditions7 AND u.deleted = 0 AND u.id=fp.userid AND u.id <> :userid";
+
     $spamusers_desc = $DB->get_recordset_sql($sql, $params);
     $spamusers_blog = $DB->get_recordset_sql($sql2, $params);
+    $spamusers_blogsub = $DB->get_recordset_sql($sql3, $params);
+    $spamusers_comment = $DB->get_recordset_sql($sql4, $params);
+    $spamusers_message = $DB->get_recordset_sql($sql5, $params);
+    $spamusers_forumpost = $DB->get_recordset_sql($sql6, $params);
+    $spamusers_forumpostsub = $DB->get_recordset_sql($sql7, $params);
 
     $keywordlist = implode(', ', $keywords);
     echo $OUTPUT->box(get_string('spamresult', 'tool_spamcleaner').s($keywordlist)).' ...';
 
-    print_user_list(array($spamusers_desc, $spamusers_blog), $keywords);
-
+    print_user_list(array($spamusers_desc,
+                          $spamusers_blog,
+                          $spamusers_blogsub,
+                          $spamusers_comment,
+                          $spamusers_message,
+                          $spamusers_forumpost,
+                          $spamusers_forumpostsub
+                         ),
+                         $keywords);
 }
 
 
@@ -212,7 +245,23 @@ function filter_user($user, $keywords, $count) {
     if (isset($user->summary)) {
         $user->description = '<h3>'.get_string('spamfromblog', 'tool_spamcleaner').'</h3>'.$user->summary;
         unset($user->summary);
+    } else if (isset($user->postsubject)) {
+        $user->description = '<h3>'.get_string('spamfromblog', 'tool_spamcleaner').'</h3>'.$user->postsubject;
+        unset($user->postsubject);
+    } else if (isset($user->content)) {
+        $user->description = '<h3>'.get_string('spamfromcomments', 'tool_spamcleaner').'</h3>'.$user->content;
+        unset($user->content);
+    } else if (isset($user->fullmessage)) {
+        $user->description = '<h3>'.get_string('spamfrommessages', 'tool_spamcleaner').'</h3>'.$user->fullmessage;
+        unset($user->fullmessage);
+    } else if (isset($user->message)) {
+        $user->description = '<h3>'.get_string('spamfromforumpost', 'tool_spamcleaner').'</h3>'.$user->message;
+        unset($user->message);
+    } else if (isset($user->subject)) {
+        $user->description = '<h3>'.get_string('spamfromforumpost', 'tool_spamcleaner').'</h3>'.$user->subject;
+        unset($user->subject);
     }
+
     if (preg_match('#<img.*src=[\"\']('.$CFG->wwwroot.')#', $user->description, $matches)
         && $image_search) {
         $result = false;
index 428f1b0..5a86357 100644 (file)
@@ -34,6 +34,9 @@ $string['spamdeleteconfirm'] = 'Are you sure you want to delete this entry?  You
 $string['spamdesc'] = 'Description';
 $string['spameg'] = 'eg:  casino, porn, xxx';
 $string['spamfromblog'] = 'From blog post:';
+$string['spamfromcomments'] = 'From comments:';
+$string['spamfrommessages'] = 'From messages:';
+$string['spamfromforumpost'] = 'From forum post:';
 $string['spaminvalidresult'] = 'Unknown but invalid result';
 $string['spamoperation'] = 'Operation';
 $string['spamresult'] = 'Results of searching user profiles containing:';
index 7520228..dab9a42 100644 (file)
@@ -179,7 +179,7 @@ class external_service_functions_form extends moodleform {
 class web_service_token_form extends moodleform {
 
     function definition() {
-        global $USER, $DB;
+        global $USER, $DB, $CFG;
 
         $mform = $this->_form;
         $data = $this->_customdata;
@@ -187,16 +187,28 @@ 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)
+                //user must be confirmed, not deleted, not suspended, not guest
+                $sql = "SELECT u.id, u.firstname, u.lastname
+                            FROM {user} u
+                            WHERE u.deleted = 0 AND u.confirmed = 1 AND u.suspended = 0 AND u.id != ?
+                            ORDER BY u.lastname";
+                $users = $DB->get_records_sql($sql, array($CFG->siteguest));
+                $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 +242,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 ec06e23..cf229ab 100644 (file)
@@ -71,6 +71,12 @@ switch ($action) {
                 }
             }
 
+            //check if the user is deleted. unconfirmed, suspended or guest
+            $user = $DB->get_record('user', array('id' => $data->user));
+            if ($user->id == $CFG->siteguest or $user->deleted or !$user->confirmed or $user->suspended) {
+                throw new moodle_exception('forbiddenwsuser', 'webservice');
+            }
+
             //process the creation
             if (empty($errormsg)) {
                 //TODO improvement: either move this function from externallib.php to webservice/lib.php
index e61fe1e..625d390 100644 (file)
@@ -208,9 +208,8 @@ class entities {
         if (!empty($files)) {
 
             foreach ($files as $file) {
-
-                $source = cc2moodle::$path_to_manifest_folder . DIRECTORY_SEPARATOR . $file->nodeValue;
-                $destination = $destination_folder . DIRECTORY_SEPARATOR . $file->nodeValue;
+                $source = cc2moodle::$path_to_manifest_folder . DIRECTORY_SEPARATOR . $file;
+                $destination = $destination_folder . DIRECTORY_SEPARATOR . $file;
 
                 $destination_directory = dirname($destination);
 
@@ -251,10 +250,9 @@ class entities {
                     if (in_array($ext, array('html', 'htm', 'xhtml'))) {
                         continue;
                     }
-                    $all_files[] = $file;
+                    $all_files[] = $file->nodeValue;
                 }
             }
-
             unset($files);
         }
 
@@ -264,8 +262,8 @@ class entities {
         if (!empty($labels) && ($labels->length > 0)) {
             $tname = 'course_files';
             $dpath = cc2moodle::$path_to_manifest_folder . DIRECTORY_SEPARATOR . $tname;
-            $fpath = $dpath . DIRECTORY_SEPARATOR . 'folder.gif';
-            $rfpath = $tname.'/folder.gif';
+            $rfpath = 'folder.gif';
+            $fpath = $dpath . DIRECTORY_SEPARATOR . $rfpath;
 
             if (!file_exists($dpath)) {
                 mkdir($dpath);
index 96c0873..9af9f3c 100644 (file)
@@ -51,7 +51,7 @@ class entities11 extends entities {
                 if (in_array($ext, array('html', 'htm', 'xhtml'))) {
                     continue;
                 }
-                $all_files[] = $file;
+                $all_files[] = $file->nodeValue;
             }
             unset($files);
         }
@@ -62,8 +62,8 @@ class entities11 extends entities {
         if (!empty($labels) && ($labels->length > 0)) {
             $tname = 'course_files';
             $dpath = cc2moodle::$path_to_manifest_folder . DIRECTORY_SEPARATOR . $tname;
+            $rfpath = 'folder.gif';
             $fpath = $dpath . DIRECTORY_SEPARATOR . 'folder.gif';
-            $rfpath = $tname.'/folder.gif';
             if (!file_exists($dpath)) {
                 mkdir($dpath);
             }
index 34e9b27..4bb8c32 100644 (file)
@@ -276,6 +276,7 @@ class cc_quiz extends entities {
                            '[#question_text#]',
                            '[#question_type#]',
                            '[#question_general_feedback#]',
+                           '[#question_defaultgrade#]',
                            '[#date_now#]',
                            '[#question_type_nodes#]',
                            '[#question_stamp#]',
@@ -308,6 +309,7 @@ class cc_quiz extends entities {
                                         self::safexml($question['title']),
                                         $question_moodle_type,
                                         self::safexml($question['feedback']),
+                                        $question['defaultgrade'], //default grade
                                         time(),
                                         $question_type_node,
                                         $quiz_stamp,
@@ -368,6 +370,7 @@ class cc_quiz extends entities {
                     $questions[$question_identifier]['moodle_type'] = $question_type['moodle'];
                     $questions[$question_identifier]['cc_type'] = $question_type['cc'];
                     $questions[$question_identifier]['feedback'] = $this->get_general_feedback($assessment, $question_identifier);
+                    $questions[$question_identifier]['defaultgrade'] = $this->get_defaultgrade($assessment, $question_identifier);
                     $questions[$question_identifier]['answers'] = $this->get_answers($question_identifier, $assessment, $last_answer_id);
 
                 }
@@ -395,6 +398,21 @@ class cc_quiz extends entities {
         }
     }
 
+    private function get_defaultgrade($assessment, $question_identifier) {
+        $result = 1;
+        $xpath = cc2moodle::newx_path($assessment, cc2moodle::getquizns());
+        $query = '//xmlns:item[@ident="' . $question_identifier . '"]';
+        $query .= '//xmlns:qtimetadatafield[xmlns:fieldlabel="cc_weighting"]/xmlns:fieldentry';
+        $defgrade = $xpath->query($query);
+        if (!empty($defgrade) && ($defgrade->length > 0)) {
+            $resp = (int)$defgrade->item(0)->nodeValue;
+            if ($resp >= 0 && $resp <= 99) {
+                $result = $resp;
+            }
+        }
+        return $result;
+    }
+
     private function get_general_feedback ($assessment, $question_identifier) {
 
         $xpath = cc2moodle::newx_path($assessment, cc2moodle::getquizns());
index 83e50d1..9472607 100644 (file)
@@ -276,6 +276,7 @@ class cc11_quiz extends entities11 {
                            '[#question_text#]',
                            '[#question_type#]',
                            '[#question_general_feedback#]',
+                           '[#question_defaultgrade#]',
                            '[#date_now#]',
                            '[#question_type_nodes#]',
                            '[#question_stamp#]',
@@ -308,6 +309,7 @@ class cc11_quiz extends entities11 {
                                         self::safexml($question['title']),
                                         $question_moodle_type,
                                         self::safexml($question['feedback']),
+                                        $question['defaultgrade'],
                                         time(),
                                         $question_type_node,
                                         $quiz_stamp,
@@ -368,6 +370,7 @@ class cc11_quiz extends entities11 {
                     $questions[$question_identifier]['moodle_type'] = $question_type['moodle'];
                     $questions[$question_identifier]['cc_type'] = $question_type['cc'];
                     $questions[$question_identifier]['feedback'] = $this->get_general_feedback($assessment, $question_identifier);
+                    $questions[$question_identifier]['defaultgrade'] = $this->get_defaultgrade($assessment, $question_identifier);
                     $questions[$question_identifier]['answers'] = $this->get_answers($question_identifier, $assessment, $last_answer_id);
 
                 }
@@ -395,6 +398,21 @@ class cc11_quiz extends entities11 {
         }
     }
 
+    private function get_defaultgrade($assessment, $question_identifier) {
+        $result = 1;
+        $xpath = cc2moodle::newx_path($assessment, cc2moodle::getquizns());
+        $query = '//xmlns:item[@ident="' . $question_identifier . '"]';
+        $query .= '//xmlns:qtimetadatafield[xmlns:fieldlabel="cc_weighting"]/xmlns:fieldentry';
+        $defgrade = $xpath->query($query);
+        if (!empty($defgrade) && ($defgrade->length > 0)) {
+            $resp = (int)$defgrade->item(0)->nodeValue;
+            if ($resp >= 0 && $resp <= 99) {
+                $result = $resp;
+            }
+        }
+        return $result;
+    }
+
     private function get_general_feedback ($assessment, $question_identifier) {
 
         $xpath = cc112moodle::newx_path($assessment, cc112moodle::getquizns());
index 99028c4..a0fb3f1 100644 (file)
@@ -6,7 +6,7 @@
             <QUESTIONTEXTFORMAT>1</QUESTIONTEXTFORMAT>
             <IMAGE></IMAGE>
             <GENERALFEEDBACK>[#question_general_feedback#]</GENERALFEEDBACK>
-            <DEFAULTGRADE>1</DEFAULTGRADE>
+            <DEFAULTGRADE>[#question_defaultgrade#]</DEFAULTGRADE>
             <PENALTY>0</PENALTY>
             <QTYPE>[#question_type#]</QTYPE>
             <LENGTH>1</LENGTH>
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 6b780a4..5cfae39 100644 (file)
@@ -99,7 +99,7 @@ class imscc1_converter extends base_converter {
         $manifestdir = dirname($manifest);
         $cc2moodle = new cc2moodle($manifest);
         if ($cc2moodle->is_auth()) {
-            throw new imscc1_convert_exception('Protected cartridge content - Skipping import!');
+            throw new imscc1_convert_exception('protected_cc_not_supported');
         }
         $status = $cc2moodle->generate_moodle_xml();
         //Final cleanup
index 3ca8267..7e885f0 100644 (file)
@@ -99,7 +99,7 @@ class imscc11_converter extends base_converter {
         $manifestdir = dirname($manifest);
         $cc112moodle = new cc112moodle($manifest);
         if ($cc112moodle->is_auth()) {
-            throw new imscc11_convert_exception('Protected cartridge content - Skipping import!');
+            throw new imscc11_convert_exception('protected_cc_not_supported');
         }
         $status = $cc112moodle->generate_moodle_xml();
         //Final cleanup
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 b2b1f53..155a823 100644 (file)
@@ -31,13 +31,6 @@ YUI.add('moodle-block_community-comments', function(Y) {
                 this.overlays[commentid].render();
                 this.overlays[commentid].hide();
 
-                // position the overlay in the middle of the web browser window
-                var WidgetPositionAlign = Y.WidgetPositionAlign;
-                this.overlays[commentid].set("align", {
-                    node:"", //empty => viewport
-                    points:[WidgetPositionAlign.CC, WidgetPositionAlign.CC]
-                });
-
                 Y.one('#comments-'+commentid).on('click', this.show, this, commentid);
             }
 
index 2ba9884..e1c0c22 100644 (file)
@@ -212,9 +212,13 @@ if (empty($entryid) && empty($modid) && empty($groupid)) {
 }
 
 if ($CFG->enablerssfeeds) {
-    $rsscontext = $filtertype = $thingid = null;
+    $rsscontext = null;
+    $filtertype = null;
+    $thingid = null;
     list($thingid, $rsscontext, $filtertype) = blog_rss_get_params($blogheaders['filters']);
-
+    if (empty($rsscontext)) {
+        $rsscontext = get_system_context();
+    }
     $rsstitle = $blogheaders['heading'];
 
     //check we haven't started output by outputting an error message
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 8a9b0d2..d043a74 100644 (file)
@@ -3062,13 +3062,13 @@ function moveto_module($mod, $section, $beforemod=NULL) {
  * @global core_renderer $OUTPUT
  * @staticvar type $str
  * @param stdClass $mod The module to produce editing buttons for
- * @param bool $absolute If true an absolute link is produced (default true)
+ * @param bool $absolute_ignored ignored - all links are absolute
  * @param bool $moveselect If true a move seleciton process is used (default true)
  * @param int $indent The current indenting
  * @param int $section The section to link back to
  * @return string XHTML for the editing buttons
  */
-function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = true, $indent=-1, $section=-1) {
+function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=-1) {
     global $CFG, $OUTPUT;
 
     static $str;
@@ -3076,11 +3076,16 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
     $coursecontext = get_context_instance(CONTEXT_COURSE, $mod->course);
     $modcontext = get_context_instance(CONTEXT_MODULE, $mod->id);
 
-    // no permission to edit
-    if (!has_capability('moodle/course:manageactivities', $modcontext)) {
+    $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
+    $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
+
+    // no permission to edit anything
+    if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
         return false;
     }
 
+    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
+
     if (!isset($str)) {
         $str = new stdClass;
         $str->assign         = get_string("assignroles", 'role');
@@ -3101,11 +3106,7 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
         $str->groupsvisible  = get_string("groupsvisible");
     }
 
-    if ($absolute) {
-        $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
-    } else {
-        $baseurl = new moodle_url('mod.php', array('sesskey' => sesskey()));
-    }
+    $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
 
     if ($section >= 0) {
         $baseurl->param('sr', $section);
@@ -3113,7 +3114,7 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
     $actions = array();
 
     // leftright
-    if (has_capability('moodle/course:update', $coursecontext)) {
+    if ($hasmanageactivities) {
         if (right_to_left()) {   // Exchange arrows on RTL
             $rightarrow = 't/left';
             $leftarrow  = 't/right';
@@ -3141,7 +3142,7 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
     }
 
     // move
-    if (has_capability('moodle/course:update', $coursecontext)) {
+    if ($hasmanageactivities) {
         if ($moveselect) {
             $actions[] = new action_link(
                 new moodle_url($baseurl, array('copy' => $mod->id)),
@@ -3166,15 +3167,16 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
     }
 
     // Update
-    $actions[] = new action_link(
-        new moodle_url($baseurl, array('update' => $mod->id)),
-        new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall')),
-        null,
-        array('class' => 'editing_update', 'title' => $str->update)
-    );
+    if ($hasmanageactivities) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('update' => $mod->id)),
+            new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall')),
+            null,
+            array('class' => 'editing_update', 'title' => $str->update)
+        );
+    }
 
     // Duplicate (require both target import caps to be able to duplicate, see modduplicate.php)
-    $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
     if (has_all_capabilities($dupecaps, $coursecontext)) {
         $actions[] = new action_link(
             new moodle_url($baseurl, array('duplicate' => $mod->id)),
@@ -3185,12 +3187,14 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
     }
 
     // Delete
-    $actions[] = new action_link(
-        new moodle_url($baseurl, array('delete' => $mod->id)),
-        new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall')),
-        null,
-        array('class' => 'editing_delete', 'title' => $str->delete)
-    );
+    if ($hasmanageactivities) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('delete' => $mod->id)),
+            new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall')),
+            null,
+            array('class' => 'editing_delete', 'title' => $str->delete)
+        );
+    }
 
     // hideshow
     if (has_capability('moodle/course:activityvisibility', $modcontext)) {
@@ -3212,7 +3216,7 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
     }
 
     // groupmode
-    if ($mod->groupmode !== false) {
+    if ($hasmanageactivities and $mod->groupmode !== false) {
         if ($mod->groupmode == SEPARATEGROUPS) {
             $groupmode = 0;
             $grouptitle = $str->groupsseparate;
@@ -3242,7 +3246,7 @@ function make_editing_buttons(stdClass $mod, $absolute = true, $moveselect = tru
     }
 
     // Assign
-    if (has_capability('moodle/course:managegroups', $modcontext)){
+    if (has_capability('moodle/role:assign', $modcontext)){
         $actions[] = new action_link(
             new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
             new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall')),
index d6b9e05..365c7e6 100644 (file)
@@ -26,8 +26,6 @@
 require("../config.php");
 require_once("lib.php");
 
-require_login();
-
 $sectionreturn = optional_param('sr', '', PARAM_INT);
 $add           = optional_param('add', '', PARAM_ALPHA);
 $type          = optional_param('type', '', PARAM_ALPHA);
@@ -66,6 +64,8 @@ if ($groupmode !== '') {
 }
 $PAGE->set_url($url);
 
+require_login();
+
 //check if we are adding / editing a module that has new forms using formslib
 if (!empty($add)) {
     $id          = required_param('id', PARAM_INT);
@@ -76,9 +76,7 @@ if (!empty($add)) {
     redirect("$CFG->wwwroot/course/modedit.php?add=$add&type=$type&course=$id&section=$section&return=$returntomod");
 
 } else if (!empty($update)) {
-    if (!$cm = get_coursemodule_from_id('', $update, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
+    $cm = get_coursemodule_from_id('', $update, 0, true, MUST_EXIST);
     $returntomod = optional_param('return', 0, PARAM_BOOL);
     redirect("$CFG->wwwroot/course/modedit.php?update=$update&return=$returntomod");
 
@@ -86,7 +84,7 @@ if (!empty($add)) {
     $cm     = get_coursemodule_from_id('', $duplicate, 0, true, MUST_EXIST);
     $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 
-    require_login($course->id);
+    require_login($course, false, $cm);
     $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
     require_capability('moodle/course:manageactivities', $coursecontext);
@@ -119,17 +117,13 @@ if (!empty($add)) {
     }
 
 } else if (!empty($delete)) {
-    if (!$cm = get_coursemodule_from_id('', $delete, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
+    $cm     = get_coursemodule_from_id('', $delete, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 
-    if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
-        print_error('invalidcourseid');
-    }
-    require_login($course->id); // needed to setup proper $COURSE
-    $context = get_context_instance(CONTEXT_COURSE, $course->id);
+    require_login($course, false, $cm);
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
-    require_capability('moodle/course:manageactivities', $context);
+    require_capability('moodle/course:manageactivities', $modcontext);
 
     $return = "$CFG->wwwroot/course/view.php?id=$cm->course#section-$cm->sectionnum";
 
@@ -203,9 +197,13 @@ if (!empty($add)) {
 
 
 if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
-    if (!$cm = get_coursemodule_from_id('', $USER->activitycopy, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
+    $cm     = get_coursemodule_from_id('', $USER->activitycopy, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+    $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
 
     if (!empty($movetosection)) {
         if (!$section = $DB->get_record('course_sections', array('id'=>$movetosection, 'course'=>$cm->course))) {
@@ -222,10 +220,6 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
         }
     }
 
-    require_login($section->course); // needed to setup proper $COURSE
-    $context = get_context_instance(CONTEXT_COURSE, $section->course);
-    require_capability('moodle/course:manageactivities', $context);
-
     if (!ismoving($section->course)) {
         print_error('needcopy', '', "view.php?id=$section->course");
     }
@@ -246,13 +240,14 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
 
 } else if (!empty($indent) and confirm_sesskey()) {
     $id = required_param('id', PARAM_INT);
-    if (!$cm = get_coursemodule_from_id('', $id, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
 
-    require_login($cm->course); // needed to setup proper $COURSE
-    $context = get_context_instance(CONTEXT_COURSE, $cm->course);
-    require_capability('moodle/course:manageactivities', $context);
+    $cm     = get_coursemodule_from_id('', $id, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+    $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
 
     $cm->indent += $indent;
 
@@ -271,13 +266,13 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
     }
 
 } else if (!empty($hide) and confirm_sesskey()) {
-    if (!$cm = get_coursemodule_from_id('', $hide, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
+    $cm     = get_coursemodule_from_id('', $hide, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 
-    require_login($cm->course); // needed to setup proper $COURSE
-    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
-    require_capability('moodle/course:activityvisibility', $context);
+    require_login($course, false, $cm);
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+    $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+    require_capability('moodle/course:activityvisibility', $modcontext);
 
     set_coursemodule_visible($cm->id, 0);
 
@@ -290,21 +285,17 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
     }
 
 } else if (!empty($show) and confirm_sesskey()) {
-    if (!$cm = get_coursemodule_from_id('', $show, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
+    $cm     = get_coursemodule_from_id('', $show, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 
-    require_login($cm->course); // needed to setup proper $COURSE
-    $context = get_context_instance(CONTEXT_COURSE, $cm->course);
-    require_capability('moodle/course:activityvisibility', $context);
+    require_login($course, false, $cm);
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+    $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+    require_capability('moodle/course:activityvisibility', $modcontext);
 
-    if (!$section = $DB->get_record('course_sections', array('id'=>$cm->section))) {
-        print_error('sectionnotexist');
-    }
+    $section = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
 
-    if (!$module = $DB->get_record('modules', array('id'=>$cm->module))) {
-        print_error('moduledoesnotexist');
-    }
+    $module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
 
     if ($module->visible and ($section->visible or (SITEID == $cm->course))) {
         set_coursemodule_visible($cm->id, 1);
@@ -319,13 +310,14 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
 
 } else if ($groupmode > -1 and confirm_sesskey()) {
     $id = required_param('id', PARAM_INT);
-    if (!$cm = get_coursemodule_from_id('', $id, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
 
-    require_login($cm->course); // needed to setup proper $COURSE
-    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
-    require_capability('moodle/course:manageactivities', $context);
+    $cm     = get_coursemodule_from_id('', $id, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+    $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
 
     set_coursemodule_groupmode($cm->id, $groupmode);
 
@@ -338,17 +330,15 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
     }
 
 } else if (!empty($copy) and confirm_sesskey()) { // value = course module
-    if (!$cm = get_coursemodule_from_id('', $copy, 0, true)) {
-        print_error('invalidcoursemodule');
-    }
+    $cm     = get_coursemodule_from_id('', $copy, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 
-    require_login($cm->course); // needed to setup proper $COURSE
-    $context = get_context_instance(CONTEXT_COURSE, $cm->course);
-    require_capability('moodle/course:manageactivities', $context);
+    require_login($course, false, $cm);
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+    $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
 
-    if (!$section = $DB->get_record('course_sections', array('id'=>$cm->section))) {
-        print_error('sectionnotexist');
-    }
+    $section = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
 
     $USER->activitycopy       = $copy;
     $USER->activitycopycourse = $cm->course;
index 7041d80..5865e4a 100644 (file)
@@ -365,7 +365,9 @@ if ($mform->is_cancelled()) {
         }
 
         // make sure visibility is set correctly (in particular in calendar)
-        set_coursemodule_visible($fromform->coursemodule, $fromform->visible);
+        if (has_capability('moodle/course:activityvisibility', $modcontext)) {
+            set_coursemodule_visible($fromform->coursemodule, $fromform->visible);
+        }
 
         if (isset($fromform->cmidnumber)) { //label
             // set cm idnumber - uniqueness is already verified by form validation
@@ -477,6 +479,7 @@ if ($mform->is_cancelled()) {
         $DB->set_field('course_modules', 'section', $sectionid, array('id'=>$fromform->coursemodule));
 
         // make sure visibility is set correctly (in particular in calendar)
+        // note: allow them to set it even without moodle/course:activityvisibility
         set_coursemodule_visible($fromform->coursemodule, $fromform->visible);
 
         if (isset($fromform->cmidnumber)) { //label
index a531061..9fc6c50 100644 (file)
@@ -173,16 +173,21 @@ abstract class moodleform_mod extends moodleform {
             }
         }
 
-        if ($mform->elementExists('groupmode') and !$mform->elementExists('groupmembersonly') and empty($COURSE->groupmodeforce)) {
-            $mform->disabledIf('groupingid', 'groupmode', 'eq', NOGROUPS);
-
-        } else if (!$mform->elementExists('groupmode') and $mform->elementExists('groupmembersonly')) {
-            $mform->disabledIf('groupingid', 'groupmembersonly', 'notchecked');
-
-        } else if (!$mform->elementExists('groupmode') and !$mform->elementExists('groupmembersonly')) {
-            // groupings have no use without groupmode or groupmembersonly
-            if ($mform->elementExists('groupingid')) {
-                $mform->removeElement('groupingid');
+        // Don't disable/remove groupingid if it is currently set to something,
+        // otherwise you cannot turn it off at same time as turning off other
+        // option (MDL-30764)
+        if (empty($this->_cm) || !$this->_cm->groupingid) {
+            if ($mform->elementExists('groupmode') and !$mform->elementExists('groupmembersonly') and empty($COURSE->groupmodeforce)) {
+                $mform->disabledIf('groupingid', 'groupmode', 'eq', NOGROUPS);
+
+            } else if (!$mform->elementExists('groupmode') and $mform->elementExists('groupmembersonly')) {
+                $mform->disabledIf('groupingid', 'groupmembersonly', 'notchecked');
+
+            } else if (!$mform->elementExists('groupmode') and !$mform->elementExists('groupmembersonly')) {
+                // groupings have no use without groupmode or groupmembersonly
+                if ($mform->elementExists('groupingid')) {
+                    $mform->removeElement('groupingid');
+                }
             }
         }
 
@@ -313,6 +318,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;
     }
 
@@ -422,6 +456,12 @@ abstract class moodleform_mod extends moodleform {
         }
 
         $mform->addElement('modvisible', 'visible', get_string('visible'));
+        if (!empty($this->_cm)) {
+            $context = get_context_instance(CONTEXT_MODULE, $this->_cm->id);
+            if (!has_capability('moodle/course:activityvisibility', $context)) {
+                $mform->hardFreeze('visible');
+            }
+        }
 
         if ($this->_features->idnumber) {
             $mform->addElement('text', 'cmidnumber', get_string('idnumbermod'));
@@ -471,8 +511,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 17be668..933e4f1 100644 (file)
@@ -43,16 +43,16 @@ $pageaction = optional_param('action', '', PARAM_ALPHA); // Used to simulate a D
 
 $PAGE->set_url('/course/rest.php', array('courseId'=>$courseid,'class'=>$class));
 
+//NOTE: when making any changes here please make sure it is using the same access control as course/mod.php !!
+
+require_login();
+
 // Authorise the user and verify some incoming data
 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
     error_log('AJAX commands.php: Course does not exist');
     die;
 }
 
-$context = get_context_instance(CONTEXT_COURSE, $course->id);
-require_login($course);
-require_capability('moodle/course:update', $context);
-
 if (empty($CFG->enablecourseajax)) {
     error_log('Course AJAX not allowed');
     die;
@@ -72,22 +72,13 @@ switch($requestmethod) {
 
         switch ($class) {
             case 'block':
-
-                switch ($field) {
-                    case 'visible':
-                        blocks_execute_action($PAGE, $pageblocks, 'toggle', $blockinstance);
-                        break;
-
-                    case 'position':  // Misleading case. Should probably call it 'move'.
-                        // We want to move the block around. This means changing
-                        // the column (position field) and/or block sort order
-                        // (weight field).
-                        blocks_move_block($PAGE, $blockinstance, $column, $value);
-                        break;
-                }
+                // not used any more
                 break;
 
             case 'section':
+                require_login($course);
+                $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+                require_capability('moodle/course:update', $coursecontext);
 
                 if (!$DB->record_exists('course_sections', array('course'=>$course->id, 'section'=>$id))) {
                     error_log('AJAX commands.php: Bad Section ID '.$id);
@@ -111,16 +102,21 @@ switch($requestmethod) {
                     error_log('AJAX commands.php: Bad course module ID '.$id);
                     die;
                 }
+                require_login($course, false, $cm);
+                $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
                 switch ($field) {
                     case 'visible':
+                        require_capability('moodle/course:activityvisibility', $modcontext);
                         set_coursemodule_visible($cm->id, $value);
                         break;
 
                     case 'groupmode':
+                        require_capability('moodle/course:manageactivities', $modcontext);
                         set_coursemodule_groupmode($cm->id, $value);
                         break;
 
                     case 'indentleft':
+                        require_capability('moodle/course:manageactivities', $modcontext);
                         if ($cm->indent > 0) {
                             $cm->indent--;
                             $DB->update_record('course_modules', $cm);
@@ -128,11 +124,13 @@ switch($requestmethod) {
                         break;
 
                     case 'indentright':
+                        require_capability('moodle/course:manageactivities', $modcontext);
                         $cm->indent++;
                         $DB->update_record('course_modules', $cm);
                         break;
 
                     case 'move':
+                        require_capability('moodle/course:manageactivities', $modcontext);
                         if (!$section = $DB->get_record('course_sections', array('course'=>$course->id, 'section'=>$sectionid))) {
                             error_log('AJAX commands.php: Bad section ID '.$sectionid);
                             die;
@@ -158,6 +156,9 @@ switch($requestmethod) {
             case 'course':
                 switch($field) {
                     case 'marker':
+                        require_login($course);
+                        $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+                        require_capability('moodle/course:update', $coursecontext);
                         $newcourse = new stdClass();
                         $newcourse->id = $course->id;
                         $newcourse->marker = $value;
@@ -171,7 +172,7 @@ switch($requestmethod) {
     case 'DELETE':
         switch ($class) {
             case 'block':
-                blocks_execute_action($PAGE, $pageblocks, 'delete', $blockinstance);
+                // not used any more
                 break;
 
             case 'resource':
@@ -179,6 +180,9 @@ switch($requestmethod) {
                     error_log('AJAX rest.php: Bad course module ID '.$id);
                     die;
                 }
+                require_login($course, false, $cm);
+                $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+                require_capability('moodle/course:manageactivities', $modcontext);
                 $modlib = "$CFG->dirroot/mod/$cm->modname/lib.php";
 
                 if (file_exists($modlib)) {
@@ -195,8 +199,6 @@ switch($requestmethod) {
                     die;
                 }
 
-                $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
-
                 // remove all module files in case modules forget to do that
                 $fs = get_file_storage();
                 $fs->delete_area_files($modcontext->id);
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 28ed124..7317094 100644 (file)
     if ($PAGE->user_allowed_editing()) {
         if (($edit == 1) and confirm_sesskey()) {
             $USER->editing = 1;
-            redirect($PAGE->url);
+            // Redirect to site root if Editing is toggled on frontpage
+            if ($course->id == SITEID) {
+                redirect($CFG->wwwroot .'/?redirect=0');
+            } else {
+                redirect($PAGE->url);
+            }
         } else if (($edit == 0) and confirm_sesskey()) {
             $USER->editing = 0;
             if(!empty($USER->activitycopy) && $USER->activitycopycourse == $course->id) {
                 $USER->activitycopy       = false;
                 $USER->activitycopycourse = NULL;
             }
-            redirect($PAGE->url);
+            // Redirect to site root if Editing is toggled on frontpage
+            if ($course->id == SITEID) {
+                redirect($CFG->wwwroot .'/?redirect=0');
+            } else {
+                redirect($PAGE->url);
+            }
         }
 
-        if ($hide && confirm_sesskey()) {
-            set_section_visible($course->id, $hide, '0');
-        }
+        if (has_capability('moodle/course:update', $context)) {
+            if ($hide && confirm_sesskey()) {
+                set_section_visible($course->id, $hide, '0');
+            }
 
-        if ($show && confirm_sesskey()) {
-            set_section_visible($course->id, $show, '1');
-        }
+            if ($show && confirm_sesskey()) {
+                set_section_visible($course->id, $show, '1');
+            }
 
-        if (!empty($section)) {
-            if (!empty($move) and confirm_sesskey()) {
-                if (!move_section($course, $section, $move)) {
-                    echo $OUTPUT->notification('An error occurred while moving a section');
+            if (!empty($section)) {
+                if (!empty($move) and confirm_sesskey()) {
+                    if (!move_section($course, $section, $move)) {
+                        echo $OUTPUT->notification('An error occurred while moving a section');
+                    }
+                    // Clear the navigation cache at this point so that the affects
+                    // are seen immediately on the navigation.
+                    $PAGE->navigation->clear_cache();
                 }
-                // Clear the navigation cache at this point so that the affects
-                // are seen immediately on the navigation.
-                $PAGE->navigation->clear_cache();
             }
         }
     } else {
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 fe5e74b..3f8b2db 100644 (file)
@@ -98,7 +98,6 @@ if ($canconfig and $action and confirm_sesskey()) {
 
             if ($confirm) {
                 $plugin->delete_instance($instance);
-                $context->mark_dirty(); // invalidate all enrol caches
                 redirect($PAGE->url);
             }
 
@@ -113,19 +112,17 @@ if ($canconfig and $action and confirm_sesskey()) {
 
         } else if ($action === 'disable') {
             $instance = $instances[$instanceid];
-            if ($instance->status == ENROL_INSTANCE_ENABLED) {
-                $instance->status = ENROL_INSTANCE_DISABLED;
-                $DB->update_record('enrol', $instance);
-                $context->mark_dirty(); // invalidate all enrol caches
+            $plugin = $plugins[$instance->enrol];
+            if ($instance->status != ENROL_INSTANCE_DISABLED) {
+                $plugin->update_status($instance, ENROL_INSTANCE_DISABLED);
                 redirect($PAGE->url);
             }
 
         } else if ($action === 'enable') {
             $instance = $instances[$instanceid];
-            if ($instance->status == ENROL_INSTANCE_DISABLED) {
-                $instance->status = ENROL_INSTANCE_ENABLED;
-                $DB->update_record('enrol', $instance);
-                $context->mark_dirty(); // invalidate all enrol caches
+            $plugin = $plugins[$instance->enrol];
+            if ($instance->status != ENROL_INSTANCE_ENABLED) {
+                $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
                 redirect($PAGE->url);
             }
         }
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 f1ad0fb..1c7e6da 100644 (file)
@@ -81,8 +81,9 @@ Y.extend(DIALOGUE, Y.Overlay, {
         if (hidden) {
             bb.setStyle('top', '-1000px').removeClass(DIALOGUE_PREFIX+'-hidden');
         }
-        var x = Math.round((bb.get('winWidth') - bb.get('offsetWidth'))/2);
-        var y = Math.round((bb.get('winHeight') - bb.get('offsetHeight'))/2)+Y.one(window).get('scrollTop');
+        var x = Math.max(Math.round((bb.get('winWidth') - bb.get('offsetWidth'))/2), 15);
+        var y = Math.max(Math.round((bb.get('winHeight') - bb.get('offsetHeight'))/2), 15) + Y.one(window).get('scrollTop');
+
         if (hidden) {
             bb.addClass(DIALOGUE_PREFIX+'-hidden');
         }
index 593a250..25071d2 100644 (file)
@@ -626,6 +626,7 @@ class grade_plugin_info {
  * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function
  * @param string  $bodytags Additional attributes that will be added to the <body> tag
  * @param string  $buttons Additional buttons to display on the page
+ * @param boolean $shownavigation should the gradebook navigation drop down (or tabs) be shown?
  *
  * @return string HTML code or nothing if $return == false
  */
@@ -673,7 +674,13 @@ function print_grade_page_head($courseid, $active_type, $active_plugin=null,
         if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN) {
             $returnval .= print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return);
         }
-        $returnval .= $OUTPUT->heading($heading);
+
+        if ($return) {
+            $returnval .= $OUTPUT->heading($heading);
+        } else {
+            echo $OUTPUT->heading($heading);
+        }
+
         if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS) {
             $returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
         }
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 2802c9d..10aeffb 100644 (file)
@@ -109,7 +109,9 @@ if (has_capability('moodle/grade:viewall', $context)) { //Teachers will see all
         while ($userdata = $gui->next_user()) {
             $user = $userdata->user;
             $report = new grade_report_user($courseid, $gpr, $context, $user->id);
-            echo $OUTPUT->heading(get_string('pluginname', 'gradereport_user'). ' - '.fullname($report->user));
+
+            $studentnamelink = html_writer::link(new moodle_url('/user/view.php', array('id' => $report->user->id, 'course' => $courseid)), fullname($report->user));
+            echo $OUTPUT->heading(get_string('pluginname', 'gradereport_user') . ' - ' . $studentnamelink);
 
             if ($report->fill_table()) {
                 echo '<br />'.$report->print_table(true);
@@ -119,7 +121,9 @@ if (has_capability('moodle/grade:viewall', $context)) { //Teachers will see all
         $gui->close();
     } else { // Only show one user's report
         $report = new grade_report_user($courseid, $gpr, $context, $userid);
-        print_grade_page_head($courseid, 'report', 'user', get_string('pluginname', 'gradereport_user'). ' - '.fullname($report->user));
+
+        $studentnamelink = html_writer::link(new moodle_url('/user/view.php', array('id' => $report->user->id, 'course' => $courseid)), fullname($report->user));
+        print_grade_page_head($courseid, 'report', 'user', get_string('pluginname', 'gradereport_user') . ' - ' . $studentnamelink);
         groups_print_course_menu($course, $gpr->get_return_url('index.php?id='.$courseid, array('userid'=>0)));
 
         if ($user_selector) {
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 3591183..cf21dec 100644 (file)
@@ -530,3 +530,4 @@ $string['wwwrootslash'] = 'Detected incorrect $CFG->wwwroot in config.php, it mu
 $string['xmldberror'] = 'XMLDB error!';
 $string['alreadyloggedin'] = 'You are already logged in as {$a}, you need to log out before logging in as different user.';
 $string['youcannotdeletecategory'] = 'You cannot delete category \'{$a}\' because you can neither delete the contents, nor move them elsewhere.';
+$string['protected_cc_not_supported'] = 'Protected cartridges not supported.';
index 75ba827..78e7705 100644 (file)
@@ -160,6 +160,7 @@ $string['dbconnectionerror'] = 'We could not connect to the database you specifi
 $string['dbcreationerror'] = 'Database creation error. Could not create the given database name with the settings provided';
 $string['dbhost'] = 'Host server';
 $string['dbpass'] = 'Password';
+$string['dbport'] = 'Port';
 $string['dbprefix'] = 'Tables prefix';
 $string['dbtype'] = 'Type';
 $string['dbwrongencoding'] = 'The selected database is running under one non-recommended encoding ({$a}). It would be better to use one Unicode (UTF-8) encoded database instead. Anyway, you can bypass this test by selecting the "Skip DB Encoding Test" check below, but you could experience problems in the future.';
index bb9b476..f087849 100644 (file)
@@ -28,6 +28,7 @@ $string['accesslevel'] = 'Access level';
 $string['addhost'] = 'Add host';
 $string['addnewhost'] = 'Add a new host';
 $string['addtoacl'] = 'Add to access control';
+$string['allhosts'] = 'All hosts';
 $string['allhosts_no_options'] = 'No options are available when viewing multiple hosts';
 $string['allow'] = 'Allow';
 $string['applicationtype'] = 'Application type';
@@ -47,6 +48,8 @@ $string['current_transport'] = 'Current transport';
 $string['databaseerror'] = 'Could not write details to the database.';
 $string['deleteaserver'] = 'Deleting a server';
 $string['deletehost'] = 'Delete host';
+$string['deletedhostinfo'] = 'This host has been deleted. If you want to undelete it, switch the deleted status back to \'No\'.';
+$string['deletedhosts'] = 'Deleted hosts: {$a}';
 $string['deletekeycheck'] = 'Are you absolutely sure you want to delete this key?';
 $string['deleteoutoftime'] = 'Your 60-second window for deleting this key has expired. Please start again.';
 $string['deleteuserrecord'] = 'SSO ACL: delete record for user \'{$a->user}\' from {$a->host}.';
@@ -77,7 +80,7 @@ $string['hidelocal'] = 'Hide local users';
 $string['hideremote'] = 'Hide remote users';
 $string['host'] = 'host';
 $string['hostcoursenotfound'] = 'Host or course not found';
-$string['hostdeleted'] = 'Ok - host deleted';
+$string['hostdeleted'] = 'Host deleted';
 $string['hostexists'] = 'A record already exists for a host with that hostname (it may be deleted). <a href="{$a}">click here</a> to edit that record.';
 $string['hostlist'] = 'List of networked hosts';
 $string['hostname'] = 'Hostname';
index 74f84c8..7eb7ce4 100644 (file)
@@ -92,6 +92,7 @@ $string['externalservices'] = 'External services';
 $string['externalserviceusers'] = 'External service users';
 $string['failedtolog'] = 'Failed to log';
 $string['filenameexist'] = 'File name already exists: {$a}';
+$string['forbiddenwsuser'] = 'Can not create token for an unconfirmed, deleted, suspended or guest user.';
 $string['function'] = 'Function';
 $string['functions'] = 'Functions';
 $string['generalstructure'] = 'General structure';
@@ -186,6 +187,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 80ebfc9..b7c0c94 100644 (file)
@@ -725,7 +725,7 @@ function get_user_access_sitewide($userid) {
     if (!empty($CFG->defaultuserroleid)) {
         $syscontext = context_system::instance();
         $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
-        $raparents[$CFG->defaultuserroleid][$syscontext->path] = $syscontext->path;
+        $raparents[$CFG->defaultuserroleid][$syscontext->id] = $syscontext->id;
     }
 
     // load the "default frontpage role"
@@ -733,25 +733,27 @@ function get_user_access_sitewide($userid) {
         $frontpagecontext = context_course::instance(get_site()->id);
         if ($frontpagecontext->path) {
             $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
-            $raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->path] = $frontpagecontext->path;
+            $raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->id] = $frontpagecontext->id;
         }
     }
 
     // preload every assigned role at and above course context
-    $sql = "SELECT ctx.path, ra.roleid
+    $sql = "SELECT ctx.path, ra.roleid, ra.contextid
               FROM {role_assignments} ra
-              JOIN {context} ctx ON ctx.id = ra.contextid
-         LEFT JOIN {context} cctx
-                   ON (cctx.contextlevel = ".CONTEXT_COURSE." AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")
-             WHERE ra.userid = :userid AND cctx.id IS NULL";
-
-
+              JOIN {context} ctx
+                   ON ctx.id = ra.contextid
+         LEFT JOIN {block_instances} bi
+                   ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
+         LEFT JOIN {context} bpctx
+                   ON (bpctx.id = bi.parentcontextid)
+             WHERE ra.userid = :userid
+                   AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")";
     $params = array('userid'=>$userid);
     $rs = $DB->get_recordset_sql($sql, $params);
     foreach ($rs as $ra) {
         // RAs leafs are arrays to support multi-role assignments...
         $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
-        $raparents[$ra->roleid][$ra->path] = $ra->path;
+        $raparents[$ra->roleid][$ra->contextid] = $ra->contextid;
     }
     $rs->close();
 
@@ -761,30 +763,32 @@ function get_user_access_sitewide($userid) {
 
     // now get overrides of interesting roles in all interesting child contexts
     // hopefully we will not run out of SQL limits here,
-    // users would have to have very many roles above course context...
+    // users would have to have very many roles at/above course context...
     $sqls = array();
     $params = array();
 
     static $cp = 0;
-    foreach ($raparents as $roleid=>$paths) {
+    foreach ($raparents as $roleid=>$ras) {
         $cp++;
-        list($paths, $rparams) = $DB->get_in_or_equal($paths, SQL_PARAMS_NAMED, 'p'.$cp.'_');
-        $params = array_merge($params, $rparams);
+        list($sqlcids, $cids) = $DB->get_in_or_equal($ras, SQL_PARAMS_NAMED, 'c'.$cp.'_');
+        $params = array_merge($params, $cids);
         $params['r'.$cp] = $roleid;
         $sqls[] = "(SELECT ctx.path, rc.roleid, rc.capability, rc.permission
                      FROM {role_capabilities} rc
                      JOIN {context} ctx
                           ON (ctx.id = rc.contextid)
-                LEFT JOIN {context} cctx
-                          ON (cctx.contextlevel = ".CONTEXT_COURSE."
-                              AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")
                      JOIN {context} pctx
-                          ON (pctx.path $paths
+                          ON (pctx.id $sqlcids
                               AND (ctx.id = pctx.id
                                    OR ctx.path LIKE ".$DB->sql_concat('pctx.path',"'/%'")."
                                    OR pctx.path LIKE ".$DB->sql_concat('ctx.path',"'/%'")."))
+                LEFT JOIN {block_instances} bi
+                          ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
+                LEFT JOIN {context} bpctx
+                          ON (bpctx.id = bi.parentcontextid)
                     WHERE rc.roleid = :r{$cp}
-                          AND cctx.id IS NULL)";
+                          AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")
+                   )";
     }
 
     // fixed capability order is necessary for rdef dedupe
@@ -1600,7 +1604,9 @@ function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0,
         $timemodified = time();
     }
 
-/// Check for existing entry
+    // Check for existing entry
+    // TODO: Revisit this sql_empty() use once Oracle bindings are improved. MDL-29765
+    $component = ($component === '') ? $DB->sql_empty() : $component;
     $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
 
     if ($ras) {
@@ -1713,6 +1719,10 @@ function role_unassign_all(array $params, $subcontexts = false, $includemanual =
         }
     }
 
+    // TODO: Revisit this sql_empty() use once Oracle bindings are improved. MDL-29765
+    if (isset($params['component'])) {
+        $params['component'] = ($params['component'] === '') ? $DB->sql_empty() : $params['component'];
+    }
     $ras = $DB->get_records('role_assignments', $params);
     foreach($ras as $ra) {
         $DB->delete_records('role_assignments', array('id'=>$ra->id));
@@ -6396,10 +6406,6 @@ class context_module extends context {
 
         $extracaps = array_merge($subcaps, $extracaps);
 
-        // All modules allow viewhiddenactivities. This is so you can hide
-        // the module then override to allow specific roles to see it.
-        // The actual check is in course page so not module-specific
-        $extracaps[] = "moodle/course:viewhiddenactivities";
         list($extra, $params) = $DB->get_in_or_equal(
             $extracaps, SQL_PARAMS_NAMED, 'cap0');
         $extra = "OR name $extra";
@@ -6407,7 +6413,7 @@ class context_module extends context {
         $sql = "SELECT *
                   FROM {capabilities}
                  WHERE (contextlevel = ".CONTEXT_MODULE."
-                       AND component = :component)
+                       AND (component = :component OR component = 'moodle'))
                        $extra";
         $params['component'] = "mod_$module->name";
 
index 0daf295..cac7cec 100644 (file)
@@ -628,6 +628,9 @@ resource_class.prototype.init_buttons = function() {
     var buttons = commandContainer.getElementsByTagName('a');
 
     // Buttons that we might need to add back in.
+    var deletePresent = false;
+    var hideshow = false;
+    var movehandle = false;
     var moveLeft = false;
     var moveRight = false;
     var updateButton = null;
@@ -654,25 +657,34 @@ resource_class.prototype.init_buttons = function() {
             this.groupmode = this.SEPARATEGROUPS;
         } else if (buttons[x].className == 'editing_groupsvisible') {
             this.groupmode = this.VISIBLEGROUPS;
+        } else if (buttons[x].className == 'editing_delete') {
+            deletePresent = true;
+        } else if (buttons[x].className == 'editing_hide') {
+            hideshow = true;
+        } else if (buttons[x].className == 'editing_show') {
+            hideshow = true;
+        } else if (buttons[x].className == 'editing_moveup') {
+            movehandle = true;
+        } else if (buttons[x].className == 'editing_movedown') {
+            movehandle = true;
+        } else if (buttons[x].className == 'editing_move') {
+            movehandle = true;
         }
     }
 
-    if (updateButton == null) {
-        // Update button must always be present.
-        YAHOO.log('Cannot find updateButton for '+this.getEl().id, 'error');
-    }
-
     // Clear all the buttons.
     commandContainer.innerHTML = '';
 
     // Add move-handle for drag and drop.
-    var handleRef = main.mk_button('a', main.portal.icons['move_2d'], main.portal.strings['move'],
-            [['style', 'cursor:move']], [['class', 'iconsmall']]);
+    if (movehandle) {
+        var handleRef = main.mk_button('a', main.portal.icons['move_2d'], main.portal.strings['move'],
+                [['style', 'cursor:move']], [['class', 'iconsmall']]);
 
-    YAHOO.util.Dom.generateId(handleRef, 'sectionHandle');
-    this.handle = handleRef;
-    commandContainer.appendChild(handleRef);
-    this.setHandleElId(this.handle.id);
+        YAHOO.util.Dom.generateId(handleRef, 'sectionHandle');
+        this.handle = handleRef;
+        commandContainer.appendChild(handleRef);
+        this.setHandleElId(this.handle.id);
+    }
 
     // Add indentation buttons if needed (move left, move right).
     if (moveLeft) {
@@ -691,27 +703,33 @@ resource_class.prototype.init_buttons = function() {
         this.indentRightButton = button;
     }
 
-    // Add edit button back in.
-    commandContainer.appendChild(updateButton);
+    if (updateButton) {
+        // Add edit button back in.
+        commandContainer.appendChild(updateButton);
+    }
 
     if (duplicateButton) {
         commandContainer.appendChild(duplicateButton);
     }
 
     // Add the delete button.
-    var button = main.mk_button('a', main.portal.icons['delete'], main.portal.strings['delete'], null, [['class', 'iconsmall']]);
-    YAHOO.util.Event.addListener(button, 'click', this.delete_button, this, true);
-    commandContainer.appendChild(button);
+    if (deletePresent) {
+        var button = main.mk_button('a', main.portal.icons['delete'], main.portal.strings['delete'], null, [['class', 'iconsmall']]);
+        YAHOO.util.Event.addListener(button, 'click', this.delete_button, this, true);
+        commandContainer.appendChild(button);
+    }
 
     // Add the hide or show button.
-    if (this.hidden) {
-        var button = main.mk_button('a', main.portal.icons['show'], main.portal.strings['show'], null, [['class', 'iconsmall']]);
-    } else {
-        var button = main.mk_button('a', main.portal.icons['hide'], main.portal.strings['hide'], null, [['class', 'iconsmall']]);
+    if (hideshow) {
+        if (this.hidden) {
+            var button = main.mk_button('a', main.portal.icons['show'], main.portal.strings['show'], null, [['class', 'iconsmall']]);
+        } else {
+            var button = main.mk_button('a', main.portal.icons['hide'], main.portal.strings['hide'], null, [['class', 'iconsmall']]);
+        }
+        YAHOO.util.Event.addListener(button, 'click', this.toggle_hide, this, true);
+        commandContainer.appendChild(button);
+        this.viewButton = button;
     }
-    YAHOO.util.Event.addListener(button, 'click', this.toggle_hide, this, true);
-    commandContainer.appendChild(button);
-    this.viewButton = button;
 
     // Add the groupmode button if needed.
     if (this.groupmode != null) {
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 75380fe..ae5ca8e 100644 (file)
@@ -856,7 +856,7 @@ $capabilities = array(
         'riskbitmask' => RISK_XSS,
 
         'captype' => 'write',
-        'contextlevel' => CONTEXT_COURSE,
+        'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
             'editingteacher' => CAP_ALLOW,
             'manager' => CAP_ALLOW
@@ -866,7 +866,7 @@ $capabilities = array(
     'moodle/course:activityvisibility' => array(
 
         'captype' => 'write',
-        'contextlevel' => CONTEXT_COURSE,
+        'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
             'editingteacher' => CAP_ALLOW,
             'manager' => CAP_ALLOW
@@ -876,7 +876,7 @@ $capabilities = array(
     'moodle/course:viewhiddenactivities' => array(
 
         'captype' => 'write',
-        'contextlevel' => CONTEXT_COURSE,
+        'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
             'teacher' => CAP_ALLOW,
             'editingteacher' => CAP_ALLOW,
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 8a9fdac..dc937ce 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20111118" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20111214" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="userid"/>
         <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="name"/>
         <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="userid" NEXT="value"/>
-        <FIELD NAME="value" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="name"/>
+        <FIELD NAME="value" TYPE="char" LENGTH="1333" NOTNULL="true" SEQUENCE="false" PREVIOUS="name"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index e540801..caca96a 100644 (file)
@@ -6951,6 +6951,47 @@ 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);
+    }
+
+    if ($oldversion < 2011120500.03) {
+
+        // Changing precision of field value on table user_preferences to (1333)
+        $table = new xmldb_table('user_preferences');
+        $field = new xmldb_field('value', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null, 'name');
+
+        // Launch change of precision for field value
+        $dbman->change_field_precision($table, $field);
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011120500.03);
+    }
+
     return true;
 }
 
index fce8f40..f7c2152 100644 (file)
@@ -919,6 +919,17 @@ class dml_test extends UnitTestCase {
         $this->assertTrue($DB->execute($sql, array('3')));
         $this->assertEqual($DB->count_records($tablename1, array('course' => 6)), 2);
 
+        // update records with subquery condition
+        // confirm that the option not using table aliases is cross-db
+        $sql = "UPDATE {{$tablename1}}
+                   SET course = 0
+                 WHERE NOT EXISTS (
+                           SELECT course
+                             FROM {{$tablename2}} tbl2
+                            WHERE tbl2.course = {{$tablename1}}.course
+                              AND 1 = 0)"; // Really we don't update anything, but verify the syntax is allowed
+        $this->assertTrue($DB->execute($sql));
+
         // insert from one into second table
         $sql = "INSERT INTO {{$tablename2}} (course)
 
index 53c8010..515a4c7 100644 (file)
@@ -1553,6 +1553,26 @@ abstract class enrol_plugin {
         return null;
     }
 
+    /**
+     * Update instance status
+     *
+     * Override when plugin needs to do some action when enabled or disabled.
+     *
+     * @param stdClass $instance
+     * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
+     * @return void
+     */
+    public function update_status($instance, $newstatus) {
+        global $DB;
+
+        $instance->status = $newstatus;
+        $DB->update_record('enrol', $instance);
+
+        // invalidate all enrol caches
+        $context = context_course::instance($instance->courseid);
+        $context->mark_dirty();
+    }
+
     /**
      * Delete course enrol plugin instance, unenrol all users.
      * @param object $instance
@@ -1579,6 +1599,10 @@ abstract class enrol_plugin {
 
         // finally drop the enrol row
         $DB->delete_records('enrol', array('id'=>$instance->id));
+
+        // invalidate all enrol caches
+        $context = context_course::instance($instance->courseid);
+        $context->mark_dirty();
     }
 
     /**
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 3832b50..1f475b3 100644 (file)
@@ -58,7 +58,7 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
     }
 
     function getValue() {
-        return $this->getAttribute('value');
+        return $this->_values;
     }
 
     function getMaxbytes() {
@@ -276,4 +276,13 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
         return $str;
     }
 
+    /**
+     * What to display when element is frozen.
+     *
+     * @return empty string
+     */
+    function getFrozenHtml() {
+
+        return '';
+    }
 }
index 9cc213d..e360b2c 100644 (file)
@@ -543,9 +543,15 @@ class html2text
      */
     function _convert_pre(&$text)
     {
-        while(preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
-            $result = preg_replace($this->pre_search, $this->pre_replace, $matches[1]);
-            $text = preg_replace('/<pre[^>]*>.*<\/pre>/ismU', '<div><br>' . $result . '<br></div>', $text, 1);
+         while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
+            // convert the content
+            $this->pre_content = sprintf('<div><br>%s<br></div>',
+                preg_replace($this->pre_search, $this->pre_replace, $matches[1]));
+            // replace the content (use callback because content can contain $0 variable)
+            $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
+                array('html2text', '_preg_pre_callback'), $text, 1);
+            // free memory
+            $this->pre_content = '';
         }
     }
 
@@ -573,6 +579,17 @@ class html2text
         }
     }
 
+    /**
+     *  Callback function for preg_replace_callback use in PRE content handler.
+     *
+     *  @param  array PREG matches
+     *  @return string
+     */
+    private function _preg_pre_callback($matches)
+    {
+        return $this->pre_content;
+    }
+
     /**
      *  Strtoupper multibyte wrapper function
      *
index d6bba11..61b4b44 100644 (file)
@@ -1652,6 +1652,9 @@ function set_user_preference($name, $value, $user = null) {
         throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
     }
     $value = (string)$value;
+    if (textlib::strlen($value) > 1333) { //value column maximum length is 1333 characters
+        throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
+    }
 
     if (is_null($user)) {
         $user = $USER;
@@ -2760,7 +2763,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;
 
@@ -7213,7 +7220,7 @@ class emoticon_manager {
  * @return string The now encrypted data
  */
 function rc4encrypt($data) {
-    $password = 'nfgjeingjk';
+    $password = get_site_identifier();
     return endecrypt($password, $data, '');
 }
 
@@ -7226,7 +7233,7 @@ function rc4encrypt($data) {
  * @return string The now decrypted data
  */
 function rc4decrypt($data) {
-    $password = 'nfgjeingjk';
+    $password = get_site_identifier();
     return endecrypt($password, $data, 'de');
 }
 
@@ -7766,35 +7773,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 +7833,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..f9d82a1 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) {
@@ -1481,6 +1481,14 @@ function question_get_all_capabilities() {
     return $caps;
 }
 
+
+/**
+ * Tracks all the contexts related to the one where we are currently editing
+ * questions, and provides helper methods to check permissions.
+ *
+ * @copyright 2007 Jamie Pratt me@jamiep.org
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class question_edit_contexts {
 
     public static $caps = array(
@@ -1507,28 +1515,27 @@ class question_edit_contexts {
     protected $allcontexts;
 
     /**
-     * @param current context
+     * Constructor
+     * @param context the current context.
      */
-    public function __construct($thiscontext) {
-        $pcontextids = get_parent_contexts($thiscontext);
-        $contexts = array($thiscontext);
-        foreach ($pcontextids as $pcontextid) {
-            $contexts[] = get_context_instance_by_id($pcontextid);
-        }
-        $this->allcontexts = $contexts;
+    public function __construct(context $thiscontext) {
+        $this->allcontexts = array_values($thiscontext->get_parent_contexts(true));
     }
+
     /**
      * @return array all parent contexts
      */
     public function all() {
         return $this->allcontexts;
     }
+
     /**
      * @return object lowest context which must be either the module or course context
      */
     public function lowest() {
         return $this->allcontexts[0];
     }
+
     /**
      * @param string $cap capability
      * @return array parent contexts having capability, zero based index
@@ -1542,6 +1549,7 @@ class question_edit_contexts {
         }
         return $contextswithcap;
     }
+
     /**
      * @param array $caps capabilities
      * @return array parent contexts having at least one of $caps, zero based index
@@ -1558,6 +1566,7 @@ class question_edit_contexts {
         }
         return $contextswithacap;
     }
+
     /**
      * @param string $tabname edit tab name
      * @return array parent contexts having at least one of $caps, zero based index
@@ -1565,6 +1574,7 @@ class question_edit_contexts {
     public function having_one_edit_tab_cap($tabname) {
         return $this->having_one_cap(self::$caps[$tabname]);
     }
+
     /**
      * Has at least one parent context got the cap $cap?
      *
@@ -1635,11 +1645,9 @@ 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 +1661,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);
+}
 
-    $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
+/**
+ * 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) {
 
-    if (!empty($ids)) {
-        $baseurl .= (implode('/', $ids) . '/');
-    }
+    return file_rewrite_pluginfile_urls($questiontext, 'pluginfile.php', $contextid,
+            'question', 'questiontext_preview', "$component/$questionid", $options);
+}
 
-    if ($itemid !== null) {
-        $baseurl .= "$itemid/";
-    }
+/**
+ * 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;
+
+    $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 +1739,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 +1838,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 b92e2c0..a60788c 100644 (file)
@@ -115,7 +115,7 @@ class accesslib_test extends UnitTestCaseUsingDatabase {
         $tablenames = array('config', 'config_plugins', 'modules', 'course', 'course_modules', 'course_sections', 'course_categories', 'mnet_host', 'mnet_application',
                 'capabilities', 'context', 'context_temp', 'role', 'role_capabilities', 'role_allow_switch', 'license', 'my_pages', 'block', 'block_instances', 'block_positions',
                 'role_allow_assign', 'role_allow_override', 'role_assignments', 'role_context_levels' ,'enrol', 'user_enrolments', 'filter_active', 'filter_config', 'comments',
-                'user', 'groups_members', 'cache_flags', 'events_handlers', 'user_lastaccess', 'rating', 'files', 'role_names', 'user_preferences');
+                'user', 'groups_members', 'cache_flags', 'events_handlers', 'user_lastaccess', 'rating', 'files', 'role_names', 'user_preferences', 'grading_areas');
         $this->create_test_tables($tablenames, 'lib');
 
         // Create all core default records and default settings
index d007054..066624e 100644 (file)
@@ -1164,6 +1164,22 @@ class moodlelib_test extends UnitTestCase {
         unset_user_preference('_test_user_preferences_pref');
         $this->assertTrue(!isset($USER->preference['_test_user_preferences_pref']));
 
+        // Test 1333 char values (no need for unicode, there are already tests for that in DB tests)
+        $longvalue = str_repeat('a', 1333);
+        set_user_preference('_test_long_user_preference', $longvalue);
+        $this->assertEqual($longvalue, get_user_preferences('_test_long_user_preference'));
+        $this->assertEqual($longvalue,
+                $DB->get_field('user_preferences', 'value', array('userid' => $USER->id, 'name' => '_test_long_user_preference')));
+
+        // Test > 1333 char values, coding_exception expected
+        $longvalue = str_repeat('a', 1334);
+        try {
+            set_user_preference('_test_long_user_preference', $longvalue);
+            $this->assertFail('Exception expected - longer than 1333 chars not allowed as preference value');
+        } catch (Exception $e) {
+            $this->assertTrue($e instanceof coding_exception);
+        }
+
         //test invalid params
         try {
             set_user_preference('_test_user_preferences_pref', array());
index cf2c84b..27359ce 100644 (file)
@@ -139,6 +139,27 @@ class web_test extends UnitTestCase {
         $this->assertIdentical('0', html_to_text('0'));
     }
 
+    public function test_html_to_text_pre_parsing_problem() {
+        $strorig = 'Consider the following function:<br /><pre><span style="color: rgb(153, 51, 102);">void FillMeUp(char* in_string) {'.
+                   '<br />  int i = 0;<br />  while (in_string[i] != \'\0\') {<br />    in_string[i] = \'X\';<br />    i++;<br />  }<br />'.
+                   '}</span></pre>What would happen if a non-terminated string were input to this function?<br /><br />';
+
+        $strconv = 'Consider the following function:
+
+void FillMeUp(char* in_string) {
+ int i = 0;
+ while (in_string[i] != \'\0\') {
+ in_string[i] = \'X\';
+ i++;
+ }
+}
+What would happen if a non-terminated string were input to this function?
+
+';
+
+        $this->assertIdentical($strconv, html_to_text($strorig));
+    }
+
     public function test_clean_text() {
         $text = "lala <applet>xx</applet>";
         $this->assertEqual($text, clean_text($text, FORMAT_PLAIN));
similarity index 95%
rename from lib/zend/Zend/Validate/Barcode/UpcA.php
rename to lib/zend/Zend/Validate/Barcode/Upca.php
index 4165528..ab425b1 100644 (file)
@@ -16,7 +16,7 @@
  * @package    Zend_Validate
  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
- * @version    $Id$
+ * @version    $Id: Upca.php 20096 2010-01-06 02:05:09Z bkarwin $
  */
 
 /**
@@ -49,4 +49,4 @@ class Zend_Validate_Barcode_Upca extends Zend_Validate_Barcode_AdapterAbstract
      * @var string
      */
     protected $_checksum = '_gtin';
-}
+}
\ No newline at end of file
index 5c4fe00..ef52efc 100644 (file)
@@ -8,4 +8,5 @@ Changes:
 * lots of files removed
 * small fix to error reporting in reflection (MDL-21460, ZF-8980)
 * SOAP and XMLRPC servers overwrite the fault() functions
+* synced and renamed file to version in ZF 1.10.6 (MDL-30603, ZF-11080)
 
index 1e6324d..074b86a 100644 (file)
@@ -645,36 +645,28 @@ function mnet_profile_field_options() {
 
 
 /**
- * Return information about all the current hosts
- * This is basically just a resultset.
+ * Returns information about MNet peers
  *
+ * @param bool $withdeleted should the deleted peers be returned too
  * @return array
  */
-function mnet_get_hosts() {
+function mnet_get_hosts($withdeleted = false) {
     global $CFG, $DB;
-    return $DB->get_records_sql('  SELECT
-                                    h.id,
-                                    h.wwwroot,
-                                    h.ip_address,
-                                    h.name,
-                                    h.public_key,
-                                    h.public_key_expires,
-                                    h.transport,
-                                    h.portno,
-                                    h.last_connect_time,
-                                    h.last_log_id,
-                                    h.applicationid,
-                                    a.name as app_name,
-                                    a.display_name as app_display_name,
-                                    a.xmlrpc_server_url
-                                FROM
-                                    {mnet_host} h,
-                                    {mnet_application} a
-                                WHERE
-                                    h.id <> ? AND
-                                    h.deleted = 0 AND
-                                    h.applicationid=a.id',
-                        array($CFG->mnet_localhost_id));
+
+    $sql = "SELECT h.id, h.deleted, h.wwwroot, h.ip_address, h.name, h.public_key, h.public_key_expires,
+                   h.transport, h.portno, h.last_connect_time, h.last_log_id, h.applicationid,
+                   a.name as app_name, a.display_name as app_display_name, a.xmlrpc_server_url
+              FROM {mnet_host} h
+              JOIN {mnet_application} a ON h.applicationid = a.id
+             WHERE h.id <> ?";
+
+    if (!$withdeleted) {
+        $sql .= "  AND h.deleted = 0";
+    }
+
+    $sql .= " ORDER BY h.deleted, h.name, h.id";
+
+    return $DB->get_records_sql($sql, array($CFG->mnet_localhost_id));
 }
 
 
index 6e24785..140ed75 100644 (file)
@@ -102,29 +102,44 @@ class assignment_upload extends assignment_base {
 
 
     function view_feedback($submission=NULL) {
-        global $USER, $CFG, $DB, $OUTPUT;
+        global $USER, $CFG, $DB, $OUTPUT, $PAGE;
         require_once($CFG->libdir.'/gradelib.php');
+        require_once("$CFG->dirroot/grade/grading/lib.php");
 
         if (!$submission) { /// Get submission for this assignment
-            $submission = $this->get_submission($USER->id);
+            $userid = $USER->id;
+            $submission = $this->get_submission($userid);
+        } else {
+            $userid = $submission->userid;
         }
 
         if (empty($submission->timemarked)) {   /// Nothing to show, so print nothing
             return;
         }
+        // Check the user can submit
+        $canviewfeedback = ($userid == $USER->id && has_capability('mod/assignment:submit', $this->context, $USER->id, false));
+        // If not then check if the user still has the view cap and has a previous submission
+        $canviewfeedback = $canviewfeedback || (!empty($submission) && $submission->userid == $USER->id && has_capability('mod/assignment:view', $this->context));
+        // Or if user can grade (is a teacher or admin)
+        $canviewfeedback = $canviewfeedback || has_capability('mod/assignment:grade', $this->context);
+
+        if (!$canviewfeedback) {
+            // can not view or submit assignments -> no feedback
+            return;
+        }
 
-        $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $USER->id);
+        $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
         $item = $grading_info->items[0];
-        $grade = $item->grades[$USER->id];
+        $grade = $item->grades[$userid];
 
         if ($grade->hidden or $grade->grade === false) { // hidden or error
             return;
         }
 
         if ($grade->grade === null and empty($grade->str_feedback)) {   // No grade to show yet
-            if ($this->count_responsefiles($USER->id)) {   // but possibly response files are present
+            if ($this->count_responsefiles($userid)) {   // but possibly response files are present
                 echo $OUTPUT->heading(get_string('responsefiles', 'assignment'), 3);
-                $responsefiles = $this->print_responsefiles($USER->id, true);
+                $responsefiles = $this->print_responsefiles($userid, true);
                 echo $OUTPUT->box($responsefiles, 'generalbox boxaligncenter');
             }
             return;
@@ -158,12 +173,14 @@ class assignment_upload extends assignment_base {
         echo '<tr>';
         echo '<td class="left side">&nbsp;</td>';
         echo '<td class="content">';
-        if ($this->assignment->grade) {
-            echo '<div class="grade">';
-            echo get_string("grade").': '.$grade->str_long_grade;
-            echo '</div>';
-            echo '<div class="clearer"></div>';
+        $gradestr = '<div class="grade">'. get_string("grade").': '.$grade->str_long_grade. '</div>';
+        if (!empty($submission) && $controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
+            $controller->set_grade_range(make_grades_menu($this->assignment->grade));
+            echo $controller->render_grade($PAGE, $submission->id, $item, $gradestr, has_capability('mod/assignment:grade', $this->context));
+        } else {
+            echo $gradestr;
         }
+        echo '<div class="clearer"></div>';
 
         echo '<div class="comment">';
         echo $grade->str_feedback;
@@ -173,7 +190,7 @@ class assignment_upload extends assignment_base {
         echo '<tr>';
         echo '<td class="left side">&nbsp;</td>';
         echo '<td class="content">';
-        echo $this->print_responsefiles($USER->id, true);
+        echo $this->print_responsefiles($userid, true);
         echo '</tr>';
 
         echo '</table>';
@@ -1051,8 +1068,8 @@ class assignment_upload extends assignment_base {
             $editable = false;
         }
 
-        // If the user has submitted something add a bit more stuff
-        if ($submission) {
+        // If the user has submitted something add some related links and data
+        if (isset($submission->data2) AND $submission->data2 == 'submitted') {
             // Add a view link to the settings nav
             $link = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
             $node->add(get_string('viewmysubmission', 'assignment'), $link, navigation_node::TYPE_SETTING);
index d0278ac..03f91af 100644 (file)
@@ -318,8 +318,8 @@ class assignment_uploadsingle extends assignment_base {
             $editable = false;
         }
 
-        // If the user has submitted something add a bit more stuff
-        if ($submission) {
+        // If the user has submitted something add some related links and data
+        if (isset($submission->numfiles) AND $submission->numfiles) {
             // Add a view link to the settings nav
             $link = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
             $node->add(get_string('viewmysubmission', 'assignment'), $link, navigation_node::TYPE_SETTING);
index 197d3c2..f3f2f82 100644 (file)
@@ -599,6 +599,8 @@ if ($fromform = $mform_post->get_data()) {
     // WARNING: the $fromform->message array has been overwritten, do not use it anymore!
     $fromform->messagetrust  = trusttext_trusted($modcontext);
 
+    $contextcheck = isset($fromform->groupinfo) && has_capability('mod/forum:movediscussions', $modcontext);
+
     if ($fromform->edit) {           // Updating a post
         unset($fromform->groupid);
         $fromform->id = $fromform->edit;
@@ -621,6 +623,11 @@ if ($fromform = $mform_post->get_data()) {
             print_error('cannotupdatepost', 'forum');
         }
 
+        // If the user has access to all groups and they are changing the group, then update the post.
+        if ($contextcheck) {
+            $DB->set_field('forum_discussions' ,'groupid' , $fromform->groupinfo, array('firstpost' => $fromform->id));
+        }
+
         $updatepost = $fromform; //realpost
         $updatepost->forum = $forum->id;
         if (!forum_update_post($updatepost, $mform_post, $message)) {
@@ -712,6 +719,10 @@ if ($fromform = $mform_post->get_data()) {
         if (!forum_user_can_post_discussion($forum, $fromform->groupid, -1, $cm, $modcontext)) {
             print_error('cannotcreatediscussion', 'forum');
         }
+        // If the user has access all groups capability let them choose the group.
+        if ($contextcheck) {
+            $fromform->groupid = $fromform->groupinfo;
+        }
         if (empty($fromform->groupid)) {
             $fromform->groupid = -1;
         }
index b26deaf..e3caa53 100644 (file)
@@ -112,15 +112,26 @@ class mod_forum_post_form extends moodleform {
         }
 
         if (groups_get_activity_groupmode($cm, $course)) { // hack alert
-            if (empty($post->groupid)) {
-                $groupname = get_string('allparticipants');
+            $groupdata = groups_get_activity_allowed_groups($cm);
+            $groupcount = count($groupdata);
+            $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+            $contextcheck = has_capability('mod/forum:movediscussions', $modulecontext) && empty($post->parent) && $groupcount > 1;
+            if ($contextcheck) {
+                $groupinfo = array('0' => get_string('allparticipants'));
+                foreach ($groupdata as $grouptemp) {
+                    $groupinfo[$grouptemp->id] = $grouptemp->name;
+                }
+                $mform->addElement('select','groupinfo', get_string('group'), $groupinfo);
+                $mform->setDefault('groupinfo', $post->groupid);
             } else {
-                $group = groups_get_group($post->groupid);
-                $groupname = format_string($group->name);
+                if (empty($post->groupid)) {
+                    $groupname = get_string('allparticipants');
+                } else {
+                    $groupname = format_string($groupdata[$post->groupid]->name);
+                }
+                $mform->addElement('static', 'groupinfo', get_string('group'), $groupname);
             }
-            $mform->addElement('static', 'groupinfo', get_string('group'), $groupname);
         }
-
         //-------------------------------------------------------------------------------
         // buttons
         if (isset($post->edit)) { // hack alert
index 719cfa0..274dfa7 100644 (file)
@@ -66,6 +66,11 @@ class restore_glossary_activity_structure_step extends restore_activity_structur
         if (!in_array($data->displayformat, $formats)) {
             $data->displayformat = 'dictionary';
         }
+        if (!empty($data->mainglossary) and $data->mainglossary == 1 and
+            $DB->record_exists('glossary', array('mainglossary' => 1, 'course' => $this->get_courseid()))) {
+            // Only allow one main glossary in the course
+            $data->mainglossary = 0;
+        }
 
         // insert the glossary record
         $newitemid = $DB->insert_record('glossary', $data);
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 c5fe160..148fe8e 100644 (file)
@@ -177,6 +177,8 @@ class lesson_page_type_essay extends lesson_page {
         $answers = $this->get_answers();
         $formattextdefoptions = new stdClass;
         $formattextdefoptions->para = false;  //I'll use it widely in this page
+        $formattextdefoptions->context = $answerpage->context;
+
         foreach ($answers as $answer) {
             if ($useranswer != NULL) {
                 $essayinfo = unserialize($useranswer->useranswer);
@@ -216,7 +218,7 @@ class lesson_page_type_essay extends lesson_page {
                 // dont think this should ever be reached....
                 $avescore = get_string("nooneansweredthisquestion", "lesson");
             }
-            $answerdata->answers[] = array(s($essayinfo->answer), $avescore);
+            $answerdata->answers[] = array(format_text($essayinfo->answer, FORMAT_MOODLE, $formattextdefoptions), $avescore);
             $answerpage->answerdata = $answerdata;
         }
         return $answerpage;
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 9a867e1..f2d1e05 100644 (file)
@@ -32,7 +32,7 @@ $pageid = optional_param('pageid', NULL, PARAM_INT);    // Lesson Page ID
 $action = optional_param('action', 'reportoverview', PARAM_ALPHA);  // action to take
 $nothingtodisplay = false;
 
-$cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);;
+$cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);
 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 $lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST));
 
@@ -455,6 +455,7 @@ if ($action === 'delete') {
 
         $answerpage->qtype = $qtypes[$page->qtype].$page->option_description_string();
         $answerpage->grayout = $page->grayout;
+        $answerpage->context = $context;
 
         if (empty($userid)) {
             // there is no userid, so set these vars and display stats.
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 73a7640..257208e 100644 (file)
@@ -356,11 +356,19 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
         //move to the end of the selected page
         $pagebreakpositions = array_keys($questions, 0);
         $numpages = count($pagebreakpositions);
+
         // Ensure the target page number is in range.
-        $moveselectedonpage = max(1, min($moveselectedonpage, $pagebreakpositions));
+        for ($i = $moveselectedonpage; $i > $numpages; $i--) {
+            $questions[] = 0;
+            $pagebreakpositions[] = count($questions) - 1;
+        }
         $moveselectedpos = $pagebreakpositions[$moveselectedonpage - 1];
+
+        // Do the move.
         array_splice($questions, $moveselectedpos, 0, $selectedquestionids);
         $quiz->questions = implode(',', $questions);
+
+        // Update the database.
         $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
         $deletepreviews = true;
     }
index 920d099..608e38e 100644 (file)
@@ -1106,6 +1106,10 @@ class quiz_question_bank_view extends question_bank_view {
                 'editaction', 'previewaction');
     }
 
+    protected function default_sort() {
+        return array('qtype' => 1, 'questionnametext' => 1);
+    }
+
     /**
      * Let the question bank display know whether the quiz has been attempted,
      * hence whether some bits of UI, like the add this question to the quiz icon,
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..554bd91 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'),
@@ -116,7 +121,7 @@ abstract class quiz_attempt_report extends quiz_default_report {
     protected function validate_common_options(&$attemptsmode, &$pagesize, $course, $currentgroup) {
         if ($currentgroup) {
             //default for when a group is selected
-            if ($attemptsmode === null  || $attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) {
+            if ($attemptsmode === null || $attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) {
                 $attemptsmode = QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH;
             }
         } else if (!$currentgroup && $course->id == SITEID) {
@@ -132,94 +137,6 @@ abstract class quiz_attempt_report extends quiz_default_report {
         }
     }
 
-    /**
-     * Contruct all the parts of the main database query.
-     * @param object $quiz the quiz settings.
-     * @param string $qmsubselect SQL fragment from {@link quiz_report_qm_filter_select()}.
-     * @param bool $qmfilter whether to show all, or only the final grade attempt.
-     * @param int $attemptsmode which attempts to show.
-     *      One of the QUIZ_REPORT_ATTEMPTS_... constants.
-     * @param array $reportstudents list if userids of users to include in the report.
-     * @return array with 4 elements ($fields, $from, $where, $params) that can be used to
-     *      build the actual database query.
-     */
-    protected function base_sql($quiz, $qmsubselect, $qmfilter, $attemptsmode, $reportstudents) {
-        global $DB;
-
-        $fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') . ' AS uniqueid,';
-
-        if ($qmsubselect) {
-            $fields .= "\n(CASE WHEN $qmsubselect THEN 1 ELSE 0 END) AS gradedattempt,";
-        }
-
-        $extrafields = get_extra_user_fields_sql($this->context, 'u', '',
-                array('id', 'idnumber', 'firstname', 'lastname', 'picture',
-                'imagealt', 'institution', 'department', 'email'));
-        $fields .= '
-                quiza.uniqueid AS usageid,
-                quiza.id AS attempt,
-                u.id AS userid,
-                u.idnumber,
-                u.firstname,
-                u.lastname,
-                u.picture,
-                u.imagealt,
-                u.institution,
-                u.department,
-                u.email' . $extrafields . ',
-                quiza.sumgrades,
-                quiza.timefinish,
-                quiza.timestart,
-                CASE WHEN quiza.timefinish = 0 THEN null
-                     WHEN quiza.timefinish > quiza.timestart THEN quiza.timefinish - quiza.timestart
-                     ELSE 0 END AS duration';
-            // To explain that last bit, in MySQL, qa.timestart and qa.timefinish
-            // are unsigned. Since MySQL 5.5.5, when they introduced strict mode,
-            // subtracting a larger unsigned int from a smaller one gave an error.
-            // Therefore, we avoid doing that. timefinish can be non-zero and less
-            // than timestart when you have two load-balanced servers with very
-            // badly synchronised clocks, and a student does a really quick attempt.';
-
-        // This part is the same for all cases - join users and quiz_attempts tables
-        $from = "\n{user} u";
-        $from .= "\nLEFT JOIN {quiz_attempts} quiza ON
-                                    quiza.userid = u.id AND quiza.quiz = :quizid";
-        $params = array('quizid' => $quiz->id);
-
-        if ($qmsubselect && $qmfilter) {
-            $from .= " AND $qmsubselect";
-        }
-        switch ($attemptsmode) {
-            case QUIZ_REPORT_ATTEMPTS_ALL:
-                // Show all attempts, including students who are no longer in the course
-                $where = 'quiza.id IS NOT NULL AND quiza.preview = 0';
-                break;
-            case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH:
-                // Show only students with attempts
-                list($usql, $uparams) = $DB->get_in_or_equal(
-                        $reportstudents, SQL_PARAMS_NAMED, 'u');
-                $params += $uparams;
-                $where = "u.id $usql AND quiza.preview = 0 AND quiza.id IS NOT NULL";
-                break;
-            case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
-                // Show only students without attempts
-                list($usql, $uparams) = $DB->get_in_or_equal(
-                        $reportstudents, SQL_PARAMS_NAMED, 'u');
-                $params += $uparams;
-                $where = "u.id $usql AND quiza.id IS NULL";
-                break;
-            case QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS:
-                // Show all students with or without attempts
-                list($usql, $uparams) = $DB->get_in_or_equal(
-                        $reportstudents, SQL_PARAMS_NAMED, 'u');
-                $params += $uparams;
-                $where = "u.id $usql AND (quiza.preview = 0 OR quiza.preview IS NULL)";
-                break;
-        }
-
-        return array($fields, $from, $where, $params);
-    }
-
     /**
      * Add all the user-related columns to the $columns and $headers arrays.
      * @param table_sql $table the table being constructed.
@@ -300,15 +217,16 @@ abstract class quiz_attempt_report extends quiz_default_report {
      * @param object $quiz the quiz settings.
      * @param array $columns the list of columns. Added to.
      * @param array $headers the columns headings. Added to.
+     * @param bool $includefeedback whether to include the feedbacktext columns
      */
-    protected function add_grade_columns($quiz, &$columns, &$headers) {
+    protected function add_grade_columns($quiz, &$columns, &$headers, $includefeedback = true) {
         if ($this->should_show_grades($quiz)) {
             $columns[] = 'sumgrades';
             $headers[] = get_string('grade', 'quiz') . '/' .
                     quiz_format_grade($quiz, $quiz->grade);
         }
 
-        if (quiz_has_feedback($quiz)) {
+        if ($includefeedback && quiz_has_feedback($quiz)) {
             $columns[] = 'feedbacktext';
             $headers[] = get_string('feedback', 'quiz');
         }
@@ -395,17 +313,22 @@ abstract class quiz_attempt_report_table extends table_sql {
     protected $quiz;
     protected $context;
     protected $qmsubselect;
+    protected $qmfilter;
+    protected $attemptsmode;
     protected $groupstudents;
     protected $students;
     protected $questions;
     protected $includecheckboxes;
 
-    public function __construct($uniqueid, $quiz, $context, $qmsubselect, $groupstudents,
-            $students, $questions, $includecheckboxes, $reporturl, $displayoptions) {
+    public function __construct($uniqueid, $quiz, $context, $qmsubselect, $qmfilter,
+            $attemptsmode, $groupstudents, $students, $questions, $includecheckboxes,
+            $reporturl, $displayoptions) {
         parent::__construct($uniqueid);
         $this->quiz = $quiz;
         $this->context = $context;
         $this->qmsubselect = $qmsubselect;
+        $this->qmfilter = $qmfilter;
+        $this->attemptsmode = $attemptsmode;
         $this->groupstudents = $groupstudents;
         $this->students = $students;
         $this->questions = $questions;
@@ -603,6 +526,89 @@ abstract class quiz_attempt_report_table extends table_sql {
         return '';
     }
 
+    /**
+     * Contruct all the parts of the main database query.
+     * @param array $reportstudents list if userids of users to include in the report.
+     * @return array with 4 elements ($fields, $from, $where, $params) that can be used to
+     *      build the actual database query.
+     */
+    public function base_sql($reportstudents) {
+        global $DB;
+
+        $fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') . ' AS uniqueid,';
+
+        if ($this->qmsubselect) {
+            $fields .= "\n(CASE WHEN $this->qmsubselect THEN 1 ELSE 0 END) AS gradedattempt,";
+        }
+
+        $extrafields = get_extra_user_fields_sql($this->context, 'u', '',
+                array('id', 'idnumber', 'firstname', 'lastname', 'picture',
+                'imagealt', 'institution', 'department', 'email'));
+        $fields .= '
+                quiza.uniqueid AS usageid,
+                quiza.id AS attempt,
+                u.id AS userid,
+                u.idnumber,
+                u.firstname,
+                u.lastname,
+                u.picture,
+                u.imagealt,
+                u.institution,
+                u.department,
+                u.email' . $extrafields . ',
+                quiza.sumgrades,
+                quiza.timefinish,
+                quiza.timestart,
+                CASE WHEN quiza.timefinish = 0 THEN null
+                     WHEN quiza.timefinish > quiza.timestart THEN quiza.timefinish - quiza.timestart
+                     ELSE 0 END AS duration';
+            // To explain that last bit, in MySQL, qa.timestart and qa.timefinish
+            // are unsigned. Since MySQL 5.5.5, when they introduced strict mode,
+            // subtracting a larger unsigned int from a smaller one gave an error.
+            // Therefore, we avoid doing that. timefinish can be non-zero and less
+            // than timestart when you have two load-balanced servers with very
+            // badly synchronised clocks, and a student does a really quick attempt.';
+
+        // This part is the same for all cases - join users and quiz_attempts tables
+        $from = "\n{user} u";
+        $from .= "\nLEFT JOIN {quiz_attempts} quiza ON
+                                    quiza.userid = u.id AND quiza.quiz = :quizid";
+        $params = array('quizid' => $this->quiz->id);
+
+        if ($this->qmsubselect && $this->qmfilter) {
+            $from .= " AND $this->qmsubselect";
+        }
+        switch ($this->attemptsmode) {
+            case QUIZ_REPORT_ATTEMPTS_ALL:
+                // Show all attempts, including students who are no longer in the course
+                $where = 'quiza.id IS NOT NULL AND quiza.preview = 0';
+                break;
+            case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH:
+                // Show only students with attempts
+                list($usql, $uparams) = $DB->get_in_or_equal(
+                        $reportstudents, SQL_PARAMS_NAMED, 'u');
+                $params += $uparams;
+                $where = "u.id $usql AND quiza.preview = 0 AND quiza.id IS NOT NULL";
+                break;
+            case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
+                // Show only students without attempts
+                list($usql, $uparams) = $DB->get_in_or_equal(
+                        $reportstudents, SQL_PARAMS_NAMED, 'u');
+                $params += $uparams;
+                $where = "u.id $usql AND quiza.id IS NULL";
+                break;
+            case QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS:
+                // Show all students with or without attempts
+                list($usql, $uparams) = $DB->get_in_or_equal(
+                        $reportstudents, SQL_PARAMS_NAMED, 'u');
+                $params += $uparams;
+                $where = "u.id $usql AND (quiza.preview = 0 OR quiza.preview IS NULL)";
+                break;
+        }
+
+        return array($fields, $from, $where, $params);
+    }
+
     /**
      * Add the information about the latest state of the question with slot
      * $slot to the query.
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 7efa3d8..fd55a97 100644 (file)
@@ -37,86 +37,102 @@ class quiz_report_overview_table extends quiz_attempt_report_table {
 
     protected $regradedqs = array();
 
-    public function __construct($quiz, $context, $qmsubselect, $groupstudents,
-            $students, $detailedmarks, $questions, $includecheckboxes, $reporturl, $displayoptions) {
+    public function __construct($quiz, $context, $qmsubselect, $qmfilter,
+            $attemptsmode, $groupstudents, $students, $detailedmarks,
+            $questions, $includecheckboxes, $reporturl, $displayoptions) {
         parent::__construct('mod-quiz-report-overview-report', $quiz , $context,
-                $qmsubselect, $groupstudents, $students, $questions, $includecheckboxes,
-                $reporturl, $displayoptions);
+                $qmsubselect, $qmfilter, $attemptsmode, $groupstudents, $students,
+                $questions, $includecheckboxes, $reporturl, $displayoptions);
         $this->detailedmarks = $detailedmarks;
     }
 
     public function build_table() {
         global $DB;
 
-        if ($this->rawdata) {
-            $this->strtimeformat = str_replace(',', '', get_string('strftimedatetime'));
-            parent::build_table();
-
-            //end of adding data from attempts data to table / download
-            //now add averages at bottom of table :
-            $params = array($this->quiz->id);
-            $averagesql = '
-                    SELECT AVG(qg.grade) AS grade, COUNT(qg.grade) AS numaveraged
-                    FROM {quiz_grades} qg
-                    WHERE quiz = ?';
-
-            $this->add_separator();
-            if ($this->is_downloading()) {
-                $namekey = 'lastname';
-            } else {
-                $namekey = 'fullname';
-            }
-            if ($this->groupstudents) {
-                list($usql, $uparams) = $DB->get_in_or_equal($this->groupstudents);
-                $record = $DB->get_record_sql($averagesql . ' AND qg.userid ' . $usql,
-                        array_merge($params, $uparams));
-                $groupaveragerow = array(
-                        $namekey => get_string('groupavg', 'grades'),
-                        'sumgrades' => $this->format_average($record),
-                        'feedbacktext'=> strip_tags(quiz_report_feedback_for_grade(
-                                            $record->grade, $this->quiz->id, $this->context)));
-                if ($this->detailedmarks && ($this->quiz->attempts == 1 || $this->qmsubselect)) {
-                    $avggradebyq = $this->load_average_question_grades($this->groupstudents);
-                    $groupaveragerow += $this->format_average_grade_for_questions($avggradebyq);
-                }
-                $this->add_data_keyed($groupaveragerow);
-            }
+        if (!$this->rawdata) {
+            return;
+        }
 
-            if ($this->students) {
-                list($usql, $uparams) = $DB->get_in_or_equal($this->students);
-                $record = $DB->get_record_sql($averagesql . ' AND qg.userid ' . $usql,
-                        array_merge($params, $uparams));
-                $overallaveragerow = array(
-                        $namekey => get_string('overallaverage', 'grades'),
-                        'sumgrades' => $this->format_average($record),
-                        'feedbacktext'=> strip_tags(quiz_report_feedback_for_grade(
-                                            $record->grade, $this->quiz->id, $this->context)));
-                if ($this->detailedmarks && ($this->quiz->attempts == 1 || $this->qmsubselect)) {
-                    $avggradebyq = $this->load_average_question_grades($this->students);
-                    $overallaveragerow += $this->format_average_grade_for_questions($avggradebyq);
-                }
-                $this->add_data_keyed($overallaveragerow);
-            }
+        $this->strtimeformat = str_replace(',', '', get_string('strftimedatetime'));
+        parent::build_table();
+
+        // End of adding the data from attempts. Now add averages at bottom.
+        $this->add_separator();
+
+        if ($this->groupstudents) {
+            $this->add_average_row(get_string('groupavg', 'grades'), $this->groupstudents);
+        }
+
+        if ($this->students) {
+            $this->add_average_row(get_string('overallaverage', 'grades'), $this->students);
         }
     }
 
+    /**
+     * Add an average grade over the attempts of a set of users.
+     * @param string $label the title ot use for this row.
+     * @param array $users the users to average over.
+     */
+    protected function add_average_row($label, $users) {
+        global $DB;
+
+        list($fields, $from, $where, $params) = $this->base_sql($users);
+        $record = $DB->get_record_sql("
+                SELECT AVG(quiza.sumgrades) AS grade, COUNT(quiza.sumgrades) AS numaveraged
+                  FROM $from
+                 WHERE $where", $params);
+        $record->grade = quiz_rescale_grade($record->grade, $this->quiz);
+
+        if ($this->is_downloading()) {
+            $namekey = 'lastname';
+        } else {
+            $namekey = 'fullname';
+        }
+        $averagerow = array(
+            $namekey    => $label,
+            'sumgrades' => $this->format_average($record),
+            'feedbacktext'=> strip_tags(quiz_report_feedback_for_grade(
+                                        $record->grade, $this->quiz->id, $this->context))
+        );
+
+        if ($this->detailedmarks) {
+            $dm = new question_engine_data_mapper();
+            $qubaids = new qubaid_join($from, 'quiza.uniqueid', $where, $params);
+            $avggradebyq = $dm->load_average_marks($qubaids, array_keys($this->questions));
+
+            $averagerow += $this->format_average_grade_for_questions($avggradebyq);
+        }
+
+        $this->add_data_keyed($averagerow);
+    }
+
+    /**
+     * Helper userd by {@link add_average_row()}.
+     * @param array $gradeaverages the raw grades.
+     * @return array the (partial) row of data.
+     */
     protected function format_average_grade_for_questions($gradeaverages) {
         $row = array();
+
         if (!$gradeaverages) {
             $gradeaverages = array();
         }
+
         foreach ($this->questions as $question) {
             if (isset($gradeaverages[$question->slot]) && $question->maxmark > 0) {
                 $record = $gradeaverages[$question->slot];
                 $record->grade = quiz_rescale_grade(
                         $record->averagefraction * $question->maxmark, $this->quiz, false);
+
             } else {
                 $record = new stdClass();
                 $record->grade = null;
                 $record->numaveraged = null;
             }
+
             $row['qsgrade' . $question->slot] = $this->format_average($record, true);
         }
+
         return $row;
     }
 
@@ -276,30 +292,6 @@ class quiz_report_overview_table extends quiz_attempt_report_table {
         }
     }
 
-    /**
-     * Load the average grade for each question, averaged over particular users.
-     * @param array $userids the user ids to average over.
-     */
-    protected function load_average_question_grades($userids) {
-        global $DB;
-
-        $qmfilter = '';
-        if ($this->quiz->attempts != 1) {
-            $qmfilter = '(' . quiz_report_qm_filter_select($this->quiz, 'quiza') . ') AND ';
-        }
-
-        list($usql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'u');
-        $params['quizid'] = $this->quiz->id;
-        $qubaids = new qubaid_join(
-                '{quiz_attempts} quiza',
-                'quiza.uniqueid',
-                "quiza.userid $usql AND quiza.quiz = :quizid",
-                $params);
-
-        $dm = new question_engine_data_mapper();
-        return $dm->load_average_marks($qubaids, array_keys($this->questions));
-    }
-
     /**
      * Get all the questions in all the attempts being displayed that need regrading.
      * @return array A two dimensional array $questionusageid => $slot => $regradeinfo.
index e863e45..c7e2681 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;
@@ -129,8 +129,8 @@ class quiz_overview_report extends quiz_attempt_report {
         $questions = quiz_report_get_significant_questions($quiz);
 
         $table = new quiz_report_overview_table($quiz, $this->context, $qmsubselect,
-                $groupstudents, $students, $detailedmarks, $questions, $includecheckboxes,
-                $reporturl, $displayoptions);
+                $qmfilter, $attemptsmode, $groupstudents, $students, $detailedmarks,
+                $questions, $includecheckboxes, $reporturl, $displayoptions);
         $filename = quiz_report_download_filename(get_string('overviewfilename', 'quiz_overview'),
                 $courseshortname, $quiz->name);
         $table->is_downloading($download, $filename,
@@ -219,8 +219,7 @@ class quiz_overview_report extends quiz_attempt_report {
                     "END) AS gradedattempt, ";
             }
 
-            list($fields, $from, $where, $params) =
-                    $this->base_sql($quiz, $qmsubselect, $qmfilter, $attemptsmode, $allowed);
+            list($fields, $from, $where, $params) = $table->base_sql($allowed);
 
             $table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params);
 
@@ -321,7 +320,7 @@ class quiz_overview_report extends quiz_attempt_report {
                 $headers[] = get_string('regrade', 'quiz_overview');
             }
 
-            $this->add_grade_columns($quiz, $columns, $headers);
+            $this->add_grade_columns($quiz, $columns, $headers, false);
 
             $this->set_up_table_columns(
                     $table, $columns, $headers, $reporturl, $displayoptions, false);
index 9f9857e..5282d3c 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;
@@ -146,7 +146,8 @@ class quiz_responses_report extends quiz_attempt_report {
                 array('context' => $displaycoursecontext));
 
         $table = new quiz_report_responses_table($quiz, $this->context, $qmsubselect,
-                $groupstudents, $students, $questions, $includecheckboxes, $reporturl, $displayoptions);
+                $qmfilter, $attemptsmode, $groupstudents, $students, $questions,
+                $includecheckboxes, $reporturl, $displayoptions);
         $filename = quiz_report_download_filename(get_string('responsesfilename', 'quiz_responses'),
                 $courseshortname, $quiz->name);
         $table->is_downloading($download, $filename,
@@ -197,8 +198,7 @@ class quiz_responses_report extends quiz_attempt_report {
                 }
             }
 
-            list($fields, $from, $where, $params) =
-                    $this->base_sql($quiz, $qmsubselect, $qmfilter, $attemptsmode, $allowed);
+            list($fields, $from, $where, $params) = $table->base_sql($allowed);
 
             $table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params);
 
index cfb4633..cb2a48d 100644 (file)
@@ -35,11 +35,12 @@ defined('MOODLE_INTERNAL') || die();
  */
 class quiz_report_responses_table extends quiz_attempt_report_table {
 
-    public function __construct($quiz, $context, $qmsubselect, $groupstudents,
-            $students, $questions, $includecheckboxes, $reporturl, $displayoptions) {
+    public function __construct($quiz, $context, $qmsubselect, $qmfilter,
+            $attemptsmode, $groupstudents, $students,
+            $questions, $includecheckboxes, $reporturl, $displayoptions) {
         parent::__construct('mod-quiz-report-responses-report', $quiz, $context,
-                $qmsubselect, $groupstudents, $students, $questions, $includecheckboxes,
-                $reporturl, $displayoptions);
+                $qmsubselect, $qmfilter, $attemptsmode, $groupstudents, $students,
+                $questions, $includecheckboxes, $reporturl, $displayoptions);
     }
 
     public function build_table() {
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..ab134da 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');
     }
 
@@ -647,7 +668,7 @@ class quiz_statistics_report extends quiz_default_report {
             $firstattempts = new stdClass();
             $firstattempts->countrecs = 0;
             $firstattempts->total = 0;
-            $firstattempts->average = '-';
+            $firstattempts->average = null;
         }
 
         $allattempts = new stdClass();
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 b91e3eb..b191990 100644 (file)
@@ -76,6 +76,12 @@ $capabilities = array(
             'editingteacher' => CAP_ALLOW,
             'manager' => CAP_ALLOW
         )
+    ),
+    'mod/scorm:deleteownresponses' => array(
+
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array()
     )
 );
 
index 5ca3aa8..00f9205 100644 (file)
@@ -73,6 +73,7 @@ $string['defaultgradesettings'] = 'Default grade settings';
 $string['defaultothersettings'] = 'Other default settings';
 $string['deleteattemptcheck'] = 'Are you absolutely sure you want to completely delete these attempts?';
 $string['deleteallattempts'] = 'Delete all SCORM attempts';
+$string['deleteuserattemptcheck'] = 'Are you absolutely sure you want to completely delete all your attempts?';
 $string['details'] = 'Track details';
 $string['directories'] = 'Show the directory links';
 $string['disabled'] = 'Disabled';
@@ -260,6 +261,7 @@ $string['scormloggingoff'] = 'API logging is off';
 $string['scormloggingon'] = 'API logging is on';
 $string['scormopen'] = 'Open';
 $string['scormresponsedeleted'] = 'Deleted user attempts';
+$string['scorm:deleteownresponses'] = 'Delete own attempts';
 $string['scorm:savetrack'] = 'Save tracks';
 $string['scorm:skipview'] = 'Skip overview';
 $string['scormtype'] = 'Type';
index 727b872..613266d 100644 (file)
@@ -1031,8 +1031,8 @@ function scorm_element_cmp($a, $b) {
  * @param object $scorm a moodle scrom object - mdl_scorm
  * @return string - Attempt status string
  */
-function scorm_get_attempt_status($user, $scorm) {
-    global $DB;
+function scorm_get_attempt_status($user, $scorm, $cm='') {
+    global $DB, $PAGE, $OUTPUT;
 
     $attempts = scorm_get_attempt_count($user->id, $scorm, true);
     if (empty($attempts)) {
@@ -1108,6 +1108,17 @@ function scorm_get_attempt_status($user, $scorm) {
     if ($attemptcount >= $scorm->maxattempt and $scorm->maxattempt > 0) {
         $result .= '<p><font color="#cc0000">'.get_string('exceededmaxattempts', 'scorm').'</font></p>';
     }
+    if (!empty($cm)) {
+        $context = context_module::instance($cm->id);
+        if (has_capability('mod/scorm:deleteownresponses', $context) &&
+            $DB->record_exists('scorm_scoes_track', array('userid' => $user->id, 'scormid' => $scorm->id))) {
+            //check to see if any data is stored for this user:
+            $deleteurl = new moodle_url($PAGE->url, array('action'=>'delete', 'sesskey' => sesskey()));
+            $result .= $OUTPUT->single_button($deleteurl, get_string('deleteallattempts', 'scorm'));
+        }
+    }
+
+
     return $result;
 }
 
index c66b908..374b9b4 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version   = 2011112900;       // The current module version (Date: YYYYMMDDXX)
+$module->version   = 2011112901;       // The current module version (Date: YYYYMMDDXX)
 $module->requires  = 2011112900;       // Requires this Moodle version
 $module->component = 'mod_scorm'; // Full name of the plugin (used for diagnostics)
 $module->cron      = 300;
index 2b7775d..787b8b1 100644 (file)
@@ -20,6 +20,7 @@ require_once($CFG->dirroot.'/mod/scorm/locallib.php');
 $id = optional_param('id', '', PARAM_INT);       // Course Module ID, or
 $a = optional_param('a', '', PARAM_INT);         // scorm ID
 $organization = optional_param('organization', '', PARAM_INT); // organization ID
+$action = optional_param('action', '', PARAM_ALPHA);
 
 if (!empty($id)) {
     if (! $cm = get_coursemodule_from_id('scorm', $id)) {
@@ -83,6 +84,20 @@ $PAGE->set_title($pagetitle);
 $PAGE->set_heading($course->fullname);
 echo $OUTPUT->header();
 
+if (!empty($action) && confirm_sesskey() && has_capability('mod/scorm:deleteownresponses', $contextmodule)) {
+    if ($action == 'delete') {
+        $confirmurl = new moodle_url($PAGE->url, array('action'=>'deleteconfirm'));
+        echo $OUTPUT->confirm(get_string('deleteuserattemptcheck', 'scorm'), $confirmurl, $PAGE->url);
+        echo $OUTPUT->footer();
+        exit;
+    } else if ($action == 'deleteconfirm') {
+        //delete this users attempts.
+        $DB->delete_records('scorm_scoes_track', array('userid' => $USER->id, 'scormid' => $scorm->id));
+        scorm_update_grades($scorm, $USER->id, true);
+        echo $OUTPUT->notification(get_string('scormresponsedeleted', 'scorm'), 'notifysuccess');
+    }
+}
+
 $currenttab = 'info';
 require($CFG->dirroot . '/mod/scorm/tabs.php');
 
@@ -90,7 +105,7 @@ require($CFG->dirroot . '/mod/scorm/tabs.php');
 echo $OUTPUT->heading(format_string($scorm->name));
 $attemptstatus = '';
 if ($scorm->displayattemptstatus == 1) {
-    $attemptstatus = scorm_get_attempt_status($USER, $scorm);
+    $attemptstatus = scorm_get_attempt_status($USER, $scorm, $cm);
 }
 echo $OUTPUT->box(format_module_intro('scorm', $scorm, $cm->id).$attemptstatus, 'generalbox boxaligncenter boxwidthwide', 'intro');
 
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..3501a44 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');
     }
 }
 
@@ -959,7 +965,7 @@ class question_bank_view {
     }
 
     /**
-     * Deal with a sort name of the forum columnname, or colname_subsort by
+     * Deal with a sort name of the form columnname, or colname_subsort by
      * breaking it up, validating the bits that are presend, and returning them.
      * If there is no subsort, then $subsort is returned as ''.
      * @return array array($colname, $subsort).
@@ -1096,14 +1102,14 @@ class question_bank_view {
         $sorts = array();
         foreach ($this->sort as $sort => $order) {
             list($colname, $subsort) = $this->parse_subsort($sort);
-            $sorts[] = $this->knowncolumntypes[$colname]->sort_expression($order < 0, $subsort);
+            $sorts[] = $this->requiredcolumns[$colname]->sort_expression($order < 0, $subsort);
         }
 
     /// 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 5ccc2c1..5a3118b 100644 (file)
@@ -42,6 +42,9 @@ require_once(dirname(__FILE__) . '/../type/questiontypebase.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 abstract class question_bank {
+    // TODO: This limit can be deleted if someday we move all TEXTS to BIG ones. MDL-19603
+    const MAX_SUMMARY_LENGTH = 32000;
+
     /** @var array question type name => question_type subclass. */
     private static $questiontypes = array();
 
index 2f41b12..751a90b 100644 (file)
@@ -89,6 +89,11 @@ class question_engine_data_mapper {
         $record->minfraction = $qa->get_min_fraction();
         $record->flagged = $qa->is_flagged();
         $record->questionsummary = $qa->get_question_summary();
+        if (textlib::strlen($record->questionsummary) > question_bank::MAX_SUMMARY_LENGTH) {
+            // It seems some people write very long quesions! MDL-30760
+            $record->questionsummary = textlib::substr($record->questionsummary,
+                    0, question_bank::MAX_SUMMARY_LENGTH - 3) . '...';
+        }
         $record->rightanswer = $qa->get_right_answer_summary();
         $record->responsesummary = $qa->get_response_summary();
         $record->timemodified = time();
@@ -851,7 +856,11 @@ ORDER BY
      * @return string SQL code for the subquery.
      */
     public function sum_usage_marks_subquery($qubaid) {
-        return "SELECT SUM(qa.maxmark * qas.fraction)
+        // To explain the COALESCE in the following SQL: SUM(lots of NULLs) gives
+        // NULL, while SUM(one 0.0 and lots of NULLS) gives 0.0. We don't want that.
+        // We always want to return a number, so the COALESCE is there to turn the
+        // NULL total into a 0.
+        return "SELECT COALESCE(SUM(qa.maxmark * qas.fraction), 0)
             FROM {question_attempts} qa
             JOIN {question_attempt_steps} qas ON qas.id = (
                 SELECT MAX(summarks_qas.id)
index 6f0aac8..7e682f0 100644 (file)
@@ -245,6 +245,11 @@ class question_engine_attempt_upgrader {
             $qa = $qas[$questionid];
             $qa->questionusageid = $attempt->uniqueid;
             $qa->slot = $i;
+            if (textlib::strlen($qa->questionsummary) > question_bank::MAX_SUMMARY_LENGTH) {
+                // It seems some people write very long quesions! MDL-30760
+                $qa->questionsummary = textlib::substr($qa->questionsummary,
+                        0, question_bank::MAX_SUMMARY_LENGTH - 3) . '...';
+            }
             $this->insert_record('question_attempts', $qa);
             $layout[$questionkeys[$questionid]] = $qa->slot;
 
index 32f36c1..c9cb5df 100644 (file)
@@ -404,7 +404,7 @@ function qtype_multianswer_extract_question($text) {
                     && preg_match('~'.NUMERICAL_ALTERNATIVE_REGEX.'~',
                             $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER], $numregs)) {
                 $wrapped->answer[] = $numregs[NUMERICAL_CORRECT_ANSWER];
-                if ($numregs[NUMERICAL_ABS_ERROR_MARGIN]) {
+                if (array_key_exists(NUMERICAL_ABS_ERROR_MARGIN, $numregs)) {
                     $wrapped->tolerance["$answerindex"] =
                     $numregs[NUMERICAL_ABS_ERROR_MARGIN];
                 } else {
index b80f299..bb1a459 100644 (file)
@@ -50,6 +50,21 @@ class moodle1_qtype_multichoice_handler extends moodle1_qtype_handler {
         }
 
         // convert and write the multichoice
+        if (!isset($data['multichoice'])) {
+            // This should never happen, but it can do if the 1.9 site contained
+            // corrupt data/
+            $data['multichoice'] = array(array(
+                'single'                         => 1,
+                'shuffleanswers'                 => 1,
+            &nbs