Odesílání emailů podle šablony

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8111
+
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.

Jod
Člen | 701
+
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‘ `

romansklenar
Člen | 655
+
0
-

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

Jod
Člen | 701
+
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)

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)

Jod
Člen | 701
+
0
-

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

Jod
Člen | 701
+
0
-

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

Warning: Circular rendering detected ..

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 : )

Jod
Člen | 701
+
0
-

Šaq a koho má renderovať :D

LM
Člen | 206
+
0
-

Tak includuju se sama do sebe : )

Jod
Člen | 701
+
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

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.

kravčo
Člen | 721
+
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)

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 ?

Ondřej Mirtes
Člen | 1536
+
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

Jod
Člen | 701
+
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.

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

Ondřej Mirtes
Člen | 1536
+
0
-

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

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)

Tomik
Nette Evangelist | 485
+
0
-

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

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)

Jod
Člen | 701
+
0
-

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

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á

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)

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 .-)

phx
Člen | 651
+
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.

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čí