Completed
Pull Request — master (#647)
by Robbie
02:08
created

EmailRecipient   F

Complexity

Total Complexity 44

Size/Duplication

Total Lines 543
Duplicated Lines 2.03 %

Coupling/Cohesion

Components 1
Dependencies 34

Importance

Changes 0
Metric Value
wmc 44
lcom 1
cbo 34
dl 11
loc 543
rs 3.9115
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A summaryFields() 0 14 4
A getFormParent() 0 7 2
A getTitle() 0 10 3
B getRulesConfig() 0 28 1
B getCMSFields() 0 213 3
A canCreate() 11 11 2
A getCanCreateContext() 0 14 4
A canView() 0 4 1
A canEdit() 0 4 1
A canDelete() 0 4 1
C canSend() 0 24 7
A emailTemplateExists() 0 6 2
A getEmailBodyContent() 0 7 2
B getEmailTemplateDropdownValues() 0 33 5
B validate() 0 26 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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\Control\Controller;
9
use SilverStripe\Control\Email\Email;
10
use SilverStripe\Control\Session;
11
use SilverStripe\Core\Manifest\ModuleLoader;
12
use SilverStripe\Forms\CheckboxField;
13
use SilverStripe\Forms\DropdownField;
14
use SilverStripe\Forms\FieldGroup;
15
use SilverStripe\Forms\FieldList;
16
use SilverStripe\Forms\Form;
17
use SilverStripe\Forms\GridField\GridField;
18
use SilverStripe\Forms\GridField\GridFieldButtonRow;
19
use SilverStripe\Forms\GridField\GridFieldConfig;
20
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
21
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
22
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
23
use SilverStripe\Forms\LiteralField;
24
use SilverStripe\Forms\TabSet;
25
use SilverStripe\Forms\TextareaField;
26
use SilverStripe\Forms\TextField;
27
use SilverStripe\ORM\ArrayList;
28
use SilverStripe\ORM\DataObject;
29
use SilverStripe\ORM\FieldType\DBField;
30
use SilverStripe\UserForms\Model\EditableFormField\EditableEmailField;
31
use SilverStripe\UserForms\Model\EditableFormField;
32
use SilverStripe\UserForms\Model\EditableFormField\EditableMultipleOptionField;
33
use SilverStripe\UserForms\Model\EditableFormField\EditableTextField;
34
use SilverStripe\UserForms\Model\Recipient\EmailRecipientCondition;
35
use SilverStripe\UserForms\Model\UserDefinedForm;
36
use SilverStripe\View\Requirements;
37
use Symbiote\GridFieldExtensions\GridFieldAddNewInlineButton;
38
use Symbiote\GridFieldExtensions\GridFieldEditableColumns;
39
40
/**
41
 * A Form can have multiply members / emails to email the submission
42
 * to and custom subjects
43
 *
44
 * @package userforms
45
 */
46
class EmailRecipient extends DataObject
47
{
48
    private static $db = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
49
        'EmailAddress' => 'Varchar(200)',
50
        'EmailSubject' => 'Varchar(200)',
51
        'EmailFrom' => 'Varchar(200)',
52
        'EmailReplyTo' => 'Varchar(200)',
53
        'EmailBody' => 'Text',
54
        'EmailBodyHtml' => 'HTMLText',
55
        'EmailTemplate' => 'Varchar',
56
        'SendPlain' => 'Boolean',
57
        'HideFormData' => 'Boolean',
58
        'CustomRulesCondition' => 'Enum("And,Or")'
59
    ];
60
61
    private static $has_one = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
62
        'Form' => UserDefinedForm::class,
63
        'SendEmailFromField' => EditableFormField::class,
64
        'SendEmailToField' => EditableFormField::class,
65
        'SendEmailSubjectField' => EditableFormField::class
66
    ];
67
68
    private static $has_many = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
69
        'CustomRules' => EmailRecipientCondition::class,
70
    ];
71
72
    private static $owns = [
0 ignored issues
show
Unused Code introduced by
The property $owns is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
73
        'CustomRules',
74
    ];
75
76
    private static $cascade_deetes = [
0 ignored issues
show
Unused Code introduced by
The property $cascade_deetes is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
77
        'CustomRules',
78
    ];
79
80
    private static $summary_fields = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $summary_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
81
        'EmailAddress',
82
        'EmailSubject',
83
        'EmailFrom'
84
    ];
85
86
    private static $table_name = 'UserDefinedForm_EmailRecipient';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $table_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
87
88
    /**
89
     * Setting this to true will allow you to select "risky" fields as
90
     * email recipient, such as free-text entry fields.
91
     *
92
     * It's advisable to leave this off.
93
     *
94
     * @config
95
     * @var bool
96
     */
97
    private static $allow_unbound_recipient_fields = false;
0 ignored issues
show
Unused Code introduced by
The property $allow_unbound_recipient_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
98
99
    public function summaryFields()
100
    {
101
        $fields = parent::summaryFields();
102
        if (isset($fields['EmailAddress'])) {
103
            $fields['EmailAddress'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILADDRESS', Email::class);
104
        }
105
        if (isset($fields['EmailSubject'])) {
106
            $fields['EmailSubject'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILSUBJECT', 'Subject');
107
        }
108
        if (isset($fields['EmailFrom'])) {
109
            $fields['EmailFrom'] = _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILFROM', 'From');
110
        }
111
        return $fields;
112
    }
113
114
    /**
115
     * Get instance of UserDefinedForm when editing in getCMSFields
116
     *
117
     * @return UserDefinedFrom
118
     */
119
    protected function getFormParent()
120
    {
121
        $formID = $this->FormID
0 ignored issues
show
Documentation introduced by
The property FormID does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
122
            ? $this->FormID
0 ignored issues
show
Documentation introduced by
The property FormID does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
123
            : Session::get('CMSMain.currentPage');
124
        return UserDefinedForm::get()->byID($formID);
125
    }
126
127
    public function getTitle()
128
    {
129
        if ($this->EmailAddress) {
0 ignored issues
show
Documentation introduced by
The property EmailAddress does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
130
            return $this->EmailAddress;
0 ignored issues
show
Documentation introduced by
The property EmailAddress does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
131
        }
132
        if ($this->EmailSubject) {
0 ignored issues
show
Documentation introduced by
The property EmailSubject does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
133
            return $this->EmailSubject;
0 ignored issues
show
Documentation introduced by
The property EmailSubject does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
134
        }
135
        return parent::getTitle();
136
    }
137
138
    /**
139
     * Generate a gridfield config for editing filter rules
140
     *
141
     * @return GridFieldConfig
142
     */
143
    protected function getRulesConfig()
144
    {
145
        $formFields = $this->getFormParent()->Fields();
146
147
        $config = GridFieldConfig::create()
148
            ->addComponents(
149
                new GridFieldButtonRow('before'),
150
                new GridFieldToolbarHeader(),
151
                new GridFieldAddNewInlineButton(),
152
                new GridFieldDeleteAction(),
153
                $columns = new GridFieldEditableColumns()
154
            );
155
156
        $columns->setDisplayFields(array(
157
            '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.

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

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

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

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

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

Loading history...
165
                return TextField::create($column);
166
            }
167
        ));
168
169
        return $config;
170
    }
171
172
    /**
173
     * @return FieldList
174
     */
175
    public function getCMSFields()
176
    {
177
        Requirements::javascript(
178
            ModuleLoader::getModule('silverstripe/userforms')->getRelativeResourcePath('client/dist/js/userforms-cms.js')
179
        );
180
181
        // Determine optional field values
182
        $form = $this->getFormParent();
183
184
        // predefined choices are also candidates
185
        $multiOptionFields = EditableMultipleOptionField::get()->filter('ParentID', $form->ID);
186
187
        // if they have email fields then we could send from it
188
        $validEmailFromFields = EditableEmailField::get()->filter('ParentID', $form->ID);
189
190
        // For the subject, only one-line entry boxes make sense
191
        $validSubjectFields = ArrayList::create(
192
            EditableTextField::get()
193
                ->filter('ParentID', $form->ID)
194
                ->exclude('Rows:GreaterThan', 1)
195
                ->toArray()
196
        );
197
        $validSubjectFields->merge($multiOptionFields);
198
199
200
        // Check valid email-recipient fields
201
        if ($this->config()->get('allow_unbound_recipient_fields')) {
202
            // To address can only be email fields or multi option fields
203
            $validEmailToFields = ArrayList::create($validEmailFromFields->toArray());
204
            $validEmailToFields->merge($multiOptionFields);
205
        } else {
206
            // To address cannot be unbound, so restrict to pre-defined lists
207
            $validEmailToFields = $multiOptionFields;
208
        }
209
210
        // Build fieldlist
211
        $fields = FieldList::create(Tabset::create('Root')->addExtraClass('EmailRecipientForm'));
212
213
        // Configuration fields
214
        $fields->addFieldsToTab('Root.EmailDetails', [
215
            // Subject
216
            FieldGroup::create(
217
                TextField::create(
218
                    'EmailSubject',
219
                    _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPESUBJECT', 'Type subject')
220
                )
221
                    ->setAttribute('style', 'min-width: 400px;'),
222
                DropdownField::create(
223
                    'SendEmailSubjectFieldID',
224
                    _t(
225
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.SELECTAFIELDTOSETSUBJECT',
226
                        '.. or select a field to use as the subject'
227
                    ),
228
                    $validSubjectFields->map('ID', 'Title')
229
                )->setEmptyString('')
230
            )
231
                ->setTitle(_t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILSUBJECT', 'Email subject')),
232
233
            // To
234
            FieldGroup::create(
235
                TextField::create(
236
                    'EmailAddress',
237
                    _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPETO', 'Type to address')
238
                )
239
                    ->setAttribute('style', 'min-width: 400px;'),
240
                DropdownField::create(
241
                    'SendEmailToFieldID',
242
                    _t(
243
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.ORSELECTAFIELDTOUSEASTO',
244
                        '.. or select a field to use as the to address'
245
                    ),
246
                    $validEmailToFields->map('ID', 'Title')
247
                )->setEmptyString(' ')
248
            )
249
                ->setTitle(_t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDEMAILTO', 'Send email to'))
250
                ->setDescription(_t(
251
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDEMAILTO_DESCRIPTION',
252
                    'You may enter multiple email addresses as a comma separated list.'
253
                )),
254
255
256
            // From
257
            TextField::create(
258
                'EmailFrom',
259
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.FROMADDRESS', 'Send email from')
260
            )
261
                ->setDescription(_t(
262
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.EmailFromContent',
263
                    "The from address allows you to set who the email comes from. On most servers this ".
264
                    "will need to be set to an email address on the same domain name as your site. ".
265
                    "For example on yoursite.com the from address may need to be [email protected]. ".
266
                    "You can however, set any email address you wish as the reply to address."
267
                )),
268
269
270
            // Reply-To
271
            FieldGroup::create(
272
                TextField::create('EmailReplyTo', _t(
273
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.TYPEREPLY',
274
                    'Type reply address'
275
                ))
276
                    ->setAttribute('style', 'min-width: 400px;'),
277
                DropdownField::create(
278
                    'SendEmailFromFieldID',
279
                    _t(
280
                        'SilverStripe\\UserForms\\Model\\UserDefinedForm.ORSELECTAFIELDTOUSEASFROM',
281
                        '.. or select a field to use as reply to address'
282
                    ),
283
                    $validEmailFromFields->map('ID', 'Title')
284
                )->setEmptyString(' ')
285
            )
286
                ->setTitle(_t(
287
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.REPLYADDRESS',
288
                    'Email for reply to'
289
                ))
290
                ->setDescription(_t(
291
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.REPLYADDRESS_DESCRIPTION',
292
                    'The email address which the recipient is able to \'reply\' to.'
293
                ))
294
        ]);
295
296
        $fields->fieldByName('Root.EmailDetails')->setTitle(_t(__CLASS__.'.EMAILDETAILSTAB', 'Email Details'));
297
298
        // Only show the preview link if the recipient has been saved.
299
        if (!empty($this->EmailTemplate)) {
0 ignored issues
show
Documentation introduced by
The property EmailTemplate does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
300
            $pageEditController = singleton(CMSPageEditController::class);
301
            $pageEditController
302
                ->getRequest()
303
                ->setSession(Controller::curr()->getRequest()->getSession());
304
305
            $preview = sprintf(
306
                '<p><a href="%s" target="_blank" class="ss-ui-button">%s</a></p><em>%s</em>',
307
                Controller::join_links(
308
                    $pageEditController->getEditForm()->FormAction(),
309
                    "field/EmailRecipients/item/{$this->ID}/preview"
310
                ),
311
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL', 'Preview email'),
312
                _t(
313
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL_DESCRIPTION',
314
                    'Note: Unsaved changes will not appear in the preview.'
315
                )
316
            );
317
        } else {
318
            $preview = sprintf(
319
                '<em>%s</em>',
320
                _t(
321
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.PREVIEW_EMAIL_UNAVAILABLE',
322
                    'You can preview this email once you have saved the Recipient.'
323
                )
324
            );
325
        }
326
327
        // Email templates
328
        $fields->addFieldsToTab('Root.EmailContent', [
329
            CheckboxField::create(
330
                'HideFormData',
331
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.HIDEFORMDATA', 'Hide form data from email?')
332
            ),
333
            CheckboxField::create(
334
                'SendPlain',
335
                _t(
336
                    'SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDPLAIN',
337
                    'Send email as plain text? (HTML will be stripped)'
338
                )
339
            ),
340
            DropdownField::create(
341
                'EmailTemplate',
342
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILTEMPLATE', 'Email template'),
343
                $this->getEmailTemplateDropdownValues()
344
            )->addExtraClass('toggle-html-only'),
345
            HTMLEditorField::create(
346
                'EmailBodyHtml',
347
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILBODYHTML', 'Body')
348
            )
349
                ->addExtraClass('toggle-html-only'),
350
            TextareaField::create(
351
                'EmailBody',
352
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.EMAILBODY', 'Body')
353
            )
354
                ->addExtraClass('toggle-plain-only'),
355
            LiteralField::create('EmailPreview', $preview)
356
        ]);
357
358
        $fields->fieldByName('Root.EmailContent')->setTitle(_t(__CLASS__.'.EMAILCONTENTTAB', 'Email Content'));
359
360
        // Custom rules for sending this field
361
        $grid = GridField::create(
362
            'CustomRules',
363
            _t('SilverStripe\\UserForms\\Model\\EditableFormField.CUSTOMRULES', 'Custom Rules'),
364
            $this->CustomRules(),
0 ignored issues
show
Documentation Bug introduced by
The method CustomRules does not exist on object<SilverStripe\User...cipient\EmailRecipient>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
365
            $this->getRulesConfig()
366
        );
367
        $grid->setDescription(_t(
368
            'SilverStripe\\UserForms\\Model\\UserDefinedForm.RulesDescription',
369
            '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.'
370
        ));
371
        $fields->addFieldsToTab('Root.CustomRules', [
372
            DropdownField::create(
373
                'CustomRulesCondition',
374
                _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIF', 'Send condition'),
375
                [
376
                    'Or' => _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFOR', 'Any conditions are true'),
377
                    'And' => _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFAND', 'All conditions are true')
378
                ]
379
            ),
380
            $grid
381
        ]);
382
383
        $fields->fieldByName('Root.CustomRules')->setTitle(_t(__CLASS__.'.CUSTOMRULESTAB', 'Custom Rules'));
384
385
        $this->extend('updateCMSFields', $fields);
386
        return $fields;
387
    }
388
389
    /**
390
     * Return whether a user can create an object of this type
391
     *
392
     * @param Member $member
393
     * @param array $context Virtual parameter to allow context to be passed in to check
394
     * @return bool
395
     */
396 View Code Duplication
    public function canCreate($member = null, $context = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
397
    {
398
        // Check parent page
399
        $parent = $this->getCanCreateContext(func_get_args());
400
        if ($parent) {
401
            return $parent->canEdit($member);
402
        }
403
404
        // Fall back to secure admin permissions
405
        return parent::canCreate($member);
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::canCreate($member); of type boolean|string adds the type string to the return on line 405 which is incompatible with the return type documented by SilverStripe\UserForms\M...ailRecipient::canCreate of type boolean.
Loading history...
406
    }
407
408
    /**
409
     * Helper method to check the parent for this object
410
     *
411
     * @param array $args List of arguments passed to canCreate
412
     * @return SiteTree Parent page instance
413
     */
414
    protected function getCanCreateContext($args)
415
    {
416
        // Inspect second parameter to canCreate for a 'Parent' context
417
        if (isset($args[1][Form::class])) {
418
            return $args[1][Form::class];
419
        }
420
        // Hack in currently edited page if context is missing
421
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
422
            return Controller::curr()->currentPage();
423
        }
424
425
        // No page being edited
426
        return null;
427
    }
428
429
    /**
430
     * @param Member
431
     *
432
     * @return boolean
433
     */
434
    public function canView($member = null)
435
    {
436
        return $this->Form()->canView($member);
0 ignored issues
show
Documentation Bug introduced by
The method Form does not exist on object<SilverStripe\User...cipient\EmailRecipient>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
437
    }
438
439
    /**
440
     * @param Member
441
     *
442
     * @return boolean
443
     */
444
    public function canEdit($member = null)
445
    {
446
        return $this->Form()->canEdit($member);
0 ignored issues
show
Documentation Bug introduced by
The method Form does not exist on object<SilverStripe\User...cipient\EmailRecipient>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
447
    }
448
449
    /**
450
     * @param Member
451
     *
452
     * @return boolean
453
     */
454
    public function canDelete($member = null)
455
    {
456
        return $this->canEdit($member);
457
    }
458
459
    /*
460
     * Determine if this recipient may receive notifications for this submission
461
     *
462
     * @param array $data
463
     * @param Form $form
464
     * @return bool
465
     */
466
    public function canSend($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

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

Loading history...
467
    {
468
        // Skip if no rules configured
469
        $customRules = $this->CustomRules();
0 ignored issues
show
Documentation Bug introduced by
The method CustomRules does not exist on object<SilverStripe\User...cipient\EmailRecipient>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
470
        if (!$customRules->count()) {
471
            return true;
472
        }
473
474
        // Check all rules
475
        $isAnd = $this->CustomRulesCondition === 'And';
0 ignored issues
show
Documentation introduced by
The property CustomRulesCondition does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
476
        foreach ($customRules as $customRule) {
477
            /** @var EmailRecipientCondition  $customRule */
478
            $matches = $customRule->matches($data);
479
            if ($isAnd && !$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
480
                return false;
481
            }
482
            if (!$isAnd && $matches) {
483
                return true;
484
            }
485
        }
486
487
        // Once all rules are checked
488
        return $isAnd;
489
    }
490
491
    /**
492
     * Make sure the email template saved against the recipient exists on the file system.
493
     *
494
     * @param string
495
     *
496
     * @return boolean
497
     */
498
    public function emailTemplateExists($template = '')
499
    {
500
        $t = ($template ? $template : $this->EmailTemplate);
0 ignored issues
show
Documentation introduced by
The property EmailTemplate does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
501
502
        return in_array($t, $this->getEmailTemplateDropdownValues());
503
    }
504
505
    /**
506
     * Get the email body for the current email format
507
     *
508
     * @return string
509
     */
510
    public function getEmailBodyContent()
511
    {
512
        if ($this->SendPlain) {
0 ignored issues
show
Documentation introduced by
The property SendPlain does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
513
            return DBField::create_field('HTMLText', $this->EmailBody)->Plain();
0 ignored issues
show
Documentation introduced by
The property EmailBody does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
514
        }
515
        return DBField::create_field('HTMLText', $this->EmailBodyHtml);
0 ignored issues
show
Documentation introduced by
The property EmailBodyHtml does not exist on object<SilverStripe\User...cipient\EmailRecipient>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
516
    }
517
518
    /**
519
     * Gets a list of email templates suitable for populating the email template dropdown.
520
     *
521
     * @return array
522
     */
523
    public function getEmailTemplateDropdownValues()
524
    {
525
        $templates = [];
526
527
        $finder = new FileFinder();
528
        $finder->setOption('name_regex', '/^.*\.ss$/');
529
530
        $templateDirectory = UserDefinedForm::config()->get('email_template_directory');
531
        // Handle cases where "userforms" might not be the base module directory, e.g. in a Travis build
532
        if (!file_exists(BASE_PATH . DIRECTORY_SEPARATOR . $templateDirectory)
533
            && substr($templateDirectory, 0, 10) === 'userforms/'
534
        ) {
535
            $templateDirectory = substr($templateDirectory, 10);
536
        }
537
        $found = $finder->find(BASE_PATH . DIRECTORY_SEPARATOR . $templateDirectory);
538
539
        foreach ($found as $key => $value) {
540
            $template = pathinfo($value);
541
            $templatePath = substr(
542
                $template['dirname'] . DIRECTORY_SEPARATOR . $template['filename'],
543
                strlen(BASE_PATH) + 1
544
            );
545
546
            $defaultPrefix = 'userforms/templates/';
547
            // Remove default userforms folder if it's provided
548
            if (substr($templatePath, 0, strlen($defaultPrefix)) === $defaultPrefix) {
549
                $templatePath = substr($templatePath, strlen($defaultPrefix));
550
            }
551
            $templates[$templatePath] = $template['filename'];
552
        }
553
554
        return $templates;
555
    }
556
557
    /**
558
     * Validate that valid email addresses are being used
559
     *
560
     * @return ValidationResult
561
     */
562
    public function validate()
563
    {
564
        $result = parent::validate();
565
        $checkEmail = [
566
            'EmailAddress' => 'EMAILADDRESSINVALID',
567
            'EmailFrom' => 'EMAILFROMINVALID',
568
            'EmailReplyTo' => 'EMAILREPLYTOINVALID',
569
        ];
570
        foreach ($checkEmail as $check => $translation) {
571
            if ($this->$check) {
572
                //may be a comma separated list of emails
573
                $addresses = explode(',', $this->$check);
574
                foreach ($addresses as $address) {
575
                    $trimAddress = trim($address);
576
                    if ($trimAddress && !Email::is_valid_address($trimAddress)) {
577
                        $error = _t(
578
                            __CLASS__.".$translation",
579
                            "Invalid email address $trimAddress"
580
                        );
581
                        $result->addError($error . " ($trimAddress)");
582
                    }
583
                }
584
            }
585
        }
586
        return $result;
587
    }
588
}
589