--- loncom/interface/lonnavmaps.pm 2005/11/16 21:09:33 1.348 +++ loncom/interface/lonnavmaps.pm 2006/03/14 22:17:20 1.369 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.348 2005/11/16 21:09:33 albertel Exp $ +# $Id: lonnavmaps.pm,v 1.369 2006/03/14 22:17:20 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -57,7 +57,7 @@ my %statusIconMap = $resObj->CLOSED => '', $resObj->OPEN => 'navmap.open.gif', $resObj->CORRECT => 'navmap.correct.gif', - $resObj->PARTIALLY_CORRECT => 'navmap.ellipsis.gif', + $resObj->PARTIALLY_CORRECT => 'navmap.partial.gif', $resObj->INCORRECT => 'navmap.wrong.gif', $resObj->ATTEMPTED => 'navmap.ellipsis.gif', $resObj->ERROR => '' @@ -530,7 +530,8 @@ sub getDescription { if ($status == $res->PAST_DUE_NO_ANSWER) { return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end'); } - if ($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) { + if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) + && $res->handgrade($part) ne 'yes') { return &mt("Answer available"); } if ($status == $res->EXCUSED) { @@ -1065,7 +1066,6 @@ sub render_resource { # it will be quoted with ' in the href. my ($left,$right) = split(/\?/, $link); - $left =~ s/'/\\'/g; $link = $left.'?'.$right; my $src = $resource->src(); @@ -1085,7 +1085,7 @@ sub render_resource { # links to open and close the folder - my $linkopen = ""; + my $linkopen = ""; my $linkclose = ""; @@ -1095,7 +1095,7 @@ sub render_resource { if ($resource->is_problem()) { if ($part eq '0' || $params->{'condensed'}) { - $icon ='  '; + $icon =''.&mt('Problem').''; } else { $icon = $params->{'indentString'}; } @@ -1112,13 +1112,14 @@ sub render_resource { } my $folderType = $resource->is_sequence() ? 'folder' : 'page'; - + my $title=$resource->title; + $title=~s/\"/\"/g; if (!$params->{'resource_no_folder_link'}) { $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; - $icon = "".
-		($nowOpen ? "; + $icon = "\""."; - $linkopen = "{'url'} . '?' . $params->{'queryString'} . '&filter='; $linkopen .= ($nowOpen xor $it->{CONDITION}) ? addToFilter($filter, $mapId) : @@ -1128,14 +1129,14 @@ sub render_resource { &Apache::lonnet::escape($params->{'here'}) . '&jump=' . &Apache::lonnet::escape($resource->symb()) . - "&folderManip=1'>"; + "&folderManip=1\">"; } else { # Don't allow users to manipulate folder $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.nomanip.gif'; - $icon = "".
-		($nowOpen ? "; + $icon = "\""."; $linkopen = ""; $linkclose = ""; @@ -1143,10 +1144,10 @@ sub render_resource { } if ($resource->randomout()) { - $nonLinkedText .= ' (hidden) '; + $nonLinkedText .= ' ('.&mt('hidden').') '; } if (!$resource->condval()) { - $nonLinkedText .= ' (conditionally hidden) '; + $nonLinkedText .= ' ('.&mt('conditionally hidden').') '; } # We're done preparing and finally ready to start the rendering @@ -1178,13 +1179,13 @@ sub render_resource { if ($resource->is_problem() && $part ne '0' && !$params->{'condensed'}) { my $displaypart=$resource->part_display($part); - $partLabel = " (Part: $displaypart)"; + $partLabel = " (".&mt('Part: [_1]', $displaypart).")"; if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); } $title = ""; } if ($params->{'condensed'} && $resource->countParts() > 1) { - $nonLinkedText .= ' (' . $resource->countParts() . ' parts)'; + $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')'; } my $target; @@ -1192,7 +1193,7 @@ sub render_resource { $target=' target="loncapaclient" '; } if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) { - $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; + $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } else { $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } @@ -1209,7 +1210,7 @@ sub render_communication_status { 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()) { @@ -1263,7 +1264,7 @@ sub render_quick_status { if ($env{'environment.remotenavmap'} eq 'on') { $target=' target="loncapaclient" '; } - my $linkopen = ""; + my $linkopen = ""; my $linkclose = ""; if ($resource->is_problem() && @@ -1472,8 +1473,8 @@ sub render { $navmap = Apache::lonnavmaps::navmap->new(); if (!defined($navmap)) { # no londer in course - return 'No course selected
- Select a course
'; + return ''.&mt('No course selected').'
+ '.&mt('Select a course').'
'; } } @@ -2066,15 +2067,16 @@ In order of increasing complexity and po =over 4 -=item * C<$navmap-EgetByX>, where X is B, B, B or B. This provides +=item * C<$navmap-EgetByX>, where X is B, B or B and getResourceByUrl. This provides various ways to obtain resource objects, based on various identifiers. Use this when you want to request information about one object or a handful of resources you already know the identities of, from some other source. For more about Ids, Symbs, and MapPcs, see the Resource documentation. Note that Url should be a B, - not your first choice; it only works when there is only one + not your first choice; it only really works when there is only one instance of the resource in the course, which only applies to - maps, and even that may change in the future. + maps, and even that may change in the future (see the B + documentation for more details.) =item * CretrieveResources(args)>. This retrieves resources matching some criterion and returns them @@ -2231,9 +2233,9 @@ sub generate_email_discuss_status { if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) { my $plain= &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); - if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { - my ($what,$url)=($1,$2); - if ($what eq 'Error') { + if ($plain=~/ \[([^\]]+)\]\:/) { + my $url=$1; + if ($plain=~/\:Error \[/) { $error{$url}.=','.$msgid; } else { $feedback{$url}.=','.$msgid; @@ -2266,6 +2268,27 @@ sub get_user_data { $self->{RETRIEVED_USER_DATA} = 1; } +sub get_discussion_data { + my $self = shift; + if ($self->{RETRIEVED_DISCUSSION_DATA}) { + return $self->{DISCUSSION_DATA}; + } + + $self->generate_email_discuss_status(); + + my $cid=$env{'request.course.id'}; + my $cdom=$env{'course.'.$cid.'.domain'}; + my $cnum=$env{'course.'.$cid.'.num'}; + # Retrieve discussion data for resources in course + my %discussion_data = &Apache::lonnet::dumpstore($cid,$cdom,$cnum); + + + $self->{DISCUSSION_DATA} = \%discussion_data; + $self->{RETRIEVED_DISCUSSION_DATA} = 1; + return $self->{DISCUSSION_DATA}; +} + + # Internal function: Takes a key to look up in the nav hash and implements internal # memory caching of that key. sub navhash { @@ -2321,6 +2344,61 @@ sub hasDiscussion { } } +sub last_post_time { + my $self = shift; + my $symb = shift; + my $ressymb = $self->wrap_symb($symb); + return $self->{DISCUSSION_TIME}->{$ressymb}; +} + +sub unread_discussion { + my $self = shift; + my $symb = shift; + + $self->get_discussion_data(); + + my $ressymb = $self->wrap_symb($symb); + + my $version = $self->{DISCUSSION_DATA}{'version:'.$ressymb}; + if (!$version) { return; } + + my $prevread = $self->{LAST_READ}{$ressymb}; + + my $unreadcount = 0; + my $hiddenflag = 0; + my $deletedflag = 0; + my ($hidden,$deleted); + + my %subjects; + + for (my $id=$version; $id>0; $id--) { + my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$ressymb}; + my @keys=split(/:/,$vkeys); + if (grep(/^hidden$/ ,@keys)) { + if (!$hiddenflag) { + $hidden = $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':hidden'}; + $hiddenflag = 1; + } + } elsif (grep(/^deleted$/,@keys)) { + if (!$deletedflag) { + $deleted = $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':deleted'}; + $deletedflag = 1; + } + } else { + if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./) + && $prevread < $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':timestamp'}) { + $unreadcount++; + $subjects{$unreadcount}= + $id.': '.$self->{DISCUSSION_DATA}{$id.':'.$ressymb.':subject'}; + } + } + } + if (wantarray) { + return ($unreadcount,\%subjects); + } + return $unreadcount +} + sub wrap_symb { my $self = shift; my $symb = shift; @@ -2476,6 +2554,12 @@ sub parmval_real { my $cid=$env{'request.course.id'}; my $csec=$env{'request.course.sec'}; + my $cgroup=''; + my @cgrps=split(/:/,$env{'request.course.groups'}); + if (@cgrps > 0) { + @cgrps = sort(@cgrps); + $cgroup = $cgrps[0]; + } my $uname=$env{'user.name'}; my $udom=$env{'user.domain'}; @@ -2493,6 +2577,10 @@ sub parmval_real { my $mapparm=$mapname.'___(all).'.$what; my $usercourseprefix=$cid; + my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what; + my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm; + my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm; + my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what; my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm; my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm; @@ -2513,6 +2601,12 @@ sub parmval_real { } # ------------------------------------------------------- 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 ($csec and defined($courseopt)) { if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; } if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; } @@ -2563,14 +2657,17 @@ sub parmval_real { =pod -=item * B(url): +=item * B(url,multiple): -Retrieves a resource object by URL of the resource. If passed a -resource object, it will simply return it, so it is safe to use this -method in code like "$res = $navmap->getResourceByUrl($res)", if -you're not sure if $res is already an object, or just a URL. If the -resource appears multiple times in the course, only the first instance -will be returned. As a result, this is probably useful only for maps. +Retrieves a resource object by URL of the resource, unless the optional +multiple parameter is included in wahich caes an array of resource +objects is returned. If passed a resource object, it will simply return +it, so it is safe to use this method in code like +"$res = $navmap->getResourceByUrl($res)" +if you're not sure if $res is already an object, or just a URL. If the +resource appears multiple times in the course, only the first instance +will be returned (useful for maps), unless the multiple parameter has +been included, in which case all instances are returned in an array. =item * B(map, filterFunc, recursive, bailout, showall): @@ -2605,22 +2702,41 @@ Convience method for which will tell whether the map has resources matching the description in the filter function. +=item * B(url): + +Retrieves version infomation for a url. Returns the version (a number, or +the string "mostrecent") for resources which have version information in +the big hash. + =cut sub getResourceByUrl { my $self = shift; my $resUrl = shift; + my $multiple = shift; if (ref($resUrl)) { return $resUrl; } $resUrl = &Apache::lonnet::clutter($resUrl); my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl}; - if ($resId =~ /,/) { - $resId = (split (/,/, $resId))[0]; - } if (!$resId) { return ''; } - return $self->getById($resId); + if ($multiple) { + my @resources = (); + my @resIds = split (/,/, $resId); + foreach my $id (@resIds) { + my $resourceId = $self->getById($id); + if ($resourceId) { + push(@resources,$resourceId); + } + } + return @resources; + } else { + if ($resId =~ /,/) { + $resId = (split (/,/, $resId))[0]; + } + return $self->getById($resId); + } } sub retrieveResources { @@ -2688,10 +2804,16 @@ sub hasResource { return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0; } +sub usedVersion { + my $self = shift; + my $linkurl = shift; + return $self->navhash("version_$linkurl"); +} + 1; package Apache::lonnavmaps::iterator; -use WeakRef; +use Scalar::Util qw(weaken); use Apache::lonnet; =pod @@ -3172,7 +3294,7 @@ sub populateStack { 1; package Apache::lonnavmaps::DFSiterator; -use WeakRef; +use Scalar::Util qw(weaken); use Apache::lonnet; # Not documented in the perldoc: This is a simple iterator that just walks @@ -3356,7 +3478,7 @@ sub populateStack { 1; package Apache::lonnavmaps::resource; -use WeakRef; +use Scalar::Util qw(weaken); use Apache::lonnet; =pod @@ -3535,8 +3657,7 @@ sub kind { my $self=shift; return $self- sub randomout { my $self=shift; return $self->navHash("randomout_", 1); } sub randompick { my $self = shift; - return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb . - '.0.parameter_randompick'}; + return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb().'.0.randompick'}; } sub link { my $self=shift; @@ -3594,9 +3715,8 @@ sub condition { } sub condval { my $self=shift; - my $uri=&Apache::lonnet::deversion(&Apache::lonnet::declutter($self->src())); - my ($pathname,$filename)=($uri=~m|(.*)/([^/]*)|); - $pathname=~s/^adm\/wrapper\///; + my ($pathname,$filename) = + &Apache::lonnet::split_uri_for_cond($self->src()); my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~ /\&\Q$filename\E\:([\d\|]+)\&/); @@ -3655,6 +3775,16 @@ sub retrieveResources { return $self->{NAV_MAP}->retrieveResources(@_); } +sub is_exam { + my ($self,$part) = @_; + if ($self->parmval('type',$part) eq 'exam') { + return 1; + } + if ($self->src() =~ /\.(exam)$/) { + return 1; + } + return 0; +} sub is_html { my $self=shift; my $src = $self->src(); @@ -3667,10 +3797,21 @@ sub is_page { return $self->navHash("is_map_", 1) && $self->navHash("map_type_" . $self->map_pc()) eq 'page'; } +sub is_practice { + my $self=shift; + my ($part) = @_; + if ($self->parmval('type',$part) eq 'practice') { + return 1; + } + return 0; +} sub is_problem { my $self=shift; my $src = $self->src(); - return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) + if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) { + return !($self->is_practice()); + } + return 0; } sub contains_problem { my $self=shift; @@ -3697,6 +3838,11 @@ sub is_survey { } return 0; } +sub is_task { + my $self=shift; + my $src = $self->src(); + return ($src =~ /\.(task)$/) +} sub is_empty_sequence { my $self=shift; @@ -3981,6 +4127,19 @@ Returns a false value if there has been logged in, true if there has. Always returns false if the discussion data was not extracted when the nav map was constructed. +=item * B: + +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: + +returns in scalar context the count of the number of unread discussion +postings + +returns in list context both the count of postings and a hash ref +containing the subjects of all unread postings + =item * B: Gets the feedback for the resource and returns the raw feedback string @@ -4001,6 +4160,16 @@ sub hasDiscussion { return $self->{NAV_MAP}->hasDiscussion($self->symb()); } +sub last_post_time { + my $self = shift; + return $self->{NAV_MAP}->last_post_time($self->symb()); +} + +sub unread_discussion { + my $self = shift; + return $self->{NAV_MAP}->unread_discussion($self->symb()); +} + sub getFeedback { my $self = shift; my $source = $self->src(); @@ -4098,7 +4267,7 @@ sub countResponses { sub responseTypes { my $self = shift; my %responses; - foreach my $part ($self->parts()) { + foreach my $part (@{$self->parts()}) { foreach my $responsetype ($self->responseType($part)) { $responses{$responsetype}++ if (defined($responsetype)); } @@ -4210,7 +4379,8 @@ sub extractParts { # 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_(.*)/) { + if ($_ =~ /^([a-zA-Z]+)response_(.*)/ + || $_ =~ /^(Task)_(.*)/) { my $responseType = $1; my $partStuff = $2; my $partIdSoFar = '';