Completed
Pull Request — master (#136)
by
unknown
02:41
created

VCard::addRole()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace JeroenDesloovere\VCard;
4
5
/*
6
 * Modified version by HTMLgraphic
7
 *
8
 * This file is part of the VCard PHP Class from Jeroen Desloovere.
9
 *
10
 * For the full copyright and license information, please view the license
11
 * file that was distributed with this source code.
12
 */
13
14
use Behat\Transliterator\Transliterator;
15
16
/**
17
 * VCard PHP Class to generate .vcard files and save them to a file or output as a download.
18
 */
19
class VCard
20
{
21
    /**
22
     * definedElements
23
     *
24
     * @var array
25
     */
26
    private $definedElements;
27
28
    /**
29
     * Filename
30
     *
31
     * @var string
32
     */
33
    private $filename;
34
35
    /**
36
     * Save Path
37
     *
38
     * @var string
39
     */
40
    private $savePath = null;
41
42
    /**
43
     * Multiple properties for element allowed
44
     *
45
     * @var array
46
     */
47
    private $multiplePropertiesForElementAllowed = [
48
        'email',
49
        'address',
50
        'phoneNumber',
51
        'url',
52
        'label',
53
        'socialmedia'
54
    ];
55
56
    /**
57
     * Properties
58
     *
59
     * @var array
60
     */
61
    private $properties;
62
63
    /**
64
     * Default Charset
65
     *
66
     * @var string
67
     */
68
    public $charset = 'utf-8';
69
70
    /**
71
     * Add address
72
     *
73
     * @param  string [optional] $name
74
     * @param  string [optional] $extended
75
     * @param  string [optional] $street
76
     * @param  string [optional] $city
77
     * @param  string [optional] $region
78
     * @param  string [optional] $zip
79
     * @param  string [optional] $country
80
     * @param  string [optional] $type
81
     *                                     $type may be DOM | INTL | POSTAL | PARCEL | HOME | WORK
82
     *                                     or any combination of these: e.g. "WORK;PARCEL;POSTAL"
83
     * @return $this
84
     */
85
    public function addAddress(
86
        $name = '',
87
        $extended = '',
88
        $street = '',
89
        $city = '',
90
        $region = '',
91
        $zip = '',
92
        $country = '',
93
        $type = 'WORK;POSTAL'
94
    ) {
95
        // init value
96
        $value = $name . ';' . $extended . ';' . $street . ';' . $city . ';' . $region . ';' . $zip . ';' . $country;
97
98
        // set property
99
        $this->setProperty(
100
            'address',
101
            'ADR' . (($type != '') ? ';' . $type : '') . $this->getCharsetString(),
102
            $value
103
        );
104
105
        return $this;
106
    }
107
108
    /**
109
     * Add birthday
110
     *
111
     * @param  string $date Format is YYYY-MM-DD
112
     * @return $this
113
     */
114
    public function addBirthday($date)
115
    {
116
        $this->setProperty(
117
            'birthday',
118
            'BDAY',
119
            $date
120
        );
121
122
        return $this;
123
    }
124
125
    /**
126
     * Add company
127
     *
128
     * @param string $company
129
     * @param string $department
130
     * @return $this
131
     */
132
    public function addCompany($company, $department = '')
133
    {
134
        $this->setProperty(
135
            'company',
136
            'ORG' . $this->getCharsetString(),
137
            $company
138
            . ($department != '' ? ';' . $department : '')
139
        );
140
141
        // if filename is empty, add to filename
142
        if ($this->filename === null) {
143
            $this->setFilename($company);
144
        }
145
146
        return $this;
147
    }
148
149
    /**
150
     * Add email
151
     *
152
     * @param  string $address The e-mail address
153
     * @param  string [optional] $type    The type of the email address
154
     *                                    $type may be  PREF | WORK | HOME
155
     *                                    or any combination of these: e.g. "PREF;WORK"
156
     * @return $this
157
     */
158
    public function addEmail($address, $type = '')
159
    {
160
        $this->setProperty(
161
            'email',
162
            'EMAIL;INTERNET' . (($type != '') ? ';' . $type : ''),
163
            $address
164
        );
165
166
        return $this;
167
    }
168
169
    /**
170
     * Add jobtitle
171
     *
172
     * @param  string $jobtitle The jobtitle for the person.
173
     * @return $this
174
     */
175
    public function addJobtitle($jobtitle)
176
    {
177
        $this->setProperty(
178
            'jobtitle',
179
            'TITLE' . $this->getCharsetString(),
180
            $jobtitle
181
        );
182
183
        return $this;
184
    }
185
186
    /**
187
     * Add a label
188
     *
189
     * @param string $label
190
     * @param string $type
191
     *
192
     * @return $this
193
     */
194
    public function addLabel($label, $type = '')
195
    {
196
        $this->setProperty(
197
            'label',
198
            'LABEL' . ($type !== '' ? ';' . $type : ''),
199
            $label
200
        );
201
202
        return $this;
203
    }
204
205
    /**
206
     * Add role
207
     *
208
     * @param  string $role The role for the person.
209
     * @return $this
210
     */
211
    public function addRole($role)
212
    {
213
        $this->setProperty(
214
            'role',
215
            'ROLE' . $this->getCharsetString(),
216
            $role
217
        );
218
219
        return $this;
220
    }
221
222
    /**
223
     * Add socialMedia
224
     *
225
     * @param  string $type The type of social media.
226
     * @param  string $addr The address of the social media platform.
227
     * @return $this
228
     */
229
    public function addSocial($type = '', $url)
230
    {
231
232
        $this->setProperty(
233
            'socialmedia',
234
            'X-SOCIALPROFILE;type=' . (($type != '') ? $type : '' ),
235
            $url
236
        );
237
238
        return $this;
239
    }
240
241
    /**
242
     * Add a photo or logo (depending on property name)
243
     *
244
     * @param string $property LOGO|PHOTO
245
     * @param string $url image url or filename
246
     * @param bool $include Do we include the image in our vcard or not?
247
     * @param string $element The name of the element to set
248
     * @throws VCardException
249
     */
250
    private function addMedia($property, $url, $include = true, $element)
251
    {
252
        $mimeType = null;
253
254
        //Is this URL for a remote resource?
255
        if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
256
            $headers = get_headers($url, 1);
257
258
            if (array_key_exists('Content-Type', $headers)) {
259
                $mimeType = $headers['Content-Type'];
260
                if (is_array($mimeType)) {
261
                    $mimeType = end($mimeType);
262
                }
263
            }
264
        } else {
265
            //Local file, so inspect it directly
266
            $mimeType = mime_content_type($url);
267
        }
268
        if (strpos($mimeType, ';') !== false) {
269
            $mimeType = strstr($mimeType, ';', true);
270
        }
271
        if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') {
272
            throw VCardException::invalidImage();
273
        }
274
        $fileType = strtoupper(substr($mimeType, 6));
275
276
        if ($include) {
277
            if ((bool) ini_get('allow_url_fopen') === true) {
278
                $value = file_get_contents($url);
279
            } else {
280
                $curl = curl_init();
281
                curl_setopt($curl, CURLOPT_URL, $url);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, 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

281
                curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_URL, $url);
Loading history...
282
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
283
                $value = curl_exec($curl);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, 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

283
                $value = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
284
                curl_close($curl);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_close() does only seem to accept resource, 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

284
                curl_close(/** @scrutinizer ignore-type */ $curl);
Loading history...
285
            }
286
287
            if (!$value) {
288
                throw VCardException::emptyURL();
289
            }
290
291
            $value = base64_encode($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type true; however, parameter $data of base64_encode() does only seem to accept string, 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

291
            $value = base64_encode(/** @scrutinizer ignore-type */ $value);
Loading history...
292
            $property .= ";ENCODING=b;TYPE=" . $fileType;
293
        } else {
294
            if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
295
                $propertySuffix = ';VALUE=URL';
296
                $propertySuffix .= ';TYPE=' . strtoupper($fileType);
297
298
                $property = $property . $propertySuffix;
299
                $value = $url;
300
            } else {
301
                $value = $url;
302
            }
303
        }
304
305
        $this->setProperty(
306
            $element,
307
            $property,
308
            $value
309
        );
310
    }
311
312
    /**
313
     * Add a photo or logo (depending on property name)
314
     *
315
     * @param string $property LOGO|PHOTO
316
     * @param string $content image content
317
     * @param string $element The name of the element to set
318
     */
319
    private function addMediaContent($property, $content, $element)
320
    {
321
        $finfo = new \finfo();
322
        $mimeType = $finfo->buffer($content, FILEINFO_MIME_TYPE);
323
324
        if (strpos($mimeType, ';') !== false) {
325
            $mimeType = strstr($mimeType, ';', true);
326
        }
327
        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...
328
            throw VCardException::invalidImage();
329
        }
330
        $fileType = strtoupper(substr($mimeType, 6));
331
332
        $content = base64_encode($content);
333
        $property .= ";ENCODING=b;TYPE=" . $fileType;
334
335
        $this->setProperty(
336
            $element,
337
            $property,
338
            $content
339
        );
340
    }
341
342
    /**
343
     * Add name
344
     *
345
     * @param  string [optional] $lastName
346
     * @param  string [optional] $firstName
347
     * @param  string [optional] $additional
348
     * @param  string [optional] $prefix
349
     * @param  string [optional] $suffix
350
     * @return $this
351
     */
352
    public function addName(
353
        $lastName = '',
354
        $firstName = '',
355
        $additional = '',
356
        $prefix = '',
357
        $suffix = ''
358
    ) {
359
        // define values with non-empty values
360
        $values = array_filter([
361
            $prefix,
362
            $firstName,
363
            $additional,
364
            $lastName,
365
            $suffix,
366
        ]);
367
368
        // define filename
369
        $this->setFilename($values);
370
371
        // set property
372
        $property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix;
373
        $this->setProperty(
374
            'name',
375
            'N' . $this->getCharsetString(),
376
            $property
377
        );
378
379
        // is property FN set?
380
        if (!$this->hasProperty('FN')) {
381
            // set property
382
            $this->setProperty(
383
                'fullname',
384
                'FN' . $this->getCharsetString(),
385
                trim(implode(' ', $values))
386
            );
387
        }
388
389
        return $this;
390
    }
391
392
    /**
393
     * Add note
394
     *
395
     * @param  string $note
396
     * @return $this
397
     */
398
    public function addNote($note)
399
    {
400
        $this->setProperty(
401
            'note',
402
            'NOTE' . $this->getCharsetString(),
403
            $note
404
        );
405
406
        return $this;
407
    }
408
409
    /**
410
     * Add categories
411
     *
412
     * @param array $categories
413
     * @return $this
414
     */
415
    public function addCategories($categories)
416
    {
417
        $this->setProperty(
418
            'categories',
419
            'CATEGORIES' . $this->getCharsetString(),
420
            trim(implode(',', $categories))
421
        );
422
423
        return $this;
424
    }
425
426
    /**
427
     * Add phone number
428
     *
429
     * @param  string $number
430
     * @param  string [optional] $type
431
     *                                   Type may be PREF | WORK | HOME | VOICE | FAX | MSG |
432
     *                                   CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO
433
     *                                   or any senseful combination, e.g. "PREF;WORK;VOICE"
434
     * @return $this
435
     */
436
    public function addPhoneNumber($number, $type = '')
437
    {
438
        $this->setProperty(
439
            'phoneNumber',
440
            'TEL' . (($type != '') ? ';' . $type : ''),
441
            $number
442
        );
443
444
        return $this;
445
    }
446
447
    /**
448
     * Add Logo
449
     *
450
     * @param  string $url image url or filename
451
     * @param  bool $include Include the image in our vcard?
452
     * @return $this
453
     */
454
    public function addLogo($url, $include = true)
455
    {
456
        $this->addMedia(
457
            'LOGO',
458
            $url,
459
            $include,
460
            'logo'
461
        );
462
463
        return $this;
464
    }
465
466
    /**
467
     * Add Logo content
468
     *
469
     * @param  string $content image content
470
     * @return $this
471
     */
472
    public function addLogoContent($content)
473
    {
474
        $this->addMediaContent(
475
            'LOGO',
476
            $content,
477
            'logo'
478
        );
479
480
        return $this;
481
    }
482
483
    /**
484
     * Add Photo
485
     *
486
     * @param  string $url image url or filename
487
     * @param  bool $include Include the image in our vcard?
488
     * @return $this
489
     */
490
    public function addPhoto($url, $include = true)
491
    {
492
        $this->addMedia(
493
            'PHOTO',
494
            $url,
495
            $include,
496
            'photo'
497
        );
498
499
        return $this;
500
    }
501
502
    /**
503
     * Add Photo content
504
     *
505
     * @param  string $content image content
506
     * @return $this
507
     */
508
    public function addPhotoContent($content)
509
    {
510
        $this->addMediaContent(
511
            'PHOTO',
512
            $content,
513
            'photo'
514
        );
515
516
        return $this;
517
    }
518
519
    /**
520
     * Add URL
521
     *
522
     * @param  string $url
523
     * @param  string [optional] $type Type may be WORK | HOME
524
     * @return $this
525
     */
526
    public function addURL($url, $type = '')
527
    {
528
        $this->setProperty(
529
            'url',
530
            'URL' . (($type != '') ? ';' . $type : ''),
531
            $url
532
        );
533
534
        return $this;
535
    }
536
537
    /**
538
     * Build VCard (.vcf)
539
     *
540
     * @return string
541
     */
542
    public function buildVCard()
543
    {
544
        // init string
545
        $string = "BEGIN:VCARD\r\n";
546
        $string .= "VERSION:3.0\r\n";
547
        $string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n";
548
549
        // loop all properties
550
        $properties = $this->getProperties();
551
        foreach ($properties as $property) {
552
            // add to string
553
            $string .= $this->fold($property['key'] . ':' . $this->escape($property['value']) . "\r\n");
554
        }
555
556
        // add to string
557
        $string .= "END:VCARD\r\n";
558
559
        // return
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
560
        return $string;
561
    }
562
563
    /**
564
     * Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround.
565
     *
566
     * @return string
567
     */
568
    public function buildVCalendar()
569
    {
570
        // init dates
571
        $dtstart = date("Ymd") . "T" . date("Hi") . "00";
572
        $dtend = date("Ymd") . "T" . date("Hi") . "01";
573
574
        // init string
575
        $string = "BEGIN:VCALENDAR\n";
576
        $string .= "VERSION:2.0\n";
577
        $string .= "BEGIN:VEVENT\n";
578
        $string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n";
579
        $string .= "DTEND;TZID=Europe/London:" . $dtend . "\n";
580
        $string .= "SUMMARY:Click attached contact below to save to your contacts\n";
581
        $string .= "DTSTAMP:" . $dtstart . "Z\n";
582
        $string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n";
583
        $string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n";
584
585
        // base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment
586
        $b64vcard = base64_encode($this->buildVCard());
587
588
        // chunk the single long line of b64 text in accordance with RFC2045
589
        // (and the exact line length determined from the original .ics file exported from Apple calendar
590
        $b64mline = chunk_split($b64vcard, 74, "\n");
591
592
        // need to indent all the lines by 1 space for the iphone (yes really?!!)
593
        $b64final = preg_replace('/(.+)/', ' $1', $b64mline);
594
        $string .= $b64final;
595
596
        // output the correctly formatted encoded text
597
        $string .= "END:VEVENT\n";
598
        $string .= "END:VCALENDAR\n";
599
600
        // return
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
601
        return $string;
602
    }
603
604
    /**
605
     * Returns the browser user agent string.
606
     *
607
     * @return string
608
     */
609
    protected function getUserAgent()
610
    {
611
        if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
612
            $browser = strtolower($_SERVER['HTTP_USER_AGENT']);
613
        } else {
614
            $browser = 'unknown';
615
        }
616
617
        return $browser;
618
    }
619
620
    /**
621
     * Decode
622
     *
623
     * @param  string $value The value to decode
624
     * @return string decoded
625
     */
626
    private function decode($value)
627
    {
628
        // convert cyrlic, greek or other caracters to ASCII characters
629
        return Transliterator::transliterate($value);
630
    }
631
632
    /**
633
     * Download a vcard or vcal file to the browser.
634
     */
635
    public function download()
636
    {
637
        // define output
638
        $output = $this->getOutput();
639
640
        foreach ($this->getHeaders(false) as $header) {
641
            header($header);
642
        }
643
644
        // echo the output and it will be a download
645
        echo $output;
646
    }
647
648
    /**
649
     * Fold a line according to RFC2425 section 5.8.1.
650
     *
651
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.1
652
     * @param  string $text
653
     * @return mixed
654
     */
655
    protected function fold($text)
656
    {
657
        if (strlen($text) <= 75) {
658
            return $text;
659
        }
660
661
        // split, wrap and trim trailing separator
662
        return substr($this->chunk_split_unicode($text, 75, "\r\n "), 0, -3);
663
    }
664
665
    /**
666
     * multibyte word chunk split
667
     * @link http://php.net/manual/en/function.chunk-split.php#107711
668
     *
669
     * @param  string  $body     The string to be chunked.
670
     * @param  integer $chunklen The chunk length.
671
     * @param  string  $end      The line ending sequence.
672
     * @return string            Chunked string
673
     */
674
    protected function chunk_split_unicode($body, $chunklen = 76, $end = "\r\n")
675
    {
676
        $array = array_chunk(
677
            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

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