[system] / trunk / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / SendMail.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4452 - (download) (as text) (annotate)
Wed Sep 6 18:18:30 2006 UTC (6 years, 8 months ago) by sh002i
File size: 39173 byte(s)
filter out students without "include_in_email" behavior - bug #938.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm,v 1.50 2006/07/16 02:40:41 gage Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::ContentGenerator::Instructor::SendMail;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::SendMail - Entry point for User-specific data editing
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 #use CGI qw(-nosticky );
   29 use WeBWorK::CGI;
   30 use HTML::Entities;
   31 use Mail::Sender;
   32 use Text::Wrap qw(wrap);
   33 use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/;
   34 use WeBWorK::Utils::FilterRecords qw/filterRecords/;
   35 
   36 #my $REFRESH_RESIZE_BUTTON = "Set preview to: ";  # handle submit value idiocy
   37 my $UPDATE_SETTINGS_BUTTON = "Update settings and refresh page"; # handle submit value idiocy
   38 sub initialize {
   39   my ($self) = @_;
   40   my $r      = $self->r;
   41   my $db     = $r->db;
   42   my $ce     = $r->ce;
   43   my $authz  = $r->authz;
   44   my $user   = $r->param('user');
   45 
   46   my @selected_filters;
   47   if (defined ($r->param('classList!filter'))){ @selected_filters = $r->param('classList!filter');}
   48   else {@selected_filters = ("all");}
   49 
   50 
   51   # Check permissions
   52   return unless $authz->hasPermissions($user, "access_instructor_tools");
   53   return unless $authz->hasPermissions($user, "send_mail");
   54 
   55 #############################################################################################
   56 # gather directory data
   57 #############################################################################################
   58   my $emailDirectory    =    $ce->{courseDirs}->{email};
   59   my $scoringDirectory  =    $ce->{courseDirs}->{scoring};
   60   my $templateDirectory =    $ce->{courseDirs}->{templates};
   61 
   62   my $action            =    $r->param('action') ;
   63   my $openfilename      =    $r->param('openfilename');
   64   my $savefilename      =    $r->param('savefilename');
   65 
   66 
   67   #FIXME  get these values from global course environment (see subroutines as well)
   68   my $default_msg_file       =    'default.msg';
   69   my $old_default_msg_file   =    'old_default.msg';
   70 
   71 
   72   #  get user record
   73   my $ur = $self->{db}->getUser($user);
   74 
   75   # store data
   76   $self->{defaultFrom}            =   $ur->email_address . " (".$ur->first_name." ".$ur->last_name.")";
   77   $self->{defaultReply}           =   $ur->email_address;
   78   $self->{defaultSubject}         =   $self->r->urlpath->arg("courseID") . " notice";
   79 
   80   $self->{rows}                   =   (defined($r->param('rows'))) ? $r->param('rows') : $ce->{mail}->{editor_window_rows};
   81   $self->{columns}                =   (defined($r->param('columns'))) ? $r->param('columns') : $ce->{mail}->{editor_window_columns};
   82   $self->{default_msg_file}     =   $default_msg_file;
   83   $self->{old_default_msg_file}   =   $old_default_msg_file;
   84   $self->{merge_file}             =   (defined($r->param('merge_file'  )))    ? $r->param('merge_file')   : 'None';
   85   #$self->{preview_user}           =   (defined($r->param('preview_user')))    ? $r->param('preview_user') : $user;
   86   # an expermiment -- share the scrolling list for preivew and sendTo actions.
   87   my @classList                   =   (defined($r->param('classList')))    ? $r->param('classList') : ($user);
   88   $self->{preview_user}           =   $classList[0] || $user;
   89 
   90 #############################################################################################
   91 # gather database data
   92 #############################################################################################
   93   # FIXME  this might be better done in body? We don't always need all of this data. or do we?
   94   my @users =  $db->listUsers;
   95   my @Users = $db->getUsers(@users);
   96   # filter out users who don't get included in email (fixes bug #938)
   97   @Users = grep { $ce->status_abbrev_has_behavior($_->status, "include_in_email") } @Users;
   98   my @user_records = ();
   99 
  100 ## Mark's code to prefilter userlist
  101 
  102 
  103   my (@viewable_sections,@viewable_recitations);
  104 
  105   if (defined @{$ce->{viewable_sections}->{$user}})
  106     {@viewable_sections = @{$ce->{viewable_sections}->{$user}};}
  107   if (defined @{$ce->{viewable_recitations}->{$user}})
  108     {@viewable_recitations = @{$ce->{viewable_recitations}->{$user}};}
  109 
  110   if (@viewable_sections or @viewable_recitations){
  111     foreach my $student (@Users){
  112       my $keep = 0;
  113       foreach my $sec (@viewable_sections){
  114         if ($student->section() eq $sec){$keep = 1;}
  115       }
  116       foreach my $rec (@viewable_recitations){
  117         if ($student->recitation() eq $rec){$keep = 1;}
  118       }
  119       if ($keep) {push @user_records, $student;}
  120     }
  121   }
  122   else {@user_records = @Users;}
  123 
  124 ## End Mark's code
  125 
  126 # foreach my $userName (@users) {
  127 #   my $userRecord = $db->getUser($userName); # checked
  128 #   die "record for user $userName not found" unless $userRecord;
  129 #   push(@user_records, $userRecord);
  130 # }
  131   ###########################
  132   # Sort the users for presentation in the select list
  133   ###########################
  134 # if (defined $r->param("sort_by") ) {
  135 #   my $sort_method = $r->param("sort_by");
  136 #   if ($sort_method eq 'section') {
  137 #     @user_records = sort { (lc($a->section) cmp lc($b->section)) || (lc($a->last_name) cmp lc($b->last_name)) } @user_records;
  138 #   } elsif ($sort_method eq 'recitation') {
  139 #     @user_records = sort { (lc($a->recitation) cmp lc($b->recitation)) || (lc($a->last_name) cmp lc($b->last_name)) } @user_records;
  140 #   } elsif ($sort_method eq 'alphabetical') {
  141 #     @user_records = sort {  (lc($a->last_name) cmp lc($b->last_name)) } @user_records;
  142 #   } elsif ($sort_method eq 'id' )          {
  143 #       @user_records = sort { $a->user_id cmp $b->user_id }  @user_records;
  144 #   }
  145 # } else {
  146 #   @user_records = sort { $a->user_id cmp $b->user_id }  @user_records;
  147 # }
  148 
  149 
  150   # replace the user names by a sorted version.
  151   @users                         =  map {$_->user_id} @user_records;
  152   # store data
  153   $self->{ra_users}              =   \@users;
  154   $self->{ra_user_records}       =   \@user_records;
  155 
  156 #############################################################################################
  157 # gather list of recipients
  158 #############################################################################################
  159   my @send_to                    =   ();
  160   #FIXME  this (radio) is a lousy name
  161   my $recipients                 = $r->param('radio');
  162   if (defined($recipients) and $recipients eq 'all_students') {  #only active students #FIXME status check??
  163 
  164 ## Add code so that only people who pass the current filters are added to our list of recipients.
  165 # @user_records = filterRecords({filter=\@selected_filters},@user_records);
  166 #  I wasn't able to make this work
  167 #  I edited the selection button to make that clear.
  168 #
  169 
  170     foreach my $ur (@user_records) {
  171       push(@send_to,$ur->user_id)
  172         if $ce->status_abbrev_has_behavior($ur->status, "include_in_email")
  173           and not $ur->user_id =~ /practice/;
  174     }
  175   } elsif (defined($recipients) and $recipients eq 'studentID' ) {
  176     @send_to                   = $r->param('classList');
  177   } else {
  178     # no recipients have been defined -- probably the first time on the page
  179   }
  180   $self->{ra_send_to}               = \@send_to;
  181 #################################################################
  182 # Check the validity of the input file name
  183 #################################################################
  184   my $input_file = '';
  185   #make sure an input message file was submitted and exists
  186   #else use the default message
  187   if ( defined($openfilename) ) {
  188     if ( -e "${emailDirectory}/$openfilename") {
  189       if ( -R "${emailDirectory}/$openfilename") {
  190         $input_file = $openfilename;
  191       } else {
  192         $self->addbadmessage(CGI::p(join("",
  193           "The file ${emailDirectory}/$openfilename is not readable by the webserver.",CGI::br(),
  194           "Check that it's permissions are set correctly.",
  195         )));
  196       }
  197     } else {
  198       $input_file = $default_msg_file;
  199       $self->addbadmessage(CGI::p(join("",
  200           "The file ${emailDirectory}/$openfilename cannot be found.",CGI::br(),
  201           "Check whether it exists and whether the directory $emailDirectory can be read by the webserver.",CGI::br(),
  202           "Using contents of the default message $default_msg_file instead.",
  203       )));
  204     }
  205   } else {
  206     $input_file     = $default_msg_file;
  207   }
  208   $self->{input_file} =$input_file;
  209 
  210 #################################################################
  211 # Determine the file name to save message into
  212 #################################################################
  213   my $output_file      = 'FIXME no output file specified';
  214   if (defined($action) and $action eq 'Save as Default') {
  215     $output_file  = $default_msg_file;
  216   } elsif ( defined($action) and ($action =~/save/i)) {
  217     if (defined($savefilename) and $savefilename ) {
  218       $output_file  = $savefilename;
  219     } else {
  220       $self->addbadmessage(CGI::p("No filename was specified for saving!  The message was not saved."));
  221     }
  222   } elsif ( defined($input_file) ) {
  223     $output_file  = $input_file;
  224   }
  225 
  226   #################################################################
  227   # Sanity check on save file name
  228   #################################################################
  229 
  230   if ($output_file =~ /^[~.]/ || $output_file =~ /\.\./) {
  231     $self->addbadmessage(CGI::p("For security reasons, you cannot specify a message file from a directory",
  232             "higher than the email directory (you can't use ../blah/blah for example). ",
  233             "Please specify a different file or move the needed file to the email directory",));
  234   }
  235   unless ($output_file =~ m|\.msg$| ) {
  236     $self->addbadmessage(CGI::p("Invalid file name.",
  237                             "The file name \"$output_file\" does not have a \".msg\" extension",
  238                 "All email file names must end in the extension \".msg\"",
  239                 "choose a file name with a \".msg\" extension.",
  240                 "The message was not saved.",));
  241   }
  242 
  243   $self->{output_file} = $output_file;  # this is ok.  It will be put back in the text input box for re-editing.
  244 
  245 
  246 #############################################################################################
  247 # Determine input source
  248 #############################################################################################
  249   #warn "Action = $action";
  250   my $input_source;
  251   if ($action){
  252     $input_source =  ( defined( $r->param('body') ) and $action ne 'Open' ) ? 'form' : 'file';}
  253   else { $input_source = ( defined($r->param('body')) ) ? 'form' : 'file';}
  254 
  255 #############################################################################################
  256 # Get inputs
  257 #############################################################################################
  258   my($from, $replyTo, $r_text, $subject);
  259   if ($input_source eq 'file') {
  260 
  261     ($from, $replyTo,$subject,$r_text) = $self->read_input_file("$emailDirectory/$input_file");
  262 
  263 
  264   } elsif ($input_source eq 'form') {
  265     # read info from the form
  266     # bail if there is no message body
  267 
  268     $from              =    $r->param('from');
  269     $replyTo           =    $r->param('replyTo');
  270     $subject           =    $r->param('subject');
  271     my $body           =    $r->param('body');
  272     # Sanity check: body must contain non-white space
  273     $self->addbadmessage(CGI::p('You didn\'t enter any message.')) unless ($r->param('body') =~ /\S/);
  274     $r_text               =    \$body;
  275 
  276   }
  277   # store data
  278   $self->{from}                   =    $from;
  279   $self->{replyTo}                =    $replyTo;
  280   $self->{subject}                =    $subject;
  281   $self->{r_text}                 =    $r_text;
  282 
  283 
  284 
  285 ###################################################################################
  286 #Determine the appropriate script action from the buttons
  287 ###################################################################################
  288 #     first time actions
  289 #          open new file
  290 #          open default file
  291 #     choose merge file actions
  292 #          chose merge button
  293 #     option actions
  294 #       'reset rows'
  295 
  296 #     save actions
  297 #   "save" button
  298 #   "save as" button
  299 #   "save as default" button
  300 #     preview actions
  301 #   'preview' button
  302 #     email actions
  303 #   'entire class'
  304 #   'selected studentIDs'
  305 #     error actions (various)
  306 
  307 
  308 #############################################################################################
  309 # if no form is submitted, gather data needed to produce the mail form and return
  310 #############################################################################################
  311   my $to                =    $r->param('To');
  312   my $script_action     = '';
  313 
  314 
  315   if(not defined($action) or $action eq 'Open'
  316      or $action eq $UPDATE_SETTINGS_BUTTON ){
  317 
  318     return '';
  319   }
  320 
  321 
  322 
  323 
  324 
  325 #############################################################################################
  326 # If form is submitted deal with filled out forms
  327 # and various actions resulting from different buttons
  328 #############################################################################################
  329 
  330 
  331   if ($action eq 'Save' or $action eq 'Save as:' or $action eq 'Save as Default') {
  332 
  333 #   warn "FIXME Saving files  action = $action  outputFileName=$output_file";
  334 
  335     #################################################################
  336     # construct message body
  337     #################################################################
  338     my $temp_body = ${ $r_text };
  339     $temp_body =~ s/\r\n/\n/g;
  340     $temp_body = join("",
  341            "From: $from \nReply-To: $replyTo\n" ,
  342            "Subject: $subject\n" ,
  343            "Message: \n    $temp_body");
  344 #   warn "FIXME from $from | subject $subject |reply $replyTo|msg $temp_body";
  345     #################################################################
  346     # overwrite protection
  347     #################################################################
  348     if ($action eq 'Save as:' and -e "$emailDirectory/$output_file") {
  349       $self->addbadmessage(CGI::p("The file $emailDirectory/$output_file already exists and cannot be overwritten",
  350                                "The message was not saved"));
  351       return;
  352     }
  353 
  354     #################################################################
  355       # Back up existing file?
  356       #################################################################
  357       if ($action eq 'Save as Default' and -e "$emailDirectory/$default_msg_file") {
  358         rename("$emailDirectory/$default_msg_file","$emailDirectory/$old_default_msg_file") or
  359                die "Can't rename $emailDirectory/$default_msg_file to $emailDirectory/$old_default_msg_file ",
  360                    "Check permissions for webserver on directory $emailDirectory. $!";
  361         $self->addgoodmessage(CGI::p("Backup file <code>$emailDirectory/$old_default_msg_file</code> created." . CGI::br()));
  362       }
  363       #################################################################
  364       # Save the message
  365     #################################################################
  366     $self->saveProblem($temp_body, "${emailDirectory}/$output_file" ) unless ($output_file =~ /^[~.]/ || $output_file =~ /\.\./ || not $output_file =~ m|\.msg$|);
  367     unless ( $self->{submit_message} or not -w "${emailDirectory}/$output_file" )  {  # if there are no errors report success
  368       $self->addgoodmessage(CGI::p("Message saved to file <code>${emailDirectory}/$output_file</code>."));
  369     }
  370 
  371   } elsif ($action eq 'Preview message') {
  372     $self->{response}         = 'preview';
  373 
  374   } elsif ($action eq 'Send Email') {
  375     $self->{response}         = 'send_email';
  376 
  377       # check that recipients have been selected.
  378     my @recipients            = @{$self->{ra_send_to}};
  379     $self->addbadmessage(CGI::p("No recipients selected ")) unless @recipients;
  380     #  get merge file
  381     my $merge_file      = ( defined($self->{merge_file}) ) ? $self->{merge_file} : 'None';
  382     my $delimiter       = ',';
  383     my $rh_merge_data   = $self->read_scoring_file("$merge_file", "$delimiter");
  384     unless (ref($rh_merge_data) ) {
  385       $self->addbadmessage(CGI::p("No merge data file"));
  386       $self->addbadmessage(CGI::p("Can't read merge file $merge_file. No message sent"));
  387       return;
  388     } ;
  389     if (@recipients) {
  390       $self->{rh_merge_data} = $rh_merge_data;
  391       $self->{smtpServer}    = $ce->{mail}->{smtpServer};
  392       my $post_connection_action = sub {
  393         my $r = shift;
  394         my $result_message = $self->mail_message_to_recipients();
  395         $self->email_notification($result_message);
  396       };
  397       $r->post_connection($post_connection_action) ;
  398     }
  399 #     foreach my $recipient (@recipients) {
  400 #       #warn "FIXME sending email to $recipient";
  401 #       my $ur      = $self->{db}->getUser($recipient); #checked
  402 #       die "record for user $recipient not found" unless $ur;
  403 #       unless ($ur->email_address) {
  404 #         $self->addbadmessage(CGI::p("user $recipient does not have an email address -- skipping"));
  405 #         next;
  406 #       }
  407 #       my ($msg, $preview_header);
  408 #       eval{ ($msg,$preview_header) = $self->process_message($ur,$rh_merge_data); };
  409 #       $self->addbadmessage(CGI::p("There were errors in processing user $ur, merge file $merge_file. $@")) if $@;
  410 #       my $mailer = Mail::Sender->new({
  411 #         from    =>   $from,
  412 #         to      =>   $ur->email_address,
  413 #         smtp    =>   $ce->{mail}->{smtpServer},
  414 #         subject =>   $subject,
  415 #         headers =>   "X-Remote-Host: ".$r->get_remote_host(),
  416 #       });
  417 #       unless (ref $mailer) {
  418 #         $self->addbadmessage(CGI::p("Failed to create a mailer for user $recipient: $Mail::Sender::Error"));
  419 #         next;
  420 #       }
  421 #       unless (ref $mailer->Open()) {
  422 #         $self->addbadmessage(CGI::p("Failed to open the mailer for user $recipient: $Mail::Sender::Error"));
  423 #         next;
  424 #       }
  425 #       my $MAIL = $mailer->GetHandle() or $self->addbadmessage(CGI::p("Couldn't get handle"));
  426 #       print $MAIL  $msg || $self->addbadmessage(CGI::p("Couldn't print to $MAIL"));
  427 #       close $MAIL || $self->addbadmessage(CGI::p("Couldn't close $MAIL"));
  428 #         #warn "FIXME mailed to ", $ur->email_address, "from $from subject $subject";
  429 #
  430 #     }
  431 
  432   } else {
  433     $self->addbadmessage(CGI::p("Didn't recognize button $action"));
  434   }
  435 
  436 
  437 
  438 }  #end initialize
  439 
  440 
  441 
  442 
  443 
  444 sub body {
  445   my ($self)          = @_;
  446   my $r               = $self->r;
  447   my $urlpath         = $r->urlpath;
  448   my $authz           = $r->authz;
  449   my $setID           = $urlpath->arg("setID");
  450   my $response        = (defined($self->{response}))? $self->{response} : '';
  451   my $user            = $r->param('user');
  452 
  453   # Check permissions
  454   return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to access instructor tools"))
  455     unless $authz->hasPermissions($user, "access_instructor_tools");
  456 
  457   return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to send mail to students"))
  458     unless $authz->hasPermissions($user, "send_mail");
  459 
  460   if ($response eq 'preview') {
  461     $self->print_preview($setID);
  462   } elsif (($response eq 'send_email')){
  463     my $message = CGI::i("Email is being sent to ".  scalar(@{$self->{ra_send_to}})." recipients. You will be notified"
  464                  ." by email when the task is completed.  This may take several minutes if the class is large."
  465     );
  466     $self->addgoodmessage($message);
  467     $self->{message} .= $message;
  468 
  469     $self->print_form($setID);
  470   } else {
  471     $self->print_form($setID);
  472   }
  473 
  474 }
  475 sub print_preview {
  476   my ($self)          = @_;
  477   my $r               = $self->r;
  478   my $urlpath         = $r->urlpath;
  479   my $setID           = $urlpath->arg("setID");
  480 
  481   #  get preview user
  482   my $ur      = $r->db->getUser($self->{preview_user}); #checked
  483   die "record for preview user ".$self->{preview_user}. " not found." unless $ur;
  484 
  485   #  get merge file
  486   my $merge_file      = ( defined($self->{merge_file}) ) ? $self->{merge_file} : 'None';
  487   my $delimiter       = ',';
  488   my $rh_merge_data   = $self->read_scoring_file("$merge_file", "$delimiter");
  489 
  490   my ($msg, $preview_header) = $self->process_message($ur,$rh_merge_data);
  491 
  492   my $recipients  = join(" ",@{$self->{ra_send_to} });
  493   my $errorMessage =  defined($self->{submit_message}) ?  CGI::i($self->{submit_message} ) : '' ;
  494   $msg = join("",
  495      $errorMessage,
  496      $preview_header,
  497      "To: "             , $ur->email_address,"\n",
  498        "From: "           , $self->{from} , "\n" ,
  499        "Reply-To: "       , $self->{replyTo} , "\n" ,
  500        "Subject:  "       , $self->{subject} , "\n" ,"\n" ,
  501      $msg , "\n"
  502   );
  503 
  504   return join("", '<pre>',$msg,"\n","\n",
  505            '</pre>',
  506            CGI::p('Use browser back button to return from preview mode'),
  507            CGI::h3('Emails to be sent to the following:'),
  508            $recipients, "\n",
  509 
  510   );
  511 
  512 }
  513 sub print_form {
  514   my ($self)          = @_;
  515   my $r               = $self->r;
  516   my $urlpath         = $r->urlpath;
  517   my $authz           = $r->authz;
  518   my $db              = $r->db;
  519   my $ce              = $r->ce;
  520   my $courseName      = $urlpath->arg("courseID");
  521   my $setID           = $urlpath->arg("setID");
  522   my $user            = $r->param('user');
  523 
  524   my $root            = $ce->{webworkURLs}->{root};
  525   my $sendMailPage    = $urlpath->newFromModule($urlpath->module,courseID=>$courseName);
  526   my $sendMailURL     = $self->systemLink($sendMailPage, authen => 0);
  527 
  528         return CGI::em("You are not authorized to access the Instructor tools.") unless $authz->hasPermissions($user, "access_instructor_tools");
  529 
  530   my $userTemplate = $db->newUser;
  531   my $permissionLevelTemplate = $db->newPermissionLevel;
  532 
  533   # This code will require changing if the permission and user tables ever have different keys.
  534   my @users                 = sort @{ $self->{ra_users} };
  535   my $ra_user_records       = $self->{ra_user_records};
  536   my %classlistLabels       = ();#  %$hr_classlistLabels;
  537   foreach my $ur (@{ $ra_user_records }) {
  538     $classlistLabels{$ur->user_id} = $ur->user_id.': '.$ur->last_name. ', '. $ur->first_name.' -- '.$ur->section." / ".$ur->recitation;
  539   }
  540 
  541 ## Mark edit define scrolling list
  542   my $scrolling_user_list = scrollingRecordList({
  543     name => "classList",      ## changed from classList to action
  544     request => $r,
  545     default_sort => "lnfn",
  546     default_format => "lnfn_uid",
  547     default_filters => ["all"],
  548     size => 5,
  549     multiple => 1,
  550     refresh_button_name =>'Update settings and refresh page',
  551   }, @{$ra_user_records});
  552 
  553 ##############################################################################################################
  554 
  555 
  556   my $from            = $self->{from};
  557   my $subject         = $self->{subject};
  558   my $replyTo         = $self->{replyTo};
  559   my $columns         = $self->{columns};
  560   my $rows            = $self->{rows};
  561   my $text            = defined($self->{r_text}) ? ${ $self->{r_text} }: 'FIXME no text was produced by initialization!!';
  562   my $input_file      = $self->{input_file};
  563   my $output_file     = $self->{output_file};
  564   my @sorted_messages = $self->get_message_file_names;
  565   my @sorted_merge_files = $self->get_merge_file_names;
  566   my $merge_file      = ( defined($self->{merge_file}) ) ? $self->{merge_file} : 'None';
  567   my $delimiter       = ',';
  568   my $rh_merge_data   = $self->read_scoring_file("$merge_file", "$delimiter");
  569   my @merge_keys      = keys %$rh_merge_data;
  570   my $preview_user    = $self->{preview_user};
  571   my $preview_record   = $db->getUser($preview_user); # checked
  572   die "record for preview user ".$self->{preview_user}. " not found." unless $preview_record;
  573 
  574 
  575 #############################################################################################
  576 
  577   print CGI::start_form({method=>"post", action=>$sendMailURL});
  578   print $self->hidden_authen_fields();
  579 #############################################################################################
  580 # begin upper table
  581 #############################################################################################
  582 
  583     print CGI::start_table({-border=>'2', -cellpadding=>'4'});
  584   print CGI::Tr({-align=>'left',-valign=>'top'},
  585 #############################################################################################
  586 # first column
  587 #############################################################################################
  588 
  589        CGI::td({},
  590            CGI::strong("Message file: "), $input_file,"\n",CGI::br(),
  591          CGI::submit(-name=>'action', -value=>'Open'), '&nbsp;&nbsp;&nbsp;&nbsp;',"\n",
  592          CGI::popup_menu(-name=>'openfilename',
  593                          -values=>\@sorted_messages,
  594                          -default=>$input_file
  595          ),
  596          "\n",CGI::br(),
  597          CGI::strong("Save file to: "), $output_file,
  598          "\n",CGI::br(),
  599          CGI::strong('Merge file: '), $merge_file,
  600          CGI::br(),
  601          CGI::popup_menu(-name=>'merge_file',
  602                          -values=>\@sorted_merge_files,
  603                          -default=>$merge_file,
  604          ), "\n",
  605          "\n",
  606          #CGI::hr(),
  607          CGI::div({style=>"background-color: #CCCCCC"},
  608            "\n", 'From:','&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',  CGI::textfield(-name=>"from", -size=>30, -value=>$from, -override=>1),
  609            "\n", CGI::br(),'Reply-To: ', CGI::textfield(-name=>"replyTo", -size=>30, -value=>$replyTo, -override=>1),
  610            "\n", CGI::br(),'Subject:  ', CGI::br(), CGI::textarea(-name=>'subject', -default=>$subject, -rows=>3,-cols=>30, -override=>1),
  611         ),
  612         #CGI::hr(),
  613         CGI::submit(-name=>'action', -value=>$UPDATE_SETTINGS_BUTTON),
  614 
  615       ),
  616 #############################################################################################
  617 # second column
  618 #############################################################################################
  619 
  620 ## Edit by Mark to insert scrolling list
  621           CGI::td({-style=>"width:33%"},
  622                CGI::strong("Send to:"),
  623                       CGI::radio_group(-name=>'radio',
  624                                        -values=>['all_students','studentID'],
  625                                        -labels=>{all_students=>'All students in course',studentID => 'Selected students'},
  626                                        -default=>'studentID', -linebreak=>0),
  627               CGI::br(),$scrolling_user_list,
  628               CGI::i("Preview set to: "), $preview_record->last_name,
  629               CGI::submit(-name=>'action', -value=>'preview',-label=>'Preview message'),'&nbsp;&nbsp;',
  630           ),
  631 
  632 ## Edit here to insert filtering
  633 ## be sure to fail GRACEFULLY!
  634 #
  635 #
  636 #               CGI::input({type=>'submit',value=>'Sort by',name=>'action'}),,
  637 #             CGI::radio_group(-name=>'sort_by', -values=>['id','alphabetical','section','recitation'],
  638 #               -labels=>{id=>'Login',alphabetical=>'Alph.',section => 'Sec.',recitation=>'Rec.'},
  639 #               -default=>defined($r->param("sort_by")) ? $r->param("sort_by") : 'id',
  640 #               -linebreak=>0
  641 #             ),
  642 #
  643 #           CGI::br(),CGI::br(),
  644 #       CGI::popup_menu(-name=>'classList',
  645 #              -values=>\@users,
  646 #              -labels=>\%classlistLabels,
  647 #              -size  => 10,
  648 #              -multiple => 1,
  649 #              -default=>$user
  650 #       ),
  651 #     ),
  652 
  653 
  654 
  655 
  656 #############################################################################################
  657 # third column
  658 #############################################################################################
  659       CGI::td({align=>'left'},
  660 
  661         " Rows: ", CGI::textfield(-name=>'rows', -size=>3, -value=>$rows),
  662         " Columns: ", CGI::textfield(-name=>'columns', -size=>3, -value=>$columns),
  663         CGI::br(),
  664 #       CGI::i('Press any action button to update display'),CGI::br(),
  665       #show available macros
  666         CGI::popup_menu(
  667             -name=>'dummyName',
  668             -values=>['', '$SID', '$FN', '$LN', '$SECTION', '$RECITATION','$STATUS', '$EMAIL', '$LOGIN', '$COL[3]', '$COL[-1]'],
  669             -labels=>{''=>'list of insertable macros',
  670               '$SID'=>'$SID - Student ID',
  671               '$FN'=>'$FN - First name',
  672               '$LN'=>'$LN - Last name',
  673               '$SECTION'=>'$SECTION',
  674               '$RECITATION'=>'$RECITATION',
  675               '$STATUS'=>'$STATUS - C, Audit, Drop, etc.',
  676               '$EMAIL'=>'$EMAIL - Email address',
  677               '$LOGIN'=>'$LOGIN - Login',
  678               '$COL[3]'=>'$COL[3] - 3rd col',
  679               '$COL[-1]'=>'$COL[-1] - Last column'
  680               }
  681         ), "\n",
  682       ),
  683 
  684   ); # end Tr
  685   print CGI::end_table();
  686 #############################################################################################
  687 # end upper table
  688 #############################################################################################
  689 
  690 # show merge file
  691 #         print  "<pre>",(map {$_ =~s/\s/\./g;$_}     map {sprintf('%-8.8s',$_);}  0..8),"</pre>";
  692 #     print  CGI::popup_menu(
  693 #             -name=>'dummyName2',
  694 #             -values=>\@merge_keys,
  695 #             -labels=>$rh_merge_data,
  696 #             -multiple=>1,
  697 #             -size    =>2,
  698 #
  699 #         ), "\n",CGI::br();
  700 #       warn "merge keys ", join( " ",@merge_keys);
  701 #############################################################################################
  702 # merge file fragment and message text area field
  703 #############################################################################################
  704     my @tmp2;
  705         eval{  @tmp2= @{$rh_merge_data->{ $db->getUser($preview_user)->student_id  }  };}; # checked
  706         if ($@ and $merge_file ne 'None') {
  707       print "No merge data for $preview_user in merge file: &lt;$merge_file&gt;",CGI::br();
  708         } else {
  709       print CGI::pre("",data_format(1..($#tmp2+1)),"<br>", data_format2(@tmp2));
  710     }
  711 #create a textbox with the subject and a textarea with the message
  712 #print actual body of message
  713 
  714   print  "\n", CGI::p( $self->{message}) if defined($self->{message});
  715     print  "\n", CGI::p( CGI::textarea(-name=>'body', -default=>$text, -rows=>$rows, -cols=>$columns, -override=>1));
  716 
  717 #############################################################################################
  718 # action button table
  719 #############################################################################################
  720   print    CGI::table( { -border=>2,-cellpadding=>4},
  721          CGI::Tr( {},
  722            CGI::td({}, CGI::submit(-name=>'action', -value=>'Send Email') ), "\n",
  723            CGI::td({}, CGI::submit(-name=>'action', -value=>'Save')," to $output_file"), " \n",
  724            CGI::td({}, CGI::submit(-name=>'action', -value=>'Save as:'),
  725                    CGI::textfield(-name=>'savefilename', -size => 20, -value=> "$output_file", -override=>1)
  726            ), "\n",
  727            CGI::td(CGI::submit(-name=>'action', -value=>'Save as Default')),
  728         )
  729   );
  730 
  731 ##############################################################################################################
  732 
  733   print CGI::end_form();
  734   return "";
  735 }
  736 
  737 ##############################################################################
  738 # Utility methods
  739 ##############################################################################
  740 
  741 sub saveProblem {
  742     my $self      = shift;
  743   my ($body, $probFileName)= @_;
  744   local(*PROBLEM);
  745   open (PROBLEM, ">$probFileName") ||
  746     $self->addbadmessage(CGI::p("Could not open $probFileName for writing.
  747             Check that the  permissions for this problem are 660 (-rw-rw----)"));
  748   print PROBLEM $body if -w $probFileName;
  749   close PROBLEM;
  750   chmod 0660, "$probFileName" ||
  751                $self->addbadmessage(CGI::p("CAN'T CHANGE PERMISSIONS ON FILE $probFileName"));
  752 }
  753 
  754 sub read_input_file {
  755   my $self         = shift;
  756   my $filePath     = shift;
  757   my ($text, @text);
  758   my $header = '';
  759   my ($subject, $from, $replyTo);
  760   local(*FILE);
  761   if (-e "$filePath" and -r "$filePath") {
  762     open FILE, "$filePath" || do { $self->addbadmessage(CGI::p("Can't open $filePath")); return};
  763     while ($header !~ s/Message:\s*$//m and not eof(FILE)) {
  764       $header .= <FILE>;
  765     }
  766     $text = join( '', <FILE>);
  767     $text =~ s/^\s*//; # remove initial white space if any.
  768     $header         =~ /^From:\s(.*)$/m;
  769     $from           = $1 or $from = $self->{defaultFrom};
  770 
  771     $header         =~ /^Reply-To:\s(.*)$/m;
  772     $replyTo        = $1 or $replyTo = $self->{defaultReply};
  773 
  774     $header         =~ /^Subject:\s(.*)$/m;
  775     $subject        = $1;
  776 
  777   } else {
  778     $from           = $self->{defaultFrom};
  779     $replyTo        = $self->{defaultReply};
  780     $text           =  (-e "$filePath") ? "FIXME file $filePath can't be read" :"FIXME file $filePath doesn't exist";
  781     $subject        = $self->{defaultSubject};
  782   }
  783   return ($from, $replyTo, $subject, \$text);
  784 }
  785 
  786 
  787 sub get_message_file_names {
  788   my $self         = shift;
  789   return $self->read_dir($self->{ce}->{courseDirs}->{email}, '\\.msg$');
  790 }
  791 sub get_merge_file_names   {
  792   my $self         = shift;
  793   return 'None', $self->read_dir($self->{ce}->{courseDirs}->{scoring}, '\\.csv$'); #FIXME ? check that only readable files are listed.
  794 }
  795 
  796 sub mail_message_to_recipients {
  797   my $self                  = shift;
  798   my $subject               = $self->{subject};
  799   my $from                  = $self->{from};
  800   my @recipients            = @{$self->{ra_send_to}};
  801   my $rh_merge_data         = $self->{rh_merge_data};
  802   my $merge_file            = $self->{merge_file};
  803   my $result_message        = '';
  804   my $failed_messages        = 0;
  805   foreach my $recipient (@recipients) {
  806       # warn "FIXME sending email to $recipient";
  807       my $error_messages = '';
  808       my $ur      = $self->{db}->getUser($recipient); #checked
  809       unless ($ur) {
  810         $error_messages .= "Record for user $recipient not found\n";
  811         next;
  812       }
  813       unless ($ur->email_address) {
  814         $error_messages .="User $recipient does not have an email address -- skipping\n";
  815         next;
  816       }
  817       my ($msg, $preview_header);
  818       eval{ ($msg,$preview_header) = $self->process_message($ur,$rh_merge_data); };
  819       $error_messages .= "There were errors in processing user $ur, merge file $merge_file. \n$@\n" if $@;
  820       my $mailer = Mail::Sender->new({
  821         from    =>   $from,
  822         to      =>   $ur->email_address,
  823         smtp    =>   $self->{smtpServer},
  824         subject =>   $subject,
  825         headers =>   "X-Remote-Host: ".$self->r->get_remote_host(),
  826       });
  827       unless (ref $mailer) {
  828         $error_messages .= "Failed to create a mailer for user $recipient: $Mail::Sender::Error\n";
  829         next;
  830       }
  831       unless (ref $mailer->Open()) {
  832         $error_messages .= "Failed to open the mailer for user $recipient: $Mail::Sender::Error\n";
  833         next;
  834       }
  835       my $MAIL         = $mailer->GetHandle() || ($error_messages .= "Couldn't get mailer handle \n");
  836       print $MAIL        $msg                 || ($error_messages .= "Couldn't print to $MAIL");
  837       close $MAIL                             || ($error_messages .= "Couldn't close $MAIL");
  838         #warn "FIXME mailed to $recipient: ", $ur->email_address, " from $from subject $subject Errors: $error_messages";
  839         $failed_messages++ if $error_messages;
  840         $result_message .= $error_messages;
  841     }
  842     my $courseName = $self->r->urlpath->arg("courseID");
  843     my $number_of_recipients = scalar(@recipients) - $failed_messages;
  844     $result_message = <<EndText.$result_message;
  845 
  846       A message with the subject line
  847                    $subject
  848       has been sent to
  849       $number_of_recipients recipient(s) in the class $courseName.
  850       There were $failed_messages message(s) that could not be delivered.
  851 
  852 EndText
  853 
  854 }
  855 sub email_notification {
  856   my $self = shift;
  857   my $result_message = shift;
  858   # find info on mailer and sender
  859   # use the defaultFrom address.
  860 
  861   # find info on instructor recipient and message
  862   my $subject="WeBWorK email sent";
  863 
  864   my $mailing_errors = "";
  865   # open MAIL handle
  866   my $mailer = Mail::Sender->new({
  867     from => $self->{defaultFrom},
  868     to   => $self->{defaultFrom},
  869     smtp    => $self->{smtpServer},
  870     subject => $subject,
  871     headers => "X-Remote-Host: ".$self->r->get_remote_host(),
  872   });
  873   unless (ref $mailer) {
  874     $mailing_errors .= "Failed to create a mailer: $Mail::Sender::Error";
  875     return "";
  876   }
  877   unless (ref $mailer->Open()) {
  878     $mailing_errors .= "Failed to open the mailer: $Mail::Sender::Error";
  879     return "";
  880   }
  881   my $MAIL = $mailer->GetHandle();
  882   # print message
  883   print $MAIL $result_message;
  884   # clean up
  885   close $MAIL;
  886 
  887     warn "instructor message sent to ", $self->{defaultFrom};
  888 
  889 }
  890 sub getRecord {
  891   my $self    = shift;
  892   my $line    = shift;
  893   my $delimiter   = shift;
  894   $delimiter       = ',' unless defined($delimiter);
  895 
  896         #       Takes a delimited line as a parameter and returns an
  897         #       array.  Note that all white space is removed.  If the
  898         #       last field is empty, the last element of the returned
  899         #       array is also empty (unlike what the perl split command
  900         #       would return).  E.G. @lineArray=&getRecord(\$delimitedLine).
  901 
  902         my(@lineArray);
  903         $line.="${delimiter}___";                       # add final field which must be non-empty
  904         @lineArray = split(/\s*${delimiter}\s*/,$line); # split line into fields
  905         $lineArray[0] =~s/^\s*//;                       # remove white space from first element
  906         pop @lineArray;                                 # remove the last artificial field
  907         @lineArray;
  908 }
  909 
  910 sub process_message {
  911   my $self          = shift;
  912   my $ur            = shift;
  913   my $rh_merge_data = shift;
  914   my $text          = defined($self->{r_text}) ? ${ $self->{r_text} }:
  915                           'FIXME no text was produced by initialization!!';
  916   my $merge_file      = ( defined($self->{merge_file}) ) ? $self->{merge_file} : 'None';
  917 
  918   my $status_name = $self->r->ce->status_abbrev_to_name($ur->status);
  919   $status_name = $ur->status unless defined $status_name;
  920 
  921   #user macros that can be used in the email message
  922   my $SID           = $ur->student_id;
  923   my $FN            = $ur->first_name;
  924   my $LN            = $ur->last_name;
  925   my $SECTION       = $ur->section;
  926   my $RECITATION    = $ur->recitation;
  927   my $STATUS        = $status_name;
  928   my $EMAIL         = $ur->email_address;
  929   my $LOGIN         = $ur->user_id;
  930 
  931   # get record from merge file
  932   # FIXME this is inefficient.  The info should be cached
  933   my @COL            = defined($rh_merge_data->{$SID}) ? @{$rh_merge_data->{$SID} } : ();
  934   if ($merge_file ne 'None' && not defined($rh_merge_data->{$SID})  ) {
  935     $self->addbadmessage(CGI::p("No merge data for student id:$SID; name:$FN $LN; login:$LOGIN"));
  936   }
  937   unshift(@COL,"");     ## this makes COL[1] the first column
  938   my $endCol = @COL;
  939   # for safety, only evaluate special variables
  940   my $msg = $text;
  941   $msg =~ s/\$SID/$SID/ge;
  942   $msg =~ s/\$LN/$LN/ge;
  943   $msg =~ s/\$FN/$FN/ge;
  944   $msg =~ s/\$STATUS/$STATUS/ge;
  945   $msg =~ s/\$SECTION/$SECTION/ge;
  946   $msg =~ s/\$RECITATION/$RECITATION/ge;
  947   $msg =~ s/\$EMAIL/$EMAIL/ge;
  948   $msg =~ s/\$LOGIN/$LOGIN/ge;
  949   if (defined($COL[1])) {   # prevents extraneous error messages.
  950     $msg =~ s/\$COL\[(\-?\d+)\]/$COL[$1]/ge
  951   }
  952   else {            # prevents extraneous $COL's in email message
  953     $msg =~ s/\$COL\[(\-?\d+)\]//g
  954   }
  955 
  956   $msg =~ s/\r//g;
  957 
  958   my @preview_COL = @COL;
  959   shift @preview_COL; ## shift back for preview
  960   my $preview_header =  CGI::pre({},data_format(1..($#COL)),"<br>", data_format2(@preview_COL)).
  961                         CGI::h3( "This sample mail would be sent to $EMAIL");
  962 
  963   return $msg, $preview_header;
  964 }
  965 
  966 
  967 # Ý sub data_format {
  968 #
  969 # Ý Ý Ý Ý Ýmap {$_ =~s/\s/\./g;$_} Ý Ý map {sprintf('%-8.8s',$_);} Ý@_;
  970  sub data_format {
  971       map {"COL[$_]".'&nbsp;'x(3-length($_));}  @_;  # problems if $_ has length bigger than 4
  972  }
  973   sub data_format2 {
  974     map {$_ =~s/\s/&nbsp;/g;$_}  map {sprintf('%-8.8s',$_);} @_;
  975  }
  976 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9