Completed
Pull Request — master (#58)
by
unknown
06:45
created

VCard::addAddress()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 22
rs 9.2
cc 2
eloc 15
nc 1
nop 8

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
 * @author Jeroen Desloovere <[email protected]>
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
     * Multiple properties for element allowed
37
     *
38
     * @var array
39
     */
40
    private $multiplePropertiesForElementAllowed = array(
41
        'email',
42
        'address',
43
        'phoneNumber',
44
        'url'
45
    );
46
47
    /**
48
     * Properties
49
     *
50
     * @var array
51
     */
52
    private $properties;
53
54
    /**
55
     * Default Charset
56
     *
57
     * @var string
58
     */
59
    public $charset = 'utf-8';
60
61
    /**
62
     * Add address
63
     *
64
     * @param  string [optional] $name
65
     * @param  string [optional] $extended
66
     * @param  string [optional] $street
67
     * @param  string [optional] $city
68
     * @param  string [optional] $region
69
     * @param  string [optional] $zip
70
     * @param  string [optional] $country
71
     * @param  string [optional] $type
72
     *                                     $type may be DOM | INTL | POSTAL | PARCEL | HOME | WORK
73
     *                                     or any combination of these: e.g. "WORK;PARCEL;POSTAL"
74
     * @return $this
75
     */
76
    public function addAddress(
77
        $name = '',
78
        $extended = '',
79
        $street = '',
80
        $city = '',
81
        $region = '',
82
        $zip = '',
83
        $country = '',
84
        $type = 'WORK;POSTAL'
85
    ) {
86
        // init value
87
        $value = $name . ';' . $extended . ';' . $street . ';' . $city . ';' . $region . ';' . $zip . ';' . $country;
88
89
        // set property
90
        $this->setProperty(
91
            'address',
92
            'ADR' . (($type != '') ? ';' . $type : '') . $this->getCharsetString(),
93
            $value
94
        );
95
96
        return $this;
97
    }
98
99
    /**
100
     * Add birthday
101
     *
102
     * @param  string $date Format is YYYY-MM-DD
103
     * @return $this
104
     */
105
    public function addBirthday($date)
106
    {
107
        $this->setProperty(
108
            'birthday',
109
            'BDAY',
110
            $date
111
        );
112
113
        return $this;
114
    }
115
116
    /**
117
     * Add company
118
     *
119
     * @param  string $company
120
     * @return $this
121
     */
122
    public function addCompany($company)
123
    {
124
        $this->setProperty(
125
            'company',
126
            'ORG' . $this->getCharsetString(),
127
            $company
128
        );
129
130
        // if filename is empty, add to filename
131
        if ($this->getFilename() === null) {
132
            $this->setFilename($company);
133
        }
134
135
        return $this;
136
    }
137
138
    /**
139
     * Add email
140
     *
141
     * @param  string            $address The e-mail address
142
     * @param  string [optional] $type    The type of the email address
143
     *                                    $type may be  PREF | WORK | HOME
144
     *                                    or any combination of these: e.g. "PREF;WORK"
145
     * @return $this
146
     */
147
    public function addEmail($address, $type = '')
148
    {
149
        $this->setProperty(
150
            'email',
151
            'EMAIL;INTERNET' . (($type != '') ? ';' . $type : ''),
152
            $address
153
        );
154
155
        return $this;
156
    }
157
158
    /**
159
     * Add jobtitle
160
     *
161
     * @param  string $jobtitle The jobtitle for the person.
162
     * @return $this
163
     */
164
    public function addJobtitle($jobtitle)
165
    {
166
        $this->setProperty(
167
            'jobtitle',
168
            'TITLE' . $this->getCharsetString(),
169
            $jobtitle
170
        );
171
172
        return $this;
173
    }
174
175
    /**
176
     * Add a photo or logo (depending on property name)
177
     *
178
     * @param  string              $property LOGO|PHOTO
179
     * @param  string              $url      image url or filename
180
     * @param  bool                $include  Do we include the image in our vcard or not?
181
     * @throws VCardMediaException if file is empty or not an image file
182
     */
183
    private function addMedia($property, $url, $include = true, $element)
184
    {
185
        if ($include) {
186
            $value = file_get_contents($url);
187
188
            if (!$value) {
189
                throw new VCardMediaException('Nothing returned from URL.');
190
            }
191
192
            $value = base64_encode($value);
193
194
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
195
            $mimetype = finfo_file($finfo, 'data://application/octet-stream;base64,' . $value);
196
            finfo_close($finfo);
197
198
            if (preg_match('/^image\//', $mimetype) !== 1) {
199
                throw new VCardMediaException('Returned data aren\'t an image.');
200
            }
201
202
            $type = strtoupper(str_replace('image/', '', $mimetype));
203
204
            $property .= ";ENCODING=b;TYPE=" . $type;
205
        } else {
206
            $value = $url;
207
        }
208
209
        $this->setProperty(
210
            $element,
211
            $property,
212
            $value
213
        );
214
    }
215
216
    /**
217
     * Add name
218
     *
219
     * @param  string [optional] $lastName
220
     * @param  string [optional] $firstName
221
     * @param  string [optional] $additional
222
     * @param  string [optional] $prefix
223
     * @param  string [optional] $suffix
224
     * @return $this
225
     */
226
    public function addName(
227
        $lastName = '',
228
        $firstName = '',
229
        $additional = '',
230
        $prefix = '',
231
        $suffix = ''
232
    ) {
233
        // define values with non-empty values
234
        $values = array_filter(array(
235
            $prefix,
236
            $firstName,
237
            $additional,
238
            $lastName,
239
            $suffix,
240
        ));
241
242
        // define filename
243
        $this->setFilename($values);
244
245
        // set property
246
        $property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix;
247
        $this->setProperty(
248
            'name',
249
            'N' . $this->getCharsetString(),
250
            $property
251
        );
252
253
        // is property FN set?
254
        if (!$this->hasProperty('FN')) {
255
            // set property
256
            $this->setProperty(
257
                'fullname',
258
                'FN' . $this->getCharsetString(),
259
                trim(implode(' ', $values))
260
            );
261
        }
262
263
        return $this;
264
    }
265
266
    /**
267
     * Add note
268
     *
269
     * @param  string $note
270
     * @return $this
271
     */
272
    public function addNote($note)
273
    {
274
        $this->setProperty(
275
            'note',
276
            'NOTE' . $this->getCharsetString(),
277
            $note
278
        );
279
280
        return $this;
281
    }
282
283
    /**
284
     * Add phone number
285
     *
286
     * @param  string            $number
287
     * @param  string [optional] $type
288
     *                                   Type may be PREF | WORK | HOME | VOICE | FAX | MSG |
289
     *                                   CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO
290
     *                                   or any senseful combination, e.g. "PREF;WORK;VOICE"
291
     * @return $this
292
     */
293
    public function addPhoneNumber($number, $type = '')
294
    {
295
        $this->setProperty(
296
            'phoneNumber',
297
            'TEL' . (($type != '') ? ';' . $type : ''),
298
            $number
299
        );
300
301
        return $this;
302
    }
303
304
    /**
305
     * Add Logo
306
     *
307
     * @param  string $url     image url or filename
308
     * @param  bool   $include Include the image in our vcard?
309
     * @return $this
310
     */
311
    public function addLogo($url, $include = true)
312
    {
313
        $this->addMedia(
314
            'LOGO',
315
            $url,
316
            $include,
317
            'logo'
318
        );
319
320
        return $this;
321
    }
322
323
    /**
324
     * Add Photo
325
     *
326
     * @param  string $url     image url or filename
327
     * @param  bool   $include Include the image in our vcard?
328
     * @return $this
329
     */
330
    public function addPhoto($url, $include = true)
331
    {
332
        $this->addMedia(
333
            'PHOTO',
334
            $url,
335
            $include,
336
            'photo'
337
        );
338
339
        return $this;
340
    }
341
342
    /**
343
     * Add URL
344
     *
345
     * @param  string            $url
346
     * @param  string [optional] $type Type may be WORK | HOME
347
     * @return $this
348
     */
349
    public function addURL($url, $type = '')
350
    {
351
        $this->setProperty(
352
            'url',
353
            'URL' . (($type != '') ? ';' . $type : ''),
354
            $url
355
        );
356
357
        return $this;
358
    }
359
360
    /**
361
     * Build VCard (.vcf)
362
     *
363
     * @return string
364
     */
365
    public function buildVCard()
366
    {
367
        // init string
368
        $string = "BEGIN:VCARD\r\n";
369
        $string .= "VERSION:3.0\r\n";
370
        $string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n";
371
372
        // loop all properties
373
        $properties = $this->getProperties();
374
        foreach ($properties as $property) {
375
            // add to string
376
            $string .= $this->fold($property['key'] . ':' . $property['value'] . "\r\n");
377
        }
378
379
        // add to string
380
        $string .= "END:VCARD\r\n";
381
382
        // return
383
        return $string;
384
    }
385
386
    /**
387
     * Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround.
388
     *
389
     * @return string
390
     */
391
    public function buildVCalendar()
392
    {
393
        // init dates
394
        $dtstart = date("Ymd") . "T" . date("Hi") . "00";
395
        $dtend = date("Ymd") . "T" . date("Hi") . "01";
396
397
        // init string
398
        $string = "BEGIN:VCALENDAR\n";
399
        $string .= "VERSION:2.0\n";
400
        $string .= "BEGIN:VEVENT\n";
401
        $string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n";
402
        $string .= "DTEND;TZID=Europe/London:" . $dtend . "\n";
403
        $string .= "SUMMARY:Click attached contact below to save to your contacts\n";
404
        $string .= "DTSTAMP:" . $dtstart . "Z\n";
405
        $string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n";
406
        $string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n";
407
408
        // base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment
409
        $b64vcard = base64_encode($this->buildVCard());
410
411
        // chunk the single long line of b64 text in accordance with RFC2045
412
        // (and the exact line length determined from the original .ics file exported from Apple calendar
413
        $b64mline = chunk_split($b64vcard, 74, "\n");
414
415
        // need to indent all the lines by 1 space for the iphone (yes really?!!)
416
        $b64final = preg_replace('/(.+)/', ' $1', $b64mline);
417
        $string .= $b64final;
418
419
        // output the correctly formatted encoded text
420
        $string .= "END:VEVENT\n";
421
        $string .= "END:VCALENDAR\n";
422
423
        // return
424
        return $string;
425
    }
426
427
    /**
428
     * Returns the browser user agent string.
429
     *
430
     * @return string
431
     */
432
    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...
433
    {
434
        if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
435
            $browser = strtolower($_SERVER['HTTP_USER_AGENT']);
436
        } else {
437
            $browser = 'unknown';
438
        }
439
440
        return $browser;
441
    }
442
443
    /**
444
     * Decode
445
     *
446
     * @param  string $value The value to decode
447
     * @return string decoded
448
     */
449
    private function decode($value)
450
    {
451
        // convert cyrlic, greek or other caracters to ASCII characters
452
        return Transliterator::transliterate($value);
453
    }
454
455
    /**
456
     * Download a vcard or vcal file to the browser.
457
     */
458
    public function download()
459
    {
460
        // define output
461
        $output = $this->getOutput();
462
463
        foreach ($this->getHeaders(false) as $header) {
464
            header($header);
465
        }
466
467
        // echo the output and it will be a download
468
        echo $output;
469
    }
470
471
    /**
472
     * Fold a line according to RFC2425 section 5.8.1.
473
     *
474
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.1
475
     * @param  string $text
476
     * @return mixed
477
     */
478
    protected function fold($text)
479
    {
480
        if (strlen($text) <= 75) {
481
            return $text;
482
        }
483
484
        // split, wrap and trim trailing separator
485
        return substr(chunk_split($text, 73, "\r\n "), 0, -3);
486
    }
487
488
    /**
489
     * Get output as string
490
     * @deprecated in the future
491
     *
492
     * @return string
493
     */
494
    public function get()
495
    {
496
        return $this->getOutput();
497
    }
498
499
    /**
500
     * Get charset
501
     *
502
     * @return string
503
     */
504
    public function getCharset()
505
    {
506
        return $this->charset;
507
    }
508
509
    /**
510
     * Get charset string
511
     *
512
     * @return string
513
     */
514
    public function getCharsetString()
515
    {
516
        $charsetString = '';
517
        if ($this->charset == 'utf-8') {
518
            $charsetString = ';CHARSET=' . $this->charset;
519
        }
520
        return $charsetString;
521
    }
522
523
    /**
524
     * Get content type
525
     *
526
     * @return string
527
     */
528
    public function getContentType()
529
    {
530
        return ($this->isIOS7()) ?
531
            'text/x-vcalendar' : 'text/x-vcard';
532
    }
533
534
    /**
535
     * Get filename
536
     *
537
     * @return string
538
     */
539
    public function getFilename()
540
    {
541
        return $this->filename;
542
    }
543
544
    /**
545
     * Get file extension
546
     *
547
     * @return string
548
     */
549
    public function getFileExtension()
550
    {
551
        return ($this->isIOS7()) ?
552
            'ics' : 'vcf';
553
    }
554
555
    /**
556
     * Get headers
557
     *
558
     * @param  bool  $asAssociative
559
     * @return array
560
     */
561
    public function getHeaders($asAssociative)
562
    {
563
        $contentType        = $this->getContentType() . '; charset=' . $this->getCharset();
564
        $contentDisposition = 'attachment; filename=' . $this->getFilename() . '.' . $this->getFileExtension();
565
        $contentLength      = strlen($this->getOutput());
566
        $connection         = 'close';
567
568
        if ((bool) $asAssociative) {
569
            return array(
570
                'Content-type'        => $contentType,
571
                'Content-Disposition' => $contentDisposition,
572
                'Content-Length'      => $contentLength,
573
                'Connection'          => $connection,
574
            );
575
        }
576
577
        return array(
578
            'Content-type: ' . $contentType,
579
            'Content-Disposition: ' . $contentDisposition,
580
            'Content-Length: ' . $contentLength,
581
            'Connection: ' . $connection,
582
        );
583
    }
584
585
    /**
586
     * Get output as string
587
     * iOS devices (and safari < iOS 8 in particular) can not read .vcf (= vcard) files.
588
     * So I build a workaround to build a .ics (= vcalender) file.
589
     *
590
     * @return string
591
     */
592
    public function getOutput()
593
    {
594
        $output = ($this->isIOS7()) ?
595
            $this->buildVCalendar() : $this->buildVCard();
596
597
        return $output;
598
    }
599
600
    /**
601
     * Get properties
602
     *
603
     * @return array
604
     */
605
    public function getProperties()
606
    {
607
        return $this->properties;
608
    }
609
610
    /**
611
     * Has property
612
     *
613
     * @param  string $key
614
     * @return bool
615
     */
616
    public function hasProperty($key)
617
    {
618
        $properties = $this->getProperties();
619
620
        foreach ($properties as $property) {
621
            if ($property['key'] === $key && $property['value'] !== '') {
622
                return true;
623
            }
624
        }
625
626
        return false;
627
    }
628
629
    /**
630
     * Is iOS - Check if the user is using an iOS-device
631
     *
632
     * @return bool
633
     */
634
    public function isIOS()
635
    {
636
        // get user agent
637
        $browser = $this->getUserAgent();
638
639
        return (strpos($browser, 'iphone') || strpos($browser, 'ipod') || strpos($browser, 'ipad'));
640
    }
641
642
    /**
643
     * Is iOS less than 7 (should cal wrapper be returned)
644
     *
645
     * @return bool
646
     */
647
    public function isIOS7()
648
    {
649
        return ($this->isIOS() && $this->shouldAttachmentBeCal());
650
    }
651
652
    /**
653
     * Save to a file
654
     *
655
     * @return void
656
     */
657
    public function save()
658
    {
659
        $file = $this->getFilename() . '.' . $this->getFileExtension();
660
661
        file_put_contents(
662
            $file,
663
            $this->getOutput()
664
        );
665
    }
666
667
    /**
668
     * Set charset
669
     *
670
     * @param  mixed  $charset
671
     * @return void
672
     */
673
    public function setCharset($charset)
674
    {
675
        $this->charset = $charset;
676
    }
677
678
    /**
679
     * Set filename
680
     *
681
     * @param  mixed  $value
682
     * @param  bool   $overwrite [optional] Default overwrite is true
683
     * @param  string $separator [optional] Default separator is an underscore '_'
684
     * @return void
685
     */
686
    public function setFilename($value, $overwrite = true, $separator = '_')
687
    {
688
        // recast to string if $value is array
689
        if (is_array($value)) {
690
            $value = implode($separator, $value);
691
        }
692
693
        // trim unneeded values
694
        $value = trim($value, $separator);
695
696
        // remove all spaces
697
        $value = preg_replace('/\s+/', $separator, $value);
698
699
        // if value is empty, stop here
700
        if (empty($value)) {
701
            return;
702
        }
703
704
        // decode value + lowercase the string
705
        $value = strtolower($this->decode($value));
706
707
        // urlize this part
708
        $value = Transliterator::urlize($value);
709
710
        // overwrite filename or add to filename using a prefix in between
711
        $this->filename = ($overwrite) ?
712
            $value : $this->filename . $separator . $value;
713
    }
714
715
    /**
716
     * Set property
717
     *
718
     * @param  string $element The element name you want to set, f.e.: name, email, phoneNumber, ...
719
     * @param  string $key
720
     * @param  string $value
721
     * @return void
722
     */
723
    private function setProperty($element, $key, $value)
724
    {
725
        if (!in_array($element, $this->multiplePropertiesForElementAllowed)
726
            && isset($this->definedElements[$element])
727
        ) {
728
            throw new Exception('You can only set "' . $element . '" once.');
729
        }
730
731
        // we define that we set this element
732
        $this->definedElements[$element] = true;
733
734
        // adding property
735
        $this->properties[] = array(
736
            'key' => $key,
737
            'value' => $value
738
        );
739
    }
740
741
    /**
742
     * Checks if we should return vcard in cal wrapper
743
     *
744
     * @return bool
745
     */
746
    protected function shouldAttachmentBeCal()
747
    {
748
        $browser = $this->getUserAgent();
749
750
        $matches = array();
751
        preg_match('/os (\d+)_(\d+)\s+/', $browser, $matches);
752
        $version = isset($matches[1]) ? ((int) $matches[1]) : 999;
753
754
        return ($version < 8);
755
    }
756
}
757