Merged branch 'MDL-29277' of git://github.com/mouneyrac/moodle.git with improvements
authorSam Hemelryk <sam@moodle.com>
Mon, 17 Oct 2011 00:18:30 +0000 (13:18 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 17 Oct 2011 00:18:30 +0000 (13:18 +1300)
50 files changed:
admin/settings/appearance.php
admin/settings/development.php
course/editcategory.php
course/editcategory_form.php
course/user.php
enrol/cohort/yui/quickenrolment/quickenrolment.js
index.php
lang/en/admin.php
lang/en/moodle.php
lib/adminlib.php
lib/componentlib.class.php
lib/db/install.xml
lib/db/upgrade.php
lib/externallib.php
lib/filestorage/file_storage.php
lib/googleapi.php
lib/moodlelib.php
lib/navigationlib.php
lib/setuplib.php
lib/simpletest/testpagelib_moodlepage.php
lib/xmldb/xmldb_key.php
mod/assignment/lib.php
mod/assignment/type/upload/assignment.class.php
mod/choice/renderer.php
mod/feedback/delete_template.php
mod/feedback/edit.php
mod/feedback/edit_form.php
mod/feedback/item/label/lib.php
mod/feedback/lib.php
mod/feedback/version.php
mod/quiz/lib.php
mod/url/db/install.xml
mod/url/db/upgrade.php
mod/url/lang/en/url.php
mod/url/mod_form.php
mod/url/version.php
mod/wiki/pagelib.php
question/behaviour/adaptive/behaviour.php
question/behaviour/adaptive/renderer.php
question/behaviour/adaptive/simpletest/testwalkthrough.php
question/behaviour/adaptivenopenalty/simpletest/testwalkthrough.php
question/engine/datalib.php
question/engine/questionattempt.php
question/type/multianswer/lang/en/qtype_multianswer.php
question/type/numerical/question.php
theme/formal_white/style/core.css
theme/yui_combo.php
user/edit.php
version.php
webservice/soap/locallib.php

index 36a287b..1646cab 100644 (file)
@@ -93,6 +93,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configtext('navcourselimit',get_string('navcourselimit','admin'),get_string('confignavcourselimit', 'admin'),20,PARAM_INT));
     $temp->add(new admin_setting_configcheckbox('navlinkcoursesections', get_string('navlinkcoursesections', 'admin'), get_string('navlinkcoursesections_help', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('usesitenameforsitepages', get_string('usesitenameforsitepages', 'admin'), get_string('configusesitenameforsitepages', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('navadduserpostslinks', get_string('navadduserpostslinks', 'admin'), get_string('navadduserpostslinks_help', 'admin'), 1));
 
     $ADMIN->add('appearance', $temp);
 
index 609fadb..192df5b 100644 (file)
@@ -26,7 +26,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('xmlstrictheaders', get_string('xmlstrictheaders', 'admin'), get_string('configxmlstrictheaders', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('debugsmtp', get_string('debugsmtp', 'admin'), get_string('configdebugsmtp', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('perfdebug', get_string('perfdebug', 'admin'), get_string('configperfdebug', 'admin'), '7', '15', '7'));
-    $temp->add(new admin_setting_configcheckbox('debugstringids', get_string('debugstringids', 'admin'), get_string('configdebugstringids', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('debugstringids', get_string('debugstringids', 'admin'), get_string('debugstringids_desc', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('debugvalidators', get_string('debugvalidators', 'admin'), get_string('configdebugvalidators', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('debugpageinfo', get_string('debugpageinfo', 'admin'), get_string('configdebugpageinfo', 'admin'), 0));
     $ADMIN->add('development', $temp);
index ceffc63..9752cce 100644 (file)
@@ -74,6 +74,7 @@ if ($mform->is_cancelled()) {
 } else if ($data = $mform->get_data()) {
     $newcategory = new stdClass();
     $newcategory->name = $data->name;
+    $newcategory->idnumber = $data->idnumber;
     $newcategory->description_editor = $data->description_editor;
     $newcategory->parent = $data->parent; // if $data->parent = 0, the new category will be a top-level category
 
index a708a02..3e3cfe3 100644 (file)
@@ -35,6 +35,8 @@ class editcategory_form extends moodleform {
         $mform->addElement('select', 'parent', get_string('parentcategory'), $options);
         $mform->addElement('text', 'name', get_string('categoryname'), array('size'=>'30'));
         $mform->addRule('name', get_string('required'), 'required', null);
+        $mform->addElement('text', 'idnumber', get_string('idnumbercoursecategory'),'maxlength="100"  size="10"');
+        $mform->addHelpButton('idnumber', 'idnumbercoursecategory');
         $mform->addElement('editor', 'description_editor', get_string('description'), null, $editoroptions);
         $mform->setType('description_editor', PARAM_RAW);
         if (!empty($CFG->allowcategorythemes)) {
@@ -54,5 +56,19 @@ class editcategory_form extends moodleform {
 
         $this->add_action_buttons(true, $strsubmit);
     }
+
+    function validation($data, $files) {
+        global $DB;
+        $errors = parent::validation($data, $files);
+        if (!empty($data['idnumber'])) {
+            if ($existing = $DB->get_record('course_categories', array('idnumber' => $data['idnumber']))) {
+                if (!$data['id'] || $existing->id != $data['id']) {
+                    $errors['idnumber']= get_string('idnumbertaken');
+                }
+            }
+        }
+
+        return $errors;
+    }
 }
 
index 1796f08..21af6d7 100644 (file)
@@ -270,6 +270,7 @@ switch ($mode) {
     case "complete" :
         get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
         $sections = get_all_sections($course->id);
+        $itemsprinted = false;
 
         for ($i=0; $i<=$course->numsections; $i++) {
 
@@ -279,8 +280,10 @@ switch ($mode) {
                 $showsection = (has_capability('moodle/course:viewhiddensections', $coursecontext) or $section->visible or !$course->hiddensections);
 
                 if ($showsection) { // prevent hidden sections in user activity. Thanks to Geoff Wilbert!
-
+                    // Check the section has a sequence. This is the sequence of modules/resources.
+                    // If there is no sequence there is nothing to display.
                     if ($section->sequence) {
+                        $itemsprinted = true;
                         echo '<div class="section">';
                         echo '<h2>';
                         echo get_section_name($course, $section);
@@ -352,6 +355,11 @@ switch ($mode) {
                 }
             }
         }
+
+        if (!$itemsprinted) {
+            echo $OUTPUT->notification(get_string('nothingtodisplay'));
+        }
+
         break;
     case "coursecompletion":
     case "coursecompletions":
index 004acf1..6607566 100644 (file)
@@ -122,11 +122,7 @@ YUI.add('moodle-enrol_cohort-quickenrolment', function(Y) {
                     break;
                 case 'defaultcohortroleloaded':
                     defaultrole = this.get(DEFAULTCOHORTROLE);
-                    panel.get('contentBox').one('.'+CSS.PANELROLES).all('option').each(function(){
-                        if (this.get('value')==defaultrole) {
-                            this.setAttribute('selected', true);
-                        }
-                    });
+                    panel.get('contentBox').one('.'+CSS.PANELROLES+' select').set('value', defaultrole);
                     break;
             }
         },
@@ -339,4 +335,4 @@ YUI.add('moodle-enrol_cohort-quickenrolment', function(Y) {
         }
     }
 
-}, '@VERSION@', {requires:['base','node', 'overlay', 'io-base', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key', 'moodle-enrol-notification']});
\ No newline at end of file
+}, '@VERSION@', {requires:['base','node', 'overlay', 'io-base', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key', 'moodle-enrol-notification']});
index aff7187..d6424a5 100644 (file)
--- a/index.php
+++ b/index.php
         user_accesstime_log();
     }
 
+    $hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
+
     $PAGE->set_url('/');
     $PAGE->set_course($SITE);
 
 /// If the site is currently under maintenance, then print a message
-    if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
+    if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) {
         print_maintenance_message();
     }
 
-    if (has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
-        if (moodle_needs_upgrading()) {
-            redirect($CFG->wwwroot .'/'. $CFG->admin .'/index.php');
-        }
-    } else if (get_home_page() != HOMEPAGE_SITE) {
+    if ($hassiteconfig && moodle_needs_upgrading()) {
+        redirect($CFG->wwwroot .'/'. $CFG->admin .'/index.php');
+    }
+
+    if (get_home_page() != HOMEPAGE_SITE) {
         // Redirect logged-in users to My Moodle overview if required
         if (optional_param('setdefaulthome', false, PARAM_BOOL)) {
             set_user_preference('user_home_page_preference', HOMEPAGE_SITE);
-        } else if ($CFG->defaulthomepage == HOMEPAGE_MY && optional_param('redirect', true, PARAM_BOOL)) {
+        } else if ($CFG->defaulthomepage == HOMEPAGE_MY && (optional_param('redirect', true, PARAM_BOOL) || !$hassiteconfig)) {
             redirect($CFG->wwwroot .'/my/');
         } else if (!empty($CFG->defaulthomepage) && $CFG->defaulthomepage == HOMEPAGE_USER) {
             $PAGE->settingsnav->get('usercurrentsettings')->add(get_string('makethismyhome'), new moodle_url('/', array('setdefaulthome'=>true)), navigation_node::TYPE_SETTING);
             break;
 
             case FRONTPAGECOURSELIST:
-                if (isloggedin() and !has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)) and !isguestuser() and empty($CFG->disablemycourses)) {
+                if (isloggedin() and !$hassiteconfig and !isguestuser() and empty($CFG->disablemycourses)) {
                     echo html_writer::tag('a', get_string('skipa', 'access', moodle_strtolower(get_string('mycourses'))), array('href'=>'#skipmycourses', 'class'=>'skip-block'));
                     echo $OUTPUT->heading(get_string('mycourses'), 2, 'headingblock header');
                     print_my_moodle();
                     echo html_writer::tag('span', '', array('class'=>'skip-block-to', 'id'=>'skipmycourses'));
-                } else if ((!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)) and !isguestuser()) or ($DB->count_records('course') <= FRONTPAGECOURSELIMIT)) {
+                } else if ((!$hassiteconfig and !isguestuser()) or ($DB->count_records('course') <= FRONTPAGECOURSELIMIT)) {
                     // admin should not see list of courses when there are too many of them
                     echo html_writer::tag('a', get_string('skipa', 'access', moodle_strtolower(get_string('availablecourses'))), array('href'=>'#skipavailablecourses', 'class'=>'skip-block'));
                     echo $OUTPUT->heading(get_string('availablecourses'), 2, 'headingblock header');
index 683da5c..3a0ce0b 100644 (file)
@@ -168,7 +168,6 @@ $string['configdebug'] = 'If you turn this on, then PHP\'s error_reporting will
 $string['configdebugdisplay'] = 'Set to on, the error reporting will go to the HTML page. This is practical, but breaks XHTML, JS, cookies and HTTP headers in general. Set to off, it will send the output to your server logs, allowing better debugging. The PHP setting error_log controls which log this goes to.';
 $string['configdebugpageinfo'] = 'Enable if you want page information printed in page footer.';
 $string['configdebugsmtp'] = 'Enable verbose debug information during sending of email messages to SMTP server.';
-$string['configdebugstringids'] = 'This option is designed to help translators. It shows the language file and string id beside each string that is output. (Changing this setting will only take effect on the next page load.)';
 $string['configdebugvalidators'] = 'Enable if you want to have links to external validator servers in page footer. You may need to create new user with username <em>w3cvalidator</em>, and enable guest access. These changes may allow unauthorized access to server, do not enable on production sites!';
 $string['configdefaultallowedmodules'] = 'For the courses which fall into the above category, which modules do you want to allow by default <b>when the course is created</b>?';
 $string['configdefaulthomepage'] = 'This determines the home page for logged in users';
@@ -403,6 +402,7 @@ $string['debugnormal'] = 'NORMAL: Show errors, warnings and notices';
 $string['debugpageinfo'] = 'Show page information';
 $string['debugsmtp'] = 'Debug email sending';
 $string['debugstringids'] = 'Show origin of languages strings';
+$string['debugstringids_desc'] = 'This option is designed to help translators. When this option is enabled, if you add the parameter strings=1 to a request URL, it will show the language file and string id beside each string that is output.';
 $string['debugvalidators'] = 'Show validator links';
 $string['defaultallowedmodules'] = 'Default allowed modules';
 $string['defaultcity'] = 'Default city';
@@ -684,6 +684,8 @@ $string['myprofile'] = 'Default profile page';
 $string['mypagelocked'] = 'Lock default page';
 $string['mysql416bypassed'] = 'However, if your site is using iso-8859-1 (latin) languages ONLY, you may continue using your currently installed MySQL 4.1.12 (or higher).';
 $string['mysql416required'] = 'MySQL 4.1.16 is the minimum version required for Moodle 1.6 in order to guarantee that all data can be converted to UTF-8 in the future.';
+$string['navadduserpostslinks'] = 'Add links to view user posts';
+$string['navadduserpostslinks_help'] = 'If enabled two links will be added to each user in the navigation to view discussions the user has started and posts the user has made in forums throughout the site or in specific courses.';
 $string['navigationupgrade'] = 'This upgrade introduces two new navigation blocks that will replace these blocks: Administration, Courses, Activities and Participants.  If you had set any special permissions on those blocks you should check to make sure everything is behaving as you want it.';
 $string['navcourselimit'] = 'Course limit';
 $string['navlinkcoursesections'] = 'Link course sections';
index a51008a..dc1bc8f 100644 (file)
@@ -216,6 +216,8 @@ $string['categorydeleted'] = 'The category \'{$a}\' was deleted';
 $string['categoryduplicate'] = 'A category named \'{$a}\' already exists!';
 $string['categorymodifiedcancel'] = 'Category was modified! Please cancel and try again.';
 $string['categoryname'] = 'Category name';
+$string['idnumbercoursecategory'] = 'Category ID number';
+$string['idnumbercoursecategory_help'] = 'The ID number of a course category  is only used when matching the category against external systems and is not displayed anywhere on the site. If the category has an official code name it may be entered, otherwise the field can be left blank.';
 $string['categoryupdated'] = 'The category \'{$a}\' was updated';
 $string['city'] = 'City/town';
 $string['clambroken'] = 'Your administrator has enabled virus checking for file uploads but has misconfigured something.<br />Your file upload was NOT successful. Your administrator has been emailed to notify them so they can fix it.<br />Maybe try uploading this file later.';
index d4adbe6..68c1761 100644 (file)
@@ -7517,7 +7517,7 @@ class admin_setting_managewebservicetokens extends admin_setting {
         //TODO: in order to let the administrator delete obsolete token, split this request in multiple request or use LEFT JOIN
 
         //here retrieve token list (including linked users firstname/lastname and linked services name)
-        $sql = "SELECT t.id, t.token, u.id AS userid, u.firstname, u.lastname, s.name, t.validuntil, s.id AS serviceid
+        $sql = "SELECT t.id, t.token, u.id AS userid, u.firstname, u.lastname, s.name, t.iprestriction, t.validuntil, s.id AS serviceid
                   FROM {external_tokens} t, {user} u, {external_services} s
                  WHERE t.creatorid=? AND t.tokentype = ? AND s.id = t.externalserviceid AND t.userid = u.id";
         $tokens = $DB->get_records_sql($sql, array($USER->id, EXTERNAL_TOKEN_PERMANENT));
index 0bb7c71..efddc9d 100644 (file)
@@ -592,7 +592,7 @@ class lang_installer {
         global $CFG;
 
         $this->set_queue($langcode);
-        $this->version = '2.1';
+        $this->version = '2.2';
 
         if (!empty($CFG->langotherroot) and $CFG->langotherroot !== $CFG->dataroot . '/lang') {
             debugging('The in-built language pack installer does not support alternative location ' .
index c98f888..e05f3a6 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20110926" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20111007" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
     <TABLE NAME="course_categories" COMMENT="Course categories" PREVIOUS="course" NEXT="course_completion_aggr_methd">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="name"/>
-        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="id" NEXT="description"/>
-        <FIELD NAME="description" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" PREVIOUS="name" NEXT="descriptionformat"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="id" NEXT="idnumber"/>
+        <FIELD NAME="idnumber" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" PREVIOUS="name" NEXT="description"/>
+        <FIELD NAME="description" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" PREVIOUS="idnumber" NEXT="descriptionformat"/>
         <FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="description" NEXT="parent"/>
         <FIELD NAME="parent" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="descriptionformat" NEXT="sortorder"/>
         <FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="parent" NEXT="coursecount"/>
index 8ab21df..ad4056b 100644 (file)
@@ -6778,6 +6778,20 @@ FROM
         upgrade_main_savepoint(true, 2011092800.03);
     }
 
+    if ($oldversion < 2011100700.02) {
+        // Define field idnumber to be added to course_categories
+        $table = new xmldb_table('course_categories');
+        $field = new xmldb_field('idnumber', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'name');
+
+        // Conditionally launch add field idnumber
+        if (!$dbman->field_exists($table,$field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011100700.02);
+    }
+
     return true;
 }
 
index eb6a996..c1f4fcd 100644 (file)
@@ -194,12 +194,7 @@ class external_api {
                 unset($params[$key]);
             }
             if (!empty($params)) {
-                //list all unexpected keys
-                $keys = '';
-                foreach($params as $key => $value) {
-                    $keys .= $key . ',';
-                }
-                throw new invalid_parameter_exception('Unexpected keys (' . $keys . ') detected in parameter array.');
+                throw new invalid_parameter_exception('Unexpected keys (' . implode(', ', array_keys($params)) . ') detected in parameter array.');
             }
             return $result;
 
index 180bb0b..21779e8 100644 (file)
@@ -656,6 +656,16 @@ class file_storage {
                 }
             }
 
+            if ($key === 'timecreated' or $key === 'timemodified') {
+                if (!is_number($value)) {
+                    throw new file_exception('storedfileproblem', 'Invalid file '.$key);
+                }
+                if ($value < 0) {
+                    //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
+                    $value = 0;
+                }
+            }
+
             $newrecord->$key = $value;
         }
 
@@ -673,12 +683,8 @@ class file_storage {
         try {
             $newrecord->id = $DB->insert_record('files', $newrecord);
         } catch (dml_exception $e) {
-            $newrecord->id = false;
-        }
-
-        if (!$newrecord->id) {
             throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
-                                                     $newrecord->filepath, $newrecord->filename);
+                                                     $newrecord->filepath, $newrecord->filename, $e->debuginfo);
         }
 
         $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
@@ -794,6 +800,29 @@ class file_storage {
         }
 
         $now = time();
+        if (isset($file_record->timecreated)) {
+            if (!is_number($file_record->timecreated)) {
+                throw new file_exception('storedfileproblem', 'Invalid file timecreated');
+            }
+            if ($file_record->timecreated < 0) {
+                //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
+                $file_record->timecreated = 0;
+            }
+        } else {
+            $file_record->timecreated = $now;
+        }
+
+        if (isset($file_record->timemodified)) {
+            if (!is_number($file_record->timemodified)) {
+                throw new file_exception('storedfileproblem', 'Invalid file timemodified');
+            }
+            if ($file_record->timemodified < 0) {
+                //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
+                $file_record->timemodified = 0;
+            }
+        } else {
+            $file_record->timemodified = $now;
+        }
 
         $newrecord = new stdClass();
 
@@ -804,8 +833,8 @@ class file_storage {
         $newrecord->filepath  = $file_record->filepath;
         $newrecord->filename  = $file_record->filename;
 
-        $newrecord->timecreated  = empty($file_record->timecreated) ? $now : $file_record->timecreated;
-        $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified;
+        $newrecord->timecreated  = $file_record->timecreated;
+        $newrecord->timemodified = $file_record->timemodified;
         $newrecord->mimetype     = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype;
         $newrecord->userid       = empty($file_record->userid) ? null : $file_record->userid;
         $newrecord->source       = empty($file_record->source) ? null : $file_record->source;
@@ -820,15 +849,11 @@ class file_storage {
         try {
             $newrecord->id = $DB->insert_record('files', $newrecord);
         } catch (dml_exception $e) {
-            $newrecord->id = false;
-        }
-
-        if (!$newrecord->id) {
             if ($newfile) {
                 $this->deleted_file_cleanup($newrecord->contenthash);
             }
             throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
-                                                    $newrecord->filepath, $newrecord->filename);
+                                                    $newrecord->filepath, $newrecord->filename, $e->debuginfo);
         }
 
         $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
@@ -889,6 +914,29 @@ class file_storage {
         }
 
         $now = time();
+        if (isset($file_record->timecreated)) {
+            if (!is_number($file_record->timecreated)) {
+                throw new file_exception('storedfileproblem', 'Invalid file timecreated');
+            }
+            if ($file_record->timecreated < 0) {
+                //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
+                $file_record->timecreated = 0;
+            }
+        } else {
+            $file_record->timecreated = $now;
+        }
+
+        if (isset($file_record->timemodified)) {
+            if (!is_number($file_record->timemodified)) {
+                throw new file_exception('storedfileproblem', 'Invalid file timemodified');
+            }
+            if ($file_record->timemodified < 0) {
+                //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
+                $file_record->timemodified = 0;
+            }
+        } else {
+            $file_record->timemodified = $now;
+        }
 
         $newrecord = new stdClass();
 
@@ -899,8 +947,8 @@ class file_storage {
         $newrecord->filepath  = $file_record->filepath;
         $newrecord->filename  = $file_record->filename;
 
-        $newrecord->timecreated  = empty($file_record->timecreated) ? $now : $file_record->timecreated;
-        $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified;
+        $newrecord->timecreated  = $file_record->timecreated;
+        $newrecord->timemodified = $file_record->timemodified;
         $newrecord->mimetype     = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype;
         $newrecord->userid       = empty($file_record->userid) ? null : $file_record->userid;
         $newrecord->source       = empty($file_record->source) ? null : $file_record->source;
@@ -915,15 +963,11 @@ class file_storage {
         try {
             $newrecord->id = $DB->insert_record('files', $newrecord);
         } catch (dml_exception $e) {
-            $newrecord->id = false;
-        }
-
-        if (!$newrecord->id) {
             if ($newfile) {
                 $this->deleted_file_cleanup($newrecord->contenthash);
             }
             throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
-                                                    $newrecord->filepath, $newrecord->filename);
+                                                    $newrecord->filepath, $newrecord->filename, $e->debuginfo);
         }
 
         $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
index cb09710..af545a0 100644 (file)
@@ -241,8 +241,8 @@ class google_authsub extends google_auth_request {
  */
 class google_docs {
     // need both docs and the spreadsheets realm
-    const REALM            = 'http://docs.google.com/feeds/ http://spreadsheets.google.com/feeds/ http://docs.googleusercontent.com/';
-    const DOCUMENTFEED_URL = 'http://docs.google.com/feeds/default/private/full';
+    const REALM            = 'https://docs.google.com/feeds/ https://spreadsheets.google.com/feeds/ https://docs.googleusercontent.com/';
+    const DOCUMENTFEED_URL = 'https://docs.google.com/feeds/default/private/full';
     const USER_PREF_NAME   = 'google_authsub_sesskey';
 
     private $google_curl = null;
@@ -292,8 +292,6 @@ class google_docs {
         $xml = new SimpleXMLElement($content);
 
 
-
-
         $files = array();
         foreach($xml->entry as $gdoc){
             $docid  = (string) $gdoc->children('http://schemas.google.com/g/2005')->resourceId;
@@ -307,15 +305,15 @@ class google_docs {
             switch($type){
                 case 'document':
                     $title = $gdoc->title.'.rtf';
-                    $source = 'http://docs.google.com/feeds/download/documents/Export?id='.$docid.'&exportFormat=rtf';
+                    $source = 'https://docs.google.com/feeds/download/documents/Export?id='.$docid.'&exportFormat=rtf';
                     break;
                 case 'presentation':
                     $title = $gdoc->title.'.ppt';
-                    $source = 'http://docs.google.com/feeds/download/presentations/Export?id='.$docid.'&exportFormat=ppt';
+                    $source = 'https://docs.google.com/feeds/download/presentations/Export?id='.$docid.'&exportFormat=ppt';
                     break;
                 case 'spreadsheet':
                     $title = $gdoc->title.'.xls';
-                    $source = 'http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key='.$docid.'&exportFormat=xls';
+                    $source = 'https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key='.$docid.'&exportFormat=xls';
                     break;
                 case 'pdf':
                     $title  = (string)$gdoc->title;
@@ -374,10 +372,10 @@ class google_docs {
 class google_picasa {
     const REALM             = 'http://picasaweb.google.com/data/';
     const USER_PREF_NAME    = 'google_authsub_sesskey_picasa';
-    const UPLOAD_LOCATION   = 'http://picasaweb.google.com/data/feed/api/user/default/albumid/default';
-    const ALBUM_PHOTO_LIST  = 'http://picasaweb.google.com/data/feed/api/user/default/albumid/';
-    const PHOTO_SEARCH_URL  = 'http://picasaweb.google.com/data/feed/api/user/default?kind=photo&q=';
-    const LIST_ALBUMS_URL   = 'http://picasaweb.google.com/data/feed/api/user/default';
+    const UPLOAD_LOCATION   = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/default';
+    const ALBUM_PHOTO_LIST  = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/';
+    const PHOTO_SEARCH_URL  = 'https://picasaweb.google.com/data/feed/api/user/default?kind=photo&q=';
+    const LIST_ALBUMS_URL   = 'https://picasaweb.google.com/data/feed/api/user/default';
 
     private $google_curl = null;
 
index aa4891e..f993bab 100644 (file)
@@ -6695,6 +6695,7 @@ class install_string_manager implements string_manager {
  * @return string The localized string.
  */
 function get_string($identifier, $component = '', $a = NULL) {
+    global $CFG;
 
     $identifier = clean_param($identifier, PARAM_STRINGID);
     if (empty($identifier)) {
@@ -6730,7 +6731,13 @@ function get_string($identifier, $component = '', $a = NULL) {
         }
     }
 
-    return get_string_manager()->get_string($identifier, $component, $a);
+    $result = get_string_manager()->get_string($identifier, $component, $a);
+
+    // Debugging feature lets you display string identifier and component
+    if ($CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
+        $result .= ' {' . $identifier . '/' . $component . '}';
+    }
+    return $result;
 }
 
 /**
@@ -7453,45 +7460,96 @@ function get_plugin_list($plugintype) {
 }
 
 /**
- * Gets a list of all plugin API functions for given plugin type, function
- * name, and filename.
- * @param string $plugintype Plugin type, e.g. 'mod' or 'report'
- * @param string $function Name of function after the frankenstyle prefix;
- *   e.g. if the function is called report_courselist_hook then this value
- *   would be 'hook'
- * @param string $file Name of file that includes function within plugin,
- *   default 'lib.php'
- * @return Array of plugin frankenstyle (e.g. 'report_courselist', 'mod_forum')
- *   to valid, existing plugin function name (e.g. 'report_courselist_hook',
- *   'forum_hook')
+ * Get a list of all the plugins of a given type that contain a particular file.
+ * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
+ * @param string $file the name of file that must be present in the plugin.
+ *      (e.g. 'view.php', 'db/install.xml').
+ * @param bool $include if true (default false), the file will be include_once-ed if found.
+ * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
+ *      to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
  */
-function get_plugin_list_with_function($plugintype, $function, $file='lib.php') {
-    global $CFG; // mandatory in case it is referenced by include()d PHP script
+function get_plugin_list_with_file($plugintype, $file, $include = false) {
+    global $CFG; // Necessary in case it is referenced by include()d PHP scripts.
 
-    $result = array();
-    // Loop through list of plugins with given type
-    $list = get_plugin_list($plugintype);
-    foreach($list as $plugin => $dir) {
+    $plugins = array();
+
+    foreach(get_plugin_list($plugintype) as $plugin => $dir) {
         $path = $dir . '/' . $file;
-        // If file exists, require it and look for function
         if (file_exists($path)) {
-            include_once($path);
-            $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
-            if (function_exists($fullfunction)) {
-                // Function exists with standard name. Store, indexed by
-                // frankenstyle name of plugin
-                $result[$plugintype . '_' . $plugin] = $fullfunction;
-            } else if ($plugintype === 'mod') {
-                // For modules, we also allow plugin without full frankenstyle
-                // but just starting with the module name
-                $shortfunction = $plugin . '_' . $function;
-                if (function_exists($shortfunction)) {
-                    $result[$plugintype . '_' . $plugin] = $shortfunction;
-                }
+            if ($include) {
+                include_once($path);
             }
+            $plugins[$plugin] = $path;
         }
     }
-    return $result;
+
+    return $plugins;
+}
+
+/**
+ * Get a list of all the plugins of a given type that define a certain API function
+ * in a certain file. The plugin component names and function names are returned.
+ *
+ * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
+ * @param string $function the part of the name of the function after the
+ *      frankenstyle prefix. e.g 'hook' if you are looking for functions with
+ *      names like report_courselist_hook.
+ * @param string $file the name of file within the plugin that defines the
+ *      function. Defaults to lib.php.
+ * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
+ *      and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
+ */
+function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
+    $pluginfunctions = array();
+    foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
+        $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
+
+        if (function_exists($fullfunction)) {
+            // Function exists with standard name. Store, indexed by
+            // frankenstyle name of plugin
+            $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
+
+        } else if ($plugintype === 'mod') {
+            // For modules, we also allow plugin without full frankenstyle
+            // but just starting with the module name
+            $shortfunction = $plugin . '_' . $function;
+            if (function_exists($shortfunction)) {
+                $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
+            }
+        }
+    }
+    return $pluginfunctions;
+}
+
+/**
+ * Get a list of all the plugins of a given type that define a certain class
+ * in a certain file. The plugin component names and class names are returned.
+ *
+ * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
+ * @param string $class the part of the name of the class after the
+ *      frankenstyle prefix. e.g 'thing' if you are looking for classes with
+ *      names like report_courselist_thing. If you are looking for classes with
+ *      the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
+ * @param string $file the name of file within the plugin that defines the class.
+ * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
+ *      and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
+ */
+function get_plugin_list_with_class($plugintype, $class, $file) {
+    if ($class) {
+        $suffix = '_' . $class;
+    } else {
+        $suffix = '';
+    }
+
+    $pluginclasses = array();
+    foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
+        $classname = $plugintype . '_' . $plugin . $suffix;
+        if (class_exists($classname)) {
+            $pluginclasses[$plugintype . '_' . $plugin] = $classname;
+        }
+    }
+
+    return $pluginclasses;
 }
 
 /**
index 0c88ad0..43ff1a2 100644 (file)
@@ -1608,6 +1608,7 @@ class global_navigation extends navigation_node {
                     $activity->hidden = (!$cm->visible);
                     $activity->modname = $cm->modname;
                     $activity->nodetype = navigation_node::NODETYPE_LEAF;
+                    $activity->onclick = $cm->get_on_click();
                     $url = $cm->get_url();
                     if (!$url) {
                         $activity->url = null;
@@ -1706,6 +1707,8 @@ class global_navigation extends navigation_node {
      * @return array Array of activity nodes
      */
     protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, $activities) {
+        // A static counter for JS function naming
+        static $legacyonclickcounter = 0;
 
         if ($activities instanceof course_modinfo) {
             debugging('global_navigation::load_section_activities argument 3 should now recieve an array of activites. See that method for an example.', DEBUG_DEVELOPER);
@@ -1722,7 +1725,34 @@ class global_navigation extends navigation_node {
             } else {
                 $icon = new pix_icon('icon', get_string('modulename', $activity->modname), $activity->modname);
             }
-            $activitynode = $sectionnode->add(format_string($activity->name), $activity->url, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);
+
+            // Prepare the default name and url for the node
+            $activityname = format_string($activity->name, true, array('context' => get_context_instance(CONTEXT_MODULE, $activity->id)));
+            $action = new moodle_url($activity->url);
+
+            // Check if the onclick property is set (puke!)
+            if (!empty($activity->onclick)) {
+                // Increment the counter so that we have a unique number.
+                $legacyonclickcounter++;
+                // Generate the function name we will use
+                $functionname = 'legacy_activity_onclick_handler_'.$legacyonclickcounter;
+                $propogrationhandler = '';
+                // Check if we need to cancel propogation. Remember inline onclick
+                // events would return false if they wanted to prevent propogation and the
+                // default action.
+                if (strpos($activity->onclick, 'return false')) {
+                    $propogrationhandler = 'e.halt();';
+                }
+                // Decode the onclick - it has already been encoded for display (puke)
+                $onclick = htmlspecialchars_decode($activity->onclick);
+                // Build the JS function the click event will call
+                $jscode = "function {$functionname}(e) { $propogrationhandler $onclick }";
+                $this->page->requires->js_init_code($jscode);
+                // Override the default url with the new action link
+                $action = new action_link($action, $activityname, new component_action('click', $functionname));
+            }
+
+            $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);
             $activitynode->title(get_string('modulename', $activity->modname));
             $activitynode->hidden = $activity->hidden;
             $activitynode->display = $activity->display;
@@ -1893,12 +1923,14 @@ class global_navigation extends navigation_node {
             }
         }
 
-        // Add nodes for forum posts and discussions if the user can view either or both
-        // There are no capability checks here as the content of the page is based
-        // purely on the forums the current user has access too.
-        $forumtab = $usernode->add(get_string('forumposts', 'forum'));
-        $forumtab->add(get_string('posts', 'forum'), new moodle_url('/mod/forum/user.php', $baseargs));
-        $forumtab->add(get_string('discussions', 'forum'), new moodle_url('/mod/forum/user.php', array_merge($baseargs, array('mode'=>'discussions'))));
+        if (!empty($CFG->navadduserpostslinks)) {
+            // Add nodes for forum posts and discussions if the user can view either or both
+            // There are no capability checks here as the content of the page is based
+            // purely on the forums the current user has access too.
+            $forumtab = $usernode->add(get_string('forumposts', 'forum'));
+            $forumtab->add(get_string('posts', 'forum'), new moodle_url('/mod/forum/user.php', $baseargs));
+            $forumtab->add(get_string('discussions', 'forum'), new moodle_url('/mod/forum/user.php', array_merge($baseargs, array('mode'=>'discussions'))));
+        }
 
         // Add blog nodes
         if (!empty($CFG->bloglevel)) {
@@ -2319,9 +2351,11 @@ class global_navigation extends navigation_node {
             $coursenode->add(get_string('tags', 'tag'), new moodle_url('/tag/search.php'));
         }
 
-        // Calendar
-        $calendarurl = new moodle_url('/calendar/view.php', array('view' => 'month'));
-        $coursenode->add(get_string('calendar', 'calendar'), $calendarurl, self::TYPE_CUSTOM, null, 'calendar');
+        if (isloggedin()) {
+            // Calendar
+            $calendarurl = new moodle_url('/calendar/view.php', array('view' => 'month'));
+            $coursenode->add(get_string('calendar', 'calendar'), $calendarurl, self::TYPE_CUSTOM, null, 'calendar');
+        }
 
         // View course reports
         if (has_capability('moodle/site:viewreports', $this->page->context)) { // basic capability for listing of reports
index 1abf90b..9b300c2 100644 (file)
@@ -139,7 +139,7 @@ class require_login_exception extends moodle_exception {
 
 /**
  * Web service parameter exception class
- * @deprecated - use moodle exception instead
+ * @deprecated since Moodle 2.2 - use moodle exception instead
  * This exception must be thrown to the web service client when a web service parameter is invalid
  * The error string is gotten from webservice.php
  */
index 2c66b45..ccaf287 100644 (file)
@@ -67,7 +67,7 @@ class moodle_page_test extends UnitTestCase {
     }
 
     public function tearDown() {
-        global $COURSE;
+        global $COURSE, $PAGE;
         $this->testpage = NULL;
         $COURSE = $this->originalcourse;
         $PAGE = $this->originalpage;
index 6a1a132..96c6091 100644 (file)
@@ -59,7 +59,7 @@ class xmldb_key extends xmldb_object {
     /**
      * Set all the attributes of one xmldb_key
      *
-     * @param string type XMLDB_KEY_PRIMARY, XMLDB_KEY_UNIQUE, XMLDB_KEY_FOREIGN
+     * @param string type XMLDB_KEY_[PRIMARY|UNIQUE|FOREIGN|FOREIGN_UNIQUE]
      * @param array fields an array of fieldnames to build the key over
      * @param string reftable name of the table the FK points to or null
      * @param array reffields an array of fieldnames in the FK table or null
@@ -418,6 +418,9 @@ class xmldb_key extends xmldb_object {
             case XMLDB_KEY_FOREIGN:
                 $result .= 'XMLDB_KEY_FOREIGN' . ', ';
                 break;
+            case XMLDB_KEY_FOREIGN_UNIQUE:
+                $result .= 'XMLDB_KEY_FOREIGN_UNIQUE' . ', ';
+                break;
         }
     /// The fields
         $keyfields = $this->getFields();
@@ -427,7 +430,8 @@ class xmldb_key extends xmldb_object {
             $result .= 'null';
         }
     /// The FKs attributes
-        if ($this->getType() == XMLDB_KEY_FOREIGN) {
+        if ($this->getType() == XMLDB_KEY_FOREIGN ||
+            $this->getType() == XMLDB_KEY_FOREIGN_UNIQUE) {
         /// The reftable
             $reftable = $this->getRefTable();
             if (!empty($reftable)) {
index f698054..c4a390c 100644 (file)
@@ -1922,8 +1922,12 @@ class assignment_base {
                     $button->set_format_by_file($file);
                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
                 }
-                $output .= plagiarism_get_links(array('userid'=>$userid, 'file'=>$file, 'cmid'=>$this->cm->id, 'course'=>$this->course, 'assignment'=>$this->assignment));
-                $output .= '<br />';
+
+                if ($CFG->enableplagiarism) {
+                    require_once($CFG->libdir.'/plagiarismlib.php');
+                    $output .= plagiarism_get_links(array('userid'=>$userid, 'file'=>$file, 'cmid'=>$this->cm->id, 'course'=>$this->course, 'assignment'=>$this->assignment));
+                    $output .= '<br />';
+                }
             }
             if ($CFG->enableportfolios && count($files) > 1  && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
                 $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id), '/mod/assignment/locallib.php');
index 3cbcc2f..a711570 100644 (file)
@@ -110,11 +110,6 @@ class assignment_upload extends assignment_base {
         }
 
         if (empty($submission->timemarked)) {   /// Nothing to show, so print nothing
-            if ($this->count_responsefiles($USER->id)) {
-                echo $OUTPUT->heading(get_string('responsefiles', 'assignment'), 3);
-                $responsefiles = $this->print_responsefiles($USER->id, true);
-                echo $OUTPUT->box($responsefiles, 'generalbox boxaligncenter');
-            }
             return;
         }
 
@@ -126,7 +121,12 @@ class assignment_upload extends assignment_base {
             return;
         }
 
-        if ($grade->grade === null and empty($grade->str_feedback)) {   /// Nothing to show yet
+        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
+                echo $OUTPUT->heading(get_string('responsefiles', 'assignment'), 3);
+                $responsefiles = $this->print_responsefiles($USER->id, true);
+                echo $OUTPUT->box($responsefiles, 'generalbox boxaligncenter');
+            }
             return;
         }
 
@@ -730,7 +730,7 @@ class assignment_upload extends assignment_base {
             $updated->data2 = '';
             $DB->update_record('assignment_submissions', $updated);
             //TODO: add unfinalize action to log
-            add_to_log($this->course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment->id, $this->assignment->id, $this->cm->id);
+            add_to_log($this->course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id.'&userid='.$userid.'&mode='.$mode.'&offset='.$offset, $this->assignment->id, $this->cm->id);
             $submission = $this->get_submission($userid);
             $this->update_grade($submission);
         }
index 4df3f6a..4ef840d 100644 (file)
@@ -204,11 +204,11 @@ class mod_choice_renderer extends plugin_renderer_base {
         if ($choices->viewresponsecapability && $choices->deleterepsonsecapability) {
             $selecturl = new moodle_url('#');
 
-            $selectallactions = new component_action('click',"select_all_in", array('div',null,'tablecontainer'));
+            $selectallactions = new component_action('click',"checkall");
             $selectall = new action_link($selecturl, get_string('selectall'), $selectallactions);
             $actiondata .= $this->output->render($selectall) . ' / ';
 
-            $deselectallactions = new component_action('click',"deselect_all_in", array('div',null,'tablecontainer'));
+            $deselectallactions = new component_action('click',"checknone");
             $deselectall = new action_link($selecturl, get_string('deselectall'), $deselectallactions);
             $actiondata .= $this->output->render($deselectall);
 
index e866a62..6adb1ba 100644 (file)
@@ -11,6 +11,7 @@
 require_once("../../config.php");
 require_once("lib.php");
 require_once('delete_template_form.php');
+require_once($CFG->libdir.'/tablelib.php');
 
 // $SESSION->feedback->current_tab = 'templates';
 $current_tab = 'templates';
@@ -77,13 +78,24 @@ if ($mform->is_cancelled()) {
 }
 
 if(isset($formdata->confirmdelete) AND $formdata->confirmdelete == 1){
-    feedback_delete_template($formdata->deletetempl);
+    if(!$template = $DB->get_record("feedback_template", array("id"=>$deletetempl))) {
+        print_error('error');
+    }
+    
+    if($template->ispublic) {
+        $systemcontext = get_system_context();
+        require_capability('mod/feedback:createpublictemplate', $systemcontext);
+        require_capability('mod/feedback:deletetemplate', $systemcontext);
+    }
+    
+    feedback_delete_template($template);
     redirect($deleteurl->out(false));
 }
 
 /// Print the page header
 $strfeedbacks = get_string("modulenameplural", "feedback");
 $strfeedback  = get_string("modulename", "feedback");
+$strdeletefeedback = get_string('delete_template','feedback');
 
 $PAGE->set_heading(format_string($course->fullname));
 $PAGE->set_title(format_string($feedback->name));
@@ -96,7 +108,7 @@ include('tabs.php');
 ///////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////
-echo $OUTPUT->heading(get_string('delete_template','feedback'));
+echo $OUTPUT->heading($strdeletefeedback);
 if($shoulddelete == 1) {
 
     echo $OUTPUT->box_start('generalbox errorboxcontent boxaligncenter boxwidthnormal');
@@ -104,36 +116,94 @@ if($shoulddelete == 1) {
     $mform->display();
     echo $OUTPUT->box_end();
 }else {
-    $templates = feedback_get_template_list($course, true);
-    echo '<div class="mdl-align">';
+    //first we get the own templates
+    $templates = feedback_get_template_list($course, 'own');
     if(!is_array($templates)) {
         echo $OUTPUT->box(get_string('no_templates_available_yet', 'feedback'), 'generalbox boxaligncenter');
     }else {
-        echo '<table width="30%">';
-        echo '<tr><th>'.get_string('templates', 'feedback').'</th><th>&nbsp;</th></tr>';
+        echo $OUTPUT->heading(get_string('course'), 3);
+        echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthnormal');
+        $tablecolumns = array('template', 'action');
+        $tableheaders = array(get_string('template', 'feedback'), '');
+        $tablecourse = new flexible_table('feedback_template_course_table');
+
+        $tablecourse->define_columns($tablecolumns);
+        $tablecourse->define_headers($tableheaders);
+        $tablecourse->define_baseurl($deleteurl);
+        $tablecourse->column_style('action', 'width', '10%');
+
+        $tablecourse->sortable(false);
+        $tablecourse->set_attribute('width', '100%');
+        $tablecourse->set_attribute('class', 'generaltable');
+        $tablecourse->setup();
+
         foreach($templates as $template) {
-            echo '<tr><td align="center">'.$template->name.'</td>';
-            echo '<td align="center">';
-            echo '<form action="delete_template.php" method="post">';
-            echo '<input title="'.get_string('delete_template','feedback').'" type="image" src="'.$OUTPUT->pix_url('t/delete') . '" hspace="1" height="11" width="11" border="0" />';
-            echo '<input type="hidden" name="deletetempl" value="'.$template->id.'" />';
-            echo '<input type="hidden" name="shoulddelete" value="1" />';
-            echo '<input type="hidden" name="id" value="'.$id.'" />';
-            echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
-            echo '</form>';
-            echo '</td></tr>';
+            $data = array();
+            $data[] = $template->name;
+            $url = new moodle_url($deleteurl, array(
+                                            'id'=>$id,
+                                            'deletetempl'=>$template->id,
+                                            'shoulddelete'=>1,
+                                            ));
+                                                                                  
+            $data[] = $OUTPUT->single_button($url, $strdeletefeedback, 'post');
+            $tablecourse->add_data($data);
         }
-        echo '</table>';
+        $tablecourse->finish_output();
+        echo $OUTPUT->box_end();
     }
-?>
-        <form name="frm" action="delete_template.php" method="post">
-            <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
-            <input type="hidden" name="id" value="<?php echo $id;?>" />
-            <input type="hidden" name="canceldelete" value="0" />
-            <button type="button" onclick="this.form.canceldelete.value=1;this.form.submit();"><?php print_string('cancel');?></button>
-        </form>
-        </div>
-<?php
+    //now we get the public templates if it is permitted
+    $systemcontext = get_system_context();
+    if(has_capability('mod/feedback:createpublictemplate', $systemcontext) AND
+        has_capability('mod/feedback:deletetemplate', $systemcontext)) {
+        $templates = feedback_get_template_list($course, 'public');
+        if(!is_array($templates)) {
+            echo $OUTPUT->box(get_string('no_templates_available_yet', 'feedback'), 'generalbox boxaligncenter');
+        }else {
+            echo $OUTPUT->heading(get_string('public', 'feedback'), 3);
+            echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthnormal');
+            $tablecolumns = array('template', 'action');
+            $tableheaders = array(get_string('template', 'feedback'), '');
+            $tablepublic = new flexible_table('feedback_template_public_table');
+
+            $tablepublic->define_columns($tablecolumns);
+            $tablepublic->define_headers($tableheaders);
+            $tablepublic->define_baseurl($deleteurl);
+            $tablepublic->column_style('action', 'width', '10%');
+
+            $tablepublic->sortable(false);
+            $tablepublic->set_attribute('width', '100%');
+            $tablepublic->set_attribute('class', 'generaltable');
+            $tablepublic->setup();
+
+            
+            // echo $OUTPUT->heading(get_string('public', 'feedback'), 3);
+            // echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide');
+            foreach($templates as $template) {
+                $data = array();
+                $data[] = $template->name;
+                $url = new moodle_url($deleteurl, array(
+                                                'id'=>$id,
+                                                'deletetempl'=>$template->id,
+                                                'shoulddelete'=>1,
+                                                ));
+                                                                                      
+                $data[] = $OUTPUT->single_button($url, $strdeletefeedback, 'post');
+                $tablepublic->add_data($data);
+            }
+            $tablepublic->finish_output();
+            echo $OUTPUT->box_end();
+        }
+    }
+    
+    echo $OUTPUT->box_start('boxaligncenter boxwidthnormal');
+    $url = new moodle_url($deleteurl, array(
+                                    'id'=>$id,
+                                    'canceldelete'=>1,
+                                    ));
+                                                                          
+    echo $OUTPUT->single_button($url, get_string('back'), 'post');
+    echo $OUTPUT->box_end();
 }
 
 echo $OUTPUT->footer();
index 0c5509f..c0bc995 100644 (file)
@@ -85,28 +85,26 @@ if($switchitemrequired) {
 
 //the create_template-form
 $create_template_form = new feedback_edit_create_template_form();
-$create_template_form->set_feedbackdata(array('context' => $context));
+$create_template_form->set_feedbackdata(array('context'=>$context, 'course'=>$course));
 $create_template_form->set_form_elements();
 $create_template_form->set_data(array('id'=>$id, 'do_show'=>'templates'));
 $create_template_formdata = $create_template_form->get_data();
 if(isset($create_template_formdata->savetemplate) && $create_template_formdata->savetemplate == 1) {
     //check the capabilities to create templates
     if(!has_capability('mod/feedback:createprivatetemplate', $context) AND
-        !has_capability('mod/feedback:createpublictemplate', $context)) {
+            !has_capability('mod/feedback:createpublictemplate', $context)) {
         print_error('cannotsavetempl', 'feedback');
     }
-    if(trim($create_template_formdata->templatename) == '')
-    {
+    if(trim($create_template_formdata->templatename) == '') {
         $savereturn = 'notsaved_name';
     }else {
-        //public templates are currently deaktivated
-        // if(has_capability('mod/feedback:createpublictemplate', $context)) {
-            // $create_template_formdata->ispublic = isset($create_template_formdata->ispublic) ? 1 : 0;
-        // }else {
+        //if the feedback is located on the frontpage then templates can be public
+        if(has_capability('mod/feedback:createpublictemplate', get_system_context())) {
+            $create_template_formdata->ispublic = isset($create_template_formdata->ispublic) ? 1 : 0;
+        }else {
             $create_template_formdata->ispublic = 0;
-        // }
-        if(!feedback_save_as_template($feedback, $create_template_formdata->templatename, $create_template_formdata->ispublic))
-        {
+        }
+        if(!feedback_save_as_template($feedback, $create_template_formdata->templatename, $create_template_formdata->ispublic)) {
             $savereturn = 'failed';
         }else {
             $savereturn = 'saved';
index 0ce6ab7..0af2868 100644 (file)
@@ -77,14 +77,31 @@ class feedback_edit_use_template_form extends moodleform {
 
         // visible elements
         $templates_options = array();
-        if($templates = feedback_get_template_list($this->feedbackdata->course)){//get the templates
-            $templates_options[' '] = get_string('select');
-            foreach($templates as $template) {
-                $templates_options[$template->id] = $template->name;
+        $owntemplates = feedback_get_template_list($this->feedbackdata->course, 'own');
+        $publictemplates = feedback_get_template_list($this->feedbackdata->course, 'public');
+
+        $options = array();
+        if($owntemplates or $publictemplates) {
+            $options[''] = array('' => get_string('choose'));
+        
+            if($owntemplates) {
+                $courseoptions = array();
+                foreach($owntemplates as $template) {
+                    $courseoptions[$template->id] = $template->name;
+                }
+                $options[get_string('course')] = $courseoptions;
             }
+        
+            if($publictemplates) {
+                $publicoptions = array();
+                foreach($publictemplates as $template) {
+                    $publicoptions[$template->id] = $template->name;
+                }
+                $options[get_string('public', 'feedback')] = $publicoptions;
+            }
+
             $attributes = 'onChange="this.form.submit()"';
-            $elementgroup[] =& $mform->createElement('select', 'templateid', '', $templates_options, $attributes);
-            // buttons
+            $elementgroup[] =& $mform->createElement('selectgroups', 'templateid', '', $options, $attributes);
             $elementgroup[] =& $mform->createElement('submit', 'use_template', get_string('use_this_template', 'feedback'));
         }else {
             $mform->addElement('static', 'info', get_string('no_templates_available_yet', 'feedback'));
@@ -134,10 +151,9 @@ class feedback_edit_create_template_form extends moodleform {
         $elementgroup[] =& $mform->createElement('static', 'templatenamelabel', get_string('name', 'feedback'));
         $elementgroup[] =& $mform->createElement('text', 'templatename', get_string('name', 'feedback'), array('size'=>'40', 'maxlength'=>'200'));
 
-        //public templates are currently deactivated
-        // if(has_capability('mod/feedback:createpublictemplate', $this->feedbackdata->context)) {
-            // $elementgroup[] =& $mform->createElement('checkbox', 'ispublic', get_string('public', 'feedback'), get_string('public', 'feedback'));
-        // }
+        if(has_capability('mod/feedback:createpublictemplate', get_system_context())) {
+            $elementgroup[] =& $mform->createElement('checkbox', 'ispublic', get_string('public', 'feedback'), get_string('public', 'feedback'));
+        }
 
         // buttons
         $elementgroup[] =& $mform->createElement('submit', 'create_template', get_string('save_as_new_template', 'feedback'));
index 5e5df4c..6a1081a 100644 (file)
@@ -123,7 +123,11 @@ class feedback_item_label extends feedback_item_base {
         //is the item a template?
         if(!$item->feedback AND $item->template) {
             $template = $DB->get_record('feedback_template', array('id'=>$item->template));
-            $context = get_context_instance(CONTEXT_COURSE, $template->course);
+            if($template->ispublic) {
+                $context = get_system_context();
+            }else {
+                $context = get_context_instance(CONTEXT_COURSE, $template->course);
+            }
             $filearea = 'template';
         }else {
             $cm = get_coursemodule_from_instance('feedback', $item->feedback);
index 9d884dd..8b8d2c3 100644 (file)
@@ -138,6 +138,9 @@ function feedback_update_instance($feedback) {
 /**
  * Serves the files included in feedback items like label. Implements needed access control ;-)
  *
+ * There are two situations in general where the files will be sent.
+ * 1) filearea = item, 2) filearea = template
+ *
  * @param object $course
  * @param object $cm
  * @param object $context
@@ -149,17 +152,59 @@ function feedback_update_instance($feedback) {
 function feedback_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
     global $CFG, $DB;
 
-    require_login($course, false, $cm);
-
     $itemid = (int)array_shift($args);
 
-    require_course_login($course, true, $cm);
-
+    //get the item what includes the file
     if (!$item = $DB->get_record('feedback_item', array('id'=>$itemid))) {
         return false;
     }
+    
+    //if the filearea is "item" so we check the permissions like view/complete the feedback
+    if($filearea === 'item') {
+        //get the feedback
+        if(!$feedback = $DB->get_record('feedback', array('id'=>$item->feedback))) {
+            return false;
+        }
 
-    if (!has_capability('mod/feedback:view', $context)) {
+        $canload = false;
+        //first check whether the user has the complete capability
+        if(has_capability('mod/feedback:complete', $context)) {
+            $canload = true;
+        }
+        
+        //now we check whether the user has the view capability
+        if(has_capability('mod/feedback:view', $context)) {
+            $canload = true;
+        }
+        
+        //if the feedback is on frontpage and anonymous and the fullanonymous is allowed
+        //so the file can be loaded too.
+        if(isset($CFG->feedback_allowfullanonymous)
+                    AND $CFG->feedback_allowfullanonymous
+                    AND $course->id == SITEID
+                    AND $feedback->anonymous == FEEDBACK_ANONYMOUS_YES ) {
+            $canload = true;
+        }
+
+        if(!$canload) {
+            return false;
+        }
+    }else if($filearea === 'template') { //now we check files in templates
+        if(!$template = $DB->get_record('feedback_template', array('id'=>$item->template))) {
+            return false;
+        }
+        
+        //if the file is not public so the capability edititems has to be there
+        if(!$template->ispublic) {
+            if(!has_capability('mod/feedback:edititems', $context)) {
+                return false;
+            }
+        }else { //on public templates, at least the user has to be logged in
+            if(!isloggedin()) {
+                return false;
+            }
+        }
+    }else {
         return false;
     }
 
@@ -175,7 +220,7 @@ function feedback_pluginfile($course, $cm, $context, $filearea, $args, $forcedow
         }
     }
 
-    if ($context->contextlevel == CONTEXT_COURSE) {
+    if ($context->contextlevel == CONTEXT_COURSE || $context->contextlevel == CONTEXT_SYSTEM) {
         if ($filearea !== 'template') {
             return false;
         }
@@ -195,7 +240,6 @@ function feedback_pluginfile($course, $cm, $context, $filearea, $args, $forcedow
     return false;
 }
 
-
 /**
  * this will delete a given instance.
  * all referenced data also will be deleted
@@ -945,7 +989,7 @@ function feedback_create_template($courseid, $name, $ispublic = 0) {
     global $DB;
 
     $templ = new stdClass();
-    $templ->course   = $courseid;
+    $templ->course   = ($ispublic ? 0 : $courseid);
     $templ->name     = $name;
     $templ->ispublic = $ispublic;
 
@@ -978,9 +1022,14 @@ function feedback_save_as_template($feedback, $name, $ispublic = 0) {
         return false;
     }
 
-    //files in the template_item are in the context of the current course
+    //files in the template_item are in the context of the current course or
+    //if the template is public the files are in the system context
     //files in the feedback_item are in the feedback_context of the feedback
-    $c_context = get_context_instance(CONTEXT_COURSE, $newtempl->course);
+    if($ispublic) {
+        $s_context = get_system_context();
+    }else {
+        $s_context = get_context_instance(CONTEXT_COURSE, $newtempl->course);
+    }
     $cm = get_coursemodule_from_instance('feedback', $feedback->id);
     $f_context = get_context_instance(CONTEXT_MODULE, $cm->id);
 
@@ -1001,7 +1050,7 @@ function feedback_save_as_template($feedback, $name, $ispublic = 0) {
         if ($itemfiles = $fs->get_area_files($f_context->id, 'mod_feedback', 'item', $item->id, "id", false)) {
             foreach($itemfiles as $ifile) {
                 $file_record = new stdClass();
-                $file_record->contextid = $c_context->id;
+                $file_record->contextid = $s_context->id;
                 $file_record->component = 'mod_feedback';
                 $file_record->filearea = 'template';
                 $file_record->itemid = $t_item->id;
@@ -1031,27 +1080,19 @@ function feedback_save_as_template($feedback, $name, $ispublic = 0) {
  *
  * @global object
  * @uses CONTEXT_COURSE
- * @param int $id the templateid
+ * @param object $template the template
  * @return void
  */
-function feedback_delete_template($id) {
+function feedback_delete_template($template) {
     global $DB;
 
-    $template = $DB->get_record("feedback_template", array("id"=>$id));
-
-    //deleting the files from the item
-    $fs = get_file_storage();
-    $context = get_context_instance(CONTEXT_COURSE, $template->course);
-
-
-    if($t_items = $DB->get_records("feedback_item", array("template"=>$id))) {
+    //deleting the files from the item is done by feedback_delete_item
+    if($t_items = $DB->get_records("feedback_item", array("template"=>$template->id))) {
         foreach($t_items as $t_item) {
-            if ($templatefiles = $fs->get_area_files($context->id, 'mod_feedback', 'template', $t_item->id, "id", false)) {
-                $fs->delete_area_files($context->id, 'mod_feedback', 'template', $t_item->id);
-            }
+            feedback_delete_item($t_item->id, false, $template);
         }
     }
-    $DB->delete_records("feedback_template", array("id"=>$id));
+    $DB->delete_records("feedback_template", array("id"=>$template->id));
 }
 
 /**
@@ -1073,6 +1114,9 @@ function feedback_items_from_template($feedback, $templateid, $deleteold = false
 
     $fs = get_file_storage();
 
+    if(!$template = $DB->get_record('feedback_template', array('id'=>$templateid))) {
+        return false;
+    }
     //get all templateitems
     if(!$templitems = $DB->get_records('feedback_item', array('template'=>$templateid))) {
         return false;
@@ -1080,7 +1124,11 @@ function feedback_items_from_template($feedback, $templateid, $deleteold = false
 
     //files in the template_item are in the context of the current course
     //files in the feedback_item are in the feedback_context of the feedback
-    $c_context = get_context_instance(CONTEXT_COURSE, $feedback->course);
+    if($template->ispublic) {
+        $s_context = get_system_context();
+    }else {
+        $s_context = get_context_instance(CONTEXT_COURSE, $feedback->course);
+    }
     $course = $DB->get_record('course', array('id'=>$feedback->course));
     $cm = get_coursemodule_from_instance('feedback', $feedback->id);
     $f_context = get_context_instance(CONTEXT_MODULE, $cm->id);
@@ -1128,9 +1176,9 @@ function feedback_items_from_template($feedback, $templateid, $deleteold = false
         $item->position = $item->position + $positionoffset;
 
         $item->id = $DB->insert_record('feedback_item', $item);
-
+        
         //TODO: moving the files to the new items
-        if ($templatefiles = $fs->get_area_files($c_context->id, 'mod_feedback', 'template', $t_item->id, "id", false)) {
+        if ($templatefiles = $fs->get_area_files($s_context->id, 'mod_feedback', 'template', $t_item->id, "id", false)) {
             foreach($templatefiles as $tfile) {
                 $file_record = new stdClass();
                 $file_record->contextid = $f_context->id;
@@ -1162,16 +1210,22 @@ function feedback_items_from_template($feedback, $templateid, $deleteold = false
  *
  * @global object
  * @param object $course
- * @param boolean $onlyown
+ * @param string $onlyownorpublic
  * @return array the template recordsets
  */
-function feedback_get_template_list($course, $onlyown = false) {
-    global $DB;
+function feedback_get_template_list($course, $onlyownorpublic = '') {
+    global $DB, $CFG;
 
-    if ($onlyown) {
-        $templates = $DB->get_records('feedback_template', array('course'=>$course->id));
-    } else {
-        $templates = $DB->get_records_select('feedback_template', 'course = ? OR ispublic = 1', array($course->id));
+    switch($onlyownorpublic) {
+        case '':
+            $templates = $DB->get_records_select('feedback_template', 'course = ? OR ispublic = 1', array($course->id), 'name');
+            break;
+        case 'own':
+            $templates = $DB->get_records('feedback_template', array('course'=>$course->id), 'name');
+            break;
+        case 'public':
+            $templates = $DB->get_records('feedback_template', array('ispublic'=>1), 'name');
+            break;
     }
     return $templates;
 }
@@ -1344,9 +1398,10 @@ function feedback_update_item($item){
  * @uses CONTEXT_MODULE
  * @param int $itemid
  * @param boolean $renumber should the kept items renumbered Yes/No
+ * @param object $template if the template is given so the items are bound to it
  * @return void
  */
-function feedback_delete_item($itemid, $renumber = true){
+function feedback_delete_item($itemid, $renumber = true, $template = false){
     global $DB;
 
 
@@ -1354,13 +1409,25 @@ function feedback_delete_item($itemid, $renumber = true){
 
     //deleting the files from the item
     $fs = get_file_storage();
-    if (!$cm = get_coursemodule_from_instance('feedback', $item->feedback)) {
-        return false;
-    }
-    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+    
+    if($template) {
+        if($template->ispublic) {
+            $context = get_system_context();
+        }else {
+            $context = get_context_instance(CONTEXT_COURSE, $template->course);
+        }
+        if ($templatefiles = $fs->get_area_files($context->id, 'mod_feedback', 'template', $item->id, "id", false)) {
+            $fs->delete_area_files($context->id, 'mod_feedback', 'template', $item->id);
+        }
+    }else {
+        if (!$cm = get_coursemodule_from_instance('feedback', $item->feedback)) {
+            return false;
+        }
+        $context = get_context_instance(CONTEXT_MODULE, $cm->id);
 
-    if ($itemfiles = $fs->get_area_files($context->id, 'mod_feedback', 'item', $item->id, "id", false)) {
-        $fs->delete_area_files($context->id, 'mod_feedback', 'item', $item->id);
+        if ($itemfiles = $fs->get_area_files($context->id, 'mod_feedback', 'item', $item->id, "id", false)) {
+            $fs->delete_area_files($context->id, 'mod_feedback', 'item', $item->id);
+        }
     }
 
     $DB->delete_records("feedback_value", array("item"=>$itemid));
index 78178bd..e166150 100644 (file)
@@ -9,7 +9,7 @@
 */
 
 
-    $module->version = 2011051600; // The current module version (Date: YYYYMMDDXX)
+    $module->version = 2011100800; // The current module version (Date: YYYYMMDDXX)
     $module->requires = 2010080300;  // Requires this Moodle version
     $feedback_version_intern = 1; //this version is used for restore older backups
     $module->cron = 0; // Period for cron to check this module (secs)
index afc6572..18f0bcd 100644 (file)
@@ -1269,12 +1269,14 @@ function quiz_reset_userdata($data) {
             'error' => false);
 
         // Remove all grades from gradebook
+        $DB->delete_records_select('quiz_grades',
+                'quiz IN (SELECT id FROM {quiz} WHERE course = ?)', array($data->courseid));
         if (empty($data->reset_gradebook_grades)) {
             quiz_reset_gradebook($data->courseid);
         }
         $status[] = array(
             'component' => $componentstr,
-            'item' => get_string('attemptsdeleted', 'quiz'),
+            'item' => get_string('gradesdeleted', 'quiz'),
             'error' => false);
     }
 
index eb8f4d2..d042027 100644 (file)
@@ -11,7 +11,7 @@
         <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="course" NEXT="intro"/>
         <FIELD NAME="intro" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" PREVIOUS="name" NEXT="introformat"/>
         <FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="intro" NEXT="externalurl"/>
-        <FIELD NAME="externalurl" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" PREVIOUS="introformat" NEXT="display"/>
+        <FIELD NAME="externalurl" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" PREVIOUS="introformat" NEXT="display"/>
         <FIELD NAME="display" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="externalurl" NEXT="displayoptions"/>
         <FIELD NAME="displayoptions" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" PREVIOUS="display" NEXT="parameters"/>
         <FIELD NAME="parameters" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" PREVIOUS="displayoptions" NEXT="timemodified"/>
index 8c995bf..01926f8 100644 (file)
@@ -53,6 +53,20 @@ function xmldb_url_upgrade($oldversion) {
 
     // Moodle v2.1.0 release upgrade line
     // Put any upgrade step following this
+    if ($oldversion < 2011092800) {
+
+        // Changing nullability of field externalurl on table urls to not-null
+        $table = new xmldb_table('url');
+        $field = new xmldb_field('externalurl', XMLDB_TYPE_TEXT, 'small', null,
+                XMLDB_NOTNULL, null, null, 'introformat');
+
+        $DB->set_field_select('url', 'externalurl', $DB->sql_empty(), 'externalurl IS NULL');
+        // Launch change of nullability for field =externalurl
+        $dbman->change_field_notnull($table, $field);
+
+        // url savepoint reached
+        upgrade_mod_savepoint(true, 2011092800, 'url');
+    }
 
     return true;
 }
index be6867d..4498394 100644 (file)
@@ -45,6 +45,7 @@ $string['displayselectexplain'] = 'Choose display type, unfortunately not all ty
 $string['externalurl'] = 'External URL';
 $string['framesize'] = 'Frame height';
 $string['chooseavariable'] = 'Choose a variable...';
+$string['invalidurl'] = 'Entered URL is invalid';
 $string['modulename'] = 'URL';
 $string['modulenameplural'] = 'URLs';
 $string['neverseen'] = 'Never seen';
index da6a720..790b85d 100644 (file)
@@ -50,6 +50,7 @@ class mod_url_mod_form extends moodleform_mod {
         //-------------------------------------------------------
         $mform->addElement('header', 'content', get_string('contentheader', 'url'));
         $mform->addElement('url', 'externalurl', get_string('externalurl', 'url'), array('size'=>'60'), array('usefilepicker'=>true));
+        $mform->addRule('externalurl', null, 'required', null, 'client');
         //-------------------------------------------------------
         $mform->addElement('header', 'optionssection', get_string('optionsheader', 'url'));
 
@@ -165,4 +166,14 @@ class mod_url_mod_form extends moodleform_mod {
         }
     }
 
+    function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+        //Validating Entered url
+        $data['externalurl'] = clean_param($data['externalurl'], PARAM_URL);
+        if (empty($data['externalurl'])) {
+            $errors['externalurl'] = get_string('invalidurl', 'url');
+        }
+        return $errors;
+    }
+
 }
index 5b4c1b5..a769049 100644 (file)
@@ -26,7 +26,7 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$module->version  = 2010101400;
+$module->version  = 2011092800;
 $module->requires = 2010080300;  // Requires this Moodle version
 $module->cron     = 0;
 
index 59690f7..4e19f57 100644 (file)
@@ -1218,7 +1218,7 @@ class page_wiki_history extends page_wiki {
         $creator = wiki_get_user_info($version0page->userid);
         $a = new StdClass;
         $a->date = userdate($this->page->timecreated, get_string('strftimedaydatetime', 'langconfig'));
-        $a->username = $creator->username;
+        $a->username = fullname($creator);
         echo $OUTPUT->heading(get_string('createddate', 'wiki', $a), 4, 'wiki_headingtime');
         if ($vcount > 0) {
 
@@ -2545,7 +2545,7 @@ class page_wiki_admin extends page_wiki {
         $creator = wiki_get_user_info($version0page->userid);
         $a = new stdClass();
         $a->date = userdate($this->page->timecreated, get_string('strftimedaydatetime', 'langconfig'));
-        $a->username = $creator->username;
+        $a->username = fullname($creator);
         echo $OUTPUT->heading(get_string('createddate', 'wiki', $a), 4, 'wiki_headingtime');
         if ($versioncount > 0) {
             /// If there is only one version, we don't need radios nor forms
index 5bcee3b..c663b52 100644 (file)
@@ -127,16 +127,24 @@ class qbehaviour_adaptive extends question_behaviour_with_save {
             return $status;
         }
 
+        $prevstep = $this->qa->get_last_step_with_behaviour_var('_try');
+        $prevresponse = $prevstep->get_qt_data();
         $prevtries = $this->qa->get_last_behaviour_var('_try', 0);
         $prevbest = $pendingstep->get_fraction();
         if (is_null($prevbest)) {
             $prevbest = 0;
         }
 
+        if ($this->question->is_same_response($response, $prevresponse)) {
+            return question_attempt::DISCARD;
+        }
+
         list($fraction, $state) = $this->question->grade_response($response);
 
         $pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
-        if ($state == question_state::$gradedright) {
+        if ($prevstep->get_state() == question_state::$complete) {
+            $pendingstep->set_state(question_state::$complete);
+        } else if ($state == question_state::$gradedright) {
             $pendingstep->set_state(question_state::$complete);
         } else {
             $pendingstep->set_state(question_state::$todo);
@@ -153,32 +161,59 @@ class qbehaviour_adaptive extends question_behaviour_with_save {
             return question_attempt::DISCARD;
         }
 
-        $laststep = $this->qa->get_last_step();
-        $response = $laststep->get_qt_data();
-        if (!$this->question->is_gradable_response($response)) {
-            $pendingstep->set_state(question_state::$gaveup);
-            return question_attempt::KEEP;
-        }
-
         $prevtries = $this->qa->get_last_behaviour_var('_try', 0);
-        $prevbest = $pendingstep->get_fraction();
+        $prevbest = $this->qa->get_fraction();
         if (is_null($prevbest)) {
             $prevbest = 0;
         }
 
-        if ($laststep->has_behaviour_var('_try')) {
-            // Last answer was graded, we want to regrade it. Otherwise the answer
-            // has changed, and we are grading a new try.
-            $prevtries -= 1;
-        }
+        $laststep = $this->qa->get_last_step();
+        $response = $laststep->get_qt_data();
+        if (!$this->question->is_gradable_response($response)) {
+            $state = question_state::$gaveup;
+            $fraction = 0;
+        } else {
 
-        list($fraction, $state) = $this->question->grade_response($response);
+            if ($laststep->has_behaviour_var('_try')) {
+                // Last answer was graded, we want to regrade it. Otherwise the answer
+                // has changed, and we are grading a new try.
+                $prevtries -= 1;
+            }
+
+            list($fraction, $state) = $this->question->grade_response($response);
+
+            $pendingstep->set_behaviour_var('_try', $prevtries + 1);
+            $pendingstep->set_behaviour_var('_rawfraction', $fraction);
+            $pendingstep->set_new_response_summary($this->question->summarise_response($response));
+        }
 
-        $pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
         $pendingstep->set_state($state);
-        $pendingstep->set_behaviour_var('_try', $prevtries + 1);
-        $pendingstep->set_behaviour_var('_rawfraction', $fraction);
-        $pendingstep->set_new_response_summary($this->question->summarise_response($response));
+        $pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
         return question_attempt::KEEP;
     }
+
+    /**
+     * Got the most recently graded step. This is mainly intended for use by the
+     * renderer.
+     * @return question_attempt_step the most recently graded step.
+     */
+    public function get_graded_step() {
+        $step = $this->qa->get_last_step_with_behaviour_var('_try');
+        if ($step->has_behaviour_var('_try')) {
+            return $step;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Determine whether a question state represents an "improvable" result,
+     * that is, whether the user can still improve their score.
+     *
+     * @param question_state $state the question state.
+     * @return bool whether the state is improvable
+     */
+    public function is_state_improvable(question_state $state) {
+        return $state == question_state::$todo;
+    }
 }
index da1a632..f5a984e 100644 (file)
@@ -36,13 +36,6 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
-    protected function get_graded_step(question_attempt $qa) {
-        foreach ($qa->get_reverse_step_iterator() as $step) {
-            if ($step->has_behaviour_var('_try')) {
-                return $step;
-            }
-        }
-    }
 
     public function controls(question_attempt $qa, question_display_options $options) {
         return $this->submit_button($qa, $options);
@@ -51,7 +44,7 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
     public function feedback(question_attempt $qa, question_display_options $options) {
         // Try to find the last graded step.
 
-        $gradedstep = $this->get_graded_step($qa);
+        $gradedstep = $qa->get_behaviour()->get_graded_step($qa);
         if (is_null($gradedstep) || $qa->get_max_mark() == 0 ||
                 $options->marks < question_display_options::MARK_AND_MAX) {
             return '';
@@ -100,14 +93,13 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
         }
         $output = '';
 
-        // print details of grade adjustment due to penalties
+        // Print details of grade adjustment due to penalties
         if ($mark->raw != $mark->cur) {
             $output .= ' ' . get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark);
         }
 
-        // print info about new penalty
-        // penalty is relevant only if the answer is not correct and further attempts are possible
-        if (!$qa->get_state()->is_finished()) {
+        // 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));
         }
index 26eb641..6a6e27c 100644 (file)
@@ -39,6 +39,18 @@ require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_base {
+    protected function get_contains_penalty_info_expectation($penalty) {
+        $penaltyinfo = get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
+                                  format_float($penalty, $this->displayoptions->markdp));
+        return new PatternExpectation('/'.preg_quote($penaltyinfo).'/');
+    }
+
+    protected function get_does_not_contain_penalty_info_expectation() {
+        $penaltyinfo = get_string('gradingdetailspenalty', '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.
@@ -72,7 +84,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mc_radio_expectation($wrongindex, true, true),
                 $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_incorrect_expectation(),
+                $this->get_contains_penalty_info_expectation(0.33));
         $this->assertPattern('/B|C/',
                 $this->quba->get_response_summary($this->slot));
 
@@ -102,9 +115,7 @@ 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(),
-                new PatternExpectation('/' . preg_quote(
-                        get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
-                            format_float($mc->penalty, $this->displayoptions->markdp))) . '/'));
+                $this->get_does_not_contain_penalty_info_expectation());
         $this->assertEqual('A',
                 $this->quba->get_response_summary($this->slot));
 
@@ -133,7 +144,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
 
         // Now change the correct answer to the question, and regrade.
         $mc->answers[13]->fraction = -0.33333333;
-        $mc->answers[15]->fraction = 1;
+        $mc->answers[14]->fraction = 1; // We don't know which "wrong" index we chose above!
+        $mc->answers[15]->fraction = 1; // Therefore, treat answers B and C with the same score.
         $this->quba->regrade_all_questions();
 
         // Verify.
@@ -144,7 +156,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_partcorrect_expectation());
 
         $autogradedstep = $this->get_step($this->get_step_count() - 2);
-        $this->assertWithinMargin($autogradedstep->get_fraction(), 0, 0.0000001);
+        $this->assertWithinMargin($autogradedstep->get_fraction(), 1, 0.0000001);
     }
 
     public function test_adaptive_multichoice2() {
@@ -173,14 +185,16 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_output(
                 $this->get_contains_mark_summary(2),
                 $this->get_contains_submit_button_expectation(true),
-                $this->get_contains_correct_expectation());
+                $this->get_contains_correct_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation());
 
-        // Save the same correct answer again. Should no do anything.
+        // Save the same correct answer again. Should not do anything.
         $numsteps = $this->get_step_count();
         $this->process_submission(array('choice0' => 1, 'choice2' => 1));
 
         // Verify.
         $this->check_step_count($numsteps);
+        $this->check_current_mark(2);
         $this->check_current_state(question_state::$complete);
 
         // Finish the attempt.
@@ -196,6 +210,229 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_correct_expectation());
     }
 
+    public function test_adaptive_shortanswer_partially_right() {
+
+        // Create a short answer question
+        $sa = test_question_maker::make_a_shortanswer_question();
+        $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 partially correct answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'toad'));
+
+        // Verify.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(0.8);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.8),
+                $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_validation_error_expectation());
+
+        // Submit an incorrect answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
+
+        // Verify.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(0.8);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.8),
+                $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_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(0.8);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.8),
+                $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_validation_error_expectation());
+
+        // Finish the attempt.
+        $this->quba->finish_all_questions();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedright);
+        $this->check_current_mark(0.8);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.8),
+                $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_wrong_right_wrong() {
+
+        // Create a short answer question
+        $sa = test_question_maker::make_a_shortanswer_question();
+        $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_contains_penalty_info_expectation(0.33),
+                $this->get_does_not_contain_validation_error_expectation());
+
+        // Submit the same wrong answer again. Nothing should change.
+        $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_contains_penalty_info_expectation(0.33),
+                $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(0.66666667);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.67),
+                $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_validation_error_expectation());
+
+        // Submit another incorrect answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
+
+        // Verify.
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(0.66666667);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.67),
+                $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_validation_error_expectation());
+
+        // Finish the attempt.
+        $this->quba->finish_all_questions();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedwrong);
+        $this->check_current_mark(0.66666667);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.67),
+                $this->get_contains_submit_button_expectation(false),
+                $this->get_contains_incorrect_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+    }
+
+    public function test_adaptive_shortanswer_invalid_after_complete() {
+
+        // Create a short answer question
+        $sa = test_question_maker::make_a_shortanswer_question();
+        $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_contains_penalty_info_expectation(0.33),
+                $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(0.66666667);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.67),
+                $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_validation_error_expectation());
+
+        // Submit an empty answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => ''));
+
+        // Verify.
+        $this->check_current_state(question_state::$invalid);
+        $this->check_current_mark(0.66666667);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.67),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_contains_validation_error_expectation());
+
+        // Submit another wrong answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
+
+        // Verify.
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(0.66666667);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.67),
+                $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_validation_error_expectation());
+
+        // Finish the attempt.
+        $this->quba->finish_all_questions();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedwrong);
+        $this->check_current_mark(0.66666667);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.67),
+                $this->get_contains_submit_button_expectation(false),
+                $this->get_contains_incorrect_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.
@@ -220,6 +457,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_marked_out_of_summary(),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
                 $this->get_contains_validation_error_expectation());
         $this->assertNull($this->quba->get_response_summary($this->slot));
 
@@ -233,6 +471,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mark_summary(0.8),
                 $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_validation_error_expectation());
 
         // Now submit blank again.
@@ -245,6 +484,60 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mark_summary(0.8),
                 $this->get_contains_submit_button_expectation(true),
                 $this->get_contains_partcorrect_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
                 $this->get_contains_validation_error_expectation());
     }
+
+    public function test_adaptive_numerical() {
+
+        // Create a numerical question
+        $sa = test_question_maker::make_question('numerical', 'pi');
+        $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 the correct answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
+
+        // Verify.
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(1);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1),
+                $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_validation_error_expectation());
+
+        // Submit an incorrect answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => '-5'));
+
+        // Verify.
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(1);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1),
+                $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_validation_error_expectation());
+
+        // Finish the attempt.
+        $this->quba->finish_all_questions();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedwrong);
+        $this->check_current_mark(1);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1),
+                $this->get_contains_submit_button_expectation(false),
+                $this->get_contains_incorrect_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+    }
 }
index 0cd5ff7..ecc45bd 100644 (file)
@@ -129,7 +129,8 @@ class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrou
 
         // Now change the correct answer to the question, and regrade.
         $mc->answers[13]->fraction = -0.33333333;
-        $mc->answers[15]->fraction = 1;
+        $mc->answers[14]->fraction = 1; // We don't know which "wrong" index we chose above!
+        $mc->answers[15]->fraction = 1; // Therefore, treat answers B and C with the same score.
         $this->quba->regrade_all_questions();
 
         // Verify.
@@ -139,8 +140,8 @@ class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrou
                 $this->get_contains_mark_summary(1),
                 $this->get_contains_partcorrect_expectation());
 
-        $autogradedstep = $this->get_step($this->get_step_count() - 2);
-        $this->assertWithinMargin($autogradedstep->get_fraction(), 0, 0.0000001);
+        $autogradedstep = $this->get_step($this->get_step_count() - 3);
+        $this->assertWithinMargin($autogradedstep->get_fraction(), 1, 0.0000001);
     }
 
     public function test_multichoice2() {
index a0eeb38..092eaa1 100644 (file)
@@ -695,8 +695,8 @@ ORDER BY
                 DELETE qu, qa, qas, qasd
                   FROM {question_usages}            qu
                   JOIN {question_attempts}          qa   ON qa.questionusageid = qu.id
-                  JOIN {question_attempt_steps}     qas  ON qas.questionattemptid = qa.id
-                  JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
+             LEFT JOIN {question_attempt_steps}     qas  ON qas.questionattemptid = qa.id
+             LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
                  WHERE qu.id ' . $qubaids->usage_id_in(),
                 $qubaids->usage_id_in_params());
     }
@@ -713,7 +713,7 @@ ORDER BY
         $this->db->execute('
                 DELETE qas, qasd
                   FROM {question_attempt_steps}     qas
-                  JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
+             LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
                  WHERE qas.questionattemptid ' . $test, $params);
     }
 
index 5f29d37..6d4811e 100644 (file)
@@ -415,6 +415,21 @@ class question_attempt {
         return new question_attempt_step_read_only();
     }
 
+    /**
+     * Get the last step with a particular behaviour variable set.
+     * @param string $name the name of the variable to get.
+     * @return question_attempt_step the last step, or a step with no variables
+     * if there was not a real step.
+     */
+    public function get_last_step_with_behaviour_var($name) {
+        foreach ($this->get_reverse_step_iterator() as $step) {
+            if ($step->has_behaviour_var($name)) {
+                return $step;
+            }
+        }
+        return new question_attempt_step_read_only();
+    }
+
     /**
      * Get the latest value of a particular question type variable. That is, get
      * the value from the latest step that has it set. Return null if it is not
index 2ce8564..4f84fe8 100644 (file)
@@ -53,7 +53,7 @@ $string['questioninquiz'] = '
   <li>change their question type (numerical, shortanswer, multiple choice). </li></ul>
 ';
 $string['questionsless'] = '{$a} question(s) less than in the multianswer question stored in the database';
-$string['questionsmissing'] = 'No valid questions, create at least one question';
+$string['questionsmissing'] = 'The question text must include at least one embedded answer.';
 $string['questionsmore'] = '{$a} question(s) more than in the multianswer question stored in the database';
 $string['questionnotfound'] = 'Unable to find question of question part #{$a}';
 $string['questionsaveasedited'] = 'The question will be saved as edited';
index 0e43a84..ea1b026 100644 (file)
@@ -153,7 +153,7 @@ class qtype_numerical_question extends question_graded_automatically {
                 $prevresponse, $newresponse, 'unit');
         }
 
-        return false;
+        return true;
     }
 
     public function get_correct_response() {
index 44e2b7e..2e41f00 100644 (file)
@@ -1,11 +1,10 @@
 /************************************************************************
-    Non sono riuscito a trovare un modo per sovrascrivere la regola
+    I could not find a way to override the rule
         border: 2px solid #ddd;
-    presente nel file
+    belonging to the file
         /theme/canvas/style/core.css
-    Per questo questo file è stato creato a partire dalla copia del
-    foglio di stile core.css del tema canvas, ed è stato adeguato alle
-    esigenze del tema corrente.
+    Therefore this file has been created from the copy of that css file
+    and adapted to the needs of the current theme.
 ************************************************************************/
 
 .sitetopic {
@@ -40,6 +39,7 @@
 label {
     margin-right: 0.3em;
 }
+
 .navbutton .singlebutton {
     margin: 0;
 }
@@ -111,10 +111,10 @@ input[type="radio"] {
 }
 
 .loginbox .loginform .form-label {
-    float: none;
-    width: 100%;
     margin: 0 auto;
-    text-align: left;
+    float: left;
+    text-align: right;
+    width: 40%;
 }
 
 .dir-rtl .loginbox .loginform .form-label {
@@ -122,8 +122,8 @@ input[type="radio"] {
 }
 
 .loginbox .loginform .form-input {
-    float: none;
-    width: 100%;
+    float: right;
+    width: 59%;
     margin: 0 auto;
 }
 
index 8d46a1d..209f54f 100644 (file)
@@ -48,7 +48,7 @@ if (substr($parts, -3) === '.js') {
 // if they are requesting a revision that's not -1, and they have supplied an
 // If-Modified-Since header, we can send back a 304 Not Modified since the
 // content never changes (the rev number is increased any time the content changes)
-if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
+if (strpos($parts, '/-1/') === false and (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))) {
     $lifetime = 60*60*24*30; // 30 days
     header('HTTP/1.1 304 Not Modified');
     header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
index a8e7138..6fa1608 100644 (file)
@@ -36,7 +36,7 @@ $userid = optional_param('id', $USER->id, PARAM_INT);    // user id
 $course = optional_param('course', SITEID, PARAM_INT);   // course id (defaults to Site)
 $cancelemailchange = optional_param('cancelemailchange', 0, PARAM_INT);   // course id (defaults to Site)
 
-$PAGE->set_url('/user/edit.php', array('course'=>$course, 'id'=>$userid, 'cancelemailchange'=>$cancelemailchange));
+$PAGE->set_url('/user/edit.php', array('course'=>$course, 'id'=>$userid));
 
 if (!$course = $DB->get_record('course', array('id'=>$course))) {
     print_error('invalidcourseid');
@@ -50,7 +50,8 @@ if ($course->id != SITEID) {
     }
     redirect(get_login_url());
 } else {
-    $PAGE->set_course($course);
+    $PAGE->set_context(get_system_context());
+    $PAGE->set_pagelayout('standard');
 }
 
 // Guest can not edit
index ae350fd..0c2e508 100644 (file)
@@ -31,10 +31,10 @@ defined('MOODLE_INTERNAL') || die();
 
 
 
-$version  = 2011100700.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2011101200.00;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
-$release  = '2.2dev (Build: 20111007)'; // Human-friendly version name
+$release  = '2.2dev (Build: 20111012)'; // Human-friendly version name
 
 $maturity = MATURITY_ALPHA;             // this version's maturity level
index a9437a8..c0be657 100644 (file)
@@ -76,7 +76,7 @@ class webservice_soap_server extends webservice_zend_server {
             $this->zend_server->setReturnResponse(true);
             //TODO: the error handling in Zend Soap server is useless, XML-RPC is much, much better :-(
             $this->zend_server->registerFaultException('moodle_exception');
-            $this->zend_server->registerFaultException('webservice_parameter_exception'); //deprecated - kept for backward compatibility
+            $this->zend_server->registerFaultException('webservice_parameter_exception'); //deprecated since Moodle 2.2 - kept for backward compatibility
             $this->zend_server->registerFaultException('invalid_parameter_exception');
             $this->zend_server->registerFaultException('invalid_response_exception');
         }