From b38c0e50a4d7d7f59a39807611cc4ef0babf5cfc Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 16 Oct 2011 15:41:59 -0430 Subject: [PATCH] Adding configuration options to CakeEmail to set the header encoding and body encoding, also translating all string from the App.encoding value to the configured charset if they differ. This makes possible to send correctly emails in japanese charset --- app/Config/email.php.default | 15 ++++- .../Templates/skel/Config/email.php.default | 15 ++++- lib/Cake/Network/Email/CakeEmail.php | 60 +++++++++++++++--- .../Test/Case/Network/Email/CakeEmailTest.php | 63 +++++++++++++++++++ 4 files changed, 138 insertions(+), 15 deletions(-) diff --git a/app/Config/email.php.default b/app/Config/email.php.default index bf2b4b51a..0d5d11a5b 100644 --- a/app/Config/email.php.default +++ b/app/Config/email.php.default @@ -30,6 +30,7 @@ * transport => The name of a supported transport; valid options are as follows: * Mail - Send using PHP mail function * Smtp - Send using SMTP + * Debug - Do not send the email, just return the result * * You can add custom transports (or override existing transports) by adding the * appropriate file to app/Network/Email. Transports should be named 'YourTransport.php', @@ -43,7 +44,9 @@ class EmailConfig { public $default = array( 'transport' => 'Mail', - 'from' => 'you@localhost' + 'from' => 'you@localhost', + //'charset' => 'utf-8', + //'headerCharset' => 'utf-8', ); public $smtp = array( @@ -54,7 +57,10 @@ class EmailConfig { 'timeout' => 30, 'username' => 'user', 'password' => 'secret', - 'client' => null + 'client' => null, + 'log' => false + //'charset' => 'utf-8', + //'headerCharset' => 'utf-8', ); public $fast = array( @@ -82,7 +88,10 @@ class EmailConfig { 'timeout' => 30, 'username' => 'user', 'password' => 'secret', - 'client' => null + 'client' => null, + 'log' => true, + //'charset' => 'utf-8', + //'headerCharset' => 'utf-8', ); } diff --git a/lib/Cake/Console/Templates/skel/Config/email.php.default b/lib/Cake/Console/Templates/skel/Config/email.php.default index bf2b4b51a..0d5d11a5b 100644 --- a/lib/Cake/Console/Templates/skel/Config/email.php.default +++ b/lib/Cake/Console/Templates/skel/Config/email.php.default @@ -30,6 +30,7 @@ * transport => The name of a supported transport; valid options are as follows: * Mail - Send using PHP mail function * Smtp - Send using SMTP + * Debug - Do not send the email, just return the result * * You can add custom transports (or override existing transports) by adding the * appropriate file to app/Network/Email. Transports should be named 'YourTransport.php', @@ -43,7 +44,9 @@ class EmailConfig { public $default = array( 'transport' => 'Mail', - 'from' => 'you@localhost' + 'from' => 'you@localhost', + //'charset' => 'utf-8', + //'headerCharset' => 'utf-8', ); public $smtp = array( @@ -54,7 +57,10 @@ class EmailConfig { 'timeout' => 30, 'username' => 'user', 'password' => 'secret', - 'client' => null + 'client' => null, + 'log' => false + //'charset' => 'utf-8', + //'headerCharset' => 'utf-8', ); public $fast = array( @@ -82,7 +88,10 @@ class EmailConfig { 'timeout' => 30, 'username' => 'user', 'password' => 'secret', - 'client' => null + 'client' => null, + 'log' => true, + //'charset' => 'utf-8', + //'headerCharset' => 'utf-8', ); } diff --git a/lib/Cake/Network/Email/CakeEmail.php b/lib/Cake/Network/Email/CakeEmail.php index 84220cd3c..a72a45ceb 100644 --- a/lib/Cake/Network/Email/CakeEmail.php +++ b/lib/Cake/Network/Email/CakeEmail.php @@ -239,12 +239,26 @@ class CakeEmail { protected $_transportClass = null; /** - * charset the email is sent in + * Charset the email body is sent in + * * * @var string */ public $charset = 'utf-8'; +/** + * Charset the email header is sent in + * If null, the $charset property will be used as default + * @var string + */ + public $headerCharset = null; + +/** + * The application wide charset, used to encode headers and body + * @var string + */ + public $_appCharset = null; + /** * List of files that should be attached to the email. * @@ -274,13 +288,16 @@ class CakeEmail { * */ public function __construct($config = null) { - $charset = Configure::read('App.encoding'); - if ($charset !== null) { - $this->charset = $charset; + $this->_appCharset = Configure::read('App.encoding'); + if ($this->_appCharset !== null) { + $this->charset = $this->_appCharset; } if ($config) { $this->config($config); } + if (empty($this->headerCharset)) { + $this->headerCharset = $this->charset; + } } /** @@ -994,15 +1011,24 @@ class CakeEmail { protected function _applyConfig($config) { if (is_string($config)) { if (!class_exists('EmailConfig') && !config('email')) { - throw new SocketException(__d('cake_dev', '%s not found.', APP . 'Config' . DS . 'email.php')); + throw new ConfigureException(__d('cake_dev', '%s not found.', APP . 'Config' . DS . 'email.php')); } $configs = new EmailConfig(); if (!isset($configs->{$config})) { - throw new SocketException(__d('cake_dev', 'Unknown email configuration "%s".', $config)); + throw new ConfigureException(__d('cake_dev', 'Unknown email configuration "%s".', $config)); } $config = $configs->{$config}; } $this->_config += $config; + if (!empty($config['charset'])) { + $this->charset = $config['charset']; + } + if (!empty($config['headerCharset'])) { + $this->headerCharset = $config['headerCharset']; + } + if (empty($this->headerCharset)) { + $this->headerCharset = $this->charset; + } $simpleMethods = array( 'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath', 'cc', 'bcc', 'messageId', 'subject', 'viewRender', 'viewVars', 'attachments', @@ -1073,15 +1099,31 @@ class CakeEmail { $internalEncoding = function_exists('mb_internal_encoding'); if ($internalEncoding) { $restore = mb_internal_encoding(); - mb_internal_encoding($this->charset); + mb_internal_encoding($this->_appCharset); } - $return = mb_encode_mimeheader($text, $this->charset, 'B'); + $text = $this->_encodeString($text, $this->headerCharset); + $return = mb_encode_mimeheader($text, $this->headerCharset, 'B'); if ($internalEncoding) { mb_internal_encoding($restore); } return $return; } +/** + * Translates a string for one charset to another if the App.encoding value + * differs and the mb_convert_encoding function exists + * + * @param string $text The text to be converted + * @param string $charset the target encoding + * @return string + */ + protected function _encodeString($text, $charset) { + if ($this->_appCharset === $charset || !function_exists('mb_convert_encoding')) { + return $text; + } + return mb_convert_encoding($text, $charset, $this->_appCharset); + } + /** * Wrap the message to follow the RFC 2822 - 2.1.1 * @@ -1322,7 +1364,7 @@ class CakeEmail { $View->viewPath = $View->layoutPath = 'Emails' . DS . $this->_emailFormat; $View->viewVars['content'] = $content; - $rendered = $View->render($template, $layout); + $rendered = $this->_encodeString($View->render($template, $layout), $this->charset); $content = explode("\n", $rendered); if ($this->_emailFormat === 'html') { diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index ebce09cef..c4523eb29 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -1120,4 +1120,67 @@ class CakeEmailTest extends CakeTestCase { $result = $this->CakeEmail->emailFormat('invalid'); } +/** + * Tests that it is possible to add charset configuration to a CakeEmail object + * + * @return void + */ + public function testConfigCharset() { + $email = new CakeEmail(); + $this->assertEquals($email->charset, Configure::read('App.encoding')); + $this->assertEquals($email->headerCharset, Configure::read('App.encoding')); + + $email = new CakeEmail(array('charset' => 'iso-2022-jp', 'headerCharset' => 'iso-2022-jp-ms')); + $this->assertEquals($email->charset, 'iso-2022-jp'); + $this->assertEquals($email->headerCharset, 'iso-2022-jp-ms'); + + $email = new CakeEmail(array('charset' => 'iso-2022-jp')); + $this->assertEquals($email->charset, 'iso-2022-jp'); + $this->assertEquals($email->headerCharset, 'iso-2022-jp'); + + $email = new CakeEmail(array('headerCharset' => 'iso-2022-jp-ms')); + $this->assertEquals($email->charset, Configure::read('App.encoding')); + $this->assertEquals($email->headerCharset, 'iso-2022-jp-ms'); + } + +/** + * Tests that the header is encoded using the configured headerCharset + * + * @return void + */ + public function testHeaderEncoding() { + $this->skipIf(!function_exists('mb_convert_encoding')); + $email = new CakeEmail(array('headerCharset' => 'iso-2022-jp-ms', 'transport' => 'Debug')); + $email->subject('あれ?もしかしての前と'); + $headers = $email->getHeaders(array('subject')); + $expected = "?ISO-2022-JP?B?GyRCJCIkbCEpJGIkNyQrJDckRiROQTAkSBsoQg==?="; + $this->assertContains($expected, $headers['Subject']); + + $email->to('someone@example.com')->from('someone@example.com'); + $result = $email->send('ってテーブルを作ってやってたらう'); + $this->assertContains('ってテーブルを作ってやってたらう', $result['message']); + } + +/** + * Tests that the body is encoded using the configured charset + * + * @return void + */ + public function testBodyEncoding() { + $this->skipIf(!function_exists('mb_convert_encoding')); + $email = new CakeEmail(array( + 'charset' => 'iso-2022-jp', + 'headerCharset' => 'iso-2022-jp-ms', + 'transport' => 'Debug' + )); + $email->subject('あれ?もしかしての前と'); + $headers = $email->getHeaders(array('subject')); + $expected = "?ISO-2022-JP?B?GyRCJCIkbCEpJGIkNyQrJDckRiROQTAkSBsoQg==?="; + $this->assertContains($expected, $headers['Subject']); + + $email->to('someone@example.com')->from('someone@example.com'); + $result = $email->send('ってテーブルを作ってやってたらう'); + $this->assertContains('Content-Type: text/plain; charset=iso-2022-jp', $result['headers']); + $this->assertContains('ってテーブルを作ってやってたらう', $result['message']); + } }