--- loncom/interface/lonfeedback.pm 2012/01/05 21:18:49 1.322 +++ loncom/interface/lonfeedback.pm 2012/03/15 12:45:21 1.340 @@ -1,7 +1,7 @@ # The LearningOnline Network # Feedback # -# $Id: lonfeedback.pm,v 1.322 2012/01/05 21:18:49 www Exp $ +# $Id: lonfeedback.pm,v 1.340 2012/03/15 12:45:21 goltermann Exp $ # # Copyright Michigan State University Board of Trustees # @@ -759,7 +759,7 @@ END sub discussion_link { - my ($ressymb,$linktext,$cmd,$item,$flag,$prev,$adds)=@_; + 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; } @@ -769,7 +769,7 @@ sub discussion_link { $width=300; $height=200; } - return &Apache::loncommon::modal_link($link,$linktext,$width,$height); + return &Apache::loncommon::modal_link($link,$linktext,$width,$height,undef,undef,$title); } @@ -787,10 +787,12 @@ sub send_feedback_link { sub send_message_link { my ($ressymb) = @_; my $output = ''. - ' '.&mt('Send Feedback').''; + &discussion_link($ressymb, + ''.&mt('Send Feedback').'', + 'sendmessageonly'). + ''; return $output; } @@ -827,7 +829,17 @@ sub action_links_bar { $discussion .= '&previous='.$prevread; } $discussion .= &group_args($group); - $discussion .= '">'.&mt('Export').''; + $discussion .= '">'.&mt('Export').''; + if (&Apache::lonnet::allowed('rin',$env{'request.course.id'})) { + $discussion .= '  '; + $discussion .=''.&mt('Undelete all deleted entries').''; + } + $discussion.=''; if ($newpostsflag) { if (!$markondisp) { $discussion .=' -
+ $lt{'note'}
-$lt{'title'}: 

+$lt{'title'}: 
ENDDISCUSS if ($env{'form.origpage'}) { @@ -931,21 +943,26 @@ sub build_posting_display { my $skip_group_check = 0; my $symb=&Apache::lonenc::check_decrypt($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'}); +# And these are the likes/unlikes my %likes=&Apache::lonnet::dump('disclikes', $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'}, '^'.$symb.':'); my $thisuser=$env{'user.name'}.':'.$env{'user.domain'}; - +# Array with likes to figure out averages, etc. + my @theselikes=(); +# 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'}; @@ -959,6 +976,7 @@ sub build_posting_display { $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') { @@ -969,11 +987,47 @@ sub build_posting_display { ($skiptest,$roleregexp,$secregexp,$statusregexp) = &filter_regexp($rolefilter,$sectionpick,$statusfilter); $rolematch = $roleregexp.':'.$secregexp.':'.$statusregexp; - } + } +# 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}) && (!$seeid)) || ($deletions{$idx}) || (!$contrib{$idx.':message'})) { + push(@theselikes,$likes{$symb.':'.$idx.':likes'}); + } + } +# 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 + my $oneplus=$ave+$stddev; + my $twoplus=$ave+2.*$stddev; + my $oneminus=$ave-$stddev; + my $twominus=$ave-2.*$stddev; +# +# 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; @@ -1101,12 +1155,12 @@ sub build_posting_display { @{$$namesort{$lastname}{$firstname}} = ("$idx"); } if ($outputtarget ne 'tex') { - unless ($likes{$symb.':'.$idx.':likers'}=~/\,\Q$thisuser\E\,/) { - $sender.=' '.&discussion_link($symb,&mt('Like'),'like',$idx,$$newpostsflag,$prevread,&group_args($group)); - } - unless ($likes{$symb.':'.$idx.':unlikers'}=~/\,\Q$thisuser\E\,/) { - $sender.=' '.&discussion_link($symb,&mt('Unlike'),'unlike',$idx,$$newpostsflag,$prevread,&group_args($group)); +# Add karma stars + my $karma=&userkarma($contrib{$idx.':sendername'},$contrib{$idx.':senderdomain'}); + for (my $i=1;$i<=$karma;$i++) { + $sender.=''.&mt('Contributor Kudos').''; } +# Can people edit this? if (&editing_allowed($escsymb.':::'.$idx,$group)) { if (($env{'user.domain'} eq $contrib{$idx.':senderdomain'}) && ($env{'user.name'} eq $contrib{$idx.':sendername'})) { $sender.=' '. @@ -1289,11 +1343,45 @@ sub build_posting_display { if ($$dischash{$toggkey}) { $$discussionitems[$idx].='  '.$ctlink; } +# Figure out size based on likes + my $thislikes=$likes{$symb.':'.$idx.':likes'}; + my $likesize="100"; + if ($thislikes>$twoplus) { + $likesize="200"; + } elsif ($thislikes>$oneplus) { + $likesize="150"; + } + if ($thislikes<$twominus) { + $likesize="50"; + } elsif ($thislikes<$oneminus) { + $likesize="75"; + } +# Actually glue in the message itself $$discussionitems[$idx].= '
'. - $message.'
'; + "
". + $message. + '
'; +# Put in the like and unlike buttons + unless ($likes{$symb.':'.$idx.':likers'}=~/\,\Q$thisuser\E\,/) { + $$discussionitems[$idx].=' '.&discussion_link($symb,''.&mt('Like').'','like',$idx,$$newpostsflag,$prevread,&group_args($group),&mt("Like this posting")); + } else { + $$discussionitems[$idx].=''.&mt('You like this posting').''; + } + unless ($likes{$symb.':'.$idx.':unlikers'}=~/\,\Q$thisuser\E\,/) { + $$discussionitems[$idx].=' '.&discussion_link($symb,''.&mt('Unlike').'',,'unlike',$idx,$$newpostsflag,$prevread,&group_args($group),&mt("Unlike this posting")); + } else { + $$discussionitems[$idx].=''.&mt('You unlike this posting').''; + } + 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.'); + $$discussionitems[$idx] .= '  '.&mt('This post has been edited by the author.'); if ($seeid) { $$discussionitems[$idx] .= '  '. &discussion_link($symb,&mt('Display all versions'),'allversions',$idx,$$newpostsflag,$prevread,&group_args($group)); @@ -1309,12 +1397,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 { @@ -1513,11 +1608,16 @@ sub mail_screen { } my %lt = &Apache::lonlocal::texthash( - 'myqu' => 'My question/comment/feedback:', + '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 = ''; @@ -1728,6 +1828,7 @@ END } $r->print(<$lt{'myqu'} $header
@@ -1748,18 +1849,26 @@ END $r->print(<$lt{'myqu'} +

$textareaheader

+

$latexHelp

-$lt{'title'}:

-

-

END + +$r->print(&Apache::lonhtmlcommon::start_pick_box()); +$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 my $attach (@currnewattach) { @@ -2600,7 +2709,7 @@ sub no_redirect_back { 'add_entries' => \%onload,); if ($feedurl !~ m{^/adm/feedback}) { - $body_options{'rediect'} = [2,$feedurl]; + $body_options{'redirect'} = [2,$feedurl]; } my $start_page= &Apache::loncommon::start_page('Feedback not sent',undef, @@ -2642,8 +2751,8 @@ sub screen_header { 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') { @@ -2651,20 +2760,20 @@ sub screen_header { } if (&feedback_available(1)) { $msgoptions.= - '

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

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

'; + '
'; } } if (($env{'request.course.id'}) && (!$env{'form.sendmessageonly'})) { @@ -2852,7 +2961,7 @@ sub storefeedbackpoints { my %record=('grader_user' => $env{'user.name'}, 'grader_domain' => $env{'user.domain'}, 'points' => $points); - return &Apache::lonnet::cstore(\%record,'_feedback'); + return &Apache::lonnet::cstore(\%record,'_feedback',$course,$udom,$uname); } # Store feedback "likes" @@ -2872,7 +2981,7 @@ sub storefeedbacklikes { 'likes_domain' => $env{'user.domain'}, 'likes' => $likes, 'totallikes' => $totallikes); - return &Apache::lonnet::cstore(\%newrecord,'_feedback'); + return &Apache::lonnet::cstore(\%newrecord,'_feedback',$course,$udom,$uname); } @@ -2982,6 +3091,7 @@ sub adddiscuss { $newrecord{'subnumber'}=$record{'subnumber'}+1; $status.='
'.&mt('Registering').': '. &Apache::lonnet::cstore(\%newrecord,'_discussion'); + &updatekarma(); } } else { $status.='Failed.'; @@ -3005,9 +3115,52 @@ sub getdiscussionrecords { sub getdiscussionstats { my %record=&getdiscussionrecords(@_); - return ($record{'subnumber'},$record{'points'},$record{'totallikes'}); + return ($record{'subnumber'},$record{'points'},$record{'totallikes'},$record{'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 @@ -3020,7 +3173,7 @@ sub storediscussionpoints { my %record=('grader_user' => $env{'user.name'}, 'grader_domain' => $env{'user.domain'}, 'points' => $points); - return &Apache::lonnet::cstore(\%record,'_discussion'); + return &Apache::lonnet::cstore(\%record,'_discussion',$course,$udom,$uname); } # Store discussion "likes" @@ -3035,12 +3188,19 @@ sub storediscussionlikes { unless ($course) { $course=$env{'request.course.id'}; } my %record=&getdiscussionrecords($uname,$udom,$course); my $totallikes=$record{'totallikes'}; + my $totalvotes=$record{'totalvotes'}; $totallikes+=$likes; + $totalvotes++; my %newrecord=('likes_user' => $env{'user.name'}, 'likes_domain' => $env{'user.domain'}, 'likes' => $likes, - 'totallikes' => $totallikes); - return &Apache::lonnet::cstore(\%newrecord,'_discussion'); + 'totallikes' => $totallikes, + 'totalvotes' => $totalvotes); + my $status=&Apache::lonnet::cstore(\%newrecord,'_discussion',$course,$udom,$uname); + if ($status eq 'ok') { + &updatekarma($uname,$udom,$course); + } + return $status; } sub get_discussion_info { @@ -3188,11 +3348,11 @@ sub modify_attachments { my %lt = &Apache::lonlocal::texthash( 'subj' => 'Subject', - 'thfo' => 'The following attachments were part of the most recent saved version of this posting.', '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.', + 'adda' => 'Add a new attachment to this post', 'stch' => 'Save Changes', + 'clic' => 'Add/remove attachments', ); my $js = < @@ -3206,7 +3366,7 @@ END # Breadcrumbs my $brcrum = [{'href' => '', 'text' => 'Discussion Post Attachments'}]; - my %parms=(); + my %parms=('only_body' => 1); if ($env{'form.modal'} ne 'yes') { 'bread_crumbs' => $brcrum } my $start_page = @@ -3235,44 +3395,43 @@ $start_page $toolarge
- - - - - - - - - -
- Subject: $subject

+

$lt{'clic'}

END - 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(&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 ((ref($currnewattach) eq 'ARRAY') && (@{$currnewattach} > 0)) { - $r->print($lt{'thef'}.'
'.$lt{'chth'}.'
'."\n"); - foreach my $attach (@{$currnewattach}) { - $attach =~ 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(<
- $lt{'adda'} -
$attachmaxtext
@@ -3330,12 +3489,12 @@ sub generate_attachments_button { my $origpage = $ENV{'REQUEST_URI'}; my $att=$attachnum.' '.&mt("attachments"); my %lt = &Apache::lonlocal::texthash( - 'clic' => 'Click to add/remove attachments', + 'clic' => 'Add/remove attachments', ); my $response = (< -$lt{'clic'}:  $likes, $prefix.'likers' => $currentlikers, $prefix.'unlikers' => $currentunlikers); +# Store data in db-file "disclikes" if (&Apache::lonnet::put('disclikes', \%newhash, $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'}) eq 'ok') { +# Also store with the person who posted the liked/unliked entry if ($env{'form.like'}) { &storediscussionlikes(1,$contrib{$idx.':sendername'},$contrib{$idx.':senderdomain'}); $result=&mt("Registered 'Like'"); @@ -3903,6 +4072,7 @@ ENDREDIR $result=&mt("Registered 'Unlike'"); } } else { +# Oops, something went wrong $result=&mt("Failed to register vote"); } } @@ -3993,6 +4163,24 @@ ENDREDIR &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'}); + $r->print(&Apache::loncommon::start_page('Undelete all deleted discussion entries')); + if (&Apache::lonnet::allowed('rin',$env{'request.course.id'})) { + if (&Apache::lonnet::store({'deleted' => ''},$symb,$env{'request.course.id'}, + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}) eq 'ok') { + $r->print(&Apache::lonhtmlcommon::confirm_success(&mt("Undeleted all entries"))); + } else { + $r->print(&Apache::lonhtmlcommon::confirm_success(&mt("Failed to undelete entries"),1)); + } + $r->print("
".&mt("Return and reload").""); + } + $r->print(&Apache::loncommon::end_page()); + return OK; } else { # ------------------------------------------------------------- Normal feedback my $feedurl=$env{'form.postdata'}; @@ -4008,6 +4196,8 @@ ENDREDIR $symb=(split(/\:\:\:/,$env{'form.editdisc'}))[0]; } elsif ($env{'form.origpage'}) { $symb=""; + } elsif ($env{'form.sendmessageonly'}) { + $symb=(split(/\:\:\:/,$env{'form.sendmessageonly'}))[0]; } else { $symb=&Apache::lonnet::symbread($feedurl); }