From 7c71d93c1e300acb80b692f7908d2ec1be2ecd76 Mon Sep 17 00:00:00 2001 From: JT Smith Date: Tue, 21 Mar 2006 03:27:57 +0000 Subject: [PATCH] added mail send queue --- docs/changelog/6.x.x.txt | 1 + docs/upgrades/upgrade_6.8.7-6.99.0.pl | 23 +++ etc/WebGUI.conf.original | 3 +- lib/WebGUI/Mail/Send.pm | 180 +++++++++++++----- .../Activity/SendQueuedMailMessages.pm | 88 +++++++++ ...orkflow_Activity_SendQueuedMailMessages.pm | 12 ++ 6 files changed, 263 insertions(+), 44 deletions(-) create mode 100644 lib/WebGUI/Workflow/Activity/SendQueuedMailMessages.pm create mode 100644 lib/WebGUI/i18n/English/Workflow_Activity_SendQueuedMailMessages.pm diff --git a/docs/changelog/6.x.x.txt b/docs/changelog/6.x.x.txt index 263c455dc..32005e526 100644 --- a/docs/changelog/6.x.x.txt +++ b/docs/changelog/6.x.x.txt @@ -29,6 +29,7 @@ otherwise be slow or complex pages. More details in migration.txt. - The SMTP mail backend has been replaced with a new API that's capable of sending attachments, HTML messages, and more. This will introduce many new + - Added a mail queue system. options for developers. - The group mail screen now allows sending of HTML messages. - Added prequery statements to the SQLReport and configurable allowed statements diff --git a/docs/upgrades/upgrade_6.8.7-6.99.0.pl b/docs/upgrades/upgrade_6.8.7-6.99.0.pl index d45df58a9..03a95a279 100644 --- a/docs/upgrades/upgrade_6.8.7-6.99.0.pl +++ b/docs/upgrades/upgrade_6.8.7-6.99.0.pl @@ -134,6 +134,11 @@ sub addWorkflow { value text, primary key (activityId, name) )"); + $session->db->write("create table mailQueue ( + messageId varchar(22) binary not null primary key, + message mediumtext, + toGroup varchar(22) binary + )"); print "\t\tPurging old workflow info.\n"; my $versionTag = WebGUI::VersionTag->getWorking($session); $versionTag->set({name=>"Upgrade to ".$toVersion}); @@ -164,6 +169,7 @@ sub addWorkflow { "WebGUI::Workflow::Activity::ExpireGroupings", "WebGUI::Workflow::Activity::PurgeOldAssetRevisions", "WebGUI::Workflow::Activity::ExpireSubscriptionCodes", "WebGUI::Workflow::Activity::PurgeOldTrash", "WebGUI::Workflow::Activity::GetSyndicatedContent", "WebGUI::Workflow::Activity::ProcessRecurringPayments", + "WebGUI::Workflow::Activity::SendQueuedMailMessages", "WebGUI::Workflow::Activity::SyncProfilesToLdap", "WebGUI::Workflow::Activity::SummarizePassiveProfileLog"], "WebGUI::User"=>["WebGUI::Workflow::Activity::CreateCronJob", "WebGUI::Workflow::Activity::NotifyAboutUser"], "WebGUI::VersionTag"=>["WebGUI::Workflow::Activity::CommitVersionTag", "WebGUI::Workflow::Activity::RollbackVersionTag", @@ -304,6 +310,23 @@ sub addWorkflow { $activity->set("subject", "Content Denied"); $activity->set("message", "Your version tag was denied. Please take corrective actions and recommit your changes."); $activity->set("who", "committer"); + $workflow = WebGUI::Workflow->create($session, { + title=>"Send Queued Email Messages", + description => "Sends all the messages in the mail queue.", + enabled=>1, + isSerial=>1, + type=>"None" + }, "pbworkflow000000000006"); + $activity = $workflow->addActivity("WebGUI::Workflow::Activity::SendQueuedMailMessages", "pbwfactivity0000000021"); + $activity->set("title", "Send Queued Messages"); + WebGUI::Workflow::Cron->create($session, { + title=>'Send Queued Email Messages Every 5 Minutes', + enabled=>1, + runOnce=>0, + minuteOfHour=>"*/5", + priority=>3, + workflowId=>$workflow->getId + }, "pbcron0000000000000004"); print "\t\tUpdating settings.\n"; $session->setting->remove("autoCommit"); $session->setting->remove("alertOnNewUser"); diff --git a/etc/WebGUI.conf.original b/etc/WebGUI.conf.original index 2c0f334fd..181a0e330 100644 --- a/etc/WebGUI.conf.original +++ b/etc/WebGUI.conf.original @@ -302,7 +302,8 @@ "WebGUI::Workflow::Activity::ExpireGroupings", "WebGUI::Workflow::Activity::PurgeOldAssetRevisions", "WebGUI::Workflow::Activity::ExpireSubscriptionCodes", "WebGUI::Workflow::Activity::PurgeOldTrash", "WebGUI::Workflow::Activity::GetSyndicatedContent", "WebGUI::Workflow::Activity::ProcessRecurringPayments", - "WebGUI::Workflow::Activity::SyncProfilesToLdap", "WebGUI::Workflow::Activity::SummarizePassiveProfileLog"], + "WebGUI::Workflow::Activity::SyncProfilesToLdap", "WebGUI::Workflow::Activity::SummarizePassiveProfileLog", + "WebGUI::Workflow::Activity::SendQueuedMailMessages"], "WebGUI::User" : ["WebGUI::Workflow::Activity::CreateCronJob", "WebGUI::Workflow::Activity::NotifyAboutUser"], "WebGUI::VersionTag" : ["WebGUI::Workflow::Activity::CommitVersionTag", "WebGUI::Workflow::Activity::RollbackVersionTag", "WebGUI::Workflow::Activity::TrashVersionTag", "WebGUI::Workflow::Activity::CreateCronJob", diff --git a/lib/WebGUI/Mail/Send.pm b/lib/WebGUI/Mail/Send.pm index 2d8603ffa..876cf557a 100644 --- a/lib/WebGUI/Mail/Send.pm +++ b/lib/WebGUI/Mail/Send.pm @@ -14,10 +14,13 @@ http://www.plainblack.com info@plainblack.com =cut +use strict; use Net::SMTP; use MIME::Entity; +use MIME::Parser; use LWP::MediaTypes qw(guess_media_type); -use strict; +use WebGUI::Group; +use WebGUI::User; =head1 NAME @@ -31,11 +34,15 @@ This package is used for sending emails via SMTP. use WebGUI::Mail::Send; -my $mail = WebGUI::Mail::Send->new($session, { to=>$to, from=>$from, subject=>$subject}); +my $mail = WebGUI::Mail::Send->create($session, { to=>$to, from=>$from, subject=>$subject}); +my $mail = WebGUI::Mail::Send->retrieve($session, $messageId); + $mail->addText($text); $mail->addHtml($html); $mail->addAttachment($pathToFile); + $mail->send; +$mail->queue; =head1 METHODS @@ -46,7 +53,7 @@ These methods are available from this class: #------------------------------------------------------------------- -=head2 addAttachment ( pathToFile ) +=head2 addAttachment ( pathToFile [ , mimetype ] ) Adds an attachment to the message. @@ -54,15 +61,20 @@ Adds an attachment to the message. The filesystem path to the file you wish to attach. +=head3 mimetype + +Optionally specify a mime type for this attachment. If one is not specified it will be guessed based upon the file extension. + =cut sub addAttachment { my $self = shift; my $path = shift; + my $mimetype = shift || guess_media_type($path); $self->{_message}->attach( Path=>$path, Encoding=>'-SUGGEST', - Type=>guess_media_type($path) + Type=>$mimetype ); } @@ -114,9 +126,9 @@ sub addText { #------------------------------------------------------------------- -=head2 new ( session, headers ) +=head2 create ( session, headers ) -Constructor. +Creates a new message and returns a WebGUI::Mail::Send object. This is a class method. =head3 session @@ -130,6 +142,10 @@ A hash reference containing addressing and other header level options. A string containing a comma seperated list of email addresses to send to. +=head4 toGroup + +A WebGUI groupId. The email address of the users in this group will be looked up and will each be sent a copy of this message. + =head4 subject A short string of text to be placed in the subject line. @@ -156,22 +172,13 @@ A mime type for the message. Defaults to "multipart/mixed". =cut -sub new { +sub create { my $class = shift; my $session = shift; my $headers = shift; - $headers->{from} ||= $session->setting->get("companyEmail"); - $headers->{contentType} ||= "multipart/mixed"; - my $override = ""; - if ($session->config->get("emailOverride")) { - $override = $headers->{to}; - $headers->{to} = $session->config->get("emailOverride"); - delete $headers->{bcc}; - delete $headers->{cc}; - } my $message = MIME::Entity->build( - Type=>$headers->{contentType}, - From=>$headers->{from}, + Type=>$headers->{contentType} || "multipart/mixed", + From=>$headers->{from} || $session->setting->get("companyEmail"), To=>$headers->{to}, Cc=>$headers->{cc}, Bcc=>$headers->{bcc}, @@ -180,12 +187,80 @@ sub new { Date=>$session->datetime->epochToHuman("","%W, %d %C %y %j:%n:%s %O"), "X-Mailer"=>"WebGUI" ); - if ($override) { - $message->attach(Data=>"This message was intended for ".$override." but was overridden in the config file.\n\n"); + if ($session->config->get("emailOverride")) { + my $to = $headers->{to}; + $to = "WebGUI Group ".$headers->{toGroup} if ($headers->{toGroup}); + $message->head->replace("to", $session->config->get("emailOverride")); + $message->head->replace("cc",undef); + $message->head->replace("bcc",undef); + delete $headers->{toGroup}; + $message->attach(Data=>"This message was intended for ".$to." but was overridden in the config file.\n\n"); } - bless {_message=>$message, _session=>$session, _headers=>$headers}, $class; + bless {_message=>$message, _session=>$session, _toGroup=>$headers->{toGroup} }, $class; } +#------------------------------------------------------------------- + +=head2 getMessageIdsInQueue ( session ) + +Returns an array reference of the message IDs in the mail queue. Use with the retrieve() method. This is a class method. + +=head3 session + +A reference to the current session. + +=cut + +sub getMessageIdsInQueue { + my $class = shift; + my $session = shift; + return $session->db->buildArrayRef("select messageId from mailQueue"); +} + + +#------------------------------------------------------------------- + +=head2 queue ( ) + +Puts this message in the mail queue so it can be sent out later by the workflow system. Returns a messageId so that the message can be retrieved later if necessary. Note that this is the preferred method of sending messages because it keeps WebGUI running faster. + +=cut + +sub queue { + my $self = shift; + return $self->session->db->setRow("mailQueue", "messageId", { messageId=>"new", message=>$self->{_message}->stringify, toGroup=>$self->{_toGroup} }); +} + + +#------------------------------------------------------------------- + +=head2 retrieve ( session, messageId ) + +Retrieves a message from the mail queue, which thusly deletes it from the queue. This is a class method. + +=head3 session + +A reference to the current session. + +=head3 messageId + +The unique id for a message in the queue. + +=cut + +sub retrieve { + my $class = shift; + my $session = shift; + my $messageId = shift; + return undef unless $messageId; + my $data = $session->db->getRow("mailQueue","messageId", $messageId); + return undef unless $data->{messageId}; + $session->db->deleteRow("mailQueue","messageId", $messageId); + my $parser = MIME::Parser->new; + bless {_session=>$session, _message=>$parser->parse_data($data->{messageId}), _toGroup=>$data->{toGroup}}, $class; +} + + #------------------------------------------------------------------- =head2 send ( ) @@ -196,31 +271,50 @@ Sends the message via SMTP. Returns 1 if successful. sub send { my $self = shift; - if ($self->session->setting->get("smtpServer") =~ /\/sendmail/) { - if (open(MAIL,"| ".$self->session->setting->get("smtpServer")." -t -oi -oem")) { - $self->{_message}->print(\*MAIL); - close(MAIL) or $self->session->errorHandler->error("Couldn't close connection to mail server: ".$self->session->setting->get("smtpServer")); + my $status = 1; + if ($self->{_message}->head->get("To")) { + if ($self->session->setting->get("smtpServer") =~ /\/sendmail/) { + if (open(MAIL,"| ".$self->session->setting->get("smtpServer")." -t -oi -oem")) { + $self->{_message}->print(\*MAIL); + close(MAIL) or $self->session->errorHandler->error("Couldn't close connection to mail server: ".$self->session->setting->get("smtpServer")); + } else { + $self->session->errorHandler->error("Couldn't connect to mail server: ".$self->session->setting->get("smtpServer")); + $status = 0; + } } else { - $self->session->errorHandler->error("Couldn't connect to mail server: ".$self->session->setting->get("smtpServer")); - return 0; - } - } else { - my $smtp = Net::SMTP->new($self->session->setting->get("smtpServer")); # connect to an SMTP server - if (defined $smtp) { - $smtp->mail($self->{_headers}{from}); # use the sender's address here - $smtp->to(split(",",$self->{_headers}{to})); # recipient's address - $smtp->cc(split(",",$self->{_headers}{cc})); - $smtp->bcc(split(",",$self->{_headers}{bcc})); - $smtp->data(); # Start the mail - $smtp->datasend($self->{_message}->stringify); - $smtp->dataend(); # Finish sending the mail - $smtp->quit; # Close the SMTP connection - } else { - $self->session->errorHandler->error("Couldn't connect to mail server: ".$self->session->setting->get("smtpServer")); - return 0; + my $smtp = Net::SMTP->new($self->session->setting->get("smtpServer")); # connect to an SMTP server + if (defined $smtp) { + $smtp->mail($self->{_message}->head->get("from")); # use the sender's address here + $smtp->to(split(",",$self->{_message}->head->get("to"))); # recipient's address + $smtp->cc(split(",",$self->{_message}->head->get("cc"))); + $smtp->bcc(split(",",$self->{_message}->head->get("bcc"))); + $smtp->data(); # Start the mail + $smtp->datasend($self->{_message}->stringify); + $smtp->dataend(); # Finish sending the mail + $smtp->quit; # Close the SMTP connection + } else { + $self->session->errorHandler->error("Couldn't connect to mail server: ".$self->session->setting->get("smtpServer")); + $status = 0; + } } } - return 1; + my $group = $self->{_toGroup}; + delete $self->{_toGroup}; + if ($group) { + my $group = WebGUI::Group->new($self->session, $self->{_toGroup}); + $self->{_message}->head->replace("bcc", undef); + $self->{_message}->head->replace("cc", undef); + foreach my $userId (@{$group->getUsers(1,1)}) { + my $user = WebGUI::User->new($self->session, $userId); + if ($user->profileField("email")) { + $self->{_message}->head->replace("To",$user->profileField("email")); + unless ($self->send) { + $status = 0; + } + } + } + } + return $status; } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Workflow/Activity/SendQueuedMailMessages.pm b/lib/WebGUI/Workflow/Activity/SendQueuedMailMessages.pm new file mode 100644 index 000000000..e40c0e62e --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/SendQueuedMailMessages.pm @@ -0,0 +1,88 @@ +package WebGUI::Workflow::Activity::SendQueuedMailMessages; + + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2006 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'WebGUI::Workflow::Activity'; +use WebGUI::Mail::Send; + +=head1 NAME + +Package WebGUI::Workflow::Activity::SendQueuedMailMessages + +=head1 DESCRIPTION + +Sends all the messages in the mail queue. + +=head1 SYNOPSIS + +See WebGUI::Workflow::Activity for details on how to use any activity. + +=head1 METHODS + +These methods are available from this class: + +=cut + + +#------------------------------------------------------------------- + +=head2 definition ( session, definition ) + +See WebGUI::Workflow::Activity::defintion() for details. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new($session, "Workflow_Activity_SendQueuedMailMessages"); + push(@{$definition}, { + name=>$i18n->get("topicName"), + properties=> { } + }); + return $class->SUPER::definition($session,$definition); +} + + +#------------------------------------------------------------------- + +=head2 execute ( ) + +See WebGUI::Workflow::Activity::execute() for details. + +=cut + +sub execute { + my $self = shift; + foreach my $id (@{WebGUI::Mail::Send->getMessageIdsInQueue($self->session)}) { + my $message = WebGUI::Mail::Send->retrieve($self->session, $id); + if (defined $message) { + unless ($message->send) { + # if the message fails to send, requeue it + $message->queue; + } + } + } + return $self->COMPLETE; +} + + + +1; + + diff --git a/lib/WebGUI/i18n/English/Workflow_Activity_SendQueuedMailMessages.pm b/lib/WebGUI/i18n/English/Workflow_Activity_SendQueuedMailMessages.pm new file mode 100644 index 000000000..3b9935326 --- /dev/null +++ b/lib/WebGUI/i18n/English/Workflow_Activity_SendQueuedMailMessages.pm @@ -0,0 +1,12 @@ +package WebGUI::i18n::English::Workflow_Activity_SendQueuedMailMessages; + +our $I18N = { + 'topicName' => { + message => q|Send Queued Mail Messages|, + context => q|The name of this workflow activity.|, + lastUpdated => 0, + }, + +}; + +1;