Merge branch 'MDL-29719-master' of git://github.com/sammarshallou/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 17 Oct 2011 10:05:05 +0000 (12:05 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 17 Oct 2011 10:05:05 +0000 (12:05 +0200)
47 files changed:
admin/environment.xml
admin/settings/development.php
course/modedit.php
course/moodleform_mod.php
course/report/completion/index.php
course/report/progress/index.php
lang/en/admin.php
lang/en/condition.php
lang/en/debug.php
lang/en/webservice.php
lib/adminlib.php
lib/componentlib.class.php
lib/conditionlib.php
lib/db/upgrade.php
lib/externallib.php
lib/filestorage/file_storage.php
lib/form/datetimeselector.php
lib/moodlelib.php
lib/setuplib.php
lib/zend/readme_moodle.txt
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/nonzero/layout/general.php
theme/nonzero/style/pagelayout.css
theme/overlay/style/pagelayout.css
theme/sky_high/style/pagelayout.css
version.php
webservice/soap/locallib.php
webservice/xmlrpc/locallib.php

index 25d3c13..a064ba8 100644 (file)
       </PHP_SETTING>
     </PHP_SETTINGS>
   </MOODLE>
+  <MOODLE version="2.2" requires="1.9">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mysql" version="5.0.25">
+        <FEEDBACK>
+          <ON_ERROR message="mysql416required" />
+        </FEEDBACK>
+      </VENDOR>
+      <VENDOR name="postgres" version="8.3" />
+      <VENDOR name="mssql" version="9.0" />
+      <VENDOR name="odbc_mssql" version="9.0" />
+      <VENDOR name="mssql_n" version="9.0" />
+      <VENDOR name="oracle" version="10.2" />
+      <VENDOR name="sqlite" version="2.0" />
+    </DATABASE>
+    <PHP version="5.3.2" level="required">
+    </PHP>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opensslrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="gdrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="intlrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="40M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="safe_mode" value="0" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingsafemode" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index 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 a68ddcc..111e655 100644 (file)
@@ -302,12 +302,6 @@ if ($mform->is_cancelled()) {
         if (!empty($CFG->enableavailability)) {
             $cm->availablefrom             = $fromform->availablefrom;
             $cm->availableuntil            = $fromform->availableuntil;
-            // The form time is midnight, but because we want it to be
-            // inclusive, set it to 23:59:59 on that day.
-            if ($cm->availableuntil) {
-                $cm->availableuntil = strtotime('23:59:59',
-                    $cm->availableuntil);
-            }
             $cm->showavailability          = $fromform->showavailability;
             condition_info::update_cm_from_form($cm,$fromform,true);
         }
@@ -393,12 +387,6 @@ if ($mform->is_cancelled()) {
         if(!empty($CFG->enableavailability)) {
             $newcm->availablefrom             = $fromform->availablefrom;
             $newcm->availableuntil            = $fromform->availableuntil;
-            // The form time is midnight, but because we want it to be
-            // inclusive, set it to 23:59:59 on that day.
-            if ($newcm->availableuntil) {
-                $newcm->availableuntil = strtotime('23:59:59',
-                    $newcm->availableuntil);
-            }
             $newcm->showavailability          = $fromform->showavailability;
         }
         if (isset($fromform->showdescription)) {
index ce5666b..b4fbb2f 100644 (file)
@@ -308,7 +308,7 @@ abstract class moodleform_mod extends moodleform {
         // Conditions: Don't let them set dates which make no sense
         if (array_key_exists('availablefrom', $data) &&
             $data['availablefrom'] && $data['availableuntil'] &&
-            $data['availablefrom'] > $data['availableuntil']) {
+            $data['availablefrom'] >= $data['availableuntil']) {
             $errors['availablefrom'] = get_string('badavailabledates', 'condition');
         }
 
@@ -429,10 +429,23 @@ abstract class moodleform_mod extends moodleform {
 
         if (!empty($CFG->enableavailability)) {
             // Conditional availability
-            $mform->addElement('header', 'availabilityconditionsheader', get_string('availabilityconditions', 'condition'));
-            $mform->addElement('date_selector', 'availablefrom', get_string('availablefrom', 'condition'), array('optional'=>true));
+
+            // Available from/to defaults to midnight because then the display
+            // will be nicer where it tells users when they can access it (it
+            // shows only the date and not time).
+            $date = usergetdate(time());
+            $midnight = make_timestamp($date['year'], $date['mon'], $date['mday']);
+
+            // From/until controls
+            $mform->addElement('header', 'availabilityconditionsheader',
+                    get_string('availabilityconditions', 'condition'));
+            $mform->addElement('date_time_selector', 'availablefrom',
+                    get_string('availablefrom', 'condition'),
+                    array('optional' => true, 'defaulttime' => $midnight));
             $mform->addHelpButton('availablefrom', 'availablefrom', 'condition');
-            $mform->addElement('date_selector', 'availableuntil', get_string('availableuntil', 'condition'), array('optional'=>true));
+            $mform->addElement('date_time_selector', 'availableuntil',
+                    get_string('availableuntil', 'condition'),
+                    array('optional' => true, 'defaulttime' => $midnight));
 
             // Conditions based on grades
             $gradeoptions = array();
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 a1bdd69..0f626f8 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';
@@ -405,6 +404,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 5c0fd77..ed05e32 100644 (file)
@@ -57,6 +57,7 @@ $string['requires_completion_3'] = 'Not available unless the activity <strong>{$
 $string['requires_date'] = 'Available from {$a}.';
 $string['requires_date_before'] = 'Available until {$a}.';
 $string['requires_date_both'] = 'Available from {$a->from} to {$a->until}.';
+$string['requires_date_both_single_day'] = 'Available on {$a}.';
 $string['requires_grade_any'] = 'Not available until you have a grade in <strong>{$a}</strong>.';
 $string['requires_grade_max'] = 'Not available unless you get an appropriate score in <strong>{$a}</strong>.';
 $string['requires_grade_min'] = 'Not available until you achieve a required score in <strong>{$a}</strong>.';
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 83b79d3..f3279a1 100644 (file)
@@ -331,24 +331,83 @@ WHERE
             }
         }
 
-        // Dates
+        // The date logic is complicated. The intention of this logic is:
+        // 1) display date without time where possible (whenever the date is
+        //    midnight)
+        // 2) when the 'until' date is e.g. 00:00 on the 14th, we display it as
+        //    'until the 13th' (experience at the OU showed that students are
+        //    likely to interpret 'until <date>' as 'until the end of <date>').
+        // 3) This behaviour becomes confusing for 'same-day' dates where there
+        //    are some exceptions.
+        // Users in different time zones will typically not get the 'abbreviated'
+        // behaviour but it should work OK for them aside from that.
+
+        // The following cases are possible:
+        // a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact)
+        // b) From 14 Oct until 12:11 on 17 Oct (midnight, exact)
+        // c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct)
+        // d) From 14 Oct until 17 Oct (midnight 14 Oct, midnight 18 Oct)
+        // e) On 14 Oct (midnight 14 Oct, midnight 15 Oct)
+        // f) From 13:05 on 14 Oct until 0:00 on 15 Oct (exact, midnight, same day)
+        // g) From 0:00 on 14 Oct until 12:05 on 14 Oct (midnight, exact, same day)
+        // h) From 13:05 on 14 Oct (exact)
+        // i) From 14 Oct (midnight)
+        // j) Until 13:05 on 14 Oct (exact)
+        // k) Until 14 Oct (midnight 15 Oct)
+
+        // Check if start and end dates are 'midnights', if so we show in short form
+        $shortfrom = self::is_midnight($this->cm->availablefrom);
+        $shortuntil = self::is_midnight($this->cm->availableuntil);
+
+        // For some checks and for display, we need the previous day for the 'until'
+        // value, if we are going to display it in short form
+        if ($this->cm->availableuntil) {
+            $daybeforeuntil = strtotime("-1 day", usergetmidnight($this->cm->availableuntil));
+        }
+
+        // Special case for if one but not both are exact and they are within a day
+        if ($this->cm->availablefrom && $this->cm->availableuntil &&
+                $shortfrom != $shortuntil && $daybeforeuntil < $this->cm->availablefrom) {
+            // Don't use abbreviated version (see examples f, g above)
+            $shortfrom = false;
+            $shortuntil = false;
+        }
+
+        // When showing short end date, the display time is the 'day before' one
+        $displayuntil = $shortuntil ? $daybeforeuntil : $this->cm->availableuntil;
+
         if ($this->cm->availablefrom && $this->cm->availableuntil) {
-            $information .= get_string('requires_date_both', 'condition',
-                (object)array(
-                    'from' => self::show_time($this->cm->availablefrom, false),
-                    'until' => self::show_time($this->cm->availableuntil, true)));
+            if ($shortfrom && $shortuntil && $daybeforeuntil == $this->cm->availablefrom) {
+                $information .= get_string('requires_date_both_single_day', 'condition',
+                        self::show_time($this->cm->availablefrom, true));
+            } else {
+                $information .= get_string('requires_date_both', 'condition', (object)array(
+                         'from' => self::show_time($this->cm->availablefrom, $shortfrom),
+                         'until' => self::show_time($displayuntil, $shortuntil)));
+            }
         } else if ($this->cm->availablefrom) {
             $information .= get_string('requires_date', 'condition',
-                self::show_time($this->cm->availablefrom, false));
+                self::show_time($this->cm->availablefrom, $shortfrom));
         } else if ($this->cm->availableuntil) {
             $information .= get_string('requires_date_before', 'condition',
-                self::show_time($this->cm->availableuntil, true));
+                self::show_time($displayuntil, $shortuntil));
         }
 
         $information = trim($information);
         return $information;
     }
 
+    /**
+     * Checks whether a given time refers exactly to midnight (in current user
+     * timezone).
+     * @param int $time Time
+     * @return bool True if time refers to midnight, false if it's some other
+     *   time or if it is set to zero
+     */
+    private static function is_midnight($time) {
+        return $time && usergetmidnight($time) == $time;
+    }
+
     /**
      * Determines whether this particular course-module is currently available
      * according to these criteria.
@@ -467,7 +526,8 @@ WHERE
                 $available = false;
 
                 $information .= get_string('requires_date', 'condition',
-                    self::show_time($this->cm->availablefrom, false));
+                        self::show_time($this->cm->availablefrom,
+                            self::is_midnight($this->cm->availablefrom)));
             }
         }
 
@@ -491,30 +551,15 @@ WHERE
     }
 
     /**
-     * Shows a time either as a date (if it falls exactly on the day) or
-     * a full date and time, according to user's timezone.
-     *
+     * Shows a time either as a date or a full date and time, according to
+     * user's timezone.
      * @param int $time Time
-     * @param bool $until True if this date should be treated as the second of
-     *   an inclusive pair - if so the time will be shown unless date is 23:59:59.
-     *   Without this the date shows for 0:00:00.
+     * @param bool $dateonly If true, uses date only
      * @return string Date
      */
-    private function show_time($time, $until) {
-        // Break down the time into fields
-        $userdate = usergetdate($time);
-
-        // Handle the 'inclusive' second date
-        if($until) {
-            $dateonly = $userdate['hours']==23 && $userdate['minutes']==59 &&
-                $userdate['seconds']==59;
-        } else {
-            $dateonly = $userdate['hours']==0 && $userdate['minutes']==0 &&
-                $userdate['seconds']==0;
-        }
-
-        return userdate($time, get_string(
-            $dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig'));
+    private function show_time($time, $dateonly) {
+        return userdate($time,
+                get_string($dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig'));
     }
 
     /**
index ad4056b..a100802 100644 (file)
@@ -6792,6 +6792,24 @@ FROM
         upgrade_main_savepoint(true, 2011100700.02);
     }
 
+    if ($oldversion < 2011101200.01) {
+        // The conditional availability date system used to rely on dates being
+        // set to 23:59:59 for the end date, but now that exact times are
+        // supported, it uses midnight on the following day.
+
+        // The query is restricted on 'time mod 10 = 9' in order that
+        // it is safe to run this upgrade twice if something goes wrong.
+        $DB->execute('UPDATE {course_modules} SET availableuntil = availableuntil + 1 ' .
+                'WHERE availableuntil > 0 AND ' . $DB->sql_modulo('availableuntil', 10) . ' = 9');
+
+        // Because availableuntil is stored in modinfo, we need to clear modinfo
+        // for all courses.
+        rebuild_course_cache(0, true);
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011101200.01);
+    }
+
     return true;
 }
 
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 94b7dfe..957d7a3 100644 (file)
@@ -40,12 +40,13 @@ class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group{
     *
     * startyear => integer start of range of years that can be selected
     * stopyear => integer last year that can be selected
+    * defaulttime => default time value if the field is currently not set
     * timezone => float/string timezone
     * applydst => apply users daylight savings adjustment?
     * step     => step to increment minutes by
     * optional => if true, show a checkbox beside the date to turn it on (or off)
     */
-    var $_options = array('startyear' => 1970, 'stopyear' => 2020,
+    var $_options = array('startyear' => 1970, 'stopyear' => 2020, 'defaulttime' => 0,
                     'timezone' => 99, 'applydst' => true, 'step' => 5, 'optional' => false);
 
    /**
@@ -159,7 +160,10 @@ class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group{
                 }
                 $requestvalue=$value;
                 if ($value == 0) {
-                    $value = time();
+                    $value = $this->_options['defaulttime'];
+                    if (!$value) {
+                        $value = time();
+                    }
                 }
                 if (!is_array($value)) {
                     $currentdate = usergetdate($value);
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 6e50e27..5c4fe00 100644 (file)
@@ -7,4 +7,5 @@ Do not use outside of our /webservice/* or mnet !!
 Changes:
 * lots of files removed
 * small fix to error reporting in reflection (MDL-21460, ZF-8980)
+* SOAP and XMLRPC servers overwrite the fault() functions
 
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..f108a27 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,93 @@ 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..98632b1 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';
@@ -211,7 +209,7 @@ if($do_show == 'edit') {
 
     if(is_array($feedbackitems)){
         $itemnr = 0;
-        
+
         $align = right_to_left() ? 'right' : 'left';
 
         $helpbutton = $OUTPUT->help_icon('preview', 'feedback');
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..2b7a8f5 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 (!has_capability('mod/feedback:view', $context)) {
+    //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;
+        }
+
+        $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);
@@ -1130,7 +1178,7 @@ function feedback_items_from_template($feedback, $templateid, $deleteold = false
         $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 ($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($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);
+        }
     }
 
     $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 988a177..d5aeff5 100644 (file)
@@ -3,28 +3,35 @@
 $hasheading = ($PAGE->heading);
 $hasnavbar = (empty($PAGE->layout_options['nonavbar']) && $PAGE->has_navbar());
 $hasfooter = (empty($PAGE->layout_options['nofooter']));
+
 $hassidepre = $PAGE->blocks->region_has_content('side-pre', $OUTPUT);
 $hassidepost = $PAGE->blocks->region_has_content('side-post', $OUTPUT);
+
+$showsidepre = ($hassidepre && !$PAGE->blocks->region_completely_docked('side-pre', $OUTPUT));
+$showsidepost = ($hassidepost && !$PAGE->blocks->region_completely_docked('side-post', $OUTPUT));
+
 $custommenu = $OUTPUT->custom_menu();
 $hascustommenu = (empty($PAGE->layout_options['nocustommenu']) && !empty($custommenu));
 
 $bodyclasses = array();
-if ($hassidepre && !$hassidepost) {
+if ($showsidepre && !$showsidepost) {
     $bodyclasses[] = 'side-pre-only';
-} else if ($hassidepost && !$hassidepre) {
+} else if ($showsidepost && !$showsidepre) {
     $bodyclasses[] = 'side-post-only';
-} else if (!$hassidepost && !$hassidepre) {
+} else if (!$showsidepost && !$showsidepre) {
     $bodyclasses[] = 'content-only';
 }
 if ($hascustommenu) {
     $bodyclasses[] = 'has_custom_menu';
 }
 
+
 echo $OUTPUT->doctype() ?>
 <html <?php echo $OUTPUT->htmlattributes() ?>>
 <head>
     <title><?php echo $PAGE->title ?></title>
     <link rel="shortcut icon" href="<?php echo $OUTPUT->pix_url('favicon', 'theme')?>" />
+    <meta name="description" content="<?php p(strip_tags(format_text($SITE->summary, FORMAT_HTML))) ?>" />
     <?php echo $OUTPUT->standard_head_html() ?>
 </head>
 
index b352752..83988b6 100644 (file)
@@ -10,7 +10,7 @@ body {margin:auto 0px;width:auto;}
 
 #page-content #region-main-box {
     float: left;
-    margin-left: -[[setting:regionpostwidth]];;
+    margin-left: -[[setting:regionpostwidth]];
     position: relative;
     width: 200%;
     right: 100%;
@@ -18,7 +18,7 @@ body {margin:auto 0px;width:auto;}
 
 #page-content #region-post-box {
     float: left;
-    margin-left: -[[setting:regionprewidth]];;
+    margin-left: -[[setting:regionprewidth]];
     width: 100%;
 }
 
@@ -67,7 +67,7 @@ body {margin:auto 0px;width:auto;}
 
 /** Only side pre **/
 .side-pre-only #page-content #region-main-box {
-    margin-left: 0px;
+    margin-left: 0;
 }
 
 .side-pre-only #page-content #region-post-box {
@@ -80,16 +80,17 @@ body {margin:auto 0px;width:auto;}
 
 .side-pre-only #page-content #region-pre {
     left: [[setting:regionprewidth]];
-     width: [[setting:regionprewidth]];
+    width: [[setting:regionprewidth]];
 }
 
 .side-pre-only #page-content #region-post {
-    width: 0%;
+    left: 0;
+    width: 0;
 }
 
 /** Only side post **/
 .side-post-only #page-content #region-main-box {
-    margin-left: 0px;
+    margin-left: 0;
 
 }
 
@@ -99,11 +100,14 @@ body {margin:auto 0px;width:auto;}
 
 .side-post-only #page-content #region-main {
     margin-left: [[setting:regionpostwidth]];
-
 }
 
+.side-post-only #page-content #region-pre {
+    left: 0;
+    width: 0;
+}
 .side-post-only #page-content #region-post {
-    left: [[setting:regionsumwidth]];
+    left: [[setting:regionpostwidth]];
     width: [[setting:regionpostwidth]];
 }
 
@@ -111,6 +115,47 @@ body {margin:auto 0px;width:auto;}
     margin-left: [[setting:regionprewidth]];
 }
 
+.blocks-moving.side-post-only #page-content #region-main-box {
+    float: left;
+    margin-left: -[[setting:regionpostwidth]];
+    position: relative;
+    width: 200%;
+    right: 100%;
+}
+
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box {
+    float: left;
+    margin-left:
+    -[[setting:regionprewidth]];
+    width: 100%;
+}
+
+.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:regionsumwidth]];
+    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:regionprewidth]];
+}
+
+.blocks-moving.side-post-only #page-content #region-main-box #region-post-box #region-post {
+    float: right;
+    position: relative;
+    left: [[setting:regiondoublepresumwidth]];
+    width: [[setting:regionpostwidth]];
+}
+
 /** No blocks whatsoever **/
 .content-only #page-content #region-main-box {
     margin-left: 0px;
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 0c2e508..91e3536 100644 (file)
@@ -31,7 +31,7 @@ defined('MOODLE_INTERNAL') || die();
 
 
 
-$version  = 2011101200.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2011101200.01;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
index 426b05a..fd49087 100644 (file)
  */
 
 require_once("$CFG->dirroot/webservice/lib.php");
+require_once 'Zend/Soap/Server.php';
+
+/**
+ * The Zend XMLRPC server but with a fault that returns debuginfo
+ */
+class moodle_zend_soap_server extends Zend_Soap_Server {
+
+    /**
+     * Generate a server fault
+     *
+     * Note that the arguments are reverse to those of SoapFault.
+     *
+     * Moodle note: the difference with the Zend server is that we throw a SoapFault exception
+     * with the debuginfo integrated to the exception message when DEBUG >= NORMAL
+     *
+     * If an exception is passed as the first argument, its message and code
+     * will be used to create the fault object if it has been registered via
+     * {@Link registerFaultException()}.
+     *
+     * @link   http://www.w3.org/TR/soap12-part1/#faultcodes
+     * @param  string|Exception $fault
+     * @param  string $code SOAP Fault Codes
+     * @return SoapFault
+     */
+    public function fault($fault = null, $code = "Receiver")
+    {
+        //intercept any exceptions with debug info and transform it in Moodle exception
+        if ($fault instanceof Exception) {
+            //add the debuginfo to the exception message if debuginfo must be returned
+            if (debugging() and isset($fault->debuginfo)) {
+                $fault = new SoapFault('Receiver', $fault->getMessage() . ' | DEBUG INFO: ' . $fault->debuginfo);
+            }
+        }
+
+        return parent::fault($fault, $code);
+    }
+}
 
 /**
  * SOAP service server implementation.
@@ -43,7 +80,7 @@ class webservice_soap_server extends webservice_zend_server {
         if (optional_param('wsdl', 0, PARAM_BOOL)) {
             parent::__construct($authmethod, 'Zend_Soap_AutoDiscover');
         } else {
-            parent::__construct($authmethod, 'Zend_Soap_Server');
+            parent::__construct($authmethod, 'moodle_zend_soap_server');
         }
         $this->wsname = 'soap';
     }
@@ -76,9 +113,14 @@ 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');
+            //when DEBUG >= NORMAL then the thrown exceptions are "casted" into a PHP SoapFault expception
+            //in order to diplay the $debuginfo (see moodle_zend_soap_server class - MDL-29435)
+            if (debugging()) {
+                $this->zend_server->registerFaultException('SoapFault');
+            }
         }
     }
 
index 0fbcee7..d94f249 100644 (file)
  */
 
 require_once("$CFG->dirroot/webservice/lib.php");
+require_once 'Zend/XmlRpc/Server.php';
+
+/**
+ * The Zend XMLRPC server but with a fault that return debuginfo
+ */
+class moodle_zend_xmlrpc_server extends Zend_XmlRpc_Server {
+
+    /**
+     * Raise an xmlrpc server fault
+     *
+     * Moodle note: the difference with the Zend server is that we throw a plain PHP Exception
+     * with the debuginfo integrated to the exception message when DEBUG >= NORMAL
+     *
+     * @param string|Exception $fault
+     * @param int $code
+     * @return Zend_XmlRpc_Server_Fault
+     */
+    public function fault($fault = null, $code = 404)
+    {
+        //intercept any exceptions with debug info and transform it in Moodle exception
+        if ($fault instanceof Exception) {
+            //add the debuginfo to the exception message if debuginfo must be returned
+            if (debugging() and isset($fault->debuginfo)) {
+                $fault = new Exception($fault->getMessage() . ' | DEBUG INFO: ' . $fault->debuginfo, 0);
+            }
+        }
+
+        return parent::fault($fault, $code);
+    }
+}
 
 /**
  * XML-RPC service server implementation.
@@ -32,11 +62,11 @@ require_once("$CFG->dirroot/webservice/lib.php");
 class webservice_xmlrpc_server extends webservice_zend_server {
     /**
      * Contructor
-     * @param integer $authmethod authentication method one of WEBSERVICE_AUTHMETHOD_* 
+     * @param integer $authmethod authentication method one of WEBSERVICE_AUTHMETHOD_*
      */
     public function __construct($authmethod) {
         require_once 'Zend/XmlRpc/Server.php';
-        parent::__construct($authmethod, 'Zend_XmlRpc_Server');
+        parent::__construct($authmethod, 'moodle_zend_xmlrpc_server');
         $this->wsname = 'xmlrpc';
     }
 
@@ -48,6 +78,11 @@ class webservice_xmlrpc_server extends webservice_zend_server {
         parent::init_zend_server();
         // this exception indicates request failed
         Zend_XmlRpc_Server_Fault::attachFaultException('moodle_exception');
+        //when DEBUG >= NORMAL then the thrown exceptions are "casted" into a plain PHP Exception class
+        //in order to display the $debuginfo (see moodle_zend_xmlrpc_server class - MDL-29435)
+        if (debugging()) {
+            Zend_XmlRpc_Server_Fault::attachFaultException('Exception');
+        }
     }
 
 }
@@ -71,4 +106,4 @@ class webservice_xmlrpc_test_client implements webservice_test_client_interface
         $client = new Zend_XmlRpc_Client($serverurl);
         return $client->call($function, $params);
     }
-}
\ No newline at end of file
+}