├── 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 | [](https://packagist.org/packages/yii2tech/activemail)
15 | [](https://packagist.org/packages/yii2tech/activemail)
16 | [](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 | }
--------------------------------------------------------------------------------