2 # This is a module with functions for HTML output.
4 # I separate it from the main STUMP stuff because these functions are
5 # bulky and not very interesting.
12 my $title = pop( @_ );
14 "Content-Type: text/html\n\n
20 print "<B> You are operating in demonstration mode. User actions will have no effect.</B><HR>\n";
26 print "\n<HR>Thank you for using <A HREF=$STUMP_URL>STUMP Robomoderator</A>.
28 Click <A HREF=$base_address>here</A> to return to WebSTUMP. -->
32 # prints a link to help
33 # accepts topic id and topic name.
36 my $topic_name = pop( @_ );
37 my $topic = pop( @_ );
39 #&print_image( "help.gif", "" );
41 print "<A HREF=$base_address?action=help&topic=$topic TARGET=new>Click here for help on $topic_name</A>\n";
45 # prints image and an alt text
47 sub print_image { # image_file, alt_text
51 print "<IMG SRC=$base_address_for_files/images/$file ALT=\"$alt\" ALIGN=BOTTOMP>\n";
54 # prints the welcome page and login screen.
55 sub html_welcome_page {
56 &begin_html( "Welcome to WebSTUMP" );
60 "Welcome to WebSTUMP, the moderators' front end for <A
61 HREF=http://www.algebra.com/~ichudov/stump>STUMP</A> users -- USENET newsgroup
62 moderators. Only authorized users are allowed to log into this
67 my $motd_file = "$webstump_home/config/motd";
69 if( -f $motd_file && -r $motd_file ){
70 open( MOTD, $motd_file );
71 print "<B>Message of the Day:</B><BR><PRE>\n";
72 print while( <MOTD> );
78 Newsgroups Status:<BR>
81 for( sort @newsgroups_array ) {
84 my $count = &get_article_count( $_ );
86 print " <A HREF=$base_address?action=login_screen\&newsgroup=$_>$_</A>";
87 &print_image( "smiley.gif", "" ) if $count;
91 print "<TD>$count messages in queue<BR></TD>";
92 # print "<TD><A HREF=$base_address?action=init_request_newsgroup_creation\&newsgroup=$_>Request creation</A></TD>\n";
96 print "<HR>Note: click on the newsgroup to login in as moderator.
97 <!-- Click on 'Request Creation' to ask a sysadmin at a specific domain
98 to carry your newsgroup. -->\n<HR>
99 <A HREF=$base_address?action=admin_login>Click here to administer this WebSTUMP installation</A>
104 # prints the login screen for newsgroup.
105 sub html_login_screen {
106 my $newsgroup = $request{'newsgroup'} || &error( "newsgroup not defined" );
108 my $count = &get_article_count( $newsgroup );
112 &begin_html( "$count articles in queue for $newsgroup" );
114 &begin_html( "Empty Queue for $newsgroup" );
118 " Welcome to the Moderation Center for $newsgroup. Please bookmark
122 my $color = "", $end_color = "";
125 $color = "<font color=red>";
126 $end_color = "<font color=black>";
130 "<FORM METHOD=$request_method action=$base_address>
131 <INPUT NAME=action VALUE=moderation_screen TYPE=hidden>
134 &print_image( "new_tiny2.gif", "new" ) if $count;
136 print " articles available)<BR> $end_color
137 Login: <INPUT NAME=moderator VALUE=\"\" SIZE=20>
139 Password: <INPUT NAME=password TYPE=password VALUE=\"\" SIZE=20>
141 <INPUT TYPE=submit VALUE=\"Proceed with Login\">
142 <INPUT TYPE=reset VALUE=\"Reset\">
143 <INPUT NAME=newsgroup VALUE=\"$newsgroup\" TYPE=hidden>
145 Please log into $newsgroup. You can only log in if you know your login id
146 and know the secret password. You should not give your password to any
147 unauthorized user. Your login id and password are NOT case sentitive,
149 for example, \"xyzzy\" and \"XyZZY\" are equally valid.<P>
153 # Log in as \"admin\" if you want to
155 # <LI> edit filtering lists.";
157 # &link_to_help( "filter-lists", "Filter Lists" );
160 # <LI> add/delete users or change their passwords.
161 # <LI> First Time Users: You have to log in as admin and add a moderator user
162 # who will be able to moderate the newsgroup. Then log in again as that
163 # user. If you are a new user, you have to have your admin password assigned to
164 # you by the administrator.
171 # prints the login screen for newsgroup.
172 sub admin_login_screen {
173 &begin_html( "Administrative login" );
177 Attention: this page is only for the maintainer of the whole WebSTUMP
178 installation. Please return to the main page if you are not the maintainer
179 of this installation. <HR>
183 "<FORM METHOD=$request_method action=$base_address>
184 <INPUT NAME=action VALUE=webstump_admin_screen TYPE=hidden>
185 Password: <INPUT NAME=password TYPE=password VALUE=\"\" SIZE=20>
187 <INPUT TYPE=submit VALUE=\"Proceed with Login\">
188 <INPUT TYPE=reset VALUE=\"Reset\">
195 # main moderation page -- old version
196 sub html_moderate_article {
197 my $newsgroup = &required_parameter( 'newsgroup' );
198 my $moderator = $request{'moderator'};
199 my $password = $request{'password'};
200 my $file = shift @_ || &required_parameter('file');
202 &begin_html( "Main Moderation Screen: $newsgroup" );
205 &read_rejection_reasons;
207 my $dir = "$queues_dir/$newsgroup";
209 if( -d "$dir/$file" && open( TEXT_FILES, "$dir/$file/text.files.lst" ) ) {
211 print "<HR>\n" if &print_article_warning( $file );
216 while( $filename = <TEXT_FILES> ) {
217 open( ARTICLE, "$dir/$file/$filename" );
219 $embolden= m/^(?:from|subject)\s*\:/i;
223 $_= "<strong>$_</strong>" if $embolden;
225 $inhead= 0 unless m/\S/;
231 print "\n</PRE>\n\n";
233 &print_images( $newsgroup, "$dir/$file", $file);
236 print "This message ($dir/$file) no longer exists -- maybe it was " .
237 "approved or rejected by another moderator.";
241 <FORM NAME=decision METHOD=$request_method action=$base_address>
245 <INPUT NAME=action VALUE=approval_decision TYPE=hidden>";
246 &html_print_credentials;
247 print "<SELECT NAME=\"decision_$file\">
248 <OPTION VALUE=\"approve\">Approve</OPTION>
249 <OPTION VALUE=\"leave\">Put to back of queue</OPTION>
250 <OPTION VALUE=\"consider\">Back of queue, adding mark requesting further consideration</OPTION>
253 foreach (sort(keys %rejection_reasons)) {
254 print "<OPTION VALUE=\"reject $_\">Reject -- $rejection_reasons{$_}</OPTION>\n";
259 print "</SELECT><BR> Comment: <INPUT NAME=comment VALUE=\"\" SIZE=80><BR>";
262 <INPUT TYPE=radio NAME=poster_decision VALUE=nothing CHECKED>Don't change poster's status</INPUT>
263 <INPUT TYPE=radio NAME=poster_decision VALUE=preapprove
264 >Preapprove poster</INPUT>
265 <INPUT TYPE=radio NAME=poster_decision VALUE=ban
266 ONCLICK=\"alert( 'Banning a poster is a controversial practice'); \"
267 > Ban All Posts by this Person (Careful!)</INPUT>
269 <INPUT TYPE=radio NAME=thread_decision VALUE=nothing CHECKED>Don't change thread's status</INPUT>
270 <!-- <INPUT TYPE=radio NAME=thread_decision VALUE=preapprove>Preapprove thread, by Subject:</INPUT> -->
273 <INPUT TYPE=radio NAME=thread_decision VALUE=ban
274 ONCLICK=\"alert( 'Banning a thread is a controversial practice'); \"
275 >Ban Entire Thread By Subject (Careful!)</INPUT>
276 <INPUT TYPE=radio NAME=thread_decision VALUE=watch>Put Entire thread on a Watch, by Subject:</INPUT>
280 NOTE: Decisions to ban and preapprove posters and threads can be reversed by
281 logging in as \"admin\" and editing respective lists of preapproved
282 and banned threads and posters.
285 &link_to_help( "filter-lists", "automatic filtering and filter lists, blacklisting and preapproved threads." );
287 print "Be really careful about blacklisting of everyone except spammers.</I><BR><BR>
289 <INPUT TYPE=radio NAME=next_screen VALUE=single CHECKED>
290 Review ONE article in next screen
291 <INPUT TYPE=radio NAME=next_screen VALUE=multiple>
292 Review multiple articles in next screen
295 <INPUT TYPE=submit VALUE=\"Submit\">
296 <INPUT TYPE=submit NAME=skip_submit VALUE=\"Skip\">
297 <INPUT TYPE=reset VALUE=\"Reset\">
301 print "<BR><A HREF=$base_address?action=change_password&newsgroup=$newsgroup&" .
302 "moderator=$moderator&password=$password>Change Password</A>";
308 # WebSTUMP administrative screen
309 sub webstump_admin_screen {
311 &verify_admin_password;
313 my $password = $request{'password'};
315 &begin_html( "WebSTUMP Administration" );
317 <FORM METHOD=$request_method action=$base_address>
318 <INPUT NAME=action VALUE=admin_add_newsgroup TYPE=hidden>
319 <INPUT NAME=password VALUE=\"$password\" TYPE=hidden>\n";
324 Create a new newsgroup on the server:<BR>
326 Newsgroup:<BR> <INPUT NAME=newsgroup_name VALUE=\"\" SIZE=50><BR>
327 Address to send approved/rejected messages <BR>
328 <INPUT NAME=newsgroup_approved_address VALUE=\"\" SIZE=30><BR>
329 Admin Password For this group:<BR> <INPUT NAME=newsgroup_password VALUE=\"\" SIZE=10><BR>
330 <INPUT TYPE=submit VALUE=\"Submit\">
331 <INPUT TYPE=reset VALUE=\"Reset\"><HR>
334 print "</FORM>\n\n<PRE>\n";
339 # WebSTUMP "add newsgroup" function
340 sub admin_add_newsgroup {
342 &verify_admin_password;
344 my $newsgroup = &required_parameter( 'newsgroup_name' );
346 $newsgroup =~ s/\///g;
347 $newsgroup = &untaint( $newsgroup );
349 my $address = &required_parameter( 'newsgroup_approved_address' );
350 my $password = &required_parameter( 'newsgroup_password' );
352 &user_error( "Newsgroup $newsgroup already exists" )
353 if defined $newsgroups_index{$newsgroup};
355 &user_error( "Password may only contain letters and digits" )
356 if( ! ($password =~ /^[a-zA-Z0-9]+$/ ) );
358 &begin_html( "WebSTUMP Administration: Newsgroup created" );
362 print "Adding $newsgroup to $webstump_home/config/newsgroups.lst...";
363 mkdir "$webstump_home/queues/$newsgroup", 0755;
366 $dir = "$webstump_home/config/newsgroups/$newsgroup";
368 print "Creating $dir...";
372 print "Creating files in $dir...";
374 &append_to_file( "$dir/address.txt", "$address\n" );
375 &append_to_file( "$dir/moderators", "ADMIN \U$password\n" );
376 &append_to_file( "$dir/rejection-reasons",
377 "offtopic::a blatantly offtopic article, spam
378 harassing::message of harassing content
379 charter::message poorly formatted
392 $web_subdir = pop( @_ );
394 $newsgroup = pop( @_ );
396 opendir( SUBDIR, $subdir );
400 while( $_ = readdir( SUBDIR ) ) {
401 my $file = "$subdir/$_";
402 next if( ! -f $file || ! -r $file );
403 my $extension = $file;
404 $extension =~ s/^.*\.//;
405 $extension = "\L$extension";
407 if( $extension eq "gif" || $extension eq "jpg" || $extension eq "jpeg" ) {
408 print "<CENTER> <IMG SRC=$base_address_for_files/queues/$newsgroup/$web_subdir/$_></CENTER><HR>\n";
412 $filename =~ s/^.*\///;
413 next if $filename eq "skeleton.skeleton"
414 || $filename eq "headers.txt"
415 || $filename eq "full_message.txt"
416 || $filename eq "text.files.lst"
417 || $filename eq "stump-prolog.txt"
418 || $filename eq "stump-warning.txt"
419 || $filename =~ /msg-.*\.doc/;
421 &print_image( "no_image.gif", "security warning" );
422 print "<B>Non-image attachment:</B><CODE>$filename</CODE> NOT SHOWN for security reasons.<BR>\n";
428 # prints warning if there is warning stored about the article
429 sub print_article_warning { # short-subdir
430 my $file = pop( @_ );
432 my $warning_file = &article_file_name( $file ) . "/stump-warning.txt";
434 if( -r $warning_file ) {
435 open( WARNING, $warning_file );
436 while ($warning = <WARNING>) {
437 next unless $warning =~ m/\S/;
438 $warning =~ s/\&/&/g;
439 $warning =~ s/</</g;
440 $warning =~ s/>/>/g;
441 &print_image( "star.gif", "warning" );
442 print "<FONT COLOR=red>$warning</FONT><br>\n";
451 sub get_queue_list ($) {
452 my ($newsgroup) = @_;
453 my $dir = "$queues_dir/$newsgroup";
456 opendir(QUEUED, $dir) or &error("could not open directory $dir");
460 my $subdir= scalar readdir(QUEUED);
461 last unless defined $subdir;
463 my $subpath= "$dir/$subdir";
464 next if $subdir =~ /^\.+/;
465 next unless -d $subpath;
467 if (!stat "$subpath/stump-warning.txt") {
468 $!==&ENOENT or die "$subpath $!";
471 $sortkey= (stat _)[9];
473 $sortkeys{$subdir}= $sortkey;
476 my @articles= sort { $sortkeys{$a} <=> $sortkeys{$b} } keys %sortkeys;
477 return ($dir, @articles);
480 # main moderation page
481 sub html_moderation_screen {
482 my $newsgroup = &required_parameter( 'newsgroup' );
483 my $moderator = $request{'moderator'};
484 my $password = $request{'password'};
487 if( $request{'next_screen'} eq 'single' ) {
488 # we show a single article if the user so requested.
489 # just get the first article from the queue if any, otherwise show
490 # an empty main screen.
492 my ($dir, @articles)= get_queue_list($newsgroup);
495 for ($i=0; $i<@articles; $i++) {
496 my $subdir= shift @articles;
497 push @articles, $subdir;
498 last if $request{"decision_$subdir"};
501 while( $subdir = shift @articles ) {
502 if( -d "$dir/$subdir" && !($subdir =~ /^\.+/)
503 && open( PROLOG, "$dir/$subdir/stump-prolog.txt" ) ) {
504 &html_moderate_article( $subdir );
509 # otherwise just show the moderator an empty main screen.
512 &begin_html( "Main Moderation Screen: $newsgroup" );
513 print "Welcome to the main moderation screen. Its main purpose is to
514 help you process most messages extremely quickly. For every message, it
515 presents you who sent it, as well as the first three non-blank lines.
516 For those messages where the decision is obvious, simply select your
517 decision (approve/reject etc) and click submit. For those messages which
518 you would like to review in more details, do not select anything and
519 use Review/Comment function from this screen or from a subsequent screen.
520 Remember that if you do not make any decision, the article would stay in the
523 &read_rejection_reasons;
525 my ($dir, @articles)= get_queue_list($newsgroup);
528 <FORM METHOD=$request_method action=$base_address>
529 <INPUT NAME=action VALUE=approval_decision TYPE=hidden>";
530 &html_print_credentials;
532 my $file, $subject = "No Subject", $from = "From nobody";
533 my $form_not_empty = "";
534 my $article_count = 0;
536 while( ($subdir = shift @articles) && $article_count++ < 40 ) {
538 if( -d "$dir/$subdir" && !($subdir =~ /^\.+/)
539 && open( PROLOG, "$dir/$subdir/stump-prolog.txt" ) ) {
542 if( /^Real-Subject: /i ) {
546 s/^Real-Subject: //g;
547 $subject = substr( $_, 0, 50 );
548 } elsif( /^From: /i ){
552 $from = substr( $_, 0, 50 );
558 print "<HR><B>$from: $subject</B>(";
559 print "<A HREF=$base_address?action=moderate_article&newsgroup=$newsgroup&" .
560 "moderator=$moderator&password=$password&file=$subdir>Review/Comment/Preapprove</A>)<BR>\n";
561 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=approve>Approve\n";
562 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=skip>Leave\n";
563 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=leave>Back of queue\n";
564 foreach (@short_rejection_reasons) {
565 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=\"reject $_\">Reject \u$_\n";
570 &print_article_warning( $file );
576 while( ($_ = <PROLOG>) && $i < 5 ) {
583 print "] " . substr( $_, 0, 75 ) . "\n";
589 $form_not_empty = "yes";
591 $article_count += &print_images( $newsgroup, "$dir/$subdir", $subdir );
595 if( $form_not_empty ) {
596 print "<HR> <INPUT TYPE=submit VALUE=Submit>
597 <INPUT TYPE=reset VALUE=Reset>
602 No articles present in the queue
603 <INPUT TYPE=submit VALUE=Refresh>
607 print "<A HREF=$base_address?action=change_password&newsgroup=$newsgroup&" .
608 "moderator=$moderator&password=$password>Change Password</A>";
612 print "<FORM METHOD=$request_method action=$base_address>";
613 &html_print_credentials;
614 print "<INPUT NAME=action VALUE=moderator_admin TYPE=hidden>
615 <INPUT TYPE=submit VALUE=\"Manage pass/grey/block-lists\">
621 # prints hidden fields -- credentials
622 sub html_print_credentials {
623 my $newsgroup = $request{'newsgroup'};
624 my $moderator = $request{'moderator'};
625 my $password = $request{'password'};
628 <INPUT NAME=newsgroup VALUE=\"$newsgroup\" TYPE=hidden>
629 <INPUT NAME=moderator VALUE=\"$moderator\" TYPE=hidden>
630 <INPUT NAME=password VALUE=\"$password\" TYPE=hidden>\n";
633 # newsgroup admin page
634 sub html_newsgroup_management {
635 &begin_html( "Administer $request{'newsgroup'}" );
637 print "All usernames and passwords are not case sensitive.\n";
638 print "<HR>Use this form to add new moderators or change passwords:<BR>
639 <FORM METHOD=$request_method action=$base_address>
640 <INPUT NAME=action VALUE=add_user TYPE=hidden>";
641 &html_print_credentials;
643 Username: <INPUT NAME=user VALUE=\"\" SIZE=20>
645 Password: <INPUT NAME=new_password VALUE=\"\" SIZE=20>
647 <INPUT TYPE=submit VALUE=\"Add/Change\">
648 <INPUT TYPE=reset VALUE=Reset>
652 print "<HR>Use this form to delete moderators:<BR>
653 <FORM METHOD=$request_method action=$base_address>
654 <INPUT NAME=action VALUE=delete_user TYPE=hidden>";
655 &html_print_credentials;
657 Username: <INPUT NAME=user VALUE=\"\" SIZE=20>
659 <INPUT TYPE=submit VALUE=\"Delete Moderator\">
660 <INPUT TYPE=reset VALUE=Reset>
663 <FORM METHOD=$request_method action=$base_address>
664 <INPUT NAME=action VALUE=edit_list TYPE=hidden>";
665 &html_print_credentials;
667 Configuration List: <SELECT NAME=list_to_edit>
669 <OPTION VALUE=good.posters.list>Good Posters List
670 <OPTION VALUE=watch.posters.list>Suspicious Posters List
671 <OPTION VALUE=bad.posters.list>Banned Posters List
672 <OPTION VALUE=good.subjects.list>Good Subjects List
673 <OPTION VALUE=watch.subjects.list>Suspicious Subjects List
674 <OPTION VALUE=bad.subjects.list>Banned Subjects List
675 <OPTION VALUE=watch.words.list>Suspicious Words List
676 <OPTION VALUE=bad.words.list>Banned Words List
679 <INPUT TYPE=submit VALUE=\"Edit\">
680 <INPUT TYPE=reset VALUE=Reset>";
682 &link_to_help( "filter-lists", "filtering lists" );
687 List of current moderators:<P>
691 foreach (keys %moderators) {
702 sub edit_configuration_list {
704 my $list_to_edit = &required_parameter( 'list_to_edit' );
706 $list_to_edit = &check_config_list( $list_to_edit );
708 my $list_file = &full_config_file_name( $list_to_edit );
710 my $list_content = "";
712 if( open( LIST, $list_file ) ) {
713 $list_content .= $_ while( <LIST> );
717 $list_content =~ s/\&/&/g;
718 $list_content =~ s/</</g;
719 $list_content =~ s/>/>/g;
721 &begin_html( "Edit $list_to_edit" );
724 " <FORM METHOD=$request_method action=$base_address>
725 <INPUT NAME=action VALUE=set_config_list TYPE=hidden>
726 <INPUT NAME=list_to_edit VALUE=$list_to_edit TYPE=hidden>";
727 &html_print_credentials;
728 &link_to_help( $list_to_edit, "$list_to_edit" );
731 <TEXTAREA NAME=list rows=20 COLS=50>
732 $list_content</TEXTAREA>
735 <INPUT TYPE=submit VALUE=\"Set\">
742 # password change page
743 sub html_change_password{
744 &begin_html( "Change Password" );
746 print "All usernames and passwords are not case sensitive.\n";
747 print "<HR>Use this form to change your password:<BR>
748 <FORM METHOD=$request_method action=$base_address>
749 <INPUT NAME=action VALUE=validate_change_password TYPE=hidden>";
750 &html_print_credentials;
753 New Password: <INPUT NAME=new_password VALUE=\"\" SIZE=20>
755 <INPUT TYPE=submit VALUE=Submit>
756 <INPUT TYPE=reset VALUE=Reset>
764 # newsgroup creation form
765 sub init_request_newsgroup_creation{
766 my $newsgroup = &required_parameter( 'newsgroup' );
768 &begin_html( "Request Creation of $newsgroup" );
770 print "This page helps you ask the system administrator of your domain
771 to create <B>$newsgroup</B> on your server. Type in your domain name and
772 click SUBMIT. An email will be sent to news\@domain and usenet\@domain
773 and postmaster\@domain
774 asking them to create your newsgroup. Please do NOT abuse this system.
775 NOTE: You can give the URL of this page to your group readers so that
776 they could request creation of their newsgroups by themselves.\n";
779 <FORM METHOD=$request_method action=$base_address>
780 <INPUT NAME=action VALUE=complete_newsgroup_creation_request TYPE=hidden>\n";
781 &html_print_credentials;
784 Domain Name ONLY: <INPUT NAME=domain_name VALUE=\"\" SIZE=40>
786 <INPUT TYPE=submit VALUE=Submit>
787 <INPUT TYPE=reset VALUE=Reset>
795 # newsgroup creation completion
796 sub complete_newsgroup_creation_request{
797 my $newsgroup = &required_parameter( 'newsgroup' );
798 my $domain_name = &required_parameter( 'domain_name' );
800 if( !($domain_name =~ /(^[a-zA-Z0-9\.-_]+$)/) ) {
801 &user_error( "invalid domain name" );
807 my $request = "To: news\@$domain_name, usenet\@$domain_name, postmaster\@$domain_name
808 Subject: Please create $newsgroup (Moderated)
809 From: devnull\@algebra.com ($newsgroup Moderator)
810 Organization: stump.algebra.com
812 Dear News Administrator:
814 A user of $domain_name has requested that you create a newsgroup
816 $newsgroup (Moderated)
818 on your server. $newsgroup
819 is a legitimately created moderated newsgroup that is available worldwide.
821 Thank you very much for your help and cooperation.
825 - Moderator of $newsgroup.
829 &email_message( $request, "news\@$domain_name" );
830 &email_message( $request, "usenet\@$domain_name" );
831 &email_message( $request, "postmaster\@$domain_name" );
833 &begin_html( "Request to create $newsgroup sent" );
835 print "The following request has been sent:<HR><PRE>\n";
837 print "$request</PRE>\n";
844 my $topic_name = &required_parameter( "topic" );
846 $topic_name =~ s/\///g;
847 $topic_name =~ s/\.\.//g;
848 $topic_name = &untaint( $topic_name );
850 my $file = "$webstump_home/doc/help/$topic_name.html";
852 &error( "Topic $topic_name not found in $file." )
855 open( FILE, "$file" );
857 $help .= $_ while( <FILE> );
860 $help =~ s/##/$base_address?action=help&topic=/g;
862 &begin_html( "$topic_name" );