useTransaction(); parent::setUp(); CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0; $this->_groupID = $this->groupCreate(); $this->createContactsInGroup(2, $this->_groupID); $this->defaultParams = [ 'name' => 'mailing name', 'created_id' => 1, 'groups' => ['include' => [$this->_groupID]], 'scheduled_date' => 'now', ]; $this->_mut = new CiviMailUtils($this, TRUE); $this->callAPISuccess('mail_settings', 'get', ['api.mail_settings.create' => ['domain' => 'chaos.org']]); } /** */ public function tearDown() { $this->_mut->stop(); CRM_Utils_Hook::singleton()->reset(); // DGW CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0; parent::tearDown(); } /** * Generate a fully-formatted mailing with standard email headers. */ public function testBasicHeaders() { $allMessages = $this->runMailingSuccess([ 'subject' => 'Accidents in cars cause children for {contact.display_name}!', 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.', ]); foreach ($allMessages as $k => $message) { /** @var ezcMail $message */ $offset = $k + 1; $this->assertEquals("FIXME", $message->from->name); $this->assertEquals("info@EXAMPLE.ORG", $message->from->email); $this->assertEquals("Mr. Foo{$offset} Anderson II", $message->to[0]->name); $this->assertEquals("mail{$offset}@nul.example.com", $message->to[0]->email); $this->assertRegExp('#^text/plain; charset=utf-8#', $message->headers['Content-Type']); $this->assertRegExp(';^b\.[\d\.a-f]+@chaos.org$;', $message->headers['Return-Path']); $this->assertRegExp(';^b\.[\d\.a-f]+@chaos.org$;', $message->headers['X-CiviMail-Bounce'][0]); $this->assertRegExp(';^\$;', $message->headers['List-Unsubscribe'][0]); $this->assertEquals('bulk', $message->headers['Precedence'][0]); } } /** * Generate a fully-formatted mailing (with body_text content). */ public function testText() { $allMessages = $this->runMailingSuccess([ 'subject' => 'Accidents in cars cause children for {contact.display_name}!', 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.', 'open_tracking' => 1, // Note: open_tracking does nothing with text, but we'll just verify that it does nothing ]); foreach ($allMessages as $message) { /** @var ezcMail $message */ /** @var ezcMailText $textPart */ $this->assertTrue($message->body instanceof ezcMailText); $this->assertEquals('plain', $message->body->subType); $this->assertRegExp( ";" . // Default header "Sample Header for TEXT formatted content.\n" . "BEWARE children need regular infusions of toys. Santa knows your .*\\. There is no http.*civicrm/mailing/optout.*\\.\n" . // Default footer "to unsubscribe: http.*civicrm/mailing/optout" . ";", $message->body->text ); } } /** * Generate a fully-formatted mailing (with body_html content). */ public function testHtmlWithOpenTracking() { $allMessages = $this->runMailingSuccess([ 'subject' => 'Example Subject', 'body_html' => '

You can go to Google or opt out.

', 'open_tracking' => 1, 'url_tracking' => 0, ]); foreach ($allMessages as $message) { /** @var ezcMail $message */ /** @var ezcMailText $htmlPart */ /** @var ezcMailText $textPart */ $this->assertTrue($message->body instanceof ezcMailMultipartAlternative); list($textPart, $htmlPart) = $message->body->getParts(); $this->assertEquals('html', $htmlPart->subType); $this->assertRegExp( ";" . // Default header "Sample Header for HTML formatted content.\n" . // FIXME: CiviMail puts double " after hyperlink! // body_html "

You can go to Google or opt out.

\n" . // Default footer "Sample Footer for HTML formatted content" . ".*\n" . "text ); $this->assertEquals('plain', $textPart->subType); $this->assertRegExp( ";" . // Default header "Sample Header for TEXT formatted content.\n" . // body_html, filtered "You can go to Google \\[1\\] or opt out \\[2\\]\\.\n" . "\n" . "Links:\n" . "------\n" . "\\[1\\] http://example.net/first\\?cs=[0-9a-f_]+\n" . "\\[2\\] http.*civicrm/mailing/optout.*\n" . "\n" . // Default footer "to unsubscribe: http.*civicrm/mailing/optout" . ";", $textPart->text ); } } /** * Generate a fully-formatted mailing (with body_html content). */ public function testHtmlWithOpenAndUrlTracking() { $allMessages = $this->runMailingSuccess([ 'subject' => 'Example Subject', 'body_html' => '

You can go to Google or opt out.

', 'open_tracking' => 1, 'url_tracking' => 1, ]); foreach ($allMessages as $message) { /** @var ezcMail $message */ /** @var ezcMailText $htmlPart */ /** @var ezcMailText $textPart */ $this->assertTrue($message->body instanceof ezcMailMultipartAlternative); list($textPart, $htmlPart) = $message->body->getParts(); $this->assertEquals('html', $htmlPart->subType); $this->assertRegExp( ";" . // body_html "

You can go to Google" . " or opt out.

\n" . // Default footer "Sample Footer for HTML formatted content" . ".*\n" . // Open-tracking code "text ); $this->assertEquals('plain', $textPart->subType); $this->assertRegExp( ";" . // body_html, filtered "You can go to Google \\[1\\] or opt out \\[2\\]\\.\n" . "\n" . "Links:\n" . "------\n" . "\\[1\\] http.*(extern/url.php|civicrm/mailing/url)(\?|&)u=\d+&qid=\d+\n" . "\\[2\\] http.*civicrm/mailing/optout.*\n" . "\n" . // Default footer "to unsubscribe: http.*civicrm/mailing/optout" . ";", $textPart->text ); } } /** * Each case comes in four parts: * 1. Mailing HTML (body_html) * 2. Regex to run against final HTML * 3. Regex to run against final text * 4. Additional mailing options * * @return array */ public function urlTrackingExamples() { $cases = []; // Tracking disabled $cases[0] = [ '

Foo

', ';

Foo

;', ';\\[1\\] http://example\.net/;', ['url_tracking' => 0], ]; $cases[1] = [ '

Foo

', // FIXME: Legacy tracker adds extra quote after URL ';

Foo

;', ';\\[1\\] http://example\.net/\?id=\d+;', ['url_tracking' => 0], ]; $cases[2] = [ '

Foo

', ';

Foo

;', ';\\[1\\] http.*civicrm/mailing/optout.*;', ['url_tracking' => 0], ]; $cases[3] = [ '

Look at .

', ';

Look at \.

;', ';Look at \.;', ['url_tracking' => 0], ]; $cases[4] = [ // Plain-text URL's are tracked in plain-text emails... // but not in HTML emails. "

Please go to: http://example.net/

", ";

Please go to: http://example\.net/

;", ';Please go to: http://example\.net/;', ['url_tracking' => 0], ]; // Tracking enabled $cases[5] = [ '

Foo

', ';

Foo

;', ';\\[1\\] .*(extern/url.php|civicrm/mailing/url)[\?&]u=\d+.*;', ['url_tracking' => 1], ]; $cases[6] = [ // FIXME: CiviMail URL tracking doesn't track tokenized links. '

Foo

', // FIXME: Legacy tracker adds extra quote after URL ';

Foo

;', ';\\[1\\] http://example\.net/\?id=\d+;', ['url_tracking' => 1], ]; $cases[7] = [ // It would be redundant/slow to track the action URLs? '

Foo

', ';

Foo

;', ';\\[1\\] http.*civicrm/mailing/optout.*;', ['url_tracking' => 1], ]; $cases[8] = [ // It would be excessive/slow to track every embedded image. '

Look at .

', ';

Look at \.

;', ';Look at \.;', ['url_tracking' => 1], ]; $cases[9] = [ // Plain-text URL's are tracked in plain-text emails... // but not in HTML emails. "

Please go to: http://example.net/

", ";

Please go to: http://example\.net/

;", ';Please go to: .*(extern/url.php|civicrm/mailing/url)[\?&]u=\d+&qid=\d+;', ['url_tracking' => 1], ]; return $cases; } /** * Generate a fully-formatted mailing (with body_html content). * * @dataProvider urlTrackingExamples * @throws \CRM_Core_Exception */ public function testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params) { $caseName = print_r(['inputHtml' => $inputHtml, 'params' => $params], 1); $allMessages = $this->runMailingSuccess($params + [ 'subject' => 'Example Subject', 'body_html' => $inputHtml, ]); foreach ($allMessages as $message) { /** @var ezcMail $message */ /** @var ezcMailText $htmlPart */ /** @var ezcMailText $textPart */ $this->assertTrue($message->body instanceof ezcMailMultipartAlternative); list($textPart, $htmlPart) = $message->body->getParts(); if ($htmlUrlRegex) { $this->assertEquals('html', $htmlPart->subType, "Should have HTML part in case: $caseName"); $this->assertRegExp($htmlUrlRegex, $htmlPart->text, "Should have correct HTML in case: $caseName"); } if ($textUrlRegex) { $this->assertEquals('plain', $textPart->subType, "Should have text part in case: $caseName"); $this->assertRegExp($textUrlRegex, $textPart->text, "Should have correct text in case: $caseName"); } } } /** * Create contacts in group. * * @param int $count * @param int $groupID * @param string $domain */ protected function createContactsInGroup( $count, $groupID, $domain = 'nul.example.com' ) { for ($i = 1; $i <= $count; $i++) { $contactID = $this->individualCreate([ 'first_name' => "Foo{$i}", 'email' => 'mail' . $i . '@' . $domain, ]); $this->callAPISuccess('group_contact', 'create', [ 'contact_id' => $contactID, 'group_id' => $groupID, 'status' => 'Added', ]); } } /** * Create and execute a mailing. Return the matching messages. * * @param array $params * List of parameters to send to Mailing.create API. * * @return array * @throws \CRM_Core_Exception */ protected function runMailingSuccess($params) { $mailingParams = array_merge($this->defaultParams, $params); $this->callAPISuccess('mailing', 'create', $mailingParams); $this->_mut->assertRecipients([]); $this->callAPISuccess('job', 'process_mailing', ['runInNonProductionEnvironment' => TRUE]); $allMessages = $this->_mut->getAllMessages('ezc'); // There are exactly two contacts produced by setUp(). $this->assertEquals(2, count($allMessages)); return $allMessages; } }