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

VCard::addAddress()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 21
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 1
nop 8
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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