--- loncom/homework/lonhomework.pm 2016/08/06 23:23:18 1.344.2.3 +++ loncom/homework/lonhomework.pm 2015/10/26 20:36:45 1.359 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Homework handler # -# $Id: lonhomework.pm,v 1.344.2.3 2016/08/06 23:23:18 raeburn Exp $ +# $Id: lonhomework.pm,v 1.359 2015/10/26 20:36:45 musolffc Exp $ # # Copyright Michigan State University Board of Trustees # @@ -49,8 +49,10 @@ use Apache::matchresponse(); use Apache::chemresponse(); use Apache::functionplotresponse(); use Apache::drawimage(); +use Apache::loncapamath(); use Apache::Constants qw(:common); use Apache::loncommon(); +use Apache::lonparmset(); use Apache::lonlocal; use Time::HiRes qw( gettimeofday tv_interval ); use HTML::Entities(); @@ -208,19 +210,50 @@ sub proctor_checked_in { } sub check_slot_access { - my ($id,$type,$partlist)=@_; + my ($id,$type,$symb)=@_; # does it pass normal muster - my ($status,$datemsg)=&check_access($id); + my ($status,$datemsg)=&check_access($id,$symb); - my $useslots = &Apache::lonnet::EXT("resource.0.useslots"); + 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); + $now = time; + $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'}; + + if ($checkedin) { + $checkinslot = $Apache::lonhomework::history{"$checkin.slot"}; + my %slot=&Apache::lonnet::get_slot($checkinslot); + if ($slot{'iptied'}) { + $ipused = $Apache::lonhomework::history{"$checkin.ip"}; + unless (($ip ne '') && ($ipused eq $ip)) { + $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); @@ -230,24 +263,30 @@ sub check_slot_access { } 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'); + } + } } - 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) { # 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; + if (!$symb) { + ($symb) = &Apache::lonnet::whichuser(); + } foreach my $slot (@slots) { $slot =~ s/(^\s*|\s*$)//g; &Apache::lonxml::debug("getting $slot"); @@ -258,12 +297,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)) { @@ -271,7 +339,7 @@ sub check_slot_access { $slotstatus=$status; } - my ($is_correct,$got_grade,$checkinslot,$checkedin,$consumed_uniq); + my ($is_correct,$got_grade); if ($type eq 'Task') { my $version=$Apache::lonhomework::history{'resource.0.version'}; $got_grade = @@ -280,59 +348,10 @@ sub check_slot_access { $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') { - $checkedin = $Apache::lonhomework::history{"resource.0.checkedin"}; - } - if ($checkedin) { - $checkinslot = $Apache::lonhomework::history{"$checkin.slot"}; - my %slot=&Apache::lonnet::get_slot($checkinslot); - $consumed_uniq = $slot{'uniqueperiod'}; - } - if ($type eq 'problem') { - 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) || ($is_correct)) { - $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; - } - } + $got_grade = 1; + $is_correct = + ($Apache::lonhomework::history{"resource.0.solved"} =~/^correct_/); } &Apache::lonxml::debug(" slot is $slotstatus checkedin ($checkedin) got_grade ($got_grade) is_correct ($is_correct)"); @@ -349,71 +368,31 @@ 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. + # (which will be one of: NOT_IN_A_SLOT, RESERVABLE, RESERVABLE_LATER, or NOTRESERVABLE). if (!defined($slot_name) && $type eq 'problem') { 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(); + 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'}; - } - } + $slotstatus = 'RESERVABLE'; + $datemsg = $reservable_now->{$reservable_now_order->[-1]}{'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 = 'RESERVABLE_LATER'; + $datemsg = $reservable_future->{$reservable_future_order->[0]}{'startreserve'}; } } } @@ -427,14 +406,18 @@ 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 (($is_correct) && ($blockip ne 'answer')) { if ($type eq 'problem') { return ($status); } @@ -442,17 +425,17 @@ sub check_slot_access { } 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); + return ($slotstatus,$datemsg,$slot_name,$returned_slot,$ipused); } # 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 = ''; @@ -482,11 +465,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'; @@ -502,12 +487,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'; @@ -516,10 +501,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:"); @@ -553,7 +538,7 @@ sub check_access { (($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'; } @@ -562,7 +547,7 @@ sub check_access { 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/) { @@ -574,14 +559,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]; @@ -855,8 +840,7 @@ sub analyze_header { .&Apache::loncommon::head_subbox( &Apache::loncommon::CSTR_pageheader()); $result .= - &Apache::lonxml::message_location().' -
'; + ' + .&Apache::lonxml::message_location(). + ''; &Apache::lonxml::add_messages(\$result); $request->print($result); $request->rflush(); @@ -1050,9 +1035,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'}); @@ -1098,27 +1082,88 @@ sub editxmlmode { ''; + + my $resource = $env{'request.ambiguous'}; + unless($env{'environment.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 ''; } @@ -1153,8 +1198,10 @@ sub renderpage { $problem=''; my $filename=(split('/',$file))[-1]; my $error = - &mt('Unable to find [_1]', - ''.$filename.''); + '' + .&mt('Unable to find [_1]', + ''.$filename.'') + ."
"; $result.= &Apache::loncommon::simple_error_page($request,'Not available', $error,{'no_auto_mt_msg' => 1}); @@ -1205,7 +1252,25 @@ sub finished_parsing { undef($Apache::lonhomework::parsing_a_task); } -sub get_template_list { +# function extracted from get_template_html +# returns "key" -> list +# key: path of template +# value 1: title +# value 2: category +# value 3: name of help topic ??? +sub get_template_list{ + my ($extension) = @_; + + my @files = glob($Apache::lonnet::perlvar{'lonIncludes'}. + '/templates/*.'.$extension); + @files = map {[$_,&mt(&Apache::lonnet::metadata($_, 'title')), + (&Apache::lonnet::metadata($_, 'category')?&mt(&Apache::lonnet::metadata($_, 'category')):&mt('Miscellaneous')), + &mt(&Apache::lonnet::metadata($_, 'help'))]} (@files); + @files = sort {$a->[2].$a->[1] cmp $b->[2].$b->[1]} (@files); + return @files; +} + +sub get_template_html { my ($extension) = @_; my $result; my @allnames; @@ -1214,12 +1279,7 @@ sub get_template_list { if ($extension eq 'survey' || $extension eq 'exam') { $glob_extension = 'problem'; } - my @files = glob($Apache::lonnet::perlvar{'lonIncludes'}. - '/templates/*.'.$glob_extension); - @files = map {[$_,&mt(&Apache::lonnet::metadata($_, 'title')), - (&Apache::lonnet::metadata($_, 'category')?&mt(&Apache::lonnet::metadata($_, 'category')):&mt('Miscellaneous')), - &Apache::lonnet::metadata($_, 'help')]} (@files); - @files = sort {$a->[2].$a->[1] cmp $b->[2].$b->[1]} (@files); + my @files = &get_template_list($extension); my ($midpoint,$seconddiv,$numfiles); my @noexamplelink = ('blank.problem','blank.library','script.library'); $numfiles = 0; @@ -1281,6 +1341,12 @@ sub get_template_list { sub newproblem { my ($request) = @_; + if ($env{'form.mode'} eq 'blank'){ + my $dest = &Apache::lonnet::filelocation("",$request->uri); + &File::Copy::copy('/home/httpd/html/res/adm/includes/templates/blank.problem',$dest); + &renderpage($request,$dest); + return; + } if ($env{'form.template'}) { my $file = $env{'form.template'}; my $dest = &Apache::lonnet::filelocation("",$request->uri); @@ -1291,7 +1357,7 @@ sub newproblem { my ($extension) = ($request->uri =~ m/\.(\w+)$/); &Apache::lonxml::debug("Looking for :$extension:"); - my $templatelist=&get_template_list($extension); + my $templatelist=&get_template_html($extension); if ($env{'form.newfile'} && !$templatelist) { # no templates found my $templatefilename = @@ -1353,10 +1419,51 @@ sub update_construct_style { } } +# +# Sets interval for current user so time left will be zero, either for the entire folder +# containing the current resource, or just the resource, depending on value of first item +# in interval array retrieved from EXT("resource.0.interval"); +# +sub zero_timer { + my ($symb) = @_; + my ($hastimeleft,$first_access,$now); + my @interval=&Apache::lonnet::EXT("resource.0.interval",$symb); + if (@interval > 1) { + if ($interval[1] eq 'course') { + return; + } else { + my $now = time; + my $first_access=&Apache::lonnet::get_first_access($interval[1],$symb); + if ($first_access > 0) { + my ($timelimit) = split(/_/,$interval[0]); + 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'}); + # Record action in "User Notes" + &Apache::lonmsg::store_instructor_comment( + 'Pressed Done button for symb: