--- loncom/interface/lonnavmaps.pm 2007/05/23 20:46:29 1.399 +++ loncom/interface/lonnavmaps.pm 2009/02/25 15:25:21 1.428 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.399 2007/05/23 20:46:29 albertel Exp $ +# $Id: lonnavmaps.pm,v 1.428 2009/02/25 15:25:21 droeschl Exp $ # # Copyright Michigan State University Board of Trustees # @@ -27,389 +27,25 @@ # ### -package Apache::lonnavmaps; - -use strict; -use GDBM_File; -use Apache::loncommon(); -use Apache::lonenc(); -use Apache::lonlocal; -use Apache::lonnet; -use POSIX qw (floor strftime); -use Data::Dumper; # for debugging, not always -use Time::HiRes qw( gettimeofday tv_interval ); -use LONCAPA; - -# symbolic constants -sub SYMB { return 1; } -sub URL { return 2; } -sub NOTHING { return 3; } - -# Some data - -my $resObj = "Apache::lonnavmaps::resource"; - -# Keep these mappings in sync with lonquickgrades, which uses the colors -# instead of the icons. -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 => '' - ); - -my %iconAltTags = - ( 'navmap.correct.gif' => 'Correct', - 'navmap.wrong.gif' => 'Incorrect', - 'navmap.open.gif' => 'Open' ); - -# Defines a status->color mapping, null string means don't color -my %colormap = - ( $resObj->NETWORK_FAILURE => '', - $resObj->CORRECT => '', - $resObj->EXCUSED => '#3333FF', - $resObj->PAST_DUE_ANSWER_LATER => '', - $resObj->PAST_DUE_NO_ANSWER => '', - $resObj->ANSWER_OPEN => '#006600', - $resObj->OPEN_LATER => '', - $resObj->TRIES_LEFT => '', - $resObj->INCORRECT => '', - $resObj->OPEN => '', - $resObj->NOTHING_SET => '', - $resObj->ATTEMPTED => '', - $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 than 24 hours -my $hurryUpColor = "#FF0000"; - -sub close { - if ($env{'environment.remotenavmap'} ne 'on') { return ''; } - return(< -window.status='Accessing Nav Control'; -menu=window.open("/adm/rat/empty.html","loncapanav", - "height=600,width=400,scrollbars=1"); -window.status='Closing Nav Control'; -menu.close(); -window.status='Done.'; - -ENDCLOSE -} - -sub update { - if ($env{'environment.remotenavmap'} ne 'on') { return ''; } - if (!$env{'request.course.id'}) { return ''; } - if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; } - return(< - -ENDUPDATE -} - -# Convenience functions: Returns a string that adds or subtracts -# the second argument from the first hash, appropriate for the -# query string that determines which folders to recurse on -sub addToFilter { - my $hashIn = shift; - my $addition = shift; - my %hash = %$hashIn; - $hash{$addition} = 1; - - return join (",", keys(%hash)); -} - -sub removeFromFilter { - my $hashIn = shift; - my $subtraction = shift; - my %hash = %$hashIn; - - delete $hash{$subtraction}; - return join(",", keys(%hash)); -} - -# Convenience function: Given a stack returned from getStack on the iterator, -# return the correct src() value. -sub getLinkForResource { - my $stack = shift; - my $res; - - # Check to see if there are any pages in the stack - foreach $res (@$stack) { - if (defined($res)) { - my $anchor; - if ($res->is_page()) { - foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } } - $anchor=&escape($anchor->shown_symb()); - return ($res->link(),$res->shown_symb(),$anchor); - } - # in case folder was skipped over as "only sequence" - my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb()); - if ($map=~/\.page$/) { - my $url=&Apache::lonnet::clutter($map); - $anchor=&escape($src->shown_symb()); - return ($url,$res->shown_symb(),$anchor); - } - } - } - - # Failing that, return the src of the last resource that is defined - # (when we first recurse on a map, it puts an undefined resource - # on the bottom because $self->{HERE} isn't defined yet, and we - # want the src for the map anyhow) - foreach my $item (@$stack) { - if (defined($item)) { $res = $item; } - } - - return ($res->link(),$res->shown_symb()); -} - -# Convenience function: This separates the logic of how to create -# the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned", -# etc.) into a separate function. It takes a resource object as the -# first parameter, and the part number of the resource as the second. -# It's basically a big switch statement on the status of the resource. - -sub getDescription { - my $res = shift; - my $part = shift; - my $status = $res->status($part); - - if ($status == $res->NETWORK_FAILURE) { - return &mt("Having technical difficulties; please check status later"); - } - if ($status == $res->NOTHING_SET) { - return &mt("Not currently assigned."); - } - if ($status == $res->OPEN_LATER) { - return "Open " . timeToHumanString($res->opendate($part),'start'); - } - if ($status == $res->OPEN) { - if ($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),'start'); - } - if ($status == $res->PAST_DUE_NO_ANSWER) { - return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end'); - } - if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) - && $res->handgrade($part) ne 'yes') { - return &mt("Answer available"); - } - if ($status == $res->EXCUSED) { - return &mt("Excused by instructor"); - } - if ($status == $res->ATTEMPTED) { - return &mt("Answer submitted, not yet graded"); - } - if ($status == $res->TRIES_LEFT) { - my $tries = $res->tries($part); - my $maxtries = $res->maxtries($part); - my $triesString = ""; - if ($tries && $maxtries) { - $triesString = "($tries of $maxtries tries used)"; - if ($maxtries > 1 && $maxtries - $tries == 1) { - $triesString = "$triesString"; - } - } - if ($res->duedate($part)) { - return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') . - " $triesString"; - } else { - return &mt("No due date")." $triesString"; - } - } - if ($status == $res->ANSWER_SUBMITTED) { - return &mt('Answer submitted'); - } -} - -# Convenience function, so others can use it: Is the problem due in less than -# 24 hours, and still can be done? - -sub dueInLessThan24Hours { - my $res = shift; - my $part = shift; - my $status = $res->status($part); - - return ($status == $res->OPEN() || - $status == $res->TRIES_LEFT()) && - $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) && - $res->duedate($part) > time(); -} - -# Convenience function, so others can use it: Is there only one try remaining for the -# part, with more than one try to begin with, not due yet and still can be done? -sub lastTry { - my $res = shift; - my $part = shift; - - my $tries = $res->tries($part); - my $maxtries = $res->maxtries($part); - return $tries && $maxtries && $maxtries > 1 && - $maxtries - $tries == 1 && $res->duedate($part) && - $res->duedate($part) > time(); -} - -# This puts a human-readable name on the env variable. - -sub advancedUser { - return $env{'request.role.adv'}; -} - - -# timeToHumanString takes a time number and converts it to a -# human-readable representation, meant to be used in the following -# manner: -# print "Due $timestring" -# print "Open $timestring" -# print "Answer available $timestring" -# Very, very, very, VERY English-only... goodness help a localizer on -# this func... - - -sub timeToHumanString { - my ($time,$type,$format) = @_; - - # zero, '0' and blank are bad times - if (!$time) { - return &mt('never'); - } - unless (&Apache::lonlocal::current_language()=~/^en/) { - return &Apache::lonlocal::locallocaltime($time); - } - my $now = time(); - - my @time = localtime($time); - my @now = localtime($now); - - # Positive = future - my $delta = $time - $now; - - my $minute = 60; - my $hour = 60 * $minute; - my $day = 24 * $hour; - my $week = 7 * $day; - my $inPast = 0; - - # Logic in comments: - # Is it now? (extremely unlikely) - if ( $delta == 0 ) { - return "this instant"; - } - - if ($delta < 0) { - $inPast = 1; - $delta = -$delta; - } - - if ( $delta > 0 ) { - - my $tense = $inPast ? " ago" : ""; - my $prefix = $inPast ? "" : "in "; - - # Less than a minute - if ( $delta < $minute ) { - if ($delta == 1) { return "${prefix}1 second$tense"; } - return "$prefix$delta seconds$tense"; - } - - # Less than an hour - if ( $delta < $hour ) { - # If so, use minutes - my $minutes = floor($delta / 60); - if ($minutes == 1) { return "${prefix}1 minute$tense"; } - return "$prefix$minutes minutes$tense"; - } - - # Is it less than 24 hours away? If so, - # display hours + minutes - if ( $delta < $hour * 24) { - my $hours = floor($delta / $hour); - my $minutes = floor(($delta % $hour) / $minute); - my $hourString = "$hours hours"; - my $minuteString = ", $minutes minutes"; - if ($hours == 1) { - $hourString = "1 hour"; - } - if ($minutes == 1) { - $minuteString = ", 1 minute"; - } - if ($minutes == 0) { - $minuteString = ""; - } - 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($time); - } - - # Less than 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 " : "this ") . - $timeStr.&Apache::lonlocal::gettimezone($time); - } - - 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("$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.&Apache::lonlocal::gettimezone($time); - } - - # Not this year, so show the year - 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.&Apache::lonlocal::gettimezone($time); - } -} - - =pod =head1 NAME -Apache::lonnavmap - Subroutines to handle and render the navigation - maps +Apache::lonnavmaps.pm =head1 SYNOPSIS +Handles navigational maps. + The main handler generates the navigational listing for the course, the other objects export this information in a usable fashion for other modules. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org. + + =head1 OVERVIEW X When a user enters a course, LON-CAPA examines the @@ -604,6 +240,11 @@ instruct the renderer to render only a p the source of the map you want to process, like '/res/103/jerf/navmap.course.sequence'. +=item * B: default: false + +If you need to include the top level map (meaning the course) in the +rendered output set this to true + =item * B: default: constructs one from %env A reference to a navmap, used only if an iterator is not passed in. If @@ -724,6 +365,475 @@ navmaps screen) uses this to display the =cut + +=head1 SUBROUTINES + +=over + +=item update() + +=item addToFilter() + +Convenience functions: Returns a string that adds or subtracts +the second argument from the first hash, appropriate for the +query string that determines which folders to recurse on + +=item removeFromFilter() + +=item getLinkForResource() + +Convenience function: Given a stack returned from getStack on the iterator, +return the correct src() value. + +=item getDescription() + +Convenience function: This separates the logic of how to create +the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned", +etc.) into a separate function. It takes a resource object as the +first parameter, and the part number of the resource as the second. +It's basically a big switch statement on the status of the resource. + +=item dueInLessThan24Hours() + +Convenience function, so others can use it: Is the problem due in less than 24 hours, and still can be done? + +=item lastTry() + +Convenience function, so others can use it: Is there only one try remaining for the +part, with more than one try to begin with, not due yet and still can be done? + +=item advancedUser() + +This puts a human-readable name on the env variable. + +=item timeToHumanString() + +timeToHumanString takes a time number and converts it to a +human-readable representation, meant to be used in the following +manner: + +=over 4 + +=item * print "Due $timestring" + +=item * print "Open $timestring" + +=item * print "Answer available $timestring" + +=back + +Very, very, very, VERY English-only... goodness help a localizer on +this func... + +=item resource() + +returns 0 + +=item communication_status() + +returns 1 + +=item quick_status() + +returns 2 + +=item long_status() + +returns 3 + +=item part_status_summary() + +returns 4 + +=item render_resource() + +=item render_communication_status() + +=item render_quick_status() + +=item render_long_status() + +=item render_parts_summary_status() + +=item setDefault() + +=item cmp_title() + +=item render() + +=item add_linkitem() + +=item show_linkitems() + +=back + +=cut + +package Apache::lonnavmaps; + +use strict; +use GDBM_File; +use Apache::loncommon(); +use Apache::lonenc(); +use Apache::lonlocal; +use Apache::lonnet; +use POSIX qw (floor strftime); +use Time::HiRes qw( gettimeofday tv_interval ); +use LONCAPA; +use DateTime(); + +# symbolic constants +sub SYMB { return 1; } +sub URL { return 2; } +sub NOTHING { return 3; } + +# Some data + +my $resObj = "Apache::lonnavmaps::resource"; + +# Keep these mappings in sync with lonquickgrades, which uses the colors +# instead of the icons. +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 => '' + ); + +my %iconAltTags = + ( 'navmap.correct.gif' => 'Correct', + 'navmap.wrong.gif' => 'Incorrect', + 'navmap.open.gif' => 'Open' ); + +# Defines a status->color mapping, null string means don't color +my %colormap = + ( $resObj->NETWORK_FAILURE => '', + $resObj->CORRECT => '', + $resObj->EXCUSED => '#3333FF', + $resObj->PAST_DUE_ANSWER_LATER => '', + $resObj->PAST_DUE_NO_ANSWER => '', + $resObj->ANSWER_OPEN => '#006600', + $resObj->OPEN_LATER => '', + $resObj->TRIES_LEFT => '', + $resObj->INCORRECT => '', + $resObj->OPEN => '', + $resObj->NOTHING_SET => '', + $resObj->ATTEMPTED => '', + $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 than 24 hours +my $hurryUpColor = "#FF0000"; + +sub close { + if ($env{'environment.remotenavmap'} ne 'on') { return ''; } + return(< +window.status='Accessing Nav Control'; +menu=window.open("/adm/rat/empty.html","loncapanav", + "height=600,width=400,scrollbars=1"); +window.status='Closing Nav Control'; +menu.close(); +window.status='Done.'; + +ENDCLOSE +} + +sub update { + if ($env{'environment.remotenavmap'} ne 'on') { return ''; } + if (!$env{'request.course.id'}) { return ''; } + if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; } + return(< + +ENDUPDATE +} + + +sub addToFilter { + my $hashIn = shift; + my $addition = shift; + my %hash = %$hashIn; + $hash{$addition} = 1; + + return join (",", keys(%hash)); +} + +sub removeFromFilter { + my $hashIn = shift; + my $subtraction = shift; + my %hash = %$hashIn; + + delete $hash{$subtraction}; + return join(",", keys(%hash)); +} + +sub getLinkForResource { + my $stack = shift; + my $res; + + # Check to see if there are any pages in the stack + foreach $res (@$stack) { + if (defined($res)) { + my $anchor; + if ($res->is_page()) { + foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } } + $anchor=&escape($anchor->shown_symb()); + return ($res->link(),$res->shown_symb(),$anchor); + } + # in case folder was skipped over as "only sequence" + my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb()); + if ($map=~/\.page$/) { + my $url=&Apache::lonnet::clutter($map); + $anchor=&escape($src->shown_symb()); + return ($url,$res->shown_symb(),$anchor); + } + } + } + + # Failing that, return the src of the last resource that is defined + # (when we first recurse on a map, it puts an undefined resource + # on the bottom because $self->{HERE} isn't defined yet, and we + # want the src for the map anyhow) + foreach my $item (@$stack) { + if (defined($item)) { $res = $item; } + } + + if ($res) { + return ($res->link(),$res->shown_symb()); + } + return; +} + + + +sub getDescription { + my $res = shift; + my $part = shift; + my $status = $res->status($part); + + my $open = $res->opendate($part); + my $due = $res->duedate($part); + my $answer = $res->answerdate($part); + + if ($status == $res->NETWORK_FAILURE) { + return &mt("Having technical difficulties; please check status later"); + } + if ($status == $res->NOTHING_SET) { + return &mt("Not currently assigned."); + } + if ($status == $res->OPEN_LATER) { + return &mt("Open ") .timeToHumanString($open,'start'); + } + if ($status == $res->OPEN) { + if ($due) { + if ($res->is_practice()) { + return &mt("Closes ")." " .timeToHumanString($due,'start'); + } else { + return &mt("Due")." " .timeToHumanString($due,'end'); + } + } else { + return &mt("Open, no due date"); + } + } + if ($status == $res->PAST_DUE_ANSWER_LATER) { + return &mt("Answer open")." " .timeToHumanString($answer,'start'); + } + if ($status == $res->PAST_DUE_NO_ANSWER) { + if ($res->is_practice()) { + return &mt("Closed")." " . timeToHumanString($due,'start'); + } else { + return &mt("Was due")." " . timeToHumanString($due,'end'); + } + } + if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) + && $res->handgrade($part) ne 'yes') { + return &mt("Answer available"); + } + if ($status == $res->EXCUSED) { + return &mt("Excused by instructor"); + } + if ($status == $res->ATTEMPTED) { + return &mt("Answer submitted, not yet graded"); + } + if ($status == $res->TRIES_LEFT) { + my $tries = $res->tries($part); + my $maxtries = $res->maxtries($part); + my $triesString = ""; + if ($tries && $maxtries) { + $triesString = '('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')'; + if ($maxtries > 1 && $maxtries - $tries == 1) { + $triesString = "$triesString"; + } + } + if ($due) { + return &mt("Due")." " . timeToHumanString($due,'end') . + " $triesString"; + } else { + return &mt("No due date")." $triesString"; + } + } + if ($status == $res->ANSWER_SUBMITTED) { + return &mt('Answer submitted'); + } +} + + +sub dueInLessThan24Hours { + my $res = shift; + my $part = shift; + my $status = $res->status($part); + + return ($status == $res->OPEN() || + $status == $res->TRIES_LEFT()) && + $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) && + $res->duedate($part) > time(); +} + + +sub lastTry { + my $res = shift; + my $part = shift; + + my $tries = $res->tries($part); + my $maxtries = $res->maxtries($part); + return $tries && $maxtries && $maxtries > 1 && + $maxtries - $tries == 1 && $res->duedate($part) && + $res->duedate($part) > time(); +} + + +sub advancedUser { + return $env{'request.role.adv'}; +} + +sub timeToHumanString { + my ($time,$type,$format) = @_; + + # zero, '0' and blank are bad times + if (!$time) { + return &mt('never'); + } + unless (&Apache::lonlocal::current_language()=~/^en/) { + return &Apache::lonlocal::locallocaltime($time); + } + my $now = time(); + + # Positive = future + my $delta = $time - $now; + + my $minute = 60; + my $hour = 60 * $minute; + my $day = 24 * $hour; + my $week = 7 * $day; + my $inPast = 0; + + # Logic in comments: + # Is it now? (extremely unlikely) + if ( $delta == 0 ) { + return "this instant"; + } + + if ($delta < 0) { + $inPast = 1; + $delta = -$delta; + } + + if ( $delta > 0 ) { + + my $tense = $inPast ? " ago" : ""; + my $prefix = $inPast ? "" : "in "; + + # Less than a minute + if ( $delta < $minute ) { + if ($delta == 1) { return "${prefix}1 second$tense"; } + return "$prefix$delta seconds$tense"; + } + + # Less than an hour + if ( $delta < $hour ) { + # If so, use minutes + my $minutes = floor($delta / 60); + if ($minutes == 1) { return "${prefix}1 minute$tense"; } + return "$prefix$minutes minutes$tense"; + } + + # Is it less than 24 hours away? If so, + # display hours + minutes + if ( $delta < $hour * 24) { + my $hours = floor($delta / $hour); + my $minutes = floor(($delta % $hour) / $minute); + my $hourString = "$hours hours"; + my $minuteString = ", $minutes minutes"; + if ($hours == 1) { + $hourString = "1 hour"; + } + if ($minutes == 1) { + $minuteString = ", 1 minute"; + } + if ($minutes == 0) { + $minuteString = ""; + } + return "$prefix$hourString$minuteString$tense"; + } + + my $dt = DateTime->from_epoch(epoch => $time) + ->set_time_zone(&Apache::lonlocal::gettimezone()); + + # If there's a caller supplied format, use it. + + if ($format ne '') { + my $timeStr = $dt->strftime($format); + return $timeStr.' ('.$dt->time_zone_short_name().')'; + } + + # Less than 5 days away, display day of the week and + # HH:MM + + if ( $delta < $day * 5 ) { + my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)"); + $timeStr =~ s/12:00 am/00:00/; + $timeStr =~ s/12:00 pm/noon/; + return ($inPast ? "last " : "this ") . + $timeStr; + } + + my $conjunction='on'; + if ($type eq 'start') { + $conjunction='at'; + } elsif ($type eq 'end') { + $conjunction='by'; + } + # Is it this year? + my $dt_now = DateTime->from_epoch(epoch => $now) + ->set_time_zone(&Apache::lonlocal::gettimezone()); + if ( $dt->year() == $dt_now->year()) { + # Return on Month Day, HH:MM meridian + my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)"); + $timeStr =~ s/12:00 am/00:00/; + $timeStr =~ s/12:00 pm/noon/; + return $timeStr; + } + + # Not this year, so show the year + my $timeStr = + $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)"); + $timeStr =~ s/12:00 am/00:00/; + $timeStr =~ s/12:00 pm/noon/; + return $timeStr; + } +} + + sub resource { return 0; } sub communication_status { return 1; } sub quick_status { return 2; } @@ -755,34 +865,32 @@ sub render_resource { my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons"); # If this is a new branch, label it so if ($params->{'isNewBranch'}) { - $newBranchText = "Branch"; + $newBranchText = "Branch"; } # links to open and close the folder - - my $linkopen = ""; - - + my $whitespace = $location.'/whitespace_21.gif'; + my $linkopen = "".""; my $linkclose = ""; # Default icon: unknown page - my $icon = ""; + my $icon = ""; if ($resource->is_problem()) { if ($part eq '0' || $params->{'condensed'}) { - $icon = ''.&mt('Task');
 	    } else {
 		$icon .= 'problem.gif'; + $icon .='" />'; } else { $icon = $params->{'indentString'}; } } else { - $icon = "  "; + $icon = ""; } # Display the correct map icon to open or shut map @@ -792,15 +900,15 @@ sub render_resource { if ($it->{CONDITION}) { $nowOpen = !$nowOpen; } - + my $folderType = $resource->is_sequence() ? 'folder' : 'page'; my $title=$resource->title; - $title=~s/\"/\"/g; + $title=~s/\"/\&qout;/g; if (!$params->{'resource_no_folder_link'}) { $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; - $icon = "\""."; - + $icon = "" + ."\"""; $linkopen = "{'url'} . '?' . $params->{'queryString'} . '&filter='; $linkopen .= ($nowOpen xor $it->{CONDITION}) ? @@ -815,10 +923,8 @@ sub render_resource { } else { # Don't allow users to manipulate folder - $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . - '.nomanip.gif'; - $icon = "\""."; + $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; + $icon = ""."\"".($nowOpen"; $linkopen = ""; $linkclose = ""; @@ -831,10 +937,14 @@ sub render_resource { if (!$resource->condval()) { $nonLinkedText .= ' ('.&mt('conditionally hidden').') '; } - - # We're done preparing and finally ready to start the rendering - my $result = ""; + if (($resource->is_practice()) && ($resource->is_raw_problem())) { + $nonLinkedText .=' '.&mt('not graded').''; + } + # We're done preparing and finally ready to start the rendering + my $result = ''; + my $newfolderType = $resource->is_sequence() ? 'folder' : 'page'; + my $indentLevel = $params->{'indentLevel'}; if ($newBranchText) { $indentLevel--; } @@ -844,7 +954,6 @@ sub render_resource { } # Decide what to display - $result .= "$newBranchText$linkopen$icon$linkclose"; my $curMarkerBegin = ''; @@ -853,9 +962,9 @@ sub render_resource { # Is this the current resource? if (!$params->{'displayedHereMarker'} && $resource->symb() eq $params->{'here'} ) { - $curMarkerBegin = '>'; - $curMarkerEnd = '<'; - $params->{'displayedHereMarker'} = 1; + $curMarkerBegin = ''; + $curMarkerEnd = ''; + $params->{'displayedHereMarker'} = 1; } if ($resource->is_problem() && $part ne '0' && @@ -875,9 +984,9 @@ 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"; + $result .= "$curMarkerBegin$linkopen$title$partLabel$curMarkerEnd$nonLinkedText"; } return $result; @@ -897,7 +1006,7 @@ sub render_communication_status { my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($resource->hasDiscussion()) { $discussionHTML = $linkopen . - ''.&mt('New Discussion').'' . + ''.&mt('New Discussion').'' . $linkclose; } @@ -907,8 +1016,7 @@ sub render_communication_status { if ($msgid) { $feedbackHTML .= ' ' - . ''.&mt('New Email').''; + . ''.&mt('New E-mail').''; } } } @@ -922,8 +1030,7 @@ sub render_communication_status { $errorcount++; $errorHTML .= ' ' - . ''.&mt('New Error').''; + . ''.&mt('New Error').''; } } } @@ -931,8 +1038,7 @@ sub render_communication_status { if ($params->{'multipart'} && $part != '0') { $discussionHTML = $feedbackHTML = $errorHTML = ''; } - - return "$discussionHTML$feedbackHTML$errorHTML "; + return "$discussionHTML$feedbackHTML$errorHTML "; } sub render_quick_status { @@ -948,33 +1054,33 @@ sub render_quick_status { } my $linkopen = ""; my $linkclose = ""; - + + $result .= ''; if ($resource->is_problem() && !$firstDisplayed) { - my $icon = $statusIconMap{$resource->simpleStatus($part)}; my $alt = $iconAltTags{$icon}; if ($icon) { my $location= &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon"); - $result .= "$linkopen$alt$linkclose\n"; + $result .= "$linkopen$alt$linkclose"; } else { - $result .= " \n"; + $result .= " "; } } else { # not problem, no icon - $result .= " \n"; + $result .= " "; } - + $result .= "\n"; return $result; } sub render_long_status { my ($resource, $part, $params) = @_; - my $result = "\n"; + my $result = ''; my $firstDisplayed = !$params->{'condensed'} && $params->{'multipart'} && $part eq "0"; my $color; - if ($resource->is_problem()) { + if ($resource->is_problem() || $resource->is_practice()) { $color = $colormap{$resource->status}; if (dueInLessThan24Hours($resource, $part) || @@ -984,14 +1090,17 @@ sub render_long_status { } if ($resource->kind() eq "res" && - $resource->is_problem() && + ($resource->is_problem() || $resource->is_practice()) && !$firstDisplayed) { if ($color) {$result .= ""; } $result .= getDescription($resource, $part); if ($color) {$result .= ""; } } - if ($resource->is_map() && advancedUser() && $resource->randompick()) { - $result .= '(randomly select ' . $resource->randompick() .')'; + if ($resource->is_map() && &advancedUser() && $resource->randompick()) { + $result .= &mt('(randomly select [_1])', $resource->randompick()); + } + if ($resource->is_map() && &advancedUser() && $resource->randomorder()) { + $result .= &mt('(randomly ordered)'); } # Debugging code @@ -1026,7 +1135,6 @@ my %statusStrings = ); my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR); -use Data::Dumper; sub render_parts_summary_status { my ($resource, $part, $params) = @_; if (!$resource->is_problem() && !$resource->contains_problem) { return ''; } @@ -1154,7 +1262,7 @@ sub render { if (!defined($navmap)) { $navmap = Apache::lonnavmaps::navmap->new(); if (!defined($navmap)) { - # no londer in course + # no longer in course return ''.&mt('No course selected').'
'.&mt('Select a course').'
'; } @@ -1222,6 +1330,11 @@ sub render { # Step 1: Check to see if we have a navmap if (!defined($navmap)) { $navmap = Apache::lonnavmaps::navmap->new(); + if (!defined($navmap)) { + # no longer in course + return ''.&mt('No course selected').'
+ '.&mt('Select a course').'
'; + } } # See if we're being passed a specific map @@ -1233,7 +1346,7 @@ sub render { $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition); } else { - $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition); + $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'}); } } @@ -1266,11 +1379,10 @@ sub render { my $printKey = $args->{'printKey'}; my $printCloseAll = $args->{'printCloseAll'}; if (!defined($printCloseAll)) { $printCloseAll = 1; } - + # Print key? if ($printKey) { $result .= ''; - my $date=localtime; $result.=''; my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($navmap->{LAST_CHECK}) { @@ -1342,6 +1454,7 @@ END $result.=''; } + if ($args->{'caller'} eq 'navmapsdisplay') { $result .= '
Key:  
'; @@ -1365,31 +1478,35 @@ END $result.=$args->{'sort_html'}; } - $result .= "
\n"; + #$result .= "
\n"; if ($r) { $r->print($result); $r->rflush(); $result = ""; } # End parameter setting - + + $result .= "\n
'. &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'
\n\n\n\n\n\n
 
\n"; + $result .= "
\n"; + # Data - $result .= '' ."\n"; + $result .= '
' ."\n"; + my $res = "Apache::lonnavmaps::resource"; my %condenseStatuses = ( $res->NETWORK_FAILURE => 1, $res->NOTHING_SET => 1, $res->CORRECT => 1 ); - my @backgroundColors = ("#FFFFFF", "#F6F6F6"); + my @backgroundColors = ("LC_trEven", "LC_trOdd"); # Shared variables $args->{'counter'} = 0; # counts the rows $args->{'indentLevel'} = 0; $args->{'isNewBranch'} = 0; - $args->{'condensed'} = 0; - my $location= - &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif"); - $args->{'indentString'} = setDefault($args->{'indentString'}, "  "); + $args->{'condensed'} = 0; + + my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif"); + $args->{'indentString'} = setDefault($args->{'indentString'}, ""); $args->{'displayedHereMarker'} = 0; # If we're suppressing empty sequences, look for them here. Use DFS for speed, @@ -1617,7 +1734,7 @@ END $rownum ++; my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)]; - $result .= " \n"; + $result .= " \n"; # Set up some data about the parts that the cols might want my $filter = $it->{FILTER}; @@ -1714,7 +1831,7 @@ ENDBLOCK $result.='} - '."\n"; foreach my $link (@linkorder) { if (defined($linkitems->{$link})) { if ($linkitems->{$link}{'text'} ne '') { @@ -1725,13 +1842,21 @@ ENDBLOCK } $result .= '  - '."\n"; + '."\n"; return $result; } 1; + + + + + + + + package Apache::lonnavmaps::navmap; =pod @@ -2275,12 +2400,24 @@ sub parmval { my $hashkey = $what."|||".$symb; if (defined($self->{PARM_CACHE}->{$hashkey})) { - return $self->{PARM_CACHE}->{$hashkey}; + if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { + if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) { + if (wantarray) { + return @{$self->{PARM_CACHE}->{$hashkey}}; + } else { + return $self->{PARM_CACHE}->{$hashkey}->[0]; + } + } + } else { + return $self->{PARM_CACHE}->{$hashkey}; + } } - my $result = $self->parmval_real($what, $symb, $recurse); $self->{PARM_CACHE}->{$hashkey} = $result; - return $result; + if (wantarray) { + return @{$result}; + } + return $result->[0]; } sub parmval_real { @@ -2301,7 +2438,7 @@ sub parmval_real { my $uname=$env{'user.name'}; my $udom=$env{'user.domain'}; - unless ($symb) { return ''; } + unless ($symb) { return ['']; } my $result=''; my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); @@ -2333,48 +2470,49 @@ sub parmval_real { # ---------------------------------------------------------- first, check user if ($uname and defined($useropt)) { - if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; } - if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; } - if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; } + if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; } + if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; } + if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; } } # ------------------------------------------------------- second, check course if ($cgroup ne '' and defined($courseopt)) { - if (defined($$courseopt{$grplevelr})) { return $$courseopt{$grplevelr}; } - if (defined($$courseopt{$grplevelm})) { return $$courseopt{$grplevelm}; } - if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; } + if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; } + if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; } + if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; } } if ($csec and defined($courseopt)) { - if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; } - if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; } - if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; } + if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; } + if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; } + if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; } } if (defined($courseopt)) { - if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; } + if (defined($$courseopt{$courselevelr})) { return [$$courseopt{$courselevelr},'resource']; } } # ----------------------------------------------------- third, check map parms my $thisparm=$$parmhash{$symbparm}; - if (defined($thisparm)) { return $thisparm; } + if (defined($thisparm)) { return [$thisparm,'map']; } # ----------------------------------------------------- fourth , check default my $meta_rwhat=$rwhat; $meta_rwhat=~s/\./_/g; my $default=&Apache::lonnet::metadata($fn,$meta_rwhat); - if (defined($default)) { return $default} + if (defined($default)) { return [$default,'resource']} $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat); - if (defined($default)) { return $default} - + if (defined($default)) { return [$default,'resource']} # --------------------------------------------------- fifth, check more course if (defined($courseopt)) { - if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; } - if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; } + if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; } + if (defined($$courseopt{$courselevel})) { + my $ret = [$$courseopt{$courselevel},'course']; + return $ret; + } } - # --------------------------------------------------- sixth , cascade up parts my ($space,@qualifier)=split(/\./,$rwhat); @@ -2384,13 +2522,13 @@ sub parmval_real { my $id=pop(@parts); my $part=join('_',@parts); if ($part eq '') { $part='0'; } - my $partgeneral=$self->parmval($part.".$qualifier",$symb,1); - if (defined($partgeneral)) { return $partgeneral; } + my @partgeneral=$self->parmval($part.".$qualifier",$symb,1); + if (defined($partgeneral[0])) { return \@partgeneral; } } - if ($recurse) { return undef; } + if ($recurse) { return []; } my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat); - if (defined($pack_def)) { return $pack_def; } - return ''; + if (defined($pack_def)) { return [$pack_def,'resource']; } + return ['']; } =pod @@ -2511,6 +2649,10 @@ sub retrieveResources { my @resources = (); + if (&$filterFunc($map)) { + push(@resources, $map); + } + # Run down the iterator and collect the resources. my $curRes; @@ -2520,7 +2662,7 @@ sub retrieveResources { next; } - push @resources, $curRes; + push(@resources, $curRes); if ($bailout) { return @resources; @@ -2851,6 +2993,10 @@ sub next { $self->{HAVE_RETURNED_0} = 1; return $self->{NAV_MAP}->getById('0.0'); } + if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) { + $self->{HAVE_RETURNED_0_BEGIN_MAP} = 1; + return $self->BEGIN_MAP(); + } if ($self->{RECURSIVE_ITERATOR_FLAG}) { # grab the next from the recursive iterator @@ -3364,8 +3510,13 @@ false. =item * B: -Returns true for a map if the randompick feature is being used on the -map. (?) +Returns the number of randomly picked items for a map if the randompick +feature is being used on the map. + +=item * B: + +Returns true for a map if the randomorder feature is being used on the +map. =item * B: @@ -3395,7 +3546,13 @@ sub kind { my $self=shift; return $self- sub randomout { my $self=shift; return $self->navHash("randomout_", 1); } sub randompick { my $self = shift; - return $self->parmval('randompick'); + my $randompick = $self->parmval('randompick'); + return $randompick; +} +sub randomorder { + my $self = shift; + my $randomorder = $self->parmval('randomorder'); + return ($randomorder =~ /^yes$/i); } sub link { my $self=shift; @@ -3516,7 +3673,8 @@ sub retrieveResources { sub is_exam { my ($self,$part) = @_; - if ($self->parmval('type',$part) eq 'exam') { + my $type = $self->parmval('type',$part); + if ($type eq 'exam') { return 1; } if ($self->src() =~ /\.(exam)$/) { @@ -3539,7 +3697,8 @@ sub is_page { sub is_practice { my $self=shift; my ($part) = @_; - if ($self->parmval('type',$part) eq 'practice') { + my $type = $self->parmval('type',$part); + if ($type eq 'practice') { return 1; } return 0; @@ -3552,6 +3711,15 @@ sub is_problem { } return 0; } +sub is_raw_problem { + my $self=shift; + my $src = $self->src(); + if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) { + return 1; + } + return 0; +} + sub contains_problem { my $self=shift; if ($self->is_page()) { @@ -3560,15 +3728,25 @@ sub contains_problem { } return 0; } +sub map_contains_problem { + my $self=shift; + if ($self->is_map()) { + my $has_problem= + $self->hasResource($self,sub { $_[0]->is_problem() },1); + return $has_problem; + } + return 0; +} sub is_sequence { my $self=shift; return $self->navHash("is_map_", 1) && - $self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; + $self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; } sub is_survey { my $self = shift(); my $part = shift(); - if ($self->parmval('type',$part) eq 'survey') { + my $type = $self->parmval('type',$part); + if ($type eq 'survey') { return 1; } if ($self->src() =~ /\.(survey)$/) { @@ -3744,16 +3922,19 @@ Get the weight for the problem. sub acc { (my $self, my $part) = @_; - return $self->parmval("acc", $part); + my $acc = $self->parmval("acc", $part); + return $acc; } sub answerdate { (my $self, my $part) = @_; # Handle intervals - if ($self->parmval("answerdate.type", $part) eq 'date_interval') { - return $self->duedate($part) + - $self->parmval("answerdate", $part); + my $answerdatetype = $self->parmval("answerdate.type", $part); + my $answerdate = $self->parmval("answerdate", $part); + my $duedate = $self->parmval("duedate", $part); + if ($answerdatetype eq 'date_interval') { + $answerdate = $duedate + $answerdate; } - return $self->parmval("answerdate", $part); + return $answerdate; } sub awarded { my $self = shift; my $part = shift; @@ -3765,13 +3946,15 @@ sub awarded { sub duedate { (my $self, my $part) = @_; my $date; - my $interval=$self->parmval("interval", $part); + my @interval=$self->parmval("interval", $part); my $due_date=$self->parmval("duedate", $part); - if ($interval =~ /\d+/) { - my $first_access=&Apache::lonnet::get_first_access('map',$self->symb); + if ($interval[0] =~ /\d+/) { + my $first_access=&Apache::lonnet::get_first_access($interval[1], + $self->symb); 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; } @@ -3785,39 +3968,46 @@ sub handgrade { my @response_ids = $self->responseIds($part); if (@response_ids) { foreach my $response_id (@response_ids) { - if (lc($self->parmval("handgrade",$part.'_'.$response_id)) - eq 'yes') { + my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id); + if (lc($handgrade) eq 'yes') { return 'yes'; } } } - return $self->parmval("handgrade", $part); + my $handgrade = $self->parmval("handgrade", $part); + return $handgrade; } sub maxtries { (my $self, my $part) = @_; - return $self->parmval("maxtries", $part); + my $maxtries = $self->parmval("maxtries", $part); + return $maxtries; } sub opendate { (my $self, my $part) = @_; - if ($self->parmval("opendate.type", $part) eq 'date_interval') { - return $self->duedate($part) - - $self->parmval("opendate", $part); + my $opendatetype = $self->parmval("opendate.type", $part); + my $opendate = $self->parmval("opendate", $part); + if ($opendatetype eq 'date_interval') { + my $duedate = $self->duedate($part); + $opendate = $duedate - $opendate; } - return $self->parmval("opendate"); + return $opendate; } sub problemstatus { (my $self, my $part) = @_; - return lc $self->parmval("problemstatus", $part); + my $problemstatus = $self->parmval("problemstatus", $part); + return lc($problemstatus); } sub sig { (my $self, my $part) = @_; - return $self->parmval("sig", $part); + my $sig = $self->parmval("sig", $part); + return $sig; } sub tol { (my $self, my $part) = @_; - return $self->parmval("tol", $part); + my $tol = $self->parmval("tol", $part); + return $tol; } -sub tries { +sub tries { my $self = shift; my $tries = $self->queryRestoreHash('tries', shift); if (!defined($tries)) { return '0';} @@ -3825,15 +4015,17 @@ sub tries { } sub type { (my $self, my $part) = @_; - return $self->parmval("type", $part); + my $type = $self->parmval("type", $part); + return $type; } sub weight { my $self = shift; my $part = shift; if (!defined($part)) { $part = '0'; } - return &Apache::lonnet::EXT('resource.'.$part.'.weight', - $self->symb(), $env{'user.domain'}, - $env{'user.name'}, - $env{'request.course.sec'}); + my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', + $self->symb(), $env{'user.domain'}, + $env{'user.name'}, + $env{'request.course.sec'}); + return $weight; } sub part_display { my $self= shift(); my $partID = shift(); @@ -4507,7 +4699,11 @@ sub status { #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; } if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; } - my $suppressFeedback = $self->problemstatus($part) eq 'no'; + my $suppressFeedback = 0; + if (($self->problemstatus($part) eq 'no') || + ($self->problemstatus($part) eq 'no_feedback_ever')) { + $suppressFeedback = 1; + } # If there's an answer date and we're past it, don't # suppress the feedback; student should know if ($self->duedate($part) && $self->duedate($part) < time() &&