![]() ![]() | ![]() |
Bug #1019. Allow limited HTML in templates.
1: # The LearningOnline Network 2: # Feedback 3: # 4: # $Id: lonfeedback.pm,v 1.39 2003/02/10 16:22:28 www Exp $ 5: # 6: # Copyright Michigan State University Board of Trustees 7: # 8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA). 9: # 10: # LON-CAPA is free software; you can redistribute it and/or modify 11: # it under the terms of the GNU General Public License as published by 12: # the Free Software Foundation; either version 2 of the License, or 13: # (at your option) any later version. 14: # 15: # LON-CAPA is distributed in the hope that it will be useful, 16: # but WITHOUT ANY WARRANTY; without even the implied warranty of 17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18: # GNU General Public License for more details. 19: # 20: # You should have received a copy of the GNU General Public License 21: # along with LON-CAPA; if not, write to the Free Software 22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23: # 24: # /home/httpd/html/adm/gpl.txt 25: # 26: # http://www.lon-capa.org/ 27: # 28: # (Internal Server Error Handler 29: # 30: # (Login Screen 31: # 5/21/99,5/22,5/25,5/26,5/31,6/2,6/10,7/12,7/14, 32: # 1/14/00,5/29,5/30,6/1,6/29,7/1,11/9 Gerd Kortemeyer) 33: # 34: # 3/1/1 Gerd Kortemeyer) 35: # 36: # 3/1,2/3,2/5,2/6,2/8 Gerd Kortemeyer 37: # 2/9 Guy Albertelli 38: # 2/10 Gerd Kortemeyer 39: # 2/13 Guy Albertelli 40: # 7/25 Gerd Kortemeyer 41: # 7/26 Guy Albertelli 42: # 7/26,8/10,10/1,11/5,11/6,12/27,12/29 Gerd Kortemeyer 43: # YEAR=2002 44: # 1/1,1/16 Gerd Kortemeyer 45: # 46: 47: package Apache::lonfeedback; 48: 49: use strict; 50: use Apache::Constants qw(:common); 51: use Apache::lonmsg(); 52: use Apache::loncommon(); 53: use Apache::lontexconvert(); 54: 55: sub mail_screen { 56: my ($r,$feedurl,$options) = @_; 57: my $bodytag=&Apache::loncommon::bodytag('Resource Feedback and Discussion', 58: '','onLoad="window.focus();"'); 59: $r->print(<<ENDDOCUMENT); 60: <html> 61: <head> 62: <title>The LearningOnline Network with CAPA</title> 63: <meta http-equiv="pragma" content="no-cache"></meta> 64: <script> 65: function gosubmit() { 66: var rec=0; 67: if (typeof(document.mailform.elements.author)!="undefined") { 68: if (document.mailform.elements.author.checked) { 69: rec=1; 70: } 71: } 72: if (typeof(document.mailform.elements.question)!="undefined") { 73: if (document.mailform.elements.question.checked) { 74: rec=1; 75: } 76: } 77: if (typeof(document.mailform.elements.course)!="undefined") { 78: if (document.mailform.elements.course.checked) { 79: rec=1; 80: } 81: } 82: if (typeof(document.mailform.elements.policy)!="undefined") { 83: if (document.mailform.elements.policy.checked) { 84: rec=1; 85: } 86: } 87: if (typeof(document.mailform.elements.discuss)!="undefined") { 88: if (document.mailform.elements.discuss.checked) { 89: rec=1; 90: } 91: } 92: if (typeof(document.mailform.elements.anondiscuss)!="undefined") { 93: if (document.mailform.elements.anondiscuss.checked) { 94: rec=1; 95: } 96: } 97: 98: if (rec) { 99: document.mailform.submit(); 100: } else { 101: alert('Please check a feedback type.'); 102: } 103: } 104: </script> 105: </head> 106: $bodytag 107: <h2><tt>$feedurl</tt></h2> 108: <form action="/adm/feedback" method=post name=mailform> 109: <input type=hidden name=postdata value="$feedurl"> 110: Please check at least one of the following feedback types: 111: $options<hr> 112: My question/comment/feedback:<p> 113: <textarea name=comment cols=60 rows=10 wrap=hard> 114: </textarea><p> 115: <input type=hidden name=sendit value=1> 116: <input type=button value="Send Feedback" onClick='gosubmit();'></input> 117: </form> 118: ENDDOCUMENT 119: $r->print(&generate_preview_button().'</body></html>'); 120: } 121: 122: sub fail_redirect { 123: my ($r,$feedurl) = @_; 124: $r->print (<<ENDFAILREDIR); 125: <head><title>Feedback not sent</title> 126: <meta http-equiv="pragma" content="no-cache"></meta> 127: <meta HTTP-EQUIV="Refresh" CONTENT="2; url=$feedurl"> 128: </head> 129: <html> 130: <body bgcolor="#FFFFFF"> 131: <img align=right src=/adm/lonIcons/lonlogos.gif> 132: <b>Sorry, no recipients ...</b> 133: </body> 134: </html> 135: ENDFAILREDIR 136: } 137: 138: sub redirect_back { 139: my ($r,$feedurl,$typestyle,$sendsomething,$sendposts,$status) = @_; 140: $r->print (<<ENDREDIR); 141: <head> 142: <title>Feedback sent</title> 143: <meta http-equiv="pragma" content="no-cache"></meta> 144: <meta HTTP-EQUIV="Refresh" CONTENT="2; url=$feedurl"> 145: </head> 146: <html> 147: <body bgcolor="#FFFFFF"> 148: <img align=right src=/adm/lonIcons/lonlogos.gif> 149: $typestyle 150: <b>Sent $sendsomething message(s), and $sendposts post(s).</b> 151: <font color=red>$status</font> 152: </body> 153: </html> 154: ENDREDIR 155: } 156: 157: sub no_redirect_back { 158: my ($r,$feedurl) = @_; 159: $r->print (<<ENDNOREDIR); 160: <head><title>Feedback not sent</title> 161: <meta http-equiv="pragma" content="no-cache"></meta> 162: ENDNOREDIR 163: 164: if ($feedurl!~/^\/adm\/feedback/) { 165: $r->print('<meta HTTP-EQUIV="Refresh" CONTENT="2; url='.$feedurl.'">'); 166: } 167: 168: $r->print (<<ENDNOREDIRTWO); 169: </head> 170: <html> 171: <body bgcolor="#FFFFFF"> 172: <img align=right src=/adm/lonIcons/lonlogos.gif> 173: <b>Sorry, no feedback possible on this resource ...</b> 174: </body> 175: </html> 176: ENDNOREDIRTWO 177: } 178: 179: sub screen_header { 180: my ($feedurl) = @_; 181: my $options=''; 182: if (($feedurl=~/^\/res\//) && ($feedurl!~/^\/res\/adm/)) { 183: $options= 184: '<p><input type=checkbox name=author> Feedback to resource author'; 185: } 186: if (&feedback_available(1)) { 187: $options.= 188: '<br><input type=checkbox name=question> Question about resource content'; 189: } 190: if (&feedback_available(0,1)) { 191: $options.= 192: '<br><input type=checkbox name=course> '. 193: 'Question/Comment/Feedback about course content'; 194: } 195: if (&feedback_available(0,0,1)) { 196: $options.= 197: '<br><input type=checkbox name=policy> '. 198: 'Question/Comment/Feedback about course policy'; 199: } 200: 201: if ($ENV{'request.course.id'}) { 202: if (&Apache::lonnet::allowed('pch', 203: $ENV{'request.course.id'}. 204: ($ENV{'request.course.sec'}?'/'.$ENV{'request.course.sec'}:''))) { 205: $options.='<br><input type=checkbox name=discuss> '. 206: '<b>Contribution to course discussion of resource</b>'; 207: $options.='<br><input type=checkbox name=anondiscuss> '. 208: '<b>Anonymous contribution to course discussion of resource</b>'. 209: ' (name only visible to course faculty)'; 210: } 211: } 212: return $options; 213: } 214: 215: sub resource_output { 216: my ($feedurl) = @_; 217: my $usersaw=&Apache::lonnet::ssi($feedurl); 218: $usersaw=~s/\<body[^\>]*\>//gi; 219: $usersaw=~s/\<\/body\>//gi; 220: $usersaw=~s/\<html\>//gi; 221: $usersaw=~s/\<\/html\>//gi; 222: $usersaw=~s/\<head\>//gi; 223: $usersaw=~s/\<\/head\>//gi; 224: $usersaw=~s/action\s*\=/would_be_action\=/gi; 225: return $usersaw; 226: } 227: 228: sub clear_out_html { 229: my ($message,$override)=@_; 230: my $cid=$ENV{'request.course.id'}; 231: if (($ENV{"course.$cid.allow_limited_html_in_feedback"} =~ m/yes/i) || 232: ($override)) { 233: # allows <B> <I> <P> <A> <LI> <OL> <UL> <EM> <BR> <TT> <STRONG> 234: # <BLOCKQUOTE> <DIV .*> <DIV> <IMG> 235: my %html=(B=>1, I=>1, P=>1, A=>1, LI=>1, OL=>1, UL=>1, EM=>1, 236: BR=>1, TT=>1, STRONG=>1, BLOCKQUOTE=>1, DIV=>1, IMG=>1); 237: 238: $message =~ s/\<(\/?\s*(\w+)[^\>\<]*)/ 239: {($html{uc($2)}&(length($1)<1000))?"\<$1":"\<$1"}/ge; 240: $message =~ s/(\<?\s*(\w+)[^\<\>]*)\>/ 241: {($html{uc($2)}&(length($1)<1000))?"$1\>":"$1\>"}/ge; 242: } else { 243: $message=~s/\<\/*m\s*\>//g; 244: $message=~s/\</\<\;/g; 245: $message=~s/\>/\>\;/g; 246: } 247: return $message; 248: } 249: 250: sub assemble_email { 251: my ($feedurl,$message,$prevattempts,$usersaw)=@_; 252: my $email=<<"ENDEMAIL"; 253: Refers to <a href="$feedurl">$feedurl</a> 254: 255: $message 256: ENDEMAIL 257: my $citations=<<"ENDCITE"; 258: <h2>Previous attempts of student (if applicable)</h2> 259: $prevattempts 260: <p><hr> 261: <h2>Original screen output (if applicable)</h2> 262: $usersaw 263: ENDCITE 264: return ($email,$citations); 265: } 266: 267: sub secapply { 268: my $rec=shift; 269: my $defaultflag=shift; 270: $rec=~s/\s+//g; 271: $rec=~s/\@/\:/g; 272: my ($adr,$sections)=($rec=~/^([^\(]+)\(([^\)]+)\)/); 273: if ($sections) { 274: foreach (split(/\;/,$sections)) { 275: if (($_ eq $ENV{'request.course.sec'}) || 276: ($defaultflag && ($_ eq '*'))) { 277: return $adr; 278: } 279: } 280: } else { 281: return $rec; 282: } 283: return ''; 284: } 285: 286: sub decide_receiver { 287: my ($feedurl,$author,$question,$course,$policy,$defaultflag) = @_; 288: my $typestyle=''; 289: my %to=(); 290: if ($ENV{'form.author'}||$author) { 291: $typestyle.='Submitting as Author Feedback<br>'; 292: $feedurl=~/^\/res\/(\w+)\/(\w+)\//; 293: $to{$2.':'.$1}=1; 294: } 295: if ($ENV{'form.question'}||$question) { 296: $typestyle.='Submitting as Question<br>'; 297: foreach (split(/\,/, 298: $ENV{'course.'.$ENV{'request.course.id'}.'.question.email'}) 299: ) { 300: my $rec=&secapply($_,$defaultflag); 301: if ($rec) { $to{$rec}=1; } 302: } 303: } 304: if ($ENV{'form.course'}||$course) { 305: $typestyle.='Submitting as Comment<br>'; 306: foreach (split(/\,/, 307: $ENV{'course.'.$ENV{'request.course.id'}.'.comment.email'}) 308: ) { 309: my $rec=&secapply($_,$defaultflag); 310: if ($rec) { $to{$rec}=1; } 311: } 312: } 313: if ($ENV{'form.policy'}||$policy) { 314: $typestyle.='Submitting as Policy Feedback<br>'; 315: foreach (split(/\,/, 316: $ENV{'course.'.$ENV{'request.course.id'}.'.policy.email'}) 317: ) { 318: my $rec=&secapply($_,$defaultflag); 319: if ($rec) { $to{$rec}=1; } 320: } 321: } 322: if ((scalar(%to) eq '0') && (!$defaultflag)) { 323: ($typestyle,%to)= 324: &decide_receiver($feedurl,$author,$question,$course,$policy,1); 325: } 326: return ($typestyle,%to); 327: } 328: 329: sub feedback_available { 330: my ($question,$course,$policy)=@_; 331: my ($typestyle,%to)=&decide_receiver('',0,$question,$course,$policy); 332: return scalar(%to); 333: } 334: 335: sub send_msg { 336: my ($feedurl,$email,$citations,%to)=@_; 337: my $status=''; 338: my $sendsomething=0; 339: foreach (keys %to) { 340: if ($_) { 341: my $declutter=&Apache::lonnet::declutter($feedurl); 342: unless (&Apache::lonmsg::user_normal_msg(split(/\:/,$_), 343: 'Feedback ['.$declutter.']',$email,$citations)=~/ok/) { 344: $status.='<br>Error sending message to '.$_.'<br>'; 345: } else { 346: $sendsomething++; 347: } 348: } 349: } 350: 351: my %record=&Apache::lonnet::restore('_feedback'); 352: my ($temp)=keys %record; 353: unless ($temp=~/^error\:/) { 354: my %newrecord=(); 355: $newrecord{'resource'}=$feedurl; 356: $newrecord{'subnumber'}=$record{'subnumber'}+1; 357: unless (&Apache::lonnet::cstore(\%newrecord,'_feedback') eq 'ok') { 358: $status.='<br>Not registered<br>'; 359: } 360: } 361: 362: return ($status,$sendsomething); 363: } 364: 365: sub adddiscuss { 366: my ($symb,$email,$anon)=@_; 367: my $status=''; 368: if (&Apache::lonnet::allowed('pch',$ENV{'request.course.id'}. 369: ($ENV{'request.course.sec'}?'/'.$ENV{'request.course.sec'}:''))) { 370: 371: my %contrib=('message' => $email, 372: 'sendername' => $ENV{'user.name'}, 373: 'senderdomain' => $ENV{'user.domain'}, 374: 'screenname' => $ENV{'environment.screenname'}, 375: 'plainname' => $ENV{'environment.firstname'}.' '. 376: $ENV{'environment.middlename'}.' '. 377: $ENV{'environment.lastname'}.' '. 378: $ENV{'enrironment.generation'}); 379: if ($anon) { 380: $contrib{'anonymous'}='true'; 381: } 382: if (($symb) && ($email)) { 383: $status='Adding to class discussion'.($anon?' (anonymous)':'').': '. 384: &Apache::lonnet::store(\%contrib,$symb,$ENV{'request.course.id'}, 385: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 386: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 387: my %storenewentry=($symb => time); 388: $status.='<br>Updating discussion time: '. 389: &Apache::lonnet::put('discussiontimes',\%storenewentry, 390: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 391: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 392: } 393: my %record=&Apache::lonnet::restore('_discussion'); 394: my ($temp)=keys %record; 395: unless ($temp=~/^error\:/) { 396: my %newrecord=(); 397: $newrecord{'resource'}=$symb; 398: $newrecord{'subnumber'}=$record{'subnumber'}+1; 399: $status.='<br>Registering: '. 400: &Apache::lonnet::cstore(\%newrecord,'_discussion'); 401: } 402: } else { 403: $status.='Failed.'; 404: } 405: return $status.'<br>'; 406: } 407: 408: # ----------------------------------------------------------- Preview function 409: 410: sub show_preview { 411: my $r=shift; 412: my $message=&clear_out_html($ENV{'form.comment'}); 413: $message=~s/\n/\<br \/\>/g; 414: $message=&Apache::lontexconvert::msgtexconverted($message); 415: $r->print('<table border="2"><tr><td>'. 416: $message.'</td></tr></table>'); 417: } 418: 419: sub generate_preview_button { 420: return(<<ENDPREVIEW); 421: <form name="preview" action="/adm/feedback?preview=1" method="post" target="preview"> 422: <input type="hidden" name="comment" /> 423: <input type="button" value="Show Preview" 424: onClick="this.form.comment.value=document.mailform.comment.value;this.form.submit();" /> 425: </form> 426: ENDPREVIEW 427: } 428: sub handler { 429: my $r = shift; 430: if ($r->header_only) { 431: $r->content_type('text/html'); 432: $r->send_http_header; 433: return OK; 434: } 435: 436: # --------------------------- Get query string for limited number of parameters 437: 438: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, 439: ['hide','unhide','deldisc','postdata','preview']); 440: 441: if (($ENV{'form.hide'}) || ($ENV{'form.unhide'})) { 442: # ----------------------------------------------------------------- Hide/unhide 443: $r->content_type('text/html'); 444: $r->send_http_header; 445: 446: my $entry=$ENV{'form.hide'}?$ENV{'form.hide'}:$ENV{'form.unhide'}; 447: 448: my ($symb,$idx)=split(/\:\:\:/,$entry); 449: my ($map,$ind,$url)=split(/\_\_\_/,$symb); 450: 451: my %contrib=&Apache::lonnet::restore($symb,$ENV{'request.course.id'}, 452: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 453: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 454: 455: 456: my $currenthidden=$contrib{'hidden'}; 457: 458: if ($ENV{'form.hide'}) { 459: $currenthidden.='.'.$idx.'.'; 460: } else { 461: $currenthidden=~s/\.$idx\.//g; 462: } 463: my %newhash=('hidden' => $currenthidden); 464: 465: &Apache::lonnet::store(\%newhash,$symb,$ENV{'request.course.id'}, 466: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 467: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 468: 469: &redirect_back($r,&Apache::lonnet::clutter($url), 470: 'Changed discussion status<p>','0','0'); 471: } elsif ($ENV{'form.deldisc'}) { 472: # --------------------------------------------------------------- Hide for good 473: $r->content_type('text/html'); 474: $r->send_http_header; 475: 476: my $entry=$ENV{'form.deldisc'}; 477: 478: my ($symb,$idx)=split(/\:\:\:/,$entry); 479: my ($map,$ind,$url)=split(/\_\_\_/,$symb); 480: 481: my %contrib=&Apache::lonnet::restore($symb,$ENV{'request.course.id'}, 482: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 483: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 484: 485: 486: my $currentdeleted=$contrib{'deleted'}; 487: 488: $currentdeleted.='.'.$idx.'.'; 489: 490: my %newhash=('deleted' => $currentdeleted); 491: 492: &Apache::lonnet::store(\%newhash,$symb,$ENV{'request.course.id'}, 493: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 494: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 495: 496: &redirect_back($r,&Apache::lonnet::clutter($url), 497: 'Changed discussion status<p>','0','0'); 498: } elsif ($ENV{'form.preview'}) { 499: # -------------------------------------------------------- User wants a preview 500: &show_preview($r); 501: } else { 502: # ------------------------------------------------------------- Normal feedback 503: my $feedurl=$ENV{'form.postdata'}; 504: $feedurl=~s/^http\:\/\///; 505: $feedurl=~s/^$ENV{'SERVER_NAME'}//; 506: $feedurl=~s/^$ENV{'HTTP_HOST'}//; 507: 508: my $symb=&Apache::lonnet::symbread($feedurl); 509: unless ($symb) { 510: $symb=$ENV{'form.symb'}; 511: if ($symb) { 512: my ($map,$id,$url)=split(/\_\_\_/,$symb); 513: $feedurl=&Apache::lonnet::clutter($url); 514: } 515: } 516: my $goahead=1; 517: if ($feedurl=~/\.(problem|exam|quiz|assess|survey|form)$/) { 518: unless ($symb) { $goahead=0; } 519: } 520: 521: if ($goahead) { 522: # Go ahead with feedback, no ambiguous reference 523: $r->content_type('text/html'); 524: $r->send_http_header; 525: 526: if ( 527: ( 528: ($feedurl=~m:^/res:) && ($feedurl!~m:^/res/adm:) 529: ) 530: || 531: ($ENV{'request.course.id'} && ($feedurl!~m:^/adm:)) 532: || 533: ($ENV{'request.course.id'} && ($symb=~/^bulletin\_\_\_/)) 534: ) { 535: # --------------------------------------------------- Print login screen header 536: unless ($ENV{'form.sendit'}) { 537: my $options=&screen_header($feedurl); 538: if ($options) { 539: &mail_screen($r,$feedurl,$options); 540: } else { 541: &fail_redirect($r,$feedurl); 542: } 543: } else { 544: 545: # Get previous user input 546: my $prevattempts=&Apache::loncommon::get_previous_attempt( 547: $symb,$ENV{'user.name'},$ENV{'user.domain'}, 548: $ENV{'request.course.id'}); 549: 550: # Get output from resource 551: my $usersaw=&resource_output($feedurl); 552: 553: # Filter HTML out of message (could be nasty) 554: my $message=&clear_out_html($ENV{'form.comment'}); 555: 556: # Assemble email 557: my ($email,$citations)=&assemble_email($feedurl,$message,$prevattempts, 558: $usersaw); 559: 560: # Who gets this? 561: my ($typestyle,%to) = &decide_receiver($feedurl); 562: 563: # Actually send mail 564: my ($status,$numsent)=&send_msg($feedurl,$email,$citations,%to); 565: 566: # Discussion? Store that. 567: 568: my $numpost=0; 569: if ($ENV{'form.discuss'}) { 570: $typestyle.=&adddiscuss($symb,$message); 571: $numpost++; 572: } 573: 574: if ($ENV{'form.anondiscuss'}) { 575: $typestyle.=&adddiscuss($symb,$message,1); 576: $numpost++; 577: } 578: 579: 580: # Receipt screen and redirect back to where came from 581: &redirect_back($r,$feedurl,$typestyle,$numsent,$numpost,$status); 582: 583: } 584: } else { 585: # Unable to give feedback 586: &no_redirect_back($r,$feedurl); 587: } 588: } else { 589: # Ambiguous Problem Resource 590: $r->internal_redirect('/adm/ambiguous'); 591: } 592: } 593: return OK; 594: } 595: 596: 1; 597: __END__