--- loncom/interface/lonnavmaps.pm 2008/01/24 22:12:02 1.411 +++ loncom/interface/lonnavmaps.pm 2010/11/17 22:46:27 1.451 @@ -1,7 +1,8 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.411 2008/01/24 22:12:02 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.451 2010/11/17 22:46:27 raeburn Exp $ + # # Copyright Michigan State University Board of Trustees # @@ -27,407 +28,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 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 -} - -# 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; } - } - - if ($res) { - return ($res->link(),$res->shown_symb()); - } - return; -} - -# 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); - - 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 "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 = "($tries of $maxtries tries used)"; - 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'); - } -} - -# 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(); - - # 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; - } -} - - =pod =head1 NAME -Apache::lonnavmap - Subroutines to handle and render the navigation - maps +Apache::lonnavmaps - Subroutines to handle and render the navigation =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 @@ -747,6 +366,492 @@ 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 usesthe 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 = #texthash does not work here + ( 'navmap.correct.gif' => 'Correct', + 'navmap.wrong.gif' => 'Incorrect', + 'navmap.open.gif' => 'Open', + 'navmap.partial.gif' => 'Partially Correct', + 'navmap.ellipsis.gif' => 'Attempted', + ); + +# 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->CREDIT_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"; + +my $future_slots_checked = 0; +my $future_slots = 0; + +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($res->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 ($res->simpleStatus($part) == $res->OPEN) { + unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) { + my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part); + if ($slot_status == $res->UNKNOWN) { + return &mt('Reservation status unknown'); + } elsif ($slot_status == $res->RESERVED) { + return &mt('Reserved - ends [_1]', + timeToHumanString($slot_time,'end')); + } elsif ($slot_status == $res->RESERVED_LOCATION) { + return &mt('Reserved - specific location(s) - ends [_1]', + timeToHumanString($slot_time,'end')); + } elsif ($slot_status == $res->RESERVED_LATER) { + return &mt('Reserved - next open [_1]', + timeToHumanString($slot_time,'start')); + } elsif ($slot_status == $res->RESERVABLE) { + return &mt('Reservable ending [_1]', + timeToHumanString($slot_time,'end')); + } elsif ($slot_status == $res->RESERVABLE_LATER) { + return &mt('Reservable starting [_1]', + timeToHumanString($slot_time,'start')); + } elsif ($slot_status == $res->NOT_IN_A_SLOT) { + return &mt('Reserve a time/place to work'); + } elsif ($slot_status == $res->NOTRESERVABLE) { + return &mt('Reservation not available'); + } elsif ($slot_status == $res->WAITING_FOR_GRADE) { + return &mt('Submission in grading queue'); + } + } + } + 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) { + if ($res->is_anonsurvey($part) || $res->is_survey($part)) { + return &mt("Survey submission recorded"); + } else { + return &mt("Answer submitted, not yet graded"); + } + } + if ($status == $res->CREDIT_ATTEMPTED) { + if ($res->is_anonsurvey($part) || $res->is_survey($part)) { + return &mt("Credit for survey submission"); + } + } + 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; } @@ -761,11 +866,6 @@ sub render_resource { my $link = $params->{"resourceLink"}; # The URL part is not escaped at this point, but the symb is... - # The stuff to the left of the ? must have ' replaced by \' since - # it will be quoted with ' in the href. - - my ($left,$right) = split(/\?/, $link); - $link = $left.'?'.$right; my $src = $resource->src(); my $it = $params->{"iterator"}; @@ -778,34 +878,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 = ".mt('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 @@ -815,15 +913,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}) ? @@ -838,10 +936,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 = ""; @@ -854,10 +950,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--; } @@ -867,7 +967,6 @@ sub render_resource { } # Decide what to display - $result .= "$newBranchText$linkopen$icon$linkclose"; my $curMarkerBegin = ''; @@ -876,9 +975,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' && @@ -893,14 +992,10 @@ sub render_resource { $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')'; } - my $target; - if ($env{'environment.remotenavmap'} eq 'on') { - $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; @@ -911,16 +1006,12 @@ sub render_communication_status { my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = ""; my $link = $params->{"resourceLink"}; - my $target; - if ($env{'environment.remotenavmap'} eq 'on') { - $target=' target="loncapaclient" '; - } - my $linkopen = ""; + my $linkopen = ""; my $linkclose = ""; my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($resource->hasDiscussion()) { $discussionHTML = $linkopen . - ''.&mt('New Discussion').'' . + ''.&mt('New Discussion').'' . $linkclose; } @@ -928,10 +1019,9 @@ sub render_communication_status { my $feedback = $resource->getFeedback(); foreach my $msgid (split(/\,/, $feedback)) { if ($msgid) { - $feedbackHTML .= ' ' - . ''.&mt('New Email').''; + . ''.&mt('New E-mail').''; } } } @@ -943,10 +1033,9 @@ sub render_communication_status { last if ($errorcount>=10); # Only output 10 bombs maximum if ($msgid) { $errorcount++; - $errorHTML .= ' ' - . ''.&mt('New Error').''; + . ''.&mt('New Error').''; } } } @@ -954,8 +1043,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 { @@ -965,34 +1053,30 @@ sub render_quick_status { $params->{'multipart'} && $part eq "0"; my $link = $params->{"resourceLink"}; - my $target; - if ($env{'environment.remotenavmap'} eq 'on') { - $target=' target="loncapaclient" '; - } - my $linkopen = ""; + 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.''.&mt($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"; @@ -1179,7 +1263,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').'
'; } @@ -1247,6 +1331,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 @@ -1291,7 +1380,7 @@ sub render { my $printKey = $args->{'printKey'}; my $printCloseAll = $args->{'printCloseAll'}; if (!defined($printCloseAll)) { $printCloseAll = 1; } - + # Print key? if ($printKey) { $result .= ''; @@ -1299,15 +1388,15 @@ sub render { my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($navmap->{LAST_CHECK}) { $result .= - ' '.&mt('New discussion since').' '. + ' '.&mt('New discussion since').' '. strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})). ''; } else { $result .= ''; } @@ -1317,19 +1406,22 @@ sub render { if ($printCloseAll && !$args->{'resource_no_folder_link'}) { my ($link,$text); if ($condition) { - $link='"navmaps?condition=0&filter=&'.$queryString. - '&here='.&escape($here).'"'; + $link='navmaps?condition=0&filter=&'.$queryString. + '&here='.&escape($here); $text='Close all folders'; } else { - $link='"navmaps?condition=1&filter=&'.$queryString. - '&here='.&escape($here).'"'; + $link='navmaps?condition=1&filter=&'.$queryString. + '&here='.&escape($here); $text='Open all folders'; } + if ($env{'form.register'}) { + $link .= '&register='.$env{'form.register'}; + } if ($args->{'caller'} eq 'navmapsdisplay') { &add_linkitem($args->{'linkitems'},'changefolder', - 'location.href='.$link,$text); + "location.href='$link'",$text); } else { - $result.=''.&mt($text).''; + $result.= ''.&mt($text).''; } $result .= "\n"; } @@ -1345,6 +1437,9 @@ sub render { END + if ($env{'form.register'}) { + $result .= ''; + } if ($args->{'sort'} eq 'discussion') { my $totdisc = 0; my $haveDisc = ''; @@ -1369,51 +1464,45 @@ END if ($args->{'caller'} eq 'navmapsdisplay') { $result .= '
  '. - ' '.&mt('New message (click to open)').'

'. + ' '.&mt('New message (click to open)').'

'. '

  '. - ' '.&mt('Discussions').''. - '   '.&mt('New message (click to open)'). + ' '.&mt('Discussions').''. + '   '.&mt('New message (click to open)'). '
'; - if ($env{'environment.remotenavmap'} ne 'on') { $result .= ''; - } else { - $result .= ''; - } - $result.=&show_linkitems($args->{'linkitems'}); + $result.=''; + $result.=&show_linkitems_toolbar($args->{'linkitems'}); if ($args->{'sort_html'}) { - if ($env{'environment.remotenavmap'} ne 'on') { - $result.=''. - ''; - } else { - $result.=''; - } - } + $result.=''. + ''; + } $result .= '
'. &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').' 
'.&mt('Tools:').'   '.$args->{'sort_html'}.'

'. - $args->{'sort_html'}.'
   '.$args->{'sort_html'}.'
'; } elsif ($args->{'sort_html'}) { $result.=$args->{'sort_html'}; } - $result .= "
\n"; + #$result .= "
\n"; if ($r) { $r->print($result); $r->rflush(); $result = ""; } # End parameter setting - + + $result .= "
\n"; + # Data - $result .= '' ."\n"; + $result.=&Apache::loncommon::start_data_table("LC_tableOfContent"); + my $res = "Apache::lonnavmaps::resource"; my %condenseStatuses = ( $res->NETWORK_FAILURE => 1, $res->NOTHING_SET => 1, $res->CORRECT => 1 ); - my @backgroundColors = ("#FFFFFF", "#F6F6F6"); # 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, @@ -1632,16 +1721,15 @@ END if (defined($anchor)) { $anchor='#'.$anchor; } my $srcHasQuestion = $src =~ /\?/; $args->{"resourceLink"} = $src. - ($srcHasQuestion?'&':'?') . + ($srcHasQuestion?'&':'?') . 'symb=' . &escape($symb).$anchor; } # Now, we've decided what parts to show. Loop through them and # show them. foreach my $part (@parts) { $rownum ++; - my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)]; - $result .= " \n"; + $result .= &Apache::loncommon::start_data_table_row(); # Set up some data about the parts that the cols might want my $filter = $it->{FILTER}; @@ -1667,7 +1755,7 @@ END } $result .= $colHTML . "\n"; } - $result .= " \n"; + $result .= &Apache::loncommon::end_data_table_row(); $args->{'isNewBranch'} = 0; } @@ -1695,16 +1783,15 @@ END # it's quite likely this might fix other browsers, too, and # certainly won't hurt anything. if ($displayedJumpMarker) { - $result .= " -"; +"); } - $result .= "
"; - + $result.=&Apache::loncommon::end_data_table(); + if ($r) { $r->print($result); $result = ""; @@ -1720,42 +1807,52 @@ sub add_linkitem { $$linkitems{$name}{'text'}=&mt($text); } -sub show_linkitems { - my ($linkitems)=@_; - my @linkorder = ("blank","launchnav","closenav","firsthomework", - "everything","uncompleted","changefolder","clearbubbles"); - - my $result .= (< - -
-   -
'."\n"; - + $result .= ''. + ''."\n"; return $result; } + 1; + + + + + + + + package Apache::lonnavmaps::navmap; =pod @@ -2212,7 +2309,7 @@ resource object. Based on the symb of the resource, get a resource object for that resource. This is one of the proper ways to get a resource object. -=item * B(map_pc): +=item * B(map_pc): Based on the map_pc of the resource, get a resource object for the given map. This is one of the proper ways to get a resource object. @@ -2482,7 +2579,7 @@ in the filter function. 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 @@ -3367,7 +3464,11 @@ sub navHash { my $self = shift; my $param = shift; my $id = shift; - return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:"")); + my $arg = $param . ($id?$self->{ID}:""); + if (ref($self) && ref($self->{NAV_MAP}) && defined($arg)) { + return $self->{NAV_MAP}->navhash($arg); + } + return; } =pod @@ -3610,6 +3711,44 @@ sub is_problem { } return 0; } +# +# The has below is the set of status that are considered 'incomplete' +# +my %incomplete_hash = +( + TRIES_LEFT() => 1, + OPEN() => 1, + ATTEMPTED() => 1 + + ); +# +# Return tru if a problem is incomplete... for now incomplete means that +# any part of the problem is incomplete. +# Note that if the resources is not a problem, 0 is returned. +# +sub is_incomplete { + my $self = shift; + if ($self->is_problem()) { + &Apache::lonnet::logthis('is problem'); + foreach my $part (@{$self->parts()}) { + &Apache::lonnet::logthis("$part status ".$self->status($part)); + if (exists($incomplete_hash{$self->status($part)})) { + return 1; + } + } + } + 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()) { @@ -3630,13 +3769,13 @@ sub map_contains_problem { 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(); my $type = $self->parmval('type',$part); - if ($type eq 'survey') { + if (($type eq 'survey') || ($type eq 'surveycred')) { return 1; } if ($self->src() =~ /\.(survey)$/) { @@ -3644,6 +3783,15 @@ sub is_survey { } return 0; } +sub is_anonsurvey { + my $self = shift(); + my $part = shift(); + my $type = $self->parmval('type',$part); + if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) { + return 1; + } + return 0; +} sub is_task { my $self=shift; my $src = $self->src(); @@ -3697,6 +3845,12 @@ resource of the map. Returns a string with the type of the map in it. +=item *B: + +Returns a string with a comma-separated ordered list of map_pc IDs +for the hierarchy of maps containing a map, with the top level +map first, then descending to deeper levels, with the enclosing map last. + =back =cut @@ -3727,6 +3881,11 @@ sub map_type { my $pc = $self->map_pc(); return $self->navHash("map_type_$pc", 0); } +sub map_hierarchy { + my $self = shift; + my $pc = $self->map_pc(); + return $self->navHash("map_hierarchy_$pc", 0); +} ##### # Property queries @@ -3832,6 +3991,35 @@ sub awarded { if (!defined($part)) { $part = '0'; } return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'}; } +sub taskversion { + my $self = shift; my $part = shift; + $self->{NAV_MAP}->get_user_data(); + if (!defined($part)) { $part = '0'; } + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.version'}; +} +sub taskstatus { + my $self = shift; my $part = shift; + $self->{NAV_MAP}->get_user_data(); + if (!defined($part)) { $part = '0'; } + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'}; +} +sub solved { + my $self = shift; my $part = shift; + $self->{NAV_MAP}->get_user_data(); + if (!defined($part)) { $part = '0'; } + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.solved'}; +} +sub checkedin { + my $self = shift; my $part = shift; + $self->{NAV_MAP}->get_user_data(); + if (!defined($part)) { $part = '0'; } + if ($self->is_task()) { + my $version = $self->taskversion($part); + return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin.slot'}); + } else { + return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin.slot'}); + } +} # this should work exactly like the copy in lonhomework.pm sub duedate { (my $self, my $part) = @_; @@ -3927,6 +4115,14 @@ sub part_display { } return $display; } +sub slot_control { + my $self=shift(); my $part = shift(); + if (!defined($part)) { $part = '0'; } + my $useslots = $self->parmval("useslots", $part); + my $availablestudent = $self->parmval("availablestudent", $part); + my $available = $self->parmval("available", $part); + return ($useslots,$availablestudent,$available); +} # Multiple things need this sub getReturnHash { @@ -4359,7 +4555,7 @@ sub OPEN { return 1; } sub PAST_DUE_NO_ANSWER { return 2; } sub PAST_DUE_ANSWER_LATER { return 3; } sub ANSWER_OPEN { return 4; } -sub NOTHING_SET { return 5; } +sub NOTHING_SET { return 5; } sub NETWORK_FAILURE { return 100; } # getDateStatus gets the date status for a given problem part. @@ -4445,6 +4641,10 @@ Information not available due to network Attempted, and not yet graded. +=item * B: + +Attempted, and credit received for attempt (survey and anonymous survey only). + =back =cut @@ -4456,6 +4656,7 @@ sub CORRECT { return 13; } sub CORRECT_BY_OVERRIDE { return 14; } sub EXCUSED { return 15; } sub ATTEMPTED { return 16; } +sub CREDIT_ATTEMPTED { return 17; } sub getCompletionStatus { my $self = shift; @@ -4474,6 +4675,13 @@ sub getCompletionStatus { if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; } if ($status eq 'excused') {return $self->EXCUSED; } if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; } + if ($status eq 'credit_attempted') { + if ($self->is_anonsurvey($part) || $self->is_survey($part)) { + return $self->CREDIT_ATTEMPTED; + } else { + return $self->ATTEMPTED; + } + } return $self->NOT_ATTEMPTED; } @@ -4563,6 +4771,10 @@ The item is open and not yet tried. The problem has been attempted. +=item * B: + +The problem has been attempted, and credit given for the attempt (survey and anonymous survey only). + =item * B: An answer has been submitted, but the student should not see it. @@ -4571,9 +4783,20 @@ An answer has been submitted, but the st =cut -sub TRIES_LEFT { return 20; } -sub ANSWER_SUBMITTED { return 21; } -sub PARTIALLY_CORRECT{ return 22; } +sub TRIES_LEFT { return 20; } +sub ANSWER_SUBMITTED { return 21; } +sub PARTIALLY_CORRECT { return 22; } + +sub RESERVED_LATER { return 30; } +sub RESERVED { return 31; } +sub RESERVED_LOCATION { return 32; } +sub RESERVABLE { return 33; } +sub RESERVABLE_LATER { return 34; } +sub NOTRESERVABLE { return 35; } +sub NOT_IN_A_SLOT { return 36; } +sub NEEDS_CHECKIN { return 37; } +sub WAITING_FOR_GRADE { return 38; } +sub UNKNOWN { return 39; } sub status { my $self = shift; @@ -4625,6 +4848,10 @@ sub status { return ATTEMPTED; } + if ($completionStatus == CREDIT_ATTEMPTED) { + return CREDIT_ATTEMPTED; + } + # If it's EXCUSED, then return that no matter what if ($completionStatus == EXCUSED) { return EXCUSED; @@ -4666,6 +4893,108 @@ sub status { return OPEN; } +sub check_for_slot { + my $self = shift; + my $part = shift; + my ($use_slots,$available,$availablestudent) = $self->slot_control($part); + if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) { + my @slots = (split(/:/,$availablestudent),split(/:/,$available)); + my $cid=$env{'request.course.id'}; + my $cdom=$env{'course.'.$cid.'.domain'}; + my $cnum=$env{'course.'.$cid.'.num'}; + my $now = time; + if (@slots > 0) { + my %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum); + if (&Apache::lonnet::error(%slots)) { + return (UNKNOWN); + } + my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots); + my ($checkedin,$checkedinslot); + foreach my $slot_name (@sorted_slots) { + next if (!defined($slots{$slot_name}) || + !ref($slots{$slot_name})); + my $end = $slots{$slot_name}->{'endtime'}; + my $start = $slots{$slot_name}->{'starttime'}; + my $ip = $slots{$slot_name}->{'ip'}; + if ($self->simpleStatus() == OPEN) { + my $startreserve = $slots{$slot_name}->{'startreserve'}; + my @proctors; + if ($slots{$slot_name}->{'proctor'} ne '') { + @proctors = split(',',$slots{$slot_name}->{'proctor'}); + } + if ($end > $now) { + ($checkedin,$checkedinslot) = $self->checkedin(); + if ($startreserve < $now) { + if ($start > $now) { + return (RESERVED_LATER,$start,$slot_name); + } else { + if ($ip ne '') { + if (!&Apache::loncommon::check_ip_acc($ip)) { + return (RESERVED_LOCATION,$ip,$slot_name); + } + } + if (@proctors > 0) { + unless ((grep(/^\Q$checkedin\E/,@proctors)) && + ($checkedinslot eq $slot_name)) { + return (NEEDS_CHECKIN,undef,$slot_name); + } + } + return (RESERVED,$end,$slot_name); + } + } else { + if ($start > $now) { + return (RESERVABLE,$startreserve,$slot_name); + } + } + } + } + } + my ($is_correct,$got_grade); + if ($self->is_task()) { + my $taskstatus = $self->taskstatus(); + $is_correct = (($taskstatus eq 'pass') || + ($self->solved() =~ /^correct_/)); + $got_grade = ($self->solved() =~ /^(?:pass|fail)$/); + } else { + $got_grade = 1; + $is_correct = ($self->solved() =~ /^correct_/); + } + ($checkedin,$checkedinslot) = $self->checkedin(); + if ($checkedin) { + if (!$got_grade) { + return (WAITING_FOR_GRADE); + } elsif ($is_correct) { + return (CORRECT); + } + } + return(NOT_IN_A_SLOT); + } else { + if (!$future_slots_checked) { + $future_slots = &get_future_slots($cdom,$cnum,$now); + $future_slots_checked = 1; + } + if ($future_slots) { + return(NOT_IN_A_SLOT); + } + return(NOTRESERVABLE); + } + } + return; +} + +sub get_future_slots { + my ($cdom,$cnum,$now) = @_; + my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum); + my $future_slots = 0; + foreach my $slot (keys(%slots)) { + if (($slots{$slot}->{'starttime'} > $now) && + ($slots{$slot}->{'endtime'} > $now)) { + $future_slots ++; + } + } + return $future_slots; +} + sub CLOSED { return 23; } sub ERROR { return 24; } @@ -4715,6 +5044,7 @@ my %compositeToSimple = INCORRECT() => INCORRECT, OPEN() => OPEN, ATTEMPTED() => ATTEMPTED, + CREDIT_ATTEMPTED() => CORRECT, ANSWER_SUBMITTED() => ATTEMPTED ); @@ -4789,6 +5119,7 @@ sub completable { # and it is not "attempted" (manually graded problem), it is # not "complete" if ($self->getCompletionStatus($part) == ATTEMPTED() || + $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() || $status == ANSWER_SUBMITTED() ) { # did this part already, as well as we can next; 500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.