--- loncom/homework/lonhomework.pm 2016/09/14 16:13:17 1.365 +++ loncom/homework/lonhomework.pm 2023/04/02 03:16:28 1.375 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Homework handler # -# $Id: lonhomework.pm,v 1.365 2016/09/14 16:13:17 raeburn Exp $ +# $Id: lonhomework.pm,v 1.375 2023/04/02 03:16:28 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,16 +50,22 @@ use Apache::chemresponse(); use Apache::functionplotresponse(); use Apache::drawimage(); use Apache::loncapamath(); +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(); @@ -190,7 +196,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, @@ -205,7 +211,6 @@ sub proctor_checked_in { return 1; } } - return 0; } @@ -214,7 +219,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') { @@ -239,7 +244,8 @@ sub check_slot_access { $consumed_uniq = $slot{'uniqueperiod'}; if ($slot{'iptied'}) { $ipused = $Apache::lonhomework::history{"$checkin.ip"}; - unless (($ip ne '') && ($ipused eq $ip)) { + unless (($ip ne '') && + (($ipused eq $ip) || ($ENV{'REMOTE_ADDR'} eq '127.0.0.1'))) { $blockip = $slot{'iptied'}; $slot_name = $checkinslot; $returned_slot = \%slot; @@ -253,7 +259,7 @@ sub check_slot_access { } else { return ($status,$datemsg); } - } + } if ($status eq 'CLOSED' || $status eq 'INVALID_ACCESS' || @@ -263,7 +269,7 @@ sub check_slot_access { if ($env{'request.state'} eq "construct") { return ($status,$datemsg); } - + if ($type eq 'Task') { if ($checkedin && $Apache::lonhomework::history{"resource.$version.0.status"} eq 'pass') { @@ -273,6 +279,12 @@ sub check_slot_access { 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); @@ -350,7 +362,7 @@ sub check_slot_access { $is_correct = ($Apache::lonhomework::history{"resource.$version.0.status"} eq 'pass' || $Apache::lonhomework::history{"resource.0.solved"} =~ /^correct_/ ); - } elsif ($type eq 'problem') { + } elsif (($type eq 'problem') || ($type eq 'tool')) { if ((ref($partlist) eq 'ARRAY') && (@{$partlist} > 0)) { my ($numcorrect,$numgraded) = (0,0); foreach my $part (@{$partlist}) { @@ -366,7 +378,7 @@ sub check_slot_access { $earlyout = 1; } } - if (($currtries == $maxtries) || ($is_correct)) { + if ($currtries == $maxtries) { $earlyout = 1; } else { $numgraded ++; @@ -410,76 +422,12 @@ sub check_slot_access { # 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') { + + 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'}) { - my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; - my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; - unless ($symb) { - ($symb)=&Apache::lonnet::whichuser(); - } - $slotstatus = 'NOTRESERVABLE'; - my ($reservable_now_order,$reservable_now,$reservable_future_order, - $reservable_future) = - &Apache::loncommon::get_future_slots($cnum,$cdom,$now,$symb); - if ((ref($reservable_now_order) eq 'ARRAY') && (ref($reservable_now) eq 'HASH')) { - if (@{$reservable_now_order} > 0) { - if ((!$checkedin) || (ref($consumed_uniq) ne 'ARRAY')) { - $slotstatus = 'RESERVABLE'; - $datemsg = $reservable_now->{$reservable_now_order->[-1]}{'endreserve'}; - } else { - my ($uniqstart,$uniqend,$useslot); - if (ref($consumed_uniq) eq 'ARRAY') { - ($uniqstart,$uniqend)=@{$consumed_uniq}; - } - foreach my $slot (reverse(@{$reservable_now_order})) { - if ($reservable_now->{$slot}{'uniqueperiod'} =~ /^(\d+)\,(\d+)$/) { - my ($new_uniq_start,$new_uniq_end) = ($1,$2); - next if (! - ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || - ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); - } - $useslot = $slot; - last; - } - if ($useslot) { - $slotstatus = 'RESERVABLE'; - $datemsg = $reservable_now->{$useslot}{'endreserve'}; - } - } - } - } - unless ($slotstatus eq 'RESERVABLE') { - if ((ref($reservable_future_order) eq 'ARRAY') && (ref($reservable_future) eq 'HASH')) { - if (@{$reservable_future_order} > 0) { - if ((!$checkedin) || (ref($consumed_uniq) ne 'ARRAY')) { - $slotstatus = 'RESERVABLE_LATER'; - $datemsg = $reservable_future->{$reservable_future_order->[0]}{'startreserve'}; - } else { - my ($uniqstart,$uniqend,$useslot); - if (ref($consumed_uniq) eq 'ARRAY') { - ($uniqstart,$uniqend)=@{$consumed_uniq}; - } - foreach my $slot (@{$reservable_future_order}) { - if ($reservable_future->{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) { - my ($new_uniq_start,$new_uniq_end) = ($1,$2); - next if (! - ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || - ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); - } - $useslot = $slot; - last; - } - if ($useslot) { - $slotstatus = 'RESERVABLE_LATER'; - $datemsg = $reservable_future->{$useslot}{'startreserve'}; - } - } - } - } - } - } + ($slotstatus,$datemsg) = &check_reservable_slot($slotstatus,$symb,$now,$checkedin, + $consumed_uniq); } } return ($slotstatus,$datemsg); @@ -501,7 +449,7 @@ sub check_slot_access { } if (($is_correct) && ($blockip ne 'answer')) { - if ($type eq 'problem') { + if (($type eq 'problem') || ($type eq 'tool')) { return ($status); } return ('SHOW_ANSWER'); @@ -515,6 +463,81 @@ sub check_slot_access { return ($slotstatus,$datemsg,$slot_name,$returned_slot,$ipused); } +sub check_reservable_slot { + my ($slotstatus,$symb,$now,$checkedin,$consumed_uniq) = @_; + my $datemsg; + if ($slotstatus eq 'NOT_IN_A_SLOT') { + if ($env{'request.course.id'}) { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + unless ($symb) { + ($symb)=&Apache::lonnet::whichuser(); + } + $slotstatus = 'NOTRESERVABLE'; + my ($reservable_now_order,$reservable_now,$reservable_future_order, + $reservable_future) = + &Apache::loncommon::get_future_slots($cnum,$cdom,$now,$symb); + if ((ref($reservable_now_order) eq 'ARRAY') && (ref($reservable_now) eq 'HASH')) { + if (@{$reservable_now_order} > 0) { + if ((!$checkedin) || (ref($consumed_uniq) ne 'ARRAY')) { + $slotstatus = 'RESERVABLE'; + $datemsg = $reservable_now->{$reservable_now_order->[-1]}{'endreserve'}; + } else { + my ($uniqstart,$uniqend,$useslot); + if (ref($consumed_uniq) eq 'ARRAY') { + ($uniqstart,$uniqend)=@{$consumed_uniq}; + } + foreach my $slot (reverse(@{$reservable_now_order})) { + if ($reservable_now->{$slot}{'uniqueperiod'} =~ /^(\d+)\,(\d+)$/) { + my ($new_uniq_start,$new_uniq_end) = ($1,$2); + next if (! + ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || + ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); + } + $useslot = $slot; + last; + } + if ($useslot) { + $slotstatus = 'RESERVABLE'; + $datemsg = $reservable_now->{$useslot}{'endreserve'}; + } + } + } + } + unless ($slotstatus eq 'RESERVABLE') { + if ((ref($reservable_future_order) eq 'ARRAY') && (ref($reservable_future) eq 'HASH')) { + if (@{$reservable_future_order} > 0) { + if ((!$checkedin) || (ref($consumed_uniq) ne 'ARRAY')) { + $slotstatus = 'RESERVABLE_LATER'; + $datemsg = $reservable_future->{$reservable_future_order->[0]}{'startreserve'}; + } else { + my ($uniqstart,$uniqend,$useslot); + if (ref($consumed_uniq) eq 'ARRAY') { + ($uniqstart,$uniqend)=@{$consumed_uniq}; + } + foreach my $slot (@{$reservable_future_order}) { + if ($reservable_future->{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) { + my ($new_uniq_start,$new_uniq_end) = ($1,$2); + next if (! + ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || + ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); + } + $useslot = $slot; + last; + } + if ($useslot) { + $slotstatus = 'RESERVABLE_LATER'; + $datemsg = $reservable_future->{$useslot}{'startreserve'}; + } + } + } + } + } + } + } + return ($slotstatus,$datemsg); +} + # JB, 9/24/2002: Any changes in this function may require a change # in lonnavmaps::resource::getDateStatus. sub check_access { @@ -774,6 +797,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; @@ -791,6 +817,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; @@ -809,7 +838,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); } @@ -1257,7 +1288,7 @@ sub editxmlmode { # Render the page in whatever target desired. # sub renderpage { - my ($request,$file,$targets,$return_string,$donebuttonmsg) = @_; + my ($request,$file,$targets,$return_string,$donebuttonmsg,$viewasuser,$symb) = @_; my @targets = @{$targets || [&get_target()]}; &Apache::lonhomework::showhashsubset(\%env,'form.'); @@ -1298,6 +1329,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); @@ -1323,15 +1358,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->rflush(); } else { return $overall_result; } @@ -1439,12 +1482,31 @@ sub newproblem { &renderpage($request,$dest); return; } + my $errormsg; if ($env{'form.template'}) { - my $file = $env{'form.template'}; - my $dest = &Apache::lonnet::filelocation("",$request->uri); - &File::Copy::copy($file,$dest); - &renderpage($request,$dest); - return; + my $file; + my ($extension) = ($env{'form.template'} =~ /\.(\w+)$/); + if ($extension) { + my @files = &get_template_list($extension); + foreach my $poss (@files) { + if (ref($poss) eq 'ARRAY') { + if ($env{'form.template'} eq $poss->[0]) { + $file = $env{'form.template'}; + last; + } + } + } + if ($file) { + my $dest = &Apache::lonnet::filelocation("",$request->uri); + &File::Copy::copy($file,$dest); + &renderpage($request,$dest); + return; + } else { + $errormsg = '

'.&mt('Invalid template file.').'

'; + } + } else { + $errormsg = '

'.&mt('Invalid template file; template needs to be a .problem, .library, or .task file.').'

'; + } } my ($extension) = ($request->uri =~ m/\.(\w+)$/); @@ -1461,7 +1523,6 @@ sub newproblem { } else { my $url=&HTML::Entities::encode($request->uri,'<>&"'); my $dest = &Apache::lonnet::filelocation("",$request->uri); - my $errormsg; my $instructions; my $brcrum = [{'href' => &Apache::loncommon::authorspace($request->uri), 'text' => 'Authoring Space'}, @@ -1635,15 +1696,47 @@ sub handler { } else { # Set the event timer to zero if the "done button" was clicked. The button is # part of the doneButton form created in lonmenu.pm - my ($donebuttonresult,$donemsg); + my ($donebuttonresult,$donemsg,$viewasuser); if ($symb && $env{'form.LC_interval_done'} eq 'true') { ($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,undef,$donemsg); + 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"); @@ -1789,16 +1882,15 @@ sub default_xml_tag { sub helpmenu_datastructure { - my $width = 500; - my $height = 600; - + # filename, title, width, height my $helpers = [ - ['Problem_LON-CAPA_Functions', &mt('Script Functions')], - ['Greek_Symbols', &mt('Greek Symbols')], - ['Other_Symbols', &mt('Other Symbols')], - ['Authoring_Output_Tags', &mt('Output Tags')], - ['Authoring_Multilingual_Problems', - &mt('How to create problems in different languages')] + ['Problem_LON-CAPA_Functions.hlp', &mt('Script Functions'), 800, 600], + ['Greek_Symbols.hlp', &mt('Greek Symbols'), 500, 600], + ['Other_Symbols.hlp', &mt('Other Symbols'), 500, 600], + ['Authoring_Output_Tags.hlp', &mt('Output Tags'), 800, 600], + ['Authoring_Multilingual_Problems.hlp', + &mt('How to create problems in different languages'), 800, 600], + ['loncapa.html', &mt('Language reference'), 800, 600], ]; my $help_structure = []; @@ -1806,7 +1898,15 @@ sub helpmenu_datastructure { foreach my $count (0..(scalar(@{$helpers})-1)) { my $filename = $helpers->[$count]->[0]; my $title = $helpers->[$count]->[1]; - my $href = &HTML::Entities::encode("javascript:openMyModal('/adm/help/$filename.hlp',$width,$height,'yes');"); + my $width = $helpers->[$count]->[2]; + my $height = $helpers->[$count]->[3]; + if ($width eq '') { + $width = 500; + } + if ($height eq '') { + $height = 600; + } + my $href = &HTML::Entities::encode("javascript:openMyModal('/adm/help/$filename',$width,$height,'yes');"); push @{$help_structure}, [$href, $title, undef]; } @@ -1824,5 +1924,84 @@ 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 $ckey = $item->{'lti'}->{'key'}; + my $secret = $item->{'lti'}->{'secret'}; + my $msgformat = $item->{'lti'}->{'passbackformat'}; + my $sigmethod = 'HMAC-SHA1'; + my $id = $item->{'pbid'}; + my $url = $item->{'pburl'}; + my $scope = $item->{'scope'}; + my $map = $item->{'ltimap'}; + my $symb = $item->{'ltisymb'}; + my $uname = $item->{'uname'}; + my $udom = $item->{'udom'}; + my $scoretype = $item->{'format'}; + my ($total,$possible); + if ($scope eq 'resource') { + $total = $item->{'total'}; + $possible = $item->{'possible'}; + } elsif ($scope eq 'map') { + ($total,$possible) = &get_lti_score($uname,$udom,$map); + } elsif ($scope eq 'course') { + ($total,$possible) = &get_lti_score($uname,$udom); + } + if (($ckey ne '') && ($secret ne '') && ($id ne '') && ($url ne '') && ($possible)) { + &LONCAPA::ltiutils::send_grade($id,$url,$ckey,$secret,$scoretype,$sigmethod, + $msgformat,$total,$possible); + } + } + } + } + undef(@Apache::lonhomework::ltipassback); + } +} + +sub get_lti_score { + my ($uname,$udom,$mapurl) = @_; + 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(); + $iterator = $navmap->getIterator($firstres,$finishres,undef,1); + } 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__