+
+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 [_2] 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; }
@@ -1097,7 +881,13 @@ sub render_resource {
if ($resource->is_problem()) {
if ($part eq '0' || $params->{'condensed'}) {
- $icon ='';
+ $icon = '';
} else {
$icon = $params->{'indentString'};
}
@@ -1122,16 +912,16 @@ sub render_resource {
($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";
$linkopen = "{'url'} . '?' .
- $params->{'queryString'} . '&filter=';
+ $params->{'queryString'} . '&filter=';
$linkopen .= ($nowOpen xor $it->{CONDITION}) ?
addToFilter($filter, $mapId) :
removeFromFilter($filter, $mapId);
- $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType='
- . $params->{'hereType'} . '&here=' .
- &Apache::lonnet::escape($params->{'here'}) .
- '&jump=' .
- &Apache::lonnet::escape($resource->symb()) .
- "&folderManip=1\">";
+ $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType='
+ . $params->{'hereType'} . '&here=' .
+ &escape($params->{'here'}) .
+ '&jump=' .
+ &escape($resource->symb()) .
+ "&folderManip=1\">";
} else {
# Don't allow users to manipulate folder
@@ -1151,9 +941,12 @@ sub render_resource {
if (!$resource->condval()) {
$nonLinkedText .= ' ('.&mt('conditionally hidden').') ';
}
-
+ 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 $result = " | ";
my $indentLevel = $params->{'indentLevel'};
if ($newBranchText) { $indentLevel--; }
@@ -1173,8 +966,8 @@ sub render_resource {
# Is this the current resource?
if (!$params->{'displayedHereMarker'} &&
$resource->symb() eq $params->{'here'} ) {
- $curMarkerBegin = '>';
- $curMarkerEnd = '<';
+ $curMarkerBegin = '>';
+ $curMarkerEnd = '<';
$params->{'displayedHereMarker'} = 1;
}
@@ -1182,7 +975,7 @@ sub render_resource {
!$params->{'condensed'}) {
my $displaypart=$resource->part_display($part);
$partLabel = " (".&mt('Part: [_1]', $displaypart).")";
- if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); }
+ if ($link!~/\#/) { $link.='#'.&escape($part); }
$title = "";
}
@@ -1217,17 +1010,17 @@ sub render_communication_status {
my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
if ($resource->hasDiscussion()) {
$discussionHTML = $linkopen .
- '' .
+ '' .
$linkclose;
}
if ($resource->getFeedback()) {
my $feedback = $resource->getFeedback();
- foreach (split(/\,/, $feedback)) {
- if ($_) {
+ foreach my $msgid (split(/\,/, $feedback)) {
+ if ($msgid) {
$feedbackHTML .= ' '
- . ''
+ . '';
}
}
@@ -1236,13 +1029,13 @@ sub render_communication_status {
if ($resource->getErrors()) {
my $errors = $resource->getErrors();
my $errorcount = 0;
- foreach (split(/,/, $errors)) {
+ foreach my $msgid (split(/,/, $errors)) {
last if ($errorcount>=10); # Only output 10 bombs maximum
- if ($_) {
+ if ($msgid) {
$errorcount++;
$errorHTML .= ' '
- . ''
+ . '';
}
}
@@ -1252,7 +1045,7 @@ sub render_communication_status {
$discussionHTML = $feedbackHTML = $errorHTML = '';
}
- return " | $discussionHTML$feedbackHTML$errorHTML | ";
+ return "$discussionHTML$feedbackHTML$errorHTML | ";
}
sub render_quick_status {
@@ -1277,7 +1070,7 @@ sub render_quick_status {
if ($icon) {
my $location=
&Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
- $result .= "$linkopen$linkclose | \n";
+ $result .= "$linkopen$linkclose | \n";
} else {
$result .= " | \n";
}
@@ -1289,12 +1082,12 @@ sub render_quick_status {
}
sub render_long_status {
my ($resource, $part, $params) = @_;
- my $result = "\n";
+ my $result = " | \n";
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) ||
@@ -1304,14 +1097,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
@@ -1346,7 +1142,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 ' | | '; }
@@ -1445,9 +1240,9 @@ sub render {
# marker
my $filterHash = {};
# Figure out what we're not displaying
- foreach (split(/\,/, $env{"form.filter"})) {
- if ($_) {
- $filterHash->{$_} = "1";
+ foreach my $item (split(/\,/, $env{"form.filter"})) {
+ if ($item) {
+ $filterHash->{$item} = "1";
}
}
@@ -1475,7 +1270,7 @@ sub render {
$navmap = Apache::lonnavmaps::navmap->new();
if (!defined($navmap)) {
# no londer in course
- return ''.&mt('No course selected').'
+ return ''.&mt('No course selected').'
'.&mt('Select a course').'
';
}
}
@@ -1553,7 +1348,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'});
}
}
@@ -1590,7 +1385,6 @@ sub render {
# Print key?
if ($printKey) {
$result .= '';
- my $date=localtime;
$result.='Key: | ';
my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
if ($navmap->{LAST_CHECK}) {
@@ -1613,13 +1407,13 @@ sub render {
if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
my ($link,$text);
if ($condition) {
- $link='"navmaps?condition=0&filter=&'.$queryString.
- '&here='.&Apache::lonnet::escape($here).'"';
- $text='Close All Folders';
+ $link='"navmaps?condition=0&filter=&'.$queryString.
+ '&here='.&escape($here).'"';
+ $text='Close all folders';
} else {
- $link='"navmaps?condition=1&filter=&'.$queryString.
- '&here='.&Apache::lonnet::escape($here).'"';
- $text='Open All Folders';
+ $link='"navmaps?condition=1&filter=&'.$queryString.
+ '&here='.&escape($here).'"';
+ $text='Open all folders';
}
if ($args->{'caller'} eq 'navmapsdisplay') {
&add_linkitem($args->{'linkitems'},'changefolder',
@@ -1664,7 +1458,7 @@ END
if ($args->{'caller'} eq 'navmapsdisplay') {
$result .= ''.
- &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').' | ';
+ &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'';
if ($env{'environment.remotenavmap'} ne 'on') {
$result .= ' | ';
} else {
@@ -1929,7 +1723,7 @@ END
my $srcHasQuestion = $src =~ /\?/;
$args->{"resourceLink"} = $src.
($srcHasQuestion?'&':'?') .
- 'symb=' . &Apache::lonnet::escape($symb).$anchor;
+ 'symb=' . &escape($symb).$anchor;
}
# Now, we've decided what parts to show. Loop through them and
# show them.
@@ -2052,6 +1846,14 @@ ENDBLOCK
1;
+
+
+
+
+
+
+
+
package Apache::lonnavmaps::navmap;
=pod
@@ -2121,6 +1923,7 @@ See iterator documentation below.
use strict;
use GDBM_File;
use Apache::lonnet;
+use LONCAPA;
sub new {
# magic invocation to create a class instance
@@ -2220,10 +2023,10 @@ sub generate_email_discuss_status {
my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
$env{'user.domain'},$env{'user.name'},'lastread');
my %lastreadtime = ();
- foreach (keys %lastread) {
- my $key = $_;
- $key =~ s/_lastread$//;
- $lastreadtime{$key} = $lastread{$_};
+ foreach my $key (keys %lastread) {
+ my $shortkey = $key;
+ $shortkey =~ s/_lastread$//;
+ $lastreadtime{$shortkey} = $lastread{$key};
}
my %feedback=();
@@ -2233,22 +2036,36 @@ sub generate_email_discuss_status {
foreach my $msgid (@keys) {
if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
- my $plain=
- &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));
- if ($plain=~/ \[([^\]]+)\]\:/) {
- my $url=$1;
- if ($plain=~/\:Error \[/) {
- $error{$url}.=','.$msgid;
- } else {
- $feedback{$url}.=','.$msgid;
- }
- }
+ my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
+ $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);
+ &Apache::lonenc::check_decrypt(\$symb);
+ if (($fromcid ne '') && ($fromcid ne $cid)) {
+ next;
+ }
+ if (defined($symb)) {
+ if (defined($error) && $error == 1) {
+ $error{$symb}.=','.$msgid;
+ } else {
+ $feedback{$symb}.=','.$msgid;
+ }
+ } else {
+ my $plain=
+ &LONCAPA::unescape(&LONCAPA::unescape($msgid));
+ if ($plain=~/ \[([^\]]+)\]\:/) {
+ my $url=$1;
+ if ($plain=~/\:Error \[/) {
+ $error{$url}.=','.$msgid;
+ } else {
+ $feedback{$url}.=','.$msgid;
+ }
+ }
+ }
}
}
- #url's of resources that have feedbacks
+ #symbs of resources that have feedbacks (will be urls pre-2.3)
$self->{FEEDBACK} = \%feedback;
- #or errors
+ #or errors (will be urls pre 2.3)
$self->{ERROR_MSG} = \%error;
$self->{DISCUSSION_TIME} = \%discussiontime;
$self->{EMAIL_STATUS} = \%emailstatus;
@@ -2353,26 +2170,25 @@ sub last_post_time {
return $self->{DISCUSSION_TIME}->{$ressymb};
}
-sub unread_discussion {
+sub discussion_info {
my $self = shift;
my $symb = shift;
+ my $filter = shift;
$self->get_discussion_data();
my $ressymb = $self->wrap_symb($symb);
# keys used to store bulletinboard postings use 'unwrapped' symb.
- my $discsymb = $self->unwrap_symb($ressymb);
+ my $discsymb = &escape($self->unwrap_symb($ressymb));
my $version = $self->{DISCUSSION_DATA}{'version:'.$discsymb};
if (!$version) { return; }
my $prevread = $self->{LAST_READ}{$ressymb};
- my $unreadcount = 0;
+ my $count = 0;
my $hiddenflag = 0;
my $deletedflag = 0;
- my ($hidden,$deleted);
-
- my %subjects;
+ my ($hidden,$deleted,%info);
for (my $id=$version; $id>0; $id--) {
my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb};
@@ -2388,18 +2204,24 @@ sub unread_discussion {
$deletedflag = 1;
}
} else {
- if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)
- && $prevread < $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
- $unreadcount++;
- $subjects{$unreadcount}=
- $id.': '.$self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};
- }
+ if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) {
+ if ($filter eq 'unread') {
+ if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
+ next;
+ }
+ }
+ $count++;
+ $info{$count}{'subject'} =
+ $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};
+ $info{$count}{'id'} = $id;
+ $info{$count}{'timestamp'} = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'};
+ }
}
}
if (wantarray) {
- return ($unreadcount,\%subjects);
+ return ($count,%info);
}
- return $unreadcount
+ return $count;
}
sub wrap_symb {
@@ -2430,23 +2252,48 @@ sub unwrap_symb {
sub getFeedback {
my $self = shift;
my $symb = shift;
+ my $source = shift;
$self->generate_email_discuss_status();
if (!defined($self->{FEEDBACK})) { return ""; }
- return $self->{FEEDBACK}->{$symb};
+ my $feedback;
+ if ($self->{FEEDBACK}->{$symb}) {
+ $feedback = $self->{FEEDBACK}->{$symb};
+ if ($self->{FEEDBACK}->{$source}) {
+ $feedback .= ','.$self->{FEEDBACK}->{$source};
+ }
+ } else {
+ if ($self->{FEEDBACK}->{$source}) {
+ $feedback = $self->{FEEDBACK}->{$source};
+ }
+ }
+ return $feedback;
}
# Private method: Get the errors for that resource (by source).
sub getErrors {
my $self = shift;
+ my $symb = shift;
my $src = shift;
$self->generate_email_discuss_status();
if (!defined($self->{ERROR_MSG})) { return ""; }
- return $self->{ERROR_MSG}->{$src};
+
+ my $errors;
+ if ($self->{ERROR_MSG}->{$symb}) {
+ $errors = $self->{ERROR_MSG}->{$symb};
+ if ($self->{ERROR_MSG}->{$src}) {
+ $errors .= ','.$self->{ERROR_MSG}->{$src};
+ }
+ } else {
+ if ($self->{ERROR_MSG}->{$src}) {
+ $errors = $self->{ERROR_MSG}->{$src};
+ }
+ }
+ return $errors;
}
=pod
@@ -2472,7 +2319,7 @@ the given map. This is one of the proper
# The strategy here is to cache the resource objects, and only construct them
# as we use them. The real point is to prevent reading any more from the tied
-# hash then we have to, which should hopefully alleviate speed problems.
+# hash than we have to, which should hopefully alleviate speed problems.
sub getById {
my $self = shift;
@@ -2546,16 +2393,28 @@ sub finishResource {
# the actual lookup; parmval caches the results.
sub parmval {
my $self = shift;
- my ($what,$symb)=@_;
+ my ($what,$symb,$recurse)=@_;
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);
+ 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 {
@@ -2576,11 +2435,11 @@ 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);
-
+ $mapname = &Apache::lonnet::deversion($mapname);
# ----------------------------------------------------- Cascading lookup scheme
my $rwhat=$what;
$what=~s/^parameter\_//;
@@ -2608,48 +2467,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);
@@ -2659,13 +2519,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; }
- my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what);
- if (defined($pack_def)) { return $pack_def; }
- return '';
+ if ($recurse) { return []; }
+ my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat);
+ if (defined($pack_def)) { return [$pack_def,'resource']; }
+ return [''];
}
=pod
@@ -2673,7 +2533,7 @@ sub parmval_real {
=item * B(url,multiple):
Retrieves a resource object by URL of the resource, unless the optional
-multiple parameter is included in wahich caes an array of resource
+multiple parameter is included in which case an array of resource
objects is returned. If passed a resource object, it will simply return
it, so it is safe to use this method in code like
"$res = $navmap->getResourceByUrl($res)"
@@ -2708,7 +2568,7 @@ all matching resources.
=item * B(map, filterFunc, recursive, showall):
-Convience method for
+Convenience method for
scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
@@ -2786,6 +2646,10 @@ sub retrieveResources {
my @resources = ();
+ if (&$filterFunc($map)) {
+ push(@resources, $map);
+ }
+
# Run down the iterator and collect the resources.
my $curRes;
@@ -2795,7 +2659,7 @@ sub retrieveResources {
next;
}
- push @resources, $curRes;
+ push(@resources, $curRes);
if ($bailout) {
return @resources;
@@ -2942,7 +2806,7 @@ be the tokens described above.
Also note there is some old code floating around that trys to track
the depth of the iterator to see when it's done; do not copy that
-code. It is difficult to get right and harder to understand then
+code. It is difficult to get right and harder to understand than
this. They should be migrated to this new style.
=cut
@@ -3126,6 +2990,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
@@ -3227,7 +3095,7 @@ sub next {
}
# Is this the end of a branch? If so, all of the resources examined above
- # led to lower levels then the one we are currently at, so we push a END_BRANCH
+ # led to lower levels than the one we are currently at, so we push a END_BRANCH
# marker onto the stack so we don't forget.
# Example: For the usual A(BC)(DE)F case, when the iterator goes down the
# BC branch and gets to C, it will see F as the only next resource, but it's
@@ -3436,9 +3304,9 @@ sub next {
# filter the next possibilities to remove things we've
# already seen.
- foreach (@$nextUnfiltered) {
- if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {
- push @$next, $_;
+ foreach my $item (@$nextUnfiltered) {
+ if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) {
+ push @$next, $item;
}
}
@@ -3563,7 +3431,7 @@ X X
All resources also have Bs, which uniquely identify a resource
in a course. Many internal LON-CAPA functions expect a symb. A symb
carries along with it the URL of the resource, and the map it appears
-in. Symbs are much larger then resource IDs.
+in. Symbs are much larger than resource IDs.
=cut
@@ -3639,8 +3507,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:
@@ -3670,7 +3543,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;
@@ -3748,6 +3627,7 @@ sub compTitle {
}
return $title;
}
+
=pod
B
@@ -3790,7 +3670,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)$/) {
@@ -3813,7 +3694,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;
@@ -3826,6 +3708,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()) {
@@ -3834,16 +3725,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;
- my $src = $self->src();
return $self->navHash("is_map_", 1) &&
$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)$/) {
@@ -3911,7 +3811,7 @@ Returns a string with the type of the ma
sub map_finish {
my $self = shift;
my $src = $self->src();
- $src = Apache::lonnet::clutter($src);
+ $src = &Apache::lonnet::clutter($src);
my $res = $self->navHash("map_finish_$src", 0);
$res = $self->{NAV_MAP}->getById($res);
return $res;
@@ -3924,7 +3824,7 @@ sub map_pc {
sub map_start {
my $self = shift;
my $src = $self->src();
- $src = Apache::lonnet::clutter($src);
+ $src = &Apache::lonnet::clutter($src);
my $res = $self->navHash("map_start_$src", 0);
$res = $self->{NAV_MAP}->getById($res);
return $res;
@@ -3941,9 +3841,9 @@ sub map_type {
# These functions will be responsible for returning the CORRECT
# VALUE for the parameter, no matter what. So while they may look
-# like direct calls to parmval, they can be more then that.
+# like direct calls to parmval, they can be more than that.
# So, for instance, the duedate function should use the "duedatetype"
-# information, rather then the resource object user.
+# information, rather than the resource object user.
=pod
@@ -4019,16 +3919,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;
@@ -4036,44 +3939,72 @@ sub awarded {
if (!defined($part)) { $part = '0'; }
return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'};
}
+# this should work exactly like the copy in lonhomework.pm
sub duedate {
(my $self, my $part) = @_;
- my $interval=$self->parmval("interval", $part);
- if ($interval) {
- my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);
- if ($first_access) { return ($first_access+$interval); }
+ my $date;
+ my @interval=$self->parmval("interval", $part);
+ my $due_date=$self->parmval("duedate", $part);
+ if ($interval[0] =~ /\d+/) {
+ my $first_access=&Apache::lonnet::get_first_access($interval[1],
+ $self->symb);
+ if (defined($first_access)) {
+ my $interval = $first_access+$interval[0];
+ $date = (!$due_date || $interval < $due_date) ? $interval
+ : $due_date;
+ } else {
+ $date = $due_date;
+ }
+ } else {
+ $date = $due_date;
}
- return $self->parmval("duedate", $part);
+ return $date;
}
sub handgrade {
(my $self, my $part) = @_;
- return $self->parmval("handgrade", $part);
+ my @response_ids = $self->responseIds($part);
+ if (@response_ids) {
+ foreach my $response_id (@response_ids) {
+ my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id);
+ if (lc($handgrade) eq 'yes') {
+ return 'yes';
+ }
+ }
+ }
+ 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';}
@@ -4081,15 +4012,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();
@@ -4145,13 +4078,15 @@ data was not extracted when the nav map
Returns a false value if there hasn't been discussion otherwise returns
unix timestamp of last time a discussion posting (or edit) was made.
-=item * B:
+=item * B:
-returns in scalar context the count of the number of unread discussion
-postings
+optional argument is a filter (currently can be 'unread');
+returns in scalar context the count of the number of discussion postings.
returns in list context both the count of postings and a hash ref
-containing the subjects of all unread postings
+containing information about the postings (subject, id, timestamp) in a hash.
+
+Default is to return counts for all postings. However if called with a second argument set to 'unread', will return information about only unread postings.
=item * B:
@@ -4160,8 +4095,8 @@ for the resource, or the null string if
email data was not extracted when the nav map was constructed. Usually
used like this:
- for (split(/\,/, $res->getFeedback())) {
- my $link = &Apache::lonnet::escape($_);
+ for my $url (split(/\,/, $res->getFeedback())) {
+ my $link = &escape($url);
...
and use the link as appropriate.
@@ -4178,23 +4113,25 @@ sub last_post_time {
return $self->{NAV_MAP}->last_post_time($self->symb());
}
-sub unread_discussion {
- my $self = shift;
- return $self->{NAV_MAP}->unread_discussion($self->symb());
+sub discussion_info {
+ my ($self,$filter) = @_;
+ return $self->{NAV_MAP}->discussion_info($self->symb(),$filter);
}
sub getFeedback {
my $self = shift;
my $source = $self->src();
+ my $symb = $self->symb();
if ($source =~ /^\/res\//) { $source = substr $source, 5; }
- return $self->{NAV_MAP}->getFeedback($source);
+ return $self->{NAV_MAP}->getFeedback($symb,$source);
}
sub getErrors {
my $self = shift;
my $source = $self->src();
+ my $symb = $self->symb();
if ($source =~ /^\/res\//) { $source = substr $source, 5; }
- return $self->{NAV_MAP}->getErrors($source);
+ return $self->{NAV_MAP}->getErrors($symb,$source);
}
=pod
@@ -4355,8 +4292,8 @@ sub extractParts {
$self->{PART_TYPE} = {};
return;
}
- foreach (split(/\,/,$metadata)) {
- if ($_ =~ /^(?:part|Task)_(.*)$/) {
+ foreach my $entry (split(/\,/,$metadata)) {
+ if ($entry =~ /^(?:part|Task)_(.*)$/) {
my $part = $1;
# This floods the logs if it blows up
if (defined($parts{$part})) {
@@ -4381,8 +4318,8 @@ sub extractParts {
# Init the responseIdHash
- foreach (@{$self->{PARTS}}) {
- $responseIdHash{$_} = [];
+ foreach my $part (@{$self->{PARTS}}) {
+ $responseIdHash{$part} = [];
}
# Now, the unfortunate thing about this is that parts, part name, and
@@ -4391,9 +4328,9 @@ sub extractParts {
# So we have to use our knowlege of part names to figure out
# where the part names begin and end, and even then, it is possible
# to construct ambiguous situations.
- foreach (split /,/, $metadata) {
- if ($_ =~ /^([a-zA-Z]+)response_(.*)/
- || $_ =~ /^(Task)_(.*)/) {
+ foreach my $data (split /,/, $metadata) {
+ if ($data =~ /^([a-zA-Z]+)response_(.*)/
+ || $data =~ /^(Task)_(.*)/) {
my $responseType = $1;
my $partStuff = $2;
my $partIdSoFar = '';
@@ -4405,8 +4342,15 @@ sub extractParts {
if ($parts{$partIdSoFar}) {
my @otherChunks = @partChunks[$i+1..$#partChunks];
my $responseId = join('_', @otherChunks);
- push @{$responseIdHash{$partIdSoFar}}, $responseId;
- push @{$responseTypeHash{$partIdSoFar}}, $responseType;
+ if ($self->is_task()) {
+ push(@{$responseIdHash{$partIdSoFar}},
+ $partIdSoFar);
+ } else {
+ push(@{$responseIdHash{$partIdSoFar}},
+ $responseId);
+ }
+ push(@{$responseTypeHash{$partIdSoFar}},
+ $responseType);
}
}
}
@@ -4455,13 +4399,13 @@ the completion information.
Idiomatic usage of these two methods would probably look something
like
- foreach ($resource->parts()) {
- my $dateStatus = $resource->getDateStatus($_);
- my $completionStatus = $resource->getCompletionStatus($_);
+ foreach my $part ($resource->parts()) {
+ my $dateStatus = $resource->getDateStatus($part);
+ my $completionStatus = $resource->getCompletionStatus($part);
or
- my $status = $resource->status($_);
+ my $status = $resource->status($part);
... use it here ...
}
@@ -4752,7 +4696,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() &&