--- loncom/homework/lonhomework.pm 2007/09/06 14:41:00 1.268.2.1 +++ loncom/homework/lonhomework.pm 2019/02/19 15:38:36 1.344.2.8.4.3 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Homework handler # -# $Id: lonhomework.pm,v 1.268.2.1 2007/09/06 14:41:00 albertel Exp $ +# $Id: lonhomework.pm,v 1.344.2.8.4.3 2019/02/19 15:38:36 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -47,13 +47,14 @@ use Apache::externalresponse(); use Apache::rankresponse(); use Apache::matchresponse(); use Apache::chemresponse(); +use Apache::functionplotresponse(); use Apache::drawimage(); use Apache::Constants qw(:common); -use HTML::Entities(); use Apache::loncommon(); use Apache::lonlocal; use Time::HiRes qw( gettimeofday tv_interval ); -use Apache::lonnet(); +use HTML::Entities(); +use File::Copy(); # FIXME - improve commenting @@ -63,6 +64,28 @@ BEGIN { } +=pod + +=item set_bubble_lines() + +Called at analysis time to set the bubble lines +hash for the problem.. This should be called in the +end_problemtype tag in analysis mode. + +We fetch the hash of part id counters from lonxml + and push them into analyze:{part_id.bubble_lines}. + +=cut + +sub set_bubble_lines { + my %bubble_counters = &Apache::lonxml::get_bubble_line_hash(); + + foreach my $key (keys(%bubble_counters)) { + $Apache::lonhomework::analyze{"$key.bubble_lines"} = + $bubble_counters{"$key"}; + } +} + # # Decides what targets to render for. # Implicit inputs: @@ -89,7 +112,11 @@ sub get_target { } elsif ( $env{'form.grade_target'} eq 'webgrade' && ($Apache::lonhomework::queuegrade eq 'F' )) { return ($env{'form.grade_target'}); - } + } elsif ($env{'form.grade_target'} eq 'answer') { + if ($env{'form.answer_output_mode'} eq 'tex') { + return ($env{'form.grade_target'}); + } + } if ($env{'form.webgrade'} && ($Apache::lonhomework::modifygrades eq 'F' || $Apache::lonhomework::queuegrade eq 'F' )) { @@ -102,44 +129,46 @@ sub get_target { return ('web'); } } elsif ($env{'request.state'} eq "construct") { +# +# We are in construction space, editing and testing problems +# if ( defined($env{'form.grade_target'}) ) { return ($env{'form.grade_target'}); } if ( defined($env{'form.preview'})) { if ( defined($env{'form.submitted'})) { +# +# We are doing a problem preview +# return ('grade', 'web'); } else { return ('web'); } } else { if ($env{'form.problemstate'} eq 'WEB_GRADE') { - #$env{'form.webgrade'} = 'yes'; return ('grade','webgrade','answer'); - } elsif ( $env{'form.problemmode'} eq &mt('View') || - $env{'form.problemmode'} eq &mt('Discard Edits and View')) { - if ( defined($env{'form.submitted'}) && - (!defined($env{'form.resetdata'})) && - (!defined($env{'form.newrandomization'}))) { - return ('grade', 'web','answer'); - } else { - return ('web','answer'); - } - } elsif ( $env{'form.problemmode'} eq &mt('Edit') || - $env{'form.problemmode'} eq 'Edit') { - if ( $env{'form.submitted'} eq 'edit' ) { - if ( $env{'form.submit'} eq &mt('Submit Changes and View') ) { - return ('modified','web','answer'); - } else { - return ('modified','no_output_web','edit'); - } - } else { - return ('no_output_web','edit'); - } + } elsif ($env{'form.problemmode'} eq 'view') { + return ('grade','web','answer'); + } elsif ($env{'form.problemmode'} eq 'saveview') { + return ('modified','web','answer'); + } elsif ($env{'form.problemmode'} eq 'discard') { + return ('web','answer'); + } elsif (($env{'form.problemmode'} eq 'saveedit') || + ($env{'form.problemmode'} eq 'undo')) { + return ('modified','no_output_web','edit'); + } elsif ($env{'form.problemmode'} eq 'edit') { + return ('no_output_web','edit'); } else { return ('web'); } - } + } +# +# End of Authoring Space +# } +# +# Huh? We are nowhere, so do nothing. +# return (); } @@ -149,16 +178,6 @@ sub setup_vars { # return ';$external::target='.$target.';'; } -sub createmenu { - my ($which,$request)=@_; - if ($which eq 'grade') { - $request->print(''); - } -} - sub proctor_checked_in { my ($slot_name,$slot,$type)=@_; my @possible_proctors=split(",",$slot->{'proctor'}); @@ -188,65 +207,13 @@ sub proctor_checked_in { return 0; } -$Apache::lonxml::browse=''; -sub check_ip_acc { - my ($acc)=@_; - &Apache::lonxml::debug("acc is $acc"); - if (!defined($acc) || $acc =~ /^\s*$/ || $acc =~/^\s*no\s*$/i) { - return 1; - } - my $allowed=0; - my $ip=$ENV{'REMOTE_ADDR'}; - my $name; - foreach my $pattern (split(',',$acc)) { - $pattern =~ s/^\s*//; - $pattern =~ s/\s*$//; - if ($pattern =~ /\*$/) { - #35.8.* - $pattern=~s/\*//; - if ($ip =~ /^\Q$pattern\E/) { $allowed=1; } - } elsif ($pattern =~ /(\d+\.\d+\.\d+)\.\[(\d+)-(\d+)\]$/) { - #35.8.3.[34-56] - my $low=$2; - my $high=$3; - $pattern=$1; - if ($ip =~ /^\Q$pattern\E/) { - my $last=(split(/\./,$ip))[3]; - if ($last <=$high && $last >=$low) { $allowed=1; } - } - } elsif ($pattern =~ /^\*/) { - #*.msu.edu - $pattern=~s/\*//; - if (!defined($name)) { - use Socket; - my $netaddr=inet_aton($ip); - ($name)=gethostbyaddr($netaddr,AF_INET); - } - if ($name =~ /\Q$pattern\E$/i) { $allowed=1; } - } elsif ($pattern =~ /\d+\.\d+\.\d+\.\d+/) { - #127.0.0.1 - if ($ip =~ /^\Q$pattern\E/) { $allowed=1; } - } else { - #some.name.com - if (!defined($name)) { - use Socket; - my $netaddr=inet_aton($ip); - ($name)=gethostbyaddr($netaddr,AF_INET); - } - if ($name =~ /\Q$pattern\E$/i) { $allowed=1; } - } - if ($allowed) { last; } - } - return $allowed; -} - sub check_slot_access { - my ($id,$type)=@_; + my ($id,$type,$symb,$partlist)=@_; # 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); @@ -270,39 +237,47 @@ sub check_slot_access { } } - my @slots= - (split(':',&Apache::lonnet::EXT("resource.0.availablestudent")), - split(':',&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); # } my $slotstatus='NOT_IN_A_SLOT'; 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"); my %slot=&Apache::lonnet::get_slot($slot); &Apache::lonhomework::showhash(%slot); - if ($slot{'starttime'} < time && - $slot{'endtime'} > time && - &check_ip_acc($slot{'ip'})) { + next if ($slot{'endtime'} < $now); + $num_usable_slots ++; + if ($slot{'starttime'} < $now && + $slot{'endtime'} > $now && + &Apache::loncommon::check_slotip_acc($slot{'ip'})) { &Apache::lonxml::debug("$slot is good"); $slotstatus='NEEDS_CHECKIN'; $returned_slot=\%slot; $slot_name=$slot; last; - } + } } if ($slotstatus eq 'NEEDS_CHECKIN' && &proctor_checked_in($slot_name,$returned_slot,$type)) { - &Apache::lonxml::debug("protoctor checked in"); - $slotstatus='CAN_ANSWER'; + &Apache::lonxml::debug("proctor checked in"); + $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)$/); @@ -312,10 +287,57 @@ sub check_slot_access { $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_/); + $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') { + 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; + } + } } &Apache::lonxml::debug(" slot is $slotstatus checkedin ($checkedin) got_grade ($got_grade) is_correct ($is_correct)"); @@ -328,12 +350,81 @@ sub check_slot_access { return ('WAITING_FOR_GRADE'); } - # no slot is currently open, and has been checked in for this version - # previous slot is therefore CLOSED, so therefore the problem is - if (!defined($slot_name) - && $checkedin - && $type eq 'problem') { - return ('CLOSED',$datemsg); + # Previously used slot is no longer open, and has been checked in for this version. + # 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') { + 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'}; + } + } + } + } + } + } + } + } + return ($slotstatus,$datemsg); } if ($slotstatus eq 'NOT_IN_A_SLOT' @@ -365,7 +456,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 = ''; @@ -376,8 +467,8 @@ sub check_access { if ($env{'request.state'} eq "construct") { if ($env{'form.problemstate'}) { if ($env{'form.problemstate'} =~ /^CANNOT_ANSWER/) { - if ( ! ($env{'form.problemstate'} eq 'CANNOT_ANSWER_correct' && - lc($Apache::lonhomework::problemstatus) eq 'no')) { + if ( ! ($env{'form.problemstate'} eq 'CANNOT_ANSWER_correct' + && &hide_problem_status())) { return ('CANNOT_ANSWER', &mt('is in this state due to author settings.')); } @@ -395,26 +486,34 @@ 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 $allowed=&check_ip_acc(&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'; $date=&mt("can not be accessed from your location."); return($status,$date); } - + if ($env{'form.grade_imsexport'}) { + if (($env{'request.course.id'}) && + (&Apache::lonnet::allowed('mdc',$env{'request.course.id'}))) { + return ('SHOW_ANSWER'); + } + } 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'; @@ -423,10 +522,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:"); @@ -436,7 +535,7 @@ sub check_access { $date = &mt("an indeterminate date"); $passed = 0; } else { if (time < $date) { $passed = 0; } else { $passed = 1; } - $date = localtime $date; + $date = &Apache::lonlocal::locallocaltime($date); } if (!$passed) { $type=$temp; last; } } @@ -446,45 +545,52 @@ 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'; } 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/ - && - lc($Apache::lonhomework::problemstatus) ne 'no') - || - $Apache::lonhomework::history{"resource.$id.solved"}=~/^excused/) { + 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",$symb) !~/^1|on|yes$/i)) { + $status = 'CANNOT_ANSWER'; + } + } elsif ($Apache::lonhomework::history{"resource.$id.solved"}=~/^excused/) { $status = 'CANNOT_ANSWER'; } + if ($status eq 'CANNOT_ANSWER' + && &show_answer_problem_status()) { + $status = 'SHOW_ANSWER'; + } } if ($status eq 'CAN_ANSWER' || $status eq 'CANNOT_ANSWER') { - my $interval=&Apache::lonnet::EXT("resource.$id.interval"); - &Apache::lonxml::debug("looking for interval $interval"); - if ($interval) { - my $first_access=&Apache::lonnet::get_first_access('map'); + 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],$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 || $due_date eq '') { - $seconds_left = $interval; + if ($seconds_left > $interval[0] || $due_date eq '') { + $seconds_left = $interval[0]; } $datemsg=&seconds_to_human_length($seconds_left); } @@ -496,7 +602,6 @@ sub check_access { # return ('UNCHECKEDOUT','needs to be checked out'); #} - &Apache::lonxml::debug("sending back :$status:$datemsg:"); if (($Apache::lonhomework::browse eq 'F') && ($status eq 'CLOSED')) { &Apache::lonxml::debug("should be allowed to browse a resource when closed"); @@ -510,25 +615,26 @@ sub check_access { sub due_date { my ($part_id,$symb,$udom,$uname)=@_; my $date; - my $interval= &Apache::lonnet::EXT("resource.$part_id.interval",$symb, + my @interval= &Apache::lonnet::EXT("resource.$part_id.interval",$symb, $udom,$uname); - &Apache::lonxml::debug("looking for interval $part_id $symb $interval"); + &Apache::lonxml::debug("looking for interval $part_id $symb @interval"); my $due_date= &Apache::lonnet::EXT("resource.$part_id.duedate",$symb, $udom,$uname); &Apache::lonxml::debug("looking for due_date $part_id $symb $due_date"); - if ($interval =~ /\d+/) { - my $first_access=&Apache::lonnet::get_first_access('map',$symb); - &Apache::lonxml::debug("looking for first_access $first_access"); + if ($interval[0] =~ /\d+/) { + 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)) { - $interval = $first_access+$interval; - $date = ($interval < $due_date)? $interval : $due_date; + my $interval = $first_access+$interval[0]; + $date = (!$due_date || $interval < $due_date) ? $interval + : $due_date; } else { $date = $due_date; } } else { $date = $due_date; } - return $date + return $date; } sub seconds_to_human_length { @@ -577,7 +683,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 ---- ". @@ -595,6 +701,9 @@ sub showhashsubset { sub setuppermissions { $Apache::lonhomework::browse= &Apache::lonnet::allowed('bre',$env{'request.filename'}); + unless ($Apache::lonhomework::browse eq 'F') { + $Apache::lonhomework::browse=&Apache::lonnet::allowed('bro',$env{'request.filename'}); + } my $viewgrades = &Apache::lonnet::allowed('vgr',$env{'request.course.id'}); if (! $viewgrades && exists($env{'request.course.sec'}) && @@ -654,16 +763,17 @@ sub setupheader { } sub handle_save_or_undo { - my ($request,$problem,$result) = @_; + my ($request,$problem,$result,$getobjref) = @_; + my $file = &Apache::lonnet::filelocation("",$request->uri); my $filebak =$file.".bak"; my $filetmp =$file.".tmp"; my $error=0; - if ($env{'form.Undo'} eq &mt('undo')) { + if (($env{'form.problemmode'} eq 'undo') || ($env{'form.problemmode'} eq 'undoxml')) { my $error=0; - if (!copy($file,$filetmp)) { $error=1; } - if ((!$error) && (!copy($filebak,$file))) { $error=1; } - if ((!$error) && (!move($filetmp,$filebak))) { $error=1; } + if (!&File::Copy::copy($file,$filetmp)) { $error=1; } + if ((!$error) && (!&File::Copy::copy($filebak,$file))) { $error=1; } + if ((!$error) && (!&File::Copy::move($filetmp,$filebak))) { $error=1; } if (!$error) { &Apache::lonxml::info("

". &mt("Undid changes, Switched [_1] and [_2]", @@ -682,13 +792,10 @@ sub handle_save_or_undo { } } else { &Apache::lonnet::correct_line_ends($result); + my $fs=Apache::File->new(">$filebak"); if (defined($fs)) { print $fs $$problem; - &Apache::lonxml::info("".&mt("Making Backup to [_1]", - ''. - $filebak.''). - ""); } else { &Apache::lonxml::info("". &mt("Unable to make backup [_1]", @@ -699,9 +806,30 @@ sub handle_save_or_undo { my $fh=Apache::File->new(">$file"); if (defined($fh)) { print $fh $$result; - &Apache::lonxml::info("".&mt("Saving Modifications to [_1]", - ''. - $file.'' ).""); + if (ref($getobjref) eq 'SCALAR') { + if ($file =~ m{([^/]+)\.(html?)$}) { + my $fname = $1; + my $ext = $2; + my $path = $file; + $path =~ s/\Q$fname\E\.\Q$ext\E$//; + my (%allfiles,%codebase); + &Apache::lonnet::extract_embedded_items($file,\%allfiles, + \%codebase,$result); + if (keys(%allfiles) > 0) { + my $url = $request->uri; + my $state = < + +STATE + $$getobjref = "

".&mt("Reference Warning")."

". + "

".&mt("Completed upload of the file. This file contained references to other files.")."

". + "

".&mt("Please select the locations from which the referenced files are to be uploaded.")."

". + &Apache::loncommon::ask_for_embedded_content($url,$state,\%allfiles,\%codebase, + {'error_on_invalid_names' => 1, + 'ignore_remote_references' => 1,}); + } + } + } } else { &Apache::lonxml::info(''. &mt("Unable to write to [_1]", @@ -716,19 +844,39 @@ sub handle_save_or_undo { sub analyze_header { my ($request) = @_; - my $result = - &Apache::loncommon::start_page('Analyzing a problem',undef); + my $js = &Apache::structuretags::setmode_javascript(); + # Breadcrumbs + my $brcrum = [{'href' => &Apache::loncommon::authorspace($request->uri), + 'text' => 'Authoring Space'}, + {'href' => '', + 'text' => 'Problem Testing'}, + {'href' => '', + 'text' => 'Analyzing a problem'}]; + + my $result = + &Apache::loncommon::start_page('Analyzing a problem', + $js, + {'bread_crumbs' => $brcrum,}) + .&Apache::loncommon::head_subbox( + &Apache::loncommon::CSTR_pageheader()); $result .= - &Apache::lonxml::message_location().' -
'. + ''. &Apache::structuretags::remember_problem_state().' - - +
+ +
- +
+
' + .&Apache::lonxml::message_location().'
'; &Apache::lonxml::add_messages(\$result); $request->print($result); @@ -751,13 +899,9 @@ sub analyze { my $rndseed=$env{'form.rndseed'}; &analyze_header($request); my %prog_state= - &Apache::lonhtmlcommon::Create_PrgWin($request,&mt('Analyze Progress'), - &mt('Getting Problem Variants'), - $env{'form.numtoanalyze'}, - 'inline',undef); + &Apache::lonhtmlcommon::Create_PrgWin($request,$env{'form.numtoanalyze'}); for(my $i=1;$i<$env{'form.numtoanalyze'}+1;$i++) { - &Apache::lonhtmlcommon::Increment_PrgWin($request,\%prog_state, - &mt('last problem')); + &Apache::lonhtmlcommon::Increment_PrgWin($request,\%prog_state,'last problem'); if (&Apache::loncommon::connection_aborted($request)) { return; } my $thisseed=$i+$rndseed; my $subresult=&Apache::lonnet::ssi($request->uri, @@ -766,7 +910,7 @@ sub analyze { (my $garbage,$subresult)=split(/_HASH_REF__/,$subresult,2); my %analyze=&Apache::lonnet::str2hash($subresult); my @parts; - if (defined(@{ $analyze{'parts'} })) { + if (ref($analyze{'parts'}) eq 'ARRAY') { @parts=@{ $analyze{'parts'} }; } foreach my $part (@parts) { @@ -788,42 +932,75 @@ sub analyze { $i++; } } + if (!keys(%{ $analyze{$part.'.answer'} })) { + my $answer_part = + [''.&mt('Error').'']; + $seedexample{join("\0",$part,0,@{$answer_part})}= + $thisseed; + push( @{ $overall{$part.'.answer'}[0] }, + $answer_part); + } } } } - &Apache::lonhtmlcommon::Update_PrgWin($request,\%prog_state, - &mt('Analyzing Results')); - $request->print('
'.&mt('List of possible answers').': '); + &Apache::lonhtmlcommon::Update_PrgWin($request,\%prog_state,&mt('Analyzing Results')); + $request->print('
' + .'

' + .&mt('List of possible answers') + .'

' + ); foreach my $part (sort(keys(%allparts))) { - if (defined(@{ $overall{$part.'.answer'} })) { + if ((ref($overall{$part.'.answer'}) eq 'ARRAY') && + (@{$overall{$part.'.answer'}} > 0)) { for (my $i=0;$iprint(''); + $request->print('' + .&Apache::loncommon::end_data_table_header_row() + ); my %frequency; foreach my $answer (sort {$a->[0] <=> $b->[0]} (@{ $overall{$part.'.answer'}[$i] })) { $frequency{join("\0",@{ $answer })}++; } - $request->print(''); + $request->print(&Apache::loncommon::start_data_table_header_row() + .'' + .'' + .&Apache::loncommon::end_data_table_header_row() + ); foreach my $answer (sort {(split("\0",$a))[0] <=> (split("\0",$b))[0]} (keys(%frequency))) { - $request->print(''); + $request->print(&Apache::loncommon::start_data_table_row() + .'' + .'' + .&Apache::loncommon::end_data_table_row() + ); } - $request->print('
'.&mt('Part').' '.$part); + $request->print(&Apache::loncommon::start_data_table() + .&Apache::loncommon::start_data_table_header_row() + .'' + .&mt('Part').' '.$part + ); if (scalar(@{ $overall{$part.'.answer'} }) > 1) { - $request->print(&mt(' Answer [_1]',$i+1)); + $request->print(' '.&mt('Answer [_1]',$i+1)); } - $request->print('
'.&mt('Answer').''.&mt('Frequency').'
(' - .&mt('click for example').')
'.&mt('Answer').''.&mt('Frequency').'
' + .'('.&mt('click for example').')
'. - join('',split("\0",$answer)). - ''.$frequency{$answer}. - '
' + .join('',split("\0",$answer)) + .'' + .''.$frequency{$answer}.'' + .'
'); + $request->print(&Apache::loncommon::end_data_table()); } } else { - $request->print('

'.&mt('Response').' '.$part.' '. - &mt('is not analyzable at this time').'

'); + $request->print('

' + .&mt('Response [_1] is not analyzable at this time.',$part) + .'

' + ); } } if (scalar(keys(%allparts)) == 0 ) { - $request->print('

'.&mt('Found no analyzable responses in this problem, currently only Numerical, Formula and String response styles are supported.').'

'); + $request->print('

' + .&mt('Found no analyzable responses in this problem.' + .' Currently only Numerical, Formula and String response styles are supported.') + .'

' + ); } &Apache::lonhtmlcommon::Close_PrgWin($request,\%prog_state); &analyze_footer($request); @@ -831,63 +1008,184 @@ sub analyze { return $result; } +{ + my $show_problem_status; + sub reset_show_problem_status { + undef($show_problem_status); + } + + sub set_show_problem_status { + my ($new_status) = @_; + $show_problem_status = lc($new_status); + } + + sub hide_problem_status { + return ($show_problem_status eq 'no' + || $show_problem_status eq 'no_feedback_ever'); + } + + sub show_problem_status { + return ($show_problem_status eq 'yes' + || $show_problem_status eq 'answer' + || $show_problem_status eq ''); + } + + sub show_some_problem_status { + return ($show_problem_status eq 'no'); + } + + sub show_no_problem_status { + return ($show_problem_status eq 'no_feedback_ever'); + } + + sub show_answer_problem_status { + return ($show_problem_status eq 'answer'); + } +} + sub editxmlmode { my ($request,$file) = @_; my $result; my $problem=&Apache::lonnet::getfile($file); if ($problem eq -1) { - &Apache::lonxml::error(" ".&mt('Unable to find'). - " $file"); + &Apache::lonxml::error( + '

' + .&mt('Unable to find [_1]', + ''.$file.'') + .'

'); + $problem=''; } - if (defined($env{'form.editxmltext'}) || defined($env{'form.Undo'})) { + + if (($env{'form.problemmode'} eq 'saveeditxml') || + ($env{'form.problemmode'} eq 'saveviewxml') || + ($env{'form.problemmode'} eq 'undoxml')) { my $error=&handle_save_or_undo($request,\$problem, \$env{'form.editxmltext'}); if (!$error) { $problem=&Apache::lonnet::getfile($file); } } &Apache::lonhomework::showhashsubset(\%env,'^form'); - if ( $env{'form.submit'} eq &mt('Submit Changes and View') ) { + if ($env{'form.problemmode'} eq 'saveviewxml') { &Apache::lonhomework::showhashsubset(\%env,'^form'); - $env{'form.problemmode'}='View'; + $env{'form.problemmode'}='view'; &renderpage($request,$file); } else { my ($rows,$cols) = &Apache::edit::textarea_sizes(\$problem); - my $xml_help = '
'. - &Apache::loncommon::helpLatexCheatsheet("Problem_Editor_XML_Index", - "Problem Editing Help"). - ''. - &Apache::loncommon::help_open_menu(undef,undef,5,'Authoring'). - '
'; if ($cols > 80) { $cols = 80; } if ($cols < 70) { $cols = 70; } if ($rows < 20) { $rows = 20; } + my $js = + &Apache::edit::js_change_detection(). + &Apache::loncommon::resize_textarea_js(). + &Apache::structuretags::setmode_javascript(). + &Apache::lonhtmlcommon::dragmath_js("EditMathPopup"); + + # Breadcrumbs + my $brcrum = [{'href' => &Apache::loncommon::authorspace($request->uri), + 'text' => 'Authoring Space'}, + {'href' => '', + 'text' => 'Problem Editing'}]; + my $start_page = - &Apache::loncommon::start_page(&mt("EditXML [_1]",$file), - &Apache::edit::js_change_detection(), - {'no_auto_mt_title' => 1,}); - - $result.=$start_page. - &renderpage($request,$file,['no_output_web'],1). - &Apache::lonxml::message_location().' -
1, + 'only_body' => 0, + 'add_entries' => { + 'onresize' => q[resize_textarea('LC_editxmltext','LC_aftertextarea')], + 'onload' => q[resize_textarea('LC_editxmltext','LC_aftertextarea')], + }, + 'bread_crumbs' => $brcrum, + }); + + $result=$start_page + .&Apache::loncommon::head_subbox( + &Apache::loncommon::CSTR_pageheader()); + $result.=&renderpage($request,$file,['no_output_web'],1). + ''. &Apache::structuretags::remember_problem_state().' - - - - -
- - -
- ' . $xml_help . ' -
- - -
'.&Apache::loncommon::end_page(); - &Apache::lonxml::add_messages(\$result); - $request->print($result); +
+
'. + &mt('Problem Editing').' '.&Apache::loncommon::help_open_topic('Problem_Editor_XML_Index'). + '
'; + + $result.=''. + &Apache::structuretags::problem_edit_buttons('editxml'); + $result.='
'; + + $result .= '
    '; + + unless ($env{'environment.nocodemirror'}) { + # dropdown menus + $result .= Apache::lonmenu::create_submenu("#", "", + &mt("Problem Templates"), template_dropdown_datastructure()); + + $result .= Apache::lonmenu::create_submenu("#", "", + &mt("Response Types"), responseblock_dropdown_datastructure()); + + $result .= Apache::lonmenu::create_submenu("#", "", + &mt("Conditional Blocks"), conditional_scripting_datastructure()); + + $result .= Apache::lonmenu::create_submenu("#", "", + &mt("Miscellaneous"), misc_datastructure()); + } + + $result .= Apache::lonmenu::create_submenu("#", "", + &mt("Help") . ' ' . &mt(', + helpmenu_datastructure(),""); + + $result.="
"; + + $result .= '
' . + &Apache::lonxml::message_location() . + &Apache::loncommon::xmleditor_js() . + '
'; + + my $resource = $env{'request.ambiguous'}; + unless($env{'environment.nocodemirror'}){ + $result .= ' + + '; + } + + $result .= &Apache::loncommon::end_page(); + &Apache::lonxml::add_messages(\$result); + $request->print($result); } return ''; } @@ -922,12 +1220,11 @@ 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); + $error,{'no_auto_mt_msg' => 1}); return; } @@ -975,103 +1272,229 @@ sub finished_parsing { undef($Apache::lonhomework::parsing_a_task); } -# with no arg it returns a HTML