Completed
Push — master ( 182e31...3dc902 )
by
unknown
20:29
created

EmailFinisher::getRecipients()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 43
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 27
nc 15
nop 3
dl 0
loc 43
rs 8.5546
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Form\Domain\Finishers;
19
20
use Symfony\Component\Mime\Address;
21
use TYPO3\CMS\Core\Mail\MailMessage;
22
use TYPO3\CMS\Core\Utility\MathUtility;
23
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
24
use TYPO3\CMS\Fluid\View\StandaloneView;
25
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
26
use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
27
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
28
use TYPO3\CMS\Form\Service\TranslationService;
29
use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
30
31
/**
32
 * This finisher sends an email to one recipient
33
 *
34
 * Options:
35
 *
36
 * - templatePathAndFilename (mandatory): Template path and filename for the mail body
37
 * - layoutRootPath: root path for the layouts
38
 * - partialRootPath: root path for the partials
39
 * - variables: associative array of variables which are available inside the Fluid template
40
 *
41
 * The following options control the mail sending. In all of them, placeholders in the form
42
 * of {...} are replaced with the corresponding form value; i.e. {email} as senderAddress
43
 * makes the recipient address configurable.
44
 *
45
 * - subject (mandatory): Subject of the email
46
 * - recipients (mandatory): Email addresses and human-readable names of the recipients
47
 * - senderAddress (mandatory): Email address of the sender
48
 * - senderName: Human-readable name of the sender
49
 * - replyToRecipients: Email addresses and human-readable names of the reply-to recipients
50
 * - carbonCopyRecipients: Email addresses and human-readable names of the copy recipients
51
 * - blindCarbonCopyRecipients: Email addresses and human-readable names of the blind copy recipients
52
 * - format: Format of the email (one of the FORMAT_* constants). By default mails are sent as HTML.
53
 *
54
 * Scope: frontend
55
 */
56
class EmailFinisher extends AbstractFinisher
57
{
58
    const FORMAT_PLAINTEXT = 'plaintext';
59
    const FORMAT_HTML = 'html';
60
61
    /**
62
     * @var array
63
     */
64
    protected $defaultOptions = [
65
        'recipientName' => '',
66
        'senderName' => '',
67
        'addHtmlPart' => true,
68
        'attachUploads' => true,
69
    ];
70
71
    /**
72
     * Executes this finisher
73
     * @see AbstractFinisher::execute()
74
     *
75
     * @throws FinisherException
76
     */
77
    protected function executeInternal()
78
    {
79
        $languageBackup = null;
80
        // Flexform overrides write strings instead of integers so
81
        // we need to cast the string '0' to false.
82
        if (
83
            isset($this->options['addHtmlPart'])
84
            && $this->options['addHtmlPart'] === '0'
85
        ) {
86
            $this->options['addHtmlPart'] = false;
87
        }
88
89
        $subject = $this->parseOption('subject');
90
        $recipients = $this->getRecipients('recipients', 'recipientAddress', 'recipientName');
91
        $senderAddress = $this->parseOption('senderAddress');
92
        $senderAddress = is_string($senderAddress) ? $senderAddress : '';
93
        $senderName = $this->parseOption('senderName');
94
        $senderName = is_string($senderName) ? $senderName : '';
95
        $replyToRecipients = $this->getRecipients('replyToRecipients', 'replyToAddress');
96
        $carbonCopyRecipients = $this->getRecipients('carbonCopyRecipients', 'carbonCopyAddress');
97
        $blindCarbonCopyRecipients = $this->getRecipients('blindCarbonCopyRecipients', 'blindCarbonCopyAddress');
98
        $addHtmlPart = $this->isHtmlPartAdded();
99
        $attachUploads = $this->parseOption('attachUploads');
100
101
        if (empty($subject)) {
102
            throw new FinisherException('The option "subject" must be set for the EmailFinisher.', 1327060320);
103
        }
104
        if (empty($recipients)) {
105
            throw new FinisherException('The option "recipients" must be set for the EmailFinisher.', 1327060200);
106
        }
107
        if (empty($senderAddress)) {
108
            throw new FinisherException('The option "senderAddress" must be set for the EmailFinisher.', 1327060210);
109
        }
110
111
        $mail = $this->objectManager->get(MailMessage::class);
112
113
        $mail->from(new Address($senderAddress, $senderName))
114
            ->to(...$recipients)
115
            ->subject($subject);
116
117
        if (!empty($replyToRecipients)) {
118
            $mail->replyTo(...$replyToRecipients);
119
        }
120
121
        if (!empty($carbonCopyRecipients)) {
122
            $mail->cc(...$carbonCopyRecipients);
123
        }
124
125
        if (!empty($blindCarbonCopyRecipients)) {
126
            $mail->bcc(...$blindCarbonCopyRecipients);
127
        }
128
129
        $formRuntime = $this->finisherContext->getFormRuntime();
130
131
        $translationService = TranslationService::getInstance();
132
        if (isset($this->options['translation']['language']) && !empty($this->options['translation']['language'])) {
133
            $languageBackup = $translationService->getLanguage();
134
            $translationService->setLanguage($this->options['translation']['language']);
135
        }
136
137
        $parts = [
138
            [
139
                'format' => 'Plaintext',
140
                'contentType' => 'text/plain',
141
            ],
142
        ];
143
144
        if ($addHtmlPart) {
145
            $parts[] = [
146
                'format' => 'Html',
147
                'contentType' => 'text/html',
148
            ];
149
        }
150
151
        foreach ($parts as $i => $part) {
152
            $standaloneView = $this->initializeStandaloneView($formRuntime, $part['format']);
153
            $message = $standaloneView->render();
154
155
            if ($part['contentType'] === 'text/plain') {
156
                $mail->text($message);
157
            } else {
158
                $mail->html($message);
159
            }
160
        }
161
162
        if (!empty($languageBackup)) {
163
            $translationService->setLanguage($languageBackup);
164
        }
165
166
        $elements = $formRuntime->getFormDefinition()->getRenderablesRecursively();
167
168
        if ($attachUploads) {
169
            foreach ($elements as $element) {
170
                if (!$element instanceof FileUpload) {
171
                    continue;
172
                }
173
                $file = $formRuntime[$element->getIdentifier()];
174
                if ($file) {
175
                    if ($file instanceof FileReference) {
176
                        $file = $file->getOriginalResource();
177
                    }
178
179
                    $mail->attach($file->getContents(), $file->getName(), $file->getMimeType());
180
                }
181
            }
182
        }
183
184
        $mail->send();
185
    }
186
187
    /**
188
     * @param FormRuntime $formRuntime
189
     * @param string $format
190
     * @return StandaloneView
191
     * @throws FinisherException
192
     */
193
    protected function initializeStandaloneView(FormRuntime $formRuntime, string $format): StandaloneView
194
    {
195
        $standaloneView = $this->objectManager->get(StandaloneView::class);
196
197
        if (isset($this->options['templatePathAndFilename'])) {
198
            $this->options['templatePathAndFilename'] = strtr($this->options['templatePathAndFilename'], [
199
                '{@format}' => $format
200
            ]);
201
            $standaloneView->setTemplatePathAndFilename($this->options['templatePathAndFilename']);
202
        } else {
203
            if (!isset($this->options['templateName'])) {
204
                throw new FinisherException('The option "templateName" must be set for the EmailFinisher.', 1327058829);
205
            }
206
            // Use local variable instead of augmenting the options to
207
            // keep the format intact when sending multi-format mails
208
            $templateName = strtr($this->options['templateName'], [
209
                '{@format}' => $format
210
            ]);
211
            $standaloneView->setTemplate($templateName);
212
        }
213
214
        $standaloneView->assign('finisherVariableProvider', $this->finisherContext->getFinisherVariableProvider());
215
216
        if (isset($this->options['templateRootPaths']) && is_array($this->options['templateRootPaths'])) {
217
            $standaloneView->setTemplateRootPaths($this->options['templateRootPaths']);
218
        }
219
220
        if (isset($this->options['partialRootPaths']) && is_array($this->options['partialRootPaths'])) {
221
            $standaloneView->setPartialRootPaths($this->options['partialRootPaths']);
222
        }
223
224
        if (isset($this->options['layoutRootPaths']) && is_array($this->options['layoutRootPaths'])) {
225
            $standaloneView->setLayoutRootPaths($this->options['layoutRootPaths']);
226
        }
227
228
        if (isset($this->options['variables'])) {
229
            $standaloneView->assignMultiple($this->options['variables']);
230
        }
231
232
        $standaloneView->assign('form', $formRuntime);
233
        $standaloneView->getRenderingContext()
234
            ->getViewHelperVariableContainer()
235
            ->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);
236
237
        return $standaloneView;
238
    }
239
240
    /**
241
     * Get mail recipients
242
     *
243
     * @param string $listOption List option name
244
     * @param string $singleAddressOption Single address option
245
     * @param string|null $singleAddressNameOption Single address name
246
     * @return array
247
     *
248
     * @deprecated since TYPO3 v10.0, will be removed in TYPO3 v11.0.
249
     */
250
    protected function getRecipients(
251
        string $listOption,
252
        string $singleAddressOption,
253
        string $singleAddressNameOption = null
254
    ): array {
255
        $recipients = $this->parseOption($listOption);
256
        $singleAddress = $this->parseOption($singleAddressOption);
257
        $singleAddressName = '';
258
259
        $recipients = $recipients ?? [];
260
261
        if (!empty($singleAddress)) {
262
            trigger_error(sprintf(
263
                'EmailFinisher option "%s" is deprecated and will be removed in TYPO3 v11.0. Use "%s" instead.',
264
                $singleAddressOption,
265
                $listOption
266
            ), E_USER_DEPRECATED);
267
268
            if (!empty($singleAddressNameOption)) {
269
                trigger_error(sprintf(
270
                    'EmailFinisher option "%s" is deprecated and will be removed in TYPO3 v11.0. Use "%s" instead.',
271
                    $singleAddressNameOption,
272
                    $listOption
273
                ), E_USER_DEPRECATED);
274
                $singleAddressName = $this->parseOption($singleAddressNameOption);
275
            }
276
277
            $recipients[$singleAddress] = $singleAddressName ?: '';
278
        }
279
280
        $addresses = [];
281
        foreach ($recipients as $address => $name) {
282
            if (MathUtility::canBeInterpretedAsInteger($address)) {
283
                $address = $name;
284
                $name = '';
285
            }
286
            // Drop entries without mail address
287
            if (empty($address)) {
288
                continue;
289
            }
290
            $addresses[] = new Address($address, $name);
291
        }
292
        return $addresses;
293
    }
294
295
    /**
296
     * Get plaintext preference
297
     *
298
     * @return bool
299
     *
300
     * @deprecated since TYPO3 v10.0, will be removed in TYPO3 v11.0.
301
     */
302
    protected function isHtmlPartAdded(): bool
303
    {
304
        $format = $this->parseOption('format');
305
306
        if ($format !== null) {
307
            trigger_error(
308
                'Usage of format option in form email finisher is deprecated - use addHtmlPart instead.',
309
                E_USER_DEPRECATED
310
            );
311
        }
312
313
        // FORMAT_HTML was the default value for "format", so
314
        // FORMAT_PLAINTEXT must have been set intentionally
315
        if ($format === self::FORMAT_PLAINTEXT) {
316
            return false;
317
        }
318
319
        return $this->parseOption('addHtmlPart') ? true : false;
320
    }
321
}
322