Skip to content

Commit e670f75

Browse files
committed
Add email serializer
1 parent ade0190 commit e670f75

File tree

2 files changed

+214
-1
lines changed

2 files changed

+214
-1
lines changed

src/Network/Email/Email.php

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,15 @@
2323
use Cake\Network\Http\FormData\Part;
2424
use Cake\Utility\Hash;
2525
use Cake\Utility\Text;
26+
use Closure;
27+
use Exception;
2628
use InvalidArgumentException;
29+
use JsonSerializable;
2730
use LogicException;
31+
use PDO;
32+
use RuntimeException;
33+
use Serializable;
34+
use SimpleXmlElement;
2835

2936
/**
3037
* CakePHP email class.
@@ -40,7 +47,7 @@
4047
* application sends.
4148
*
4249
*/
43-
class Email
50+
class Email implements JsonSerializable, Serializable
4451
{
4552

4653
use StaticConfigTrait;
@@ -1885,4 +1892,114 @@ protected function _getContentTypeCharset()
18851892
}
18861893
return strtoupper($this->charset);
18871894
}
1895+
1896+
/**
1897+
* Serializes the email object to a value that can be natively serialized and re-used
1898+
* to clone this email instance.
1899+
*
1900+
* It has certain limitations for viewVars that are good to know:
1901+
*
1902+
* - ORM\Query executed and stored as resultset
1903+
* - SimpleXmlElements stored as associative array
1904+
* - Exceptions stored as strings
1905+
* - Resources, \Closure and \PDO are not supported.
1906+
*
1907+
* @return array Serializable array of configuration properties.
1908+
* @throws \Exception When a view var object can not be properly serialized.
1909+
*/
1910+
public function jsonSerialize()
1911+
{
1912+
$properties = [
1913+
'_to', '_from', '_sender', '_replyTo', '_cc', '_bcc', '_subject', '_returnPath', '_readReceipt',
1914+
'_template', '_layout', '_viewRender', '_viewVars', '_theme', '_helpers', '_emailFormat',
1915+
'_emailPattern', '_attachments', '_domain', '_messageId', '_headers', 'charset', 'headerCharset',
1916+
];
1917+
1918+
$array = [];
1919+
1920+
foreach ($properties as $property) {
1921+
$array[$property] = $this->{$property};
1922+
}
1923+
1924+
array_walk($array['_attachments'], function (&$item, $key) {
1925+
if (!empty($item['file'])) {
1926+
$item['data'] = $this->_readFile($item['file']);
1927+
unset($item['file']);
1928+
}
1929+
});
1930+
1931+
array_walk_recursive($array['_viewVars'], [$this, '_checkViewVars']);
1932+
1933+
return array_filter($array, function ($i) {
1934+
return !is_array($i) && strlen($i) || !empty($i);
1935+
});
1936+
}
1937+
1938+
/**
1939+
* Iterates through hash to clean up and normalize.
1940+
*
1941+
* @param mixed $item Reference to the view var value.
1942+
* @param string $key View var key.
1943+
* @return void
1944+
*/
1945+
protected function _checkViewVars(&$item, $key)
1946+
{
1947+
if ($item instanceof Exception) {
1948+
$item = (string)$item;
1949+
}
1950+
1951+
if (
1952+
is_resource($item) ||
1953+
$item instanceof Closure ||
1954+
$item instanceof PDO
1955+
) {
1956+
throw new RuntimeException(sprintf(
1957+
'Failed serializing the `%s` %s in the `%s` view var',
1958+
is_resource($item) ? get_resource_type($item) : get_class($item),
1959+
is_resource($item) ? 'resource' : 'object',
1960+
$key
1961+
));
1962+
}
1963+
}
1964+
1965+
/**
1966+
* Configures an email instance object from serialized config.
1967+
*
1968+
* @param array $config Email configuration array.
1969+
* @return \Cake\Network\Email\Email Configured email instance.
1970+
*/
1971+
public function createFromArray($config)
1972+
{
1973+
foreach ($config as $property => $value) {
1974+
$this->{$property} = $value;
1975+
}
1976+
1977+
return $this;
1978+
}
1979+
1980+
/**
1981+
* Serializes the Email object.
1982+
*
1983+
* @return void.
1984+
*/
1985+
public function serialize()
1986+
{
1987+
$array = $this->jsonSerialize();
1988+
array_walk_recursive($array, function (&$item, $key) {
1989+
if ($item instanceof SimpleXmlElement) {
1990+
$item = json_decode(json_encode((array)$item), true);
1991+
}
1992+
});
1993+
return serialize($array);
1994+
}
1995+
1996+
/**
1997+
* Unserializes the Email object.
1998+
*
1999+
* @return void.
2000+
*/
2001+
public function unserialize($data)
2002+
{
2003+
return $this->createFromArray(unserialize($data));
2004+
}
18882005
}

tests/TestCase/Network/Email/EmailTest.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
use Cake\Log\Log;
2222
use Cake\Network\Email\DebugTransport;
2323
use Cake\Network\Email\Email;
24+
use Cake\ORM\TableRegistry;
2425
use Cake\TestSuite\TestCase;
2526
use Cake\View\Exception\MissingTemplateException;
27+
use SimpleXmlElement;
2628

2729
/**
2830
* Help to test Email
@@ -88,6 +90,8 @@ public function render($content)
8890
class EmailTest extends TestCase
8991
{
9092

93+
public $fixtures = ['core.users'];
94+
9195
/**
9296
* setUp
9397
*
@@ -2591,6 +2595,98 @@ public function testZeroOnlyLinesNotBeingEmptied()
25912595
$this->assertEquals($expected, $result['message']);
25922596
}
25932597

2598+
/**
2599+
* testJsonSerialize()
2600+
*
2601+
* @return void
2602+
*/
2603+
public function testJsonSerialize()
2604+
{
2605+
$xmlstr = <<<XML
2606+
<?xml version='1.0' standalone='yes'?>
2607+
<framework>
2608+
<name>CakePHP</name>
2609+
<url>http://cakephp.org</url>
2610+
</framework>
2611+
XML;
2612+
2613+
$this->CakeEmail->reset()
2614+
->to(['cakephp@cakephp.org' => 'CakePHP'])
2615+
->from('noreply@cakephp.org')
2616+
->replyTo('cakephp@cakephp.org')
2617+
->cc(['mark@cakephp.org', 'juan@cakephp.org' => 'Juan Basso'])
2618+
->bcc('phpnut@cakephp.org')
2619+
->subject('Test Serialize')
2620+
->template('default', 'test')
2621+
->messageId('<uuid@server.com>')
2622+
->domain('foo.bar')
2623+
->viewVars([
2624+
'users' => TableRegistry::get('Users')->get(1, ['fields' => ['id', 'username']]),
2625+
'xml' => new SimpleXmlElement($xmlstr),
2626+
'exception' => new \Exception('test')
2627+
])
2628+
->attachments([
2629+
'test.txt' => TEST_APP . 'config' . DS . 'empty.ini',
2630+
'image' => [
2631+
'data' => file_get_contents(TEST_APP . 'webroot' . DS . 'img' . DS . 'cake.icon.png'),
2632+
'mimetype' => 'image/png'
2633+
]
2634+
]);
2635+
2636+
$result = json_decode(json_encode($this->CakeEmail), true);
2637+
$this->assertContains('test', $result['_viewVars']['exception']);
2638+
unset($result['_viewVars']['exception']);
2639+
2640+
$encode = function ($path) {
2641+
return chunk_split(base64_encode(file_get_contents($path)), 76, "\r\n");
2642+
};
2643+
2644+
$expected = [
2645+
'_to' => ['cakephp@cakephp.org' => 'CakePHP'],
2646+
'_from' => ['noreply@cakephp.org' => 'noreply@cakephp.org'],
2647+
'_replyTo' => ['cakephp@cakephp.org' => 'cakephp@cakephp.org'],
2648+
'_cc' => ['mark@cakephp.org' => 'mark@cakephp.org', 'juan@cakephp.org' => 'Juan Basso'],
2649+
'_bcc' => ['phpnut@cakephp.org' => 'phpnut@cakephp.org'],
2650+
'_subject' => 'Test Serialize',
2651+
'_template' => 'default',
2652+
'_layout' => 'test',
2653+
'_viewRender' => 'Cake\View\View',
2654+
'_helpers' => ['Html'],
2655+
'_emailFormat' => 'text',
2656+
'_messageId' => '<uuid@server.com>',
2657+
'_domain' => 'foo.bar',
2658+
'charset' => 'utf-8',
2659+
'headerCharset' => 'utf-8',
2660+
'_viewVars' => [
2661+
'users' => [
2662+
'id' => 1,
2663+
'username' => 'mariano'
2664+
],
2665+
'xml' => [
2666+
'name' => 'CakePHP',
2667+
'url' => 'http://cakephp.org'
2668+
],
2669+
],
2670+
'_attachments' => [
2671+
'test.txt' => [
2672+
'data' => $encode(TEST_APP . 'config' . DS . 'empty.ini'),
2673+
'mimetype' => 'application/octet-stream'
2674+
],
2675+
'image' => [
2676+
'data' => $encode(TEST_APP . 'webroot' . DS . 'img' . DS . 'cake.icon.png'),
2677+
'mimetype' => 'image/png'
2678+
]
2679+
],
2680+
'_emailPattern' => '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-.]+)$/ui'
2681+
];
2682+
$this->assertEquals($expected, $result);
2683+
2684+
$result = json_decode(json_encode(unserialize(serialize($this->CakeEmail))), true);
2685+
$this->assertContains('test', $result['_viewVars']['exception']);
2686+
unset($result['_viewVars']['exception']);
2687+
$this->assertEquals($expected, $result);
2688+
}
2689+
25942690
/**
25952691
* CakeEmailTest::assertLineLengths()
25962692
*

0 commit comments

Comments
 (0)