Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Mail;
|
Chris@0
|
4
|
Chris@17
|
5 use Drupal\Component\Render\MarkupInterface;
|
Chris@0
|
6 use Drupal\Component\Render\PlainTextOutput;
|
Chris@17
|
7 use Drupal\Component\Utility\Html;
|
Chris@17
|
8 use Drupal\Component\Utility\Mail as MailHelper;
|
Chris@0
|
9 use Drupal\Core\Logger\LoggerChannelFactoryInterface;
|
Chris@17
|
10 use Drupal\Core\Messenger\MessengerTrait;
|
Chris@0
|
11 use Drupal\Core\Plugin\DefaultPluginManager;
|
Chris@0
|
12 use Drupal\Core\Cache\CacheBackendInterface;
|
Chris@0
|
13 use Drupal\Core\Extension\ModuleHandlerInterface;
|
Chris@0
|
14 use Drupal\Core\Config\ConfigFactoryInterface;
|
Chris@17
|
15 use Drupal\Core\Render\Markup;
|
Chris@0
|
16 use Drupal\Core\Render\RenderContext;
|
Chris@0
|
17 use Drupal\Core\Render\RendererInterface;
|
Chris@0
|
18 use Drupal\Core\StringTranslation\StringTranslationTrait;
|
Chris@0
|
19 use Drupal\Core\StringTranslation\TranslationInterface;
|
Chris@0
|
20
|
Chris@0
|
21 /**
|
Chris@0
|
22 * Provides a Mail plugin manager.
|
Chris@0
|
23 *
|
Chris@0
|
24 * @see \Drupal\Core\Annotation\Mail
|
Chris@0
|
25 * @see \Drupal\Core\Mail\MailInterface
|
Chris@0
|
26 * @see plugin_api
|
Chris@0
|
27 */
|
Chris@0
|
28 class MailManager extends DefaultPluginManager implements MailManagerInterface {
|
Chris@0
|
29
|
Chris@17
|
30 use MessengerTrait;
|
Chris@0
|
31 use StringTranslationTrait;
|
Chris@0
|
32
|
Chris@0
|
33 /**
|
Chris@0
|
34 * The config factory.
|
Chris@0
|
35 *
|
Chris@0
|
36 * @var \Drupal\Core\Config\ConfigFactoryInterface
|
Chris@0
|
37 */
|
Chris@0
|
38 protected $configFactory;
|
Chris@0
|
39
|
Chris@0
|
40 /**
|
Chris@0
|
41 * The logger factory.
|
Chris@0
|
42 *
|
Chris@0
|
43 * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
|
Chris@0
|
44 */
|
Chris@0
|
45 protected $loggerFactory;
|
Chris@0
|
46
|
Chris@0
|
47 /**
|
Chris@0
|
48 * The renderer.
|
Chris@0
|
49 *
|
Chris@0
|
50 * @var \Drupal\Core\Render\RendererInterface
|
Chris@0
|
51 */
|
Chris@0
|
52 protected $renderer;
|
Chris@0
|
53
|
Chris@0
|
54 /**
|
Chris@0
|
55 * List of already instantiated mail plugins.
|
Chris@0
|
56 *
|
Chris@0
|
57 * @var array
|
Chris@0
|
58 */
|
Chris@0
|
59 protected $instances = [];
|
Chris@0
|
60
|
Chris@0
|
61 /**
|
Chris@0
|
62 * Constructs the MailManager object.
|
Chris@0
|
63 *
|
Chris@0
|
64 * @param \Traversable $namespaces
|
Chris@0
|
65 * An object that implements \Traversable which contains the root paths
|
Chris@0
|
66 * keyed by the corresponding namespace to look for plugin implementations.
|
Chris@0
|
67 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
Chris@0
|
68 * Cache backend instance to use.
|
Chris@0
|
69 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
Chris@0
|
70 * The module handler to invoke the alter hook with.
|
Chris@0
|
71 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
Chris@0
|
72 * The configuration factory.
|
Chris@0
|
73 * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
|
Chris@0
|
74 * The logger channel factory.
|
Chris@0
|
75 * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
Chris@0
|
76 * The string translation service.
|
Chris@0
|
77 * @param \Drupal\Core\Render\RendererInterface $renderer
|
Chris@0
|
78 * The renderer.
|
Chris@0
|
79 */
|
Chris@0
|
80 public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, TranslationInterface $string_translation, RendererInterface $renderer) {
|
Chris@0
|
81 parent::__construct('Plugin/Mail', $namespaces, $module_handler, 'Drupal\Core\Mail\MailInterface', 'Drupal\Core\Annotation\Mail');
|
Chris@0
|
82 $this->alterInfo('mail_backend_info');
|
Chris@0
|
83 $this->setCacheBackend($cache_backend, 'mail_backend_plugins');
|
Chris@0
|
84 $this->configFactory = $config_factory;
|
Chris@0
|
85 $this->loggerFactory = $logger_factory;
|
Chris@0
|
86 $this->stringTranslation = $string_translation;
|
Chris@0
|
87 $this->renderer = $renderer;
|
Chris@0
|
88 }
|
Chris@0
|
89
|
Chris@0
|
90 /**
|
Chris@0
|
91 * Overrides PluginManagerBase::getInstance().
|
Chris@0
|
92 *
|
Chris@0
|
93 * Returns an instance of the mail plugin to use for a given message ID.
|
Chris@0
|
94 *
|
Chris@0
|
95 * The selection of a particular implementation is controlled via the config
|
Chris@0
|
96 * 'system.mail.interface', which is a keyed array. The default
|
Chris@0
|
97 * implementation is the mail plugin whose ID is the value of 'default' key. A
|
Chris@0
|
98 * more specific match first to key and then to module will be used in
|
Chris@0
|
99 * preference to the default. To specify a different plugin for all mail sent
|
Chris@0
|
100 * by one module, set the plugin ID as the value for the key corresponding to
|
Chris@0
|
101 * the module name. To specify a plugin for a particular message sent by one
|
Chris@0
|
102 * module, set the plugin ID as the value for the array key that is the
|
Chris@0
|
103 * message ID, which is "${module}_${key}".
|
Chris@0
|
104 *
|
Chris@0
|
105 * For example to debug all mail sent by the user module by logging it to a
|
Chris@0
|
106 * file, you might set the variable as something like:
|
Chris@0
|
107 *
|
Chris@0
|
108 * @code
|
Chris@0
|
109 * array(
|
Chris@0
|
110 * 'default' => 'php_mail',
|
Chris@0
|
111 * 'user' => 'devel_mail_log',
|
Chris@0
|
112 * );
|
Chris@0
|
113 * @endcode
|
Chris@0
|
114 *
|
Chris@0
|
115 * Finally, a different system can be specified for a specific message ID (see
|
Chris@0
|
116 * the $key param), such as one of the keys used by the contact module:
|
Chris@0
|
117 *
|
Chris@0
|
118 * @code
|
Chris@0
|
119 * array(
|
Chris@0
|
120 * 'default' => 'php_mail',
|
Chris@0
|
121 * 'user' => 'devel_mail_log',
|
Chris@0
|
122 * 'contact_page_autoreply' => 'null_mail',
|
Chris@0
|
123 * );
|
Chris@0
|
124 * @endcode
|
Chris@0
|
125 *
|
Chris@0
|
126 * Other possible uses for system include a mail-sending plugin that actually
|
Chris@0
|
127 * sends (or duplicates) each message to SMS, Twitter, instant message, etc,
|
Chris@0
|
128 * or a plugin that queues up a large number of messages for more efficient
|
Chris@0
|
129 * bulk sending or for sending via a remote gateway so as to reduce the load
|
Chris@0
|
130 * on the local server.
|
Chris@0
|
131 *
|
Chris@0
|
132 * @param array $options
|
Chris@0
|
133 * An array with the following key/value pairs:
|
Chris@0
|
134 * - module: (string) The module name which was used by
|
Chris@0
|
135 * \Drupal\Core\Mail\MailManagerInterface->mail() to invoke hook_mail().
|
Chris@0
|
136 * - key: (string) A key to identify the email sent. The final message ID
|
Chris@0
|
137 * is a string represented as {$module}_{$key}.
|
Chris@0
|
138 *
|
Chris@0
|
139 * @return \Drupal\Core\Mail\MailInterface
|
Chris@0
|
140 * A mail plugin instance.
|
Chris@0
|
141 *
|
Chris@0
|
142 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
|
Chris@0
|
143 */
|
Chris@0
|
144 public function getInstance(array $options) {
|
Chris@0
|
145 $module = $options['module'];
|
Chris@0
|
146 $key = $options['key'];
|
Chris@0
|
147 $message_id = $module . '_' . $key;
|
Chris@0
|
148
|
Chris@0
|
149 $configuration = $this->configFactory->get('system.mail')->get('interface');
|
Chris@0
|
150
|
Chris@0
|
151 // Look for overrides for the default mail plugin, starting from the most
|
Chris@0
|
152 // specific message_id, and falling back to the module name.
|
Chris@0
|
153 if (isset($configuration[$message_id])) {
|
Chris@0
|
154 $plugin_id = $configuration[$message_id];
|
Chris@0
|
155 }
|
Chris@0
|
156 elseif (isset($configuration[$module])) {
|
Chris@0
|
157 $plugin_id = $configuration[$module];
|
Chris@0
|
158 }
|
Chris@0
|
159 else {
|
Chris@0
|
160 $plugin_id = $configuration['default'];
|
Chris@0
|
161 }
|
Chris@0
|
162
|
Chris@0
|
163 if (empty($this->instances[$plugin_id])) {
|
Chris@0
|
164 $this->instances[$plugin_id] = $this->createInstance($plugin_id);
|
Chris@0
|
165 }
|
Chris@0
|
166 return $this->instances[$plugin_id];
|
Chris@0
|
167 }
|
Chris@0
|
168
|
Chris@0
|
169 /**
|
Chris@0
|
170 * {@inheritdoc}
|
Chris@0
|
171 */
|
Chris@0
|
172 public function mail($module, $key, $to, $langcode, $params = [], $reply = NULL, $send = TRUE) {
|
Chris@0
|
173 // Mailing can invoke rendering (e.g., generating URLs, replacing tokens),
|
Chris@0
|
174 // but e-mails are not HTTP responses: they're not cached, they don't have
|
Chris@0
|
175 // attachments. Therefore we perform mailing inside its own render context,
|
Chris@0
|
176 // to ensure it doesn't leak into the render context for the HTTP response
|
Chris@0
|
177 // to the current request.
|
Chris@0
|
178 return $this->renderer->executeInRenderContext(new RenderContext(), function () use ($module, $key, $to, $langcode, $params, $reply, $send) {
|
Chris@0
|
179 return $this->doMail($module, $key, $to, $langcode, $params, $reply, $send);
|
Chris@0
|
180 });
|
Chris@0
|
181 }
|
Chris@0
|
182
|
Chris@0
|
183 /**
|
Chris@0
|
184 * Composes and optionally sends an email message.
|
Chris@0
|
185 *
|
Chris@0
|
186 * @param string $module
|
Chris@0
|
187 * A module name to invoke hook_mail() on. The {$module}_mail() hook will be
|
Chris@0
|
188 * called to complete the $message structure which will already contain
|
Chris@0
|
189 * common defaults.
|
Chris@0
|
190 * @param string $key
|
Chris@0
|
191 * A key to identify the email sent. The final message ID for email altering
|
Chris@0
|
192 * will be {$module}_{$key}.
|
Chris@0
|
193 * @param string $to
|
Chris@0
|
194 * The email address or addresses where the message will be sent to. The
|
Chris@0
|
195 * formatting of this string will be validated with the
|
Chris@0
|
196 * @link http://php.net/manual/filter.filters.validate.php PHP email validation filter. @endlink
|
Chris@0
|
197 * Some examples are:
|
Chris@0
|
198 * - user@example.com
|
Chris@0
|
199 * - user@example.com, anotheruser@example.com
|
Chris@0
|
200 * - User <user@example.com>
|
Chris@0
|
201 * - User <user@example.com>, Another User <anotheruser@example.com>
|
Chris@0
|
202 * @param string $langcode
|
Chris@0
|
203 * Language code to use to compose the email.
|
Chris@0
|
204 * @param array $params
|
Chris@0
|
205 * (optional) Parameters to build the email.
|
Chris@0
|
206 * @param string|null $reply
|
Chris@0
|
207 * Optional email address to be used to answer.
|
Chris@0
|
208 * @param bool $send
|
Chris@0
|
209 * If TRUE, call an implementation of
|
Chris@0
|
210 * \Drupal\Core\Mail\MailInterface->mail() to deliver the message, and
|
Chris@0
|
211 * store the result in $message['result']. Modules implementing
|
Chris@0
|
212 * hook_mail_alter() may cancel sending by setting $message['send'] to
|
Chris@0
|
213 * FALSE.
|
Chris@0
|
214 *
|
Chris@0
|
215 * @return array
|
Chris@0
|
216 * The $message array structure containing all details of the message. If
|
Chris@0
|
217 * already sent ($send = TRUE), then the 'result' element will contain the
|
Chris@0
|
218 * success indicator of the email, failure being already written to the
|
Chris@0
|
219 * watchdog. (Success means nothing more than the message being accepted at
|
Chris@0
|
220 * php-level, which still doesn't guarantee it to be delivered.)
|
Chris@0
|
221 *
|
Chris@0
|
222 * @see \Drupal\Core\Mail\MailManagerInterface::mail()
|
Chris@0
|
223 */
|
Chris@0
|
224 public function doMail($module, $key, $to, $langcode, $params = [], $reply = NULL, $send = TRUE) {
|
Chris@0
|
225 $site_config = $this->configFactory->get('system.site');
|
Chris@0
|
226 $site_mail = $site_config->get('mail');
|
Chris@0
|
227 if (empty($site_mail)) {
|
Chris@0
|
228 $site_mail = ini_get('sendmail_from');
|
Chris@0
|
229 }
|
Chris@0
|
230
|
Chris@0
|
231 // Bundle up the variables into a structured array for altering.
|
Chris@0
|
232 $message = [
|
Chris@0
|
233 'id' => $module . '_' . $key,
|
Chris@0
|
234 'module' => $module,
|
Chris@0
|
235 'key' => $key,
|
Chris@0
|
236 'to' => $to,
|
Chris@0
|
237 'from' => $site_mail,
|
Chris@0
|
238 'reply-to' => $reply,
|
Chris@0
|
239 'langcode' => $langcode,
|
Chris@0
|
240 'params' => $params,
|
Chris@0
|
241 'send' => TRUE,
|
Chris@0
|
242 'subject' => '',
|
Chris@0
|
243 'body' => [],
|
Chris@0
|
244 ];
|
Chris@0
|
245
|
Chris@0
|
246 // Build the default headers.
|
Chris@0
|
247 $headers = [
|
Chris@0
|
248 'MIME-Version' => '1.0',
|
Chris@0
|
249 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
|
Chris@0
|
250 'Content-Transfer-Encoding' => '8Bit',
|
Chris@0
|
251 'X-Mailer' => 'Drupal',
|
Chris@0
|
252 ];
|
Chris@0
|
253 // To prevent email from looking like spam, the addresses in the Sender and
|
Chris@0
|
254 // Return-Path headers should have a domain authorized to use the
|
Chris@0
|
255 // originating SMTP server.
|
Chris@0
|
256 $headers['Sender'] = $headers['Return-Path'] = $site_mail;
|
Chris@17
|
257 // Make sure the site-name is a RFC-2822 compliant 'display-name'.
|
Chris@17
|
258 $headers['From'] = MailHelper::formatDisplayName($site_config->get('name')) . ' <' . $site_mail . '>';
|
Chris@0
|
259 if ($reply) {
|
Chris@0
|
260 $headers['Reply-to'] = $reply;
|
Chris@0
|
261 }
|
Chris@0
|
262 $message['headers'] = $headers;
|
Chris@0
|
263
|
Chris@0
|
264 // Build the email (get subject and body, allow additional headers) by
|
Chris@0
|
265 // invoking hook_mail() on this module. We cannot use
|
Chris@0
|
266 // moduleHandler()->invoke() as we need to have $message by reference in
|
Chris@0
|
267 // hook_mail().
|
Chris@0
|
268 if (function_exists($function = $module . '_mail')) {
|
Chris@0
|
269 $function($key, $message, $params);
|
Chris@0
|
270 }
|
Chris@0
|
271
|
Chris@0
|
272 // Invoke hook_mail_alter() to allow all modules to alter the resulting
|
Chris@0
|
273 // email.
|
Chris@0
|
274 $this->moduleHandler->alter('mail', $message);
|
Chris@0
|
275
|
Chris@0
|
276 // Retrieve the responsible implementation for this message.
|
Chris@0
|
277 $system = $this->getInstance(['module' => $module, 'key' => $key]);
|
Chris@0
|
278
|
Chris@17
|
279 // Attempt to convert relative URLs to absolute.
|
Chris@17
|
280 foreach ($message['body'] as &$body_part) {
|
Chris@17
|
281 if ($body_part instanceof MarkupInterface) {
|
Chris@17
|
282 $body_part = Markup::create(Html::transformRootRelativeUrlsToAbsolute((string) $body_part, \Drupal::request()->getSchemeAndHttpHost()));
|
Chris@17
|
283 }
|
Chris@17
|
284 }
|
Chris@17
|
285
|
Chris@0
|
286 // Format the message body.
|
Chris@0
|
287 $message = $system->format($message);
|
Chris@0
|
288
|
Chris@0
|
289 // Optionally send email.
|
Chris@0
|
290 if ($send) {
|
Chris@0
|
291 // The original caller requested sending. Sending was canceled by one or
|
Chris@0
|
292 // more hook_mail_alter() implementations. We set 'result' to NULL,
|
Chris@0
|
293 // because FALSE indicates an error in sending.
|
Chris@0
|
294 if (empty($message['send'])) {
|
Chris@0
|
295 $message['result'] = NULL;
|
Chris@0
|
296 }
|
Chris@0
|
297 // Sending was originally requested and was not canceled.
|
Chris@0
|
298 else {
|
Chris@0
|
299 // Ensure that subject is plain text. By default translated and
|
Chris@0
|
300 // formatted strings are prepared for the HTML context and email
|
Chris@0
|
301 // subjects are plain strings.
|
Chris@0
|
302 if ($message['subject']) {
|
Chris@0
|
303 $message['subject'] = PlainTextOutput::renderFromHtml($message['subject']);
|
Chris@0
|
304 }
|
Chris@0
|
305 $message['result'] = $system->mail($message);
|
Chris@0
|
306 // Log errors.
|
Chris@0
|
307 if (!$message['result']) {
|
Chris@0
|
308 $this->loggerFactory->get('mail')
|
Chris@0
|
309 ->error('Error sending email (from %from to %to with reply-to %reply).', [
|
Chris@0
|
310 '%from' => $message['from'],
|
Chris@0
|
311 '%to' => $message['to'],
|
Chris@0
|
312 '%reply' => $message['reply-to'] ? $message['reply-to'] : $this->t('not set'),
|
Chris@0
|
313 ]);
|
Chris@17
|
314 $this->messenger()->addError($this->t('Unable to send email. Contact the site administrator if the problem persists.'));
|
Chris@0
|
315 }
|
Chris@0
|
316 }
|
Chris@0
|
317 }
|
Chris@0
|
318
|
Chris@0
|
319 return $message;
|
Chris@0
|
320 }
|
Chris@0
|
321
|
Chris@0
|
322 }
|