--- loncom/interface/lonnavmaps.pm 2005/04/11 12:20:22 1.321 +++ loncom/interface/lonnavmaps.pm 2005/12/29 18:50:35 1.349.2.5 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.321 2005/04/11 12:20:22 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.349.2.5 2005/12/29 18:50:35 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -57,6 +57,7 @@ my %statusIconMap = $resObj->CLOSED => '', $resObj->OPEN => 'navmap.open.gif', $resObj->CORRECT => 'navmap.correct.gif', + $resObj->PARTIALLY_CORRECT => 'navmap.partial.gif', $resObj->INCORRECT => 'navmap.wrong.gif', $resObj->ATTEMPTED => 'navmap.ellipsis.gif', $resObj->ERROR => '' @@ -81,21 +82,28 @@ my %colormap = $resObj->OPEN => '', $resObj->NOTHING_SET => '', $resObj->ATTEMPTED => '', - $resObj->ANSWER_SUBMITTED => '' + $resObj->ANSWER_SUBMITTED => '', + $resObj->PARTIALLY_CORRECT => '#006600' ); # And a special case in the nav map; what to do when the assignment # is not yet done and due in less then 24 hours my $hurryUpColor = "#FF0000"; sub launch_win { - my ($mode,$script,$toplinkitems)=@_; + my ($mode,$script,$toplinkitems,$firsttime)=@_; my $result; if ($script ne 'no') { $result.=' +MENU + } + } + if ($ENV{QUERY_STRING} eq 'turningOffExternal') { + $env{'environment.remotenavmap'}='off'; } # Create the nav map @@ -216,6 +238,7 @@ ENDSUBM $env{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized"; return HTTP_NOT_ACCEPTABLE; } + $r->send_http_header; my $html=&Apache::lonxml::xmlbegin(); $r->print("$html\n"); $r->print("".&mt('Navigate Course Contents').""); @@ -288,7 +311,7 @@ ENDSUBM if ($ENV{QUERY_STRING} eq 'launchExternal') { $r->print(' -
'); $r->print(' @@ -492,22 +515,23 @@ sub getDescription { return &mt("Not currently assigned."); } if ($status == $res->OPEN_LATER) { - return "Open " . timeToHumanString($res->opendate($part)); + return "Open " . timeToHumanString($res->opendate($part),'start'); } if ($status == $res->OPEN) { if ($res->duedate($part)) { - return &mt("Due")." " .timeToHumanString($res->duedate($part)); + return &mt("Due")." " .timeToHumanString($res->duedate($part),'end'); } else { return &mt("Open, no due date"); } } if ($status == $res->PAST_DUE_ANSWER_LATER) { - return &mt("Answer open")." " . timeToHumanString($res->answerdate($part)); + return &mt("Answer open")." " . timeToHumanString($res->answerdate($part),'start'); } if ($status == $res->PAST_DUE_NO_ANSWER) { - return &mt("Was due")." " . timeToHumanString($res->duedate($part)); + return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end'); } - if ($status == $res->ANSWER_OPEN) { + if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) + && $res->handgrade($part) ne 'yes') { return &mt("Answer available"); } if ($status == $res->EXCUSED) { @@ -527,7 +551,7 @@ sub getDescription { } } if ($res->duedate($part)) { - return &mt("Due")." " . timeToHumanString($res->duedate($part)) . + return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') . " $triesString"; } else { return &mt("No due date")." $triesString"; @@ -580,8 +604,11 @@ sub advancedUser { # print "Answer available $timestring" # Very, very, very, VERY English-only... goodness help a localizer on # this func... + + sub timeToHumanString { - my ($time) = @_; + my ($time,$type,$format) = @_; + # zero, '0' and blank are bad times if (!$time) { return &mt('never'); @@ -652,30 +679,44 @@ sub timeToHumanString { return "$prefix$hourString$minuteString$tense"; } + # If there's a caller supplied format, use it. + + if($format ne '') { + my $timeStr = strftime($format, localtime($time)); + return $timeStr.&Apache::lonlocal::gettimezone(); + } + # Less then 5 days away, display day of the week and # HH:MM + if ( $delta < $day * 5 ) { my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time)); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; - return ($inPast ? "last " : "next ") . - $timeStr; + return ($inPast ? "last " : "this ") . + $timeStr.&Apache::lonlocal::gettimezone(); } + my $conjunction='on'; + if ($type eq 'start') { + $conjunction='at'; + } elsif ($type eq 'end') { + $conjunction='by'; + } # Is it this year? if ( $time[5] == $now[5]) { # Return on Month Day, HH:MM meridian - my $timeStr = strftime("on %A, %b %e at %I:%M %P", localtime($time)); + my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time)); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; - return $timeStr; + return $timeStr.&Apache::lonlocal::gettimezone(); } # Not this year, so show the year - my $timeStr = strftime("on %A, %b %e %Y at %I:%M %P", localtime($time)); + my $timeStr = strftime("$conjunction %A, %b %e %Y at %I:%M %P", localtime($time)); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; - return $timeStr; + return $timeStr.&Apache::lonlocal::gettimezone(); } } @@ -1025,7 +1066,6 @@ sub render_resource { # it will be quoted with ' in the href. my ($left,$right) = split(/\?/, $link); - $left =~ s/'/\\'/g; $link = $left.'?'.$right; my $src = $resource->src(); @@ -1039,28 +1079,28 @@ sub render_resource { my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons"); # If this is a new branch, label it so if ($params->{'isNewBranch'}) { - $newBranchText = ""; + $newBranchText = "Branch"; } # links to open and close the folder - my $linkopen = ""; + my $linkopen = ""; my $linkclose = ""; # Default icon: unknown page - my $icon = ""; + my $icon = ""; if ($resource->is_problem()) { if ($part eq '0' || $params->{'condensed'}) { - $icon =''; + $icon ='  '; } else { $icon = $params->{'indentString'}; } } else { - $icon = ""; + $icon = "  "; } # Display the correct map icon to open or shut map @@ -1075,9 +1115,10 @@ sub render_resource { if (!$params->{'resource_no_folder_link'}) { $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; - $icon = ""; + $icon = "".
+		($nowOpen ? "; - $linkopen = "{'url'} . '?' . $params->{'queryString'} . '&filter='; $linkopen .= ($nowOpen xor $it->{CONDITION}) ? addToFilter($filter, $mapId) : @@ -1087,13 +1128,14 @@ sub render_resource { &Apache::lonnet::escape($params->{'here'}) . '&jump=' . &Apache::lonnet::escape($resource->symb()) . - "&folderManip=1'>"; + "&folderManip=1\">"; } else { # Don't allow users to manipulate folder $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.nomanip.gif'; - $icon = ""; + $icon = "".
+		($nowOpen ? "; $linkopen = ""; $linkclose = ""; @@ -1103,6 +1145,9 @@ sub render_resource { if ($resource->randomout()) { $nonLinkedText .= ' (hidden) '; } + if (!$resource->condval()) { + $nonLinkedText .= ' (conditionally hidden) '; + } # We're done preparing and finally ready to start the rendering my $result = ""; @@ -1125,7 +1170,7 @@ sub render_resource { # Is this the current resource? if (!$params->{'displayedHereMarker'} && $resource->symb() eq $params->{'here'} ) { - $curMarkerBegin = '> '; + $curMarkerBegin = '>'; $curMarkerEnd = '<'; $params->{'displayedHereMarker'} = 1; } @@ -1147,7 +1192,7 @@ sub render_resource { $target=' target="loncapaclient" '; } if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) { - $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; + $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } else { $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } @@ -1160,7 +1205,11 @@ sub render_communication_status { my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = ""; my $link = $params->{"resourceLink"}; - my $linkopen = ""; + my $target; + if ($env{'environment.remotenavmap'} eq 'on') { + $target=' target="loncapaclient" '; + } + my $linkopen = ""; my $linkclose = ""; my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($resource->hasDiscussion()) { @@ -1173,7 +1222,7 @@ sub render_communication_status { my $feedback = $resource->getFeedback(); foreach (split(/\,/, $feedback)) { if ($_) { - $feedbackHTML .= ' ' . ''; @@ -1188,7 +1237,7 @@ sub render_communication_status { last if ($errorcount>=10); # Only output 10 bombs maximum if ($_) { $errorcount++; - $errorHTML .= ' ' . ''; @@ -1210,7 +1259,11 @@ sub render_quick_status { $params->{'multipart'} && $part eq "0"; my $link = $params->{"resourceLink"}; - my $linkopen = ""; + my $target; + if ($env{'environment.remotenavmap'} eq 'on') { + $target=' target="loncapaclient" '; + } + my $linkopen = ""; my $linkclose = ""; if ($resource->is_problem() && @@ -1369,7 +1422,6 @@ sub render { # no columns, no nav maps. return ''; } - my $mustCloseNavMap = 0; my $navmap; if (defined($args->{'navmap'})) { $navmap = $args->{'navmap'}; @@ -1418,22 +1470,27 @@ sub render { # Step 1: Check to see if we have a navmap if (!defined($navmap)) { $navmap = Apache::lonnavmaps::navmap->new(); - $mustCloseNavMap = 1; - } + if (!defined($navmap)) { + # no londer in course + return 'No course selected
+ Select a course
'; + } + } # Step two: Locate what kind of here marker is necessary # Determine where the "here" marker is and where the screen jumps to. - if ($env{'form.postsymb'}) { + if ($env{'form.postsymb'} ne '') { $here = $jump = &Apache::lonnet::symbclean($env{'form.postsymb'}); - } elsif ($env{'form.postdata'}) { + } elsif ($env{'form.postdata'} ne '') { # couldn't find a symb, is there a URL? my $currenturl = $env{'form.postdata'}; #$currenturl=~s/^http\:\/\///; #$currenturl=~s/^[^\/]+//; $here = $jump = &Apache::lonnet::symbread($currenturl); - } else { + } + if ($here eq '') { my $last; if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', &GDBM_READER(),0640)) { @@ -1482,7 +1539,6 @@ sub render { # Step 1: Check to see if we have a navmap if (!defined($navmap)) { $navmap = Apache::lonnavmaps::navmap->new(); - $mustCloseNavMap = 1; } # See if we're being passed a specific map @@ -1588,11 +1644,7 @@ END my @allres=$navmap->retrieveResources(); foreach my $resource (@allres) { if ($resource->hasDiscussion()) { - my $ressymb = $resource->symb(); - if ($ressymb =~ m-___adm/\w+/\w+/\d+/bulletinboard$-) { - $ressymb = $resource->wrap_symb(); - } - $haveDisc .= $ressymb.':'; + $haveDisc .= $resource->wrap_symb().':'; $totdisc ++; } } @@ -1654,7 +1706,7 @@ END $args->{'condensed'} = 0; my $location= &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif"); - $args->{'indentString'} = setDefault($args->{'indentString'}, ""); + $args->{'indentString'} = setDefault($args->{'indentString'}, "  "); $args->{'displayedHereMarker'} = 0; # If we're suppressing empty sequences, look for them here. Use DFS for speed, @@ -1807,10 +1859,6 @@ END $args->{'multipart'} = $curRes->multipart(); if ($condenseParts) { # do the condensation - if (!$curRes->opendate("0")) { - @parts = (); - $args->{'condensed'} = 1; - } if (!$args->{'condensed'}) { # Decide whether to condense based on similarity my $status = $curRes->status($parts[0]); @@ -2116,54 +2164,34 @@ sub generate_course_user_opt { my $uname=$env{'user.name'}; my $udom=$env{'user.domain'}; - my $uhome=$env{'user.home'}; my $cid=$env{'request.course.id'}; - my $chome=$env{'course.'.$cid.'.home'}; - my ($cdom,$cnum)=split(/\_/,$cid); + my $cdom=$env{'course.'.$cid.'.domain'}; + my $cnum=$env{'course.'.$cid.'.num'}; - my $userprefix=$uname.'_'.$udom.'_'; - - my %courserdatas; my %useropt; my %courseopt; my %userrdatas; - unless ($uhome eq 'no_host') { # ------------------------------------------------- Get coursedata (if present) - unless ((time-$courserdatas{$cid.'.last_cache'})<240) { - my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum. - ':resourcedata',$chome); - # Check for network failure - if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) { - $self->{NETWORK_FAILURE} = 1; - } elsif ($reply!~/^error\:/) { - $courserdatas{$cid}=$reply; - $courserdatas{$cid.'.last_cache'}=time; - } - } - foreach (split(/\&/,$courserdatas{$cid})) { - my ($name,$value)=split(/\=/,$_); - $courseopt{$userprefix.&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); + my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom); + # Check for network failure + if (!ref($courseopt)) { + if ( $courseopt =~ /no.such.host/i || $courseopt =~ /con_lost/i) { + $self->{NETWORK_FAILURE} = 1; } + undef($courseopt); + } + # --------------------------------------------------- Get userdata (if present) - unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { - my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); - if ($reply!~/^error\:/) { - $userrdatas{$uname.'___'.$udom}=$reply; - $userrdatas{$uname.'___'.$udom.'.last_cache'}=time; - } - # check to see if network failed - elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i ) - { - $self->{NETWORK_FAILURE} = 1; - } - } - foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) { - my ($name,$value)=split(/\=/,$_); - $useropt{$userprefix.&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); + + my $useropt=&Apache::lonnet::get_userresdata($uname,$udom); + # Check for network failure + if (!ref($useropt)) { + if ( $useropt =~ /no.such.host/i || $useropt =~ /con_lost/i) { + $self->{NETWORK_FAILURE} = 1; } - $self->{COURSE_OPT} = \%courseopt; - $self->{USER_OPT} = \%useropt; + undef($useropt); } + $self->{COURSE_OPT} = $courseopt; + $self->{USER_OPT} = $useropt; + $self->{COURSE_USER_OPT_GENERATED} = 1; return; @@ -2175,7 +2203,8 @@ sub generate_email_discuss_status { if ($self->{EMAIL_DISCUSS_GENERATED}) { return; } my $cid=$env{'request.course.id'}; - my ($cdom,$cnum)=split(/\_/,$cid); + my $cdom=$env{'course.'.$cid.'.domain'}; + my $cnum=$env{'course.'.$cid.'.num'}; my %emailstatus = &Apache::lonnet::dump('email_status'); my $logoutTime = $emailstatus{'logout'}; @@ -2195,19 +2224,16 @@ sub generate_email_discuss_status { my %feedback=(); my %error=(); - my $keys = &Apache::lonnet::reply('keys:'. - $env{'user.domain'}.':'. - $env{'user.name'}.':nohist_email', - $env{'user.home'}); + my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'}, + $env{'user.name'}); - foreach my $msgid (split(/\&/, $keys)) { - $msgid=&Apache::lonnet::unescape($msgid); + foreach my $msgid (@keys) { if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) { my $plain= &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); - if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { - my ($what,$url)=($1,$2); - if ($what eq 'Error') { + if ($plain=~/ \[([^\]]+)\]\:/) { + my $url=$1; + if ($plain=~/\:Error \[/) { $error{$url}.=','.$msgid; } else { $feedback{$url}.=','.$msgid; @@ -2240,6 +2266,25 @@ sub get_user_data { $self->{RETRIEVED_USER_DATA} = 1; } +sub get_discussion_data { + my $self = shift; + if ($self->{RETRIEVED_DISCUSSION_DATA}) { + return $self->{DISCUSSION_DATA}; + } + + my $cid=$env{'request.course.id'}; + my $cdom=$env{'course.'.$cid.'.domain'}; + my $cnum=$env{'course.'.$cid.'.num'}; + + # Retrieve discussion data for resources in course + my %discussion_data = &Apache::lonnet::dump($cid,$cdom,$cnum); + + $self->{DISCUSSION_DATA} = \%discussion_data; + $self->{RETRIEVED_DISCUSSION_DATA} = 1; + return $self->{DISCUSSION_DATA}; +} + + # Internal function: Takes a key to look up in the nav hash and implements internal # memory caching of that key. sub navhash { @@ -2285,11 +2330,8 @@ sub hasDiscussion { #return defined($self->{DISCUSSION_TIME}->{$symb}); -# backward compatibility (bulletin boards used to be 'wrapped') - my $ressymb = $symb; - if ($ressymb =~ m-___adm/\w+/\w+/\d+/bulletinboard$-) { - $ressymb = $self->wrap_symb($ressymb); - } + # backward compatibility (bulletin boards used to be 'wrapped') + my $ressymb = $self->wrap_symb($symb); if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) { return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb}; } else { @@ -2298,22 +2340,21 @@ sub hasDiscussion { } } -# Private method: Does the given resource (as a symb string) have -# current feedback? Returns the string in the feedback hash, which -# will be false if it does not exist. - sub wrap_symb { - my $self=shift; + my $self = shift; my $symb = shift; - my $ressymb = $symb; - if ($ressymb =~ m-___(adm/\w+/\w+/)(\d+)(/bulletinboard)$-) { - unless ($ressymb =~ m|adm/wrapper/adm|) { - $ressymb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3; + if ($symb =~ m-___(adm/\w+/\w+/)(\d+)(/bulletinboard)$-) { + unless ($symb =~ m|adm/wrapper/adm|) { + $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3; } } - return $ressymb; + return $symb; } +# Private method: Does the given resource (as a symb string) have +# current feedback? Returns the string in the feedback hash, which +# will be false if it does not exist. + sub getFeedback { my $self = shift; my $symb = shift; @@ -2469,7 +2510,7 @@ sub parmval_real { my $symbparm=$symb.'.'.$what; my $mapparm=$mapname.'___(all).'.$what; - my $usercourseprefix=$uname.'_'.$udom.'_'.$cid; + my $usercourseprefix=$cid; my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what; my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm; @@ -2541,14 +2582,17 @@ sub parmval_real { =pod -=item * B(url): +=item * B(url,multiple): -Retrieves a resource object by URL of the resource. If passed a -resource object, it will simply return it, so it is safe to use this -method in code like "$res = $navmap->getResourceByUrl($res)", if -you're not sure if $res is already an object, or just a URL. If the -resource appears multiple times in the course, only the first instance -will be returned. As a result, this is probably useful only for maps. +Retrieves a resource object by URL of the resource, unless the optional +multiple parameter is included in wahich caes an array of resource +objects is returned. If passed a resource object, it will simply return +it, so it is safe to use this method in code like +"$res = $navmap->getResourceByUrl($res)" +if you're not sure if $res is already an object, or just a URL. If the +resource appears multiple times in the course, only the first instance +will be returned (useful for maps), unless the multiple parameter has +been included, in which case all instances are returned in an array. =item * B(map, filterFunc, recursive, bailout, showall): @@ -2583,22 +2627,41 @@ Convience method for which will tell whether the map has resources matching the description in the filter function. +=item * B(url): + +Retrieves version infomation for a url. Returns the version (a number, or +the string "mostrecent") for resources which have version information in +the big hash. + =cut sub getResourceByUrl { my $self = shift; my $resUrl = shift; + my $multiple = shift; if (ref($resUrl)) { return $resUrl; } $resUrl = &Apache::lonnet::clutter($resUrl); my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl}; - if ($resId =~ /,/) { - $resId = (split (/,/, $resId))[0]; - } if (!$resId) { return ''; } - return $self->getById($resId); + if ($multiple) { + my @resources = (); + my @resIds = split (/,/, $resId); + foreach my $id (@resIds) { + my $resourceId = $self->getById($id); + if ($resourceId) { + push(@resources,$resourceId); + } + } + return @resources; + } else { + if ($resId =~ /,/) { + $resId = (split (/,/, $resId))[0]; + } + return $self->getById($resId); + } } sub retrieveResources { @@ -2666,6 +2729,12 @@ sub hasResource { return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0; } +sub usedVersion { + my $self = shift; + my $linkurl = shift; + return $self->navhash("version_$linkurl"); +} + 1; package Apache::lonnavmaps::iterator; @@ -3530,6 +3599,15 @@ sub shown_symb { if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());} return $self->symb(); } +sub id { + my $self=shift; + return $self->{ID}; +} +sub enclosing_map_src { + my $self=shift; + (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/; + return $self->navHash('map_id_'.$first); +} sub symb { my $self=shift; (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/; @@ -3561,7 +3639,19 @@ sub condition { my $condition=&Apache::lonnet::directcondval($condid); return $condition; } - +sub condval { + my $self=shift; + my $uri=&Apache::lonnet::deversion(&Apache::lonnet::declutter($self->src())); + my ($pathname,$filename)=($uri=~m|(.*)/([^/]*)|); + $pathname=~s/^adm\/wrapper\///; + + my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~ + /\&\Q$filename\E\:([\d\|]+)\&/); + if ($match) { + return &Apache::lonnet::condval($1); + } + return 0; +} sub compTitle { my $self = shift; my $title = $self->title(); @@ -3627,7 +3717,7 @@ sub is_page { sub is_problem { my $self=shift; my $src = $self->src(); - return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/) + return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) } sub contains_problem { my $self=shift; @@ -3843,6 +3933,10 @@ sub duedate { } return $self->parmval("duedate", $part); } +sub handgrade { + (my $self, my $part) = @_; + return $self->parmval("handgrade", $part); +} sub maxtries { (my $self, my $part) = @_; return $self->parmval("maxtries", $part); @@ -4127,7 +4221,7 @@ sub extractParts { return; } foreach (split(/\,/,$metadata)) { - if ($_ =~ /^part_(.*)$/) { + if ($_ =~ /^(?:part|Task)_(.*)$/) { my $part = $1; # This floods the logs if it blows up if (defined($parts{$part})) { @@ -4392,14 +4486,17 @@ sub ATTEMPTED { return 16; } sub getCompletionStatus { my $self = shift; + my $part = shift; return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); - my $status = $self->queryRestoreHash('solved', shift); + my $status = $self->queryRestoreHash('solved', $part); # Left as separate if statements in case we ever do more with this if ($status eq 'correct_by_student') {return $self->CORRECT;} if ($status eq 'correct_by_scantron') {return $self->CORRECT;} - if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; } + if ($status eq 'correct_by_override') { + return $self->CORRECT_BY_OVERRIDE; + } if ($status eq 'incorrect_attempted') {return $self->INCORRECT; } if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; } if ($status eq 'excused') {return $self->EXCUSED; } @@ -4503,6 +4600,7 @@ An answer has been submitted, but the st sub TRIES_LEFT { return 20; } sub ANSWER_SUBMITTED { return 21; } +sub PARTIALLY_CORRECT{ return 22; } sub status { my $self = shift; @@ -4521,14 +4619,29 @@ sub status { my $suppressFeedback = $self->problemstatus($part) eq 'no'; # If there's an answer date and we're past it, don't # suppress the feedback; student should know - if ($self->answerdate($part) && $self->answerdate($part) < time()) { + if ($self->duedate($part) && $self->duedate($part) < time() && + $self->answerdate($part) && $self->answerdate($part) < time()) { $suppressFeedback = 0; } # There are a few whole rows we can dispose of: if ($completionStatus == CORRECT || $completionStatus == CORRECT_BY_OVERRIDE ) { - return $suppressFeedback? ANSWER_SUBMITTED : CORRECT; + if ( $suppressFeedback ) { return ANSWER_SUBMITTED } + my $awarded=$self->awarded($part); + if ($awarded < 1 && $awarded > 0) { + return PARTIALLY_CORRECT; + } elsif ($awarded<1) { + return INCORRECT; + } + return CORRECT; + } + + # If it's WRONG... and not open + if ( ($completionStatus == INCORRECT || + $completionStatus == INCORRECT_BY_OVERRIDE) + && (!$self->opendate($part) || $self->opendate($part) > time()) ) { + return INCORRECT; } if ($completionStatus == ATTEMPTED) { @@ -4615,6 +4728,7 @@ my %compositeToSimple = NETWORK_FAILURE() => ERROR, NOTHING_SET() => CLOSED, CORRECT() => CORRECT, + PARTIALLY_CORRECT() => PARTIALLY_CORRECT, EXCUSED() => CORRECT, PAST_DUE_NO_ANSWER() => INCORRECT, PAST_DUE_ANSWER_LATER() => INCORRECT, @@ -4735,7 +4849,7 @@ sub getNext { my $to = $self->to(); foreach my $branch ( split(/,/, $to) ) { my $choice = $self->{NAV_MAP}->getById($branch); - if (!$choice->condition()) { next; } + #if (!$choice->condition()) { next; } my $next = $choice->goesto(); $next = $self->{NAV_MAP}->getById($next);