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