1 | <?php |
||
2 | /** |
||
3 | * @link http://www.yiiframework.com/ |
||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||
5 | * @license http://www.yiiframework.com/license/ |
||
6 | */ |
||
7 | |||
8 | namespace yii\mail; |
||
9 | |||
10 | use Yii; |
||
11 | use yii\base\Component; |
||
12 | use yii\base\InvalidConfigException; |
||
13 | use yii\base\ViewContextInterface; |
||
14 | use yii\web\View; |
||
15 | |||
16 | /** |
||
17 | * BaseMailer serves as a base class that implements the basic functions required by [[MailerInterface]]. |
||
18 | * |
||
19 | * Concrete child classes should may focus on implementing the [[sendMessage()]] method. |
||
20 | * |
||
21 | * @see BaseMessage |
||
22 | * |
||
23 | * For more details and usage information on BaseMailer, see the [guide article on mailing](guide:tutorial-mailing). |
||
24 | * |
||
25 | * @property View $view View instance. Note that the type of this property differs in getter and setter. See |
||
26 | * [[getView()]] and [[setView()]] for details. |
||
27 | * @property string $viewPath The directory that contains the view files for composing mail messages Defaults |
||
28 | * to '@app/mail'. |
||
29 | * |
||
30 | * @author Paul Klimov <[email protected]> |
||
31 | * @since 2.0 |
||
32 | */ |
||
33 | abstract class BaseMailer extends Component implements MailerInterface, ViewContextInterface |
||
34 | { |
||
35 | /** |
||
36 | * @event MailEvent an event raised right before send. |
||
37 | * You may set [[MailEvent::isValid]] to be false to cancel the send. |
||
38 | */ |
||
39 | const EVENT_BEFORE_SEND = 'beforeSend'; |
||
40 | /** |
||
41 | * @event MailEvent an event raised right after send. |
||
42 | */ |
||
43 | const EVENT_AFTER_SEND = 'afterSend'; |
||
44 | |||
45 | /** |
||
46 | * @var string|bool HTML layout view name. This is the layout used to render HTML mail body. |
||
47 | * The property can take the following values: |
||
48 | * |
||
49 | * - a relative view name: a view file relative to [[viewPath]], e.g., 'layouts/html'. |
||
50 | * - a [path alias](guide:concept-aliases): an absolute view file path specified as a path alias, e.g., '@app/mail/html'. |
||
51 | * - a boolean false: the layout is disabled. |
||
52 | */ |
||
53 | public $htmlLayout = 'layouts/html'; |
||
54 | /** |
||
55 | * @var string|bool text layout view name. This is the layout used to render TEXT mail body. |
||
56 | * Please refer to [[htmlLayout]] for possible values that this property can take. |
||
57 | */ |
||
58 | public $textLayout = 'layouts/text'; |
||
59 | /** |
||
60 | * @var array the configuration that should be applied to any newly created |
||
61 | * email message instance by [[createMessage()]] or [[compose()]]. Any valid property defined |
||
62 | * by [[MessageInterface]] can be configured, such as `from`, `to`, `subject`, `textBody`, `htmlBody`, etc. |
||
63 | * |
||
64 | * For example: |
||
65 | * |
||
66 | * ```php |
||
67 | * [ |
||
68 | * 'charset' => 'UTF-8', |
||
69 | * 'from' => '[email protected]', |
||
70 | * 'bcc' => '[email protected]', |
||
71 | * ] |
||
72 | * ``` |
||
73 | */ |
||
74 | public $messageConfig = []; |
||
75 | /** |
||
76 | * @var string the default class name of the new message instances created by [[createMessage()]] |
||
77 | */ |
||
78 | public $messageClass = 'yii\mail\BaseMessage'; |
||
79 | /** |
||
80 | * @var bool whether to save email messages as files under [[fileTransportPath]] instead of sending them |
||
81 | * to the actual recipients. This is usually used during development for debugging purpose. |
||
82 | * @see fileTransportPath |
||
83 | */ |
||
84 | public $useFileTransport = false; |
||
85 | /** |
||
86 | * @var string the directory where the email messages are saved when [[useFileTransport]] is true. |
||
87 | */ |
||
88 | public $fileTransportPath = '@runtime/mail'; |
||
89 | /** |
||
90 | * @var callable a PHP callback that will be called by [[send()]] when [[useFileTransport]] is true. |
||
91 | * The callback should return a file name which will be used to save the email message. |
||
92 | * If not set, the file name will be generated based on the current timestamp. |
||
93 | * |
||
94 | * The signature of the callback is: |
||
95 | * |
||
96 | * ```php |
||
97 | * function ($mailer, $message) |
||
98 | * ``` |
||
99 | */ |
||
100 | public $fileTransportCallback; |
||
101 | |||
102 | /** |
||
103 | * @var \yii\base\View|array view instance or its array configuration. |
||
104 | */ |
||
105 | private $_view = []; |
||
106 | /** |
||
107 | * @var string the directory containing view files for composing mail messages. |
||
108 | */ |
||
109 | private $_viewPath; |
||
110 | |||
111 | |||
112 | /** |
||
113 | * @param array|View $view view instance or its array configuration that will be used to |
||
114 | * render message bodies. |
||
115 | * @throws InvalidConfigException on invalid argument. |
||
116 | */ |
||
117 | 1 | public function setView($view) |
|
118 | { |
||
119 | 1 | if (!is_array($view) && !is_object($view)) { |
|
120 | throw new InvalidConfigException('"' . get_class($this) . '::view" should be either object or configuration array, "' . gettype($view) . '" given.'); |
||
121 | } |
||
122 | 1 | $this->_view = $view; |
|
123 | 1 | } |
|
124 | |||
125 | /** |
||
126 | * @return View view instance. |
||
127 | */ |
||
128 | 7 | public function getView() |
|
129 | { |
||
130 | 7 | if (!is_object($this->_view)) { |
|
131 | 7 | $this->_view = $this->createView($this->_view); |
|
132 | } |
||
133 | |||
134 | 7 | return $this->_view; |
|
135 | } |
||
136 | |||
137 | /** |
||
138 | * Creates view instance from given configuration. |
||
139 | * @param array $config view configuration. |
||
140 | * @return View view instance. |
||
141 | */ |
||
142 | 7 | protected function createView(array $config) |
|
143 | { |
||
144 | 7 | if (!array_key_exists('class', $config)) { |
|
145 | 7 | $config['class'] = View::className(); |
|
146 | } |
||
147 | |||
148 | 7 | return Yii::createObject($config); |
|
149 | } |
||
150 | |||
151 | private $_message; |
||
152 | |||
153 | /** |
||
154 | * Creates a new message instance and optionally composes its body content via view rendering. |
||
155 | * |
||
156 | * @param string|array|null $view the view to be used for rendering the message body. This can be: |
||
157 | * |
||
158 | * - a string, which represents the view name or [path alias](guide:concept-aliases) for rendering the HTML body of the email. |
||
159 | * In this case, the text body will be generated by applying `strip_tags()` to the HTML body. |
||
160 | * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name or path alias |
||
161 | * for rendering the HTML body, while 'text' element is for rendering the text body. For example, |
||
162 | * `['html' => 'contact-html', 'text' => 'contact-text']`. |
||
163 | * - null, meaning the message instance will be returned without body content. |
||
164 | * |
||
165 | * The view to be rendered can be specified in one of the following formats: |
||
166 | * |
||
167 | * - path alias (e.g. "@app/mail/contact"); |
||
168 | * - a relative view name (e.g. "contact") located under [[viewPath]]. |
||
169 | * |
||
170 | * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. |
||
171 | * @return MessageInterface message instance. |
||
172 | */ |
||
173 | 8 | public function compose($view = null, array $params = []) |
|
174 | { |
||
175 | 8 | $message = $this->createMessage(); |
|
176 | 8 | if ($view === null) { |
|
177 | 5 | return $message; |
|
178 | } |
||
179 | |||
180 | 3 | if (!array_key_exists('message', $params)) { |
|
181 | 3 | $params['message'] = $message; |
|
182 | } |
||
183 | |||
184 | 3 | $this->_message = $message; |
|
185 | |||
186 | 3 | if (is_array($view)) { |
|
187 | 3 | if (isset($view['html'])) { |
|
188 | 3 | $html = $this->render($view['html'], $params, $this->htmlLayout); |
|
189 | } |
||
190 | 3 | if (isset($view['text'])) { |
|
191 | 3 | $text = $this->render($view['text'], $params, $this->textLayout); |
|
192 | } |
||
193 | } else { |
||
194 | 1 | $html = $this->render($view, $params, $this->htmlLayout); |
|
195 | } |
||
196 | |||
197 | |||
198 | 3 | $this->_message = null; |
|
199 | |||
200 | 3 | if (isset($html)) { |
|
201 | 3 | $message->setHtmlBody($html); |
|
202 | } |
||
203 | 3 | if (isset($text)) { |
|
204 | 1 | $message->setTextBody($text); |
|
205 | } elseif (isset($html)) { |
||
206 | 3 | if (preg_match('~<body[^>]*>(.*?)</body>~is', $html, $match)) { |
|
207 | 1 | $html = $match[1]; |
|
208 | } |
||
209 | // remove style and script |
||
210 | 3 | $html = preg_replace('~<((style|script))[^>]*>(.*?)</\1>~is', '', $html); |
|
211 | // strip all HTML tags and decoded HTML entities |
||
212 | 3 | $text = html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, Yii::$app ? Yii::$app->charset : 'UTF-8'); |
|
213 | // improve whitespace |
||
214 | 3 | $text = preg_replace("~^[ \t]+~m", '', trim($text)); |
|
215 | 3 | $text = preg_replace('~\R\R+~mu', "\n\n", $text); |
|
216 | 3 | $message->setTextBody($text); |
|
217 | } |
||
218 | |||
219 | 3 | return $message; |
|
220 | } |
||
221 | |||
222 | /** |
||
223 | * Creates a new message instance. |
||
224 | * The newly created instance will be initialized with the configuration specified by [[messageConfig]]. |
||
225 | * If the configuration does not specify a 'class', the [[messageClass]] will be used as the class |
||
226 | * of the new message instance. |
||
227 | * @return MessageInterface message instance. |
||
228 | */ |
||
229 | 8 | protected function createMessage() |
|
230 | { |
||
231 | 8 | $config = $this->messageConfig; |
|
232 | 8 | if (!array_key_exists('class', $config)) { |
|
233 | 8 | $config['class'] = $this->messageClass; |
|
234 | } |
||
235 | 8 | $config['mailer'] = $this; |
|
236 | 8 | return Yii::createObject($config); |
|
237 | } |
||
238 | |||
239 | /** |
||
240 | * Sends the given email message. |
||
241 | * This method will log a message about the email being sent. |
||
242 | * If [[useFileTransport]] is true, it will save the email as a file under [[fileTransportPath]]. |
||
243 | * Otherwise, it will call [[sendMessage()]] to send the email to its recipient(s). |
||
244 | * Child classes should implement [[sendMessage()]] with the actual email sending logic. |
||
245 | * @param MessageInterface $message email message instance to be sent |
||
246 | * @return bool whether the message has been sent successfully |
||
247 | */ |
||
248 | 3 | public function send($message) |
|
249 | { |
||
250 | 3 | if (!$this->beforeSend($message)) { |
|
251 | return false; |
||
252 | } |
||
253 | |||
254 | 3 | $address = $message->getTo(); |
|
255 | 3 | if (is_array($address)) { |
|
256 | $address = implode(', ', array_keys($address)); |
||
257 | } |
||
258 | 3 | Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__); |
|
259 | |||
260 | 3 | if ($this->useFileTransport) { |
|
261 | 1 | $isSuccessful = $this->saveMessage($message); |
|
262 | } else { |
||
263 | 2 | $isSuccessful = $this->sendMessage($message); |
|
264 | } |
||
265 | 3 | $this->afterSend($message, $isSuccessful); |
|
266 | |||
267 | 3 | return $isSuccessful; |
|
268 | } |
||
269 | |||
270 | /** |
||
271 | * Sends multiple messages at once. |
||
272 | * |
||
273 | * The default implementation simply calls [[send()]] multiple times. |
||
274 | * Child classes may override this method to implement more efficient way of |
||
275 | * sending multiple messages. |
||
276 | * |
||
277 | * @param array $messages list of email messages, which should be sent. |
||
278 | * @return int number of messages that are successfully sent. |
||
279 | */ |
||
280 | public function sendMultiple(array $messages) |
||
281 | { |
||
282 | $successCount = 0; |
||
283 | foreach ($messages as $message) { |
||
284 | if ($this->send($message)) { |
||
285 | $successCount++; |
||
286 | } |
||
287 | } |
||
288 | |||
289 | return $successCount; |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Renders the specified view with optional parameters and layout. |
||
294 | * The view will be rendered using the [[view]] component. |
||
295 | * @param string $view the view name or the [path alias](guide:concept-aliases) of the view file. |
||
296 | * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. |
||
297 | * @param string|bool $layout layout view name or [path alias](guide:concept-aliases). If false, no layout will be applied. |
||
298 | * @return string the rendering result. |
||
299 | */ |
||
300 | 5 | public function render($view, $params = [], $layout = false) |
|
301 | { |
||
302 | 5 | $output = $this->getView()->render($view, $params, $this); |
|
303 | 5 | if ($layout !== false) { |
|
304 | 1 | return $this->getView()->render($layout, ['content' => $output, 'message' => $this->_message], $this); |
|
305 | } |
||
306 | |||
307 | 4 | return $output; |
|
308 | } |
||
309 | |||
310 | /** |
||
311 | * Sends the specified message. |
||
312 | * This method should be implemented by child classes with the actual email sending logic. |
||
313 | * @param MessageInterface $message the message to be sent |
||
314 | * @return bool whether the message is sent successfully |
||
315 | */ |
||
316 | abstract protected function sendMessage($message); |
||
317 | |||
318 | /** |
||
319 | * Saves the message as a file under [[fileTransportPath]]. |
||
320 | * @param MessageInterface $message |
||
321 | * @return bool whether the message is saved successfully |
||
322 | */ |
||
323 | 1 | protected function saveMessage($message) |
|
324 | { |
||
325 | 1 | $path = Yii::getAlias($this->fileTransportPath); |
|
326 | 1 | if (!is_dir($path)) { |
|
327 | 1 | mkdir($path, 0777, true); |
|
328 | } |
||
329 | 1 | if ($this->fileTransportCallback !== null) { |
|
330 | 1 | $file = $path . '/' . call_user_func($this->fileTransportCallback, $this, $message); |
|
331 | } else { |
||
332 | $file = $path . '/' . $this->generateMessageFileName(); |
||
333 | } |
||
334 | 1 | file_put_contents($file, $message->toString()); |
|
335 | |||
336 | 1 | return true; |
|
337 | } |
||
338 | |||
339 | /** |
||
340 | * @return string the file name for saving the message when [[useFileTransport]] is true. |
||
341 | */ |
||
342 | public function generateMessageFileName() |
||
343 | { |
||
344 | $time = microtime(true); |
||
345 | |||
346 | return date('Ymd-His-', $time) . sprintf('%04d', (int) (($time - (int) $time) * 10000)) . '-' . sprintf('%04d', mt_rand(0, 10000)) . '.eml'; |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * @return string the directory that contains the view files for composing mail messages |
||
351 | * Defaults to '@app/mail'. |
||
352 | */ |
||
353 | 5 | public function getViewPath() |
|
354 | { |
||
355 | 5 | if ($this->_viewPath === null) { |
|
356 | $this->setViewPath('@app/mail'); |
||
357 | } |
||
358 | |||
359 | 5 | return $this->_viewPath; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
360 | } |
||
361 | |||
362 | /** |
||
363 | * @param string $path the directory that contains the view files for composing mail messages |
||
364 | * This can be specified as an absolute path or a [path alias](guide:concept-aliases). |
||
365 | */ |
||
366 | 11 | public function setViewPath($path) |
|
367 | { |
||
368 | 11 | $this->_viewPath = Yii::getAlias($path); |
|
0 ignored issues
–
show
It seems like
Yii::getAlias($path) can also be of type boolean . However, the property $_viewPath is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
369 | 11 | } |
|
370 | |||
371 | /** |
||
372 | * This method is invoked right before mail send. |
||
373 | * You may override this method to do last-minute preparation for the message. |
||
374 | * If you override this method, please make sure you call the parent implementation first. |
||
375 | * @param MessageInterface $message |
||
376 | * @return bool whether to continue sending an email. |
||
377 | */ |
||
378 | 2 | public function beforeSend($message) |
|
379 | { |
||
380 | 2 | $event = new MailEvent(['message' => $message]); |
|
381 | 2 | $this->trigger(self::EVENT_BEFORE_SEND, $event); |
|
382 | |||
383 | 2 | return $event->isValid; |
|
384 | } |
||
385 | |||
386 | /** |
||
387 | * This method is invoked right after mail was send. |
||
388 | * You may override this method to do some postprocessing or logging based on mail send status. |
||
389 | * If you override this method, please make sure you call the parent implementation first. |
||
390 | * @param MessageInterface $message |
||
391 | * @param bool $isSuccessful |
||
392 | */ |
||
393 | 2 | public function afterSend($message, $isSuccessful) |
|
394 | { |
||
395 | 2 | $event = new MailEvent(['message' => $message, 'isSuccessful' => $isSuccessful]); |
|
396 | 2 | $this->trigger(self::EVENT_AFTER_SEND, $event); |
|
397 | 2 | } |
|
398 | } |
||
399 |