--- loncom/interface/lonnavmaps.pm 2010/11/17 22:46:27 1.451 +++ loncom/interface/lonnavmaps.pm 2011/11/07 20:05:56 1.466 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.451 2010/11/17 22:46:27 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.466 2011/11/07 20:05:56 www Exp $ # # Copyright Michigan State University Board of Trustees @@ -478,12 +478,18 @@ use Apache::loncommon(); use Apache::lonenc(); use Apache::lonlocal; use Apache::lonnet; +use Apache::lonmap; use POSIX qw (floor strftime); use Time::HiRes qw( gettimeofday tv_interval ); use LONCAPA; use DateTime(); +# For debugging + +use Data::Dumper; + + # symbolic constants sub SYMB { return 1; } sub URL { return 2; } @@ -509,7 +515,7 @@ my %statusIconMap = my %iconAltTags = #texthash does not work here ( 'navmap.correct.gif' => 'Correct', 'navmap.wrong.gif' => 'Incorrect', - 'navmap.open.gif' => 'Open', + 'navmap.open.gif' => 'Is Open', 'navmap.partial.gif' => 'Partially Correct', 'navmap.ellipsis.gif' => 'Attempted', ); @@ -609,10 +615,10 @@ sub getDescription { return &mt("Having technical difficulties; please check status later"); } if ($status == $res->NOTHING_SET) { - return &mt("Not currently assigned."); + return &Apache::lonhtmlcommon::direct_parm_link(&mt("Not currently assigned.",$res->symb(),'opendate'),$part); } if ($status == $res->OPEN_LATER) { - return &mt("Open ") .timeToHumanString($open,'start'); + return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start')),$res->symb(),'opendate',$part); } if ($res->simpleStatus($part) == $res->OPEN) { unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) { @@ -646,27 +652,27 @@ sub getDescription { if ($status == $res->OPEN) { if ($due) { if ($res->is_practice()) { - return &mt("Closes ")." " .timeToHumanString($due,'start'); + return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start')),$res->symb(),'duedate',$part); } else { - return &mt("Due")." " .timeToHumanString($due,'end'); + return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end')),$res->symb(),'duedate',$part); } } else { - return &mt("Open, no due date"); + return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part); } } if ($status == $res->PAST_DUE_ANSWER_LATER) { - return &mt("Answer open")." " .timeToHumanString($answer,'start'); + return &mt("Answer open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($answer,'start')),$res->symb(),'answerdate',$part); } if ($status == $res->PAST_DUE_NO_ANSWER) { if ($res->is_practice()) { - return &mt("Closed")." " . timeToHumanString($due,'start'); + return &mt("Closed [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start')),$res->symb(),'answerdate,duedate',$part); } else { - return &mt("Was due")." " . timeToHumanString($due,'end'); + return &mt("Was due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end')),$res->symb(),'answerdate,duedate',$part); } } if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) && $res->handgrade($part) ne 'yes') { - return &mt("Answer available"); + return &Apache::lonhtmlcommon::direct_parm_link(&mt("Answer available"),$res->symb(),'answerdate,duedate',$part); } if ($status == $res->EXCUSED) { return &mt("Excused by instructor"); @@ -694,10 +700,10 @@ sub getDescription { } } if ($due) { - return &mt("Due")." " . timeToHumanString($due,'end') . + return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end')),$res->symb(),'duedate',$part) . " $triesString"; } else { - return &mt("No due date")." $triesString"; + return &Apache::lonhtmlcommon::direct_parm_link(&mt("No due date"),$res->symb(),'duedate',$part)." $triesString"; } } if ($status == $res->ANSWER_SUBMITTED) { @@ -779,14 +785,31 @@ sub timeToHumanString { # Less than an hour if ( $delta < $hour ) { - # If so, use minutes + # If so, use minutes; or minutes, seconds (if format requires) my $minutes = floor($delta / 60); + if (($format ne '') && ($format =~ /\%(T|S)/)) { + my $display; + if ($minutes == 1) { + $display = "${prefix}1 minute"; + } else { + $display = "$prefix$minutes minutes"; + } + my $seconds = $delta % $minute; + if ($seconds == 0) { + $display .= $tense; + } elsif ($seconds == 1) { + $display .= ", 1 second$tense"; + } else { + $display .= ", $seconds seconds$tense"; + } + return $display; + } 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 + # display hours + minutes, (and + seconds, if format specified it) if ( $delta < $hour * 24) { my $hours = floor($delta / $hour); my $minutes = floor(($delta % $hour) / $minute); @@ -801,15 +824,30 @@ sub timeToHumanString { if ($minutes == 0) { $minuteString = ""; } + if (($format ne '') && ($format =~ /\%(T|S)/)) { + my $display = "$prefix$hourString$minuteString"; + my $seconds = $delta-(($hours * $hour)+($minutes * $minute)); + if ($seconds == 0) { + $display .= $tense; + } elsif ($seconds == 1) { + $display .= ", 1 second$tense"; + } else { + $display .= ", $seconds seconds$tense"; + } + return $display; + } return "$prefix$hourString$minuteString$tense"; } + # Date/time is more than 24 hours away + my $dt = DateTime->from_epoch(epoch => $time) ->set_time_zone(&Apache::lonlocal::gettimezone()); - # If there's a caller supplied format, use it. + # If there's a caller supplied format, use it, unless it only displays + # H:M:S or H:M. - if ($format ne '') { + if (($format ne '') && ($format ne '%T') && ($format ne '%R')) { my $timeStr = $dt->strftime($format); return $timeStr.' ('.$dt->time_zone_short_name().')'; } @@ -945,10 +983,10 @@ sub render_resource { } if ($resource->randomout()) { - $nonLinkedText .= ' ('.&mt('hidden').') '; + $nonLinkedText .= ' ('.&mt('hidden').') '; } if (!$resource->condval()) { - $nonLinkedText .= ' ('.&mt('conditionally hidden').') '; + $nonLinkedText .= ' ('.&mt('conditionally hidden').') '; } if (($resource->is_practice()) && ($resource->is_raw_problem())) { $nonLinkedText .=' '.&mt('not graded').''; @@ -1091,7 +1129,7 @@ sub render_long_status { } if ($resource->kind() eq "res" && - ($resource->is_problem() || $resource->is_practice()) && + $resource->is_raw_problem() && !$firstDisplayed) { if ($color) {$result .= ""; } $result .= getDescription($resource, $part); @@ -1929,6 +1967,12 @@ sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; + bless($self); # So we can call change_user if neceesary + + $self->{USERNAME} = shift || $env{'user.name'}; + $self->{DOMAIN} = shift || $env{'user.domain'}; + + # Resource cache stores navmap resources as we reference them. We generate # them on-demand so we don't pay for creating resources unless we use them. @@ -1938,38 +1982,98 @@ sub new { # failed $self->{NETWORK_FAILURE} = 0; - # tie the nav hash + # We can only tie the nav hash as done below if the username/domain + # match the env one. Otherwise change_user does everything we need...since we can't + # assume there are course hashes for the specific requested user@domamin: + # - my %navmaphash; - my %parmhash; - my $courseFn = $env{"request.course.fn"}; - if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db", - &GDBM_READER(), 0640))) { - return undef; + if (($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'})) { + + # tie the nav hash + + my %navmaphash; + my %parmhash; + my $courseFn = $env{"request.course.fn"}; + if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db", + &GDBM_READER(), 0640))) { + return undef; + } + + if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db", + &GDBM_READER(), 0640))) + { + untie %{$self->{PARM_HASH}}; + return undef; + } + + $self->{NAV_HASH} = \%navmaphash; + $self->{PARM_HASH} = \%parmhash; + $self->{PARM_CACHE} = {}; + } else { + $self->change_user($self->{USERNAME}, $self->{DOMAIN}); } + + return $self; +} + +# +# In some instances it is useful to be able to dynamically change the +# username/domain associated with a navmap (e.g. to navigate for someone +# else besides the current user...if sufficiently privileged. +# Parameters: +# user - New user. +# domain- Domain the user belongs to. +# Implicit inputs: +# +sub change_user { + my $self = shift; + $self->{USERNAME} = shift; + $self->{DOMAIN} = shift; + + # If the hashes are already tied make sure to break that bond: + + untie %{$self->{NAV_HASH}}; + untie %{$self->{PARM_HASH}}; + + # The assumption is that we have to + # use lonmap here to re-read the hash and from it reconstruct + # new big and parameter hashes. An implicit assumption at this time + # is that the course file is probably not created locally yet + # an that we will therefore just read without tying. + + my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'}); + + my %big_hash; + &Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, \%big_hash); + $self->{NAV_HASH} = \%big_hash; + + # Now clear the parm cache and reconstruct the parm hash fromt he big_hash + # param.xxxx keys. + + $self->{PARM_CACHE} = {}; - if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db", - &GDBM_READER(), 0640))) - { - untie %{$self->{PARM_HASH}}; - return undef; + my %parm_hash = {}; + foreach my $key (keys %big_hash) { + if ($key =~ /^param\./) { + my $param_key = $key; + $param_key =~ s/^param\.//; + $parm_hash{$param_key} = $big_hash{$key}; + } } - $self->{NAV_HASH} = \%navmaphash; - $self->{PARM_HASH} = \%parmhash; - $self->{PARM_CACHE} = {}; + $self->{PARM_HASH} = \%parm_hash; - bless($self); - - return $self; -} + + +} sub generate_course_user_opt { my $self = shift; if ($self->{COURSE_USER_OPT_GENERATED}) { return; } - my $uname=$env{'user.name'}; - my $udom=$env{'user.domain'}; + my $uname=$self->{USERNAME}; + my $udom=$self->{DOMAIN}; + my $cid=$env{'request.course.id'}; my $cdom=$env{'course.'.$cid.'.domain'}; my $cnum=$env{'course.'.$cid.'.num'}; @@ -2012,7 +2116,7 @@ sub generate_email_discuss_status { my $cdom=$env{'course.'.$cid.'.domain'}; my $cnum=$env{'course.'.$cid.'.num'}; - my %emailstatus = &Apache::lonnet::dump('email_status'); + my %emailstatus = &Apache::lonnet::dump('email_status',$self->{DOMAIN},$self->{USERNAME}); my $logoutTime = $emailstatus{'logout'}; my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}}; $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ? @@ -2020,7 +2124,7 @@ sub generate_email_discuss_status { my %discussiontime = &Apache::lonnet::dump('discussiontimes', $cdom, $cnum); my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss', - $env{'user.domain'},$env{'user.name'},'lastread'); + $self->{DOMAIN},$self->{USERNAME},'lastread'); my %lastreadtime = (); foreach my $key (keys %lastread) { my $shortkey = $key; @@ -2030,8 +2134,8 @@ sub generate_email_discuss_status { my %feedback=(); my %error=(); - my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'}, - $env{'user.name'}); + my @keys = &Apache::lonnet::getkeys('nohist_email',$self->{DOMAIN}, + $self->{USERNAME}); foreach my $msgid (@keys) { if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) { @@ -2079,8 +2183,8 @@ sub get_user_data { # Retrieve performance data on problems my %student_data = Apache::lonnet::currentdump($env{'request.course.id'}, - $env{'user.domain'}, - $env{'user.name'}); + $self->{DOMAIN}, + $self->{USERNAME}); $self->{STUDENT_DATA} = \%student_data; $self->{RETRIEVED_USER_DATA} = 1; @@ -2394,7 +2498,7 @@ sub parmval { my $self = shift; my ($what,$symb,$recurse)=@_; my $hashkey = $what."|||".$symb; - + my $cache = $self->{PARM_CACHE}; if (defined($self->{PARM_CACHE}->{$hashkey})) { if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) { @@ -2431,8 +2535,8 @@ sub parmval_real { @cgrps = sort(@cgrps); $cgroup = $cgrps[0]; } - my $uname=$env{'user.name'}; - my $udom=$env{'user.domain'}; + my $uname=$self->{USERNAME}; + my $udom=$self->{DOMAIN}; unless ($symb) { return ['']; } my $result=''; @@ -2834,6 +2938,9 @@ sub new { weaken($self->{NAV_MAP} = shift); return undef unless ($self->{NAV_MAP}); + $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME}; + $self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN}; + # Handle the parameters $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource(); $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource(); @@ -3207,6 +3314,9 @@ sub new { weaken($self->{NAV_MAP} = shift); return undef unless ($self->{NAV_MAP}); + $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME}; + $self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN}; + $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource(); $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource(); @@ -3443,6 +3553,9 @@ sub new { weaken($self->{NAV_MAP} = shift); $self->{ID} = shift; + $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME}; + $self->{DOMAIN} = $self->{NAV_MAP}->{DOMAIN}; + # Store this new resource in the parent nav map's cache. $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self; $self->{RESOURCE_ERROR} = 0; @@ -3706,7 +3819,7 @@ sub is_practice { sub is_problem { my $self=shift; my $src = $self->src(); - if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) { + if ($src =~ /$LONCAPA::assess_re/) { return !($self->is_practice()); } return 0; @@ -3729,9 +3842,7 @@ my %incomplete_hash = 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; } @@ -3743,7 +3854,7 @@ sub is_incomplete { sub is_raw_problem { my $self=shift; my $src = $self->src(); - if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) { + if ($src =~ /$LONCAPA::assess_re/) { return 1; } return 0; @@ -3916,7 +4027,17 @@ their code.) =over 4 -=item * B: +=item * B + +returns true if the current date is such that the +specified resource part is printable. + +=item * B + +Returns true if all parts in the resource are printable making the +entire resource printable. + +=item * B Get the Client IP/Name Access Control information. @@ -3969,6 +4090,55 @@ Get the weight for the problem. =cut +sub printable { + + my ($self, $part) = @_; + + # Get the print open/close dates for the resource. + + my $start = $self->parmval("prinstartdate", $part); + my $end = $self->parmval("printenddate", $part); + + # The following cases apply: + # - No dates set: Printable. + # - Start date set but no end date: Printable if now >= start date. + # - End date set but no start date: Printable if now <= end date. + # - both defined: printable if start <= now <= end + # + my $now = time(); + + my $startok = 1; + my $endok = 1; + + if ((defined $start) && ($start ne '')) { + $startok = $start <= $now; + } + if ((defined $end) && ($end != '')) { + $endok = $end >= $now; + } + return $startok && $endok; +} + +sub resprintable { + my $self = shift; + + # get parts...or realize there are no parts. + + my $partsref = $self->parts(); + my @parts = @$partsref; + + if ((!defined(@parts)) || (scalar(@parts) == 0)) { + return $self->printable(0); + } else { + foreach my $part (@parts) { + if (!$self->printable($part)) { + return 0; + } + } + return 1; + } +} + sub acc { (my $self, my $part) = @_; my $acc = $self->parmval("acc", $part); @@ -4021,6 +4191,11 @@ sub checkedin { } } # this should work exactly like the copy in lonhomework.pm +# Why is there a copy in lonhomework? Why not centralized? +# +# TODO: Centralize duedate. +# + sub duedate { (my $self, my $part) = @_; my $date; @@ -4100,8 +4275,8 @@ sub weight { my $self = shift; my $part = shift; if (!defined($part)) { $part = '0'; } my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', - $self->symb(), $env{'user.domain'}, - $env{'user.name'}, + $self->symb(), $self->{DOMAIN}, + $self->{USERNAME}, $env{'request.course.sec'}); return $weight; } @@ -4129,7 +4304,7 @@ sub getReturnHash { my $self = shift; if (!defined($self->{RETURN_HASH})) { - my %tmpHash = &Apache::lonnet::restore($self->symb()); + my %tmpHash = &Apache::lonnet::restore($self->symb(),undef,$self->{DOMAIN},$self->{USERNAME}); $self->{RETURN_HASH} = \%tmpHash; } } @@ -4954,7 +5129,7 @@ sub check_for_slot { my $taskstatus = $self->taskstatus(); $is_correct = (($taskstatus eq 'pass') || ($self->solved() =~ /^correct_/)); - $got_grade = ($self->solved() =~ /^(?:pass|fail)$/); + $got_grade = ($taskstatus =~ /^(?:pass|fail)$/); } else { $got_grade = 1; $is_correct = ($self->solved() =~ /^correct_/);