Completed
Push — master ( 699dd3...983308 )
by
unknown
10s
created

EmailRecipient::getEmailTemplateDropdownValues()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 44
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 24
nc 7
nop 0
dl 0
loc 44
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\UserForms\Model\Recipient;
4
5
use SilverStripe\Admin\LeftAndMain;
6
use SilverStripe\Assets\FileFinder;
7
use SilverStripe\CMS\Controllers\CMSMain;
8
use SilverStripe\CMS\Controllers\CMSPageEditController;
9
use SilverStripe\CMS\Model\SiteTree;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Email\Email;
12
use SilverStripe\Core\Manifest\ModuleResource;
13
use SilverStripe\Core\Manifest\ModuleResourceLoader;
14
use SilverStripe\Forms\CheckboxField;
15
use SilverStripe\Forms\DropdownField;
16
use SilverStripe\Forms\FieldGroup;
17
use SilverStripe\Forms\FieldList;
18
use SilverStripe\Forms\Form;
19
use SilverStripe\Forms\GridField\GridField;
20
use SilverStripe\Forms\GridField\GridFieldButtonRow;
21
use SilverStripe\Forms\GridField\GridFieldConfig;
22
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
23
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
24
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
25
use SilverStripe\Forms\LiteralField;
26
use SilverStripe\Forms\TabSet;
27
use SilverStripe\Forms\TextareaField;
28
use SilverStripe\Forms\TextField;
29
use SilverStripe\ORM\ArrayList;
30
use SilverStripe\ORM\DataObject;
31
use SilverStripe\ORM\DB;
32
use SilverStripe\ORM\FieldType\DBField;
33
use SilverStripe\UserForms\Model\EditableFormField;
34
use SilverStripe\UserForms\Model\EditableFormField\EditableEmailField;
35
use SilverStripe\UserForms\Model\EditableFormField\EditableMultipleOptionField;
36
use SilverStripe\UserForms\Model\EditableFormField\EditableTextField;
37
use SilverStripe\UserForms\Model\UserDefinedForm;
38
use SilverStripe\View\Requirements;
39
use Symbiote\GridFieldExtensions\GridFieldAddNewInlineButton;
40
use Symbiote\GridFieldExtensions\GridFieldEditableColumns;
41
42
/**
43
 * A Form can have multiply members / emails to email the submission
44
 * to and custom subjects
45
 *
46
 * @package userforms
47
 */
48
class EmailRecipient extends DataObject
49
{
50
    private static $db = [
51
        'EmailAddress' => 'Varchar(200)',
52
        'EmailSubject' => 'Varchar(200)',
53
        'EmailFrom' => 'Varchar(200)',
54
        'EmailReplyTo' => 'Varchar(200)',
55
        'EmailBody' => 'Text',
56
        'EmailBodyHtml' => 'HTMLText',
57
        'EmailTemplate' => 'Varchar',
58
        'SendPlain' => 'Boolean',
59
        'HideFormData' => 'Boolean',
60
        'CustomRulesCondition' => 'Enum("And,Or")'
61
    ];
62
63
    private static $has_one = [
64
        'Form' => DataObject::class,
65
        'SendEmailFromField' => EditableFormField::class,
66
        'SendEmailToField' => EditableFormField::class,
67
        'SendEmailSubjectField' => EditableFormField::class
68
    ];
69
70
    private static $has_many = [
71
        'CustomRules' => EmailRecipientCondition::class,
72
    ];
73
74
    private static $owns = [
75
        'CustomRules',
76
    ];
77
78
    private static $cascade_deetes = [
79
        'CustomRules',
80
    ];
81
82
    private static $summary_fields = [
83
        'EmailAddress',
84
        'EmailSubject',
85
        'EmailFrom'
86
    ];
87
88
    private static $table_name = 'UserDefinedForm_EmailRecipient';
89
90
    /**
91
     * Setting this to true will allow you to select "risky" fields as
92
     * email recipient, such as free-text entry fields.
93
     *
94
     * It's advisable to leave this off.
95
     *
96
     * @config
97
     * @var bool
98
     */
99
    private static $allow_unbound_recipient_fields = false;
100
101
    public function requireDefaultRecords()
102
    {
103
        parent::requireDefaultRecords();
104
105
        // make sure to migrate the class across (prior to v5.x)
106
        DB::query("UPDATE UserDefinedForm_EmailRecipient SET FormClass = 'Page' WHERE FormClass IS NULL");
107
    }
108
109
    public function summaryFields()
110
    {
111
        $fields = parent::summaryFields();
112
        if (isset($fields['EmailAddress'])) {
113
            /** @skipUpgrade */
114
            $fields['EmailAddress'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILADDRESS', 'Email');
115
        }
116
        if (isset($fields['EmailSubject'])) {
117
            $fields['EmailSubject'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILSUBJECT', 'Subject');
118
        }
119
        if (isset($fields['EmailFrom'])) {
120
            $fields['EmailFrom'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILFROM', 'From');
121
        }
122
        return $fields;
123
    }
124
125
    /**
126
     * Get instance of UserDefinedForm when editing in getCMSFields
127
     *
128
     * @return UserDefinedFrom
129
     */
130
    protected function getFormParent()
131
    {
132
        // LeftAndMain::sessionNamespace is protected. @todo replace this with a non-deprecated equivalent.
133
        $sessionNamespace = $this->config()->get('session_namespace') ?: LeftAndMain::class;
134
135
        $formID = $this->FormID
136
            ? $this->FormID
137
            : Controller::curr()->getRequest()->getSession()->get($sessionNamespace . '.currentPage');
138
        return UserDefinedForm::get()->byID($formID);
139
    }
140
141
    public function getTitle()
142
    {
143
        if ($this->EmailAddress) {
144
            return $this->EmailAddress;
145
        }
146
        if ($this->EmailSubject) {
147
            return $this->EmailSubject;
148
        }
149
        return parent::getTitle();
150
    }
151
152
    /**
153
     * Generate a gridfield config for editing filter rules
154
     *
155
     * @return GridFieldConfig
156
     */
157
    protected function getRulesConfig()
158
    {
159
        $formFields = $this->getFormParent()->Fields();
160
161
        $config = GridFieldConfig::create()
162
            ->addComponents(
163
                new GridFieldButtonRow('before'),
164
                new GridFieldToolbarHeader(),
165
                new GridFieldAddNewInlineButton(),
166
                new GridFieldDeleteAction(),
167
                $columns = new GridFieldEditableColumns()
168
            );
169
170
        $columns->setDisplayFields(array(
171
            'ConditionFieldID' => function ($record, $column, $grid) use ($formFields) {
172
                return DropdownField::create($column, false, $formFields->map('ID', 'Title'));
173
            },
174
            'ConditionOption' => function ($record, $column, $grid) {
175
                $options = EmailRecipientCondition::config()->condition_options;
176
                return DropdownField::create($column, false, $options);
177
            },
178
            'ConditionValue' => function ($record, $column, $grid) {
179
                return TextField::create($column);
180
            }
181
        ));
182
183
        return $config;
184
    }
185
186
    /**
187
     * @return FieldList
188
     */
189
    public function getCMSFields()
190
    {
191
        Requirements::javascript('silverstripe/userforms:client/dist/js/userforms-cms.js');
192
193
        // Determine optional field values
194
        $form = $this->getFormParent();
195
196
        // predefined choices are also candidates
197
        $multiOptionFields = EditableMultipleOptionField::get()->filter('ParentID', $form->ID);
198
199
        // if they have email fields then we could send from it
200
        $validEmailFromFields = EditableEmailField::get()->filter('ParentID', $form->ID);
201
202
        // For the subject, only one-line entry boxes make sense
203
        $validSubjectFields = ArrayList::create(
204
            EditableTextField::get()
205
                ->filter('ParentID', $form->ID)
206
                ->exclude('Rows:GreaterThan', 1)
207
                ->toArray()
208
        );
209
        $validSubjectFields->merge($multiOptionFields);
210
211
212
        // Check valid email-recipient fields
213
        if ($this->config()->get('allow_unbound_recipient_fields')) {
214
            // To address can only be email fields or multi option fields
215
            $validEmailToFields = ArrayList::create($validEmailFromFields->toArray());
216
            $validEmailToFields->merge($multiOptionFields);
217
        } else {
218
            // To address cannot be unbound, so restrict to pre-defined lists
219
            $validEmailToFields = $multiOptionFields;
220
        }
221
222
        // Build fieldlist
223
        $fields = FieldList::create(Tabset::create('Root')->addExtraClass('EmailRecipientForm'));
224
225
        // Configuration fields
226
        $fields->addFieldsToTab('Root.EmailDetails', [
227
            // Subject
228
            FieldGroup::create(
229
                TextField::create(
230
                    'EmailSubject',
231
                    _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPESUBJECT', 'Type subject')
232
                )
233
                    ->setAttribute('style', 'min-width: 400px;'),
234
                DropdownField::create(
235
                    'SendEmailSubjectFieldID',
236
                    _t(
237
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.SELECTAFIELDTOSETSUBJECT',
238
                        '.. or select a field to use as the subject'
239
                    ),
240
                    $validSubjectFields->map('ID', 'Title')
241
                )->setEmptyString('')
242
            )
243
                ->setTitle(_t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILSUBJECT', 'Email subject')),
244
245
            // To
246
            FieldGroup::create(
247
                TextField::create(
248
                    'EmailAddress',
249
                    _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPETO', 'Type to address')
250
                )
251
                    ->setAttribute('style', 'min-width: 400px;'),
252
                DropdownField::create(
253
                    'SendEmailToFieldID',
254
                    _t(
255
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.ORSELECTAFIELDTOUSEASTO',
256
                        '.. or select a field to use as the to address'
257
                    ),
258
                    $validEmailToFields->map('ID', 'Title')
259
                )->setEmptyString(' ')
260
            )
261
                ->setTitle(_t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDEMAILTO', 'Send email to'))
262
                ->setDescription(_t(
263
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDEMAILTO_DESCRIPTION',
264
                    'You may enter multiple email addresses as a comma separated list.'
265
                )),
266
267
268
            // From
269
            TextField::create(
270
                'EmailFrom',
271
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.FROMADDRESS', 'Send email from')
272
            )
273
                ->setDescription(_t(
274
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.EmailFromContent',
275
                    "The from address allows you to set who the email comes from. On most servers this ".
276
                    "will need to be set to an email address on the same domain name as your site. ".
277
                    "For example on yoursite.com the from address may need to be [email protected]. ".
278
                    "You can however, set any email address you wish as the reply to address."
279
                )),
280
281
282
            // Reply-To
283
            FieldGroup::create(
284
                TextField::create('EmailReplyTo', _t(
285
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPEREPLY',
286
                    'Type reply address'
287
                ))
288
                    ->setAttribute('style', 'min-width: 400px;'),
289
                DropdownField::create(
290
                    'SendEmailFromFieldID',
291
                    _t(
292
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.ORSELECTAFIELDTOUSEASFROM',
293
                        '.. or select a field to use as reply to address'
294
                    ),
295
                    $validEmailFromFields->map('ID', 'Title')
296
                )->setEmptyString(' ')
297
            )
298
                ->setTitle(_t(
299
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.REPLYADDRESS',
300
                    'Email for reply to'
301
                ))
302
                ->setDescription(_t(
303
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.REPLYADDRESS_DESCRIPTION',
304
                    'The email address which the recipient is able to \'reply\' to.'
305
                ))
306
        ]);
307
308
        $fields->fieldByName('Root.EmailDetails')->setTitle(_t(__CLASS__.'.EMAILDETAILSTAB', 'Email Details'));
309
310
        // Only show the preview link if the recipient has been saved.
311
        if (!empty($this->EmailTemplate)) {
312
            $pageEditController = singleton(CMSPageEditController::class);
313
            $pageEditController
314
                ->getRequest()
315
                ->setSession(Controller::curr()->getRequest()->getSession());
316
317
            $preview = sprintf(
318
                '<p><a href="%s" target="_blank" class="btn btn-outline-secondary">%s</a></p><em>%s</em>',
319
                Controller::join_links(
320
                    $pageEditController->getEditForm()->FormAction(),
321
                    "field/EmailRecipients/item/{$this->ID}/preview"
322
                ),
323
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL', 'Preview email'),
324
                _t(
325
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL_DESCRIPTION',
326
                    'Note: Unsaved changes will not appear in the preview.'
327
                )
328
            );
329
        } else {
330
            $preview = sprintf(
331
                '<em>%s</em>',
332
                _t(
333
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL_UNAVAILABLE',
334
                    'You can preview this email once you have saved the Recipient.'
335
                )
336
            );
337
        }
338
339
        // Email templates
340
        $fields->addFieldsToTab('Root.EmailContent', [
341
            CheckboxField::create(
342
                'HideFormData',
343
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.HIDEFORMDATA', 'Hide form data from email?')
344
            ),
345
            CheckboxField::create(
346
                'SendPlain',
347
                _t(
348
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDPLAIN',
349
                    'Send email as plain text? (HTML will be stripped)'
350
                )
351
            ),
352
            HTMLEditorField::create(
353
                'EmailBodyHtml',
354
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILBODYHTML', 'Body')
355
            )
356
                ->addExtraClass('toggle-html-only'),
357
            TextareaField::create(
358
                'EmailBody',
359
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILBODY', 'Body')
360
            )
361
                ->addExtraClass('toggle-plain-only'),
362
            LiteralField::create('EmailPreview', $preview)
363
        ]);
364
365
        $templates = $this->getEmailTemplateDropdownValues();
366
367
        if ($templates) {
368
            $fields->insertBefore(
369
                DropdownField::create(
370
                    'EmailTemplate',
371
                    _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILTEMPLATE', 'Email template'),
372
                    $templates
373
                )->addExtraClass('toggle-html-only'),
374
                'EmailBodyHtml'
375
            );
376
        }
377
378
        $fields->fieldByName('Root.EmailContent')->setTitle(_t(__CLASS__.'.EMAILCONTENTTAB', 'Email Content'));
379
380
        // Custom rules for sending this field
381
        $grid = GridField::create(
382
            'CustomRules',
383
            _t('SilverStripe\\UserForms\\Model\\EditableFormField.CUSTOMRULES', 'Custom Rules'),
384
            $this->CustomRules(),
385
            $this->getRulesConfig()
386
        );
387
        $grid->setDescription(_t(
388
            'SilverStripe\\UserForms\\Model\\UserDefinedForm.RulesDescription',
389
            'Emails will only be sent to the recipient if the custom rules are met. If no rules are defined, this receipient will receive notifications for every submission.'
390
        ));
391
        $fields->addFieldsToTab('Root.CustomRules', [
392
            DropdownField::create(
393
                'CustomRulesCondition',
394
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIF', 'Send condition'),
395
                [
396
                    'Or' => _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFOR', 'Any conditions are true'),
397
                    'And' => _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFAND', 'All conditions are true')
398
                ]
399
            ),
400
            $grid
401
        ]);
402
403
        $fields->fieldByName('Root.CustomRules')->setTitle(_t(__CLASS__.'.CUSTOMRULESTAB', 'Custom Rules'));
404
405
        $this->extend('updateCMSFields', $fields);
406
        return $fields;
407
    }
408
409
    /**
410
     * Return whether a user can create an object of this type
411
     *
412
     * @param Member $member
413
     * @param array $context Virtual parameter to allow context to be passed in to check
414
     * @return bool
415
     */
416
    public function canCreate($member = null, $context = [])
417
    {
418
        // Check parent page
419
        $parent = $this->getCanCreateContext(func_get_args());
420
        if ($parent) {
421
            return $parent->canEdit($member);
422
        }
423
424
        // Fall back to secure admin permissions
425
        return parent::canCreate($member);
426
    }
427
428
    /**
429
     * Helper method to check the parent for this object
430
     *
431
     * @param array $args List of arguments passed to canCreate
432
     * @return SiteTree Parent page instance
433
     */
434
    protected function getCanCreateContext($args)
435
    {
436
        // Inspect second parameter to canCreate for a 'Parent' context
437
        if (isset($args[1][Form::class])) {
438
            return $args[1][Form::class];
439
        }
440
        // Hack in currently edited page if context is missing
441
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
442
            return Controller::curr()->currentPage();
443
        }
444
445
        // No page being edited
446
        return null;
447
    }
448
449
    public function canView($member = null)
450
    {
451
        if ($form = $this->Form()) {
452
            return $form->canView($member);
453
        }
454
455
        return parent::canView($member);
456
    }
457
458
    public function canEdit($member = null)
459
    {
460
        if ($form = $this->Form()) {
461
            return $form->canEdit($member);
462
        }
463
464
        return parent::canEdit($member);
465
    }
466
467
    /**
468
     * @param Member
469
     *
470
     * @return boolean
471
     */
472
    public function canDelete($member = null)
473
    {
474
        return $this->canEdit($member);
475
    }
476
477
    /**
478
     * Determine if this recipient may receive notifications for this submission
479
     *
480
     * @param array $data
481
     * @param Form $form
482
     * @return bool
483
     */
484
    public function canSend($data, $form)
485
    {
486
        // Skip if no rules configured
487
        $customRules = $this->CustomRules();
488
        if (!$customRules->count()) {
489
            return true;
490
        }
491
492
        // Check all rules
493
        $isAnd = $this->CustomRulesCondition === 'And';
494
        foreach ($customRules as $customRule) {
495
            /** @var EmailRecipientCondition  $customRule */
496
            $matches = $customRule->matches($data);
497
            if ($isAnd && !$matches) {
498
                return false;
499
            }
500
            if (!$isAnd && $matches) {
501
                return true;
502
            }
503
        }
504
505
        // Once all rules are checked
506
        return $isAnd;
507
    }
508
509
    /**
510
     * Make sure the email template saved against the recipient exists on the file system.
511
     *
512
     * @param string
513
     *
514
     * @return boolean
515
     */
516
    public function emailTemplateExists($template = '')
517
    {
518
        $t = ($template ? $template : $this->EmailTemplate);
519
520
        return array_key_exists($t, (array) $this->getEmailTemplateDropdownValues());
521
    }
522
523
    /**
524
     * Get the email body for the current email format
525
     *
526
     * @return string
527
     */
528
    public function getEmailBodyContent()
529
    {
530
        if ($this->SendPlain) {
531
            return DBField::create_field('HTMLText', $this->EmailBody)->Plain();
532
        }
533
        return DBField::create_field('HTMLText', $this->EmailBodyHtml);
534
    }
535
536
    /**
537
     * Gets a list of email templates suitable for populating the email template dropdown.
538
     *
539
     * @return array
540
     */
541
    public function getEmailTemplateDropdownValues()
542
    {
543
        $templates = [];
544
545
        $finder = new FileFinder();
546
        $finder->setOption('name_regex', '/^.*\.ss$/');
547
548
        $parent = $this->getFormParent();
549
550
        if (!$parent) {
551
            return [];
552
        }
553
554
        $emailTemplateDirectory = $parent->config()->get('email_template_directory');
555
        $templateDirectory = ModuleResourceLoader::resourcePath($emailTemplateDirectory);
556
557
        if (!$templateDirectory) {
558
            return [];
559
        }
560
561
        $found = $finder->find(BASE_PATH . DIRECTORY_SEPARATOR . $templateDirectory);
562
563
        foreach ($found as $key => $value) {
564
            $template = pathinfo($value);
565
            $absoluteFilename = $template['dirname'] . DIRECTORY_SEPARATOR . $template['filename'];
566
567
            // Optionally remove vendor/ path prefixes
568
            $resource = ModuleResourceLoader::singleton()->resolveResource($emailTemplateDirectory);
569
            if ($resource instanceof ModuleResource && $resource->getModule()) {
570
                $prefixToStrip = $resource->getModule()->getPath();
571
            } else {
572
                $prefixToStrip = BASE_PATH;
573
            }
574
            $templatePath = substr($absoluteFilename, strlen($prefixToStrip) + 1);
575
576
            // Optionally remove "templates/" prefixes
577
            if (substr($templatePath, 0, 10)) {
578
                $templatePath = substr($templatePath, 10);
579
            }
580
581
            $templates[$templatePath] = $template['filename'];
582
        }
583
584
        return $templates;
585
    }
586
587
    /**
588
     * Validate that valid email addresses are being used
589
     *
590
     * @return ValidationResult
591
     */
592
    public function validate()
593
    {
594
        $result = parent::validate();
595
        $checkEmail = [
596
            'EmailAddress' => 'EMAILADDRESSINVALID',
597
            'EmailFrom' => 'EMAILFROMINVALID',
598
            'EmailReplyTo' => 'EMAILREPLYTOINVALID',
599
        ];
600
        foreach ($checkEmail as $check => $translation) {
601
            if ($this->$check) {
602
                //may be a comma separated list of emails
603
                $addresses = explode(',', $this->$check);
604
                foreach ($addresses as $address) {
605
                    $trimAddress = trim($address);
606
                    if ($trimAddress && !Email::is_valid_address($trimAddress)) {
607
                        $error = _t(
608
                            __CLASS__.".$translation",
609
                            "Invalid email address $trimAddress"
610
                        );
611
                        $result->addError($error . " ($trimAddress)");
612
                    }
613
                }
614
            }
615
        }
616
        return $result;
617
    }
618
}
619