Ukládání emailů v Laravelu

PHP, Laravel

Ukládat si v Laravelu historii všech odeslaných emailů není nic těžkého, přesto to tak na první pohled ale není. Zapeklitější problém ještě nastává, když je potřeba email přiřadit konkrétnímu člověku nebo jiné entitě v databázi.

Ukládání emailů v Laravelu

Někdy je vhodné si ukládat všechny odeslané emaily. Takže asi nikoho nepřekvapí, že již vzniklo hodně balíčků do Laravelu, které tuto problematiku řeší. Jedním z nich je kvalitní Mail tracker, který dokonce upraví odchozí emaily tak, aby mohl sledovat otevírání emailů a počet kliknutí na odkazy. Mezi ty jednodušší balíčky se řadí Laravel Email Database Log.

Vytvoření vazby mezi emailem a entitou v DB

Oba dva zmíněné balíčky využívají základní události, které jsou v Laravelu nachystány. Jedná se o MessageSending a MessageSent, ze kterých získají výslednou zprávu a tu uloží.

V systému, kde jsem měl ukládání řešit, ale byl navíc požadavek ke spárování odchozích emailů s konkrétní přihláškou dítěte na kurz. Ani toto ale není problém. Kromě objektu SwiftMessage je druhý parametr eventu předáno pole. To obsahuje veškeré proměnné, které byly použity pro sestavení šablony zprávy a lze je využít ke spárování.

Vytvoření a registrace listeneru

V prvním kroku je nutné vytvořit listener, a poté jej zaregistrovat. Pro zjednodušení lze využít příkazy Artisanu, který vytvoří soubor do složky app/Listeners.

php artisan make:listener SaveMailSentListener --event=Illuminate\Mail\Events\MessageSent

Ten je nutné následně registrovat v souboru app/Providers/EventServiceProvider.php 

protected $listen = [
    \Illuminate\Mail\Events\MessageSent::class => [
        \App\Listeners\MailLog\SaveSentMailListener::class,
    ],
];

Vytvoření migrací

php artisan make:migration CreateSendEmailsTable

Pokud se mají soubory ukládat do databáze, je nutné vytvořit tabulku, případně i model. Artisan v příkazu výše  poznal z názvu, že se bude vytvářet nová tabulku se jménem send_emails a proto připravil základní strukturu. Stačí tedy jen doplnit sloupečky. Osobně neodesílám žádné emaily s kopií nebo skrytou kopií, proto dané údaje neukládám. 

Schema::create('send_emails', function (Blueprint $table) {
      $table->increments('id');

      $table->unsignedInteger('student_id')->nullable();
      $table->string('from')->nullable();
      $table->text('to');
      $table->string('subject');
      $table->text('body');
      $table->text('attachments')->nullable();

      $table->timestamps();
      $table->foreign('student_id')->references('id')->on('students');
});

Ukládání a parsování zprávy

Do databáze se uloží veškeré informace o zprávě včetně názvů příloh, samotný obsah příloh se ale neukládá. Co je ale vhodné uložit navíc jsou embeded obrázky vložené pomocí CID, jsou totiž nedílnou součástí obsahu. Obrázky jsou prvně vloženy jako base64 do JavaScriptu, který je po vykreslení stránky doplní do jednotlivých src atributů.

Následující metody je potřeba vložit do dříve vytvořeného listeneru.

public function handle(\Illuminate\Mail\Events\MessageSent $event)
{
    DB::table('send_emails')->insert([
        'student_id'    => $event->data['student']->id,
        'from'          => $this->formatAddresses($event->message->getFrom()),
        'to'            => $this->formatAddresses($event->message->getTo()),
        // 'cc'            => $this->formatAddresses($event->message->getCc()),
        // 'bcc'           => $this->formatAddresses($event->message->getBcc()),
        'subject'       => $event->message->getSubject(),
        'body'          => $this->getBodyWithEmbededImage($event->message),
        'attachments'   => $this->formatAttachments($event->message->getChildren()),
    ]);
}

protected function formatAddresses($addresses)
{
    $formated = [];
    foreach ($addresses as $mail => $name) {
        $formated[] = empty($name) ? $mail : $name . ' <' . $mail . '>';
    }
    return empty($formated) ? null : implode(', ', $formated);
}

protected function formatAttachments($children)
{
    $attachments = [];
    foreach ($children as $child) {
        if ($child instanceof \Swift_Attachment) {
            $attachments[] = $child->getFilename();
        }
    }
    return empty($attachments) ? null : $attachments;
}

public function getBodyWithEmbededImage($message)
{
    $body = $message->getBody();
    if (Str::contains($body, '</body>')) {
        $script = "document.querySelectorAll('img[src^=cid]').forEach(
            function(el){
                var key = el.src.replace(/^cid:/, '');
                if( cids[key] ){ el.src = cids[key]; }
            }
        )";

        $cids = [];
        foreach ($message->getChildren() as $child) {
            if ($child instanceof \Swift_Image) {
                $cids[$child->getId()] = sprintf(
                    "data:%s;base64,%s",
                    $child->getContentType(),
                    base64_encode($child->getBody())
                );
            }
        }
        $body = Str::replaceFirst(
            '</body>',
            sprintf('<script>var cids = %s; %s</script></body>', json_encode($cids), $script),
            $body
        );
    }
    return $body;
}

Obrázek přejat z Freepik. Vaše zkušenosti s maily v Laravelu můžete sdílet v komentářích

K tomuto článku již není možné přidávat další komentáře