Completed
Push — master ( adb17f...230106 )
by
unknown
01:18
created

EmailReminder_NotificationSchedule::getCMSFields()   D

Complexity

Conditions 10
Paths 96

Size

Total Lines 146
Code Lines 101

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 146
c 0
b 0
f 0
rs 4.8196
cc 10
eloc 101
nc 96
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 3 and the first side effect is on line 541.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
class EmailReminder_NotificationSchedule extends DataObject
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
4
{
5
6
    /**
7
     * @var int
8
     */
9
    private static $grace_days = 3;
0 ignored issues
show
Unused Code introduced by
The property $grace_days 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...
10
11
    /**
12
     * @var string
13
     */
14
    private static $default_data_object = 'Member';
0 ignored issues
show
Unused Code introduced by
The property $default_data_object 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...
15
16
    /**
17
     * @var string
18
     */
19
    private static $default_date_field = '';
0 ignored issues
show
Unused Code introduced by
The property $default_date_field 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...
20
21
    /**
22
     * @var string
23
     */
24
    private static $default_email_field = '';
0 ignored issues
show
Unused Code introduced by
The property $default_email_field 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...
25
26
    /**
27
     * @var string
28
     */
29
    private static $replaceable_record_fields = array('FirstName', 'Surname', 'Email');
0 ignored issues
show
Unused Code introduced by
The property $replaceable_record_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...
30
31
    /**
32
     * @var string
33
     */
34
    private static $include_method = 'EmailReminderInclude';
0 ignored issues
show
Unused Code introduced by
The property $include_method 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...
35
36
    /**
37
     * @var string
38
     */
39
    private static $exclude_method = 'EmailReminderExclude';
0 ignored issues
show
Unused Code introduced by
The property $exclude_method 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...
40
41
    /**
42
     * @var string
43
     */
44
    private static $mail_out_class = 'EmailReminder_DailyMailOut';
0 ignored issues
show
Unused Code introduced by
The property $mail_out_class 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...
45
46
    private static $singular_name = 'Email Reminder Schedule';
47
    public function i18n_singular_name()
48
    {
49
        return self::$singular_name;
50
    }
51
52
    private static $plural_name = 'Email Reminder Schedules';
53
    public function i18n_plural_name()
54
    {
55
        return self::$plural_name;
56
    }
57
58
    private static $db = array(
0 ignored issues
show
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...
59
        'DataObject' => 'Varchar(100)',
60
        'EmailField' => 'Varchar(100)',
61
        'DateField' => 'Varchar(100)',
62
        'Days' => 'Int',
63
        'RepeatDays' => 'Int',
64
        'BeforeAfter' => "Enum('before,after,immediately','before')",
65
        'EmailFrom' => 'Varchar(100)',
66
        'EmailSubject' => 'Varchar(100)',
67
        'Content' => 'HTMLText',
68
        'Disable' => 'Boolean',
69
        'SendTestTo' => 'Text'
70
    );
71
72
73
    private static $has_many = array(
0 ignored issues
show
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...
74
        'EmailsSent' => 'EmailReminder_EmailRecord'
75
    );
76
77
    private static $summary_fields = array(
0 ignored issues
show
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...
78
        'EmailSubject',
79
        'Days'
80
    );
81
82
    private static $field_labels = array(
0 ignored issues
show
Unused Code introduced by
The property $field_labels 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...
83
        'DataObject' => 'Class/Table',
84
        'Days' => 'Days from Expiry',
85
        'RepeatDays' => 'Repeat cycle days'
86
    );
87
88
    public function populateDefaults()
89
    {
90
        parent::populateDefaults();
91
        $this->DataObject = $this->Config()->get('default_data_object');
92
        $this->EmailField = $this->Config()->get('default_email_field');
93
        $this->DateField = $this->Config()->get('default_date_field');
94
        $this->Days = 7;
95
        $this->RepeatDays = 300;
96
        $this->BeforeAfter = 'before';
97
        $this->EmailFrom = Config::inst()->get('Email', 'admin_email');
98
        $this->EmailSubject = 'Your memberships expires in [days] days';
99
    }
100
101
    public function getCMSFields()
102
    {
103
        $fields = parent::getCMSFields();
104
105
        $emailsSentField = $fields->dataFieldByName('EmailsSent');
106
        $fields->removeFieldFromTab('Root', 'EmailsSent');
107
108
        $fields->addFieldToTab(
109
            'Root.Main',
110
            CheckboxField::create('Disable')
111
        );
112
        $fields->addFieldToTab(
113
            'Root.Main',
114
            $dataObjecField = DropdownField::create(
115
                'DataObject',
116
                'Table/Class Name',
117
                $this->dataObjectOptions()
118
            )
119
            ->setRightTitle('Type a valid table/class name')
120
        );
121
        if ($this->Config()->get('default_data_object')) {
122
            $fields->replaceField('DataObject', $dataObjecField->performReadonlyTransformation());
123
        }
124
125
126
127
        $fields->addFieldToTab(
128
            'Root.Main',
129
            $emailFieldField = DropdownField::create(
130
                'EmailField',
131
                'Email Field',
132
                $this->emailFieldOptions()
133
            )
134
            ->setRightTitle('Select the field that will contain a valid email address')
135
            ->setEmptyString('[ Please select ]')
136
        );
137
        if ($this->Config()->get('default_email_field')) {
138
            $fields->replaceField('EmailField', $emailFieldField->performReadonlyTransformation());
139
        }
140
141
        $fields->addFieldToTab(
142
            'Root.Main',
143
            $dateFieldField = DropdownField::create(
144
                'DateField',
145
                'Date Field',
146
                $this->dateFieldOptions()
147
            )
148
            ->setRightTitle('Select a valid Date field to calculate when reminders should be sent')
149
            ->setEmptyString('[ Please select ]')
150
        );
151
        if ($this->Config()->get('default_date_field')) {
152
            $fields->replaceField('DateField', $dateFieldField->performReadonlyTransformation());
153
        }
154
155
        $fields->removeFieldsFromTab(
156
            'Root.Main',
157
            array('Days', 'BeforeAfter', 'RepeatDays')
158
        );
159
        $fields->addFieldsToTab(
160
            'Root.Main',
161
            array(
162
                DropdownField::create('BeforeAfter', 'Before / After Expiration', array('before' => 'before', 'after' => 'after', 'immediately' => 'immediately'))
163
                    ->setRightTitle('Are the days listed above before or after the actual expiration date.'),
164
                NumericField::create('Days', 'Days')
165
                    ->setRightTitle('How many days in advance (before) or in arrears (after) of the expiration date should this email be sent? </br>This field is ignored if set to send immediately.'),
166
                NumericField::create('RepeatDays', 'Repeat Cycle Days')
167
                    ->setRightTitle(
168
                        '
169
                        Number of days after which the same reminder can be sent to the same email address.
170
                        <br />We allow an e-mail to be sent to one specific email address for one specific reminder only once.
171
                        <br />In this field you can indicate for how long we will apply this rule.'
172
                )
173
            )
174
        );
175
        $fields->addFieldsToTab(
176
            'Root.EmailContent',
177
            array(
178
                TextField::create('EmailFrom', 'Email From Address')
179
                    ->setRightTitle('The email from address, eg: "My Company &lt;[email protected]&gt;"'),
180
                $subjectField = TextField::create('EmailSubject', 'Email Subject Line')
181
                    ->setRightTitle('The subject of the email'),
182
                $contentField = HTMLEditorField::create('Content', 'Email Content')
183
                    ->SetRows(20)
184
            )
185
        );
186
        if ($obj = $this->getReplacerObject()) {
187
            $html = $obj->replaceHelpList($asHTML = true);
188
            $otherFieldsThatCanBeUsed = $this->getFieldsFromDataObject(array('*'));
189
            $replaceableFields = $this->Config()->get('replaceable_record_fields');
190
            if (count($otherFieldsThatCanBeUsed)) {
191
                $html .= '<h3>You can also use the record fields (not replaced in tests):</h3><ul>';
192
                foreach ($otherFieldsThatCanBeUsed as $key => $value) {
193
                    if (in_array($key, $replaceableFields)) {
194
                        $html .= '<li><strong>$'.$key.'</strong> <span>'.$value.'</span></li>';
195
                    }
196
                }
197
            }
198
            $html .= '</ul>';
199
            $subjectField->setRightTitle('for replacement options, please see below ...');
200
            $contentField->setRightTitle($html);
201
        }
202
        $fields->addFieldsToTab(
203
            'Root.Sent',
204
            array(
205
                TextareaField::create('SendTestTo', 'Send test email to ...')
206
                    ->setRightTitle(
207
                        '
208
                        Separate emails by commas, a test email will be sent every time you save this Email Reminder, if you do not want test emails to be sent make sure this field is empty
209
                        '
210
                    )
211
                    ->SetRows(3)
212
            )
213
        );
214
        if ($emailsSentField) {
215
            $config = $emailsSentField->getConfig();
216
            $config->removeComponentsByType('GridFieldAddExistingAutocompleter');
217
            $fields->addFieldToTab(
218
                'Root.Sent',
219
                $emailsSentField
220
            );
221
        }
222
        $records = $this->CurrentRecords();
223
        if ($records) {
224
            $fields->addFieldsToTab(
225
                'Root.Review',
226
                array(
227
                    GridField::create(
228
                        'CurrentRecords',
229
                        'Today we are sending to ...',
230
                        $records
231
                    ),
232
                    LiteralField::create(
233
                        'SampleSelectStatement',
234
                        '<h3>Here is a sample statement used to select records:</h3>
235
                        <pre>'.$this->whereStatementForDays().'</pre>'
236
                    ),
237
                    LiteralField::create(
238
                        'SampleFieldDataForRecords',
239
                        '<h3>sample of '.$this->DateField.' field values:</h3>
240
                        <li>'.implode('</li><li>', $this->SampleFieldDataForRecords()).'</li>'
241
                    )
242
                )
243
            );
244
        }
245
        return $fields;
246
    }
247
248
    /**
249
     * @return array
250
     */
251
    protected function dataObjectOptions()
252
    {
253
        return ClassInfo::subclassesFor("DataObject");
254
    }
255
256
    /**
257
     * @return array
258
     */
259
    protected function emailFieldOptions()
260
    {
261
        return $this->getFieldsFromDataObject(array('Varchar', 'Email'));
262
    }
263
264
    /**
265
     * @return array
266
     */
267
    protected function dateFieldOptions()
268
    {
269
        return $this->getFieldsFromDataObject(array('Date'));
270
    }
271
272
273
    /**
274
     * list of database fields available
275
     * @param  array $fieldTypeMatchArray - strpos filter
276
     * @return array
277
     */
278
    protected function getFieldsFromDataObject($fieldTypeMatchArray = array())
279
    {
280
        $array = array();
281
        if ($this->hasValidDataObject()) {
282
            $object = Injector::inst()->get($this->DataObject);
283
            if ($object) {
284
                $allOptions = $object->stat('db');
285
                $fieldLabels = $object->fieldLabels();
286
                foreach ($allOptions as $fieldName => $fieldType) {
287
                    foreach ($fieldTypeMatchArray as $matchString) {
288
                        if ((strpos($fieldType, $matchString) !== false) || $matchString == '*') {
289
                            if (isset($fieldLabels[$fieldName])) {
290
                                $label = $fieldLabels[$fieldName];
291
                            } else {
292
                                $label = $fieldName;
293
                            }
294
                            $array[$fieldName] = $label;
295
                        }
296
                    }
297
                }
298
            }
299
        }
300
        return $array;
301
    }
302
303
304
    /**
305
     * Test if valid classname has been set
306
     * @param null
307
     * @return Boolean
308
     */
309
    public function hasValidDataObject()
310
    {
311
        return (! $this->DataObject) || ClassInfo::exists($this->DataObject) ? true : false;
312
    }
313
314
    /**
315
     * Test if valid fields have been set
316
     * @param null
317
     * @return Boolean
318
     */
319
    public function hasValidDataObjectFields()
320
    {
321
        if (!$this->hasValidDataObject()) {
322
            return false;
323
        }
324
        $emailFieldOptions = $this->emailFieldOptions();
325
        if (!isset($emailFieldOptions[$this->EmailField])) {
326
            return false;
327
        }
328
        $dateFieldOptions = $this->dateFieldOptions();
329
        if (!isset($dateFieldOptions[$this->DateField])) {
330
            return false;
331
        }
332
        return true;
333
    }
334
335
    /**
336
     * @return string
337
     */
338
    public function getTitle()
339
    {
340
        $niceTitle = '[' . $this->EmailSubject . '] // send ';
341
        $niceTitle .= ($this->BeforeAfter === 'immediately') ?  $this->BeforeAfter : $this->Days . ' days '.$this->BeforeAfter.' Expiration Date';
342
        return ($this->hasValidDataObjectFields()) ? $niceTitle : 'uncompleted';
343
    }
344
345
    /**
346
     * @return boolean
347
     */
348
    public function hasValidFields()
349
    {
350
        if (!$this->hasValidDataObject()) {
351
            return false;
352
        }
353
        if (!$this->hasValidDataObjectFields()) {
354
            return false;
355
        }
356
        if ($this->EmailFrom && $this->EmailSubject && $this->Content) {
357
            return true;
358
        }
359
        return false;
360
    }
361
362
    /**
363
     * @return boolean
364
     */
365
    public function validate()
366
    {
367
        $valid = parent::validate();
368
        if ($this->exists()) {
369
            if (! $this->hasValidDataObject()) {
370
                $valid->error('Please enter valid Tabe/Class name ("' . htmlspecialchars($this->DataObject) .'" does not exist)');
371
            } elseif (! $this->hasValidDataObjectFields()) {
372
                $valid->error('Please select valid fields for both Email & Date');
373
            } elseif (! $this->hasValidFields()) {
374
                $valid->error('Please fill in all fields.  Make sure not to forget the email details (from who, subject, content)');
375
            }
376
        }
377
        return $valid;
378
    }
379
380
    public function onBeforeWrite()
381
    {
382
        parent::onBeforeWrite();
383
        if ($this->RepeatDays < ($this->Days * 3)) {
384
            $this->RepeatDays = ($this->Days * 3);
385
        }
386
    }
387
388
    public function onAfterWrite()
389
    {
390
        parent::onAfterWrite();
391
        if ($this->SendTestTo) {
392
            if ($mailOutObject = $this->getMailOutObject()) {
393
                $mailOutObject->setTestOnly(true);
394
                $mailOutObject->setVerbose(true);
395
                $mailOutObject->run(null);
396
            }
397
        }
398
    }
399
400
    /**
401
     *
402
     * @return null | EmailReminder_ReplacerClassInterface
403
     */
404
    public function getReplacerObject()
405
    {
406
        if ($mailOutObject = $this->getMailOutObject()) {
407
            return $mailOutObject->getReplacerObject();
408
        }
409
    }
410
411
    /**
412
     *
413
     * @return null | ScheduledTask
414
     */
415
    public function getMailOutObject()
416
    {
417
        $mailOutClass = $this->Config()->get('mail_out_class');
418
        if (class_exists($mailOutClass)) {
419
            $obj = Injector::inst()->get($mailOutClass);
420
            if ($obj instanceof BuildTask) {
421
                return $obj;
422
            } else {
423
                user_error($mailOutClass.' needs to be an instance of a Scheduled Task');
424
            }
425
        }
426
    }
427
428
    /**
429
     * @param int $limit
430
     * @return array
431
     */
432
    public function SampleFieldDataForRecords($limit = 200)
433
    {
434
        if ($this->hasValidFields()) {
435
            $className = $this->DataObject;
436
            $objects = $className::get()->sort('RAND()')
437
                ->where('"'.$this->DateField.'" IS NOT NULL AND "'.$this->DateField.'" <> \'\' AND "'.$this->DateField.'" <> 0')
438
                ->limit($limit);
439
            if ($objects->count()) {
440
                return array_unique($objects->column($this->DateField));
441
            } else {
442
                return array();
443
            }
444
        }
445
    }
446
447
448
    /**
449
     * @return DataList | null
450
     */
451
    public function CurrentRecords()
452
    {
453
        if ($this->hasValidFields()) {
454
            $className = $this->DataObject;
455
456
            // Use StartsWith to match Date and DateTime fields
457
            $records = $className::get()->where($this->whereStatementForDays());
458
            //sample record
459
            $firstRecord = $records->first();
460
            if($firstRecord && $firstRecord->exists())
461
                //methods
462
                $includeMethod = $this->Config()->get('include_method');
463
                $excludeMethod = $this->Config()->get('exclude_method');
464
465
                //included method?
466
                $hasIncludeMethod = false;
467
                if($record->hasMethod($includeMethod)) {
468
                    $includedRecords = [0 => 0];
469
                    $hasIncludeMethod = true;
470
                }
471
472
                //excluded method?
473
                $hasExcludeMethod = false;
474
                if($record->hasMethod($excludeMethod)) {
475
                    $excludedRecords = [0 => 0];
476
                    $hasExcludeMethod = true;
477
                }
478
479
                //see who is in and out
480
                if($hasIncludeMethod || $hasExcludeMethod) {
481
                    foreach($records as $record) {
482
                        if($hasIncludeMethod) {
483
                            $in = $record->$includeMethod($this, $records);
484
                            if($in === true) {
485
                                $includedRecords[$record->ID] = $record->ID;
486
                            }
487
                        }
488
                        if($hasExcludeMethod) {
489
                            $out = $record->$excludeMethod($this, $records);
490
                            if($out === true) {
491
                                $excludedRecords[$record->ID] = $record->ID;
492
                            }
493
                        }
494
                    }
495
                }
496
497
                //apply inclusions and exclusions
498
                if($hasIncludeMethod) {
499
                    $records = $className::get()->filter(['ID' => $includedRecords]);
500
                }
501
                if($hasExcludeMethod) {
502
                    $records = $records->exclude(['ID' => $excludedRecords]);
503
                }
504
            }
505
            return $records;
506
        }
507
    }
508
509
    /**
510
     * BeforeAfter = 'after'
511
     * Days = 3
512
     * GraceDays = 2
513
     *  -> minDays = -5 days start of day
514
     *  -> maxDays = -3 days end of day
515
     *
516
     * @return string
517
     */
518
    protected function whereStatementForDays()
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_PROTECTED
Loading history...
519
    {
520
        if ($this->hasValidFields()) {
521
            $sign = $this->BeforeAfter == 'before' ? '+' : '-';
522
            $graceDays = 10; //Config::inst()->get('EmailReminder_NotificationSchedule', 'grace_days');
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
523
524
            if ($sign == '+') {
525
                $minDays = $sign . ($this->Days - $graceDays) . ' days';
526
                $maxDays = $sign . $this->Days . ' days';
527
                $minDate = date('Y-m-d', strtotime($minDays)).' 00:00:00';
528
                $maxDate = date('Y-m-d', strtotime($maxDays)).' 23:59:59';
529
            } else {
530
                $minDays = $sign . ($this->Days) . ' days';
531
                $maxDays = $sign . $this->Days - $graceDays . ' days';
532
                //we purposely change these days around here ...
533
                $minDate = date('Y-m-d', strtotime($maxDays)).' 00:00:00';
534
                $maxDate = date('Y-m-d', strtotime($minDays)).' 23:59:59';
535
            }
536
537
            return '("'. $this->DateField.'" BETWEEN \''.$minDate.'\' AND \''.$maxDate.'\')';
538
        }
539
        return '1 == 2';
540
    }
541
}
542