Contact   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 663
Duplicated Lines 0 %

Importance

Changes 11
Bugs 0 Features 1
Metric Value
wmc 66
eloc 319
c 11
b 0
f 1
dl 0
loc 663
rs 3.12

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefaultSearchContext() 0 6 1
A getFullName() 0 17 3
A getTitle() 0 21 4
A onBeforeDelete() 0 12 3
A getDefaultAddress() 0 8 2
A getListsList() 0 10 1
A getFlagged() 0 13 3
A canDelete() 0 17 5
A getTagsList() 0 12 1
A canView() 0 17 5
A canCreate() 0 17 5
A canEdit() 0 17 5
A getCMSFields() 0 65 2
A getCMSValidator() 0 12 1
A providePermissions() 0 57 1
B getExportFields() 0 39 8
A relField() 0 10 3
A onAfterWrite() 0 6 4
A getModelAdminSearchContext() 0 6 1
A DefaultLocation() 0 15 2
A getName() 0 3 1
A getFlaggedNice() 0 8 2
A getByMostLocations() 0 21 3

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
namespace SilverCommerce\ContactAdmin\Model;
4
5
use SilverStripe\ORM\DB;
6
use SilverStripe\ORM\DataQuery;
7
use SilverStripe\ORM\DataObject;
8
use SilverStripe\Security\Member;
9
use SilverStripe\Security\Security;
10
use SilverStripe\TagField\TagField;
11
use SilverStripe\Forms\DropdownField;
12
use SilverStripe\Security\Permission;
13
use SilverStripe\Versioned\Versioned;
14
use SilverStripe\Forms\RequiredFields;
15
use SilverStripe\ORM\Queries\SQLSelect;
16
use SilverStripe\Forms\GridField\GridField;
17
use SilverStripe\Security\PermissionProvider;
18
use SilverCommerce\ContactAdmin\Model\ContactTag;
19
use SilverCommerce\ContactAdmin\Model\ContactLocation;
20
use SilverStripe\ORM\FieldType\DBHTMLText as HTMLText;
21
use SilverCommerce\CatalogueAdmin\Search\ContactSearchContext;
22
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
23
use SilverCommerce\VersionHistoryField\Forms\VersionHistoryField;
24
use NathanCox\HasOneAutocompleteField\Forms\HasOneAutocompleteField;
25
use SilverCommerce\ContactAdmin\Helpers\ContactHelper;
26
use SilverStripe\Core\Config\Config;
27
28
/**
29
 * Details on a particular contact
30
 *
31
 * @property string FirstName
32
 * @property string Surname
33
 * @property string Company
34
 * @property string Phone
35
 * @property string Mobile
36
 * @property string Email
37
 * @property string Source
38
 * @property bool   Flagged
39
 *
40
 * @method \SilverStripe\ORM\HasManyList  Locations
41
 * @method \SilverStripe\ORM\HasManyList  Notes
42
 * @method \SilverStripe\ORM\ManyManyList Tags
43
 * @method \SilverStripe\ORM\ManyManyList Lists
44
 *
45
 * @author  ilateral
46
 * @package Contacts
47
 */
48
class Contact extends DataObject implements PermissionProvider
49
{
50
    const LOCATION_BY_POS = 'LocationByPos';
51
52
    const PERMISSION_MANAGE = 'CONTACTS_MANAGE';
53
54
    const PERMISSION_CREATE = 'CONTACTS_CREATE';
55
56
    const PERMISSION_VIEW = 'CONTACTS_VIEW';
57
58
    const PERMISSION_EDIT = 'CONTACTS_EDIT';
59
60
    const PERMISSION_DELETE = 'CONTACTS_DELETE';
61
62
    private static $table_name = 'Contact';
63
64
    /**
65
     * String used to seperate tags, lists, etc
66
     * when rendering a summary.
67
     *
68
     * @var string
69
     */
70
    private static $list_seperator = ", ";
71
72
    private static $db = [
73
        "FirstName" => "Varchar(255)",
74
        "Surname" => "Varchar(255)",
75
        "Company" => "Varchar(255)",
76
        "Phone" => "Varchar(15)",
77
        "Mobile" => "Varchar(15)",
78
        "Email" => "Varchar(255)",
79
        "Source" => "Text"
80
    ];
81
82
    private static $has_one = [
83
        "Member" => Member::class
84
    ];
85
86
    private static $has_many = [
87
        "Locations" => ContactLocation::class,
88
        "Notes" => ContactNote::class
89
    ];
90
    
91
    private static $many_many = [
92
        'Tags' => ContactTag::class
93
    ];
94
95
    private static $belongs_many_many = [
96
        'Lists' => ContactList::class
97
    ];
98
    
99
    private static $casting = [
100
        'TagsList' => 'Varchar',
101
        'ListsList' => 'Varchar',
102
        'FlaggedNice' => 'Boolean',
103
        'FullName' => 'Varchar',
104
        'Name' => 'Varchar',
105
        "DefaultAddress" => "Text"
106
    ];
107
108
    private static $field_labels = [
109
        "FlaggedNice" =>"Flagged",
110
        "DefaultAddress" => "Default Address",
111
        "TagsList" => "Tags",
112
        "ListsList" => "Lists",
113
        "Locations.Address1" => 'Address 1',
114
        "Locations.Address2" => 'Address 2',
115
        "Locations.City" => 'City',
116
        "Locations.Country" => 'Country',
117
        "Locations.PostCode" => 'Post Code',
118
        "Tags.Title" => 'Tag',
119
        "Lists.Title" => 'List'
120
    ];
121
    
122
    private static $summary_fields = [
123
        "FlaggedNice",
124
        "FirstName",
125
        "Surname",
126
        "Email",
127
        "DefaultAddress",
128
        "TagsList",
129
        "ListsList"
130
    ];
131
132
    private static $searchable_fields = [
133
        "FirstName",
134
        "Surname",
135
        "Email",
136
        "Locations.Address1",
137
        "Locations.Address2",
138
        "Locations.City",
139
        "Locations.Country",
140
        "Locations.PostCode",
141
        "Tags.Title",
142
        "Lists.Title"
143
    ];
144
145
    private static $export_fields = [
146
        "ID",
147
        "FirstName",
148
        "Surname",
149
        "Company",
150
        "Phone",
151
        "Mobile",
152
        "Email",
153
        "Source",
154
        "TagsList",
155
        "ListsList",
156
        "MemberID",
157
        "CreateMember"
158
    ];
159
160
    private static $default_sort = [
161
        "FirstName" => "ASC",
162
        "Surname" => "ASC"
163
    ];
164
165
    /**
166
     * Add extension classes
167
     *
168
     * @var    array
169
     * @config
170
     */
171
    private static $extensions = [
172
        Versioned::class . '.versioned',
173
    ];
174
175
    /**
176
     * Declare version history
177
     *
178
     * @var    array
179
     * @config
180
     */
181
    private static $versioning = [
182
        "History"
183
    ];
184
    
185
    public function getTitle()
186
    {
187
        $parts = [];
188
189
        if (!empty($this->FirstName)) {
190
            $parts[] = $this->FirstName;
191
        }
192
193
        if (!empty($this->Surname)) {
194
            $parts[] = $this->Surname;
195
        }
196
197
        if (!empty($this->Email)) {
198
            $parts[] = "($this->Email)";
199
        }
200
201
        $title = implode(" ", $parts);
202
203
        $this->extend("updateTitle", $title);
204
        
205
        return $title;
206
    }
207
208
    public function getFullName()
209
    {
210
        $parts = [];
211
212
        if (!empty($this->FirstName)) {
213
            $parts[] = $this->FirstName;
214
        }
215
216
        if (!empty($this->Surname)) {
217
            $parts[] = $this->Surname;
218
        }
219
220
        $name = implode(" ", $parts);
221
        
222
        $this->extend("updateFullName", $name);
223
224
        return $name;
225
    }
226
    
227
    public function getFlaggedNice()
228
    {
229
        $obj = HTMLText::create();
230
        $obj->setValue(($this->Flagged)? '<span class="red">&#10033;</span>' : '');
231
232
        $this->extend("updateFlaggedNice", $obj);
233
       
234
        return $obj;
235
    }
236
237
    /**
238
     * Get a contact with the most locations assigned
239
     *
240
     * @return self|null
241
     */
242
    public static function getByMostLocations()
243
    {
244
        $id = null;
245
        $query = new SQLSelect();
246
        $query
247
            ->setFrom('"Contact"')
248
            ->setSelect('"Contact"."ID", count("ContactLocation"."ID") as LocationsCount')
249
            ->addLeftJoin('ContactLocation', '"Contact"."ID" = "ContactLocation"."ContactID"')
250
            ->addGroupBy('"Contact"."ID"')
251
            ->addOrderBy('LocationsCount', 'DESC')
252
            ->setLimit(1);
253
254
        foreach ($query->execute() as $row) {
255
            $id = $row['ID'];
256
        }
257
258
        if (!empty($id)) {
259
            return Contact::get()->byID($id);
260
        }
261
262
        return;
263
    }
264
265
    /**
266
     * Find from our locations one marked as default (of if not the
267
     * first in the list).
268
     *
269
     * If not location available, return a blank one
270
     *
271
     * @return ContactLocation
272
     */
273
    public function DefaultLocation()
274
    {
275
        $location = $this
276
            ->Locations()
277
            ->sort("Default", "DESC")
278
            ->first();
279
        
280
        if (empty($location)) {
281
            $location = ContactLocation::create();
282
            $location->ID = -1;
283
        }
284
        
285
        $this->extend("updateDefaultLocation", $location);
286
287
        return $location;
288
    }
289
290
    /**
291
     * Find from our locations one marked as default (of if not the
292
     * first in the list).
293
     *
294
     * @return string
295
     */
296
    public function getDefaultAddress()
297
    {
298
        $location = $this->DefaultLocation();
299
300
        if ($location) {
0 ignored issues
show
introduced by
$location is of type SilverCommerce\ContactAdmin\Model\ContactLocation, thus it always evaluated to true.
Loading history...
301
            return $location->Address;
302
        } else {
303
            return "";
304
        }
305
    }
306
307
    /**
308
     * Get the complete name of the member
309
     *
310
     * @return string Returns the first- and surname of the member.
311
     */
312
    public function getName()
313
    {
314
        return $this->getFullName();
315
    }
316
317
    /**
318
     * Generate as string of tag titles seperated by a comma
319
     *
320
     * @return string
321
     */
322
    public function getTagsList()
323
    {
324
        $tags = $this
325
            ->Tags()
326
            ->sort('Title', 'ASC')
327
            ->column("Title");
328
329
        $this->extend("updateTagsList", $tags);
330
331
        return implode(
332
            $this->config()->list_seperator,
333
            $tags
334
        );
335
    }
336
337
    /**
338
     * Generate as string of list titles seperated by a comma
339
     *
340
     * @return string
341
     */
342
    public function getListsList()
343
    {
344
        $return = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $return is dead and can be removed.
Loading history...
345
        $list = $this->Lists()->column("Title");
346
347
        $this->extend("updateListsList", $tags);
348
349
        return implode(
350
            $this->config()->list_seperator,
351
            $list
352
        );
353
    }
354
    
355
    public function getFlagged()
356
    {
357
        $flagged = false;
358
        
359
        foreach ($this->Notes() as $note) {
360
            if ($note->Flag) {
361
                $flagged = true;
362
            }
363
        }
364
365
        $this->extend("updateFlagged", $flagged);
366
367
        return $flagged;
368
    }
369
370
    /**
371
     * Load custom search context to allow for filtering by flagged notes
372
     *
373
     * @return ContactSearchContext
374
     */
375
    public function getDefaultSearchContext()
376
    {
377
        return ContactSearchContext::create(
378
            static::class,
379
            $this->scaffoldSearchFields(),
380
            $this->defaultSearchFilters()
381
        );
382
    }
383
384
    /**
385
     * Load custom search context for model admin plus
386
     *
387
     * @return ContactSearchContext
388
     */
389
    public function getModelAdminSearchContext()
390
    {
391
        return ContactSearchContext::create(
392
            static::class,
393
            $this->scaffoldSearchFields(),
394
            $this->defaultSearchFilters()
395
        );
396
    }
397
398
    public function getCMSFields()
399
    {
400
        $self = $this;
401
        $this->beforeUpdateCMSFields(
402
            function ($fields) use ($self) {
403
                $fields->removeByName("Tags");
404
                $fields->removeByName("Notes");
405
            
406
                $tag_field = TagField::create(
407
                    'Tags',
408
                    null,
409
                    ContactTag::get(),
410
                    $self->Tags()
411
                )->setRightTitle(
412
                    _t(
413
                        "Contacts.TagDescription",
414
                        "List of tags related to this contact, seperated by a comma."
415
                    )
416
                )->setShouldLazyLoad(true);
417
            
418
                if ($self->exists()) {
419
                    $gridField = GridField::create(
420
                        'Notes',
421
                        'Notes',
422
                        $self->Notes()
423
                    );
424
                
425
                    $config = GridFieldConfig_RelationEditor::create();
426
427
                    $gridField->setConfig($config);
428
429
                    $fields->addFieldToTab(
430
                        "Root.Notes",
431
                        $gridField
432
                    );
433
434
                    $fields->addFieldToTab(
435
                        "Root.History",
436
                        VersionHistoryField::create(
437
                            "History",
438
                            _t("SilverCommerce\VersionHistoryField.History", "History"),
439
                            $self
440
                        )->addExtraClass("stacked")
441
                    );
442
                }
443
            
444
                $fields->addFieldsToTab(
445
                    "Root.Main",
446
                    [
447
                    $member_field = HasOneAutocompleteField::create(
0 ignored issues
show
Unused Code introduced by
The assignment to $member_field is dead and can be removed.
Loading history...
448
                        'MemberID',
449
                        _t(
450
                            'SilverCommerce\ContactAdmin.LinkContactToAccount',
451
                            'Link this contact to a user account?'
452
                        ),
453
                        Member::class,
454
                        'Title'
455
                    ),
456
                    $tag_field
457
                    ]
458
                );
459
            }
460
        );
461
462
        return parent::getCMSFields();
463
    }
464
    
465
    public function getCMSValidator()
466
    {
467
        $validator = new RequiredFields(
468
            [
469
            "FirstName",
470
            "Surname"
471
            ]
472
        );
473
474
        $this->extend('updateCMSValidator', $validator);
475
476
        return $validator;
477
    }
478
479
    /**
480
     * Get the default export fields for this object
481
     *
482
     * @return array
483
     */
484
    public function getExportFields()
485
    {
486
        $raw_fields = $this->config()->get('export_fields');
487
        $loc_fields = ContactLocation::singleton()->getExportFields();
488
489
        // Merge associative / numeric keys
490
        $fields = [];
491
        foreach ($raw_fields as $key => $value) {
492
            if (is_int($key)) {
493
                $key = $value;
494
            }
495
            $fields[$key] = $value;
496
        }
497
498
        // Work out address fields for export
499
        $most_locations = self::getByMostLocations();
500
        $location_count = 0;
501
502
        if (!empty($most_locations)) {
503
            $location_count = $most_locations->Locations()->count();
504
        }
505
506
        for ($i = 0; $i < $location_count; $i++) {
507
            foreach ($loc_fields as $key => $value) {
508
                if (is_int($key)) {
509
                    $key = $value;
510
                }
511
                $fields[self::LOCATION_BY_POS . $i . '.' . $key] = 'Address' . $i . '_' . $value;
512
            }
513
        }
514
515
        $this->extend("updateExportFields", $fields);
516
517
        // Final fail-over, just list ID field
518
        if (!$fields) {
519
            $fields['ID'] = 'ID';
520
        }
521
522
        return $fields;
523
    }
524
525
    /**
526
     * Check field
527
     *
528
     * @param string $fieldName string
529
     *
530
     * @return mixed Will return null on a missing value
531
     */
532
    public function relField($fieldName)
533
    {
534
        /** @todo This is a bit in-effitient, woud be nice to do this with slightly less queries */
535
        if (strpos($fieldName, self::LOCATION_BY_POS) !== false) {
536
            $pos = (int) substr($fieldName, strlen(self::LOCATION_BY_POS), 1);
537
            $loc_field = substr($fieldName, strpos($fieldName, '.') + 1);
538
            $location = $this->Locations()->limit(1, $pos)->first();
539
            return empty($location) ? "" : $location->$loc_field;
540
        } else {
541
            return parent::relField($fieldName);
542
        }
543
    }
544
545
    public function providePermissions()
546
    {
547
        return [
548
            self::PERMISSION_MANAGE => [
549
                'name' => _t(
550
                    'Contacts.PERMISSION_MANAGE_CONTACTS_DESCRIPTION',
551
                    'Manage contacts'
552
                ),
553
                'help' => _t(
554
                    'Contacts.PERMISSION_MANAGE_CONTACTS_HELP',
555
                    'Allow creation and editing of contacts'
556
                ),
557
                'category' => _t('Contacts.Contacts', 'Contacts')
558
            ],
559
            self::PERMISSION_CREATE => [
560
                'name' => _t(
561
                    'Contacts.PERMISSION_CREATE_CONTACTS_DESCRIPTION',
562
                    'Create contacts'
563
                ),
564
                'help' => _t(
565
                    'Contacts.PERMISSION_CREATE_CONTACTS_HELP',
566
                    'Allow creation of contacts'
567
                ),
568
                'category' => _t('Contacts.Contacts', 'Contacts')
569
            ],
570
            self::PERMISSION_VIEW => [
571
                'name' => _t(
572
                    'Contacts.PERMISSION_VIEW_CONTACTS_DESCRIPTION',
573
                    'View contacts'
574
                ),
575
                'help' => _t(
576
                    'Contacts.PERMISSION_CREATE_CONTACTS_HELP',
577
                    'Allow viewing of contacts'
578
                ),
579
                'category' => _t('Contacts.Contacts', 'Contacts')
580
            ],
581
            self::PERMISSION_EDIT => [
582
                'name' => _t(
583
                    'Contacts.PERMISSION_EDIT_CONTACTS_DESCRIPTION',
584
                    'Edit contacts'
585
                ),
586
                'help' => _t(
587
                    'Contacts.PERMISSION_EDIT_CONTACTS_HELP',
588
                    'Allow editing of contacts'
589
                ),
590
                'category' => _t('Contacts.Contacts', 'Contacts')
591
            ],
592
            self::PERMISSION_DELETE => [
593
                'name' => _t(
594
                    'Contacts.PERMISSION_DELETE_CONTACTS_DESCRIPTION',
595
                    'Delete contacts'
596
                ),
597
                'help' => _t(
598
                    'Contacts.PERMISSION_DELETE_CONTACTS_HELP',
599
                    'Allow deleting of contacts'
600
                ),
601
                'category' => _t('Contacts.Contacts', 'Contacts')
602
            ]
603
        ];
604
    }
605
    
606
    public function canView($member = null)
607
    {
608
        $extended = $this->extendedCan(__FUNCTION__, $member);
609
610
        if ($extended !== null) {
611
            return $extended;
612
        }
613
614
        if (!$member) {
615
            $member = Security::getCurrentUser();
616
        }
617
618
        if ($member && Permission::checkMember($member->ID, [self::PERMISSION_MANAGE, self::PERMISSION_VIEW])) {
619
            return true;
620
        }
621
622
        return false;
623
    }
624
625
    public function canCreate($member = null, $context = [])
626
    {
627
        $extended = $this->extendedCan(__FUNCTION__, $member, $context);
628
629
        if ($extended !== null) {
630
            return $extended;
631
        }
632
633
        if (!$member) {
634
            $member = Security::getCurrentUser();
635
        }
636
637
        if ($member && Permission::checkMember($member->ID, [self::PERMISSION_MANAGE, self::PERMISSION_CREATE])) {
638
            return true;
639
        }
640
641
        return false;
642
    }
643
644
    public function canEdit($member = null)
645
    {
646
        $extended = $this->extendedCan(__FUNCTION__, $member);
647
648
        if ($extended !== null) {
649
            return $extended;
650
        }
651
652
        if (!$member) {
653
            $member = Security::getCurrentUser();
654
        }
655
656
        if ($member && Permission::checkMember($member->ID, [self::PERMISSION_MANAGE, self::PERMISSION_EDIT])) {
657
            return true;
658
        }
659
660
        return false;
661
    }
662
663
    public function canDelete($member = null, $context = [])
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

663
    public function canDelete($member = null, /** @scrutinizer ignore-unused */ $context = [])

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

Loading history...
664
    {
665
        $extended = $this->extendedCan(__FUNCTION__, $member);
666
667
        if ($extended !== null) {
668
            return $extended;
669
        }
670
671
        if (!$member) {
672
            $member = Security::getCurrentUser();
673
        }
674
675
        if ($member && Permission::checkMember($member->ID, self::PERMISSION_DELETE)) {
676
            return true;
677
        }
678
679
        return false;
680
    }
681
682
    /**
683
     * Sync to associated member (if needed)
684
     *
685
     * @return void
686
     */
687
    public function onAfterWrite()
688
    {
689
        parent::onAfterWrite();
690
691
        if (ContactHelper::config()->get('auto_sync') && $this->isChanged() && $this->Member()->exists()) {
0 ignored issues
show
Bug introduced by
The method Member() does not exist on SilverCommerce\ContactAdmin\Model\Contact. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

691
        if (ContactHelper::config()->get('auto_sync') && $this->isChanged() && $this->/** @scrutinizer ignore-call */ Member()->exists()) {
Loading history...
692
            ContactHelper::pushChangedFields($this, $this->Member());
693
        }
694
    }
695
696
    /**
697
     * Cleanup DB on removal
698
     */
699
    public function onBeforeDelete()
700
    {
701
        parent::onBeforeDelete();
702
        
703
        // Delete all locations attached to this order
704
        foreach ($this->Locations() as $item) {
705
            $item->delete();
706
        }
707
708
        // Delete all notes attached to this order
709
        foreach ($this->Notes() as $item) {
710
            $item->delete();
711
        }
712
    }
713
}
714