Passed
Push — master ( 1ca2f1...8be1cb )
by Jeroen
26:34
created

VCard::getCharsetString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 2
b 0
f 0
nc 2
nop 0
dl 0
loc 8
rs 9.4285
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);
0 ignored issues
show
Bug introduced by
The call to get_headers() has too few arguments starting with context. ( Ignorable by Annotation )

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

213
            $headers = /** @scrutinizer ignore-call */ get_headers($url, 1);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
214
215
            if (array_key_exists('Content-Type', $headers)) {
216
                $mimeType = $headers['Content-Type'];
217
                if (is_array($mimeType)) {
218
                    $mimeType = end($mimeType);
219
                }
220
            }
221
        } else {
222
            //Local file, so inspect it directly
223
            $mimeType = mime_content_type($url);
224
        }
225 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...
226
            $mimeType = strstr($mimeType, ';', true);
227
        }
228 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...
229
            throw VCardException::invalidImage();
230
        }
231
        $fileType = strtoupper(substr($mimeType, 6));
232
233
        if ($include) {
234
            $value = file_get_contents($url);
235
236
            if (!$value) {
237
                throw VCardException::emptyURL();
238
            }
239
240
            $value = base64_encode($value);
241
            $property .= ";ENCODING=b;TYPE=" . $fileType;
242
        } else {
243
            if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
244
                $propertySuffix = ';VALUE=URL';
245
                $propertySuffix .= ';TYPE=' . strtoupper($fileType);
246
247
                $property = $property . $propertySuffix;
248
                $value = $url;
249
            } else {
250
                $value = $url;
251
            }
252
        }
253
254
        $this->setProperty(
255
            $element,
256
            $property,
257
            $value
258
        );
259
    }
260
261
    /**
262
     * Add a photo or logo (depending on property name)
263
     *
264
     * @param string $property LOGO|PHOTO
265
     * @param string $content image content
266
     * @param string $element The name of the element to set
267
     */
268
    private function addMediaContent($property, $content, $element)
269
    {
270
        $finfo = new \finfo();
0 ignored issues
show
Bug introduced by
The call to finfo::finfo() has too few arguments starting with options. ( Ignorable by Annotation )

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

270
        $finfo = /** @scrutinizer ignore-call */ new \finfo();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
271
        $mimeType = $finfo->buffer($content, FILEINFO_MIME_TYPE);
272
273 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...
274
            $mimeType = strstr($mimeType, ';', true);
275
        }
276 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...
277
            throw VCardException::invalidImage();
278
        }
279
        $fileType = strtoupper(substr($mimeType, 6));
280
281
        $content = base64_encode($content);
282
        $property .= ";ENCODING=b;TYPE=" . $fileType;
283
284
        $this->setProperty(
285
            $element,
286
            $property,
287
            $content
288
        );
289
    }
290
291
    /**
292
     * Add name
293
     *
294
     * @param  string [optional] $lastName
295
     * @param  string [optional] $firstName
296
     * @param  string [optional] $additional
297
     * @param  string [optional] $prefix
298
     * @param  string [optional] $suffix
299
     * @return $this
300
     */
301
    public function addName(
302
        $lastName = '',
303
        $firstName = '',
304
        $additional = '',
305
        $prefix = '',
306
        $suffix = ''
307
    ) {
308
        // define values with non-empty values
309
        $values = array_filter([
310
            $prefix,
311
            $firstName,
312
            $additional,
313
            $lastName,
314
            $suffix,
315
        ]);
316
317
        // define filename
318
        $this->setFilename($values);
319
320
        // set property
321
        $property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix;
322
        $this->setProperty(
323
            'name',
324
            'N' . $this->getCharsetString(),
325
            $property
326
        );
327
328
        // is property FN set?
329
        if (!$this->hasProperty('FN')) {
330
            // set property
331
            $this->setProperty(
332
                'fullname',
333
                'FN' . $this->getCharsetString(),
334
                trim(implode(' ', $values))
335
            );
336
        }
337
338
        return $this;
339
    }
340
341
    /**
342
     * Add note
343
     *
344
     * @param  string $note
345
     * @return $this
346
     */
347
    public function addNote($note)
348
    {
349
        $this->setProperty(
350
            'note',
351
            'NOTE' . $this->getCharsetString(),
352
            $note
353
        );
354
355
        return $this;
356
    }
357
358
    /**
359
     * Add categories
360
     *
361
     * @param array $categories
362
     * @return $this
363
     */
364
    public function addCategories($categories)
365
    {
366
        $this->setProperty(
367
            'categories',
368
            'CATEGORIES' . $this->getCharsetString(),
369
            trim(implode(',', $categories))
370
        );
371
372
        return $this;
373
    }
374
375
    /**
376
     * Add phone number
377
     *
378
     * @param  string $number
379
     * @param  string [optional] $type
380
     *                                   Type may be PREF | WORK | HOME | VOICE | FAX | MSG |
381
     *                                   CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO
382
     *                                   or any senseful combination, e.g. "PREF;WORK;VOICE"
383
     * @return $this
384
     */
385
    public function addPhoneNumber($number, $type = '')
386
    {
387
        $this->setProperty(
388
            'phoneNumber',
389
            'TEL' . (($type != '') ? ';' . $type : ''),
390
            $number
391
        );
392
393
        return $this;
394
    }
395
396
    /**
397
     * Add Logo
398
     *
399
     * @param  string $url image url or filename
400
     * @param  bool $include Include the image in our vcard?
401
     * @return $this
402
     */
403
    public function addLogo($url, $include = true)
404
    {
405
        $this->addMedia(
406
            'LOGO',
407
            $url,
408
            $include,
409
            'logo'
410
        );
411
412
        return $this;
413
    }
414
415
    /**
416
     * Add Logo content
417
     *
418
     * @param  string $content image content
419
     * @return $this
420
     */
421
    public function addLogoContent($content)
422
    {
423
        $this->addMediaContent(
424
            'LOGO',
425
            $content,
426
            'logo'
427
        );
428
429
        return $this;
430
    }
431
432
    /**
433
     * Add Photo
434
     *
435
     * @param  string $url image url or filename
436
     * @param  bool $include Include the image in our vcard?
437
     * @return $this
438
     */
439
    public function addPhoto($url, $include = true)
440
    {
441
        $this->addMedia(
442
            'PHOTO',
443
            $url,
444
            $include,
445
            'photo'
446
        );
447
448
        return $this;
449
    }
450
451
    /**
452
     * Add Photo content
453
     *
454
     * @param  string $content image content
455
     * @return $this
456
     */
457
    public function addPhotoContent($content)
458
    {
459
        $this->addMediaContent(
460
            'PHOTO',
461
            $content,
462
            'photo'
463
        );
464
465
        return $this;
466
    }
467
468
    /**
469
     * Add URL
470
     *
471
     * @param  string $url
472
     * @param  string [optional] $type Type may be WORK | HOME
473
     * @return $this
474
     */
475
    public function addURL($url, $type = '')
476
    {
477
        $this->setProperty(
478
            'url',
479
            'URL' . (($type != '') ? ';' . $type : ''),
480
            $url
481
        );
482
483
        return $this;
484
    }
485
486
    /**
487
     * Build VCard (.vcf)
488
     *
489
     * @return string
490
     */
491
    public function buildVCard()
492
    {
493
        // init string
494
        $string = "BEGIN:VCARD\r\n";
495
        $string .= "VERSION:3.0\r\n";
496
        $string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n";
497
498
        // loop all properties
499
        $properties = $this->getProperties();
500
        foreach ($properties as $property) {
501
            // add to string
502
            $string .= $this->fold($property['key'] . ':' . $this->escape($property['value']) . "\r\n");
503
        }
504
505
        // add to string
506
        $string .= "END:VCARD\r\n";
507
508
        // return
509
        return $string;
510
    }
511
512
    /**
513
     * Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround.
514
     *
515
     * @return string
516
     */
517
    public function buildVCalendar()
518
    {
519
        // init dates
520
        $dtstart = date("Ymd") . "T" . date("Hi") . "00";
521
        $dtend = date("Ymd") . "T" . date("Hi") . "01";
522
523
        // init string
524
        $string = "BEGIN:VCALENDAR\n";
525
        $string .= "VERSION:2.0\n";
526
        $string .= "BEGIN:VEVENT\n";
527
        $string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n";
528
        $string .= "DTEND;TZID=Europe/London:" . $dtend . "\n";
529
        $string .= "SUMMARY:Click attached contact below to save to your contacts\n";
530
        $string .= "DTSTAMP:" . $dtstart . "Z\n";
531
        $string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n";
532
        $string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n";
533
534
        // base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment
535
        $b64vcard = base64_encode($this->buildVCard());
536
537
        // chunk the single long line of b64 text in accordance with RFC2045
538
        // (and the exact line length determined from the original .ics file exported from Apple calendar
539
        $b64mline = chunk_split($b64vcard, 74, "\n");
540
541
        // need to indent all the lines by 1 space for the iphone (yes really?!!)
542
        $b64final = preg_replace('/(.+)/', ' $1', $b64mline);
543
        $string .= $b64final;
544
545
        // output the correctly formatted encoded text
546
        $string .= "END:VEVENT\n";
547
        $string .= "END:VCALENDAR\n";
548
549
        // return
550
        return $string;
551
    }
552
553
    /**
554
     * Returns the browser user agent string.
555
     *
556
     * @return string
557
     */
558
    protected function getUserAgent()
559
    {
560
        if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
561
            $browser = strtolower($_SERVER['HTTP_USER_AGENT']);
562
        } else {
563
            $browser = 'unknown';
564
        }
565
566
        return $browser;
567
    }
568
569
    /**
570
     * Decode
571
     *
572
     * @param  string $value The value to decode
573
     * @return string decoded
574
     */
575
    private function decode($value)
576
    {
577
        // convert cyrlic, greek or other caracters to ASCII characters
578
        return Transliterator::transliterate($value);
579
    }
580
581
    /**
582
     * Download a vcard or vcal file to the browser.
583
     */
584
    public function download()
585
    {
586
        // define output
587
        $output = $this->getOutput();
588
589
        foreach ($this->getHeaders(false) as $header) {
590
            header($header);
591
        }
592
593
        // echo the output and it will be a download
594
        echo $output;
595
    }
596
597
    /**
598
     * Fold a line according to RFC2425 section 5.8.1.
599
     *
600
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.1
601
     * @param  string $text
602
     * @return mixed
603
     */
604
    protected function fold($text)
605
    {
606
        if (strlen($text) <= 75) {
607
            return $text;
608
        }
609
610
        // split, wrap and trim trailing separator
611
        return substr(chunk_split($text, 73, "\r\n "), 0, -3);
612
    }
613
614
    /**
615
     * Escape newline characters according to RFC2425 section 5.8.4.
616
     *
617
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.4
618
     * @param  string $text
619
     * @return string
620
     */
621
    protected function escape($text)
622
    {
623
        $text = str_replace("\r\n", "\\n", $text);
624
        $text = str_replace("\n", "\\n", $text);
625
626
        return $text;
627
    }
628
629
    /**
630
     * Get output as string
631
     * @deprecated in the future
632
     *
633
     * @return string
634
     */
635
    public function get()
636
    {
637
        return $this->getOutput();
638
    }
639
640
    /**
641
     * Get charset
642
     *
643
     * @return string
644
     */
645
    public function getCharset()
646
    {
647
        return $this->charset;
648
    }
649
650
    /**
651
     * Get charset string
652
     *
653
     * @return string
654
     */
655
    public function getCharsetString()
656
    {
657
        $charsetString = '';
658
        if ($this->charset == 'utf-8') {
659
            $charsetString = ';CHARSET=' . $this->charset;
660
        }
661
662
        return $charsetString;
663
    }
664
665
    /**
666
     * Get content type
667
     *
668
     * @return string
669
     */
670
    public function getContentType()
671
    {
672
        return ($this->isIOS7()) ?
673
            'text/x-vcalendar' : 'text/x-vcard';
674
    }
675
676
    /**
677
     * Get filename
678
     *
679
     * @return string
680
     */
681
    public function getFilename()
682
    {
683
        if (!$this->filename) {
684
            return 'unknown';
685
        }
686
687
        return $this->filename;
688
    }
689
690
    /**
691
     * Get file extension
692
     *
693
     * @return string
694
     */
695
    public function getFileExtension()
696
    {
697
        return ($this->isIOS7()) ?
698
            'ics' : 'vcf';
699
    }
700
701
    /**
702
     * Get headers
703
     *
704
     * @param  bool $asAssociative
705
     * @return array
706
     */
707
    public function getHeaders($asAssociative)
708
    {
709
        $contentType = $this->getContentType() . '; charset=' . $this->getCharset();
710
        $contentDisposition = 'attachment; filename=' . $this->getFilename() . '.' . $this->getFileExtension();
711
        $contentLength = mb_strlen($this->getOutput(), $this->getCharset());
712
        $connection = 'close';
713
714
        if ((bool)$asAssociative) {
715
            return [
716
                'Content-type' => $contentType,
717
                'Content-Disposition' => $contentDisposition,
718
                'Content-Length' => $contentLength,
719
                'Connection' => $connection,
720
            ];
721
        }
722
723
        return [
724
            'Content-type: ' . $contentType,
725
            'Content-Disposition: ' . $contentDisposition,
726
            'Content-Length: ' . $contentLength,
727
            'Connection: ' . $connection,
728
        ];
729
    }
730
731
    /**
732
     * Get output as string
733
     * iOS devices (and safari < iOS 8 in particular) can not read .vcf (= vcard) files.
734
     * So I build a workaround to build a .ics (= vcalender) file.
735
     *
736
     * @return string
737
     */
738
    public function getOutput()
739
    {
740
        $output = ($this->isIOS7()) ?
741
            $this->buildVCalendar() : $this->buildVCard();
742
743
        return $output;
744
    }
745
746
    /**
747
     * Get properties
748
     *
749
     * @return array
750
     */
751
    public function getProperties()
752
    {
753
        return $this->properties;
754
    }
755
756
    /**
757
     * Has property
758
     *
759
     * @param  string $key
760
     * @return bool
761
     */
762
    public function hasProperty($key)
763
    {
764
        $properties = $this->getProperties();
765
766
        foreach ($properties as $property) {
767
            if ($property['key'] === $key && $property['value'] !== '') {
768
                return true;
769
            }
770
        }
771
772
        return false;
773
    }
774
775
    /**
776
     * Is iOS - Check if the user is using an iOS-device
777
     *
778
     * @return bool
779
     */
780
    public function isIOS()
781
    {
782
        // get user agent
783
        $browser = $this->getUserAgent();
784
785
        return (strpos($browser, 'iphone') || strpos($browser, 'ipod') || strpos($browser, 'ipad'));
786
    }
787
788
    /**
789
     * Is iOS less than 7 (should cal wrapper be returned)
790
     *
791
     * @return bool
792
     */
793
    public function isIOS7()
794
    {
795
        return ($this->isIOS() && $this->shouldAttachmentBeCal());
796
    }
797
798
    /**
799
     * Save to a file
800
     *
801
     * @return void
802
     */
803
    public function save()
804
    {
805
        $file = $this->getFilename() . '.' . $this->getFileExtension();
806
807
        // Add save path if given
808
        if (null !== $this->savePath) {
809
            $file = $this->savePath . $file;
810
        }
811
812
        file_put_contents(
813
            $file,
814
            $this->getOutput()
815
        );
816
    }
817
818
    /**
819
     * Set charset
820
     *
821
     * @param  mixed $charset
822
     * @return void
823
     */
824
    public function setCharset($charset)
825
    {
826
        $this->charset = $charset;
827
    }
828
829
    /**
830
     * Set filename
831
     *
832
     * @param  mixed $value
833
     * @param  bool $overwrite [optional] Default overwrite is true
834
     * @param  string $separator [optional] Default separator is an underscore '_'
835
     * @return void
836
     */
837
    public function setFilename($value, $overwrite = true, $separator = '_')
838
    {
839
        // recast to string if $value is array
840
        if (is_array($value)) {
841
            $value = implode($separator, $value);
842
        }
843
844
        // trim unneeded values
845
        $value = trim($value, $separator);
846
847
        // remove all spaces
848
        $value = preg_replace('/\s+/', $separator, $value);
849
850
        // if value is empty, stop here
851
        if (empty($value)) {
852
            return;
853
        }
854
855
        // decode value + lowercase the string
856
        $value = strtolower($this->decode($value));
857
858
        // urlize this part
859
        $value = Transliterator::urlize($value);
860
861
        // overwrite filename or add to filename using a prefix in between
862
        $this->filename = ($overwrite) ?
863
            $value : $this->filename . $separator . $value;
864
    }
865
866
    /**
867
     * Set the save path directory
868
     *
869
     * @param  string $savePath Save Path
870
     * @throws VCardException
871
     */
872
    public function setSavePath($savePath)
873
    {
874
        if (!is_dir($savePath)) {
875
            throw VCardException::outputDirectoryNotExists();
876
        }
877
878
        // Add trailing directory separator the save path
879
        if (substr($savePath, -1) != DIRECTORY_SEPARATOR) {
880
            $savePath .= DIRECTORY_SEPARATOR;
881
        }
882
883
        $this->savePath = $savePath;
884
    }
885
886
    /**
887
     * Set property
888
     *
889
     * @param  string $element The element name you want to set, f.e.: name, email, phoneNumber, ...
890
     * @param  string $key
891
     * @param  string $value
892
     * @throws VCardException
893
     */
894
    private function setProperty($element, $key, $value)
895
    {
896
        if (!in_array($element, $this->multiplePropertiesForElementAllowed)
897
            && isset($this->definedElements[$element])
898
        ) {
899
            throw VCardException::elementAlreadyExists($element);
900
        }
901
902
        // we define that we set this element
903
        $this->definedElements[$element] = true;
904
905
        // adding property
906
        $this->properties[] = [
907
            'key' => $key,
908
            'value' => $value
909
        ];
910
    }
911
912
    /**
913
     * Checks if we should return vcard in cal wrapper
914
     *
915
     * @return bool
916
     */
917
    protected function shouldAttachmentBeCal()
918
    {
919
        $browser = $this->getUserAgent();
920
921
        $matches = [];
922
        preg_match('/os (\d+)_(\d+)\s+/', $browser, $matches);
923
        $version = isset($matches[1]) ? ((int)$matches[1]) : 999;
924
925
        return ($version < 8);
926
    }
927
}
928