Merge branch 'MDL-27242-master' of git://github.com/sammarshallou/moodle
authorSam Hemelryk <sam@moodle.com>
Mon, 17 Oct 2011 04:28:32 +0000 (17:28 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 17 Oct 2011 04:28:32 +0000 (17:28 +1300)
35 files changed:
admin/settings/development.php
course/report/completion/index.php
course/report/progress/index.php
lang/en/admin.php
lang/en/debug.php
lang/en/webservice.php
lib/adminlib.php
lib/componentlib.class.php
lib/externallib.php
lib/filestorage/file_storage.php
lib/moodlelib.php
lib/setuplib.php
mod/assignment/type/upload/assignment.class.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/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/questionattempt.php
question/type/multianswer/lang/en/qtype_multianswer.php
question/type/numerical/question.php
theme/overlay/style/pagelayout.css
theme/sky_high/style/pagelayout.css
webservice/soap/locallib.php

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 5936ed3..0118cf1 100644 (file)
@@ -241,6 +241,9 @@ $pagingbar = '';
 foreach ($initials as $initial) {
     $var = 'si'.$initial;
 
+    $othervar = $initial == 'first' ? 'silast' : 'sifirst';
+    $othervar = $$othervar != 'all' ? "&amp;{$othervar}={$$othervar}" : '';
+
     $pagingbar .= ' <div class="initialbar '.$initial.'initial">';
     $pagingbar .= get_string($initial.'name').':&nbsp;';
 
@@ -248,7 +251,7 @@ foreach ($initials as $initial) {
         $pagingbar .= '<strong>'.get_string('all').'</strong> ';
     }
     else {
-        $pagingbar .= '<a href="'.$link.'">'.get_string('all').'</a> ';
+        $pagingbar .= "<a href=\"{$link}{$othervar}\">".get_string('all').'</a> ';
     }
 
     foreach ($alphabet as $letter) {
@@ -256,7 +259,7 @@ foreach ($initials as $initial) {
             $pagingbar .= '<strong>'.$letter.'</strong> ';
         }
         else {
-            $pagingbar .= '<a href="'.$link.'&amp;'.$var.'='.$letter.'">'.$letter.'</a> ';
+            $pagingbar .= "<a href=\"$link&amp;$var={$letter}{$othervar}\">$letter</a> ";
         }
     }
 
@@ -270,10 +273,15 @@ if($total > COMPLETION_REPORT_PAGE) {
     $pagingbar .= '<div class="paging">';
     $pagingbar .= get_string('page').': ';
 
+    $sistrings = array();
+    $sistrings[] = $sifirst != 'all' ? "sifirst={$sifirst}" : null;
+    $sistrings[] = $silast != 'all' ? "silast={$silast}" : null;
+    $sistring = !empty($sistrings) ? implode('&amp;', $sistrings) : '';
+
     // Display previous link
     if ($start > 0) {
         $pstart = max($start - COMPLETION_REPORT_PAGE, 0);
-        $pagingbar .= '(<a class="previous" href="'.$link.$pstart.'">'.get_string('previous').'</a>)&nbsp;';
+        $pagingbar .= "(<a class=\"previous\" href=\"{$link}{$pstart}{$sistring}\">".get_string('previous').'</a>)&nbsp;';
     }
 
     // Create page links
@@ -286,7 +294,7 @@ if($total > COMPLETION_REPORT_PAGE) {
             $pagingbar .= '&nbsp;'.$curpage.'&nbsp;';
         }
         else {
-            $pagingbar .= '&nbsp;<a href="'.$link.$curstart.'">'.$curpage.'</a>&nbsp;';
+            $pagingbar .= "&nbsp;<a href=\"{$link}{$curstart}{$sistring}\">$curpage</a>&nbsp;";
         }
 
         $curstart += COMPLETION_REPORT_PAGE;
@@ -295,7 +303,7 @@ if($total > COMPLETION_REPORT_PAGE) {
     // Display next link
     $nstart = $start + COMPLETION_REPORT_PAGE;
     if ($nstart < $total) {
-        $pagingbar .= '&nbsp;(<a class="next" href="'.$link.$nstart.'">'.get_string('next').'</a>)';
+        $pagingbar .= "&nbsp;(<a class=\"next\" href=\"{$link}{$nstart}{$sistring}\">".get_string('next').'</a>)';
     }
 
     $pagingbar .= '</div>';
@@ -441,12 +449,15 @@ if(!$csv) {
 
     // User heading / sort option
     print '<th scope="col" class="completion-sortchoice" style="clear: both;">';
+
+    $sistring = "&amp;silast={$silast}&amp;sifirst={$sifirst}";
+
     if($firstnamesort) {
         print
-            get_string('firstname').' / <a href="./?course='.$course->id.'">'.
+            get_string('firstname')." / <a href=\"./?course={$course->id}{$sistring}\">".
             get_string('lastname').'</a>';
     } else {
-        print '<a href="./?course='.$course->id.'&amp;sort=firstname">'.
+        print "<a href=\"./?course={$course->id}&amp;sort=firstname{$sistring}\">".
             get_string('firstname').'</a> / '.
             get_string('lastname');
     }
index b205c0b..a45a643 100644 (file)
@@ -173,6 +173,9 @@ $pagingbar = '';
 foreach ($initials as $initial) {
     $var = 'si'.$initial;
 
+    $othervar = $initial == 'first' ? 'silast' : 'sifirst';
+    $othervar = $$othervar != 'all' ? "&amp;{$othervar}={$$othervar}" : '';
+
     $pagingbar .= ' <div class="initialbar '.$initial.'initial">';
     $pagingbar .= get_string($initial.'name').':&nbsp;';
 
@@ -180,7 +183,7 @@ foreach ($initials as $initial) {
         $pagingbar .= '<strong>'.get_string('all').'</strong> ';
     }
     else {
-        $pagingbar .= '<a href="'.$link.'">'.get_string('all').'</a> ';
+        $pagingbar .= "<a href=\"{$link}{$othervar}\">".get_string('all').'</a> ';
     }
 
     foreach ($alphabet as $letter) {
@@ -188,7 +191,7 @@ foreach ($initials as $initial) {
             $pagingbar .= '<strong>'.$letter.'</strong> ';
         }
         else {
-            $pagingbar .= '<a href="'.$link.'&amp;'.$var.'='.$letter.'">'.$letter.'</a> ';
+            $pagingbar .= "<a href=\"$link&amp;$var={$letter}{$othervar}\">$letter</a> ";
         }
     }
 
@@ -202,10 +205,15 @@ if($total > COMPLETION_REPORT_PAGE) {
     $pagingbar .= '<div class="paging">';
     $pagingbar .= get_string('page').': ';
 
+    $sistrings = array();
+    $sistrings[] = $sifirst != 'all' ? "sifirst={$sifirst}" : null;
+    $sistrings[] = $silast != 'all' ? "silast={$silast}" : null;
+    $sistring = !empty($sistrings) ? implode('&amp;', $sistrings) : '';
+
     // Display previous link
     if ($start > 0) {
         $pstart = max($start - COMPLETION_REPORT_PAGE, 0);
-        $pagingbar .= '(<a class="previous" href="'.$link.$pstart.'">'.get_string('previous').'</a>)&nbsp;';
+        $pagingbar .= "(<a class=\"previous\" href=\"{$link}{$pstart}{$sistring}\">".get_string('previous').'</a>)&nbsp;';
     }
 
     // Create page links
@@ -218,7 +226,7 @@ if($total > COMPLETION_REPORT_PAGE) {
             $pagingbar .= '&nbsp;'.$curpage.'&nbsp;';
         }
         else {
-            $pagingbar .= '&nbsp;<a href="'.$link.$curstart.'">'.$curpage.'</a>&nbsp;';
+            $pagingbar .= "&nbsp;<a href=\"{$link}{$curstart}{$sistring}\">$curpage</a>&nbsp;";
         }
 
         $curstart += COMPLETION_REPORT_PAGE;
@@ -227,7 +235,7 @@ if($total > COMPLETION_REPORT_PAGE) {
     // Display next link
     $nstart = $start + COMPLETION_REPORT_PAGE;
     if ($nstart < $total) {
-        $pagingbar .= '&nbsp;(<a class="next" href="'.$link.$nstart.'">'.get_string('next').'</a>)';
+        $pagingbar .= "&nbsp;(<a class=\"next\" href=\"{$link}{$nstart}{$sistring}\">".get_string('next').'</a>)';
     }
 
     $pagingbar .= '</div>';
@@ -251,12 +259,15 @@ if(!$csv) {
 
     // User heading / sort option
     print '<th scope="col" class="completion-sortchoice">';
+
+    $sistring = "&amp;silast={$silast}&amp;sifirst={$sifirst}";
+
     if($firstnamesort) {
         print
-            get_string('firstname').' / <a href="./?course='.$course->id.'">'.
+            get_string('firstname')." / <a href=\"./?course={$course->id}{$sistring}\">".
             get_string('lastname').'</a>';
     } else {
-        print '<a href="./?course='.$course->id.'&amp;sort=firstname">'.
+        print "<a href=\"./?course={$course->id}&amp;sort=firstname{$sistring}\">".
             get_string('firstname').'</a> / '.
             get_string('lastname');
     }
index 03c0ed7..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';
index f951261..a7e2563 100644 (file)
@@ -35,8 +35,8 @@ $string['configmoodle'] = 'Moodle has not been configured yet. You need to edit
 $string['erroroccur'] = 'An error has occurred during this process';
 $string['invalidarraysize'] = 'Incorrect size of arrays in params of {$a}';
 $string['invalideventdata'] = 'Incorrect eventadata submitted: {$a}';
-$string['invalidparameter'] = 'Invalid parameter value detected, execution can not continue.';
-$string['invalidresponse'] = 'Invalid response value detected, execution can not continue.';
+$string['invalidparameter'] = 'Invalid parameter value detected';
+$string['invalidresponse'] = 'Invalid response value detected';
 $string['missingconfigversion'] = 'Config table does not contain version, can not continue, sorry.';
 $string['modulenotexist'] = '{$a} module doesn\'t exist';
 $string['morethanonerecordinfetch'] = 'Found more than one record in fetch() !';
index d4f16bd..075ca23 100644 (file)
@@ -79,17 +79,8 @@ $string['errorcatcontextnotvalid'] = 'You cannot execute functions in the catego
 $string['errorcodes'] = 'Error message';
 $string['errorcoursecontextnotvalid'] = 'You cannot execute functions in the course context (course id:{$a->courseid}). The context error message was: {$a->message}';
 $string['errorinvalidparam'] = 'The param "{$a}" is invalid.';
-$string['errorinvalidparamsapi'] = 'Invalid external api parameter';
-$string['errorinvalidparamsdesc'] = 'Invalid external api description';
-$string['errorinvalidresponseapi'] = 'Invalid external api response';
-$string['errorinvalidresponsedesc'] = 'Invalid external api response description';
-$string['errormissingkey'] = 'Missing required key in single structure: {$a}';
 $string['errornotemptydefaultparamarray'] = 'The web service description parameter named \'{$a}\' is an single or multiple structure. The default can only be empty array. Check web service description.';
-$string['erroronlyarray'] = 'Only arrays accepted.';
 $string['erroroptionalparamarray'] = 'The web service description parameter named \'{$a}\' is an single or multiple structure. It can not be set as VALUE_OPTIONAL. Check web service description.';
-$string['errorresponsemissingkey'] = 'Error in response - Missing following required key in a single structure: {$a}';
-$string['errorscalartype'] = 'Scalar type expected, array or object received.';
-$string['errorunexpectedkey'] = 'Unexpected keys ({$a}) detected in parameter array.';
 $string['execute'] = 'Execute';
 $string['executewarnign'] = 'WARNING: If you press execute your database will be modified and changes can not be reverted automatically!';
 $string['externalservice'] = 'External service';
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 0efc009..c1f4fcd 100644 (file)
@@ -149,7 +149,7 @@ class external_api {
     public static function validate_parameters(external_description $description, $params) {
         if ($description instanceof external_value) {
             if (is_array($params) or is_object($params)) {
-                throw new invalid_parameter_exception(get_string('errorscalartype', 'webservice'));
+                throw new invalid_parameter_exception('Scalar type expected, array or object received.');
             }
 
             if ($description->type == PARAM_BOOL) {
@@ -158,48 +158,50 @@ class external_api {
                     return (bool)$params;
                 }
             }
-            return validate_param($params, $description->type, $description->allownull, get_string('errorinvalidparamsapi', 'webservice'));
+            $debuginfo = 'Invalid external api parameter: the value is "' . $params .
+                    '", the server was expecting "' . $description->type . '" type';
+            return validate_param($params, $description->type, $description->allownull, $debuginfo);
 
         } else if ($description instanceof external_single_structure) {
             if (!is_array($params)) {
-                throw new invalid_parameter_exception(get_string('erroronlyarray', 'webservice'));
+                throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \''
+                        . print_r($params, true) . '\'');
             }
             $result = array();
             foreach ($description->keys as $key=>$subdesc) {
                 if (!array_key_exists($key, $params)) {
                     if ($subdesc->required == VALUE_REQUIRED) {
-                        throw new invalid_parameter_exception(get_string('errormissingkey', 'webservice', $key));
+                        throw new invalid_parameter_exception('Missing required key in single structure: '. $key);
                     }
                     if ($subdesc->required == VALUE_DEFAULT) {
                         try {
                             $result[$key] = self::validate_parameters($subdesc, $subdesc->default);
                         } catch (invalid_parameter_exception $e) {
-                            throw new webservice_parameter_exception('invalidextparam',$key);
+                            //we are only interested by exceptions returned by validate_param() and validate_parameters()
+                            //(in order to build the path to the faulty attribut)
+                            throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo);
                         }
                     }
                 } else {
                     try {
                         $result[$key] = self::validate_parameters($subdesc, $params[$key]);
                     } catch (invalid_parameter_exception $e) {
-                        //it's ok to display debug info as here the information is useful for ws client/dev
-                        throw new webservice_parameter_exception('invalidextparam',$key." (".$e->debuginfo.")");
+                        //we are only interested by exceptions returned by validate_param() and validate_parameters()
+                        //(in order to build the path to the faulty attribut)
+                        throw new invalid_parameter_exception($key." => ".$e->getMessage() . ': ' .$e->debuginfo);
                     }
                 }
                 unset($params[$key]);
             }
             if (!empty($params)) {
-                //list all unexpected keys
-                $keys = '';
-                foreach($params as $key => $value) {
-                    $keys .= $key . ',';
-                }
-                throw new invalid_parameter_exception(get_string('errorunexpectedkey', 'webservice', $keys));
+                throw new invalid_parameter_exception('Unexpected keys (' . implode(', ', array_keys($params)) . ') detected in parameter array.');
             }
             return $result;
 
         } else if ($description instanceof external_multiple_structure) {
             if (!is_array($params)) {
-                throw new invalid_parameter_exception(get_string('erroronlyarray', 'webservice'));
+                throw new invalid_parameter_exception('Only arrays accepted. The bad value is: \''
+                        . print_r($params, true) . '\'');
             }
             $result = array();
             foreach ($params as $param) {
@@ -208,7 +210,7 @@ class external_api {
             return $result;
 
         } else {
-            throw new invalid_parameter_exception(get_string('errorinvalidparamsdesc', 'webservice'));
+            throw new invalid_parameter_exception('Invalid external api description');
         }
     }
 
@@ -225,7 +227,7 @@ class external_api {
     public static function clean_returnvalue(external_description $description, $response) {
         if ($description instanceof external_value) {
             if (is_array($response) or is_object($response)) {
-                throw new invalid_response_exception(get_string('errorscalartype', 'webservice'));
+                throw new invalid_response_exception('Scalar type expected, array or object received.');
             }
 
             if ($description->type == PARAM_BOOL) {
@@ -234,33 +236,42 @@ class external_api {
                     return (bool)$response;
                 }
             }
-            return validate_param($response, $description->type, $description->allownull, get_string('errorinvalidresponseapi', 'webservice'));
+            $debuginfo = 'Invalid external api response: the value is "' . $response .
+                    '", the server was expecting "' . $description->type . '" type';
+            try {
+                return validate_param($response, $description->type, $description->allownull, $debuginfo);
+            } catch (invalid_parameter_exception $e) {
+                //proper exception name, to be recursively catched to build the path to the faulty attribut
+                throw new invalid_response_exception($e->debuginfo);
+            }
 
         } else if ($description instanceof external_single_structure) {
             if (!is_array($response)) {
-                throw new invalid_response_exception(get_string('erroronlyarray', 'webservice'));
+                throw new invalid_response_exception('Only arrays accepted. The bad value is: \'' .
+                        print_r($response, true) . '\'');
             }
             $result = array();
             foreach ($description->keys as $key=>$subdesc) {
                 if (!array_key_exists($key, $response)) {
                     if ($subdesc->required == VALUE_REQUIRED) {
-                        throw new webservice_parameter_exception('errorresponsemissingkey', $key);
+                        throw new invalid_response_exception('Error in response - Missing following required key in a single structure: ' . $key);
                     }
                     if ($subdesc instanceof external_value) {
                         if ($subdesc->required == VALUE_DEFAULT) {
                             try {
                                     $result[$key] = self::clean_returnvalue($subdesc, $subdesc->default);
-                            } catch (Exception $e) {
-                                    throw new webservice_parameter_exception('invalidextresponse',$key." (".$e->debuginfo.")");
+                            } catch (invalid_response_exception $e) {
+                                //build the path to the faulty attribut
+                                throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo);
                             }
                         }
                     }
                 } else {
                     try {
                         $result[$key] = self::clean_returnvalue($subdesc, $response[$key]);
-                    } catch (Exception $e) {
-                        //it's ok to display debug info as here the information is useful for ws client/dev
-                        throw new webservice_parameter_exception('invalidextresponse',$key." (".$e->debuginfo.")");
+                    } catch (invalid_response_exception $e) {
+                        //build the path to the faulty attribut
+                        throw new invalid_response_exception($key." => ".$e->getMessage() . ': ' . $e->debuginfo);
                     }
                 }
                 unset($response[$key]);
@@ -270,7 +281,8 @@ class external_api {
 
         } else if ($description instanceof external_multiple_structure) {
             if (!is_array($response)) {
-                throw new invalid_response_exception(get_string('erroronlyarray', 'webservice'));
+                throw new invalid_response_exception('Only arrays accepted. The bad value is: \'' .
+                        print_r($response, true) . '\'');
             }
             $result = array();
             foreach ($response as $param) {
@@ -279,7 +291,7 @@ class external_api {
             return $result;
 
         } else {
-            throw new invalid_response_exception(get_string('errorinvalidresponsedesc', 'webservice'));
+            throw new invalid_response_exception('Invalid external api response description');
         }
     }
 
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 5f5e935..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;
 }
 
 /**
index f45221b..9b300c2 100644 (file)
@@ -139,7 +139,7 @@ class require_login_exception extends moodle_exception {
 
 /**
  * Web service parameter exception class
- *
+ * @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
  */
@@ -149,8 +149,8 @@ class webservice_parameter_exception extends moodle_exception {
      * @param string $errorcode The name of the string from webservice.php to print
      * @param string $a The name of the parameter
      */
-    function __construct($errorcode=null, $a = '') {
-        parent::__construct($errorcode, 'webservice', '', $a, null);
+    function __construct($errorcode=null, $a = '', $debuginfo = null) {
+        parent::__construct($errorcode, 'webservice', '', $a, $debuginfo);
     }
 }
 
index b909f25..a711570 100644 (file)
@@ -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 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 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 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 9c951e6..22f1792 100644 (file)
@@ -1,37 +1,43 @@
 /** Path: theme pagelayout **/
-body {margin:auto 0px;width:auto; height: 100%}
-#page {width:100%; min-height: 100%;}
+body {margin:auto 0px;
+    width:auto;
+    height: 100%
+}
+#page {
+    width:100%;
+    min-height: 100%;
+}
 #page-content {
     clear: both;
     position: relative;
-    width: 100%;min-height: 100%;
+    width: 100%;
+    min-height: 100%;
 }
-
 #page-content #region-main-box {
     float: left;
     margin-left: -200px;
     position: relative;
     width: 200%;
-    right: 100%;min-height: 100%;
+    right: 100%;
+    min-height: 100%;
 }
-
 #page-content #region-post-box {
     float: left;
     margin-left: -200px;
-    width: 100%;min-height: 100%;
+    width: 100%;
+    min-height: 100%;
 }
-
 #page-content #region-main-wrap {
     float: left;
-    width: 50%;min-height: 100%;
+    width: 50%;
+    min-height: 100%;
 }
-
 #page-content #region-main {
     position: relative;
     margin-left: 400px;
-    left: 100%;min-height: 100%;
+    left: 100%;
+    min-height: 100%;
 }
-
 #page-content #region-pre {
     float: right;
     position: relative;
@@ -39,32 +45,29 @@ body {margin:auto 0px;width:auto; height: 100%}
     left: 200px;
     min-height: 100%;
 }
-
 #page-content #region-post {
     float: right;
     position: relative;
     left: 600px;
-    width: 200px;min-height: 100%;
+    width: 200px;
+    min-height: 100%;
 }
-
 #page-content #region-main .region-content {
     overflow: hidden;
-    padding: 20px 20px 20px 0;min-height: 100%;
+    padding: 20px 20px 20px 0;
+    min-height: 100%;
 }
-
 .pagelayout-report #page-content #region-main .region-content {
     overflow: auto;
-    padding-bottom:0;
-    margin-bottom:20px;
+    padding-bottom: 0;
+    margin-bottom: 20px;
 }
-
 #page-content #region-pre .region-content,
 #page-content #region-post .region-content {
     overflow: hidden;
     padding: 20px 10px;
     min-height: 100%;
 }
-
 #page-footer {
     clear: both;
     float: left;
@@ -74,45 +77,78 @@ body {margin:auto 0px;width:auto; height: 100%}
 /** Only side pre **/
 
 .side-pre-only #page-content #region-main-box {
-    margin-left: 0px;
+    margin-left: 0;
 }
-
 .side-pre-only #page-content #region-main-box #region-post-box {
     margin-left: -200px;
 }
-
 .side-pre-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {
     margin-left: 200px;
 }
-
 .side-pre-only #page-content #region-main-box #region-post-box #region-pre {
     left: 200px;
     width: 200px;
 }
-
 .side-pre-only #page-content #region-main-box #region-post-box #region-post {
-    width: 0%;
+    width: 0;
 }
 
 /** Only side post **/
 
 .side-post-only #page-content #region-main-box {
-    margin-left: 0px;
+    margin-left: 0;
 }
-
 .side-post-only #page-content #region-main-box #region-post-box {
     margin-left: -200px;
 }
-
 .side-post-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {
     margin-left: 200px;
 }
-
+.side-post-only #page-content #region-main-box #region-post-box #region-pre {
+    left: 0;
+    width: 0;
+}
 .side-post-only #page-content #region-main-box #region-post-box #region-post {
-    left: 400px;
+    left: 200px;
     width: 200px;
 }
-
+.blocks-moving.side-post-only #page-content #region-main-box {
+    float: left;
+    margin-left: -200px;
+    position: relative;
+    width: 200%;
+    right: 100%;min-height: 100%;
+}
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box {
+    float: left;
+    margin-left: -200px;
+    width: 100%;min-height: 100%;
+}
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-main-wrap {
+    float: left;
+    width: 50%;
+    min-height: 100%;
+}
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {
+    position: relative;
+    margin-left: 400px;
+    left: 100%;
+    min-height: 100%;
+}
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-pre {
+    float: right;
+    position: relative;
+    width: 200px;
+    left: 200px;
+    min-height: 100%;
+}
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-post {
+    float: right;
+    position: relative;
+    left: 600px;
+    width: 200px;
+    min-height: 100%;
+}
 .has_dock.side-post-only .page-middle #region-main-box #region-post-box #region-main-wrap #region-main {
     margin-left: 200px;
 }
@@ -120,21 +156,17 @@ body {margin:auto 0px;width:auto; height: 100%}
 /** No blocks whatsoever **/
 
 .content-only #page-content #region-main-box {
-    margin-left: 0px;
+    margin-left: 0;
 }
-
 .content-only #page-content #region-main-box #region-post-box {
-    margin-left: 0px;
+    margin-left: 0;
 }
-
 .content-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {
-    margin-left: 0px;
+    margin-left: 0;
 }
-
 .content-only #page-content #region-main-box #region-post-box #region-pre {
-    width: 0px;
+    width: 0;
 }
-
 .content-only #page-content #region-main-box #region-post-box #region-post {
-    width: 0px;
+    width: 0;
 }
\ No newline at end of file
index 349eb10..1bf154a 100644 (file)
@@ -1,12 +1,7 @@
 /** Path: theme pagelayout **/
 
 /*********************************************************************************************
-
-    left column: 230px
-    right column: 330px
-    padding left/right column: 10px
-    padding center column: 30px
-
+    column witdh: [[setting:regionwidth]]
 **********************************************************************************************/
 
 body {
@@ -91,8 +86,6 @@ body {
     width: 100%;
 }
 
-
-
 /* @end */
 
 /* @group Pre Side Only */
@@ -118,8 +111,6 @@ body {
     width: 0%;
 }
 
-
-
 /* @end */
 
 /* @group Post Side Only */
@@ -136,8 +127,13 @@ body {
     margin-left: [[setting:regionwidth]];
 }
 
+.side-post-only #page-content #region-main-box #region-post-box #region-pre {
+    left: 0;
+    width: 0;
+}
+
 .side-post-only #page-content #region-main-box #region-post-box #region-post {
-    left: [[setting:regionwidthdouble]];
+    left: [[setting:regionwidth]];
     width: [[setting:regionwidth]];
 }
 
@@ -145,7 +141,49 @@ body {
     margin-left: 200px;
 }
 
+.blocks-moving.side-post-only #page-content #region-main-box {
+    float: left;
+    margin-left: -[[setting:regionwidth]];
+    position: relative;
+    width: 200%;
+    right: 100%;
+}
 
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box {
+    float: left;
+    margin-left: -[[setting:regionwidth]];
+    width: 100%;
+    border-right: 2px solid #98bcd6;
+    background: url([[pix:theme|top_bg]]) repeat-x top #fff;
+}
+
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-main-wrap {
+    float: left;
+    width: 50%;
+}
+
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-main-wrap #region-main {
+    overflow: hidden;
+    position: relative;
+    margin-left: [[setting:regionwidthdouble]];
+    left: 100%;
+}
+
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-pre {
+    float: right;
+    position: relative;
+    left: [[setting:leftregionwidthmargin]];
+    width: [[setting:regionwidth]];
+    background: transparent;
+}
+
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-post {
+    float: right;
+    position: relative;
+    left: [[setting:rightregionwidthmargin]];
+    width: [[setting:regionwidth]];
+    background: transparent;
+}
 
 /* @end */
 
@@ -171,12 +209,13 @@ body {
     width: 0px;
 }
 
+/* @end */
 
+/* @pagelayout-report - overflow */
 
-/* @end */
 .pagelayout-report #page-content #region-main {
-    overflow:auto;
+    overflow: auto;
 }
 .pagelayout-report #page-content #region-main .region-content {
-    overflow:visible;
+    overflow: visible;
 }
\ No newline at end of file
index 426b05a..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');
+            $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');
         }