--- loncom/homework/lonhomework.pm 2017/04/03 13:11:47 1.344.2.8 +++ loncom/homework/lonhomework.pm 2023/11/17 17:02:21 1.380 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Homework handler # -# $Id: lonhomework.pm,v 1.344.2.8 2017/04/03 13:11:47 raeburn Exp $ +# $Id: lonhomework.pm,v 1.380 2023/11/17 17:02:21 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -49,15 +49,23 @@ use Apache::matchresponse(); 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(); @@ -155,9 +163,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 +206,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 +221,6 @@ sub proctor_checked_in { return 1; } } - return 0; } @@ -212,15 +229,49 @@ 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') { return ($status,$datemsg); } - if ($status eq 'SHOW_ANSWER' || - $status eq 'CLOSED' || + my $checkin = 'resource.0.checkedin'; + my $version; + if ($type eq 'Task') { + $version=$Apache::lonhomework::history{'resource.version'}; + $checkin = "resource.$version.0.checkedin"; + } + my $checkedin = $Apache::lonhomework::history{$checkin}; + my ($returned_slot,$slot_name,$checkinslot,$ipused,$blockip,$now,$ip, + $consumed_uniq); + $now = time; + $ip=$ENV{'REMOTE_ADDR'} || $env{'request.host'}; + + if ($checkedin) { + $checkinslot = $Apache::lonhomework::history{"$checkin.slot"}; + my %slot=&Apache::lonnet::get_slot($checkinslot); + $consumed_uniq = $slot{'uniqueperiod'}; + if ($slot{'iptied'}) { + $ipused = $Apache::lonhomework::history{"$checkin.ip"}; + unless (($ip ne '') && + (($ipused eq $ip) || ($ENV{'REMOTE_ADDR'} eq '127.0.0.1'))) { + $blockip = $slot{'iptied'}; + $slot_name = $checkinslot; + $returned_slot = \%slot; + } + } + } + + if ($status eq 'SHOW_ANSWER') { + if ($blockip eq 'answer') { + return ('NEED_DIFFERENT_IP','',$slot_name,$returned_slot,$ipused); + } else { + return ($status,$datemsg); + } + } + + if ($status eq 'CLOSED' || $status eq 'INVALID_ACCESS' || $status eq 'UNAVAILABLE') { return ($status,$datemsg); @@ -228,13 +279,22 @@ sub check_slot_access { if ($env{'request.state'} eq "construct") { return ($status,$datemsg); } - + if ($type eq 'Task') { - my $version=$Apache::lonhomework::history{'resource.version'}; - if ($Apache::lonhomework::history{"resource.$version.0.checkedin"} && + if ($checkedin && $Apache::lonhomework::history{"resource.$version.0.status"} eq 'pass') { - return ('SHOW_ANSWER'); - } + if ($blockip eq 'answer') { + return ('NEED_DIFFERENT_IP','',$slot_name,$returned_slot,$ipused); + } else { + 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); @@ -244,11 +304,11 @@ sub check_slot_access { # if (!@slots) { # return ($status,$datemsg); # } + undef($returned_slot); + undef($slot_name); my $slotstatus='NOT_IN_A_SLOT'; - my ($returned_slot,$slot_name); - my $now = time; my $num_usable_slots = 0; - unless ($symb) { + if (!$symb) { ($symb) = &Apache::lonnet::whichuser(); } foreach my $slot (@slots) { @@ -261,12 +321,41 @@ sub check_slot_access { if ($slot{'starttime'} < $now && $slot{'endtime'} > $now && &Apache::loncommon::check_ip_acc($slot{'ip'})) { - &Apache::lonxml::debug("$slot is good"); - $slotstatus='NEEDS_CHECKIN'; - $returned_slot=\%slot; - $slot_name=$slot; - last; - } + if ($slot{'iptied'}) { + if ($env{'request.course.id'}) { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + if ($slot eq $checkinslot) { + if ($ip eq $ipused) { + &Apache::lonxml::debug("$slot is good"); + $slotstatus ='NEEDS_CHECKIN'; + } else { + $slotstatus = 'NEED_DIFFERENT_IP'; + $slot_name = $slot; + $returned_slot = \%slot; + last; + } + } elsif ($ip) { + my $uniqkey = "$slot\0$symb\0$ip"; + my %used_ip = &Apache::lonnet::get('slot_uniqueips',[$uniqkey],$cdom,$cnum); + if ($used_ip{$uniqkey}) { + $slotstatus = 'NEED_DIFFERENT_IP'; + } else { + &Apache::lonxml::debug("$slot is good"); + $slotstatus ='NEEDS_CHECKIN'; + } + } + } + } else { + &Apache::lonxml::debug("$slot is good"); + $slotstatus='NEEDS_CHECKIN'; + } + if ($slotstatus eq 'NEEDS_CHECKIN') { + $returned_slot=\%slot; + $slot_name=$slot; + last; + } + } } if ($slotstatus eq 'NEEDS_CHECKIN' && &proctor_checked_in($slot_name,$returned_slot,$type)) { @@ -274,28 +363,16 @@ sub check_slot_access { $slotstatus=$status; } - my ($is_correct,$got_grade,$checkin,$checkinslot,$checkedin,$consumed_uniq); + my ($is_correct,$got_grade); 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)$/); $is_correct = ($Apache::lonhomework::history{"resource.$version.0.status"} eq 'pass' || $Apache::lonhomework::history{"resource.0.solved"} =~ /^correct_/ ); - $checkedin = - $Apache::lonhomework::history{"resource.$version.0.checkedin"}; - } elsif ($type eq 'problem') { - $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') { + } elsif (($type eq 'problem') || ($type eq 'tool')) { if ((ref($partlist) eq 'ARRAY') && (@{$partlist} > 0)) { my ($numcorrect,$numgraded) = (0,0); foreach my $part (@{$partlist}) { @@ -311,7 +388,7 @@ sub check_slot_access { $earlyout = 1; } } - if (($currtries == $maxtries) || ($is_correct)) { + if ($currtries == $maxtries) { $earlyout = 1; } else { $numgraded ++; @@ -354,74 +431,13 @@ 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'}; - $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); @@ -431,26 +447,105 @@ sub check_slot_access { && $checkedin ) { if ($got_grade) { - return ('SHOW_ANSWER'); + if ($blockip eq 'answer') { + return ('NEED_DIFFERENT_IP','',$slot_name,$returned_slot,$ipused); + } else { + return ('SHOW_ANSWER'); + } } else { return ('WAITING_FOR_GRADE'); } } - if ( $is_correct) { - if ($type eq 'problem') { + if (($is_correct) && ($blockip ne 'answer')) { + if (($type eq 'problem') || ($type eq 'tool')) { return ($status); } return ('SHOW_ANSWER'); } if ( $status eq 'CANNOT_ANSWER' && - ($slotstatus ne 'NEEDS_CHECKIN' && $slotstatus ne 'NOT_IN_A_SLOT')) { + ($slotstatus ne 'NEEDS_CHECKIN' && $slotstatus ne 'NOT_IN_A_SLOT' && + $slotstatus ne 'NEED_DIFFERENT_IP') ) { return ($status,$datemsg); } + return ($slotstatus,$datemsg,$slot_name,$returned_slot,$ipused); +} - return ($slotstatus,$datemsg,$slot_name,$returned_slot); +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 @@ -582,15 +677,16 @@ sub check_access { if ($status eq 'CAN_ANSWER' || $status eq 'CANNOT_ANSWER') { my @interval=&Apache::lonnet::EXT("resource.$id.interval",$symb); &Apache::lonxml::debug("looking for interval @interval"); - if ($interval[0]) { + if ($interval[0]=~ /^\d+/) { 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,$symb); my $seconds_left = $due_date - time; - if ($seconds_left > $interval[0] || $due_date eq '') { - $seconds_left = $interval[0]; + my ($timelimit) = ($interval[0] =~ /^(\d+)/); + if ($seconds_left > $timelimit || $due_date eq '') { + $seconds_left = $timelimit; } $datemsg=&seconds_to_human_length($seconds_left); } @@ -625,7 +721,8 @@ sub due_date { my $first_access=&Apache::lonnet::get_first_access($interval[1],$symb); &Apache::lonxml::debug("looking for first_access $first_access ($interval[1])"); if (defined($first_access)) { - my $interval = $first_access+$interval[0]; + my ($timelimit) = ($interval[0] =~ /^(\d+)/); + my $interval = $first_access+$timelimit; $date = (!$due_date || $interval < $due_date) ? $interval : $due_date; } else { @@ -710,6 +807,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 +827,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 +848,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); } @@ -847,8 +952,17 @@ sub analyze_header { my $js = &Apache::structuretags::setmode_javascript(); # Breadcrumbs - my $brcrum = [{'href' => &Apache::loncommon::authorspace($request->uri), - 'text' => 'Authoring Space'}, + my $text = 'Authoring Space'; + my $href = &Apache::loncommon::authorspace($request->uri); + if ($env{'request.course.id'}) { + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + if ($href eq "/priv/$cdom/$cnum/") { + $text = 'Course Authoring Space'; + } + } + my $brcrum = [{'href' => $href, + 'text' => $text}, {'href' => '', 'text' => 'Problem Testing'}, {'href' => '', @@ -860,24 +974,32 @@ 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::structuretags::remember_problem_state().' -
- - -
+
'; + my %editors = &Apache::loncommon::permitted_editors(); + foreach my $item ('editxml','edit') { + next unless ($editors{$item}); + $result .= ''. + "\n"; + } + $result .= + '

' - .&Apache::lonxml::message_location().' - '; + .&Apache::lonxml::message_location(). + ''; &Apache::lonxml::add_messages(\$result); $request->print($result); $request->rflush(); @@ -1056,9 +1178,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'}); @@ -1081,8 +1202,17 @@ sub editxmlmode { &Apache::lonhtmlcommon::dragmath_js("EditMathPopup"); # Breadcrumbs - my $brcrum = [{'href' => &Apache::loncommon::authorspace($request->uri), - 'text' => 'Authoring Space'}, + my $text = 'Authoring Space'; + my $href = &Apache::loncommon::authorspace($request->uri); + if ($env{'request.course.id'}) { + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + if ($href eq "/priv/$cdom/$cnum/") { + $text = 'Course Authoring Space'; + } + } + my $brcrum = [{'href' => $href, + 'text' => $text}, {'href' => '', 'text' => 'Problem Editing'}]; @@ -1107,7 +1237,7 @@ sub editxmlmode {
'. &mt('Problem Editing').' '.&Apache::loncommon::help_open_topic('Problem_Editor_XML_Index'). - '
'; + '
'; $result.=''. &Apache::structuretags::problem_edit_buttons('editxml'); @@ -1115,7 +1245,8 @@ sub editxmlmode { $result .= '
    '; - unless ($env{'environment.nocodemirror'}) { + my $nocodemirror = &Apache::loncommon::nocodemirror(); + unless ($nocodemirror) { # dropdown menus $result .= Apache::lonmenu::create_submenu("#", "", &mt("Problem Templates"), template_dropdown_datastructure()); @@ -1147,7 +1278,7 @@ sub editxmlmode { '
    '; my $resource = $env{'request.ambiguous'}; - unless($env{'environment.nocodemirror'}){ + unless ($nocodemirror) { $result .= '