--- loncom/interface/lonfeedback.pm 2006/01/11 08:31:37 1.178 +++ loncom/interface/lonfeedback.pm 2013/01/03 20:51:23 1.363 @@ -1,7 +1,7 @@ # The LearningOnline Network # Feedback # -# $Id: lonfeedback.pm,v 1.178 2006/01/11 08:31:37 albertel Exp $ +# $Id: lonfeedback.pm,v 1.363 2013/01/03 20:51:23 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -41,18 +41,30 @@ use Apache::lonnavmaps; use Apache::lonenc(); use Apache::lonrss(); use HTML::LCParser(); +#use HTML::Tidy::libXML; use Apache::lonspeller(); +use Apache::longroup; use Cwd; +use LONCAPA; sub discussion_open { my ($status,$symb)=@_; +# Advanced roles can always discuss if ($env{'request.role.adv'}) { return 1; } +# Get discussion closing date + my $close=&Apache::lonnet::EXT('resource.0.discussend',$symb); +# If it is defined and in the future, the instructor wants this discussion to be open + if (defined($close) && $close ne '' && $close > time) { + return 1; + } +# It was not explicitly open, check if the problem is available. +# If the problem is not available, close the discussion if (defined($status) && !($status eq 'CAN_ANSWER' || $status eq 'CANNOT_ANSWER' || $status eq 'OPEN')) { return 0; } - my $close=&Apache::lonnet::EXT('resource.0.discussend',$symb); +# The problem is available, but check if the instructor explictly closed discussion if (defined($close) && $close ne '' && $close < time) { return 0; } @@ -70,8 +82,31 @@ sub discussion_visible { return 1; } +sub discussion_vote_available { + my ($status,$symb)=@_; + my $canvote=&Apache::lonnet::EXT('resource.0.discussvote',$symb); + if ((lc($canvote) eq 'yes') || + ((lc($canvote) eq 'notended') && (&discussion_open($status,$symb)))) { + return 1; + } +} + +sub get_realsymb { + my ($symb) = @_; + my $realsymb = $symb; + if ($symb=~/^bulletin___/) { + my $filename=(&Apache::lonnet::decode_symb($symb))[2]; + $filename=~s{^adm/wrapper/}{}; + $realsymb=&Apache::lonnet::symbread($filename); + } + return $realsymb; +} + sub list_discussion { - my ($mode,$status,$ressymb,$imsextras)=@_; + my ($mode,$status,$ressymb,$imsextras,$group)=@_; + unless ($ressymb) { $ressymb=&Apache::lonnet::symbread(); } + unless ($ressymb) { return ''; } + $ressymb=&wrap_symb($ressymb); my $outputtarget=$env{'form.grade_target'}; if (defined($env{'form.export'})) { if($env{'form.export'}) { @@ -83,8 +118,32 @@ sub list_discussion { $outputtarget = 'export'; } } - if (not &discussion_visible($status)) { return ''; } - my @bgcols = ("#cccccc","#eeeeee"); + if (not &discussion_visible($status)) { + if ($mode ne 'board') { + &Apache::lonenc::check_encrypt(\$ressymb); + return '
"; + } + } + if ($group ne '' && $mode eq 'board') { + if (&check_group_priv($group,'vgb') ne 'ok') { + return ''; + } + } + + my ($blocked,$blocktext) = + &Apache::loncommon::blocking_status('boards'); + if ($blocked) { + $blocktext = '
'; + }else{ + $blocktext.=""; + } + return $blocktext; + } + + my @bgcols = ("LC_disc_old_item","LC_disc_new_item"); my $discussiononly=0; if ($mode eq 'board') { $discussiononly=1; } unless ($env{'request.course.id'}) { return ''; } @@ -94,12 +153,9 @@ sub list_discussion { $crs.='_'.$env{'request.course.sec'}; } $crs=~s/\_/\//g; - unless ($ressymb) { $ressymb=&Apache::lonnet::symbread(); } - unless ($ressymb) { return ''; } - $ressymb=&wrap_symb($ressymb); my $encsymb=&Apache::lonenc::check_encrypt($ressymb); my $viewgrades=(&Apache::lonnet::allowed('vgr',$crs) - && ($ressymb=~/\.(problem|exam|quiz|assess|survey|form|task)$/)); + && ($ressymb=~/$LONCAPA::assess_re/)); my %usernamesort = (); my %namesort =(); @@ -124,24 +180,16 @@ sub list_discussion { my $previous = 0; my $visit = 0; my $newpostsflag = 0; - my @posters = split/\&/,$dischash{$userpickkey}; + 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','sortposts','rolefilter','statusfilter','sectionpick','totposters']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['previous','sortposts','rolefilter','statusfilter','sectionpick','grouppick','totposters']); my $sortposts = $env{'form.sortposts'}; my $statusfilter = $env{'form.statusfilter'}; - my @sectionpick = (); - if ($env{'form.sectionpick'} =~ /,/) { - @sectionpick = split/,/,$env{'form.sectionpick'}; - } else { - $sectionpick[0] = $env{'form.sectionpick'}; - } - my @rolefilter = (); - if ($env{'form.rolefilter'} =~ /,/) { - @rolefilter = split/,/,$env{'form.rolefilter'}; - } else { - $rolefilter[0] = $env{'form.rolefilter'}; - } + my @sectionpick = split(/,/,$env{'form.sectionpick'}); + my @grouppick = split(/,/,$env{'form.grouppick'}); + my @rolefilter = split(/,/,$env{'form.rolefilter'}); + my $totposters = $env{'form.totposters'}; $previous = $env{'form.previous'}; if ($previous > 0) { @@ -152,38 +200,43 @@ sub list_discussion { } } + my $cdom = $env{'course.'.$cid.'.domain'}; + my $cnum = $env{'course.'.$cid.'.num'}; + my $crstype = &Apache::loncommon::course_type(); + # Get information about students and non-students in course for filtering display of posts my %roleshash = (); my %roleinfo = (); + my ($classgroups,$studentgroups); if ($env{'form.rolefilter'}) { - %roleshash = &Apache::lonnet::dump('nohist_userroles', - $env{'course.'.$cid.'.domain'}, - $env{'course.'.$cid.'.num'}); - foreach (keys %roleshash) { - my ($role,$uname,$udom,$sec) = split/:/,$_; + %roleshash = &Apache::lonnet::dump('nohist_userroles',$cdom,$cnum); + foreach my $rolekey (keys(%roleshash)) { + my ($role,$uname,$udom,$sec) = split(/:/,$rolekey); if ($role =~ /^cr/) { $role = 'cr'; } - my ($end,$start) = split/:/,$roleshash{$_}; + my ($end,$start) = split(/:/,$roleshash{$rolekey}); my $now = time; my $status = 'Active'; if (($now < $start) || ($end > 0 && $now > $end)) { $status = 'Expired'; } if ($uname && $udom) { - push @{$roleinfo{$uname.':'.$udom}}, $role.':'.$sec.':'.$status; + push(@{$roleinfo{$uname.':'.$udom}}, $role.':'.$sec.':'.$status); } } - my ($classlist) = &Apache::loncoursedata::get_classlist( - $env{'course.'.$cid.'.domain'}, - $env{'course.'.$cid.'.num'}); + my ($classlist,$keylist) = + &Apache::loncoursedata::get_classlist($cdom,$cnum); 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; + push(@{$roleinfo{$student}}, 'st:'.$section.':'.$status); } + ($classgroups,$studentgroups) = + &Apache::loncoursedata::get_group_memberships($classlist,$keylist, + $cdom,$cnum); } # Get discussion display default settings for user @@ -223,7 +276,16 @@ sub list_discussion { } $visit ++; - my $seeid=&Apache::lonnet::allowed('rin',$crs); + my $seeid; + if (&Apache::lonnet::allowed('rin',$env{'request.course.id'}.($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) { + $seeid = 1; + } + my $seehidden = &can_see_hidden($mode,$ressymb,undef,$group,$cdom,$cnum,$crs); + +# Is voting on discussions available + my $realsymb = &get_realsymb($ressymb); + my $canvote = &discussion_vote_available($status,$realsymb); + my @discussionitems=(); my %shown = (); my @posteridentity=(); @@ -240,17 +302,12 @@ sub list_discussion { my $maxdepth=0; my %anonhash=(); my $anoncnt=0; - my $target=''; - unless ($env{'browser.interface'} eq 'textual' || - $env{'environment.remote'} eq 'off' ) { - $target='target="LONcom"'; - } my $now = time; $discinfo{$visitkey} = $visit; &Apache::lonnet::put('nohist_'.$cid.'_discuss',\%discinfo,$env{'user.domain'},$env{'user.name'}); - &build_posting_display(\%usernamesort,\%subjectsort,\%namesort,\%notshown,\%newitem,\%dischash,\%shown,\%alldiscussion,\%imsitems,\%imsfiles,\%roleinfo,\@discussionitems,\@replies,\@depth,\@posters,\$maxdepth,\$visible,\$newpostsflag,\$current,$status,$viewgrades,$seeid,$prevread,$sortposts,$encsymb,$target,$readkey,$showunmark,$showonlyunread,$totposters,\@rolefilter,\@sectionpick,$statusfilter,$toggkey,$outputtarget,\%anonhash,$anoncnt); + &build_posting_display(\%usernamesort,\%subjectsort,\%namesort,\%notshown,\%newitem,\%dischash,\%shown,\%alldiscussion,\%imsitems,\%imsfiles,\%roleinfo,\@discussionitems,\@replies,\@depth,\@posters,\$maxdepth,\$visible,\$newpostsflag,\$current,$status,$viewgrades,$seeid,$seehidden,$canvote,$prevread,$sortposts,$encsymb,$readkey,$showunmark,$showonlyunread,$totposters,\@rolefilter,\@sectionpick,\@grouppick,$classgroups,$statusfilter,$toggkey,$outputtarget,\%anonhash,$anoncnt,$group); my $discussion=''; my $manifestfile; @@ -260,10 +317,8 @@ sub list_discussion { my $copyresult; my $function = &Apache::loncommon::get_users_function(); - my $color = &Apache::loncommon::designparm($function.'.tabbg', - $env{'user.domain'}); my %lt = &Apache::lonlocal::texthash( - 'cuse' => 'Current discussion settings', + 'cuse' => 'My settings for this discussion', 'allposts' => 'All posts', 'unread' => 'New posts only', 'unmark' => 'Unread only', @@ -278,6 +333,14 @@ sub list_discussion { 'disp' => 'Display', 'nolo' => 'Not new', 'togg' => 'Toggle read/unread', + 'aner' => 'An error occurred opening the manifest file.', + 'difo' => 'Discussion for', + 'aerr' => 'An error occurred opening the export file for posting', + 'aysu' => 'Are you sure you want to delete this post?', + 'dpwn' => 'Deleted posts will no longer be visible to you and other students', + 'bwco' => 'but will continue to be visible to your instructor', + 'depo' => 'Deleted posts will no longer be visible to you or anyone else.', + 'discussions' => 'DISCUSSIONS' ); my $currdisp = $lt{'allposts'}; @@ -318,18 +381,18 @@ sub list_discussion { $togglink = 'toggoff'; } - $chglink .= '&changes='.$displinkA.'_'.$displinkB.'_'.$marklink.'_'.$togglink; + $chglink .= '&changes='.$displinkA.'_'.$displinkB.'_'.$marklink.'_'.$togglink; if ($newpostsflag) { - $chglink .= '&previous='.$prevread; + $chglink .= '&previous='.$prevread; } + $chglink.=&group_args($group); if ($visible) { # Print the discusssion if ($outputtarget eq 'tex') { $discussion.='{\tiny \vskip 0 mm\noindent\makebox[2 cm][b]{\hrulefill}'. - '\textbf{DISCUSSIONS}\makebox[2 cm][b]{\hrulefill}'. - '\vskip 0 mm\noindent\textbf{'.$lt{'cuse'}.'}:\vskip 0 mm'. + '\textbf{'.$lt{'discussions'}.'}\makebox[2 cm][b]{\hrulefill}\vskip 0 mm'. '\noindent\textbf{'.$lt{'disa'}.'}: \textit{'.$currdisp.'}\vskip 0 mm'. '\noindent\textbf{'.$lt{'npce'}.'}: \textit{'.$currmark.'}}'; } elsif ($outputtarget eq 'export') { @@ -363,84 +426,54 @@ sub list_discussion { my $manifestfilename = $tempexport.$manifest; if ($manifestfile = Apache::File->new('>'.$manifestfilename)) { $manifestok=1; - print $manifestfile qq| - + print $manifestfile qq| - Discussion for $ressymb\n|; + $lt{'difo'} $ressymb\n|; } else { - $discussion .= 'An error occurred opening the manifest file.
'; + $discussion .= $lt{'aner'}.'
'; } } else { my $colspan=$maxdepth+1; - $discussion.= qq| - - |; - $discussion.='
'; - $discussion .=''; - - my $numhidden = keys %notshown; + |); + $discussion.=''. + "\n".'
'. - ''; - my $escsymb=&Apache::lonnet::escape($ressymb); - if ($visible>2) { - $discussion.=''; - if ($newpostsflag) { - if (!$markondisp) { - $discussion .=''; - } - } else { - $discussion .= ''; - } - $discussion .= '
'. - ''.&mt('Threaded View').'  '. - ''.&mt('Chronological View').'   - '.&mt('Sorting/Filtering options').'  '; - } else { - $discussion .= ''; - } - $discussion .=''.&mt('Export').'?  '. - &mt('Preferences on what is marked as NEW'). - '
'.&mt('Mark NEW posts no longer new').''; - } else { - $discussion .= '
  
'; + $discussion .= &action_links_bar($colspan,$ressymb,$visible, + $newpostsflag,$group, + $prevread,$markondisp,$seehidden); + my $escsymb=&escape($ressymb); + my $numhidden = keys(%notshown); if ($numhidden > 0) { my $colspan = $maxdepth+1; $discussion.="\n".'"; } - my $thisdepth=$depth[$alldiscussion{$_}]; + my $thisdepth=$depth[$alldiscussion{$post}]; if ($outputtarget ne 'tex' && $outputtarget ne 'export') { for (1..$thisdepth) { - $discussion.=''; + $discussion.=''; } } my $colspan=$maxdepth-$thisdepth+1; if ($outputtarget eq 'tex') { #cleanup block - $discussionitems[$alldiscussion{$_}]=~s/]*)>/
'. ''.&mt('Show all posts').' '.&mt('to display').' '. $numhidden.' '; if ($showunmark) { @@ -455,93 +488,94 @@ imscp_v1p1.xsd http://www.imsglobal.org/ # Choose sort mechanism my @showposts = (); if ($sortposts eq 'descdate') { - @showposts = (sort { $b <=> $a } keys %alldiscussion); + @showposts = (sort { $b <=> $a } keys(%alldiscussion)); } elsif ($sortposts eq 'thread') { - @showposts = (sort { $a <=> $b } keys %alldiscussion); + @showposts = (sort { $a <=> $b } keys(%alldiscussion)); } elsif ($sortposts eq 'subject') { - foreach (sort keys %subjectsort) { - push @showposts, @{$subjectsort{$_}}; + foreach my $key (sort(keys(%subjectsort))) { + push(@showposts, @{$subjectsort{$key}}); } } elsif ($sortposts eq 'username') { - foreach my $domain (sort keys %usernamesort) { - foreach (sort keys %{$usernamesort{$domain}}) { - push @showposts, @{$usernamesort{$domain}{$_}}; + foreach my $domain (sort(keys(%usernamesort))) { + foreach my $key (sort(keys(%{$usernamesort{$domain}}))) { + push(@showposts, @{$usernamesort{$domain}{$key}}); } } } elsif ($sortposts eq 'lastfirst') { - foreach my $last (sort keys %namesort) { - foreach (sort keys %{$namesort{$last}}) { - push @showposts, @{$namesort{$last}{$_}}; + foreach my $last (sort(keys(%namesort))) { + foreach my $key (sort(keys(%{$namesort{$last}}))) { + push(@showposts, @{$namesort{$last}{$key}}); } } } else { - @showposts = (sort { $a <=> $b } keys %alldiscussion); + @showposts = (sort { $a <=> $b } keys(%alldiscussion)); } my $currdepth = 0; my $firstidx = $alldiscussion{$showposts[0]}; - foreach (@showposts) { - unless (($sortposts eq 'thread') || (($sortposts eq '') && ($env{'environment.threadeddiscussion'})) || ($outputtarget eq 'export')) { - $alldiscussion{$_} = $_; + foreach my $post (@showposts) { + unless (($sortposts eq 'thread') || + (($sortposts eq '') && (!$env{'environment.unthreadeddiscussion'})) || + ($outputtarget eq 'export')) { + $alldiscussion{$post} = $post; } - unless ( ($notshown{$alldiscussion{$_}} eq '1') || ($shown{$alldiscussion{$_}} == 0) ) { + unless ( ($notshown{$alldiscussion{$post}} eq '1') || ($shown{$alldiscussion{$post}} == 0) ) { if ($outputtarget ne 'tex' && $outputtarget ne 'export') { $discussion.="\n
       
/; - $discussionitems[$alldiscussion{$_}]=~s/]*)>]*)>/ +END + my $save = &mt('Save'); + $r->print(&Apache::loncommon::end_data_table_row()); + $r->print(&Apache::loncommon::end_data_table()); + $r->print(<
- - - + + + - + +END + if (exists($env{'form.group'})) { + $r->print(''); + } + if (exists($env{'form.ref'})) { + $r->print(''); + } + $r->print("

- - -END +$end_page + "); return; } @@ -1813,35 +2312,66 @@ sub print_sortfilter_options { $r->send_http_header; &Apache::lonenc::check_encrypt(\$symb); - my @sections = (); + my @sections; my $section_sel = ''; - my $numsections = 0; my $numvisible = 5; - my %sectioncount = (); - - $numsections = &Apache::loncommon::get_sections($env{'course.'.$env{'request.course.id'}.'.domain'},$env{'course.'.$env{'request.course.id'}.'.num'},\%sectioncount); - + my @groups; + my $group_sel = ''; + my $numgroupvis = 5; + my %sectioncount = &Apache::loncommon::get_sections(); + my @courseroles = qw(st ad ep ta in); + my $crstype = &Apache::loncommon::course_type(); + my $ccrole = 'cc'; + if ($crstype eq 'Community') { + $ccrole = 'co'; + } + push(@courseroles,$ccrole); + if ($env{'request.course.sec'} !~ /^\s*$/) { #Restrict section choice to current section @sections = ('all',$env{'request.course.sec'}); $numvisible = 2; } else { @sections = sort {$a cmp $b} keys(%sectioncount); + if (scalar(@sections) < 4) { + $numvisible = scalar(@sections) + 1; + } unshift(@sections,'all'); # Put 'all' at the front of the list - if ($numsections < 4) { - $numvisible = $numsections + 1; + + } + foreach my $sec (@sections) { + $section_sel .= " \n"; + } + + if (&check_group_priv() eq 'ok') { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my %curr_groups = &Apache::longroup::coursegroups($cdom,$cnum); + @groups = sort {$a cmp $b} keys(%curr_groups); + if (scalar(@groups) < 4) { + $numgroupvis = scalar(@groups) + 1; + } + unshift(@groups,'all'); # Put 'all' at the front of the list + } else { + my @coursegroups = split(/:/,$env{'request.course.groups'}); + if (@coursegroups > 0) { + @coursegroups = sort {$a cmp $b} @coursegroups; + @groups = ('all',@coursegroups); + if (scalar(@groups) < 4) { + $numgroupvis = scalar(@groups) + 1; + } + } else { + @groups = ('all'); + $numgroupvis = 1; } } - foreach (@sections) { - $section_sel .= " \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', @@ -1850,20 +2380,18 @@ sub print_sortfilter_options { 'spur' => 'Specific user roles', 'sprs' => 'Specific role status', 'spse' => 'Specific sections', + 'spgr' => 'Specific groups', 'psub' => 'Pick specific users (by name)', - 'shal' => 'Show a list of current posters' + 'shal' => 'Show a list of current posters', + 'stor' => 'Save changes', ); my %sort_types = (); my %role_types = (); my %status_types = (); - &sort_filter_names(\%sort_types,\%role_types,\%status_types); - my $html=&Apache::lonxml::xmlbegin(); - $r->print(< -$lt{'diso'} - + &sort_filter_names(\%sort_types,\%role_types,\%status_types,$crstype); + + my $js = < function verifyFilter() { var rolenum = 0 @@ -1885,79 +2413,114 @@ function verifyFilter() { if (secnum == 0) { document.modifyshown.sectionpick.options[0].selected = true } + + var grpnum = 0 + for (var i=0; i - -$bodytag +END + + my $start_page= + &Apache::loncommon::start_page('Discussion options',$js); + my $end_page= + &Apache::loncommon::end_page(); + + $r->print(< $lt{'diso'}
$lt{'prca'} -

+
/; + $discussionitems[$alldiscussion{$post}]=~s/]*)>//; + $discussionitems[$alldiscussion{$post}]=~s/]*)>]*)>/'; } } @@ -549,17 +583,12 @@ imscp_v1p1.xsd http://www.imsglobal.org/ unless ($outputtarget eq 'tex' || $outputtarget eq 'export') { my $colspan=$maxdepth+1; $discussion .= < - '; + return $discussion; +} + +sub postingform_display { + my ($mode,$ressymb,$now,$subject,$comment,$outputtarget,$attachnum, + $currnewattach,$currdelold,$group,$crstype) = @_; + my $newattachmsg; + my %lt = &Apache::lonlocal::texthash( + 'note' => 'Note: in anonymous discussion, your name is visible only to course faculty', + 'title' => 'Title', + 'podi' => 'Post Discussion', + 'poan' => 'Post Anonymous Discussion', + 'newa' => 'New attachments', + ); + if ($crstype eq 'Community') { + $lt{'note'} = &mt('Note: in anonymous discussion, your name is visible only to community facilitators'); + } + my ($postingform,$textareaclass); + if (&Apache::lonhtmlcommon::htmlareabrowser()) { + $postingform = &Apache::lonhtmlcommon::htmlareaselectactive(); + $textareaclass = 'class="LC_richDefaultOff"'; + if ($env{'request.course.id'}) { + unless (($env{'course.'.$env{'request.course.id'}.'.allow_limited_html_in_feedback'} =~ /^\s*yes\s*$/i) || ($env{'form.sendmessageonly'})) { + undef($textareaclass); + } + } + } + $postingform .= (< + + + + +$lt{'note'}
+$lt{'title'}: 
+ +ENDDISCUSS + if ($env{'form.origpage'}) { + $postingform .= ''."\n"; + foreach my $att (@{$currnewattach}) { + $postingform .= ''."\n"; + } + } + if (exists($env{'form.ref'})) { + $postingform .= ''; + } + if ($group ne '') { + $postingform .=''; + } + my $blockblog = &Apache::loncommon::blocking_status('blogs'); + if (!$blockblog) { + $postingform .= &add_blog_checkbox($crstype); + } + $postingform .= "\n"; + $postingform .= &generate_attachments_button('',$attachnum,$ressymb, + $now,$currnewattach, + $currdelold,'',$mode, + $blockblog); + if ((ref($currnewattach) eq 'ARRAY') && (@{$currnewattach} > 0)) { + $newattachmsg = '
'.$lt{'newa'}.'
'; + if (@{$currnewattach} > 1) { + $newattachmsg .= '
    '; + foreach my $item (@{$currnewattach}) { + $item =~ m#.*/([^/]+)$#; + $newattachmsg .= '
  1. '.$1.'
  2. '."\n"; + } + $newattachmsg .= '
'."\n"; + } else { + $$currnewattach[0] =~ m#.*/([^/]+)$#; + $newattachmsg .= ''.$1.'
'."\n"; + } + } + $postingform .= $newattachmsg; + $postingform .= &generate_preview_button(); + return $postingform; +} + sub build_posting_display { - my ($usernamesort,$subjectsort,$namesort,$notshown,$newitem,$dischash,$shown,$alldiscussion,$imsitems,$imsfiles,$roleinfo,$discussionitems,$replies,$depth,$posters,$maxdepth,$visible,$newpostsflag,$current,$status,$viewgrades,$seeid,$prevread,$sortposts,$ressymb,$target,$readkey,$showunmark,$showonlyunread,$totposters,$rolefilter,$sectionpick,$statusfilter,$toggkey,$outputtarget,$anonhash,$anoncnt) = @_; + my ($usernamesort,$subjectsort,$namesort,$notshown,$newitem,$dischash,$shown,$alldiscussion,$imsitems,$imsfiles,$roleinfo,$discussionitems,$replies,$depth,$posters,$maxdepth,$visible,$newpostsflag,$current,$status,$viewgrades,$seeid,$seehidden,$canvote,$prevread,$sortposts,$ressymb,$readkey,$showunmark,$showonlyunread,$totposters,$rolefilter,$sectionpick,$grouppick,$classgroups,$statusfilter,$toggkey,$outputtarget,$anonhash,$anoncnt,$group) = @_; my @original=(); my @index=(); + my $skip_group_check = 0; my $symb=&Apache::lonenc::check_decrypt($ressymb); - my $escsymb=&Apache::lonnet::escape($ressymb); + my $escsymb=&escape($ressymb); +# These are the discussion contributions my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'}); - + my (%likes,%userlikes,%userunlikes,@theselikes,$oneplus,$twoplus,$oneminus,$twominus); + my $thisuser=$env{'user.name'}.':'.$env{'user.domain'}; + if ($seeid || $canvote) { +# And these are the likes/unlikes + %likes=&Apache::lonnet::dump('disclikes', + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}, + '^'.$symb.':'); +# Array with likes to figure out averages, etc. + @theselikes=(); +# Hashes containing likes and unlikes for this user. + %userlikes=(); + %userunlikes=(); + } +# Is the user allowed to see the real name behind anonymous postings? + my $see_anonymous = + &Apache::lonnet::allowed('rin',$env{'request.course.id'}.($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:'')); + + if ((@{$grouppick} == 0) || (grep(/^all$/,@{$grouppick}))) { + $skip_group_check = 1; + } +# Deletions and hiddens are just lists. Split them up into a hash for quicker lookup + my (%deletions,%hiddens); + if ($contrib{'deleted'}) { + my $deleted = $contrib{'deleted'}; + $deleted =~ s/^\.//; + $deleted =~ s/\.$//; + %deletions = map { $_ => 1 } (split(/\.\./,$deleted)); + } + if ($contrib{'hidden'}) { + my $hidden = $contrib{'hidden'}; + $hidden =~ s/^\.//; + $hidden =~ s/\.$//; + %hiddens = map { $_ => 1 } (split(/\.\./,$hidden)); + } +# Versions if store/restore are used to actually store the messages. if ($contrib{'version'}) { my $oldest = $contrib{'1:timestamp'}; if ($prevread eq '0') { @@ -776,22 +1052,99 @@ sub build_posting_display { } my ($skiptest,$rolematch,$roleregexp,$secregexp,$statusregexp); if ($sortposts) { - ($skiptest,$roleregexp,$secregexp,$statusregexp) = &filter_regexp($rolefilter,$sectionpick,$statusfilter); + ($skiptest,$roleregexp,$secregexp,$statusregexp) = + &filter_regexp($rolefilter,$sectionpick,$statusfilter); $rolematch = $roleregexp.':'.$secregexp.':'.$statusregexp; - } + } + my %votestyle; + if ($seeid || $canvote) { +# We need to go through this twice, first to get the likes/dislikes, then to actually build the display + for (my $id=1;$id<=$contrib{'version'};$id++) { + my $idx=$id; + next if ($contrib{$idx.':deleted'}); + next if ($contrib{$idx.':hidden'}); + unless ((($hiddens{$idx}) && (!$seehidden)) || ($deletions{$idx}) || (!$contrib{$idx.':message'})) { + push(@theselikes,$likes{$symb.':'.$idx.':likes'}); + if ($likes{$symb.':'.$idx.':likes'} ne '') { + if (ref($likes{$symb.':'.$idx.':likers'}) eq 'HASH') { + if (exists($likes{$symb.':'.$idx.':likers'}{$thisuser})) { + $userlikes{$idx} = 1; + } + } + if (ref($likes{$symb.':'.$idx.':unlikers'}) eq 'HASH') { + if (exists($likes{$symb.':'.$idx.':unlikers'}{$thisuser})) { + $userunlikes{$idx} = 1; + } + } + } + } + } +# Figure out average likes and standard deviation if there are enough +# discussions to warrant that + my $ave=0; + my $stddev=10000; + if ($#theselikes>1) { + my $sum=0; + my $num=$#theselikes+1; + foreach my $thislike (@theselikes) { + $sum+=$thislike; + } + $ave=$sum/$num; + my $sumsq=0; + foreach my $thislike (@theselikes) { + $sumsq+=($thislike-$ave)*($thislike-$ave); + } + $stddev=sqrt($sumsq/$num); + } +# Now we know the average likes $ave and the standard deviation $stddev +# Get the boundaries for markup + $oneplus=$ave+$stddev; + $twoplus=$ave+2.*$stddev; + $oneminus=$ave-$stddev; + $twominus=$ave-2.*$stddev; + if ($#theselikes>1) { + foreach my $class ('twoplus','oneplus','zero','oneminus','twominus') { + my $fontstyle = $env{'course.'.$env{'request.course.id'}.'.discussion_post_fonts_'.$class}; + if ($fontstyle ne '') { + my ($size,$weight,$style,$other) = split(/,/,$fontstyle); + if ($size ne '') { + $votestyle{$class} .= 'font-size: '.$size.';'; + } + if ($weight ne '') { + $votestyle{$class} .= 'font-weight: '.$weight.';'; + } + if ($style ne '') { + $votestyle{$class} .= 'font-style: '.$style.';'; + } + if ($other ne '') { + $votestyle{$class} .= $other; + } + if ($votestyle{$class} ne '') { + $votestyle{$class} = 'style="'.$votestyle{$class}.'"'; + } + } + } + } + } +# +# This is now the real loop. Go through all entries, pick up what we need +# for (my $id=1;$id<=$contrib{'version'};$id++) { my $idx=$id; + next if ($contrib{$idx.':deleted'}); + next if ($contrib{$idx.':hidden'}); +# If we get here, we are actually going to display the message - we don't know where and we don't know if we display +# previous edits, but it counts as one entry my $posttime = $contrib{$idx.':timestamp'}; if ($prevread <= $posttime) { $$newpostsflag = 1; } - my $hidden=($contrib{'hidden'}=~/\.$idx\./); my $studenthidden=($contrib{'studenthidden'}=~/\.$idx\./); - my $deleted=($contrib{'deleted'}=~/\.$idx\./); my $origindex='0.'; my $numoldver=0; if ($contrib{$idx.':replyto'}) { - if ( (($env{'environment.threadeddiscussion'}) && ($sortposts eq '')) || ($sortposts eq 'thread') || ($outputtarget eq 'export')) { + if ( ((!$env{'environment.unthreadeddiscussion'}) && ($sortposts eq '')) || + ($sortposts eq 'thread') || ($outputtarget eq 'export')) { # this is a follow-up message $original[$idx]=$original[$contrib{$idx.':replyto'}]; $$depth[$idx]=$$depth[$contrib{$idx.':replyto'}]+1; @@ -811,11 +1164,11 @@ sub build_posting_display { } else { $$replies[$$depth[$idx]]=1; } - unless ((($hidden) && (!$seeid)) || ($deleted)) { + unless ((($hiddens{$idx}) && (!$seehidden)) || ($deletions{$idx})) { $$visible++; if ($contrib{$idx.':history'}) { if ($contrib{$idx.':history'} =~ /:/) { - my @oldversions = split/:/,$contrib{$idx.':history'}; + my @oldversions = split(/:/,$contrib{$idx.':history'}); $numoldver = @oldversions; } else { $numoldver = 1; @@ -826,7 +1179,7 @@ sub build_posting_display { my %subjects = (); my %attachtxt = (); my %allattachments = (); - my ($screenname,$plainname); + my ($screenname,$plainname,$showaboutme); my $sender = &mt('Anonymous'); # Anonymous users getting number within a discussion # Since idx is in static order, this should give the same sequence every time. @@ -836,7 +1189,7 @@ sub build_posting_display { $$anonhash{$key}=&mt('Anonymous').' '.$anoncnt; } my ($message,$subject,$vgrlink,$ctlink); - &get_post_contents(\%contrib,$idx,$seeid,$outputtarget,\%messages,\%subjects,\%allattachments,\%attachtxt,$imsfiles,\$screenname,\$plainname,$numoldver); + &get_post_contents(\%contrib,$idx,$seeid,$seehidden,$outputtarget,\%messages,\%subjects,\%allattachments,\%attachtxt,$imsfiles,\$screenname,\$plainname,\$showaboutme,$numoldver); # Set up for sorting by subject @@ -845,7 +1198,7 @@ sub build_posting_display { $message.=$attachtxt{$numoldver}; $subject=$subjects{$numoldver}; if ($message) { - if ($hidden) { + if ($hiddens{$idx}) { $message=''.$message.''; if ($studenthidden) { $message .='

Deleted by poster (student).'; @@ -854,35 +1207,44 @@ sub build_posting_display { if ($subject eq '') { if (defined($$subjectsort{'__No subject'})) { - push @{$$subjectsort{'__No subject'}}, $idx; + push(@{$$subjectsort{'__No subject'}}, $idx); } else { @{$$subjectsort{'__No subject'}} = ("$idx"); } } else { if (defined($$subjectsort{$subject})) { - push @{$$subjectsort{$subject}}, $idx; + push(@{$$subjectsort{$subject}}, $idx); } else { @{$$subjectsort{$subject}} = ("$idx"); } } - if ((!$contrib{$idx.':anonymous'}) || ($seeid)) { - $sender=&Apache::loncommon::aboutmewrapper( - $plainname, - $contrib{$idx.':sendername'}, - $contrib{$idx.':senderdomain'}).' ('. - $contrib{$idx.':sendername'}.' at '. - $contrib{$idx.':senderdomain'}.')'; + if (!$contrib{$idx.':anonymous'} || $see_anonymous) { + if ($showaboutme) { + $sender = &Apache::loncommon::aboutmewrapper( + $plainname, + $contrib{$idx.':sendername'}, + $contrib{$idx.':senderdomain'}); + } else { + $sender = $plainname; + } + if ($see_anonymous) { + $sender .= ' ('.$contrib{$idx.':sendername'}.':'. + $contrib{$idx.':senderdomain'}.')'; + } + $sender = ''.$sender.''; if ($contrib{$idx.':anonymous'}) { $sender.=' ['.$$anonhash{$key}.'] '. $screenname; } - + if ($see_anonymous) { + $sender.=&Apache::loncommon::student_image_tag($contrib{$idx.':senderdomain'},$contrib{$idx.':sendername'}); + } # 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; + push(@{$$usernamesort{$contrib{$idx.':senderdomain'}}{$contrib{$idx.':sendername'}}}, $idx); } else { @{$$usernamesort{$contrib{$idx.':senderdomain'}}{$contrib{$idx.':sendername'}}} = ("$idx"); } @@ -902,61 +1264,57 @@ sub build_posting_display { %{$$namesort{$lastname}} = (); } if (defined($$namesort{$lastname}{$firstname})) { - push @{$$namesort{$lastname}{$firstname}}, $idx; + push(@{$$namesort{$lastname}{$firstname}}, $idx); } else { @{$$namesort{$lastname}{$firstname}} = ("$idx"); } - if ($env{'course.'.$env{'request.course.id'}.'.allow_discussion_post_editing'} =~ m/yes/i) { - if (($env{'user.domain'} eq $contrib{$idx.':senderdomain'}) && ($env{'user.name'} eq $contrib{$idx.':sendername'})) { - $sender.=' '.&mt('Edit').''; - unless ($seeid) { - $sender.=" '; - } + if ($outputtarget ne 'tex') { +# Add karma stars + my $karma=&userkarma($contrib{$idx.':sendername'},$contrib{$idx.':senderdomain'}); + for (my $i=1;$i<=$karma;$i++) { + $sender.=''.&mt('Contributor Kudos').''; } - } - if ($seeid) { - if ($hidden) { - unless ($studenthidden) { - $sender.=' '; } - $sender .= '">'.&mt('Make Visible').''; - } - } else { - $sender.=' '.&mt('Hide').''; - } - $sender.=' '.&mt('Delete').''; + if ($seehidden) { + if ($hiddens{$idx}) { + unless ($studenthidden) { + $sender.=' '. + &discussion_link($symb,&mt('Make Visible'),'unhide',$idx,$$newpostsflag,$prevread,&group_args($group)); + } + } else { + $sender.=' '. + &discussion_link($symb,&mt('Hide'),'hide',$idx,$$newpostsflag,$prevread,&group_args($group)); + } + my $grpargs = &group_args($group); + $sender.= + " "; + $sender .= &mt('Delete').''; + } } - } else { + } else { if ($screenname) { $sender=''.$screenname.''; } else { $sender=''.$$anonhash{$key}.''; } + $sender = ''.$sender.''; # 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; + push(@{$$usernamesort{'__anon'}{'__anon'}}, $idx); } else { @{$$usernamesort{'__anon'}{'__anon'}} = ("$idx"); } @@ -965,37 +1323,42 @@ sub build_posting_display { %{$$namesort{'__anon'}} = (); } if (defined($$namesort{'__anon'}{'__anon'})) { - push @{$$namesort{'__anon'}{'__anon'}}, $idx; + push(@{$$namesort{'__anon'}{'__anon'}}, $idx); } else { @{$$namesort{'__anon'}{'__anon'}} = ("$idx"); } } - if (&discussion_open($status) && - &Apache::lonnet::allowed('pch', - $env{'request.course.id'}. - ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) { - $sender.=' '; + } else { + $ctlink = ''; } - $sender .= '" '.$target.'>'.&mt('Reply').''; - } - if ($viewgrades) { - $vgrlink=&Apache::loncommon::submlink('Submissions', - $contrib{$idx.':sendername'},$contrib{$idx.':senderdomain'},$ressymb); - } - if ($$dischash{$readkey}=~/\.$idx\./) { - $ctlink = ''; - } else { - $ctlink = ''; } } #figure out at what position this needs to print } if ($outputtarget eq 'export' || $message) { my $thisindex=$idx; - if ( (($env{'environment.threadeddiscussion'}) && ($sortposts eq '')) || ($sortposts eq 'thread') || ($outputtarget eq 'export')) { + if ( ((!$env{'environment.unthreadeddiscussion'}) && ($sortposts eq '')) || + ($sortposts eq 'thread') || ($outputtarget eq 'export')) { $thisindex=$origindex.substr('00'.$$replies[$$depth[$idx]],-2,2); } $$alldiscussion{$thisindex}=$idx; @@ -1005,7 +1368,7 @@ sub build_posting_display { if ($outputtarget eq 'export') { %{$$imsitems{$idx}} = (); $$imsitems{$idx}{'isvisible'}='true'; - if ($hidden) { + if ($hiddens{$idx}) { $$imsitems{$idx}{'isvisible'}='false'; } $$imsitems{$idx}{'title'}=$subjects{$numoldver}; @@ -1013,7 +1376,7 @@ sub build_posting_display { $$imsitems{$idx}{'attach'}=$attachtxt{$numoldver}; $$imsitems{$idx}{'timestamp'}=$contrib{$idx.':timestamp'}; $$imsitems{$idx}{'sender'}=$plainname.' ('. - $contrib{$idx.':sendername'}.' at '. + $contrib{$idx.':sendername'}.':'. $contrib{$idx.':senderdomain'}.')'; $$imsitems{$idx}{'isanonymous'}='false'; if ($contrib{$idx.':anonymous'}) { @@ -1027,16 +1390,17 @@ sub build_posting_display { } else { if ($message) { my $spansize = 2; + my ($uname,$udom); if ($showonlyunread && $prevread > $posttime) { $$notshown{$idx} = 1; } elsif ($showunmark && $$dischash{$readkey}=~/\.$idx\./) { $$notshown{$idx} = 1; } else { # apply filters - my $uname = $contrib{$idx.':sendername'}; - my $udom = $contrib{$idx.':senderdomain'}; + $uname = $contrib{$idx.':sendername'}; + $udom = $contrib{$idx.':senderdomain'}; my $poster = $uname.':'.$udom; - if (defined($env{'form.totposters'})) { + if ($env{'form.totposters'} ne '') { if ($totposters == 0) { $$shown{$idx} = 0; } elsif ($totposters > 0) { @@ -1061,6 +1425,23 @@ sub build_posting_display { } } } + if ($$shown{$idx} && !$skip_group_check) { + my $showflag = 0; + if (ref($$classgroups{$poster}{active}) eq 'HASH') { + foreach my $grp (@{$grouppick}) { + if (grep/^\Q$grp\E$/, + keys(%{$$classgroups{$poster}{active}})) { + $showflag = 1; + last; + } + } + } + if ($showflag) { + $$shown{$idx} = 1; + } else { + $$shown{$idx} = 0; + } + } } else { $$shown{$idx} = 1; } @@ -1068,34 +1449,90 @@ sub build_posting_display { unless ($$notshown{$idx} == 1) { if ($prevread > 0 && $prevread <= $posttime) { $$newitem{$idx} = 1; - $$discussionitems[$idx] .= ' -

/; my $threadinsert=''; if ($thisdepth > 0) { $threadinsert='
Reply: '.$thisdepth.''; } - $discussionitems[$alldiscussion{$_}]=~s/<\/td>]*)>/$threadinsert<\/td>
/; - $discussionitems[$alldiscussion{$_}]=~s/]+)>(Edit|Hide|Delete|Reply|Submissions)<\/a>//g; - $discussionitems[$alldiscussion{$_}]=~s/(|<\/b>|<\/a>|]+)>)//g; + $discussionitems[$alldiscussion{$post}]=~s/<\/td>]*)>/$threadinsert<\/td>/; + $discussionitems[$alldiscussion{$post}]=~s/(|<\/b>|<\/a>|]+)>)//g; - $discussionitems[$alldiscussion{$_}]='\vskip 0 mm\noindent\makebox[2 cm][b]{\hrulefill}'.$discussionitems[$alldiscussion{$_}]; - $discussion.=$discussionitems[$alldiscussion{$_}]; + $discussionitems[$alldiscussion{$post}]='\vskip 0 mm\noindent\makebox[2 cm][b]{\hrulefill}'.$discussionitems[$alldiscussion{$post}]; + $discussion.=$discussionitems[$alldiscussion{$post}]; } elsif ($outputtarget eq 'export') { - my $postfilename = $alldiscussion{$_}.'-'.$imsitems{$alldiscussion{$_}}{'timestamp'}.'.html'; + my $postfilename = $alldiscussion{$post}.'-'.$imsitems{$alldiscussion{$post}}{'timestamp'}.'.html'; if ($manifestok) { - if (($depth[$alldiscussion{$_}] <= $currdepth) && ($alldiscussion{$_} != $firstidx)) { + if (($depth[$alldiscussion{$post}] <= $currdepth) && ($alldiscussion{$post} != $firstidx)) { print $manifestfile ' '."\n"; } - $currdepth = $depth[$alldiscussion{$_}]; + $currdepth = $depth[$alldiscussion{$post}]; print $manifestfile "\n". - ''. - ''.$imsitems{$alldiscussion{$_}}{'title'}.''; + ''. + ''.$imsitems{$alldiscussion{$post}}{'title'}.''; $imsresources .= "\n". - ''."\n". + ''."\n". ''."\n". - $imsfiles{$alldiscussion{$_}}{$imsitems{$alldiscussion{$_}}{'currversion'}}."\n". + $imsfiles{$alldiscussion{$post}}{$imsitems{$alldiscussion{$post}}{'currversion'}}.''."\n". ''; } my $postingfile; my $postingfilename = $tempexport.'/'.$postfilename; if ($postingfile = Apache::File->new('>'.$postingfilename)) { print $postingfile 'Discussion Post'. - $imsitems{$alldiscussion{$_}}{'title'}.' '. - $imsitems{$alldiscussion{$_}}{'sender'}. - $imsitems{$alldiscussion{$_}}{'timestamp'}.'

'. - $imsitems{$alldiscussion{$_}}{'message'}.'
'. - $imsitems{$alldiscussion{$_}}{'attach'}.''."\n"; + $imsitems{$alldiscussion{$post}}{'title'}.' '. + $imsitems{$alldiscussion{$post}}{'sender'}. + $imsitems{$alldiscussion{$post}}{'timestamp'}.'

'. + $imsitems{$alldiscussion{$post}}{'message'}.'
'. + $imsitems{$alldiscussion{$post}}{'attach'}.''."\n"; close($postingfile); } else { - $discussion .= 'An error occurred opening the export file for posting '.$alldiscussion{$_}.'
'; + $discussion .= $lt{'aerr'}.' '.$alldiscussion{$post}.'
'; } - $copyresult.=&replicate_attachments($imsitems{$alldiscussion{$_}}{'allattachments'},$tempexport); + $copyresult.=&replicate_attachments($imsitems{$alldiscussion{$post}}{'allattachments'},$tempexport); } else { - $discussion.='
'. $discussionitems[$alldiscussion{$_}]. + $discussion.=''. $discussionitems[$alldiscussion{$post}]. '
- + +
+ - END if ($sortposts) { my %sort_types = (); my %role_types = (); my %status_types = (); - &sort_filter_names(\%sort_types,\%role_types,\%status_types); + &sort_filter_names(\%sort_types,\%role_types,\%status_types,$crstype); - $discussion .= ''; } + $discussion .= ''; + } if ($dischash{$toggkey}) { - my $storebutton = &mt('Store read/unread changes'); + my $storebutton = &mt('Save read/unread changes'); $discussion.=''; } $discussion .= (< -
- - - - - - - - -
- $lt{'cuse'}:  + + $lt{'cuse'}:  END if ($newpostsflag) { $discussion .= @@ -576,27 +605,20 @@ END } } $discussion .= <  - $lt{'chgt'}? -
+   $lt{'chgt'}
'.&mt('Sorted by').': '.$sort_types{$sortposts}.'
'; + $discussion .= '
'.&mt('Sorted by').': '.$sort_types{$sortposts}.'
'; if (defined($env{'form.totposters'})) { $discussion .= &mt('Posts by').':'; if ($totposters > 0) { foreach my $poster (@posters) { - $poster =~ s/:/\@/; $discussion .= ' '.$poster.','; } $discussion =~ s/,$//; @@ -609,13 +631,17 @@ END $filterchoice = ''.&mt('sections').'- '.$env{'form.sectionpick'}; $filterchoice .= '    '; } + if (@grouppick > 0) { + $filterchoice = ''.&mt('groups').'- '.$env{'form.grouppick'}; + $filterchoice .= '    '; + } if (@rolefilter > 0) { $filterchoice .= ''.&mt('roles').'-'; - foreach (@rolefilter) { - $filterchoice .= ' '.$role_types{$_}.','; + foreach my $role (@rolefilter) { + $filterchoice .= ' '.$role_types{$role}.','; } $filterchoice =~ s/,$//; - $filterchoice .= '
        '; + $filterchoice .= '
'.(' ' x8); } if ($statusfilter) { $filterchoice .= ''.&mt('status').'- '.$status_types{$statusfilter}; @@ -623,15 +649,16 @@ END if ($filterchoice) { $discussion .= ''.&mt('Filters').': '.$filterchoice; } - $discussion .= '
'. - ''."\n". + ''."\n". ''."\n". + ' onclick="this.form.submit();" />'."\n". '
-

END + $discussion .= &action_links_bar($colspan,$ressymb,$visible, + $newpostsflag,$group, + $prevread,$markondisp,$seehidden); + $discussion .= "
\n"; } if ($outputtarget eq 'export') { if ($manifestok) { @@ -673,13 +702,13 @@ END open(OUTPUT, "zip -r $imszip * 2> /dev/null |"); close(OUTPUT); chdir $cwd; - $discussion .= 'Download the zip file from Discussion Posting Archive
'; + $discussion .= &mt('Download the zip file from [_1]Discussion Posting Archive','').'
'; if ($copyresult) { - $discussion .= 'The following errors occurred during export -
'.$copyresult; + $discussion .= &mt('The following errors occurred during export').' -
'.$copyresult; } } } else { - $discussion .= '
Unfortunately you will not be able to retrieve an archive of the discussion posts at this time, because there was a problem creating a manifest file.
'; + $discussion .= '
'.&mt('Unfortunately you will not be able to retrieve an archive of the discussion posts at this time, because there was a problem creating a manifest file.').'
'; } return $discussion; } @@ -687,88 +716,335 @@ END if ($discussiononly) { my $now = time; my $attachnum = 0; - my $newattachmsg = ''; - my @currnewattach = (); - my @currdelold = (); + my $currnewattach = []; + my $currdelold = []; my $comment = ''; my $subject = ''; if ($env{'form.origpage'}) { &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['addnewattach','deloldattach','delnewattach','timestamp','idx','subject','comment']); - $subject = &Apache::lonnet::unescape($env{'form.subject'}); - $comment = &Apache::lonnet::unescape($env{'form.comment'}); + $subject = &unescape($env{'form.subject'}); + $comment = &unescape($env{'form.comment'}); my @keepold = (); - &process_attachments(\@currnewattach,\@currdelold,\@keepold); - if (@currnewattach > 0) { - $attachnum += @currnewattach; + &process_attachments($currnewattach,$currdelold,\@keepold); + if (@{$currnewattach} > 0) { + $attachnum += @{$currnewattach}; } } - if (&discussion_open($status)) { - $discussion.=(< - - - - - -
-Note: in anonymous discussion, your name is visible only -to course faculty
-Title: 

- -ENDDISCUSS - if ($env{'form.origpage'}) { - $discussion.=''."\n"; - foreach (@currnewattach) { - $discussion.=''."\n"; - } - } - $discussion.="\n"; - if ($outputtarget ne 'tex') { - $discussion.=&generate_attachments_button('',$attachnum,$ressymb,$now,\@currnewattach,\@currdelold,'',$mode); - if (@currnewattach > 0) { - $newattachmsg .= 'New attachments
'; - if (@currnewattach > 1) { - $newattachmsg .= '
    '; - foreach my $item (@currnewattach) { - $item =~ m#.*/([^/]+)$#; - $newattachmsg .= '
  1. '.$1.'
  2. '."\n"; - } - $newattachmsg .= '
'."\n"; - } else { - $currnewattach[0] =~ m#.*/([^/]+)$#; - $newattachmsg .= ''.$1.'
'."\n"; - } + if ((&discussion_open($status)) && ($outputtarget ne 'tex')) { + if (($group ne '') && ($mode eq 'board')) { + if ((&check_group_priv($group,'pgd') eq 'ok') && + ($ressymb =~ m{^bulletin___\d+___adm/wrapper/adm/\Q$cdom\E/\Q$cnum\E/\d+/bulletinboard$})) { + $discussion .= + &postingform_display($mode,$ressymb,$now,$subject, + $comment,$outputtarget,$attachnum, + $currnewattach,$currdelold, + $group,$crstype); } - $discussion.=$newattachmsg; - $discussion.=&generate_preview_button(); - } + } else { + if (&Apache::lonnet::allowed('pch',$env{'request.course.id'}. + ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) { + + $discussion.= + &postingform_display($mode,$ressymb,$now,$subject, + $comment,$outputtarget,$attachnum, + $currnewattach,$currdelold,'',$crstype); + } else { + $discussion.= ''. + &mt('This discussion is closed.').''; + } + } } - } else { + if (!(&discussion_open($status)) && ($outputtarget ne 'tex')) { + $discussion.= ''. + &mt('This discussion is closed.').''; + } + } elsif ($outputtarget ne 'tex') { + $discussion.=''; } return $discussion; } +sub can_see_hidden { + my ($mode,$ressymb,$feedurl,$group,$cdom,$cnum,$crs) = @_; + my $seehidden; + if ($env{'request.course.id'}) { + unless ($cdom ne '' && $cnum ne '') { + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + } + if ($crs eq '') { + $crs = '/'.$env{'request.course.id'}; + if ($env{'request.course.sec'}) { + $crs.='_'.$env{'request.course.sec'}; + } + $crs=~s{_}{/}g; + } + if ($mode eq '') { + $mode='board'; + if ($feedurl =~ /$LONCAPA::assess_re/) { + $mode='problem'; + } + } + if (($group ne '') && ($mode eq 'board') && + ($ressymb =~ m{^bulletin___\d+\Q___adm/wrapper/adm/$cdom/$cnum/\E\d+/bulletinboard$})) { + if (&check_group_priv($group,'dgp') eq 'ok') { + $seehidden = 1; + } + } else { + $seehidden=&Apache::lonnet::allowed('rin',$crs); + } + } + return $seehidden; +} + +sub discussion_link { + my ($ressymb,$linktext,$cmd,$item,$flag,$prev,$adds,$title)=@_; + my $link='/adm/feedback?inhibitmenu=yes&modal=yes&'.$cmd.'='.&escape($ressymb).':::'.$item; + if ($flag) { $link .= '&previous='.$prev; } + if ($adds) { $link .= $adds; } + my $width=600; + my $height=600; + if (($cmd eq 'hide') || ($cmd eq 'unhide') || ($cmd eq 'like') || ($cmd eq 'unlike')) { + $width=300; + $height=200; + } + return &Apache::loncommon::modal_link($link,$linktext,$width,$height,undef,undef,$title); +} + + +sub send_feedback_link { + my ($ressymb) = @_; + return ''. + &discussion_link($ressymb, + ''.&mt('Post Discussion').'', + 'replydisc'). + ''; +} + +sub send_message_link { + my ($ressymb) = @_; + my $output = ''. + &discussion_link($ressymb, + ''.&mt('Send Feedback').'', + 'sendmessageonly'). + ''; + return $output; +} + +sub action_links_bar { + my ($colspan,$ressymb,$visible,$newpostsflag,$group,$prevread,$markondisp, + $seehidden) = @_; + my $discussion = '
'. + ''. + ''; + if ($newpostsflag) { + if (!$markondisp) { + $discussion .=''; + } else { + $discussion .= ''; + } + } else { + $discussion .= ''; + } + $discussion .= '
'; + my $escsymb=&escape($ressymb); + if ($visible) { + $discussion .= ''.&mt('Threaded View').'  '. + ''.&mt('Chronological View').'  '; + + my $otherviewurl='/adm/feedback?cmd=sortfilter&symb='.$escsymb.'&inhibitmenu=yes&modal=yes'; + if ($newpostsflag) { + $otherviewurl .= '&previous='.$prevread; + } + $otherviewurl .= &group_args($group); + $discussion .= &Apache::loncommon::modal_link($otherviewurl,&mt('Other Views ...'),800,340); + $discussion .= '
'; + } + $discussion .=''.&mt('Export').''; + if ($seehidden) { + $discussion .= '  '; + $discussion .=''.&mt('Undelete all deleted entries').''; + } + $discussion.='
'. + &mt('My general preferences on what is marked as NEW'). + '
'.&mt('Mark NEW posts no longer new').'
  
- '; + $$discussionitems[$idx] .= ''.&mt('NEW').'  '; } else { $$newitem{$idx} = 0; - $$discussionitems[$idx] .= ' -

NEW
- '; - } - $$discussionitems[$idx] .= ''; + } + $$discussionitems[$idx] .= ''.$subject.'  '. + $sender.' '.$vgrlink.' ('. + &Apache::lonlocal::locallocaltime($posttime).')'; if ($$dischash{$toggkey}) { - $$discussionitems[$idx].=''; + $$discussionitems[$idx].='  '.$ctlink; } - $$discussionitems[$idx].= '
   '. - ''.$subject.'  '. - $sender.' '.$vgrlink.' ('. - &Apache::lonlocal::locallocaltime($posttime).')  '. - $ctlink.'
'. - $message.'

'; + my $thislikes=$likes{$symb.':'.$idx.':likes'}; + my $likestyle; + if ($seeid || $canvote) { +# Figure out size based on likes + my $class = 'zero'; + my $thislikes=$likes{$symb.':'.$idx.':likes'}; + if ($thislikes>$twoplus) { + $class = 'twoplus'; + } elsif ($thislikes>$oneplus) { + $class = 'oneplus'; + } + if ($thislikes<$twominus) { + $class = 'twominus'; + } elsif ($thislikes<$oneminus) { + $class = 'oneminus'; + } + $likestyle = $votestyle{$class}; + } +# Actually glue in the message itself + $$discussionitems[$idx].= '
'. + "
". + $message. + '
'; + if ($canvote) { + my $ownpost; + if (($uname eq $env{'user.name'}) && + ($udom eq $env{'user.domain'})) { + $ownpost = 1; + } +# Put in the like and unlike buttons + if ($ownpost || (($hiddens{$idx}) && ($seehidden))) { + my $novote; + if ($ownpost) { + $novote = &mt('No voting for your own posts.'); + } else { + $novote = &mt('No voting for hidden posts.'); + } + $$discussionitems[$idx].= + ''. + ''.$novote.' '. + ''.$novote.''; + + } else { + if ($userlikes{$idx}) { + $$discussionitems[$idx].=''.&mt('You like this posting').''; + } else { + $$discussionitems[$idx].=' '.&discussion_link($symb,''.&mt('Like').'','like',$idx,$$newpostsflag,$prevread,&group_args($group),&mt("Like this posting")); + } + if ($userunlikes{$idx}) { + $$discussionitems[$idx].=''.&mt('You unlike this posting').''; + } else { + $$discussionitems[$idx].=' '.&discussion_link($symb,''.&mt('Unlike').'','unlike',$idx,$$newpostsflag,$prevread,&group_args($group),&mt("Unlike this posting")); + } + } + } + if ($seeid || $canvote) { + my $thislikes=$likes{$symb.':'.$idx.':likes'}; + if ($thislikes>0) { + $$discussionitems[$idx].=' ('.&mt("[_1] likes",$thislikes).')'; + } elsif ($thislikes<0) { + $$discussionitems[$idx].=' ('.&mt("[_1] unlikes",abs($thislikes)).')'; + } + } +# If there is any history to this post, inform the reader if ($contrib{$idx.':history'}) { my @postversions = (); - $$discussionitems[$idx] .= &mt('This post has been edited by the author.'); - if ($seeid) { - $$discussionitems[$idx] .= '  '.&mt('Display all versions').''; + $$discussionitems[$idx] .= '  '.&mt('This post has been edited by the author.'); + if ($seehidden) { + $$discussionitems[$idx] .= '  '. + &discussion_link($symb,&mt('Display all versions'),'allversions',$idx,$$newpostsflag,$prevread,&group_args($group)); } $$discussionitems[$idx].='
'.&mt('Earlier version(s) were posted on: '); if ($contrib{$idx.':history'} =~ m/:/) { - @postversions = split/:/,$contrib{$idx.':history'}; + @postversions = split(/:/,$contrib{$idx.':history'}); } else { @postversions = ("$contrib{$idx.':history'}"); } @@ -1104,12 +1541,19 @@ sub build_posting_display { $$discussionitems[$idx] .= ''.$version.'. - '.&Apache::lonlocal::locallocaltime($postversions[$i]).' '; } } +# end of unless ($$notshown ...) } +# end of if ($message) ... } +# end of the else-branch of target being export } +# end of unless hidden or deleted } +# end of the loop over all discussion entries } +# end of "if there actually are any discussions } +# end of subroutine "build_posting_display" } sub filter_regexp { @@ -1118,9 +1562,9 @@ sub filter_regexp { my $skiptest = 1; if (@{$rolefilter} > 0) { my @okrolefilter = (); - foreach (@{$rolefilter}) { - unless ($_ eq '') { - push @okrolefilter, $_; + foreach my $role (@{$rolefilter}) { + unless ($role eq '') { + push(@okrolefilter, $role); } } if (@okrolefilter > 0) { @@ -1138,9 +1582,9 @@ sub filter_regexp { } if (@{$sectionpick} > 0) { my @oksectionpick = (); - foreach (@{$sectionpick}) { - unless ($_ eq '') { - push @oksectionpick, $_; + foreach my $sec (@{$sectionpick}) { + unless ($sec eq '') { + push(@oksectionpick, $sec); } } if ((@oksectionpick > 0) && (!grep/^all$/,@oksectionpick)) { @@ -1154,6 +1598,7 @@ sub filter_regexp { $secregexp .= '[^:]*'; } } + if (defined($statusfilter) && $statusfilter ne '') { if ($statusfilter eq 'all') { $statusregexp = '[^:]+'; @@ -1167,14 +1612,14 @@ sub filter_regexp { sub get_post_contents { - my ($contrib,$idx,$seeid,$type,$messages,$subjects,$allattachments,$attachtxt,$imsfiles,$screenname,$plainname,$numver) = @_; + my ($contrib,$idx,$seeid,$seehidden,$type,$messages,$subjects,$allattachments,$attachtxt,$imsfiles,$screenname,$plainname,$showaboutme,$numver) = @_; my $discussion = ''; my $start=$numver; my $end=$numver + 1; %{$$imsfiles{$idx}}=(); if ($type eq 'allversions') { - unless($seeid) { - $discussion=&mt('You do not have privileges to view all versions of posts.').&mt('Please select a different role'); + unless($seehidden) { + $discussion=&mt('You do not have privileges to view all versions of posts.').' '.&mt('Please select a different role.'); return $discussion; } } @@ -1185,13 +1630,20 @@ sub get_post_contents { $$contrib{$idx.':sendername'}, $$contrib{$idx.':senderdomain'}); $$screenname=$$contrib{$idx.':screenname'}; - - my $sender=&Apache::loncommon::aboutmewrapper( + $$showaboutme = &Apache::lonnet::usertools_access($$contrib{$idx.':sendername'}, + $$contrib{$idx.':senderdomain'}, + 'aboutme'); + my $sender = $$plainname; + if ($$showaboutme) { + $sender = &Apache::loncommon::aboutmewrapper( $$plainname, $$contrib{$idx.':sendername'}, - $$contrib{$idx.':senderdomain'}).' ('. - $$contrib{$idx.':sendername'}.' at '. - $$contrib{$idx.':senderdomain'}.')'; + $$contrib{$idx.':senderdomain'}); + } + if ($seeid) { + $sender .= ' ('.$$contrib{$idx.':sendername'}.':'. + $$contrib{$idx.':senderdomain'}.')'; + } my $attachmenturls = $$contrib{$idx.':attachmenturl'}; my @postversions = (); if ($type eq 'allversions' || $type eq 'export') { @@ -1201,7 +1653,7 @@ sub get_post_contents { } &get_post_versions($messages,$$contrib{$idx.':message'},1); &get_post_versions($subjects,$$contrib{$idx.':subject'},1); - push @postversions,$$contrib{$idx.':timestamp'}; + push(@postversions,$$contrib{$idx.':timestamp'}); $end = @postversions; } else { &get_post_versions($messages,$$contrib{$idx.':message'},1,$numver); @@ -1218,7 +1670,9 @@ sub get_post_contents { my ($timesent,$attachmsg); my %currattach = (); $timesent = &Apache::lonlocal::locallocaltime($postversions[$i]); - &newline_to_br(\$messages->{$i}); + unless (&contains_block_html($messages->{$i})) { + &newline_to_br(\$messages->{$i}); + } $$messages{$i}=&Apache::lontexconvert::msgtexconverted($$messages{$i}); $$subjects{$i}=~s/\n/\
/g; $$subjects{$i}=&Apache::lontexconvert::msgtexconverted($$subjects{$i}); @@ -1228,18 +1682,18 @@ sub get_post_contents { if ($type eq 'export') { $$imsfiles{$idx}{$i} = ''; if ($attachmsg) { - $$attachtxt{$i} = '
Attachments:
'; - foreach (sort keys %currattach) { - if ($$allattachments{$_}{'filename'} =~ m-^/uploaded/([^/]+/[^/]+)(/feedback)?(/?\d*)/([^/]+)$-) { + $$attachtxt{$i} = '
'.&mt('Attachments').':
'; + foreach my $key (sort(keys(%currattach))) { + if ($$allattachments{$key}{'filename'} =~ m-^/uploaded/([^/]+/[^/]+)(/feedback)?(/?\d*)/([^/]+)$-) { my $fname = $1.$3.'/'.$4; - $$imsfiles{$idx}{$i} .= ''."\n"; + $$imsfiles{$idx}{$i} .= ''."\n"; $$attachtxt{$i}.= ''.$4.'
'; } } } } else { if ($attachmsg) { - $$attachtxt{$i} = '
Attachments:'.$attachmsg.'
'; + $$attachtxt{$i} = '
'.&mt('Attachments').':'.$attachmsg.'
'; } else { $$attachtxt{$i} = ''; } @@ -1253,7 +1707,7 @@ END } } if ($type eq 'allversions') { - $discussion.=(''); + $discussion.=''; return $discussion; } else { return; @@ -1263,11 +1717,11 @@ END sub replicate_attachments { my ($attachrefs,$tempexport) = @_; my $response; - foreach my $id (keys %{$attachrefs}) { + foreach my $id (keys(%{$attachrefs})) { if ($$attachrefs{$id}{'filename'} =~ m-^/uploaded/([^/]+)/([^/]+)(/feedback)?(/?\d*)/([^/]+)$-) { my $path = $tempexport; my $tail = $1.'/'.$2.$4; - my @extras = split/\//,$tail; + my @extras = split(/\//,$tail); my $destination = $tempexport.'/'.$1.'/'.$2.$4.'/'.$5; if (!-e $destination) { my $i= 0; @@ -1286,11 +1740,11 @@ sub replicate_attachments { print $attachcopy $content; close($attachcopy); } else { - $response .= 'Error copying file attachment - '.$5.' to IMS package: '.$!.'
'."\n"; + $response .= &mt('Error copying file attachment - [_1] to IMS package',$5).': '.$!.'
'."\n"; } } else { &Apache::lonnet::logthis("Replication of attachment failed when building IMS export of discussion posts - domain: $1, course: $2, file: $$attachrefs{$id}{'filename'} -error: $rtncode"); - $response .= 'Error copying file attachment - '.$5.' to IMS package: '.$rtncode.'
'."\n"; + $response .= &mt('Error copying file attachment - [_1] to IMS package: ',$5).$rtncode.'
'."\n"; } } } @@ -1299,14 +1753,23 @@ sub replicate_attachments { } sub mail_screen { - my ($r,$feedurl,$options) = @_; + my ($r,$feedurl,$options,$caller_symb,$attachmaxtext) = @_; if (exists($env{'form.origpage'})) { - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['subject','comment','currnewattach','addnewattach','deloldattach','delnewattach','timestamp','idx','anondiscuss','discuss','blog']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['subject','comment','currnewattach','addnewattach','deloldattach','delnewattach','timestamp','idx','anondiscuss','discuss','blog','group','ref']); } - my $bodytag=&Apache::loncommon::bodytag('Resource Feedback and Discussion', - '','onLoad="window.focus();setposttype();"'); - my $title=&Apache::lonnet::gettitle($feedurl); - if (!$title) { $title = $feedurl; } + + my %lt = &Apache::lonlocal::texthash( + 'myqu' => 'Question/comment/feedback:', + 'title' => 'Title', + 'reta' => 'Retained attachments', + 'atta' => 'Attachment', + ); + if($env{'form.editdisc'} || $env{'form.replydisc'}){ + %lt = &Apache::lonlocal::texthash( + 'myqu' => 'Post Discussion', + ); + } + my $restitle = &get_resource_title($caller_symb,$feedurl); my $quote=''; my $subject = ''; my $comment = ''; @@ -1324,12 +1787,15 @@ sub mail_screen { my $attachnum = 0; my $anonchk = (< 0) { + my (%msgversions,%subversions,$htmldecode); + $htmldecode = 0; + if ($env{'form.replydisc'}) { + $htmldecode = 1; } - my $message; - if ($idx > 0) { - my %msgversions = (); - &get_post_versions(\%msgversions,$contrib{$idx.':message'},0,$numoldver); - $message = $msgversions{$numoldver}; - } - &newline_to_br(\$message); - $quote='
'.&Apache::lontexconvert::msgtexconverted($message).'
'; - if ($idx > 0) { - my %subversions = (); - &get_post_versions(\%subversions,$contrib{$idx.':subject'},1,$numoldver); - $subject = 'Re: '.$subversions{$numoldver}; - } - $subject = &HTML::Entities::encode($subject,'<>&"'); - } else { - $attachmenturls = $contrib{$idx.':attachmenturl'}; - if ($idx > 0) { - my %msgversions = (); - &get_post_versions(\%msgversions,$contrib{$idx.':message'},0,$numoldver); + &get_post_versions(\%msgversions,$contrib{$idx.':message'},0,$numoldver); + &get_post_versions(\%subversions,$contrib{$idx.':subject'},$htmldecode, + $numoldver); + $subject = $subversions{$numoldver}; + if ($env{'form.replydisc'}) { + $quote = $msgversions{$numoldver}; + $subject = &HTML::Entities::encode(&mt('Re: ').$subject,'<>&"'); + } else { $comment = $msgversions{$numoldver}; - my %subversions = (); - &get_post_versions(\%subversions,$contrib{$idx.':subject'},0,$numoldver); - $subject = $subversions{$numoldver}; } + } + if ($env{'form.editdisc'}) { + $attachmenturls = $contrib{$idx.':attachmenturl'}; if (defined($contrib{$idx.':replyto'})) { $parentmsg = $contrib{$idx.':replyto'}; } unless (exists($env{'form.origpage'})) { - my $anonflag = 0; + my $anonflag = 'nonanon'; if ($contrib{$idx.':anonymous'}) { - $anonflag = 1; + $anonflag = 'anon'; } $anonscript = (<print(< -The LearningOnline Network with CAPA - -$htmlheader + my $alert = &mt('Please select a feedback type.'); + my $js= < // - -$bodytag -

$title

+END + + my ($textareaheader,$textareaclass); + if (&Apache::lonhtmlcommon::htmlareabrowser()) { + $textareaheader = &Apache::lonhtmlcommon::htmlareaselectactive(); + $textareaclass = 'class="LC_richDefaultOff"'; + if ($env{'request.course.id'}) { + unless (($env{'course.'.$env{'request.course.id'}.'.allow_limited_html_in_feedback'} =~ /^\s*yes\s*$/i) || ($env{'form.sendmessageonly'})) { + undef($textareaclass); + } + } + } + + # Breadcrumbs + my $brcrum = [{'href' => '', + 'text' => 'Resource Feedback and Discussion'}]; + + my %onload = ('onload' => 'window.focus();setposttype();'); + my %parms=('add_entries' => \%onload); + if ($env{'form.modal'} ne 'yes') { $parms{'bread_crumbs'} = $brcrum; } + my $start_page= + &Apache::loncommon::start_page('Resource Feedback and Discussion',$js,\%parms); + + if ($quote ne '') { + $quote = &HTML::Entities::decode($quote); + unless (&contains_block_html($quote)) { + &newline_to_br(\$quote); + } + $quote=&Apache::lonhtmlcommon::start_pick_box(). + &Apache::lonhtmlcommon::row_title(&mt('Quote')). + &Apache::lontexconvert::msgtexconverted($quote). + &Apache::lonhtmlcommon::row_closure(1). + &Apache::lonhtmlcommon::end_pick_box(); + } + my $header=''; + unless ($env{'form.modal'}) { + $header="

$restitle

"; + } + $r->print(<$lt{'myqu'} +$header
$prevtag + END if ($env{'form.replydisc'}) { $r->print(<print(< - + + END } $r->print(< +$options +
+END +$r->print(&Apache::lonhtmlcommon::start_pick_box()); +$r->print(<My question/comment/feedback:

-$latexHelp -Title:

+$textareaheader +

-

+$latexHelp +

END + + + $r->print(&Apache::lonhtmlcommon::row_title(&mt('Subject'))); + $r->print('

'); + $r->print(&Apache::lonhtmlcommon::row_closure()); + $r->print(&Apache::lonhtmlcommon::row_title(&mt('Message'))); + $r->print(''); + $r->print(&Apache::lonhtmlcommon::row_closure(1)); + $r->print(&Apache::lonhtmlcommon::end_pick_box()); + if ( ($env{'form.editdisc'}) || ($env{'form.replydisc'}) ) { if ($env{'form.origpage'}) { - foreach (@currnewattach) { - $r->print(''."\n"); + foreach my $attach (@currnewattach) { + $r->print(''."\n"); } - foreach (@currdelold) { - $r->print(''."\n"); + foreach my $oldatt (@currdelold) { + $r->print(''."\n"); } } if ($env{'form.editdisc'}) { if ($attachmenturls) { &extract_attachments($attachmenturls,$idx,$numoldver,\$attachmsg,\%attachments,\%currattach,\@currdelold); - $attachnum = scalar(keys %currattach); - foreach (keys %currattach) { - $r->print(''."\n"); + $attachnum = scalar(keys(%currattach)); + foreach my $key (keys(%currattach)) { + $r->print(''."\n"); } } } } else { $r->print(< +

+$lt{'atta'} $attachmaxtext:

END } + if (exists($env{'form.group'})) { + $r->print(''); + } + if (exists($env{'form.ref'})) { + $r->print(''); + } $r->print(< - -

+ END if ($env{'form.editdisc'} || $env{'form.replydisc'}) { my $now = time; my $ressymb = $symb; + &Apache::lonenc::check_encrypt(\$ressymb); my $postidx = ''; if ($env{'form.editdisc'}) { $postidx = $idx; @@ -1576,10 +2066,10 @@ END if (@currnewattach > 0) { $attachnum += @currnewattach; } - $r->print(&generate_attachments_button($postidx,$attachnum,$ressymb,$now,\@currnewattach,\@currdelold,$numoldver)); + my $blockblog = &Apache::loncommon::blocking_status('blogs'); if ($attachnum > 0) { if (@currnewattach > 0) { - $newattachmsg .= 'New attachments
'; + $newattachmsg .= '
'.&mt('New attachments').'
'; if (@currnewattach > 1) { $newattachmsg .= '
    '; foreach my $item (@currnewattach) { @@ -1593,16 +2083,17 @@ END } } if ($attachmsg) { - $r->print("Retained attachments:$attachmsg
    \n"); + $r->print("
    $lt{'reta'}:$attachmsg
    \n"); } if ($newattachmsg) { - $r->print("$newattachmsg
    "); + $r->print("$newattachmsg"); } } + $r->print(&generate_attachments_button($postidx,$attachnum,$ressymb,$now,\@currnewattach,\@currdelold,$numoldver,'',$blockblog)); } $r->print(&generate_preview_button(). - &Apache::lonhtmlcommon::htmlareaselectactive('comment'). - ''); + &Apache::loncommon::end_page()); + } sub print_display_options { @@ -1613,24 +2104,21 @@ sub print_display_options { 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( - 'dido' => 'Discussion display options', 'pref' => 'Display Preference', 'curr' => 'Current setting ', 'actn' => 'Action', 'deff' => 'Default for all discussions', 'prca' => 'Preferences can be set for this discussion that determine ....', - 'whpo' => 'Which posts are displayed when you display this bulletin board or resource, and', + 'whpo' => 'Which posts are displayed when you display this discussion board or resource, and', 'unwh' => 'Under what circumstances posts are identified as "NEW", and', 'wipa' => 'Whether individual posts can be marked as read/unread', 'allposts' => 'All posts', 'unread' => 'New posts only', 'unmark' => 'Posts not marked read', 'ondisp' => 'Once displayed', - 'onmark' => 'Once marked not NEW ', + 'onmark' => 'Once marked not NEW', 'toggon' => 'Shown', 'toggoff' => 'Not shown', 'disa' => 'Posts displayed?', @@ -1675,13 +2163,9 @@ sub print_display_options { $currtogg = $lt{'toggon'}; $disctogg = 'toggon'; } - my $html=&Apache::lonxml::xmlbegin(); - $r->print(< -$lt{'dido'} - - - -$bodytag +END + + + my $start_page = + &Apache::loncommon::start_page('Discussion display options',$js); + my $end_page = + &Apache::loncommon::end_page(); + $r->print(< $lt{'sdpf'}
    $lt{'prca'}
    1. $lt{'whpo'}
    2. $lt{'unwh'}
    3. $lt{'wipa'}

    - - - - -
    - - - - -
    - - - - - +END + $r->print(&Apache::loncommon::start_data_table()); + $r->print(< + - +END + $r->print(&Apache::loncommon::start_data_table_row()); + $r->print(<$lt{'disa'} - - +END + $r->print(&Apache::loncommon::end_data_table_row()); + $r->print(&Apache::loncommon::start_data_table_row()); + $r->print(<$lt{'npmr'} - +END + $r->print(&Apache::loncommon::end_data_table_row()); + $r->print(&Apache::loncommon::start_data_table_row()); + $r->print(<$lt{'dotm'} - - -
    $lt{'pref'}$lt{'curr'}$lt{'actn'}?$lt{'pref'} + $lt{'curr'} + $lt{'actn'}?
    $lt{$discdisp} +
    - +
    $lt{$discmark}
    $lt{$disctogg}
    -
    -
    +
- - - - - - - - - + + + + + + + + + + + - - - - - + + +
$lt{'soor'} $lt{'sprs'} $lt{'spur'} $lt{'spse'} $lt{'psub'}$lt{'soor'} $lt{'sprs'} $lt{'spur'} $lt{'spse'} $lt{'spgr'} $lt{'psub'}
+   +   - +   - +   + +  

-
+ + - + +END + if (exists($env{'form.group'})) { + $r->print(''); + } + if (exists($env{'form.ref'})) { + $r->print(''); + } + $r->print("

- - -END +$end_page +"); } sub print_showposters { @@ -1967,28 +2530,28 @@ sub print_showposters { $r->send_http_header; &Apache::lonenc::check_encrypt(\$symb); - 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 $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $group = $env{'form.group'}; + my $ressymb = &wrap_symb($symb); + my $seehidden = &can_see_hidden('',$ressymb,$feedurl,$group,$cdom,$cnum); my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); + $cdom,$cnum); my %namesort = (); my %postcounts = (); - my %lt=&Apache::lonlocal::texthash( - 'diso' => 'Discussion filtering options', - ); - my $bodytag=&Apache::loncommon::bodytag('Discussion options', - '',''); + + my %lt = &Apache::lonlocal::texthash( + sele => 'Select', + full => 'Fullname', + usdo => 'Username:domain', + post => 'Posts', + ); 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)) { + unless ((($hidden) && (!$seehidden)) || ($deleted)) { + if ((!$contrib{$idx.':anonymous'}) || (&Apache::lonnet::allowed('rin',$env{'request.course.id'}.($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:'')))) { my %names = &Apache::lonnet::get('environment',['firstname','lastname'],$contrib{$idx.':senderdomain'},$contrib{$idx.':sendername'}); my $lastname = $names{'lastname'}; my $firstname = $names{'firstname'}; @@ -2005,61 +2568,59 @@ sub print_showposters { $postcounts{$poster} ++; if (defined($namesort{$lastname}{$firstname})) { if (!grep/^$poster$/,@{$namesort{$lastname}{$firstname}}) { - push @{$namesort{$lastname}{$firstname}}, $poster; + push(@{$namesort{$lastname}{$firstname}}, $poster); } } else { @{$namesort{$lastname}{$firstname}} = ("$poster"); } } } - } + } } - my $html=&Apache::lonxml::xmlbegin(); + + my $start_page = &Apache::loncommon::start_page('Discussion options'); + my $table_start =&Apache::loncommon::start_data_table(); $r->print(< -$lt{'diso'} - - -$bodytag +$start_page
- - - '. + ''; + } if ($discussoptions) { - $discussoptions='

'.&mt('Discussion Contributions').'

'.$discussoptions; } - return $msgoptions.$discussoptions; + $discussoptions=''. + ''; + } + return &Apache::loncommon::start_data_table().$msgoptions.$discussoptions.&Apache::loncommon::end_data_table(); } sub resource_output { @@ -2338,7 +2961,6 @@ sub resource_output { sub clear_out_html { my ($message,$override)=@_; - unless (&Apache::lonhtmlcommon::htmlareablocked()) { return $message; } # Always allow the -tag my %html=(M=>1); # Check if more is allowed @@ -2347,11 +2969,12 @@ sub clear_out_html { ($override)) { # allows


      • #

        - # + #

  • - - - - - - +
    + $table_start + + + + + 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/:/,$_; + foreach my $last (sort(keys(%namesort))) { + foreach my $first (sort(keys(%{$namesort{$last}}))) { + foreach my $user (sort(@{$namesort{$last}{$first}})) { + my ($uname,$udom) = split(/:/,$user); if (!$uname || !$udom) { next; } else { $count ++; - $r->print(''); + $r->print(&Apache::loncommon::start_data_table_row(). + ' + + + '. + &Apache::loncommon::end_data_table_row()); } } } } + $r->print(&Apache::loncommon::end_data_table()); + my $end_page = &Apache::loncommon::end_page(); $r->print(< - - -
    No.SelectFullname(Username/domain)Posts
    #$lt{'sele'}$lt{'full'} ($lt{'usdo'})$lt{'post'}
    '.$count.''.$last.', '.$first.' ('.$uname.','.$udom.')'.$postcounts{$_}.'
    '.$count.''.$last.', '.$first.' ('.$uname.':'.$udom.')'.$postcounts{$user}.'

    + - + - - +$end_page END } @@ -2067,7 +2628,8 @@ sub get_post_versions { my ($versions,$incoming,$htmldecode,$numver) = @_; if ($incoming =~ /^/) { my $p = HTML::LCParser->new(\$incoming); - my $done = 0; + my $done = 0; + while ( (my $token = $p->get_tag("version")) && (!$done)) { my $num = $token->[1]{num}; my $text = $p->get_text("/version"); @@ -2127,43 +2689,39 @@ sub get_post_attachments { sub fail_redirect { my ($r,$feedurl) = @_; if ($feedurl=~/^\/adm\//) { $feedurl.='?register=1' }; + my %lt = &Apache::lonlocal::texthash( + 'sorr' => 'Sorry, no recipients ...', + ); my $logo=&Apache::loncommon::lonhttpdurl('/adm/lonIcons/lonlogos.gif'); - my $html=&Apache::lonxml::xmlbegin(); - $r->print (< -Feedback not sent - - - - + $r->print(&Apache::loncommon::start_page('Feedback not sent',undef, + {'redirect' => [2,$feedurl], + 'only_body' => 1,})); + $r->print(< -Sorry, no recipients ... -
    Continue - - +

    $lt{'sorr'}

    ENDFAILREDIR + $r->print(&Apache::loncommon::end_page()); } sub redirect_back { - my ($r,$feedurl,$typestyle,$sendsomething,$sendposts,$blog,$status,$previous,$sort,$rolefilter,$statusfilter,$sectionpick,$numpicks) = @_; + my ($r,$feedurl,$typestyle,$sendsomething,$sendposts,$blog,$status,$previous,$sort,$rolefilter,$statusfilter,$sectionpick,$grouppick,$numpicks,$group,$toolarge) = @_; my $sorttag = ''; my $roletag = ''; my $statustag = ''; my $sectag = ''; + my $grptag = ''; my $userpicktag = ''; my $qrystr = ''; my $prevtag = ''; &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; - &dewrapper(\$feedurl); if ($feedurl=~/^\/adm\//) { $feedurl.='?register=1' }; if ($previous > 0) { $qrystr = 'previous='.$previous; if ($feedurl =~ /\?register=1/) { - $feedurl .= '&'.$qrystr; + $feedurl .= '&'.$qrystr; } else { $feedurl .= '?'.$qrystr; } @@ -2172,63 +2730,94 @@ sub redirect_back { if (defined($sort)) { my $sortqry = 'sortposts='.$sort; if (($feedurl =~ /\?register=1/) || ($feedurl =~ /\?previous=/)) { - $feedurl .= '&'.$sortqry; + $feedurl .= '&'.$sortqry; } else { $feedurl .= '?'.$sortqry; } $sorttag = ''; if (defined($numpicks)) { my $userpickqry = 'totposters='.$numpicks; - $feedurl .= '&'.$userpickqry; + $feedurl .= '&'.$userpickqry; $userpicktag = ''; } else { if (ref($sectionpick) eq 'ARRAY') { - $feedurl .= '§ionpick='; + $feedurl .= '&sectionpick='; $sectag .= ''; } else { - $feedurl .= '§ionpick='.$sectionpick; + $feedurl .= '&sectionpick='.$sectionpick; $sectag = ''; } + if (ref($grouppick) eq 'ARRAY') { + $feedurl .= '&grouppick='; + $sectag .= ''; + } else { + $feedurl .= '&grouppick='.$grouppick; + $grptag = ''; + } if (ref($rolefilter) eq 'ARRAY') { - $feedurl .= '&rolefilter='; + $feedurl .= '&rolefilter='; $roletag .= ''; } else { - $feedurl .= '&rolefilter='.$rolefilter; + $feedurl .= '&rolefilter='.$rolefilter; $roletag = ''; } - $feedurl .= '&statusfilter='.$statusfilter; + $feedurl .= '&statusfilter='.$statusfilter; $statustag =''; } } - $feedurl=&Apache::lonenc::check_encrypt($feedurl); + my $grouptag; + if ($group ne '') { + $grouptag = ''; + my $refarg; + if (exists($env{'form.ref'})) { + $refarg = '&ref='.$env{'form.ref'}; + $grouptag .= ''; + } + if ($feedurl =~ /\?/) { + $feedurl .= '&group='.$group.$refarg; + } else { + $feedurl .= '?group='.$group.$refarg; + } + } + &Apache::lonenc::check_encrypt(\$feedurl); my $logo=&Apache::loncommon::lonhttpdurl('/adm/lonIcons/lonlogos.gif'); - my $html=&Apache::lonxml::xmlbegin(); - $r->print (< -Feedback sent - - - - + my %parms=('only_body' => 1); + if ($env{'form.modal'}) { + $parms{'add_entries'}={'onLoad' => 'document.forms.reldt.submit()'}; + } else { + $parms{'redirect'}=[0,$feedurl]; + } + my $start_page= + &Apache::loncommon::start_page('Feedback sent',undef,\%parms); + my $end_page = &Apache::loncommon::end_page(); + $r->print(< $typestyle Sent $sendsomething message(s), and $sendposts post(s). $blog +$toolarge $status
    $prevtag @@ -2236,91 +2825,125 @@ $sorttag $statustag $roletag $sectag +$grptag $userpicktag +$grouptag
    -
    Continue - - +$end_page ENDREDIR } sub no_redirect_back { my ($r,$feedurl) = @_; - my $nofeed=&mt('Sorry, no feedback possible on this resource ...'); - my $continue=&mt('Continue'); - my $html=&Apache::lonxml::xmlbegin(); - $r->print (< -Feedback not sent - -ENDNOREDIR - - if ($feedurl!~/^\/adm\/feedback/) { - $r->print(''); + my $nofeed=&mt('Sorry, no feedback possible on this resource ...'); + + my %onload; + + my %body_options = ('only_body' => 1, + 'bgcolor' => '#FFFFFF', + 'add_entries' => \%onload,); + + if ($feedurl !~ m{^/adm/feedback}) { + $body_options{'redirect'} = [2,$feedurl]; } - $feedurl=&Apache::lonenc::check_encrypt($feedurl); + my $start_page= + &Apache::loncommon::start_page('Feedback not sent',undef, + \%body_options); + + my $end_page = &Apache::loncommon::end_page(); + + &Apache::lonenc::check_encrypt(\$feedurl); my $logo=&Apache::loncommon::lonhttpdurl('/adm/lonIcons/lonlogos.gif'); $r->print (< - +$start_page $nofeed -
    $continue - - +
    +$end_page ENDNOREDIRTWO } sub screen_header { - my ($feedurl,$symb) = @_; + my ($feedurl,$symb,$group) = @_; + my $crscontent = &mt('Question/Comment/Feedback about course content'); + my $crspolicy = &mt('Question/Comment/Feedback about course policy'); + my $contribdisc = &mt('Contribution to course discussion of resource'); + my $anoncontrib = &mt('Anonymous contribution to course discussion of resource'); + my $namevis = &mt('name only visible to course faculty'); + my $crstype; + if ($env{'request.course.id'}) { + $crstype = &Apache::loncommon::course_type(); + if ($crstype eq 'Community') { + $crscontent = &mt('Question/Comment/Feedback about community content'); + $crspolicy = &mt('Question/Comment/Feedback about community policy'); + $contribdisc = &mt('Contribution to community discussion of resource'); + $anoncontrib = &mt('Anonymous contribution to community discussion of resource'); + $namevis = &mt('name only visible to community facilitators'); + } + } my $msgoptions=''; my $discussoptions=''; unless (($env{'form.replydisc'}) || ($env{'form.editdisc'})) { if (($feedurl=~/^\/res\//) && ($feedurl!~/^\/res\/adm/) && ($env{'user.adv'})) { $msgoptions= - '

    '; + '
    '; + } + my %optionhash=(); + foreach my $type ('question','comment','policy') { + $optionhash{$type}=$env{'course.'.$env{'request.course.id'}.'.'.$type.'.email.text'}; } if (&feedback_available(1)) { $msgoptions.= - '

    '; + '
    '; } if (&feedback_available(0,1)) { $msgoptions.= - '

    '; + '
    '; } if (&feedback_available(0,0,1)) { $msgoptions.= - '

    '; + '
    '; } } - if ($env{'request.course.id'}) { - if (&discussion_open(undef,$symb) && - &Apache::lonnet::allowed('pch', - $env{'request.course.id'}. - ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) { - $discussoptions='
    '. + if (($env{'request.course.id'}) && (!$env{'form.sendmessageonly'})) { + my ($blocked,$blocktext) = &Apache::loncommon::blocking_status('boards'); + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $realsymb = &get_realsymb($symb); + if (!$blocked && &discussion_open(undef,$realsymb) && + (&Apache::lonnet::allowed('pch', + $env{'request.course.id'}. + ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:'')) || + (($group ne '') && ($symb =~ m{^bulletin___\d+___adm/wrapper/adm/\Q$cdom\E/\Q$cnum\E/\d+/bulletinboard$}) && (&check_group_priv($group,'pgd') eq 'ok')))) { + $discussoptions='
    '. ''.&mt('Change Screenname').''; + my $blockblog = &Apache::loncommon::blocking_status('blogs'); + if (!$blockblog) { + $discussoptions.= &add_blog_checkbox($crstype); + } } - #$discussoptions.='
    '; } - if ($msgoptions) { $msgoptions='

    '.&mt('Sending Messages').'

    '.$msgoptions; } + if ($msgoptions) { + $msgoptions='
    ' + .'
    '.&mt('Send Feedback').'
    '.&Apache::lonhtmlcommon::coursepreflink(&mt('Feedback Settings'),'feedback').'
    '.$msgoptions.'
    ' + .'
    '.&mt('Discussion Contributions').'
    '.&Apache::lonhtmlcommon::coursepreflink(&mt('Discussion Settings'),'discussion').'
    '.$discussoptions.'
    %html=(B=>1, I=>1, P=>1, A=>1, LI=>1, OL=>1, UL=>1, EM=>1, - BR=>1, TT=>1, STRONG=>1, BLOCKQUOTE=>1, DIV=>1, IMG=>1, - M=>1, ALGEBRA=>1, SUB=>1, SUP=>1, SPAN=>1, - H1=>1, H2=>1, H3=>1, H4=>1, H5=>1); + BR=>1, TT=>1, STRONG=>1, BLOCKQUOTE=>1, PRE=>1, DIV=>1, IMG=>1, + M=>1, CHEM=>1, ALGEBRA=>1, SUB=>1, SUP=>1, SPAN=>1, + H1=>1, H2=>1, H3=>1, H4=>1, H5=>1, H6=>1, + TABLE=>1, TR=>1, TD=>1, TH=>1, TBODY=>1); } # Do the substitution of everything that is not explicitly allowed $message =~ s/\<(\/?\s*(\w+)[^\>\<]*)/ @@ -2362,164 +2985,150 @@ sub clear_out_html { } sub assemble_email { - my ($feedurl,$message,$prevattempts,$usersaw,$useranswer)=@_; + my ($message,$prevattempts,$usersaw,$useranswer)=@_; + my %lt = &Apache::lonlocal::texthash( + 'prev' => 'Previous attempts of student (if applicable)', + 'orig' => 'Original screen output (if applicable)', + 'corr' => 'Correct Answer(s) (if applicable)', + ); + if (&Apache::loncommon::course_type() eq 'Community') { + $lt{'prev'} = &mt('Previous attempts of member (if applicable)'); + } my $email=<<"ENDEMAIL"; $message ENDEMAIL my $citations=<<"ENDCITE"; -

    Previous attempts of student (if applicable)

    +

    $lt{'prev'}

    $prevattempts

    -

    Original screen output (if applicable)

    +

    $lt{'orig'}

    $usersaw -

    Correct Answer(s) (if applicable)

    +

    $lt{'corr'}

    $useranswer ENDCITE return ($email,$citations); } -sub secapply { - my $rec=shift; - my $defaultflag=shift; - $rec=~s/\s+//g; - $rec=~s/\@/\:/g; - my ($adr,$sections)=($rec=~/^([^\(]+)\(([^\)]+)\)/); - if ($sections) { - foreach (split(/\;/,$sections)) { - if (($_ eq $env{'request.course.sec'}) || - ($defaultflag && ($_ eq '*'))) { - return $adr; - } - } - } else { - return $rec; - } - return ''; -} - -=pod - -=over 4 - -=item * - -decide_receiver($feedurl,$author,$question,$course,$policy,$defaultflag); - -Arguments - $feedurl - /res/ url of resource (only need if $author is true) - $author,$question,$course,$policy - all true/false parameters - if true will attempt to find the addresses of user that should receive - this type of feedback (author - feedback to author of resource $feedurl, - $question 'Resource Content Questions', $course 'Course Content Question', - $policy 'Course Policy') - (Additionally it also checks $env for whether the corresponding form. - element exists, for ease of use in a html response context) - - $defaultflag - (internal should be left blank) if true gather addresses - that aren't for a section even if I have a section - (used for reccursion internally, first we look for - addresses for our specific section then we recurse - and look for non section addresses) - -Returns - $typestyle - string of html text, describing what addresses were found - %to - a hash, which keys are addresses of users to send messages to - the keys will look like name:domain - -=cut - -sub decide_receiver { - my ($feedurl,$author,$question,$course,$policy,$defaultflag) = @_; - my $typestyle=''; - my %to=(); - if ($env{'form.author'}||$author) { - $typestyle.='Submitting as Author Feedback
    '; - $feedurl=~/^\/res\/(\w+)\/(\w+)\//; - $to{$2.':'.$1}=1; - } - if ($env{'form.question'}||$question) { - $typestyle.='Submitting as Question
    '; - foreach (split(/\,/, - $env{'course.'.$env{'request.course.id'}.'.question.email'}) - ) { - my $rec=&secapply($_,$defaultflag); - if ($rec) { $to{$rec}=1; } - } - } - if ($env{'form.course'}||$course) { - $typestyle.='Submitting as Comment
    '; - foreach (split(/\,/, - $env{'course.'.$env{'request.course.id'}.'.comment.email'}) - ) { - my $rec=&secapply($_,$defaultflag); - if ($rec) { $to{$rec}=1; } - } - } - if ($env{'form.policy'}||$policy) { - $typestyle.='Submitting as Policy Feedback
    '; - foreach (split(/\,/, - $env{'course.'.$env{'request.course.id'}.'.policy.email'}) - ) { - my $rec=&secapply($_,$defaultflag); - if ($rec) { $to{$rec}=1; } - } - } - if ((scalar(%to) eq '0') && (!$defaultflag)) { - ($typestyle,%to)= - &decide_receiver($feedurl,$author,$question,$course,$policy,1); - } - return ($typestyle,%to); -} sub feedback_available { my ($question,$course,$policy)=@_; - my ($typestyle,%to)=&decide_receiver('',0,$question,$course,$policy); + my ($typestyle,%to)=&Apache::lonmsg::decide_receiver('',0,$question, + $course,$policy); return scalar(%to); } sub send_msg { - my ($feedurl,$email,$citations,$attachmenturl,%to)=@_; - my $status=''; - my $sendsomething=0; - foreach (keys %to) { - if ($_) { - my $declutter=&Apache::lonnet::declutter($feedurl); - unless (&Apache::lonmsg::user_normal_msg(split(/\:/,$_), - 'Feedback ['.$declutter.']',$email,$citations,$feedurl, - $attachmenturl)=~/ok/) { - $status.='
    '.&mt('Error sending message to').' '.$_.'
    '; - } else { - $sendsomething++; - } + my ($title,$feedurl,$email,$citations,$attachmenturl,$symb,%to)=@_; + my $status=''; + my $sendsomething=0; + my $restitle = &get_resource_title($symb,$feedurl); + if ($title=~/^Error/) { $title=&mt('Feedback').': '.$title; } + unless ($title=~/\w/) { $title=&mt('Feedback'); } + foreach my $key (keys(%to)) { + if ($key) { + my ($user,$domain) = split(/\:/,$key,2); + if (!defined($user)) { + $status.='
    '.&mt('Error sending message to [_1], no user specified.',$key); + } elsif (!defined($domain)) { + $status.='
    '.&mt('Error sending message to [_1], no domain specified.',$key); + } else { + unless (&Apache::lonmsg::user_normal_msg($user,$domain, + $title.' ['.$restitle.']',$email,$citations,$feedurl, + $attachmenturl,undef,undef,$symb,$restitle)=~/ok/) { + $status.='
    '.&mt('Error sending message to').' '.$key.'
    '; + } else { + $sendsomething++; + } + } + } } - } - my %record=&Apache::lonnet::restore('_feedback'); - my ($temp)=keys %record; - unless ($temp=~/^error\:/) { - my %newrecord=(); - $newrecord{'resource'}=$feedurl; - $newrecord{'subnumber'}=$record{'subnumber'}+1; - unless (&Apache::lonnet::cstore(\%newrecord,'_feedback') eq 'ok') { - $status.='
    '.&mt('Not registered').'
    '; - } +# Records of number of feedback messages are kept under the "symb" called "_feedback" +# There are two entries within the framework of a course: +# - the URLs for which feedback was provided +# - the total number of contributions + if ($sendsomething) { + my %record=&getfeedbackrecords(); + my ($temp)=keys(%record); + unless ($temp=~/^error\:/) { + my %newrecord=(); + $newrecord{'resource'}=$feedurl; + $newrecord{'subnumber'}=$record{'subnumber'}+1; + unless (&Apache::lonnet::cstore(\%newrecord,'_feedback') eq 'ok') { + $status.='
    '.&mt('Not registered').'
    '; + } + } } - - return ($status,$sendsomething); + return ($status,$sendsomething); +} + +# Routine to get the complete feedback records + +sub getfeedbackrecords { + my ($uname,$udom,$course)=@_; + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my %record=&Apache::lonnet::restore('_feedback',$course,$udom,$uname); + return %record; +} + +# Routine to get feedback statistics + +sub getfeedbackstats { + my %record=&getfeedbackrecords(@_); + return ($record{'subnumber'},$record{'points'},$record{'totallikes'}); +} + +# Store feedback credit + +sub storefeedbackpoints { + my ($points,$uname,$udom,$course)=@_; + unless ($points) { $points=0; } + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my %record=('grader_user' => $env{'user.name'}, + 'grader_domain' => $env{'user.domain'}, + 'points' => $points); + return &Apache::lonnet::cstore(\%record,'_feedback',$course,$udom,$uname); +} + +# Store feedback "likes" + +sub storefeedbacklikes { + my ($likes,$uname,$udom,$course)=@_; + unless ($likes) { $likes=0; } + if ($likes>0) { $likes=1; } + if ($likes<0) { $likes=-1; } + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my %record=&getfeedbackrecords($uname,$udom,$course); + my $totallikes=$record{'totallikes'}; + $totallikes+=$likes; + my %newrecord=('likes_user' => $env{'user.name'}, + 'likes_domain' => $env{'user.domain'}, + 'likes' => $likes, + 'totallikes' => $totallikes); + return &Apache::lonnet::cstore(\%newrecord,'_feedback',$course,$udom,$uname); } + sub adddiscuss { - my ($symb,$email,$anon,$attachmenturl,$subject)=@_; + my ($symb,$email,$anon,$attachmenturl,$subject,$group)=@_; my $status=''; - my $realsymb; - if ($symb=~/^bulletin___/) { - my $filename=(&Apache::lonnet::decode_symb($symb))[2]; - $filename=~s|^adm/wrapper/||; - $realsymb=&Apache::lonnet::symbread($filename); + my $realsymb = &get_realsymb($symb); + my ($cnum,$cdom); + if ($env{'request.course.id'}) { + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; } if (&discussion_open(undef,$realsymb) && - &Apache::lonnet::allowed('pch',$env{'request.course.id'}. - ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) { + (&Apache::lonnet::allowed('pch',$env{'request.course.id'}. + ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:'')) || + (($group ne '') && (&check_group_priv($group,'pgd') eq 'ok') && + ($symb =~ m{^bulletin___\d+___adm/wrapper/adm/\Q$cdom\E/\Q$cnum\E/\d+/bulletinboard$})))) { my %contrib=('message' => $email, 'sendername' => $env{'user.name'}, @@ -2538,11 +3147,11 @@ sub adddiscuss { $contrib{'anonymous'}='true'; } if (($symb) && ($email)) { + my $now = time; if ($env{'form.editdisc'}) { - my %newcontrib = (); $contrib{'ip'}=$ENV{'REMOTE_ADDR'}; $contrib{'host'}=$Apache::lonnet::perlvar{'lonHostID'}; - $contrib{'timestamp'} = time; + $contrib{'timestamp'} = $now; $contrib{'history'} = ''; my $numoldver = 0; my ($oldsymb,$oldidx)=split(/\:\:\:/,$env{'form.editdisc'}); @@ -2557,7 +3166,7 @@ sub adddiscuss { } if (defined($oldcontrib{$oldidx.':history'})) { if ($oldcontrib{$oldidx.':history'} =~ /:/) { - my @oldversions = split/:/,$oldcontrib{$oldidx.':history'}; + my @oldversions = split(/:/,$oldcontrib{$oldidx.':history'}); $numoldver = @oldversions; } else { $numoldver = 1; @@ -2582,12 +3191,8 @@ sub adddiscuss { } } $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, + $oldsymb,$oldidx,\%contrib, $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'}); $status='Editing class discussion'.($anon?' (anonymous)':''); @@ -2597,20 +3202,21 @@ sub adddiscuss { $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'}); } - my %storenewentry=($symb => time); + my %storenewentry=($symb => $now); $status.='
    '.&mt('Updating discussion time').': '. &Apache::lonnet::put('discussiontimes',\%storenewentry, $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'}); } - my %record=&Apache::lonnet::restore('_discussion'); - my ($temp)=keys %record; + my %record=&getdiscussionrecords(); + my ($temp)=keys(%record); unless ($temp=~/^error\:/) { my %newrecord=(); $newrecord{'resource'}=$symb; $newrecord{'subnumber'}=$record{'subnumber'}+1; $status.='
    '.&mt('Registering').': '. &Apache::lonnet::cstore(\%newrecord,'_discussion'); + &updatekarma(); } } else { $status.='Failed.'; @@ -2618,12 +3224,160 @@ sub adddiscuss { return $status.'
    '; } + +# Routine to get the complete discussion records + +sub getdiscussionrecords { + my ($uname,$udom,$course)=@_; + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my %record=&Apache::lonnet::restore('_discussion',$course,$udom,$uname); + return %record; +} + +# Routine to get discussion statistics + +sub getdiscussionstats { + my %record=&getdiscussionrecords(@_); + my $totalvotes = $record{'totallikes'} + $record{'totalunlikes'}; + return ($record{'subnumber'},$record{'points'},$record{'totallikes'},$totalvotes); +} + +# Calculate discussion karma + +sub calcdiscussionkarma { + my ($subs,$pts,$likes,$votes)=&getdiscussionstats(@_); + my $karma=0; + if ($votes>0) { + $karma=int(.1+5.*(1.-exp(-$subs/10.))*$likes/$votes); + if ($karma<0) { $karma=0; } + if ($karma>5) { $karma=5; } + } + return $karma; +} + +# Update karma + +sub updatekarma { + my ($uname,$udom,$course)=@_; + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my $karma=&calcdiscussionkarma($uname,$udom,$course); + &Apache::lonnet::cstore({ 'karma' => $karma },'_discussion',$course,$udom,$uname); + &Apache::lonnet::do_cache_new('karma',$uname.':'.$udom.':'.$course,$karma,3600); + return $karma; +} + +# Retrieve karma + +sub userkarma { + my ($uname,$udom,$course)=@_; + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my $hashkey=$uname.':'.$udom.':'.$course; + my ($karma,$cached)=&Apache::lonnet::is_cached_new('karma',$hashkey); + if ($cached) { + return $karma; + } + my %userdisc=&getdiscussionrecords($uname,$udom,$course); + $karma=$userdisc{'karma'}; + &Apache::lonnet::do_cache_new('karma',$hashkey,$karma,3600); + return $karma; +} + +# Store discussion credit + +sub storediscussionpoints { + my ($points,$uname,$udom,$course)=@_; + unless ($points) { $points=0; } + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my %record=('grader_user' => $env{'user.name'}, + 'grader_domain' => $env{'user.domain'}, + 'points' => $points); + return &Apache::lonnet::cstore(\%record,'_discussion',$course,$udom,$uname); +} + +# Store discussion "likes" + +sub storediscussionlikes { + my ($chglikes,$chgunlikes,$uname,$udom,$course,$context)=@_; + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + unless ($course) { $course=$env{'request.course.id'}; } + my %record=&getdiscussionrecords($uname,$udom,$course); + my $totallikes=$record{'totallikes'}; + my $totalunlikes=$record{'totalunlikes'}; + $totallikes += $chglikes; + $totalunlikes += $chgunlikes; + my %newrecord=('likes_user' => $env{'user.name'}, + 'likes_domain' => $env{'user.domain'}, + 'totallikes' => $totallikes, + 'totalunlikes' => $totalunlikes, + 'context' => $context); + my $status=&Apache::lonnet::cstore(\%newrecord,'_discussion',$course,$udom,$uname); + if ($status eq 'ok') { + &updatekarma($uname,$udom,$course); + } + return $status; +} + +sub get_discussion_info { + my ($idx,%contrib) = @_; + my $changelast = 0; + my $count = 0; + my $hiddenflag = 0; + my $deletedflag = 0; + my ($hidden,$deleted,%info,$newlastdisc); + my $version = $contrib{'version'}; + if ($version) { + for (my $id=$version; $id>0; $id--) { + my $vkeys=$contrib{$id.':keys'}; + my @keys=split(/:/,$vkeys); + if (grep(/^hidden$/,@keys)) { + if (!$hiddenflag) { + $hidden = $contrib{$id.':hidden'}; + $hiddenflag = 1; + } + } elsif (grep(/^deleted$/,@keys)) { + if (!$deletedflag) { + $deleted = $contrib{$id.':deleted'}; + $deletedflag = 1; + } + } else { + if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) { + $count++; + $info{$count}{'id'} = $id; + $info{$count}{'timestamp'}=$contrib{$id.':timestamp'}; + } + } + } + if ($info{'1'}{'id'} == $idx) { + $changelast = 1; + if ($count > 1) { + $newlastdisc = $info{'2'}{'timestamp'}; + } else { + $newlastdisc = 0; + } + } + } + return ($changelast,$newlastdisc); +} + # ----------------------------------------------------------- Preview function sub show_preview { - my $r=shift; + my ($r) = @_; &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; + my $start_page= + &Apache::loncommon::start_page('Preview',undef, + {'only_body' => 1,}); + my $message=&clear_out_html($env{'form.comment'}); &newline_to_br(\$message); $message=&Apache::lonspeller::markeduptext($message); @@ -2631,13 +3385,48 @@ sub show_preview { my $subject=&clear_out_html($env{'form.subject'}); $subject=~s/\n/\
    /g; $subject=&Apache::lontexconvert::msgtexconverted($subject); - my $html=&Apache::lonxml::xmlbegin(); - $r->print($html.''. - '
    '. - 'Subject: '.$subject.'

    '. - $message.'
    '); + + my $end_page = &Apache::loncommon::end_page(); + + $r->print($start_page + .'

    '.&mt('Preview').'

    ' + .&Apache::lonhtmlcommon::start_pick_box() + .&Apache::lonhtmlcommon::row_title(&mt('Subject')) + .$subject + .&Apache::lonhtmlcommon::row_closure() + .&Apache::lonhtmlcommon::row_title(&mt('Message')) + .$message + .&Apache::lonhtmlcommon::row_closure(1) + .&Apache::lonhtmlcommon::end_pick_box() + .$end_page + ); } +sub contains_block_html { + my ($message)=@_; + return ($message =~ m{ + <(br|h1|h2|h3|h4|h5|h6|p|ol|ul|table|pre|address|blockquote|center|div) + \s* + (\w+\=['"]\w+['"])* + \s* + ( + \s*/>| + >.* + )}xs + ); +} + +sub tidy_html { + my ($message)=@_; +# my $tidy = HTML::Tidy::libXML->new(); +# my $xhtml = $tidy->clean($message, 'utf-8', 1); +# $xhtml =~ m/(.*)<\/body>/is; +# my $clean = $1; +# # remove any empty block-level tags +# $clean =~ s/<(table|p|div|tbody|blockquote|m|pre|algebra|center|ol|ul|span|h1|h2|h3|h4|h5|h6)\s*\/>//i; +# $message=$clean; + return $message; +} sub newline_to_br { my ($message)=@_; @@ -2666,25 +3455,53 @@ sub generate_preview_button { unless ($fieldname) { $fieldname='comment'; } my $pre=&mt("Show Preview and Check Spelling"); return(<
    - + +onclick="if (typeof(document.$formname.onsubmit)=='function') {document.$formname.onsubmit();};this.form.comment.value=document.$formname.$fieldname.value;this.form.subject.value=document.$formname.subject.value;this.form.submit();" />
    ENDPREVIEW } sub modify_attachments { - my ($r,$currnewattach,$currdelold,$symb,$idx,$attachmenturls)=@_; - my $orig_subject = &Apache::lonnet::unescape($env{'form.subject'}); + my ($r,$currnewattach,$currdelold,$symb,$idx,$attachmenturls, + $attachmaxtext,$toolarge)=@_; + + my %lt = &Apache::lonlocal::texthash( + 'subj' => 'Subject', + 'chth' => 'Check the checkboxes for any you wish to remove.', + 'thef' => 'The following attachments have been uploaded for inclusion with this posting.', + 'adda' => 'Add a new attachment to this post', + 'stch' => 'Save Changes', + 'clic' => 'Add/remove attachments', + ); + my $js = < + function setAction () { + document.modattachments.action = document.modattachments.origpage.value; + document.modattachments.submit(); + } + +END + + # Breadcrumbs + my $brcrum = [{'href' => '', + 'text' => 'Discussion Post Attachments'}]; + my %parms=('only_body' => 1); + if ($env{'form.modal'} ne 'yes') { $parms{'bread_crumbs'} = $brcrum; } + + my $start_page = + &Apache::loncommon::start_page('Discussion Post Attachments',$js,\%parms); + + my $orig_subject = &unescape($env{'form.subject'}); my $subject=&clear_out_html($orig_subject); $subject=~s/\n/\
    /g; $subject=&Apache::lontexconvert::msgtexconverted($subject); my $timestamp=$env{'form.timestamp'}; my $numoldver=$env{'form.numoldver'}; - my $bodytag=&Apache::loncommon::bodytag('Discussion Post Attachments', - '',''); + my $msg = ''; my %attachments = (); my %currattach = (); @@ -2692,73 +3509,70 @@ sub modify_attachments { &extract_attachments($attachmenturls,$idx,$numoldver,\$msg,\%attachments,\%currattach,$currdelold); } &Apache::lonenc::check_encrypt(\$symb); - my $html=&Apache::lonxml::xmlbegin(); + + my $end_page = + &Apache::loncommon::end_page(); + $r->print(< -Managing Attachments - - -$bodytag +$start_page +$toolarge
    - - - - -
    - Subject: $subject

    + +

    $lt{'clic'}

    END - if ($idx) { - if ($attachmenturls) { - my @currold = keys %currattach; - if (@currold > 0) { - $r->print("The following attachments were part of the most recent saved version of this posting.
    Check the checkboxes for any you wish to remove
    \n"); - foreach my $id (@currold) { - my $attachurl = &HTML::Entities::decode($attachments{$id}{'filename'}); - $attachurl =~ m#/([^/]+)$#; - $r->print('
    '."\n"); + $r->print(&Apache::lonhtmlcommon::start_pick_box()); + $r->print(&Apache::lonhtmlcommon::row_title(&mt('Subject'))); + $r->print(''.$subject.''); + $r->print(&Apache::lonhtmlcommon::row_closure()); + $r->print(&Apache::lonhtmlcommon::row_title($lt{'adda'})); + $r->print(' '.$attachmaxtext); + if(($idx)||(ref($currnewattach) eq 'ARRAY') && (@{$currnewattach} > 0)){ + $r->print(&Apache::lonhtmlcommon::row_closure()); + $r->print(&Apache::lonhtmlcommon::row_title(&mt('Attachments'))); + if ($idx) { + if ($attachmenturls) { + my @currold = keys(%currattach); + if (@currold > 0) { + $r->print($lt{'thfo'}.'
    '.$lt{'chth'}.'
    '."\n"); + foreach my $id (@currold) { + my $attachurl = &HTML::Entities::decode($attachments{$id}{'filename'}); + $attachurl =~ m#/([^/]+)$#; + $r->print('
    '."\n"); + } + $r->print("
    "); } - $r->print("
    "); } } - } - if (@{$currnewattach} > 0) { - $r->print("The following attachments have been uploaded for inclusion with this posting.
    Check the checkboxes for any you wish to remove
    \n"); - foreach (@{$currnewattach}) { - $_ =~ m#/([^/]+)$#; - $r->print('
    '."\n"); + if ((ref($currnewattach) eq 'ARRAY') && (@{$currnewattach} > 0)) { + $r->print($lt{'chth'}.'
    '."\n"); + foreach my $attach (@{$currnewattach}) { + $attach =~ m#/([^/]+)$#; + $r->print('
    '."\n"); + } } - $r->print("
    "); } + $r->print(&Apache::lonhtmlcommon::row_closure(1)); + $r->print(&Apache::lonhtmlcommon::end_pick_box()); $r->print(< -
    - END - foreach (@{$currnewattach}) { - $r->print(''."\n"); + foreach my $item (@{$currnewattach}) { + $r->print(''."\n"); } - foreach (@{$currdelold}) { - $r->print(''."\n"); + foreach my $item (@{$currdelold}) { + $r->print(''."\n"); } $r->print(< + - - +$end_page END return; } @@ -2776,14 +3590,14 @@ sub process_attachments { my @currnew = (); foreach my $newone (@{$currnewattach}) { my $delflag = 0; - foreach (@currdelnew) { - if ($newone eq $_) { + foreach my $item (@currdelnew) { + if ($newone eq $item) { $delflag = 1; last; } } unless ($delflag) { - push @currnew, $newone; + push(@currnew, $newone); } } @{$currnewattach} = @currnew; @@ -2792,14 +3606,22 @@ sub process_attachments { } sub generate_attachments_button { - my ($idx,$attachnum,$ressymb,$now,$currnewattach,$deloldattach,$numoldver,$mode) = @_; + my ($idx,$attachnum,$ressymb,$now,$currnewattach,$deloldattach, + $numoldver,$mode,$blockblog) = @_; my $origpage = $ENV{'REQUEST_URI'}; my $att=$attachnum.' '.&mt("attachments"); + my %lt = &Apache::lonlocal::texthash( + 'clic' => 'Add/remove attachments', + ); my $response = (<
    -Click to add/remove attachments:  - - - + + ENDATTACH if (defined($deloldattach)) { if (@{$deloldattach} > 0) { - foreach (@{$deloldattach}) { - $response .= ''."\n"; + foreach my $delatt (@{$deloldattach}) { + $response .= ''."\n"; } } } if (defined($currnewattach)) { if (@{$currnewattach} > 0) { - foreach (@{$currnewattach}) { - $response .= ''."\n"; + foreach my $attach (@{$currnewattach}) { + $response .= ''."\n"; } } } @@ -2837,7 +3658,7 @@ sub extract_attachments { my ($attachmenturls,$idx,$numoldver,$message,$attachments,$currattach,$currdelold) = @_; %{$attachments}=(); &get_post_attachments($attachments,$attachmenturls); - foreach my $id (sort keys %{$attachments}) { + foreach my $id (sort(keys(%{$attachments}))) { if (exists($$attachments{$id}{$numoldver})) { if (defined($currdelold)) { if (@{$currdelold} > 0) { @@ -2852,7 +3673,7 @@ sub extract_attachments { } } } - my @attached = (sort { $a <=> $b } keys %{$currattach}); + my @attached = (sort { $a <=> $b } keys(%{$currattach})); if (@attached == 1) { my $id = $attached[0]; my $attachurl; @@ -2868,8 +3689,8 @@ sub extract_attachments { $attachurl); } elsif (@attached > 1) { $$message.='
      '; - foreach (@attached) { - my $id = $_; + foreach my $attach (@attached) { + my $id = $attach; my $attachurl = &HTML::Entities::decode($$attachments{$id}{'filename'}); my ($fname) =($attachurl=~m|/([^/]+)$|); @@ -2896,7 +3717,7 @@ sub construct_attachmenturl { $oldattachmenturl = $contrib{$idx.':attachmenturl'}; if ($contrib{$idx.':history'}) { if ($contrib{$idx.':history'} =~ /:/) { - my @oldversions = split/:/,$contrib{$idx.':history'}; + my @oldversions = split(/:/,$contrib{$idx.':history'}); $currver = 1 + scalar(@oldversions); } else { $currver = 2; @@ -2909,13 +3730,13 @@ sub construct_attachmenturl { my %attachments = (); my $prevver = $currver-1; &get_post_attachments(\%attachments,$oldattachmenturl); - my $numattach = scalar(keys %attachments); + my $numattach = scalar(keys(%attachments)); $startnum += $numattach; - foreach my $num (sort {$a <=> $b} keys %attachments) { + foreach my $num (sort {$a <=> $b} keys(%attachments)) { $newattachmenturl .= ''.$attachments{$num}{'filename'}.''; - foreach $_ (sort {$a <=> $b} keys %{$attachments{$num}}) { - unless ($_ eq 'filename') { - $newattachmenturl .= ''.$attachments{$num}{$_}.''; + foreach my $item (sort {$a <=> $b} keys(%{$attachments{$num}})) { + unless ($item eq 'filename') { + $newattachmenturl .= ''.$attachments{$num}{$item}.''; } } if (grep/^$num$/,@{$keepold}) { @@ -2940,22 +3761,55 @@ sub construct_attachmenturl { return $newattachmenturl; } +sub add_blog_checkbox { + my ($crstype) = @_; + my $checkstatus; + if ($env{'form.blog'}) { + $checkstatus = 'checked="checked"'; + } + my $output = ' +
      +
      '."\n"; + return $output; +} + sub has_discussion { my $resourcesref = shift; my $navmap = Apache::lonnavmaps::navmap->new(); - my @allres=$navmap->retrieveResources(); - foreach my $resource (@allres) { - if ($resource->hasDiscussion()) { - my $ressymb = $resource->wrap_symb(); - push @{$resourcesref}, $ressymb; + if (defined($navmap)) { + my @allres=$navmap->retrieveResources(); + foreach my $resource (@allres) { + if ($resource->hasDiscussion()) { + my $ressymb = $resource->wrap_symb(); + if (ref($resourcesref) eq 'ARRAY') { + push(@{$resourcesref}, $ressymb); + } + } } + } else { + &Apache::lonnet::logthis('Has discussion check failed - could not create navmap object.'); } return; } sub sort_filter_names { - my ($sort_types,$role_types,$status_types) = @_; - %{$sort_types} = ( + my ($sort_types,$role_types,$status_types,$crstype) = @_; + if (ref($sort_types) eq 'HASH') { + %{$sort_types} = ( ascdate => 'Date order - oldest first', descdate => 'Date order - newest first', thread => 'Threaded', @@ -2963,21 +3817,28 @@ sub sort_filter_names { username => 'By domain and username', lastfirst => 'By last name, first name' ); - %{$role_types} = ( - all => 'All roles', - st => 'Students', - cc => 'Course Coordinators', - in => 'Instructors', - ta => 'TAs', - ep => 'Exam proctors', - ad => 'Administrators', - cr => 'Custom roles' - ); - %{$status_types} = ( + } + my @courseroles = qw(st in ta ep ad); + if ($crstype eq 'Community') { + push(@courseroles,'co'); + } else { + push(@courseroles,'cc'); + } + if (ref($role_types) eq 'HASH') { + foreach my $role (@courseroles) { + $role_types->{$role} = &Apache::lonnet::plaintext($role,$crstype); + } + $role_types->{'all'} = 'All roles'; + $role_types->{'cr'} = 'Custom role'; + } + if (ref($status_types) eq 'HASH') { + %{$status_types} = ( all => 'Roles of any status', - Active => 'Only active roles', - Expired => 'Only inactive roles' + Active => 'Only active roles', + Expired => 'Only past roles', + Future => 'Only future roles', ); + } } sub handler { @@ -2991,14 +3852,31 @@ sub handler { # --------------------------- Get query string for limited number of parameters &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, - ['hide','unhide','deldisc','postdata','preview','replydisc','editdisc','cmd','symb','onlyunread','allposts','onlyunmark','previous','markread','markonread','markondisp','toggoff','toggon','modifydisp','changes','navtime','navmaps','navurl','sortposts','applysort','rolefilter','statusfilter','sectionpick','posterlist','userpick','attach','origpage','currnewattach','deloldattach','keepold','allversions','export']); - + ['like','unlike','modal','hide','unhide','deldisc','undeleteall','postdata','preview','replydisc','editdisc','cmd','symb','onlyunread','allposts','onlyunmark','previous','markread','markonread','markondisp','toggoff','toggon','modifydisp','changes','navtime','navmaps','navurl','sortposts','applysort','rolefilter','statusfilter','sectionpick','groupick','posterlist','userpick','attach','origpage','currnewattach','deloldattach','keepold','allversions','export','sendmessageonly','group','ref']); + my $group = $env{'form.group'}; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + + my %attachmax = ( + text => &mt('(128 KB max size)'), + num => 131072, + ); + if ($env{'form.editdisc'}) { + if (!(&editing_allowed($env{'form.editdisc'},$group))) { + my $symb=(split(/\:\:\:/,$env{'form.editdisc'}))[0]; + my ($map,$id,$url)=&Apache::lonnet::decode_symb($symb); + my $feedurl=&Apache::lonnet::clutter($url); + &redirect_back($r,$feedurl,&mt('Editing not permitted').'
      ', '0','0','','',$env{'form.previous'},undef,undef,undef, + undef,undef,undef,$group); + return OK; + } + } if ($env{'form.discsymb'}) { my ($symb,$feedurl) = &get_feedurl_and_clean_symb($env{'form.discsymb'}); my $readkey = $symb.'_read'; my $chgcount = 0; my %readinghash = &Apache::lonnet::get('nohist_'.$env{'request.course.id'}.'_discuss',[$readkey],$env{'user.domain'},$env{'user.name'}); - foreach my $key (keys %env) { + foreach my $key (keys(%env)) { if ($key =~ m/^form\.postunread_(\d+)/) { if ($readinghash{$readkey} =~ /\.$1\./) { $readinghash{$readkey} =~ s/\.$1\.//; @@ -3016,42 +3894,44 @@ sub handler { \%readinghash,$env{'user.domain'},$env{'user.name'}); } &redirect_back($r,$feedurl,&mt('Marked postings read/unread').'
      ', - '0','0','','',$env{'form.previous'},'','','',); + '0','0','','',$env{'form.previous'},'','','', + undef,undef,undef,$group); return OK; } if ($env{'form.allversions'}) { &Apache::loncommon::content_type($r,'text/html'); + &Apache::loncommon::no_cache($r); $r->send_http_header; - my $html=&Apache::lonxml::xmlbegin(); - my $bodytag=&Apache::loncommon::bodytag('Discussion Post Versions'); - $r->print(< -Post Versions - - -$bodytag -END - 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); + + # Breadcrumbs + my $brcrum = [{'href' => '', + 'text' => 'Discussion Post Versions'}]; + + my %parms=(); + if ($env{'form.modal'} ne 'yes') { $parms{'bread_crumbs'} = $brcrum; } + + $r->print(&Apache::loncommon::start_page('Discussion Post Versions',undef,\%parms)); + my ($symb,$idx)=split(/\:\:\:/,$env{'form.allversions'}); - ($symb)=&get_feedurl_and_clean_symb($symb); + ($symb, my $feedurl)=&get_feedurl_and_clean_symb($symb); + my $ressymb = &wrap_symb($symb); + my $seeid; + if (&Apache::lonnet::allowed('rin',$env{'request.course.id'}.($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''))) { + $seeid = 1; + } + my $seehidden = &can_see_hidden('',$ressymb,$feedurl,$group,$cdom,$cnum); if ($idx > 0) { my %messages = (); my %subjects = (); my %attachmsgs = (); my %allattachments = (); my %imsfiles = (); - my ($screenname,$plainname); + my ($screenname,$plainname,$showaboutme); my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); - $r->print(&get_post_contents(\%contrib,$idx,$seeid,'allversions',\%messages,\%subjects,\%allattachments,\%attachmsgs,\%imsfiles,\$screenname,\$plainname)); + $cdom,$cnum); + $r->print(&get_post_contents(\%contrib,$idx,$seeid,$seehidden,'allversions',\%messages,\%subjects,\%allattachments,\%attachmsgs,\%imsfiles,\$screenname,\$plainname,\$showaboutme)); } + $r->print(&Apache::loncommon::end_page()); return OK; } if ($env{'form.posterlist'}) { @@ -3070,7 +3950,7 @@ END \%discinfo,$env{'user.domain'},$env{'user.name'}); &redirect_back($r,$feedurl,&mt('Changed sort/filter').'
      ','0','0','', '',$env{'form.previous'},$env{'form.sortposts'},'','','', - $numpicks); + '',$numpicks,$group); return OK; } if ($env{'form.applysort'}) { @@ -3078,7 +3958,8 @@ END &redirect_back($r,$feedurl,&mt('Changed sort/filter').'
      ','0','0','', '',$env{'form.previous'},$env{'form.sortposts'}, $env{'form.rolefilter'},$env{'form.statusfilter'}, - $env{'form.sectionpick'}); + $env{'form.sectionpick'},$env{'form.grouppick'}, + undef,$group); return OK; } elsif ($env{'form.cmd'} eq 'sortfilter') { my ($symb,$feedurl)=&get_feedurl_and_clean_symb($env{'form.symb'}); @@ -3089,7 +3970,7 @@ END my @resources = (); if (defined($env{'form.navmaps'})) { if ($env{'form.navmaps'} =~ /:/) { - @resources = split/:/,$env{'form.navmaps'}; + @resources = split(/:/,$env{'form.navmaps'}); } else { @resources = ("$env{'form.navmaps'}"); } @@ -3100,17 +3981,18 @@ END my $feedurl = '/adm/navmaps'; if ($env{'form.navurl'}) { $feedurl .= '?'.$env{'form.navurl'}; } my %lt = &Apache::lonlocal::texthash( - 'mnpa' => 'Marked "New" posts as read in a total of', - 'robb' => 'resources/bulletin boards.', - 'twnp' => 'There are currently no resources or bulletin boards with unread discussion postings.' + 'twnp' => 'There are currently no resources or discussion boards with unread discussion postings.' ); - foreach (@resources) { - my $ressymb=$_; + foreach my $res (@resources) { + my $ressymb=$res; &Apache::lonenc::check_decrypt(\$ressymb); my $lastkey = $ressymb.'_lastread'; $discinfo{$lastkey} = $env{'form.navtime'}; } - my $textline = "$lt{'mnpa'} $numitems $lt{'robb'}"; + my $textline = ''. + &mt('Marked "New" posts as read in a total of [_1] resources/bulletin boards.', + $numitems). + ''; if ($numitems > 0) { &Apache::lonnet::put('nohist_'.$env{'request.course.id'}.'_discuss', \%discinfo,$env{'user.domain'},$env{'user.name'}); @@ -3120,22 +4002,22 @@ END &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; my $logo=&Apache::loncommon::lonhttpdurl('/adm/lonIcons/lonlogos.gif'); - my $html=&Apache::lonxml::xmlbegin(); + my %onload; + + my $start_page= + &Apache::loncommon::start_page('New posts marked as read',undef, + {'redirect' => [2,$feedurl], + 'only_body' => 1, + 'add_entries' => \%onload}); + my $end_page = &Apache::loncommon::end_page(); $r->print (< -New posts marked as read - - - - +$start_page $textline -
      Continue - - +
      +$end_page ENDREDIR return OK; } elsif ($env{'form.modifydisp'}) { @@ -3179,73 +4061,305 @@ ENDREDIR my $previous=$env{'form.previous'}; if ($env{'form.markondisp'}) { $previous=undef; } &redirect_back($r,$feedurl,&mt('Changed display status').'
      ', - '0','0','','',$previous); + '0','0','','',$previous,'','','','','','',$group); return OK; } elsif (($env{'form.hide'}) || ($env{'form.unhide'})) { # ----------------------------------------------------------------- Hide/unhide my $entry=$env{'form.hide'}?$env{'form.hide'}:$env{'form.unhide'}; my ($symb,$idx)=split(/\:\:\:/,$entry); ($symb,my $feedurl)=&get_feedurl_and_clean_symb($symb); + my $ressymb = &wrap_symb($symb); + + my $seehidden = &can_see_hidden('',$ressymb,$feedurl,$group,$cdom,$cnum); + unless (($seehidden) || (&editing_allowed($env{'form.hide'},$group))) { + &redirect_back($r,$feedurl,&mt('Hiding not permitted').'
      ', + '0','0','','',$env{'form.previous'},'','','','', + undef,undef,$group,); + return OK; + } my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); + $cdom,$cnum); 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) { + unless ($seehidden) { $currentstudenthidden.='.'.$idx.'.'; } } else { $currenthidden=~s/\.$idx\.//g; } my %newhash=('hidden' => $currenthidden); - if ( ($env{'form.hide'}) && (!$seeid) ) { + if ( ($env{'form.hide'}) && (!$seehidden) ) { $newhash{'studenthidden'} = $currentstudenthidden; } - - &Apache::lonnet::store(\%newhash,$symb,$env{'request.course.id'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); - - &redirect_back($r,$feedurl,&mt('Changed discussion status').'
      ', - '0','0','','',$env{'form.previous'}); + if ($env{'form.hide'}) { + my $changelast = 0; + my $newlast; + ($changelast,$newlast) = &get_discussion_info($idx,%contrib); + if ($changelast) { + &Apache::lonnet::put('discussiontimes',{$symb => $newlast}, + $cdom,$cnum); + } + } + my $result; + if (&Apache::lonnet::store(\%newhash,$symb,$env{'request.course.id'}, + $cdom,$cnum) eq 'ok') { + my $prefix=$symb.':'.$idx.':'; + my %likes=&Apache::lonnet::dump('disclikes',$cdom,$cnum, + '^'.$prefix); + my ($totallikes,$totalunlikes); + if (ref($likes{$prefix.'likers'}) eq 'HASH') { + $totallikes = scalar(keys(%{$likes{$prefix.'likers'}})); + } + if (ref($likes{$prefix.'unlikers'}) eq 'HASH') { + $totalunlikes = scalar(keys(%{$likes{$prefix.'unlikers'}})); + } + if ($totallikes || $totalunlikes) { + my ($chglikes,$chgunlikes,$context); + if ($env{'form.hide'}) { + $chglikes = -1 * $totallikes; + $chgunlikes = -1 * $totalunlikes; + $context = 'hide'; + } else { + $chglikes = $totallikes; + $chgunlikes = $totalunlikes; + $context = 'unhide'; + } + &storediscussionlikes($chglikes,$chgunlikes, + $contrib{$idx.':sendername'}, + $contrib{$idx.':senderdomain'}, + $env{'request.course.id'}, + $context); + + } + $result = &mt('Changed discussion status'); + } else { + $result = &mt('Discussion status unchanged'); + } + &redirect_back($r,$feedurl,$result.'
      ','0','0','','', + $env{'form.previous'},undef,undef,undef, + undef,undef,undef,$group); + return OK; + } elsif (($env{'form.like'}) || ($env{'form.unlike'})) { +# ----------------------------------------------------------------- Like/unlike + my $entry=$env{'form.like'}?$env{'form.like'}:$env{'form.unlike'}; + my ($symb,$idx)=split(/\:\:\:/,$entry); + ($symb,my $feedurl)=&get_feedurl_and_clean_symb($symb); + my $result; + if ($idx > 0) { + my $realsymb = &get_realsymb($symb); + my $status='OPEN'; + if ($Apache::lonhomework::parsing_a_problem || + $Apache::lonhomework::parsing_a_task) { + $status=$Apache::inputtags::status[-1]; + } + if (&discussion_vote_available($status,$realsymb)) { + my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, + $cdom,$cnum); + my $ownpost; + if (($contrib{$idx.':sendername'} eq $env{'user.name'}) && + ($contrib{$idx.':senderdomain'} eq $env{'user.domain'})) { + $ownpost = 1; + } + if ($ownpost || $contrib{$idx.':hidden'} || $contrib{$idx.':deleted'}) { + $result = &mt('Vote not registered.').' '; + } + if ($ownpost) { + $result .= &mt('No voting for your own posts.'); + } elsif ($contrib{$idx.':hidden'}) { + $result .= &mt('No voting for hidden posts.'); + } elsif ($contrib{$idx.':deleted'}) { + $result .= &mt('No voting for deleted posts.'); + } else { +# +# Likes and unlikes are in db-file "disclikes" of the course +# The prefix is the $symb to identify the resource discussion, +# and the $idx to identify the entry +# + my $prefix=$symb.':'.$idx.':'; + my %likes=&Apache::lonnet::dump('disclikes',$cdom,$cnum, + '^'.$prefix); + +# Get current like or unlike status for the $idx for this user. + my $thisuser=$env{'user.name'}.':'.$env{'user.domain'}; + my ($userlikes,$userunlikes); + if (ref($likes{$prefix.'likers'}) eq 'HASH') { + if (exists($likes{$prefix.'likers'}{$thisuser})) { + $userlikes = 1; + } + } + if (ref($likes{$prefix.'unlikers'}) eq 'HASH') { + if (exists($likes{$prefix.'unlikers'}{$thisuser})) { + $userunlikes = 1; + } + } +# Get the current "likes" count + my $likescount=$likes{$prefix.'likes'}; +# Find out if they already voted +# Users cannot like a post twice, or unlike it twice. +# They can change their mind, though. + my $alreadyflag=0; + my $votetype; + if ($env{'form.like'}) { + if ($userlikes) { + $alreadyflag=1; + } elsif ($userunlikes) { + delete($likes{$prefix.'unlikers'}{$thisuser}); + $votetype = 'switch'; + $likescount++; + } else { + if (ref($likes{$prefix.'likers'}) eq 'HASH') { + $likes{$prefix.'likers'}{$thisuser} = 1; + } else { + $likes{$prefix.'likers'} = {$thisuser => 1}; + } + $likescount++; + } + } else { + if ($userunlikes) { + $alreadyflag=1; + } elsif ($userlikes) { + delete($likes{$prefix.'likers'}{$thisuser}); + $votetype = 'switch'; + $likescount--; + } else { + if (ref($likes{$prefix.'unlikers'}) eq 'HASH') { + $likes{$prefix.'unlikers'}{$thisuser} = 1; + } else { + $likes{$prefix.'unlikers'} = {$thisuser => 1}; + } + $likescount--; + } + } +# $alreadyflag would be 1 if they tried to double-like or double-unlike + if ($alreadyflag) { + if ($env{'form.like'}) { + $result= &mt("'Like' already registered"); + } else { + $result= &mt("'Unlike' already registered"); + } + } else { + my %newhash=($prefix.'likes' => $likescount, + $prefix.'likers' => $likes{$prefix.'likers'}, + $prefix.'unlikers' => $likes{$prefix.'unlikers'}); +# Store data in db-file "disclikes" + if (&Apache::lonnet::put('disclikes',\%newhash,$cdom,$cnum) eq 'ok') { +# Also store with the person who posted the liked/unliked entry + my ($chglike,$chgunlike); + if ($env{'form.like'}) { + if ($votetype eq 'switch') { + $chglike = 0; + $chgunlike = -1; + } else { + $chglike = 1; + $chgunlike = 0; + } + &storediscussionlikes($chglike,$chgunlike, + $contrib{$idx.':sendername'}, + $contrib{$idx.':senderdomain'}, + $env{'request.course.id'},'like'); + $result=&mt("Registered 'Like'"); + } else { + if ($votetype eq 'switch') { + $chglike = -1; + $chgunlike = 0; + } else { + $chglike = 0; + $chgunlike = 1; + } + &storediscussionlikes($chglike,$chgunlike, + $contrib{$idx.':sendername'}, + $contrib{$idx.':senderdomain'}, + $env{'request.course.id'},'unlike'); + $result=&mt("Registered 'Unlike'"); + } + } else { +# Oops, something went wrong + $result=&mt("Failed to register vote"); + } + } + } + } else { + $result=&mt('Voting unavailable for this discussion'); + } + } else { + $result=&mt('Invalid post number'); + } + &redirect_back($r,$feedurl,$result.'
      ', + '0','0','','',$env{'form.previous'},undef,undef,undef, + undef,undef,undef,$group); return OK; } elsif ($env{'form.cmd'}=~/^(threadedoff|threadedon)$/) { my ($symb,$feedurl)=&get_feedurl_and_clean_symb($env{'form.symb'}); - if ($env{'form.cmd'} eq 'threadedon') { - &Apache::lonnet::put('environment',{'threadeddiscussion' => 'on'}); - &Apache::lonnet::appenv('environment.threadeddiscussion' => 'on'); + if ($env{'form.cmd'} eq 'threadedoff') { + &Apache::lonnet::put('environment',{'unthreadeddiscussion' => 'on'}); + &Apache::lonnet::appenv({'environment.unthreadeddiscussion' => 'on'}); + &Apache::lonnet::del('environment',['threadeddiscussion']); + &Apache::lonnet::delenv('environment.threadeddiscussion'); } else { - &Apache::lonnet::del('environment',['threadeddiscussion']); - &Apache::lonnet::delenv('environment\.threadeddiscussion'); + &Apache::lonnet::put('environment',{'threadeddiscussion' => 'on'}); + &Apache::lonnet::appenv({'environment.threadeddiscussion' => 'on'}); + &Apache::lonnet::del('environment',['unthreadeddiscussion']); + &Apache::lonnet::delenv('environment.unthreadeddiscussion'); } &redirect_back($r,$feedurl,&mt('Changed discussion view mode').'
      ', - '0','0','','',$env{'form.previous'}); + '0','0','','',$env{'form.previous'},undef,undef,undef, + undef,undef,undef,$group); return OK; } elsif ($env{'form.deldisc'}) { # --------------------------------------------------------------- Hide for good my ($symb,$idx)=split(/\:\:\:/,$env{'form.deldisc'}); ($symb,my $feedurl)=&get_feedurl_and_clean_symb($symb); + my $ressymb=&wrap_symb($symb); + + unless (&can_see_hidden('',$ressymb,$feedurl,$group,$cdom,$cnum)) { + &redirect_back($r,$feedurl,&mt('Deletion not permitted').'
      ', + '0','0','','',$env{'form.previous'},'','','','', + undef,undef,$group); + return OK; + } my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); + $cdom,$cnum); + my ($changelast,$newlast) = &get_discussion_info($idx,%contrib); + if ($changelast) { + &Apache::lonnet::put('discussiontimes',{$symb => $newlast}, + $cdom,$cnum); + } my %newhash=('deleted' => $contrib{'deleted'}.".$idx."); - &Apache::lonnet::store(\%newhash,$symb,$env{'request.course.id'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); - &redirect_back($r,$feedurl,&mt('Changed discussion status').'
      ', - '0','0','','',$env{'form.previous'}); + + my $result; + if (&Apache::lonnet::store(\%newhash,$symb,$env{'request.course.id'}, + $cdom,$cnum) eq 'ok') { + $result = &mt('Changed discussion status'); + my $prefix=$symb.':'.$idx.':'; + my %likes=&Apache::lonnet::dump('disclikes',$cdom,$cnum, + '^'.$prefix); + my ($totallikes,$totalunlikes); + if (ref($likes{$prefix.'likers'}) eq 'HASH') { + $totallikes = scalar(keys(%{$likes{$prefix.'likers'}})); + } + if (ref($likes{$prefix.'unlikers'}) eq 'HASH') { + $totalunlikes = scalar(keys(%{$likes{$prefix.'unlikers'}})); + } + if ($totallikes || $totalunlikes) { + my $chglikes = -1 * $totallikes; + my $chgunlikes = -1 * $totalunlikes; + &storediscussionlikes($chglikes,$chgunlikes, + $contrib{$idx.':sendername'}, + $contrib{$idx.':senderdomain'}, + $env{'request.course.id'}, + 'delete'); + } + } else { + $result = &mt('Discussion status unchanged'); + } + &redirect_back($r,$feedurl,$result.'
      ','0','0','','', + $env{'form.previous'},undef,undef,undef, + undef,undef,undef,$group); return OK; } elsif ($env{'form.preview'}) { # -------------------------------------------------------- User wants a preview @@ -3255,14 +4369,16 @@ ENDREDIR # -------------------------------------------------------- Work on attachments &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['subject','comment','addnewattach','delnewattach','timestamp','numoldver','idx','anondiscuss','discuss','blog']); - my (@currnewattach,@currdelold,@keepold); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['subject','comment','addnewattach','delnewattach','timestamp','numoldver','idx','discuss','blog']); + my (@currnewattach,@currdelold,@keepold,$toolarge); &process_attachments(\@currnewattach,\@currdelold,\@keepold); if (exists($env{'form.addnewattach.filename'})) { - unless (length($env{'form.addnewattach'})>131072) { + if (length($env{'form.addnewattach'})<=$attachmax{'num'}) { my $subdir = 'feedback/'.$env{'form.timestamp'}; my $newattachment=&Apache::lonnet::userfileupload('addnewattach',undef,$subdir); - push @currnewattach, $newattachment; + push(@currnewattach, $newattachment); + } else { + $toolarge = '

      '.&mt('Attachment not included - exceeded permitted length').'

      '; } } my $attachmenturls; @@ -3270,12 +4386,11 @@ ENDREDIR my $idx = $env{'form.idx'}; if ($idx) { my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); + $cdom,$cnum); $attachmenturls = $contrib{$idx.':attachmenturl'}; } &modify_attachments($r,\@currnewattach,\@currdelold,$symb,$idx, - $attachmenturls); + $attachmenturls,$attachmax{'text'},$toolarge); return OK; } elsif ($env{'form.export'}) { &Apache::loncommon::content_type($r,'text/html'); @@ -3284,18 +4399,72 @@ ENDREDIR my $mode='board'; my $status='OPEN'; my $previous=$env{'form.previous'}; - if ($feedurl =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) { + if ($feedurl =~ /$LONCAPA::assess_re/) { $mode='problem'; $status=$Apache::inputtags::status[-1]; } + my $discussion = &list_discussion($mode,$status,$symb); - my $bodytag=&Apache::loncommon::bodytag('Resource Feedback and Discussion'); - $r->print($bodytag.$discussion); + my $start_page = + &Apache::loncommon::start_page('Resource Feedback and Discussion'); + my $end_page = + &Apache::loncommon::end_page(); + $r->print($start_page.$discussion.$end_page); + return OK; + + } elsif ($env{'form.undeleteall'}) { + &Apache::loncommon::content_type($r,'text/html'); + $r->send_http_header; + my ($symb,$feedurl) = &get_feedurl_and_clean_symb($env{'form.undeleteall'}); + my $ressymb=&wrap_symb($symb); + $r->print(&Apache::loncommon::start_page('Undelete all deleted discussion entries')); + if (&can_see_hidden('',$ressymb,$feedurl,$group,$cdom,$cnum)) { + my %contrib=&Apache::lonnet::restore($symb,$env{'request.course.id'}, + $cdom,$cnum); + $contrib{'deleted'} =~ s/^\.//; + $contrib{'deleted'} =~ s/\.$//; + if ($contrib{'deleted'} ne '') { + if (&Apache::lonnet::store({'deleted' => ''},$symb,$env{'request.course.id'}, + $cdom,$cnum) eq 'ok') { + my %likes=&Apache::lonnet::dump('disclikes',$cdom,$cnum,'^'.$symb.':'); + my @ids = split(/\.\./,$contrib{'deleted'}); + my (%chglikes,%chgunlikes); + foreach my $idx (@ids) { + my $uname = $contrib{$idx.':sendername'}; + my $udom = $contrib{$idx.':senderdomain'}; + my ($totallikes,$totalunlikes); + if (ref($likes{$symb.':'.$idx.':likers'}) eq 'HASH') { + $totallikes = scalar(keys(%{$likes{$symb.':'.$idx.':likers'}})); + } + if (ref($likes{$symb.':'.$idx.':unlikers'}) eq 'HASH') { + $totalunlikes = scalar(keys(%{$likes{$symb.':'.$idx.':unlikers'}})); + } + if ($totallikes || $totalunlikes) { + $chglikes{$uname.':'.$udom} += $totallikes; + $chgunlikes{$uname.':'.$udom} += $totalunlikes; + } + } + foreach my $user (keys(%chglikes)) { + my ($uname,$udom) = split(/:/,$user); + &storediscussionlikes($chglikes{$user},$chgunlikes{$user}, + $uname,$udom,$env{'request.course.id'}, + 'undelete'); + } + $r->print(&Apache::lonhtmlcommon::confirm_success(&mt("Undeleted all entries"))); + } else { + $r->print(&Apache::lonhtmlcommon::confirm_success(&mt("Failed to undelete entries"),1)); + } + } else { + $r->print(&Apache::lonhtmlcommon::confirm_success(&mt("No entries to undelete"),1)); + } + $r->print("
      ".&mt("Return and reload").""); + } + $r->print(&Apache::loncommon::end_page()); return OK; } else { # ------------------------------------------------------------- Normal feedback my $feedurl=$env{'form.postdata'}; - $feedurl=~s/^http\:\/\///; + $feedurl=~s/^https?\:\/\///; $feedurl=~s/^$ENV{'SERVER_NAME'}//; $feedurl=~s/^$ENV{'HTTP_HOST'}//; $feedurl=~s/\?.+$//; @@ -3303,31 +4472,29 @@ ENDREDIR my $symb; if ($env{'form.replydisc'}) { $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); } elsif ($env{'form.origpage'}) { $symb=""; + } elsif ($env{'form.sendmessageonly'}) { + $symb=(split(/\:\:\:/,$env{'form.sendmessageonly'}))[0]; } else { $symb=&Apache::lonnet::symbread($feedurl); } unless ($symb) { $symb=$env{'form.symb'}; - if ($symb) { - my ($map,$id,$url)=&Apache::lonnet::decode_symb($symb); - $feedurl=&Apache::lonnet::clutter($url); - } } - &Apache::lonenc::check_decrypt(\$symb); + if (defined($symb)) { + ($symb,$feedurl)=&get_feedurl_and_clean_symb($symb); + } else { + # backward compatibility (bulletin boards used to be 'wrapped') + &Apache::lonenc::check_decrypt(\$feedurl); + &dewrapper(\$feedurl); + } my $goahead=1; - if ($feedurl=~/\.(problem|exam|quiz|assess|survey|form|task)$/) { + if ($feedurl=~/$LONCAPA::assess_re/) { unless ($symb) { $goahead=0; } } - # backward compatibility (bulletin boards used to be 'wrapped') - &dewrapper(\$feedurl); if (!$goahead) { # Ambiguous Problem Resource $r->internal_redirect('/adm/ambiguous'); @@ -3346,16 +4513,26 @@ ENDREDIR &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; # Unable to give feedback + &Apache::lonenc::check_encrypt(\$feedurl); &no_redirect_back($r,$feedurl); return OK; } # --------------------------------------------------- Print login screen header unless ($env{'form.sendit'}) { + &Apache::lonenc::check_encrypt(\$feedurl); &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; - my $options=&screen_header($feedurl,$symb); + if (($env{'form.replydisc'}) || ($env{'form.editdisc'})) { + my ($blocked,$blocktext) = + &Apache::loncommon::blocking_status('boards'); + if ($blocked) { + $r->print(&blocked_reply_or_edit($blocktext)); + return OK; + } + } + my $options=&screen_header($feedurl,$symb,$group); if ($options) { - &mail_screen($r,$feedurl,$options); + &mail_screen($r,$feedurl,$options,$symb,$attachmax{'text'}); } else { &fail_redirect($r,$feedurl); } @@ -3368,16 +4545,20 @@ ENDREDIR $env{'request.course.id'}); # Get output from resource + &Apache::lonenc::check_encrypt(\$feedurl); my $usersaw=&resource_output($feedurl); # Get resource answer (need to allow student to view grades for this to work) - &Apache::lonnet::appenv(('allowed.vgr'=>'F')); - my $useranswer=&Apache::loncommon::get_student_answers( - $symb,$env{'user.name'},$env{'user.domain'}, - $env{'request.course.id'}); + &Apache::lonnet::appenv({'allowed.vgr'=>'F'}); + my $usersymb = &Apache::lonenc::check_encrypt($symb); + my $useranswer= + &Apache::loncommon::get_student_answers( + $usersymb,$env{'user.name'},$env{'user.domain'}, + $env{'request.course.id'}); &Apache::lonnet::delenv('allowed.vgr'); # Get attachments, if any, and not too large my $attachmenturl=''; + my $toolarge=''; if (($env{'form.origpage'}) || ($env{'form.editdisc'}) || ($env{'form.replydisc'})) { my ($symb,$idx); @@ -3396,32 +4577,42 @@ ENDREDIR $symb=~s|(bulletin___\d+___)adm/wrapper/|$1|; $attachmenturl=&construct_attachmenturl(\@currnewattach,\@keepold,$symb,$idx); } elsif ($env{'form.attachment.filename'}) { - unless (length($env{'form.attachment'})>131072) { - $attachmenturl=&Apache::lonnet::userfileupload('attachment',undef,'feedback'); - } + if (length($env{'form.attachment'})<=$attachmax{'num'}) { + my $now = time; + my $subdir = 'feedback/'.$now; + $attachmenturl=&Apache::lonnet::userfileupload('attachment',undef,$subdir); + } else { + $toolarge = '

      '.&mt('Attachment not included - exceeded permitted length').'

      '; + } } # Filter HTML out of message (could be nasty) - my $message=&clear_out_html($env{'form.comment'}); + my $override; + if ($env{'form.discuss'} =~ /^(?:author|question|course|policy)$/) { + $override = 1; + } + my $message=&clear_out_html($env{'form.comment'},$override); # Assemble email - my ($email,$citations)=&assemble_email($feedurl,$message,$prevattempts, + my ($email,$citations)=&assemble_email($message,$prevattempts, $usersaw,$useranswer); # Who gets this? - my ($typestyle,%to) = &decide_receiver($feedurl); + my ($typestyle,%to) = &Apache::lonmsg::decide_receiver($feedurl); # Actually send mail - my ($status,$numsent)=&send_msg($feedurl,$email,$citations, - $attachmenturl,%to); + my ($status,$numsent)=&send_msg(&clear_out_html($env{'form.subject'}), + $feedurl,$email,$citations, + $attachmenturl,$usersymb,%to); # Discussion? Store that. - my $numpost=0; - if ($env{'form.discuss'} || $env{'form.anondiscuss'}) { + if ( ($env{'form.discuss'} ne '' + && $env{'form.discuss'} !~ /^(?:author|question|course|policy)/) + || $env{'form.anondiscuss'} ne '') { my $subject = &clear_out_html($env{'form.subject'}); - my $anonmode=(defined($env{'form.anondiscuss'})); + my $anonmode=($env{'form.discuss'} eq 'anon' || $env{'form.anondiscuss'} ); $typestyle.=&adddiscuss($symb,$message,$anonmode,$attachmenturl, - $subject); + $subject,$group); $numpost++; } @@ -3434,13 +4625,26 @@ ENDREDIR $env{'user.domain'}, 'CourseBlog_'.$env{'request.course.id'}, $subject,$message,$feedurl,'public'); - $blog='
      '.&mt('Added to my course blog').'
      '; + if (&Apache::loncommon::course_type() eq 'Community') { + $blog='
      '.&mt('Added to my community blog').'
      '; + } else { + $blog='
      '.&mt('Added to my course blog').'
      '; + } } # Receipt screen and redirect back to where came from - &redirect_back($r,$feedurl,$typestyle,$numsent,$numpost,$blog,$status,$env{'form.previous'}); + &redirect_back($r,$feedurl,$typestyle,$numsent,$numpost,$blog,$status,$env{'form.previous'},undef,undef,undef,undef,undef,undef,$group,$toolarge); } return OK; +} + +sub blocked_reply_or_edit { + my ($blocktext) = @_; + return + &Apache::loncommon::start_page('Resource Feedback and Discussion'). + $blocktext.'

      '. + &mt('Back to previous page'). + &Apache::loncommon::end_page(); } sub wrap_symb { @@ -3452,6 +4656,7 @@ sub wrap_symb { } return $ressymb; } + sub dewrapper { my ($feedurl)=@_; if ($$feedurl=~m|^/adm/wrapper/adm/.*/bulletinboard$|) { @@ -3477,5 +4682,238 @@ sub get_feedurl_and_clean_symb { my $feedurl = &get_feedurl($symb); return ($symb,$feedurl); } + +sub editing_allowed { + my ($postid,$group) = @_; + $postid = &unescape($postid); + my $can_edit = 0; + if ($group ne '') { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + if ($postid =~ m|^bulletin___\d+___adm/wrapper(/adm/\Q$cdom\E/\Q$cnum\E/\d+/bulletinboard)|) { + if (&check_group_priv($group,'egp') eq 'ok') { + $can_edit = 1; + } + return $can_edit; + } + } + my $cid = $env{'request.course.id'}; + my $role = (split(/\./,$env{'request.role'}))[0]; + my $section = $env{'request.course.sec'}; + my $allow_editing_config = + $env{'course.'.$cid.'.allow_discussion_post_editing'}; + if ($allow_editing_config =~ m/^\s*yes\s*$/i) { + $can_edit = 1; + } else { + foreach my $editor (split(/,/,$allow_editing_config)) { + my ($editor_role,$editor_sec) = split(/:/,$editor); + if ($editor_role eq $role + && defined($editor_sec) + && defined($section) + && $editor_sec eq $section) { + $can_edit = 1; + last; + } + if ($editor_role eq $role + && !defined($editor_sec)) { + $can_edit = 1; + } + } + } + return $can_edit; +} + +sub check_group_priv { + my ($group,$grp_priv) = @_; + foreach my $priv ('mdg','vcg') { + my $checkcourse = $env{'request.course.id'}. + ($env{'request.course.sec'}?'/'.$env{'request.course.sec'}:''); + if (&Apache::lonnet::allowed($priv,$checkcourse)) { + return 'ok'; + } + } + if ($grp_priv && $group ne '') { + if (&Apache::lonnet::allowed($grp_priv,$env{'request.course.id'}.'/'.$group)) { + return 'ok'; + } + } + return ''; +} + +sub group_args { + my ($group) = @_; + if ($group eq '') { return ''; } + my $extra_args = '&group='.$group; + if (exists($env{'form.ref'})) { + $extra_args .= '&ref='.$env{'form.ref'}; + } + return $extra_args; +} + +sub get_resource_title { + my ($symb,$feedurl) = @_; + my ($restitle,$plainurl); + if (defined($symb)) { + my $plain_symb = &Apache::lonenc::check_decrypt($symb); + $restitle = &Apache::lonnet::gettitle($plain_symb); + } + if (defined($feedurl)) { + $plainurl = &Apache::lonenc::check_decrypt($feedurl); + } + if (!defined($restitle)) { + if (defined($feedurl)) { + $restitle = &Apache::lonnet::gettitle($plainurl); + } + } + if ($plainurl ne $feedurl) { + my ($plain_filename) = ($plainurl =~ m-/([^/]+)$-); + if ($plain_filename eq $restitle) { + $restitle = &mt('Untitled resource'); + } + } + if ($restitle eq '') { + $restitle = &mt('Untitled resource'); + } + return $restitle; +} + 1; __END__ + + +=pod + +=head1 NAME + +Apache::lonfeedback.pm + +=head1 SYNOPSIS + +Handles feedback from students to instructors and system administrators. + +Provides a screenshot of the current resource, as well as previous attempts if the resource was a homework. + +Used by lonmsg.pm. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org. + +=head1 OVERVIEW + +None + +=head1 SUBROUTINES + +=over + +=item discussion_open() + +=item discussion_visible() + +=item discussion_vote_available() + +=item get_realsymb() + +=item list_discussion() + +=item can_see_hidden() + +=item discussion_link() + +=item send_feedback_link() + +=item send_message_link() + +=item action_links_bar() + +=item postingform_display() + +=item build_posting_display + +=item filter_regexp() + +=item get_post_contents() + +=item replicate_attachments() + +=item mail_screen() + +=item print_display_options() + +=item print_sortfilter_options() + +=item print_showposters() + +=item get_post_versions() + +=item get_post_attachments() + +=item fail_redirect() + +=item redirect_back() + +=item no_redirect_back() + +=item screen_header() + +=item resource_output() + +=item clear_out_html() + +=item assemble_email() + +=item feedback_available() + +=item send_msg() + +=item adddiscuss() + +=item get_discussion_info() + +=item show_preview() + +=item newline_to_br() + +=item tidy_html() + +=item generate_preview_button() + +=item modify_attachments() + +=item process_attachments() + +=item generate_attachments_button() + +=item extract_attachments() + +=item construct_attachmenturl() + +=item add_blog_checkbox() + +=item has_discussion() + +=item sort_filter_names() + +=item handler() + +=item blocked_reply_or_edit() + +=item wrap_symb() + +=item dewrapper() + +=item get_feedurl() + +=item get_feedurl_and_clean_symb() + +=item editing_allowed() + +=item check_group_priv() + +=item group_args() + +=item get_resource_title() + +=back + +=cut 500 Internal Server Error

      Internal Server Error

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

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

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