/** * Builds and returns the generated mailing body HTML or plaintext. * * @param array $replacements An array of key/value pairs, where key is the string to replace with the value. * @return string The generated mailing body. */ public function buildBody(array $replacements = null) { $templateHtml = $this->getTemplateContent(); $editorHtml = $this->getEditorContent(); $css = $this->getCSS(); if (empty($templateHtml)) { throw new Exception('No valid template HTML was set.'); } if (empty($editorHtml)) { throw new Exception('No valid editor content HTML was set.'); } // we replace the editor tags with the content the user gave into the editor in the CMS $body = preg_replace('/<!-- editor -->.*?<!-- \\/editor -->/is', $editorHtml, $templateHtml); // we have to do this so we have entities in our body instead $body = mb_convert_encoding($body, 'HTML-ENTITIES', 'UTF-8'); // add Google UTM parameters to all anchors $body = $this->processUTMParameters($body); $body = $this->processReplacements($body, $replacements); $body = $this->processCSS($body); // we parse the template CSS into the template, and re-build our body $body = $this->processCSS($body); // we will return plaintext if the user asked for it. if ($this->isPlaintext()) { $body = SpoonFilter::stripHTML($body); } return $body; }
public function testStripHTML() { $html = ' <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Fork CMS</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <p> <a href="http://www.spoon-library.com">Spoon Library</a> </p> </body> </html>'; $this->assertEquals('Spoon Library', SpoonFilter::stripHTML($html)); $this->assertEquals('<a href="http://www.spoon-library.com">Spoon Library</a>', SpoonFilter::stripHTML($html, '<a>')); $this->assertEquals('Spoon Library (http://www.spoon-library.com)', SpoonFilter::stripHTML($html, null, true)); }
/** * Execute the action */ public function execute() { parent::execute(); // get parameters $mailingId = \SpoonFilter::getPostValue('mailing_id', null, '', 'int'); $subject = \SpoonFilter::getPostValue('subject', null, ''); $contentHTML = urldecode(\SpoonFilter::getPostValue('content_html', null, '')); $contentPlain = \SpoonFilter::getPostValue('content_plain', null, ''); // validate mailing ID if ($mailingId == '') { $this->output(self::BAD_REQUEST, null, 'No mailing ID provided'); } else { // get mailing record $this->mailing = BackendMailmotorModel::getMailing($mailingId); // check if record is empty if (empty($this->mailing)) { $this->output(self::BAD_REQUEST, null, BL::err('MailingDoesNotExist', $this->getModule())); } else { // validate subject if ($subject == '') { $this->output(500, array('element' => 'subject', 'element_error' => BL::err('NoSubject', $this->getModule())), BL::err('FormError')); } else { // set plain content $contentPlain = empty($contentPlain) ? \SpoonFilter::stripHTML($contentHTML) : $contentPlain; // add unsubscribe link if (mb_strpos($contentPlain, '[unsubscribe]') === false) { $contentPlain .= PHP_EOL . '[unsubscribe]'; } // build data $item['id'] = $this->mailing['id']; $item['subject'] = $subject; $item['content_plain'] = $contentPlain; $item['content_html'] = $contentHTML; $item['edited_on'] = date('Y-m-d H:i:s'); // update mailing in our database BackendMailmotorModel::updateMailing($item); /* we should insert the draft into campaignmonitor here, so we can use sendCampaignPreview in step 4. */ $item['groups'] = $this->mailing['groups']; $item['name'] = $this->mailing['name']; $item['from_name'] = $this->mailing['from_name']; $item['from_email'] = $this->mailing['from_email']; $item['reply_to_email'] = $this->mailing['reply_to_email']; try { BackendMailmotorCMHelper::saveMailingDraft($item); } catch (Exception $e) { // CM did not receive a valid URL if (strpos($e->getMessage(), 'HTML Content URL Required')) { $message = BL::err('HTMLContentURLRequired', $this->getModule()); } elseif (strpos($e->getMessage(), 'Payment details required')) { // no payment details were set for the CM client yet $error = BL::err('PaymentDetailsRequired', $this->getModule()); $cmUsername = $this->get('fork.settings')->get($this->getModule(), 'cm_username'); $message = sprintf($error, $cmUsername); } elseif (strpos($e->getMessage(), 'Duplicate Campaign Name')) { // the campaign name already exists in CM $message = BL::err('DuplicateCampaignName', $this->getModule()); } else { // we received an unknown error $message = $e->getMessage(); } // stop the script and show our error $this->output(500, null, $message); return; } // trigger event BackendModel::triggerEvent($this->getModule(), 'after_edit_mailing_step3', array('item' => $item)); // output $this->output(self::OK, array('mailing_id' => $mailingId), BL::msg('MailingEdited', $this->getModule())); return; } } // error $this->output(500, null, $message); return; } }
/** * Execute the action * * @return void */ public function execute() { // call parent, this will probably add some general CSS/JS or other required files parent::execute(); // get parameters $mailingId = SpoonFilter::getPostValue('mailing_id', null, '', 'int'); $subject = SpoonFilter::getPostValue('subject', null, ''); $contentHTML = urldecode(SpoonFilter::getPostValue('content_html', null, '')); $contentPlain = SpoonFilter::getPostValue('content_plain', null, ''); $fullContentHTML = SpoonFilter::getPostValue('full_content_html', null, ''); // validate mailing ID if ($mailingId == '') { $this->output(self::BAD_REQUEST, null, 'No mailing ID provided'); } // get mailing record $this->mailing = BackendMailmotorModel::getMailing($mailingId); // record is empty if (empty($this->mailing)) { $this->output(self::BAD_REQUEST, null, BL::err('MailingDoesNotExist', $this->getModule())); } // validate other fields if ($subject == '') { $this->output(900, array('element' => 'subject', 'element_error' => BL::err('NoSubject', $this->getModule())), BL::err('FormError')); } // set full HTML $HTML = $this->getEmailContent($this->mailing['template'], $contentHTML, $fullContentHTML); // set plain content $contentPlain = empty($contentPlain) ? SpoonFilter::stripHTML($HTML) : $contentPlain; // add unsubscribe link if (mb_strpos($contentPlain, '[unsubscribe]') === false) { $contentPlain .= PHP_EOL . '[unsubscribe]'; } // build data $item['id'] = $this->mailing['id']; $item['subject'] = $subject; $item['content_plain'] = $contentPlain; $item['content_html'] = $contentHTML; $item['data'] = serialize(array('full_content_html' => $HTML)); $item['edited_on'] = date('Y-m-d H:i:s'); // update mailing BackendMailmotorModel::updateMailing($item); // trigger event BackendModel::triggerEvent($this->getModule(), 'after_edit_mailing_step3', array('item' => $item)); // output $this->output(self::OK, array('mailing_id' => $mailingId), BL::msg('MailingEdited', $this->getModule())); }
/** * Builds the e-mail headers. */ private function getHeaders() { // create boundaries $uniqueId = md5(uniqid(time())); $boundary = 'SEB1_' . $uniqueId; $secondBoundary = 'SEB2_' . $uniqueId; // if plain body is not set, we'll strip the HTML tags from the HTML body if (empty($this->content['plain'])) { $this->content['plain'] = SpoonFilter::stripHTML($this->content['html'], null, true); } // encode the content $this->content['html'] = $this->encodeContent($this->content['html']); $this->content['plain'] = $this->encodeContent($this->content['plain']); // build headers $this->addHeader('Date: ' . SpoonDate::getDate('r')); $this->addHeader('From: ' . $this->from['name'] . ' <' . $this->from['email'] . '>'); // check mailmethod, some media don't need these (like mail()) if ($this->method == 'smtp') { // set subject $this->addHeader('Subject: ' . $this->subject); // set general To: header. useful if you prefer to customize it if (!empty($this->to['name'])) { $this->addHeader('To: ' . $this->to['name'] . ' <' . $this->to['email'] . '>'); } else { $this->addHeader('To: ' . $this->reformatRecipientString($this->recipients)); } } // loop and add CCs to headers if (!empty($this->CC)) { $this->addHeader('cc: ' . $this->reformatRecipientString($this->CC)); } // loop and add BCCs to headers if (!empty($this->BCC)) { $this->addHeader('bcc: ' . $this->reformatRecipientString($this->BCC)); } // add Reply-To header to headers if (!empty($this->replyTo)) { $this->addHeader('Reply-To: ' . $this->reformatRecipientString($this->replyTo)); } // if attachments are set, change the mail content type if (!empty($this->attachments)) { $this->contentType = 'multipart/mixed'; } // continue the rest of the headers $this->addHeader('X-Priority: ' . $this->priority); $this->addHeader('X-Mailer: SpoonEmail (part of Spoon library - http://www.spoon-library.com)'); $this->addHeader('MIME-Version: 1.0'); $this->addHeader('Content-Type: ' . $this->contentType . '; charset="' . $this->charset . '"; boundary="' . $boundary . '"' . self::LF); $this->addHeader('Importance: normal'); $this->addHeader('Priority: normal'); $this->addHeader('This is a multi-part message in MIME format.' . self::LF); $this->addHeader('--' . $boundary); // attachments found if (!empty($this->attachments)) { // means we need a second boundary defined to send html/plain mails. $this->addHeader('Content-Type: multipart/alternative; boundary="' . $secondBoundary . '"' . self::LF); $this->addHeader('--' . $secondBoundary); $this->addHeader('Content-Type: text/plain; charset="' . $this->charset . '"'); $this->addHeader('Content-Disposition: inline'); $this->addHeader('Content-Transfer-Encoding: ' . $this->contentTransferEncoding . self::LF); $this->addHeader($this->content['plain'] . self::LF); $this->addHeader('--' . $secondBoundary); $this->addHeader('Content-Type: text/html; charset="' . $this->charset . '"'); $this->addHeader('Content-Disposition: inline'); $this->addHeader('Content-Transfer-Encoding: ' . $this->contentTransferEncoding . self::LF); $this->addHeader($this->content['html'] . self::LF); $this->addHeader('--' . $secondBoundary . '--'); } else { // continue the rest of the headers $this->addHeader('Content-Type: text/plain; charset="' . $this->charset . '"'); $this->addHeader('Content-Disposition: inline'); $this->addHeader('Content-Transfer-Encoding: ' . $this->contentTransferEncoding . self::LF); $this->addHeader($this->content['plain'] . self::LF); $this->addHeader('--' . $boundary); $this->addHeader('Content-Type: text/html; charset="' . $this->charset . '"'); $this->addHeader('Content-Disposition: inline'); $this->addHeader('Content-Transfer-Encoding: ' . $this->contentTransferEncoding . self::LF); $this->addHeader($this->content['html'] . self::LF); } // attachments found if (!empty($this->attachments)) { // loop attachments foreach ($this->attachments as $attachment) { // set attachment headers $this->addHeader('--' . $boundary); $this->addHeader('Content-Type: ' . $attachment['type'] . '; name="' . $attachment['name'] . '"'); $this->addHeader('Content-Transfer-Encoding: ' . $attachment['encoding']); $this->addHeader('Content-Disposition: ' . $attachment['disposition'] . '; filename="' . $attachment['name'] . '"' . self::LF); $this->addHeader($attachment['data'] . self::LF); } } // final boundary, closes the headers $this->headers .= '--' . $boundary . '--'; // return headers string return $this->headers; }