[system] / branches / rel-2-3-dev / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / SendMail.pm Repository:
ViewVC logotype

View of /branches/rel-2-3-dev/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3973 - (download) (as text) (annotate)
Wed Jan 25 23:13:56 2006 UTC (7 years, 3 months ago) by sh002i
Original Path: trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm
File size: 40126 byte(s)
forward-port from rel-2-2-dev: (update copyright date range -- 2000-2006.
this is probably overkill, since there are some files that were created
after 2000 and some files that were last modified before 2006.)

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9