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