diff --git a/README.md b/README.md index 9f1babc..aba1bd7 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ When you are in a dev environment (i.e. using the `.test` domain with Valet, or ## Events -When an email is sent, viewed, or a link is clicked, its tracking information is counted in the database using the jdavidbakr\MailTracker\Model\SentEmail model. This processing is done via dispatched jobs to the default queue in order to prevent the database from being overwhelmed is an email blast situation. +When an email is sent, viewed, or a link is clicked, its tracking information is counted in the database using the jdavidbakr\MailTracker\Model\SentEmail model. This processing is done via dispatched jobs to the queue in order to prevent the database from being overwhelmed in an email blast situation. You may choose the queue that these events are dispatched via the `mail-tracker.tracker-queue` config setting, or leave it `null` to use the default queue. You may want to do additional processing on these events, so an event is fired in these cases: diff --git a/config/mail-tracker.php b/config/mail-tracker.php index de43686..365ad86 100644 --- a/config/mail-tracker.php +++ b/config/mail-tracker.php @@ -71,6 +71,11 @@ /** * Determines whether or not the body of the email is logged in the sent_emails table */ - 'log-content' => true + 'log-content' => true, + + /** + * What queue should we dispatch our tracking jobs to? Null will use the default queue. + */ + 'tracker-queue' => null, ]; diff --git a/src/MailTracker.php b/src/MailTracker.php index 4f323ea..d00f449 100644 --- a/src/MailTracker.php +++ b/src/MailTracker.php @@ -67,7 +67,7 @@ protected function injectTrackingPixel($html, $hash) // Append the tracking url $tracking_pixel = ''; - $linebreak = Str::random(32); + $linebreak = app(Str::class)->random(32); $html = str_replace("\n", $linebreak, $html); if (preg_match("/^(.*]*>)(.*)$/", $html, $matches)) { @@ -85,7 +85,7 @@ protected function injectLinkTracker($html, $hash) $this->hash = $hash; $html = preg_replace_callback( - "/(]*href=['\"])([^'\"]*)/", + "/(]*href=[\"])([^\"]*)/", [$this, 'inject_link_callback'], $html ); @@ -175,7 +175,7 @@ protected function createTrackers($message) continue; } do { - $hash = Str::random(32); + $hash = app(Str::class)->random(32); $used = SentEmail::where('hash', $hash)->count(); } while ($used > 0); $headers->addTextHeader('X-Mailer-Hash', $hash); diff --git a/src/MailTrackerController.php b/src/MailTrackerController.php index 1d30028..4cc4f93 100644 --- a/src/MailTrackerController.php +++ b/src/MailTrackerController.php @@ -31,7 +31,8 @@ public function getT($hash) $tracker = Model\SentEmail::where('hash', $hash) ->first(); if ($tracker) { - RecordTrackingJob::dispatch($tracker); + RecordTrackingJob::dispatch($tracker) + ->onQueue(config('mail-tracker.tracker-queue')); } return $response; @@ -58,7 +59,8 @@ protected function linkClicked($url, $hash) $tracker = Model\SentEmail::where('hash', $hash) ->first(); if ($tracker) { - RecordLinkClickJob::dispatch($tracker, $url); + RecordLinkClickJob::dispatch($tracker, $url) + ->onQueue(config('mail-tracker.tracker-queue')); return redirect($url); } diff --git a/src/SNSController.php b/src/SNSController.php index d218239..2869172 100644 --- a/src/SNSController.php +++ b/src/SNSController.php @@ -70,16 +70,19 @@ protected function process_notification($message) protected function process_delivery($message) { - RecordDeliveryJob::dispatch($message); + RecordDeliveryJob::dispatch($message) + ->onQueue(config('mail-tracker.tracker-queue')); } public function process_bounce($message) { - RecordBounceJob::dispatch($message); + RecordBounceJob::dispatch($message) + ->onQueue(config('mail-tracker.tracker-queue')); } public function process_complaint($message) { - RecordComplaintJob::dispatch($message); + RecordComplaintJob::dispatch($message) + ->onQueue(config('mail-tracker.tracker-queue')); } } diff --git a/tests/MailTrackerTest.php b/tests/MailTrackerTest.php index 90f220d..6fb604b 100644 --- a/tests/MailTrackerTest.php +++ b/tests/MailTrackerTest.php @@ -69,6 +69,11 @@ public function testSendMessage() ]); // Go into the future to make sure that the old email gets removed \Carbon\Carbon::setTestNow(\Carbon\Carbon::now()->addWeek()); + $str = Mockery::mock(Str::class); + app()->instance(Str::class, $str); + $str->shouldReceive('random') + ->once() + ->andReturn('random-hash'); Event::fake(); @@ -99,6 +104,7 @@ public function testSendMessage() Event::assertDispatched(EmailSentEvent::class); $this->assertDatabaseHas('sent_emails', [ + 'hash' => 'random-hash', 'recipient' => $name.' <'.$email.'>', 'subject' => $subject, 'sender' => 'From Name ', @@ -115,6 +121,11 @@ public function testSendMessageWithMailRaw() $name = $faker->firstName . ' ' .$faker->lastName; $content = 'Text to e-mail'; View::addLocation(__DIR__); + $str = Mockery::mock(Str::class); + app()->instance(Str::class, $str); + $str->shouldReceive('random') + ->once() + ->andReturn('random-hash'); try { Mail::raw($content, function ($message) use ($email, $name) { @@ -126,6 +137,7 @@ public function testSendMessageWithMailRaw() } $this->assertDatabaseHas('sent_emails', [ + 'hash' => 'random-hash', 'recipient' => $name.' <'.$email.'>', 'sender' => 'From Name ', 'recipient' => "{$name} <{$email}>", @@ -180,7 +192,7 @@ public function it_doesnt_track_if_told_not_to() */ public function testPing() { - $this->disableExceptionHandling(); + Config::set('mail-tracker.tracker-queue', 'alt-queue'); Bus::fake(); $track = \jdavidbakr\MailTracker\Model\SentEmail::create([ 'hash' => Str::random(32), @@ -193,12 +205,14 @@ public function testPing() $response->assertSuccessful(); Bus::assertDispatched(RecordTrackingJob::class, function ($e) use ($track) { - return $e->sentEmail->id == $track->id; + return $e->sentEmail->id == $track->id && + $e->queue == 'alt-queue'; }); } public function testLegacyLink() { + Config::set('mail-tracker.tracker-queue', 'alt-queue'); Bus::fake(); $track = \jdavidbakr\MailTracker\Model\SentEmail::create([ 'hash' => Str::random(32), @@ -215,12 +229,14 @@ public function testLegacyLink() $response->assertRedirect($redirect); Bus::assertDispatched(RecordLinkClickJob::class, function ($job) use ($track, $redirect) { return $job->sentEmail->id == $track->id && - $job->url == $redirect; + $job->url == $redirect && + $job->queue == 'alt-queue'; }); } public function testLink() { + Config::set('mail-tracker.tracker-queue', 'alt-queue'); Bus::fake(); $track = \jdavidbakr\MailTracker\Model\SentEmail::create([ 'hash' => Str::random(32), @@ -236,7 +252,8 @@ public function testLink() $response->assertRedirect($redirect); Bus::assertDispatched(RecordLinkClickJob::class, function ($job) use ($track, $redirect) { return $job->sentEmail->id == $track->id && - $job->url == $redirect; + $job->url == $redirect && + $job->queue == 'alt-queue'; }); } @@ -357,6 +374,12 @@ public function it_retrieves_the_mesage_id_from_ses_mail_default() */ public function it_retrieves_the_mesage_id_from_ses_mail_driver() { + $str = Mockery::mock(Str::class); + app()->instance(Str::class, $str); + $str->shouldReceive('random') + ->with(32) + ->once() + ->andReturn('random-hash'); Config::set('mail.driver', 'ses'); Config::set('mail.default', null); $headers = Mockery::mock(); @@ -364,17 +387,14 @@ public function it_retrieves_the_mesage_id_from_ses_mail_driver() ->with('X-No-Track') ->once() ->andReturn(null); - $this->mailer_hash = ''; $headers->shouldReceive('addTextHeader') ->once() - ->andReturnUsing(function ($key, $value) { - $this->mailer_hash = $value; - }); + ->with('X-Mailer-Hash', 'random-hash'); $mailer_hash_header = Mockery::mock(); $mailer_hash_header->shouldReceive('getFieldBody') ->once() ->andReturnUsing(function () { - return $this->mailer_hash; + return 'random-hash'; }); $headers->shouldReceive('get') ->with('X-Mailer-Hash') @@ -502,7 +522,7 @@ public function it_ignores_invalid_topic() */ public function it_processes_a_delivery() { - $this->disableExceptionHandling(); + Config::set('mail-tracker.tracker-queue', 'alt-queue'); Bus::fake(); $message = [ 'notificationType' => 'Delivery', @@ -523,7 +543,8 @@ public function it_processes_a_delivery() $response->assertSee('notification processed'); Bus::assertDispatched(RecordDeliveryJob::class, function ($job) use ($message) { - return $job->message == (object)$message; + return $job->message == (object)$message && + $job->queue == 'alt-queue'; }); } @@ -532,6 +553,7 @@ public function it_processes_a_delivery() */ public function it_processes_a_bounce() { + Config::set('mail-tracker.tracker-queue', 'alt-queue'); Bus::fake(); $message = [ 'notificationType' => 'Bounce', @@ -552,7 +574,8 @@ public function it_processes_a_bounce() $response->assertSee('notification processed'); Bus::assertDispatched(RecordBounceJob::class, function ($job) use ($message) { - return $job->message == (object)$message; + return $job->message == (object)$message && + $job->queue == 'alt-queue'; }); } @@ -561,7 +584,7 @@ public function it_processes_a_bounce() */ public function it_processes_a_complaint() { - $this->disableExceptionHandling(); + Config::set('mail-tracker.tracker-queue', 'alt-queue'); Bus::fake(); $message = [ 'notificationType' => 'Complaint', @@ -582,7 +605,8 @@ public function it_processes_a_complaint() $response->assertSee('notification processed'); Bus::assertDispatched(RecordComplaintJob::class, function ($job) use ($message) { - return $job->message == (object)$message; + return $job->message == (object)$message && + $job->queue == 'alt-queue'; }); } @@ -651,6 +675,67 @@ public function it_handles_ampersands_in_links() $this->assertEquals(1, $track->clicks); } + /** + * @test + */ + public function it_handles_apostrophes_in_links() + { + Event::fake(); + Config::set('mail-tracker.track-links', true); + Config::set('mail-tracker.inject-pixel', true); + Config::set('mail.driver', 'array'); + (new MailServiceProvider(app()))->register(); + // Must re-register the MailTracker to get the test to work + $this->app['mailer']->getSwiftMailer()->registerPlugin(new MailTracker()); + + $faker = Factory::create(); + $email = $faker->email; + $subject = $faker->sentence; + $name = $faker->firstName . ' ' . $faker->lastName; + View::addLocation(__DIR__); + + Mail::send('email.testApostrophe', [], function ($message) use ($email, $subject, $name) { + $message->from('from@johndoe.com', 'From Name'); + $message->sender('sender@johndoe.com', 'Sender Name'); + $message->to($email, $name); + $message->cc('cc@johndoe.com', 'CC Name'); + $message->bcc('bcc@johndoe.com', 'BCC Name'); + $message->replyTo('reply-to@johndoe.com', 'Reply-To Name'); + $message->subject($subject); + $message->priority(3); + }); + $driver = app('mailer')->getSwiftMailer()->getTransport(); + $this->assertEquals(1, count($driver->messages())); + + $mes = $driver->messages()[0]; + $body = $mes->getBody(); + $hash = $mes->getHeaders()->get('X-Mailer-Hash')->getValue(); + + $matches = null; + preg_match_all('/(]*href=[\"])([^\"]*)/', $body, $matches); + $links = $matches[2]; + $aLink = $links[0]; + + $expected_url = "http://www.google.com?q=foo'bar"; + $this->assertNotNull($aLink); + $this->assertNotEquals($expected_url, $aLink); + + $response = $this->call('GET', $aLink); + $response->assertRedirect($expected_url); + + Event::assertDispatched(LinkClickedEvent::class); + + $this->assertDatabaseHas('sent_emails_url_clicked', [ + 'url' => $expected_url, + 'clicks' => 1, + ]); + + $track = \jdavidbakr\MailTracker\Model\SentEmail::whereHash($hash)->first(); + $this->assertNotNull($track); + $this->assertEquals(1, $track->clicks); + } + + /** * @test */ diff --git a/tests/email/testApostrophe.blade.php b/tests/email/testApostrophe.blade.php new file mode 100644 index 0000000..551d5fd --- /dev/null +++ b/tests/email/testApostrophe.blade.php @@ -0,0 +1,13 @@ + + This is the head + + +

+ This is a test! +

+

+ + Click here + +

+