--- loncom/homework/grades.pm 2007/07/24 21:21:31 1.423 +++ loncom/homework/grades.pm 2007/09/05 00:22:20 1.428.2.2 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.423 2007/07/24 21:21:31 albertel Exp $ +# $Id: grades.pm,v 1.428.2.2 2007/09/05 00:22:20 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -45,8 +45,7 @@ use LONCAPA; use POSIX qw(floor); -my %oldessays=(); -my %perm=(); +my %perm; # ----- These first few routines are general use routines.---- # @@ -195,22 +194,54 @@ sub showResourceInfo { return $result,$responseType,$hdgrade,$partlist,$handgrade; } +sub reset_caches { + &reset_analyze_cache(); + &reset_perm(); +} -sub get_order { - my ($partid,$respid,$symb,$uname,$udom)=@_; - my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); - $url=&Apache::lonnet::clutter($url); - my $subresult=&Apache::lonnet::ssi($url, - ('grade_target' => 'analyze'), - ('grade_domain' => $udom), - ('grade_symb' => $symb), - ('grade_courseid' => - $env{'request.course.id'}), - ('grade_username' => $uname)); - (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); - my %analyze=&Apache::lonnet::str2hash($subresult); - return ($analyze{"$partid.$respid.shown"}); +{ + my %analyze_cache; + + sub reset_analyze_cache { + undef(%analyze_cache); + } + + sub get_analyze { + my ($symb,$uname,$udom)=@_; + my $key = "$symb\0$uname\0$udom"; + return $analyze_cache{$key} if (exists($analyze_cache{$key})); + + my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); + $url=&Apache::lonnet::clutter($url); + my $subresult=&Apache::lonnet::ssi($url, + ('grade_target' => 'analyze'), + ('grade_domain' => $udom), + ('grade_symb' => $symb), + ('grade_courseid' => + $env{'request.course.id'}), + ('grade_username' => $uname)); + (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); + my %analyze=&Apache::lonnet::str2hash($subresult); + return $analyze_cache{$key} = \%analyze; + } + + sub get_order { + my ($partid,$respid,$symb,$uname,$udom)=@_; + my $analyze = &get_analyze($symb,$uname,$udom); + return $analyze->{"$partid.$respid.shown"}; + } + + sub get_radiobutton_correct_foil { + my ($partid,$respid,$symb,$uname,$udom)=@_; + my $analyze = &get_analyze($symb,$uname,$udom); + foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) { + if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') { + return $foil; + } + } + } } + #--- Clean response type for display #--- Currently filters option/rank/radiobutton/match/essay/Task # response types only. @@ -259,11 +290,11 @@ sub cleanRecord { } elsif ($response eq 'radiobutton') { my %answer=&Apache::lonnet::str2hash($answer); my ($toprow,$bottomrow); - my $correct=($order->[0])+1; - for (my $i=1;$i<=$#$order;$i++) { - my $foil=$order->[$i]; + my $correct = + &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom); + foreach my $foil (@$order) { if (exists($answer{$foil})) { - if ($i == $correct) { + if ($foil eq $correct) { $toprow.='true'; } else { $toprow.='true'; @@ -538,7 +569,7 @@ sub compute_points { # sub most_similar { - my ($uname,$udom,$uessay)=@_; + my ($uname,$udom,$uessay,$old_essays)=@_; # ignore spaces and punctuation @@ -555,23 +586,22 @@ sub most_similar { my $scrsid=''; my $sessay=''; # go through all essays ... - foreach my $tkey (keys %oldessays) { - my ($tname,$tdom,$tcrsid)=split(/\./,$tkey); + foreach my $tkey (keys(%$old_essays)) { + my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey)); # ... except the same student - if (($tname ne $uname) || ($tdom ne $udom)) { - my $tessay=$oldessays{$tkey}; - $tessay=~s/\W+/ /gs; + next if (($tname eq $uname) && ($tdom eq $udom)); + my $tessay=$old_essays->{$tkey}; + $tessay=~s/\W+/ /gs; # String similarity gives up if not even limit - my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); + my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); # Found one - if ($tsimilar>$limit) { - $limit=$tsimilar; - $sname=$tname; - $sdom=$tdom; - $scrsid=$tcrsid; - $sessay=$oldessays{$tkey}; - } - } + if ($tsimilar>$limit) { + $limit=$tsimilar; + $sname=$tname; + $sdom=$tdom; + $scrsid=$tcrsid; + $sessay=$old_essays->{$tkey}; + } } if ($limit>0.6) { return ($sname,$sdom,$scrsid,$sessay,$limit); @@ -1691,6 +1721,7 @@ sub submission { '" src="'.$request->dir_config('lonIconsURL'). '/check.gif" height="16" border="0" />'; + my %old_essays; # header info if ($counter == 0) { &sub_page_js($request); @@ -1805,7 +1836,7 @@ KEYWORDS my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/); $apath=&escape($apath); $apath=~s/\W/\_/gs; - %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); + %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); } } @@ -1943,12 +1974,21 @@ KEYWORDS my $similar=''; if($env{'form.checkPlag'}){ my ($oname,$odom,$ocrsid,$oessay,$osim)= - &most_similar($uname,$udom,$subval); + &most_similar($uname,$udom,$subval,\%old_essays); if ($osim) { $osim=int($osim*100.0); - $similar="

Essay". - " is $osim% similar to an essay by ". - &Apache::loncommon::plainname($oname,$odom). + my %old_course_desc = + &Apache::lonnet::coursedescription($ocrsid, + {'one_time' => 1}); + + $similar="

". + &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])', + $osim, + &Apache::loncommon::plainname($oname,$odom), + $oname,$odom, + $old_course_desc{'description'}, + $old_course_desc{'num'}, + $old_course_desc{'domain'}). '

'. &keywords_highlight($oessay). '

'; @@ -4357,9 +4397,47 @@ sub updateGradeByPage { =head1 Bubble sheet grading routines - (For this documentation 'scanline' refers to the full line of characters - from the file that we are parsing 'bubble line' refers to the data - representing the line of bubbles that are on the physical bubble sheet) + For this documentation: + + 'scanline' refers to the full line of characters + from the file that we are parsing that represents one entire sheet + + 'bubble line' refers to the data + representing the line of bubbles that are on the physical bubble sheet + + +The overall process is that a scanned in bubble sheet data is uploaded +into a course. When a user wants to grade, they select a +sequence/folder of resources, a file of bubble sheet info, and pick +one of the predefined configurations for what each scanline looks +like. + +Next each scanline is checked for any errors of either 'missing +bubbles' (it's an error because it may have been missed scanned +because too light bubbling), 'double bubble' (each bubble line should +have no more that one letter picked), invalid or duplicated CODE, +invalid student ID + +If the CODE option is used that determines the randomization of the +homework problems, either way the student ID is looked up into a +username:domain. + +During the validation phase the instructor can choose to skip scanlines. + +After the validation phase, there is now 3 bubble sheet files + + scantron_original_filename (unmodified original file) + scantron_corrected_filename (file where the corrected information has replaced the original information) + scantron_skipped_filename (contains the exact text of scanlines that where skipped) + +Also there is a separate hash nohist_scantrondata that contains extra +correction information that isn't representable in the bubble sheet +file (see &scantron_getfile() for more information) + +After all scanlines are either valid, marked as valid or skipped, then +foreach line foreach problem in the picked sequence, an ssi request is +made that simulates a user submitting their selected letter(s) against +the homework problem. =over 4 @@ -4531,7 +4609,7 @@ sub scantron_CODEunique { Generates the initial screen to start the bubble sheet process. Allows for - starting a grading run. - - downloading exisiting scan data (original, corrected + - downloading existing scan data (original, corrected or skipped info) - uploading new scan data @@ -4589,7 +4667,7 @@ sub scantron_selectphase { Options:
-
+
@@ -4732,17 +4810,18 @@ SCANTRONFORM 'questions' start Qlength - number of columns comprising a single bubble line from the sheet. (usually either 1 or 10) - Qon - either a single charater representing the character used + Qon - either a single character representing the character used to signal a bubble was chosen in the positional setup, or the string 'letter' if the letter of the chosen bubble is in the final, or 'number' if a number representing the chosen bubble is in the file (1->A 0->J) - Qoff - the character used to represent that a buble was left blank + Qoff - the character used to represent that a bubble was + left blank PaperID - if the scanning process generates a unique number for each sheet scanned the column that this ID number starts in PaperIDlength - number of columns that comprise the unique ID number for the sheet of paper - FirstName - column that the firs tname starts in + FirstName - column that the first name starts in FirstNameLength - number of columns that the first name spans LastName - column that the last name starts in @@ -4793,7 +4872,7 @@ sub get_scantron_config { $classlist - reference to the class list hash. This is a hash keyed by student name:domain whose elements are references - to arrays containng various chunks of information + to arrays containing various chunks of information about the student. (See loncoursedata for more info). Returns @@ -4813,7 +4892,7 @@ sub username_to_idmap { =pod -=item scatron_fixup_scanline +=item scantron_fixup_scanline Process a requested correction to a scanline. @@ -4832,12 +4911,12 @@ sub username_to_idmap { $args - hash of additional info, - 'ID' 'newid' -> studentID to use in replacement - of exisiting one + of existing one - 'CODE' 'CODE_ignore_dup' - set to true if duplicates should be ignored. 'CODE' - is new code or 'use_unfound' - if the exisitng unfound code should + if the existing unfound code should be used as is - 'answer' 'response' - new answer or 'none' if blank @@ -4920,7 +4999,7 @@ sub scantron_fixup_scanline { Arguments: $scan_data - The hash (see scantron_getfile) $key - shorthand of the key to edit (actual key is - scatronfilename_key). + scantronfilename_key). $data - New value of the hash entry. $delete - If true, the entry is removed from the hash. @@ -5107,13 +5186,13 @@ sub scantron_parse_scanline { queue of messages to be shown after grading pass is complete Arguments: - $delayqueue - arrary ref of hash ref of erro messages + $delayqueue - arrary ref of hash ref of error messages $scanline - the scanline that caused the error $errormesage - the error message $errorcode - a numeric code for the error Side Effects: - updates the $dealyqueue to have a new hash ref of the error + updates the $delayqueue to have a new hash ref of the error =cut @@ -5129,6 +5208,18 @@ sub scantron_add_delay { =item scantron_find_student + Finds the username for the current scanline + + Arguments: + $scantron_record - hash result from scantron_parse_scanline + $scan_data - hash of correction information + (see &scantron_getfile() form more information) + $idmap - hash from &username_to_idmap() + $line - number of current scanline + + Returns: + Either 'username:domain' or undef if unknown + =cut sub scantron_find_student { @@ -5149,6 +5240,9 @@ sub scantron_find_student { =item scantron_filter + Filter sub for lonnavmaps, filters out hidden resources if ignore + hidden resources was selected + =cut sub scantron_filter { @@ -5171,6 +5265,9 @@ sub scantron_filter { =item scantron_process_corrections + Gets correction information out of submitted form data and corrects + the scanline + =cut sub scantron_process_corrections { @@ -5234,6 +5331,10 @@ sub scantron_process_corrections { =item reset_skipping_status + Forgets the current set of remember skipped scanlines (and thus + reverts back to considering all lines in the + scantron_skipped_ file) + =cut sub reset_skipping_status { @@ -5246,6 +5347,8 @@ sub reset_skipping_status { =item start_skipping + Marks a scanline to be skipped. + =cut sub start_skipping { @@ -5263,6 +5366,8 @@ sub start_skipping { =item should_be_skipped + Checks whether a scanline should be skipped. + =cut sub should_be_skipped { @@ -5284,6 +5389,9 @@ sub should_be_skipped { =item remember_current_skipped + Discovers what scanlines are in the scantron_skipped_ + file and remembers them into scan_data for later use. + =cut sub remember_current_skipped { @@ -5303,6 +5411,10 @@ sub remember_current_skipped { =item check_for_error + Checks if there was an error when attempting to remove a specific + scantron_.. bubble sheet data file. Prints out an error if + something went wrong. + =cut sub check_for_error { @@ -5316,6 +5428,9 @@ sub check_for_error { =item scantron_warning_screen + Interstitial screen to make sure the operator has selected the + correct options before we start the validation phase. + =cut sub scantron_warning_screen { @@ -5354,6 +5469,9 @@ STUFF =item scantron_do_warning + Check if the operator has picked something for all required + fields. Error out if something is missing. + =cut sub scantron_do_warning { @@ -5391,6 +5509,8 @@ STUFF =item scantron_form_start + html hidden input for remembering all selected grading options + =cut sub scantron_form_start { @@ -5414,6 +5534,12 @@ SCANTRONFORM =item scantron_validate_file + Dispatch routine for doing validation of a bubble sheet data file. + + Also processes any necessary information resets that need to + occur before validation begins (ignore previous corrections, + restarting the skipped records processing) + =cut sub scantron_validate_file { @@ -5423,7 +5549,7 @@ sub scantron_validate_file { my $default_form_data=&defaultFormData($symb); # do the detection of only doing skipped records first befroe we delete - # them when doing the corrections reset + # them when doing the corrections reset if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') { &reset_skipping_status(); } @@ -5442,7 +5568,7 @@ sub scantron_validate_file { if ($env{'form.scantron_corrections'}) { &scantron_process_corrections($r); } - $r->print("

Gathering neccessary info.

");$r->rflush(); + $r->print("

Gathering necessary info.

");$r->rflush(); #get the student pick code ready $r->print(&Apache::loncommon::studentbrowser_javascript()); my $max_bubble=&scantron_get_maxbubble(); @@ -5504,6 +5630,10 @@ STUFF =item scantron_remove_file + Removes the requested bubble sheet data file, makes sure that + scantron_original_ is never removed + + =cut sub scantron_remove_file { @@ -5525,6 +5655,11 @@ sub scantron_remove_file { =item scantron_remove_scan_data + Removes all scan_data correction for the requested bubble sheet + data file. (In the case that both the are doing skipped records we need + to remember the old skipped lines for the time being so that element + persists for a while.) + =cut sub scantron_remove_scan_data { @@ -5554,6 +5689,44 @@ sub scantron_remove_scan_data { =item scantron_getfile + Fetches the requested bubble sheet data file (all 3 versions), and + the scan_data hash + + Arguments: + None + + Returns: + 2 hash references + + - first one has + orig - + corrected - + skipped - each of which points to an array ref of the specified + file broken up into individual lines + count - number of scanlines + + - second is the scan_data hash possible keys are + ($number refers to scanline numbered $number and thus the key affects + only that scanline + $bubline refers to the specific bubble line element and the aspects + refers to that specific bubble line element) + + $number.user - username:domain to use + $number.CODE_ignore_dup + - ignore the duplicate CODE error + $number.useCODE + - use the CODE in the scanline as is + $number.no_bubble.$bubline + - it is valid that there is no bubbled in bubble + at $number $bubline + remember_skipping + - a frozen hash containing keys of $number and values + of either + 1 - we are on a 'do skipped records pass' and plan + on processing this line + 2 - we are on a 'do skipped records pass' and this + scanline has been marked to skip yet again + =cut sub scantron_getfile { @@ -5592,6 +5765,15 @@ sub scantron_getfile { =item lonnet_putfile + Wrapper routine to call &Apache::lonnet::finishuserfileupload + + Arguments: + $contents - data to store + $filename - filename to store $contents into + + Returns: + result value from &Apache::lonnet::finishuserfileupload + =cut sub lonnet_putfile { @@ -5607,6 +5789,16 @@ sub lonnet_putfile { =item scantron_putfile + Stores the current version of the bubble sheet data files, and the + scan_data hash. (Does not modify the original version only the + corrected and skipped versions. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + =cut sub scantron_putfile { @@ -5633,6 +5825,22 @@ sub scantron_putfile { =item scantron_get_line + Returns the correct version of the scanline + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - number of the requested line (starts at 0) + + Returns: + A scanline, (either the original or the corrected one if it + exists), or undef if the requested scanline should be + skipped. (Either because it's an skipped scanline, or it's an + unskipped scanline and we are not doing a 'do skipped scanlines' + pass. + =cut sub scantron_get_line { @@ -5647,6 +5855,17 @@ sub scantron_get_line { =item scantron_todo_count + Counts the number of scanlines that need processing. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + + Returns: + $count - number of scanlines to process + =cut sub get_todo_count { @@ -5664,6 +5883,19 @@ sub get_todo_count { =item scantron_put_line + Updates the 'corrected' or 'skipped' versions of the bubble sheet + data file. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - line number to update + $newline - contents of the updated scanline + $skip - if true make the line for skipping and update the + 'skipped' file + =cut sub scantron_put_line { @@ -5680,6 +5912,15 @@ sub scantron_put_line { =item scantron_clear_skip + Remove a line from the 'skipped' file + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - line number to update + =cut sub scantron_clear_skip { @@ -5695,6 +5936,9 @@ sub scantron_clear_skip { =item scantron_filter_not_exam + Filter routine used by &Apache::lonnavmaps::retrieveResources(), to + filter out resources that are not marked as 'exam' mode + =cut sub scantron_filter_not_exam { @@ -5717,6 +5961,9 @@ sub scantron_filter_not_exam { =item scantron_validate_sequence + Validates the selected sequence, checking for resource that are + not set to exam mode. + =cut sub scantron_validate_sequence { @@ -5746,6 +5993,9 @@ sub scantron_validate_sequence { =item scantron_validate_ID + Validates all scanlines in the selected file to not have any + invalid or underspecified student IDs + =cut sub scantron_validate_ID { @@ -5814,6 +6064,30 @@ sub scantron_validate_ID { =item scantron_get_correction + Builds the interface screen to interact with the operator to fix a + specific error condition in a specific scanline + + Arguments: + $r - Apache request object + $i - number of the current scanline + $scan_record - hash ref as returned from &scantron_parse_scanline() + $scan_config - hash ref as returned from &get_scantron_config() + $line - full contents of the current scanline + $error - error condition, valid values are + 'incorrectCODE', 'duplicateCODE', + 'doublebubble', 'missingbubble', + 'duplicateID', 'incorrectID' + $arg - extra information needed + For errors: + - duplicateID - paper number that this studentID was seen before on + - duplicateCODE - array ref of the paper numbers this CODE was + seen on before + - incorrectCODE - current incorrect CODE + - doublebubble - array ref of the bubble lines that have double + bubble errors + - missingbubble - array ref of the bubble lines that have missing + bubble errors + =cut sub scantron_get_correction { @@ -5945,7 +6219,7 @@ ENDSCRIPT =item scantron_bubble_selector Generates the html radiobuttons to correct a single bubble line - possibly showing the exisiting the selected bubbles if known + possibly showing the existing the selected bubbles if known Arguments: $r - Apache request object @@ -6021,6 +6295,16 @@ sub scantron_bubble_selector { =item num_matches + Counts the number of characters that are the same between the two arguments. + + Arguments: + $orig - CODE from the scanline + $code - CODE to match against + + Returns: + $count - integer count of the number of same characters between the + two arguments + =cut sub num_matches { @@ -6038,6 +6322,20 @@ sub num_matches { =item scantron_get_closely_matching_CODEs + Cycles through all CODEs and finds the set that has the greatest + number of same characters as the provided CODE + + Arguments: + $allcodes - hash ref returned by &get_codes() + $CODE - CODE from the current scanline + + Returns: + 2 element list + - first elements is number of how closely matching the best fit is + (5 means best set has 5 matching characters) + - second element is an arrary ref containing the set of valid CODEs + that best fit the passed in CODE + =cut sub scantron_get_closely_matching_CODEs { @@ -6054,6 +6352,17 @@ sub scantron_get_closely_matching_CODEs =item get_codes + Builds a hash which has keys of all of the valid CODEs from the selected + set of remembered CODEs. + + Arguments: + $old_name - name of the set of remembered CODEs + $cdom - domain of the course + $cnum - internal course name + + Returns: + %allcodes - keys are the valid CODEs, values are all 1 + =cut sub get_codes { @@ -6082,6 +6391,10 @@ sub get_codes { =item scantron_validate_CODE + Validates all scanlines in the selected file to not have any + invalid or underspecified CODEs and that none of the codes are + duplicated if this was requested. + =cut sub scantron_validate_CODE { @@ -6139,6 +6452,9 @@ sub scantron_validate_CODE { =item scantron_validate_doublebubble + Validates all scanlines in the selected file to not have any + bubble lines with multiple bubbles marked. + =cut sub scantron_validate_doublebubble { @@ -6168,6 +6484,13 @@ sub scantron_validate_doublebubble { =item scantron_get_maxbubble + Returns the maximum number of bubble lines that are expected to + occur. Does this by walking the selected sequence rendering the + resource and then checking &Apache::lonxml::get_problem_counter() + for what the current value of the problem counter is. + + Caches the result to $env{'form.scantron_maxbubble'} + =cut sub scantron_get_maxbubble { @@ -6200,6 +6523,9 @@ sub scantron_get_maxbubble { =item scantron_validate_missingbubbles + Validates all scanlines in the selected file to not have any + bubble lines with missing bubbles that haven't been verified as missing. + =cut sub scantron_validate_missingbubbles { @@ -6474,7 +6800,7 @@ sub scantron_upload_scantron_data_save { =item valid_file - Vaildates that the requested bubble data file has exists in the course. + Validates that the requested bubble data file exists in the course. =cut @@ -6746,9 +7072,10 @@ sub gather_clicker_ids { # Set up a couple variables. my $username_idx = &Apache::loncoursedata::CL_SNAME(); my $domain_idx = &Apache::loncoursedata::CL_SDOM(); + my $status_idx = &Apache::loncoursedata::CL_STATUS(); foreach my $student (keys(%$classlist)) { - + if ($classlist->{$student}->[$status_idx] ne 'Active') { next; } my $username = $classlist->{$student}->[$username_idx]; my $domain = $classlist->{$student}->[$domain_idx]; my $clickers = @@ -7009,8 +7336,21 @@ ENDHEADER $result.="\n".''; $correct_count++; } elsif ($clicker_ids{$id}) { - $result.="\n".''; - $student_count++; + if ($clicker_ids{$id}=~/\,/) { +# More than one user with the same clicker! + $result.="\n
".&mt('Clicker registered more than once').": ".$id."
"; + $result.="\n".''. + "'; + $unknown_count++; + } else { +# Good: found one and only one user with the right clicker + $result.="\n".''; + $student_count++; + } } else { $result.="\n
".&mt('Unregistered Clicker')." ".$id."
"; $result.="\n".''. @@ -7030,6 +7370,9 @@ ENDHEADER $result.='
'.&mt("Found [_1] entries for grading!",$correct_count).''; } } + if ($number<1) { + $errormsg.="Found no questions."; + } if ($errormsg) { $result.='
'.&mt($errormsg).''; } else { @@ -7158,6 +7501,8 @@ ENDHEADER my $id=$1; if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) { $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id}; + } elsif ($env{'form.multi'.$id}) { + $user=$env{'form.multi'.$id}; } } if ($user) { @@ -7203,7 +7548,7 @@ ENDHEADER sub handler { my $request=$_[0]; - &reset_perm(); + &reset_caches(); if ($env{'browser.mathml'}) { &Apache::loncommon::content_type($request,'text/xml'); } else { @@ -7316,6 +7661,7 @@ sub handler { } } $request->print(&Apache::loncommon::end_page()); + &reset_caches(); return ''; }