EmailRecipient   F
last analyzed

Complexity

Total Complexity 81

Size/Duplication

Total Lines 704
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 330
c 8
b 0
f 0
dl 0
loc 704
rs 2
wmc 81

25 Methods

Rating   Name   Duplication   Size   Complexity  
A requireDefaultRecords() 0 6 1
A getTitle() 0 9 3
A getEmailBodyContent() 0 6 2
B getEmailTemplateDropdownValues() 0 44 7
A getUnsavedFormLiteralField() 0 9 1
A getEmailFromCMSFields() 0 12 1
A getRulesConfig() 0 30 2
A getValidEmailFromFields() 0 8 2
B getCMSFields() 0 136 6
A getEmailReplyToCMSFields() 0 29 3
A getFormParent() 0 15 5
A canDelete() 0 3 1
A getValidEmailToFields() 0 15 3
A getSubjectCMSFields() 0 23 3
A canCreate() 0 10 2
A canEdit() 0 7 2
A canView() 0 6 2
A getValidSubjectFields() 0 14 2
A getMultiOptionFields() 0 6 2
A getEmailToCMSFields() 0 27 3
B canSend() 0 23 7
A emailTemplateExists() 0 5 2
B validate() 0 36 11
A summaryFields() 0 14 4
A getCanCreateContext() 0 13 4

How to fix   Complexity   

Complex Class

Complex classes like EmailRecipient often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EmailRecipient, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\UserForms\Model\Recipient;
4
5
use SilverStripe\Assets\FileFinder;
6
use SilverStripe\CMS\Controllers\CMSMain;
7
use SilverStripe\CMS\Controllers\CMSPageEditController;
8
use SilverStripe\CMS\Model\SiteTree;
9
use SilverStripe\Control\Controller;
10
use SilverStripe\Control\Email\Email;
11
use SilverStripe\Core\Manifest\ModuleResource;
12
use SilverStripe\Core\Manifest\ModuleResourceLoader;
13
use SilverStripe\Forms\CheckboxField;
14
use SilverStripe\Forms\DropdownField;
15
use SilverStripe\Forms\FieldGroup;
16
use SilverStripe\Forms\FieldList;
17
use SilverStripe\Forms\Form;
18
use SilverStripe\Forms\GridField\GridField;
19
use SilverStripe\Forms\GridField\GridFieldButtonRow;
20
use SilverStripe\Forms\GridField\GridFieldConfig;
21
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
22
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
23
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
24
use SilverStripe\Forms\LiteralField;
25
use SilverStripe\Forms\TabSet;
26
use SilverStripe\Forms\TextareaField;
27
use SilverStripe\Forms\TextField;
28
use SilverStripe\ORM\ArrayList;
29
use SilverStripe\ORM\DataList;
30
use SilverStripe\ORM\DataObject;
31
use SilverStripe\ORM\DB;
32
use SilverStripe\ORM\FieldType\DBField;
33
use SilverStripe\ORM\ValidationResult;
34
use SilverStripe\Security\Member;
35
use SilverStripe\UserForms\Model\EditableFormField;
36
use SilverStripe\UserForms\Model\EditableFormField\EditableEmailField;
37
use SilverStripe\UserForms\Model\EditableFormField\EditableMultipleOptionField;
38
use SilverStripe\UserForms\Model\EditableFormField\EditableTextField;
39
use SilverStripe\UserForms\Model\UserDefinedForm;
40
use SilverStripe\UserForms\UserForm;
41
use SilverStripe\View\Requirements;
42
use Symbiote\GridFieldExtensions\GridFieldAddNewInlineButton;
43
use Symbiote\GridFieldExtensions\GridFieldEditableColumns;
44
45
/**
46
 * A Form can have multiply members / emails to email the submission
47
 * to and custom subjects
48
 *
49
 * @package userforms
50
 */
51
class EmailRecipient extends DataObject
52
{
53
    private static $db = [
54
        'EmailAddress' => 'Varchar(200)',
55
        'EmailSubject' => 'Varchar(200)',
56
        'EmailFrom' => 'Varchar(200)',
57
        'EmailReplyTo' => 'Varchar(200)',
58
        'EmailBody' => 'Text',
59
        'EmailBodyHtml' => 'HTMLText',
60
        'EmailTemplate' => 'Varchar',
61
        'SendPlain' => 'Boolean',
62
        'HideFormData' => 'Boolean',
63
        'CustomRulesCondition' => 'Enum("And,Or")'
64
    ];
65
66
    private static $has_one = [
67
        'Form' => DataObject::class,
68
        'SendEmailFromField' => EditableFormField::class,
69
        'SendEmailToField' => EditableFormField::class,
70
        'SendEmailSubjectField' => EditableFormField::class
71
    ];
72
73
    private static $has_many = [
74
        'CustomRules' => EmailRecipientCondition::class,
75
    ];
76
77
    private static $owns = [
78
        'CustomRules',
79
    ];
80
81
    private static $cascade_deletes = [
82
        'CustomRules',
83
    ];
84
85
    private static $summary_fields = [
86
        'EmailAddress',
87
        'EmailSubject',
88
        'EmailFrom'
89
    ];
90
91
    private static $table_name = 'UserDefinedForm_EmailRecipient';
92
93
    /**
94
     * Disable versioned GridField to ensure that it doesn't interfere with {@link UserFormRecipientItemRequest}
95
     *
96
     * {@inheritDoc}
97
     */
98
    private static $versioned_gridfield_extensions = false;
99
100
    /**
101
     * Setting this to true will allow you to select "risky" fields as
102
     * email recipient, such as free-text entry fields.
103
     *
104
     * It's advisable to leave this off.
105
     *
106
     * @config
107
     * @var bool
108
     */
109
    private static $allow_unbound_recipient_fields = false;
110
111
    public function requireDefaultRecords()
112
    {
113
        parent::requireDefaultRecords();
114
115
        // make sure to migrate the class across (prior to v5.x)
116
        DB::query("UPDATE \"UserDefinedForm_EmailRecipient\" SET \"FormClass\" = 'Page' WHERE \"FormClass\" IS NULL");
117
    }
118
119
    public function summaryFields()
120
    {
121
        $fields = parent::summaryFields();
122
        if (isset($fields['EmailAddress'])) {
123
            /** @skipUpgrade */
124
            $fields['EmailAddress'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILADDRESS', 'Email');
125
        }
126
        if (isset($fields['EmailSubject'])) {
127
            $fields['EmailSubject'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILSUBJECT', 'Subject');
128
        }
129
        if (isset($fields['EmailFrom'])) {
130
            $fields['EmailFrom'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILFROM', 'From');
131
        }
132
        return $fields;
133
    }
134
135
    /**
136
     * Get instance of UserForm when editing in getCMSFields
137
     *
138
     * @return UserDefinedForm|UserForm|null
139
     */
140
    protected function getFormParent()
141
    {
142
        // If polymorphic relationship is actually defined, use it
143
        if ($this->FormID && $this->FormClass) {
0 ignored issues
show
Bug Best Practice introduced by
The property FormID does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property FormClass does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
144
            $formClass = $this->FormClass;
145
            return $formClass::get()->byID($this->FormID);
146
        }
147
148
        // Revert to checking for a form from the session
149
        // LeftAndMain::sessionNamespace is protected. @todo replace this with a non-deprecated equivalent.
150
        $sessionNamespace = $this->config()->get('session_namespace') ?: CMSMain::class;
151
152
        $formID = Controller::curr()->getRequest()->getSession()->get($sessionNamespace . '.currentPage');
153
        if ($formID) {
154
            return UserDefinedForm::get()->byID($formID);
155
        }
156
    }
157
158
    public function getTitle()
159
    {
160
        if ($this->EmailAddress) {
0 ignored issues
show
Bug Best Practice introduced by
The property EmailAddress does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
161
            return $this->EmailAddress;
162
        }
163
        if ($this->EmailSubject) {
0 ignored issues
show
Bug Best Practice introduced by
The property EmailSubject does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
164
            return $this->EmailSubject;
165
        }
166
        return parent::getTitle();
167
    }
168
169
    /**
170
     * Generate a gridfield config for editing filter rules
171
     *
172
     * @return GridFieldConfig
173
     */
174
    protected function getRulesConfig()
175
    {
176
        if (!$this->getFormParent()) {
177
            return null;
178
        }
179
        $formFields = $this->getFormParent()->Fields();
0 ignored issues
show
Bug introduced by
The method Fields() does not exist on SilverStripe\UserForms\UserForm. Did you maybe mean getCMSFields()? ( Ignorable by Annotation )

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

179
        $formFields = $this->getFormParent()->/** @scrutinizer ignore-call */ Fields();

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...
180
181
        $config = GridFieldConfig::create()
182
            ->addComponents(
183
                new GridFieldButtonRow('before'),
184
                new GridFieldToolbarHeader(),
185
                new GridFieldAddNewInlineButton(),
186
                new GridFieldDeleteAction(),
187
                $columns = new GridFieldEditableColumns()
188
            );
189
190
        $columns->setDisplayFields(array(
191
            'ConditionFieldID' => function ($record, $column, $grid) use ($formFields) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed. ( Ignorable by Annotation )

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

191
            'ConditionFieldID' => function ($record, $column, /** @scrutinizer ignore-unused */ $grid) use ($formFields) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
192
                return DropdownField::create($column, false, $formFields->map('ID', 'Title'));
193
            },
194
            'ConditionOption' => function ($record, $column, $grid) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed. ( Ignorable by Annotation )

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

194
            'ConditionOption' => function ($record, $column, /** @scrutinizer ignore-unused */ $grid) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
195
                $options = EmailRecipientCondition::config()->condition_options;
196
                return DropdownField::create($column, false, $options);
197
            },
198
            'ConditionValue' => function ($record, $column, $grid) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed. ( Ignorable by Annotation )

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

198
            'ConditionValue' => function ($record, $column, /** @scrutinizer ignore-unused */ $grid) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
199
                return TextField::create($column);
200
            }
201
        ));
202
203
        return $config;
204
    }
205
206
    /**
207
     * @return FieldList
208
     */
209
    public function getCMSFields()
210
    {
211
        Requirements::javascript('silverstripe/userforms:client/dist/js/userforms-cms.js');
212
213
        // Build fieldlist
214
        $fields = FieldList::create(Tabset::create('Root')->addExtraClass('EmailRecipientForm'));
215
216
        if (!$this->getFormParent()) {
217
            $fields->addFieldToTab('Root.EmailDetails', $this->getUnsavedFormLiteralField());
218
        }
219
220
        // Configuration fields
221
        $fields->addFieldsToTab('Root.EmailDetails', [
222
            $this->getSubjectCMSFields(),
223
            $this->getEmailToCMSFields(),
224
            $this->getEmailFromCMSFields(),
225
            $this->getEmailReplyToCMSFields(),
226
        ]);
227
228
        $fields->fieldByName('Root.EmailDetails')->setTitle(_t(__CLASS__ . '.EMAILDETAILSTAB', 'Email Details'));
229
230
        // Only show the preview link if the recipient has been saved.
231
        if (!empty($this->EmailTemplate)) {
0 ignored issues
show
Bug Best Practice introduced by
The property EmailTemplate does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
232
            $request = Controller::curr()->getRequest();
233
234
            $pageEditController = singleton(CMSPageEditController::class);
235
            $pageEditController->getRequest()->setSession($request->getSession());
236
237
            $currentUrl = $request->getURL();
238
            // If used in a regular page context, will have "/edit" on the end, if used in a trait context
239
            // it won't. Strip that off in case. It may also have "ItemEditForm" on the end instead if this is
240
            // an AJAX request, e.g. saving a GridFieldDetailForm
241
            $remove = ['/edit', '/ItemEditForm'];
242
            foreach ($remove as $badSuffix) {
243
                $badSuffixLength = strlen($badSuffix);
244
                if (substr($currentUrl, -$badSuffixLength) === $badSuffix) {
245
                    $currentUrl = substr($currentUrl, 0, -$badSuffixLength);
246
                }
247
            }
248
            $previewUrl = Controller::join_links($currentUrl, 'preview');
249
250
            $preview = sprintf(
251
                '<p><a href="%s" target="_blank" class="btn btn-outline-secondary">%s</a></p><em>%s</em>',
252
                $previewUrl,
253
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL', 'Preview email'),
254
                _t(
255
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL_DESCRIPTION',
256
                    'Note: Unsaved changes will not appear in the preview.'
257
                )
258
            );
259
        } else {
260
            $preview = sprintf(
261
                '<p class="alert alert-warning">%s</p>',
262
                _t(
263
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL_UNAVAILABLE',
264
                    'You can preview this email once you have saved the Recipient.'
265
                )
266
            );
267
        }
268
269
        // Email templates
270
        $fields->addFieldsToTab('Root.EmailContent', [
271
            CheckboxField::create(
272
                'HideFormData',
273
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.HIDEFORMDATA', 'Hide form data from email?')
274
            ),
275
            CheckboxField::create(
276
                'SendPlain',
277
                _t(
278
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDPLAIN',
279
                    'Send email as plain text? (HTML will be stripped)'
280
                )
281
            ),
282
            HTMLEditorField::create(
283
                'EmailBodyHtml',
284
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILBODYHTML', 'Body')
285
            )
286
                ->addExtraClass('toggle-html-only'),
287
            TextareaField::create(
288
                'EmailBody',
289
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILBODY', 'Body')
290
            )
291
                ->addExtraClass('toggle-plain-only'),
292
            LiteralField::create('EmailPreview', $preview)
293
        ]);
294
295
        $templates = $this->getEmailTemplateDropdownValues();
296
297
        if ($templates) {
298
            $fields->insertBefore(
299
                DropdownField::create(
300
                    'EmailTemplate',
301
                    _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILTEMPLATE', 'Email template'),
302
                    $templates
303
                )->addExtraClass('toggle-html-only'),
304
                'EmailBodyHtml'
305
            );
306
        }
307
308
        $fields->fieldByName('Root.EmailContent')->setTitle(_t(__CLASS__ . '.EMAILCONTENTTAB', 'Email Content'));
309
310
        // Custom rules for sending this field
311
        $grid = GridField::create(
312
            'CustomRules',
313
            _t('SilverStripe\\UserForms\\Model\\EditableFormField.CUSTOMRULES', 'Custom Rules'),
314
            $this->CustomRules(),
0 ignored issues
show
Bug introduced by
The method CustomRules() does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

314
            $this->/** @scrutinizer ignore-call */ 
315
                   CustomRules(),
Loading history...
315
            $this->getRulesConfig()
316
        );
317
        $grid->setDescription(_t(
318
            'SilverStripe\\UserForms\\Model\\UserDefinedForm.RulesDescription',
319
            'Emails will only be sent to the recipient if the custom rules are met. If no rules are defined, '
320
            . 'this recipient will receive notifications for every submission.'
321
        ));
322
323
        $fields->addFieldsToTab('Root.CustomRules', [
324
            DropdownField::create(
325
                'CustomRulesCondition',
326
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIF', 'Send condition'),
327
                [
328
                    'Or' => _t(
329
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFOR',
330
                        'Any conditions are true'
331
                    ),
332
                    'And' => _t(
333
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFAND',
334
                        'All conditions are true'
335
                    )
336
                ]
337
            ),
338
            $grid
339
        ]);
340
341
        $fields->fieldByName('Root.CustomRules')->setTitle(_t(__CLASS__ . '.CUSTOMRULESTAB', 'Custom Rules'));
342
343
        $this->extend('updateCMSFields', $fields);
344
        return $fields;
345
    }
346
347
    /**
348
     * Return whether a user can create an object of this type
349
     *
350
     * @param Member $member
351
     * @param array $context Virtual parameter to allow context to be passed in to check
352
     * @return bool
353
     */
354
    public function canCreate($member = null, $context = [])
355
    {
356
        // Check parent page
357
        $parent = $this->getCanCreateContext(func_get_args());
358
        if ($parent) {
0 ignored issues
show
introduced by
$parent is of type SilverStripe\CMS\Model\SiteTree, thus it always evaluated to true.
Loading history...
359
            return $parent->canEdit($member);
360
        }
361
362
        // Fall back to secure admin permissions
363
        return parent::canCreate($member);
364
    }
365
366
    /**
367
     * Helper method to check the parent for this object
368
     *
369
     * @param array $args List of arguments passed to canCreate
370
     * @return SiteTree Parent page instance
371
     */
372
    protected function getCanCreateContext($args)
373
    {
374
        // Inspect second parameter to canCreate for a 'Parent' context
375
        if (isset($args[1][Form::class])) {
376
            return $args[1][Form::class];
377
        }
378
        // Hack in currently edited page if context is missing
379
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
380
            return Controller::curr()->currentPage();
381
        }
382
383
        // No page being edited
384
        return null;
385
    }
386
387
    public function canView($member = null)
388
    {
389
        if ($form = $this->getFormParent()) {
390
            return $form->canView($member);
0 ignored issues
show
Bug introduced by
It seems like canView() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

390
            return $form->/** @scrutinizer ignore-call */ canView($member);
Loading history...
391
        }
392
        return parent::canView($member);
393
    }
394
395
    public function canEdit($member = null)
396
    {
397
        if ($form = $this->getFormParent()) {
398
            return $form->canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like canEdit() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

398
            return $form->/** @scrutinizer ignore-call */ canEdit($member);
Loading history...
399
        }
400
401
        return parent::canEdit($member);
402
    }
403
404
    /**
405
     * @param Member
406
     *
407
     * @return boolean
408
     */
409
    public function canDelete($member = null)
410
    {
411
        return $this->canEdit($member);
412
    }
413
414
    /**
415
     * Determine if this recipient may receive notifications for this submission
416
     *
417
     * @param array $data
418
     * @param Form $form
419
     * @return bool
420
     */
421
    public function canSend($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed. ( Ignorable by Annotation )

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

421
    public function canSend($data, /** @scrutinizer ignore-unused */ $form)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
422
    {
423
        // Skip if no rules configured
424
        $customRules = $this->CustomRules();
425
        if (!$customRules->count()) {
426
            return true;
427
        }
428
429
        // Check all rules
430
        $isAnd = $this->CustomRulesCondition === 'And';
0 ignored issues
show
Bug Best Practice introduced by
The property CustomRulesCondition does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
431
        foreach ($customRules as $customRule) {
432
            /** @var EmailRecipientCondition  $customRule */
433
            $matches = $customRule->matches($data);
434
            if ($isAnd && !$matches) {
435
                return false;
436
            }
437
            if (!$isAnd && $matches) {
438
                return true;
439
            }
440
        }
441
442
        // Once all rules are checked
443
        return $isAnd;
444
    }
445
446
    /**
447
     * Make sure the email template saved against the recipient exists on the file system.
448
     *
449
     * @param string
450
     *
451
     * @return boolean
452
     */
453
    public function emailTemplateExists($template = '')
454
    {
455
        $t = ($template ? $template : $this->EmailTemplate);
0 ignored issues
show
Bug Best Practice introduced by
The property EmailTemplate does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
456
457
        return array_key_exists($t, (array) $this->getEmailTemplateDropdownValues());
458
    }
459
460
    /**
461
     * Get the email body for the current email format
462
     *
463
     * @return string
464
     */
465
    public function getEmailBodyContent()
466
    {
467
        if ($this->SendPlain) {
0 ignored issues
show
Bug Best Practice introduced by
The property SendPlain does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
468
            return DBField::create_field('HTMLText', $this->EmailBody)->Plain();
0 ignored issues
show
Bug Best Practice introduced by
The property EmailBody does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
469
        }
470
        return DBField::create_field('HTMLText', $this->EmailBodyHtml);
0 ignored issues
show
Bug Best Practice introduced by
The property EmailBodyHtml does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
471
    }
472
473
    /**
474
     * Gets a list of email templates suitable for populating the email template dropdown.
475
     *
476
     * @return array
477
     */
478
    public function getEmailTemplateDropdownValues()
479
    {
480
        $templates = [];
481
482
        $finder = new FileFinder();
483
        $finder->setOption('name_regex', '/^.*\.ss$/');
484
485
        $parent = $this->getFormParent();
486
487
        if (!$parent) {
488
            return [];
489
        }
490
491
        $emailTemplateDirectory = $parent->config()->get('email_template_directory');
492
        $templateDirectory = ModuleResourceLoader::resourcePath($emailTemplateDirectory);
493
494
        if (!$templateDirectory) {
495
            return [];
496
        }
497
498
        $found = $finder->find(BASE_PATH . DIRECTORY_SEPARATOR . $templateDirectory);
499
500
        foreach ($found as $key => $value) {
501
            $template = pathinfo($value);
502
            $absoluteFilename = $template['dirname'] . DIRECTORY_SEPARATOR . $template['filename'];
503
504
            // Optionally remove vendor/ path prefixes
505
            $resource = ModuleResourceLoader::singleton()->resolveResource($emailTemplateDirectory);
506
            if ($resource instanceof ModuleResource && $resource->getModule()) {
507
                $prefixToStrip = $resource->getModule()->getPath();
508
            } else {
509
                $prefixToStrip = BASE_PATH;
510
            }
511
            $templatePath = substr($absoluteFilename, strlen($prefixToStrip) + 1);
512
513
            // Optionally remove "templates/" prefixes
514
            if (preg_match('/(?<=templates\/).*$/', $templatePath, $matches)) {
515
                $templatePath = $matches[0];
516
            }
517
518
            $templates[$templatePath] = $template['filename'];
519
        }
520
521
        return $templates;
522
    }
523
524
    /**
525
     * Validate that valid email addresses are being used
526
     *
527
     * @return ValidationResult
528
     */
529
    public function validate()
530
    {
531
        $result = parent::validate();
532
        $checkEmail = [
533
            'EmailAddress' => 'EMAILADDRESSINVALID',
534
            'EmailFrom' => 'EMAILFROMINVALID',
535
            'EmailReplyTo' => 'EMAILREPLYTOINVALID',
536
        ];
537
        foreach ($checkEmail as $check => $translation) {
538
            if ($this->$check) {
539
                //may be a comma separated list of emails
540
                $addresses = explode(',', $this->$check);
541
                foreach ($addresses as $address) {
542
                    $trimAddress = trim($address);
543
                    if ($trimAddress && !Email::is_valid_address($trimAddress)) {
544
                        $error = _t(
545
                            __CLASS__.".$translation",
546
                            "Invalid email address $trimAddress"
547
                        );
548
                        $result->addError($error . " ($trimAddress)");
549
                    }
550
                }
551
            }
552
        }
553
554
        // if there is no from address and no fallback, you'll have errors if this isn't defined
555
        if (!$this->EmailFrom && empty(Email::getSendAllEmailsFrom()) && empty(Email::config()->get('admin_email'))) {
0 ignored issues
show
Bug Best Practice introduced by
The property EmailFrom does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
556
            $result->addError(_t(__CLASS__.".EMAILFROMREQUIRED", '"Email From" address is required'));
557
        }
558
559
        // Sending will also fail if there's no recipient defined
560
        if (!$this->EmailAddress && !$this->SendEmailToFieldID) {
0 ignored issues
show
Bug Best Practice introduced by
The property SendEmailToFieldID does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property EmailAddress does not exist on SilverStripe\UserForms\M...ecipient\EmailRecipient. Since you implemented __get, consider adding a @property annotation.
Loading history...
561
            $result->addError(_t(__CLASS__.".EMAILTOREQUIRED", '"Send email to" address or field is required'));
562
        }
563
564
        return $result;
565
    }
566
567
    /**
568
     * @return FieldGroup|TextField
569
     */
570
    protected function getSubjectCMSFields()
571
    {
572
        $subjectTextField = TextField::create(
573
            'EmailSubject',
574
            _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPESUBJECT', 'Type subject')
575
        )
576
            ->setAttribute('style', 'min-width: 400px;');
577
578
        if ($this->getFormParent() && $this->getValidSubjectFields()) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getValidSubjectFields() targeting SilverStripe\UserForms\M...getValidSubjectFields() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
579
            return FieldGroup::create(
580
                $subjectTextField,
581
                DropdownField::create(
582
                    'SendEmailSubjectFieldID',
583
                    _t(
584
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.SELECTAFIELDTOSETSUBJECT',
585
                        '.. or select a field to use as the subject'
586
                    ),
587
                    $this->getValidSubjectFields()->map('ID', 'Title')
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getValidSubjectFields() targeting SilverStripe\UserForms\M...getValidSubjectFields() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
588
                )->setEmptyString('')
589
            )
590
                ->setTitle(_t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILSUBJECT', 'Email subject'));
591
        } else {
592
            return $subjectTextField;
593
        }
594
    }
595
596
    /**
597
     * @return FieldGroup|TextField
598
     */
599
    protected function getEmailToCMSFields()
600
    {
601
        $emailToTextField = TextField::create(
602
            'EmailAddress',
603
            _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPETO', 'Type to address')
604
        )
605
            ->setAttribute('style', 'min-width: 400px;');
606
607
        if ($this->getFormParent() && $this->getValidEmailToFields()) {
608
            return FieldGroup::create(
609
                $emailToTextField,
610
                DropdownField::create(
611
                    'SendEmailToFieldID',
612
                    _t(
613
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.ORSELECTAFIELDTOUSEASTO',
614
                        '.. or select a field to use as the to address'
615
                    ),
616
                    $this->getValidEmailToFields()->map('ID', 'Title')
617
                )->setEmptyString(' ')
618
            )
619
                ->setTitle(_t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDEMAILTO', 'Send email to'))
620
                ->setDescription(_t(
621
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDEMAILTO_DESCRIPTION',
622
                    'You may enter multiple email addresses as a comma separated list.'
623
                ));
624
        } else {
625
            return $emailToTextField;
626
        }
627
    }
628
629
    /**
630
     * @return TextField
631
     */
632
    protected function getEmailFromCMSFields()
633
    {
634
        return TextField::create(
635
            'EmailFrom',
636
            _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.FROMADDRESS', 'Send email from')
637
        )
638
            ->setDescription(_t(
639
                'SilverStripe\\UserForms\\Model\\UserDefinedForm.EmailFromContent',
640
                "The from address allows you to set who the email comes from. On most servers this " .
641
                "will need to be set to an email address on the same domain name as your site. " .
642
                "For example on yoursite.com the from address may need to be [email protected]. " .
643
                "You can however, set any email address you wish as the reply to address."
644
            ));
645
    }
646
647
    /**
648
     * @return FieldGroup|TextField
649
     */
650
    protected function getEmailReplyToCMSFields()
651
    {
652
        $replyToTextField = TextField::create('EmailReplyTo', _t(
653
            'SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPEREPLY',
654
            'Type reply address'
655
        ))
656
            ->setAttribute('style', 'min-width: 400px;');
657
        if ($this->getFormParent() && $this->getValidEmailFromFields()) {
658
            return FieldGroup::create(
659
                $replyToTextField,
660
                DropdownField::create(
661
                    'SendEmailFromFieldID',
662
                    _t(
663
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.ORSELECTAFIELDTOUSEASFROM',
664
                        '.. or select a field to use as reply to address'
665
                    ),
666
                    $this->getValidEmailFromFields()->map('ID', 'Title')
667
                )->setEmptyString(' ')
668
            )
669
                ->setTitle(_t(
670
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.REPLYADDRESS',
671
                    'Email for reply to'
672
                ))
673
                ->setDescription(_t(
674
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.REPLYADDRESS_DESCRIPTION',
675
                    'The email address which the recipient is able to \'reply\' to.'
676
                ));
677
        } else {
678
            return $replyToTextField;
679
        }
680
    }
681
682
    /**
683
     * @return DataList|null
684
     */
685
    protected function getMultiOptionFields()
686
    {
687
        if (!$form = $this->getFormParent()) {
688
            return null;
689
        }
690
        return EditableMultipleOptionField::get()->filter('ParentID', $form->ID);
691
    }
692
693
    /**
694
     * @return ArrayList|null
695
     */
696
    protected function getValidSubjectFields()
697
    {
698
        if (!$form = $this->getFormParent()) {
699
            return null;
700
        }
701
        // For the subject, only one-line entry boxes make sense
702
        $validSubjectFields = ArrayList::create(
703
            EditableTextField::get()
704
                ->filter('ParentID', $form->ID)
705
                ->exclude('Rows:GreaterThan', 1)
706
                ->toArray()
707
        );
708
        $validSubjectFields->merge($this->getMultiOptionFields());
709
        return $validSubjectFields;
710
    }
711
712
    /**
713
     * @return DataList|null
714
     */
715
    protected function getValidEmailFromFields()
716
    {
717
        if (!$form = $this->getFormParent()) {
718
            return null;
719
        }
720
721
        // if they have email fields then we could send from it
722
        return EditableEmailField::get()->filter('ParentID', $form->ID);
723
    }
724
725
    /**
726
     * @return ArrayList|DataList|null
727
     */
728
    protected function getValidEmailToFields()
729
    {
730
        if (!$this->getFormParent()) {
731
            return null;
732
        }
733
734
        // Check valid email-recipient fields
735
        if ($this->config()->get('allow_unbound_recipient_fields')) {
736
            // To address can only be email fields or multi option fields
737
            $validEmailToFields = ArrayList::create($this->getValidEmailFromFields()->toArray());
738
            $validEmailToFields->merge($this->getMultiOptionFields());
739
            return $validEmailToFields;
740
        } else {
741
            // To address cannot be unbound, so restrict to pre-defined lists
742
            return $this->getMultiOptionFields();
743
        }
744
    }
745
746
    protected function getUnsavedFormLiteralField()
747
    {
748
        return LiteralField::create(
749
            'UnsavedFormMessage',
750
            sprintf(
751
                '<p class="alert alert-warning">%s</p>',
752
                _t(
753
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAIL_RECIPIENT_UNSAVED_FORM',
754
                    'You will be able to select from valid form fields after saving this record.'
755
                )
756
            )
757
        );
758
    }
759
}
760