Parent Directory
|
Revision Log
Changed references to "feedback" to "email instructor", "cancel email" and so forth for consistency. This was in response to a suggestion by Bob Palais at University of Utah. -- Mike
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Feedback.pm,v 1.45 2008/03/13 22:22:23 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::Feedback; 18 use base qw(WeBWorK::ContentGenerator); 19 20 =head1 NAME 21 22 WeBWorK::ContentGenerator::Feedback - Send mail to professors. 23 24 =cut 25 26 # *** feedback should be exempt from authentication, so that people can send 27 # feedback from the login page! 28 29 use strict; 30 use warnings; 31 use Data::Dumper; 32 #use CGI qw(-nosticky ); 33 use WeBWorK::CGI; 34 use Mail::Sender; 35 use Socket qw/unpack_sockaddr_in inet_ntoa/; # for remote host/port info 36 use Text::Wrap qw(wrap); 37 use WeBWorK::Utils qw/formatDateTime decodeAnswers/; 38 39 use mod_perl; 40 use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 ); 41 42 # request paramaters used 43 # 44 # user 45 # key 46 # module 47 # set (if from ProblemSet or Problem) 48 # problem (if from Problem) 49 # displayMode (if from Problem) 50 # showOldAnswers (if from Problem) 51 # showCorrectAnswers (if from Problem) 52 # showHints (if from Problem) 53 # showSolutions (if from Problem) 54 55 # state data sent 56 # 57 # user object for current user 58 # permission level of current user 59 # current session key 60 # which ContentGenerator module called Feedback? 61 # set object for current set (if from ProblemSet or Problem) 62 # problem object for current problem (if from Problem) 63 # display options (if from Problem) 64 65 sub body { 66 my ($self) = @_; 67 my $r = $self->r; 68 my $ce = $r->ce; 69 my $db = $r->db; 70 my $authz = $r->authz; 71 72 # get form fields 73 my $key = $r->param("key"); 74 my $userName = $r->param("user"); 75 my $module = $r->param("module"); 76 my $setName = $r->param("set"); 77 my $problemNumber = $r->param("problem"); 78 my $displayMode = $r->param("displayMode"); 79 my $showOldAnswers = $r->param("showOldAnswers"); 80 my $showCorrectAnswers = $r->param("showCorrectAnswers"); 81 my $showHints = $r->param("showHints"); 82 my $showSolutions = $r->param("showSolutions"); 83 my $from = $r->param("from"); 84 my $feedback = $r->param("feedback"); 85 my $courseID = $r->urlpath->arg("courseID"); 86 87 my ($user, $set, $problem); 88 $user = $db->getUser($userName) # checked 89 if defined $userName and $userName ne ""; 90 if (defined $user) { 91 $set = $db->getMergedSet($userName, $setName) # checked 92 if defined $setName and $setName ne ""; 93 $problem = $db->getMergedProblem($userName, $setName, $problemNumber) # checked 94 if defined $set and defined $problemNumber && $problemNumber ne ""; 95 } else { 96 $set = $db->getGlobalSet($setName) # checked 97 if defined $setName and $setName ne ""; 98 $problem = $db->getGlobalProblem($setName, $problemNumber) # checked 99 if defined $set and defined $problemNumber && $problemNumber ne ""; 100 } 101 102 # generate context URLs 103 my $emailableURL; 104 my $returnURL; 105 if ($user) { 106 my $modulePath; 107 my @args; 108 if ($set) { 109 if ($problem) { 110 $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", 111 courseID => $r->urlpath->arg("courseID"), 112 setID => $set->set_id, 113 problemID => $problem->problem_id, 114 ); 115 @args = qw/displayMode showOldAnswers showCorrectAnswers showHints showSolutions/; 116 } else { 117 $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet", 118 courseID => $r->urlpath->arg("courseID"), 119 setID => $set->set_id, 120 ); 121 @args = (); 122 } 123 } else { 124 $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets", 125 courseID => $r->urlpath->arg("courseID"), 126 ); 127 @args = (); 128 } 129 $emailableURL = $self->systemLink($modulePath, 130 authen => 0, 131 params => [ "effectiveUser", @args ], 132 use_abs_url => 1, 133 ); 134 $returnURL = $self->systemLink($modulePath, 135 authen => 1, 136 params => [ @args ], 137 ); 138 } else { 139 $emailableURL = "(not available)"; 140 $returnURL = ""; 141 } 142 my $homeModulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::Home"); 143 my $systemURL = $self->systemLink($homeModulePath, authen=>0, use_abs_url=>1); 144 145 unless ($authz->hasPermissions($userName, "submit_feedback")) { 146 $self->feedbackNotAllowed($returnURL); 147 return ""; 148 } 149 150 # determine the recipients of the email 151 my @recipients = $self->getFeedbackRecipients($user); 152 153 unless (@recipients) { 154 $self->noRecipientsAvailable($returnURL); 155 return ""; 156 } 157 158 if (defined $r->param("sendFeedback")) { 159 # get verbosity level 160 my $verbosity = $ce->{mail}->{feedbackVerbosity}; 161 162 # determine the sender of the email 163 my $sender; 164 if ($user) { 165 if ($user->email_address) { 166 $sender = $user->rfc822_mailbox; 167 } else { 168 if ($user->full_name) { 169 $sender = $user->full_name . " <$from>" 170 } else { 171 $sender = $from; 172 } 173 } 174 } else { 175 $sender = $from; 176 } 177 178 # sanity checks 179 unless ($sender) { 180 $self->feedbackForm($user, $returnURL, 181 "No Sender specified."); 182 return ""; 183 } 184 unless ($feedback) { 185 $self->feedbackForm($user, $returnURL, 186 "Message was blank."); 187 return ""; 188 } 189 190 my %subject_map = ( 191 'c' => $courseID, 192 'u' => $user ? $user->user_id : undef, 193 's' => $set ? $set->set_id : undef, 194 'p' => $problem ? $problem->problem_id : undef, 195 'x' => $user ? $user->section : undef, 196 'r' => $user ? $user->recitation : undef, 197 '%' => '%', 198 ); 199 my $chars = join("", keys %subject_map); 200 my $subject = $ce->{mail}{feedbackSubjectFormat} 201 || "WeBWorK question from %c: %u set %s/prob %p"; # default if not entered 202 $subject =~ s/%([$chars])/defined $subject_map{$1} ? $subject_map{$1} : ""/eg; 203 204 # get info about remote user (stolen from &WeBWorK::Authen::write_log_entry) 205 my ($remote_host, $remote_port); 206 if (MP2) { 207 $remote_host = $r->connection->remote_addr->ip_get || "UNKNOWN"; 208 $remote_port = $r->connection->remote_addr->port || "UNKNOWN"; 209 } else { 210 ($remote_port, $remote_host) = unpack_sockaddr_in($r->connection->remote_addr); 211 $remote_host = defined $remote_host ? inet_ntoa($remote_host) : "UNKNOWN"; 212 $remote_port = "UNKNOWN" unless defined $remote_port; 213 } 214 #my $user_agent = $r->headers_in("User-Agent"); 215 216 my $headers = "X-Remote-Host: $remote_host\n"; 217 $headers .= "X-WeBWorK-Module: $module\n" if defined $module; 218 $headers .= "X-WeBWorK-Course: $courseID\n" if defined $courseID; 219 if ($user) { 220 $headers .= "X-WeBWorK-User: ".$user->user_id."\n"; 221 $headers .= "X-WeBWorK-Section: ".$user->section."\n"; 222 $headers .= "X-WeBWorK-Recitation: ".$user->recitation."\n"; 223 } 224 $headers .= "X-WeBWorK-Set: ".$set->set_id."\n" if $set; 225 $headers .= "X-WeBWorK-Problem: ".$problem->problem_id."\n" if $problem; 226 227 # bring up a mailer 228 my $mailer = Mail::Sender->new({ 229 from => $ce->{mail}{smtpSender}, 230 fake_from => $sender, 231 to => join(",", @recipients), 232 smtp => $ce->{mail}->{smtpServer}, 233 subject => $subject, 234 headers => $headers, 235 }); 236 unless (ref $mailer) { 237 $self->feedbackForm($user, $returnURL, 238 "Failed to create a mailer: $Mail::Sender::Error"); 239 return ""; 240 } 241 unless (ref $mailer->Open()) { 242 $self->feedbackForm($user, $returnURL, 243 "Failed to open the mailer: $Mail::Sender::Error"); 244 return ""; 245 } 246 my $MAIL = $mailer->GetHandle(); 247 248 # print message 249 print $MAIL 250 wrap("", "", "This message was automatically generated by the WeBWorK", 251 "system at $systemURL, in response to a request from $remote_host:$remote_port." 252 ), "\n\n"; 253 254 print $MAIL "Click this link to see the page from which the user sent feedback:\n", 255 "$emailableURL\n\n"; 256 257 if ($feedback) { 258 print $MAIL 259 "***** The feedback message: *****\n\n", 260 wrap("", "", $feedback), "\n\n"; 261 } 262 if ($problem and $verbosity >= 1) { 263 print $MAIL 264 "***** Data about the problem processor: *****\n\n", 265 266 "Display Mode: $displayMode\n", 267 "Show Old Answers: " . ($showOldAnswers ? "yes" : "no") . "\n", 268 "Show Correct Answers: " . ($showCorrectAnswers ? "yes" : "no") . "\n", 269 "Show Hints: " . ($showHints ? "yes" : "no") . "\n", 270 "Show Solutions: " . ($showSolutions ? "yes" : "no") . "\n\n", 271 } 272 if ($user and $verbosity >= 1) { 273 print $MAIL 274 "***** Data about the user: *****\n\n", 275 #$user->toString(), "\n\n"; 276 $self->format_user($user), "\n"; 277 } 278 if ($problem and $verbosity >= 1) { 279 print $MAIL 280 "***** Data about the problem: *****\n\n", 281 #$problem->toString(), "\n\n"; 282 $self->format_userproblem($problem), "\n"; 283 } 284 if ($set and $verbosity >= 1) { 285 print $MAIL 286 "***** Data about the homework set: *****\n\n", 287 #$set->toString(), "\n\n"; 288 $self->format_userset($set), "\n"; 289 } 290 if ($ce and $verbosity >= 2) { 291 print $MAIL 292 "***** Data about the environment: *****\n\n", 293 Dumper($ce), "\n\n"; 294 } 295 296 # Close returns the mailer object on success, a negative value on failure, 297 # zero if mailer was not opened. 298 my $result = $mailer->Close; 299 300 if (ref $result) { 301 # print confirmation 302 print CGI::p("Your message was sent successfully."); 303 print CGI::p(CGI::a({-href => $returnURL}, "Return to your work")); 304 print CGI::pre(wrap("", "", $feedback)); 305 } else { 306 $self->feedbackForm($user, $returnURL, 307 "Failed to send message ($result): $Mail::Sender::Error"); 308 } 309 } else { 310 # just print the feedback form, with no message 311 $self->feedbackForm($user, $returnURL, ""); 312 } 313 314 return ""; 315 } 316 317 sub feedbackNotAllowed { 318 my ($self, $returnURL) = @_; 319 320 print CGI::p("You are not allowed to send e-mail."); 321 print CGI::p(CGI::a({-href=>$returnURL}, "Cancel E-Mail")) if $returnURL; 322 } 323 324 sub noRecipientsAvailable { 325 my ($self, $returnURL) = @_; 326 327 print CGI::p("No e-mail recipients are listed for this course."); 328 print CGI::p(CGI::a({-href=>$returnURL}, "Cancel E-Mail")) if $returnURL; 329 } 330 sub title { 331 return "E-mail Instructor"; 332 } 333 sub feedbackForm { 334 my ($self, $user, $returnURL, $message) = @_; 335 my $r = $self->r; 336 337 print CGI::start_form(-method=>"POST", -action=>$r->uri); 338 print $self->hidden_authen_fields; 339 print $self->hidden_fields(qw( 340 module set problem displayMode showOldAnswers showCorrectAnswers 341 showHints showSolutions 342 )); 343 print CGI::p(CGI::b("From:"), " ", 344 ($user && $user->email_address 345 ? CGI::tt($user->email_address) 346 : CGI::textfield("from", "", 40)) 347 ); 348 print CGI::p("Use this form to report to your professor a 349 problem with the WeBWorK system or an error in a problem 350 you are attempting. Along with your message, additional 351 information about the state of the system will be 352 included."); 353 print CGI::p(CGI::i($message)) if $message; 354 print CGI::p( 355 CGI::b("E-mail:"), CGI::br(), 356 CGI::textarea("feedback", "", 20, 80), 357 ); 358 print CGI::submit("sendFeedback", "Send E-mail"); 359 print CGI::end_form(); 360 print CGI::p(CGI::a({-href=>$returnURL}, "Cancel E-mail")) if $returnURL; 361 } 362 363 sub getFeedbackRecipients { 364 my ($self, $user) = @_; 365 my $ce = $self->r->ce; 366 my $db = $self->r->db; 367 my $authz = $self->r->authz; 368 369 my @recipients; 370 371 # send to all users with permission to receive_feedback and an email address 372 # DBFIXME iterator? 373 foreach my $rcptName ($db->listUsers()) { 374 if ($authz->hasPermissions($rcptName, "receive_feedback")) { 375 my $rcpt = $db->getUser($rcptName); # checked 376 next if $ce->{feedback_by_section} and defined $user 377 and defined $rcpt->section and defined $user->section 378 and $rcpt->section ne $user->section; 379 if ($rcpt and $rcpt->email_address) { 380 push @recipients, $rcpt->rfc822_mailbox; 381 } 382 } 383 } 384 385 if (defined $ce->{mail}->{feedbackRecipients}) { 386 push @recipients, @{$ce->{mail}->{feedbackRecipients}}; 387 } 388 389 return @recipients; 390 } 391 392 sub format_user { 393 my ($self, $User) = @_; 394 my $ce = $self->r->ce; 395 396 my $result = "User ID: " . $User->user_id . "\n"; 397 $result .= "Name: " . $User->full_name . "\n"; 398 $result .= "Email: " . $User->email_address . "\n"; 399 $result .= "Student ID: " . $User->student_id . "\n"; 400 401 my $status_name = $ce->status_abbrev_to_name($User->status); 402 my $status_string = defined $status_name 403 ? "$status_name ('" . $User->status . "')" 404 : $User->status . " (unknown status abbreviation)"; 405 $result .= "Status: $status_string\n"; 406 407 $result .= "Section: " . $User->section . "\n"; 408 $result .= "Recitation: " . $User->recitation . "\n"; 409 $result .= "Comment: " . $User->comment . "\n"; 410 411 return $result; 412 } 413 414 sub format_userset { 415 my ($self, $Set) = @_; 416 my $ce = $self->r->ce; 417 418 my $result = "Set ID: " . $Set->set_id . "\n"; 419 $result .= "Set header file: " . $Set->set_header . "\n"; 420 $result .= "Hardcopy header file: " . $Set->hardcopy_header . "\n"; 421 422 my $tz = $ce->{siteDefaults}{timezone}; 423 $result .= "Open date: " . formatDateTime($Set->open_date, $tz) . "\n"; 424 $result .= "Due date: " . formatDateTime($Set->due_date, $tz) . "\n"; 425 $result .= "Answer date: " . formatDateTime($Set->answer_date, $tz) . "\n"; 426 $result .= "Published: " . ($Set->published ? "yes" : "no") . "\n"; 427 $result .= "Assignment type: " . $Set->assignment_type . "\n"; 428 if ($Set->assignment_type =~ /gateway/) { 429 $result .= "Attempts per version: " . $Set->assignment_type . "\n"; 430 $result .= "Time interval: " . $Set->time_interval . "\n"; 431 $result .= "Versions per interval: " . $Set->versions_per_interval . "\n"; 432 $result .= "Version time limit: " . $Set->version_time_limit . "\n"; 433 $result .= "Version creation time: " . formatDateTime($Set->version_creation_time, $tz) . "\n"; 434 $result .= "Problem randorder: " . $Set->problem_randorder . "\n"; 435 $result .= "Version last attempt time: " . $Set->version_last_attempt_time . "\n"; 436 } 437 438 return $result; 439 } 440 441 sub format_userproblem { 442 my ($self, $Problem) = @_; 443 my $ce = $self->r->ce; 444 445 my $result = "Problem ID: " . $Problem->problem_id . "\n"; 446 $result .= "Source file: " . $Problem->source_file . "\n"; 447 $result .= "Value: " . $Problem->value . "\n"; 448 $result .= "Max attempts " . ($Problem->max_attempts == -1 ? "unlimited" : $Problem->max_attempts) . "\n"; 449 $result .= "Random seed: " . $Problem->problem_seed . "\n"; 450 $result .= "Status: " . $Problem->status . "\n"; 451 $result .= "Attempted: " . ($Problem->attempted ? "yes" : "no") . "\n"; 452 453 my %last_answer = decodeAnswers($Problem->last_answer); 454 if (%last_answer) { 455 $result .= "Last answer:\n"; 456 foreach my $key (sort keys %last_answer) { 457 $result .= "\t$key: $last_answer{$key}\n"; 458 } 459 } else { 460 $result .= "Last answer: none\n"; 461 } 462 463 $result .= "Number of correct attempts: " . $Problem->num_correct . "\n"; 464 $result .= "Number of incorrect attempts: " . $Problem->num_incorrect . "\n"; 465 466 return $result; 467 } 468 469 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |