--- loncom/homework/lonhomework.pm 2019/02/19 15:38:36 1.344.2.8.4.3 +++ loncom/homework/lonhomework.pm 2024/01/13 12:31:32 1.344.2.10.4.5 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Homework handler # -# $Id: lonhomework.pm,v 1.344.2.8.4.3 2019/02/19 15:38:36 raeburn Exp $ +# $Id: lonhomework.pm,v 1.344.2.10.4.5 2024/01/13 12:31:32 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -49,15 +49,22 @@ use Apache::matchresponse(); use Apache::chemresponse(); use Apache::functionplotresponse(); use Apache::drawimage(); +use Apache::loncourseuser(); +use Apache::grades(); use Apache::Constants qw(:common); use Apache::loncommon(); +use Apache::lonparmset(); +use Apache::lonnavmaps(); use Apache::lonlocal; +use LONCAPA qw(:DEFAULT :match); +use LONCAPA::ltiutils(); use Time::HiRes qw( gettimeofday tv_interval ); use HTML::Entities(); use File::Copy(); # FIXME - improve commenting +my $registered_cleanup; BEGIN { &Apache::lonxml::register_insert(); @@ -188,7 +195,7 @@ sub proctor_checked_in { if ($type eq 'Task') { my $version=$Apache::lonhomework::history{'resource.0.version'}; $key ="resource.$version.0.checkedin"; - } elsif ($type eq 'problem') { + } elsif (($type eq 'problem') || ($type eq 'tool')) { $key ='resource.0.checkedin'; } # backward compatability, used to be username@domain, @@ -203,7 +210,6 @@ sub proctor_checked_in { return 1; } } - return 0; } @@ -212,7 +218,7 @@ sub check_slot_access { # does it pass normal muster my ($status,$datemsg)=&check_access($id,$symb); - + my $useslots = &Apache::lonnet::EXT("resource.0.useslots",$symb); if ($useslots ne 'resource' && $useslots ne 'map' && $useslots ne 'map_map') { @@ -235,6 +241,12 @@ sub check_slot_access { $Apache::lonhomework::history{"resource.$version.0.status"} eq 'pass') { return ('SHOW_ANSWER'); } + } elsif (($type eq 'problem') && + ($Apache::lonhomework::browse eq 'F') && + ($ENV{'REMOTE_ADDR'} eq '127.0.0.1') && + ($env{'form.grade_courseid'} eq $env{'request.course.id'}) && + (&Apache::lonnet::allowed('mgr',$env{'request.course.id'}))) { + return ($status,$datemsg); } my $availablestudent = &Apache::lonnet::EXT("resource.0.availablestudent",$symb); @@ -260,7 +272,7 @@ sub check_slot_access { $num_usable_slots ++; if ($slot{'starttime'} < $now && $slot{'endtime'} > $now && - &Apache::loncommon::check_slotip_acc($slot{'ip'})) { + &Apache::loncommon::check_ip_acc($slot{'ip'})) { &Apache::lonxml::debug("$slot is good"); $slotstatus='NEEDS_CHECKIN'; $returned_slot=\%slot; @@ -286,7 +298,7 @@ sub check_slot_access { || $Apache::lonhomework::history{"resource.0.solved"} =~ /^correct_/ ); $checkedin = $Apache::lonhomework::history{"resource.$version.0.checkedin"}; - } elsif ($type eq 'problem') { + } elsif (($type eq 'problem') || ($type eq 'tool')) { $checkin = 'resource.0.checkedin'; $checkedin = $Apache::lonhomework::history{$checkin}; } @@ -295,7 +307,7 @@ sub check_slot_access { my %slot=&Apache::lonnet::get_slot($checkinslot); $consumed_uniq = $slot{'uniqueperiod'}; } - if ($type eq 'problem') { + if (($type eq 'problem') || ($type eq 'tool')) { if ((ref($partlist) eq 'ARRAY') && (@{$partlist} > 0)) { my ($numcorrect,$numgraded) = (0,0); foreach my $part (@{$partlist}) { @@ -311,7 +323,7 @@ sub check_slot_access { $earlyout = 1; } } - if (($currtries == $maxtries) || ($is_correct)) { + if ($currtries == $maxtries) { $earlyout = 1; } else { $numgraded ++; @@ -354,8 +366,9 @@ sub check_slot_access { # However, the problem is not closed, and potentially, another slot might be # used to gain access to it to work on it, until the due date is reached, and the # problem then becomes CLOSED. Therefore return the slotstatus - - # (which will be one of: NOT_IN_A_SLOT, RESERVABLE, RESERVABLE_LATER, or NOTRESERVABLE. - if (!defined($slot_name) && $type eq 'problem') { + # (which will be one of: NOT_IN_A_SLOT, RESERVABLE, RESERVABLE_LATER, or NOTRESERVABLE). + + if (!defined($slot_name) && (($type eq 'problem') || ($type eq 'tool'))) { if ($slotstatus eq 'NOT_IN_A_SLOT') { if (!$num_usable_slots) { if ($env{'request.course.id'}) { @@ -439,7 +452,7 @@ sub check_slot_access { } if ( $is_correct) { - if ($type eq 'problem') { + if (($type eq 'problem') || ($type eq 'tool')) { return ($status); } return ('SHOW_ANSWER'); @@ -710,6 +723,9 @@ sub setuppermissions { $env{'request.course.sec'} !~ /^\s*$/) { $viewgrades = &Apache::lonnet::allowed('vgr',$env{'request.course.id'}. '/'.$env{'request.course.sec'}); + if ($viewgrades) { + $Apache::lonhomework::viewgradessec = $env{'request.course.sec'}; + } } $Apache::lonhomework::viewgrades = $viewgrades; @@ -727,6 +743,9 @@ sub setuppermissions { $modifygrades = &Apache::lonnet::allowed('mgr',$env{'request.course.id'}. '/'.$env{'request.course.sec'}); + if ($modifygrades) { + $Apache::lonhomework::modifygradessec = $env{'request.course.sec'}; + } } $Apache::lonhomework::modifygrades = $modifygrades; @@ -745,7 +764,9 @@ sub setuppermissions { sub unset_permissions { undef($Apache::lonhomework::queuegrade); undef($Apache::lonhomework::modifygrades); + undef($Apache::lonhomework::modifygradessec); undef($Apache::lonhomework::viewgrades); + undef($Apache::lonhomework::viewgradessec); undef($Apache::lonhomework::browse); } @@ -844,7 +865,7 @@ STATE sub analyze_header { my ($request) = @_; - my $js = &Apache::structuretags::setmode_javascript(); + my $js = &Apache::lonxml::setmode_javascript(); # Breadcrumbs my $brcrum = [{'href' => &Apache::loncommon::authorspace($request->uri), @@ -1056,9 +1077,8 @@ sub editxmlmode { $problem=''; } - if (($env{'form.problemmode'} eq 'saveeditxml') || - ($env{'form.problemmode'} eq 'saveviewxml') || + ($env{'form.problemmode'} eq 'saveviewxml') || ($env{'form.problemmode'} eq 'undoxml')) { my $error=&handle_save_or_undo($request,\$problem, \$env{'form.editxmltext'}); @@ -1077,7 +1097,7 @@ sub editxmlmode { my $js = &Apache::edit::js_change_detection(). &Apache::loncommon::resize_textarea_js(). - &Apache::structuretags::setmode_javascript(). + &Apache::lonxml::setmode_javascript(). &Apache::lonhtmlcommon::dragmath_js("EditMathPopup"); # Breadcrumbs @@ -1194,7 +1214,7 @@ sub editxmlmode { # Render the page in whatever target desired. # sub renderpage { - my ($request,$file,$targets,$return_string) = @_; + my ($request,$file,$targets,$return_string,$donebuttonmsg,$viewasuser,$symb) = @_; my @targets = @{$targets || [&get_target()]}; &Apache::lonhomework::showhashsubset(\%env,'form.'); @@ -1233,6 +1253,10 @@ sub renderpage { if ($target eq 'answer') { &showhash(%Apache::lonhomework::history); } if ($target eq 'web') {&Apache::lonhomework::showhashsubset(\%env,'^form');} + if (($target eq 'web') && ($viewasuser ne '') && ($symb ne '')) { + $env{'request.user_in_effect'} = $viewasuser; + } + &Apache::lonxml::debug("Should be parsing now"); $result .= &Apache::lonxml::xmlparse($request, $target, $problem, &setup_vars($target),%mystyle); @@ -1245,6 +1269,11 @@ sub renderpage { if ($target eq 'analyze') { $result=&Apache::lonnet::hashref2str(\%Apache::lonhomework::analyze); undef(%Apache::lonhomework::analyze); + } elsif ($target eq 'web') { + if ($donebuttonmsg) { + $result =~ s{}{}; + $result.= &Apache::loncommon::confirmwrapper(&Apache::lonhtmlcommon::confirm_success($donebuttonmsg,1))."\n"; + } } #my $td=&tv_interval($t0); #if ( $Apache::lonxml::debug) { @@ -1253,15 +1282,23 @@ sub renderpage { #} # $request->print($result); $overall_result.=$result; + if (($target eq 'web') && ($viewasuser ne '') && ($symb ne '')) { + my ($vuname,$vudom) = split(/:/,$viewasuser); + $overall_result .= &Apache::grades::view_as_user($symb,$vuname,$vudom). + ''; + } # $request->rflush(); } + if (($target eq 'web') && ($viewasuser ne '') && ($symb ne '')) { + undef($env{'request.user_in_effect'}); + } #$request->print(":Result ends"); #my $td=&tv_interval($t0); } if (!$return_string) { &Apache::lonxml::add_messages(\$overall_result); - $request->print($overall_result); - $request->rflush(); + $request->print($overall_result); + $request->rflush(); } else { return $overall_result; } @@ -1272,7 +1309,6 @@ sub finished_parsing { undef($Apache::lonhomework::parsing_a_task); } - # function extracted from get_template_html # returns "key" -> list # key: path of template @@ -1468,32 +1504,61 @@ sub update_construct_style { sub zero_timer { my ($symb) = @_; my ($hastimeleft,$first_access,$now); - my @interval=&Apache::lonnet::EXT("resource.0.interval"); + my @interval=&Apache::lonnet::EXT("resource.0.interval",$symb); if (@interval > 1) { if ($interval[1] eq 'course') { - return; + return ('fail',&mt('Ending of timed events not supported for intervals set course-wide')); } else { my $now = time; my $first_access=&Apache::lonnet::get_first_access($interval[1],$symb); if ($first_access > 0) { - if ($first_access+$interval[0] > $now) { - my $done_time = $now - $first_access; - my $snum = 1; - if ($interval[1] eq 'map') { - $snum = 2; + my ($timelimit,$donesuffix) = split(/_/,$interval[0],2); + if ($donesuffix =~ /^done(?:|\:[^\:]+\:)(.*)$/) { + my ($dummy,$proctor,$secret) = split(/_/,$1); + if (($proctor) && ($secret ne '')) { + my $key = $env{'form.LC_interval_done_proctorpass'}; + $key =~ s/^\s+//; + $key =~ s/\s+$//; + if ($env{'form.LC_interval_done_proctorpass'} ne $secret) { + return ('fail', + &mt('Incorrect key entered by proctor')); + } } - my $result = - &Apache::lonparmset::storeparm_by_symb_inner($symb,'0_interval', - $snum,$done_time, - 'date_interval', - $env{'user.name'}, - $env{'user.domain'}); - return $result; + if ($first_access+$timelimit > $now) { + my $done_time = $now - $first_access; + my $snum = 1; + if ($interval[1] eq 'map') { + $snum = 2; + } + my $result = + &Apache::lonparmset::storeparm_by_symb_inner($symb,'0_interval', + $snum,$done_time, + 'date_interval', + $env{'user.name'}, + $env{'user.domain'}); + if ($result eq '') { + # Record action in "User Notes" + &Apache::lonmsg::store_instructor_comment( + 'Pressed Done button for symb:
'.$symb, + $env{'user.name'}, $env{'user.domain'}); + return ('ok'); + } else { + return ('fail',&mt('Error ending timed event: [_1]',$result)); + } + } else { + return ('fail',&mt('Timed event already ended')); + } + } else { + return ('fail',&mt('Timed event can not be ended before the time limit')); } + } else { + return ('fail',&mt('Timer not yet started for this timed event')); } } + } else { + return ('fail',&mt('No timer in use')); } - return; + return(); } sub handler { @@ -1507,9 +1572,9 @@ sub handler { my $file=&Apache::lonnet::filelocation("",$request->uri); #check if we know where we are - if ($env{'request.course.fn'} && !&Apache::lonnet::symbread('','',1,1)) { + if ($env{'request.course.fn'} && !&Apache::lonnet::symbread('','',1,1)) { # if we are browsing we might not be able to know where we are - if ($Apache::lonhomework::browse ne 'F' && + if ($Apache::lonhomework::browse ne 'F' && $env{'request.state'} ne "construct") { #should know where we are, so ask &unset_permissions(); @@ -1521,6 +1586,7 @@ sub handler { &unset_permissions(); return OK; } + &Apache::lonxml::debug("Permissions:$Apache::lonhomework::browse:$Apache::lonhomework::viewgrades:$Apache::lonhomework::modifygrades:$Apache::lonhomework::queuegrade"); &Apache::lonxml::debug("Problem Mode ".$env{'form.problemmode'}); my ($symb) = &Apache::lonnet::whichuser(); @@ -1552,14 +1618,48 @@ sub handler { } } else { # Set the event timer to zero if the "done button" was clicked. The button is - # part of the LCdoneButton form created in lonmenu.pm + # part of the doneButton form created in lonmenu.pm + my ($donebuttonresult,$donemsg,$viewasuser); if ($symb && $env{'form.LC_interval_done'} eq 'true') { - &zero_timer($symb); + ($donebuttonresult,$donemsg) = &zero_timer($symb); undef($env{'form.LC_interval_done'}); + undef($env{'form.LC_interval_done_proctorpass'}); + } + if (($env{'form.LC_viewas'} ne '') && $symb && $env{'request.course.id'} && + ($Apache::lonhomework::viewgrades || $Apache::lonhomework::modifygrades)) { + if ($env{'form.LC_viewas'} =~ /^($match_username):($match_domain)$/) { + my ($possuname,$possudom) = ($1,$2); + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my ($canview,$posssec); + if ($env{'request.course.sec'} ne '') { + if ($Apache::lonhomework::modifygradessec eq $env{'request.course.sec'}) { + $canview = 'section'; + $posssec = $env{'request.course.sec'}; + } elsif ($Apache::lonhomework::viewgradessec eq $env{'request.course.sec'}) { + $canview = 'section'; + $posssec = $env{'request.course.sec'}; + } + } + my $crstype = &Apache::loncommon::course_type(); + if (&Apache::loncourseuser::is_course_user($possudom,$possuname,$cdom,$cnum, + $canview,$crstype,$posssec)) { + $viewasuser = $possuname.':'.$possudom; + } + } + undef($env{'form.LC_viewas'}); } # just render the page normally outside of construction space &Apache::lonxml::debug("not construct"); - &renderpage($request,$file); + undef(@Apache::lonhomework::ltipassback); + &renderpage($request,$file,undef,undef,$donemsg,$viewasuser,$symb); + if (@Apache::lonhomework::ltipassback) { + unless ($registered_cleanup) { + my $handlers = $request->get_handlers('PerlCleanupHandler'); + $request->set_handlers('PerlCleanupHandler' => + [\&do_ltipassback,@{$handlers}]); + } + } } #my $td=&tv_interval($t0); #&Apache::lonxml::debug("Spent $td seconds processing"); @@ -1744,5 +1844,89 @@ sub convert_for_js { return $return; } +sub do_ltipassback { + if (@Apache::lonhomework::ltipassback) { + foreach my $item (@Apache::lonhomework::ltipassback) { + if (ref($item) eq 'HASH') { + if ((ref($item->{'lti'}) eq 'HASH') && ($item->{'cid'} =~ /^($match_domain)_($match_courseid)$/)) { + my ($cdom,$cnum) = ($1,$2); + my $msgformat = $item->{'lti'}->{'passbackformat'}; + my $sigmethod = 'HMAC-SHA1'; + my $ltinum = $item->{'ltinum'}; + my $id = $item->{'pbid'}; + my $url = $item->{'pburl'}; + my $type = $item->{'pbtype'}; + my $scope = $item->{'scope'}; + my $map = $item->{'ltimap'}; + my $symb = $item->{'ltisymb'}; + my $uname = $item->{'uname'}; + my $udom = $item->{'udom'}; + my $keynum = $item->{'lti'}->{'cipher'}; + my $crsdef = $item->{'crsdef'}; + my $scoretype = $item->{'format'}; + my ($total,$possible); + if ($scope eq 'resource') { + $total = $item->{'total'}; + $possible = $item->{'possible'}; + } elsif (($scope eq 'map') || ($scope eq 'nonrec')) { + ($total,$possible) = &get_lti_score($uname,$udom,$map,$scope); + } elsif ($scope eq 'course') { + ($total,$possible) = &get_lti_score($uname,$udom); + } + if (($id ne '') && ($url ne '') && ($possible)) { + &LONCAPA::ltiutils::send_grade($cdom,$cnum,$crsdef,$type,$ltinum,$keynum,$id,$url,$scoretype,$sigmethod,$msgformat,$total,$possible); + } + } + } + } + undef(@Apache::lonhomework::ltipassback); + } +} + +sub get_lti_score { + my ($uname,$udom,$mapurl,$scope) = @_; + my $navmap = Apache::lonnavmaps::navmap->new($uname,$udom); + if (ref($navmap)) { + my $iterator; + if ($mapurl ne '') { + my $map = $navmap->getResourceByUrl($mapurl); + my $firstres = $map->map_start(); + my $finishres = $map->map_finish(); + my $recursive = 1; + if ($scope eq 'nonrec') { + $recursive = 0; + } + $iterator = $navmap->getIterator($firstres,$finishres,undef,$recursive); + } else { + $iterator = $navmap->getIterator(undef,undef,undef,1); + } + if (ref($iterator)) { + my $depth = 1; + my $total = 0; + my $possible = 0; + $iterator->next(); # ignore first BEGIN_MAP + my $curRes = $iterator->next(); + while ( $depth > 0 ) { + if ($curRes == $iterator->BEGIN_MAP()) {$depth++;} + if ($curRes == $iterator->END_MAP()) { $depth--; } + if (ref($curRes) && $curRes->is_gradable() && !$curRes->randomout) { + my $parts = $curRes->parts(); + foreach my $part (@{$parts}) { + next if ($curRes->solved($part) eq 'excused'); + $total += $curRes->weight($part) * $curRes->awarded($part); + $possible += $curRes->weight($part); + } + } + $curRes = $iterator->next(); + } + if ($total > $possible) { + $total = $possible; + } + return ($total,$possible); + } + } + return; +} + 1; __END__