Completed
Push — master ( 1cb084...1ca2f1 )
by Jeroen
13s
created

VCard::addMediaContent()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 6
Ratio 27.27 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 6
loc 22
rs 8.9197
cc 4
eloc 14
nc 4
nop 3
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
    ];
51
52
    /**
53
     * Properties
54
     *
55
     * @var array
56
     */
57
    private $properties;
58
59
    /**
60
     * Default Charset
61
     *
62
     * @var string
63
     */
64
    public $charset = 'utf-8';
65
66
    /**
67
     * Add address
68
     *
69
     * @param  string [optional] $name
70
     * @param  string [optional] $extended
71
     * @param  string [optional] $street
72
     * @param  string [optional] $city
73
     * @param  string [optional] $region
74
     * @param  string [optional] $zip
75
     * @param  string [optional] $country
76
     * @param  string [optional] $type
77
     *                                     $type may be DOM | INTL | POSTAL | PARCEL | HOME | WORK
78
     *                                     or any combination of these: e.g. "WORK;PARCEL;POSTAL"
79
     * @return $this
80
     */
81
    public function addAddress(
82
        $name = '',
83
        $extended = '',
84
        $street = '',
85
        $city = '',
86
        $region = '',
87
        $zip = '',
88
        $country = '',
89
        $type = 'WORK;POSTAL'
90
    ) {
91
        // init value
92
        $value = $name . ';' . $extended . ';' . $street . ';' . $city . ';' . $region . ';' . $zip . ';' . $country;
93
94
        // set property
95
        $this->setProperty(
96
            'address',
97
            'ADR' . (($type != '') ? ';' . $type : '') . $this->getCharsetString(),
98
            $value
99
        );
100
101
        return $this;
102
    }
103
104
    /**
105
     * Add birthday
106
     *
107
     * @param  string $date Format is YYYY-MM-DD
108
     * @return $this
109
     */
110
    public function addBirthday($date)
111
    {
112
        $this->setProperty(
113
            'birthday',
114
            'BDAY',
115
            $date
116
        );
117
118
        return $this;
119
    }
120
121
    /**
122
     * Add company
123
     *
124
     * @param string $company
125
     * @param string $department
126
     * @return $this
127
     */
128
    public function addCompany($company, $department = '')
129
    {
130
        $this->setProperty(
131
            'company',
132
            'ORG' . $this->getCharsetString(),
133
            $company
134
            . ($department != '' ? ';' . $department : '')
135
        );
136
137
        // if filename is empty, add to filename
138
        if ($this->filename === null) {
139
            $this->setFilename($company);
140
        }
141
142
        return $this;
143
    }
144
145
    /**
146
     * Add email
147
     *
148
     * @param  string $address The e-mail address
149
     * @param  string [optional] $type    The type of the email address
150
     *                                    $type may be  PREF | WORK | HOME
151
     *                                    or any combination of these: e.g. "PREF;WORK"
152
     * @return $this
153
     */
154
    public function addEmail($address, $type = '')
155
    {
156
        $this->setProperty(
157
            'email',
158
            'EMAIL;INTERNET' . (($type != '') ? ';' . $type : ''),
159
            $address
160
        );
161
162
        return $this;
163
    }
164
165
    /**
166
     * Add jobtitle
167
     *
168
     * @param  string $jobtitle The jobtitle for the person.
169
     * @return $this
170
     */
171
    public function addJobtitle($jobtitle)
172
    {
173
        $this->setProperty(
174
            'jobtitle',
175
            'TITLE' . $this->getCharsetString(),
176
            $jobtitle
177
        );
178
179
        return $this;
180
    }
181
182
    /**
183
     * Add role
184
     *
185
     * @param  string $role The role for the person.
186
     * @return $this
187
     */
188
    public function addRole($role)
189
    {
190
        $this->setProperty(
191
            'role',
192
            'ROLE' . $this->getCharsetString(),
193
            $role
194
        );
195
196
        return $this;
197
    }
198
199
    /**
200
     * Add a photo or logo (depending on property name)
201
     *
202
     * @param string $property LOGO|PHOTO
203
     * @param string $url image url or filename
204
     * @param bool $include Do we include the image in our vcard or not?
205
     * @param string $element The name of the element to set
206
     */
207
    private function addMedia($property, $url, $include = true, $element)
208
    {
209
        $mimeType = null;
210
211
        //Is this URL for a remote resource?
212
        if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
213
            $headers = get_headers($url, 1);
214
215
            if (array_key_exists('Content-Type', $headers)) {
216
                $mimeType = $headers['Content-Type'];
217
            }
218
        } else {
219
            //Local file, so inspect it directly
220
            $mimeType = mime_content_type($url);
221
        }
222 View Code Duplication
        if (strpos($mimeType, ';') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
            $mimeType = strstr($mimeType, ';', true);
224
        }
225 View Code Duplication
        if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
            throw VCardException::invalidImage();
227
        }
228
        $fileType = strtoupper(substr($mimeType, 6));
229
230
        if ($include) {
231
            $value = file_get_contents($url);
232
233
            if (!$value) {
234
                throw VCardException::emptyURL();
235
            }
236
237
            $value = base64_encode($value);
238
            $property .= ";ENCODING=b;TYPE=" . $fileType;
239
        } else {
240
            if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
241
                $propertySuffix = ';VALUE=URL';
242
                $propertySuffix .= ';TYPE=' . strtoupper($fileType);
243
244
                $property = $property . $propertySuffix;
245
                $value = $url;
246
            } else {
247
                $value = $url;
248
            }
249
        }
250
251
        $this->setProperty(
252
            $element,
253
            $property,
254
            $value
255
        );
256
    }
257
258
    /**
259
     * Add a photo or logo (depending on property name)
260
     *
261
     * @param string $property LOGO|PHOTO
262
     * @param string $content image content
263
     * @param string $element The name of the element to set
264
     */
265
    private function addMediaContent($property, $content, $element)
266
    {
267
        $finfo = new \finfo();
268
        $mimeType = $finfo->buffer($content, FILEINFO_MIME_TYPE);
269
270 View Code Duplication
        if (strpos($mimeType, ';') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
            $mimeType = strstr($mimeType, ';', true);
272
        }
273 View Code Duplication
        if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
274
            throw VCardException::invalidImage();
275
        }
276
        $fileType = strtoupper(substr($mimeType, 6));
277
278
        $content = base64_encode($content);
279
        $property .= ";ENCODING=b;TYPE=" . $fileType;
280
281
        $this->setProperty(
282
            $element,
283
            $property,
284
            $content
285
        );
286
    }
287
288
    /**
289
     * Add name
290
     *
291
     * @param  string [optional] $lastName
292
     * @param  string [optional] $firstName
293
     * @param  string [optional] $additional
294
     * @param  string [optional] $prefix
295
     * @param  string [optional] $suffix
296
     * @return $this
297
     */
298
    public function addName(
299
        $lastName = '',
300
        $firstName = '',
301
        $additional = '',
302
        $prefix = '',
303
        $suffix = ''
304
    ) {
305
        // define values with non-empty values
306
        $values = array_filter([
307
            $prefix,
308
            $firstName,
309
            $additional,
310
            $lastName,
311
            $suffix,
312
        ]);
313
314
        // define filename
315
        $this->setFilename($values);
316
317
        // set property
318
        $property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix;
319
        $this->setProperty(
320
            'name',
321
            'N' . $this->getCharsetString(),
322
            $property
323
        );
324
325
        // is property FN set?
326
        if (!$this->hasProperty('FN')) {
327
            // set property
328
            $this->setProperty(
329
                'fullname',
330
                'FN' . $this->getCharsetString(),
331
                trim(implode(' ', $values))
332
            );
333
        }
334
335
        return $this;
336
    }
337
338
    /**
339
     * Add note
340
     *
341
     * @param  string $note
342
     * @return $this
343
     */
344
    public function addNote($note)
345
    {
346
        $this->setProperty(
347
            'note',
348
            'NOTE' . $this->getCharsetString(),
349
            $note
350
        );
351
352
        return $this;
353
    }
354
355
    /**
356
     * Add categories
357
     *
358
     * @param array $categories
359
     * @return $this
360
     */
361
    public function addCategories($categories)
362
    {
363
        $this->setProperty(
364
            'categories',
365
            'CATEGORIES' . $this->getCharsetString(),
366
            trim(implode(',', $categories))
367
        );
368
369
        return $this;
370
    }
371
372
    /**
373
     * Add phone number
374
     *
375
     * @param  string $number
376
     * @param  string [optional] $type
377
     *                                   Type may be PREF | WORK | HOME | VOICE | FAX | MSG |
378
     *                                   CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO
379
     *                                   or any senseful combination, e.g. "PREF;WORK;VOICE"
380
     * @return $this
381
     */
382
    public function addPhoneNumber($number, $type = '')
383
    {
384
        $this->setProperty(
385
            'phoneNumber',
386
            'TEL' . (($type != '') ? ';' . $type : ''),
387
            $number
388
        );
389
390
        return $this;
391
    }
392
393
    /**
394
     * Add Logo
395
     *
396
     * @param  string $url image url or filename
397
     * @param  bool $include Include the image in our vcard?
398
     * @return $this
399
     */
400
    public function addLogo($url, $include = true)
401
    {
402
        $this->addMedia(
403
            'LOGO',
404
            $url,
405
            $include,
406
            'logo'
407
        );
408
409
        return $this;
410
    }
411
412
    /**
413
     * Add Logo content
414
     *
415
     * @param  string $content image content
416
     * @return $this
417
     */
418
    public function addLogoContent($content)
419
    {
420
        $this->addMediaContent(
421
            'LOGO',
422
            $content,
423
            'logo'
424
        );
425
426
        return $this;
427
    }
428
429
    /**
430
     * Add Photo
431
     *
432
     * @param  string $url image url or filename
433
     * @param  bool $include Include the image in our vcard?
434
     * @return $this
435
     */
436
    public function addPhoto($url, $include = true)
437
    {
438
        $this->addMedia(
439
            'PHOTO',
440
            $url,
441
            $include,
442
            'photo'
443
        );
444
445
        return $this;
446
    }
447
448
    /**
449
     * Add Photo content
450
     *
451
     * @param  string $content image content
452
     * @return $this
453
     */
454
    public function addPhotoContent($content)
455
    {
456
        $this->addMediaContent(
457
            'PHOTO',
458
            $content,
459
            'photo'
460
        );
461
462
        return $this;
463
    }
464
465
    /**
466
     * Add URL
467
     *
468
     * @param  string $url
469
     * @param  string [optional] $type Type may be WORK | HOME
470
     * @return $this
471
     */
472
    public function addURL($url, $type = '')
473
    {
474
        $this->setProperty(
475
            'url',
476
            'URL' . (($type != '') ? ';' . $type : ''),
477
            $url
478
        );
479
480
        return $this;
481
    }
482
483
    /**
484
     * Build VCard (.vcf)
485
     *
486
     * @return string
487
     */
488
    public function buildVCard()
489
    {
490
        // init string
491
        $string = "BEGIN:VCARD\r\n";
492
        $string .= "VERSION:3.0\r\n";
493
        $string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n";
494
495
        // loop all properties
496
        $properties = $this->getProperties();
497
        foreach ($properties as $property) {
498
            // add to string
499
            $string .= $this->fold($property['key'] . ':' . $this->escape($property['value']) . "\r\n");
500
        }
501
502
        // add to string
503
        $string .= "END:VCARD\r\n";
504
505
        // return
506
        return $string;
507
    }
508
509
    /**
510
     * Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround.
511
     *
512
     * @return string
513
     */
514
    public function buildVCalendar()
515
    {
516
        // init dates
517
        $dtstart = date("Ymd") . "T" . date("Hi") . "00";
518
        $dtend = date("Ymd") . "T" . date("Hi") . "01";
519
520
        // init string
521
        $string = "BEGIN:VCALENDAR\n";
522
        $string .= "VERSION:2.0\n";
523
        $string .= "BEGIN:VEVENT\n";
524
        $string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n";
525
        $string .= "DTEND;TZID=Europe/London:" . $dtend . "\n";
526
        $string .= "SUMMARY:Click attached contact below to save to your contacts\n";
527
        $string .= "DTSTAMP:" . $dtstart . "Z\n";
528
        $string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n";
529
        $string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n";
530
531
        // base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment
532
        $b64vcard = base64_encode($this->buildVCard());
533
534
        // chunk the single long line of b64 text in accordance with RFC2045
535
        // (and the exact line length determined from the original .ics file exported from Apple calendar
536
        $b64mline = chunk_split($b64vcard, 74, "\n");
537
538
        // need to indent all the lines by 1 space for the iphone (yes really?!!)
539
        $b64final = preg_replace('/(.+)/', ' $1', $b64mline);
540
        $string .= $b64final;
541
542
        // output the correctly formatted encoded text
543
        $string .= "END:VEVENT\n";
544
        $string .= "END:VCALENDAR\n";
545
546
        // return
547
        return $string;
548
    }
549
550
    /**
551
     * Returns the browser user agent string.
552
     *
553
     * @return string
554
     */
555
    protected function getUserAgent()
0 ignored issues
show
Coding Style introduced by
getUserAgent uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
556
    {
557
        if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
558
            $browser = strtolower($_SERVER['HTTP_USER_AGENT']);
559
        } else {
560
            $browser = 'unknown';
561
        }
562
563
        return $browser;
564
    }
565
566
    /**
567
     * Decode
568
     *
569
     * @param  string $value The value to decode
570
     * @return string decoded
571
     */
572
    private function decode($value)
573
    {
574
        // convert cyrlic, greek or other caracters to ASCII characters
575
        return Transliterator::transliterate($value);
576
    }
577
578
    /**
579
     * Download a vcard or vcal file to the browser.
580
     */
581
    public function download()
582
    {
583
        // define output
584
        $output = $this->getOutput();
585
586
        foreach ($this->getHeaders(false) as $header) {
587
            header($header);
588
        }
589
590
        // echo the output and it will be a download
591
        echo $output;
592
    }
593
594
    /**
595
     * Fold a line according to RFC2425 section 5.8.1.
596
     *
597
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.1
598
     * @param  string $text
599
     * @return mixed
600
     */
601
    protected function fold($text)
602
    {
603
        if (strlen($text) <= 75) {
604
            return $text;
605
        }
606
607
        // split, wrap and trim trailing separator
608
        return substr(chunk_split($text, 73, "\r\n "), 0, -3);
609
    }
610
611
    /**
612
     * Escape newline characters according to RFC2425 section 5.8.4.
613
     *
614
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.4
615
     * @param  string $text
616
     * @return string
617
     */
618
    protected function escape($text)
619
    {
620
        $text = str_replace("\r\n", "\\n", $text);
621
        $text = str_replace("\n", "\\n", $text);
622
623
        return $text;
624
    }
625
626
    /**
627
     * Get output as string
628
     * @deprecated in the future
629
     *
630
     * @return string
631
     */
632
    public function get()
633
    {
634
        return $this->getOutput();
635
    }
636
637
    /**
638
     * Get charset
639
     *
640
     * @return string
641
     */
642
    public function getCharset()
643
    {
644
        return $this->charset;
645
    }
646
647
    /**
648
     * Get charset string
649
     *
650
     * @return string
651
     */
652
    public function getCharsetString()
653
    {
654
        $charsetString = '';
655
        if ($this->charset == 'utf-8') {
656
            $charsetString = ';CHARSET=' . $this->charset;
657
        }
658
659
        return $charsetString;
660
    }
661
662
    /**
663
     * Get content type
664
     *
665
     * @return string
666
     */
667
    public function getContentType()
668
    {
669
        return ($this->isIOS7()) ?
670
            'text/x-vcalendar' : 'text/x-vcard';
671
    }
672
673
    /**
674
     * Get filename
675
     *
676
     * @return string
677
     */
678
    public function getFilename()
679
    {
680
        if (!$this->filename) {
681
            return 'unknown';
682
        }
683
684
        return $this->filename;
685
    }
686
687
    /**
688
     * Get file extension
689
     *
690
     * @return string
691
     */
692
    public function getFileExtension()
693
    {
694
        return ($this->isIOS7()) ?
695
            'ics' : 'vcf';
696
    }
697
698
    /**
699
     * Get headers
700
     *
701
     * @param  bool $asAssociative
702
     * @return array
703
     */
704
    public function getHeaders($asAssociative)
705
    {
706
        $contentType = $this->getContentType() . '; charset=' . $this->getCharset();
707
        $contentDisposition = 'attachment; filename=' . $this->getFilename() . '.' . $this->getFileExtension();
708
        $contentLength = mb_strlen($this->getOutput(), $this->getCharset());
709
        $connection = 'close';
710
711
        if ((bool)$asAssociative) {
712
            return [
713
                'Content-type' => $contentType,
714
                'Content-Disposition' => $contentDisposition,
715
                'Content-Length' => $contentLength,
716
                'Connection' => $connection,
717
            ];
718
        }
719
720
        return [
721
            'Content-type: ' . $contentType,
722
            'Content-Disposition: ' . $contentDisposition,
723
            'Content-Length: ' . $contentLength,
724
            'Connection: ' . $connection,
725
        ];
726
    }
727
728
    /**
729
     * Get output as string
730
     * iOS devices (and safari < iOS 8 in particular) can not read .vcf (= vcard) files.
731
     * So I build a workaround to build a .ics (= vcalender) file.
732
     *
733
     * @return string
734
     */
735
    public function getOutput()
736
    {
737
        $output = ($this->isIOS7()) ?
738
            $this->buildVCalendar() : $this->buildVCard();
739
740
        return $output;
741
    }
742
743
    /**
744
     * Get properties
745
     *
746
     * @return array
747
     */
748
    public function getProperties()
749
    {
750
        return $this->properties;
751
    }
752
753
    /**
754
     * Has property
755
     *
756
     * @param  string $key
757
     * @return bool
758
     */
759
    public function hasProperty($key)
760
    {
761
        $properties = $this->getProperties();
762
763
        foreach ($properties as $property) {
764
            if ($property['key'] === $key && $property['value'] !== '') {
765
                return true;
766
            }
767
        }
768
769
        return false;
770
    }
771
772
    /**
773
     * Is iOS - Check if the user is using an iOS-device
774
     *
775
     * @return bool
776
     */
777
    public function isIOS()
778
    {
779
        // get user agent
780
        $browser = $this->getUserAgent();
781
782
        return (strpos($browser, 'iphone') || strpos($browser, 'ipod') || strpos($browser, 'ipad'));
783
    }
784
785
    /**
786
     * Is iOS less than 7 (should cal wrapper be returned)
787
     *
788
     * @return bool
789
     */
790
    public function isIOS7()
791
    {
792
        return ($this->isIOS() && $this->shouldAttachmentBeCal());
793
    }
794
795
    /**
796
     * Save to a file
797
     *
798
     * @return void
799
     */
800
    public function save()
801
    {
802
        $file = $this->getFilename() . '.' . $this->getFileExtension();
803
804
        // Add save path if given
805
        if (null !== $this->savePath) {
806
            $file = $this->savePath . $file;
807
        }
808
809
        file_put_contents(
810
            $file,
811
            $this->getOutput()
812
        );
813
    }
814
815
    /**
816
     * Set charset
817
     *
818
     * @param  mixed $charset
819
     * @return void
820
     */
821
    public function setCharset($charset)
822
    {
823
        $this->charset = $charset;
824
    }
825
826
    /**
827
     * Set filename
828
     *
829
     * @param  mixed $value
830
     * @param  bool $overwrite [optional] Default overwrite is true
831
     * @param  string $separator [optional] Default separator is an underscore '_'
832
     * @return void
833
     */
834
    public function setFilename($value, $overwrite = true, $separator = '_')
835
    {
836
        // recast to string if $value is array
837
        if (is_array($value)) {
838
            $value = implode($separator, $value);
839
        }
840
841
        // trim unneeded values
842
        $value = trim($value, $separator);
843
844
        // remove all spaces
845
        $value = preg_replace('/\s+/', $separator, $value);
846
847
        // if value is empty, stop here
848
        if (empty($value)) {
849
            return;
850
        }
851
852
        // decode value + lowercase the string
853
        $value = strtolower($this->decode($value));
854
855
        // urlize this part
856
        $value = Transliterator::urlize($value);
857
858
        // overwrite filename or add to filename using a prefix in between
859
        $this->filename = ($overwrite) ?
860
            $value : $this->filename . $separator . $value;
861
    }
862
863
    /**
864
     * Set the save path directory
865
     *
866
     * @param  string $savePath Save Path
867
     * @throws VCardException
868
     */
869
    public function setSavePath($savePath)
870
    {
871
        if (!is_dir($savePath)) {
872
            throw VCardException::outputDirectoryNotExists();
873
        }
874
875
        // Add trailing directory separator the save path
876
        if (substr($savePath, -1) != DIRECTORY_SEPARATOR) {
877
            $savePath .= DIRECTORY_SEPARATOR;
878
        }
879
880
        $this->savePath = $savePath;
881
    }
882
883
    /**
884
     * Set property
885
     *
886
     * @param  string $element The element name you want to set, f.e.: name, email, phoneNumber, ...
887
     * @param  string $key
888
     * @param  string $value
889
     * @throws VCardException
890
     */
891
    private function setProperty($element, $key, $value)
892
    {
893
        if (!in_array($element, $this->multiplePropertiesForElementAllowed)
894
            && isset($this->definedElements[$element])
895
        ) {
896
            throw VCardException::elementAlreadyExists($element);
897
        }
898
899
        // we define that we set this element
900
        $this->definedElements[$element] = true;
901
902
        // adding property
903
        $this->properties[] = [
904
            'key' => $key,
905
            'value' => $value
906
        ];
907
    }
908
909
    /**
910
     * Checks if we should return vcard in cal wrapper
911
     *
912
     * @return bool
913
     */
914
    protected function shouldAttachmentBeCal()
915
    {
916
        $browser = $this->getUserAgent();
917
918
        $matches = [];
919
        preg_match('/os (\d+)_(\d+)\s+/', $browser, $matches);
920
        $version = isset($matches[1]) ? ((int)$matches[1]) : 999;
921
922
        return ($version < 8);
923
    }
924
}
925