Passed
Push — master ( 4f3790...b8156a )
by Stefan
01:34
created

VCardContact::getGender()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace SKien\VCard;
5
6
/**
7
 * Class representing all data to one contact.
8
 *
9
 * Uses helpers from trait VCardHelper
10
 * @see VCardHelper
11
 *
12
* history:
13
 * date         version
14
 * 2020-02-23   initial version.
15
 * 2020-05-28   renamed namespace to fit PSR-4 recommendations for autoloading.
16
 * 2020-07-22   added missing PHP 7.4 type hints / docBlock changes
17
 * 2021-07-14   The transparency of images is retained
18
 *
19
 * @package SKien-VCard
20
 * @since 1.0.0
21
 * @version 1.0.4
22
 * @author Stefanius <[email protected]>
23
 * @copyright MIT License - see the LICENSE file for details
24
 */
25
class VCardContact
26
{
27
    use VCardHelper;
28
29
    /** @var string  lastname   */
30
    protected string $strLastName = '';
31
    /** @var string  firstname  */
32
    protected string $strFirstName = '';
33
    /** @var string  prefix (salutation, title,...) */
34
    protected string $strPrefix = '';
35
    /** @var string  suffix (graduation,...)    */
36
    protected string $strSuffix = '';
37
    /** @var string  nickname   */
38
    protected string $strNickName = '';
39
    /** @var string  organisation name  */
40
    protected string $strOrganisation = '';
41
    /** @var string  position within the organisation   */
42
    protected string $strPosition = '';
43
    /** @var string  section within the organisation    */
44
    protected string $strSection = '';
45
    /** @var string  role / profession  */
46
    protected string $strRole = '';
47
    /** @var array   array of VCardAddress objects  */
48
    protected array $aAddress = array();
49
    /** @var array   array of phone numbers */
50
    protected array $aPhone = array();
51
    /** @var array   array of email addresses   */
52
    protected array $aEMail = array();
53
    /** @var array   array of categories    */
54
    protected array $aCategories = array();
55
    /** @var string  homepage URL   */
56
    protected string $strHomepage = '';
57
    /** @var string  date of birth in format YYYY-DD-MM */
58
    protected string $strDateOfBirth = '';
59
    /** @var int     gender (0: not specified, 1: female, 2: male)  */
60
    protected int $iGender = 0;
61
    /** @var string  note   */
62
    protected string $strNote = '';
63
    /** @var string  address label (readonly)   */
64
    protected string $strLabel = '';
65
    /** @var string  binary portrait base64 coded   */
66
    protected string $blobPortrait = '';
67
68
    /**
69
     * Create empty contact
70
     */
71
    public function __construct()
72
    {
73
    }
74
75
    /**
76
     * Add property from import file.
77
     * @param string $strName
78
     * @param array $aParams
79
     * @param string $strValue
80
     */
81
    public function addProperty(string $strName, array $aParams, string $strValue) : void
82
    {
83
        // table to parse property depending on propertyname.
84
        // value have to be either name of method with signature
85
        //
86
        //      methodname( string strValue, array aParams )
87
        //
88
        // or
89
        //      propertyname  ( => unmasked value will be assigned to property)
90
        //
91
        $aMethodOrProperty = array(
92
            'N'             => 'parseName',
93
            'ADR'           => 'parseAdr',
94
            'TEL'           => 'parseTel',
95
            'EMAIL'         => 'parseEMail',
96
            'CATEGORIES'    => 'parseCategories',
97
            'CATEGORY'      => 'parseCategories',
98
            'ORG'           => 'parseOrg',
99
            'PHOTO'         => 'parsePhoto',
100
            'NICKNAME'      => 'strNickName',
101
            'TITLE'         => 'strPosition',
102
            'ROLE'          => 'strRole',
103
            'URL'           => 'strHomepage',
104
            'NOTE'          => 'strNote',
105
            'LABEL'         => 'strLabel',
106
            'BDAY'          => 'strDateOfBirth',
107
            'X-WAB-GENDER'  => 'iGender'
108
        );
109
110
        // supported only by vcard version 2.1
111
        if (isset($aParams['ENCODING']) && $aParams['ENCODING'] == 'QUOTED-PRINTABLE') {
112
            $strValue = quoted_printable_decode($strValue);
113
        }
114
115
        if (isset($aMethodOrProperty[$strName])) {
116
            $strPtr = $aMethodOrProperty[$strName];
117
            if (method_exists($this, $strPtr)) {
118
                // call method
119
                call_user_func_array(array($this, $strPtr), array($strValue, $aParams));
120
            } elseif (property_exists($this, $strPtr)) {
121
                // assign unmasket vsalue to property
122
                $this->$strPtr = $this->unmaskString($strValue);
123
            }
124
        }
125
    }
126
127
    /**
128
     * Explode string into name components.
129
     * Order of the components separated by ';':
130
     *  - family name
131
     *  - given name
132
     *  - additional name(s) (not supported)
133
     *  - honorific prefixes
134
     *  - honorific suffixes
135
     *  delimitered by semicolon (be aware of masked delimiters)
136
     * @param string $strValue
137
     * @param array $aParams
138
     */
139
    protected function parseName(string $strValue, array $aParams) : void
140
    {
141
        $aSplit = $this->explodeMaskedString(';', $strValue);
142
        $this->strLastName = $this->unmaskString($aSplit[0]); // family name
143
        if (isset($aSplit[1])) {
144
            $this->strFirstName = $this->unmaskString($aSplit[1]); // given name
145
        }
146
        if (isset($aSplit[3])) {
147
            $this->strPrefix = $this->unmaskString($aSplit[3]); // honorific prefixes
148
        }
149
        if (isset($aSplit[4])) {
150
            $this->strSuffix = $this->unmaskString($aSplit[4]); // honorific suffixes
151
        }
152
    }
153
154
    /**
155
     * @param string $strValue
156
     * @param array $aParams
157
     * @see VCardAddress::parseFullAddress()
158
     */
159
    protected function parseAdr(string $strValue, array $aParams) : void
160
    {
161
        $oAdr = new VCardAddress();
162
        $oAdr->parseFullAddress($strValue, $aParams);
163
        $this->aAddress[] = $oAdr;
164
    }
165
166
    /**
167
     * Unmask value and add with typeinfo to phone list.
168
     * @param string $strValue
169
     * @param array $aParams
170
     */
171
    protected function parseTel(string $strValue, array $aParams) : void
172
    {
173
        $strValue = $this->unmaskString($strValue);
174
        $this->addPhone($strValue, $aParams['TYPE'], strpos($aParams['TYPE'], 'PREF') !== false);
175
    }
176
177
    /**
178
     * Unmask value and add to email list.
179
     * @param string $strValue
180
     * @param array $aParams
181
     */
182
    protected function parseEMail(string $strValue, array $aParams) : void
183
    {
184
        $strValue = $this->unmaskString($strValue);
185
        $this->addEMail($strValue, strpos($aParams['TYPE'], 'PREF') !== false);
186
    }
187
188
    /**
189
     * Split into company and section.
190
     * @param string $strValue
191
     * @param array $aParams
192
     */
193
    protected function parseOrg(string $strValue, array $aParams) : void
194
    {
195
        $aSplit = $this->explodeMaskedString(';', $strValue);
196
        $this->strOrganisation = $this->unmaskString($aSplit[0]);
197
        if (isset($aSplit[1])) {
198
            $this->strSection = $this->unmaskString($aSplit[1]);
199
        }
200
    }
201
202
    /**
203
     * Split comma separated categories.
204
     * @param string $strValue
205
     * @param array $aParams
206
     */
207
    protected function parseCategories(string $strValue, array $aParams) : void
208
    {
209
        $aSplit = $this->explodeMaskedString(',', $strValue);
210
        foreach ($aSplit as $strCategory) {
211
            $this->addCategory($this->unmaskString($strCategory));
212
        }
213
    }
214
215
    /**
216
     * @param string $strValue
217
     * @param array $aParams
218
     */
219
    protected function parsePhoto(string $strValue, array $aParams) : void
220
    {
221
        $strEncoding = isset($aParams['ENCODING']) ? $aParams['ENCODING'] : '';
222
        if ($strEncoding == 'B' || $strEncoding == 'BASE64') {
223
            $strType = strtolower($aParams['TYPE']);
224
            $this->blobPortrait = 'data:image/' . $strType . ';base64,' . $strValue;
225
        } else {
226
            // assuming URL value... e.g. export from google contacts
227
            $this->setPortraitFile($strValue);
228
        }
229
    }
230
231
    /**
232
     * Add address.
233
     * Only one address should be marked as preferred. In case of multiple addresses
234
     * specified as preferred, last call counts!
235
     * @param VCardAddress $oAddress
236
     * @param bool $bPreferred
237
     */
238
    public function addAddress(VCardAddress $oAddress, bool $bPreferred) : void
239
    {
240
        $oAddress->setPreferred($bPreferred);
241
        $this->aAddress[] = $oAddress;
242
    }
243
244
    /**
245
     * Aadd phone number.
246
     * Can also be used to set FAX number
247
     * there may be defined multiple numbers with same type.
248
     * @param string $strPhone
249
     * @param string $strType   one of VCard::WORK, VCard::HOME, VCard::CELL, VCard::FAX
250
     * @param bool $bPreferred
251
     */
252
    public function addPhone(string $strPhone, string $strType, bool $bPreferred) : void
253
    {
254
        if ($bPreferred && strpos($strType, 'PREF') === false) {
255
            $strType .= ',PREF';
256
        }
257
        $this->aPhone[] = array('strPhone' => $strPhone, 'strType' => $strType);
258
    }
259
260
    /**
261
     * aAdd mail address.
262
     * @param string $strEMail
263
     * @param bool $bPreferred
264
     */
265
    public function addEMail(string $strEMail, bool $bPreferred) : void
266
    {
267
        if ($bPreferred) {
268
            // just set preferred mail on top of the list!
269
            array_unshift($this->aEMail, $strEMail);
270
        } else {
271
            $this->aEMail[] = $strEMail;
272
        }
273
    }
274
275
    /**
276
     * Add category.
277
     * @param string $strCategory
278
     */
279
    public function addCategory(string $strCategory) : void
280
    {
281
        $this->aCategories[] = $strCategory;
282
    }
283
284
    /**
285
     * Set date of birth.
286
     * @param mixed $DateOfBirth    may be string (format YYYY-MM-DD), int (unixtimestamp) or DateTime - object
287
     */
288
    public function setDateOfBirth($DateOfBirth) : void
289
    {
290
        if (is_object($DateOfBirth) && get_class($DateOfBirth) == 'DateTime') {
291
            // DateTime -object
292
            $this->strDateOfBirth = $DateOfBirth->format('Y-m-d');
293
        } else if (is_numeric($DateOfBirth)) {
294
            $this->strDateOfBirth = date('Y-m-d', $DateOfBirth);
295
        } else {
296
            $this->strDateOfBirth = $DateOfBirth;
297
        }
298
    }
299
300
    /**
301
     * Set the gender.
302
     * MS-extension!
303
     * windows contacts: export/import.
304
     * outlook: import only.
305
     * only male or female accepted
306
     * @param string $strGender
307
     */
308
    public function setGender(string $strGender) : void
309
    {
310
        $chGender = strtolower(substr($strGender, 0, 1));
311
        if (in_array($chGender, array('w', 'f'))) {
312
            // weibl., female
313
            $this->iGender = 1;
314
        } elseif ($chGender == 'm') {
315
            // männl., male
316
            $this->iGender = 2;
317
        }
318
    }
319
320
    /**
321
     * Set portrait from image file.
322
     * supported types are JPG, PNG, GIF and BMP
323
     * @param string $strFilename
324
     */
325
    public function setPortraitFile(string $strFilename) : void
326
    {
327
        if (filter_var($strFilename, FILTER_VALIDATE_URL)) {
328
            // get type from extension
329
            $strType = strtolower((string) pathinfo($strFilename, PATHINFO_EXTENSION));
330
            $this->blobPortrait = 'data:image/' . $strType . ';base64,';
331
332
            // use curl to be independet of [allow_url_fopen] enabled on system
333
            $curl = curl_init();
334
            curl_setopt($curl, CURLOPT_URL, $strFilename);
335
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
336
337
            $img = curl_exec($curl);
338
            curl_close($curl);
339
340
            if (is_string($img)) {
341
                $this->blobPortrait .= base64_encode($img);
342
            }
343
        } elseif (file_exists($strFilename)) {
344
            switch (exif_imagetype($strFilename)) {
345
                case IMAGETYPE_JPEG:
346
                    $this->blobPortrait = 'data:image/jpg;base64,';
347
                    break;
348
                case IMAGETYPE_PNG:
349
                    $this->blobPortrait = 'data:image/png;base64,';
350
                    break;
351
                case IMAGETYPE_GIF:
352
                    $this->blobPortrait = 'data:image/gif;base64,';
353
                    break;
354
                case IMAGETYPE_BMP:
355
                    $this->blobPortrait = 'data:image/bmp;base64,';
356
                    break;
357
                default:
358
                    break;
359
            }
360
            $img = file_get_contents($strFilename);
361
362
            $this->blobPortrait .= base64_encode($img);
363
        }
364
    }
365
366
    /**
367
     * @param string $strLastName
368
     * @param string $strFirstName
369
     */
370
    public function setName(string $strLastName, string $strFirstName) : void
371
    {
372
        $this->strLastName = $strLastName;
373
        $this->strFirstName = $strFirstName;
374
    }
375
376
    /**
377
     * @param string $strPrefix
378
     */
379
    public function setPrefix(string $strPrefix) : void
380
    {
381
        $this->strPrefix = $strPrefix;
382
    }
383
384
    /**
385
     * @param string $strSuffix
386
     */
387
    public function setStrSuffix(string $strSuffix) : void
388
    {
389
        $this->strSuffix = $strSuffix;
390
    }
391
392
    /**
393
     * @param string $strNickName
394
     */
395
    public function setNickName(string $strNickName) : void
396
    {
397
        $this->strNickName = $strNickName;
398
    }
399
400
    /**
401
     * @param string $strOrganisation
402
     */
403
    public function setOrganisation(string $strOrganisation) : void
404
    {
405
        $this->strOrganisation = $strOrganisation;
406
    }
407
408
    /**
409
     * @param string $strSection
410
     */
411
    public function setSection(string $strSection) : void
412
    {
413
        $this->strSection = $strSection;
414
    }
415
416
    /**
417
     * @param string $strPosition
418
     */
419
    public function setPosition(string $strPosition) : void
420
    {
421
        $this->strPosition = $strPosition;
422
    }
423
424
    /**
425
     * @param string $strRole
426
     */
427
    public function setRole(string $strRole) : void
428
    {
429
        $this->strPosition = $strRole;
430
    }
431
432
    /**
433
     * @param string $strHomepage
434
     */
435
    public function setHomepage(string $strHomepage) : void
436
    {
437
        $this->strHomepage = $strHomepage;
438
    }
439
440
    /**
441
     * @param string $strNote
442
     */
443
    public function setNote(string $strNote) : void
444
    {
445
        $this->strNote = $strNote;
446
    }
447
448
    /**
449
     * Set portrait from data.
450
     * @param string $blobPortrait base64 encoded image
451
     */
452
    public function setPortraitBlob(string $blobPortrait) : void
453
    {
454
        $this->blobPortrait = $blobPortrait;
455
    }
456
457
    /**
458
     * Save portrait as file.
459
     * Supportet types are JPG, PNG, GIF and BMP
460
     * The type depends on the fileextension. If no extensiomnm given, the
461
     * type of the imported image will be used.
462
     * @param string $strFilename
463
     */
464
    public function savePortrait(string $strFilename) : void
465
    {
466
        if (strlen($this->blobPortrait) > 0) {
467
            $strType = '';
468
            $strImage = '';
469
            $this->parseImageData($this->blobPortrait, $strType, $strImage);
470
            if (strlen($strType) > 0 && strlen($strImage) > 0) {
471
                $img = $this->imageFromString($strImage, $strType);
472
                imagealphablending($img, true);
473
                imagesavealpha($img, true);
474
                $strExt = strtolower((string) pathinfo($strFilename, PATHINFO_EXTENSION));
475
                if (strlen($strExt) == 0) {
476
                    $strExt = strtolower($strType);
477
                    $strFilename .= '.' . $strExt;
478
                }
479
                switch ($strExt) {
480
                    case 'jpg':
481
                    case 'jpeg':
482
                        imagejpeg($img, $strFilename);
483
                        break;
484
                    case 'png':
485
                        imagepng($img, $strFilename);
486
                        break;
487
                    case 'gif':
488
                        imagegif($img, $strFilename);
489
                        break;
490
                    case 'bmp':
491
                        imagebmp($img, $strFilename);
492
                        break;
493
                }
494
            }
495
        }
496
    }
497
498
    /**
499
     * Number of addresses the contact contains.
500
     * @return int
501
     */
502
    public function getAddressCount() : int
503
    {
504
        return count($this->aAddress);
505
    }
506
507
    /**
508
     * Get address.
509
     * can be referenced by index or type.
510
     * type requests (=> $i non numeric value):
511
     * - first address matches specified type is used (contact may contains multiple addresses of same type)
512
     * - if VCard::PREF specified, first address in contact used, if no preferred item found
513
     * @param mixed $i     reference to address (int => index, string => type)
514
     * @return VCardAddress or null
515
     */
516
    public function getAddress($i) : ?VCardAddress
517
    {
518
        $oAddr = null;
519
        if (is_numeric($i)) {
520
            if ($i >= 0 && $i < count($this->aAddress)) {
521
                $oAddr = $this->aAddress[$i];
522
            }
523
        } else {
524
            foreach ($this->aAddress as $oAddress) {
525
                if (strpos($oAddress->getType(), $i) !== false) {
526
                    $oAddr = $oAddress;
527
                    break;
528
                }
529
            }
530
        }
531
        if (!$oAddr && $i == VCard::PREF && count($this->aAddress) > 0) {
532
            // if preferred item requested and no address in contact defined as prefered, just return first...
533
            $oAddr = $this->aAddress[0];
534
        }
535
        return $oAddr;
536
    }
537
538
    /**
539
     * Count of phone numbers.
540
     * @return int
541
     */
542
    public function getPhoneCount() : int
543
    {
544
        return count($this->aPhone);
545
    }
546
547
    /**
548
     * Get phone number.
549
     * can be referenced by index or type.
550
     * type requests (=> $i non numeric value):
551
     * - first phone matches specified type is used (contact may contains multiple phone numbers of same type)
552
     * - if VCard::PREF specified, first number in contact used, if no preferred item found
553
     * @param mixed $i     reference to address (int => index, string => type)
554
     * @return array or null
555
     */
556
    public function getPhone($i) : ?array
557
    {
558
        $aPhone = null;
559
        if (is_numeric($i)) {
560
            if ($i >= 0 && $i < count($this->aPhone)) {
561
                $aPhone = $this->aPhone[$i];
562
            }
563
        } else {
564
            foreach ($this->aPhone as $aPhone) {
565
                if (strpos($aPhone['strType'], $i) !== false) {
566
                    return $aPhone;
567
                }
568
                $aPhone = null;
569
            }
570
        }
571
        if (!$aPhone && $i == VCard::PREF && count($this->aPhone) > 0) {
572
            // if preferred item requested and no phone in contact defined as prefered, just return first...
573
            $aPhone = $this->aPhone[0];
574
        }
575
        return $aPhone;
576
    }
577
578
    /**
579
     * Number of EMail addresses.
580
     * @return int
581
     */
582
    public function getEMailCount() : int
583
    {
584
        return count($this->aEMail);
585
    }
586
587
    /**
588
     * @param int $i
589
     * @return string
590
     */
591
    public function getEMail(int $i) : string
592
    {
593
        $strEMail = '';
594
        if ($i >= 0 && $i < count($this->aEMail)) {
595
            $strEMail = $this->aEMail[$i];
596
        }
597
        return $strEMail;
598
    }
599
600
    /**
601
     * Number of categories.
602
     * @return int
603
     */
604
    public function getCategoriesCount() : int
605
    {
606
        return count($this->aCategories);
607
    }
608
609
    /**
610
     * @param int $i
611
     * @return string
612
     */
613
    public function getCategory(int $i) : string
614
    {
615
        $strCategory = '';
616
        if ($i >= 0 && $i < count($this->aCategories)) {
617
            $strCategory = $this->aCategories[$i];
618
        }
619
        return $strCategory;
620
    }
621
622
    /**
623
     * Return Categories separated by comma.
624
     * @return string
625
     */
626
    public function getCategories() : string
627
    {
628
        $strCategories = '';
629
        $strSep = '';
630
        foreach ($this->aCategories as $strCategory) {
631
            $strCategories .= $strSep . $strCategory;
632
            $strSep = ',';
633
        }
634
        return $strCategories;
635
    }
636
637
    /**
638
     * @return string
639
     */
640
    public function getName() : string
641
    {
642
        return $this->strFirstName . ' ' . $this->strLastName;
643
    }
644
645
    /**
646
     * @return string
647
     */
648
    public function getLastName() : string
649
    {
650
        return $this->strLastName;
651
    }
652
653
    /**
654
     * @return string
655
     */
656
    public function getFirstName() : string
657
    {
658
        return $this->strFirstName;
659
    }
660
661
    /**
662
     * @return string
663
     */
664
    public function getNickName() : string
665
    {
666
        return $this->strNickName;
667
    }
668
669
    /**
670
     * @return string
671
     */
672
    public function getOrganisation() : string
673
    {
674
        return $this->strOrganisation;
675
    }
676
677
    /**
678
     * @return string
679
     */
680
    public function getPosition() : string
681
    {
682
        return $this->strPosition;
683
    }
684
685
    /**
686
     * @return string
687
     */
688
    public function getRole() : string
689
    {
690
        return $this->strRole;
691
    }
692
693
    /**
694
     * @return string
695
     */
696
    public function getHomepage() : string
697
    {
698
        return $this->strHomepage;
699
    }
700
701
    /**
702
     * Get date of birth.
703
     * @return string   format YYYY-DD-MM
704
     */
705
    public function getDateOfBirth() : string
706
    {
707
        return $this->strDateOfBirth;
708
    }
709
710
    /**
711
     * Get gender (MS only).
712
     * @return int  0: not set, 1: male, 2: female
713
     */
714
    public function getGender() : int
715
    {
716
        return $this->iGender;
717
    }
718
719
    /**
720
     * @return string
721
     */
722
    public function getSection() : string
723
    {
724
        return $this->strSection;
725
    }
726
727
    /**
728
     * @return string
729
     */
730
    public function getNote() : string
731
    {
732
        return $this->strNote;
733
    }
734
735
    /**
736
     * @return string
737
     */
738
    public function getLabel() : string
739
    {
740
        return $this->strLabel;
741
    }
742
743
    /**
744
     * @return string
745
     */
746
    public function getPrefix() : string
747
    {
748
        return $this->strPrefix;
749
    }
750
751
    /**
752
     * @return string
753
     */
754
    public function getSuffix() : string
755
    {
756
        return $this->strSuffix;
757
    }
758
759
    /**
760
     * @return string base64 encoded image
761
     */
762
    public function getPortraitBlob() : string
763
    {
764
        return $this->blobPortrait;
765
    }
766
}
767