--- loncom/homework/lonhomework.pm 2013/08/20 14:37:33 1.339 +++ loncom/homework/lonhomework.pm 2024/02/27 17:10:23 1.344.2.10.4.7 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Homework handler # -# $Id: lonhomework.pm,v 1.339 2013/08/20 14:37:33 bisitz Exp $ +# $Id: lonhomework.pm,v 1.344.2.10.4.7 2024/02/27 17:10:23 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(); @@ -155,9 +162,19 @@ sub get_target { return ('web','answer'); } elsif (($env{'form.problemmode'} eq 'saveedit') || ($env{'form.problemmode'} eq 'undo')) { - return ('modified','no_output_web','edit'); + my %editors = &Apache::loncommon::permitted_editors(); + if ($editors{'edit'}) { + return ('modified','no_output_web','edit'); + } else { + return ('web'); + } } elsif ($env{'form.problemmode'} eq 'edit') { - return ('no_output_web','edit'); + my %editors = &Apache::loncommon::permitted_editors(); + if ($editors{'edit'}) { + return ('no_output_web','edit'); + } else { + return ('web'); + } } else { return ('web'); } @@ -188,7 +205,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,17 +220,16 @@ sub proctor_checked_in { return 1; } } - return 0; } sub check_slot_access { - my ($id,$type)=@_; + my ($id,$type,$symb,$partlist)=@_; # does it pass normal muster - my ($status,$datemsg)=&check_access($id); - - my $useslots = &Apache::lonnet::EXT("resource.0.useslots"); + 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') { return ($status,$datemsg); @@ -235,10 +251,16 @@ 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"); - my $available = &Apache::lonnet::EXT("resource.0.available"); + my $availablestudent = &Apache::lonnet::EXT("resource.0.availablestudent",$symb); + my $available = &Apache::lonnet::EXT("resource.0.available",$symb); my @slots= (split(':',$availablestudent),split(':',$available)); # if (!@slots) { @@ -248,6 +270,9 @@ sub check_slot_access { my ($returned_slot,$slot_name); my $now = time; my $num_usable_slots = 0; + unless ($symb) { + ($symb) = &Apache::lonnet::whichuser(); + } foreach my $slot (@slots) { $slot =~ s/(^\s*|\s*$)//g; &Apache::lonxml::debug("getting $slot"); @@ -271,9 +296,10 @@ sub check_slot_access { $slotstatus=$status; } - my ($is_correct,$got_grade,$checkedin); + my ($is_correct,$got_grade,$checkin,$checkinslot,$checkedin,$consumed_uniq); if ($type eq 'Task') { my $version=$Apache::lonhomework::history{'resource.0.version'}; + $checkin = "resource.$version.0.checkedin"; $got_grade = ($Apache::lonhomework::history{"resource.$version.0.status"} =~ /^(?:pass|fail)$/); @@ -282,11 +308,58 @@ sub check_slot_access { || $Apache::lonhomework::history{"resource.0.solved"} =~ /^correct_/ ); $checkedin = $Apache::lonhomework::history{"resource.$version.0.checkedin"}; - } elsif ($type eq 'problem') { - $got_grade = 1; - $checkedin = $Apache::lonhomework::history{"resource.0.checkedin"}; - $is_correct = - ($Apache::lonhomework::history{"resource.0.solved"} =~/^correct_/); + } elsif (($type eq 'problem') || ($type eq 'tool')) { + $checkin = 'resource.0.checkedin'; + $checkedin = $Apache::lonhomework::history{$checkin}; + } + if ($checkedin) { + $checkinslot = $Apache::lonhomework::history{"$checkin.slot"}; + my %slot=&Apache::lonnet::get_slot($checkinslot); + $consumed_uniq = $slot{'uniqueperiod'}; + } + if (($type eq 'problem') || ($type eq 'tool')) { + if ((ref($partlist) eq 'ARRAY') && (@{$partlist} > 0)) { + my ($numcorrect,$numgraded) = (0,0); + foreach my $part (@{$partlist}) { + my $currtries = $Apache::lonhomework::history{"resource.$part.tries"}; + my $maxtries = &Apache::lonnet::EXT("resource.$part.maxtries",$symb); + my $probstatus = &Apache::structuretags::get_problem_status($part); + my $earlyout; + unless (($probstatus eq 'no') || + ($probstatus eq 'no_feedback_ever')) { + if ($Apache::lonhomework::history{"resource.$part.solved"} =~/^correct_/) { + $numcorrect ++; + } else { + $earlyout = 1; + } + } + if ($currtries == $maxtries) { + $earlyout = 1; + } else { + $numgraded ++; + } + last if ($earlyout); + } + my $numparts = scalar(@{$partlist}); + if ($numparts == $numcorrect) { + $is_correct = 1; + } + if ($numparts == $numgraded) { + $got_grade = 1; + } + } else { + my $currtries = $Apache::lonhomework::history{"resource.0.tries"}; + my $maxtries = &Apache::lonnet::EXT("resource.0.maxtries",$symb); + my $probstatus = &Apache::structuretags::get_problem_status('0'); + unless (($probstatus eq 'no') || + ($probstatus eq 'no_feedback_ever')) { + $is_correct = + ($Apache::lonhomework::history{"resource.0.solved"} =~/^correct_/); + } + unless (($currtries == $maxtries) || ($is_correct)) { + $got_grade = 1; + } + } } &Apache::lonxml::debug(" slot is $slotstatus checkedin ($checkedin) got_grade ($got_grade) is_correct ($is_correct)"); @@ -303,29 +376,71 @@ 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'}) { my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; - my ($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) { - $slotstatus = 'RESERVABLE'; - $datemsg = $reservable_now->{$reservable_now_order->[-1]}{'endreserve'}; + 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) { - $slotstatus = 'RESERVABLE_LATER'; - $datemsg = $reservable_future->{$reservable_future_order->[0]}{'startreserve'}; + 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'}; + } + } } } } @@ -347,7 +462,7 @@ sub check_slot_access { } if ( $is_correct) { - if ($type eq 'problem') { + if (($type eq 'problem') || ($type eq 'tool')) { return ($status); } return ('SHOW_ANSWER'); @@ -364,7 +479,7 @@ sub check_slot_access { # JB, 9/24/2002: Any changes in this function may require a change # in lonnavmaps::resource::getDateStatus. sub check_access { - my ($id) = @_; + my ($id,$symb) = @_; my $date =''; my $status; my $datemsg = ''; @@ -394,11 +509,13 @@ sub check_access { &Apache::lonxml::debug("checking for part :$id:"); &Apache::lonxml::debug("time:".time); - my ($symb)=&Apache::lonnet::whichuser(); + unless ($symb) { + ($symb)=&Apache::lonnet::whichuser(); + } &Apache::lonxml::debug("symb:".$symb); #if ($env{'request.state'} ne "construct" && $symb ne '') { if ($env{'request.state'} ne "construct") { - my $idacc = &Apache::lonnet::EXT("resource.$id.acc"); + my $idacc = &Apache::lonnet::EXT("resource.$id.acc",$symb); my $allowed=&Apache::loncommon::check_ip_acc($idacc); if (!$allowed && ($Apache::lonhomework::browse ne 'F')) { $status='INVALID_ACCESS'; @@ -414,12 +531,12 @@ sub check_access { foreach my $temp ("opendate","duedate","answerdate") { $lastdate = $date; if ($temp eq 'duedate') { - $date = &due_date($id); + $date = &due_date($id,$symb); } else { - $date = &Apache::lonnet::EXT("resource.$id.$temp"); + $date = &Apache::lonnet::EXT("resource.$id.$temp",$symb); } - my $thistype = &Apache::lonnet::EXT("resource.$id.$temp.type"); + my $thistype = &Apache::lonnet::EXT("resource.$id.$temp.type",$symb); if ($thistype =~ /^(con_lost|no_such_host)/ || $date =~ /^(con_lost|no_such_host)/) { $status='UNAVAILABLE'; @@ -428,10 +545,10 @@ sub check_access { } if ($thistype eq 'date_interval') { if ($temp eq 'opendate') { - $date=&Apache::lonnet::EXT("resource.$id.duedate")-$date; + $date=&Apache::lonnet::EXT("resource.$id.duedate",$symb)-$date; } if ($temp eq 'answerdate') { - $date=&Apache::lonnet::EXT("resource.$id.duedate")+$date; + $date=&Apache::lonnet::EXT("resource.$id.duedate",$symb)+$date; } } &Apache::lonxml::debug("found :$date: for :$temp:"); @@ -451,30 +568,30 @@ sub check_access { $datemsg=$date; } elsif ($type eq 'opendate') { $status='CLOSED'; - $datemsg = &mt("will open on")." $date"; + $datemsg = &mt('will open on [_1]',$date); } elsif ($type eq 'duedate') { $status='CAN_ANSWER'; - $datemsg = &mt("is due at")." $date"; + $datemsg = &mt('is due at [_1]',$date); } elsif ($type eq 'answerdate') { $status='CLOSED'; - $datemsg = &mt("was due on")." $lastdate".&mt(", and answers will be available on")." $date"; + $datemsg = &mt('was due on [_1], and answers will be available on [_2]', + $lastdate,$date); } } if ($status eq 'CAN_ANSWER' || (($Apache::lonhomework::browse eq 'F') && ($status eq 'CLOSED'))) { #check #tries, and if correct. my $tries = $Apache::lonhomework::history{"resource.$id.tries"}; - my $maxtries = &Apache::lonnet::EXT("resource.$id.maxtries"); + my $maxtries = &Apache::lonnet::EXT("resource.$id.maxtries",$symb); if ( $tries eq '' ) { $tries = '0'; } if ( $maxtries eq '' && $env{'request.state'} ne 'construct') { $maxtries = '2'; } - $Apache::lonhomework::results{'resource.'.$id.'.maxtries'}=$maxtries; if ($maxtries && $tries >= $maxtries) { $status = 'CANNOT_ANSWER'; } # if (correct and show prob status) or excused then CANNOT_ANSWER if ( ($Apache::lonhomework::history{"resource.$id.solved"}=~/^correct/) && (&show_problem_status()) ) { if (($Apache::lonhomework::history{"resource.$id.awarded"} >= 1) || - (&Apache::lonnet::EXT("resource.$id.retrypartial") !~/^1|on|yes$/i)) { + (&Apache::lonnet::EXT("resource.$id.retrypartial",$symb) !~/^1|on|yes$/i)) { $status = 'CANNOT_ANSWER'; } } elsif ($Apache::lonhomework::history{"resource.$id.solved"}=~/^excused/) { @@ -486,14 +603,14 @@ sub check_access { } } if ($status eq 'CAN_ANSWER' || $status eq 'CANNOT_ANSWER') { - my @interval=&Apache::lonnet::EXT("resource.$id.interval"); + my @interval=&Apache::lonnet::EXT("resource.$id.interval",$symb); &Apache::lonxml::debug("looking for interval @interval"); if ($interval[0]) { - my $first_access=&Apache::lonnet::get_first_access($interval[1]); + my $first_access=&Apache::lonnet::get_first_access($interval[1],$symb); &Apache::lonxml::debug("looking for accesstime $first_access"); if (!$first_access) { $status='NOT_YET_VIEWED'; - my $due_date = &due_date($id); + my $due_date = &due_date($id,$symb); my $seconds_left = $due_date - time; if ($seconds_left > $interval[0] || $due_date eq '') { $seconds_left = $interval[0]; @@ -540,7 +657,7 @@ sub due_date { } else { $date = $due_date; } - return $date + return $date; } sub seconds_to_human_length { @@ -589,7 +706,7 @@ sub showarray { sub showhashsubset { my ($hash,$keyre) = @_; my $resultkey; - foreach $resultkey (sort keys %$hash) { + foreach $resultkey (sort(keys(%$hash))) { if ($resultkey !~ /$keyre/) { next; } if (ref($$hash{$resultkey}) eq 'ARRAY' ) { &Apache::lonxml::debug("$resultkey ---- ". @@ -616,6 +733,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; @@ -633,6 +753,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; @@ -651,7 +774,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); } @@ -750,7 +875,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), @@ -766,23 +891,31 @@ sub analyze_header { {'bread_crumbs' => $brcrum,}) .&Apache::loncommon::head_subbox( &Apache::loncommon::CSTR_pageheader()); + my %lt = &Apache::lonlocal::texthash( + edit => 'Edit', + editxml => 'EditXML', + ); $result .= - &Apache::lonxml::message_location().' -
'; &Apache::lonxml::add_messages(\$result); $request->print($result); @@ -962,9 +1095,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'}); @@ -983,7 +1115,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 @@ -1010,27 +1142,89 @@ sub editxmlmode { ''; + + my $resource = $env{'request.ambiguous'}; + unless ($nocodemirror) { + $result .= ' + + ", + mode: CodeMirror.getMode(config, "perl"), + delimStyle: "tag", + } + ); + }); + var cm = CodeMirror.fromTextArea(document.getElementById("LC_editxmltext"), + { + mode: "mixedmode", + lineWrapping: true, + lineNumbers: true, + tabSize: 4, + indentUnit: 4, + + autoCloseTags: true, + autoCloseBrackets: true, + height: "auto", + styleActiveLine: true, + + extraKeys: { + "Tab": "indentMore", + "Shift-Tab": "indentLess", + } + }); + restoreScrollPosition("'.$resource.'"); + '; + } + + $result .= &Apache::loncommon::end_page(); + &Apache::lonxml::add_messages(\$result); + $request->print($result); } return ''; } @@ -1039,7 +1233,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.'); @@ -1069,7 +1263,7 @@ sub renderpage { ''.$filename.''); $result.= &Apache::loncommon::simple_error_page($request,'Not available', - $error,{'no_auto_mt_msg'}); + $error,{'no_auto_mt_msg' => 1}); return; } @@ -1078,6 +1272,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); @@ -1090,6 +1288,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{