Completed
Push — master ( c52a8c...2ff9bf )
by Jeroen
04:43 queued 02:23
created

VCard::addLabel()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 1
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace JeroenDesloovere\VCard;
4
5
/*
6
 * This file is part of the VCard PHP Class from Jeroen Desloovere.
7
 *
8
 * For the full copyright and license information, please view the license
9
 * file that was distributed with this source code.
10
 */
11
12
use Behat\Transliterator\Transliterator;
13
14
/**
15
 * VCard PHP Class to generate .vcard files and save them to a file or output as a download.
16
 */
17
class VCard
18
{
19
    /**
20
     * definedElements
21
     *
22
     * @var array
23
     */
24
    private $definedElements;
25
26
    /**
27
     * Filename
28
     *
29
     * @var string
30
     */
31
    private $filename;
32
33
    /**
34
     * Save Path
35
     *
36
     * @var string
37
     */
38
    private $savePath = null;
39
40
    /**
41
     * Multiple properties for element allowed
42
     *
43
     * @var array
44
     */
45
    private $multiplePropertiesForElementAllowed = [
46
        'email',
47
        'address',
48
        'phoneNumber',
49
        'url',
50
        'label'
51
    ];
52
53
    /**
54
     * Properties
55
     *
56
     * @var array
57
     */
58
    private $properties;
59
60
    /**
61
     * Default Charset
62
     *
63
     * @var string
64
     */
65
    public $charset = 'utf-8';
66
67
    /**
68
     * Add address
69
     *
70
     * @param  string [optional] $name
71
     * @param  string [optional] $extended
72
     * @param  string [optional] $street
73
     * @param  string [optional] $city
74
     * @param  string [optional] $region
75
     * @param  string [optional] $zip
76
     * @param  string [optional] $country
77
     * @param  string [optional] $type
78
     *                                     $type may be DOM | INTL | POSTAL | PARCEL | HOME | WORK
79
     *                                     or any combination of these: e.g. "WORK;PARCEL;POSTAL"
80
     * @return $this
81
     */
82
    public function addAddress(
83
        $name = '',
84
        $extended = '',
85
        $street = '',
86
        $city = '',
87
        $region = '',
88
        $zip = '',
89
        $country = '',
90
        $type = 'WORK;POSTAL'
91
    ) {
92
        // init value
93
        $value = $name . ';' . $extended . ';' . $street . ';' . $city . ';' . $region . ';' . $zip . ';' . $country;
94
95
        // set property
96
        $this->setProperty(
97
            'address',
98
            'ADR' . (($type != '') ? ';' . $type : '') . $this->getCharsetString(),
99
            $value
100
        );
101
102
        return $this;
103
    }
104
105
    /**
106
     * Add birthday
107
     *
108
     * @param  string $date Format is YYYY-MM-DD
109
     * @return $this
110
     */
111
    public function addBirthday($date)
112
    {
113
        $this->setProperty(
114
            'birthday',
115
            'BDAY',
116
            $date
117
        );
118
119
        return $this;
120
    }
121
122
    /**
123
     * Add company
124
     *
125
     * @param string $company
126
     * @param string $department
127
     * @return $this
128
     */
129
    public function addCompany($company, $department = '')
130
    {
131
        $this->setProperty(
132
            'company',
133
            'ORG' . $this->getCharsetString(),
134
            $company
135
            . ($department != '' ? ';' . $department : '')
136
        );
137
138
        // if filename is empty, add to filename
139
        if ($this->filename === null) {
140
            $this->setFilename($company);
141
        }
142
143
        return $this;
144
    }
145
146
    /**
147
     * Add email
148
     *
149
     * @param  string $address The e-mail address
150
     * @param  string [optional] $type    The type of the email address
151
     *                                    $type may be  PREF | WORK | HOME
152
     *                                    or any combination of these: e.g. "PREF;WORK"
153
     * @return $this
154
     */
155
    public function addEmail($address, $type = '')
156
    {
157
        $this->setProperty(
158
            'email',
159
            'EMAIL;INTERNET' . (($type != '') ? ';' . $type : ''),
160
            $address
161
        );
162
163
        return $this;
164
    }
165
166
    /**
167
     * Add jobtitle
168
     *
169
     * @param  string $jobtitle The jobtitle for the person.
170
     * @return $this
171
     */
172
    public function addJobtitle($jobtitle)
173
    {
174
        $this->setProperty(
175
            'jobtitle',
176
            'TITLE' . $this->getCharsetString(),
177
            $jobtitle
178
        );
179
180
        return $this;
181
    }
182
183
    /**
184
     * Add a label
185
     *
186
     * @param string $label
187
     * @param string $type
188
     *
189
     * @return $this
190
     */
191
    public function addLabel($label, $type = '')
192
    {
193
        $this->setProperty(
194
            'label',
195
            'LABEL' . ($type !== '' ? ';' . $type : ''),
196
            $label
197
        );
198
199
        return $this;
200
    }
201
202
    /**
203
     * Add role
204
     *
205
     * @param  string $role The role for the person.
206
     * @return $this
207
     */
208
    public function addRole($role)
209
    {
210
        $this->setProperty(
211
            'role',
212
            'ROLE' . $this->getCharsetString(),
213
            $role
214
        );
215
216
        return $this;
217
    }
218
219
    /**
220
     * Add a photo or logo (depending on property name)
221
     *
222
     * @param string $property LOGO|PHOTO
223
     * @param string $url image url or filename
224
     * @param bool $include Do we include the image in our vcard or not?
225
     * @param string $element The name of the element to set
226
     */
227
    private function addMedia($property, $url, $include = true, $element)
228
    {
229
        $mimeType = null;
230
231
        //Is this URL for a remote resource?
232
        if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
233
            $headers = get_headers($url, 1);
234
235
            if (array_key_exists('Content-Type', $headers)) {
236
                $mimeType = $headers['Content-Type'];
237
                if (is_array($mimeType)) {
238
                    $mimeType = end($mimeType);
239
                }
240
            }
241
        } else {
242
            //Local file, so inspect it directly
243
            $mimeType = mime_content_type($url);
244
        }
245
        if (strpos($mimeType, ';') !== false) {
246
            $mimeType = strstr($mimeType, ';', true);
247
        }
248
        if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') {
249
            throw VCardException::invalidImage();
250
        }
251
        $fileType = strtoupper(substr($mimeType, 6));
252
253
        if ($include) {
254
            $value = file_get_contents($url);
255
256
            if (!$value) {
257
                throw VCardException::emptyURL();
258
            }
259
260
            $value = base64_encode($value);
261
            $property .= ";ENCODING=b;TYPE=" . $fileType;
262
        } else {
263
            if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
264
                $propertySuffix = ';VALUE=URL';
265
                $propertySuffix .= ';TYPE=' . strtoupper($fileType);
266
267
                $property = $property . $propertySuffix;
268
                $value = $url;
269
            } else {
270
                $value = $url;
271
            }
272
        }
273
274
        $this->setProperty(
275
            $element,
276
            $property,
277
            $value
278
        );
279
    }
280
281
    /**
282
     * Add a photo or logo (depending on property name)
283
     *
284
     * @param string $property LOGO|PHOTO
285
     * @param string $content image content
286
     * @param string $element The name of the element to set
287
     */
288
    private function addMediaContent($property, $content, $element)
289
    {
290
        $finfo = new \finfo();
291
        $mimeType = $finfo->buffer($content, FILEINFO_MIME_TYPE);
292
293
        if (strpos($mimeType, ';') !== false) {
294
            $mimeType = strstr($mimeType, ';', true);
295
        }
296
        if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') {
0 ignored issues
show
introduced by
The condition is_string($mimeType) is always true.
Loading history...
297
            throw VCardException::invalidImage();
298
        }
299
        $fileType = strtoupper(substr($mimeType, 6));
300
301
        $content = base64_encode($content);
302
        $property .= ";ENCODING=b;TYPE=" . $fileType;
303
304
        $this->setProperty(
305
            $element,
306
            $property,
307
            $content
308
        );
309
    }
310
311
    /**
312
     * Add name
313
     *
314
     * @param  string [optional] $lastName
315
     * @param  string [optional] $firstName
316
     * @param  string [optional] $additional
317
     * @param  string [optional] $prefix
318
     * @param  string [optional] $suffix
319
     * @return $this
320
     */
321
    public function addName(
322
        $lastName = '',
323
        $firstName = '',
324
        $additional = '',
325
        $prefix = '',
326
        $suffix = ''
327
    ) {
328
        // define values with non-empty values
329
        $values = array_filter([
330
            $prefix,
331
            $firstName,
332
            $additional,
333
            $lastName,
334
            $suffix,
335
        ]);
336
337
        // define filename
338
        $this->setFilename($values);
339
340
        // set property
341
        $property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix;
342
        $this->setProperty(
343
            'name',
344
            'N' . $this->getCharsetString(),
345
            $property
346
        );
347
348
        // is property FN set?
349
        if (!$this->hasProperty('FN')) {
350
            // set property
351
            $this->setProperty(
352
                'fullname',
353
                'FN' . $this->getCharsetString(),
354
                trim(implode(' ', $values))
355
            );
356
        }
357
358
        return $this;
359
    }
360
361
    /**
362
     * Add note
363
     *
364
     * @param  string $note
365
     * @return $this
366
     */
367
    public function addNote($note)
368
    {
369
        $this->setProperty(
370
            'note',
371
            'NOTE' . $this->getCharsetString(),
372
            $note
373
        );
374
375
        return $this;
376
    }
377
378
    /**
379
     * Add categories
380
     *
381
     * @param array $categories
382
     * @return $this
383
     */
384
    public function addCategories($categories)
385
    {
386
        $this->setProperty(
387
            'categories',
388
            'CATEGORIES' . $this->getCharsetString(),
389
            trim(implode(',', $categories))
390
        );
391
392
        return $this;
393
    }
394
395
    /**
396
     * Add phone number
397
     *
398
     * @param  string $number
399
     * @param  string [optional] $type
400
     *                                   Type may be PREF | WORK | HOME | VOICE | FAX | MSG |
401
     *                                   CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO
402
     *                                   or any senseful combination, e.g. "PREF;WORK;VOICE"
403
     * @return $this
404
     */
405
    public function addPhoneNumber($number, $type = '')
406
    {
407
        $this->setProperty(
408
            'phoneNumber',
409
            'TEL' . (($type != '') ? ';' . $type : ''),
410
            $number
411
        );
412
413
        return $this;
414
    }
415
416
    /**
417
     * Add Logo
418
     *
419
     * @param  string $url image url or filename
420
     * @param  bool $include Include the image in our vcard?
421
     * @return $this
422
     */
423
    public function addLogo($url, $include = true)
424
    {
425
        $this->addMedia(
426
            'LOGO',
427
            $url,
428
            $include,
429
            'logo'
430
        );
431
432
        return $this;
433
    }
434
435
    /**
436
     * Add Logo content
437
     *
438
     * @param  string $content image content
439
     * @return $this
440
     */
441
    public function addLogoContent($content)
442
    {
443
        $this->addMediaContent(
444
            'LOGO',
445
            $content,
446
            'logo'
447
        );
448
449
        return $this;
450
    }
451
452
    /**
453
     * Add Photo
454
     *
455
     * @param  string $url image url or filename
456
     * @param  bool $include Include the image in our vcard?
457
     * @return $this
458
     */
459
    public function addPhoto($url, $include = true)
460
    {
461
        $this->addMedia(
462
            'PHOTO',
463
            $url,
464
            $include,
465
            'photo'
466
        );
467
468
        return $this;
469
    }
470
471
    /**
472
     * Add Photo content
473
     *
474
     * @param  string $content image content
475
     * @return $this
476
     */
477
    public function addPhotoContent($content)
478
    {
479
        $this->addMediaContent(
480
            'PHOTO',
481
            $content,
482
            'photo'
483
        );
484
485
        return $this;
486
    }
487
488
    /**
489
     * Add URL
490
     *
491
     * @param  string $url
492
     * @param  string [optional] $type Type may be WORK | HOME
493
     * @return $this
494
     */
495
    public function addURL($url, $type = '')
496
    {
497
        $this->setProperty(
498
            'url',
499
            'URL' . (($type != '') ? ';' . $type : ''),
500
            $url
501
        );
502
503
        return $this;
504
    }
505
506
    /**
507
     * Build VCard (.vcf)
508
     *
509
     * @return string
510
     */
511
    public function buildVCard()
512
    {
513
        // init string
514
        $string = "BEGIN:VCARD\r\n";
515
        $string .= "VERSION:3.0\r\n";
516
        $string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n";
517
518
        // loop all properties
519
        $properties = $this->getProperties();
520
        foreach ($properties as $property) {
521
            // add to string
522
            $string .= $this->fold($property['key'] . ':' . $this->escape($property['value']) . "\r\n");
523
        }
524
525
        // add to string
526
        $string .= "END:VCARD\r\n";
527
528
        // return
529
        return $string;
530
    }
531
532
    /**
533
     * Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround.
534
     *
535
     * @return string
536
     */
537
    public function buildVCalendar()
538
    {
539
        // init dates
540
        $dtstart = date("Ymd") . "T" . date("Hi") . "00";
541
        $dtend = date("Ymd") . "T" . date("Hi") . "01";
542
543
        // init string
544
        $string = "BEGIN:VCALENDAR\n";
545
        $string .= "VERSION:2.0\n";
546
        $string .= "BEGIN:VEVENT\n";
547
        $string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n";
548
        $string .= "DTEND;TZID=Europe/London:" . $dtend . "\n";
549
        $string .= "SUMMARY:Click attached contact below to save to your contacts\n";
550
        $string .= "DTSTAMP:" . $dtstart . "Z\n";
551
        $string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n";
552
        $string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n";
553
554
        // base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment
555
        $b64vcard = base64_encode($this->buildVCard());
556
557
        // chunk the single long line of b64 text in accordance with RFC2045
558
        // (and the exact line length determined from the original .ics file exported from Apple calendar
559
        $b64mline = chunk_split($b64vcard, 74, "\n");
560
561
        // need to indent all the lines by 1 space for the iphone (yes really?!!)
562
        $b64final = preg_replace('/(.+)/', ' $1', $b64mline);
563
        $string .= $b64final;
564
565
        // output the correctly formatted encoded text
566
        $string .= "END:VEVENT\n";
567
        $string .= "END:VCALENDAR\n";
568
569
        // return
570
        return $string;
571
    }
572
573
    /**
574
     * Returns the browser user agent string.
575
     *
576
     * @return string
577
     */
578
    protected function getUserAgent()
579
    {
580
        if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
581
            $browser = strtolower($_SERVER['HTTP_USER_AGENT']);
582
        } else {
583
            $browser = 'unknown';
584
        }
585
586
        return $browser;
587
    }
588
589
    /**
590
     * Decode
591
     *
592
     * @param  string $value The value to decode
593
     * @return string decoded
594
     */
595
    private function decode($value)
596
    {
597
        // convert cyrlic, greek or other caracters to ASCII characters
598
        return Transliterator::transliterate($value);
599
    }
600
601
    /**
602
     * Download a vcard or vcal file to the browser.
603
     */
604
    public function download()
605
    {
606
        // define output
607
        $output = $this->getOutput();
608
609
        foreach ($this->getHeaders(false) as $header) {
610
            header($header);
611
        }
612
613
        // echo the output and it will be a download
614
        echo $output;
615
    }
616
617
    /**
618
     * Fold a line according to RFC2425 section 5.8.1.
619
     *
620
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.1
621
     * @param  string $text
622
     * @return mixed
623
     */
624
    protected function fold($text)
625
    {
626
        if (strlen($text) <= 75) {
627
            return $text;
628
        }
629
630
        // split, wrap and trim trailing separator
631
        return substr($this->chunk_split_unicode($text, 73, "\r\n "), 0, -3);
632
    }
633
634
    /**
635
     * multibyte word chunk split
636
     * @link http://php.net/manual/en/function.chunk-split.php#107711
637
     * 
638
     * @param  string  $body     The string to be chunked.
639
     * @param  integer $chunklen The chunk length.
640
     * @param  string  $end      The line ending sequence.
641
     * @return string            Chunked string
642
     */
643
    protected function chunk_split_unicode($body, $chunklen = 76, $end = "\r\n")
644
    {
645
        $array = array_chunk(
646
            preg_split("//u", $body, -1, PREG_SPLIT_NO_EMPTY), $chunklen);
0 ignored issues
show
Bug introduced by
It seems like preg_split('//u', $body,...rd\PREG_SPLIT_NO_EMPTY) can also be of type false; however, parameter $input of array_chunk() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

646
            /** @scrutinizer ignore-type */ preg_split("//u", $body, -1, PREG_SPLIT_NO_EMPTY), $chunklen);
Loading history...
647
        $body = "";
648
        foreach ($array as $item) {
649
            $body .= join("", $item) . $end;
650
        }
651
        return $body;
652
    }
653
654
    /**
655
     * Escape newline characters according to RFC2425 section 5.8.4.
656
     *
657
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.4
658
     * @param  string $text
659
     * @return string
660
     */
661
    protected function escape($text)
662
    {
663
        $text = str_replace("\r\n", "\\n", $text);
664
        $text = str_replace("\n", "\\n", $text);
665
666
        return $text;
667
    }
668
669
    /**
670
     * Get output as string
671
     * @deprecated in the future
672
     *
673
     * @return string
674
     */
675
    public function get()
676
    {
677
        return $this->getOutput();
678
    }
679
680
    /**
681
     * Get charset
682
     *
683
     * @return string
684
     */
685
    public function getCharset()
686
    {
687
        return $this->charset;
688
    }
689
690
    /**
691
     * Get charset string
692
     *
693
     * @return string
694
     */
695
    public function getCharsetString()
696
    {
697
        return ';CHARSET=' . $this->charset;
698
    }
699
700
    /**
701
     * Get content type
702
     *
703
     * @return string
704
     */
705
    public function getContentType()
706
    {
707
        return ($this->isIOS7()) ?
708
            'text/x-vcalendar' : 'text/x-vcard';
709
    }
710
711
    /**
712
     * Get filename
713
     *
714
     * @return string
715
     */
716
    public function getFilename()
717
    {
718
        if (!$this->filename) {
719
            return 'unknown';
720
        }
721
722
        return $this->filename;
723
    }
724
725
    /**
726
     * Get file extension
727
     *
728
     * @return string
729
     */
730
    public function getFileExtension()
731
    {
732
        return ($this->isIOS7()) ?
733
            'ics' : 'vcf';
734
    }
735
736
    /**
737
     * Get headers
738
     *
739
     * @param  bool $asAssociative
740
     * @return array
741
     */
742
    public function getHeaders($asAssociative)
743
    {
744
        $contentType = $this->getContentType() . '; charset=' . $this->getCharset();
745
        $contentDisposition = 'attachment; filename=' . $this->getFilename() . '.' . $this->getFileExtension();
746
        $contentLength = mb_strlen($this->getOutput(), $this->getCharset());
747
        $connection = 'close';
748
749
        if ((bool)$asAssociative) {
750
            return [
751
                'Content-type' => $contentType,
752
                'Content-Disposition' => $contentDisposition,
753
                'Content-Length' => $contentLength,
754
                'Connection' => $connection,
755
            ];
756
        }
757
758
        return [
759
            'Content-type: ' . $contentType,
760
            'Content-Disposition: ' . $contentDisposition,
761
            'Content-Length: ' . $contentLength,
762
            'Connection: ' . $connection,
763
        ];
764
    }
765
766
    /**
767
     * Get output as string
768
     * iOS devices (and safari < iOS 8 in particular) can not read .vcf (= vcard) files.
769
     * So I build a workaround to build a .ics (= vcalender) file.
770
     *
771
     * @return string
772
     */
773
    public function getOutput()
774
    {
775
        $output = ($this->isIOS7()) ?
776
            $this->buildVCalendar() : $this->buildVCard();
777
778
        return $output;
779
    }
780
781
    /**
782
     * Get properties
783
     *
784
     * @return array
785
     */
786
    public function getProperties()
787
    {
788
        return $this->properties;
789
    }
790
791
    /**
792
     * Has property
793
     *
794
     * @param  string $key
795
     * @return bool
796
     */
797
    public function hasProperty($key)
798
    {
799
        $properties = $this->getProperties();
800
801
        foreach ($properties as $property) {
802
            if ($property['key'] === $key && $property['value'] !== '') {
803
                return true;
804
            }
805
        }
806
807
        return false;
808
    }
809
810
    /**
811
     * Is iOS - Check if the user is using an iOS-device
812
     *
813
     * @return bool
814
     */
815
    public function isIOS()
816
    {
817
        // get user agent
818
        $browser = $this->getUserAgent();
819
820
        return (strpos($browser, 'iphone') || strpos($browser, 'ipod') || strpos($browser, 'ipad'));
821
    }
822
823
    /**
824
     * Is iOS less than 7 (should cal wrapper be returned)
825
     *
826
     * @return bool
827
     */
828
    public function isIOS7()
829
    {
830
        return ($this->isIOS() && $this->shouldAttachmentBeCal());
831
    }
832
833
    /**
834
     * Save to a file
835
     *
836
     * @return void
837
     */
838
    public function save()
839
    {
840
        $file = $this->getFilename() . '.' . $this->getFileExtension();
841
842
        // Add save path if given
843
        if (null !== $this->savePath) {
844
            $file = $this->savePath . $file;
845
        }
846
847
        file_put_contents(
848
            $file,
849
            $this->getOutput()
850
        );
851
    }
852
853
    /**
854
     * Set charset
855
     *
856
     * @param  mixed $charset
857
     * @return void
858
     */
859
    public function setCharset($charset)
860
    {
861
        $this->charset = $charset;
862
    }
863
864
    /**
865
     * Set filename
866
     *
867
     * @param  mixed $value
868
     * @param  bool $overwrite [optional] Default overwrite is true
869
     * @param  string $separator [optional] Default separator is an underscore '_'
870
     * @return void
871
     */
872
    public function setFilename($value, $overwrite = true, $separator = '_')
873
    {
874
        // recast to string if $value is array
875
        if (is_array($value)) {
876
            $value = implode($separator, $value);
877
        }
878
879
        // trim unneeded values
880
        $value = trim($value, $separator);
881
882
        // remove all spaces
883
        $value = preg_replace('/\s+/', $separator, $value);
884
885
        // if value is empty, stop here
886
        if (empty($value)) {
887
            return;
888
        }
889
890
        // decode value + lowercase the string
891
        $value = strtolower($this->decode($value));
892
893
        // urlize this part
894
        $value = Transliterator::urlize($value);
895
896
        // overwrite filename or add to filename using a prefix in between
897
        $this->filename = ($overwrite) ?
898
            $value : $this->filename . $separator . $value;
899
    }
900
901
    /**
902
     * Set the save path directory
903
     *
904
     * @param  string $savePath Save Path
905
     * @throws VCardException
906
     */
907
    public function setSavePath($savePath)
908
    {
909
        if (!is_dir($savePath)) {
910
            throw VCardException::outputDirectoryNotExists();
911
        }
912
913
        // Add trailing directory separator the save path
914
        if (substr($savePath, -1) != DIRECTORY_SEPARATOR) {
915
            $savePath .= DIRECTORY_SEPARATOR;
916
        }
917
918
        $this->savePath = $savePath;
919
    }
920
921
    /**
922
     * Set property
923
     *
924
     * @param  string $element The element name you want to set, f.e.: name, email, phoneNumber, ...
925
     * @param  string $key
926
     * @param  string $value
927
     * @throws VCardException
928
     */
929
    private function setProperty($element, $key, $value)
930
    {
931
        if (!in_array($element, $this->multiplePropertiesForElementAllowed)
932
            && isset($this->definedElements[$element])
933
        ) {
934
            throw VCardException::elementAlreadyExists($element);
935
        }
936
937
        // we define that we set this element
938
        $this->definedElements[$element] = true;
939
940
        // adding property
941
        $this->properties[] = [
942
            'key' => $key,
943
            'value' => $value
944
        ];
945
    }
946
947
    /**
948
     * Checks if we should return vcard in cal wrapper
949
     *
950
     * @return bool
951
     */
952
    protected function shouldAttachmentBeCal()
953
    {
954
        $browser = $this->getUserAgent();
955
956
        $matches = [];
957
        preg_match('/os (\d+)_(\d+)\s+/', $browser, $matches);
958
        $version = isset($matches[1]) ? ((int)$matches[1]) : 999;
959
960
        return ($version < 8);
961
    }
962
}
963