Browse Source

Add email serializer

Jad Bitar 11 years ago
parent
commit
e670f75dd4
2 changed files with 214 additions and 1 deletions
  1. 118 1
      src/Network/Email/Email.php
  2. 96 0
      tests/TestCase/Network/Email/EmailTest.php

+ 118 - 1
src/Network/Email/Email.php

@@ -23,8 +23,15 @@ use Cake\Log\Log;
 use Cake\Network\Http\FormData\Part;
 use Cake\Utility\Hash;
 use Cake\Utility\Text;
+use Closure;
+use Exception;
 use InvalidArgumentException;
+use JsonSerializable;
 use LogicException;
+use PDO;
+use RuntimeException;
+use Serializable;
+use SimpleXmlElement;
 
 /**
  * CakePHP email class.
@@ -40,7 +47,7 @@ use LogicException;
  * application sends.
  *
  */
-class Email
+class Email implements JsonSerializable, Serializable
 {
 
     use StaticConfigTrait;
@@ -1885,4 +1892,114 @@ class Email
         }
         return strtoupper($this->charset);
     }
+
+    /**
+     * Serializes the email object to a value that can be natively serialized and re-used
+     * to clone this email instance.
+     *
+     * It has certain limitations for viewVars that are good to know:
+     *
+     *    - ORM\Query executed and stored as resultset
+     *    - SimpleXmlElements stored as associative array
+     *    - Exceptions stored as strings
+     *    - Resources, \Closure and \PDO are not supported.
+     *
+     * @return array Serializable array of configuration properties.
+     * @throws \Exception When a view var object can not be properly serialized.
+     */
+    public function jsonSerialize()
+    {
+        $properties = [
+            '_to', '_from', '_sender', '_replyTo', '_cc', '_bcc', '_subject', '_returnPath', '_readReceipt',
+            '_template', '_layout', '_viewRender', '_viewVars', '_theme', '_helpers', '_emailFormat',
+            '_emailPattern', '_attachments', '_domain', '_messageId', '_headers', 'charset', 'headerCharset',
+        ];
+
+        $array = [];
+
+        foreach ($properties as $property) {
+            $array[$property] = $this->{$property};
+        }
+
+        array_walk($array['_attachments'], function (&$item, $key) {
+            if (!empty($item['file'])) {
+                $item['data'] = $this->_readFile($item['file']);
+                unset($item['file']);
+            }
+        });
+
+        array_walk_recursive($array['_viewVars'], [$this, '_checkViewVars']);
+
+        return array_filter($array, function ($i) {
+            return !is_array($i) && strlen($i) || !empty($i);
+        });
+    }
+
+    /**
+     * Iterates through hash to clean up and normalize.
+     *
+     * @param mixed $item Reference to the view var value.
+     * @param string $key View var key.
+     * @return void
+     */
+    protected function _checkViewVars(&$item, $key)
+    {
+        if ($item instanceof Exception) {
+            $item = (string)$item;
+        }
+
+        if (
+            is_resource($item) ||
+            $item instanceof Closure ||
+            $item instanceof PDO
+        ) {
+            throw new RuntimeException(sprintf(
+                'Failed serializing the `%s` %s in the `%s` view var',
+                is_resource($item) ? get_resource_type($item) : get_class($item),
+                is_resource($item) ? 'resource' : 'object',
+                $key
+            ));
+        }
+    }
+
+    /**
+     * Configures an email instance object from serialized config.
+     *
+     * @param array $config Email configuration array.
+     * @return \Cake\Network\Email\Email Configured email instance.
+     */
+    public function createFromArray($config)
+    {
+        foreach ($config as $property => $value) {
+            $this->{$property} = $value;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Serializes the Email object.
+     *
+     * @return void.
+     */
+    public function serialize()
+    {
+        $array = $this->jsonSerialize();
+        array_walk_recursive($array, function (&$item, $key) {
+            if ($item instanceof SimpleXmlElement) {
+                $item = json_decode(json_encode((array)$item), true);
+            }
+        });
+        return serialize($array);
+    }
+
+    /**
+     * Unserializes the Email object.
+     *
+     * @return void.
+     */
+    public function unserialize($data)
+    {
+        return $this->createFromArray(unserialize($data));
+    }
 }

+ 96 - 0
tests/TestCase/Network/Email/EmailTest.php

@@ -21,8 +21,10 @@ use Cake\Filesystem\File;
 use Cake\Log\Log;
 use Cake\Network\Email\DebugTransport;
 use Cake\Network\Email\Email;
+use Cake\ORM\TableRegistry;
 use Cake\TestSuite\TestCase;
 use Cake\View\Exception\MissingTemplateException;
+use SimpleXmlElement;
 
 /**
  * Help to test Email
@@ -88,6 +90,8 @@ class TestEmail extends Email
 class EmailTest extends TestCase
 {
 
+    public $fixtures = ['core.users'];
+
     /**
      * setUp
      *
@@ -2592,6 +2596,98 @@ HTML;
     }
 
     /**
+     * testJsonSerialize()
+     *
+     * @return void
+     */
+    public function testJsonSerialize()
+    {
+        $xmlstr = <<<XML
+<?xml version='1.0' standalone='yes'?>
+<framework>
+    <name>CakePHP</name>
+    <url>http://cakephp.org</url>
+</framework>
+XML;
+
+        $this->CakeEmail->reset()
+            ->to(['cakephp@cakephp.org' => 'CakePHP'])
+            ->from('noreply@cakephp.org')
+            ->replyTo('cakephp@cakephp.org')
+            ->cc(['mark@cakephp.org', 'juan@cakephp.org' => 'Juan Basso'])
+            ->bcc('phpnut@cakephp.org')
+            ->subject('Test Serialize')
+            ->template('default', 'test')
+            ->messageId('<uuid@server.com>')
+            ->domain('foo.bar')
+            ->viewVars([
+                'users' => TableRegistry::get('Users')->get(1, ['fields' => ['id', 'username']]),
+                'xml' => new SimpleXmlElement($xmlstr),
+                'exception' => new \Exception('test')
+            ])
+            ->attachments([
+                'test.txt' => TEST_APP . 'config' . DS . 'empty.ini',
+                'image' => [
+                    'data' => file_get_contents(TEST_APP . 'webroot' . DS . 'img' . DS . 'cake.icon.png'),
+                    'mimetype' => 'image/png'
+                ]
+            ]);
+
+        $result = json_decode(json_encode($this->CakeEmail), true);
+        $this->assertContains('test', $result['_viewVars']['exception']);
+        unset($result['_viewVars']['exception']);
+
+        $encode = function ($path) {
+            return chunk_split(base64_encode(file_get_contents($path)), 76, "\r\n");
+        };
+
+        $expected = [
+            '_to' => ['cakephp@cakephp.org' => 'CakePHP'],
+            '_from' => ['noreply@cakephp.org' => 'noreply@cakephp.org'],
+            '_replyTo' => ['cakephp@cakephp.org' => 'cakephp@cakephp.org'],
+            '_cc' => ['mark@cakephp.org' => 'mark@cakephp.org', 'juan@cakephp.org' => 'Juan Basso'],
+            '_bcc' => ['phpnut@cakephp.org' => 'phpnut@cakephp.org'],
+            '_subject' => 'Test Serialize',
+            '_template' => 'default',
+            '_layout' => 'test',
+            '_viewRender' => 'Cake\View\View',
+            '_helpers' => ['Html'],
+            '_emailFormat' => 'text',
+            '_messageId' => '<uuid@server.com>',
+            '_domain' => 'foo.bar',
+            'charset' => 'utf-8',
+            'headerCharset' => 'utf-8',
+            '_viewVars' => [
+                'users' => [
+                    'id' => 1,
+                    'username' => 'mariano'
+                ],
+                'xml' => [
+                    'name' => 'CakePHP',
+                    'url' => 'http://cakephp.org'
+                ],
+            ],
+            '_attachments' => [
+                'test.txt' => [
+                    'data' => $encode(TEST_APP . 'config' . DS . 'empty.ini'),
+                    'mimetype' => 'application/octet-stream'
+                ],
+                'image' => [
+                    'data' => $encode(TEST_APP . 'webroot' . DS . 'img' . DS . 'cake.icon.png'),
+                    'mimetype' => 'image/png'
+                ]
+            ],
+            '_emailPattern' => '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-.]+)$/ui'
+        ];
+        $this->assertEquals($expected, $result);
+
+        $result = json_decode(json_encode(unserialize(serialize($this->CakeEmail))), true);
+        $this->assertContains('test', $result['_viewVars']['exception']);
+        unset($result['_viewVars']['exception']);
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
      * CakeEmailTest::assertLineLengths()
      *
      * @param string $message