[FEATURE] Add contentObject functionality to form MailPostProcessor
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / PostProcess / MailPostProcessor.php
1 <?php
2 namespace TYPO3\CMS\Form\PostProcess;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\MailUtility;
19 use TYPO3\CMS\Core\Utility\MathUtility;
20 use TYPO3\CMS\Core\Mail\Rfc822AddressesParser;
21 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
22 use TYPO3\CMS\Form\Utility\FormUtility;
23
24 /**
25 * The mail post processor
26 */
27 class MailPostProcessor extends AbstractPostProcessor implements PostProcessorInterface {
28
29 /**
30 * Constant for localization
31 *
32 * @var string
33 */
34 const LOCALISATION_OBJECT_NAME = 'tx_form_view_mail';
35
36 /**
37 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
38 */
39 protected $objectManager;
40
41 /**
42 * @var \TYPO3\CMS\Form\Utility\SessionUtility
43 */
44 protected $sessionUtility;
45
46 /**
47 * @var FormUtility
48 */
49 protected $formUtility;
50
51 /**
52 * @var \TYPO3\CMS\Form\Domain\Model\Element
53 */
54 protected $form;
55
56 /**
57 * @var array
58 */
59 protected $typoScript;
60
61 /**
62 * @var \TYPO3\CMS\Core\Mail\MailMessage
63 */
64 protected $mailMessage;
65
66 /**
67 * @var string
68 */
69 protected $htmlMailTemplatePath = 'Html';
70
71 /**
72 * @var string
73 */
74 protected $plaintextMailTemplatePath = 'Plain';
75
76 /**
77 * @var array
78 */
79 protected $dirtyHeaders = array();
80
81 /**
82 * @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
83 * @return void
84 */
85 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager) {
86 $this->objectManager = $objectManager;
87 }
88
89 /**
90 * @param \TYPO3\CMS\Form\Utility\SessionUtility $sessionUtility
91 * @return void
92 */
93 public function injectSessionUtility(\TYPO3\CMS\Form\Utility\SessionUtility $sessionUtility) {
94 $this->sessionUtility = $sessionUtility;
95 }
96
97 /**
98 * Constructor
99 *
100 * @param \TYPO3\CMS\Form\Domain\Model\Element $form Form domain model
101 * @param array $typoScript Post processor TypoScript settings
102 */
103 public function __construct(\TYPO3\CMS\Form\Domain\Model\Element $form, array $typoScript) {
104 $this->form = $form;
105 $this->typoScript = $typoScript;
106 $this->mailMessage = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
107 $this->setTemplatePaths();
108 }
109
110 /**
111 * The main method called by the post processor
112 *
113 * Configures the mail message
114 *
115 * @return string HTML message from this processor
116 */
117 public function process() {
118 $this->formUtility = FormUtility::create($this->controllerContext->getConfiguration());
119 $this->setSubject();
120 $this->setFrom();
121 $this->setTo();
122 $this->setCc();
123 $this->setReplyTo();
124 $this->setPriority();
125 $this->setOrganization();
126 $this->setHtmlContent();
127 $this->setPlainContent();
128 $this->addAttachmentsFromSession();
129 $this->send();
130 return $this->render();
131 }
132
133 /**
134 * Sets the subject of the mail message
135 *
136 * If not configured, it will use a default setting
137 *
138 * @return void
139 */
140 protected function setSubject() {
141 if (isset($this->typoScript['subject'])) {
142 $subject = $this->formUtility->renderItem(
143 $this->typoScript['subject.'],
144 $this->typoScript['subject']
145 );
146 } elseif ($this->getTypoScriptValueFromIncomingData('subjectField') !== NULL) {
147 $subject = $this->getTypoScriptValueFromIncomingData('subjectField');
148 } else {
149 $subject = 'Formmail on ' . GeneralUtility::getIndpEnv('HTTP_HOST');
150 }
151
152 $subject = $this->sanitizeHeaderString($subject);
153 $this->mailMessage->setSubject($subject);
154 }
155
156 /**
157 * Sets the sender of the mail message
158 *
159 * Mostly the sender is a combination of the name and the email address
160 *
161 * @return void
162 */
163 protected function setFrom() {
164 if (isset($this->typoScript['senderEmail'])) {
165 $fromEmail = $this->formUtility->renderItem(
166 $this->typoScript['senderEmail.'],
167 $this->typoScript['senderEmail']
168 );
169 } elseif ($this->getTypoScriptValueFromIncomingData('senderEmailField') !== NULL) {
170 $fromEmail = $this->getTypoScriptValueFromIncomingData('senderEmailField');
171 } else {
172 $fromEmail = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'];
173 }
174 if (!GeneralUtility::validEmail($fromEmail)) {
175 $fromEmail = MailUtility::getSystemFromAddress();
176 }
177 if (isset($this->typoScript['senderName'])) {
178 $fromName = $this->formUtility->renderItem(
179 $this->typoScript['senderName.'],
180 $this->typoScript['senderName']
181 );
182 } elseif ($this->getTypoScriptValueFromIncomingData('senderNameField') !== NULL) {
183 $fromName = $this->getTypoScriptValueFromIncomingData('senderNameField');
184 } else {
185 $fromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'];
186 }
187 $fromName = $this->sanitizeHeaderString($fromName);
188 if (!empty($fromName)) {
189 $from = array($fromEmail => $fromName);
190 } else {
191 $from = $fromEmail;
192 }
193 $this->mailMessage->setFrom($from);
194 }
195
196 /**
197 * Filter input-string for valid email addresses
198 *
199 * @param string $emails If this is a string, it will be checked for one or more valid email addresses.
200 * @return array List of valid email addresses
201 */
202 protected function filterValidEmails($emails) {
203 if (!is_string($emails)) {
204 // No valid addresses - empty list
205 return array();
206 }
207
208 /** @var $addressParser Rfc822AddressesParser */
209 $addressParser = GeneralUtility::makeInstance(Rfc822AddressesParser::class, $emails);
210 $addresses = $addressParser->parseAddressList();
211
212 $validEmails = array();
213 foreach ($addresses as $address) {
214 $fullAddress = $address->mailbox . '@' . $address->host;
215 if (GeneralUtility::validEmail($fullAddress)) {
216 if ($address->personal) {
217 $validEmails[$fullAddress] = $address->personal;
218 } else {
219 $validEmails[] = $fullAddress;
220 }
221 }
222 }
223 return $validEmails;
224 }
225
226 /**
227 * Adds the receiver of the mail message when configured
228 *
229 * Checks the address if it is a valid email address
230 *
231 * @return void
232 */
233 protected function setTo() {
234 $emails = $this->formUtility->renderItem(
235 $this->typoScript['recipientEmail.'],
236 $this->typoScript['recipientEmail']
237 );
238 $validEmails = $this->filterValidEmails($emails);
239 if (!empty($validEmails)) {
240 $this->mailMessage->setTo($validEmails);
241 }
242 }
243
244 /**
245 * Adds the carbon copy receiver of the mail message when configured
246 *
247 * Checks the address if it is a valid email address
248 *
249 * @return void
250 */
251 protected function setCc() {
252 $emails = $this->formUtility->renderItem(
253 $this->typoScript['ccEmail.'],
254 $this->typoScript['ccEmail']
255 );
256 $validEmails = $this->filterValidEmails($emails);
257 if (!empty($validEmails)) {
258 $this->mailMessage->setCc($validEmails);
259 }
260 }
261
262 /**
263 * Adds the reply to header of the mail message when configured
264 *
265 * Checks the address if it is a valid email address
266 *
267 * @return void
268 */
269 protected function setReplyTo() {
270 if (isset($this->typoScript['replyToEmail'])) {
271 $emails = $this->formUtility->renderItem(
272 $this->typoScript['replyToEmail.'],
273 $this->typoScript['replyToEmail']
274 );
275 } elseif ($this->getTypoScriptValueFromIncomingData('replyToEmailField') !== NULL) {
276 $emails = $this->getTypoScriptValueFromIncomingData('replyToEmailField');
277 }
278 $validEmails = $this->filterValidEmails($emails);
279 if (!empty($validEmails)) {
280 $this->mailMessage->setReplyTo($validEmails);
281 }
282 }
283
284 /**
285 * Set the priority of the mail message
286 *
287 * When not in settings, the value will be 3. If the priority is configured,
288 * but too big, it will be set to 5, which means very low.
289 *
290 * @return void
291 */
292 protected function setPriority() {
293 $priority = 3;
294 if (isset($this->typoScript['priority'])) {
295 $priorityFromTs = $this->formUtility->renderItem(
296 $this->typoScript['priority.'],
297 $this->typoScript['priority']
298 );
299 }
300 if (!empty($priorityFromTs)) {
301 $priority = MathUtility::forceIntegerInRange($priorityFromTs, 1, 5);
302 }
303 $this->mailMessage->setPriority($priority);
304 }
305
306 /**
307 * Add a text header to the mail header of the type Organization
308 *
309 * Sanitizes the header string when necessary
310 *
311 * @return void
312 */
313 protected function setOrganization() {
314 if (isset($this->typoScript['organization'])) {
315 $organization = $this->formUtility->renderItem(
316 $this->typoScript['organization.'],
317 $this->typoScript['organization']
318 );
319 }
320 if (!empty($organization)) {
321 $organization = $this->sanitizeHeaderString($organization);
322 $this->mailMessage->getHeaders()->addTextHeader('Organization', $organization);
323 }
324 }
325
326 /**
327 * Set the default character set used
328 *
329 * Respect formMailCharset if it was set, otherwise use metaCharset for mail
330 * if different from renderCharset
331 *
332 * @return void
333 */
334 protected function setCharacterSet() {
335 $characterSet = NULL;
336 if ($GLOBALS['TSFE']->config['config']['formMailCharset']) {
337 $characterSet = $GLOBALS['TSFE']->csConvObj->parse_charset($GLOBALS['TSFE']->config['config']['formMailCharset']);
338 } elseif ($GLOBALS['TSFE']->metaCharset != $GLOBALS['TSFE']->renderCharset) {
339 $characterSet = $GLOBALS['TSFE']->metaCharset;
340 }
341 if ($characterSet) {
342 $this->mailMessage->setCharset($characterSet);
343 }
344 }
345
346 /**
347 * Add the HTML content
348 *
349 * Add a MimePart of the type text/html to the message.
350 *
351 * @return void
352 */
353 protected function setHtmlContent() {
354 $htmlContent = $this->getView($this->htmlMailTemplatePath)->render();
355 $this->mailMessage->setBody($htmlContent, 'text/html');
356 }
357
358 /**
359 * Add the plain content
360 *
361 * Add a MimePart of the type text/plain to the message.
362 *
363 * @return void
364 */
365 protected function setPlainContent() {
366 $plainContent = $this->getView($this->plaintextMailTemplatePath, 'Plain')->render();
367 $this->mailMessage->addPart($plainContent, 'text/plain');
368 }
369
370 /**
371 * Sends the mail.
372 * Sending the mail requires the recipient and message to be set.
373 *
374 * @return void
375 */
376 protected function send() {
377 if ($this->mailMessage->getTo() && $this->mailMessage->getBody()) {
378 $this->mailMessage->send();
379 }
380 }
381
382 /**
383 * Render the message after trying to send the mail
384 *
385 * @return string HTML message from the mail view
386 */
387 protected function render() {
388 if ($this->mailMessage->isSent()) {
389 $output = $this->renderMessage('success');
390 } else {
391 $output = $this->renderMessage('error');
392 }
393 return $output;
394 }
395
396 /**
397 * Checks string for suspicious characters
398 *
399 * @param string $string String to check
400 * @return string Valid or empty string
401 */
402 protected function sanitizeHeaderString($string) {
403 $pattern = '/[\\r\\n\\f\\e]/';
404 if (preg_match($pattern, $string) > 0) {
405 $this->dirtyHeaders[] = $string;
406 $string = '';
407 }
408 return $string;
409 }
410
411 /**
412 * Loop through all elements of the session and attach the file
413 * if its a uploaded file
414 *
415 * @return void
416 */
417 protected function addAttachmentsFromSession() {
418 $sessionData = $this->sessionUtility->getSessionData();
419 if (is_array($sessionData)) {
420 foreach ($sessionData as $fieldName => $values) {
421 if (is_array($values)) {
422 foreach ($values as $file) {
423 if (isset($file['tempFilename'])) {
424 if (
425 is_file($file['tempFilename'])
426 && GeneralUtility::isAllowedAbsPath($file['tempFilename'])
427 ) {
428 $this->mailMessage->attach(\Swift_Attachment::fromPath($file['tempFilename'])->setFilename($file['originalFilename']));
429 }
430 }
431 }
432 }
433 }
434 }
435 }
436
437 /**
438 * Set the html and plaintext templates
439 *
440 * @return void
441 */
442 protected function setTemplatePaths() {
443 if (
444 isset($this->typoScript['htmlMailTemplatePath'])
445 && $this->typoScript['htmlMailTemplatePath'] !== ''
446 ) {
447 $this->htmlMailTemplatePath = $this->typoScript['htmlMailTemplatePath'];
448 }
449
450 if (
451 isset($this->typoScript['plaintextMailTemplatePath'])
452 && $this->typoScript['plaintextMailTemplatePath'] !== ''
453 ) {
454 $this->plaintextMailTemplatePath = $this->typoScript['plaintextMailTemplatePath'];
455 }
456 }
457
458 /**
459 * Make fluid view instance
460 *
461 * @param string $templateName
462 * @param string $scope
463 * @return \TYPO3\CMS\Fluid\View\StandaloneView
464 */
465 protected function getView($templateName, $scope = 'Html') {
466 $configurationManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Configuration\ConfigurationManager::class);
467 $typoScript = $configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
468 /** @var \TYPO3\CMS\Fluid\View\StandaloneView $view */
469 $view = $this->objectManager->get(\TYPO3\CMS\Fluid\View\StandaloneView::class);
470
471 $viewParts = array(
472 'templateRootPaths' => $typoScript['view']['templateRootPaths'],
473 'partialRootPaths' => $typoScript['view']['partialRootPaths'],
474 );
475 /* Extend all template root paths to $templateRootPaths/PostProcessor/Mail/$themeName */
476 foreach ($typoScript['view']['templateRootPaths'] as &$path) {
477 if (substr($path, -1) !== '/') {
478 $path .= '/';
479 }
480 $path .= 'PostProcessor/Mail/' . $this->controllerContext->getConfiguration()->getThemeName();
481 }
482 /* Extend all partial root paths to $partialRootPaths/$themeName/PostProcessor/Mail/$scope/ */
483 foreach ($typoScript['view']['partialRootPaths'] as &$path) {
484 if (substr($path, -1) !== '/') {
485 $path .= '/';
486 }
487 $path .= $this->controllerContext->getConfiguration()->getThemeName() . '/PostProcessor/Mail/' . $scope . '/';
488 }
489 $view->setLayoutRootPaths($typoScript['view']['layoutRootPaths']);
490 $view->setTemplateRootPaths($typoScript['view']['templateRootPaths']);
491 $view->setPartialRootPaths($typoScript['view']['partialRootPaths']);
492 $view->setTemplate($templateName);
493 $view->assignMultiple(array(
494 'model' => $this->form
495 ));
496 return $view;
497 }
498
499 /**
500 * Render the processor message
501 *
502 * @param string $messageType
503 * @return string
504 */
505 protected function renderMessage($messageType) {
506 return $this->formUtility->renderItem(
507 $this->typoScript['messages.'][$messageType . '.'],
508 $this->typoScript['messages.'][$messageType],
509 $this->getLocalLanguageLabel($messageType)
510 );
511 }
512
513 /**
514 * Get the local language label(s) for the message
515 * In some cases this method will be override by rule class
516 *
517 * @param string $type The type
518 * @return string The local language message label
519 */
520 protected function getLocalLanguageLabel($type = '') {
521 $label = static::LOCALISATION_OBJECT_NAME . '.' . $type;
522 $message = LocalizationUtility::translate($label, 'form');
523 return $message;
524 }
525
526 /**
527 * Determines user submitted data from a field
528 * that has been defined as TypoScript property.
529 *
530 * @param string $propertyName
531 * @return NULL|mixed
532 */
533 protected function getTypoScriptValueFromIncomingData($propertyName) {
534 if (empty($this->typoScript[$propertyName])) {
535 return NULL;
536 }
537
538 $propertyValue = $this->typoScript[$propertyName];
539 $incomingData = $this->controllerContext->getValidationElement();
540 if (!$incomingData->hasIncomingField($propertyValue)) {
541 return NULL;
542 }
543
544 return $incomingData->getIncomingField($propertyValue);
545 }
546
547 }