Passed
Push — 1.0 ( 748b47...49e249 )
by Morven
08:23
created

Contact::LocationByPos()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 0
c 1
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 1
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 SilverStripe\Core\Config\Config;
26
27
/**
28
 * Details on a particular contact
29
 *
30
 * @property string FirstName
31
 * @property string Surname
32
 * @property string Company
33
 * @property string Phone
34
 * @property string Mobile
35
 * @property string Email
36
 * @property string Source
37
 * @property bool   Flagged
38
 *
39
 * @method \SilverStripe\ORM\HasManyList  Locations
40
 * @method \SilverStripe\ORM\HasManyList  Notes
41
 * @method \SilverStripe\ORM\ManyManyList Tags
42
 * @method \SilverStripe\ORM\ManyManyList Lists
43
 *
44
 * @author  ilateral
45
 * @package Contacts
46
 */
47
class Contact extends DataObject implements PermissionProvider
48
{
49
    const LOCATION_BY_POS = 'LocationByPos';
50
51
    const PERMISSION_MANAGE = 'CONTACTS_MANAGE';
52
53
    const PERMISSION_CREATE = 'CONTACTS_CREATE';
54
55
    const PERMISSION_VIEW = 'CONTACTS_VIEW';
56
57
    const PERMISSION_EDIT = 'CONTACTS_EDIT';
58
59
    const PERMISSION_DELETE = 'CONTACTS_DELETE';
60
61
    private static $table_name = 'Contact';
62
63
    /**
64
     * String used to seperate tags, lists, etc
65
     * when rendering a summary.
66
     *
67
     * @var string
68
     */
69
    private static $list_seperator = ", ";
70
71
    private static $db = [
72
        "FirstName" => "Varchar(255)",
73
        "Surname" => "Varchar(255)",
74
        "Company" => "Varchar(255)",
75
        "Phone" => "Varchar(15)",
76
        "Mobile" => "Varchar(15)",
77
        "Email" => "Varchar(255)",
78
        "Source" => "Text"
79
    ];
80
81
    private static $has_one = [
82
        "Member" => Member::class
83
    ];
84
85
    private static $has_many = [
86
        "Locations" => ContactLocation::class,
87
        "Notes" => ContactNote::class
88
    ];
89
    
90
    private static $many_many = [
91
        'Tags' => ContactTag::class
92
    ];
93
94
    private static $belongs_many_many = [
95
        'Lists' => ContactList::class
96
    ];
97
    
98
    private static $casting = [
99
        'TagsList' => 'Varchar',
100
        'ListsList' => 'Varchar',
101
        'FlaggedNice' => 'Boolean',
102
        'FullName' => 'Varchar',
103
        'Name' => 'Varchar',
104
        "DefaultAddress" => "Text"
105
    ];
106
107
    private static $field_labels = [
108
        "FlaggedNice" =>"Flagged",
109
        "DefaultAddress" => "Default Address",
110
        "TagsList" => "Tags",
111
        "ListsList" => "Lists",
112
        "Locations.Address1" => 'Address 1',
113
        "Locations.Address2" => 'Address 2',
114
        "Locations.City" => 'City',
115
        "Locations.Country" => 'Country',
116
        "Locations.PostCode" => 'Post Code',
117
        "Tags.Title" => 'Tag',
118
        "Lists.Title" => 'List'
119
    ];
120
    
121
    private static $summary_fields = [
122
        "FlaggedNice",
123
        "FirstName",
124
        "Surname",
125
        "Email",
126
        "DefaultAddress",
127
        "TagsList",
128
        "ListsList"
129
    ];
130
131
    private static $searchable_fields = [
132
        "FirstName",
133
        "Surname",
134
        "Email",
135
        "Locations.Address1",
136
        "Locations.Address2",
137
        "Locations.City",
138
        "Locations.Country",
139
        "Locations.PostCode",
140
        "Tags.Title",
141
        "Lists.Title"
142
    ];
143
144
    private static $export_fields = [
145
        "FirstName",
146
        "Surname",
147
        "Company",
148
        "Phone",
149
        "Mobile",
150
        "Email",
151
        "Source",
152
        "TagsList",
153
        "ListsList"
154
    ];
155
156
    private static $default_sort = [
157
        "FirstName" => "ASC",
158
        "Surname" => "ASC"
159
    ];
160
161
    /**
162
     * Add extension classes
163
     *
164
     * @var    array
165
     * @config
166
     */
167
    private static $extensions = [
168
        Versioned::class . '.versioned',
169
    ];
170
171
    /**
172
     * Declare version history
173
     *
174
     * @var    array
175
     * @config
176
     */
177
    private static $versioning = [
178
        "History"
179
    ];
180
181
    /**
182
     * Fields that can be synced with associated member
183
     * 
184
     * @var array
185
     */
186
    private static $sync_fields = [
187
        "FirstName",
188
        "Surname",
189
        "Company",
190
        "Phone",
191
        "Mobile",
192
        "Email"
193
    ];
194
    
195
    public function getTitle()
196
    {
197
        $parts = [];
198
199
        if (!empty($this->FirstName)) {
200
            $parts[] = $this->FirstName;
201
        }
202
203
        if (!empty($this->Surname)) {
204
            $parts[] = $this->Surname;
205
        }
206
207
        if (!empty($this->Email)) {
208
            $parts[] = "($this->Email)";
209
        }
210
211
        $title = implode(" ", $parts);
212
213
        $this->extend("updateTitle", $title);
214
        
215
        return $title;
216
    }
217
218
    public function getFullName() 
219
    {
220
        $parts = [];
221
222
        if (!empty($this->FirstName)) {
223
            $parts[] = $this->FirstName;
224
        }
225
226
        if (!empty($this->Surname)) {
227
            $parts[] = $this->Surname;
228
        }
229
230
        $name = implode(" ", $parts);
231
        
232
        $this->extend("updateFullName", $name);
233
234
        return $name;
235
    }
236
    
237
    public function getFlaggedNice()
238
    {
239
        $obj = HTMLText::create();
240
        $obj->setValue(($this->Flagged)? '<span class="red">&#10033;</span>' : '');
241
242
        $this->extend("updateFlaggedNice", $obj);
243
       
244
        return $obj;
245
    }
246
247
    /**
248
     * Get a contact with the most locations assigned
249
     *
250
     * @return self|null
251
     */
252
    public static function getByMostLocations()
253
    {
254
        $id = null;
255
        $query = new SQLSelect();
256
        $query
257
            ->setFrom('Contact')
258
            ->setSelect('Contact.ID, count(ContactLocation.ID) as LocationsCount')
259
            ->addLeftJoin('ContactLocation', 'Contact.ID = ContactLocation.ContactID')
260
            ->addGroupBy('Contact.ID')
261
            ->addOrderBy('LocationsCount', 'DESC')
262
            ->setLimit(1);
263
264
        foreach($query->execute() as $row) {
265
            $id = $row['ID'];
266
        }
267
268
        if (!empty($id)) {
269
            return Contact::get()->byID($id);
270
        }
271
272
        return;
273
    }
274
275
    /**
276
     * Find from our locations one marked as default (of if not the
277
     * first in the list).
278
     *
279
     * If not location available, return a blank one
280
     *
281
     * @return ContactLocation
282
     */
283
    public function DefaultLocation()
284
    {
285
        $location = $this
286
            ->Locations()
287
            ->sort("Default", "DESC")
288
            ->first();
289
        
290
        if (empty($location)) {
291
            $location = ContactLocation::create();
292
            $location->ID = -1;
293
        }
294
        
295
        $this->extend("updateDefaultLocation", $location);
296
297
        return $location;
298
    }
299
300
    /**
301
     * Find from our locations one marked as default (of if not the
302
     * first in the list).
303
     *
304
     * @return string
305
     */
306
    public function getDefaultAddress()
307
    {
308
        $location = $this->DefaultLocation();
309
310
        if ($location) {
0 ignored issues
show
introduced by
$location is of type SilverCommerce\ContactAdmin\Model\ContactLocation, thus it always evaluated to true.
Loading history...
311
            return $location->Address;
312
        } else {
313
            return "";
314
        }
315
    }
316
317
    /**
318
     * Get the complete name of the member
319
     *
320
     * @return string Returns the first- and surname of the member.
321
     */
322
    public function getName() 
323
    {
324
        return $this->getFullName();
325
    }
326
327
    /**
328
     * Generate as string of tag titles seperated by a comma
329
     *
330
     * @return string
331
     */
332
    public function getTagsList()
333
    {
334
        $tags = $this->Tags()->column("Title");
335
336
        $this->extend("updateTagsList", $tags);
337
338
        return implode(
339
            $this->config()->list_seperator,
340
            $tags
341
        );
342
    }
343
344
    /**
345
     * Generate as string of list titles seperated by a comma
346
     *
347
     * @return string
348
     */
349
    public function getListsList()
350
    {
351
        $return = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $return is dead and can be removed.
Loading history...
352
        $list = $this->Lists()->column("Title");
353
354
        $this->extend("updateListsList", $tags);
355
356
        return implode(
357
            $this->config()->list_seperator,
358
            $list
359
        );
360
    }
361
    
362
    public function getFlagged()
363
    {
364
        $flagged = false;
365
        
366
        foreach ($this->Notes() as $note) {
367
            if ($note->Flag) {
368
                $flagged = true;
369
            }
370
        }
371
372
        $this->extend("updateFlagged", $flagged);
373
374
        return $flagged;
375
    }
376
    
377
    /**
378
     * Update an associated member with the data from this contact
379
     * 
380
     * @return void
381
     */
382
    public function syncToMember()
383
    {
384
        $member = $this->Member();
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

384
        /** @scrutinizer ignore-call */ 
385
        $member = $this->Member();
Loading history...
385
        $sync = $this->config()->sync_fields;
386
        $write = false;
387
388
        if (!$member->exists()) {
389
            return;
390
        }
391
392
        foreach ($this->getChangedFields() as $field => $change) {
393
            // If this field is a field to sync, and it is different
394
            // then update member
395
            if (in_array($field, $sync) && $member->$field != $this->$field) {
396
                $member->$field = $this->$field;
397
                $write = true;
398
            }
399
        }
400
401
        if ($write) {
402
            $member->write();
403
        }
404
    }
405
406
    /**
407
     * Load custom search context to allow for filtering by flagged notes
408
     * 
409
     * @return ContactSearchContext
410
     */
411
    public function getDefaultSearchContext()
412
    {
413
        return ContactSearchContext::create(
414
            static::class,
415
            $this->scaffoldSearchFields(),
416
            $this->defaultSearchFilters()
417
        );
418
    }
419
420
    /**
421
     * Load custom search context for model admin plus
422
     * 
423
     * @return ContactSearchContext
424
     */
425
    public function getModelAdminSearchContext()
426
    {
427
        return ContactSearchContext::create(
428
            static::class,
429
            $this->scaffoldSearchFields(),
430
            $this->defaultSearchFilters()
431
        );
432
    }
433
434
    public function getCMSFields()
435
    {
436
        $self = $this;
437
        $this->beforeUpdateCMSFields(
438
            function ($fields) use ($self) {
439
                $fields->removeByName("Tags");
440
                $fields->removeByName("Notes");
441
            
442
                $tag_field = TagField::create(
443
                    'Tags',
444
                    null,
445
                    ContactTag::get(),
446
                    $self->Tags()
447
                )->setRightTitle(
448
                    _t(
449
                        "Contacts.TagDescription",
450
                        "List of tags related to this contact, seperated by a comma."
451
                    )
452
                )->setShouldLazyLoad(true);
453
            
454
                if ($self->exists()) {
455
                    $gridField = GridField::create(
456
                        'Notes',
457
                        'Notes',
458
                        $self->Notes()
459
                    );
460
                
461
                    $config = GridFieldConfig_RelationEditor::create();
462
463
                    $gridField->setConfig($config);
464
465
                    $fields->addFieldToTab(
466
                        "Root.Notes",
467
                        $gridField
468
                    );
469
470
                    $fields->addFieldToTab(
471
                        "Root.History",
472
                        VersionHistoryField::create(
473
                            "History",
474
                            _t("SilverCommerce\VersionHistoryField.History", "History"),
475
                            $self
476
                        )->addExtraClass("stacked")
477
                    );
478
                }
479
            
480
                $fields->addFieldsToTab(
481
                    "Root.Main",
482
                    [
483
                    $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...
484
                        'MemberID',
485
                        _t(
486
                            'SilverCommerce\ContactAdmin.LinkContactToAccount',
487
                            'Link this contact to a user account?'
488
                        ),
489
                        Member::class,
490
                        'Title'
491
                    ),
492
                    $tag_field
493
                    ]
494
                );
495
            }
496
        );
497
498
        return parent::getCMSFields();
499
    }
500
    
501
    public function getCMSValidator()
502
    {
503
        $validator = new RequiredFields(
504
            array(
505
            "FirstName",
506
            "Surname"
507
            )
508
        );
509
510
        $this->extend('updateCMSValidator', $validator);
511
512
        return $validator;
513
    }
514
515
    public function LocationByPos($pos = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $pos 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

515
    public function LocationByPos(/** @scrutinizer ignore-unused */ $pos = 0)

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...
516
    {
517
    }
518
519
    /**
520
     * Get the default export fields for this object
521
     *
522
     * @return array
523
     */
524
    public function getExportFields()
525
    {
526
        $raw_fields = $this->config()->get('export_fields');
527
        $loc_fields = ContactLocation::singleton()->getExportFields();
528
529
        // Merge associative / numeric keys
530
        $fields = [];
531
        foreach ($raw_fields as $key => $value) {
532
            if (is_int($key)) {
533
                $key = $value;
534
            }
535
            $fields[$key] = $value;
536
        }
537
538
        // Work out address fields for export
539
        $most_locations = self::getByMostLocations();
540
        $location_count = 0;
541
542
        if (!empty($most_locations)) {
543
            $location_count = $most_locations->Locations()->count();
544
        }
545
546
        for ($i = 0; $i < $location_count; $i++) {
547
            foreach ($loc_fields as $key => $value) {
548
                if (is_int($key)) {
549
                    $key = $value;
550
                }
551
                $fields[self::LOCATION_BY_POS . $i . '.' . $key] = 'Address' . $i . '_' . $value;
552
            }
553
        }
554
555
        $this->extend("updateExportFields", $fields);
556
557
        // Final fail-over, just list ID field
558
        if (!$fields) {
559
            $fields['ID'] = 'ID';
560
        }
561
562
        return $fields;
563
    }
564
565
    /**
566
     * Check field 
567
     *
568
     * @param string $fieldName string
569
     *
570
     * @return mixed Will return null on a missing value
571
     */
572
    public function relField($fieldName)
573
    {
574
        /** @todo This is a bit in-effitient, woud be nice to do this with slightly less queries */
575
        if (strpos($fieldName, self::LOCATION_BY_POS) !== false) {
576
            $pos = (int) substr($fieldName, strlen(self::LOCATION_BY_POS), 1);
577
            $loc_field = substr($fieldName, strpos($fieldName, '.') + 1);
578
            $location = $this->Locations()->limit(1, $pos)->first();
579
            return empty($location) ? "" : $location->$loc_field;
580
        } else {
581
            return parent::relField($fieldName);
582
        }
583
    }
584
585
    public function providePermissions()
586
    {
587
        return array(
588
            self::PERMISSION_MANAGE => array(
589
                'name' => _t(
590
                    'Contacts.PERMISSION_MANAGE_CONTACTS_DESCRIPTION',
591
                    'Manage contacts'
592
                ),
593
                'help' => _t(
594
                    'Contacts.PERMISSION_MANAGE_CONTACTS_HELP',
595
                    'Allow creation and editing of contacts'
596
                ),
597
                'category' => _t('Contacts.Contacts', 'Contacts')
598
            ),
599
            self::PERMISSION_CREATE => array(
600
                'name' => _t(
601
                    'Contacts.PERMISSION_CREATE_CONTACTS_DESCRIPTION',
602
                    'Create contacts'
603
                ),
604
                'help' => _t(
605
                    'Contacts.PERMISSION_CREATE_CONTACTS_HELP',
606
                    'Allow creation of contacts'
607
                ),
608
                'category' => _t('Contacts.Contacts', 'Contacts')
609
            ),
610
            self::PERMISSION_VIEW => array(
611
                'name' => _t(
612
                    'Contacts.PERMISSION_VIEW_CONTACTS_DESCRIPTION',
613
                    'View contacts'
614
                ),
615
                'help' => _t(
616
                    'Contacts.PERMISSION_CREATE_CONTACTS_HELP',
617
                    'Allow viewing of contacts'
618
                ),
619
                'category' => _t('Contacts.Contacts', 'Contacts')
620
            ),
621
            self::PERMISSION_EDIT => array(
622
                'name' => _t(
623
                    'Contacts.PERMISSION_EDIT_CONTACTS_DESCRIPTION',
624
                    'Edit contacts'
625
                ),
626
                'help' => _t(
627
                    'Contacts.PERMISSION_EDIT_CONTACTS_HELP',
628
                    'Allow editing of contacts'
629
                ),
630
                'category' => _t('Contacts.Contacts', 'Contacts')
631
            ),
632
            self::PERMISSION_DELETE => array(
633
                'name' => _t(
634
                    'Contacts.PERMISSION_DELETE_CONTACTS_DESCRIPTION',
635
                    'Delete contacts'
636
                ),
637
                'help' => _t(
638
                    'Contacts.PERMISSION_DELETE_CONTACTS_HELP',
639
                    'Allow deleting of contacts'
640
                ),
641
                'category' => _t('Contacts.Contacts', 'Contacts')
642
            )
643
        );
644
    }
645
    
646
    public function canView($member = null)
647
    {
648
        $extended = $this->extendedCan(__FUNCTION__, $member);
649
650
        if ($extended !== null) {
651
            return $extended;
652
        }
653
654
        if (!$member) {
655
            $member = Security::getCurrentUser();
656
        }
657
658
        if ($member && Permission::checkMember($member->ID, [self::PERMISSION_MANAGE, self::PERMISSION_VIEW])) {
659
            return true;
660
        }
661
662
        return false;
663
    }
664
665
    public function canCreate($member = null, $context = [])
666
    {
667
        $extended = $this->extendedCan(__FUNCTION__, $member, $context);
668
669
        if ($extended !== null) {
670
            return $extended;
671
        }
672
673
        if (!$member) {
674
            $member = Security::getCurrentUser();
675
        }
676
677
        if ($member && Permission::checkMember($member->ID, [self::PERMISSION_MANAGE, self::PERMISSION_CREATE])) {
678
            return true;
679
        }
680
681
        return false;
682
    }
683
684
    public function canEdit($member = null)
685
    {
686
        $extended = $this->extendedCan(__FUNCTION__, $member);
687
688
        if ($extended !== null) {
689
            return $extended;
690
        }
691
692
        if (!$member) {
693
            $member = Security::getCurrentUser();
694
        }
695
696
        if ($member && Permission::checkMember($member->ID, [self::PERMISSION_MANAGE, self::PERMISSION_EDIT])) {
697
            return true;
698
        }
699
700
        return false;
701
    }
702
703
    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

703
    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...
704
    {
705
        $extended = $this->extendedCan(__FUNCTION__, $member);
706
707
        if ($extended !== null) {
708
            return $extended;
709
        }
710
711
        if (!$member) {
712
            $member = Security::getCurrentUser();
713
        }
714
715
        if ($member && Permission::checkMember($member->ID, self::PERMISSION_DELETE)) {
716
            return true;
717
        }
718
719
        return false;
720
    }
721
722
    /**
723
     * Sync to associated member (if needed)
724
     * 
725
     * @return void
726
     */
727
    public function onAfterWrite()
728
    {
729
        parent::onAfterWrite();
730
731
        $this->syncToMember();
732
    }
733
734
    /**
735
     * Cleanup DB on removal
736
     */
737
    public function onBeforeDelete()
738
    {
739
        parent::onBeforeDelete();
740
        
741
        // Delete all locations attached to this order
742
        foreach ($this->Locations() as $item) {
743
            $item->delete();
744
        }
745
746
        // Delete all notes attached to this order
747
        foreach ($this->Notes() as $item) {
748
            $item->delete();
749
        }
750
    }
751
}
752