--- loncom/interface/lonmsg.pm 2003/02/18 15:47:40 1.49 +++ loncom/interface/lonmsg.pm 2003/08/18 17:37:42 1.64 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Routines for messaging # -# $Id: lonmsg.pm,v 1.49 2003/02/18 15:47:40 albertel Exp $ +# $Id: lonmsg.pm,v 1.64 2003/08/18 17:37:42 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -44,6 +44,68 @@ # package Apache::lonmsg; +=pod + +=head1 NAME + +Apache::lonmsg: supports internal messaging + +=head1 SYNOPSIS + +lonmsg provides routines for sending messages, receiving messages, and +a handler to allow users to read, send, and delete messages. + +=head1 OVERVIEW + +=head2 Messaging Overview + +X<messages>LON-CAPA provides an internal messaging system similar to +email, but customized for LON-CAPA's usage. LON-CAPA implements its +own messaging system, rather then building on top of email, because of +the features LON-CAPA messages can offer that conventional e-mail can +not: + +=over 4 + +=item * B<Critical messages>: A message the recipient B<must> +acknowlegde receipt of before they are allowed to continue using the +system, preventing a user from claiming they never got a message + +=item * B<Receipts>: LON-CAPA can reliably send reciepts informing the +sender that it has been read; again, useful for preventing students +from claiming they did not see a message. (While conventional e-mail +has some reciept support, it's sporadic, e-mail client-specific, and +generally the receiver can opt to not send one, making it useless in +this case.) + +=item * B<Context>: LON-CAPA knows about the sender, such as where +they are in a course. When a student mails an instructor asking for +help on the problem, the instructor receives not just the student's +question, but all submissions the student has made up to that point, +the user's rendering of the problem, and the complete view the student +saw of the resource, including discussion up to that point. Finally, +the instructor is reading all of this inside of LON-CAPA, not their +email program, so they have full access to LON-CAPA's grading +interface, or other features they may wish to use in response to the +student's query. + +=back + +Users can ask LON-CAPA to forward messages to conventional e-mail +addresses on their B<PREF> screen, but generally, LON-CAPA messages +are much more useful then traditional email can be made to be, even +with HTML support. + +Right now, this document will cover just how to send a message, since +it is likely you will not need to programmatically read messages, +since lonmsg already implements that functionality. + +=head1 FUNCTIONS + +=over 4 + +=cut + use strict; use Apache::lonnet(); use vars qw($msgcount); @@ -52,18 +114,22 @@ use Apache::Constants qw(:common); use Apache::loncommon(); use Apache::lontexconvert(); use HTML::Entities(); +use Mail::Send; # ===================================================================== Package sub packagemsg { - my ($subject,$message,$citation,$baseurl)=@_; - &Apache::lonnet::logthis("base is $baseurl"); + my ($subject,$message,$citation,$baseurl,$attachmenturl)=@_; $message =&HTML::Entities::encode($message); $citation=&HTML::Entities::encode($citation); $subject =&HTML::Entities::encode($subject); #remove machine specification $baseurl =~ s|^http://[^/]+/|/|; $baseurl =&HTML::Entities::encode($baseurl); + #remove machine specification + $attachmenturl =~ s|^http://[^/]+/|/|; + $attachmenturl =&HTML::Entities::encode($attachmenturl); + my $now=time; $msgcount++; my $partsubj=$subject; @@ -94,13 +160,16 @@ sub packagemsg { if (defined($baseurl)) { $result.= '<baseurl>'.$baseurl.'</baseurl>'; } + if (defined($attachmenturl)) { + $result.= '<attachmenturl>'.$attachmenturl.'</attachmenturl>'; + } return $msgid,$result; } # ================================================== Unpack message into a hash sub unpackagemsg { - my $message=shift; + my ($message,$notoken)=@_; my %content=(); my $parser=HTML::TokeParser->new(\$message); my $token; @@ -109,8 +178,16 @@ sub unpackagemsg { my $entry=$token->[1]; my $value=$parser->get_text('/'.$entry); $content{$entry}=$value; - &Apache::lonnet::logthis("setting $entry to $value"); - + } + } + if ($content{'attachmenturl'}) { + my ($fname,$ft)=($content{'attachmenturl'}=~/\/(\w+)\.(\w+)$/); + if ($notoken) { + $content{'message'}.='<p>Attachment: <tt>'.$fname.'.'.$ft.'</tt>'; + } else { + $content{'message'}.='<p>Attachment: <a href="'. + &Apache::lonnet::tokenwrapper($content{'attachmenturl'}). + '"><tt>'.$fname.'.'.$ft.'</tt></a>'; } } return %content; @@ -128,6 +205,42 @@ sub unpackmsgid { return ($sendtime,$shortsubj,$fromname,$fromdomain,$status{$msgid}); } + +sub sendemail { + my ($to,$subject,$body)=@_; + $body= + "*** This is an automatic message generated by the LON-CAPA system.\n". + "*** Please do not reply to this address.\n\n".$body; + my $msg = new Mail::Send; + $msg->to($to); + $msg->subject('[LON-CAPA] '.$subject); + my $fh = $msg->open('smtp',Server => 'localhost'); + print $fh $body; + $fh->close; +} + +# ==================================================== Send notification emails + +sub sendnotification { + my ($to,$touname,$toudom,$subj,$crit)=@_; + my $sender=$ENV{'environment.firstname'}.' '.$ENV{'environment.lastname'}; + my $critical=($crit?' critical':''); + my $url='http://'. + $Apache::lonnet::hostname{&Apache::lonnet::homeserver($touname,$toudom)}. + '/adm/email?username='.$touname.'&domain='.$toudom; + my $body=(<<ENDMSG); +You received a$critical message from $sender in LON-CAPA. The subject is + + $subj + +Use + + $url + +to access this message. +ENDMSG + &sendemail($to,'New'.$critical.' message from '.$sender,$body); +} # ============================================================= Check for email sub newmail { @@ -141,6 +254,13 @@ sub newmail { # =============================== Automated message to the author of a resource +=pod + +=item * B<author_res_msg($filename, $message)>: Sends message $message to the owner + of the resource with the URI $filename. + +=cut + sub author_res_msg { my ($filename,$message)=@_; unless ($message) { return 'empty'; } @@ -185,6 +305,13 @@ sub user_crit_msg_raw { } else { $status='no_host'; } +# Notifications + my %userenv = &Apache::lonnet::get('environment',['critnotification'], + $domain,$user); + if ($userenv{'critnotification'}) { + &sendnotification($userenv{'critnotification'},$user,$domain,$subject,1); + } +# Log this &Apache::lonnet::logthis( 'Sending critical email '.$msgid. ', log status: '. @@ -197,6 +324,14 @@ sub user_crit_msg_raw { # New routine that respects "forward" and calls old routine +=pod + +=item * B<user_crit_msg($user, $domain, $subject, $message, $sendback)>: Sends + a critical message $message to the $user at $domain. If $sendback is true, + a reciept will be sent to the current user when $user recieves the message. + +=cut + sub user_crit_msg { my ($user,$domain,$subject,$message,$sendback)=@_; my $status=''; @@ -221,7 +356,7 @@ sub user_crit_msg { sub user_crit_received { my $msgid=shift; my %message=&Apache::lonnet::get('critical',[$msgid]); - my %contents=&unpackagemsg($message{$msgid}); + my %contents=&unpackagemsg($message{$msgid},1); my $status='rec: '.($contents{'sendback'}? &user_normal_msg($contents{'sendername'},$contents{'senderdomain'}, 'Receipt: '.$ENV{'user.name'}.' at '.$ENV{'user.domain'}, @@ -245,15 +380,15 @@ sub user_crit_received { # ======================================================== Normal communication sub user_normal_msg_raw { - my ($user,$domain,$subject,$message,$citation,$baseurl)=@_; + my ($user,$domain,$subject,$message,$citation,$baseurl,$attachmenturl)=@_; # Check if allowed missing my $status=''; my $msgid='undefined'; unless (($message)&&($user)&&($domain)) { $status='empty'; }; my $homeserver=&Apache::lonnet::homeserver($user,$domain); if ($homeserver ne 'no_host') { - &Apache::lonnet::logthis("baseraw is $baseurl"); - ($msgid,$message)=&packagemsg($subject,$message,$citation,$baseurl); + ($msgid,$message)=&packagemsg($subject,$message,$citation,$baseurl, + $attachmenturl); $status=&Apache::lonnet::critical( 'put:'.$domain.':'.$user.':nohist_email:'. &Apache::lonnet::escape($msgid).'='. @@ -263,6 +398,12 @@ sub user_normal_msg_raw { } else { $status='no_host'; } +# Notifications + my %userenv = &Apache::lonnet::get('environment',['notification'], + $domain,$user); + if ($userenv{'notification'}) { + &sendnotification($userenv{'notification'},$user,$domain,$subject,0); + } &Apache::lonnet::log($ENV{'user.domain'},$ENV{'user.name'}, $ENV{'user.home'}, 'Sending '.$msgid.' to '.$user.' at '.$domain.' with status: '.$status); @@ -271,8 +412,16 @@ sub user_normal_msg_raw { # New routine that respects "forward" and calls old routine +=pod + +=item * B<user_normal_msg($user, $domain, $subject, $message, + $citation, $baseurl, $attachmenturl)>: Sends a message to the + $user at $domain, with subject $subject and message $message. + +=cut + sub user_normal_msg { - my ($user,$domain,$subject,$message,$citation,$baseurl)=@_; + my ($user,$domain,$subject,$message,$citation,$baseurl,$attachmenturl)=@_; my $status=''; my %userenv = &Apache::lonnet::get('environment',['msgforward'], $domain,$user); @@ -282,11 +431,11 @@ sub user_normal_msg { my ($forwuser,$forwdomain)=split(/\:/,$_); $status.= &user_normal_msg_raw($forwuser,$forwdomain,$subject,$message, - $citation,$baseurl).' '; + $citation,$baseurl,$attachmenturl).' '; } } else { $status=&user_normal_msg_raw($user,$domain,$subject,$message, - $citation,$baseurl); + $citation,$baseurl,$attachmenturl); } return $status; } @@ -354,6 +503,19 @@ sub discourse { <input type=button onClick="uncheckall()" value="Check for None"> <p> ENDDISHEADER + my %coursepersonnel= + &Apache::lonnet::get_course_adv_roles(); + foreach my $role (sort keys %coursepersonnel) { + foreach (split(/\,/,$coursepersonnel{$role})) { + my ($puname,$pudom)=split(/\:/,$_); + $r->print( + '<br /><input type="checkbox" name="send_to_&&&&&&_'. + $puname.':'.$pudom.'" /> '. + &Apache::loncommon::plainname($puname, + $pudom).' ('.$_.'), <i>'.$role.'</i>'); + } + } + foreach (sort keys %courselist) { my ($end,$start)=split(/\:/,$courselist{$_}); my $active=1; @@ -404,7 +566,8 @@ $content{'sendername'}.'@'. } # Check to see if there were any messages. if ($result eq '') { - $result = "<h2>You have no critical messages.</h2>"; + $result = "<h2>You have no critical messages.</h2>". + '<a href="/adm/roles">Select a course</a>'; } else { $r->print($header); } @@ -417,11 +580,15 @@ $content{'sendername'}.'@'. sub comprep { my ($r,$msgid)=@_; my %message=&Apache::lonnet::get('nohist_email',[$msgid]); - my %content=&unpackagemsg($message{$msgid}); + my %content=&unpackagemsg($message{$msgid},1); my $quotemsg='> '.$content{'message'}; $quotemsg=~s/\r/\n/g; $quotemsg=~s/\f/\n/g; $quotemsg=~s/\n+/\n\> /g; + my $torepl=&Apache::loncommon::aboutmewrapper( + &Apache::loncommon::plainname($content{'sendername'},$content{'senderdomain'}),$content{'sendername'},$content{'senderdomain'}).' ('. +$content{'sendername'}.'@'. + $content{'senderdomain'}.')'; my $subject='Re: '.$content{'subject'}; my $dispcrit=''; if (&Apache::lonnet::allowed('srm',$ENV{'request.course.id'})) { @@ -435,6 +602,7 @@ sub comprep { $r->print(<<"ENDREPLY"); <form action="/adm/email" method=post> <input type=hidden name=sendreply value="$msgid"> +To: $torepl<br /> Subject: <input type=text size=50 name=subject value="$subject"><p> <textarea name=message cols=84 rows=10 wrap=hard> $quotemsg @@ -470,36 +638,101 @@ sub disall { } </script> ENDDISHEADER - $r->print( - '<h1>Display All Messages</h1><form method=post name=disall '. - 'action="/adm/email">'. - '<table border=2><tr><th colspan=2> </th><th>Date</th>'. - '<th>Username</th><th>Domain</th><th>Subject</th><th>Status</th></tr>'); - foreach (sort split(/\&/,&Apache::lonnet::reply('keys:'. - $ENV{'user.domain'}.':'. - $ENV{'user.name'}.':nohist_email', - $ENV{'user.home'}))) { - my ($sendtime,$shortsubj,$fromname,$fromdomain,$status)= + $r->print('<h1>Display All Messages</h1><form method=post name=disall '. + 'action="/adm/email">'. + '<table border=2><tr><th colspan=2> </th><th>'); + if ($ENV{'form.sortedby'} eq "revdate") { + $r->print('<a href = "?sortedby=date">Date</a></th>'); + } else { + $r->print('<a href = "?sortedby=revdate">Date</a></th>'); + } + $r->print('<th>'); + if ($ENV{'form.sortedby'} eq "revuser") { + $r->print('<a href = "?sortedby=user">Username</a>'); + } else { + $r->print('<a href = "?sortedby=revuser">Username</a>'); + } + $r->print('</th><th>'); + if ($ENV{'form.sortedby'} eq "revdomain") { + $r->print('<a href = "?sortedby=domain">Domain</a>'); + } else { + $r->print('<a href = "?sortedby=revdomain">Domain</a>'); + } + $r->print('</th><th>'); + if ($ENV{'form.sortedby'} eq "revsubject") { + $r->print('<a href = "?sortedby=subject">Subject</a>'); + } else { + $r->print('<a href = "?sortedby=revsubject">Subject</a>'); + } + $r->print('</th><th>'); + if ($ENV{'form.sortedby'} eq "revstatus") { + $r->print('<a href = "?sortedby=status">Status</th>'); + } else { + $r->print('<a href = "?sortedby=revstatus">Status</th>'); + } + $r->print('</tr>'); + my @messages = split(/\&/,&Apache::lonnet::reply('keys:'.$ENV{'user.domain'}.':'.$ENV{'user.name'}.':nohist_email',$ENV{'user.home'})); + #unpack the varibles and repack into temp for sorting + my @temp; + foreach (@messages) { + my ($sendtime,$shortsubj,$fromname,$fromdomain,$status)= &Apache::lonmsg::unpackmsgid($_); + my @temp1 = ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$_); + push @temp ,\@temp1; + } + #default sort + @temp = sort {$a->[0] <=> $b->[0]} @temp; + if ($ENV{'form.sortedby'} eq "date"){ + @temp = sort {$a->[0] <=> $b->[0]} @temp; + } + if ($ENV{'form.sortedby'} eq "revdate"){ + @temp = sort {$b->[0] <=> $a->[0]} @temp; + } + if ($ENV{'form.sortedby'} eq "user"){ + @temp = sort {lc($a->[2]) cmp lc($b->[2])} @temp; + } + if ($ENV{'form.sortedby'} eq "revuser"){ + @temp = sort {lc($b->[2]) cmp lc($a->[2])} @temp; + } + if ($ENV{'form.sortedby'} eq "domain"){ + @temp = sort {$a->[3] cmp $b->[3]} @temp; + } + if ($ENV{'form.sortedby'} eq "revdomain"){ + @temp = sort {$b->[3] cmp $a->[3]} @temp; + } + if ($ENV{'form.sortedby'} eq "subject"){ + @temp = sort {lc($a->[1]) cmp lc($b->[1])} @temp; + } + if ($ENV{'form.sortedby'} eq "revsubject"){ + @temp = sort {lc($b->[1]) cmp lc($a->[1])} @temp; + } + if ($ENV{'form.sortedby'} eq "status"){ + @temp = sort {$a->[4] cmp $b->[4]} @temp; + } + if ($ENV{'form.sortedby'} eq "revstatus"){ + @temp = sort {$b->[4] cmp $a->[4]} @temp; + } + foreach (@temp){ + my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$origID)= @$_; if (($status ne 'deleted') && defined($sendtime) && $sendtime!~/error/) { if ($status eq 'new') { $r->print('<tr bgcolor="#FFBB77">'); } elsif ($status eq 'read') { $r->print('<tr bgcolor="#BBBB77">'); } elsif ($status eq 'replied') { - $r->print('<tr bgcolor="#AAAA88">'); + $r->print('<tr bgcolor="#AAAA88">'); } else { $r->print('<tr bgcolor="#99BBBB">'); } - $r->print('<td><a href="/adm/email?display='.$_. - '">Open</a></td><td><a href="/adm/email?markdel='.$_. - '">Delete</a><input type=checkbox name="delmark_'.$_.'"></td>'. + $r->print('<td><a href="/adm/email?display='.$origID. + '">Open</a></td><td><a href="/adm/email?markdel='.$origID. + '">Delete</a><input type=checkbox name="delmark_'.$origID.'"></td>'. '<td>'.localtime($sendtime).'</td><td>'. $fromname.'</td><td>'.$fromdomain.'</td><td>'. &Apache::lonnet::unescape($shortsubj).'</td><td>'. $status.'</td></tr>'); } - } + } $r->print('</table><p>'. '<a href="javascript:checkall()">Check All</a> '. '<a href="javascript:uncheckall()">Uncheck All</a><p>'. @@ -552,6 +785,7 @@ sub compout { <td>$domform</td></tr> ENDREC } + my $latexHelp = Apache::loncommon::helpLatexCheatsheet(); if ($broadcast ne 'upload') { $r->print(<<"ENDCOMP"); <tr><td>Additional Recipients<br><tt>username\@domain,username\@domain, ... @@ -559,6 +793,7 @@ ENDREC <input type=text size=50 name=additionalrec></td></tr> <tr><td>Subject:</td><td><input type=text size=50 name=subject value="$dissub"> </td></tr></table> +$latexHelp <textarea name=message cols=80 rows=10 wrap=hard>$dismsg </textarea><p> $dispcrit @@ -718,7 +953,7 @@ sub handler { &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['display','replyto','forward','markread','markdel','markunread', 'sendreply','compose','sendmail','critical','recname','recdom', - 'recordftf']); + 'recordftf','sortedby']); # ------------------------------------------------------ They checked for email &Apache::lonnet::put('email_status',{'recnewemail'=>0}); @@ -766,21 +1001,21 @@ $content{'sendername'},$content{'senderd } elsif ($ENV{'form.sendreply'}) { my $msgid=$ENV{'form.sendreply'}; my %message=&Apache::lonnet::get('nohist_email',[$msgid]); - my %content=&unpackagemsg($message{$msgid}); + my %content=&unpackagemsg($message{$msgid},1); &statuschange($msgid,'replied'); if ((($ENV{'form.critmsg'}) || ($ENV{'form.sendbck'})) && (&Apache::lonnet::allowed('srm',$ENV{'request.course.id'}))) { $r->print('Sending critical: '. &user_crit_msg($content{'sendername'}, $content{'senderdomain'}, - $ENV{'form.subject'}, - $ENV{'form.message'}, + &Apache::lonfeedback::clear_out_html($ENV{'form.subject'}), + &Apache::lonfeedback::clear_out_html($ENV{'form.message'}), $ENV{'form.sendbck'})); } else { $r->print('Sending: '.&user_normal_msg($content{'sendername'}, $content{'senderdomain'}, - $ENV{'form.subject'}, - $ENV{'form.message'})); + &Apache::lonfeedback::clear_out_html($ENV{'form.subject'}), + &Apache::lonfeedback::clear_out_html($ENV{'form.message'}))); } if ($ENV{'form.displayedcrit'}) { &discrit($r); @@ -832,7 +1067,7 @@ $content{'sendername'},$content{'senderd if ($ENV{'form.forwid'}) { my $msgid=$ENV{'form.forwid'}; my %message=&Apache::lonnet::get('nohist_email',[$msgid]); - %content=&unpackagemsg($message{$msgid}); + %content=&unpackagemsg($message{$msgid},1); &statuschange($msgid,'forwarded'); $ENV{'form.message'}.="\n\n-- Forwarded message --\n\n". $content{'message'}; @@ -864,18 +1099,18 @@ $content{'sendername'},$content{'senderd } foreach (keys %toaddr) { my ($recuname,$recdomain)=split(/\:/,$_); - my $msgtxt=$ENV{'form.message'}; + my $msgtxt=&Apache::lonfeedback::clear_out_html($ENV{'form.message'}); if ($toaddr{$_}) { $msgtxt.='<hr>'.$toaddr{$_}; } if ((($ENV{'form.critmsg'}) || ($ENV{'form.sendbck'})) && (&Apache::lonnet::allowed('srm',$ENV{'request.course.id'}))) { $r->print('Sending critical: '. &user_crit_msg($recuname,$recdomain, - $ENV{'form.subject'}, + &Apache::lonfeedback::clear_out_html($ENV{'form.subject'}), $msgtxt, $ENV{'form.sendbck'})); } else { $r->print('Sending: '.&user_normal_msg($recuname,$recdomain, - $ENV{'form.subject'}, + &Apache::lonfeedback::clear_out_html($ENV{'form.subject'}), $msgtxt, $content{'citation'})); } @@ -899,7 +1134,14 @@ BEGIN { $msgcount=0; } -1; +=pod + +=back + +=cut + +1; + __END__