Odesílání emailů podle šablony

před 11 lety

David Grudl
Nette Core | 6849
+
0
-

Častým úkolem webových aplikací je rozesílat potvrzující a notifikační emaily. K tomu se výborně hodí šablonovací podpora Nette.

Dnes už existuje šikovnější řešení pomocí Nette\Mail – viz info

Mějme běžný presenter, který do šablony registruje nějaké filtry a helpery:

abstract class BasePresenter extends Presenter
{
    protected function createTemplate()
    {
        $template = parent::createTemplate();
        $template->registerHelper('money', array($this, 'moneyHelper'));
        $template->registerHelper('texy', array($this, 'texyHelper'));
        ...
        return $template;
    }
}

Z něj podědíme presenter Checkout, který zpracuje objednávku zboží a po jejím dokončení odešle zákazníkovi email. Šablonu emailu uložíme do adresáře emails ve složce šablon a bude se jmenovat např. checkout-confirmation.phtml.

class CheckoutPresenter extends BasePresenter
{
    public function actionSend()
    {
        ... // zpracování objednávky

        $email = $this->createTemplate();
        $email->setFile(Environment::expand('%templatesDir%/emails/checkout-confirmation.phtml'));
        $email->name = $name; // naplníme nějaká data
        ...

        $mailer = new Zend_Mail();
        $mailer->setBodyText((string) $email); // vyrenderujeme šablonu
        $mailer->setFrom('somebody@example.com', 'Some Sender');
        $mailer->addTo('somebody_else@example.com', 'Some Recipient');
        $mailer->setSubject('Potvrzení objednávky');
        $mailer->send();
    }
}

Mohli bychom jít i dál a chtít do šablony umístit i hlavičky From a Subject. To lze řešit tak, že šabloně předáme objekt, který ona naplní:

class CheckoutPresenter extends BasePresenter
{
    public function actionSend()
    {
        ... // zpracování objednávky

        $email = $this->createTemplate();
        $email->setFile(Environment::expand('%templatesDir%/emails/checkout-confirmation.phtml'));
        $email->name = $name; // naplníme nějaká data
        $email->headers = (object) NULL;
        ...

        $body = (string) $email; // vyrenderujeme šablonu už nyní

        $mailer = new Zend_Mail();
        $mailer->setBodyText($body);
        $mailer->setFrom($email->headers->from);
        $mailer->addTo('somebody_else@example.com', 'Some Recipient');
        $mailer->setSubject($email->headers->subject);
        $mailer->send();
    }
}

Šablona emailu by pak vypadala nějak takto:

{**
 * Checkout notification

 * @param stdClass $headers
 * @param string   $name
 *}

{? $headers->from = '"Super Giga Eshop" <info@example.com>'; }
{? $headers->subject = "Potvrzení objednávky"; }

Dobrý den {$name},

vaše objednávka byla přijata.

Čest!

František Novák
Super Giga Eshop

Pokud budeme v šabloně generovat URL, je potřeba zajistit, aby byly absolutní. K tomu účelu si můžeme například zaregistrovat filtr, který před každý odkaz začínající znakem / vloží http://example.com.

před 11 lety

Jod
Člen | 703
+
0
-

Mám nejaký problém s pridávaním Zend mail knižnice, nevie mi niekto poradiť ako to riešiť v nette, aby som všade nemusel prepisovať cesty v require?

`Failed opening required ‚Zend/Mail/Transport/Abstract.php‘ `

před 11 lety

romansklenar
Člen | 657
+
0
-

Úplně jednoduše: všechny je smazat a načítat si celou knihovnu autoloaderem ;)

před 11 lety

Jod
Člen | 703
+
0
-

No veď to som myslel, či sa to nedá inak než aby som v každom súbore Zend_Mail knižnice nemusel mazať require. Šaq je toho mrte.

Editoval Jod (2. 12. 2008 9:48)

před 11 lety

LM
Člen | 206
+
0
-

Musíš do include_path přidat adresář kde máš Zend.

<?php
set_include_path(implode(PATH_SEPARATOR, array(
    '/var/www/libs/',
    get_include_path()
)));
?>

Editoval LM (2. 12. 2008 10:36)

před 11 lety

Jod
Člen | 703
+
0
-

Super, dík, dúfam, že to pôjde aj na hostingu

před 11 lety

Jod
Člen | 703
+
0
-

Neviete niekto prečo mi píše Warning v priatom maile že:

Warning: Circular rendering detected ..

před 11 lety

LM
Člen | 206
+
0
-

Jod napsal(a):

Neviete niekto prečo mi píše Warning v priatom maile že:

Warning: Circular rendering detected ..

Šablona renderuje sama sebe : )

před 11 lety

Jod
Člen | 703
+
0
-

Šaq a koho má renderovať :D

před 11 lety

LM
Člen | 206
+
0
-

Tak includuju se sama do sebe : )

před 11 lety

Jod
Člen | 703
+
0
-

Aha ako kukám asi je chyba, že tam pracujem s dvoma premennými s rovnakým názvom =D

Hold, najväčší problém programátorov je vymýšlať názvy premenných a metód =D

před 10 lety

edke
Člen | 198
+
0
-

Zdar David,

mam problem na ktory som natrafil, ked som sa snazil implementovat tento tvoj sikovny snipplet. V nasledujucom kode:

final class FrontEnd_SandboxPresenter extends FrontEnd_BasePresenter
{

    public function actionDefault()
    {

        $email= $this->createTemplate();
        $email->setFile(Environment::expand('%templatesDir%/emails/activation.phtml'));
        $email->name = 'xxx';

        $email->headers = (object) null;


        #alternativa 1
        $body = (string) $email; // vyrenderujeme šablonu už nyní



        #alternativa 2
        ob_start();
        $email->render();
        $body= ob_get_clean();

Ak pouzijem alternativu 2, vsetko bezi ako treba. Ale ak pouzijem alternativu 1, po tomto riadku je problem s output bufferingom, nic (ani koncovy zdrojak, ani exceptions) uz sa do prehliadaca nevypise.

před 10 lety

kravčo
Člen | 723
+
0
-

edke napsal(a):

Ak pouzijem alternativu 2, vsetko bezi ako treba. Ale ak pouzijem alternativu 1, po tomto riadku je problem s output bufferingom, nic (ani koncovy zdrojak, ani exceptions) uz sa do prehliadaca nevypise.

Vyskúšaj alternatívu 1'

$body = $email->__toString(TRUE);

Robí to to isté ako (string) $email, akurát môže vyhadzovať výnimky. Keďže PHP nedovoľuje vyhodiť výnimku z automaticky zavolanej magickej funkcie __toString(), táto v Nette štandardne výnimky nevyhadzuje a vypisuje tuším prípadnú zachytenú výnimku ako E_USER_WARNING. V prípade, ak ju zavoláš priamo, výnimky nemajú problém a parameter TRUE zabezpečí, že sa výnimka nezachytí ale pustí ďalej. viď Nette\Template::__toString()

Editoval kravco (4. 2. 2009 13:48)

před 10 lety

edke
Člen | 198
+
0
-

kravco wrote:
Vyskúšaj alternatívu 1'

$body = $email->__toString(TRUE);

Robí to to isté ako (string) $email, akurát môže vyhadzovať výnimky. Keďže PHP nedovoľuje vyhodiť výnimku z automaticky zavolanej magickej funkcie __toString(), táto v Nette štandardne výnimky nevyhadzuje a vypisuje tuším prípadnú zachytenú výnimku ako E_USER_WARNING. V prípade, ak ju zavoláš priamo, výnimky nemajú problém a parameter TRUE zabezpečí, že sa výnimka nezachytí ale pustí ďalej. viď Nette\Template::__toString()

Jo, super, ked to zavolam takto priamo cez magic metodu, tak to ide ako ma. Co sa bude diat, ak pri vykonani sablony nastane chyba alebo exception ?

před 10 lety

Ondřej Mirtes
Člen | 1539
+
0
-

Super návod a elegance na n-tou! :)

Dokázal jsem to implementovat, jen mám takový mini-pidi problém:

Části kódu:

$mail->setSubject(Environment::getConfig('mail')->subject);

config.ini:

mail.subject = "Nová zpráva z kontaktního formuláře"

A nevím, jestli se někde něco přetypovává nebo escapuje, ale v e-mailu mi vyleze:

Novázpráva z kontaktníhoformuláře

Jako mailer používám tuto třídu:
http://www.phpguru.org/…meMail5.html

před 10 lety

Jod
Člen | 703
+
0
-

A Environment::getConfig(‚mail‘)->subject vracia správny, alebo zdeformovaný reťazec? Ak správny chyba neni v nette. Odporúčam používať Zend_Mail.

před 10 lety

Ondrej
Člen | 110
+
0
-

LastHunter napsal(a):
Jako mailer používám tuto třídu:
 http://www.phpguru.org/…meMail5.html

htmlMimeMail5 ma problemy s cestinou v subjektu

před 10 lety

Ondřej Mirtes
Člen | 1539
+
0
-

Jod, Ondrej: Jojo, koukam, Nette za to fakt nemuze :( Takze zkusim ten Zend_Mail, diky.

před 10 lety

A.
Člen | 87
+
0
-

Zkus dat predmet:
$mail->setSubject("=?utf-8?B?".base64_encode('žluťoučký kůň ...')."?=");

Editoval A. (17. 3. 2009 11:34)

před 10 lety

Tomik
Nette Evangelist | 489
+
0
-

V případě, že by Zend_Mail z nějakého důvodu nevyhovoval, doporučuju http://swiftmailer.org/ …

před 10 lety

amsys
Člen | 20
+
0
-

Zdravím,

potřeboval jsem odesílat trošku osobnější maily a tak jsem si splácal jeden pomocnej objekt tímto způsobem, jsem na správné cestě?

<?php

require LIBS_DIR . '/Zend/Mail.php';

class TemplateMail extends Control {

    private $t_name = '';
    private $identity;
    private $from;
    private $plain = '';
    private $delayed = false;

    public function __construct($template_name, IIdentity $user_identity, $delayed = false) {
        $this->t_name = $template_name;
        $this->identity = $user_identity;
        $this->delayed = $delayed;
    }

    protected function createTemplate(){
        $template = parent::createTemplate();
        $template->setFile(Environment::expand('%templatesDir%/Mail/') . $this->t_name . '_'. $this->identity->getLanguage() . '.phtml');
        $template->registerFilter('Nette\Templates\CurlyBracketsFilter::invoke');
        $template->options = (object) NULL;
        return $template;
    }

    public function getOutput(){
        return (string) $this->template;
    }

    public function getPlainOutput(){
        if (empty($this->plain)) {
            // http://www.talkincode.com/convert-html-to-ascii-with-php-143.html
            $s = preg_replace(array(
                    '/\n/', // strip all newlines
                    '/<a\s+.*?href="?mailto:([^\" >?]*)"?[^>]*>(.*?)<\/a>/i', // emails
                    '/<a\s+.*?href="?([^\" >]*)"?[^>]*>(.*?)<\/a>/i', // links
                    '@<(b|h)r[^>]*>@i', // br and hr tags
                    '@<p[^>]*>@i', // paragraphs
                    '@<div[^>]*>(.*)</div>@i', // boxes
                    '@<b[^>]*>(.*?)</b>@i', // bold tags
                    '@<strong[^>]*>(.*?)</strong>@i', // bold tags
                    '@<i[^>]*>(.*?)</i>@i', // italic tags
                    '@<em[^>]*>(.*?)</em>@i', // italic tags
                ),array(
                    '',
                    '$1',
                    '$2 ($1)',
                    "\n",
                    "\n\n",
                    '$1',
                    '*$1*',
                    '*$1*',
                    '_$1_',
                    '_$1_'
                ), $this->getOutput());
            // decode any entities
            $s = strtr($s,array_flip(get_html_translation_table(HTML_ENTITIES)));
            // decode numbered entities
            $s = preg_replace('/&#(\d+);/e','chr(str_replace(";","",str_replace("&#","","$0")))',$s);
            // strip any remaining HTML tags
            $this->plain = strip_tags($s);
        }
        // return the string
        return $this->plain;
    }

    public function render()
    {
        $this->template->render();
    }

    public function send()
    {
        $mail = new Zend_Mail('utf-8');

        $mail->setType(Zend_Mime::MULTIPART_RELATED);

        // embed logo
        $img = $mail->createAttachment(file_get_contents(WWW_DIR . '/images/logo-mail.gif'));
        $img->id = md5(uniqid(time()));
        $img->type = 'image/gif';
        $img->disposition = Zend_Mime::DISPOSITION_INLINE;
        $img->encoding = Zend_Mime::ENCODING_BASE64;
        $img->filename = 'logo.gif';

        $this->template->logo_cid = $img->id;

        $mail->setBodyHtml($this->getOutput(), NULL, Zend_Mime::MULTIPART_RELATED);
        $mail->setBodyText($this->getPlainOutput());

        $mail->setFrom(
            isset($this->template->admin_email) ? $this->template->admin_email : $this->template->options->email,
            isset($this->template->admin_name) ? $this->template->admin_name : $this->template->options->name
        );

        $mail->addTo($this->identity->getEmail(), $this->identity->getFullname());
        $mail->setSubject($this->template->options->subject);

        if ($this->delayed) {
            $filename = Environment::expand('%spoolDir%/mail/') . uniqid();
            $fh = fopen($filename, 'w');
            fwrite($fh, serialize($mail));
            fclose($fh);
        } else {
            $mail->send();
        }
    }
}

?>

Editoval amsys (9. 4. 2009 21:16)

před 10 lety

Jod
Člen | 703
+
0
-

Veď Thunderbird prílohy vždy zobrazuje pod správou.

před 10 lety

R2D2
Člen | 22
+
0
-

inline/embedded (či jak to chceš nazvat) přílohy samozřejmě thunderbird zobrazí správně v kódu, když jsou dobře poslané.

Se Zendem jsem je ale nikdy neposílal, jen s phpmailerem … zkontroluj v tom thunderbirdu zdrojovej kód, co to tam ve skutečnosti teda napíše (hlavička přílohy), nic konkrétního mě ale nenapadá

před 10 lety

amsys
Člen | 20
+
0
-

Se Zendem jsem je ale nikdy neposílal, jen s phpmailerem … zkontroluj v tom thunderbirdu zdrojovej kód, co to tam ve skutečnosti teda napíše (hlavička přílohy), nic konkrétního mě ale nenapadá

Thunderbird má ten obrázek se stejným boundary jako je ta html zpráva, Zend_Mail ničim jiným se to neliší takže teď už jenom přijít na to jak to spojit dohromady :-)

Už jsem na to přišel a upravil jsem kód v předchozím příspěvku

Až to budete také řešit tak zkoukněte tohle:

* http://www.anty.info/…l-obstacles/
* http://www.sitepoint.com/…newsletters/

Editoval amsys (9. 4. 2009 21:18)

před 10 lety

amsys
Člen | 20
+
0
-

Tak ještě to neni úplně ideální, thunderbird a webmaily to sice zobrazí, ale je tu jedna vyjímka v náhledu mailu u KMailu kde se ten obrázek nezobrazí (pokud mail otevřu tak už ano), ale mám jeden mail který jsem generoval přes Zend_Mail kdysi dávno kde se to povedlo, snažil jsem se najít rozdíly a historii souboru, ale bylo to marné hledání. Třeba za týden se na to přijde .-)

před 10 lety

phx
Člen | 652
+
0
-

Jakozto uzivatel TB bych spise rek, ze prilohy (obrazky) se zobrazi v/pod mailem kdyz je mail v HTML. Pokud jde o Plain text tak jsou dole ve specialnim okenku.

před 10 lety

amsys
Člen | 20
+
0
-

phx napsal(a):

Jakozto uzivatel TB bych spise rek, ze prilohy (obrazky) se zobrazi v/pod mailem kdyz je mail v HTML. Pokud jde o Plain text tak jsou dole ve specialnim okenku.

Poslední kód co jsem vkládal zobrazuje obrázky korektně v TB, Squirrelmail je taky ok po otevření html zprávy, u Outlooku zatím netuším, pouze vím, že v KMailu to zlobí a zobrazí se když ten mail otevřu a to korektně, ne mezi přilohami)

Protože se to nakonec nějak zobrazí tak to prozatím neřeším a k tomuto problému se vrátím až dohákuju problémy mnohem důležitější, jinak ten přístup který jsem zvolil html->plain by možná šel překopat na texy->html a bylo by to možná hezčí