fromname;
$email = $this->fromemail;
return "\"$name\" <$email>";
}
}
class DefaultMailing extends BaseMailing {
private $user;
public function __construct(User $user) {
$this->user = $user;
}
public function getFromname() {
return $this->user->name;
}
public function getFromemail() {
return $this->user->emailaddress;
}
public function getSubject() {
return "onderwerp";
}
public function getBody() {
return "";
}
}
class Mailing extends BaseMailing {
/** @inject @var \App\Services\SendmailMailer */
public $mailer;
public static function create(\App\Services\Database $database, $params = []) {
$user = is_null(@$params['user']) ? null : $params['user']->primary;
$usergroup = is_null(@$params['usergroup']) ? null : $params['usergroup']->primary;
$addresslist = is_null(@$params['addresslist']) ? null : $params['addresslist']->primary;
$context = $database->context;
$activerow = $context->table('mailings')->insert([
'mailsubject' => @$params['subject'],
'mailfromname' => @$params['fromname'],
'mailfromemail' => @$params['fromemail'],
'mailfromtoken' => bin2hex(openssl_random_pseudo_bytes(16)),
'addresslist' => $addresslist,
'luser' => $user,
'lusergroup' => $usergroup,
]);
$body = is_null(@$params['body']) ? '' : $params['body'];
$context->table('mailbodies')->insert([
'mailing' => $activerow->mailing,
'bodytext' => $body,
]);
return $database->createModel('Mailing', $activerow);
}
public function createClone() {
return Mailing::create($this->database, [
'subject' => $this->getSubject(),
'fromname' => $this->getFromname(),
'fromemail' => $this->getFromemail(),
'addresslist' => $this->getAddresslist(),
'user' => $this->getUser(),
'usergroup' => $this->getUsergroup(),
'body' => $this->getBody(),
]);
}
public function delete() {
$this->activerow->delete();
}
public function getCtime() {
return $this->activerow->ctime;
}
public function getMtime() {
$mine = $this->activerow->mtime;
$addresslist = $this->addresslist;
if($addresslist)
return max($mine, $addresslist->mtime);
return $mine;
}
public function getMailingdate() {
return $this->getCtime();
}
public function getApproved() {
return $this->activerow->approved_when > $this->getMtime();
}
public function getSent() {
return $this->activerow->sent_when > $this->getMtime();
}
public function getSubmitted() {
return $this->activerow->submitted_when > $this->getMtime();
}
public function getRejected() {
return $this->activerow->rejected_when > $this->getMtime();
}
public function getFromapproved() {
return $this->activerow->mailfromapproved;
}
public function getToken() {
return $this->activerow->mailfromtoken;
}
public function setToken($token) {
return $this->activerow->update(['mailfromtoken' => $token]);
}
// NB: getStage() en getIncomplete() zijn voor gebruik in templates
// Dus *niet* voor de rest van de code als basis om beslissingen te nemen.
// Om dat te doen zou het gerefactord moeten worden naar een klassehierarchie.
// (Visitor pattern)
public function getStage() {
$rows = $this->activerow->related('finaldestinations')->where('sent_when IS NULL')->limit(1);
foreach($rows as $row)
return 'sending';
$rows = $this->activerow->related('finaldestinations')->limit(1);
foreach($rows as $row)
return 'sent';
if($this->getRejected())
return 'rejected';
if($this->getApproved())
return 'approved';
if($this->getSubmitted())
return 'submitted';
if($this->getProofread())
return 'proofread';
if($this->getIncomplete())
return 'draft';
return 'complete';
}
// zie comment bij getStage()
public function getIncomplete() {
$incomplete = [];
if(empty($this->getFromname()))
$incomplete[] = 'from-name';
if(empty($this->getFromemail()))
$incomplete[] = 'from-mail';
if (!$this->getFromapproved()) {
$incomplete[] = 'from-mail-not-approved';
}
if(empty($this->getSubject()))
$incomplete[] = 'subject';
if(empty($this->getBody()))
$incomplete[] = 'body';
$addresslist = $this->getAddresslist();
if($addresslist) {
if(!$addresslist->hasAddresses())
$incomplete[] = 'addresslist-empty';
} else {
$incomplete[] = 'addresslist';
}
return $incomplete;
}
public function getProofread() {
return $this->activerow->proofread_when > $this->getMtime();
}
public function getAddressapproved() {
return $this->activerow->mailfromapproved;
}
public function setAddressapproved($state = true) {
return $this->activerow->update(['mailfromapproved' => $state]);
}
private $attachments;
public function getAttachments() {
if (!is_null($this->attachments))
return $this->attachments;
$database = $this->database;
$rows = $this->activerow->related('attachments');
$attachments = [];
foreach($rows as $row) {
$attachments[] = $database->createModel('Attachment', $row);
}
return $this->attachments = $attachments;
}
private $attachment;
public function get_attachment($id) {
if (!is_null($this->attachment)) return $this->attachment;
$database = $this->database;
$rows = $this->activerow->related('attachments')->where('attachment', $id);
foreach($rows as $row) {
return $database->createModel('Attachment', $row);
}
return null;
}
public function addAttachments($attachments) {
$luser = $this->activerow->luser;
$results = [];
// beschikbaar voor Nette\Http\FileUpload: name, type, size, tmpName, error
foreach($attachments as $attachment) {
$contents = file_get_contents($attachment->TemporaryFile);
$download = 0;
if ($attachment->size > $this->cfg->attachment_warnsize) {
$download = 1;
$results[] = "$attachment->name is a relative large file hence we didn't attach it to the message but created a downloadlink.";
}
$att=Attachment::create($this->database, [
'user' => $luser,
'mailing' => $this,
'filename' => $attachment->name,
'size' => $attachment->size,
'download' => $download,
'contents' => $contents
]);
}
return $results;
}
public function getBody() {
$rows = $this->activerow->related('mailbodies');
# should only return one row, but anyway…
foreach($rows as $row)
return $row->bodytext;
return "";
}
private static $sanitize = <<<'XSL'
XSL;
public function getSanitized_body() {
$insane = new \DOMDocument('1.0', 'UTF-8');
$insane->recover = true;
$insane->resolveExternals = false;
$insane->strictErrorChecking = false;
@$insane->loadHTML($this->getBody());
$xml = new \DOMDocument;
$xml->loadXML(self::$sanitize);
$xsl = new \XSLTProcessor;
$xsl->importStylesheet($xml);
$sane = $xsl->transformToDoc($insane);
return $sane->saveHTML();
}
public function setBody($text) {
// TODO: hier aanroepen van checkBody($text) of beter in de presenter laten?
$rows = $this->activerow->related('mailbodies');
$this->activerow->update(['mtime' => new \DateTime]);
# should only return one row, but anyway…
foreach($rows as $row)
return $row->update(['bodytext' => $text]);
}
public function checkBody($text) {
// TODO: Check body and attachments ($cfg>message_maxsize MB) en presence of all
// attachments (and no links to non-existing ones binnen "attachmentdownloadurl")
$errors = [];
return $errors;
}
public function getRejection() {
return $this->activerow->rejection;
}
public function getFromname() {
return $this->activerow->mailfromname;
}
public function setFromname($name) {
return $this->activerow->update(['mailfromname' => $name, 'mtime' => new \DateTime]);
}
public function getFromemail() {
return $this->activerow->mailfromemail;
}
public function setFromemail($email) {
return $this->activerow->update(['mailfromemail' => $email, 'mtime' => new \DateTime]);
}
public function getSubject() {
return $this->activerow->mailsubject;
}
public function setSubject($subject) {
return $this->activerow->update(['mailsubject' => $subject, 'mtime' => new \DateTime]);
}
public function getAddresslist() {
$row = $this->activerow->ref('addresslist');
return $row ? $this->database->createModel('Addresslist', $row) : null;
}
public function setAddresslist($addresslist) {
$this->activerow->update(['addresslist' => $addresslist->primary, 'mtime' => new \DateTime]);
}
public function getOwner() {
return $this->activerow->luser;
}
public function getOwnergroup() {
return $this->activerow->lusergroup;
}
public function getUser() {
$row = $this->activerow->ref('luser');
return $row ? $this->database->createModel('User', $row) : null;
}
public function setUser($user) {
$this->activerow->update(['luser' => $user->primary]);
}
public function getUsergroup() {
$row = $this->activerow->ref('lusergroup');
return $row ? $this->database->createModel('Usergroup', $row) : null;
}
public function setUsergroup($usergroup) {
$this->activerow->update(['lusergroup' => $usergroup->primary]);
}
public function sendTestMail($luser) {
$message = $this->as_message();
$message->addTo($luser->emailaddress)
->setSubject("[PROOFREAD] ".$this->getSubject());
$this->mailer->send($message);
$this->activerow->update(['proofread_when' => new \DateTime]);
}
public function submitForApproval($luser) {
if (!$this->getProofread() || $this->getSubmitted()) {
throw new \ErrorException("Mailing not in the proper state for submission.");
}
$cfg = $this->cfg;
$message = $this->mailer->createMessage()
->setEnvelopeFrom($this->cfg->helpdeskemailaddress)
->setFrom($luser->emailaddress)
->addTo($cfg->moderators)
->setSubject("[SUBMISSION] ".$this->getSubject())
->setBody("$luser->name created a mailing. Please verify.");
$this->mailer->send($message);
$this->activerow->update(['submitted_when' => new \DateTime]);
}
public function reject($luser, $mtime, $reason) {
if ((string)$this->mtime !== (string)$mtime) {
throw new \ErrorException("Mailing changed before approval finished.");
}
if (!$this->getSubmitted() || $this->getApproved() || $this->getRejected()) {
throw new \ErrorException("Mailing not in the proper state for rejection.");
}
$this->activerow->update(['rejected_when' => new \DateTime, 'rejection' => $reason]);
$this->sendmessagetouser(
$luser,
$this->getFrom(), # FIXME: Moet worden: de mailing->luser (bv vanwege noreply@)
"{$this->templateDir}/reject.latte");
}
public function approve($luser, $mtime) {
if ((string)$this->mtime !== (string)$mtime) {
throw new \ErrorException("Mailing changed before approval finished.");
}
if (!$this->getSubmitted() || $this->getApproved() || $this->getRejected()) {
throw new \ErrorException("Mailing not in the proper state for approval.");
}
$this->sendmessagetouser(
$luser,
$this->getFrom(), # FIXME: Moet worden: de mailing->luser (bv vanwege noreply@)
"{$this->templateDir}/approve.latte");
$this->activerow->update(['approved_when' => new \DateTime]);
}
/**
* @return 0|1 result of message-sent (no|yes)
*/
public function requestAddresschange($luser, $mtime) {
if ((string)$this->mtime !== (string)$mtime) {
throw new \ErrorException("Mailing changed before request was sent.");
}
// Generate a new token here to prevent abuse
$this->activerow->update(['mailfromapproved' => false, 'mailfromtoken' => bin2hex(openssl_random_pseudo_bytes(16))]);
# FIXME: Future version: use mailfromtoken_expires to prevent too long changeable field
$this->sendmessagetouser(
$luser,
$this->getFromemail(),
"{$this->templateDir}/addresschange.latte");
return 1;
}
public function addresschangeapproved($luser, $token) {
if ($this->activerow->mailfromtoken != $token) {
return "bad-token";
}
if ($this->activerow->mailfromapproved) {
// No need for approval, do not send a message
return "no-approval-necessary";
}
// Accept entry and invalidate token
$this->activerow->update(['mailfromapproved' => true, 'mailfromtoken' => bin2hex(openssl_random_pseudo_bytes(16))]);
$this->sendmessagetouser(
$luser,
$this->getFromemail(),
"{$this->templateDir}/addresschangeok.latte"
);
return "ok";
}
public function addresschangerejected($luser, $token) {
if ($this->activerow->mailfromtoken != $token) {
return "bad-token";
}
if ($this->activerow->mailfromapproved) {
// No need for approval, do not send a message
return "no-approval-necessary";
}
// Accept entry and invalidate token
$this->activerow->update(['mailfromtoken' => bin2hex(openssl_random_pseudo_bytes(16))]);
$this->sendmessagetouser(
$luser,
$this->getFromemail(),
"{$this->templateDir}/addresschangedenied.latte"
);
// Since this address is denied we change the address here to a NULL address
// (change here because we need to send a message first)
$this->activerow->update(['mailfromemail' => NULL, 'mailfromapproved' => true]);
return "ok";
}
private function sendmessagetouser($luser, $to, $messagetemplate, $mailtemplate=NULL) {
$cfg = $this->cfg;
$messagecontent = new Nette\Templating\FileTemplate("$messagetemplate");
$messagecontent->registerFilter(new Nette\Latte\Engine);
$messagecontent->luser = $luser;
$messagecontent->mailing = $this;
$messagecontent->cfg = $cfg;
if($mailtemplate) {
$messagetext = new Nette\Templating\FileTemplate("$mailtemplate");
} else {
$messagetext = new Nette\Templating\FileTemplate("{$this->templateDir}/mailtemplate.tpl");
}
$messagetext->registerFilter(new Nette\Latte\Engine);
$messagetext->content = (string)$messagecontent;
# Leave subject out (NULL) to use from the template (
SUBJECT)
$message = $this->mailer->createMessage()
// FIXME addEmbeddedFile toevoegen aan App\Services\MailMessage om logos in mail mogelijk te maken
// ->addEmbeddedFile('logo', file_get_contents("{$this->templateDir}/logo-smaller.png"), 'image/png')
// ->addEmbeddedFile('slogan', file_get_contents("{$this->templateDir}/slogan-smaller.png"), 'image/png')
->setEnvelopeFrom($cfg->helpdeskemailaddress)
->setFrom($luser->emailaddress)
->addReplyTo($cfg->administrativefrom)
->addTo($to)
->setHTMLBody((string)$messagetext);
$this->mailer->send($message);
}
public function send() {
if(!$this->getApproved() || $this->getSent())
throw new \ErrorException("Mailing not in the proper state for sending.");
$this->activerow->update(['sent_when' => new \DateTime]);
$finaldestinations = $this->context->table('finaldestinations');
$addresslist = $this->getAddresslist();
$primary = $this->getPrimary();
foreach($addresslist->addresses as $key => $value) {
$finaldestinations->insert([
'mailing' => $primary,
'emailaddress' => $value->primary,
'actual_emailaddress' => $value->address,
]);
}
}
public function as_message() {
$message = $this->mailer->createMessage()
->setFrom($this->getFrom())
->setEnvelopeFrom($this->cfg->helpdeskemailaddress)
->setSubject($this->getSubject())
->setHtmlBody($this->getBody());
$attachments = $this->getAttachments();
foreach($attachments as $attachment)
if(!$attachment->download)
$message->addAttachment($attachment->filename, $attachment->contents);
return $message;
}
private $cache_bounces;
public function getBounces() {
if (!$this->getSent())
return [];
if (!is_null($this->cache_bounces))
return $this->cache_bounces;
$bounces = [];
$rows = $this->activerow->related('finaldestinations')->where('last_bounce_received_when IS NOT NULL');
foreach($rows as $row) {
$bounces[] = [
'emailaddress' => $row['actual_emailaddress'],
'reason' => "Bounced message" # FIXME: reden uit subject halen?
];
}
return $this->cache_bounces = $bounces;
}
}