Passed
Push — master ( fc4e87...bbd3e2 )
by Blizzz
17:23 queued 18s
created

Mailer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 7
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arne Hamann <[email protected]>
9
 * @author Branko Kokanovic <[email protected]>
10
 * @author Carsten Wiedmann <[email protected]>
11
 * @author Christoph Wurst <[email protected]>
12
 * @author Jared Boone <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Julius Härtl <[email protected]>
15
 * @author kevin147147 <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author Morris Jobke <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Tekhnee <[email protected]>
20
 *
21
 * @license AGPL-3.0
22
 *
23
 * This code is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License, version 3,
25
 * as published by the Free Software Foundation.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU Affero General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU Affero General Public License, version 3,
33
 * along with this program. If not, see <http://www.gnu.org/licenses/>
34
 *
35
 */
36
namespace OC\Mail;
37
38
use Egulias\EmailValidator\EmailValidator;
39
use Egulias\EmailValidator\Validation\RFCValidation;
40
use OCP\Defaults;
41
use OCP\EventDispatcher\IEventDispatcher;
42
use OCP\IBinaryFinder;
43
use OCP\IConfig;
44
use OCP\IL10N;
45
use OCP\IURLGenerator;
46
use OCP\L10N\IFactory;
47
use OCP\Mail\Events\BeforeMessageSent;
48
use OCP\Mail\IAttachment;
49
use OCP\Mail\IEMailTemplate;
50
use OCP\Mail\IMailer;
51
use OCP\Mail\IMessage;
52
use Psr\Log\LoggerInterface;
53
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
54
use Symfony\Component\Mailer\Mailer as SymfonyMailer;
55
use Symfony\Component\Mailer\MailerInterface;
56
use Symfony\Component\Mailer\Transport\SendmailTransport;
57
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
58
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
59
use Symfony\Component\Mime\Email;
60
use Symfony\Component\Mime\Exception\InvalidArgumentException;
61
use Symfony\Component\Mime\Exception\RfcComplianceException;
62
63
/**
64
 * Class Mailer provides some basic functions to create a mail message that can be used in combination with
65
 * \OC\Mail\Message.
66
 *
67
 * Example usage:
68
 *
69
 * 	$mailer = \OC::$server->getMailer();
70
 * 	$message = $mailer->createMessage();
71
 * 	$message->setSubject('Your Subject');
72
 * 	$message->setFrom(array('[email protected]' => 'ownCloud Notifier'));
73
 * 	$message->setTo(array('[email protected]' => 'Recipient'));
74
 * 	$message->setBody('The message text', 'text/html');
75
 * 	$mailer->send($message);
76
 *
77
 * This message can then be passed to send() of \OC\Mail\Mailer
78
 *
79
 * @package OC\Mail
80
 */
81
class Mailer implements IMailer {
82
	private ?MailerInterface $instance = null;
83
	private IConfig $config;
84
	private LoggerInterface $logger;
85
	private Defaults $defaults;
86
	private IURLGenerator $urlGenerator;
87
	private IL10N $l10n;
88
	private IEventDispatcher $dispatcher;
89
	private IFactory $l10nFactory;
90
91
	public function __construct(IConfig $config,
92
						 LoggerInterface $logger,
93
						 Defaults $defaults,
94
						 IURLGenerator $urlGenerator,
95
						 IL10N $l10n,
96
						 IEventDispatcher $dispatcher,
97
						 IFactory $l10nFactory) {
98
		$this->config = $config;
99
		$this->logger = $logger;
100
		$this->defaults = $defaults;
101
		$this->urlGenerator = $urlGenerator;
102
		$this->l10n = $l10n;
103
		$this->dispatcher = $dispatcher;
104
		$this->l10nFactory = $l10nFactory;
105
	}
106
107
	/**
108
	 * Creates a new message object that can be passed to send()
109
	 *
110
	 * @return Message
111
	 */
112
	public function createMessage(): Message {
113
		$plainTextOnly = $this->config->getSystemValue('mail_send_plaintext_only', false);
114
		return new Message(new Email(), $plainTextOnly);
115
	}
116
117
	/**
118
	 * @param string|null $data
119
	 * @param string|null $filename
120
	 * @param string|null $contentType
121
	 * @return IAttachment
122
	 * @since 13.0.0
123
	 */
124
	public function createAttachment($data = null, $filename = null, $contentType = null): IAttachment {
125
		return new Attachment($data, $filename, $contentType);
126
	}
127
128
	/**
129
	 * @param string $path
130
	 * @param string|null $contentType
131
	 * @return IAttachment
132
	 * @since 13.0.0
133
	 */
134
	public function createAttachmentFromPath(string $path, $contentType = null): IAttachment {
135
		return new Attachment(null, null, $contentType, $path);
136
	}
137
138
	/**
139
	 * Creates a new email template object
140
	 *
141
	 * @param string $emailId
142
	 * @param array $data
143
	 * @return IEMailTemplate
144
	 * @since 12.0.0
145
	 */
146
	public function createEMailTemplate(string $emailId, array $data = []): IEMailTemplate {
147
		$class = $this->config->getSystemValue('mail_template_class', '');
148
149
		if ($class !== '' && class_exists($class) && is_a($class, EMailTemplate::class, true)) {
150
			return new $class(
151
				$this->defaults,
152
				$this->urlGenerator,
153
				$this->l10nFactory,
154
				$emailId,
155
				$data
156
			);
157
		}
158
159
		return new EMailTemplate(
160
			$this->defaults,
161
			$this->urlGenerator,
162
			$this->l10nFactory,
163
			$emailId,
164
			$data
165
		);
166
	}
167
168
	/**
169
	 * Send the specified message. Also sets the from address to the value defined in config.php
170
	 * if no-one has been passed.
171
	 *
172
	 * If sending failed, the recipients that failed will be returned (to, cc and bcc).
173
	 * Will output additional debug info if 'mail_smtpdebug' => 'true' is set in config.php
174
	 *
175
	 * @param IMessage $message Message to send
176
	 * @return string[] $failedRecipients
177
	 */
178
	public function send(IMessage $message): array {
179
		$debugMode = $this->config->getSystemValue('mail_smtpdebug', false);
180
181
		if (!($message instanceof Message)) {
182
			throw new InvalidArgumentException('Object not of type ' . Message::class);
183
		}
184
185
		if (empty($message->getFrom())) {
186
			$message->setFrom([\OCP\Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]);
187
		}
188
189
		$mailer = $this->getInstance();
190
191
		$this->dispatcher->dispatchTyped(new BeforeMessageSent($message));
192
193
		try {
194
			$message->setRecipients();
195
		} catch (InvalidArgumentException|RfcComplianceException $e) {
196
			$logMessage = sprintf(
197
				'Could not send mail to "%s" with subject "%s" as validation for address failed',
198
				print_r(array_merge($message->getTo(), $message->getCc(), $message->getBcc()), true),
199
				$message->getSubject()
200
			);
201
			$this->logger->debug($logMessage, ['app' => 'core', 'exception' => $e]);
202
			$recipients = array_merge($message->getTo(), $message->getCc(), $message->getBcc());
203
			$failedRecipients = [];
204
205
			array_walk($recipients, function ($value, $key) use (&$failedRecipients) {
206
				if (is_numeric($key)) {
207
					$failedRecipients[] = $value;
208
				} else {
209
					$failedRecipients[] = $key;
210
				}
211
			});
212
213
			return $failedRecipients;
214
		}
215
216
		try {
217
			$mailer->send($message->getSymfonyEmail());
218
		} catch (TransportExceptionInterface $e) {
219
			$logMessage = sprintf('Sending mail to "%s" with subject "%s" failed', print_r($message->getTo(), true), $message->getSubject());
220
			$this->logger->debug($logMessage, ['app' => 'core', 'exception' => $e]);
221
			if ($debugMode) {
222
				$this->logger->debug($e->getDebug(), ['app' => 'core']);
223
			}
224
			$recipients = array_merge($message->getTo(), $message->getCc(), $message->getBcc());
225
			$failedRecipients = [];
226
227
			array_walk($recipients, function ($value, $key) use (&$failedRecipients) {
228
				if (is_numeric($key)) {
229
					$failedRecipients[] = $value;
230
				} else {
231
					$failedRecipients[] = $key;
232
				}
233
			});
234
235
			return $failedRecipients;
236
		}
237
238
		// Debugging logging
239
		$logMessage = sprintf('Sent mail to "%s" with subject "%s"', print_r($message->getTo(), true), $message->getSubject());
240
		$this->logger->debug($logMessage, ['app' => 'core']);
241
242
		return [];
243
	}
244
245
	/**
246
	 * @deprecated 26.0.0 Implicit validation is done in \OC\Mail\Message::setRecipients
247
	 *                    via \Symfony\Component\Mime\Address::__construct
248
	 *
249
	 * @param string $email Email address to be validated
250
	 * @return bool True if the mail address is valid, false otherwise
251
	 */
252
	public function validateMailAddress(string $email): bool {
253
		if ($email === '') {
254
			// Shortcut: empty addresses are never valid
255
			return false;
256
		}
257
		$validator = new EmailValidator();
258
		$validation = new RFCValidation();
259
260
		return $validator->isValid($email, $validation);
261
	}
262
263
	protected function getInstance(): MailerInterface {
264
		if (!is_null($this->instance)) {
265
			return $this->instance;
266
		}
267
268
		$transport = null;
269
270
		switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) {
271
			case 'sendmail':
272
				$transport = $this->getSendMailInstance();
273
				break;
274
			case 'smtp':
275
			default:
276
				$transport = $this->getSmtpInstance();
277
				break;
278
		}
279
280
		return new SymfonyMailer($transport);
281
	}
282
283
	/**
284
	 * Returns the SMTP transport
285
	 *
286
	 * Only supports ssl/tls
287
	 * starttls is not enforcable with Symfony Mailer but might be available
288
	 * via the automatic config (Symfony Mailer internal)
289
	 *
290
	 * @return EsmtpTransport
291
	 */
292
	protected function getSmtpInstance(): EsmtpTransport {
293
		// either null or true - if nothing is passed, let the symfony mailer figure out the configuration by itself
294
		$mailSmtpsecure = ($this->config->getSystemValue('mail_smtpsecure', null) === 'ssl') ? true : null;
295
		$transport = new EsmtpTransport(
296
			$this->config->getSystemValue('mail_smtphost', '127.0.0.1'),
297
			(int)$this->config->getSystemValue('mail_smtpport', 25),
298
			$mailSmtpsecure,
299
			null,
300
			$this->logger
301
		);
302
		/** @var SocketStream $stream */
303
		$stream = $transport->getStream();
304
		/** @psalm-suppress InternalMethod */
305
		$stream->setTimeout($this->config->getSystemValue('mail_smtptimeout', 10));
306
307
		if ($this->config->getSystemValue('mail_smtpauth', false)) {
308
			$transport->setUsername($this->config->getSystemValue('mail_smtpname', ''));
309
			$transport->setPassword($this->config->getSystemValue('mail_smtppassword', ''));
310
		}
311
312
		$streamingOptions = $this->config->getSystemValue('mail_smtpstreamoptions', []);
313
		if (is_array($streamingOptions) && !empty($streamingOptions)) {
314
			/** @psalm-suppress InternalMethod */
315
			$currentStreamingOptions = $stream->getStreamOptions();
316
317
			$currentStreamingOptions = array_merge_recursive($currentStreamingOptions, $streamingOptions);
318
319
			/** @psalm-suppress InternalMethod */
320
			$stream->setStreamOptions($currentStreamingOptions);
321
		}
322
323
		$overwriteCliUrl = parse_url(
324
			$this->config->getSystemValueString('overwrite.cli.url', ''),
325
			PHP_URL_HOST
326
		);
327
328
		if (!empty($overwriteCliUrl)) {
329
			$transport->setLocalDomain($overwriteCliUrl);
330
		}
331
332
		return $transport;
333
	}
334
335
	/**
336
	 * Returns the sendmail transport
337
	 *
338
	 * @return SendmailTransport
339
	 */
340
	protected function getSendMailInstance(): SendmailTransport {
341
		switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) {
342
			case 'qmail':
343
				$binaryPath = '/var/qmail/bin/sendmail';
344
				break;
345
			default:
346
				$sendmail = \OCP\Server::get(IBinaryFinder::class)->findBinaryPath('sendmail');
347
				if ($sendmail === null) {
348
					$sendmail = '/usr/sbin/sendmail';
349
				}
350
				$binaryPath = $sendmail;
351
				break;
352
		}
353
354
		switch ($this->config->getSystemValue('mail_sendmailmode', 'smtp')) {
355
			case 'pipe':
356
				$binaryParam = ' -t';
357
				break;
358
			default:
359
				$binaryParam = ' -bs';
360
				break;
361
		}
362
363
		return new SendmailTransport($binaryPath . $binaryParam, null, $this->logger);
364
	}
365
}
366