├── views └── activeMessage.php ├── CHANGELOG.md ├── ActiveMessageEvent.php ├── composer.json ├── TemplateStorage.php ├── LICENSE.md ├── TemplateStoragePhp.php ├── TemplateStorageActiveRecord.php ├── TemplateStorageMongoDb.php ├── TemplateStorageDb.php ├── TemplateModelFinder.php ├── README.md └── ActiveMessage.php /views/activeMessage.php: -------------------------------------------------------------------------------- 1 | getBodyHtml(); 6 | ?> -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Yii 2 ActiveMail extension Change Log 2 | ===================================== 3 | 4 | 1.0.0, April 24, 2017 5 | --------------------- 6 | 7 | - Initial release. 8 | -------------------------------------------------------------------------------- /ActiveMessageEvent.php: -------------------------------------------------------------------------------- 1 | 18 | * @since 1.0 19 | */ 20 | class ActiveMessageEvent extends ModelEvent 21 | { 22 | /** 23 | * @var \yii\mail\MessageInterface mail message instance. 24 | */ 25 | public $mailMessage; 26 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yii2tech/activemail", 3 | "description": "Project installation support extension for the Yii2 framework", 4 | "keywords": ["yii2", "mail", "email", "active", "model", "template", "db", "database", "mongodb"], 5 | "type": "yii2-extension", 6 | "license": "BSD-3-Clause", 7 | "support": { 8 | "issues": "https://github.com/yii2tech/activemail/issues", 9 | "forum": "http://www.yiiframework.com/forum/", 10 | "wiki": "https://github.com/yii2tech/activemail/wiki", 11 | "source": "https://github.com/yii2tech/activemail" 12 | }, 13 | "authors": [ 14 | { 15 | "name": "Paul Klimov", 16 | "email": "klimov.paul@gmail.com" 17 | } 18 | ], 19 | "require": { 20 | "yiisoft/yii2": "*" 21 | }, 22 | "repositories": [ 23 | { 24 | "type": "composer", 25 | "url": "https://asset-packagist.org" 26 | } 27 | ], 28 | "suggest": { 29 | "yiisoft/yii2-swiftmailer": "actual mailer implementation such as this is required", 30 | "yiisoft/yii2-mongodb": "you need this package, if you wish to store email templates in MongoDB" 31 | }, 32 | "autoload": { 33 | "psr-4": { "yii2tech\\activemail\\": "" } 34 | }, 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "1.0.x-dev" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TemplateStorage.php: -------------------------------------------------------------------------------- 1 | 16 | * @since 1.0 17 | */ 18 | abstract class TemplateStorage extends Component 19 | { 20 | /** 21 | * @var array found templates cache in format: templateName => templateData 22 | */ 23 | private $_templates = []; 24 | 25 | /** 26 | * Returns the template data fro the given name. 27 | * @param string $name template name. 28 | * @param boolean $refresh whether to ignore cache. 29 | * @return array|null template data. 30 | */ 31 | public function getTemplate($name, $refresh = false) 32 | { 33 | if ($refresh || !array_key_exists($name, $this->_templates)) { 34 | $this->_templates[$name] = $this->findTemplate($name); 35 | } 36 | return $this->_templates[$name]; 37 | } 38 | 39 | /** 40 | * Finds the actual template data in the storage. 41 | * @param string $name template name. 42 | * @return array|null template data. 43 | */ 44 | abstract protected function findTemplate($name); 45 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The Yii framework is free software. It is released under the terms of 2 | the following BSD License. 3 | 4 | Copyright © 2015 by Yii2tech (https://github.com/yii2tech) 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Yii2tech nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /TemplateStoragePhp.php: -------------------------------------------------------------------------------- 1 | 'Contact message', 24 | * 'htmlBody' => 'Contact inquiry:
{message}', 25 | * ]; 26 | * ``` 27 | * 28 | * @author Paul Klimov 29 | * @since 1.0 30 | */ 31 | class TemplateStoragePhp extends TemplateStorage 32 | { 33 | /** 34 | * @var string template file path. 35 | * By default "@app/mail/templates" is used. 36 | */ 37 | public $templatePath = '@app/mail/templates'; 38 | 39 | 40 | /** 41 | * @inheritdoc 42 | */ 43 | protected function findTemplate($name) 44 | { 45 | $templateFile = Yii::getAlias($this->templatePath) . DIRECTORY_SEPARATOR . $name . '.php'; 46 | if (file_exists($templateFile)) { 47 | $template = require $templateFile; 48 | if (!is_array($template)) { 49 | throw new Exception("Unable to get template from file '{$templateFile}': file should return array, '" . gettype($template) . "' returned instead."); 50 | } 51 | return $template; 52 | } 53 | return null; 54 | } 55 | } -------------------------------------------------------------------------------- /TemplateStorageActiveRecord.php: -------------------------------------------------------------------------------- 1 | 17 | * @since 1.0 18 | */ 19 | class TemplateStorageActiveRecord extends TemplateStorage 20 | { 21 | /** 22 | * @var string name of the ActiveRecord class, which should be used for template finding. 23 | * This class should match [[\yii\db\ActiveRecordInterface]] interface. 24 | */ 25 | public $activeRecordClass; 26 | /** 27 | * @var array list of ActiveRecord attributes, which should compose the template data. 28 | * Only these fields will be selected while querying template row. 29 | * You may adjust fields list according to the actual ActiveRecord class. 30 | */ 31 | public $templateDataAttributes = ['subject', 'bodyHtml']; 32 | /** 33 | * @var string name of the ActiveRecord attribute, which stores the template name. 34 | */ 35 | public $templateNameAttribute = 'name'; 36 | 37 | 38 | /** 39 | * @inheritdoc 40 | */ 41 | protected function findTemplate($name) 42 | { 43 | /* @var $activeRecordClass \yii\db\ActiveRecordInterface */ 44 | $activeRecordClass = $this->activeRecordClass; 45 | $templateModel = $activeRecordClass::findOne([$this->templateNameAttribute => $name]); 46 | 47 | if (!is_object($templateModel)) { 48 | return null; 49 | } 50 | $template = []; 51 | foreach ($this->templateDataAttributes as $attribute) { 52 | $template[$attribute] = $templateModel->$attribute; 53 | } 54 | 55 | return $template; 56 | } 57 | } -------------------------------------------------------------------------------- /TemplateStorageMongoDb.php: -------------------------------------------------------------------------------- 1 | 21 | * @since 1.0 22 | */ 23 | class TemplateStorageMongoDb extends TemplateStorage 24 | { 25 | /** 26 | * @var Connection|array|string the MongoDB connection object or the application component ID of the MongoDB connection. 27 | * After the TemplateStorageMongoDb object is created, if you want to change this property, you should only assign it 28 | * with a MongoDB connection object. 29 | */ 30 | public $db = 'mongodb'; 31 | /** 32 | * @var string|array name of the MongoDB collection, which stores email templates. 33 | */ 34 | public $templateCollection = 'EmailTemplate'; 35 | /** 36 | * @var array list of mail template collection fields, which should compose the template data. 37 | * Only these fields will be selected while querying template row. 38 | * You may adjust fields list according to the actual collection data. 39 | */ 40 | public $templateDataFields = ['subject', 'bodyHtml']; 41 | /** 42 | * @var string name of the mail template collection field, which stores the template name. 43 | */ 44 | public $templateNameField = 'name'; 45 | 46 | 47 | /** 48 | * @inheritdoc 49 | */ 50 | public function init() 51 | { 52 | parent::init(); 53 | $this->db = Instance::ensure($this->db, Connection::className()); 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | protected function findTemplate($name) 60 | { 61 | $query = new Query(); 62 | $template = $query 63 | ->select($this->templateDataFields) 64 | ->from($this->templateCollection) 65 | ->where([$this->templateNameField => $name]) 66 | ->one(); 67 | 68 | if ($template === false) { 69 | return null; 70 | } 71 | return $template; 72 | } 73 | } -------------------------------------------------------------------------------- /TemplateStorageDb.php: -------------------------------------------------------------------------------- 1 | createTable('EmailTemplate', [ 22 | * 'name' => 'string', 23 | * 'subject' => 'string', 24 | * 'bodyHtml' => 'text', 25 | * 'PRIMARY KEY (name)', 26 | * ]); 27 | * ``` 28 | * 29 | * @author Paul Klimov 30 | * @since 1.0 31 | */ 32 | class TemplateStorageDb extends TemplateStorage 33 | { 34 | /** 35 | * @var Connection|array|string the DB connection object or the application component ID of the DB connection. 36 | * After the TemplateStorageDb object is created, if you want to change this property, you should only assign it 37 | * with a DB connection object. 38 | */ 39 | public $db = 'db'; 40 | /** 41 | * @var string name of the table, which stores email templates. 42 | */ 43 | public $templateTable = 'EmailTemplate'; 44 | /** 45 | * @var array list of mail template table fields, which should compose the template data. 46 | * Only these fields will be selected while querying template row. 47 | * You may adjust fields list according to the actual table schema. 48 | */ 49 | public $templateDataFields = ['subject', 'bodyHtml']; 50 | /** 51 | * @var string name of the mail template table field, which stores the template name. 52 | */ 53 | public $templateNameField = 'name'; 54 | 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | public function init() 60 | { 61 | parent::init(); 62 | $this->db = Instance::ensure($this->db, Connection::className()); 63 | } 64 | 65 | /** 66 | * @inheritdoc 67 | */ 68 | protected function findTemplate($name) 69 | { 70 | $query = new Query(); 71 | $template = $query 72 | ->select($this->templateDataFields) 73 | ->from($this->templateTable) 74 | ->where([$this->templateNameField => $name]) 75 | ->one(); 76 | 77 | if ($template === false) { 78 | return null; 79 | } 80 | return $template; 81 | } 82 | } -------------------------------------------------------------------------------- /TemplateModelFinder.php: -------------------------------------------------------------------------------- 1 | 21 | * @since 1.0 22 | */ 23 | class TemplateModelFinder extends Component 24 | { 25 | /** 26 | * @var string namespace which under all active message classes declared. 27 | */ 28 | public $activeMessageNamespace = 'app\mail\active'; 29 | /** 30 | * @var string path to directory, which contains active message classes source files. 31 | * If not set path composed from [[activeMessageNamespace]] will be used. 32 | */ 33 | public $activeMessageFilePath; 34 | /** 35 | * @var TemplateStorage|string|array the mail template storage object or the application component ID. 36 | * After the TemplateModelFinder object is created, if you want to change this property, you should only assign it 37 | * with an object. 38 | */ 39 | public $mailTemplateStorage = 'mailTemplateStorage'; 40 | /** 41 | * @var string name of the ActiveRecord class, which should be used for template finding. 42 | * This class should match [[\yii\db\ActiveRecordInterface]] interface. 43 | * If not set and [[mailTemplateStorage]] refers to [[TemplateStorageActiveRecord]], this value as well as 44 | * [[templateDataAttributes]] and [[templateNameAttribute]] will be copied from the storage instance. 45 | */ 46 | public $activeRecordClass; 47 | /** 48 | * @var array list of ActiveRecord attributes, which should compose the template data. 49 | * Only these fields will be selected while querying template row. 50 | * You may adjust fields list according to the actual ActiveRecord class. 51 | */ 52 | public $templateDataAttributes = ['subject', 'bodyHtml']; 53 | /** 54 | * @var string name of the ActiveRecord attribute, which stores the template name. 55 | */ 56 | public $templateNameAttribute = 'name'; 57 | 58 | 59 | /** 60 | * @inheritdoc 61 | */ 62 | public function init() 63 | { 64 | parent::init(); 65 | $this->mailTemplateStorage = Instance::ensure($this->mailTemplateStorage, TemplateStorage::className()); 66 | 67 | if ($this->mailTemplateStorage instanceof TemplateStorageActiveRecord) { 68 | if ($this->activeRecordClass === null) { 69 | $this->activeRecordClass = $this->mailTemplateStorage->activeRecordClass; 70 | $this->templateDataAttributes = $this->mailTemplateStorage->templateDataAttributes; 71 | $this->templateNameAttribute = $this->mailTemplateStorage->templateNameAttribute; 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * Finds all active messages available for this application. 78 | * @return ActiveMessage[] list of active messages. 79 | */ 80 | public function findAllActiveMessages() 81 | { 82 | $activeMessageNamespace = trim($this->activeMessageNamespace, '\\'); 83 | if (empty($this->activeMessageFilePath)) { 84 | $activeMessageFilePath = Yii::getAlias('@' . str_replace('\\', DIRECTORY_SEPARATOR, $this->activeMessageNamespace)); 85 | } else { 86 | $activeMessageFilePath = Yii::getAlias($this->activeMessageFilePath); 87 | } 88 | 89 | $files = FileHelper::findFiles($activeMessageFilePath, ['only' => ['*.php']]); 90 | $activeMessages = []; 91 | foreach ($files as $file) { 92 | $className = $activeMessageNamespace . '\\' . basename($file, '.php'); 93 | try { 94 | $reflection = new \ReflectionClass($className); 95 | } catch (\Exception $exception) { 96 | continue; 97 | } 98 | if ($reflection->isAbstract() || !$reflection->isSubclassOf(ActiveMessage::className())) { 99 | continue; 100 | } 101 | $activeMessages[] = $reflection->newInstance(); 102 | } 103 | return $activeMessages; 104 | } 105 | 106 | /** 107 | * Finds active message by template name. 108 | * @param string $templateName template name. 109 | * @return ActiveMessage|null active message instance. 110 | */ 111 | public function findActiveMessage($templateName) 112 | { 113 | $activeMessages = $this->findAllActiveMessages(); 114 | foreach ($activeMessages as $activeMessage) { 115 | if ($activeMessage->templateName() === $templateName) { 116 | return $activeMessage; 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | /** 123 | * Finds template models for all available template names. 124 | * If existing model not found, new one will be created. 125 | * @return \yii\db\ActiveRecordInterface[] list of template models. 126 | * @throws InvalidConfigException on invalid configuration. 127 | */ 128 | public function findAllTemplateModels() 129 | { 130 | $activeMessages = $this->findAllActiveMessages(); 131 | if (empty($activeMessages)) { 132 | return []; 133 | } 134 | 135 | $existingMailTemplateModels = $this->createQuery()->all(); 136 | 137 | return $this->combineActiveMessagesWithTemplates($activeMessages, $existingMailTemplateModels); 138 | } 139 | 140 | /** 141 | * Finds template model for specified template name. 142 | * If existing model not found, new one will be created. 143 | * @param string $templateName mail template name. 144 | * @return \yii\db\ActiveRecordInterface|null template model instance. 145 | */ 146 | public function findTemplateModel($templateName) 147 | { 148 | $activeMessage = $this->findActiveMessage($templateName); 149 | if (!is_object($activeMessage)) { 150 | return null; 151 | } 152 | $existingMailTemplateModels = $this->createQuery()->where([$this->templateNameAttribute => $templateName])->all(); 153 | $models = $this->combineActiveMessagesWithTemplates([$activeMessage], $existingMailTemplateModels); 154 | return array_shift($models); 155 | } 156 | 157 | /** 158 | * Creates ActiveQuery for [[activeRecordClass]]. 159 | * @return \yii\db\ActiveQueryInterface active query instance. 160 | * @throws InvalidConfigException on invalid configuration. 161 | */ 162 | protected function createQuery() 163 | { 164 | /* @var $activeRecordClass \yii\db\ActiveRecordInterface */ 165 | $activeRecordClass = $this->activeRecordClass; 166 | if (empty($activeRecordClass)) { 167 | throw new InvalidConfigException('"' . get_class($this) . '::activeRecordClass" should be specified.'); 168 | } 169 | return $activeRecordClass::find(); 170 | } 171 | 172 | /** 173 | * Combines active messages with template models, ensuring template model for each active message. 174 | * @param ActiveMessage[] $activeMessages active messages. 175 | * @param \yii\db\ActiveRecordInterface[] $templateModels existing template models. 176 | * @return \yii\db\ActiveRecordInterface[] list of template models. 177 | * @throws InvalidConfigException on invalid configuration. 178 | */ 179 | protected function combineActiveMessagesWithTemplates(array $activeMessages, array $templateModels) 180 | { 181 | $templateNameAttribute = $this->templateNameAttribute; 182 | if (empty($templateNameAttribute)) { 183 | throw new InvalidConfigException('"' . get_class($this) . '::templateNameAttribute" should be specified.'); 184 | } 185 | $result = []; 186 | foreach ($activeMessages as $activeMessage) { 187 | $matchFound = false; 188 | foreach ($templateModels as $existingTemplateModelKey => $existingTemplateModel) { 189 | if ($existingTemplateModel->{$templateNameAttribute} == $activeMessage->templateName()) { 190 | $result[] = $existingTemplateModel; 191 | unset($templateModels[$existingTemplateModelKey]); 192 | $matchFound = true; 193 | break; 194 | } 195 | } 196 | if (!$matchFound) { 197 | $newTemplateModel = new $this->activeRecordClass(); 198 | foreach ($this->templateDataAttributes as $attribute) { 199 | $newTemplateModel->$attribute = $activeMessage->$attribute; 200 | } 201 | $result[] = $newTemplateModel; 202 | } 203 | } 204 | return $result; 205 | } 206 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

ActiveMail Extension for Yii 2

6 |
7 |

8 | 9 | This extension provides 'active mail message' concept implementation for Yii2. 10 | Active message is a model, which knows all necessary data for self composition and can send itself. 11 | 12 | For license information check the [LICENSE](LICENSE.md)-file. 13 | 14 | [![Latest Stable Version](https://poser.pugx.org/yii2tech/activemail/v/stable.png)](https://packagist.org/packages/yii2tech/activemail) 15 | [![Total Downloads](https://poser.pugx.org/yii2tech/activemail/downloads.png)](https://packagist.org/packages/yii2tech/activemail) 16 | [![Build Status](https://travis-ci.org/yii2tech/activemail.svg?branch=master)](https://travis-ci.org/yii2tech/activemail) 17 | 18 | 19 | Requirements 20 | ------------ 21 | 22 | This extension requires any implementation of the Yii2 mailer, such as [yiisoft/yii2-swiftmailer](https://github.com/yiisoft/yii2-swiftmailer). 23 | 24 | 25 | Installation 26 | ------------ 27 | 28 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 29 | 30 | Either run 31 | 32 | ``` 33 | php composer.phar require --prefer-dist yii2tech/activemail 34 | ``` 35 | 36 | or add 37 | 38 | ```json 39 | "yii2tech/activemail": "*" 40 | ``` 41 | 42 | to the require section of your composer.json. 43 | 44 | > Note: you should install particular mailer extension such as 'yiisoft/yii2-swiftmailer' separately. 45 | 46 | 47 | Usage 48 | ----- 49 | 50 | This extension provides 'active mail message' concept implementation for Yii2. 51 | ActiveMessage is a model, which knows all necessary data for self composition and can send itself. 52 | It allows email message composition based on templates stored inside PHP files or database. 53 | 54 | In order to use this extension you need to add mail template storage component to your application: 55 | 56 | ```php 57 | return [ 58 | 'components' => [ 59 | 'mailTemplateStorage' => [ 60 | 'class' => 'yii2tech\activemail\TemplateStoragePhp', 61 | 'templatePath' => '@app/mail/templates', 62 | ], 63 | // ... 64 | ], 65 | // ... 66 | ]; 67 | ``` 68 | 69 | 70 | ## ActiveMessage 71 | 72 | Each particular active message should extend [[\yii2tech\activemail\ActiveMessage]] class, implementing at least 73 | all abstract methods, which guarantees particular active message has default values for each necessary part. 74 | As a regular model it can contain attributes, which are defined via public fields. Validation rules can be setup 75 | for those attributes. 76 | For example: 77 | 78 | ```php 79 | namespace app\mail\active; 80 | 81 | use yii2tech\activemail\ActiveMessage; 82 | use Yii; 83 | 84 | class ContactUs extends ActiveMessage 85 | { 86 | public $name; 87 | public $email; 88 | public $message; 89 | public $subject; 90 | 91 | public function rules() 92 | { 93 | return [ 94 | [$this->attributes, 'required'], 95 | ['email', 'email'], 96 | ]; 97 | } 98 | 99 | public function defaultFrom() 100 | { 101 | return Yii::$app->params['applicationEmail']; 102 | } 103 | 104 | public function defaultTo() 105 | { 106 | return Yii::$app->params->mail['adminEmail']; 107 | } 108 | 109 | public function defaultSubject() 110 | { 111 | return 'Contact: {subject}'; 112 | } 113 | 114 | public function defaultBodyHtml() 115 | { 116 | return <<{email}
118 | Name: {name}
119 |
120 | {subject} 121 |
122 | {message} 123 | BODY; 124 | } 125 | } 126 | ``` 127 | 128 | Once declared active message can be used as regular model inside controller: 129 | 130 | ```php 131 | use app\mail\active\ContactUs; 132 | 133 | // ... 134 | 135 | public function actionContact() 136 | { 137 | $model = new ContactUs(); 138 | if ($model->load(Yii::$app->request->post()) && $model->send()) { 139 | Yii::$app->session->setFlash('contactFormSubmitted'); 140 | return $this->refresh(); 141 | } 142 | return $this->render('contact', [ 143 | 'model' => $model, 144 | ]); 145 | } 146 | ``` 147 | 148 | [[\yii2tech\activemail\ActiveMessage]] uses regular Yii2 mail composition mechanism based on view files. 149 | By default it uses internal view provided by this extension. However in order to work properly it obviously 150 | requires layout view to exist. 151 | Each particular active message may specify its own view via `viewName()` method declaration. 152 | The most basic content for such view would be following: 153 | 154 | ```php 155 | getBodyHtml(); 160 | ?> 161 | ``` 162 | 163 | 164 | ## Working with placeholders 165 | 166 | Each part of active message such as subject or body may contain placeholders in format: `{placeholderName}`. 167 | While message composition these placeholders will be replaced by thier actual values. The actual placeholders 168 | are defined via `templatePlaceholders()` method. By default it it uses current active message attribute values, 169 | but you may override it in order to add extra placeholders: 170 | 171 | ```php 172 | public function templatePlaceholders() 173 | { 174 | return array_merge( 175 | parent::templatePlaceholders(), 176 | [ 177 | 'nowDate' => date('Y-m-d') 178 | ] 179 | ); 180 | } 181 | ``` 182 | 183 | [[\yii2tech\activemail\ActiveMessage]] also declares `templatePlaceholderHints()` method, which can be used 184 | to specify hints for each used placeholder. You may use it, while composing edit form for the mail template. 185 | 186 | 187 | ## Template usage 188 | 189 | The main benefit of [[\yii2tech\activemail\ActiveMessage]] usage is mail template feature. 190 | Each active message can have a named template, which overrides its default values for subject, body etc. 191 | The template name is defined via `templateName()` method. By default the active message class base name is used. 192 | 193 | Actual template source is defined via 'mail template storage' component, which has been already mentioned above. 194 | 195 | Following template storages are available: 196 | - [[\yii2tech\activemail\TemplateStoragePhp]] - stores templates inside PHP files 197 | - [[\yii2tech\activemail\TemplateStorageDb]] - stores templates inside relational database 198 | - [[\yii2tech\activemail\TemplateStorageMongoDb]] - stores templates inside MongoDB 199 | - [[\yii2tech\activemail\TemplateStorageActiveRecord]] - finds templates using ActiveRecord 200 | 201 | Please refer to the particular storage class for more details. 202 | 203 | For example: assume we use [[\yii2tech\activemail\TemplateStoragePhp]] as template storage. In order to define 204 | a template for our `app\mail\active\ContactUs` active message, we should create a file under '@app/mail/templates' 205 | named 'ContactUs.php' with following content: 206 | 207 | ```php 208 | 'Override', 212 | 'htmlBody' => 'Override:
{message}', 213 | ]; 214 | ``` 215 | 216 | After this is done, values from this file for 'subject' and 'htmlBody' will override default ones 217 | declared by `app\mail\active\ContactUs`. 218 | 219 | This feature may prove itself very useful, while creating multi-lingual sites. In this case you can declare 220 | `templateName()` method for active message as following: 221 | 222 | ```php 223 | class ContactUs extends ActiveMessage 224 | { 225 | // ... 226 | 227 | public function templateName() 228 | { 229 | return Yii::$app->language . DIRECTORY_SEPARATOR . 'ContactUs'; 230 | } 231 | } 232 | ``` 233 | 234 | Then you may create multiple templates named 'ContactUs' under sub-directories, which names matching particular 235 | language code like 'en-US', 'de' and so on. 236 | 237 | Using database template storages allows application administrator override mail messages content if necessary, 238 | by inserting corresponding row into a table and restore default value by deleting it. 239 | 240 | > Note: templates are meant to override default active message values, thus if particular template is missing 241 | in the storage, the program will NOT trigger any error or throw any exception. 242 | 243 | 244 | ## Template management 245 | 246 | The most common reason of using special mail template system is allowing application administrator to edit them 247 | via web interface. In order to simplify such feature creation, this extension provides [[\yii2tech\activemail\TemplateModelFinder]] 248 | class, which allows listing all available active messages and created templates. 249 | The search model for the active messages can look like following: 250 | 251 | ```php 252 | use yii\base\Model; 253 | use yii\data\ArrayDataProvider; 254 | use yii2tech\activemail\TemplateModelFinder; 255 | use app\models\MailTemplate; 256 | 257 | class MailTemplateSearch extends Model 258 | { 259 | public $name; 260 | public $subject; 261 | 262 | public function search() 263 | { 264 | // get raw data 265 | $finder = new TemplateModelFinder([ 266 | 'activeRecordClass' => MailTemplate::className(); 267 | ]); 268 | $models = $finder->findAllTemplateModels(); 269 | 270 | // filter list : 271 | $filterModel = $this; 272 | $models = array_filter($models, function ($model) use ($filterModel) { 273 | /* @var $model MailTemplate */ 274 | if (!empty($filterModel->name)) { 275 | if ($filterModel->name != $model->name) { 276 | return false; 277 | } 278 | } 279 | if (!empty($filterModel->subject)) { 280 | if (strpos($model->subject, $filterModel->subject) === false) { 281 | return false; 282 | } 283 | } 284 | return true; 285 | }); 286 | 287 | // compose data provider 288 | return new ArrayDataProvider([ 289 | 'allModels' => $models, 290 | 'sort' => [ 291 | 'attributes' => ['name', 'subject'], 292 | ], 293 | ]); 294 | } 295 | } 296 | ``` 297 | 298 | The web controller for email templates can look like following: 299 | 300 | ```php 301 | use yii\web\Controller; 302 | use yii\web\NotFoundHttpException; 303 | use Yii; 304 | use app\models\MailTemplate; 305 | use app\models\MailTemplateSearch; 306 | 307 | class MailTemplateController extends Controller 308 | { 309 | public function actionIndex() 310 | { 311 | $searchModel = new MailTemplateSearch(); 312 | $dataProvider = $searchModel->search(Yii::$app->request->queryParams); 313 | 314 | return $this->render('index', [ 315 | 'searchModel' => $searchModel, 316 | 'dataProvider' => $dataProvider, 317 | ]); 318 | } 319 | 320 | public function actionUpdate($name) 321 | { 322 | $finder = new TemplateModelFinder([ 323 | 'activeRecordClass' => MailTemplate::className(); 324 | ]); 325 | 326 | $model = $finder->findTemplateModel($name); 327 | if ($model === null) { 328 | throw new NotFoundHttpException('The requested page does not exist.'); 329 | } 330 | 331 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 332 | return $this->redirect(['index']); 333 | } 334 | 335 | return $this->render('update', [ 336 | 'model' => $model, 337 | ]); 338 | } 339 | } 340 | ``` 341 | -------------------------------------------------------------------------------- /ActiveMessage.php: -------------------------------------------------------------------------------- 1 | params['applicationEmail']; 33 | * } 34 | * 35 | * public function defaultTo() 36 | * { 37 | * return Yii::$app->params->mail['adminEmail']; 38 | * } 39 | * 40 | * public function defaultSubject() 41 | * { 42 | * return 'Contact message on ' . Yii::$app->name; 43 | * } 44 | * 45 | * public function defaultBodyHtml() 46 | * { 47 | * return 'Contact message'; 48 | * } 49 | * } 50 | * ``` 51 | * 52 | * Once message created and populated it can be sent via [[send()]] method: 53 | * 54 | * ```php 55 | * use app\mail\active\Notification; 56 | * 57 | * $message = new Notification(); 58 | * $message->to = 'some@domain.com'; 59 | * $message->message = 'Notification message'; 60 | * $message->send(); 61 | * ``` 62 | * 63 | * ActiveMessage supports using of the mail templates provided by [[\yii2tech\activemail\TemplateStorage]]. 64 | * 65 | * @see yii2tech\activemail\TemplateStorage 66 | * 67 | * @property string|array $from message sender email address. 68 | * @property string|array $replyTo the reply-to address. 69 | * @property array $to message recipients. 70 | * @property string $subject message subject. 71 | * @property string $bodyText message plain text content. 72 | * @property string $bodyHtml message HTML content. 73 | * 74 | * @author Paul Klimov 75 | * @since 1.0 76 | */ 77 | abstract class ActiveMessage extends Model 78 | { 79 | /** 80 | * @event Event an event that is triggered before message is sent. 81 | */ 82 | const EVENT_BEFORE_SEND = 'beforeSend'; 83 | 84 | /** 85 | * @var string|array message sender email address. 86 | */ 87 | private $_from; 88 | /** 89 | * @var string|array the reply-to address. 90 | */ 91 | private $_replyTo; 92 | /** 93 | * @var array message recipients. 94 | */ 95 | private $_to; 96 | /** 97 | * @var string message subject. 98 | */ 99 | private $_subject; 100 | /** 101 | * @var string message plain text content. 102 | */ 103 | private $_bodyText; 104 | /** 105 | * @var string message HTML content. 106 | */ 107 | private $_bodyHtml; 108 | 109 | 110 | /** 111 | * @param string|array $from message sender email address. 112 | */ 113 | public function setFrom($from) 114 | { 115 | $this->_from = $from; 116 | } 117 | 118 | /** 119 | * @return string|array message sender email address. 120 | */ 121 | public function getFrom() 122 | { 123 | if (empty($this->_from)) { 124 | $this->_from = $this->defaultFrom(); 125 | } 126 | return $this->_from; 127 | } 128 | 129 | /** 130 | * @param array|string $replyTo the reply-to address. 131 | */ 132 | public function setReplyTo($replyTo) 133 | { 134 | $this->_replyTo = $replyTo; 135 | } 136 | 137 | /** 138 | * @return array|string the reply-to address. 139 | */ 140 | public function getReplyTo() 141 | { 142 | if (empty($this->_replyTo)) { 143 | $this->_replyTo = $this->defaultReplyTo(); 144 | } 145 | return $this->_replyTo; 146 | } 147 | 148 | /** 149 | * @param array $to message recipients. 150 | */ 151 | public function setTo($to) 152 | { 153 | $this->_to = $to; 154 | } 155 | 156 | /** 157 | * @return array message recipients. 158 | */ 159 | public function getTo() 160 | { 161 | if (empty($this->_to)) { 162 | $this->_to = $this->defaultTo(); 163 | } 164 | return $this->_to; 165 | } 166 | 167 | /** 168 | * @param string $subject message subject. 169 | */ 170 | public function setSubject($subject) 171 | { 172 | $this->_subject = $subject; 173 | } 174 | 175 | /** 176 | * @return string message subject. 177 | */ 178 | public function getSubject() 179 | { 180 | if (empty($this->_subject)) { 181 | $this->_subject = $this->defaultSubject(); 182 | } 183 | return $this->_subject; 184 | } 185 | 186 | /** 187 | * @param string $bodyHtml message HTML content. 188 | */ 189 | public function setBodyHtml($bodyHtml) 190 | { 191 | $this->_bodyHtml = $bodyHtml; 192 | } 193 | 194 | /** 195 | * @return string message HTML content. 196 | */ 197 | public function getBodyHtml() 198 | { 199 | if (empty($this->_bodyHtml)) { 200 | $this->_bodyHtml = $this->defaultBodyHtml(); 201 | } 202 | return $this->_bodyHtml; 203 | } 204 | 205 | /** 206 | * @param string $bodyText message plain text content. 207 | */ 208 | public function setBodyText($bodyText) 209 | { 210 | $this->_bodyText = $bodyText; 211 | } 212 | 213 | /** 214 | * @return string message plain text content. 215 | */ 216 | public function getBodyText() 217 | { 218 | if (empty($this->_bodyText)) { 219 | $this->_bodyText = $this->defaultBodyText(); 220 | } 221 | return $this->_bodyText; 222 | } 223 | 224 | /** 225 | * @return \yii\mail\MailerInterface mailer instance. 226 | */ 227 | public function getMailer() 228 | { 229 | return Yii::$app->getMailer(); 230 | } 231 | 232 | /** 233 | * @return TemplateStorage template storage instance. 234 | */ 235 | public function getTemplateStorage() 236 | { 237 | return Yii::$app->get('mailTemplateStorage'); 238 | } 239 | 240 | /** 241 | * @inheritdoc 242 | */ 243 | public function rules() 244 | { 245 | return [ 246 | [$this->attributes(), 'required'], 247 | ]; 248 | } 249 | 250 | /** 251 | * @return string default sender 252 | */ 253 | abstract public function defaultFrom(); 254 | 255 | /** 256 | * @return string the default reply-to address. 257 | */ 258 | public function defaultReplyTo() 259 | { 260 | return $this->getFrom(); 261 | } 262 | 263 | /** 264 | * @return string default receiver email address. 265 | */ 266 | abstract public function defaultTo(); 267 | 268 | /** 269 | * @return string default message subject 270 | */ 271 | abstract public function defaultSubject(); 272 | 273 | /** 274 | * @return string default message HTML content. 275 | */ 276 | abstract public function defaultBodyHtml(); 277 | 278 | /** 279 | * @return string default message plain text content. 280 | */ 281 | public function defaultBodyText() 282 | { 283 | return 'You need email client with HTML support to view this message.'; 284 | } 285 | 286 | /** 287 | * @return string message view name. 288 | */ 289 | public function viewName() 290 | { 291 | return '@yii2tech/activemail/views/activeMessage.php'; 292 | } 293 | 294 | /** 295 | * @return string message template name. 296 | */ 297 | public function templateName() 298 | { 299 | return StringHelper::basename(get_class($this)); 300 | } 301 | 302 | /** 303 | * Returns the hints for template placeholders. 304 | * Hints are can be used, while composing edit form for the mail template. 305 | * @return array template placeholder hints in format: placeholderName => hint 306 | */ 307 | public function templatePlaceholderHints() 308 | { 309 | return []; 310 | } 311 | 312 | /** 313 | * Returns all this model error messages as single summary string. 314 | * @param string $glue messages separator. 315 | * @return string error summary. 316 | */ 317 | public function getErrorSummary($glue = "\n") 318 | { 319 | $errors = $this->getErrors(); 320 | $summaryParts = []; 321 | foreach ($errors as $attributeErrors) { 322 | $summaryParts = array_merge($summaryParts, $attributeErrors); 323 | } 324 | return implode($glue, $summaryParts); 325 | } 326 | 327 | /** 328 | * Parses template string. 329 | * @param string $template template string. 330 | * @param array $data parsing data. 331 | * @return string parsing result. 332 | */ 333 | protected function parseTemplate($template, array $data = []) 334 | { 335 | $replacePairs = []; 336 | foreach ($data as $name => $value) { 337 | $replacePairs['{' . $name . '}'] = $value; 338 | } 339 | return strtr($template, $replacePairs); 340 | } 341 | 342 | /** 343 | * Sends this message 344 | * @param boolean $runValidation whether to perform validation before sending the message. 345 | * @return boolean success. 346 | * @throws InvalidConfigException on failure 347 | */ 348 | public function send($runValidation = true) 349 | { 350 | if ($runValidation && !$this->validate()) { 351 | throw new InvalidConfigException('Unable to send message: ' . $this->getErrorSummary()); 352 | } 353 | $data = $this->templatePlaceholders(); 354 | 355 | //$this->beforeCompose($mailMessage, $data); 356 | 357 | $this->applyTemplate(); 358 | $this->applyParse($data); 359 | 360 | $data['activeMessage'] = $this; 361 | 362 | $mailMessage = $this->getMailer() 363 | ->compose($this->viewName(), $data) 364 | ->setSubject($this->getSubject()) 365 | ->setTo($this->getTo()) 366 | ->setFrom($this->getFrom()) 367 | ->setReplyTo($this->getReplyTo()); 368 | 369 | if ($this->beforeSend($mailMessage)) { 370 | return $this->getMailer()->send($mailMessage); 371 | } else { 372 | return false; 373 | } 374 | } 375 | 376 | /** 377 | * Composes placeholders, which should be used to parse template. 378 | * Those placeholders will also be passed to the mail view, while composition. 379 | * By default this method returns all current message model attributes. 380 | * Child classes may override this method to customize template placeholders. 381 | * @return array template placeholders in format: placeholderName => value. 382 | */ 383 | protected function templatePlaceholders() 384 | { 385 | return $this->getAttributes(); 386 | } 387 | 388 | /** 389 | * Applies corresponding template to the message if it exist. 390 | */ 391 | protected function applyTemplate() 392 | { 393 | $templateAttributes = $this->getTemplateStorage()->getTemplate($this->templateName()); 394 | if (!empty($templateAttributes)) { 395 | foreach ($templateAttributes as $name => $value) { 396 | $setter = 'set' . $name; 397 | if (method_exists($this, $setter)) { 398 | $this->$setter($value); 399 | } else { 400 | $this->$name = $value; 401 | } 402 | } 403 | } 404 | } 405 | 406 | /** 407 | * Applies parsing to this message internal fields. 408 | * @param array $data template parse data. 409 | */ 410 | protected function applyParse(array $data) 411 | { 412 | $propertyNames = [ 413 | 'subject', 414 | 'bodyText', 415 | 'bodyHtml', 416 | 'bodyHtml', 417 | ]; 418 | foreach ($propertyNames as $propertyName) { 419 | $getter = 'get' . $propertyName; 420 | $setter = 'set' . $propertyName; 421 | $content = $this->$getter(); 422 | $content = $this->parseTemplate($content, $data); 423 | $this->$setter($content); 424 | } 425 | } 426 | 427 | // Events : 428 | 429 | /** 430 | * This method is invoked before mail message sending. 431 | * The default implementation raises a `beforeSend` event. 432 | * You may override this method to do preliminary checks or adjustments before sending. 433 | * Make sure the parent implementation is invoked so that the event can be raised. 434 | * @param \yii\mail\MessageInterface $mailMessage mail message instance. 435 | * @return boolean whether message should be sent. Defaults to true. 436 | * If false is returned, no message sending will be performed. 437 | */ 438 | protected function beforeSend($mailMessage) 439 | { 440 | $event = new ActiveMessageEvent(['mailMessage' => $mailMessage]); 441 | $this->trigger(self::EVENT_BEFORE_SEND, $event); 442 | return $event->isValid; 443 | } 444 | } --------------------------------------------------------------------------------