--- loncom/interface/lonfeedback.pm	2004/07/09 23:05:52	1.99
+++ loncom/interface/lonfeedback.pm	2004/07/23 16:56:29	1.103
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Feedback
 #
-# $Id: lonfeedback.pm,v 1.99 2004/07/09 23:05:52 raeburn Exp $
+# $Id: lonfeedback.pm,v 1.103 2004/07/23 16:56:29 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -72,6 +72,7 @@ sub list_discussion {
     if ($mode eq 'board') { $discussiononly=1; }
     unless ($ENV{'request.course.id'}) { return ''; }
     my $crs='/'.$ENV{'request.course.id'};
+    my $cid=$ENV{'request.course.id'};
     if ($ENV{'request.course.sec'}) {
 	$crs.='_'.$ENV{'request.course.sec'};
     }                 
@@ -80,7 +81,9 @@ sub list_discussion {
 	$symb=&Apache::lonnet::symbread();
     }
     unless ($symb) { return ''; }
-
+    my %usernamesort = ();
+    my %namesort =();
+    my %subjectsort = ();
 # backward compatibility (bulletin boards used to be 'wrapped')
     my $ressymb=$symb;
     if ($mode eq 'board') {
@@ -94,7 +97,8 @@ sub list_discussion {
     my $showkey = $ressymb.'_showonlyunread';
     my $visitkey = $ressymb.'_visit';
     my $ondispkey = $ressymb.'_markondisp';
-    my %dischash = &Apache::lonnet::get('nohist_'.$ENV{'request.course.id'}.'_discuss',[$lastkey,$showkey,$visitkey,$ondispkey],$ENV{'user.domain'},$ENV{'user.name'});
+    my $userpickkey = $ressymb.'_userpick';
+    my %dischash = &Apache::lonnet::get('nohist_'.$ENV{'request.course.id'}.'_discuss',[$lastkey,$showkey,$visitkey,$ondispkey,$userpickkey],$ENV{'user.domain'},$ENV{'user.name'});
     my %discinfo = ();
     my $showonlyunread = 0;
     my $markondisp = 0;
@@ -102,9 +106,15 @@ sub list_discussion {
     my $previous = 0;
     my $visit = 0;
     my $newpostsflag = 0;
+    my @posters = split/\&/,$dischash{$userpickkey};
 
 # Retain identification of "NEW" posts identified in last display, if continuing 'previous' browsing of posts.
-    &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['previous']);
+    &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['previous','sortposts','rolefilter','statusfilter','sectionpick','totposters']);
+    my $sortposts = $ENV{'form.sortposts'};
+    my $rolefilter = $ENV{'form.rolefilter'};
+    my $statusfilter = $ENV{'form.statusfilter'};
+    my $sectionpick = $ENV{'form.sectionpick'};
+    my $totposters = $ENV{'form.totposters'};
     $previous = $ENV{'form.previous'};
     if ($previous > 0) {
         $prevread = $previous;
@@ -114,6 +124,34 @@ sub list_discussion {
         }
     }
 
+# Get information about students and non-stundents in course for filtering display of posts
+    my %roleshash = ();
+    my %roleinfo = ();
+    if ($rolefilter) {
+        %roleshash = &Apache::lonnet::dump('nohist_userroles',$ENV{'course.'.$ENV{'request.course.id'}.'.domain'},$ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+        foreach (keys %roleshash) {
+            my ($role,$uname,$udom,$sec) = split/:/,$_;
+            my ($end,$start) = split/:/,$roleshash{$_};
+            my $now = time;
+            my $status = 'Active';
+            if (($now < $start) || ($end > 0 && $now > $end)) {
+                $status = 'Expired';
+            }
+            push @{$roleinfo{$uname.':'.$udom}}, $role.':'.$sec.':'.$status;
+        }
+        my ($classlist) = &Apache::loncoursedata::get_classlist(
+                              $ENV{'request.course.id'},
+                              $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+                              $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+        my $sec_index = &Apache::loncoursedata::CL_SECTION();
+        my $status_index = &Apache::loncoursedata::CL_STATUS();
+        while (my ($student,$data) = each %$classlist) {
+            my ($section,$status) = ($data->[$sec_index],
+                                 $data->[$status_index]);
+            push @{$roleinfo{$student}}, 'st:'.$section.':'.$status;
+        }
+    }
+
 # Get discussion display default settings for user
     my %userenv = &Apache::lonnet::get('environment',['discdisplay','discmarkread'],$ENV{'user.domain'},$ENV{'user.name'});
     my $discdisplay=$userenv{'discdisplay'};
@@ -146,6 +184,8 @@ sub list_discussion {
     my $viewgrades=(&Apache::lonnet::allowed('vgr',$crs)
 	&& ($symb=~/\.(problem|exam|quiz|assess|survey|form)$/));
     my @discussionitems=();
+    my %shown = ();
+    my @posteridentity=();
     my %contrib=&Apache::lonnet::restore($ressymb,$ENV{'request.course.id'},
 			  $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
 			  $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
@@ -182,14 +222,21 @@ sub list_discussion {
                 $newpostsflag = 1;
             }
 	    my $hidden=($contrib{'hidden'}=~/\.$idx\./);
+            my $studenthidden=($contrib{'studenthidden'}=~/\.$idx\./);
 	    my $deleted=($contrib{'deleted'}=~/\.$idx\./);
 	    my $origindex='0.';
-	    if (($contrib{$idx.':replyto'}) && ($ENV{'environment.threadeddiscussion'})) {
+            my $numoldver=0;
+	    if ($contrib{$idx.':replyto'}) {
+                if ( (($ENV{'environment.threadeddiscussion'}) && (($sortposts eq '') || ($sortposts eq 'ascdate'))) || ($sortposts eq 'thread')) {
 # this is a follow-up message
-		$original[$idx]=$original[$contrib{$idx.':replyto'}];
-		$depth[$idx]=$depth[$contrib{$idx.':replyto'}]+1;
-		$origindex=$index[$contrib{$idx.':replyto'}];
-		if ($depth[$idx]>$maxdepth) { $maxdepth=$depth[$idx]; }
+		    $original[$idx]=$original[$contrib{$idx.':replyto'}];
+		    $depth[$idx]=$depth[$contrib{$idx.':replyto'}]+1;
+		    $origindex=$index[$contrib{$idx.':replyto'}];
+		    if ($depth[$idx]>$maxdepth) { $maxdepth=$depth[$idx]; }
+                } else {
+                    $original[$idx]=0;
+                    $depth[$idx]=0;
+                }
 	    } else {
 # this is an original message
 		$original[$idx]=0;
@@ -202,13 +249,21 @@ sub list_discussion {
 	    }
 	    unless ((($hidden) && (!$seeid)) || ($deleted)) {
 		$visible++;
+                if ($contrib{$idx.':history'}) {
+                    if ($contrib{$idx.':history'} =~ /:/) {
+                        my @oldversions = split/:/,$contrib{$idx.':history'};
+                        $numoldver = @oldversions;
+                    } else {
+                        $numoldver = 1;
+                    } 
+                }
 		my $message=$contrib{$idx.':message'};
 		$message=~s/\n/\<br \/\>/g;
-		$message=&Apache::lontexconvert::msgtexconverted($message);
+		$message=&Apache::lontexconvert::msgtexconverted($message,undef,$numoldver);
                 my $subject=$contrib{$idx.':subject'};
                 if (defined($subject)) {
                     $subject=~s/\n/\<br \/\>/g;
-                    $subject=&Apache::lontexconvert::msgtexconverted($subject);
+                    $subject=&Apache::lontexconvert::msgtexconverted($subject,undef,$numoldver);
                 }
 		if ($contrib{$idx.':attachmenturl'}) {
 		    my ($fname)
@@ -222,6 +277,9 @@ sub list_discussion {
 		if ($message) {
 		    if ($hidden) {
 			$message='<font color="#888888">'.$message.'</font>';
+                        if ($studenthidden) {
+                            $message .='<br /><br />Deleted by poster (student).';
+                        }
 		    }
 		    my $screenname=&Apache::loncommon::screenname(
 					    $contrib{$idx.':sendername'},
@@ -231,6 +289,20 @@ sub list_discussion {
 					    $contrib{$idx.':senderdomain'});
 		    
 		    my $sender=&mt('Anonymous');
+# Set up for sorting by subject
+                    if ($contrib{$idx.':subject'} eq '') {
+                        if (defined($subjectsort{'__No subject'})) {
+                            push @{$subjectsort{'__No subject'}}, $idx;
+                        } else {
+                            @{$subjectsort{'__No subject'}} = ("$idx");
+                        }
+                    } else {
+                        if (defined($subjectsort{$contrib{$idx.':subject'}})) {
+                            push @{$subjectsort{$contrib{$idx.':subject'}}}, $idx;
+                        } else {
+                            @{$subjectsort{$contrib{$idx.':subject'}}} = ("$idx");
+                        }
+                    }
 		    if ((!$contrib{$idx.':anonymous'}) || ($seeid)) {
 			$sender=&Apache::loncommon::aboutmewrapper(
 					 $plainname,
@@ -242,14 +314,57 @@ sub list_discussion {
 			    $sender.=' ['.&mt('anonymous').'] '.
 				$screenname;
 			}
-			if ($seeid) {
-			    if ($hidden) {
-				$sender.=' <a href="/adm/feedback?unhide='.
-				    $ressymb.':::'.$idx;
+# Set up for sorting by domain, then username
+                        unless (defined($usernamesort{$contrib{$idx.':senderdomain'}})) {
+                            %{$usernamesort{$contrib{$idx.':senderdomain'}}} = ();
+                        }
+                        if (defined($usernamesort{$contrib{$idx.':senderdomain'}}{$contrib{$idx.':sendername'}})) {
+                            push @{$usernamesort{$contrib{$idx.':senderdomain'}}{$contrib{$idx.':sendername'}}}, $idx;
+                        } else {
+                            @{$usernamesort{$contrib{$idx.':senderdomain'}}{$contrib{$idx.':sendername'}}} = ("$idx");
+                        }
+# Set up for sorting by last name, then first name
+                        my %names = &Apache::lonnet::get('environment',['firstname','lastname'],
+                                  $contrib{$idx.':senderdomain'},$contrib{$idx.':sendername'});
+                        my $lastname = $names{'lastname'};
+                        my $firstname = $names{'firstname'};
+                        if ($lastname eq '') {
+                            $lastname = '_';
+                        }
+                        if ($firstname eq '') {
+                            $firstname = '_';
+                        }
+                        unless (defined($namesort{$lastname})) {
+                            %{$namesort{$lastname}} = ();
+                        }
+                        if (defined($namesort{$lastname}{$firstname})) {
+                            push @{$namesort{$lastname}{$firstname}}, $idx;
+                        } else {
+                            @{$namesort{$lastname}{$firstname}} = ("$idx");
+                        }
+                        if ($ENV{"course.$cid.allow_discussion_post_editing"} =~ m/yes/i) {
+                            if (($ENV{'user.domain'} eq $contrib{$idx.':senderdomain'}) && ($ENV{'user.name'} eq $contrib{$idx.':sendername'})) {
+                                $sender.=' <a href="/adm/feedback?editdisc='.
+                                    $ressymb.':::'.$idx;
                                 if ($newpostsflag) {
                                     $sender .= '&previous='.$prevread;
                                 }
-                                $sender .= '">'.&mt('Make Visible').'</a>';
+                                $sender .= '" '.$target.'>'.&mt('Edit').'</a>';                                      unless ($seeid) {
+                                    $sender.=" <a href=\"javascript:studentdelete('$ressymb','$idx','$newpostsflag','$prevread')";
+                                    $sender .= '">'.&mt('Delete').'</a>';
+                                }
+                            }
+                        }
+			if ($seeid) {
+			    if ($hidden) {
+                                unless ($studenthidden) {
+				    $sender.=' <a href="/adm/feedback?unhide='.
+				        $ressymb.':::'.$idx;
+                                    if ($newpostsflag) {
+                                        $sender .= '&previous='.$prevread;
+                                    }
+                                    $sender .= '">'.&mt('Make Visible').'</a>';
+                                }
 			    } else {
 				$sender.=' <a href="/adm/feedback?hide='.
 				    $ressymb.':::'.$idx;
@@ -259,16 +374,34 @@ sub list_discussion {
                                 $sender .= '">'.&mt('Hide').'</a>';
 			    }                     
 			    $sender.=' <a href="/adm/feedback?deldisc='.
-				$ressymb.':::'.$idx;
-                                if ($newpostsflag) {
-                                    $sender .= '&previous='.$prevread;
-                                }
-                                $sender .= '">'.&mt('Delete').'</a>';
+				    $ressymb.':::'.$idx;
+                            if ($newpostsflag) {
+                                $sender .= '&previous='.$prevread;
+                            }
+                            $sender .= '">'.&mt('Delete').'</a>';
 			}
 		    } else {
 			if ($screenname) {
 			    $sender='<i>'.$screenname.'</i>';
 			}
+# Set up for sorting by domain, then username for anonymous
+                        unless (defined($usernamesort{'__anon'})) {
+                            %{$usernamesort{'__anon'}} = ();
+                        }
+                        if (defined($usernamesort{'__anon'}{'__anon'})) {
+                            push @{$usernamesort{'__anon'}{'__anon'}}, $idx;
+                        } else {
+                            @{$usernamesort{'__anon'}{'__anon'}} = ("$idx");
+                        }
+# Set up for sorting by last name, then first name for anonymous
+                        unless (defined($namesort{'__anon'})) {
+                            %{$namesort{'__anon'}} = ();
+                        }
+                        if (defined($namesort{'__anon'}{'__anon'})) {
+                            push @{$namesort{'__anon'}{'__anon'}}, $idx;
+                        } else {
+                            @{$namesort{'__anon'}{'__anon'}} = ("$idx");
+                        }
 		    }
 		    if (&discussion_open($status) &&
 			&Apache::lonnet::allowed('pch',
@@ -288,15 +421,63 @@ sub list_discussion {
 		    }
 #figure out at what position this needs to print
 		    my $thisindex=$idx;
-		    if ($ENV{'environment.threadeddiscussion'}) {
-			$thisindex=$origindex.substr('00'.$replies[$depth[$idx]],-2,2);	
+		    if ( (($ENV{'environment.threadeddiscussion'}) && (($sortposts eq '') || ($sortposts eq 'ascdate'))) || ($sortposts eq 'thread')) {
+			$thisindex=$origindex.substr('00'.$replies[$depth[$idx]],-2,2);
 		    }
 		    $alldiscussion{$thisindex}=$idx;
-		    $index[$idx]=$thisindex;
+                    $shown{$idx} = 0;
+                    $index[$idx]=$thisindex;
                     my $spansize = 2;
                     if ($showonlyunread && $prevread > $posttime) {
                         $notshown{$idx} = 1;
                     } else {
+                        my $uname = $contrib{$idx.':sendername'};
+                        my $udom = $contrib{$idx.':senderdomain'};
+                        my $poster = $uname.':'.$udom;
+                        my $rolematch = '';
+                        my $skiptest = 1;
+                        if ($totposters > 0) {
+                            if (grep/^$poster$/,@posters) {
+                                $shown{$idx} = 1;
+                            }
+                        } else {
+                            if ($rolefilter) {
+                                if ($rolefilter eq 'all') {
+                                    $rolematch = '([^:]+)';
+                                } else {
+                                    $rolematch = $rolefilter;
+                                    $skiptest = 0;
+                                }
+                            }
+                            if ($sectionpick) {
+                                if ($sectionpick eq 'all') {
+                                    $rolematch .= ':([^:]*)';
+                                } else {
+                                    $rolematch .= ':'.$sectionpick;
+                                    $skiptest = 0;
+                                }
+                            }
+                            if ($statusfilter) {
+                                if ($statusfilter eq 'all') {
+                                    $rolematch .= ':([^:]+)';
+                                } else {
+                                    $rolematch .= ':'.$statusfilter;
+                                    $skiptest = 0;
+                                }
+                            }
+                            if ($skiptest) {
+                                $shown{$idx} = 1;
+                            } else {
+                                foreach my $role (@{$roleinfo{$poster}}) {
+                                    if ($role =~ m/^$rolematch$/) {
+                                        $shown{$idx} = 1;
+                                        last;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    unless ($notshown{$idx} == 1) {
                         if ($prevread > 0 && $prevread <= $posttime) {
                             $newitem{$idx} = 1;
                             $discussionitems[$idx] .= '
@@ -313,6 +494,20 @@ sub list_discussion {
                             $sender.'</b> '.$vgrlink.' ('.
                             localtime($posttime).')</td></tr>'.
                             '</table><blockquote>'.$message.'</blockquote></p>';
+                        if ($contrib{$idx.':history'}) {
+                            my @postversions = ();
+                            $discussionitems[$idx] .= '<br />'.&mt('This post has been edited by the author.').'<br/>'.&mt('Earlier version(s) were posted on: ');
+                            if ($contrib{$idx.':history'} =~ m/:/) {
+                                @postversions = split/:/,$contrib{$idx.':history'};
+                            } else {
+                                @postversions = ("$contrib{$idx.':history'}");
+                            }
+                            for (my $i=0; $i<@postversions; $i++) {
+                                my $version = $i+1;
+                                $discussionitems[$idx] .= '<b>'.$version.'.</b> - '.&Apache::lonlocal::locallocaltime($postversions[$i]).'  ';
+                            }
+                            $discussionitems[$idx] .= '<br />';
+                        }
                     }
                 }
             }
@@ -367,6 +562,20 @@ sub list_discussion {
 # Print the discusssion
 	if ($outputtarget ne 'tex') {
             my $colspan=$maxdepth+1;
+            $discussion.= qq|
+<script>
+   function studentdelete (symb,idx,newflag,previous) {
+       var symbparm = symb+':::'+idx
+       var prevparm = ""
+       if (newflag == 1) {
+           prevparm = "&previous="+previous
+       }
+       if (confirm("Are you sure you want to delete this post?\\nDeleted posts will no longer be visible to you and other students,\\nbut will continue to be visible to your instructor")) {
+           document.location.href = "/adm/feedback?hide="+symbparm+prevparm
+       }  
+   }
+</script>
+            |;
 	    $discussion.='<table bgcolor="#AAAAAA" cellpadding="2" cellspacing="2" border="0">';
 	    $discussion .='<tr><td bgcolor="#DDDDBB" colspan="'.$colspan.'">'.
 		'<table border="0" width="100%" bgcolor="#DDDDBB"><tr>';
@@ -381,8 +590,20 @@ sub list_discussion {
 		if ($newpostsflag) {
 		    $discussion .= '&previous='.$prevread;
 		}
-		$discussion .='">'.&mt('Chronological View').'</a>&nbsp;&nbsp;</td>';
-	    }
+		$discussion .='">'.&mt('Chronological View').'</a>&nbsp;&nbsp;
+                              <a href= "/adm/feedback?sortfilter='.$ressymb;
+                if ($newpostsflag) {
+                    $discussion .= '&previous='.$prevread;
+                }
+                $discussion .='">'.&mt('Sorting/Filtering options').'</a>&nbsp;&nbsp';
+            } else {
+                $discussion .= '<td align="left">';
+            }
+            $discussion .='<a href= "/adm/feedback?export='.$ressymb;
+            if ($newpostsflag) {
+                $discussion .= '&previous='.$prevread;
+            }
+            $discussion .= '">'.&mt('Export').'?</a>&nbsp;&nbsp;</td>';
 	    if ($newpostsflag) {
 		if (!$markondisp) {
 		    $discussion .='<td align="right"><a href="/adm/feedback?markread='.$ressymb.'">'.&mt('Mark new posts as read').'</a>&nbsp;&nbsp;';
@@ -412,8 +633,38 @@ sub list_discussion {
                          $numhidden.' '.&mt('previously viewed posts').
                          '<br/></td></tr>';
         }
-	foreach (sort { $a <=> $b } keys %alldiscussion) {
-            unless ($notshown{$alldiscussion{$_}} eq '1') {
+
+# Choose sort mechanism
+        my @showposts = ();
+        if ($sortposts eq 'descdate') {
+            @showposts = (sort { $b <=> $a } keys %alldiscussion);
+        } elsif ($sortposts eq 'thread') {
+            @showposts = (sort { $a <=> $b } keys %alldiscussion);
+        } elsif ($sortposts eq 'subject') {
+            foreach (sort keys %subjectsort) {
+                push @showposts, @{$subjectsort{$_}};
+            }
+        } elsif ($sortposts eq 'username') {
+            foreach my $domain (sort keys %usernamesort) {
+                foreach (sort keys %{$usernamesort{$domain}}) {
+                    push @showposts, @{$usernamesort{$domain}{$_}};
+                }
+            }
+        } elsif ($sortposts eq 'lastfirst') {
+            foreach my $last (sort keys %namesort) {
+                 foreach (sort keys %{$namesort{$last}}) {
+                     push @showposts, @{$namesort{$last}{$_}};
+                 }
+            }
+        } else {
+            $sortposts = 'ascdate';
+            @showposts =  (sort { $a <=> $b } keys %alldiscussion);
+        }
+        foreach (@showposts) {
+            unless (($sortposts eq 'thread') || ($sortposts eq 'ascdate' && $ENV{'environment.threadeddiscussion'})) {
+                $alldiscussion{$_} = $_;
+            }
+            unless ( ($notshown{$alldiscussion{$_}} eq '1') || ($shown{$alldiscussion{$_}} == 0) ) {
                 if ($outputtarget ne 'tex') {
 		    $discussion.="\n<tr>";
 		} else {
@@ -439,7 +690,7 @@ sub list_discussion {
 			$threadinsert='<br /><strong>Reply: '.$thisdepth.'</strong>';
 		    }
 		    $discussionitems[$alldiscussion{$_}]=~s/<\/td><td([^>]*)>/$threadinsert<\/td><td TeXwidth="65 mm" align="left">/;
-		    $discussionitems[$alldiscussion{$_}]=~s/<a([^>]+)>(Hide|Delete|Reply|Submissions)<\/a>//g;
+		    $discussionitems[$alldiscussion{$_}]=~s/<a([^>]+)>(Edit|Hide|Delete|Reply|Submissions)<\/a>//g;
                     $discussionitems[$alldiscussion{$_}]=~s/(<b>|<\/b>|<\/a>|<a([^>]+)>)//g;
 		    
                     #FIXME xmlparse can't be safely called from inside xmlparse
@@ -519,7 +770,7 @@ ENDDISCUSS
 		    '<img src="/adm/lonMisc/chat.gif" border="0" />'.
 		    &mt('Post Discussion').'</a></td></tr></table>';
 	    }
-			}
+	}
     }
    return $discussion;
 }
@@ -527,23 +778,84 @@ ENDDISCUSS
 sub mail_screen {
   my ($r,$feedurl,$options) = @_;
   my $bodytag=&Apache::loncommon::bodytag('Resource Feedback and Discussion',
-                                          '','onLoad="window.focus();"');
+                                          '','onLoad="window.focus();setposttype();"');
   my $title=&Apache::lonnet::gettitle($feedurl);
   if (!$title) { $title = $feedurl; }
   my $quote='';
   my $subject = '';
+  my $oldmessage = '';
   my $prevtag = '';
-  if ($ENV{'form.replydisc'}) {
-      my ($symb,$idx)=split(/\:\:\:/,$ENV{'form.replydisc'});
+  my $parentmsg = '';
+  my $anonscript = (<<END);
+  function setposttype() {
+      return
+  }
+END
+  if (($ENV{'form.replydisc'}) || ($ENV{'form.editdisc'})) {
+      my ($symb,$idx);
+      if ($ENV{'form.replydisc'}) {
+          ($symb,$idx)=split(/\:\:\:/,$ENV{'form.replydisc'});
+      } else {
+          ($symb,$idx)=split(/\:\:\:/,$ENV{'form.editdisc'});
+      }
       my %contrib=&Apache::lonnet::restore($symb,$ENV{'request.course.id'},
 					   $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
 					   $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
       unless (($contrib{'hidden'}=~/\.$idx\./) || ($contrib{'deleted'}=~/\.$idx\./)) {
-	  my $message=$contrib{$idx.':message'};
-	  $message=~s/\n/\<br \/\>/g;
-	  $quote='<blockquote>'.&Apache::lontexconvert::msgtexconverted($message).'</blockquote>';
-          if ($idx > 0) {
-              $subject = 'Re: '.$contrib{$idx.':subject'};
+          if ($ENV{'form.replydisc'}) {
+              my $numoldver = 0;
+              if ($contrib{$idx.':history'}) {
+                  if ($contrib{$idx.':history'} =~ /:/) {
+                      my @oldversions = split/:/,$contrib{$idx.':history'};
+                      $numoldver = @oldversions;
+                  } else {
+                      $numoldver = 1;
+                  }
+              }
+	      my $message=$contrib{$idx.':message'};
+	      $message=~s/\n/\<br \/\>/g;
+	      $quote='<blockquote>'.&Apache::lontexconvert::msgtexconverted($message,undef,$numoldver).'</blockquote>';
+              if ($idx > 0) {
+                  if ($contrib{'subject'} =~ /::::\d+::::(.+)$/si) {
+                      $subject = $1;
+                  } else {
+                      $subject = $contrib{$idx.':subject'};
+                  }
+                  $subject = 'Re: '.$subject;
+              }
+          } else {
+              if ($contrib{$idx.':message'} =~ /::::\d+::::(.+)$/si) {
+                  $oldmessage = $1;
+              } else {
+                  $oldmessage = $contrib{$idx.':message'};
+              }
+	      $oldmessage=&HTML::Entities::encode($oldmessage,'<>&"');
+              if ($contrib{$idx.':subject'} =~ /::::\d+::::(.+)$/si) {
+                  $subject = $1;
+              } else {
+                  $subject = $contrib{$idx.':subject'};
+              }
+              if (defined($contrib{$idx.':replyto'})) {
+                  $parentmsg = $contrib{$idx.':replyto'};
+              }
+              my $anonflag = 0;
+              if ($contrib{$idx.':anonymous'}) {
+                  $anonflag = 1;
+              }
+              $anonscript = (<<END);
+  function setposttype () {
+      var currtype = $anonflag
+      if (currtype == 1) {
+          document.mailform.elements.discuss.checked = false
+          document.mailform.elements.anondiscuss.checked = true
+      }
+      if (currtype == 0) {
+          document.mailform.elements.anondiscuss.checked = false
+          document.mailform.elements.discuss.checked = true
+      }
+      return
+  }
+END
           }
       }
       if ($ENV{'form.previous'}) {
@@ -558,7 +870,7 @@ sub mail_screen {
       $onsubmit='document.mailform.onsubmit();';
   }
   my $send=&mt('Send');
-  $r->print(<<ENDDOCUMENT);
+  $r->print(<<END);
 <html>
 <head>
 <title>The LearningOnline Network with CAPA</title>
@@ -606,6 +918,7 @@ $htmlheader
             alert('Please check a feedback type.');
 	}
     }
+    $anonscript
 //-->
 </script>
 </head>
@@ -615,7 +928,18 @@ $bodytag
 enctype="multipart/form-data">
 $prevtag
 <input type="hidden" name="postdata" value="$feedurl" />
+END
+  if ($ENV{'form.replydisc'}) {
+      $r->print(<<END);
 <input type="hidden" name="replydisc" value="$ENV{'form.replydisc'}" />
+END
+  } elsif ($ENV{'form.editdisc'}) {
+     $r->print(<<END);
+<input type="hidden" name="editdisc" value ="$ENV{'form.editdisc'}" />
+<input type="hidden" name="parentmsg" value ="$parentmsg" />
+END
+  }
+  $r->print(<<ENDDOCUMENT);
 Please check at least one of the following feedback types:
 $options<hr />
 $quote
@@ -624,7 +948,7 @@ $quote
 $latexHelp
 Title: <input type="text" name="subject" size="30" value="$subject" /></p>
 <p>
-<textarea name="comment" id="comment" cols="60" rows="10" wrap="hard">
+<textarea name="comment" id="comment" cols="60" rows="10" wrap="hard">$oldmessage
 </textarea></p>
 <p>
 Attachment (128 KB max size): <input type="file" name="attachment" />
@@ -770,6 +1094,253 @@ END
     return;
 }
 
+sub print_sortfilter_options {
+    my ($r,$symb,$previous,$feedurl) = @_;
+ # backward compatibility (bulletin boards used to be 'wrapped')
+    if ($feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) {
+        $feedurl=~s|^/adm/wrapper||;
+    }
+    my @sections = ();
+    my $section_sel = '';
+    my $numsections = 0;
+    my $numvisible = 5;
+    my ($classlist) = &Apache::loncoursedata::get_classlist(
+                              $ENV{'request.course.id'},
+                              $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+                              $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+                                                                                   
+    my $sec_index = &Apache::loncoursedata::CL_SECTION();
+    my $status_index = &Apache::loncoursedata::CL_STATUS();
+    my %sectioncount = ();
+    while (my ($student,$data) = each %$classlist) {
+        my ($section,$status) = ($data->[$sec_index],
+                                 $data->[$status_index]);
+        unless ($section eq '' || $section =~ /^\s*$/) {
+            if (!defined($sectioncount{$section})) {
+                $sectioncount{$section} = 1;
+                $numsections ++;
+            } else {
+                $sectioncount{$section} ++;
+            }
+        }
+    }
+                                                                                   
+    if ($ENV{'request.course.sec'} !~ /^\s*$/) {
+        @sections = ($ENV{'request.course.sec'});
+        $numvisible = 1;
+    } else {
+        @sections = sort {$a cmp $b} keys(%sectioncount);
+        unshift(@sections,'all'); # Put 'all' at the front of the list
+        if ($numsections < 4) {
+            $numvisible = $numsections + 1;
+        }
+    }
+    foreach (@sections) {
+        $section_sel .= "  <option value=\"$_\" />$_\n";
+    }
+                                                                                   
+    my $function = &Apache::loncommon::get_users_function();
+    my $tabcolor = &Apache::loncommon::designparm($function.'.tabbg',
+                                                    $ENV{'user.domain'});
+    my $bodytag=&Apache::loncommon::bodytag('Discussion options',
+                                          '','');
+    my %lt = &Apache::lonlocal::texthash(
+        'diso' => 'Discussion sorting and filtering options',
+        'diop' => 'Display Options',
+        'curr' => 'Current setting ',
+        'actn' => 'Action',
+        'prca' => 'Options can be set that control the sort order of the posts, in addition to which posts are displayed.',
+        'soor' => 'Sort order',
+        'disp' => 'Specific user roles',
+        'actv' => 'Specific role status',
+        'spse' => 'Specific sections',
+        'psub' => 'Pick specific users (by name)',
+        'shal' => 'Show a list of current posters'
+    );
+    $r->print(<<END);
+<html>
+<head>
+<title>$lt{'diso'}</title>
+<meta http-equiv="pragma" content="no-cache" />
+</head>
+$bodytag
+<form name="modifyshown" method="post" action="/adm/feedback">
+<b>$lt{'diso'}</b><br/> $lt{'prca'}
+<br /><br />
+<table border="0">
+ <tr>
+  <td><b>$lt{'soor'}</b></td>
+  <td>&nbsp;</td>
+  <td><b>$lt{'disp'}</b></td>
+  <td>&nbsp;</td>
+  <td><b>$lt{'actv'}</b></td>
+  <td>&nbsp;</td>
+  <td><b>$lt{'spse'}</b></td>
+  <td>&nbsp;</td>
+  <td><b>$lt{'psub'}</b></td>
+ </tr>
+ <tr>
+  <td>
+   <select name="sortposts">
+    <option value="ascdate" />Date order - oldest first
+    <option value="descdate" />Date order - newest first
+    <option value="thread" />Threaded
+    <option value="subject" />By subject
+    <option value="username" />By domain and username
+    <option value="lastfirst" />By last name, first name
+   </select>
+  </td>
+  <td>&nbsp;</td>
+  <td>
+   <select name="rolefilter" multiple="true" size="5">
+    <option value="all" />All users
+    <option value="st" />Students
+    <option value="cc" />Course Coordinators
+    <option value="in" />Instructors
+    <option value="ta" />TAs
+    <option value="pr" />Exam proctors
+    <option value="cr" />Custom roles
+   </select>
+  </td>
+  <td>&nbsp;</td>
+  <td>
+   <select name="statusfilter">
+    <option value="all" />Roles of any status
+    <option value="Active" />Only active roles
+    <option value="Expired" />Only inactive roles
+   </select>
+  </td>
+  <td>&nbsp;</td>
+  <td>
+   <select name="sectionpick" multiple="true" size="$numvisible">
+    $section_sel
+   </select>
+  </td>
+  <td>&nbsp;</td>
+  <td><input type="checkbox" name="posterlist" value="$symb" />$lt{'shal'}</td>
+ </tr>
+</table>
+<br />
+<br />
+<input type="hidden" name="previous" value="$previous" />
+<input type="hidden" name="applysort" value="$symb" />
+<input type="button" name="sub" value="Store Changes" onClick="javascript:document.modifyshown.submit()" />
+<br />
+<br />
+</form>
+</body>
+</html>
+END
+}
+
+sub print_showposters {
+    my ($r,$symb,$previous,$feedurl,$sortposts) = @_;
+ # backward compatibility (bulletin boards used to be 'wrapped')
+    if ($feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) {
+        $feedurl=~s|^/adm/wrapper||;
+    }
+# backward compatibility (bulletin boards used to be 'wrapped')
+    my $ressymb=$symb;
+    if ($ressymb =~ /bulletin___\d+___/) {
+        unless ($ressymb =~ m|bulletin___\d+___adm/wrapper|) {
+            $ressymb=~s|(bulletin___\d+___)|$1adm/wrapper|;
+        }
+    }
+    my $crs='/'.$ENV{'request.course.id'};
+    if ($ENV{'request.course.sec'}) {
+        $crs.='_'.$ENV{'request.course.sec'};
+    }
+    $crs=~s/\_/\//g;
+    my $seeid=&Apache::lonnet::allowed('rin',$crs);
+    my %contrib=&Apache::lonnet::restore($ressymb,$ENV{'request.course.id'},
+                          $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+                          $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+    my %namesort = ();
+    my %postcounts = ();
+    my %lt=&Apache::lonlocal::texthash(
+                     'diso' => 'Discussion filtering options',
+    );
+    my $bodytag=&Apache::loncommon::bodytag('Discussion options',
+                                          '','');
+    if ($contrib{'version'}) {
+        for (my $idx=1;$idx<=$contrib{'version'};$idx++) {
+            my $hidden=($contrib{'hidden'}=~/\.$idx\./);
+            my $deleted=($contrib{'deleted'}=~/\.$idx\./);
+            unless ((($hidden) && (!$seeid)) || ($deleted)) {
+                if ((!$contrib{$idx.':anonymous'}) || ($seeid)) {
+                    my %names = &Apache::lonnet::get('environment',['firstname','lastname'],$contrib{$idx.':senderdomain'},$contrib{$idx.':sendername'});
+                    my $lastname = $names{'lastname'};
+                    my $firstname = $names{'firstname'};
+                    if ($lastname eq '') {
+                        $lastname = '_';
+                    }
+                    if ($firstname eq '') {
+                        $firstname = '_';
+                    }
+                    unless (defined($namesort{$lastname})) {
+                        %{$namesort{$lastname}} = ();
+                    }
+                    my $poster =  $contrib{$idx.':sendername'}.':'.$contrib{$idx.':senderdomain'};
+                    $postcounts{$poster} ++;
+                    if (defined($namesort{$lastname}{$firstname})) {
+                        if (!grep/^$poster$/,@{$namesort{$lastname}{$firstname}}) {
+                            push @{$namesort{$lastname}{$firstname}}, $poster;
+                        }
+                    } else {
+                        @{$namesort{$lastname}{$firstname}} = ("$poster");
+                    }
+                }
+            }
+        }
+    }
+    $r->print(<<END);
+<html>
+<head>
+<title>$lt{'diso'}</title>
+<meta http-equiv="pragma" content="no-cache" />
+</head>
+$bodytag
+ <form name="pickpostersform" method="post">
+  <table border="0">
+   <tr>
+    <td bgcolor="#777777">
+     <table border="0" cellpadding="3">
+      <tr bgcolor="#e6ffff">
+       <td><b>No.</b></td>
+       <td><b>Select</b></td>
+       <td><b>Fullname</b><font color="#999999">(Username/domain)</font></td>
+       <td><b>Posts</td>
+      </tr>
+END
+    my $count = 0;
+    foreach my $last (sort keys %namesort) {
+        foreach my $first (sort keys %{$namesort{$last}}) {
+            foreach (sort @{$namesort{$last}{$first}}) {
+                my ($uname,$udom) = split/:/,$_;
+                if (!$uname || !$udom) { 
+                    next;
+                } else {
+                    $count ++;
+                    $r->print('<tr bgcolor="#ffffe6"><td align="right">'.$count.'</td><td align="center"><input name="stuinfo" type="checkbox" value="'.$_.'" /></td><td>'.$last.', '.$first.' ('.$uname.','.$udom.')</td><td>'.$postcounts{$_}.'</td></tr>');
+                }
+            }
+        }
+    }
+    $r->print(<<END);
+     </table>
+    </td>
+   </tr>
+  </table>
+<br />
+<input type="hidden" name="sortposts" value="$sortposts" />
+<input type="hidden" name="userpick" value="$symb" />
+<input type="button" name="store" value="Display posts" onClick="javascript:document.pickpostersform.submit()" />
+</form>
+</body>
+</html>
+END
+}
+
 sub fail_redirect {
   my ($r,$feedurl) = @_;
   if ($feedurl=~/^\/adm\//) { $feedurl.='?register=1' };
@@ -788,14 +1359,18 @@ ENDFAILREDIR
 }
 
 sub redirect_back {
-  my ($r,$feedurl,$typestyle,$sendsomething,$sendposts,$status,$previous) = @_;
-  my $prevtag = '';
+  my ($r,$feedurl,$typestyle,$sendsomething,$sendposts,$status,$previous,$sort,$rolefilter,$statusfilter,$secpick,$numpicks) = @_;
+  my $sorttag = '';
+  my $roletag = '';
+  my $statustag = '';
+  my $sectag = '';
+  my $userpicktag = '';
   my $qrystr = '';
+  my $prevtag = '';
  # backward compatibility (bulletin boards used to be 'wrapped')
   if ($feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) {
       $feedurl=~s|^/adm/wrapper||;
   }
-
   if ($feedurl=~/^\/adm\//) { $feedurl.='?register=1' };
   if ($previous > 0) {
       $qrystr = 'previous='.$previous;
@@ -806,6 +1381,28 @@ sub redirect_back {
       }
       $prevtag = '<input type="hidden" name="previous" value="'.$previous.'" />';
   }
+  if (defined($sort)) {
+      my $sortqry = 'sortposts='.$sort;
+      if (($feedurl =~ /\?register=1/) || ($feedurl =~ /\?previous=/)) {
+          $feedurl .= '&'.$sortqry;
+      } else {
+          $feedurl .= '?'.$sortqry;
+      }
+      $sorttag = '<input type="hidden" name="sortposts" value="'.$sort.'" />';
+      if ( (defined($numpicks)) && ($numpicks > 0) ) {
+          my $userpickqry = 'totposters='.$numpicks;
+          $feedurl .= '&'.$userpickqry;
+          $userpicktag = '<input type="hidden" name="totposters" value="'.$numpicks.'" />';
+      } else {
+          my $roleqry = 'rolefilter='.$rolefilter;
+          $feedurl .= '&'.$roleqry;
+          $roletag = '<input type="hidden" name="rolefilter" value="'.$rolefilter.'" />';
+          $feedurl .= '&statusfilter='.$statusfilter;
+          $statustag ='<input type="hidden" name="statusfilter" value="'.$statusfilter.'" />';
+          $feedurl .= '&sectionpick='.$secpick;
+          $sectag = '<input type="hidden" name="sectionpick" value="'.$secpick.'" />';
+      }
+  }
   $r->print (<<ENDREDIR);
 <html>
 <head>
@@ -820,6 +1417,11 @@ $typestyle
 <font color="red">$status</font>
 <form name="reldt" action="$feedurl" target="loncapaclient">
 $prevtag
+$sorttag
+$statustag
+$roletag
+$sectag
+$userpicktag
 </form>
 </body>
 </html>
@@ -852,7 +1454,7 @@ sub screen_header {
     my ($feedurl) = @_;
     my $msgoptions='';
     my $discussoptions='';
-    unless ($ENV{'form.replydisc'}) {
+    unless (($ENV{'form.replydisc'}) || ($ENV{'form.editdisc'})) {
 	if (($feedurl=~/^\/res\//) && ($feedurl!~/^\/res\/adm/)) {
 	    $msgoptions= 
 		'<p><input type="checkbox" name="author" /> '.
@@ -1073,10 +1675,52 @@ sub adddiscuss {
 	$contrib{'anonymous'}='true';
     }
     if (($symb) && ($email)) {
-       $status='Adding to class discussion'.($anon?' (anonymous)':'').': '.
-        &Apache::lonnet::store(\%contrib,$symb,$ENV{'request.course.id'},
+        if ($ENV{'form.editdisc'}) {
+            my %newcontrib = ();
+            $contrib{'ip'}=$ENV{'REMOTE_ADDR'};
+            $contrib{'host'}=$Apache::lonnet::perlvar{'lonHostID'};
+            $contrib{'timestamp'} = time;
+            $contrib{'history'} = '';
+            my $numoldver = 0;
+            my ($oldsymb,$oldidx)=split(/\:\:\:/,$ENV{'form.editdisc'});
+# get timestamp for last post and history
+            my %oldcontrib=&Apache::lonnet::restore($oldsymb,$ENV{'request.course.id'},
+                     $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+                     $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+            if (defined($oldcontrib{$oldidx.':replyto'})) {
+                $contrib{'replyto'} = $oldcontrib{$oldidx.':replyto'};
+            }
+            if (defined($oldcontrib{$oldidx.':history'})) {
+                if ($oldcontrib{$oldidx.':history'} =~ /:/) {
+                    my @oldversions = split/:/,$oldcontrib{$oldidx.':history'};
+                    $numoldver = @oldversions;
+                } else {
+                    $numoldver = 1;
+                }
+                $contrib{'history'} = $oldcontrib{$oldidx.':history'}.':';
+            }
+            if (defined($oldcontrib{$oldidx.':subject'})) {
+                $contrib{'subject'} = $oldcontrib{$oldidx.':subject'}.'::::'.$numoldver.'::::'.$contrib{'subject'};            
+            } 
+            if (defined($oldcontrib{$oldidx.':message'})) {
+                $contrib{'message'} = $oldcontrib{$oldidx.':message'}.'::::'.$numoldver.'::::'.$contrib{'message'};
+            }
+            $contrib{'history'} .= $oldcontrib{$oldidx.':timestamp'};
+            foreach (keys %contrib) {
+                my $key = $oldidx.':'.&Apache::lonnet::escape($oldsymb).':'.$_;                                                                               
+                $newcontrib{$key} = $contrib{$_};
+            }
+            my $put_reply = &Apache::lonnet::putstore($ENV{'request.course.id'},
+                  \%newcontrib,
+                  $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+                  $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+            $status='Editing class discussion'.($anon?' (anonymous)':'');
+        } else {
+           $status='Adding to class discussion'.($anon?' (anonymous)':'').': '.
+           &Apache::lonnet::store(\%contrib,$symb,$ENV{'request.course.id'},
                      $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
 		     $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+        }
         my %storenewentry=($symb => time);
         $status.='<br />'.&mt('Updating discussion time').': '.
         &Apache::lonnet::put('discussiontimes',\%storenewentry,
@@ -1136,8 +1780,85 @@ sub handler {
 # --------------------------- Get query string for limited number of parameters
 
   &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
-         ['hide','unhide','deldisc','postdata','preview','replydisc','threadedon','threadedoff','onlyunread','allposts','previous','markread','markonread','markondisp','modifydisp','changes','navmaps','navurl']);
-  if ($ENV{'form.navmaps'}) {
+         ['hide','unhide','deldisc','postdata','preview','replydisc','editdisc','threadedon','threadedoff','onlyunread','allposts','previous','markread','markonread','markondisp','modifydisp','changes','navmaps','navurl','sortfilter','sortposts','applysort','rolefilter','statusfilter','sectionpick','posterlist','userpick']);
+  if ($ENV{'form.posterlist'}) {
+      &Apache::loncommon::content_type($r,'text/html');
+      $r->send_http_header;
+      my $symb=$ENV{'form.posterlist'};
+      my $sortposts = $ENV{'form.sortposts'};
+      my ($map,$ind,$url)=&Apache::lonnet::decode_symb($symb);
+      my $previous=$ENV{'form.previous'};
+      my $feedurl = &Apache::lonnet::clutter($url);
+ # backward compatibility (bulletin boards used to be 'wrapped')
+      if ($feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) {
+          $feedurl=~s|^/adm/wrapper||;
+      }
+      &print_showposters($r,$symb,$previous,$feedurl,$sortposts);
+      return OK;
+  }
+  if ($ENV{'form.userpick'}) {
+      &Apache::loncommon::content_type($r,'text/html');
+      $r->send_http_header;
+      my $symb=$ENV{'form.userpick'};
+      my ($map,$ind,$url)=&Apache::lonnet::decode_symb($symb);
+      my $previous=$ENV{'form.previous'};
+# backward compatibility (bulletin boards used to be 'wrapped')
+      my $ressymb=$symb;
+      unless ($ressymb =~ m|bulletin___\d+___adm/wrapper|) {
+          $ressymb=~s|(bulletin___\d+___)|$1adm/wrapper|;
+      }
+      my $sort=$ENV{'form.sortposts'};
+      my @posters = ();
+      if (ref($ENV{'form.stuinfo'}) eq 'ARRAY') {
+          @posters = $ENV{'form.stuinfo'};
+      } else {
+          $posters[0] = $ENV{'form.stuinfo'};
+      }
+      my $numpicks = @posters;
+      if (defined($ENV{'form.userpick'})) {
+          my %discinfo = ();
+          $discinfo{$ressymb.'_userpick'} = join('&',@posters);
+          &Apache::lonnet::put('nohist_'.$ENV{'request.course.id'}.'_discuss',\%discinfo,$ENV{'user.domain'},$ENV{'user.name'});
+      }
+      my $feedurl = &Apache::lonnet::clutter($url);
+ # backward compatibility (bulletin boards used to be 'wrapped')
+      if ($feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) {
+          $feedurl=~s|^/adm/wrapper||;
+      }
+      &redirect_back($r,$feedurl,&mt('Changed sort/filter').'<br />','0','0','',$previous,$sort,'','','',$numpicks);
+      return OK;
+  }
+  if ($ENV{'form.applysort'}) {
+      &Apache::loncommon::content_type($r,'text/html');
+      $r->send_http_header;
+      my $symb=$ENV{'form.applysort'};
+      my ($map,$ind,$url)=&Apache::lonnet::decode_symb($symb);
+      my $previous=$ENV{'form.previous'};
+      my $sort = $ENV{'form.sortposts'};
+      my $rolefilter = $ENV{'form.rolefilter'};
+      my $statusfilter = $ENV{'form.statusfilter'};
+      my $secpick = $ENV{'form.sectionpick'};
+      my $feedurl = &Apache::lonnet::clutter($url);
+ # backward compatibility (bulletin boards used to be 'wrapped')
+      if ($feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) {
+          $feedurl=~s|^/adm/wrapper||;
+      }
+      &redirect_back($r,$feedurl,&mt('Changed sort/filter').'<br />','0','0','',$previous,$sort,$rolefilter,$statusfilter,$secpick);
+      return OK;
+  } elsif ($ENV{'form.sortfilter'}) {
+      &Apache::loncommon::content_type($r,'text/html');
+      $r->send_http_header;
+      my $symb=$ENV{'form.sortfilter'};
+      my ($map,$ind,$url)=&Apache::lonnet::decode_symb($symb);
+      my $previous=$ENV{'form.previous'};
+      my $feedurl = &Apache::lonnet::clutter($url);
+ # backward compatibility (bulletin boards used to be 'wrapped')
+      if ($feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) {
+          $feedurl=~s|^/adm/wrapper||;
+      }
+      &print_sortfilter_options($r,$symb,$previous,$feedurl);
+      return OK;
+  } elsif ($ENV{'form.navmaps'}) {
       my %discinfo = ();
       my @resources = ();
       if ($ENV{'form.navmaps'} =~ /:/) {
@@ -1279,13 +2000,27 @@ ENDREDIR
 
         
     my $currenthidden=$contrib{'hidden'};
+    my $currentstudenthidden=$contrib{'studenthidden'};
+
+    my $crs='/'.$ENV{'request.course.id'};
+    if ($ENV{'request.course.sec'}) {
+        $crs.='_'.$ENV{'request.course.sec'};
+    }
+    $crs=~s/\_/\//g;
+    my $seeid=&Apache::lonnet::allowed('rin',$crs);
     
     if ($ENV{'form.hide'}) {
 	$currenthidden.='.'.$idx.'.';
+        unless ($seeid) {
+            $currentstudenthidden.='.'.$idx.'.';
+        }
     } else {
         $currenthidden=~s/\.$idx\.//g;
     }
     my %newhash=('hidden' => $currenthidden);
+    if ( ($ENV{'form.hide'}) && (!$seeid) ) {
+        $newhash{'studenthidden'} = $currentstudenthidden;
+    }
 
     &Apache::lonnet::store(\%newhash,$symb,$ENV{'request.course.id'},
                      $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
@@ -1352,6 +2087,10 @@ ENDREDIR
       $symb=(split(/\:\:\:/,$ENV{'form.replydisc'}))[0];
       my ($map,$id,$url)=&Apache::lonnet::decode_symb($symb);
       $feedurl=&Apache::lonnet::clutter($url);
+  } elsif ($ENV{'form.editdisc'}) {
+      $symb=(split(/\:\:\:/,$ENV{'form.editdisc'}))[0];
+      my ($map,$id,$url)=&Apache::lonnet::decode_symb($symb);
+      $feedurl=&Apache::lonnet::clutter($url);
   } else {
       $symb=&Apache::lonnet::symbread($feedurl);
   }