EmailFinisher::executeInternal()   F
last analyzed

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 116
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 73
nc 49344
nop 0
dl 0
loc 116
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Psr\Http\Message\ServerRequestInterface;
21
use Symfony\Component\Mime\Address;
22
use TYPO3\CMS\Core\Mail\FluidEmail;
23
use TYPO3\CMS\Core\Mail\Mailer;
24
use TYPO3\CMS\Core\Mail\MailMessage;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
27
use TYPO3\CMS\Fluid\View\StandaloneView;
28
use TYPO3\CMS\Fluid\View\TemplatePaths;
29
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
30
use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
31
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
32
use TYPO3\CMS\Form\Service\TranslationService;
33
use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
34
35
/**
36
 * This finisher sends an email to one recipient
37
 *
38
 * Options:
39
 *
40
 * - templatePathAndFilename (mandatory for Mail): Template path and filename for the mail body
41
 * - templateName (mandatory for FluidEmail): Template name for the mail body
42
 * - templateRootPaths: root paths for the templates
43
 * - layoutRootPaths: root paths for the layouts
44
 * - partialRootPaths: root paths for the partials
45
 * - variables: associative array of variables which are available inside the Fluid template
46
 *
47
 * The following options control the mail sending. In all of them, placeholders in the form
48
 * of {...} are replaced with the corresponding form value; i.e. {email} as senderAddress
49
 * makes the recipient address configurable.
50
 *
51
 * - subject (mandatory): Subject of the email
52
 * - recipients (mandatory): Email addresses and human-readable names of the recipients
53
 * - senderAddress (mandatory): Email address of the sender
54
 * - senderName: Human-readable name of the sender
55
 * - replyToRecipients: Email addresses and human-readable names of the reply-to recipients
56
 * - carbonCopyRecipients: Email addresses and human-readable names of the copy recipients
57
 * - blindCarbonCopyRecipients: Email addresses and human-readable names of the blind copy recipients
58
 *
59
 * Scope: frontend
60
 */
61
class EmailFinisher extends AbstractFinisher
62
{
63
    /**
64
     * @var array
65
     */
66
    protected $defaultOptions = [
67
        'recipientName' => '',
68
        'senderName' => '',
69
        'addHtmlPart' => true,
70
        'attachUploads' => true,
71
    ];
72
73
    /**
74
     * Executes this finisher
75
     * @see AbstractFinisher::execute()
76
     *
77
     * @throws FinisherException
78
     */
79
    protected function executeInternal()
80
    {
81
        $languageBackup = null;
82
        // Flexform overrides write strings instead of integers so
83
        // we need to cast the string '0' to false.
84
        if (
85
            isset($this->options['addHtmlPart'])
86
            && $this->options['addHtmlPart'] === '0'
87
        ) {
88
            $this->options['addHtmlPart'] = false;
89
        }
90
91
        $subject = $this->parseOption('subject');
92
        $recipients = $this->getRecipients('recipients');
93
        $senderAddress = $this->parseOption('senderAddress');
94
        $senderAddress = is_string($senderAddress) ? $senderAddress : '';
95
        $senderName = $this->parseOption('senderName');
96
        $senderName = is_string($senderName) ? $senderName : '';
97
        $replyToRecipients = $this->getRecipients('replyToRecipients');
98
        $carbonCopyRecipients = $this->getRecipients('carbonCopyRecipients');
99
        $blindCarbonCopyRecipients = $this->getRecipients('blindCarbonCopyRecipients');
100
        $addHtmlPart = $this->parseOption('addHtmlPart') ? true : false;
101
        $attachUploads = $this->parseOption('attachUploads');
102
        $useFluidEmail = $this->parseOption('useFluidEmail');
103
        $title = $this->parseOption('title');
104
        $title = is_string($title) && $title !== '' ? $title : $subject;
105
106
        if (empty($subject)) {
107
            throw new FinisherException('The option "subject" must be set for the EmailFinisher.', 1327060320);
108
        }
109
        if (empty($recipients)) {
110
            throw new FinisherException('The option "recipients" must be set for the EmailFinisher.', 1327060200);
111
        }
112
        if (empty($senderAddress)) {
113
            throw new FinisherException('The option "senderAddress" must be set for the EmailFinisher.', 1327060210);
114
        }
115
116
        $formRuntime = $this->finisherContext->getFormRuntime();
117
118
        $translationService = TranslationService::getInstance();
119
        if (is_string($this->options['translation']['language'] ?? null) && $this->options['translation']['language'] !== '') {
120
            $languageBackup = $translationService->getLanguage();
121
            $translationService->setLanguage($this->options['translation']['language']);
122
        }
123
124
        $mail = $useFluidEmail
125
            ? $this
126
                ->initializeFluidEmail($formRuntime)
127
                ->format($addHtmlPart ? FluidEmail::FORMAT_BOTH : FluidEmail::FORMAT_PLAIN)
128
                ->assign('title', $title)
129
            : GeneralUtility::makeInstance(MailMessage::class);
130
131
        $mail
132
            ->from(new Address($senderAddress, $senderName))
133
            ->to(...$recipients)
134
            ->subject($subject);
0 ignored issues
show
Bug introduced by
It seems like $subject can also be of type array; however, parameter $subject of Symfony\Component\Mime\Email::subject() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
            ->subject(/** @scrutinizer ignore-type */ $subject);
Loading history...
135
136
        if (!empty($replyToRecipients)) {
137
            $mail->replyTo(...$replyToRecipients);
138
        }
139
140
        if (!empty($carbonCopyRecipients)) {
141
            $mail->cc(...$carbonCopyRecipients);
142
        }
143
144
        if (!empty($blindCarbonCopyRecipients)) {
145
            $mail->bcc(...$blindCarbonCopyRecipients);
146
        }
147
148
        if (!$useFluidEmail) {
149
            $parts = [
150
                [
151
                    'format' => 'Plaintext',
152
                    'contentType' => 'text/plain',
153
                ],
154
            ];
155
156
            if ($addHtmlPart) {
157
                $parts[] = [
158
                    'format' => 'Html',
159
                    'contentType' => 'text/html',
160
                ];
161
            }
162
163
            foreach ($parts as $i => $part) {
164
                $standaloneView = $this->initializeStandaloneView($formRuntime, $part['format']);
165
                $message = $standaloneView->render();
166
167
                if ($part['contentType'] === 'text/plain') {
168
                    $mail->text($message);
169
                } else {
170
                    $mail->html($message);
171
                }
172
            }
173
        }
174
175
        if (!empty($languageBackup)) {
176
            $translationService->setLanguage($languageBackup);
177
        }
178
179
        if ($attachUploads) {
180
            foreach ($formRuntime->getFormDefinition()->getRenderablesRecursively() as $element) {
181
                if (!$element instanceof FileUpload) {
182
                    continue;
183
                }
184
                $file = $formRuntime[$element->getIdentifier()];
185
                if ($file) {
186
                    if ($file instanceof FileReference) {
187
                        $file = $file->getOriginalResource();
188
                    }
189
                    $mail->attach($file->getContents(), $file->getName(), $file->getMimeType());
190
                }
191
            }
192
        }
193
194
        $useFluidEmail ? GeneralUtility::makeInstance(Mailer::class)->send($mail) : $mail->send();
0 ignored issues
show
Bug introduced by
The method send() does not exist on TYPO3\CMS\Core\Mail\FluidEmail. Did you maybe mean sender()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

194
        $useFluidEmail ? GeneralUtility::makeInstance(Mailer::class)->send($mail) : $mail->/** @scrutinizer ignore-call */ send();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
195
    }
196
197
    /**
198
     * @param FormRuntime $formRuntime
199
     * @param string $format
200
     * @return StandaloneView
201
     * @throws FinisherException
202
     * @deprecated since v11, will be removed in v12
203
     */
204
    protected function initializeStandaloneView(FormRuntime $formRuntime, string $format): StandaloneView
205
    {
206
        trigger_error(
207
            'Using StandaloneView for EmailFinisher \'' . $this->finisherIdentifier . '\' is deprecated and will be removed in v12. Please use FluidEmail in the finishers configuration instead.',
208
            E_USER_DEPRECATED
209
        );
210
211
        $standaloneView = GeneralUtility::makeInstance(StandaloneView::class);
212
213
        if (isset($this->options['templatePathAndFilename'])) {
214
            trigger_error(
215
                'The option \'templatePathAndFilename\' for EmailFinisher \'' . $this->finisherIdentifier . '\' is deprecated and will be removed in v12. Please use \'templateName\' and \'templateRootPaths\' in the finishers definition instead.',
216
                E_USER_DEPRECATED
217
            );
218
            // Use local variable instead of augmenting the options to
219
            // keep the format intact when sending multi-format mails
220
            $templatePathAndFilename = strtr($this->options['templatePathAndFilename'], [
221
                '{@format}' => $format
222
            ]);
223
            $standaloneView->setTemplatePathAndFilename($templatePathAndFilename);
224
        } else {
225
            if (!isset($this->options['templateName'])) {
226
                throw new FinisherException('The option "templateName" must be set for the EmailFinisher.', 1327058829);
227
            }
228
            // Use local variable instead of augmenting the options to
229
            // keep the format intact when sending multi-format mails
230
            $templateName = strtr($this->options['templateName'], [
231
                '{@format}' => $format
232
            ]);
233
            $standaloneView->setTemplate($templateName);
234
        }
235
236
        $standaloneView->assign('finisherVariableProvider', $this->finisherContext->getFinisherVariableProvider());
237
238
        if (isset($this->options['templateRootPaths']) && is_array($this->options['templateRootPaths'])) {
239
            $standaloneView->setTemplateRootPaths($this->options['templateRootPaths']);
240
        }
241
242
        if (isset($this->options['partialRootPaths']) && is_array($this->options['partialRootPaths'])) {
243
            $standaloneView->setPartialRootPaths($this->options['partialRootPaths']);
244
        }
245
246
        if (isset($this->options['layoutRootPaths']) && is_array($this->options['layoutRootPaths'])) {
247
            $standaloneView->setLayoutRootPaths($this->options['layoutRootPaths']);
248
        }
249
250
        if (is_array($this->options['variables'] ?? null)) {
251
            $standaloneView->assignMultiple($this->options['variables']);
252
        }
253
254
        $standaloneView->assign('form', $formRuntime);
255
        $standaloneView->getRenderingContext()
256
                       ->getViewHelperVariableContainer()
257
                       ->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);
258
259
        return $standaloneView;
260
    }
261
262
    protected function initializeFluidEmail(FormRuntime $formRuntime): FluidEmail
263
    {
264
        $templateConfiguration = $GLOBALS['TYPO3_CONF_VARS']['MAIL'];
265
266
        if (is_array($this->options['templateRootPaths'] ?? null)) {
267
            $templateConfiguration['templateRootPaths'] = array_replace_recursive(
268
                $templateConfiguration['templateRootPaths'],
269
                $this->options['templateRootPaths']
270
            );
271
            ksort($templateConfiguration['templateRootPaths']);
272
        }
273
274
        if (is_array($this->options['partialRootPaths'] ?? null)) {
275
            $templateConfiguration['partialRootPaths'] = array_replace_recursive(
276
                $templateConfiguration['partialRootPaths'],
277
                $this->options['partialRootPaths']
278
            );
279
            ksort($templateConfiguration['partialRootPaths']);
280
        }
281
282
        if (is_array($this->options['layoutRootPaths'] ?? null)) {
283
            $templateConfiguration['layoutRootPaths'] = array_replace_recursive(
284
                $templateConfiguration['layoutRootPaths'],
285
                $this->options['layoutRootPaths']
286
            );
287
            ksort($templateConfiguration['layoutRootPaths']);
288
        }
289
290
        $fluidEmail = GeneralUtility::makeInstance(
291
            FluidEmail::class,
292
            GeneralUtility::makeInstance(TemplatePaths::class, $templateConfiguration)
293
        );
294
295
        if (!isset($this->options['templateName']) || $this->options['templateName'] === '') {
296
            throw new FinisherException('The option "templateName" must be set to use FluidEmail.', 1599834020);
297
        }
298
299
        // Migrate old template name to default FluidEmail name
300
        if ($this->options['templateName'] === '{@format}.html') {
301
            $this->options['templateName'] = 'Default';
302
        }
303
304
        // Set the PSR-7 request object if available
305
        if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface) {
306
            $fluidEmail->setRequest($GLOBALS['TYPO3_REQUEST']);
307
        }
308
309
        $fluidEmail
310
            ->setTemplate($this->options['templateName'])
311
            ->assignMultiple([
312
                'finisherVariableProvider' => $this->finisherContext->getFinisherVariableProvider(),
313
                'form' => $formRuntime
314
            ]);
315
316
        if (is_array($this->options['variables'] ?? null)) {
317
            $fluidEmail->assignMultiple($this->options['variables']);
318
        }
319
320
        $fluidEmail
321
            ->getViewHelperVariableContainer()
322
            ->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);
323
324
        return $fluidEmail;
325
    }
326
327
    /**
328
     * Get mail recipients
329
     *
330
     * @param string $listOption List option name
331
     * @return array
332
     */
333
    protected function getRecipients(string $listOption): array
334
    {
335
        $recipients = $this->parseOption($listOption) ?? [];
336
        $addresses = [];
337
        foreach ($recipients as $address => $name) {
338
            if (!GeneralUtility::validEmail($address)) {
339
                // Drop entries without valid address
340
                continue;
341
            }
342
            $addresses[] = new Address($address, $name);
343
        }
344
        return $addresses;
345
    }
346
}
347