--- loncom/homework/lonhomework.pm 2007/08/18 00:01:45 1.269 +++ loncom/homework/lonhomework.pm 2011/07/04 13:01:57 1.327 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Homework handler # -# $Id: lonhomework.pm,v 1.269 2007/08/18 00:01:45 albertel Exp $ +# $Id: lonhomework.pm,v 1.327 2011/07/04 13:01:57 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 Construction 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,58 +207,6 @@ 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)=@_; @@ -270,9 +237,9 @@ 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"); + my $available = &Apache::lonnet::EXT("resource.0.available"); + my @slots= (split(':',$availablestudent),split(':',$available)); # if (!@slots) { # return ($status,$datemsg); @@ -286,7 +253,7 @@ sub check_slot_access { &Apache::lonhomework::showhash(%slot); if ($slot{'starttime'} < time && $slot{'endtime'} > time && - &check_ip_acc($slot{'ip'})) { + &Apache::loncommon::check_ip_acc($slot{'ip'})) { &Apache::lonxml::debug("$slot is good"); $slotstatus='NEEDS_CHECKIN'; $returned_slot=\%slot; @@ -296,8 +263,8 @@ sub check_slot_access { } 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); @@ -328,12 +295,15 @@ 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 + # 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 NOT_IN_SLOT). if (!defined($slot_name) && $checkedin && $type eq 'problem') { - return ('CLOSED',$datemsg); + return ($slotstatus); } if ($slotstatus eq 'NOT_IN_A_SLOT' @@ -376,8 +346,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.')); } @@ -399,13 +369,19 @@ sub check_access { &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"); + 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') { @@ -436,7 +412,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; } } @@ -463,28 +439,35 @@ sub check_access { 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/ && - lc($Apache::lonhomework::problemstatus) ne 'no') + &show_problem_status() + && + $Apache::lonhomework::history{"resource.$id.awarded"}==1) || $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"); + &Apache::lonxml::debug("looking for interval @interval"); + if ($interval[0]) { + my $first_access=&Apache::lonnet::get_first_access($interval[1]); &Apache::lonxml::debug("looking for accesstime $first_access"); if (!$first_access) { $status='NOT_YET_VIEWED'; my $due_date = &due_date($id); 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); } @@ -510,18 +493,19 @@ 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; } @@ -655,15 +639,16 @@ sub setupheader { sub handle_save_or_undo { my ($request,$problem,$result) = @_; + 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 +667,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 +681,6 @@ 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.'' )."");
} else {
&Apache::lonxml::info(''.
&mt("Unable to write to [_1]",
@@ -716,19 +695,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(),
+ 'text' => 'Construction 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::lonxml::add_messages(\$result);
$request->print($result);
@@ -788,42 +787,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('Response').' '.$part.' '.
- &mt('is not analyzable at this time').' '
+ .&mt('Response [_1] is not analyzable at this time.',$part)
+ .' '.&mt('Found no analyzable responses in this problem, currently only Numerical, Formula and String response styles are supported.').' '
+ .&mt('Found no analyzable responses in this problem.'
+ .' Currently only Numerical, Formula and String response styles are supported.')
+ .'
'.&mt('List of possible answers').': ');
+ $request->print('
'
+ .''
+ .&mt('List of possible answers')
+ .'
'
+ );
foreach my $part (sort(keys(%allparts))) {
if (defined(@{ $overall{$part.'.answer'} })) {
for (my $i=0;$i
');
+ $request->print(&Apache::loncommon::end_data_table());
}
} else {
- $request->print(' ');
+ $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(''.&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(' ');
+ $request->print(&Apache::loncommon::start_data_table_header_row()
+ .''.&mt('Answer').' '.&mt('Frequency').'
('
- .&mt('click for example').')'.&mt('Answer').' '
+ .''.&mt('Frequency').' '
+ .&Apache::loncommon::end_data_table_header_row()
+ );
foreach my $answer (sort {(split("\0",$a))[0] <=> (split("\0",$b))[0]} (keys(%frequency))) {
- $request->print('
'
+ .'('.&mt('click for example').') ');
+ $request->print(&Apache::loncommon::start_data_table_row()
+ .''.
- join(' ',split("\0",$answer)).
- ' '.$frequency{$answer}.
- ' '
+ .join(' ',split("\0",$answer))
+ .' '
+ .''
+ .''.$frequency{$answer}.''
+ .' '
+ .&Apache::loncommon::end_data_table_row()
+ );
}
- $request->print('
';
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::loncommon::resize_textarea_js().
+ &Apache::structuretags::setmode_javascript().
+ &Apache::lonhtmlcommon::dragmath_js("EditMathPopup");
+
+ # Breadcrumbs
+ my $brcrum = [{'href' => &Apache::loncommon::authorspace(),
+ 'text' => 'Construction Space'},
+ {'href' => '',
+ 'text' => 'Problem Editing'}];
+
my $start_page =
&Apache::loncommon::start_page(&mt("EditXML [_1]",$file),$js,
{'no_auto_mt_title' => 1,
- 'only_body' => 1,
+ 'only_body' => 0,
'add_entries' => {
'onresize' => q[resize_textarea('LC_editxmltext','LC_aftertextarea')],
'onload' => q[resize_textarea('LC_editxmltext','LC_aftertextarea')],
- }});
-
- $result.=$start_page.
- &renderpage($request,$file,['no_output_web'],1).
- &Apache::lonxml::message_location().'
- '.&Apache::loncommon::end_page();
&Apache::lonxml::add_messages(\$result);
@@ -934,7 +1015,7 @@ sub renderpage {
my $filename=(split('/',$file))[-1];
my $error =
" ".&mt('Unable to find [_1]',
- ' '.$filename.'')
+ ''.$filename.'')
."";
$result.=
&Apache::loncommon::simple_error_page($request,'Not available',
@@ -950,7 +1031,7 @@ sub renderpage {
&Apache::lonxml::debug("Should be parsing now");
$result .= &Apache::lonxml::xmlparse($request, $target, $problem,
&setup_vars($target),%mystyle);
- undef($Apache::lonhomework::parsing_a_problem);
+ &finished_parsing();
if (!$output) { $result = ''; }
#$request->print("Result follows:");
if ($target eq 'modified') {
@@ -981,52 +1062,98 @@ sub renderpage {
}
}
-# with no arg it returns a HTML '.
- &Apache::loncommon::helpLatexCheatsheet("Problem_Editor_XML_Index",
- "Problem Editing Help").
- ' '.
- &Apache::loncommon::help_open_menu(undef,undef,5,'Authoring').
- '