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

Annotation of /branches/rel-2-3-dev/webwork2/lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4396 - (view) (download) (as text)

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9